1 /* 2 Copyright (C) 1996-1997 Id Software, Inc. 3 4 This program is free software; you can redistribute it and/or 5 modify it under the terms of the GNU General Public License 6 as published by the Free Software Foundation; either version 2 7 of the License, or (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 13 See the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 19 */ 20 // net_ipx.c 21 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <dpmi.h> 25 26 #include "quakedef.h" 27 #include "dosisms.h" 28 #include "net_ipx.h" 29 30 #define EIO 5 /* I/O error */ 31 32 #define AF_NETWARE 64 33 34 #define IPX_OPEN 0 35 #define IPX_CLOSE 1 36 #define IPX_GETROUTE 2 37 #define IPX_SEND 3 38 #define IPX_LISTEN 4 39 #define IPX_SCHEDULEEVENT 5 40 #define IPX_CANCEL 6 41 #define IPX_SCHEDULESPECIALEVENT 7 42 #define IPX_GETINTERVALMARKER 8 43 #define IPX_GETADDRESS 9 44 #define IPX_RELINQUISH 10 45 46 #define PTYPE_UNKNOWN 0 47 #define PTYPE_RIP 1 48 #define PTYPE_ECHO 2 49 #define PTYPE_ERROR 3 50 #define PTYPE_IPX 4 51 #define PTYPE_SPX 5 52 53 #pragma pack(1) 54 55 typedef struct 56 { 57 byte network[4]; 58 byte node[6]; 59 short socket; 60 } IPXaddr; 61 62 struct sockaddr_ipx 63 { 64 short sipx_family; 65 IPXaddr sipx_addr; 66 char sipx_zero[2]; 67 }; 68 #define sipx_port sipx_addr.socket 69 70 typedef struct 71 { 72 short checkSum; 73 short length; 74 byte transportControl; 75 byte type; 76 IPXaddr destination; 77 IPXaddr source; 78 } IPXheader; 79 80 typedef struct ECBStructure 81 { 82 struct ECBStructure *link; 83 unsigned short ESR_off; 84 unsigned short ESR_seg; 85 byte inUse; 86 byte completionCode; 87 short socket; 88 byte IPXWorkspace[4]; 89 byte driverWorkspace[12]; 90 byte immediateAddress[6]; 91 short fragCount; 92 short fragOff; 93 short fragSeg; 94 short fragSize; 95 } ECB; 96 97 #pragma pack() 98 99 typedef struct 100 { 101 ECB ecb; 102 IPXheader header; 103 int sequence; 104 char data[NET_DATAGRAMSIZE]; 105 } ipx_lowmem_buffer_t; 106 107 #define LOWMEMSIZE (100 * 1024) 108 #define LOWMEMSAVE 256 109 #define IPXBUFFERS ((LOWMEMSIZE - LOWMEMSAVE)/ sizeof(ipx_lowmem_buffer_t)) 110 #define IPXSOCKBUFFERS 5 111 #define IPXSOCKETS (IPXBUFFERS / IPXSOCKBUFFERS) 112 113 // each socket's socketbuffer 0 is used for sending, the others for listening 114 115 typedef struct 116 { 117 char reserved[LOWMEMSAVE]; 118 ipx_lowmem_buffer_t socketbuffer[IPXSOCKETS][IPXSOCKBUFFERS]; 119 } ipx_lowmem_area_t; 120 121 122 static int ipxsocket[IPXSOCKETS]; 123 static ECB *readlist[IPXSOCKETS]; 124 static int sequence[IPXSOCKETS]; 125 static int handlesInUse; 126 static ipx_lowmem_area_t *lma; 127 static char *lowmem_buffer; 128 static int lowmem_bufseg; 129 static int lowmem_bufoff; 130 static unsigned short ipx_cs; 131 static unsigned short ipx_ip; 132 static int net_acceptsocket = -1; 133 static int net_controlsocket; 134 135 static void IPX_PollProcedure(void); 136 static PollProcedure pollProcedure = {NULL, 0.0, IPX_PollProcedure}; 137 138 //============================================================================= 139 140 static void IPX_GetLocalAddress(IPXaddr *addr) 141 { 142 regs.x.cs = ipx_cs; 143 regs.x.ip = ipx_ip; 144 regs.x.bx = IPX_GETADDRESS; 145 regs.x.es = lowmem_bufseg; 146 regs.x.si = lowmem_bufoff; 147 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 148 Q_memcpy(addr, lowmem_buffer, 10); 149 } 150 151 //============================================================================= 152 153 static int IPX_GetLocalTarget(IPXaddr *addr, byte *localTarget) 154 { 155 regs.x.cs = ipx_cs; 156 regs.x.ip = ipx_ip; 157 regs.x.bx = IPX_GETROUTE; 158 regs.x.es = lowmem_bufseg; 159 regs.x.si = lowmem_bufoff; 160 regs.x.di = lowmem_bufoff + sizeof(IPXaddr); 161 Q_memcpy(lowmem_buffer, addr, sizeof(IPXaddr)); 162 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 163 if (regs.h.al) 164 return -1; 165 Q_memcpy(localTarget, lowmem_buffer + sizeof(IPXaddr), 6); 166 return 0; 167 } 168 169 //============================================================================= 170 171 static void IPX_ListenForPacket(ECB *ecb) 172 { 173 regs.x.cs = ipx_cs; 174 regs.x.ip = ipx_ip; 175 regs.x.bx = IPX_LISTEN; 176 regs.x.es = ptr2real(ecb) >> 4; 177 regs.x.si = ptr2real(ecb) & 0xf; 178 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 179 } 180 181 //============================================================================= 182 183 static void IPX_RelinquishControl(void) 184 { 185 regs.x.cs = ipx_cs; 186 regs.x.ip = ipx_ip; 187 regs.x.bx = IPX_RELINQUISH; 188 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 189 } 190 191 192 void IPX_PollProcedure(void) 193 { 194 IPX_RelinquishControl(); 195 SchedulePollProcedure(&pollProcedure, 0.01); 196 } 197 198 //============================================================================= 199 200 static void ProcessReadyList(int s) 201 { 202 int n; 203 ECB *ecb; 204 ECB *prev; 205 206 for (n = 1; n < IPXSOCKBUFFERS; n++) 207 { 208 if (lma->socketbuffer[s][n].ecb.inUse == 0) 209 { 210 for (ecb = readlist[s], prev = NULL; ecb; ecb = ecb->link) 211 { 212 if (lma->socketbuffer[s][n].sequence < ((ipx_lowmem_buffer_t *) ecb)->sequence) 213 break; 214 prev = ecb; 215 } 216 if (ecb) 217 lma->socketbuffer[s][n].ecb.link = ecb; 218 else 219 lma->socketbuffer[s][n].ecb.link = NULL; 220 if (prev) 221 prev->link = &lma->socketbuffer[s][n].ecb; 222 else 223 readlist[s] = &lma->socketbuffer[s][n].ecb; 224 lma->socketbuffer[s][n].ecb.inUse = 0xff; 225 } 226 } 227 } 228 229 //============================================================================= 230 231 int IPX_Init(void) 232 { 233 int s; 234 int n; 235 struct qsockaddr addr; 236 char *colon; 237 238 if (COM_CheckParm ("-noipx")) 239 return -1; 240 241 // find the IPX far call entry point 242 regs.x.ax = 0x7a00; 243 __dpmi_simulate_real_mode_interrupt (0x2f, (__dpmi_regs *)®s); 244 if (regs.h.al != 0xff) 245 { 246 Con_Printf("IPX not detected\n"); 247 return -1; 248 } 249 ipx_cs = regs.x.es; 250 ipx_ip = regs.x.di; 251 252 // grab a chunk of memory down in DOS land 253 lowmem_buffer = dos_getmemory(LOWMEMSIZE); 254 if (!lowmem_buffer) 255 { 256 Con_Printf("IPX_Init: Not enough low memory\n"); 257 return -1; 258 } 259 lowmem_bufoff = ptr2real(lowmem_buffer) & 0xf; 260 lowmem_bufseg = ptr2real(lowmem_buffer) >> 4; 261 262 // init socket handles & buffers 263 handlesInUse = 0; 264 lma = (ipx_lowmem_area_t *)lowmem_buffer; 265 for (s = 0; s < IPXSOCKETS; s++) 266 { 267 ipxsocket[s] = 0; 268 for (n = 0; n < IPXSOCKBUFFERS; n++) 269 { 270 lma->socketbuffer[s][n].ecb.link = NULL; 271 lma->socketbuffer[s][n].ecb.ESR_off = 0; 272 lma->socketbuffer[s][n].ecb.ESR_seg = 0; 273 lma->socketbuffer[s][n].ecb.socket = 0; 274 lma->socketbuffer[s][n].ecb.inUse = 0xff; 275 lma->socketbuffer[s][n].ecb.completionCode = 0; 276 lma->socketbuffer[s][n].ecb.fragCount = 1; 277 lma->socketbuffer[s][n].ecb.fragOff = ptr2real(&lma->socketbuffer[s][n].header) & 0xf; 278 lma->socketbuffer[s][n].ecb.fragSeg = ptr2real(&lma->socketbuffer[s][n].header) >> 4; 279 lma->socketbuffer[s][n].ecb.fragSize = sizeof(IPXheader) + sizeof(int) + NET_DATAGRAMSIZE; 280 } 281 } 282 283 if ((net_controlsocket = IPX_OpenSocket (0)) == -1) 284 { 285 dos_freememory(lowmem_buffer); 286 Con_DPrintf ("IPX_Init: Unable to open control socket\n"); 287 return -1; 288 } 289 290 SchedulePollProcedure(&pollProcedure, 0.01); 291 292 IPX_GetSocketAddr (net_controlsocket, &addr); 293 Q_strcpy(my_ipx_address, IPX_AddrToString (&addr)); 294 colon = Q_strrchr (my_ipx_address, ':'); 295 if (colon) 296 *colon = 0; 297 298 Con_Printf("IPX initialized\n"); 299 ipxAvailable = true; 300 return net_controlsocket; 301 } 302 303 //============================================================================= 304 305 void IPX_Shutdown(void) 306 { 307 IPX_Listen (false); 308 IPX_CloseSocket (net_controlsocket); 309 dos_freememory(lowmem_buffer); 310 } 311 312 //============================================================================= 313 314 void IPX_Listen (qboolean state) 315 { 316 // enable listening 317 if (state) 318 { 319 if (net_acceptsocket != -1) 320 return; 321 if ((net_acceptsocket = IPX_OpenSocket (net_hostport)) == -1) 322 Sys_Error ("IPX_Listen: Unable to open accept socket\n"); 323 return; 324 } 325 326 // disable listening 327 if (net_acceptsocket == -1) 328 return; 329 IPX_CloseSocket (net_acceptsocket); 330 net_acceptsocket = -1; 331 } 332 333 //============================================================================= 334 335 int IPX_OpenSocket(int port) 336 { 337 int handle; 338 int n; 339 unsigned short socket; 340 341 if (handlesInUse == IPXSOCKETS) 342 return -1; 343 344 // open the IPX socket 345 regs.x.cs = ipx_cs; 346 regs.x.ip = ipx_ip; 347 regs.x.bx = IPX_OPEN; 348 regs.h.al = 0; 349 regs.x.dx = htons(port); 350 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 351 if (regs.h.al == 0xfe) 352 { 353 Con_DPrintf("IPX_OpenSocket: all sockets in use\n"); 354 return -1; 355 } 356 if (regs.h.al == 0xff) 357 { 358 Con_DPrintf("IPX_OpenSocket: socket already open\n"); 359 return -1; 360 } 361 if (regs.h.al != 0) 362 { 363 Con_DPrintf("IPX_OpenSocket: error %02x\n", regs.h.al); 364 return -1; 365 } 366 socket = regs.x.dx; 367 368 // grab a handle; fill in the ECBs, and get them listening 369 for (handle = 0; handle < IPXSOCKETS; handle++) 370 { 371 if (ipxsocket[handle] == 0) 372 { 373 ipxsocket[handle] = socket; 374 readlist[handle] = NULL; 375 sequence[handle] = 0; 376 for (n = 0; n < IPXSOCKBUFFERS; n ++) 377 { 378 lma->socketbuffer[handle][n].ecb.socket = socket; 379 lma->socketbuffer[handle][n].ecb.inUse = 0; 380 if (n) 381 IPX_ListenForPacket(&lma->socketbuffer[handle][n].ecb); 382 } 383 handlesInUse++; 384 return handle; 385 } 386 } 387 388 // "this will NEVER happen" 389 Sys_Error("IPX_OpenSocket: handle allocation failed\n"); 390 return -1; 391 } 392 393 //============================================================================= 394 395 int IPX_CloseSocket(int handle) 396 { 397 // if there's a send in progress, give it one last chance 398 if (lma->socketbuffer[handle][0].ecb.inUse != 0) 399 IPX_RelinquishControl(); 400 401 // close the socket (all pending sends/received are cancelled) 402 regs.x.cs = ipx_cs; 403 regs.x.ip = ipx_ip; 404 regs.x.bx = IPX_CLOSE; 405 regs.x.dx = ipxsocket[handle]; 406 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 407 408 ipxsocket[handle] = 0; 409 handlesInUse--; 410 411 return 0; 412 } 413 414 //============================================================================= 415 416 int IPX_Connect (int handle, struct qsockaddr *addr) 417 { 418 IPXaddr ipxaddr; 419 420 Q_memcpy(&ipxaddr, &((struct sockaddr_ipx *)addr)->sipx_addr, sizeof(IPXaddr)); 421 if (IPX_GetLocalTarget(&ipxaddr, lma->socketbuffer[handle][0].ecb.immediateAddress) != 0) 422 { 423 Con_Printf("Get Local Target failed\n"); 424 return -1; 425 } 426 427 return 0; 428 } 429 430 //============================================================================= 431 432 int IPX_CheckNewConnections (void) 433 { 434 int n; 435 436 if (net_acceptsocket == -1) 437 return -1; 438 439 for (n = 1; n < IPXSOCKBUFFERS; n ++) 440 if (lma->socketbuffer[net_acceptsocket][n].ecb.inUse == 0) 441 return net_acceptsocket; 442 return -1; 443 } 444 445 //============================================================================= 446 447 int IPX_Read (int handle, byte *buf, int len, struct qsockaddr *addr) 448 { 449 ECB *ecb; 450 ipx_lowmem_buffer_t *rcvbuf; 451 int copylen; 452 453 ProcessReadyList(handle); 454 tryagain: 455 if (readlist[handle] == NULL) 456 return 0; 457 ecb = readlist[handle]; 458 readlist[handle] = ecb->link; 459 460 if (ecb->completionCode != 0) 461 { 462 Con_Printf("Warning: IPX_Read error %02x\n", ecb->completionCode); 463 ecb->fragSize = sizeof(IPXheader) + sizeof(int) + NET_DATAGRAMSIZE; 464 IPX_ListenForPacket(ecb); 465 goto tryagain; 466 } 467 468 rcvbuf = (ipx_lowmem_buffer_t *)ecb; 469 470 // copy the data up to the buffer 471 copylen = ntohs(rcvbuf->header.length) - (sizeof(int) + sizeof(IPXheader)); 472 if (len < copylen) 473 Sys_Error("IPX_Read: buffer too small (%d vs %d)\n", len, copylen); 474 Q_memcpy(buf, rcvbuf->data, copylen); 475 476 // fill in the addr if they want it 477 if (addr) 478 { 479 ((struct sockaddr_ipx *)addr)->sipx_family = AF_NETWARE; 480 Q_memcpy(&((struct sockaddr_ipx *)addr)->sipx_addr, rcvbuf->header.source.network, sizeof(IPXaddr)); 481 ((struct sockaddr_ipx *)addr)->sipx_zero[0] = 0; 482 ((struct sockaddr_ipx *)addr)->sipx_zero[1] = 0; 483 } 484 485 // update the send ecb's immediate address 486 Q_memcpy(lma->socketbuffer[handle][0].ecb.immediateAddress, rcvbuf->ecb.immediateAddress, 6); 487 488 // get this ecb listening again 489 rcvbuf->ecb.fragSize = sizeof(IPXheader) + sizeof(int) + NET_DATAGRAMSIZE; 490 IPX_ListenForPacket(&rcvbuf->ecb); 491 return copylen; 492 } 493 494 //============================================================================= 495 496 int IPX_Broadcast (int handle, byte *buf, int len) 497 { 498 struct sockaddr_ipx addr; 499 int ret; 500 501 Q_memset(addr.sipx_addr.network, 0x00, 4); 502 Q_memset(addr.sipx_addr.node, 0xff, 6); 503 addr.sipx_port = htons(net_hostport); 504 Q_memset(lma->socketbuffer[handle][0].ecb.immediateAddress, 0xff, 6); 505 ret = IPX_Write (handle, buf, len, (struct qsockaddr *)&addr); 506 return ret; 507 } 508 509 //============================================================================= 510 511 int IPX_Write (int handle, byte *buf, int len, struct qsockaddr *addr) 512 { 513 // has the previous send completed? 514 while (lma->socketbuffer[handle][0].ecb.inUse != 0) 515 IPX_RelinquishControl(); 516 517 switch (lma->socketbuffer[handle][0].ecb.completionCode) 518 { 519 case 0x00: // success 520 case 0xfc: // request cancelled 521 break; 522 523 case 0xfd: // malformed packet 524 default: 525 Con_Printf("IPX driver send failure: %02x\n", lma->socketbuffer[handle][0].ecb.completionCode); 526 break; 527 528 case 0xfe: // packet undeliverable 529 case 0xff: // unable to send packet 530 Con_Printf("IPX lost route, trying to re-establish\n"); 531 532 // look for a new route 533 if (IPX_GetLocalTarget (&lma->socketbuffer[handle][0].header.destination, lma->socketbuffer[handle][0].ecb.immediateAddress) != 0) 534 return -1; 535 536 // re-send the one that failed 537 regs.x.cs = ipx_cs; 538 regs.x.ip = ipx_ip; 539 regs.x.bx = IPX_SEND; 540 regs.x.es = ptr2real(&lma->socketbuffer[handle][0].ecb) >> 4; 541 regs.x.si = ptr2real(&lma->socketbuffer[handle][0].ecb) & 0xf; 542 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 543 544 // report that we did not send the current one 545 return 0; 546 } 547 548 // ecb : length 549 lma->socketbuffer[handle][0].ecb.fragSize = sizeof(IPXheader) + sizeof(int) + len; 550 551 // ipx header : type 552 lma->socketbuffer[handle][0].header.type = PTYPE_IPX; 553 554 // ipx header : destination 555 Q_memcpy(&lma->socketbuffer[handle][0].header.destination, &((struct sockaddr_ipx *)addr)->sipx_addr, sizeof(IPXaddr)); 556 557 // sequence number 558 lma->socketbuffer[handle][0].sequence = sequence[handle]; 559 sequence[handle]++; 560 561 // copy down the data 562 Q_memcpy(lma->socketbuffer[handle][0].data, buf, len); 563 564 regs.x.cs = ipx_cs; 565 regs.x.ip = ipx_ip; 566 regs.x.bx = IPX_SEND; 567 regs.x.es = ptr2real(&lma->socketbuffer[handle][0].ecb) >> 4; 568 regs.x.si = ptr2real(&lma->socketbuffer[handle][0].ecb) & 0xf; 569 __dpmi_simulate_real_mode_procedure_retf((__dpmi_regs *)®s); 570 571 return len; 572 } 573 574 //============================================================================= 575 576 char *IPX_AddrToString (struct qsockaddr *addr) 577 { 578 static char buf[28]; 579 580 sprintf(buf, "%02x%02x%02x%02x:%02x%02x%02x%02x%02x%02x:%u", 581 ((struct sockaddr_ipx *)addr)->sipx_addr.network[0], 582 ((struct sockaddr_ipx *)addr)->sipx_addr.network[1], 583 ((struct sockaddr_ipx *)addr)->sipx_addr.network[2], 584 ((struct sockaddr_ipx *)addr)->sipx_addr.network[3], 585 ((struct sockaddr_ipx *)addr)->sipx_addr.node[0], 586 ((struct sockaddr_ipx *)addr)->sipx_addr.node[1], 587 ((struct sockaddr_ipx *)addr)->sipx_addr.node[2], 588 ((struct sockaddr_ipx *)addr)->sipx_addr.node[3], 589 ((struct sockaddr_ipx *)addr)->sipx_addr.node[4], 590 ((struct sockaddr_ipx *)addr)->sipx_addr.node[5], 591 ntohs(((struct sockaddr_ipx *)addr)->sipx_port) 592 ); 593 return buf; 594 } 595 596 //============================================================================= 597 598 int IPX_StringToAddr (char *string, struct qsockaddr *addr) 599 { 600 int val; 601 char buf[3]; 602 603 buf[2] = 0; 604 Q_memset(addr, 0, sizeof(struct qsockaddr)); 605 addr->sa_family = AF_NETWARE; 606 607 #define DO(src,dest) \ 608 buf[0] = string[src]; \ 609 buf[1] = string[src + 1]; \ 610 if (sscanf (buf, "%x", &val) != 1) \ 611 return -1; \ 612 ((struct sockaddr_ipx *)addr)->sipx_addr.dest = val 613 614 DO(0, network[0]); 615 DO(2, network[1]); 616 DO(4, network[2]); 617 DO(6, network[3]); 618 DO(9, node[0]); 619 DO(11, node[1]); 620 DO(13, node[2]); 621 DO(15, node[3]); 622 DO(17, node[4]); 623 DO(19, node[5]); 624 #undef DO 625 626 sscanf (&string[22], "%u", &val); 627 ((struct sockaddr_ipx *)addr)->sipx_port = htons(val); 628 629 return 0; 630 } 631 632 //============================================================================= 633 634 int IPX_GetSocketAddr (int handle, struct qsockaddr *addr) 635 { 636 Q_memset(addr, 0, sizeof(struct qsockaddr)); 637 addr->sa_family = AF_NETWARE; 638 IPX_GetLocalAddress(&((struct sockaddr_ipx *)addr)->sipx_addr); 639 ((struct sockaddr_ipx *)addr)->sipx_port = ipxsocket[handle]; 640 return 0; 641 } 642 643 //============================================================================= 644 645 int IPX_GetNameFromAddr (struct qsockaddr *addr, char *name) 646 { 647 Q_strcpy(name, IPX_AddrToString(addr)); 648 return 0; 649 } 650 651 //============================================================================= 652 653 int IPX_GetAddrFromName (char *name, struct qsockaddr *addr) 654 { 655 int n; 656 char buf[32]; 657 658 n = Q_strlen(name); 659 660 if (n == 12) 661 { 662 sprintf(buf, "00000000:%s:%u", name, net_hostport); 663 return IPX_StringToAddr (buf, addr); 664 } 665 if (n == 21) 666 { 667 sprintf(buf, "%s:%u", name, net_hostport); 668 return IPX_StringToAddr (buf, addr); 669 } 670 if (n > 21 && n <= 27) 671 return IPX_StringToAddr (name, addr); 672 673 return -1; 674 } 675 676 //============================================================================= 677 678 int IPX_AddrCompare (struct qsockaddr *addr1, struct qsockaddr *addr2) 679 { 680 if (addr1->sa_family != addr2->sa_family) 681 return -1; 682 683 if(Q_memcmp(&((struct sockaddr_ipx *)addr1)->sipx_addr, &((struct sockaddr_ipx *)addr2)->sipx_addr, 10)) 684 return -1; 685 686 if (((struct sockaddr_ipx *)addr1)->sipx_port != ((struct sockaddr_ipx *)addr2)->sipx_port) 687 return 1; 688 689 return 0; 690 } 691 692 //============================================================================= 693 694 int IPX_GetSocketPort (struct qsockaddr *addr) 695 { 696 return ntohs(((struct sockaddr_ipx *)addr)->sipx_port); 697 } 698 699 700 int IPX_SetSocketPort (struct qsockaddr *addr, int port) 701 { 702 ((struct sockaddr_ipx *)addr)->sipx_port = htons(port); 703 return 0; 704 } 705 706 //============================================================================= 707