/* net_ipx.c @description@ Copyright (C) 1996-1997 Id Software, Inc. 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 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif static __attribute__ ((unused)) const char rcsid[] = "$Id$"; #include #include #include #include "dosisms.h" #include "net_ipx.h" #define EIO 5 /* I/O error */ #define AF_NETWARE 64 #define IPX_OPEN 0 #define IPX_CLOSE 1 #define IPX_GETROUTE 2 #define IPX_SEND 3 #define IPX_LISTEN 4 #define IPX_SCHEDULEEVENT 5 #define IPX_CANCEL 6 #define IPX_SCHEDULESPECIALEVENT 7 #define IPX_GETINTERVALMARKER 8 #define IPX_GETADDRESS 9 #define IPX_RELINQUISH 10 #define PTYPE_UNKNOWN 0 #define PTYPE_RIP 1 #define PTYPE_ECHO 2 #define PTYPE_ERROR 3 #define PTYPE_IPX 4 #define PTYPE_SPX 5 #pragma pack(1) typedef struct { byte network[4]; byte node[6]; short socket; } IPXaddr; struct sockaddr_ipx { short sipx_family; IPXaddr sipx_addr; char sipx_zero[2]; }; #define sipx_port sipx_addr.socket typedef struct { short checkSum; short length; byte transportControl; byte type; IPXaddr destination; IPXaddr source; } IPXheader; typedef struct ECBStructure { struct ECBStructure *link; unsigned short ESR_off; unsigned short ESR_seg; byte inUse; byte completionCode; short socket; byte IPXWorkspace[4]; byte driverWorkspace[12]; byte immediateAddress[6]; short fragCount; short fragOff; short fragSeg; short fragSize; } ECB; #pragma pack() typedef struct { ECB ecb; IPXheader header; int sequence; char data[NET_DATAGRAMSIZE]; } ipx_lowmem_buffer_t; #define LOWMEMSIZE (100 * 1024) #define LOWMEMSAVE 256 #define IPXBUFFERS ((LOWMEMSIZE - LOWMEMSAVE)/ sizeof(ipx_lowmem_buffer_t)) #define IPXSOCKBUFFERS 5 #define IPXSOCKETS (IPXBUFFERS / IPXSOCKBUFFERS) // each socket's socketbuffer 0 is used for sending, the others for listening typedef struct { char reserved[LOWMEMSAVE]; ipx_lowmem_buffer_t socketbuffer[IPXSOCKETS][IPXSOCKBUFFERS]; } ipx_lowmem_area_t; static int ipxsocket[IPXSOCKETS]; static ECB *readlist[IPXSOCKETS]; static int sequence[IPXSOCKETS]; static int handlesInUse; static ipx_lowmem_area_t *lma; static char *lowmem_buffer; static int lowmem_bufseg; static int lowmem_bufoff; static unsigned short ipx_cs; static unsigned short ipx_ip; static int net_acceptsocket = -1; static int net_controlsocket; static void IPX_PollProcedure (void); static PollProcedure pollProcedure = { NULL, 0.0, IPX_PollProcedure }; //============================================================================= static void IPX_GetLocalAddress (IPXaddr * addr) { regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_GETADDRESS; regs.x.es = lowmem_bufseg; regs.x.si = lowmem_bufoff; __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); Q_memcpy (addr, lowmem_buffer, 10); } //============================================================================= static int IPX_GetLocalTarget (IPXaddr * addr, byte * localTarget) { regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_GETROUTE; regs.x.es = lowmem_bufseg; regs.x.si = lowmem_bufoff; regs.x.di = lowmem_bufoff + sizeof (IPXaddr); Q_memcpy (lowmem_buffer, addr, sizeof (IPXaddr)); __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); if (regs.h.al) return -1; Q_memcpy (localTarget, lowmem_buffer + sizeof (IPXaddr), 6); return 0; } //============================================================================= static void IPX_ListenForPacket (ECB * ecb) { regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_LISTEN; regs.x.es = ptr2real (ecb) >> 4; regs.x.si = ptr2real (ecb) & 0xf; __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); } //============================================================================= static void IPX_RelinquishControl (void) { regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_RELINQUISH; __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); } void IPX_PollProcedure (void) { IPX_RelinquishControl (); SchedulePollProcedure (&pollProcedure, 0.01); } //============================================================================= static void ProcessReadyList (int s) { int n; ECB *ecb; ECB *prev; for (n = 1; n < IPXSOCKBUFFERS; n++) { if (lma->socketbuffer[s][n].ecb.inUse == 0) { for (ecb = readlist[s], prev = NULL; ecb; ecb = ecb->link) { if (lma->socketbuffer[s][n].sequence < ((ipx_lowmem_buffer_t *) ecb)->sequence) break; prev = ecb; } if (ecb) lma->socketbuffer[s][n].ecb.link = ecb; else lma->socketbuffer[s][n].ecb.link = NULL; if (prev) prev->link = &lma->socketbuffer[s][n].ecb; else readlist[s] = &lma->socketbuffer[s][n].ecb; lma->socketbuffer[s][n].ecb.inUse = 0xff; } } } //============================================================================= int IPX_Init (void) { int s; int n; struct qsockaddr addr; char *colon; if (COM_CheckParm ("-noipx")) return -1; // find the IPX far call entry point regs.x.ax = 0x7a00; __dpmi_simulate_real_mode_interrupt (0x2f, (__dpmi_regs *) & regs); if (regs.h.al != 0xff) { Con_Printf ("IPX not detected\n"); return -1; } ipx_cs = regs.x.es; ipx_ip = regs.x.di; // grab a chunk of memory down in DOS land lowmem_buffer = dos_getmemory (LOWMEMSIZE); if (!lowmem_buffer) { Con_Printf ("IPX_Init: Not enough low memory\n"); return -1; } lowmem_bufoff = ptr2real (lowmem_buffer) & 0xf; lowmem_bufseg = ptr2real (lowmem_buffer) >> 4; // init socket handles & buffers handlesInUse = 0; lma = (ipx_lowmem_area_t *) lowmem_buffer; for (s = 0; s < IPXSOCKETS; s++) { ipxsocket[s] = 0; for (n = 0; n < IPXSOCKBUFFERS; n++) { lma->socketbuffer[s][n].ecb.link = NULL; lma->socketbuffer[s][n].ecb.ESR_off = 0; lma->socketbuffer[s][n].ecb.ESR_seg = 0; lma->socketbuffer[s][n].ecb.socket = 0; lma->socketbuffer[s][n].ecb.inUse = 0xff; lma->socketbuffer[s][n].ecb.completionCode = 0; lma->socketbuffer[s][n].ecb.fragCount = 1; lma->socketbuffer[s][n].ecb.fragOff = ptr2real (&lma->socketbuffer[s][n].header) & 0xf; lma->socketbuffer[s][n].ecb.fragSeg = ptr2real (&lma->socketbuffer[s][n].header) >> 4; lma->socketbuffer[s][n].ecb.fragSize = sizeof (IPXheader) + sizeof (int) + NET_DATAGRAMSIZE; } } if ((net_controlsocket = IPX_OpenSocket (0)) == -1) { dos_freememory (lowmem_buffer); Con_DPrintf ("IPX_Init: Unable to open control socket\n"); return -1; } SchedulePollProcedure (&pollProcedure, 0.01); IPX_GetSocketAddr (net_controlsocket, &addr); Q_strcpy (my_ipx_address, IPX_AddrToString (&addr)); colon = Q_strrchr (my_ipx_address, ':'); if (colon) *colon = 0; Con_Printf ("IPX initialized\n"); ipxAvailable = true; return net_controlsocket; } //============================================================================= void IPX_Shutdown (void) { IPX_Listen (false); IPX_CloseSocket (net_controlsocket); dos_freememory (lowmem_buffer); } //============================================================================= void IPX_Listen (qboolean state) { // enable listening if (state) { if (net_acceptsocket != -1) return; if ((net_acceptsocket = IPX_OpenSocket (net_hostport)) == -1) Sys_Error ("IPX_Listen: Unable to open accept socket"); return; } // disable listening if (net_acceptsocket == -1) return; IPX_CloseSocket (net_acceptsocket); net_acceptsocket = -1; } //============================================================================= int IPX_OpenSocket (int port) { int handle; int n; unsigned short socket; if (handlesInUse == IPXSOCKETS) return -1; // open the IPX socket regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_OPEN; regs.h.al = 0; regs.x.dx = htons (port); __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); if (regs.h.al == 0xfe) { Con_DPrintf ("IPX_OpenSocket: all sockets in use\n"); return -1; } if (regs.h.al == 0xff) { Con_DPrintf ("IPX_OpenSocket: socket already open\n"); return -1; } if (regs.h.al != 0) { Con_DPrintf ("IPX_OpenSocket: error %02x\n", regs.h.al); return -1; } socket = regs.x.dx; // grab a handle; fill in the ECBs, and get them listening for (handle = 0; handle < IPXSOCKETS; handle++) { if (ipxsocket[handle] == 0) { ipxsocket[handle] = socket; readlist[handle] = NULL; sequence[handle] = 0; for (n = 0; n < IPXSOCKBUFFERS; n++) { lma->socketbuffer[handle][n].ecb.socket = socket; lma->socketbuffer[handle][n].ecb.inUse = 0; if (n) IPX_ListenForPacket (&lma->socketbuffer[handle][n].ecb); } handlesInUse++; return handle; } } // "this will NEVER happen" Sys_Error ("IPX_OpenSocket: handle allocation failed"); return -1; } //============================================================================= int IPX_CloseSocket (int handle) { // if there's a send in progress, give it one last chance if (lma->socketbuffer[handle][0].ecb.inUse != 0) IPX_RelinquishControl (); // close the socket (all pending sends/received are cancelled) regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_CLOSE; regs.x.dx = ipxsocket[handle]; __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); ipxsocket[handle] = 0; handlesInUse--; return 0; } //============================================================================= int IPX_Connect (int handle, struct qsockaddr *addr) { IPXaddr ipxaddr; Q_memcpy (&ipxaddr, &((struct sockaddr_ipx *) addr)->sipx_addr, sizeof (IPXaddr)); if (IPX_GetLocalTarget (&ipxaddr, lma->socketbuffer[handle][0].ecb.immediateAddress) != 0) { Con_Printf ("Get Local Target failed\n"); return -1; } return 0; } //============================================================================= int IPX_CheckNewConnections (void) { int n; if (net_acceptsocket == -1) return -1; for (n = 1; n < IPXSOCKBUFFERS; n++) if (lma->socketbuffer[net_acceptsocket][n].ecb.inUse == 0) return net_acceptsocket; return -1; } //============================================================================= int IPX_Read (int handle, byte * buf, int len, struct qsockaddr *addr) { ECB *ecb; ipx_lowmem_buffer_t *rcvbuf; int copylen; ProcessReadyList (handle); tryagain: if (readlist[handle] == NULL) return 0; ecb = readlist[handle]; readlist[handle] = ecb->link; if (ecb->completionCode != 0) { Con_Printf ("Warning: IPX_Read error %02x\n", ecb->completionCode); ecb->fragSize = sizeof (IPXheader) + sizeof (int) + NET_DATAGRAMSIZE; IPX_ListenForPacket (ecb); goto tryagain; } rcvbuf = (ipx_lowmem_buffer_t *) ecb; // copy the data up to the buffer copylen = ntohs (rcvbuf->header.length) - (sizeof (int) + sizeof (IPXheader)); if (len < copylen) Sys_Error ("IPX_Read: buffer too small (%d vs %d)", len, copylen); Q_memcpy (buf, rcvbuf->data, copylen); // fill in the addr if they want it if (addr) { ((struct sockaddr_ipx *) addr)->sipx_family = AF_NETWARE; Q_memcpy (&((struct sockaddr_ipx *) addr)->sipx_addr, rcvbuf->header.source.network, sizeof (IPXaddr)); ((struct sockaddr_ipx *) addr)->sipx_zero[0] = 0; ((struct sockaddr_ipx *) addr)->sipx_zero[1] = 0; } // update the send ecb's immediate address Q_memcpy (lma->socketbuffer[handle][0].ecb.immediateAddress, rcvbuf->ecb.immediateAddress, 6); // get this ecb listening again rcvbuf->ecb.fragSize = sizeof (IPXheader) + sizeof (int) + NET_DATAGRAMSIZE; IPX_ListenForPacket (&rcvbuf->ecb); return copylen; } //============================================================================= int IPX_Broadcast (int handle, byte * buf, int len) { struct sockaddr_ipx addr; int ret; Q_memset (addr.sipx_addr.network, 0x00, 4); Q_memset (addr.sipx_addr.node, 0xff, 6); addr.sipx_port = htons (net_hostport); Q_memset (lma->socketbuffer[handle][0].ecb.immediateAddress, 0xff, 6); ret = IPX_Write (handle, buf, len, (struct qsockaddr *) &addr); return ret; } //============================================================================= int IPX_Write (int handle, byte * buf, int len, struct qsockaddr *addr) { // has the previous send completed? while (lma->socketbuffer[handle][0].ecb.inUse != 0) IPX_RelinquishControl (); switch (lma->socketbuffer[handle][0].ecb.completionCode) { case 0x00: // success case 0xfc: // request cancelled break; case 0xfd: // malformed packet default: Con_Printf ("IPX driver send failure: %02x\n", lma->socketbuffer[handle][0].ecb.completionCode); break; case 0xfe: // packet undeliverable case 0xff: // unable to send packet Con_Printf ("IPX lost route, trying to re-establish\n"); // look for a new route if (IPX_GetLocalTarget (&lma->socketbuffer[handle][0].header.destination, lma->socketbuffer[handle][0].ecb.immediateAddress) != 0) return -1; // re-send the one that failed regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_SEND; regs.x.es = ptr2real (&lma->socketbuffer[handle][0].ecb) >> 4; regs.x.si = ptr2real (&lma->socketbuffer[handle][0].ecb) & 0xf; __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); // report that we did not send the current one return 0; } // ecb : length lma->socketbuffer[handle][0].ecb.fragSize = sizeof (IPXheader) + sizeof (int) + len; // ipx header : type lma->socketbuffer[handle][0].header.type = PTYPE_IPX; // ipx header : destination Q_memcpy (&lma->socketbuffer[handle][0].header.destination, &((struct sockaddr_ipx *) addr)->sipx_addr, sizeof (IPXaddr)); // sequence number lma->socketbuffer[handle][0].sequence = sequence[handle]; sequence[handle]++; // copy down the data Q_memcpy (lma->socketbuffer[handle][0].data, buf, len); regs.x.cs = ipx_cs; regs.x.ip = ipx_ip; regs.x.bx = IPX_SEND; regs.x.es = ptr2real (&lma->socketbuffer[handle][0].ecb) >> 4; regs.x.si = ptr2real (&lma->socketbuffer[handle][0].ecb) & 0xf; __dpmi_simulate_real_mode_procedure_retf ((__dpmi_regs *) & regs); return len; } //============================================================================= char * IPX_AddrToString (struct qsockaddr *addr) { static char buf[28]; snprintf (buf, sizeof (buf), "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", ((struct sockaddr_ipx *) addr)->sipx_addr.network[0], ((struct sockaddr_ipx *) addr)->sipx_addr.network[1], ((struct sockaddr_ipx *) addr)->sipx_addr.network[2], ((struct sockaddr_ipx *) addr)->sipx_addr.network[3], ((struct sockaddr_ipx *) addr)->sipx_addr.node[0], ((struct sockaddr_ipx *) addr)->sipx_addr.node[1], ((struct sockaddr_ipx *) addr)->sipx_addr.node[2], ((struct sockaddr_ipx *) addr)->sipx_addr.node[3], ((struct sockaddr_ipx *) addr)->sipx_addr.node[4], ((struct sockaddr_ipx *) addr)->sipx_addr.node[5], ntohs (((struct sockaddr_ipx *) addr)->sipx_port) ); return buf; } //============================================================================= int IPX_StringToAddr (char *string, struct qsockaddr *addr) { int val; char buf[3]; buf[2] = 0; Q_memset (addr, 0, sizeof (struct qsockaddr)); addr->sa_family = AF_NETWARE; #define DO(src,dest) \ buf[0] = string[src]; \ buf[1] = string[src + 1]; \ if (sscanf (buf, "%x", &val) != 1) \ return -1; \ ((struct sockaddr_ipx *)addr)->sipx_addr.dest = val DO (0, network[0]); DO (2, network[1]); DO (4, network[2]); DO (6, network[3]); DO (9, node[0]); DO (11, node[1]); DO (13, node[2]); DO (15, node[3]); DO (17, node[4]); DO (19, node[5]); #undef DO sscanf (&string[22], "%u", &val); ((struct sockaddr_ipx *) addr)->sipx_port = htons (val); return 0; } //============================================================================= int IPX_GetSocketAddr (int handle, struct qsockaddr *addr) { Q_memset (addr, 0, sizeof (struct qsockaddr)); addr->sa_family = AF_NETWARE; IPX_GetLocalAddress (&((struct sockaddr_ipx *) addr)->sipx_addr); ((struct sockaddr_ipx *) addr)->sipx_port = ipxsocket[handle]; return 0; } //============================================================================= int IPX_GetNameFromAddr (struct qsockaddr *addr, char *name) { Q_strcpy (name, IPX_AddrToString (addr)); return 0; } //============================================================================= int IPX_GetAddrFromName (char *name, struct qsockaddr *addr) { int n; char buf[32]; n = Q_strlen (name); if (n == 12) { snprintf (buf, sizeof (buf), "00000000:%s:%u", name, net_hostport); return IPX_StringToAddr (buf, addr); } if (n == 21) { snprintf (buf, sizeof (buf), "%s:%u", name, net_hostport); return IPX_StringToAddr (buf, addr); } if (n > 21 && n <= 27) return IPX_StringToAddr (name, addr); return -1; } //============================================================================= int IPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) { if (addr1->sa_family != addr2->sa_family) return -1; if (Q_memcmp (&((struct sockaddr_ipx *) addr1)->sipx_addr, &((struct sockaddr_ipx *) addr2)->sipx_addr, 10)) return -1; if (((struct sockaddr_ipx *) addr1)->sipx_port != ((struct sockaddr_ipx *) addr2)->sipx_port) return 1; return 0; } //============================================================================= int IPX_GetSocketPort (struct qsockaddr *addr) { return ntohs (((struct sockaddr_ipx *) addr)->sipx_port); } int IPX_SetSocketPort (struct qsockaddr *addr, int port) { ((struct sockaddr_ipx *) addr)->sipx_port = htons (port); return 0; } //=============================================================================