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