3 * IPX driver for native Linux TCP/IP networking (UDP implementation)
5 * Contact Jan [Lace] Kratochvil <short@ucw.cz> for assistance
6 * (no "It somehow doesn't work! What should I do?" complaints, please)
7 * Special thanks to Vojtech Pavlik <vojtech@ucw.cz> for testing.
9 * Also you may see KIX - KIX kix out KaliNix (in Linux-Linux only):
10 * http://atrey.karlin.mff.cuni.cz/~short/sw/kix.c.gz
12 * Primarily based on ipx_kali.c - "IPX driver for KaliNix interface"
13 * which is probably mostly by Jay Cotton <jay@kali.net>.
14 * Parts shamelessly stolen from my KIX v0.99.2 and GGN v0.100
18 * 0.99.1 - now the default broadcast list also contains all point-to-point
19 * links with their destination peer addresses
20 * 0.99.2 - commented a bit :-)
21 * - now adds to broadcast list each host it gets some packet from
22 * which is already not covered by local physical ethernet broadcast
23 * - implemented short-signature packet format
24 * - compatibility mode for old D1X releases due to the previous bullet
28 * No network server software is needed, neither KIX nor KaliNIX.
30 * NOTE: with the change to allow the user to choose the network
31 * driver from the game menu, the following is not anymore precise:
32 * the command line argument "-udp" is only necessary to supply udp
35 * Add command line argument "-udp". In default operation D1X will send
36 * broadcasts too all the local interfaces found. But you may also use
37 * additional parameter specified after "-udp" to modify the default
38 * broadcasting style and UDP port numbers binding:
40 * ./d1x -udp [@SHIFT]=HOST_LIST Broadcast ONLY to HOST_LIST
41 * ./d1x -udp [@SHIFT]+HOST_LIST Broadcast both to local ifaces & to HOST_LIST
43 * HOST_LIST is a comma (',') separated list of HOSTs:
44 * HOST is an IPv4 address (so-called quad like 192.168.1.2) or regular hostname
45 * HOST can also be in form 'address:SHIFT'
46 * SHIFT sets the UDP port base offset (e.g. +2), can be used to run multiple
47 * clients on one host simultaneously. This SHIFT has nothing to do
48 * with the dynamic-sockets (PgUP/PgDOWN) option in Descent, it's another
49 * higher-level differentiation option.
54 * - Run D1X to participate in normal local network (Linux only, of course)
56 * ./d1x -udp @1=localhost:2 & ./d1x -udp @2=localhost:1
57 * - Run two clients simultaneously fighting each other (only each other)
59 * ./d1x -udp =192.168.4.255
60 * - Run distant Descent which will participate with remote network
61 * 192.168.4.0 with netmask 255.255.255.0 (broadcast has 192.168.4.255)
62 * - You'll have to also setup hosts in that network accordingly:
63 * ./d1x -udp +UPPER_DISTANT_MACHINE_NAME
82 # include <sys/sockio.h>
91 #include <arpa/inet.h>
92 #include <netinet/in.h>
93 #include <sys/socket.h>
95 #include <sys/ioctl.h>
97 #define closesocket(x) close(x)
98 #define ioctlsocket(x) ioctl(x)
105 extern unsigned char ipx_MyAddress[10];
109 #define UDP_BASEPORT 28342
110 #define PORTSHIFT_TOLERANCE 0x100
111 #define MAX_PACKETSIZE 8192
113 /* Packet format: first is the signature { 0xD1,'X' } which can be also
114 * { 'D','1','X','u','d','p'} for old-fashioned packets.
115 * Then follows virtual socket number (as changed during PgDOWN/PgUP)
116 * in network-byte-order as 2 bytes (u_short). After such 4/8 byte header
117 * follows raw data as communicated with D1X network core functions.
120 // Length HAS TO BE 6!
121 #define D1Xudp "D1Xudp"
122 // Length HAS TO BE 2!
123 #define D1Xid "\xD1X"
125 static int open_sockets = 0;
126 static int dynamic_socket = 0x401;
127 static const int val_one=1;
129 /* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value)
132 static int baseport=UDP_BASEPORT;
134 /* Have we some old D1X in network and have we to maintain compatibility?
135 * FIXME: Following scenario is braindead:
136 * A (NEW) , B (OLD) , C (NEW)
137 * host A) We start new D1X. A-newcomm, B-none , C-none
138 * host B) We start OLD D1X. A-newcomm, B-oldcomm, C-none
139 * Now host A hears host B and switches: A-oldcomm, B-oldcomm, C-none
140 * host C) We start new D1X. A-oldcomm, B-oldcomm, C-newcomm
141 * Now host C hears host A/B and switches: A-oldcomm, B-oldcomm, C-oldcomm
142 * Now host B finishes: A-oldcomm, B-none , C-oldcomm
144 * But right now we have hosts A and C, both new code equipped but
145 * communicating wastefully by the OLD protocol! Bummer.
148 static char compatibility=0;
150 static int have_empty_address() {
153 for (i = 0; i < 10 && !ipx_MyAddress[i]; i++) ;
157 #define MSGHDR "IPX_udp: "
159 static void msg(const char *fmt,...)
163 fputs(MSGHDR,stdout);
170 static void chk(void *p)
173 msg("FATAL: Virtual memory exhausted!");
177 #define FAIL(...) do { msg(__VA_ARGS__); return -1; } while (0)
179 /* Find as much as MAX_BRDINTERFACES during local iface autoconfiguration.
180 * Note that more interfaces can be added during manual configuration
181 * or host-received-packet autoconfiguration
184 #define MAX_BRDINTERFACES 16
186 /* We require the interface to be UP and RUNNING to accept it.
189 #define IF_REQFLAGS (IFF_UP|IFF_RUNNING)
191 /* We reject any interfaces declared as LOOPBACK type.
193 #define IF_NOTFLAGS (IFF_LOOPBACK)
195 static struct sockaddr_in *broads,broadmasks[MAX_BRDINTERFACES];
196 static int broadnum,masksnum,broadsize;
198 /* We'll check whether the "broads" array of destination addresses is now
199 * full and so needs expanding.
202 static void chkbroadsize(void)
204 if (broadnum<broadsize) return;
205 broadsize=broadsize?broadsize*2:8;
206 chk(broads=realloc(broads,sizeof(*broads)*broadsize));
209 /* This function is called during init and has to grab all system interfaces
210 * and collect their broadcast-destination addresses (and their netmasks).
211 * Typically it founds only one ethernet card and so returns address in
212 * the style "192.168.1.255" with netmask "255.255.255.0".
213 * Broadcast addresses are filled into "broads", netmasks to "broadmasks".
216 /* Stolen from my GGN */
217 static int addiflist(void)
220 unsigned cnt=MAX_BRDINTERFACES,i,j;
221 struct ifconf ifconf;
223 struct sockaddr_in *sinp,*sinmp;
226 if ((sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
227 FAIL("Creating socket() failure during broadcast detection: %m");
230 if (ioctl(sock,SIOCGIFCOUNT,&cnt))
231 { /* msg("Getting iterface count error: %m"); */ }
236 chk(ifconf.ifc_req=alloca((ifconf.ifc_len=cnt*sizeof(struct ifreq))));
237 if (ioctl(sock,SIOCGIFCONF,&ifconf)||ifconf.ifc_len%sizeof(struct ifreq)) {
239 FAIL("ioctl(SIOCGIFCONF) failure during broadcast detection: %m");
241 cnt=ifconf.ifc_len/sizeof(struct ifreq);
242 chk(broads=malloc(cnt*sizeof(*broads)));
244 for (i=j=0;i<cnt;i++) {
245 if (ioctl(sock,SIOCGIFFLAGS,ifconf.ifc_req+i)) {
247 FAIL("ioctl(udp,\"%s\",SIOCGIFFLAGS) error: %m",ifconf.ifc_req[i].ifr_name);
249 if (((ifconf.ifc_req[i].ifr_flags&IF_REQFLAGS)!=IF_REQFLAGS)||
250 (ifconf.ifc_req[i].ifr_flags&IF_NOTFLAGS))
252 if (ioctl(sock,(ifconf.ifc_req[i].ifr_flags&IFF_BROADCAST?SIOCGIFBRDADDR:SIOCGIFDSTADDR),ifconf.ifc_req+i)) {
254 FAIL("ioctl(udp,\"%s\",SIOCGIF{DST/BRD}ADDR) error: %m",ifconf.ifc_req[i].ifr_name);
257 sinp = (struct sockaddr_in *)&ifconf.ifc_req[i].ifr_broadaddr;
258 #if 0 // old, not portable code
259 sinmp = (struct sockaddr_in *)&ifconf.ifc_req[i].ifr_netmask;
260 #else // portable code
261 if (ioctl(sock, SIOCGIFNETMASK, ifconf.ifc_req+i)) {
263 FAIL("ioctl(udp,\"%s\",SIOCGIFNETMASK) error: %m", ifconf.ifc_req[i].ifr_name);
265 sinmp = (struct sockaddr_in *)&ifconf.ifc_req[i].ifr_addr;
267 if (sinp->sin_family!=AF_INET || sinmp->sin_family!=AF_INET) continue;
269 broads[j].sin_port=UDP_BASEPORT; //FIXME: No possibility to override from cmdline
270 broadmasks[j]=*sinmp;
279 #define addreq(a,b) ((a).sin_port==(b).sin_port&&(a).sin_addr.s_addr==(b).sin_addr.s_addr)
281 /* Previous function addiflist() can (and probably will) report multiple
282 * same addresses. On some Linux boxes is present both device "eth0" and
283 * "dummy0" with the same IP addreesses - we'll filter it here.
286 static void unifyiflist(void)
290 for (s=0;s<broadnum;s++) {
292 if (addreq(broads[s],broads[i])) break;
293 if (i>=s) broads[d++]=broads[s];
298 static unsigned char qhbuf[6];
300 /* Parse PORTSHIFT numeric parameter
303 static void portshift(const char *cs)
306 unsigned short ports=0;
309 if (port<-PORTSHIFT_TOLERANCE || port>+PORTSHIFT_TOLERANCE)
310 msg("Invalid portshift in \"%s\", tolerance is +/-%d",cs,PORTSHIFT_TOLERANCE);
311 else ports=htons(port);
312 memcpy(qhbuf+4,&ports,2);
315 /* Do hostname resolve on name "buf" and return the address in buffer "qhbuf".
317 static unsigned char *queryhost(char *buf)
323 if ((s=strrchr(buf,':'))) {
328 else memset(qhbuf+4,0,2);
329 he=gethostbyname((char *)buf);
332 msg("Error resolving my hostname \"%s\"",buf);
335 if (he->h_addrtype!=AF_INET || he->h_length!=4) {
336 msg("Error parsing resolved my hostname \"%s\"",buf);
339 if (!*he->h_addr_list) {
340 msg("My resolved hostname \"%s\" address list empty",buf);
343 memcpy(qhbuf,(*he->h_addr_list),4);
347 /* Dump raw form of IP address/port by fancy output to user
349 static void dumpraddr(unsigned char *a)
352 printf("[%u.%u.%u.%u]",a[0],a[1],a[2],a[3]);
353 port=(signed short)ntohs(*(unsigned short *)(a+4));
354 if (port) printf(":%+d",port);
357 /* Like dumpraddr() but for structure "sockaddr_in"
360 static void dumpaddr(struct sockaddr_in *sin)
362 unsigned short ports;
364 memcpy(qhbuf+0,&sin->sin_addr,4);
365 ports=htons(((short)ntohs(sin->sin_port))-UDP_BASEPORT);
366 memcpy(qhbuf+4,&ports,2);
370 /* Startup... Uninteresting parsing...
373 int ipx_udp_GetMyAddress(void) {
377 char *s = NULL, *s2, *ns;
379 if (!have_empty_address())
382 if (!((i=FindArg("-udp")) && (s=Args[i+1]) && (*s=='=' || *s=='+' || *s=='@'))) s=NULL;
384 if (gethostname(buf,sizeof(buf))) FAIL("Error getting my hostname");
385 if (!(queryhost(buf))) FAIL("Querying my own hostname \"%s\"",buf);
387 if (s) while (*s=='@') {
389 while (isdigit(*s)) s++;
392 memset(ipx_MyAddress+0,0,4);
393 memcpy(ipx_MyAddress+4,qhbuf,6);
394 baseport+=(short)ntohs(*(unsigned short *)(qhbuf+4));
396 if (!s || (s && !*s)) addiflist();
398 if (*s=='+') addiflist();
401 struct sockaddr_in *sin;
402 while (isspace(*s)) s++;
404 for (s2=s;*s2 && *s2!=',';s2++);
405 chk(ns=malloc(s2-s+1));
408 if (!queryhost(ns)) msg("Ignored broadcast-destination \"%s\" as being invalid",ns);
411 sin=broads+(broadnum++);
412 sin->sin_family=AF_INET;
413 memcpy(&sin->sin_addr,qhbuf+0,4);
414 sin->sin_port=htons(((short)ntohs(*(unsigned short *)(qhbuf+4)))+UDP_BASEPORT);
421 printf(MSGHDR "Using TCP/IP address ");
422 dumpraddr(ipx_MyAddress+4);
425 printf(MSGHDR "Using %u broadcast-dest%s:",broadnum,(broadnum==1?"":"s"));
426 for (i=0;i<broadnum;i++) {
436 /* We should probably avoid such insanity here as during PgUP/PgDOWN
437 * (virtual port number change) we wastefully destroy/create the same
438 * socket here (binded always to "baseport"). FIXME.
439 * "open_sockets" can be only 0 or 1 anyway.
442 static int ipx_udp_OpenSocket(ipx_socket_t *sk, int port) {
443 struct sockaddr_in sin;
446 if (have_empty_address())
447 if (ipx_udp_GetMyAddress() < 0) FAIL("Error getting my address");
449 msg("OpenSocket on D1X socket port %d",port);
452 port = dynamic_socket++;
454 if ((sk->fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) {
456 FAIL("socket() creation failed on port %d: %m",port);
458 if (setsockopt(sk->fd, SOL_SOCKET, SO_BROADCAST, (const void *)&val_one, sizeof(val_one))) {
459 if (closesocket(sk->fd)) msg("closesocket() failed during error recovery: %m");
461 FAIL("setsockopt(SO_BROADCAST) failed: %m");
463 sin.sin_family=AF_INET;
464 sin.sin_addr.s_addr=htonl(INADDR_ANY);
465 sin.sin_port=htons(baseport);
466 if (bind(sk->fd,(struct sockaddr *)&sin,sizeof(sin))) {
467 if (closesocket(sk->fd)) msg("closesocket() failed during error recovery: %m");
469 FAIL("bind() to UDP port %d failed: %m",baseport);
477 /* The same comment as in previous "ipx_udp_OpenSocket"...
480 static void ipx_udp_CloseSocket(ipx_socket_t *mysock) {
482 msg("close w/o open");
485 msg("CloseSocket on D1X socket port %d",mysock->socket);
486 if (closesocket(mysock->fd))
487 msg("closesocket() failed on CloseSocket D1X socket port %d: %m",mysock->socket);
489 if (--open_sockets) {
490 msg("(closesocket) %d sockets left", open_sockets);
495 /* Here we'll send the packet to our host. If it is unicast packet, send
496 * it to IP address/port as retrieved from IPX address. Otherwise (broadcast)
497 * we'll repeat the same data to each host in our broadcasting list.
500 static int ipx_udp_SendPacket(ipx_socket_t *mysock, IPXPacket_t *IPXHeader,
501 u_char *data, int dataLen) {
502 struct sockaddr_in toaddr,*dest;
508 msg("SendPacket enter, dataLen=%d",dataLen);
510 if (dataLen<0 || dataLen>MAX_PACKETSIZE) return -1;
511 chk(buf=alloca(8+dataLen));
513 memcpy(buf + 0, D1Xudp, 6);
516 memcpy(buf + 0, D1Xid, 2);
519 memcpy(buf+0,IPXHeader->Destination.Socket,2);
520 memcpy(buf+2,data,dataLen);
522 toaddr.sin_family=AF_INET;
523 memcpy(&toaddr.sin_addr,IPXHeader->Destination.Node+0,4);
526 memcpy(&port, IPXHeader->Destination.Node + 4, 2);
527 toaddr.sin_port = htons(ntohs(port + UDP_BASEPORT));
530 for (bcast=(toaddr.sin_addr.s_addr==htonl(INADDR_BROADCAST)?0:-1);bcast<broadnum;bcast++) {
531 if (bcast>=0) dest=broads+bcast;
535 printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,",
537 IPXHeader->Destination.Node [4],IPXHeader->Destination.Node [5],
538 IPXHeader->Destination.Socket[0],IPXHeader->Destination.Socket[1],
539 ntohs(dest->sin_port));
543 i = (int)sendto(mysock->fd, buf - (compatibility?6:2), (compatibility?8:4) + dataLen, 0, (struct sockaddr *)dest, sizeof(*dest));
544 if (bcast==-1) return (i<8?-1:i-8);
549 /* Here we will receive new packet to the given buffer. Both formats of packets
550 * are supported, we fallback to old format when first obsolete packet is seen.
551 * If the (valid) packet is received from unknown host, we will add it to our
552 * broadcasting list. FIXME: For now such autoconfigured hosts are NEVER removed.
555 static int ipx_udp_ReceivePacket(ipx_socket_t *s, char *outbuf, int outbufsize,
556 struct ipx_recv_data *rd) {
558 struct sockaddr_in fromaddr;
559 socklen_t fromaddrsize = sizeof(fromaddr);
560 unsigned short ports;
564 if ((size = (int)recvfrom(s->fd, outbuf, outbufsize, 0, (struct sockaddr *)&fromaddr, &fromaddrsize)) < 0)
567 printf(MSGHDR "recvfrom((%d-8=%d),",size,size-8);
571 if (fromaddr.sin_family!=AF_INET) return -1;
572 if (size<4) return -1;
573 if (memcmp(outbuf+0,D1Xid,2)) {
574 if (size<8 || memcmp(outbuf+0,D1Xudp,6)) return -1;
575 if (!compatibility) {
577 fputs(MSGHDR "Received obsolete packet from ",stdout);
579 puts(", upgrade that machine.\n" MSGHDR "Turning on compatibility mode...");
585 /* Lace: (dst_socket & src_socket) should be network-byte-order by comment in include/ipx_drv.h */
586 /* This behaviour presented here is broken. It is not used anywhere, so why bother? */
587 rd->src_socket = ntohs(*(unsigned short *)(outbuf+offs));
588 if (rd->src_socket != s->socket) {
590 msg(" - pkt was dropped (dst=%d,my=%d)",rd->src_socket,s->socket);
594 rd->dst_socket = s->socket;
596 // check if we already have sender of this packet in broadcast list
597 for (i=0;i<broadnum;i++) {
599 if (addreq(fromaddr,broads[i])) break;
602 if (fromaddr.sin_port==broads[i].sin_port
603 &&( fromaddr.sin_addr.s_addr & broadmasks[i].sin_addr.s_addr)
604 ==(broads[i].sin_addr.s_addr & broadmasks[i].sin_addr.s_addr)) break;
607 if (i>=broadnum) { // we don't have sender of this packet in our broadcast list
609 broads[broadnum++]=fromaddr;
610 fputs(MSGHDR "Adding host ",stdout);
612 puts(" to broadcasting address list");
615 memmove(outbuf,outbuf+offs+2,size-(offs+2));
618 memcpy(rd->src_node+0,&fromaddr.sin_addr,4);
619 ports=htons(ntohs(fromaddr.sin_port)-UDP_BASEPORT);
620 memcpy(rd->src_node+4,&ports,2);
621 memset(rd->src_network, 0, 4);
624 printf(MSGHDR "ReceivePacket: size=%d,from=",size);
625 dumpraddr(rd->src_node);
632 struct ipx_driver ipx_udp = {
633 ipx_udp_GetMyAddress,
637 ipx_udp_ReceivePacket,
638 ipx_general_PacketReady,
639 NULL, // InitNetgameAuxData
640 NULL, // HandleNetgameAuxData
641 NULL, // HandleLeaveGame
642 NULL // SendGamePacket