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