From 922c3542fe607f53fad1fdd3a8456a5634b7a591 Mon Sep 17 00:00:00 2001 From: Bradley Bell Date: Sun, 12 Oct 2003 09:17:47 +0000 Subject: [PATCH] add IPv4 multicasting support --- ChangeLog | 12 ++ arch/linux/Makefile.am | 4 +- arch/linux/include/ipx_drv.h | 6 +- arch/linux/include/ipx_mcast4.h | 15 ++ arch/linux/ipx_bsd.c | 8 +- arch/linux/ipx_kali.c | 8 +- arch/linux/ipx_mcast4.c | 331 +++++++++++++++++++++++++++++++ arch/linux/ipx_udp.c | 10 +- arch/linux/linuxnet.c | 60 +++++- arch/win32/Makefile.am | 4 +- arch/win32/include/ipx_mcast4.h | 15 ++ arch/win32/ipx_drv.h | 6 +- arch/win32/ipx_mcast4.c | 332 ++++++++++++++++++++++++++++++++ arch/win32/ipx_udp.c | 10 +- arch/win32/ipx_win.c | 8 +- arch/win32/winnet.c | 54 +++++- include/ipx.h | 12 +- main/menu.c | 9 +- main/multi.h | 7 +- 19 files changed, 885 insertions(+), 26 deletions(-) create mode 100644 arch/linux/include/ipx_mcast4.h create mode 100644 arch/linux/ipx_mcast4.c create mode 100644 arch/win32/include/ipx_mcast4.h create mode 100644 arch/win32/ipx_mcast4.c diff --git a/ChangeLog b/ChangeLog index fca1c1af..266aac15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2003-10-12 Aaron Plattner + + * arch/linux/Makefile.am, arch/linux/ipx_bsd.c, + arch/linux/ipx_kali.c, arch/linux/ipx_mcast4.c, + arch/linux/ipx_udp.c, arch/linux/linuxnet.c, + arch/linux/include/ipx_drv.h, arch/linux/include/ipx_mcast4.h, + arch/win32/Makefile.am, arch/win32/ipx_drv.h, + arch/win32/ipx_mcast4.c, arch/win32/ipx_udp.c, + arch/win32/ipx_win.c, arch/win32/winnet.c, + arch/win32/include/ipx_mcast4.h, include/ipx.h, main/menu.c, + main/multi.h: add IPv4 multicasting support + 2003-10-11 Bradley Bell * main/endlevel.c, main/piggy.c: fixed endlevel stuff diff --git a/arch/linux/Makefile.am b/arch/linux/Makefile.am index ac2f20db..f35cca3a 100644 --- a/arch/linux/Makefile.am +++ b/arch/linux/Makefile.am @@ -5,7 +5,7 @@ noinst_LIBRARIES = libarch_linux.a INCLUDES = -I$(top_srcdir)/arch/include -I$(top_srcdir)/include -I$(top_srcdir)/main -I$(srcdir)/include if USE_NETWORK -NETWORK_SRCS = ipx_kali.c ipx_udp.c linuxnet.c ukali.c +NETWORK_SRCS = ipx_kali.c ipx_udp.c linuxnet.c ukali.c ipx_mcast4.c if USE_NATIVE_IPX IPX_SRCS = ipx_bsd.c endif @@ -18,7 +18,7 @@ endif libarch_linux_a_SOURCES = ${NETWORK_SRCS} ${IPX_SRCS} ${JOYSTICK_SRCS} findfile.c init.c EXTRA_libarch_linux_a_SOURCES = \ -ipx_bsd.c ipx_kali.c ipx_udp.c linuxnet.c ukali.c \ +ipx_bsd.c ipx_kali.c ipx_mcast4.c ipx_udp.c linuxnet.c ukali.c \ joydefs.c joystick.c EXTRA_DIST = ${EXTRA_SUBDIRS} \ diff --git a/arch/linux/include/ipx_drv.h b/arch/linux/include/ipx_drv.h index 44b5799b..3aaeed3a 100644 --- a/arch/linux/include/ipx_drv.h +++ b/arch/linux/include/ipx_drv.h @@ -1,4 +1,4 @@ -/* $Id: ipx_drv.h,v 1.4 2003-03-13 00:20:21 btb Exp $ */ +/* $Id: ipx_drv.h,v 1.5 2003-10-12 09:17:47 btb Exp $ */ /* * * IPX driver interface @@ -63,6 +63,10 @@ struct ipx_driver { int (*ReceivePacket)(ipx_socket_t *s, char *buffer, int bufsize, struct ipx_recv_data *rec); int (*PacketReady)(ipx_socket_t *s); + void (*InitNetgameAuxData)(ipx_socket_t *s, u_char buf[]); + int (*HandleNetgameAuxData)(ipx_socket_t *s, const u_char buf[]); + void (*HandleLeaveGame)(ipx_socket_t *s); + int (*SendGamePacket)(ipx_socket_t *s, u_char *data, int dataLen); }; int ipx_general_PacketReady(ipx_socket_t *s); diff --git a/arch/linux/include/ipx_mcast4.h b/arch/linux/include/ipx_mcast4.h new file mode 100644 index 00000000..8452f917 --- /dev/null +++ b/arch/linux/include/ipx_mcast4.h @@ -0,0 +1,15 @@ +/* $Id: ipx_mcast4.h,v 1.1 2003-10-12 09:17:47 btb Exp $ */ +/* + * + * FIXME: add description + * + */ + +#ifndef _IPX_MCAST4_H +#define _IPX_MCAST4_H +#include "ipx_drv.h" + +extern struct ipx_driver ipx_mcast4; +#define IPX_MCAST4_VERSION 0 + +#endif diff --git a/arch/linux/ipx_bsd.c b/arch/linux/ipx_bsd.c index 38a9fdf8..0c091d7d 100644 --- a/arch/linux/ipx_bsd.c +++ b/arch/linux/ipx_bsd.c @@ -1,4 +1,4 @@ -/* $Id: ipx_bsd.c,v 1.6 2003-03-13 00:20:21 btb Exp $ */ +/* $Id: ipx_bsd.c,v 1.7 2003-10-12 09:17:47 btb Exp $ */ /* * * IPX driver using BSD style sockets @@ -229,5 +229,9 @@ struct ipx_driver ipx_bsd = { ipx_bsd_CloseSocket, ipx_bsd_SendPacket, ipx_bsd_ReceivePacket, - ipx_general_PacketReady + ipx_general_PacketReady, + NULL, // InitNetgameAuxData + NULL, // HandleNetgameAuxData + NULL, // HandleLeaveGame + NULL // SendGamePacket }; diff --git a/arch/linux/ipx_kali.c b/arch/linux/ipx_kali.c index a9cd7668..d85b2879 100644 --- a/arch/linux/ipx_kali.c +++ b/arch/linux/ipx_kali.c @@ -1,4 +1,4 @@ -/* $Id: ipx_kali.c,v 1.5 2003-03-13 00:20:21 btb Exp $ */ +/* $Id: ipx_kali.c,v 1.6 2003-10-12 09:17:47 btb Exp $ */ /* * * IPX driver for KaliNix interface @@ -123,5 +123,9 @@ struct ipx_driver ipx_kali = { ipx_kali_CloseSocket, ipx_kali_SendPacket, ipx_kali_ReceivePacket, - ipx_general_PacketReady + ipx_general_PacketReady, + NULL, // InitNetgameAuxData + NULL, // HandleNetgameAuxData + NULL, // HandleLeaveGame + NULL // SendGamePacket }; diff --git a/arch/linux/ipx_mcast4.c b/arch/linux/ipx_mcast4.c new file mode 100644 index 00000000..92938250 --- /dev/null +++ b/arch/linux/ipx_mcast4.c @@ -0,0 +1,331 @@ +/* $Id: ipx_mcast4.c,v 1.1 2003-10-12 09:17:47 btb Exp $ */ + +/* + * + * "ipx driver" for IPv4 multicasting + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pstypes.h" +#include "ipx_mcast4.h" +#include "args.h" +#include "error.h" +#include "newmenu.h" +#include "../../main/multi.h" + +//#define IPX_MCAST4_DEBUG + +extern unsigned char ipx_MyAddress[10]; + +#define UDP_BASEPORT 28342 +#define PORTSHIFT_TOLERANCE 0x100 +#define MAX_PACKETSIZE 8192 + +/* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value) + */ +static int baseport=UDP_BASEPORT; + +static struct in_addr game_addr; // The game's multicast address + +#define MSGHDR "IPX_mcast4: " + +#ifdef IPX_MCAST4_DEBUG +static void msg(const char *fmt, ...) +{ + va_list ap; + + fputs(MSGHDR, stdout); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); +} +#else +#define msg(m...) +#endif + +#define FAIL(m...) do{ nm_messagebox("Error", 1, "Ok", ##m); return -1; } while (0) + +#ifdef IPX_MCAST4_DEBUG +/* Dump raw form of IP address/port by fancy output to user + */ +static void dumpraddr(unsigned char *a) +{ + short port; + + printf("[%u.%u.%u.%u]", a[0], a[1], a[2], a[3]); + port=(signed short)ntohs(*(unsigned short *)(a+4)); + if (port) printf(":%+d",port); +} + +/* Like dumpraddr() but for structure "sockaddr_in" + */ +static void dumpaddr(struct sockaddr_in *sin) +{ + unsigned short ports; + unsigned char qhbuf[8]; + + memcpy(qhbuf + 0, &sin->sin_addr, 4); + ports = htons(((short)ntohs(sin->sin_port)) - UDP_BASEPORT); + memcpy(qhbuf + 4, &ports, 2); + dumpraddr(qhbuf); +} +#endif + +// The multicast address for Descent 2 game announcements. +// TODO: Pick a better address for this +#define DESCENT2_ANNOUNCE_ADDR inet_addr("239.255.1.2") + +/* Open the socket and subscribe to the multicast session */ +static int ipx_mcast4_OpenSocket(ipx_socket_t *sk, int port) +{ + u_char loop; + struct ip_mreq mreq; + struct sockaddr_in sin; + int ttl = 128; + + if((sk->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + { + sk->fd = -1; + FAIL("socket() creation failed on port %d: %m", port); + } + + // Bind to the port + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(baseport); + if (bind(sk->fd, (struct sockaddr *)&sin, sizeof(sin))) + { + if (close(sk->fd)) + msg("close() failed during error recovery: %m"); + sk->fd = -1; + FAIL("bind() to UDP port %d failed: %m", baseport); + } + + // Set the TTL so the packets can get out of the local network. + if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) + FAIL("setsockopt() failed to set TTL to 128"); + + // Disable multicast loopback + loop = 0; + if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) + FAIL("setsockopt() failed to disable multicast loopback: %m"); + + // Subscribe to the game announcement address + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = DESCENT2_ANNOUNCE_ADDR; + mreq.imr_interface.s_addr = INADDR_ANY; + if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + FAIL("setsockopt() failed to subscribe to the game announcement multicast group"); + + // We're not subscribed to a game address yet + game_addr.s_addr = 0; + + sk->socket = port; + return 0; +} + +static void ipx_mcast4_CloseSocket(ipx_socket_t *sk) +{ + if(close(sk->fd) < 0) + msg("Close failed"); + sk->fd = -1; +} + +static int ipx_mcast4_SendPacket(ipx_socket_t *sk, IPXPacket_t *IPXHeader, u_char *data, int dataLen) +{ + struct sockaddr_in toaddr; + int i; + + msg("SendPacket enter, dataLen=%d", dataLen); + + if(dataLen < 0 || dataLen > MAX_PACKETSIZE) + return -1; + + toaddr.sin_family = AF_INET; + memcpy(&toaddr.sin_addr, IPXHeader->Destination.Node + 0, 4); + //toaddr.sin_port = htons(((short)ntohs(*(unsigned short *)(IPXHeader->Destination.Node + 4))) + UDP_BASEPORT); + // For now, just use the same port for everything + toaddr.sin_port = htons(UDP_BASEPORT); + + // If it's the broadcast address, then we want to send it to the + // GAME ANNOUNCEMENT address. + // Data to be sent to the GAME has the destination already set by + // ipx_mcast4_SendGamePacket + if(toaddr.sin_addr.s_addr == INADDR_BROADCAST) + toaddr.sin_addr.s_addr = DESCENT2_ANNOUNCE_ADDR; + +#ifdef IPX_MCAST4_DEBUG + printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,", + dataLen, + IPXHeader->Destination.Node[4], IPXHeader->Destination.Node[5], + IPXHeader->Destination.Socket[0], IPXHeader->Destination.Socket[1], + ntohs(toaddr.sin_port)); + dumpaddr(&toaddr); + puts(")."); +#endif + + i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr)); + return i; +} + +static int ipx_mcast4_ReceivePacket(ipx_socket_t *sk, char *outbuf, int outbufsize, struct ipx_recv_data *rd) +{ + int size; + struct sockaddr_in fromaddr; + int fromaddrsize = sizeof(fromaddr); + + if((size = recvfrom(sk->fd, outbuf, outbufsize, 0, (struct sockaddr*)&fromaddr, &fromaddrsize)) < 0) + return -1; + +#ifdef IPX_MCAST4_DEBUG + printf(MSGHDR "Got packet from "); + dumpaddr(&fromaddr); + puts(""); +#endif + + // We have the packet, now fill out the receive data. + memset(rd, 0, sizeof(*rd)); + memcpy(rd->src_node, &fromaddr.sin_addr, 4); + // TODO: Include the port like in ipx_udp.c + rd->pkt_type = 0; + + return size; +} + +/* Handle the netgame aux data + * Byte 0 is the protocol version number. + * Bytes 1-4 are the IPv4 multicast session to join, in network byte order. + */ +static int ipx_mcast4_HandleNetgameAuxData(ipx_socket_t *sk, const u_char buf[NETGAME_AUX_SIZE]) +{ + // Extract the multicast session and subscribe to it. We should + // now be getting packets intended for the players of this game. + + // Note that we stay subscribed to the game announcement session, + // so we can reply to game info requests + struct ip_mreq mreq; + int ttl = 128; + + // Check the protocol version + if(buf[0] != IPX_MCAST4_VERSION) + { + FAIL("mcast4 protocol\nversion mismatch!\nGame version is %02x,\nour version is %02x", buf[0], IPX_MCAST4_VERSION); + } + + // Get the multicast session + memcpy(&game_addr, buf + 1, sizeof(game_addr)); + +#ifdef IPX_MCAST4_DEBUG + { + struct sockaddr_in tmpaddr; + tmpaddr.sin_addr = game_addr; + tmpaddr.sin_port = 0; + + printf("Handling netgame aux data: Subscribing to "); + dumpaddr(&tmpaddr); + puts(""); + } +#endif + + // Set the TTL so the packets can get out of the local network. + if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) + FAIL("setsockopt() failed to set TTL to 128"); + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr = game_addr; + mreq.imr_interface.s_addr = INADDR_ANY; + + if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + FAIL("setsockopt() failed to subscribe to the game group"); + + return 0; +} + +/* Create the netgame aux data. + * Byte 0 is the protcol version number. + * Bytes 1-4 hold the IPv4 multicast session for the game. + */ +static void ipx_mcast4_InitNetgameAuxData(ipx_socket_t *sk, u_char buf[NETGAME_AUX_SIZE]) +{ + Assert(game_addr.s_addr == 0); + + // The first byte is the version number + buf[0] = IPX_MCAST4_VERSION; + + // Generate a random session + game_addr = inet_makeaddr(239*256 + 255, d_rand() % 0xFFFF); + memcpy(buf + 1, &game_addr, sizeof(game_addr)); + + // Since we're obviously the hosting machine, subscribe to this address + ipx_mcast4_HandleNetgameAuxData(sk, buf); +} + +static void ipx_mcast4_HandleLeaveGame(ipx_socket_t *sk) +{ + // We left the game, so unsubscribe from its multicast session + struct ip_mreq mreq; + + Assert(game_addr.s_addr != 0); + +#ifdef IPX_MCAST4_DEBUG + printf("Unsubscribing from game's multicast group: "); + dumpraddr(&game_addr.s_addr); + printf("\n"); +#endif + + mreq.imr_multiaddr = game_addr; + mreq.imr_interface.s_addr = INADDR_ANY; + if(setsockopt(sk->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + msg("setsockopt() failed unsubscribing from previous group!"); + game_addr.s_addr = 0; +} + +// Send a packet to every member of the game. We can just multicast it here. +static int ipx_mcast4_SendGamePacket(ipx_socket_t *sk, ubyte *data, int dataLen) +{ + struct sockaddr_in toaddr; + int i; + + memset(&toaddr, 0, sizeof(toaddr)); + toaddr.sin_addr = game_addr; + toaddr.sin_port = htons(UDP_BASEPORT); + + msg("ipx_mcast4_SendGamePacket"); + + i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr)); + + return i; +} + +// Pull this in from ipx_udp.c since it's the same for us. +extern int ipx_udp_GetMyAddress(); + +struct ipx_driver ipx_mcast4 = { + ipx_udp_GetMyAddress, + ipx_mcast4_OpenSocket, + ipx_mcast4_CloseSocket, + ipx_mcast4_SendPacket, + ipx_mcast4_ReceivePacket, + ipx_general_PacketReady, + ipx_mcast4_InitNetgameAuxData, + ipx_mcast4_HandleNetgameAuxData, + ipx_mcast4_HandleLeaveGame, + ipx_mcast4_SendGamePacket +}; diff --git a/arch/linux/ipx_udp.c b/arch/linux/ipx_udp.c index 6b234391..c3ffd55b 100644 --- a/arch/linux/ipx_udp.c +++ b/arch/linux/ipx_udp.c @@ -1,4 +1,4 @@ -/* $Id: ipx_udp.c,v 1.7 2003-10-03 07:58:14 btb Exp $ */ +/* $Id: ipx_udp.c,v 1.8 2003-10-12 09:17:47 btb Exp $ */ /* * * IPX driver for native Linux TCP/IP networking (UDP implementation) @@ -353,7 +353,7 @@ unsigned short ports; /* Startup... Uninteresting parsing... */ -static int ipx_udp_GetMyAddress(void) { +int ipx_udp_GetMyAddress(void) { char buf[256]; int i; @@ -610,5 +610,9 @@ struct ipx_driver ipx_udp = { ipx_udp_CloseSocket, ipx_udp_SendPacket, ipx_udp_ReceivePacket, - ipx_general_PacketReady + ipx_general_PacketReady, + NULL, // InitNetgameAuxData + NULL, // HandleNetgameAuxData + NULL, // HandleLeaveGame + NULL // SendGamePacket }; diff --git a/arch/linux/linuxnet.c b/arch/linux/linuxnet.c index 9becc7ab..ace971aa 100644 --- a/arch/linux/linuxnet.c +++ b/arch/linux/linuxnet.c @@ -1,4 +1,4 @@ -/* $Id: linuxnet.c,v 1.11 2003-10-10 09:36:34 btb Exp $ */ +/* $Id: linuxnet.c,v 1.12 2003-10-12 09:17:47 btb Exp $ */ /* THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO @@ -19,6 +19,7 @@ COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. * */ + #ifdef HAVE_CONFIG_H #include #endif @@ -38,10 +39,14 @@ COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. #include "ipx.h" #include "ipx_drv.h" #ifdef NATIVE_IPX -# include "ipx_bsd.h" +# include "ipx_bsd.h" #endif //NATIVE_IPX #include "ipx_kali.h" #include "ipx_udp.h" +#include "ipx_mcast4.h" +#include "error.h" +#include "../../main/player.h" /* for Players */ +#include "../../main/multi.h" /* for NetPlayers */ //added 05/17/99 Matt Mueller - needed to redefine FD_* so that no asm is used //#include "checker.h" //end addition -MM @@ -105,6 +110,7 @@ void arch_ipx_set_driver(int ipx_driver) #endif //NATIVE_IPX case IPX_DRIVER_KALI: driver = &ipx_kali; break; case IPX_DRIVER_UDP: driver = &ipx_udp; break; + case IPX_DRIVER_MCAST4: driver = &ipx_mcast4; break; default: Int3(); } } @@ -174,6 +180,8 @@ void ipx_send_packet_data( ubyte * data, int datasize, ubyte *network, ubyte *ad { u_char buf[MAX_IPX_DATA]; IPXPacket_t ipx_header; + + Assert(datasize <= MAX_IPX_DATA+4); memcpy(ipx_header.Destination.Network, network, 4); memcpy(ipx_header.Destination.Node, immediate_address, 6); @@ -373,3 +381,51 @@ void ipx_read_network_file(char * filename) } fclose(fp); } + +// Initalizes the protocol-specific member of the netgame packet. +void ipx_init_netgame_aux_data(ubyte buf[]) +{ + if(driver->InitNetgameAuxData) + driver->InitNetgameAuxData(&ipx_socket_data, buf); +} + +// Handles the protocol-specific member of the netgame packet. +int ipx_handle_netgame_aux_data(const ubyte buf[]) +{ + if(driver->HandleNetgameAuxData) + return driver->HandleNetgameAuxData(&ipx_socket_data, buf); + return 0; +} + +// Notifies the protocol that we're done with a particular game +void ipx_handle_leave_game() +{ + if(driver->HandleLeaveGame) + driver->HandleLeaveGame(&ipx_socket_data); +} + +// Send a packet to every member of the game. +int ipx_send_game_packet(ubyte *data, int datasize) +{ + if(driver->SendGamePacket) { + u_char buf[MAX_IPX_DATA]; + + *(uint *)buf = ipx_packetnum++; + memcpy(buf + 4, data, datasize); + *(uint *)data = ipx_packetnum++; + return driver->SendGamePacket(&ipx_socket_data, buf, datasize + 4); + } else { + // Loop through all the players unicasting the packet. + int i; + + //printf("Sending game packet: N_players = %i\n", N_players); + + for(i=0; i +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipx_mcast4.h" +#include "args.h" +#include "error.h" +#include "../../main/multi.h" + +//#define IPX_MCAST4_DEBUG + +extern unsigned char ipx_MyAddress[10]; + +#define UDP_BASEPORT 28342 +#define PORTSHIFT_TOLERANCE 0x100 +#define MAX_PACKETSIZE 8192 + +/* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value) + */ +static int baseport=UDP_BASEPORT; + +static struct in_addr game_addr; // The game's multicast address + +#define MSGHDR "IPX_mcast4: " + +#ifdef IPX_MCAST4_DEBUG +static void msg(const char *fmt, ...) +{ + va_list ap; + + fputs(MSGHDR, stdout); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); +} +#else +#define msg(m...) +#endif + +#define FAIL(m...) do{ nm_messagebox("Error", 1, "Ok", ##m); return -1; } while (0) + +#ifdef IPX_MCAST4_DEBUG +/* Dump raw form of IP address/port by fancy output to user + */ +static void dumpraddr(unsigned char *a) +{ + short port; + + printf("[%u.%u.%u.%u]", a[0], a[1], a[2], a[3]); + port=(signed short)ntohs(*(unsigned short *)(a+4)); + if (port) printf(":%+d",port); +} + +/* Like dumpraddr() but for structure "sockaddr_in" + */ +static void dumpaddr(struct sockaddr_in *sin) +{ + unsigned short ports; + unsigned char qhbuf[8]; + + memcpy(qhbuf + 0, &sin->sin_addr, 4); + ports = htons(((short)ntohs(sin->sin_port)) - UDP_BASEPORT); + memcpy(qhbuf + 4, &ports, 2); + dumpraddr(qhbuf); +} +#endif + +// The multicast address for Descent 2 game announcements. +// TODO: Pick a better address for this +#define DESCENT2_ANNOUNCE_ADDR inet_addr("239.255.1.2") + +/* Open the socket and subscribe to the multicast session */ +static int ipx_mcast4_OpenSocket(ipx_socket_t *sk, int port) +{ + u_char loop; + struct ip_mreq mreq; + struct sockaddr_in sin; + int ttl = 128; + + if((sk->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) + { + sk->fd = -1; + FAIL("socket() creation failed on port %d: %m", port); + } + + // Bind to the port + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(INADDR_ANY); + sin.sin_port = htons(baseport); + if (bind(sk->fd, (struct sockaddr *)&sin, sizeof(sin))) + { + if (closesocket(sk->fd)) + msg("close() failed during error recovery: %m"); + sk->fd = -1; + FAIL("bind() to UDP port %d failed: %m", baseport); + } + + // Set the TTL so the packets can get out of the local network. + if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, (const void*)&ttl, sizeof(ttl)) < 0) + FAIL("setsockopt() failed to set TTL to 128"); + + // Disable multicast loopback + loop = 0; + if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void*)&loop, sizeof(loop)) < 0) + FAIL("setsockopt() failed to disable multicast loopback: %m"); + + // Subscribe to the game announcement address + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = DESCENT2_ANNOUNCE_ADDR; + mreq.imr_interface.s_addr = INADDR_ANY; + if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void*)&mreq, sizeof(mreq)) < 0) + FAIL("setsockopt() failed to subscribe to the game announcement multicast group"); + + // We're not subscribed to a game address yet + game_addr.s_addr = 0; + + sk->socket = port; + return 0; +} + +static void ipx_mcast4_CloseSocket(ipx_socket_t *sk) +{ + if(closesocket(sk->fd) < 0) + msg("Close failed"); + sk->fd = -1; +} + +static int ipx_mcast4_SendPacket(ipx_socket_t *sk, IPXPacket_t *IPXHeader, u_char *data, int dataLen) +{ + struct sockaddr_in toaddr; + int i; + + msg("SendPacket enter, dataLen=%d", dataLen); + + if(dataLen < 0 || dataLen > MAX_PACKETSIZE) + return -1; + + toaddr.sin_family = AF_INET; + memcpy(&toaddr.sin_addr, IPXHeader->Destination.Node + 0, 4); + //toaddr.sin_port = htons(((short)ntohs(*(unsigned short *)(IPXHeader->Destination.Node + 4))) + UDP_BASEPORT); + // For now, just use the same port for everything + toaddr.sin_port = htons(UDP_BASEPORT); + + // If it's the broadcast address, then we want to send it to the + // GAME ANNOUNCEMENT address. + // Data to be sent to the GAME has the destination already set by + // ipx_mcast4_SendGamePacket + if(toaddr.sin_addr.s_addr == INADDR_BROADCAST) + toaddr.sin_addr.s_addr = DESCENT2_ANNOUNCE_ADDR; + +#ifdef IPX_MCAST4_DEBUG + printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,", + dataLen, + IPXHeader->Destination.Node[4], IPXHeader->Destination.Node[5], + IPXHeader->Destination.Socket[0], IPXHeader->Destination.Socket[1], + ntohs(toaddr.sin_port)); + dumpaddr(&toaddr); + puts(")."); +#endif + + i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr)); + return i; +} + +static int ipx_mcast4_ReceivePacket(ipx_socket_t *sk, char *outbuf, int outbufsize, struct ipx_recv_data *rd) +{ + int size; + struct sockaddr_in fromaddr; + int fromaddrsize = sizeof(fromaddr); + + if((size = recvfrom(sk->fd, outbuf, outbufsize, 0, (struct sockaddr*)&fromaddr, &fromaddrsize)) < 0) + return -1; + +#ifdef IPX_MCAST4_DEBUG + printf(MSGHDR "Got packet from "); + dumpaddr(&fromaddr); + puts(""); +#endif + + // We have the packet, now fill out the receive data. + memset(rd, 0, sizeof(*rd)); + memcpy(rd->src_node, &fromaddr.sin_addr, 4); + // TODO: Include the port like in ipx_udp.c + rd->pkt_type = 0; + + return size; +} + +/* Handle the netgame aux data + * Byte 0 is the protocol version number. + * Bytes 1-4 are the IPv4 multicast session to join, in network byte order. + */ +static int ipx_mcast4_HandleNetgameAuxData(ipx_socket_t *sk, const u_char buf[NETGAME_AUX_SIZE]) +{ + // Extract the multicast session and subscribe to it. We should + // now be getting packets intended for the players of this game. + + // Note that we stay subscribed to the game announcement session, + // so we can reply to game info requests + struct ip_mreq mreq; + int ttl = 128; + + // Check the protocol version + if(buf[0] != IPX_MCAST4_VERSION) + { + FAIL("mcast4 protocol\nversion mismatch!\nGame version is %02x,\nour version is %02x", buf[0], IPX_MCAST4_VERSION); + } + + // Get the multicast session + memcpy(&game_addr, buf + 1, sizeof(game_addr)); + +#ifdef IPX_MCAST4_DEBUG + { + struct sockaddr_in tmpaddr; + tmpaddr.sin_addr = game_addr; + tmpaddr.sin_port = 0; + + printf("Handling netgame aux data: Subscribing to "); + dumpaddr(&tmpaddr); + puts(""); + } +#endif + + // Set the TTL so the packets can get out of the local network. + if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, (const void*)&ttl, sizeof(ttl)) < 0) + FAIL("setsockopt() failed to set TTL to 128"); + + memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr = game_addr; + mreq.imr_interface.s_addr = INADDR_ANY; + + if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void*)&mreq, sizeof(mreq)) < 0) + FAIL("setsockopt() failed to subscribe to the game group"); + + return 0; +} + +/* Create the netgame aux data. + * Byte 0 is the protcol version number. + * Bytes 1-4 hold the IPv4 multicast session for the game. + */ +static void ipx_mcast4_InitNetgameAuxData(ipx_socket_t *sk, u_char buf[NETGAME_AUX_SIZE]) +{ + char addr[16]; + Assert(game_addr.s_addr == 0); + + // The first byte is the version number + buf[0] = IPX_MCAST4_VERSION; + + // Generate a random session + // game_addr = inet_makeaddr(239*256 + 255, d_rand() % 0xFFFF); + sprintf(addr, "%i.%i.%i.%i", 239, 255, d_rand() % 0xFF, d_rand() % 0xFF); + game_addr.s_addr = inet_addr(addr); + memcpy(buf + 1, &game_addr, sizeof(game_addr)); + + // Since we're obviously the hosting machine, subscribe to this address + ipx_mcast4_HandleNetgameAuxData(sk, buf); +} + +static void ipx_mcast4_HandleLeaveGame(ipx_socket_t *sk) +{ + // We left the game, so unsubscribe from its multicast session + struct ip_mreq mreq; + + Assert(game_addr.s_addr != 0); + +#ifdef IPX_MCAST4_DEBUG + printf("Unsubscribing from game's multicast group: "); + dumpraddr(&game_addr.s_addr); + printf("\n"); +#endif + + mreq.imr_multiaddr = game_addr; + mreq.imr_interface.s_addr = INADDR_ANY; + if(setsockopt(sk->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void*)&mreq, sizeof(mreq)) < 0) + msg("setsockopt() failed unsubscribing from previous group!"); + game_addr.s_addr = 0; +} + +// Send a packet to every member of the game. We can just multicast it here. +static int ipx_mcast4_SendGamePacket(ipx_socket_t *sk, ubyte *data, int dataLen) +{ + struct sockaddr_in toaddr; + int i; + + memset(&toaddr, 0, sizeof(toaddr)); + toaddr.sin_family = AF_INET; + toaddr.sin_addr = game_addr; + toaddr.sin_port = htons(UDP_BASEPORT); + + msg("ipx_mcast4_SendGamePacket"); + + i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr)); + + return i; +} + +// Pull this in from ipx_udp.c since it's the same for us. +extern int ipx_udp_GetMyAddress(); + +struct ipx_driver ipx_mcast4 = { + ipx_udp_GetMyAddress, + ipx_mcast4_OpenSocket, + ipx_mcast4_CloseSocket, + ipx_mcast4_SendPacket, + ipx_mcast4_ReceivePacket, + ipx_general_PacketReady, + ipx_mcast4_InitNetgameAuxData, + ipx_mcast4_HandleNetgameAuxData, + ipx_mcast4_HandleLeaveGame, + ipx_mcast4_SendGamePacket +}; diff --git a/arch/win32/ipx_udp.c b/arch/win32/ipx_udp.c index b2742f70..92bed29f 100644 --- a/arch/win32/ipx_udp.c +++ b/arch/win32/ipx_udp.c @@ -1,4 +1,4 @@ -/* $Id: ipx_udp.c,v 1.2 2003-10-08 19:24:17 btb Exp $ */ +/* $Id: ipx_udp.c,v 1.3 2003-10-12 09:17:47 btb Exp $ */ /* * * IPX driver for native Linux TCP/IP networking (UDP implementation) @@ -357,7 +357,7 @@ unsigned short ports; /* Startup... Uninteresting parsing... */ -static int ipx_udp_GetMyAddress(void) { +int ipx_udp_GetMyAddress(void) { char buf[256]; int i; @@ -614,5 +614,9 @@ struct ipx_driver ipx_udp = { ipx_udp_CloseSocket, ipx_udp_SendPacket, ipx_udp_ReceivePacket, - ipx_general_PacketReady + ipx_general_PacketReady, + NULL, // InitNetgameAuxData + NULL, // HandleNetgameAuxData + NULL, // HandleLeaveGame + NULL // SendGamePacke }; diff --git a/arch/win32/ipx_win.c b/arch/win32/ipx_win.c index 47cda640..b960ac2a 100644 --- a/arch/win32/ipx_win.c +++ b/arch/win32/ipx_win.c @@ -1,4 +1,4 @@ -/* $Id: ipx_win.c,v 1.6 2003-03-19 23:10:34 btb Exp $ */ +/* $Id: ipx_win.c,v 1.7 2003-10-12 09:17:47 btb Exp $ */ /* * @@ -205,5 +205,9 @@ struct ipx_driver ipx_win = { ipx_win_CloseSocket, ipx_win_SendPacket, ipx_win_ReceivePacket, - ipx_general_PacketReady + ipx_general_PacketReady, + NULL, // InitNetgameAuxData + NULL, // HandleNetgameAuxData + NULL, // HandleLeaveGame + NULL // SendGamePack }; diff --git a/arch/win32/winnet.c b/arch/win32/winnet.c index dfcbba8c..e55591d0 100644 --- a/arch/win32/winnet.c +++ b/arch/win32/winnet.c @@ -1,4 +1,4 @@ -/* $Id: winnet.c,v 1.9 2003-10-11 02:36:21 btb Exp $ */ +/* $Id: winnet.c,v 1.10 2003-10-12 09:17:47 btb Exp $ */ /* THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO @@ -33,6 +33,9 @@ COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. #include "ipx.h" #include "ipx_drv.h" #include "ipx_udp.h" +#include "ipx_mcast4.h" +#include "../../main/player.h" /* for Players */ +#include "../../main/multi.h" /* for NetPlayers */ extern struct ipx_driver ipx_win; @@ -90,6 +93,7 @@ void arch_ipx_set_driver(int ipx_driver) switch(ipx_driver) { case IPX_DRIVER_IPX: driver = &ipx_win; break; case IPX_DRIVER_UDP: driver = &ipx_udp; break; + case IPX_DRIVER_MCAST4: driver = &ipx_mcast4; break; default: Int3(); } } @@ -365,3 +369,51 @@ void ipx_read_network_file(char * filename) } fclose(fp); } + +// Initalizes the protocol-specific member of the netgame packet. +void ipx_init_netgame_aux_data(ubyte buf[]) +{ + if(driver->InitNetgameAuxData) + driver->InitNetgameAuxData(&ipx_socket_data, buf); +} + +// Handles the protocol-specific member of the netgame packet. +int ipx_handle_netgame_aux_data(const ubyte buf[]) +{ + if(driver->HandleNetgameAuxData) + return driver->HandleNetgameAuxData(&ipx_socket_data, buf); + return 0; +} + +// Notifies the protocol that we're done with a particular game +void ipx_handle_leave_game() +{ + if(driver->HandleLeaveGame) + driver->HandleLeaveGame(&ipx_socket_data); +} + +// Send a packet to every member of the game. +int ipx_send_game_packet(ubyte *data, int datasize) +{ + if(driver->SendGamePacket) { + u_char buf[MAX_IPX_DATA]; + + *(uint *)buf = ipx_packetnum++; + memcpy(buf + 4, data, datasize); + *(uint *)data = ipx_packetnum++; + return driver->SendGamePacket(&ipx_socket_data, buf, datasize + 4); + } else { + // Loop through all the players unicasting the packet. + int i; + + //printf("Sending game packet: N_players = %i\n", N_players); + + for(i=0; i