add IPv4 multicasting support
[btb/d2x.git] / arch / linux / ipx_mcast4.c
1 /* $Id: ipx_mcast4.c,v 1.1 2003-10-12 09:17:47 btb Exp $ */
2
3 /*
4  *
5  * "ipx driver" for IPv4 multicasting
6  *
7  */
8
9
10 #ifdef HAVE_CONFIG_H
11 #include <conf.h>
12 #endif
13
14 #include <stdio.h>
15 #include <string.h>
16 #include <netinet/in.h>
17 #include <arpa/inet.h>
18 #include <stdarg.h>
19 #include <unistd.h>
20 #include <sys/socket.h>
21 #include <netdb.h>
22 #include <stdlib.h>
23 #include <ctype.h>
24
25 #include "pstypes.h"
26 #include "ipx_mcast4.h"
27 #include "args.h"
28 #include "error.h"
29 #include "newmenu.h"
30 #include "../../main/multi.h"
31
32 //#define IPX_MCAST4_DEBUG
33
34 extern unsigned char ipx_MyAddress[10];
35
36 #define UDP_BASEPORT 28342
37 #define PORTSHIFT_TOLERANCE 0x100
38 #define MAX_PACKETSIZE 8192
39
40 /* OUR port. Can be changed by "@X[+=]..." argument (X is the shift value)
41  */
42 static int baseport=UDP_BASEPORT;
43
44 static struct in_addr game_addr;    // The game's multicast address
45
46 #define MSGHDR "IPX_mcast4: "
47
48 #ifdef IPX_MCAST4_DEBUG
49 static void msg(const char *fmt, ...)
50 {
51         va_list ap;
52
53         fputs(MSGHDR, stdout);
54         va_start(ap, fmt);
55         vprintf(fmt, ap);
56         va_end(ap);
57         putchar('\n');
58 }
59 #else
60 #define msg(m...)
61 #endif
62
63 #define FAIL(m...) do{ nm_messagebox("Error", 1, "Ok", ##m); return -1; } while (0)
64
65 #ifdef IPX_MCAST4_DEBUG
66 /* Dump raw form of IP address/port by fancy output to user
67  */
68 static void dumpraddr(unsigned char *a)
69 {
70         short port;
71
72         printf("[%u.%u.%u.%u]", a[0], a[1], a[2], a[3]);
73         port=(signed short)ntohs(*(unsigned short *)(a+4));
74         if (port) printf(":%+d",port);
75 }
76
77 /* Like dumpraddr() but for structure "sockaddr_in"
78  */
79 static void dumpaddr(struct sockaddr_in *sin)
80 {
81         unsigned short ports;
82         unsigned char qhbuf[8];
83
84         memcpy(qhbuf + 0, &sin->sin_addr, 4);
85         ports = htons(((short)ntohs(sin->sin_port)) - UDP_BASEPORT);
86         memcpy(qhbuf + 4, &ports, 2);
87         dumpraddr(qhbuf);
88 }
89 #endif
90
91 // The multicast address for Descent 2 game announcements.
92 // TODO: Pick a better address for this
93 #define DESCENT2_ANNOUNCE_ADDR inet_addr("239.255.1.2")
94
95 /* Open the socket and subscribe to the multicast session */
96 static int ipx_mcast4_OpenSocket(ipx_socket_t *sk, int port)
97 {
98         u_char loop;
99         struct ip_mreq mreq;
100         struct sockaddr_in sin;
101         int ttl = 128;
102
103         if((sk->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
104         {
105                 sk->fd = -1;
106                 FAIL("socket() creation failed on port %d: %m", port);
107         }
108
109         // Bind to the port
110         sin.sin_family = AF_INET;
111         sin.sin_addr.s_addr = htonl(INADDR_ANY);
112         sin.sin_port = htons(baseport);
113         if (bind(sk->fd, (struct sockaddr *)&sin, sizeof(sin)))
114         {
115                 if (close(sk->fd))
116                         msg("close() failed during error recovery: %m");
117                 sk->fd = -1;
118                 FAIL("bind() to UDP port %d failed: %m", baseport);
119         }
120
121         // Set the TTL so the packets can get out of the local network.
122         if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
123                 FAIL("setsockopt() failed to set TTL to 128");
124
125         // Disable multicast loopback
126         loop = 0;
127         if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0)
128                 FAIL("setsockopt() failed to disable multicast loopback: %m");
129
130         // Subscribe to the game announcement address
131         memset(&mreq, 0, sizeof(mreq));
132         mreq.imr_multiaddr.s_addr = DESCENT2_ANNOUNCE_ADDR;
133         mreq.imr_interface.s_addr = INADDR_ANY;
134         if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
135                 FAIL("setsockopt() failed to subscribe to the game announcement multicast group");
136
137         // We're not subscribed to a game address yet
138         game_addr.s_addr = 0;
139
140         sk->socket = port;
141         return 0;
142 }
143
144 static void ipx_mcast4_CloseSocket(ipx_socket_t *sk)
145 {
146         if(close(sk->fd) < 0)
147                 msg("Close failed");
148         sk->fd = -1;
149 }
150
151 static int ipx_mcast4_SendPacket(ipx_socket_t *sk, IPXPacket_t *IPXHeader, u_char *data, int dataLen)
152 {
153         struct sockaddr_in toaddr;
154         int i;
155
156         msg("SendPacket enter, dataLen=%d", dataLen);
157
158         if(dataLen < 0 || dataLen > MAX_PACKETSIZE)
159                 return -1;
160
161         toaddr.sin_family = AF_INET;
162         memcpy(&toaddr.sin_addr, IPXHeader->Destination.Node + 0, 4);
163         //toaddr.sin_port = htons(((short)ntohs(*(unsigned short *)(IPXHeader->Destination.Node + 4))) + UDP_BASEPORT);
164         // For now, just use the same port for everything
165         toaddr.sin_port = htons(UDP_BASEPORT);
166
167         // If it's the broadcast address, then we want to send it to the
168         // GAME ANNOUNCEMENT address.
169         // Data to be sent to the GAME has the destination already set by
170         // ipx_mcast4_SendGamePacket
171         if(toaddr.sin_addr.s_addr == INADDR_BROADCAST)
172                 toaddr.sin_addr.s_addr = DESCENT2_ANNOUNCE_ADDR;
173
174 #ifdef IPX_MCAST4_DEBUG
175         printf(MSGHDR "sendto((%d),Node=[4] %02X %02X,Socket=%02X %02X,s_port=%u,",
176                dataLen,
177                IPXHeader->Destination.Node[4], IPXHeader->Destination.Node[5],
178                IPXHeader->Destination.Socket[0], IPXHeader->Destination.Socket[1],
179                ntohs(toaddr.sin_port));
180         dumpaddr(&toaddr);
181         puts(").");
182 #endif
183
184         i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
185         return i;
186 }
187
188 static int ipx_mcast4_ReceivePacket(ipx_socket_t *sk, char *outbuf, int outbufsize, struct ipx_recv_data *rd)
189 {
190         int size;
191         struct sockaddr_in fromaddr;
192         int fromaddrsize = sizeof(fromaddr);
193
194         if((size = recvfrom(sk->fd, outbuf, outbufsize, 0, (struct sockaddr*)&fromaddr, &fromaddrsize)) < 0)
195                 return -1;
196
197 #ifdef IPX_MCAST4_DEBUG
198         printf(MSGHDR "Got packet from ");
199         dumpaddr(&fromaddr);
200         puts("");
201 #endif
202
203         // We have the packet, now fill out the receive data.
204         memset(rd, 0, sizeof(*rd));
205         memcpy(rd->src_node, &fromaddr.sin_addr, 4);
206         // TODO: Include the port like in ipx_udp.c
207         rd->pkt_type = 0;
208
209         return size;
210 }
211
212 /* Handle the netgame aux data
213  * Byte 0 is the protocol version number.
214  * Bytes 1-4 are the IPv4 multicast session to join, in network byte order.
215  */
216 static int ipx_mcast4_HandleNetgameAuxData(ipx_socket_t *sk, const u_char buf[NETGAME_AUX_SIZE])
217 {
218         // Extract the multicast session and subscribe to it.  We should
219         // now be getting packets intended for the players of this game.
220
221         // Note that we stay subscribed to the game announcement session,
222         // so we can reply to game info requests
223         struct ip_mreq mreq;
224         int ttl = 128;
225
226         // Check the protocol version
227         if(buf[0] != IPX_MCAST4_VERSION)
228         {
229                 FAIL("mcast4 protocol\nversion mismatch!\nGame version is %02x,\nour version is %02x", buf[0], IPX_MCAST4_VERSION);
230         }
231
232         // Get the multicast session
233         memcpy(&game_addr, buf + 1, sizeof(game_addr));
234
235 #ifdef IPX_MCAST4_DEBUG
236         {
237                 struct sockaddr_in tmpaddr;
238                 tmpaddr.sin_addr = game_addr;
239                 tmpaddr.sin_port = 0;
240
241                 printf("Handling netgame aux data: Subscribing to ");
242                 dumpaddr(&tmpaddr);
243                 puts("");
244         }
245 #endif
246
247         // Set the TTL so the packets can get out of the local network.
248         if(setsockopt(sk->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0)
249                 FAIL("setsockopt() failed to set TTL to 128");
250
251         memset(&mreq, 0, sizeof(mreq));
252         mreq.imr_multiaddr = game_addr;
253         mreq.imr_interface.s_addr = INADDR_ANY;
254
255         if(setsockopt(sk->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
256                 FAIL("setsockopt() failed to subscribe to the game group");
257
258         return 0;
259 }
260
261 /* Create the netgame aux data.
262  * Byte 0 is the protcol version number.
263  * Bytes 1-4 hold the IPv4 multicast session for the game.
264  */
265 static void ipx_mcast4_InitNetgameAuxData(ipx_socket_t *sk, u_char buf[NETGAME_AUX_SIZE])
266 {
267         Assert(game_addr.s_addr == 0);
268
269         // The first byte is the version number
270         buf[0] = IPX_MCAST4_VERSION;
271
272         // Generate a random session
273         game_addr = inet_makeaddr(239*256 + 255, d_rand() % 0xFFFF);
274         memcpy(buf + 1, &game_addr, sizeof(game_addr));
275
276         // Since we're obviously the hosting machine, subscribe to this address
277         ipx_mcast4_HandleNetgameAuxData(sk, buf);
278 }
279
280 static void ipx_mcast4_HandleLeaveGame(ipx_socket_t *sk)
281 {
282         // We left the game, so unsubscribe from its multicast session
283         struct ip_mreq mreq;
284
285         Assert(game_addr.s_addr != 0);
286
287 #ifdef IPX_MCAST4_DEBUG
288         printf("Unsubscribing from game's multicast group: ");
289         dumpraddr(&game_addr.s_addr);
290         printf("\n");
291 #endif
292
293         mreq.imr_multiaddr = game_addr;
294         mreq.imr_interface.s_addr = INADDR_ANY;
295         if(setsockopt(sk->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
296                 msg("setsockopt() failed unsubscribing from previous group!");
297         game_addr.s_addr = 0;
298 }
299
300 // Send a packet to every member of the game.  We can just multicast it here.
301 static int ipx_mcast4_SendGamePacket(ipx_socket_t *sk, ubyte *data, int dataLen)
302 {
303         struct sockaddr_in toaddr;
304         int i;
305
306         memset(&toaddr, 0, sizeof(toaddr));
307         toaddr.sin_addr = game_addr;
308         toaddr.sin_port = htons(UDP_BASEPORT);
309
310         msg("ipx_mcast4_SendGamePacket");
311
312         i = sendto(sk->fd, data, dataLen, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
313
314         return i;
315 }
316
317 // Pull this in from ipx_udp.c since it's the same for us.
318 extern int ipx_udp_GetMyAddress();
319
320 struct ipx_driver ipx_mcast4 = {
321         ipx_udp_GetMyAddress,
322         ipx_mcast4_OpenSocket,
323         ipx_mcast4_CloseSocket,
324         ipx_mcast4_SendPacket,
325         ipx_mcast4_ReceivePacket,
326         ipx_general_PacketReady,
327         ipx_mcast4_InitNetgameAuxData,
328         ipx_mcast4_HandleNetgameAuxData,
329         ipx_mcast4_HandleLeaveGame,
330         ipx_mcast4_SendGamePacket
331 };