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
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
35 #ifdef HAVE_SYS_PARAM_H
36 #include <sys/param.h>
39 #ifdef HAVE_SYS_SYSCTL_H
40 #include <sys/sysctl.h>
43 #ifdef HAVE_SYS_SOCKET_H
44 #include <sys/socket.h>
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>
63 guint8 opcode
; /* 128 + n */
67 } PurplePmpIpResponse
;
76 } PurplePmpMapRequest
;
78 struct _PurplePmpMapResponse
{
88 typedef struct _PurplePmpMapResponse PurplePmpMapResponse
;
91 PURPLE_PMP_STATUS_UNDISCOVERED
= -1,
92 PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
,
93 PURPLE_PMP_STATUS_DISCOVERING
,
94 PURPLE_PMP_STATUS_DISCOVERED
98 PurpleUPnPStatus status
;
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))
120 get_rtaddrs(int bitmask
, struct sockaddr
*sa
, struct sockaddr
*addrs
[])
124 for (i
= 0; i
< RTAX_MAX
; i
++)
126 if (bitmask
& (1 << i
))
129 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
130 sa
= (struct sockaddr
*)(ROUNDUP(sa
->sa_len
) + (char *)sa
);
132 if (sa
->sa_family
== AF_INET
)
133 sa
= (struct sockaddr
*)(sizeof(struct sockaddr_in
) + (char *)sa
);
135 else if (sa
->sa_family
== AF_INET6
)
136 sa
= (struct sockaddr
*)(sizeof(struct sockaddr_in6
) + (char *)sa
);
148 is_default_route(struct sockaddr
*sa
, struct sockaddr
*mask
)
150 struct sockaddr_in
*sin
;
152 if (sa
->sa_family
!= AF_INET
)
155 sin
= (struct sockaddr_in
*)sa
;
156 if ((sin
->sin_addr
.s_addr
== INADDR_ANY
) &&
158 (ntohl(((struct sockaddr_in
*)mask
)->sin_addr
.s_addr
) == 0L ||
159 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
171 * The return sockaddr_in must be g_free()'d when no longer needed
173 static struct sockaddr_in
*
178 char *buf
, *next
, *lim
;
179 struct rt_msghdr
*rtm
;
181 struct sockaddr_in
*sin
= NULL
;
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
;
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");
197 if (!(buf
= malloc(needed
)))
199 purple_debug_warning("nat-pmp", "Failed to malloc %" G_GSIZE_FORMAT
"\n", needed
);
203 /* Read the routing table into buf */
204 if (sysctl(mib
, 6, buf
, &needed
, NULL
, 0) < 0)
207 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump\n");
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");
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
270 purple_pmp_get_public_ip()
272 struct sockaddr_in addr
, *gateway
, *publicsockaddr
= NULL
;
273 struct timeval req_timeout
;
276 PurplePmpIpRequest req
;
277 PurplePmpIpResponse resp
;
280 if (pmp_info
.status
== PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
)
283 if ((pmp_info
.status
== PURPLE_PMP_STATUS_DISCOVERED
) && (pmp_info
.publicip
!= NULL
))
286 purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info
.publicip
);
288 return pmp_info
.publicip
;
291 gateway
= default_gw();
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
;
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
));
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.
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
);
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
));
333 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
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
));
341 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
345 /* TODO: Non-blocking! */
346 len
= sizeof(struct sockaddr_in
);
347 if (recvfrom(sendfd
, &resp
, sizeof(PurplePmpIpResponse
), 0, (struct sockaddr
*)(&addr
), &len
) < 0)
351 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno
));
353 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
358 if (addr
.sin_addr
.s_addr
== gateway
->sin_addr
.s_addr
)
359 publicsockaddr
= &addr
;
362 purple_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr
.sin_addr
));
365 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
369 if (!publicsockaddr
) {
372 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
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
));
383 in
.s_addr
= resp
.address
;
384 purple_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in
));
387 publicsockaddr
->sin_addr
.s_addr
= resp
.address
;
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
);
399 purple_pmp_create_map(PurplePmpType type
, unsigned short privateport
, unsigned short publicport
, int lifetime
)
401 struct sockaddr_in
*gateway
;
402 gboolean success
= TRUE
;
404 struct timeval req_timeout
;
405 PurplePmpMapRequest req
;
406 PurplePmpMapResponse
*resp
;
408 gateway
= default_gw();
412 purple_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
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
);
428 memset(&req
, 0, sizeof(PurplePmpMapRequest
));
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?
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
);
448 /* TODO: Non-blocking! */
449 success
= (sendto(sendfd
, &req
, sizeof(req
), 0, (struct sockaddr
*)(gateway
), sizeof(struct sockaddr
)) >= 0);
451 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", g_strerror(errno
));
455 success
= (setsockopt(sendfd
, SOL_SOCKET
, SO_RCVTIMEO
, &req_timeout
, sizeof(req_timeout
)) >= 0);
457 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno
));
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) ||
467 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno
));
472 success
= (resp
->opcode
== (req
.opcode
+ 128));
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);
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
));
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.
505 purple_pmp_destroy_map(PurplePmpType type
, unsigned short privateport
)
509 success
= purple_pmp_create_map(((type
== PURPLE_PMP_TYPE_UDP
) ? PMP_MAP_OPCODE_UDP
: PMP_MAP_OPCODE_TCP
),
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
);
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
;
527 purple_pmp_get_handle(void)
537 g_signal_connect(g_network_monitor_get_default(),
539 G_CALLBACK(purple_pmp_network_config_changed_cb
),
542 #else /* #ifdef NET_RT_DUMP */
544 purple_pmp_get_public_ip()
550 purple_pmp_create_map(PurplePmpType type
, unsigned short privateport
, unsigned short publicport
, int lifetime
)
556 purple_pmp_destroy_map(PurplePmpType type
, unsigned short privateport
)
566 #endif /* #if !(defined(HAVE_SYS_SYCTL_H) && defined(NET_RT_DUMP)) */