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