From d3b646ad17c47de6bafc618d14f42f8342abcb08 Mon Sep 17 00:00:00 2001 From: Bradley Bell Date: Sun, 5 Oct 2003 22:27:01 +0000 Subject: [PATCH] added UDP support for win32 --- ChangeLog | 6 + arch/win32/Makefile.am | 4 +- arch/win32/include/ipx_udp.h | 14 + arch/win32/ipx_udp.c | 618 +++++++++++++++++++++++++++++++++++ arch/win32/winnet.c | 13 +- main/menu.c | 4 +- 6 files changed, 650 insertions(+), 9 deletions(-) create mode 100644 arch/win32/include/ipx_udp.h create mode 100644 arch/win32/ipx_udp.c diff --git a/ChangeLog b/ChangeLog index 48bc34b9f..33468cca9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2003-10-05 Bradley Bell + + * arch/win32/Makefile.am, arch/win32/include/ipx_udp.h, + arch/win32/ipx_udp.c, arch/win32/winnet.c, main/menu.c: added UDP + support for win32 + 2003-10-04 Bradley Bell * cfile/cfile.c, main/config.c: oops, fgets does not return EOF diff --git a/arch/win32/Makefile.am b/arch/win32/Makefile.am index 3c2a761df..0fdaa25e8 100644 --- a/arch/win32/Makefile.am +++ b/arch/win32/Makefile.am @@ -5,12 +5,12 @@ noinst_LIBRARIES = libarch_win32.a INCLUDES = -I$(top_srcdir)/arch/include -I$(top_srcdir)/include -I$(top_srcdir)/main -I$(srcdir)/include if USE_NETWORK -NETWORK_SRCS = winnet.c ipx_win.c +NETWORK_SRCS = winnet.c ipx_win.c ipx_udp.c endif libarch_win32_a_SOURCES = ${NETWORK_SRCS} findfile.c mingw_init.c -EXTRA_libarch_win32_a_SOURCES = winnet.c ipx_win.c +EXTRA_libarch_win32_a_SOURCES = winnet.c ipx_win.c ipx_udp.c EXTRA_DIST = ${EXTRA_SUBDIRS} \ d1x.ico d1x.rc d3d.ico debug.cpp descent.ico digi.c findfile.c glinit.c gr.c \ diff --git a/arch/win32/include/ipx_udp.h b/arch/win32/include/ipx_udp.h new file mode 100644 index 000000000..a3d042e81 --- /dev/null +++ b/arch/win32/include/ipx_udp.h @@ -0,0 +1,14 @@ +/* $Id: ipx_udp.h,v 1.1 2003-10-05 22:27:01 btb Exp $ */ +/* + * + * FIXME: add description + * + */ + +#ifndef _IPX_UDP_H +#define _IPX_UDP_H +#include "ipx_drv.h" + +extern struct ipx_driver ipx_udp; + +#endif diff --git a/arch/win32/ipx_udp.c b/arch/win32/ipx_udp.c new file mode 100644 index 000000000..e2edb6f2c --- /dev/null +++ b/arch/win32/ipx_udp.c @@ -0,0 +1,618 @@ +/* $Id: ipx_udp.c,v 1.1 2003-10-05 22:27:01 btb Exp $ */ +/* + * + * IPX driver for native Linux TCP/IP networking (UDP implementation) + * Version 0.99.2 + * Contact Jan [Lace] Kratochvil for assistance + * (no "It somehow doesn't work! What should I do?" complaints, please) + * Special thanks to Vojtech Pavlik for testing. + * + * Also you may see KIX - KIX kix out KaliNix (in Linux-Linux only): + * http://atrey.karlin.mff.cuni.cz/~short/sw/kix.c.gz + * + * Primarily based on ipx_kali.c - "IPX driver for KaliNix interface" + * which is probably mostly by Jay Cotton . + * Parts shamelessly stolen from my KIX v0.99.2 and GGN v0.100 + * + * Changes: + * -------- + * 0.99.1 - now the default broadcast list also contains all point-to-point + * links with their destination peer addresses + * 0.99.2 - commented a bit :-) + * - now adds to broadcast list each host it gets some packet from + * which is already not covered by local physical ethernet broadcast + * - implemented short-signature packet format + * - compatibility mode for old D1X releases due to the previous bullet + * + * Configuration: + * -------------- + * No network server software is needed, neither KIX nor KaliNIX. + * + * NOTE: with the change to allow the user to choose the network + * driver from the game menu, the following is not anymore precise: + * the command line argument "-udp" is only necessary to supply udp + * options! + * + * Add command line argument "-udp". In default operation D1X will send + * broadcasts too all the local interfaces found. But you may also use + * additional parameter specified after "-udp" to modify the default + * broadcasting style and UDP port numbers binding: + * + * ./d1x -udp [@SHIFT]=HOST_LIST Broadcast ONLY to HOST_LIST + * ./d1x -udp [@SHIFT]+HOST_LIST Broadcast both to local ifaces & to HOST_LIST + * + * HOST_LIST is a comma (',') separated list of HOSTs: + * HOST is an IPv4 address (so-called quad like 192.168.1.2) or regular hostname + * HOST can also be in form 'address:SHIFT' + * SHIFT sets the UDP port base offset (e.g. +2), can be used to run multiple + * clients on one host simultaneously. This SHIFT has nothing to do + * with the dynamic-sockets (PgUP/PgDOWN) option in Descent, it's another + * higher-level differentiation option. + * + * Examples: + * --------- + * ./d1x -udp + * - Run D1X to participate in normal local network (Linux only, of course) + * + * ./d1x -udp @1=localhost:2 & ./d1x -udp @2=localhost:1 + * - Run two clients simultaneously fighting each other (only each other) + * + * ./d1x -udp =192.168.4.255 + * - Run distant Descent which will participate with remote network + * 192.168.4.0 with netmask 255.255.255.0 (broadcast has 192.168.4.255) + * - You'll have to also setup hosts in that network accordingly: + * ./d1x -udp +UPPER_DISTANT_MACHINE_NAME + * + * Have fun! + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ipx_drv.h" +#include "args.h" + +extern unsigned char ipx_MyAddress[10]; + +// #define UDPDEBUG + +#define UDP_BASEPORT 28342 +#define PORTSHIFT_TOLERANCE 0x100 +#define MAX_PACKETSIZE 8192 + +/* Packet format: first is the signature { 0xD1,'X' } which can be also + * { 'D','1','X','u','d','p'} for old-fashioned packets. + * Then follows virtual socket number (as changed during PgDOWN/PgUP) + * in network-byte-order as 2 bytes (u_short). After such 4/8 byte header + * follows raw data as communicated with D1X network core functions. + */ + +// Length HAS TO BE 6! +#define D1Xudp "D1Xudp" +// Length HAS TO BE 2! +#define D1Xid "\xD1X" + +static int open_sockets = 0; +static int dynamic_socket = 0x401; +static const int val_one=1; + +/* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value) + */ + +static int baseport=UDP_BASEPORT; + +/* Have we some old D1X in network and have we to maintain compatibility? + * FIXME: Following scenario is braindead: + * A (NEW) , B (OLD) , C (NEW) + * host A) We start new D1X. A-newcomm, B-none , C-none + * host B) We start OLD D1X. A-newcomm, B-oldcomm, C-none + * Now host A hears host B and switches: A-oldcomm, B-oldcomm, C-none + * host C) We start new D1X. A-oldcomm, B-oldcomm, C-newcomm + * Now host C hears host A/B and switches: A-oldcomm, B-oldcomm, C-oldcomm + * Now host B finishes: A-oldcomm, B-none , C-oldcomm + * + * But right now we have hosts A and C, both new code equipped but + * communicating wastefully by the OLD protocol! Bummer. + */ + +static char compatibility=0; + +static int have_empty_address() { + int i; + + for (i = 0; i < 10 && !ipx_MyAddress[i]; i++) ; + return i == 10; +} + +#define MSGHDR "IPX_udp: " + +static void msg(const char *fmt,...) +{ + va_list ap; + + fputs(MSGHDR,stdout); + va_start(ap,fmt); + vprintf(fmt,ap); + va_end(ap); + putchar('\n'); +} + +static void chk(void *p) +{ + if (p) return; + msg("FATAL: Virtual memory exhausted!"); + exit(EXIT_FAILURE); +} + +#define FAIL(m...) do { msg(#m); return -1; } while (0) + +/* Find as much as MAX_BRDINTERFACES during local iface autoconfiguration. + * Note that more interfaces can be added during manual configuration + * or host-received-packet autoconfiguration + */ + +#define MAX_BRDINTERFACES 16 + +/* We require the interface to be UP and RUNNING to accept it. + */ + +#define IF_REQFLAGS (IFF_UP|IFF_RUNNING) + +/* We reject any interfaces declared as LOOPBACK type. + */ +#define IF_NOTFLAGS (IFF_LOOPBACK) + +static struct sockaddr_in *broads,broadmasks[MAX_BRDINTERFACES]; +static int broadnum,masksnum,broadsize; + +/* We'll check whether the "broads" array of destination addresses is now + * full and so needs expanding. + */ + +static void chkbroadsize(void) +{ + if (broadnumsin_family!=AF_INET || sinmp->sin_family!=AF_INET) continue; + broads[j]=*sinp; + broads[j].sin_port=UDP_BASEPORT; //FIXME: No possibility to override from cmdline + broadmasks[j]=*sinmp; + j++; + } + broadnum=j; + masksnum=j; +#endif + return(0); +} + +#define addreq(a,b) ((a).sin_port==(b).sin_port&&(a).sin_addr.s_addr==(b).sin_addr.s_addr) + +/* Previous function addiflist() can (and probably will) report multiple + * same addresses. On some Linux boxes is present both device "eth0" and + * "dummy0" with the same IP addreesses - we'll filter it here. + */ + +static void unifyiflist(void) +{ +int d=0,s,i; + + for (s=0;s=s) broads[d++]=broads[s]; + } + broadnum=d; +} + +static unsigned char qhbuf[6]; + +/* Parse PORTSHIFT numeric parameter + */ + +static void portshift(const char *cs) +{ +long port; +unsigned short ports=0; + + port=atol(cs); + if (port<-PORTSHIFT_TOLERANCE || port>+PORTSHIFT_TOLERANCE) + msg("Invalid portshift in \"%s\", tolerance is +/-%d",cs,PORTSHIFT_TOLERANCE); + else ports=htons(port); + memcpy(qhbuf+4,&ports,2); +} + +/* Do hostname resolve on name "buf" and return the address in buffer "qhbuf". + */ +static unsigned char *queryhost(char *buf) +{ +struct hostent *he; +char *s; +char c=0; + + if ((s=strrchr(buf,':'))) { + c=*s; + *s='\0'; + portshift(s+1); + } + else memset(qhbuf+4,0,2); + he=gethostbyname((char *)buf); + if (s) *s=c; + if (!he) { + msg("Error resolving my hostname \"%s\"",buf); + return(NULL); + } + if (he->h_addrtype!=AF_INET || he->h_length!=4) { + msg("Error parsing resolved my hostname \"%s\"",buf); + return(NULL); + } + if (!*he->h_addr_list) { + msg("My resolved hostname \"%s\" address list empty",buf); + return(NULL); + } + memcpy(qhbuf,(*he->h_addr_list),4); + return(qhbuf); +} + +/* Dump raw form of IP address/port by fancy output to user + */ +static void dumpraddr(unsigned char *a) +{ +short port; + printf("[%u.%u.%u.%u]",a[0],a[1],a[2],a[3]); + port=(signed short)ntohs(*(unsigned short *)(a+4)); + if (port) printf(":%+d",port); +} + +/* Like dumpraddr() but for structure "sockaddr_in" + */ + +static void dumpaddr(struct sockaddr_in *sin) +{ +unsigned short ports; + + memcpy(qhbuf+0,&sin->sin_addr,4); + ports=htons(((short)ntohs(sin->sin_port))-UDP_BASEPORT); + memcpy(qhbuf+4,&ports,2); + dumpraddr(qhbuf); +} + +/* Startup... Uninteresting parsing... + */ + +static int ipx_udp_GetMyAddress(void) { + +char buf[256]; +int i; +char *s,*s2,*ns; + + if (!have_empty_address()) + return 0; + + if (!((i=FindArg("-udp")) && (s=Args[i+1]) && (*s=='=' || *s=='+' || *s=='@'))) s=NULL; + + if (gethostname(buf,sizeof(buf))) FAIL("Error getting my hostname"); + if (!(queryhost(buf))) FAIL("Querying my own hostname \"%s\"",buf); + + if (s) while (*s=='@') { + portshift(++s); + while (isdigit(*s)) s++; + } + + memset(ipx_MyAddress+0,0,4); + memcpy(ipx_MyAddress+4,qhbuf,6); + baseport+=(short)ntohs(*(unsigned short *)(qhbuf+4)); + + if (!s || (s && !*s)) addiflist(); + else { + if (*s=='+') addiflist(); + s++; + for (;;) { +struct sockaddr_in *sin; + while (isspace(*s)) s++; + if (!*s) break; + for (s2=s;*s2 && *s2!=',';s2++); + chk(ns=malloc(s2-s+1)); + memcpy(ns,s,s2-s); + ns[s2-s]='\0'; + if (!queryhost(ns)) msg("Ignored broadcast-destination \"%s\" as being invalid",ns); + free(ns); + chkbroadsize(); + sin=broads+(broadnum++); + sin->sin_family=AF_INET; + memcpy(&sin->sin_addr,qhbuf+0,4); + sin->sin_port=htons(((short)ntohs(*(unsigned short *)(qhbuf+4)))+UDP_BASEPORT); + s=s2+(*s2==','); + } + } + + unifyiflist(); + + printf(MSGHDR "Using TCP/IP address "); + dumpraddr(ipx_MyAddress+4); + putchar('\n'); + if (broadnum) { + printf(MSGHDR "Using %u broadcast-dest%s:",broadnum,(broadnum==1?"":"s")); + for (i=0;ifd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) { + sk->fd = -1; + FAIL("socket() creation failed on port %d: %m",port); + } + if (setsockopt(sk->fd, SOL_SOCKET, SO_BROADCAST, (char *)&val_one, sizeof(val_one))) { + if (close(sk->fd)) msg("close() failed during error recovery: %m"); + sk->fd=-1; + FAIL("setsockopt(SO_BROADCAST) failed: %m"); + } + sin.sin_family=AF_INET; + sin.sin_addr.s_addr=htonl(INADDR_ANY); + sin.sin_port=htons(baseport); + if (bind(sk->fd,(struct sockaddr *)&sin,sizeof(sin))) { + if (close(sk->fd)) msg("close() failed during error recovery: %m"); + sk->fd=-1; + FAIL("bind() to UDP port %d failed: %m",baseport); + } + + open_sockets++; + sk->socket = port; + return 0; +} + +/* The same comment as in previous "ipx_udp_OpenSocket"... + */ + +static void ipx_udp_CloseSocket(ipx_socket_t *mysock) { + if (!open_sockets) { + msg("close w/o open"); + return; + } + msg("CloseSocket on D1X socket port %d",mysock->socket); + if (close(mysock->fd)) + msg("close() failed on CloseSocket D1X socket port %d: %m",mysock->socket); + mysock->fd=-1; + if (--open_sockets) { + msg("(closesocket) %d sockets left", open_sockets); + return; + } +} + +/* Here we'll send the packet to our host. If it is unicast packet, send + * it to IP address/port as retrieved from IPX address. Otherwise (broadcast) + * we'll repeat the same data to each host in our broadcasting list. + */ + +static int ipx_udp_SendPacket(ipx_socket_t *mysock, IPXPacket_t *IPXHeader, + u_char *data, int dataLen) { + struct sockaddr_in toaddr,*dest; + int i=dataLen; + int bcast; + char *buf; + +#ifdef UDPDEBUG + msg("SendPacket enter, dataLen=%d",dataLen); +#endif + if (dataLen<0 || dataLen>MAX_PACKETSIZE) return -1; + chk(buf=alloca(8+dataLen)); + if (compatibility) memcpy(buf+0,D1Xudp,6),buf+=6; + else memcpy(buf+0,D1Xid ,2),buf+=2; + memcpy(buf+0,IPXHeader->Destination.Socket,2); + memcpy(buf+2,data,dataLen); + + toaddr.sin_family=AF_INET; + memcpy(&toaddr.sin_addr,IPXHeader->Destination.Node+0,4); + toaddr.sin_port=htons(((short)ntohs(*(unsigned short *)(IPXHeader->Destination.Node+4)))+UDP_BASEPORT); + + for (bcast=(toaddr.sin_addr.s_addr==htonl(INADDR_BROADCAST)?0:-1);bcast=0) dest=broads+bcast; + else dest=&toaddr; + +#ifdef UDPDEBUG + printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,", + dataLen, + IPXHeader->Destination.Node [4],IPXHeader->Destination.Node [5], + IPXHeader->Destination.Socket[0],IPXHeader->Destination.Socket[1], + ntohs(dest->sin_port)); + dumpaddr(dest); + puts(")."); +#endif + i=sendto(mysock->fd,buf-(compatibility?6:2),(compatibility?8:4)+dataLen, + 0,(struct sockaddr *)dest,sizeof(*dest)); + if (bcast==-1) return (i<8?-1:i-8); + } + return(dataLen); +} + +/* Here we will receive new packet to the given buffer. Both formats of packets + * are supported, we fallback to old format when first obsolete packet is seen. + * If the (valid) packet is received from unknown host, we will add it to our + * broadcasting list. FIXME: For now such autoconfigured hosts are NEVER removed. + */ + +static int ipx_udp_ReceivePacket(ipx_socket_t *s, char *outbuf, int outbufsize, + struct ipx_recv_data *rd) { + int size; + struct sockaddr_in fromaddr; + int fromaddrsize=sizeof(fromaddr); + unsigned short ports; + size_t offs; + int i; + + if ((size=recvfrom(s->fd,outbuf,outbufsize,0,(struct sockaddr *)&fromaddr,&fromaddrsize))<0) + return -1; +#ifdef UDPDEBUG + printf(MSGHDR "recvfrom((%d-8=%d),",size,size-8); + dumpaddr(&fromaddr); + puts(")."); +#endif + if (fromaddr.sin_family!=AF_INET) return -1; + if (size<4) return -1; + if (memcmp(outbuf+0,D1Xid,2)) { + if (size<8 || memcmp(outbuf+0,D1Xudp,6)) return -1; + if (!compatibility) { + compatibility=1; + fputs(MSGHDR "Received obsolete packet from ",stdout); + dumpaddr(&fromaddr); + puts(", upgrade that machine.\n" MSGHDR "Turning on compatibility mode..."); + } + offs=6; + } + else offs=2; + + /* Lace: (dst_socket & src_socket) should be network-byte-order by comment in include/ipx_drv.h */ + /* This behaviour presented here is broken. It is not used anywhere, so why bother? */ + rd->src_socket = ntohs(*(unsigned short *)(outbuf+offs)); + if (rd->src_socket != s->socket) { +#ifdef UDPDEBUG + msg(" - pkt was dropped (dst=%d,my=%d)",rd->src_socket,s->socket); +#endif + return -1; + } + rd->dst_socket = s->socket; + + // check if we already have sender of this packet in broadcast list + for (i=0;i=masksnum) { + if (addreq(fromaddr,broads[i])) break; + } + else { + if (fromaddr.sin_port==broads[i].sin_port + &&( fromaddr.sin_addr.s_addr & broadmasks[i].sin_addr.s_addr) + ==(broads[i].sin_addr.s_addr & broadmasks[i].sin_addr.s_addr)) break; + } + } + if (i>=broadnum) { // we don't have sender of this packet in our broadcast list + chkbroadsize(); + broads[broadnum++]=fromaddr; + fputs(MSGHDR "Adding host ",stdout); + dumpaddr(&fromaddr); + puts(" to broadcasting address list"); + } + + memmove(outbuf,outbuf+offs+2,size-(offs+2)); + size-=offs+2; + + memcpy(rd->src_node+0,&fromaddr.sin_addr,4); + ports=htons(ntohs(fromaddr.sin_port)-UDP_BASEPORT); + memcpy(rd->src_node+4,&ports,2); + memset(rd->src_network, 0, 4); + rd->pkt_type = 0; +#ifdef UDPDEBUG + printf(MSGHDR "ReceivePacket: size=%d,from=",size); + dumpraddr(rd->src_node); + putchar('\n'); +#endif + + return size; +} + +struct ipx_driver ipx_udp = { + ipx_udp_GetMyAddress, + ipx_udp_OpenSocket, + ipx_udp_CloseSocket, + ipx_udp_SendPacket, + ipx_udp_ReceivePacket, + ipx_general_PacketReady +}; diff --git a/arch/win32/winnet.c b/arch/win32/winnet.c index 715ce9abc..c796ecd6b 100644 --- a/arch/win32/winnet.c +++ b/arch/win32/winnet.c @@ -1,4 +1,4 @@ -/* $Id: winnet.c,v 1.7 2003-10-04 03:18:02 btb Exp $ */ +/* $Id: winnet.c,v 1.8 2003-10-05 22:27:01 btb Exp $ */ /* THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO @@ -29,8 +29,10 @@ COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. #include #include "args.h" +#include "error.h" #include "ipx.h" #include "ipx_drv.h" +#include "ipx_udp.h" extern struct ipx_driver ipx_win; @@ -85,10 +87,11 @@ ubyte * ipx_get_my_local_address() void arch_ipx_set_driver(int ipx_driver) { - if (ipx_driver != IPX_DRIVER_IPX) - Warning("Unknown network driver! Defaulting to real IPX"); - - driver = &ipx_win; + switch(ipx_driver) { + case IPX_DRIVER_IPX: driver = &ipx_win; break; + case IPX_DRIVER_UDP: driver = &ipx_udp; break; + default: Int3(); + } } int ipx_init(int socket_number) diff --git a/main/menu.c b/main/menu.c index 70d36f3a8..379745e69 100644 --- a/main/menu.c +++ b/main/menu.c @@ -1,4 +1,4 @@ -/* $Id: menu.c,v 1.26 2003-10-04 02:58:23 btb Exp $ */ +/* $Id: menu.c,v 1.27 2003-10-05 22:27:01 btb Exp $ */ /* THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO @@ -1722,9 +1722,9 @@ void do_multi_player_menu() #endif //NATIVE_IPX //ADD_ITEM(TXT_START_TCP_NET_GAME, MENU_START_TCP_NETGAME, -1); //ADD_ITEM(TXT_JOIN_TCP_NET_GAME, MENU_JOIN_TCP_NETGAME, -1); -#ifdef __unix__ ADD_ITEM("Start UDP/IP Netgame", MENU_START_UDP_NETGAME, -1); ADD_ITEM("Join UDP/IP Netgame\n", MENU_JOIN_UDP_NETGAME, -1); +#ifdef __unix__ ADD_ITEM("Start Kali Netgame", MENU_START_KALI_NETGAME, -1); ADD_ITEM("Join Kali Netgame\n", MENU_JOIN_KALI_NETGAME, -1); #endif //__unix__