2 * @file nat-pmp.c NAT-PMP Implementation
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
38 #ifdef HAVE_SYS_PARAM_H
39 #include <sys/param.h>
42 #ifdef HAVE_SYS_SYSCTL_H
43 #include <sys/sysctl.h>
46 #ifdef HAVE_SYS_SOCKET_H
47 #include <sys/socket.h>
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>
66 guint8 opcode
; /* 128 + n */
70 } PurplePmpIpResponse
;
79 } PurplePmpMapRequest
;
81 struct _PurplePmpMapResponse
{
91 typedef struct _PurplePmpMapResponse PurplePmpMapResponse
;
94 PURPLE_PMP_STATUS_UNDISCOVERED
= -1,
95 PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
,
96 PURPLE_PMP_STATUS_DISCOVERING
,
97 PURPLE_PMP_STATUS_DISCOVERED
101 PurpleUPnPStatus status
;
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))
123 get_rtaddrs(int bitmask
, struct sockaddr
*sa
, struct sockaddr
*addrs
[])
127 for (i
= 0; i
< RTAX_MAX
; i
++)
129 if (bitmask
& (1 << i
))
132 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
133 sa
= (struct sockaddr
*)(ROUNDUP(sa
->sa_len
) + (char *)sa
);
135 if (sa
->sa_family
== AF_INET
)
136 sa
= (struct sockaddr
*)(sizeof(struct sockaddr_in
) + (char *)sa
);
138 else if (sa
->sa_family
== AF_INET6
)
139 sa
= (struct sockaddr
*)(sizeof(struct sockaddr_in6
) + (char *)sa
);
151 is_default_route(struct sockaddr
*sa
, struct sockaddr
*mask
)
153 struct sockaddr_in
*sin
;
155 if (sa
->sa_family
!= AF_INET
)
158 sin
= (struct sockaddr_in
*)sa
;
159 if ((sin
->sin_addr
.s_addr
== INADDR_ANY
) &&
161 (ntohl(((struct sockaddr_in
*)mask
)->sin_addr
.s_addr
) == 0L ||
162 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
174 * The return sockaddr_in must be g_free()'d when no longer needed
176 static struct sockaddr_in
*
181 char *buf
, *next
, *lim
;
182 struct rt_msghdr
*rtm
;
184 struct sockaddr_in
*sin
= NULL
;
187 mib
[1] = PF_ROUTE
; /* entire routing table or a subset of it */
188 mib
[2] = 0; /* protocol number - always 0 */
189 mib
[3] = 0; /* address family - 0 for all addres families */
190 mib
[4] = NET_RT_DUMP
;
193 /* Determine the buffer side needed to get the full routing table */
194 if (sysctl(mib
, 6, NULL
, &needed
, NULL
, 0) < 0)
196 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate\n");
200 if (!(buf
= malloc(needed
)))
202 purple_debug_warning("nat-pmp", "Failed to malloc %" G_GSIZE_FORMAT
"\n", needed
);
206 /* Read the routing table into buf */
207 if (sysctl(mib
, 6, buf
, &needed
, NULL
, 0) < 0)
210 purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump\n");
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 struct sockaddr_in
*cursin
= (struct sockaddr_in
*) sa
;
225 if ((rtm
->rtm_flags
& RTF_GATEWAY
)
226 && cursin
->sin_addr
.s_addr
== INADDR_ANY
)
228 /* We found the default route. Now get the destination address and netmask. */
229 struct sockaddr
*rti_info
[RTAX_MAX
];
230 struct sockaddr addr
, mask
;
232 get_rtaddrs(rtm
->rtm_addrs
, sa
, rti_info
);
233 memset(&addr
, 0, sizeof(addr
));
235 if (rtm
->rtm_addrs
& RTA_DST
)
236 memcpy(&addr
, rti_info
[RTAX_DST
], sizeof(addr
));
238 memset(&mask
, 0, sizeof(mask
));
240 if (rtm
->rtm_addrs
& RTA_NETMASK
)
241 memcpy(&mask
, rti_info
[RTAX_NETMASK
], sizeof(mask
));
243 if (rtm
->rtm_addrs
& RTA_GATEWAY
&&
244 is_default_route(&addr
, &mask
))
246 if (rti_info
[RTAX_GATEWAY
]) {
247 struct sockaddr_in
*rti_sin
= (struct sockaddr_in
*)rti_info
[RTAX_GATEWAY
];
248 sin
= g_new0(struct sockaddr_in
, 1);
249 sin
->sin_family
= rti_sin
->sin_family
;
250 sin
->sin_port
= rti_sin
->sin_port
;
251 sin
->sin_addr
.s_addr
= rti_sin
->sin_addr
.s_addr
;
252 memcpy(sin
, rti_info
[RTAX_GATEWAY
], sizeof(struct sockaddr_in
));
254 purple_debug_info("nat-pmp", "Found a default gateway\n");
267 * purple_pmp_get_public_ip() will return the publicly facing IP address of the
268 * default NAT gateway. The function will return NULL if:
269 * - The gateway doesn't support NAT-PMP
270 * - The gateway errors in some other spectacular fashion
273 purple_pmp_get_public_ip()
275 struct sockaddr_in addr
, *gateway
, *publicsockaddr
= NULL
;
276 struct timeval req_timeout
;
279 PurplePmpIpRequest req
;
280 PurplePmpIpResponse resp
;
283 if (pmp_info
.status
== PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
)
286 if ((pmp_info
.status
== PURPLE_PMP_STATUS_DISCOVERED
) && (pmp_info
.publicip
!= NULL
))
289 purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info
.publicip
);
291 return pmp_info
.publicip
;
294 gateway
= default_gw();
298 purple_debug_info("nat-pmp", "Cannot request public IP from a NULL gateway!\n");
299 /* If we get a NULL gateway, don't try again next time */
300 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
304 /* Default port for NAT-PMP is 5351 */
305 if (gateway
->sin_port
!= PMP_PORT
)
306 gateway
->sin_port
= htons(PMP_PORT
);
308 req_timeout
.tv_sec
= 0;
309 req_timeout
.tv_usec
= PMP_TIMEOUT
;
311 sendfd
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
313 /* Clean out both req and resp structures */
314 memset(&req
, 0, sizeof(PurplePmpIpRequest
));
315 memset(&resp
, 0, sizeof(PurplePmpIpResponse
));
319 /* The NAT-PMP spec says we should attempt to contact the gateway 9 times, doubling the time we wait each time.
320 * Even starting with a timeout of 0.1 seconds, that means that we have a total waiting of 204.6 seconds.
321 * With the recommended timeout of 0.25 seconds, we're talking 511.5 seconds (8.5 minutes).
323 * This seems really silly... if this were nonblocking, a couple retries might be in order, but it's not at present.
326 purple_debug_info("nat-pmp", "Attempting to retrieve the public ip address for the NAT device at: %s\n", inet_ntoa(gateway
->sin_addr
));
327 purple_debug_info("nat-pmp", "\tTimeout: %ds %dus\n", req_timeout
.tv_sec
, req_timeout
.tv_usec
);
330 /* TODO: Non-blocking! */
332 if (sendto(sendfd
, &req
, sizeof(req
), 0, (struct sockaddr
*)(gateway
), sizeof(struct sockaddr
)) < 0)
334 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", g_strerror(errno
));
336 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
340 if (setsockopt(sendfd
, SOL_SOCKET
, SO_RCVTIMEO
, &req_timeout
, sizeof(req_timeout
)) < 0)
342 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno
));
344 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
348 /* TODO: Non-blocking! */
349 len
= sizeof(struct sockaddr_in
);
350 if (recvfrom(sendfd
, &resp
, sizeof(PurplePmpIpResponse
), 0, (struct sockaddr
*)(&addr
), &len
) < 0)
354 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno
));
356 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
361 if (addr
.sin_addr
.s_addr
== gateway
->sin_addr
.s_addr
)
362 publicsockaddr
= &addr
;
365 purple_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr
.sin_addr
));
368 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
372 if (!publicsockaddr
) {
375 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
380 purple_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
381 purple_debug_info("nat-pmp", "version: %d\n", resp
.version
);
382 purple_debug_info("nat-pmp", "opcode: %d\n", resp
.opcode
);
383 purple_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp
.resultcode
));
384 purple_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp
.epoch
));
386 in
.s_addr
= resp
.address
;
387 purple_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in
));
390 publicsockaddr
->sin_addr
.s_addr
= resp
.address
;
394 g_free(pmp_info
.publicip
);
395 pmp_info
.publicip
= g_strdup(inet_ntoa(publicsockaddr
->sin_addr
));
396 pmp_info
.status
= PURPLE_PMP_STATUS_DISCOVERED
;
398 return inet_ntoa(publicsockaddr
->sin_addr
);
402 purple_pmp_create_map(PurplePmpType type
, unsigned short privateport
, unsigned short publicport
, int lifetime
)
404 struct sockaddr_in
*gateway
;
405 gboolean success
= TRUE
;
407 struct timeval req_timeout
;
408 PurplePmpMapRequest req
;
409 PurplePmpMapResponse
*resp
;
411 gateway
= default_gw();
415 purple_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
419 /* Default port for NAT-PMP is 5351 */
420 if (gateway
->sin_port
!= PMP_PORT
)
421 gateway
->sin_port
= htons(PMP_PORT
);
423 resp
= g_new0(PurplePmpMapResponse
, 1);
425 req_timeout
.tv_sec
= 0;
426 req_timeout
.tv_usec
= PMP_TIMEOUT
;
428 sendfd
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
431 memset(&req
, 0, sizeof(PurplePmpMapRequest
));
433 req
.opcode
= ((type
== PURPLE_PMP_TYPE_UDP
) ? PMP_MAP_OPCODE_UDP
: PMP_MAP_OPCODE_TCP
);
434 req
.privateport
= htons(privateport
); /* What a difference byte ordering makes...d'oh! */
435 req
.publicport
= htons(publicport
);
436 req
.lifetime
= htonl(lifetime
);
438 /* The NAT-PMP spec says we should attempt to contact the gateway 9 times, doubling the time we wait each time.
439 * Even starting with a timeout of 0.1 seconds, that means that we have a total waiting of 204.6 seconds.
440 * With the recommended timeout of 0.25 seconds, we're talking 511.5 seconds (8.5 minutes).
442 * This seems really silly... if this were nonblocking, a couple retries might be in order, but it's not at present.
443 * XXX Make this nonblocking.
444 * XXX This code looks like the pmp_get_public_ip() code. Can it be consolidated?
447 purple_debug_info("nat-pmp", "Attempting to create a NAT-PMP mapping the private port %d, and the public port %d\n", privateport
, publicport
);
448 purple_debug_info("nat-pmp", "\tTimeout: %ds %dus\n", req_timeout
.tv_sec
, req_timeout
.tv_usec
);
451 /* TODO: Non-blocking! */
452 success
= (sendto(sendfd
, &req
, sizeof(req
), 0, (struct sockaddr
*)(gateway
), sizeof(struct sockaddr
)) >= 0);
454 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", g_strerror(errno
));
458 success
= (setsockopt(sendfd
, SOL_SOCKET
, SO_RCVTIMEO
, &req_timeout
, sizeof(req_timeout
)) >= 0);
460 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno
));
465 /* The original code treats EAGAIN as a reason to iterate.. but I've removed iteration. This may be a problem */
466 /* TODO: Non-blocking! */
467 success
= ((recvfrom(sendfd
, resp
, sizeof(PurplePmpMapResponse
), 0, NULL
, NULL
) >= 0) ||
470 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno
));
475 success
= (resp
->opcode
== (req
.opcode
+ 128));
477 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",
478 resp
->opcode
, req
.opcode
, req
.opcode
+ 128);
484 purple_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
485 purple_debug_info("nat-pmp", "version: %d\n", resp
->version
);
486 purple_debug_info("nat-pmp", "opcode: %d\n", resp
->opcode
);
487 purple_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp
->resultcode
));
488 purple_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp
->epoch
));
489 purple_debug_info("nat-pmp", "privateport: %d\n", ntohs(resp
->privateport
));
490 purple_debug_info("nat-pmp", "publicport: %d\n", ntohs(resp
->publicport
));
491 purple_debug_info("nat-pmp", "lifetime: %d\n", ntohl(resp
->lifetime
));
498 /* XXX The private port may actually differ from the one we requested, according to the spec.
499 * We don't handle that situation at present.
501 * TODO: Look at the result and verify it matches what we wanted; either return a failure if it doesn't,
502 * or change network.c to know what to do if the desired private port shifts as a result of the nat-pmp operation.
508 purple_pmp_destroy_map(PurplePmpType type
, unsigned short privateport
)
512 success
= purple_pmp_create_map(((type
== PURPLE_PMP_TYPE_UDP
) ? PMP_MAP_OPCODE_UDP
: PMP_MAP_OPCODE_TCP
),
515 purple_debug_warning("nat-pmp", "Failed to properly destroy mapping for %s port %d!\n",
516 ((type
== PURPLE_PMP_TYPE_UDP
) ? "UDP" : "TCP"), privateport
);
522 purple_pmp_network_config_changed_cb(void *data
)
524 pmp_info
.status
= PURPLE_PMP_STATUS_UNDISCOVERED
;
525 g_free(pmp_info
.publicip
);
526 pmp_info
.publicip
= NULL
;
530 purple_pmp_get_handle(void)
540 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
541 purple_pmp_get_handle(), PURPLE_CALLBACK(purple_pmp_network_config_changed_cb
),
544 #else /* #ifdef NET_RT_DUMP */
546 purple_pmp_get_public_ip()
552 purple_pmp_create_map(PurplePmpType type
, unsigned short privateport
, unsigned short publicport
, int lifetime
)
558 purple_pmp_destroy_map(PurplePmpType type
, unsigned short privateport
)
568 #endif /* #if !(defined(HAVE_SYS_SYCTL_H) && defined(NET_RT_DUMP)) */