2 * @file stun.c STUN (RFC3489) Implementation
8 * STUN implementation inspired by jstun [http://jstun.javawi.de/]
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.
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.
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.
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
34 #include <sys/ioctl.h>
38 #if defined (__SVR4) && defined (__sun)
39 #include <sys/sockio.h>
51 #define MSGTYPE_BINDINGREQUEST 0x0001
52 #define MSGTYPE_BINDINGRESPONSE 0x0101
54 #define ATTRIB_MAPPEDADDRESS 0x0001
69 struct stun_header hdr
;
70 struct stun_attrib attrib
;
77 struct sockaddr_in addr
;
82 struct stun_header
*packet
;
86 static PurpleStunNatDiscovery nattype
= {
87 PURPLE_STUN_STATUS_UNDISCOVERED
,
88 PURPLE_STUN_NAT_TYPE_PUBLIC_IP
,
91 static GSList
*callbacks
= NULL
;
93 static void close_stun_conn(struct stun_conn
*sc
) {
96 purple_input_remove(sc
->incb
);
99 purple_timeout_remove(sc
->timeout
);
107 static void do_callbacks(void) {
109 StunCallback cb
= callbacks
->data
;
112 callbacks
= g_slist_remove(callbacks
, cb
);
116 static gboolean
timeoutfunc(gpointer data
) {
117 struct stun_conn
*sc
= data
;
119 purple_debug_warning("stun", "request timed out, giving up.\n");
121 nattype
.type
= PURPLE_STUN_NAT_TYPE_SYMMETRIC
;
124 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
126 nattype
.lookup_time
= time(NULL
);
131 /* we don't need to remove the timeout (returning FALSE) */
137 purple_debug_info("stun", "request timed out, retrying.\n");
139 sendto(sc
->fd
, sc
->packet
, sc
->packetsize
, 0,
140 (struct sockaddr
*)&(sc
->addr
), sizeof(struct sockaddr_in
));
145 static void do_test2(struct stun_conn
*sc
) {
146 struct stun_change data
;
147 data
.hdr
.type
= htons(0x0001);
149 data
.hdr
.transid
[0] = rand();
150 data
.hdr
.transid
[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
151 data
.hdr
.transid
[2] = rand();
152 data
.hdr
.transid
[3] = rand();
153 data
.attrib
.type
= htons(0x003);
154 data
.attrib
.len
= htons(4);
156 sc
->packet
= (struct stun_header
*)&data
;
157 sc
->packetsize
= sizeof(struct stun_change
);
160 sendto(sc
->fd
, sc
->packet
, sc
->packetsize
, 0, (struct sockaddr
*)&(sc
->addr
), sizeof(struct sockaddr_in
));
161 sc
->timeout
= purple_timeout_add(500, (GSourceFunc
) timeoutfunc
, sc
);
165 static void reply_cb(gpointer data
, gint source
, PurpleInputCondition cond
) {
166 struct stun_conn
*sc
= data
;
171 struct stun_attrib
*attrib
;
172 struct stun_header
*hdr
;
175 struct sockaddr_in
*sinptr
;
177 len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0);
179 purple_debug_warning("stun", "unable to read stun response\n");
184 if (len
< sizeof(struct stun_header
)) {
185 purple_debug_warning("stun", "got invalid response\n");
189 hdr
= (struct stun_header
*) buffer
;
190 if (len
!= (ntohs(hdr
->len
) + sizeof(struct stun_header
))) {
191 purple_debug_warning("stun", "got incomplete response\n");
195 /* wrong transaction */
196 if(hdr
->transid
[0] != sc
->packet
->transid
[0]
197 || hdr
->transid
[1] != sc
->packet
->transid
[1]
198 || hdr
->transid
[2] != sc
->packet
->transid
[2]
199 || hdr
->transid
[3] != sc
->packet
->transid
[3]) {
200 purple_debug_warning("stun", "got wrong transid\n");
205 if (hdr
->type
!= MSGTYPE_BINDINGRESPONSE
) {
206 purple_debug_warning("stun",
207 "Expected Binding Response, got %d\n",
212 tmp
= buffer
+ sizeof(struct stun_header
);
213 while((buffer
+ len
) > (tmp
+ sizeof(struct stun_attrib
))) {
214 attrib
= (struct stun_attrib
*) tmp
;
215 tmp
+= sizeof(struct stun_attrib
);
217 if (!((buffer
+ len
) > (tmp
+ ntohs(attrib
->len
))))
220 if(attrib
->type
== htons(ATTRIB_MAPPEDADDRESS
)
221 && ntohs(attrib
->len
) == 8) {
223 /* Skip the first unused byte,
224 * the family(1 byte), and the port(2 bytes);
225 * then read the 4 byte IPv4 address */
226 memcpy(&in
.s_addr
, tmp
+ 4, 4);
229 strcpy(nattype
.publicip
, ip
);
232 tmp
+= ntohs(attrib
->len
);
234 purple_debug_info("stun", "got public ip %s\n", nattype
.publicip
);
235 nattype
.status
= PURPLE_STUN_STATUS_DISCOVERED
;
236 nattype
.type
= PURPLE_STUN_NAT_TYPE_UNKNOWN_NAT
;
237 nattype
.lookup_time
= time(NULL
);
241 ifc
.ifc_len
= sizeof(buffer
);
242 ifc
.ifc_req
= (struct ifreq
*) buffer
;
243 ioctl(source
, SIOCGIFCONF
, &ifc
);
246 while(tmp
< buffer
+ ifc
.ifc_len
) {
247 ifr
= (struct ifreq
*) tmp
;
249 tmp
+= sizeof(struct ifreq
);
251 if(ifr
->ifr_addr
.sa_family
== AF_INET
) {
252 /* we only care about ipv4 interfaces */
253 sinptr
= (struct sockaddr_in
*) &ifr
->ifr_addr
;
254 if(sinptr
->sin_addr
.s_addr
== in
.s_addr
) {
256 purple_debug_info("stun", "no nat\n");
257 nattype
.type
= PURPLE_STUN_NAT_TYPE_PUBLIC_IP
;
266 purple_timeout_remove(sc
->timeout
);
270 } else if(sc
->test
== 2) {
272 nattype
.type
= PURPLE_STUN_NAT_TYPE_FULL_CONE
;
279 static void hbn_listen_cb(int fd
, gpointer data
) {
280 GSList
*hosts
= data
;
281 struct stun_conn
*sc
;
282 static struct stun_header hdr_data
;
286 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
287 nattype
.lookup_time
= time(NULL
);
292 sc
= g_new0(struct stun_conn
, 1);
295 sc
->addr
.sin_family
= AF_INET
;
296 sc
->addr
.sin_port
= htons(purple_network_get_port_from_fd(fd
));
297 sc
->addr
.sin_addr
.s_addr
= INADDR_ANY
;
299 sc
->incb
= purple_input_add(fd
, PURPLE_INPUT_READ
, reply_cb
, sc
);
301 ret
= GPOINTER_TO_INT(hosts
->data
);
302 hosts
= g_slist_remove(hosts
, hosts
->data
);
303 memcpy(&(sc
->addr
), hosts
->data
, sizeof(struct sockaddr_in
));
305 hosts
= g_slist_remove(hosts
, hosts
->data
);
307 hosts
= g_slist_remove(hosts
, hosts
->data
);
309 hosts
= g_slist_remove(hosts
, hosts
->data
);
312 hdr_data
.type
= htons(MSGTYPE_BINDINGREQUEST
);
314 hdr_data
.transid
[0] = rand();
315 hdr_data
.transid
[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
316 hdr_data
.transid
[2] = rand();
317 hdr_data
.transid
[3] = rand();
319 if(sendto(sc
->fd
, &hdr_data
, sizeof(struct stun_header
), 0,
320 (struct sockaddr
*)&(sc
->addr
),
321 sizeof(struct sockaddr_in
)) < sizeof(struct stun_header
)) {
322 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
323 nattype
.lookup_time
= time(NULL
);
329 sc
->packet
= &hdr_data
;
330 sc
->packetsize
= sizeof(struct stun_header
);
331 sc
->timeout
= purple_timeout_add(500, (GSourceFunc
) timeoutfunc
, sc
);
334 static void hbn_cb(GSList
*hosts
, gpointer data
, const char *error_message
) {
336 if(!hosts
|| !hosts
->data
) {
337 nattype
.status
= PURPLE_STUN_STATUS_UNDISCOVERED
;
338 nattype
.lookup_time
= time(NULL
);
343 if (!purple_network_listen_range(12108, 12208, SOCK_DGRAM
, hbn_listen_cb
, hosts
)) {
345 hosts
= g_slist_remove(hosts
, hosts
->data
);
347 hosts
= g_slist_remove(hosts
, hosts
->data
);
350 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
351 nattype
.lookup_time
= time(NULL
);
359 static void do_test1(PurpleSrvResponse
*resp
, int results
, gpointer sdata
) {
360 const char *servername
= sdata
;
364 servername
= resp
[0].hostname
;
367 purple_debug_info("stun", "got %d SRV responses, server: %s, port: %d\n",
368 results
, servername
, port
);
370 purple_dnsquery_a(servername
, port
, hbn_cb
, NULL
);
374 static gboolean
call_callback(gpointer data
) {
375 StunCallback cb
= data
;
380 PurpleStunNatDiscovery
*purple_stun_discover(StunCallback cb
) {
381 const char *servername
= purple_prefs_get_string("/purple/network/stun_server");
383 purple_debug_info("stun", "using server %s\n", servername
);
385 if(nattype
.status
== PURPLE_STUN_STATUS_DISCOVERING
) {
387 callbacks
= g_slist_append(callbacks
, cb
);
391 if(nattype
.status
!= PURPLE_STUN_STATUS_UNDISCOVERED
) {
392 gboolean use_cached_result
= TRUE
;
394 /** Deal with the server name having changed since we did the
396 if (servername
&& strlen(servername
) > 1
397 && !purple_strequal(servername
, nattype
.servername
)) {
398 use_cached_result
= FALSE
;
401 /* If we don't have a successful status and it has been 5
402 minutes since we last did a lookup, redo the lookup */
403 if (nattype
.status
!= PURPLE_STUN_STATUS_DISCOVERED
404 && (time(NULL
) - nattype
.lookup_time
) > 300) {
405 use_cached_result
= FALSE
;
408 if (use_cached_result
) {
410 purple_timeout_add(10, call_callback
, cb
);
415 if(!servername
|| (strlen(servername
) < 2)) {
416 nattype
.status
= PURPLE_STUN_STATUS_UNKNOWN
;
417 nattype
.lookup_time
= time(NULL
);
419 purple_timeout_add(10, call_callback
, cb
);
423 nattype
.status
= PURPLE_STUN_STATUS_DISCOVERING
;
424 nattype
.publicip
[0] = '\0';
425 g_free(nattype
.servername
);
426 nattype
.servername
= g_strdup(servername
);
428 callbacks
= g_slist_append(callbacks
, cb
);
429 purple_srv_resolve("stun", "udp", servername
, do_test1
,
430 (gpointer
) servername
);
435 void purple_stun_init() {
436 purple_prefs_add_string("/purple/network/stun_server", "");