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