Mercurial > pidgin
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 */ |