Mercurial > pidgin.yaz
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", ""); |