I think this was accidentally changed in revision
[pidgin-git.git] / libpurple / nat-pmp.c
blob9bec0e1ef813df5ab15c35086df3051cda4baa70
1 /**
2 * @file nat-pmp.c NAT-PMP Implementation
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * Most code in nat-pmp.c copyright (C) 2007, R. Tyler Ballance, bleep, LLC.
13 * This file is distributed under the 3-clause (modified) BSD license:
14 * Redistribution and use in source and binary forms, with or without modification, are permitted
15 * provided that the following conditions are met:
17 * Redistributions of source code must retain the above copyright notice, this list of conditions and
18 * the following disclaimer.
19 * Neither the name of the bleep. LLC nor the names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
23 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
29 * OF SUCH DAMAGE.
32 #include "internal.h"
33 #include "nat-pmp.h"
34 #include "debug.h"
35 #include "signals.h"
36 #include "network.h"
38 #ifdef HAVE_SYS_PARAM_H
39 #include <sys/param.h>
40 #endif
42 #ifdef HAVE_SYS_SYSCTL_H
43 #include <sys/sysctl.h>
44 #endif
46 #ifdef HAVE_SYS_SOCKET_H
47 #include <sys/socket.h>
48 #endif
50 /* We will need sysctl() and NET_RT_DUMP, both of which are not present
51 * on all platforms, to continue. */
52 #if defined(HAVE_SYS_SYSCTL_H) && defined(NET_RT_DUMP)
54 #include <sys/types.h>
55 #include <net/route.h>
57 #define PMP_DEBUG 1
59 typedef struct {
60 guint8 version;
61 guint8 opcode;
62 } PurplePmpIpRequest;
64 typedef struct {
65 guint8 version;
66 guint8 opcode; /* 128 + n */
67 guint16 resultcode;
68 guint32 epoch;
69 guint32 address;
70 } PurplePmpIpResponse;
72 typedef struct {
73 guint8 version;
74 guint8 opcode;
75 char reserved[2];
76 guint16 privateport;
77 guint16 publicport;
78 guint32 lifetime;
79 } PurplePmpMapRequest;
81 struct _PurplePmpMapResponse {
82 guint8 version;
83 guint8 opcode;
84 guint16 resultcode;
85 guint32 epoch;
86 guint16 privateport;
87 guint16 publicport;
88 guint32 lifetime;
91 typedef struct _PurplePmpMapResponse PurplePmpMapResponse;
93 typedef enum {
94 PURPLE_PMP_STATUS_UNDISCOVERED = -1,
95 PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER,
96 PURPLE_PMP_STATUS_DISCOVERING,
97 PURPLE_PMP_STATUS_DISCOVERED
98 } PurpleUPnPStatus;
100 typedef struct {
101 PurpleUPnPStatus status;
102 gchar *publicip;
103 } PurplePmpInfo;
105 static PurplePmpInfo pmp_info = {PURPLE_PMP_STATUS_UNDISCOVERED, NULL};
108 * Thanks to R. Matthew Emerson for the fixes on this
111 #define PMP_MAP_OPCODE_UDP 1
112 #define PMP_MAP_OPCODE_TCP 2
114 #define PMP_VERSION 0
115 #define PMP_PORT 5351
116 #define PMP_TIMEOUT 250000 /* 250000 useconds */
118 /* alignment constraint for routing socket */
119 #define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
120 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
122 static void
123 get_rtaddrs(int bitmask, struct sockaddr *sa, struct sockaddr *addrs[])
125 int i;
127 for (i = 0; i < RTAX_MAX; i++)
129 if (bitmask & (1 << i))
131 addrs[i] = sa;
132 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
133 sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);
134 #else
135 if (sa->sa_family == AF_INET)
136 sa = (struct sockaddr*)(sizeof(struct sockaddr_in) + (char *)sa);
137 #ifdef AF_INET6
138 else if (sa->sa_family == AF_INET6)
139 sa = (struct sockaddr*)(sizeof(struct sockaddr_in6) + (char *)sa);
140 #endif
141 #endif
143 else
145 addrs[i] = NULL;
150 static int
151 is_default_route(struct sockaddr *sa, struct sockaddr *mask)
153 struct sockaddr_in *sin;
155 if (sa->sa_family != AF_INET)
156 return 0;
158 sin = (struct sockaddr_in *)sa;
159 if ((sin->sin_addr.s_addr == INADDR_ANY) &&
160 mask &&
161 (ntohl(((struct sockaddr_in *)mask)->sin_addr.s_addr) == 0L ||
162 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
163 mask->sa_len == 0
164 #else
166 #endif
168 return 1;
169 else
170 return 0;
174 * The return sockaddr_in must be g_free()'d when no longer needed
176 static struct sockaddr_in *
177 default_gw()
179 int mib[6];
180 size_t needed;
181 char *buf, *next, *lim;
182 struct rt_msghdr *rtm;
183 struct sockaddr *sa;
184 struct sockaddr_in *sin = NULL;
185 gboolean found = FALSE;
187 mib[0] = CTL_NET;
188 mib[1] = PF_ROUTE; /* entire routing table or a subset of it */
189 mib[2] = 0; /* protocol number - always 0 */
190 mib[3] = 0; /* address family - 0 for all addres families */
191 mib[4] = NET_RT_DUMP;
192 mib[5] = 0;
194 /* Determine the buffer side needed to get the full routing table */
195 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
197 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate\n");
198 return NULL;
201 if (!(buf = malloc(needed)))
203 purple_debug_warning("nat-pmp", "Failed to malloc %" G_GSIZE_FORMAT "\n", needed);
204 return NULL;
207 /* Read the routing table into buf */
208 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
210 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump\n");
211 return NULL;
214 lim = buf + needed;
216 for (next = buf; next < lim; next += rtm->rtm_msglen)
218 rtm = (struct rt_msghdr *)next;
219 sa = (struct sockaddr *)(rtm + 1);
221 if (sa->sa_family == AF_INET)
223 sin = (struct sockaddr_in*) sa;
225 if ((rtm->rtm_flags & RTF_GATEWAY) && sin->sin_addr.s_addr == INADDR_ANY)
227 /* We found the default route. Now get the destination address and netmask. */
228 struct sockaddr *rti_info[RTAX_MAX];
229 struct sockaddr addr, mask;
231 get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
232 memset(&addr, 0, sizeof(addr));
234 if (rtm->rtm_addrs & RTA_DST)
235 memcpy(&addr, rti_info[RTAX_DST], sizeof(addr));
237 memset(&mask, 0, sizeof(mask));
239 if (rtm->rtm_addrs & RTA_NETMASK)
240 memcpy(&mask, rti_info[RTAX_NETMASK], sizeof(mask));
242 if (rtm->rtm_addrs & RTA_GATEWAY &&
243 is_default_route(&addr, &mask))
245 if (rti_info[RTAX_GATEWAY]) {
246 struct sockaddr_in *rti_sin = (struct sockaddr_in *)rti_info[RTAX_GATEWAY];
247 sin = g_new0(struct sockaddr_in, 1);
248 sin->sin_family = rti_sin->sin_family;
249 sin->sin_port = rti_sin->sin_port;
250 sin->sin_addr.s_addr = rti_sin->sin_addr.s_addr;
251 memcpy(sin, rti_info[RTAX_GATEWAY], sizeof(struct sockaddr_in));
253 purple_debug_info("nat-pmp", "Found a default gateway\n");
254 found = TRUE;
255 break;
262 return (found ? sin : NULL);
266 * purple_pmp_get_public_ip() will return the publicly facing IP address of the
267 * default NAT gateway. The function will return NULL if:
268 * - The gateway doesn't support NAT-PMP
269 * - The gateway errors in some other spectacular fashion
271 char *
272 purple_pmp_get_public_ip()
274 struct sockaddr_in addr, *gateway, *publicsockaddr = NULL;
275 struct timeval req_timeout;
276 socklen_t len;
278 PurplePmpIpRequest req;
279 PurplePmpIpResponse resp;
280 int sendfd;
282 if (pmp_info.status == PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER)
283 return NULL;
285 if ((pmp_info.status == PURPLE_PMP_STATUS_DISCOVERED) && (pmp_info.publicip != NULL))
287 #ifdef PMP_DEBUG
288 purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info.publicip);
289 #endif
290 return pmp_info.publicip;
293 gateway = default_gw();
295 if (!gateway)
297 purple_debug_info("nat-pmp", "Cannot request public IP from a NULL gateway!\n");
298 /* If we get a NULL gateway, don't try again next time */
299 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
300 return NULL;
303 /* Default port for NAT-PMP is 5351 */
304 if (gateway->sin_port != PMP_PORT)
305 gateway->sin_port = htons(PMP_PORT);
307 req_timeout.tv_sec = 0;
308 req_timeout.tv_usec = PMP_TIMEOUT;
310 sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
312 /* Clean out both req and resp structures */
313 memset(&req, 0, sizeof(PurplePmpIpRequest));
314 memset(&resp, 0, sizeof(PurplePmpIpResponse));
315 req.version = 0;
316 req.opcode = 0;
318 /* The NAT-PMP spec says we should attempt to contact the gateway 9 times, doubling the time we wait each time.
319 * Even starting with a timeout of 0.1 seconds, that means that we have a total waiting of 204.6 seconds.
320 * With the recommended timeout of 0.25 seconds, we're talking 511.5 seconds (8.5 minutes).
322 * This seems really silly... if this were nonblocking, a couple retries might be in order, but it's not at present.
324 #ifdef PMP_DEBUG
325 purple_debug_info("nat-pmp", "Attempting to retrieve the public ip address for the NAT device at: %s\n", inet_ntoa(gateway->sin_addr));
326 purple_debug_info("nat-pmp", "\tTimeout: %ds %dus\n", req_timeout.tv_sec, req_timeout.tv_usec);
327 #endif
329 /* TODO: Non-blocking! */
331 if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0)
333 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", g_strerror(errno));
334 g_free(gateway);
335 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
336 return NULL;
339 if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0)
341 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno));
342 g_free(gateway);
343 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
344 return NULL;
347 /* TODO: Non-blocking! */
348 len = sizeof(struct sockaddr_in);
349 if (recvfrom(sendfd, &resp, sizeof(PurplePmpIpResponse), 0, (struct sockaddr *)(&addr), &len) < 0)
351 if (errno != EAGAIN)
353 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno));
354 g_free(gateway);
355 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
356 return NULL;
360 if (addr.sin_addr.s_addr == gateway->sin_addr.s_addr)
361 publicsockaddr = &addr;
362 else
364 purple_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr.sin_addr));
365 g_free(gateway);
367 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
368 return NULL;
371 if (!publicsockaddr) {
372 g_free(gateway);
374 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
375 return NULL;
378 #ifdef PMP_DEBUG
379 purple_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
380 purple_debug_info("nat-pmp", "version: %d\n", resp.version);
381 purple_debug_info("nat-pmp", "opcode: %d\n", resp.opcode);
382 purple_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp.resultcode));
383 purple_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp.epoch));
384 struct in_addr in;
385 in.s_addr = resp.address;
386 purple_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in));
387 #endif
389 publicsockaddr->sin_addr.s_addr = resp.address;
391 g_free(gateway);
393 g_free(pmp_info.publicip);
394 pmp_info.publicip = g_strdup(inet_ntoa(publicsockaddr->sin_addr));
395 pmp_info.status = PURPLE_PMP_STATUS_DISCOVERED;
397 return inet_ntoa(publicsockaddr->sin_addr);
400 gboolean
401 purple_pmp_create_map(PurplePmpType type, unsigned short privateport, unsigned short publicport, int lifetime)
403 struct sockaddr_in *gateway;
404 gboolean success = TRUE;
405 int sendfd;
406 struct timeval req_timeout;
407 PurplePmpMapRequest req;
408 PurplePmpMapResponse *resp;
410 gateway = default_gw();
412 if (!gateway)
414 purple_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
415 return FALSE;
418 /* Default port for NAT-PMP is 5351 */
419 if (gateway->sin_port != PMP_PORT)
420 gateway->sin_port = htons(PMP_PORT);
422 resp = g_new0(PurplePmpMapResponse, 1);
424 req_timeout.tv_sec = 0;
425 req_timeout.tv_usec = PMP_TIMEOUT;
427 sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
429 /* Set up the req */
430 memset(&req, 0, sizeof(PurplePmpMapRequest));
431 req.version = 0;
432 req.opcode = ((type == PURPLE_PMP_TYPE_UDP) ? PMP_MAP_OPCODE_UDP : PMP_MAP_OPCODE_TCP);
433 req.privateport = htons(privateport); /* What a difference byte ordering makes...d'oh! */
434 req.publicport = htons(publicport);
435 req.lifetime = htonl(lifetime);
437 /* The NAT-PMP spec says we should attempt to contact the gateway 9 times, doubling the time we wait each time.
438 * Even starting with a timeout of 0.1 seconds, that means that we have a total waiting of 204.6 seconds.
439 * With the recommended timeout of 0.25 seconds, we're talking 511.5 seconds (8.5 minutes).
441 * This seems really silly... if this were nonblocking, a couple retries might be in order, but it's not at present.
442 * XXX Make this nonblocking.
443 * XXX This code looks like the pmp_get_public_ip() code. Can it be consolidated?
445 #ifdef PMP_DEBUG
446 purple_debug_info("nat-pmp", "Attempting to create a NAT-PMP mapping the private port %d, and the public port %d\n", privateport, publicport);
447 purple_debug_info("nat-pmp", "\tTimeout: %ds %dus\n", req_timeout.tv_sec, req_timeout.tv_usec);
448 #endif
450 /* TODO: Non-blocking! */
451 success = (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) >= 0);
452 if (!success)
453 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", g_strerror(errno));
455 if (success)
457 success = (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) >= 0);
458 if (!success)
459 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno));
462 if (success)
464 /* The original code treats EAGAIN as a reason to iterate.. but I've removed iteration. This may be a problem */
465 /* TODO: Non-blocking! */
466 success = ((recvfrom(sendfd, resp, sizeof(PurplePmpMapResponse), 0, NULL, NULL) >= 0) ||
467 (errno == EAGAIN));
468 if (!success)
469 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno));
472 if (success)
474 success = (resp->opcode == (req.opcode + 128));
475 if (!success)
476 purple_debug_info("nat-pmp", "The opcode for the response from the NAT device (%i) does not match the request opcode (%i + 128 = %i)!\n",
477 resp->opcode, req.opcode, req.opcode + 128);
480 #ifdef PMP_DEBUG
481 if (success)
483 purple_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
484 purple_debug_info("nat-pmp", "version: %d\n", resp->version);
485 purple_debug_info("nat-pmp", "opcode: %d\n", resp->opcode);
486 purple_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp->resultcode));
487 purple_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp->epoch));
488 purple_debug_info("nat-pmp", "privateport: %d\n", ntohs(resp->privateport));
489 purple_debug_info("nat-pmp", "publicport: %d\n", ntohs(resp->publicport));
490 purple_debug_info("nat-pmp", "lifetime: %d\n", ntohl(resp->lifetime));
492 #endif
494 g_free(resp);
495 g_free(gateway);
497 /* XXX The private port may actually differ from the one we requested, according to the spec.
498 * We don't handle that situation at present.
500 * TODO: Look at the result and verify it matches what we wanted; either return a failure if it doesn't,
501 * or change network.c to know what to do if the desired private port shifts as a result of the nat-pmp operation.
503 return success;
506 gboolean
507 purple_pmp_destroy_map(PurplePmpType type, unsigned short privateport)
509 gboolean success;
511 success = purple_pmp_create_map(((type == PURPLE_PMP_TYPE_UDP) ? PMP_MAP_OPCODE_UDP : PMP_MAP_OPCODE_TCP),
512 privateport, 0, 0);
513 if (!success)
514 purple_debug_warning("nat-pmp", "Failed to properly destroy mapping for %s port %d!\n",
515 ((type == PURPLE_PMP_TYPE_UDP) ? "UDP" : "TCP"), privateport);
517 return success;
520 static void
521 purple_pmp_network_config_changed_cb(void *data)
523 pmp_info.status = PURPLE_PMP_STATUS_UNDISCOVERED;
524 g_free(pmp_info.publicip);
525 pmp_info.publicip = NULL;
528 static void*
529 purple_pmp_get_handle(void)
531 static int handle;
533 return &handle;
536 void
537 purple_pmp_init()
539 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
540 purple_pmp_get_handle(), PURPLE_CALLBACK(purple_pmp_network_config_changed_cb),
541 GINT_TO_POINTER(0));
543 #else /* #ifdef NET_RT_DUMP */
544 char *
545 purple_pmp_get_public_ip()
547 return NULL;
550 gboolean
551 purple_pmp_create_map(PurplePmpType type, unsigned short privateport, unsigned short publicport, int lifetime)
553 return FALSE;
556 gboolean
557 purple_pmp_destroy_map(PurplePmpType type, unsigned short privateport)
559 return FALSE;
562 void
563 purple_pmp_init()
567 #endif /* #if !(defined(HAVE_SYS_SYCTL_H) && defined(NET_RT_DUMP)) */