Disable installing plugins that aren't built.
[pidgin-git.git] / libpurple / nat-pmp.c
bloba61a51f69c341fb16cbf48eac1f6d913e26eaf47
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * Most code in nat-pmp.c copyright (C) 2007, R. Tyler Ballance, bleep, LLC.
8 * This file is distributed under the 3-clause (modified) BSD license:
9 * Redistribution and use in source and binary forms, with or without modification, are permitted
10 * provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice, this list of conditions and
13 * the following disclaimer.
14 * Neither the name of the bleep. LLC nor the names of its contributors may be used to endorse or promote
15 * products derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
24 * OF SUCH DAMAGE.
27 #include <gio/gio.h>
29 #include "internal.h"
30 #include "nat-pmp.h"
31 #include "debug.h"
32 #include "signals.h"
33 #include "network.h"
35 #ifdef HAVE_SYS_PARAM_H
36 #include <sys/param.h>
37 #endif
39 #ifdef HAVE_SYS_SYSCTL_H
40 #include <sys/sysctl.h>
41 #endif
43 #ifdef HAVE_SYS_SOCKET_H
44 #include <sys/socket.h>
45 #endif
47 /* We will need sysctl() and NET_RT_DUMP, both of which are not present
48 * on all platforms, to continue. */
49 #if defined(HAVE_SYS_SYSCTL_H) && defined(NET_RT_DUMP)
51 #include <sys/types.h>
52 #include <net/route.h>
54 #define PMP_DEBUG 1
56 typedef struct {
57 guint8 version;
58 guint8 opcode;
59 } PurplePmpIpRequest;
61 typedef struct {
62 guint8 version;
63 guint8 opcode; /* 128 + n */
64 guint16 resultcode;
65 guint32 epoch;
66 guint32 address;
67 } PurplePmpIpResponse;
69 typedef struct {
70 guint8 version;
71 guint8 opcode;
72 char reserved[2];
73 guint16 privateport;
74 guint16 publicport;
75 guint32 lifetime;
76 } PurplePmpMapRequest;
78 struct _PurplePmpMapResponse {
79 guint8 version;
80 guint8 opcode;
81 guint16 resultcode;
82 guint32 epoch;
83 guint16 privateport;
84 guint16 publicport;
85 guint32 lifetime;
88 typedef struct _PurplePmpMapResponse PurplePmpMapResponse;
90 typedef enum {
91 PURPLE_PMP_STATUS_UNDISCOVERED = -1,
92 PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER,
93 PURPLE_PMP_STATUS_DISCOVERING,
94 PURPLE_PMP_STATUS_DISCOVERED
95 } PurpleUPnPStatus;
97 typedef struct {
98 PurpleUPnPStatus status;
99 gchar *publicip;
100 } PurplePmpInfo;
102 static PurplePmpInfo pmp_info = {PURPLE_PMP_STATUS_UNDISCOVERED, NULL};
105 * Thanks to R. Matthew Emerson for the fixes on this
108 #define PMP_MAP_OPCODE_UDP 1
109 #define PMP_MAP_OPCODE_TCP 2
111 #define PMP_VERSION 0
112 #define PMP_PORT 5351
113 #define PMP_TIMEOUT 250000 /* 250000 useconds */
115 /* alignment constraint for routing socket */
116 #define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
117 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
119 static void
120 get_rtaddrs(int bitmask, struct sockaddr *sa, struct sockaddr *addrs[])
122 int i;
124 for (i = 0; i < RTAX_MAX; i++)
126 if (bitmask & (1 << i))
128 addrs[i] = sa;
129 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
130 sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);
131 #else
132 if (sa->sa_family == AF_INET)
133 sa = (struct sockaddr*)(sizeof(struct sockaddr_in) + (char *)sa);
134 #ifdef AF_INET6
135 else if (sa->sa_family == AF_INET6)
136 sa = (struct sockaddr*)(sizeof(struct sockaddr_in6) + (char *)sa);
137 #endif
138 #endif
140 else
142 addrs[i] = NULL;
147 static int
148 is_default_route(struct sockaddr *sa, struct sockaddr *mask)
150 struct sockaddr_in *sin;
152 if (sa->sa_family != AF_INET)
153 return 0;
155 sin = (struct sockaddr_in *)sa;
156 if ((sin->sin_addr.s_addr == INADDR_ANY) &&
157 mask &&
158 (ntohl(((struct sockaddr_in *)mask)->sin_addr.s_addr) == 0L ||
159 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
160 mask->sa_len == 0
161 #else
163 #endif
165 return 1;
166 else
167 return 0;
171 * The return sockaddr_in must be g_free()'d when no longer needed
173 static struct sockaddr_in *
174 default_gw()
176 int mib[6];
177 size_t needed;
178 char *buf, *next, *lim;
179 struct rt_msghdr *rtm;
180 struct sockaddr *sa;
181 struct sockaddr_in *sin = NULL;
183 mib[0] = CTL_NET;
184 mib[1] = PF_ROUTE; /* entire routing table or a subset of it */
185 mib[2] = 0; /* protocol number - always 0 */
186 mib[3] = 0; /* address family - 0 for all addres families */
187 mib[4] = NET_RT_DUMP;
188 mib[5] = 0;
190 /* Determine the buffer side needed to get the full routing table */
191 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
193 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate\n");
194 return NULL;
197 if (!(buf = malloc(needed)))
199 purple_debug_warning("nat-pmp", "Failed to malloc %" G_GSIZE_FORMAT "\n", needed);
200 return NULL;
203 /* Read the routing table into buf */
204 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
206 free(buf);
207 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump\n");
208 return NULL;
211 lim = buf + needed;
213 for (next = buf; next < lim; next += rtm->rtm_msglen)
215 rtm = (struct rt_msghdr *)next;
216 sa = (struct sockaddr *)(rtm + 1);
218 if (sa->sa_family == AF_INET)
220 struct sockaddr_in *cursin = (struct sockaddr_in*) sa;
222 if ((rtm->rtm_flags & RTF_GATEWAY)
223 && cursin->sin_addr.s_addr == INADDR_ANY)
225 /* We found the default route. Now get the destination address and netmask. */
226 struct sockaddr *rti_info[RTAX_MAX];
227 struct sockaddr addr, mask;
229 get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
230 memset(&addr, 0, sizeof(addr));
232 if (rtm->rtm_addrs & RTA_DST)
233 memcpy(&addr, rti_info[RTAX_DST], sizeof(addr));
235 memset(&mask, 0, sizeof(mask));
237 if (rtm->rtm_addrs & RTA_NETMASK)
238 memcpy(&mask, rti_info[RTAX_NETMASK], sizeof(mask));
240 if (rtm->rtm_addrs & RTA_GATEWAY &&
241 is_default_route(&addr, &mask))
243 if (rti_info[RTAX_GATEWAY]) {
244 struct sockaddr_in *rti_sin = (struct sockaddr_in *)rti_info[RTAX_GATEWAY];
245 sin = g_new0(struct sockaddr_in, 1);
246 sin->sin_family = rti_sin->sin_family;
247 sin->sin_port = rti_sin->sin_port;
248 sin->sin_addr.s_addr = rti_sin->sin_addr.s_addr;
249 memcpy(sin, rti_info[RTAX_GATEWAY], sizeof(struct sockaddr_in));
251 purple_debug_info("nat-pmp", "Found a default gateway\n");
252 break;
259 free(buf);
260 return sin;
264 * purple_pmp_get_public_ip() will return the publicly facing IP address of the
265 * default NAT gateway. The function will return NULL if:
266 * - The gateway doesn't support NAT-PMP
267 * - The gateway errors in some other spectacular fashion
269 char *
270 purple_pmp_get_public_ip()
272 struct sockaddr_in addr, *gateway, *publicsockaddr = NULL;
273 struct timeval req_timeout;
274 socklen_t len;
276 PurplePmpIpRequest req;
277 PurplePmpIpResponse resp;
278 int sendfd;
280 if (pmp_info.status == PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER)
281 return NULL;
283 if ((pmp_info.status == PURPLE_PMP_STATUS_DISCOVERED) && (pmp_info.publicip != NULL))
285 #ifdef PMP_DEBUG
286 purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info.publicip);
287 #endif
288 return pmp_info.publicip;
291 gateway = default_gw();
293 if (!gateway)
295 purple_debug_info("nat-pmp", "Cannot request public IP from a NULL gateway!\n");
296 /* If we get a NULL gateway, don't try again next time */
297 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
298 return NULL;
301 /* Default port for NAT-PMP is 5351 */
302 if (gateway->sin_port != PMP_PORT)
303 gateway->sin_port = htons(PMP_PORT);
305 req_timeout.tv_sec = 0;
306 req_timeout.tv_usec = PMP_TIMEOUT;
308 sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
310 /* Clean out both req and resp structures */
311 memset(&req, 0, sizeof(PurplePmpIpRequest));
312 memset(&resp, 0, sizeof(PurplePmpIpResponse));
313 req.version = 0;
314 req.opcode = 0;
316 /* The NAT-PMP spec says we should attempt to contact the gateway 9 times, doubling the time we wait each time.
317 * Even starting with a timeout of 0.1 seconds, that means that we have a total waiting of 204.6 seconds.
318 * With the recommended timeout of 0.25 seconds, we're talking 511.5 seconds (8.5 minutes).
320 * This seems really silly... if this were nonblocking, a couple retries might be in order, but it's not at present.
322 #ifdef PMP_DEBUG
323 purple_debug_info("nat-pmp", "Attempting to retrieve the public ip address for the NAT device at: %s\n", inet_ntoa(gateway->sin_addr));
324 purple_debug_info("nat-pmp", "\tTimeout: %ds %dus\n", req_timeout.tv_sec, req_timeout.tv_usec);
325 #endif
327 /* TODO: Non-blocking! */
329 if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0)
331 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", g_strerror(errno));
332 g_free(gateway);
333 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
334 return NULL;
337 if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0)
339 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno));
340 g_free(gateway);
341 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
342 return NULL;
345 /* TODO: Non-blocking! */
346 len = sizeof(struct sockaddr_in);
347 if (recvfrom(sendfd, &resp, sizeof(PurplePmpIpResponse), 0, (struct sockaddr *)(&addr), &len) < 0)
349 if (errno != EAGAIN)
351 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno));
352 g_free(gateway);
353 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
354 return NULL;
358 if (addr.sin_addr.s_addr == gateway->sin_addr.s_addr)
359 publicsockaddr = &addr;
360 else
362 purple_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr.sin_addr));
363 g_free(gateway);
365 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
366 return NULL;
369 if (!publicsockaddr) {
370 g_free(gateway);
372 pmp_info.status = PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER;
373 return NULL;
376 #ifdef PMP_DEBUG
377 purple_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
378 purple_debug_info("nat-pmp", "version: %d\n", resp.version);
379 purple_debug_info("nat-pmp", "opcode: %d\n", resp.opcode);
380 purple_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp.resultcode));
381 purple_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp.epoch));
382 struct in_addr in;
383 in.s_addr = resp.address;
384 purple_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in));
385 #endif
387 publicsockaddr->sin_addr.s_addr = resp.address;
389 g_free(gateway);
391 g_free(pmp_info.publicip);
392 pmp_info.publicip = g_strdup(inet_ntoa(publicsockaddr->sin_addr));
393 pmp_info.status = PURPLE_PMP_STATUS_DISCOVERED;
395 return inet_ntoa(publicsockaddr->sin_addr);
398 gboolean
399 purple_pmp_create_map(PurplePmpType type, unsigned short privateport, unsigned short publicport, int lifetime)
401 struct sockaddr_in *gateway;
402 gboolean success = TRUE;
403 int sendfd;
404 struct timeval req_timeout;
405 PurplePmpMapRequest req;
406 PurplePmpMapResponse *resp;
408 gateway = default_gw();
410 if (!gateway)
412 purple_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
413 return FALSE;
416 /* Default port for NAT-PMP is 5351 */
417 if (gateway->sin_port != PMP_PORT)
418 gateway->sin_port = htons(PMP_PORT);
420 resp = g_new0(PurplePmpMapResponse, 1);
422 req_timeout.tv_sec = 0;
423 req_timeout.tv_usec = PMP_TIMEOUT;
425 sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
427 /* Set up the req */
428 memset(&req, 0, sizeof(PurplePmpMapRequest));
429 req.version = 0;
430 req.opcode = ((type == PURPLE_PMP_TYPE_UDP) ? PMP_MAP_OPCODE_UDP : PMP_MAP_OPCODE_TCP);
431 req.privateport = htons(privateport); /* What a difference byte ordering makes...d'oh! */
432 req.publicport = htons(publicport);
433 req.lifetime = htonl(lifetime);
435 /* The NAT-PMP spec says we should attempt to contact the gateway 9 times, doubling the time we wait each time.
436 * Even starting with a timeout of 0.1 seconds, that means that we have a total waiting of 204.6 seconds.
437 * With the recommended timeout of 0.25 seconds, we're talking 511.5 seconds (8.5 minutes).
439 * This seems really silly... if this were nonblocking, a couple retries might be in order, but it's not at present.
440 * XXX Make this nonblocking.
441 * XXX This code looks like the pmp_get_public_ip() code. Can it be consolidated?
443 #ifdef PMP_DEBUG
444 purple_debug_info("nat-pmp", "Attempting to create a NAT-PMP mapping the private port %d, and the public port %d\n", privateport, publicport);
445 purple_debug_info("nat-pmp", "\tTimeout: %ds %dus\n", req_timeout.tv_sec, req_timeout.tv_usec);
446 #endif
448 /* TODO: Non-blocking! */
449 success = (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) >= 0);
450 if (!success)
451 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", g_strerror(errno));
453 if (success)
455 success = (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) >= 0);
456 if (!success)
457 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno));
460 if (success)
462 /* The original code treats EAGAIN as a reason to iterate.. but I've removed iteration. This may be a problem */
463 /* TODO: Non-blocking! */
464 success = ((recvfrom(sendfd, resp, sizeof(PurplePmpMapResponse), 0, NULL, NULL) >= 0) ||
465 (errno == EAGAIN));
466 if (!success)
467 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno));
470 if (success)
472 success = (resp->opcode == (req.opcode + 128));
473 if (!success)
474 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",
475 resp->opcode, req.opcode, req.opcode + 128);
478 #ifdef PMP_DEBUG
479 if (success)
481 purple_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
482 purple_debug_info("nat-pmp", "version: %d\n", resp->version);
483 purple_debug_info("nat-pmp", "opcode: %d\n", resp->opcode);
484 purple_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp->resultcode));
485 purple_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp->epoch));
486 purple_debug_info("nat-pmp", "privateport: %d\n", ntohs(resp->privateport));
487 purple_debug_info("nat-pmp", "publicport: %d\n", ntohs(resp->publicport));
488 purple_debug_info("nat-pmp", "lifetime: %d\n", ntohl(resp->lifetime));
490 #endif
492 g_free(resp);
493 g_free(gateway);
495 /* XXX The private port may actually differ from the one we requested, according to the spec.
496 * We don't handle that situation at present.
498 * TODO: Look at the result and verify it matches what we wanted; either return a failure if it doesn't,
499 * or change network.c to know what to do if the desired private port shifts as a result of the nat-pmp operation.
501 return success;
504 gboolean
505 purple_pmp_destroy_map(PurplePmpType type, unsigned short privateport)
507 gboolean success;
509 success = purple_pmp_create_map(((type == PURPLE_PMP_TYPE_UDP) ? PMP_MAP_OPCODE_UDP : PMP_MAP_OPCODE_TCP),
510 privateport, 0, 0);
511 if (!success)
512 purple_debug_warning("nat-pmp", "Failed to properly destroy mapping for %s port %d!\n",
513 ((type == PURPLE_PMP_TYPE_UDP) ? "UDP" : "TCP"), privateport);
515 return success;
518 static void
519 purple_pmp_network_config_changed_cb(GNetworkMonitor *monitor, gboolean avialable, gpointer data)
521 pmp_info.status = PURPLE_PMP_STATUS_UNDISCOVERED;
522 g_free(pmp_info.publicip);
523 pmp_info.publicip = NULL;
526 static void*
527 purple_pmp_get_handle(void)
529 static int handle;
531 return &handle;
534 void
535 purple_pmp_init()
537 g_signal_connect(g_network_monitor_get_default(),
538 "network-changed",
539 G_CALLBACK(purple_pmp_network_config_changed_cb),
540 NULL);
542 #else /* #ifdef NET_RT_DUMP */
543 char *
544 purple_pmp_get_public_ip()
546 return NULL;
549 gboolean
550 purple_pmp_create_map(PurplePmpType type, unsigned short privateport, unsigned short publicport, int lifetime)
552 return FALSE;
555 gboolean
556 purple_pmp_destroy_map(PurplePmpType type, unsigned short privateport)
558 return FALSE;
561 void
562 purple_pmp_init()
566 #endif /* #if !(defined(HAVE_SYS_SYCTL_H) && defined(NET_RT_DUMP)) */