SoV.c - Viewing source
Download the source file/* ** SoV - Starcraft over VPN v0.1 ** ----------------------------- ** Written by Eric Doughty-Papassideris ** ** Description ** ----------- ** SoV allows to connect to a starcraft game ** over networks that don't route broadcast ** packets - such as OpenVPN (I hear version 2.0 ** fixed it though) or Hamachi on Mac OS X. ** ** How to use it: ** -------------- ** 1. Start by connecting the VPN, and making sure you ** have connectivity with the host. Use the opportunity ** to figure out his IP address. ** ** 2. Get the host to create the game in starcraft and ** wait for you to join. (Using UDP) ** ** 3. Run starcraft, and get to the game selection screen. ** ** 4. Run 'sudo path/sov en0 tun0 host' on the client. ** In that command, you should replace: ** path/ : by the path to the sov binary (its true!) ** en0 : by an interface that is known to route ** broadcast packets without surprises. ** You should use your usual LAN interface. ** tun0 : by the name of the interface your VPN ** works on. Make sure its connected ** starting. ** host : by the ip address of the game creator ** (should be in tun0's subnet) ** ** 5. You should see the game in the list, and should be ** able to join it. ** ** 6. Repeat on the computer of each player joining the ** game. ** ** 7. hfgl ** ** While its running it will show how packets it sees ** going through and how many it re-injected. ** Once you have joined the game, you can close sov ** by hitting ctrl+c (but you don't have to. Are you ** really only going to play a single game ? - tss.) ** ** How it works: ** ------------- ** When listing games, starcraft sends a broadcast ** packet that servers respond to with game ** details. SoV uses libpcap to see such messages, ** and injects them over another interface (while ** correcting the source address, and setting the ** destination to the ip provided on the command ** line). The server sees the packet, doesn't care ** that its not a broadcast, and responds to the ** client with a normal UDP packet. ** The client sees the game, and the rest works ** fine without broadcast. ** ** How to build: ** ------------- ** gcc -lpcap -o sov sov.c -Wall ** ** Credits ** ------- ** - sniffex.c sample by The Tcpdump Group ** - B. Borde for his patience in testing this. ** ** License ** ------- ** IDC v2.0 ("I Dont Care") ** ** Disclaimer ** ---------- ** Carefull: I don't want to know about what ** happened (or not) because of this code. ** You are running it "as-is", ** and it "is not my problem". ** */ #include <pcap.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <errno.h> #include <fcntl.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <unistd.h> #define BANNER "SoV - Starcraft over VPN - by EDP\n" #define STARCRAFT_PORT 6111 #define PCAP_FILTER "udp port 6111" #define SNAP_LEN 65535 #define SIZE_ETHERNET 14 /* Injection packet queue item */ typedef struct buffer_s { size_t len; void *ptr; struct buffer_s *next; } buf_t; /* Global variables */ typedef struct globals_s { int nbclients; bpf_u_int32 *clients; bpf_u_int32 remote; /* Capture interface for good packets */ int fd_pcap; pcap_t *pcap_hnd; char *pcap_dev; bpf_u_int32 pcap_mask; bpf_u_int32 pcap_net; struct bpf_program pcap_bpfilter; /* Injection interface */ int fd_tun; char *tun_dev; pcap_t *tun_hnd; bpf_u_int32 tun_mask; bpf_u_int32 tun_net; /* Packet queue (yes, old school) */ buf_t *buf_head; buf_t *buf_tail; /* Statistics */ int stats_cntcap; int stats_cntsent; int stats_cnterrorsent; } globals_t; /* Ethernet, IP and UDP headers */ typedef struct sniff_ethernet { u_char ether_dhost[6]; u_char ether_shost[6]; u_short ether_type; } ether_t; typedef struct sniff_ip { u_char ip_vhl; /* version << 4 | header length >> 2 */ u_char ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_char ip_ttl; /* time to live */ u_char ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src,ip_dst; /* source and dest address */ } ip_t; #define IP_HL(ip) (((ip)->ip_vhl) & 0x0f) #define IP_V(ip) (((ip)->ip_vhl) >> 4) typedef struct sniff_udp { u_short sport; u_short dport; u_short len; u_short crc; } udp_t; /* Raw packet building code */ unsigned short csum (unsigned short *buf, int nwords) { unsigned long sum; for (sum = 0; nwords > 0; nwords--) sum += *buf++; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return ~sum; } void buildrawudp(globals_t *g, u_char *data, size_t len, bpf_u_int32 source) { u_char *buf; ip_t *ip; udp_t *udp; struct sockaddr_in sin; buf = (u_char *)malloc(20 + 8 + len); bzero((char *)buf, 20 + 8 + len); ip = (ip_t *)(buf); udp = (udp_t *)(buf + 20); // IP Layer ip->ip_vhl = 0x45; ip->ip_len = htons(20 + 8 + len); ip->ip_ttl = 64; ip->ip_id = htons(0x6a45); //<- Bogus non null value ip->ip_p = 0x11; //UDP ip->ip_dst.s_addr = g->remote; ip->ip_src.s_addr = source; ip->ip_sum = csum((unsigned short*)(buf), 20); // UDP Layer udp->sport = htons(STARCRAFT_PORT); udp->dport = htons(STARCRAFT_PORT); udp->len = htons(len + 8); memcpy(buf + 20 + 8, data, len); udp->crc = 0; //Really should calculate the correct packet. But this works for now. // Inject packet sin.sin_family = AF_INET; sin.sin_port = htons(STARCRAFT_PORT); sin.sin_addr.s_addr = g->remote; if ((pcap_inject(g->tun_hnd, buf, 20 + 8 + len)) < 0) { fprintf(stderr, "pcap_inject: Couldn't inject packet: %s.\n***Please check sov is running as root (sudo)\n", pcap_geterr(g->tun_hnd)); g->stats_cnterrorsent++; } else g->stats_cntsent++; free(buf); } /* pcap initialisation */ int initpcap(globals_t *g) { char errbuf[PCAP_ERRBUF_SIZE]; if (pcap_lookupnet(g->pcap_dev, &g->pcap_net, &g->pcap_mask, errbuf) == -1) { fprintf(stderr, "pcap_lookupnet: Couldn't get netmask for device: %s\n", errbuf); return 0; } g->pcap_hnd = pcap_open_live(g->pcap_dev, SNAP_LEN, 1, 100, errbuf); if (g->pcap_hnd == NULL) { fprintf(stderr, "pcap_open_live: Couldn't open device %s: %s\n", g->pcap_dev, errbuf); return 0; } if (pcap_datalink(g->pcap_hnd) != DLT_EN10MB) { fprintf(stderr, "pcap: %s is not an Ethernet device\n", g->pcap_dev); return 0; } if (pcap_compile(g->pcap_hnd, &g->pcap_bpfilter, PCAP_FILTER, 0, g->pcap_net) == -1) { fprintf(stderr, "pcap_compile: Couldn't parse filter %s: %s\n", "udp", pcap_geterr(g->pcap_hnd)); return 0; } if (pcap_setfilter(g->pcap_hnd, &g->pcap_bpfilter) == -1) { fprintf(stderr, "pcap_setfilter: Couldn't install filter %s: %s\n", "udp", pcap_geterr(g->pcap_hnd)); return 0; } if (pcap_setnonblock(g->pcap_hnd, 1, errbuf) == -1) { fprintf(stderr, "pcap_setnonblock: Couldn't set device %s to non-blocking mode: %s\n", g->pcap_dev, errbuf); return 0; } if ((g->fd_pcap = pcap_get_selectable_fd(g->pcap_hnd)) == -1) { fprintf(stderr, "pcap_get_selectable_fd: Failed on %s: %s\n", g->pcap_dev, pcap_geterr(g->pcap_hnd)); return 0; } if (pcap_lookupnet(g->tun_dev, &g->tun_net, &g->tun_mask, errbuf) == -1) { fprintf(stderr, "pcap_lookupnet: Couldn't get netmask for device: %s\n", errbuf); fprintf(stderr, " ** Please make sure you're already connected to the VPN!\n"); return 0; } g->tun_hnd = pcap_open_live(g->tun_dev, SNAP_LEN, 1, 100, errbuf); if (g->tun_hnd == NULL) { fprintf(stderr, "pcap_open_live: Couldn't open tun device %s: %s\n", g->tun_dev, errbuf); return 0; } return 1; } /* Finalisation */ globals_t *glob_toclean = 0; void cleanup(globals_t *g) { if (g->fd_pcap > 0) close(g->fd_pcap); if (g->pcap_hnd) pcap_close(g->pcap_hnd); if (g->tun_hnd) pcap_close(g->tun_hnd); pcap_freecode(&g->pcap_bpfilter); } void signal_handler(int signum) { printf("\nCaught signal %i... cleaning up and exiting...\n", signum); pcap_breakloop(glob_toclean->pcap_hnd); cleanup(glob_toclean); exit(1); } void setupsignals() { struct sigaction new_action, old_action; new_action.sa_handler = signal_handler; sigemptyset (&new_action.sa_mask); new_action.sa_flags = 0; sigaction (SIGINT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction (SIGINT, &new_action, NULL); sigaction (SIGHUP, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction (SIGHUP, &new_action, NULL); sigaction (SIGTERM, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) sigaction (SIGTERM, &new_action, NULL); } /* Packet queue */ void dequeuepacket(globals_t *g) { buf_t *cur; int i; cur = g->buf_head; if (!cur) return; g->buf_head = cur->next; if (!cur->next) g->buf_tail = 0; buildrawudp(g, cur->ptr, cur->len, g->tun_net); for (i = 0; i < g->nbclients; i++) buildrawudp(g, cur->ptr, cur->len, g->clients[i]); free(cur); } void enqueuepacket(globals_t *g, void *data, size_t len) { buf_t *cur; cur = (buf_t *)malloc(sizeof(buf_t) + len); cur->ptr = (void *)(cur + 1); memcpy(cur->ptr, data, len); cur->len = len; cur->next = 0; if (!g->buf_tail) { g->buf_head = cur; g->buf_tail = cur; return; } g->buf_tail->next = cur; g->buf_tail = cur; } /* libpcap callback */ void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet) { globals_t *g = (globals_t *)args; const ether_t *ethernet; const ip_t *ip; const udp_t *udp; int size_ip; // Double check packet is wanted g->stats_cntcap++; ethernet = (ether_t*)(packet); ip = (ip_t*)(packet + SIZE_ETHERNET); size_ip = IP_HL(ip)*4; if (size_ip < 20) return; if (ip->ip_p != IPPROTO_UDP) return; udp = (udp_t*)(packet + SIZE_ETHERNET + size_ip); if (ntohs(udp->dport) != STARCRAFT_PORT || ntohs(udp->sport) != STARCRAFT_PORT) return; enqueuepacket(g, (void *)(packet + SIZE_ETHERNET + size_ip + 8), ntohs(udp->len) - 8); } /* Printouts */ void printstats(globals_t *g) { struct timeval ts; static long last = 0; char buf[255]; int len; gettimeofday(&ts, 0); if (ts.tv_sec == last) // Dont update statistics more than once per second return; last = ts.tv_sec; len = snprintf(buf, 255, "\rCaptured : %i sent: %i errors: %i ", g->stats_cntcap, g->stats_cntsent, g->stats_cnterrorsent); write(0, buf, strlen(buf)); } void printuse(char *prog) { printf(BANNER "A relay tool that captures outgoing broadcast starcraft packets\n\ and injects them to a specific host.\n\n\ Use: %s capture_device inject_device game_creator_ip\n\ \tcapture_device: the name of the interface to capture local starcraft packets from.\n\ \tinject_device: the name of the interface to reinject packets into\n\ \tgame_creator_ip: the host to forward packets to\n", prog); } /* Main loop */ void mainloop(globals_t *g) { fd_set readfds, writefds, errfds; int maxfd; struct timeval ts; while (1) { FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&errfds); FD_SET(g->fd_pcap, &readfds); FD_SET(g->fd_pcap, &errfds); maxfd = g->fd_pcap + 1; ts.tv_sec = 0; ts.tv_usec = g->buf_head ? 50 : 700; if (select(maxfd, &readfds, &writefds, &errfds, &ts) < 0) { perror("select failed"); return; } pcap_dispatch(g->pcap_hnd, 1, got_packet, (u_char *)g); if (g->buf_head) dequeuepacket(g); printstats(g); } } bpf_u_int32 getip(struct hostent *host) { bpf_u_int32 result; bcopy(host->h_addr, (char *)&result, sizeof(bpf_u_int32)); return result; } /* Just a random utility function */ int main(int ac, char**av) { globals_t globals; int i; struct hostent *host; bzero((char *)&globals, sizeof(globals)); if (ac < 4) { printuse(*av); return 1; } globals.nbclients = ac - 4; globals.clients = malloc(sizeof(bpf_u_int32) * globals.nbclients); if ((host = gethostbyname(av[3])) == NULL) { perror("Error: SOV: Invalid game creator IP"); printuse(*av); return 1; } else globals.remote = getip(host); printf(BANNER); for (i = 0; i < ac - 4; i++) if ((host = gethostbyname(av[4 + i])) == NULL) { perror("Error: SOV: Invalid additional client IP"); return 1; } else { globals.clients[i] = getip(host); printf(" - Registered client '%s'\n", av[4 + i]); } globals.pcap_dev = av[1]; globals.tun_dev = av[2]; glob_toclean = &globals; if (initpcap(&globals)) { setupsignals(); mainloop(&globals); glob_toclean = NULL; } cleanup(&globals); return 0; }