/* Quake UDP proxy
Original ipx->udp proxy by
[email protected]
Adapted for udp->udp, other changes by
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
compile with "gcc -O2 -o qudproxy qudproxy.c"
version 1.0
todo: translate broadcasts, server list
Note: This program properly handles proxying for multiple clients, but
as of Quake 0.92 the quake server only allows one client from each IP
address, so only one player can use the proxy at a time; if another
player connects to the proxy, the remote server will disconnect the
first player. I've complained about this to id.
*/
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
extern char *optarg;
extern int optind;
#define VERSION "1.0"
#define MAX 4096
unsigned char msg[MAX];
char *name;
void usage ()
{
fprintf (stderr, "usage: %s [-l localport] [-r remoteport]", name);
fprintf (stderr, " [-d[d]] [-h] server\n");
}
int main (int argc, char *argv[])
{
int sfd, cfd, c2fd, n, ucl, i, pid, opt;
fd_set fds;
struct sockaddr_in cremote_addr, clocal_addr;
struct sockaddr_in sremote_addr, slocal_addr;
struct hostent *host;
struct timeval tv, *tvp;
int localport = 26000, remoteport = 26000, debug = 0;
name = argv[0];
while (opt = getopt (argc, argv, "l:r:dhv"), opt != -1)
{
switch (opt)
{
case 'l':
localport = atoi (optarg);
break;
case 'r':
remoteport = atoi (optarg);
break;
case 'd':
++debug;
break;
case 'v':
fprintf (stderr, "qudproxy version %s", VERSION);
return 0;
default:
usage ();
return opt != 'h';
}
}
if (optind != argc - 1)
{
usage ();
return 1;
}
ucl = sizeof (struct sockaddr_in);
bzero (&sremote_addr, ucl);
sremote_addr.sin_family = AF_INET;
sremote_addr.sin_port = htons (remoteport);
n = inet_addr (argv[optind]);
if (n != -1)
memcpy (&sremote_addr.sin_addr, &n, sizeof(n));
else
{
host = gethostbyname (argv[optind]);
if (host == NULL)
return (-1);
memcpy (&sremote_addr.sin_addr, host->h_addr, host->h_length);
}
bzero (&clocal_addr, ucl);
clocal_addr.sin_family = AF_INET;
clocal_addr.sin_port = htons (localport);
/* Surely there's a better way to determine our IP address. */
gethostname (msg, MAX);
host = gethostbyname (msg);
memcpy (&clocal_addr.sin_addr, host->h_addr, host->h_length);
bzero (&slocal_addr, ucl);
slocal_addr.sin_family = AF_INET;
slocal_addr.sin_addr.s_addr = htonl(INADDR_ANY);
cfd = socket (AF_INET, SOCK_DGRAM, 0);
bind (cfd, (struct sockaddr *) &clocal_addr, ucl);
sfd = socket (AF_INET, SOCK_DGRAM, 0);
bind (sfd, (struct sockaddr *) &slocal_addr, ucl);
tvp = NULL;
for (;;)
{
int i;
/* this is to exit the spawned processes */
if (tvp != NULL)
{
tv.tv_sec = 15;
tv.tv_usec = 0;
}
FD_ZERO (&fds);
FD_SET (sfd, &fds);
FD_SET (cfd, &fds);
if (0 >= select (1 + (cfd > sfd ? cfd : sfd), &fds, NULL, NULL, tvp))
return 0;
/* this should be the 12 byte connect request broadcast */
if (FD_ISSET (cfd, &fds))
{
n = recvfrom (cfd, msg, MAX, 0,
(struct sockaddr *) &cremote_addr, &ucl);
if (n == -1)
{
perror ("qudproxy");
return 1;
}
if (debug > 1)
{
fprintf (stderr, "client@%d: ", ntohs (cremote_addr.sin_port));
for (i = 0; i < n; ++i)
fprintf (stderr, "%x ", msg[i]);
fflush (stderr);
write (2, "(", 1);
write (2, msg, n);
write (2, ")\n", 2);
}
sendto (sfd, msg, n, 0, (struct sockaddr *) &sremote_addr, ucl);
}
if (FD_ISSET (sfd, &fds))
{
/* SunOS 4.1.3_U1 requires that we pass in &ucl. Odd. */
n = recvfrom (sfd, msg, MAX, 0, NULL, &ucl);
if (n == -1)
{
perror ("qudproxy");
return 1;
}
if (debug > 1)
{
fprintf (stderr, "server@%d: ", ntohs (sremote_addr.sin_port));
for (i = 0; i < n; ++i)
fprintf (stderr, "%x ", msg[i]);
fflush (stderr);
write (2, "(", 1);
write (2, msg, n);
write (2, ")\n", 2);
}
/* this might be the connect response */
if (tvp == NULL && msg[0] == 0x80 && msg[1] == 00
&& msg[4] == 0x81)
{
#if 1
/* FORK HERE */
pid = fork ();
if (!pid)
{
/* spawn again */
if (pid = fork (), pid)
{
if (debug)
fprintf (stderr,
"Spawning Quake Proxy Handler Pid %d\n",
pid);
return 0;
/* if parent, exit (so init gets grandkid) */
}
}
else
{
/* wait for kid to exit and continue */
waitpid (pid, NULL, 0);
/* create new udp socket for next connections */
close (sfd);
sfd = socket (AF_INET, SOCK_DGRAM, 0);
bind (sfd, (struct sockaddr *) &slocal_addr, ucl);
continue;
}
/* we are in grandkid */
#endif
sremote_addr.sin_port = htons (msg[5] + (msg[6] << 8));
clocal_addr.sin_port = 0;
c2fd = socket (AF_INET, SOCK_DGRAM, 0);
bind (c2fd, (struct sockaddr *) &clocal_addr, ucl);
getsockname (c2fd, (struct sockaddr *) &clocal_addr, &ucl);
msg[5] = ntohs (clocal_addr.sin_port) & 0xff;
msg[6] = ntohs (clocal_addr.sin_port) >> 8;
if (debug)
{
fprintf (stderr, "Quake UDP Proxy to %s:%d",
inet_ntoa (sremote_addr.sin_addr),
ntohs (sremote_addr.sin_port));
#if 0
/* We haven't asked for this information with
getsockname. */
fprintf (stderr, " from %s:%d",
inet_ntoa (slocal_addr.sin_addr),
ntohs (slocal_addr.sin_port));
#endif
fprintf (stderr, " for %s:%d",
inet_ntoa (cremote_addr.sin_addr),
ntohs (cremote_addr.sin_port));
fprintf (stderr, " at %s:%d\n",
inet_ntoa (clocal_addr.sin_addr),
msg[5] + (msg[6] << 8));
fflush (stderr);
}
tvp = &tv;
sendto (cfd, msg, n, 0, (struct sockaddr *) &cremote_addr, ucl);
close (cfd);
cfd = c2fd;
}
else
sendto (cfd, msg, n, 0, (struct sockaddr *) &cremote_addr, ucl);
}
}
}