/src/pidgin/libpurple/stun.c
Line | Count | Source |
1 | | /** |
2 | | * @file stun.c STUN (RFC3489) Implementation |
3 | | * @ingroup core |
4 | | */ |
5 | | |
6 | | /* purple |
7 | | * |
8 | | * STUN implementation inspired by jstun [http://jstun.javawi.de/] |
9 | | * |
10 | | * Purple is the legal property of its developers, whose names are too numerous |
11 | | * to list here. Please refer to the COPYRIGHT file distributed with this |
12 | | * source distribution. |
13 | | * |
14 | | * This program is free software; you can redistribute it and/or modify |
15 | | * it under the terms of the GNU General Public License as published by |
16 | | * the Free Software Foundation; either version 2 of the License, or |
17 | | * (at your option) any later version. |
18 | | * |
19 | | * This program is distributed in the hope that it will be useful, |
20 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | | * GNU General Public License for more details. |
23 | | * |
24 | | * You should have received a copy of the GNU General Public License |
25 | | * along with this program; if not, write to the Free Software |
26 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
27 | | * |
28 | | */ |
29 | | |
30 | | #include "internal.h" |
31 | | |
32 | | #ifndef _WIN32 |
33 | | #include <net/if.h> |
34 | | #include <sys/ioctl.h> |
35 | | #endif |
36 | | |
37 | | /* Solaris */ |
38 | | #if defined (__SVR4) && defined (__sun) |
39 | | #include <sys/sockio.h> |
40 | | #endif |
41 | | |
42 | | #ifdef __HAIKU__ |
43 | | # ifndef SIOCGIFCONF |
44 | | # include <sys/sockio.h> |
45 | | # endif |
46 | | #endif |
47 | | |
48 | | #include "debug.h" |
49 | | #include "account.h" |
50 | | #include "dnsquery.h" |
51 | | #include "dnssrv.h" |
52 | | #include "network.h" |
53 | | #include "proxy.h" |
54 | | #include "stun.h" |
55 | | #include "prefs.h" |
56 | | |
57 | | #define MSGTYPE_BINDINGREQUEST 0x0001 |
58 | 0 | #define MSGTYPE_BINDINGRESPONSE 0x0101 |
59 | | |
60 | | #define ATTRIB_MAPPEDADDRESS 0x0001 |
61 | | |
62 | | struct stun_header { |
63 | | guint16 type; |
64 | | guint16 len; |
65 | | guint32 transid[4]; |
66 | | }; |
67 | | |
68 | | struct stun_attrib { |
69 | | guint16 type; |
70 | | guint16 len; |
71 | | }; |
72 | | |
73 | | #ifdef NOTYET |
74 | | struct stun_change { |
75 | | struct stun_header hdr; |
76 | | struct stun_attrib attrib; |
77 | | char value[4]; |
78 | | }; |
79 | | #endif |
80 | | |
81 | | struct stun_conn { |
82 | | int fd; |
83 | | struct sockaddr_in addr; |
84 | | int test; |
85 | | int retry; |
86 | | guint incb; |
87 | | guint timeout; |
88 | | struct stun_header *packet; |
89 | | size_t packetsize; |
90 | | }; |
91 | | |
92 | | static PurpleStunNatDiscovery nattype = { |
93 | | PURPLE_STUN_STATUS_UNDISCOVERED, |
94 | | PURPLE_STUN_NAT_TYPE_PUBLIC_IP, |
95 | | "\0", NULL, 0}; |
96 | | |
97 | | static GSList *callbacks = NULL; |
98 | | |
99 | 0 | static void close_stun_conn(struct stun_conn *sc) { |
100 | |
|
101 | 0 | if (sc->incb) |
102 | 0 | purple_input_remove(sc->incb); |
103 | |
|
104 | 0 | if (sc->timeout) |
105 | 0 | purple_timeout_remove(sc->timeout); |
106 | |
|
107 | 0 | if (sc->fd) |
108 | 0 | close(sc->fd); |
109 | |
|
110 | 0 | g_free(sc); |
111 | 0 | } |
112 | | |
113 | 0 | static void do_callbacks(void) { |
114 | 0 | while (callbacks) { |
115 | 0 | StunCallback cb = callbacks->data; |
116 | 0 | if (cb) |
117 | 0 | cb(&nattype); |
118 | 0 | callbacks = g_slist_delete_link(callbacks, callbacks); |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | 0 | static gboolean timeoutfunc(gpointer data) { |
123 | 0 | struct stun_conn *sc = data; |
124 | 0 | if(sc->retry >= 2) { |
125 | 0 | purple_debug_warning("stun", "request timed out, giving up.\n"); |
126 | 0 | if(sc->test == 2) |
127 | 0 | nattype.type = PURPLE_STUN_NAT_TYPE_SYMMETRIC; |
128 | | |
129 | | /* set unknown */ |
130 | 0 | nattype.status = PURPLE_STUN_STATUS_UNKNOWN; |
131 | |
|
132 | 0 | nattype.lookup_time = time(NULL); |
133 | | |
134 | | /* callbacks */ |
135 | 0 | do_callbacks(); |
136 | | |
137 | | /* we don't need to remove the timeout (returning FALSE) */ |
138 | 0 | sc->timeout = 0; |
139 | 0 | close_stun_conn(sc); |
140 | |
|
141 | 0 | return FALSE; |
142 | 0 | } |
143 | 0 | purple_debug_info("stun", "request timed out, retrying.\n"); |
144 | 0 | sc->retry++; |
145 | 0 | if (sendto(sc->fd, sc->packet, sc->packetsize, 0, |
146 | 0 | (struct sockaddr *)&(sc->addr), sizeof(struct sockaddr_in)) != |
147 | 0 | (gssize)sc->packetsize) |
148 | 0 | { |
149 | 0 | purple_debug_warning("stun", "sendto failed\n"); |
150 | 0 | return FALSE; |
151 | 0 | } |
152 | 0 | return TRUE; |
153 | 0 | } |
154 | | |
155 | | #ifdef NOTYET |
156 | | static void do_test2(struct stun_conn *sc) { |
157 | | struct stun_change data; |
158 | | data.hdr.type = htons(0x0001); |
159 | | data.hdr.len = 0; |
160 | | data.hdr.transid[0] = rand(); |
161 | | data.hdr.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m'); |
162 | | data.hdr.transid[2] = rand(); |
163 | | data.hdr.transid[3] = rand(); |
164 | | data.attrib.type = htons(0x003); |
165 | | data.attrib.len = htons(4); |
166 | | data.value[3] = 6; |
167 | | sc->packet = (struct stun_header*)&data; |
168 | | sc->packetsize = sizeof(struct stun_change); |
169 | | sc->retry = 0; |
170 | | sc->test = 2; |
171 | | sendto(sc->fd, sc->packet, sc->packetsize, 0, (struct sockaddr *)&(sc->addr), sizeof(struct sockaddr_in)); |
172 | | sc->timeout = purple_timeout_add(500, (GSourceFunc) timeoutfunc, sc); |
173 | | } |
174 | | #endif |
175 | | |
176 | 0 | static void reply_cb(gpointer data, gint source, PurpleInputCondition cond) { |
177 | 0 | struct stun_conn *sc = data; |
178 | 0 | char buffer[65536]; |
179 | 0 | char *tmp; |
180 | 0 | gssize len; |
181 | 0 | struct in_addr in; |
182 | 0 | struct stun_attrib *attrib; |
183 | 0 | struct stun_header *hdr; |
184 | 0 | struct ifconf ifc; |
185 | 0 | struct ifreq *ifr; |
186 | 0 | struct sockaddr_in *sinptr; |
187 | |
|
188 | 0 | memset(&in, 0, sizeof(in)); |
189 | |
|
190 | 0 | len = recv(source, buffer, sizeof(buffer) - 1, 0); |
191 | 0 | if (len < 0) { |
192 | 0 | purple_debug_warning("stun", "unable to read stun response\n"); |
193 | 0 | return; |
194 | 0 | } |
195 | 0 | buffer[len] = '\0'; |
196 | |
|
197 | 0 | if ((gsize)len < sizeof(struct stun_header)) { |
198 | 0 | purple_debug_warning("stun", "got invalid response\n"); |
199 | 0 | return; |
200 | 0 | } |
201 | | |
202 | 0 | hdr = (struct stun_header*) buffer; |
203 | 0 | if ((gsize)len != (ntohs(hdr->len) + sizeof(struct stun_header))) { |
204 | 0 | purple_debug_warning("stun", "got incomplete response\n"); |
205 | 0 | return; |
206 | 0 | } |
207 | | |
208 | | /* wrong transaction */ |
209 | 0 | if(hdr->transid[0] != sc->packet->transid[0] |
210 | 0 | || hdr->transid[1] != sc->packet->transid[1] |
211 | 0 | || hdr->transid[2] != sc->packet->transid[2] |
212 | 0 | || hdr->transid[3] != sc->packet->transid[3]) { |
213 | 0 | purple_debug_warning("stun", "got wrong transid\n"); |
214 | 0 | return; |
215 | 0 | } |
216 | | |
217 | 0 | if(sc->test==1) { |
218 | 0 | if (hdr->type != MSGTYPE_BINDINGRESPONSE) { |
219 | 0 | purple_debug_warning("stun", |
220 | 0 | "Expected Binding Response, got %d\n", |
221 | 0 | hdr->type); |
222 | 0 | return; |
223 | 0 | } |
224 | | |
225 | 0 | tmp = buffer + sizeof(struct stun_header); |
226 | 0 | while((buffer + len) > (tmp + sizeof(struct stun_attrib))) { |
227 | 0 | attrib = (struct stun_attrib*) tmp; |
228 | 0 | tmp += sizeof(struct stun_attrib); |
229 | |
|
230 | 0 | if (!((buffer + len) > (tmp + ntohs(attrib->len)))) |
231 | 0 | break; |
232 | | |
233 | 0 | if(attrib->type == htons(ATTRIB_MAPPEDADDRESS) |
234 | 0 | && ntohs(attrib->len) == 8) { |
235 | 0 | char *ip; |
236 | | /* Skip the first unused byte, |
237 | | * the family(1 byte), and the port(2 bytes); |
238 | | * then read the 4 byte IPv4 address */ |
239 | 0 | memcpy(&in.s_addr, tmp + 4, 4); |
240 | 0 | ip = inet_ntoa(in); |
241 | 0 | if(ip) |
242 | 0 | g_strlcpy(nattype.publicip, ip, sizeof(nattype.publicip)); |
243 | 0 | } |
244 | |
|
245 | 0 | tmp += ntohs(attrib->len); |
246 | 0 | } |
247 | 0 | purple_debug_info("stun", "got public ip %s\n", nattype.publicip); |
248 | 0 | nattype.status = PURPLE_STUN_STATUS_DISCOVERED; |
249 | 0 | nattype.type = PURPLE_STUN_NAT_TYPE_UNKNOWN_NAT; |
250 | 0 | nattype.lookup_time = time(NULL); |
251 | | |
252 | | /* is it a NAT? */ |
253 | |
|
254 | 0 | ifc.ifc_len = sizeof(buffer); |
255 | 0 | ifc.ifc_req = (struct ifreq *) buffer; |
256 | 0 | ioctl(source, SIOCGIFCONF, &ifc); |
257 | |
|
258 | 0 | tmp = buffer; |
259 | 0 | while(tmp < buffer + ifc.ifc_len) { |
260 | 0 | ifr = (struct ifreq *) tmp; |
261 | |
|
262 | 0 | tmp += sizeof(struct ifreq); |
263 | |
|
264 | 0 | if(ifr->ifr_addr.sa_family == AF_INET) { |
265 | | /* we only care about ipv4 interfaces */ |
266 | 0 | sinptr = (struct sockaddr_in *) &ifr->ifr_addr; |
267 | 0 | if(sinptr->sin_addr.s_addr == in.s_addr) { |
268 | | /* no NAT */ |
269 | 0 | purple_debug_info("stun", "no nat\n"); |
270 | 0 | nattype.type = PURPLE_STUN_NAT_TYPE_PUBLIC_IP; |
271 | 0 | } |
272 | 0 | } |
273 | 0 | } |
274 | |
|
275 | 0 | #ifndef NOTYET |
276 | 0 | close_stun_conn(sc); |
277 | 0 | do_callbacks(); |
278 | | #else |
279 | | purple_timeout_remove(sc->timeout); |
280 | | sc->timeout = 0; |
281 | | |
282 | | do_test2(sc); |
283 | | } else if(sc->test == 2) { |
284 | | close_stun_conn(sc); |
285 | | nattype.type = PURPLE_STUN_NAT_TYPE_FULL_CONE; |
286 | | do_callbacks(); |
287 | | #endif |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | |
292 | 0 | static void hbn_listen_cb(int fd, gpointer data) { |
293 | 0 | GSList *hosts = data; |
294 | 0 | struct stun_conn *sc; |
295 | 0 | static struct stun_header hdr_data; |
296 | |
|
297 | 0 | if(fd < 0) { |
298 | 0 | nattype.status = PURPLE_STUN_STATUS_UNKNOWN; |
299 | 0 | nattype.lookup_time = time(NULL); |
300 | 0 | do_callbacks(); |
301 | 0 | return; |
302 | 0 | } |
303 | | |
304 | 0 | sc = g_new0(struct stun_conn, 1); |
305 | 0 | sc->fd = fd; |
306 | |
|
307 | 0 | sc->addr.sin_family = AF_INET; |
308 | 0 | sc->addr.sin_port = htons(purple_network_get_port_from_fd(fd)); |
309 | 0 | sc->addr.sin_addr.s_addr = INADDR_ANY; |
310 | |
|
311 | 0 | sc->incb = purple_input_add(fd, PURPLE_INPUT_READ, reply_cb, sc); |
312 | |
|
313 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
314 | 0 | memcpy(&(sc->addr), hosts->data, sizeof(struct sockaddr_in)); |
315 | 0 | g_free(hosts->data); |
316 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
317 | 0 | while (hosts) { |
318 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
319 | 0 | g_free(hosts->data); |
320 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
321 | 0 | } |
322 | |
|
323 | 0 | hdr_data.type = htons(MSGTYPE_BINDINGREQUEST); |
324 | 0 | hdr_data.len = 0; |
325 | 0 | hdr_data.transid[0] = rand(); |
326 | 0 | hdr_data.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m'); |
327 | 0 | hdr_data.transid[2] = rand(); |
328 | 0 | hdr_data.transid[3] = rand(); |
329 | |
|
330 | 0 | if(sendto(sc->fd, &hdr_data, sizeof(struct stun_header), 0, |
331 | 0 | (struct sockaddr *)&(sc->addr), |
332 | 0 | sizeof(struct sockaddr_in)) < (gssize)sizeof(struct stun_header)) { |
333 | 0 | nattype.status = PURPLE_STUN_STATUS_UNKNOWN; |
334 | 0 | nattype.lookup_time = time(NULL); |
335 | 0 | do_callbacks(); |
336 | 0 | close_stun_conn(sc); |
337 | 0 | return; |
338 | 0 | } |
339 | 0 | sc->test = 1; |
340 | 0 | sc->packet = &hdr_data; |
341 | 0 | sc->packetsize = sizeof(struct stun_header); |
342 | 0 | sc->timeout = purple_timeout_add(500, (GSourceFunc) timeoutfunc, sc); |
343 | 0 | } |
344 | | |
345 | 0 | static void hbn_cb(GSList *hosts, gpointer data, const char *error_message) { |
346 | |
|
347 | 0 | if(!hosts || !hosts->data) { |
348 | 0 | nattype.status = PURPLE_STUN_STATUS_UNDISCOVERED; |
349 | 0 | nattype.lookup_time = time(NULL); |
350 | 0 | do_callbacks(); |
351 | 0 | return; |
352 | 0 | } |
353 | | |
354 | 0 | if (!purple_network_listen_range(12108, 12208, SOCK_DGRAM, hbn_listen_cb, hosts)) { |
355 | 0 | while (hosts) { |
356 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
357 | 0 | g_free(hosts->data); |
358 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
359 | 0 | } |
360 | |
|
361 | 0 | nattype.status = PURPLE_STUN_STATUS_UNKNOWN; |
362 | 0 | nattype.lookup_time = time(NULL); |
363 | 0 | do_callbacks(); |
364 | 0 | return; |
365 | 0 | } |
366 | | |
367 | |
|
368 | 0 | } |
369 | | |
370 | 0 | static void do_test1(PurpleSrvResponse *resp, int results, gpointer sdata) { |
371 | 0 | const char *servername = sdata; |
372 | 0 | int port = 3478; |
373 | |
|
374 | 0 | if(results) { |
375 | 0 | servername = resp[0].hostname; |
376 | 0 | port = resp[0].port; |
377 | 0 | } |
378 | 0 | purple_debug_info("stun", "got %d SRV responses, server: %s, port: %d\n", |
379 | 0 | results, servername, port); |
380 | |
|
381 | 0 | purple_dnsquery_a_account(NULL, servername, port, hbn_cb, NULL); |
382 | 0 | g_free(resp); |
383 | 0 | } |
384 | | |
385 | 0 | static gboolean call_callback(gpointer data) { |
386 | 0 | StunCallback cb = data; |
387 | 0 | cb(&nattype); |
388 | 0 | return FALSE; |
389 | 0 | } |
390 | | |
391 | 0 | PurpleStunNatDiscovery *purple_stun_discover(StunCallback cb) { |
392 | 0 | const char *servername = purple_prefs_get_string("/purple/network/stun_server"); |
393 | |
|
394 | 0 | purple_debug_info("stun", "using server %s\n", servername); |
395 | |
|
396 | 0 | if(nattype.status == PURPLE_STUN_STATUS_DISCOVERING) { |
397 | 0 | if(cb) |
398 | 0 | callbacks = g_slist_append(callbacks, cb); |
399 | 0 | return &nattype; |
400 | 0 | } |
401 | | |
402 | 0 | if(nattype.status != PURPLE_STUN_STATUS_UNDISCOVERED) { |
403 | 0 | gboolean use_cached_result = TRUE; |
404 | | |
405 | | /** Deal with the server name having changed since we did the |
406 | | lookup */ |
407 | 0 | if (servername && strlen(servername) > 1 |
408 | 0 | && !purple_strequal(servername, nattype.servername)) { |
409 | 0 | use_cached_result = FALSE; |
410 | 0 | } |
411 | | |
412 | | /* If we don't have a successful status and it has been 5 |
413 | | minutes since we last did a lookup, redo the lookup */ |
414 | 0 | if (nattype.status != PURPLE_STUN_STATUS_DISCOVERED |
415 | 0 | && (time(NULL) - nattype.lookup_time) > 300) { |
416 | 0 | use_cached_result = FALSE; |
417 | 0 | } |
418 | |
|
419 | 0 | if (use_cached_result) { |
420 | 0 | if(cb) |
421 | 0 | purple_timeout_add(10, call_callback, cb); |
422 | 0 | return &nattype; |
423 | 0 | } |
424 | 0 | } |
425 | | |
426 | 0 | if(!servername || (strlen(servername) < 2)) { |
427 | 0 | nattype.status = PURPLE_STUN_STATUS_UNKNOWN; |
428 | 0 | nattype.lookup_time = time(NULL); |
429 | 0 | if(cb) |
430 | 0 | purple_timeout_add(10, call_callback, cb); |
431 | 0 | return &nattype; |
432 | 0 | } |
433 | | |
434 | 0 | nattype.status = PURPLE_STUN_STATUS_DISCOVERING; |
435 | 0 | nattype.publicip[0] = '\0'; |
436 | 0 | g_free(nattype.servername); |
437 | 0 | nattype.servername = g_strdup(servername); |
438 | |
|
439 | 0 | callbacks = g_slist_append(callbacks, cb); |
440 | 0 | purple_srv_resolve_account(NULL, "stun", "udp", servername, do_test1, |
441 | 0 | (gpointer) servername); |
442 | |
|
443 | 0 | return &nattype; |
444 | 0 | } |
445 | | |
446 | 0 | void purple_stun_init() { |
447 | 0 | purple_prefs_add_string("/purple/network/stun_server", ""); |
448 | 0 | } |