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