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;
}