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
;
185 gboolean found
= FALSE
;
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
;
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");
201 if (!(buf
= malloc(needed
)))
203 purple_debug_warning("nat-pmp", "Failed to malloc %" G_GSIZE_FORMAT
"\n", needed
);
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");
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");
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
272 purple_pmp_get_public_ip()
274 struct sockaddr_in addr
, *gateway
, *publicsockaddr
= NULL
;
275 struct timeval req_timeout
;
278 PurplePmpIpRequest req
;
279 PurplePmpIpResponse resp
;
282 if (pmp_info
.status
== PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
)
285 if ((pmp_info
.status
== PURPLE_PMP_STATUS_DISCOVERED
) && (pmp_info
.publicip
!= NULL
))
288 purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info
.publicip
);
290 return pmp_info
.publicip
;
293 gateway
= default_gw();
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
;
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
));
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.
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
);
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
));
335 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
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
));
343 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
347 /* TODO: Non-blocking! */
348 len
= sizeof(struct sockaddr_in
);
349 if (recvfrom(sendfd
, &resp
, sizeof(PurplePmpIpResponse
), 0, (struct sockaddr
*)(&addr
), &len
) < 0)
353 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno
));
355 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
360 if (addr
.sin_addr
.s_addr
== gateway
->sin_addr
.s_addr
)
361 publicsockaddr
= &addr
;
364 purple_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr
.sin_addr
));
367 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
371 if (!publicsockaddr
) {
374 pmp_info
.status
= PURPLE_PMP_STATUS_UNABLE_TO_DISCOVER
;
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
));
385 in
.s_addr
= resp
.address
;
386 purple_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in
));
389 publicsockaddr
->sin_addr
.s_addr
= resp
.address
;
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
);
401 purple_pmp_create_map(PurplePmpType type
, unsigned short privateport
, unsigned short publicport
, int lifetime
)
403 struct sockaddr_in
*gateway
;
404 gboolean success
= TRUE
;
406 struct timeval req_timeout
;
407 PurplePmpMapRequest req
;
408 PurplePmpMapResponse
*resp
;
410 gateway
= default_gw();
414 purple_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
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
);
430 memset(&req
, 0, sizeof(PurplePmpMapRequest
));
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?
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
);
450 /* TODO: Non-blocking! */
451 success
= (sendto(sendfd
, &req
, sizeof(req
), 0, (struct sockaddr
*)(gateway
), sizeof(struct sockaddr
)) >= 0);
453 purple_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", g_strerror(errno
));
457 success
= (setsockopt(sendfd
, SOL_SOCKET
, SO_RCVTIMEO
, &req_timeout
, sizeof(req_timeout
)) >= 0);
459 purple_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", g_strerror(errno
));
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) ||
469 purple_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", g_strerror(errno
));
474 success
= (resp
->opcode
== (req
.opcode
+ 128));
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);
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
));
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.
507 purple_pmp_destroy_map(PurplePmpType type
, unsigned short privateport
)
511 success
= purple_pmp_create_map(((type
== PURPLE_PMP_TYPE_UDP
) ? PMP_MAP_OPCODE_UDP
: PMP_MAP_OPCODE_TCP
),
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
);
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
;
529 purple_pmp_get_handle(void)
539 purple_signal_connect(purple_network_get_handle(), "network-configuration-changed",
540 purple_pmp_get_handle(), PURPLE_CALLBACK(purple_pmp_network_config_changed_cb
),
543 #else /* #ifdef NET_RT_DUMP */
545 purple_pmp_get_public_ip()
551 purple_pmp_create_map(PurplePmpType type
, unsigned short privateport
, unsigned short publicport
, int lifetime
)
557 purple_pmp_destroy_map(PurplePmpType type
, unsigned short privateport
)
567 #endif /* #if !(defined(HAVE_SYS_SYCTL_H) && defined(NET_RT_DUMP)) */