]> icculus.org git repositories - btb/d2x.git/blob - arch/linux_ipx_udp.c
More header unification...
[btb/d2x.git] / arch / linux_ipx_udp.c
1 /* IPX driver for native Linux TCP/IP networking (UDP implementation)
2  *   Version 0.99.2
3  * Contact Jan [Lace] Kratochvil <short@ucw.cz> for assistance
4  * (no "It somehow doesn't work! What should I do?" complaints, please)
5  * Special thanks to Vojtech Pavlik <vojtech@ucw.cz> for testing.
6  *
7  * Also you may see KIX - KIX kix out KaliNix (in Linux-Linux only):
8  *   http://atrey.karlin.mff.cuni.cz/~short/sw/kix.c.gz
9  *
10  * Primarily based on ipx_kali.c - "IPX driver for KaliNix interface"
11  *   which is probably mostly by Jay Cotton <jay@kali.net>.
12  * Parts shamelessly stolen from my KIX v0.99.2 and GGN v0.100
13  *
14  * Changes:
15  * --------
16  * 0.99.1 - now the default broadcast list also contains all point-to-point
17  *          links with their destination peer addresses
18  * 0.99.2 - commented a bit :-) 
19  *        - now adds to broadcast list each host it gets some packet from
20  *          which is already not covered by local physical ethernet broadcast
21  *        - implemented short-signature packet format
22  *        - compatibility mode for old D1X releases due to the previous bullet
23  *
24  * Configuration:
25  * --------------
26  * No network server software is needed, neither KIX nor KaliNIX.
27  *
28  * Add command line argument "-udp". In default operation D1X will send
29  * broadcasts too all the local interfaces found. But you may also use
30  * additional parameter specified after "-udp" to modify the default
31  * broadcasting style and UDP port numbers binding:
32  *
33  * ./d1x -udp [@SHIFT]=HOST_LIST   Broadcast ONLY to HOST_LIST
34  * ./d1x -udp [@SHIFT]+HOST_LIST   Broadcast both to local ifaces & to HOST_LIST
35  *
36  * HOST_LIST is a comma (',') separated list of HOSTs:
37  * HOST is an IPv4 address (so-called quad like 192.168.1.2) or regular hostname
38  * HOST can also be in form 'address:SHIFT'
39  * SHIFT sets the UDP port base offset (e.g. +2), can be used to run multiple
40  *       clients on one host simultaneously. This SHIFT has nothing to do
41  *       with the dynamic-sockets (PgUP/PgDOWN) option in Descent, it's another
42  *       higher-level differentiation option.
43  *
44  * Examples:
45  * ---------
46  * ./d1x -udp
47  *  - Run D1X to participate in normal local network (Linux only, of course)
48  *
49  * ./d1x -udp @1=localhost:2 & ./d1x -udp @2=localhost:1
50  *  - Run two clients simultaneously fighting each other (only each other)
51  *
52  * ./d1x -udp =192.168.4.255
53  *  - Run distant Descent which will participate with remote network
54  *    192.168.4.0 with netmask 255.255.255.0 (broadcast has 192.168.4.255)
55  *  - You'll have to also setup hosts in that network accordingly:
56  * ./d1x -udp +UPPER_DISTANT_MACHINE_NAME
57  *
58  * Have fun!
59  */
60
61 #include <conf.h>
62 #ifdef __ENV_LINUX__
63 #ifdef NETWORK
64 #include <stdio.h>
65 #include <string.h>
66 #include <netinet/in.h> /* for htons & co. */
67 #include <unistd.h>
68 #include <stdarg.h>
69 #include <netdb.h>
70 #include <stdlib.h>
71 #include <net/if.h>
72 #include <sys/ioctl.h>
73 #include <ctype.h>
74
75 #include "ipx_drv.h"
76 #include "args.h"
77
78 extern unsigned char ipx_MyAddress[10];
79
80 // #define UDPDEBUG
81
82 #define UDP_BASEPORT 28342
83 #define PORTSHIFT_TOLERANCE 0x100
84 #define MAX_PACKETSIZE 8192
85
86 /* Packet format: first is the signature { 0xD1,'X' } which can be also
87  * { 'D','1','X','u','d','p'} for old-fashioned packets.
88  * Then follows virtual socket number (as changed during PgDOWN/PgUP)
89  * in network-byte-order as 2 bytes (u_short). After such 4/8 byte header
90  * follows raw data as communicated with D1X network core functions.
91  */
92
93 // Length HAS TO BE 6!
94 #define D1Xudp "D1Xudp"
95 // Length HAS TO BE 2!
96 #define D1Xid "\xD1X"
97
98 static int open_sockets = 0;
99 static int dynamic_socket = 0x401;
100 static const int val_one=1;
101
102 /* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value)
103  */
104
105 static int baseport=UDP_BASEPORT;
106
107 /* Have we some old D1X in network and have we to maintain compatibility?
108  * FIXME: Following scenario is braindead:
109  *                                           A  (NEW) , B  (OLD) , C  (NEW)
110  *   host A) We start new D1X.               A-newcomm, B-none   , C-none
111  *   host B) We start OLD D1X.               A-newcomm, B-oldcomm, C-none
112  *   Now host A hears host B and switches:   A-oldcomm, B-oldcomm, C-none
113  *   host C) We start new D1X.               A-oldcomm, B-oldcomm, C-newcomm
114  *   Now host C hears host A/B and switches: A-oldcomm, B-oldcomm, C-oldcomm
115  *   Now host B finishes:                    A-oldcomm, B-none   , C-oldcomm
116  *
117  * But right now we have hosts A and C, both new code equipped but
118  * communicating wastefully by the OLD protocol! Bummer.
119  */
120
121 static char compatibility=0;
122
123 static int have_empty_address() {
124         int i;
125         for (i = 0; i < 10 && !ipx_MyAddress[i]; i++) ;
126         return i == 10;
127 }
128
129 #define MSGHDR "IPX_udp: "
130
131 static void msg(const char *fmt,...)
132 {
133 va_list ap;
134         fputs(MSGHDR,stdout);
135         va_start(ap,fmt);
136         vprintf(fmt,ap);
137         va_end(ap);
138         putchar('\n');
139 }
140
141 static void chk(void *p)
142 {
143         if (p) return;
144         msg("FATAL: Virtual memory exhausted!");
145         exit(EXIT_FAILURE);
146 }
147
148 #define FAIL(m...) do { msg(#m); return -1; } while (0)
149
150 /* Find as much as MAX_BRDINTERFACES during local iface autoconfiguration.
151  * Note that more interfaces can be added during manual configuration
152  * or host-received-packet autoconfiguration
153  */
154
155 #define MAX_BRDINTERFACES 16
156
157 /* We require the interface to be UP and RUNNING to accept it.
158  */
159
160 #define IF_REQFLAGS (IFF_UP|IFF_RUNNING)
161
162 /* We reject any interfaces declared as LOOPBACK type.
163  */
164 #define IF_NOTFLAGS (IFF_LOOPBACK)
165
166 static struct sockaddr_in *broads,broadmasks[MAX_BRDINTERFACES];
167 static int broadnum,masksnum,broadsize;
168
169 /* We'll check whether the "broads" array of destination addresses is now
170  * full and so needs expanding.
171  */
172
173 static void chkbroadsize(void)
174 {
175         if (broadnum<broadsize) return;
176         broadsize=broadsize?broadsize*2:8;
177         chk(broads=realloc(broads,sizeof(*broads)*broadsize));
178 }
179
180 /* This function is called during init and has to grab all system interfaces
181  * and collect their broadcast-destination addresses (and their netmasks).
182  * Typically it founds only one ethernet card and so returns address in
183  * the style "192.168.1.255" with netmask "255.255.255.0".
184  * Broadcast addresses are filled into "broads", netmasks to "broadmasks".
185  */
186
187 /* Stolen from my GGN */
188 static int addiflist(void)
189 {
190 unsigned cnt=MAX_BRDINTERFACES,i,j;
191 struct ifconf ifconf;
192 int sock;
193 struct sockaddr_in *sinp,*sinmp;
194
195         free(broads);
196         if ((sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))<0)
197                 FAIL("Creating socket() failure during broadcast detection: %m");
198
199 #ifdef SIOCGIFCOUNT
200         if (ioctl(sock,SIOCGIFCOUNT,&cnt))
201                 { /* msg("Getting iterface count error: %m"); */ }
202         else
203                 cnt=cnt*2+2;
204 #endif
205
206         chk(ifconf.ifc_req=alloca((ifconf.ifc_len=cnt*sizeof(struct ifreq))));
207         if (ioctl(sock,SIOCGIFCONF,&ifconf)||ifconf.ifc_len%sizeof(struct ifreq)) {
208                 close(sock);
209                 FAIL("ioctl(SIOCGIFCONF) failure during broadcast detection: %m");
210                 }
211         cnt=ifconf.ifc_len/sizeof(struct ifreq);
212         chk(broads=malloc(cnt*sizeof(*broads)));
213         broadsize=cnt;
214         for (i=j=0;i<cnt;i++) {
215                 if (ioctl(sock,SIOCGIFFLAGS,ifconf.ifc_req+i)) {
216                         close(sock);
217                         FAIL("ioctl(udp,\"%s\",SIOCGIFFLAGS) error: %m",ifconf.ifc_req[i].ifr_name);
218                         }
219                 if (((ifconf.ifc_req[i].ifr_flags&IF_REQFLAGS)!=IF_REQFLAGS)||
220                                  (ifconf.ifc_req[i].ifr_flags&IF_NOTFLAGS))
221                         continue;
222                 if (ioctl(sock,(ifconf.ifc_req[i].ifr_flags&IFF_BROADCAST?SIOCGIFBRDADDR:SIOCGIFDSTADDR),ifconf.ifc_req+i)) {
223                         close(sock);
224                         FAIL("ioctl(udp,\"%s\",SIOCGIF{DST/BRD}ADDR) error: %m",ifconf.ifc_req[i].ifr_name);
225                         }
226
227                 sinp =(struct sockaddr_in *)&ifconf.ifc_req[i].ifr_broadaddr;
228                 sinmp=(struct sockaddr_in *)&ifconf.ifc_req[i].ifr_netmask  ;
229                 if (sinp->sin_family!=AF_INET || sinmp->sin_family!=AF_INET) continue;
230                 broads[j]=*sinp;
231                 broads[j].sin_port=UDP_BASEPORT; //FIXME: No possibility to override from cmdline
232                 broadmasks[j]=*sinmp;
233                 j++;
234                 }
235         broadnum=j;
236         masksnum=j;
237         return(0);
238 }
239
240 #define addreq(a,b) ((a).sin_port==(b).sin_port&&(a).sin_addr.s_addr==(b).sin_addr.s_addr)
241
242 /* Previous function addiflist() can (and probably will) report multiple
243  * same addresses. On some Linux boxes is present both device "eth0" and
244  * "dummy0" with the same IP addreesses - we'll filter it here.
245  */
246
247 static void unifyiflist(void)
248 {
249 int d=0,s,i;
250
251         for (s=0;s<broadnum;s++) {
252                 for (i=0;i<s;i++)
253                         if (addreq(broads[s],broads[i])) break;
254                 if (i>=s) broads[d++]=broads[s];
255                 }
256         broadnum=d;
257 }
258
259 static unsigned char qhbuf[6];
260
261 /* Parse PORTSHIFT numeric parameter
262  */
263
264 static void portshift(const char *cs)
265 {
266 long port;
267 unsigned short ports=0;
268
269         port=atol(cs);
270         if (port<-PORTSHIFT_TOLERANCE || port>+PORTSHIFT_TOLERANCE)
271                 msg("Invalid portshift in \"%s\", tolerance is +/-%d",cs,PORTSHIFT_TOLERANCE);
272         else ports=htons(port);
273         memcpy(qhbuf+4,&ports,2);
274 }
275
276 /* Do hostname resolve on name "buf" and return the address in buffer "qhbuf".
277  */
278 static unsigned char *queryhost(char *buf)
279 {
280 struct hostent *he;
281 char *s;
282 char c=0;
283
284         if ((s=strrchr(buf,':'))) {
285           c=*s;
286           *s='\0';
287           portshift(s+1);
288           }
289         else memset(qhbuf+4,0,2);
290         he=gethostbyname((char *)buf);
291         if (s) *s=c;
292         if (!he) {
293                 msg("Error resolving my hostname \"%s\"",buf);
294                 return(NULL);
295                 }
296         if (he->h_addrtype!=AF_INET || he->h_length!=4) {
297           msg("Error parsing resolved my hostname \"%s\"",buf);
298                 return(NULL);
299                 }
300         if (!*he->h_addr_list) {
301           msg("My resolved hostname \"%s\" address list empty",buf);
302                 return(NULL);
303                 }
304         memcpy(qhbuf,(*he->h_addr_list),4);
305         return(qhbuf);
306 }
307
308 /* Dump raw form of IP address/port by fancy output to user
309  */
310 static void dumpraddr(unsigned char *a)
311 {
312 short port;
313         printf("[%u.%u.%u.%u]",a[0],a[1],a[2],a[3]);
314         port=(signed short)ntohs(*(unsigned short *)(a+4));
315         if (port) printf(":%+d",port);
316 }
317
318 /* Like dumpraddr() but for structure "sockaddr_in"
319  */
320
321 static void dumpaddr(struct sockaddr_in *sin)
322 {
323 unsigned short ports;
324
325         memcpy(qhbuf+0,&sin->sin_addr,4);
326         ports=htons(((short)ntohs(sin->sin_port))-UDP_BASEPORT);
327         memcpy(qhbuf+4,&ports,2);
328         dumpraddr(qhbuf);
329 }
330
331 /* Startup... Uninteresting parsing...
332  */
333
334 static int ipx_udp_GetMyAddress(void) {
335
336 char buf[256];
337 int i;
338 char *s,*s2,*ns;
339
340         if (!have_empty_address())
341                 return 0;
342
343         if (!((i=FindArg("-udp")) && (s=Args[i+1]) && (*s=='=' || *s=='+' || *s=='@'))) s=NULL;
344
345         if (gethostname(buf,sizeof(buf))) FAIL("Error getting my hostname");
346         if (!(queryhost(buf))) FAIL("Querying my own hostname \"%s\"",buf);
347
348         if (s) while (*s=='@') {
349                 portshift(++s);
350                 while (isdigit(*s)) s++;
351                 }
352
353         memset(ipx_MyAddress+0,0,4);
354         memcpy(ipx_MyAddress+4,qhbuf,6);
355         baseport+=(short)ntohs(*(unsigned short *)(qhbuf+4));
356
357         if (!s || (s && !*s)) addiflist();
358         else {
359                 if (*s=='+') addiflist();
360                 s++;
361                 for (;;) {
362 struct sockaddr_in *sin;
363                         while (isspace(*s)) s++;
364                         if (!*s) break;
365                         for (s2=s;*s2 && *s2!=',';s2++);
366                         chk(ns=malloc(s2-s+1));
367                         memcpy(ns,s,s2-s);
368                         ns[s2-s]='\0';
369                         if (!queryhost(ns)) msg("Ignored broadcast-destination \"%s\" as being invalid",ns);
370                         free(ns);
371                         chkbroadsize();
372                         sin=broads+(broadnum++);
373                         sin->sin_family=AF_INET;
374                         memcpy(&sin->sin_addr,qhbuf+0,4);
375                         sin->sin_port=htons(((short)ntohs(*(unsigned short *)(qhbuf+4)))+UDP_BASEPORT);
376                         s=s2+(*s2==',');
377                         }
378                 }
379
380         unifyiflist();
381
382         printf(MSGHDR "Using TCP/IP address ");
383         dumpraddr(ipx_MyAddress+4);
384         putchar('\n');
385         if (broadnum) {
386                 printf(MSGHDR "Using %u broadcast-dest%s:",broadnum,(broadnum==1?"":"s"));
387                 for (i=0;i<broadnum;i++) {
388                         putchar(' ');
389                         dumpaddr(broads+i);
390                         }
391                 putchar('\n');
392                 }
393
394         return 0;
395 }
396
397 /* We should probably avoid such insanity here as during PgUP/PgDOWN
398  * (virtual port number change) we wastefully destroy/create the same
399  * socket here (binded always to "baseport"). FIXME.
400  * "open_sockets" can be only 0 or 1 anyway.
401  */
402
403 static int ipx_udp_OpenSocket(ipx_socket_t *sk, int port) {
404 struct sockaddr_in sin;
405
406         if (!open_sockets)
407                 if (have_empty_address())
408                 if (ipx_udp_GetMyAddress() < 0) FAIL("Error getting my address");
409
410         msg("OpenSocket on D1X socket port %d",port);
411
412         if (!port)
413                 port = dynamic_socket++;
414
415         if ((sk->fd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)) < 0) {
416                 sk->fd = -1;
417                 FAIL("socket() creation failed on port %d: %m",port);
418                 }
419   if (setsockopt(sk->fd,SOL_SOCKET,SO_BROADCAST,&val_one,sizeof(val_one))) {
420                 if (close(sk->fd)) msg("close() failed during error recovery: %m");
421                 sk->fd=-1;
422                 FAIL("setsockopt(SO_BROADCAST) failed: %m");
423                 }
424         sin.sin_family=AF_INET;
425         sin.sin_addr.s_addr=htonl(INADDR_ANY);
426         sin.sin_port=htons(baseport);
427         if (bind(sk->fd,(struct sockaddr *)&sin,sizeof(sin))) {
428                 if (close(sk->fd)) msg("close() failed during error recovery: %m");
429                 sk->fd=-1;
430                 FAIL("bind() to UDP port %d failed: %m",baseport);
431                 }
432
433         open_sockets++;
434         sk->socket = port;
435         return 0;
436 }
437
438 /* The same comment as in previous "ipx_udp_OpenSocket"...
439  */
440
441 static void ipx_udp_CloseSocket(ipx_socket_t *mysock) {
442         if (!open_sockets) {
443                 msg("close w/o open");
444                 return;
445         }
446         msg("CloseSocket on D1X socket port %d",mysock->socket);
447         if (close(mysock->fd))
448                 msg("close() failed on CloseSocket D1X socket port %d: %m",mysock->socket);
449         mysock->fd=-1;
450         if (--open_sockets) {
451                 msg("(closesocket) %d sockets left", open_sockets);
452                 return;
453         }
454 }
455
456 /* Here we'll send the packet to our host. If it is unicast packet, send
457  * it to IP address/port as retrieved from IPX address. Otherwise (broadcast)
458  * we'll repeat the same data to each host in our broadcasting list.
459  */
460
461 static int ipx_udp_SendPacket(ipx_socket_t *mysock, IPXPacket_t *IPXHeader,
462  u_char *data, int dataLen) {
463         struct sockaddr_in toaddr,*dest;
464         int i=dataLen;
465         int bcast;
466         char *buf;
467
468 #ifdef UDPDEBUG
469         msg("SendPacket enter, dataLen=%d",dataLen);
470 #endif
471         if (dataLen<0 || dataLen>MAX_PACKETSIZE) return -1;
472         chk(buf=alloca(8+dataLen));
473         if (compatibility) memcpy(buf+0,D1Xudp,6),buf+=6;
474         else               memcpy(buf+0,D1Xid ,2),buf+=2;
475         memcpy(buf+0,IPXHeader->Destination.Socket,2);
476         memcpy(buf+2,data,dataLen);
477  
478         toaddr.sin_family=AF_INET;
479         memcpy(&toaddr.sin_addr,IPXHeader->Destination.Node+0,4);
480         toaddr.sin_port=htons(((short)ntohs(*(unsigned short *)(IPXHeader->Destination.Node+4)))+UDP_BASEPORT);
481
482         for (bcast=(toaddr.sin_addr.s_addr==htonl(INADDR_BROADCAST)?0:-1);bcast<broadnum;bcast++) {
483                 if (bcast>=0) dest=broads+bcast;
484                 else dest=&toaddr;
485         
486 #ifdef UDPDEBUG
487                 printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,",
488                         dataLen,
489                         IPXHeader->Destination.Node  [4],IPXHeader->Destination.Node  [5],
490                         IPXHeader->Destination.Socket[0],IPXHeader->Destination.Socket[1],
491                         ntohs(dest->sin_port));
492                 dumpaddr(dest);
493                 puts(").");
494 #endif
495                 i=sendto(mysock->fd,buf-(compatibility?6:2),(compatibility?8:4)+dataLen,
496                         0,(struct sockaddr *)dest,sizeof(*dest));
497                 if (bcast==-1) return (i<8?-1:i-8);
498                 }
499         return(dataLen);
500 }
501
502 /* Here we will receive new packet to the given buffer. Both formats of packets
503  * are supported, we fallback to old format when first obsolete packet is seen.
504  * If the (valid) packet is received from unknown host, we will add it to our
505  * broadcasting list. FIXME: For now such autoconfigured hosts are NEVER removed.
506  */
507
508 static int ipx_udp_ReceivePacket(ipx_socket_t *s, char *outbuf, int outbufsize, 
509  struct ipx_recv_data *rd) {
510         int size;
511         struct sockaddr_in fromaddr;
512         int fromaddrsize=sizeof(fromaddr);
513         unsigned short ports;
514         size_t offs;
515         int i;
516
517         if ((size=recvfrom(s->fd,outbuf,outbufsize,0,(struct sockaddr *)&fromaddr,&fromaddrsize))<0)
518                 return -1;
519 #ifdef UDPDEBUG
520         printf(MSGHDR "recvfrom((%d-8=%d),",size,size-8);
521         dumpaddr(&fromaddr);
522         puts(").");
523 #endif
524         if (fromaddr.sin_family!=AF_INET) return -1;
525         if (size<4) return -1;
526         if (memcmp(outbuf+0,D1Xid,2)) {
527                 if (size<8 || memcmp(outbuf+0,D1Xudp,6)) return -1;
528                 if (!compatibility) {
529                         compatibility=1;
530                         fputs(MSGHDR "Received obsolete packet from ",stdout);
531                         dumpaddr(&fromaddr);
532                         puts(", upgrade that machine.\n" MSGHDR "Turning on compatibility mode...");
533                         }
534                 offs=6;
535                 }
536         else offs=2;
537
538         /* Lace: (dst_socket & src_socket) should be network-byte-order by comment in include/ipx_drv.h */
539         /*       This behaviour presented here is broken. It is not used anywhere, so why bother? */
540         rd->src_socket = ntohs(*(unsigned short *)(outbuf+offs));
541         if (rd->src_socket != s->socket) {
542 #ifdef UDPDEBUG
543                 msg(" - pkt was dropped (dst=%d,my=%d)",rd->src_socket,s->socket);
544 #endif
545                 return -1;
546                 }
547         rd->dst_socket = s->socket;
548
549         for (i=0;i<broadnum;i++) {
550                 if (i>=masksnum) {
551                         if (addreq(fromaddr,broads[i])) break; 
552                         }
553                 else {
554                         if (fromaddr.sin_port==broads[i].sin_port
555                         &&( fromaddr.sin_addr.s_addr & broadmasks[i].sin_addr.s_addr)
556                         ==(broads[i].sin_addr.s_addr & broadmasks[i].sin_addr.s_addr)) break;
557                         }
558                 }
559         if (i>=broadnum) {
560                 chkbroadsize();
561                 broads[broadnum++]=fromaddr;
562                 fputs(MSGHDR "Adding host ",stdout);
563                 dumpaddr(&fromaddr);
564                 puts(" to broadcasting address list");
565                 }
566
567         memmove(outbuf,outbuf+offs+2,size-(offs+2));
568         size-=offs+2;
569
570         memcpy(rd->src_node+0,&fromaddr.sin_addr,4);
571         ports=htons(ntohs(fromaddr.sin_port)-UDP_BASEPORT);
572         memcpy(rd->src_node+4,&ports,2);
573         memset(rd->src_network, 0, 4);
574         rd->pkt_type = 0;
575 #ifdef UDPDEBUG
576         printf(MSGHDR "ReceivePacket: size=%d,from=",size);
577         dumpraddr(rd->src_node);
578         putchar('\n');
579 #endif
580
581         return size;
582 }
583
584 struct ipx_driver ipx_udp = {
585         ipx_udp_GetMyAddress,
586         ipx_udp_OpenSocket,
587         ipx_udp_CloseSocket,
588         ipx_udp_SendPacket,
589         ipx_udp_ReceivePacket,
590         ipx_general_PacketReady
591 };
592 #endif // NETWORK
593 #endif // __ENV_LINUX__