comparison src/stun.c @ 12756:6ef1cdc26b40

[gaim-migrate @ 15103] Cleanup to STUN code. Fixed endianness. Fixed to work where sizeof(short) != 2 or sizeof(int) != 4. Close the socket when we're done with it. Instead of using a bunch of static variables, pass data around the various callbacks. Don't invoke the specified StunCallback before the initial function has returned. Deal with requerying if the STUN server has changed since last query, or the last query was unsuccessful and 5 minutes have elapsed. committer: Tailor Script <tailor@pidgin.im>
author Daniel Atallah <daniel.atallah@gmail.com>
date Sat, 07 Jan 2006 22:10:18 +0000
parents cf0d9e207cdc
children e2f1f49fad1a
comparison
equal deleted inserted replaced
12755:505bc707b641 12756:6ef1cdc26b40
39 #endif 39 #endif
40 40
41 #include "debug.h" 41 #include "debug.h"
42 #include "account.h" 42 #include "account.h"
43 #include "dnssrv.h" 43 #include "dnssrv.h"
44 #include "network.h"
44 #include "proxy.h" 45 #include "proxy.h"
45 #include "stun.h" 46 #include "stun.h"
46 #include "prefs.h" 47 #include "prefs.h"
47 48
49 #define MSGTYPE_BINDINGREQUEST 0x0001
50 #define MSGTYPE_BINDINGRESPONSE 0x0101
51
52 #define ATTRIB_MAPPEDADDRESS 0x0001
53
48 struct stun_header { 54 struct stun_header {
49 short type; 55 guint16 type;
50 short len; 56 guint16 len;
51 int transid[4]; 57 guint32 transid[4];
52 }; 58 };
53 59
54 struct stun_attrib { 60 struct stun_attrib {
55 short type; 61 guint16 type;
56 short len; 62 guint16 len;
57 }; 63 };
58 64
65 #ifdef NOTYET
59 struct stun_change { 66 struct stun_change {
60 struct stun_header hdr; 67 struct stun_header hdr;
61 struct stun_attrib attrib; 68 struct stun_attrib attrib;
62 char value[4]; 69 char value[4];
63 }; 70 };
64 71 #endif
65 static GaimStunNatDiscovery nattype = {-1, 0, "\0"}; 72
73 struct stun_conn {
74 int fd;
75 struct sockaddr_in addr;
76 int test;
77 int retry;
78 guint incb;
79 guint timeout;
80 struct stun_header *packet;
81 size_t packetsize;
82 };
83
84 static GaimStunNatDiscovery nattype = {-1, 0, "\0", NULL, 0};
66 85
67 static GSList *callbacks = 0; 86 static GSList *callbacks = 0;
68 static int fd = -1; 87
69 static gint incb = -1; 88 static void close_stun_conn(struct stun_conn *sc) {
70 static gint timeout = -1; 89
71 static struct stun_header *packet; 90 if (sc->incb)
72 static int packetsize = 0; 91 gaim_input_remove(sc->incb);
73 static int test = 0; 92
74 static int retry = 0; 93 if (sc->timeout)
75 static struct sockaddr_in addr; 94 gaim_timeout_remove(sc->timeout);
95
96 if (sc->fd)
97 close(sc->fd);
98
99 g_free(sc);
100 }
76 101
77 static void do_callbacks() { 102 static void do_callbacks() {
78 while(callbacks) { 103 while(callbacks) {
79 StunCallback cb = callbacks->data; 104 StunCallback cb = callbacks->data;
80 if(cb) 105 if(cb)
81 cb(&nattype); 106 cb(&nattype);
82 callbacks = g_slist_remove(callbacks, cb); 107 callbacks = g_slist_remove(callbacks, cb);
83 } 108 }
84 } 109 }
85 110
86 static gboolean timeoutfunc(void *blah) { 111 static gboolean timeoutfunc(gpointer data) {
87 if(retry > 2) { 112 struct stun_conn *sc = data;
88 if(test == 2) 113 if(sc->retry >= 2) {
114 gaim_debug_info("stun", "request timed out, giving up.\n");
115 if(sc->test == 2)
89 nattype.type = GAIM_STUN_NAT_TYPE_SYMMETRIC; 116 nattype.type = GAIM_STUN_NAT_TYPE_SYMMETRIC;
90
91 /* remove input */
92 gaim_input_remove(incb);
93 117
94 /* set unknown */ 118 /* set unknown */
95 nattype.status = GAIM_STUN_STATUS_UNKNOWN; 119 nattype.status = GAIM_STUN_STATUS_UNKNOWN;
96 120
121 nattype.lookup_time = time(NULL);
122
97 /* callbacks */ 123 /* callbacks */
98 do_callbacks(); 124 do_callbacks();
99 125
126 /* we don't need to remove the timeout (returning FALSE) */
127 sc->timeout = 0;
128 close_stun_conn(sc);
129
100 return FALSE; 130 return FALSE;
101 } 131 }
102 retry++; 132 gaim_debug_info("stun", "request timed out, retrying.\n");
103 sendto(fd, packet, packetsize, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 133 sc->retry++;
134 sendto(sc->fd, sc->packet, sc->packetsize, 0,
135 (struct sockaddr *)&(sc->addr), sizeof(struct sockaddr_in));
104 return TRUE; 136 return TRUE;
105 } 137 }
106 138
107 #ifdef NOTYET 139 #ifdef NOTYET
108 static void do_test2() { 140 static void do_test2(struct stun_conn *sc) {
109 struct stun_change data; 141 struct stun_change data;
110 data.hdr.type = htons(0x0001); 142 data.hdr.type = htons(0x0001);
111 data.hdr.len = 0; 143 data.hdr.len = 0;
112 data.hdr.transid[0] = rand(); 144 data.hdr.transid[0] = rand();
113 data.hdr.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m'); 145 data.hdr.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
114 data.hdr.transid[2] = rand(); 146 data.hdr.transid[2] = rand();
115 data.hdr.transid[3] = rand(); 147 data.hdr.transid[3] = rand();
116 data.attrib.type = htons(0x003); 148 data.attrib.type = htons(0x003);
117 data.attrib.len = htons(4); 149 data.attrib.len = htons(4);
118 data.value[3] = 6; 150 data.value[3] = 6;
119 packet = (struct stun_header*)&data; 151 sc->packet = (struct stun_header*)&data;
120 packetsize = sizeof(struct stun_change); 152 sc->packetsize = sizeof(struct stun_change);
121 retry = 0; 153 sc->retry = 0;
122 test = 2; 154 sc->test = 2;
123 sendto(fd, packet, packetsize, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); 155 sendto(sc->fd, sc->packet, sc->packetsize, 0, (struct sockaddr *)&(sc->addr), sizeof(struct sockaddr_in));
124 timeout = gaim_timeout_add(500, (GSourceFunc)timeoutfunc, NULL); 156 sc->timeout = gaim_timeout_add(500, (GSourceFunc) timeoutfunc, sc);
125 } 157 }
126 #endif 158 #endif
127 159
128 static void reply_cb(gpointer data, gint source, GaimInputCondition cond) { 160 static void reply_cb(gpointer data, gint source, GaimInputCondition cond) {
129 char buffer[1024]; 161 struct stun_conn *sc = data;
162 char buffer[65536];
130 char *tmp; 163 char *tmp;
131 int len; 164 int len;
132 struct in_addr in; 165 struct in_addr in;
133 struct stun_attrib *attrib; 166 struct stun_attrib *attrib;
134 struct stun_header *hdr; 167 struct stun_header *hdr;
135 struct ifconf ifc; 168 struct ifconf ifc;
136 struct ifreq *ifr; 169 struct ifreq *ifr;
137 struct sockaddr_in *sinptr; 170 struct sockaddr_in *sinptr;
138 171
139 len = recv(source, buffer, 1024, 0); 172 len = recv(source, buffer, sizeof(buffer) - 1, 0);
140 173 if (!len) {
141 hdr = (struct stun_header*)buffer; 174 gaim_debug_info("stun", "unable to read stun response\n");
142 if(hdr->transid[0]!=packet->transid[0] || hdr->transid[1]!=packet->transid[1] || hdr->transid[2]!=packet->transid[2] || hdr->transid[3]!=packet->transid[3]) { /* wrong transaction */ 175 return;
176 }
177 buffer[len] = '\0';
178
179 if (len < sizeof(struct stun_header)) {
180 gaim_debug_info("stun", "got invalid response\n");
181 return;
182 }
183
184 hdr = (struct stun_header*) buffer;
185 if (len != (ntohs(hdr->len) + sizeof(struct stun_header))) {
186 gaim_debug_info("stun", "got incomplete response\n");
187 return;
188 }
189
190 /* wrong transaction */
191 if(hdr->transid[0] != sc->packet->transid[0]
192 || hdr->transid[1] != sc->packet->transid[1]
193 || hdr->transid[2] != sc->packet->transid[2]
194 || hdr->transid[3] != sc->packet->transid[3]) {
143 gaim_debug_info("stun", "got wrong transid\n"); 195 gaim_debug_info("stun", "got wrong transid\n");
144 return; 196 return;
145 } 197 }
146 if(test==1) { 198
199 if(sc->test==1) {
200 if (hdr->type != MSGTYPE_BINDINGRESPONSE) {
201 gaim_debug_info("stun",
202 "Expected Binding Response, got %d\n",
203 hdr->type);
204 return;
205 }
206
147 tmp = buffer + sizeof(struct stun_header); 207 tmp = buffer + sizeof(struct stun_header);
148 while(buffer+len > tmp) { 208 while((buffer + len) > (tmp + sizeof(struct stun_attrib))) {
149
150 attrib = (struct stun_attrib*) tmp; 209 attrib = (struct stun_attrib*) tmp;
151 if(attrib->type == htons(0x0001) && attrib->len == htons(8)) { 210 tmp += sizeof(struct stun_attrib);
152 memcpy(&in.s_addr, tmp+sizeof(struct stun_attrib)+2+2, 4); 211
153 strcpy(nattype.publicip, inet_ntoa(in)); 212 if (!((buffer + len) > (tmp + ntohs(attrib->len))))
213 break;
214
215 if(attrib->type == htons(ATTRIB_MAPPEDADDRESS)
216 && ntohs(attrib->len) == 8) {
217 char *ip;
218 /* Skip the first unused byte,
219 * the family(1 byte), and the port(2 bytes);
220 * then read the 4 byte IPv4 address */
221 memcpy(&in.s_addr, tmp + 4, 4);
222 ip = inet_ntoa(in);
223 if(ip)
224 strcpy(nattype.publicip, ip);
154 } 225 }
155 tmp += sizeof(struct stun_attrib) + attrib->len; 226
227 tmp += attrib->len;
156 } 228 }
157 gaim_debug_info("stun", "got public ip %s\n", nattype.publicip); 229 gaim_debug_info("stun", "got public ip %s\n", nattype.publicip);
158 nattype.status = GAIM_STUN_STATUS_DISCOVERED; 230 nattype.status = GAIM_STUN_STATUS_DISCOVERED;
159 nattype.type = GAIM_STUN_NAT_TYPE_UNKNOWN_NAT; 231 nattype.type = GAIM_STUN_NAT_TYPE_UNKNOWN_NAT;
232 nattype.lookup_time = time(NULL);
160 233
161 /* is it a NAT? */ 234 /* is it a NAT? */
162 235
163 ifc.ifc_len = sizeof(buffer); 236 ifc.ifc_len = sizeof(buffer);
164 ifc.ifc_req = (struct ifreq *) buffer; 237 ifc.ifc_req = (struct ifreq *) buffer;
178 gaim_debug_info("stun", "no nat"); 251 gaim_debug_info("stun", "no nat");
179 nattype.type = GAIM_STUN_NAT_TYPE_PUBLIC_IP; 252 nattype.type = GAIM_STUN_NAT_TYPE_PUBLIC_IP;
180 } 253 }
181 } 254 }
182 } 255 }
183 gaim_timeout_remove(timeout); 256
184 257 #ifndef NOTYET
185 #ifdef NOTYET 258 close_stun_conn(sc);
186 do_test2(); 259 do_callbacks();
260 #else
261 gaim_timeout_remove(sc->timeout);
262 sc->timeout = 0;
263
264 do_test2(sc);
265 } else if(sc->test == 2) {
266 close_stun_conn(sc);
267 nattype.type = GAIM_STUN_NAT_TYPE_FULL_CONE;
268 do_callbacks();
187 #endif 269 #endif
188 return; 270 }
189 } else if(test == 2) { 271 }
190 do_callbacks(); 272
191 gaim_input_remove(incb); 273 static void hbn_cb(GSList *hosts, gpointer data, const char *error_message) {
192 gaim_timeout_remove(timeout); 274 struct stun_conn *sc;
193 nattype.type = GAIM_STUN_NAT_TYPE_FULL_CONE; 275 static struct stun_header hdr_data;
194 } 276 int ret, fd;
195 } 277
196 278 if(!hosts || !hosts->data) {
197 static void hbn_cb(GSList *hosts, gpointer edata, const char *error_message) { 279 nattype.status = GAIM_STUN_STATUS_UNDISCOVERED;
198 static struct stun_header data; 280 nattype.lookup_time = time(NULL);
199 int ret; 281 do_callbacks();
200 282 return;
201 if(!hosts) return; 283 }
202 if(!hosts->data) return; 284
203 285
204 if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 286 fd = gaim_network_listen_range(12108, 12208, SOCK_DGRAM);
287
288 if(!fd) {
205 nattype.status = GAIM_STUN_STATUS_UNKNOWN; 289 nattype.status = GAIM_STUN_STATUS_UNKNOWN;
206 do_callbacks(); 290 nattype.lookup_time = time(NULL);
207 return; 291 do_callbacks();
208 } 292 return;
209 293 }
210 addr.sin_family = AF_INET; 294
211 addr.sin_port = htons(12108); 295 sc = g_new0(struct stun_conn, 1);
212 addr.sin_addr.s_addr = INADDR_ANY; 296 sc->fd = fd;
213 while( ((ret = bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in))) < 0 ) && ntohs(addr.sin_port) < 12208) { 297
214 addr.sin_port = htons(ntohs(addr.sin_port)+1); 298 sc->addr.sin_family = AF_INET;
215 } 299 sc->addr.sin_port = htons(gaim_network_get_port_from_fd(fd));
216 if( ret < 0 ) { 300 sc->addr.sin_addr.s_addr = INADDR_ANY;
217 nattype.status = GAIM_STUN_STATUS_UNKNOWN; 301
218 do_callbacks(); 302 sc->incb = gaim_input_add(fd, GAIM_INPUT_READ, reply_cb, sc);
219 return;
220 }
221 incb = gaim_input_add(fd, GAIM_INPUT_READ, reply_cb, NULL);
222 303
223 ret = GPOINTER_TO_INT(hosts->data); 304 ret = GPOINTER_TO_INT(hosts->data);
224 hosts = g_slist_remove(hosts, hosts->data); 305 hosts = g_slist_remove(hosts, hosts->data);
225 memcpy(&addr, hosts->data, sizeof(struct sockaddr_in)); 306 memcpy(&(sc->addr), hosts->data, sizeof(struct sockaddr_in));
226 g_free(hosts->data); 307 g_free(hosts->data);
227 hosts = g_slist_remove(hosts, hosts->data); 308 hosts = g_slist_remove(hosts, hosts->data);
228 while(hosts) { 309 while(hosts) {
229 hosts = g_slist_remove(hosts, hosts->data); 310 hosts = g_slist_remove(hosts, hosts->data);
230 g_free(hosts->data); 311 g_free(hosts->data);
231 hosts = g_slist_remove(hosts, hosts->data); 312 hosts = g_slist_remove(hosts, hosts->data);
232 } 313 }
233 314
234 data.type = htons(0x0001); 315 hdr_data.type = htons(MSGTYPE_BINDINGREQUEST);
235 data.len = 0; 316 hdr_data.len = 0;
236 data.transid[0] = rand(); 317 hdr_data.transid[0] = rand();
237 data.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m'); 318 hdr_data.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m');
238 data.transid[2] = rand(); 319 hdr_data.transid[2] = rand();
239 data.transid[3] = rand(); 320 hdr_data.transid[3] = rand();
240 321
241 if(sendto(fd, &data, sizeof(struct stun_header), 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < sizeof(struct stun_header)) { 322 if(sendto(sc->fd, &hdr_data, sizeof(struct stun_header), 0,
323 (struct sockaddr *)&(sc->addr),
324 sizeof(struct sockaddr_in)) < sizeof(struct stun_header)) {
242 nattype.status = GAIM_STUN_STATUS_UNKNOWN; 325 nattype.status = GAIM_STUN_STATUS_UNKNOWN;
243 do_callbacks(); 326 nattype.lookup_time = time(NULL);
244 return; 327 do_callbacks();
245 } 328 close_stun_conn(sc);
246 test = 1; 329 return;
247 packet = &data; 330 }
248 packetsize = sizeof(struct stun_header); 331 sc->test = 1;
249 timeout = gaim_timeout_add(500, (GSourceFunc)timeoutfunc, NULL); 332 sc->packet = &hdr_data;
333 sc->packetsize = sizeof(struct stun_header);
334 sc->timeout = gaim_timeout_add(500, (GSourceFunc) timeoutfunc, sc);
250 } 335 }
251 336
252 static void do_test1(GaimSrvResponse *resp, int results, gpointer sdata) { 337 static void do_test1(GaimSrvResponse *resp, int results, gpointer sdata) {
253 const char *servername = sdata; 338 const char *servername = sdata;
254 int port = 3478; 339 int port = 3478;
255 340
256 if(results) { 341 if(results) {
257 servername = resp[0].hostname; 342 servername = resp[0].hostname;
258 port = resp[0].port; 343 port = resp[0].port;
259 } 344 }
260 gaim_debug_info("stun", "got %d SRV responses, server: %s, port: %d\n", results, servername, port); 345 gaim_debug_info("stun", "got %d SRV responses, server: %s, port: %d\n",
346 results, servername, port);
261 347
262 gaim_gethostbyname_async(servername, port, hbn_cb, NULL); 348 gaim_gethostbyname_async(servername, port, hbn_cb, NULL);
263 g_free(resp); 349 g_free(resp);
350 }
351
352 static gboolean call_callback(gpointer data) {
353 StunCallback cb = data;
354 cb(&nattype);
355 return FALSE;
264 } 356 }
265 357
266 GaimStunNatDiscovery *gaim_stun_discover(StunCallback cb) { 358 GaimStunNatDiscovery *gaim_stun_discover(StunCallback cb) {
267 const char *servername = gaim_prefs_get_string("/core/network/stun_server"); 359 const char *servername = gaim_prefs_get_string("/core/network/stun_server");
268 360
269 gaim_debug_info("stun", "using server %s\n", servername); 361 gaim_debug_info("stun", "using server %s\n", servername);
270 362
271 if(nattype.status == GAIM_STUN_STATUS_DISCOVERING) { 363 if(nattype.status == GAIM_STUN_STATUS_DISCOVERING) {
272 if(cb) 364 if(cb)
273 callbacks = g_slist_append(callbacks, cb); 365 callbacks = g_slist_append(callbacks, cb);
274 return NULL; 366 return &nattype;
275 } 367 }
276 368
277 if(nattype.status != GAIM_STUN_STATUS_UNDISCOVERED) { 369 if(nattype.status != GAIM_STUN_STATUS_UNDISCOVERED) {
278 if(cb) 370 gboolean use_cached_result = TRUE;
279 cb(&nattype); 371
280 return &nattype; 372 /** Deal with the server name having changed since we did the
373 lookup */
374 if (servername && strlen(servername) > 1
375 && ((nattype.servername
376 && strcmp(servername, nattype.servername))
377 || !nattype.servername)) {
378 use_cached_result = FALSE;
379 }
380
381 /* If we don't have a successful status and it has been 5
382 minutes since we last did a lookup, redo the lookup */
383 if (nattype.status != GAIM_STUN_STATUS_DISCOVERED
384 && (time(NULL) - nattype.lookup_time) > 300) {
385 use_cached_result = FALSE;
386 }
387
388 if (use_cached_result) {
389 if(cb)
390 gaim_timeout_add(10, call_callback, cb);
391 return &nattype;
392 }
281 } 393 }
282 394
283 if(!servername || (strlen(servername) < 2)) { 395 if(!servername || (strlen(servername) < 2)) {
284 nattype.status = GAIM_STUN_STATUS_UNKNOWN; 396 nattype.status = GAIM_STUN_STATUS_UNKNOWN;
397 nattype.lookup_time = time(NULL);
285 if(cb) 398 if(cb)
286 cb(&nattype); 399 gaim_timeout_add(10, call_callback, cb);
287 return &nattype; 400 return &nattype;
288 } 401 }
402
403 nattype.status = GAIM_STUN_STATUS_DISCOVERING;
404 nattype.publicip[0] = '\0';
405 g_free(nattype.servername);
406 nattype.servername = g_strdup(servername);
407
289 callbacks = g_slist_append(callbacks, cb); 408 callbacks = g_slist_append(callbacks, cb);
290 gaim_srv_resolve("stun", "udp", servername, do_test1, 409 gaim_srv_resolve("stun", "udp", servername, do_test1,
291 (gpointer) servername); 410 (gpointer) servername);
411
292 return &nattype; 412 return &nattype;
293 } 413 }
294 414
295 void gaim_stun_init() { 415 void gaim_stun_init() {
296 gaim_prefs_add_string("/core/network/stun_server", ""); 416 gaim_prefs_add_string("/core/network/stun_server", "");