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