332 lines
8.7 KiB
C
332 lines
8.7 KiB
C
/* $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 <conf.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
|
|
#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
|
|
};
|