comparison libpurple/nat-pmp.c @ 15646:552be3958d6a

Added nat-pmp implementation and #ifdef'd out changes to network.c which would utilize it.
author Evan Schoenberg <evan.s@dreskin.net>
date Sun, 18 Feb 2007 18:26:55 +0000
parents
children 32c366eeeb99
comparison
equal deleted inserted replaced
15645:61b42cf81aa4 15646:552be3958d6a
1 /**
2 * @file nat-pmp.c NAT-PMP Implementation
3 * @ingroup core
4 *
5 * gaim
6 *
7 * Gaim is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * Most code in nat-pmp.c copyright (C) 2007, R. Tyler Ballance, bleep, LLC.
12 * This file is distributed under the 3-clause (modified) BSD license:
13 * Redistribution and use in source and binary forms, with or without modification, are permitted
14 * provided that the following conditions are met:
15 *
16 * Redistributions of source code must retain the above copyright notice, this list of conditions and
17 * the following disclaimer.
18 * Neither the name of the bleep. LLC nor the names of its contributors may be used to endorse or promote
19 * products derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
22 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
28 * OF SUCH DAMAGE.
29 */
30
31 #include "nat-pmp.h"
32 #include "debug.h"
33
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/sysctl.h>
37
38 #include <net/route.h>
39 #include <netinet/in.h>
40
41 #include <arpa/inet.h>
42
43 #include <netdb.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <err.h>
48
49 #include <errno.h>
50 #include <assert.h>
51 #include <sys/types.h>
52 #include <net/if.h>
53
54 #ifdef NET_RT_DUMP2
55 /*
56 * Thanks to R. Matthew Emerson for the fixes on this
57 */
58
59 /* alignment constraint for routing socket */
60 #define ROUNDUP(a) \
61 ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
62 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
63
64 static void
65 get_rtaddrs(int bitmask, struct sockaddr *sa, struct sockaddr *addrs[])
66 {
67 int i;
68
69 for (i = 0; i < RTAX_MAX; i++)
70 {
71 if (bitmask & (1 << i))
72 {
73 addrs[i] = sa;
74 sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);
75 }
76 else
77 {
78 addrs[i] = NULL;
79 }
80 }
81 }
82
83 static int
84 is_default_route(struct sockaddr *sa, struct sockaddr *mask)
85 {
86 struct sockaddr_in *sin;
87
88 if (sa->sa_family != AF_INET)
89 return 0;
90
91 sin = (struct sockaddr_in *)sa;
92 if ((sin->sin_addr.s_addr == INADDR_ANY) &&
93 mask &&
94 (ntohl(((struct sockaddr_in *)mask)->sin_addr.s_addr) == 0L ||
95 mask->sa_len == 0))
96 return 1;
97 else
98 return 0;
99 }
100
101 static struct sockaddr_in *
102 default_gw()
103 {
104 int mib[6];
105 size_t needed;
106 char *buf, *next, *lim;
107 struct rt_msghdr2 *rtm;
108 struct sockaddr *sa;
109 struct sockaddr *rti_info[RTAX_MAX];
110 struct sockaddr_in *sin;
111
112 mib[0] = CTL_NET;
113 mib[1] = PF_ROUTE;
114 mib[2] = 0;
115 mib[3] = 0;
116 mib[4] = NET_RT_DUMP2;
117 mib[5] = 0;
118
119 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
120 {
121 err(1, "sysctl: net.route.0.0.dump estimate");
122 }
123
124 buf = malloc(needed);
125
126 if (buf == 0)
127 {
128 err(2, "malloc");
129 }
130
131 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
132 {
133 err(1, "sysctl: net.route.0.0.dump");
134 }
135
136 lim = buf + needed;
137
138 for (next = buf; next < lim; next += rtm->rtm_msglen)
139 {
140 rtm = (struct rt_msghdr2 *)next;
141 sa = (struct sockaddr *)(rtm + 1);
142
143 if (sa->sa_family == AF_INET)
144 {
145 sin = (struct sockaddr_in *)sa;
146 struct sockaddr addr, mask;
147
148 get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
149 bzero(&addr, sizeof(addr));
150
151 if (rtm->rtm_addrs & RTA_DST)
152 bcopy(rti_info[RTAX_DST], &addr, rti_info[RTAX_DST]->sa_len);
153
154 bzero(&mask, sizeof(mask));
155
156 if (rtm->rtm_addrs & RTA_NETMASK)
157 bcopy(rti_info[RTAX_NETMASK], &mask, rti_info[RTAX_NETMASK]->sa_len);
158
159 if (is_default_route(&addr, &mask))
160 {
161 sin = (struct sockaddr_in *)rti_info[RTAX_GATEWAY];
162 break;
163 }
164 }
165
166 rtm = (struct rt_msghdr2 *)next;
167 }
168
169 free(buf);
170
171 return sin;
172 }
173
174 //! double_timeout(struct timeval *) will handle doubling a timeout for backoffs required by NAT-PMP
175 static void
176 double_timeout(struct timeval *to)
177 {
178 int second = 1000000; // number of useconds
179
180 to->tv_sec = (to->tv_sec * 2);
181 to->tv_usec = (to->tv_usec * 2);
182
183 // Overflow useconds if necessary
184 if (to->tv_usec >= second)
185 {
186 int overflow = (to->tv_usec / second);
187 to->tv_usec = (to->tv_usec - (overflow * second));
188 to->tv_sec = (to->tv_sec + overflow);
189 }
190 }
191
192 /*!
193 * gaim_pmp_get_public_ip() will return the publicly facing IP address of the
194 * default NAT gateway. The function will return NULL if:
195 * - The gateway doesn't support NAT-PMP
196 * - The gateway errors in some other spectacular fashion
197 */
198 char *
199 gaim_pmp_get_public_ip()
200 {
201 struct sockaddr_in *gateway = default_gw();
202
203 if (gateway == NULL)
204 {
205 gaim_debug_info("nat-pmp", "Cannot request public IP from a NULL gateway!\n");
206 return NULL;
207 }
208 if (gateway->sin_port != PMP_PORT)
209 {
210 gateway->sin_port = htons(PMP_PORT); // Default port for NAT-PMP is 5351
211 }
212
213 int sendfd;
214 int req_attempts = 1;
215 struct timeval req_timeout;
216 pmp_ip_request_t req;
217 pmp_ip_response_t resp;
218 struct sockaddr_in *publicsockaddr = NULL;
219
220 req_timeout.tv_sec = 0;
221 req_timeout.tv_usec = PMP_TIMEOUT;
222
223 sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
224
225 // Clean out both req and resp structures
226 bzero(&req, sizeof(pmp_ip_request_t));
227 bzero(&resp, sizeof(pmp_ip_response_t));
228 req.version = 0;
229 req.opcode = 0;
230
231 // Attempt to contact NAT-PMP device 9 times as per: draft-cheshire-nat-pmp-02.txt
232 while (req_attempts < 10)
233 {
234 #ifdef PMP_DEBUG
235 gaim_debug_info("nat-pmp", "Attempting to retrieve the public ip address for the NAT device at: %s\n", inet_ntoa(gateway->sin_addr));
236 gaim_debug_info("nat-pmp", "\tTimeout: %ds %dus, Request #: %d\n", req_timeout.tv_sec, req_timeout.tv_usec, req_attempts);
237 #endif
238 struct sockaddr_in addr;
239 socklen_t len = sizeof(struct sockaddr_in);
240
241 if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0)
242 {
243 gaim_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", strerror(errno));
244 return NULL;
245 }
246
247 if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0)
248 {
249 gaim_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", strerror(errno));
250 return NULL;
251 }
252
253 if (recvfrom(sendfd, &resp, sizeof(pmp_ip_response_t), 0, (struct sockaddr *)(&addr), &len) < 0)
254 {
255 if ( (errno != EAGAIN) || (req_attempts == 9) )
256 {
257 gaim_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", strerror(errno));
258 return NULL;
259 }
260 else
261 {
262 goto iterate;
263 }
264 }
265
266 if (addr.sin_addr.s_addr != gateway->sin_addr.s_addr)
267 {
268 gaim_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr.sin_addr));
269 goto iterate;
270 }
271 else
272 {
273 publicsockaddr = &addr;
274 break;
275 }
276
277 iterate:
278 ++req_attempts;
279 double_timeout(&req_timeout);
280 }
281
282 if (publicsockaddr == NULL)
283 return NULL;
284
285 #ifdef PMP_DEBUG
286 gaim_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
287 gaim_debug_info("nat-pmp", "version: %d\n", resp.version);
288 gaim_debug_info("nat-pmp", "opcode: %d\n", resp.opcode);
289 gaim_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp.resultcode));
290 gaim_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp.epoch));
291 struct in_addr in;
292 in.s_addr = resp.address;
293 gaim_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in));
294 #endif
295
296 publicsockaddr->sin_addr.s_addr = resp.address;
297
298 return inet_ntoa(publicsockaddr->sin_addr);
299 }
300
301 /*!
302 * will return NULL on error, or a pointer to the pmp_map_response_t type
303 */
304 pmp_map_response_t *
305 gaim_pmp_create_map(uint8_t type, uint16_t privateport, uint16_t publicport, uint32_t lifetime)
306 {
307 struct sockaddr_in *gateway = default_gw();
308
309 if (gateway == NULL)
310 {
311 gaim_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
312 return NULL;
313 }
314 if (gateway->sin_port != PMP_PORT)
315 {
316 gateway->sin_port = htons(PMP_PORT); // Default port for NAT-PMP is 5351
317 }
318
319 int sendfd;
320 int req_attempts = 1;
321 struct timeval req_timeout;
322 pmp_map_request_t req;
323 pmp_map_response_t *resp = (pmp_map_response_t *)(malloc(sizeof(pmp_map_response_t)));
324
325 req_timeout.tv_sec = 0;
326 req_timeout.tv_usec = PMP_TIMEOUT;
327
328 sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
329
330 // Clean out both req and resp structures
331 bzero(&req, sizeof(pmp_map_request_t));
332 bzero(resp, sizeof(pmp_map_response_t));
333 req.version = 0;
334 req.opcode = type;
335 req.privateport = htons(privateport); // What a difference byte ordering makes...d'oh!
336 req.publicport = htons(publicport);
337 req.lifetime = htonl(lifetime);
338
339 // Attempt to contact NAT-PMP device 9 times as per: draft-cheshire-nat-pmp-02.txt
340 while (req_attempts < 10)
341 {
342 #ifdef PMP_DEBUG
343 gaim_debug_info("nat-pmp", "Attempting to create a NAT-PMP mapping the private port %d, and the public port %d\n", privateport, publicport);
344 gaim_debug_info("nat-pmp", "\tTimeout: %ds %dus, Request #: %d\n", req_timeout.tv_sec, req_timeout.tv_usec, req_attempts);
345 #endif
346
347 if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0)
348 {
349 gaim_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", strerror(errno));
350 return NULL;
351 }
352
353 if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0)
354 {
355 gaim_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", strerror(errno));
356 return NULL;
357 }
358
359 if (recvfrom(sendfd, resp, sizeof(pmp_map_response_t), 0, NULL, NULL) < 0)
360 {
361 if ( (errno != EAGAIN) || (req_attempts == 9) )
362 {
363 gaim_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", strerror(errno));
364 return NULL;
365 }
366 else
367 {
368 goto iterate;
369 }
370 }
371
372 if (resp->opcode != (req.opcode + 128))
373 {
374 gaim_debug_info("nat-pmp", "The opcode for the response from the NAT device does not match the request opcode!\n");
375 goto iterate;
376 }
377
378 break;
379
380 iterate:
381 ++req_attempts;
382 double_timeout(&req_timeout);
383 }
384
385 #ifdef PMP_DEBUG
386 gaim_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
387 gaim_debug_info("nat-pmp", "version: %d\n", resp->version);
388 gaim_debug_info("nat-pmp", "opcode: %d\n", resp->opcode);
389 gaim_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp->resultcode));
390 gaim_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp->epoch));
391 gaim_debug_info("nat-pmp", "privateport: %d\n", ntohs(resp->privateport));
392 gaim_debug_info("nat-pmp", "publicport: %d\n", ntohs(resp->publicport));
393 gaim_debug_info("nat-pmp", "lifetime: %d\n", ntohl(resp->lifetime));
394 #endif
395
396 return resp;
397 }
398
399 /*!
400 * pmp_destroy_map(uint8_t,uint16_t)
401 * will return NULL on error, or a pointer to the pmp_map_response_t type
402 */
403 pmp_map_response_t *
404 gaim_pmp_destroy_map(uint8_t type, uint16_t privateport)
405 {
406 pmp_map_response_t *response = NULL;
407
408 if ((response = gaim_pmp_create_map(type, privateport, 0, 0)) == NULL)
409 {
410 gaim_debug_info("nat-pmp", "Failed to properly destroy mapping for %d!\n", privateport);
411 return NULL;
412 }
413 else
414 {
415 return response;
416 }
417 }
418 #else /* #ifdef NET_RT_DUMP2 */
419 char *
420 gaim_pmp_get_public_ip()
421 {
422 return NULL;
423 }
424
425 pmp_map_response_t *
426 gaim_pmp_create_map(uint8_t type, uint16_t privateport, uint16_t publicport, uint32_t lifetime)
427 {
428 return NULL;
429 }
430
431 pmp_map_response_t *
432 gaim_pmp_destroy_map(uint8_t type, uint16_t privateport)
433 {
434 return NULL;
435 }
436 #endif /* #ifndef NET_RT_DUMP2 */