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 sockaddr_in addr
;
73 struct stun_header
*packet
;
82 static PurpleStunNatDiscovery nattype
= {
83 PURPLE_STUN_STATUS_UNDISCOVERED
,
84 PURPLE_STUN_NAT_TYPE_PUBLIC_IP
,
87 static GSList
*callbacks
= NULL
;
89 static void close_stun_conn(struct stun_conn
*sc
) {
92 purple_input_remove(sc
->incb
);
95 g_source_remove(sc
->timeout
);
103 static void do_callbacks(void) {
105 PurpleStunCallback cb
= callbacks
->data
;
108 callbacks
= g_slist_delete_link(callbacks
, callbacks
);
112 static gboolean
timeoutfunc(gpointer data
) {
113 struct stun_conn
*sc
= data
;
115 purple_debug_warning("stun", "request timed out, giving up.\n");
117 nattype
.type
= PURPLE_STUN_NAT_TYPE_SYMMETRIC
;
120 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
122 nattype
.lookup_time
= g_get_monotonic_time();
127 /* we don't need to remove the timeout (returning FALSE) */
133 purple_debug_info("stun", "request timed out, retrying.\n");
135 if (sendto(sc
->fd
, sc
->packet
, sc
->packetsize
, 0,
136 (struct sockaddr
*)&(sc
->addr
), sizeof(struct sockaddr_in
)) !=
137 (gssize
)sc
->packetsize
)
139 purple_debug_warning("stun", "sendto failed\n");
145 static void reply_cb(gpointer data
, gint source
, PurpleInputCondition cond
) {
146 struct stun_conn
*sc
= data
;
147 guchar buffer
[65536];
148 struct ifreq buffer_ifr
[1000];
152 struct stun_attrib attrib
;
153 struct stun_header hdr
;
156 struct sockaddr_in
*sinptr
;
158 memset(&in
, 0, sizeof(in
));
160 len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0);
162 purple_debug_warning("stun", "unable to read stun response\n");
167 if ((gsize
)len
< sizeof(struct stun_header
)) {
168 purple_debug_warning("stun", "got invalid response\n");
172 memcpy(&hdr
, buffer
, sizeof(hdr
));
173 if ((gsize
)len
!= (ntohs(hdr
.len
) + sizeof(struct stun_header
))) {
174 purple_debug_warning("stun", "got incomplete response\n");
178 /* wrong transaction */
179 if(hdr
.transid
[0] != sc
->packet
->transid
[0]
180 || hdr
.transid
[1] != sc
->packet
->transid
[1]
181 || hdr
.transid
[2] != sc
->packet
->transid
[2]
182 || hdr
.transid
[3] != sc
->packet
->transid
[3]) {
183 purple_debug_warning("stun", "got wrong transid\n");
188 if (hdr
.type
!= MSGTYPE_BINDINGRESPONSE
) {
189 purple_debug_warning("stun",
190 "Expected Binding Response, got %d\n",
195 it
= buffer
+ sizeof(struct stun_header
);
196 while((buffer
+ len
) > (it
+ sizeof(struct stun_attrib
))) {
197 memcpy(&attrib
, it
, sizeof(attrib
));
198 it
+= sizeof(struct stun_attrib
);
200 if (!((buffer
+ len
) > (it
+ ntohs(attrib
.len
))))
203 if(attrib
.type
== htons(ATTRIB_MAPPEDADDRESS
)
204 && ntohs(attrib
.len
) == 8) {
206 /* Skip the first unused byte,
207 * the family(1 byte), and the port(2 bytes);
208 * then read the 4 byte IPv4 address */
209 memcpy(&in
.s_addr
, it
+ 4, 4);
212 g_strlcpy(nattype
.publicip
, ip
, sizeof(nattype
.publicip
));
215 it
+= ntohs(attrib
.len
);
217 purple_debug_info("stun", "got public ip %s\n", nattype
.publicip
);
218 nattype
.status
= PURPLE_STUN_STATUS_DISCOVERED
;
219 nattype
.type
= PURPLE_STUN_NAT_TYPE_UNKNOWN_NAT
;
220 nattype
.lookup_time
= g_get_monotonic_time();
224 ifc
.ifc_len
= sizeof(buffer_ifr
);
225 ifc
.ifc_req
= buffer_ifr
;
226 ioctl(source
, SIOCGIFCONF
, &ifc
);
228 it
= (guchar
*)buffer_ifr
;
229 it_end
= it
+ ifc
.ifc_len
;
230 while (it
< it_end
) {
231 ifr
= (struct ifreq
*)(gpointer
)it
;
232 it
+= _SIZEOF_ADDR_IFREQ(*ifr
);
234 if(ifr
->ifr_addr
.sa_family
== AF_INET
) {
235 /* we only care about ipv4 interfaces */
236 sinptr
= (struct sockaddr_in
*)(gpointer
)&ifr
->ifr_addr
;
237 if(sinptr
->sin_addr
.s_addr
== in
.s_addr
) {
239 purple_debug_info("stun", "no nat\n");
240 nattype
.type
= PURPLE_STUN_NAT_TYPE_PUBLIC_IP
;
249 g_source_remove(sc
->timeout
);
253 } else if(sc
->test
== 2) {
255 nattype
.type
= PURPLE_STUN_NAT_TYPE_FULL_CONE
;
263 hbn_listen_cb(int fd
, gpointer data
) {
264 StunHBNListenData
*ld
= (StunHBNListenData
*)data
;
265 GInetAddress
*address
= NULL
;
266 GSocketAddress
*socket_address
= NULL
;
267 struct stun_conn
*sc
;
268 static struct stun_header hdr_data
;
271 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
272 nattype
.lookup_time
= g_get_monotonic_time();
274 g_resolver_free_addresses(ld
->addresses
);
279 sc
= g_new0(struct stun_conn
, 1);
282 sc
->addr
.sin_family
= AF_INET
;
283 sc
->addr
.sin_port
= htons(purple_network_get_port_from_fd(fd
));
284 sc
->addr
.sin_addr
.s_addr
= INADDR_ANY
;
286 sc
->incb
= purple_input_add(fd
, PURPLE_INPUT_READ
, reply_cb
, sc
);
288 address
= g_object_ref(G_INET_ADDRESS(ld
->addresses
->data
));
289 socket_address
= g_inet_socket_address_new(address
, ld
->port
);
291 g_socket_address_to_native(socket_address
, &(sc
->addr
), g_socket_address_get_native_size(socket_address
), NULL
);
293 g_object_unref(G_OBJECT(address
));
294 g_object_unref(G_OBJECT(socket_address
));
295 g_resolver_free_addresses(ld
->addresses
);
298 hdr_data
.type
= htons(MSGTYPE_BINDINGREQUEST
);
300 hdr_data
.transid
[0] = g_random_int();
301 hdr_data
.transid
[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
302 hdr_data
.transid
[2] = g_random_int();
303 hdr_data
.transid
[3] = g_random_int();
305 if(sendto(sc
->fd
, &hdr_data
, sizeof(struct stun_header
), 0,
306 (struct sockaddr
*)&(sc
->addr
),
307 sizeof(struct sockaddr_in
)) < (gssize
)sizeof(struct stun_header
)) {
308 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
309 nattype
.lookup_time
= g_get_monotonic_time();
315 sc
->packet
= &hdr_data
;
316 sc
->packetsize
= sizeof(struct stun_header
);
317 sc
->timeout
= g_timeout_add(500, (GSourceFunc
) timeoutfunc
, sc
);
321 hbn_cb(GObject
*sender
, GAsyncResult
*res
, gpointer data
) {
322 StunHBNListenData
*ld
= NULL
;
323 GError
*error
= NULL
;
325 ld
= g_new0(StunHBNListenData
, 1);
327 ld
->addresses
= g_resolver_lookup_by_name_finish(G_RESOLVER(sender
),
330 nattype
.status
= PURPLE_STUN_STATUS_UNDISCOVERED
;
331 nattype
.lookup_time
= g_get_monotonic_time();
339 ld
->port
= GPOINTER_TO_INT(data
);
340 if (!purple_network_listen_range(12108, 12208, AF_UNSPEC
, SOCK_DGRAM
, TRUE
, hbn_listen_cb
, ld
)) {
341 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
342 nattype
.lookup_time
= g_get_monotonic_time();
346 g_resolver_free_addresses(ld
->addresses
);
353 do_test1(GObject
*sender
, GAsyncResult
*res
, gpointer data
) {
354 GList
*services
= NULL
;
355 GError
*error
= NULL
;
357 const char *servername
= data
;
360 services
= g_resolver_lookup_service_finish(G_RESOLVER(sender
),
363 purple_debug_info("stun", "Failed to look up srv record : %s\n", error
->message
);
367 servername
= g_srv_target_get_hostname((GSrvTarget
*)services
->data
);
368 port
= g_srv_target_get_port((GSrvTarget
*)services
->data
);
371 purple_debug_info("stun", "connecting to %s:%d\n", servername
, port
);
373 resolver
= g_resolver_get_default();
374 g_resolver_lookup_by_name_async(resolver
,
378 GINT_TO_POINTER(port
));
379 g_object_unref(resolver
);
381 g_resolver_free_targets(services
);
384 static gboolean
call_callback(gpointer data
) {
385 PurpleStunCallback cb
= data
;
390 PurpleStunNatDiscovery
*purple_stun_discover(PurpleStunCallback cb
) {
391 const char *servername
= purple_prefs_get_string("/purple/network/stun_server");
394 purple_debug_info("stun", "using server %s\n", servername
);
396 if(nattype
.status
== PURPLE_STUN_STATUS_DISCOVERING
) {
398 callbacks
= g_slist_append(callbacks
, cb
);
402 if(nattype
.status
!= PURPLE_STUN_STATUS_UNDISCOVERED
) {
403 gboolean use_cached_result
= TRUE
;
405 /* Deal with the server name having changed since we did the
407 if (servername
&& strlen(servername
) > 1
408 && !purple_strequal(servername
, nattype
.servername
)) {
409 use_cached_result
= FALSE
;
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 if (nattype
.status
!= PURPLE_STUN_STATUS_DISCOVERED
&&
415 (g_get_monotonic_time() - nattype
.lookup_time
) >
416 300 * G_USEC_PER_SEC
) {
417 use_cached_result
= FALSE
;
420 if (use_cached_result
) {
422 g_timeout_add(10, call_callback
, cb
);
427 if(!servername
|| (strlen(servername
) < 2)) {
428 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
429 nattype
.lookup_time
= g_get_monotonic_time();
431 g_timeout_add(10, call_callback
, cb
);
435 nattype
.status
= PURPLE_STUN_STATUS_DISCOVERING
;
436 nattype
.publicip
[0] = '\0';
437 g_free(nattype
.servername
);
438 nattype
.servername
= g_strdup(servername
);
440 callbacks
= g_slist_append(callbacks
, cb
);
442 resolver
= g_resolver_get_default();
443 g_resolver_lookup_service_async(resolver
,
449 (gpointer
)servername
);
450 g_object_unref(resolver
);
455 void purple_stun_init() {
456 purple_prefs_add_string("/purple/network/stun_server", "");