3 * STUN implementation inspired by jstun [http://jstun.javawi.de/]
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
29 #include <sys/ioctl.h>
35 #if defined (__SVR4) && defined (__sun)
36 #include <sys/sockio.h>
46 #define MSGTYPE_BINDINGREQUEST 0x0001
47 #define MSGTYPE_BINDINGRESPONSE 0x0101
49 #define ATTRIB_MAPPEDADDRESS 0x0001
51 #ifndef _SIZEOF_ADDR_IFREQ
52 # define _SIZEOF_ADDR_IFREQ(a) sizeof(a)
68 struct stun_header hdr
;
69 struct stun_attrib attrib
;
76 struct sockaddr_in addr
;
81 struct stun_header
*packet
;
90 static PurpleStunNatDiscovery nattype
= {
91 PURPLE_STUN_STATUS_UNDISCOVERED
,
92 PURPLE_STUN_NAT_TYPE_PUBLIC_IP
,
95 static GSList
*callbacks
= NULL
;
97 static void close_stun_conn(struct stun_conn
*sc
) {
100 purple_input_remove(sc
->incb
);
103 purple_timeout_remove(sc
->timeout
);
111 static void do_callbacks(void) {
113 PurpleStunCallback cb
= callbacks
->data
;
116 callbacks
= g_slist_delete_link(callbacks
, callbacks
);
120 static gboolean
timeoutfunc(gpointer data
) {
121 struct stun_conn
*sc
= data
;
123 purple_debug_warning("stun", "request timed out, giving up.\n");
125 nattype
.type
= PURPLE_STUN_NAT_TYPE_SYMMETRIC
;
128 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
130 nattype
.lookup_time
= time(NULL
);
135 /* we don't need to remove the timeout (returning FALSE) */
141 purple_debug_info("stun", "request timed out, retrying.\n");
143 if (sendto(sc
->fd
, sc
->packet
, sc
->packetsize
, 0,
144 (struct sockaddr
*)&(sc
->addr
), sizeof(struct sockaddr_in
)) !=
145 (gssize
)sc
->packetsize
)
147 purple_debug_warning("stun", "sendto failed\n");
154 static void do_test2(struct stun_conn
*sc
) {
155 struct stun_change data
;
156 data
.hdr
.type
= htons(0x0001);
158 data
.hdr
.transid
[0] = rand();
159 data
.hdr
.transid
[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
160 data
.hdr
.transid
[2] = rand();
161 data
.hdr
.transid
[3] = rand();
162 data
.attrib
.type
= htons(0x003);
163 data
.attrib
.len
= htons(4);
165 sc
->packet
= (struct stun_header
*)&data
;
166 sc
->packetsize
= sizeof(struct stun_change
);
169 sendto(sc
->fd
, sc
->packet
, sc
->packetsize
, 0, (struct sockaddr
*)&(sc
->addr
), sizeof(struct sockaddr_in
));
170 sc
->timeout
= purple_timeout_add(500, (GSourceFunc
) timeoutfunc
, sc
);
174 static void reply_cb(gpointer data
, gint source
, PurpleInputCondition cond
) {
175 struct stun_conn
*sc
= data
;
176 guchar buffer
[65536];
177 struct ifreq buffer_ifr
[1000];
181 struct stun_attrib attrib
;
182 struct stun_header hdr
;
185 struct sockaddr_in
*sinptr
;
187 memset(&in
, 0, sizeof(in
));
189 len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0);
191 purple_debug_warning("stun", "unable to read stun response\n");
196 if ((gsize
)len
< sizeof(struct stun_header
)) {
197 purple_debug_warning("stun", "got invalid response\n");
201 memcpy(&hdr
, buffer
, sizeof(hdr
));
202 if ((gsize
)len
!= (ntohs(hdr
.len
) + sizeof(struct stun_header
))) {
203 purple_debug_warning("stun", "got incomplete response\n");
207 /* wrong transaction */
208 if(hdr
.transid
[0] != sc
->packet
->transid
[0]
209 || hdr
.transid
[1] != sc
->packet
->transid
[1]
210 || hdr
.transid
[2] != sc
->packet
->transid
[2]
211 || hdr
.transid
[3] != sc
->packet
->transid
[3]) {
212 purple_debug_warning("stun", "got wrong transid\n");
217 if (hdr
.type
!= MSGTYPE_BINDINGRESPONSE
) {
218 purple_debug_warning("stun",
219 "Expected Binding Response, got %d\n",
224 it
= buffer
+ sizeof(struct stun_header
);
225 while((buffer
+ len
) > (it
+ sizeof(struct stun_attrib
))) {
226 memcpy(&attrib
, it
, sizeof(attrib
));
227 it
+= sizeof(struct stun_attrib
);
229 if (!((buffer
+ len
) > (it
+ ntohs(attrib
.len
))))
232 if(attrib
.type
== htons(ATTRIB_MAPPEDADDRESS
)
233 && ntohs(attrib
.len
) == 8) {
235 /* Skip the first unused byte,
236 * the family(1 byte), and the port(2 bytes);
237 * then read the 4 byte IPv4 address */
238 memcpy(&in
.s_addr
, it
+ 4, 4);
241 g_strlcpy(nattype
.publicip
, ip
, sizeof(nattype
.publicip
));
244 it
+= ntohs(attrib
.len
);
246 purple_debug_info("stun", "got public ip %s\n", nattype
.publicip
);
247 nattype
.status
= PURPLE_STUN_STATUS_DISCOVERED
;
248 nattype
.type
= PURPLE_STUN_NAT_TYPE_UNKNOWN_NAT
;
249 nattype
.lookup_time
= time(NULL
);
253 ifc
.ifc_len
= sizeof(buffer_ifr
);
254 ifc
.ifc_req
= buffer_ifr
;
255 ioctl(source
, SIOCGIFCONF
, &ifc
);
258 it_end
= it
+ ifc
.ifc_len
;
259 while (it
< it_end
) {
260 ifr
= (struct ifreq
*)(gpointer
)it
;
261 it
+= _SIZEOF_ADDR_IFREQ(*ifr
);
263 if(ifr
->ifr_addr
.sa_family
== AF_INET
) {
264 /* we only care about ipv4 interfaces */
265 sinptr
= (struct sockaddr_in
*)(gpointer
)&ifr
->ifr_addr
;
266 if(sinptr
->sin_addr
.s_addr
== in
.s_addr
) {
268 purple_debug_info("stun", "no nat\n");
269 nattype
.type
= PURPLE_STUN_NAT_TYPE_PUBLIC_IP
;
278 purple_timeout_remove(sc
->timeout
);
282 } else if(sc
->test
== 2) {
284 nattype
.type
= PURPLE_STUN_NAT_TYPE_FULL_CONE
;
292 hbn_listen_cb(int fd
, gpointer data
) {
293 StunHBNListenData
*ld
= (StunHBNListenData
*)data
;
294 GInetAddress
*address
= NULL
;
295 GSocketAddress
*socket_address
= NULL
;
296 struct stun_conn
*sc
;
297 static struct stun_header hdr_data
;
300 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
301 nattype
.lookup_time
= time(NULL
);
306 sc
= g_new0(struct stun_conn
, 1);
309 sc
->addr
.sin_family
= AF_INET
;
310 sc
->addr
.sin_port
= htons(purple_network_get_port_from_fd(fd
));
311 sc
->addr
.sin_addr
.s_addr
= INADDR_ANY
;
313 sc
->incb
= purple_input_add(fd
, PURPLE_INPUT_READ
, reply_cb
, sc
);
315 address
= g_object_ref(G_INET_ADDRESS(ld
->addresses
->data
));
316 socket_address
= g_inet_socket_address_new(address
, ld
->port
);
318 g_socket_address_to_native(socket_address
, &(sc
->addr
), g_socket_address_get_native_size(socket_address
), NULL
);
320 g_object_unref(G_OBJECT(address
));
321 g_object_unref(G_OBJECT(socket_address
));
322 g_resolver_free_addresses(ld
->addresses
);
325 hdr_data
.type
= htons(MSGTYPE_BINDINGREQUEST
);
327 hdr_data
.transid
[0] = rand();
328 hdr_data
.transid
[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
329 hdr_data
.transid
[2] = rand();
330 hdr_data
.transid
[3] = rand();
332 if(sendto(sc
->fd
, &hdr_data
, sizeof(struct stun_header
), 0,
333 (struct sockaddr
*)&(sc
->addr
),
334 sizeof(struct sockaddr_in
)) < (gssize
)sizeof(struct stun_header
)) {
335 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
336 nattype
.lookup_time
= time(NULL
);
342 sc
->packet
= &hdr_data
;
343 sc
->packetsize
= sizeof(struct stun_header
);
344 sc
->timeout
= purple_timeout_add(500, (GSourceFunc
) timeoutfunc
, sc
);
348 hbn_cb(GObject
*sender
, GAsyncResult
*res
, gpointer data
) {
349 StunHBNListenData
*ld
= NULL
;
350 GError
*error
= NULL
;
352 ld
= g_new0(StunHBNListenData
, 1);
354 ld
->addresses
= g_resolver_lookup_by_name_finish(G_RESOLVER(sender
),
357 nattype
.status
= PURPLE_STUN_STATUS_UNDISCOVERED
;
358 nattype
.lookup_time
= time(NULL
);
365 ld
->port
= GPOINTER_TO_INT(data
);
366 if (!purple_network_listen_range(12108, 12208, AF_UNSPEC
, SOCK_DGRAM
, TRUE
, hbn_listen_cb
, ld
)) {
367 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
368 nattype
.lookup_time
= time(NULL
);
377 do_test1(GObject
*sender
, GAsyncResult
*res
, gpointer data
) {
378 GList
*services
= NULL
;
379 GError
*error
= NULL
;
381 const char *servername
= data
;
384 services
= g_resolver_lookup_service_finish(G_RESOLVER(sender
),
387 purple_debug_info("stun", "Failed to look up srv record : %s\n", error
->message
);
391 servername
= g_srv_target_get_hostname((GSrvTarget
*)services
->data
);
392 port
= g_srv_target_get_port((GSrvTarget
*)services
->data
);
395 purple_debug_info("stun", "connecting to %s:%d\n", servername
, port
);
397 resolver
= g_resolver_get_default();
398 g_resolver_lookup_by_name_async(resolver
,
402 GINT_TO_POINTER(port
));
403 g_object_unref(resolver
);
405 g_resolver_free_targets(services
);
408 static gboolean
call_callback(gpointer data
) {
409 PurpleStunCallback cb
= data
;
414 PurpleStunNatDiscovery
*purple_stun_discover(PurpleStunCallback cb
) {
415 const char *servername
= purple_prefs_get_string("/purple/network/stun_server");
418 purple_debug_info("stun", "using server %s\n", servername
);
420 if(nattype
.status
== PURPLE_STUN_STATUS_DISCOVERING
) {
422 callbacks
= g_slist_append(callbacks
, cb
);
426 if(nattype
.status
!= PURPLE_STUN_STATUS_UNDISCOVERED
) {
427 gboolean use_cached_result
= TRUE
;
429 /* Deal with the server name having changed since we did the
431 if (servername
&& strlen(servername
) > 1
432 && !purple_strequal(servername
, nattype
.servername
)) {
433 use_cached_result
= FALSE
;
436 /* If we don't have a successful status and it has been 5
437 minutes since we last did a lookup, redo the lookup */
438 if (nattype
.status
!= PURPLE_STUN_STATUS_DISCOVERED
439 && (time(NULL
) - nattype
.lookup_time
) > 300) {
440 use_cached_result
= FALSE
;
443 if (use_cached_result
) {
445 purple_timeout_add(10, call_callback
, cb
);
450 if(!servername
|| (strlen(servername
) < 2)) {
451 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
452 nattype
.lookup_time
= time(NULL
);
454 purple_timeout_add(10, call_callback
, cb
);
458 nattype
.status
= PURPLE_STUN_STATUS_DISCOVERING
;
459 nattype
.publicip
[0] = '\0';
460 g_free(nattype
.servername
);
461 nattype
.servername
= g_strdup(servername
);
463 callbacks
= g_slist_append(callbacks
, cb
);
465 resolver
= g_resolver_get_default();
466 g_resolver_lookup_service_async(resolver
,
472 (gpointer
)servername
);
473 g_object_unref(resolver
);
478 void purple_stun_init() {
479 purple_prefs_add_string("/purple/network/stun_server", "");