comparison libpurple/network.c @ 15373:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 56a2a0bb290a
comparison
equal deleted inserted replaced
15372:f79e0f4df793 15373:5fe8042783c1
1 /**
2 * @file network.c Network Implementation
3 * @ingroup core
4 *
5 * gaim
6 *
7 * Gaim is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25
26 #include "internal.h"
27
28 #ifndef _WIN32
29 #include <resolv.h>
30 #include <netinet/in.h>
31 #include <arpa/nameser.h>
32 #include <net/if.h>
33 #include <sys/ioctl.h>
34 #else
35 #include <nspapi.h>
36 #endif
37
38 /* Solaris */
39 #if defined (__SVR4) && defined (__sun)
40 #include <sys/sockio.h>
41 #endif
42
43 #include "debug.h"
44 #include "account.h"
45 #include "network.h"
46 #include "prefs.h"
47 #include "stun.h"
48 #include "upnp.h"
49
50 /*
51 * Calling sizeof(struct ifreq) isn't always correct on
52 * Mac OS X (and maybe others).
53 */
54 #ifdef _SIZEOF_ADDR_IFREQ
55 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
56 #else
57 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
58 #endif
59
60 #ifdef HAVE_LIBNM
61 #include <libnm_glib.h>
62
63 libnm_glib_ctx *nm_context = NULL;
64 guint nm_callback_idx = 0;
65
66 #elif defined _WIN32
67 static int current_network_count;
68 #endif
69
70 struct _GaimNetworkListenData {
71 int listenfd;
72 int socket_type;
73 gboolean retry;
74 gboolean adding;
75 GaimNetworkListenCallback cb;
76 gpointer cb_data;
77 };
78
79 #ifdef HAVE_LIBNM
80 void nm_callback_func(libnm_glib_ctx* ctx, gpointer user_data);
81 #endif
82
83 const unsigned char *
84 gaim_network_ip_atoi(const char *ip)
85 {
86 static unsigned char ret[4];
87 gchar *delimiter = ".";
88 gchar **split;
89 int i;
90
91 g_return_val_if_fail(ip != NULL, NULL);
92
93 split = g_strsplit(ip, delimiter, 4);
94 for (i = 0; split[i] != NULL; i++)
95 ret[i] = atoi(split[i]);
96 g_strfreev(split);
97
98 /* i should always be 4 */
99 if (i != 4)
100 return NULL;
101
102 return ret;
103 }
104
105 void
106 gaim_network_set_public_ip(const char *ip)
107 {
108 g_return_if_fail(ip != NULL);
109
110 /* XXX - Ensure the IP address is valid */
111
112 gaim_prefs_set_string("/core/network/public_ip", ip);
113 }
114
115 const char *
116 gaim_network_get_public_ip(void)
117 {
118 return gaim_prefs_get_string("/core/network/public_ip");
119 }
120
121 const char *
122 gaim_network_get_local_system_ip(int fd)
123 {
124 char buffer[1024];
125 static char ip[16];
126 char *tmp;
127 struct ifconf ifc;
128 struct ifreq *ifr;
129 struct sockaddr_in *sinptr;
130 guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
131 long unsigned int add;
132 int source = fd;
133
134 if (fd < 0)
135 source = socket(PF_INET,SOCK_STREAM, 0);
136
137 ifc.ifc_len = sizeof(buffer);
138 ifc.ifc_req = (struct ifreq *)buffer;
139 ioctl(source, SIOCGIFCONF, &ifc);
140
141 if (fd < 0)
142 close(source);
143
144 tmp = buffer;
145 while (tmp < buffer + ifc.ifc_len)
146 {
147 ifr = (struct ifreq *)tmp;
148 tmp += HX_SIZE_OF_IFREQ(*ifr);
149
150 if (ifr->ifr_addr.sa_family == AF_INET)
151 {
152 sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
153 if (sinptr->sin_addr.s_addr != lhost)
154 {
155 add = ntohl(sinptr->sin_addr.s_addr);
156 g_snprintf(ip, 16, "%lu.%lu.%lu.%lu",
157 ((add >> 24) & 255),
158 ((add >> 16) & 255),
159 ((add >> 8) & 255),
160 add & 255);
161
162 return ip;
163 }
164 }
165 }
166
167 return "0.0.0.0";
168 }
169
170 const char *
171 gaim_network_get_my_ip(int fd)
172 {
173 const char *ip = NULL;
174 GaimStunNatDiscovery *stun;
175
176 /* Check if the user specified an IP manually */
177 if (!gaim_prefs_get_bool("/core/network/auto_ip")) {
178 ip = gaim_network_get_public_ip();
179 /* Make sure the IP address entered by the user is valid */
180 if ((ip != NULL) && (gaim_network_ip_atoi(ip) != NULL))
181 return ip;
182 }
183
184 /* Check if STUN discovery was already done */
185 stun = gaim_stun_discover(NULL);
186 if ((stun != NULL) && (stun->status == GAIM_STUN_STATUS_DISCOVERED))
187 return stun->publicip;
188
189 /* Attempt to get the IP from a NAT device using UPnP */
190 ip = gaim_upnp_get_public_ip();
191 if (ip != NULL)
192 return ip;
193
194 /* Just fetch the IP of the local system */
195 return gaim_network_get_local_system_ip(fd);
196 }
197
198
199 static void
200 gaim_network_set_upnp_port_mapping_cb(gboolean success, gpointer data)
201 {
202 GaimNetworkListenData *listen_data;
203
204 listen_data = data;
205 /* TODO: Once we're keeping track of upnp requests... */
206 /* listen_data->pnp_data = NULL; */
207
208 if (!success) {
209 gaim_debug_info("network", "Couldn't create UPnP mapping\n");
210 if (listen_data->retry) {
211 listen_data->retry = FALSE;
212 listen_data->adding = FALSE;
213 /* TODO: Need to keep track of this return value! */
214 gaim_upnp_remove_port_mapping(
215 gaim_network_get_port_from_fd(listen_data->listenfd),
216 (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
217 gaim_network_set_upnp_port_mapping_cb, listen_data);
218 return;
219 }
220 } else if (!listen_data->adding) {
221 /* We've tried successfully to remove the port mapping.
222 * Try to add it again */
223 listen_data->adding = TRUE;
224 /* TODO: Need to keep track of this return value! */
225 gaim_upnp_set_port_mapping(
226 gaim_network_get_port_from_fd(listen_data->listenfd),
227 (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
228 gaim_network_set_upnp_port_mapping_cb, listen_data);
229 return;
230 }
231
232 if (listen_data->cb)
233 listen_data->cb(listen_data->listenfd, listen_data->cb_data);
234
235 gaim_network_listen_cancel(listen_data);
236 }
237
238
239 static GaimNetworkListenData *
240 gaim_network_do_listen(unsigned short port, int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
241 {
242 int listenfd = -1;
243 const int on = 1;
244 GaimNetworkListenData *listen_data;
245 #ifdef HAVE_GETADDRINFO
246 int errnum;
247 struct addrinfo hints, *res, *next;
248 char serv[6];
249
250 /*
251 * Get a list of addresses on this machine.
252 */
253 snprintf(serv, sizeof(serv), "%hu", port);
254 memset(&hints, 0, sizeof(struct addrinfo));
255 hints.ai_flags = AI_PASSIVE;
256 hints.ai_family = AF_UNSPEC;
257 hints.ai_socktype = socket_type;
258 errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
259 if (errnum != 0) {
260 #ifndef _WIN32
261 gaim_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum));
262 if (errnum == EAI_SYSTEM)
263 gaim_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno));
264 #else
265 gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum);
266 #endif
267 return NULL;
268 }
269
270 /*
271 * Go through the list of addresses and attempt to listen on
272 * one of them.
273 * XXX - Try IPv6 addresses first?
274 */
275 for (next = res; next != NULL; next = next->ai_next) {
276 listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol);
277 if (listenfd < 0)
278 continue;
279 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
280 gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno));
281 if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0)
282 break; /* success */
283 /* XXX - It is unclear to me (datallah) whether we need to be
284 using a new socket each time */
285 close(listenfd);
286 }
287
288 freeaddrinfo(res);
289
290 if (next == NULL)
291 return NULL;
292 #else
293 struct sockaddr_in sockin;
294
295 if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
296 gaim_debug_warning("network", "socket: %s\n", strerror(errno));
297 return NULL;
298 }
299
300 if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
301 gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno));
302
303 memset(&sockin, 0, sizeof(struct sockaddr_in));
304 sockin.sin_family = PF_INET;
305 sockin.sin_port = htons(port);
306
307 if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
308 gaim_debug_warning("network", "bind: %s\n", strerror(errno));
309 close(listenfd);
310 return NULL;
311 }
312 #endif
313
314 if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) {
315 gaim_debug_warning("network", "listen: %s\n", strerror(errno));
316 close(listenfd);
317 return NULL;
318 }
319 fcntl(listenfd, F_SETFL, O_NONBLOCK);
320
321 gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd));
322
323 listen_data = g_new0(GaimNetworkListenData, 1);
324 listen_data->listenfd = listenfd;
325 listen_data->adding = TRUE;
326 listen_data->retry = TRUE;
327 listen_data->cb = cb;
328 listen_data->cb_data = cb_data;
329
330 /* TODO: Need to keep track of this return value! */
331 gaim_upnp_set_port_mapping(
332 gaim_network_get_port_from_fd(listenfd),
333 (socket_type == SOCK_STREAM) ? "TCP" : "UDP",
334 gaim_network_set_upnp_port_mapping_cb, listen_data);
335
336 return listen_data;
337 }
338
339 GaimNetworkListenData *
340 gaim_network_listen(unsigned short port, int socket_type,
341 GaimNetworkListenCallback cb, gpointer cb_data)
342 {
343 g_return_val_if_fail(port != 0, NULL);
344
345 return gaim_network_do_listen(port, socket_type, cb, cb_data);
346 }
347
348 GaimNetworkListenData *
349 gaim_network_listen_range(unsigned short start, unsigned short end,
350 int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
351 {
352 GaimNetworkListenData *ret = NULL;
353
354 if (gaim_prefs_get_bool("/core/network/ports_range_use")) {
355 start = gaim_prefs_get_int("/core/network/ports_range_start");
356 end = gaim_prefs_get_int("/core/network/ports_range_end");
357 } else {
358 if (end < start)
359 end = start;
360 }
361
362 for (; start <= end; start++) {
363 ret = gaim_network_do_listen(start, socket_type, cb, cb_data);
364 if (ret != NULL)
365 break;
366 }
367
368 return ret;
369 }
370
371 void gaim_network_listen_cancel(GaimNetworkListenData *listen_data)
372 {
373 g_free(listen_data);
374 }
375
376 unsigned short
377 gaim_network_get_port_from_fd(int fd)
378 {
379 struct sockaddr_in addr;
380 socklen_t len;
381
382 g_return_val_if_fail(fd >= 0, 0);
383
384 len = sizeof(addr);
385 if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) {
386 gaim_debug_warning("network", "getsockname: %s\n", strerror(errno));
387 return 0;
388 }
389
390 return ntohs(addr.sin_port);
391 }
392
393 #ifdef _WIN32
394 #ifndef NS_NLA
395 #define NS_NLA 15
396 #endif
397 static gint
398 wgaim_get_connected_network_count(void)
399 {
400 guint net_cnt = 0;
401
402 WSAQUERYSET qs;
403 HANDLE h;
404 gint retval;
405 int errorid;
406
407 memset(&qs, 0, sizeof(WSAQUERYSET));
408 qs.dwSize = sizeof(WSAQUERYSET);
409 qs.dwNameSpace = NS_NLA;
410
411 retval = WSALookupServiceBegin(&qs, LUP_RETURN_ALL, &h);
412 if (retval != ERROR_SUCCESS) {
413 gchar *msg;
414 errorid = WSAGetLastError();
415 msg = g_win32_error_message(errorid);
416 gaim_debug_warning("network", "Couldn't retrieve NLA SP lookup handle. "
417 "NLA service is probably not running. Message: %s (%d).\n",
418 msg, errorid);
419 g_free(msg);
420
421 return -1;
422 } else {
423 char buf[4096];
424 WSAQUERYSET *res = (LPWSAQUERYSET) buf;
425 DWORD size = sizeof(buf);
426 while ((retval = WSALookupServiceNext(h, 0, &size, res)) == ERROR_SUCCESS) {
427 net_cnt++;
428 gaim_debug_info("network", "found network '%s'\n",
429 res->lpszServiceInstanceName ? res->lpszServiceInstanceName : "(NULL)");
430 size = sizeof(buf);
431 }
432
433 errorid = WSAGetLastError();
434 if (!(errorid == WSA_E_NO_MORE || errorid == WSAENOMORE)) {
435 gchar *msg = g_win32_error_message(errorid);
436 gaim_debug_error("network", "got unexpected NLA response %s (%d)\n", msg, errorid);
437 g_free(msg);
438
439 net_cnt = -1;
440 }
441
442 retval = WSALookupServiceEnd(h);
443 }
444
445 return net_cnt;
446
447 }
448
449 static gboolean wgaim_network_change_thread_cb(gpointer data)
450 {
451 gint new_count;
452 GaimConnectionUiOps *ui_ops = gaim_connections_get_ui_ops();
453
454 new_count = wgaim_get_connected_network_count();
455
456 if (new_count < 0)
457 return FALSE;
458
459 gaim_debug_info("network", "Received Network Change Notification. Current network count is %d, previous count was %d.\n", new_count, current_network_count);
460
461 if (new_count > 0 && ui_ops != NULL && ui_ops->network_connected != NULL) {
462 ui_ops->network_connected();
463 } else if (new_count == 0 && current_network_count > 0 &&
464 ui_ops != NULL && ui_ops->network_disconnected != NULL) {
465 ui_ops->network_disconnected();
466 }
467
468 current_network_count = new_count;
469
470 return FALSE;
471 }
472
473 static gpointer wgaim_network_change_thread(gpointer data)
474 {
475 HANDLE h;
476 WSAQUERYSET qs;
477 time_t last_trigger = time(NULL);
478
479 int WSAAPI (*MyWSANSPIoctl) (
480 HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer,
481 DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,
482 LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL;
483
484 if (!(MyWSANSPIoctl = (void*) wgaim_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) {
485 g_thread_exit(NULL);
486 return NULL;
487 }
488
489 while (TRUE) {
490 int retval;
491 DWORD retLen = 0;
492
493 memset(&qs, 0, sizeof(WSAQUERYSET));
494 qs.dwSize = sizeof(WSAQUERYSET);
495 qs.dwNameSpace = NS_NLA;
496 if (WSALookupServiceBegin(&qs, 0, &h) == SOCKET_ERROR) {
497 int errorid = WSAGetLastError();
498 gchar *msg = g_win32_error_message(errorid);
499 gaim_debug_warning("network", "Couldn't retrieve NLA SP lookup handle. "
500 "NLA service is probably not running. Message: %s (%d).\n",
501 msg, errorid);
502 g_free(msg);
503 g_thread_exit(NULL);
504 return NULL;
505 }
506
507 /* Make sure at least 30 seconds have elapsed since the last
508 * notification so we don't peg the cpu if this keeps changing. */
509 if ((time(NULL) - last_trigger) < 30)
510 Sleep(30000);
511
512 last_trigger = time(NULL);
513
514 /* This will block until there is a network change */
515 if (MyWSANSPIoctl(h, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &retLen, NULL) == SOCKET_ERROR) {
516 int errorid = WSAGetLastError();
517 gchar *msg = g_win32_error_message(errorid);
518 gaim_debug_warning("network", "Unable to wait for changes. Message: %s (%d).\n",
519 msg, errorid);
520 g_free(msg);
521 }
522
523 retval = WSALookupServiceEnd(h);
524
525 g_idle_add(wgaim_network_change_thread_cb, NULL);
526
527 }
528
529 g_thread_exit(NULL);
530 return NULL;
531 }
532 #endif
533
534 gboolean
535 gaim_network_is_available(void)
536 {
537 #ifdef HAVE_LIBNM
538 /* Try NetworkManager first, maybe we'll get lucky */
539 int libnm_retval = -1;
540
541 if (nm_context)
542 {
543 if ((libnm_retval = libnm_glib_get_network_state(nm_context)) == LIBNM_NO_NETWORK_CONNECTION)
544 {
545 gaim_debug_warning("network", "NetworkManager not active or reports no connection (retval = %i)\n", libnm_retval);
546 return FALSE;
547 }
548 if (libnm_retval == LIBNM_ACTIVE_NETWORK_CONNECTION) return TRUE;
549 }
550 #elif defined _WIN32
551 return (current_network_count > 0);
552 #endif
553 return TRUE;
554 }
555
556 #ifdef HAVE_LIBNM
557 void
558 nm_callback_func(libnm_glib_ctx* ctx, gpointer user_data)
559 {
560 GList *l;
561 GaimAccount *account;
562 static libnm_glib_state prev = LIBNM_NO_DBUS;
563 libnm_glib_state current;
564 GaimConnectionUiOps *ui_ops = gaim_connections_get_ui_ops();
565
566 current = libnm_glib_get_network_state(ctx);
567 gaim_debug_info("network","Entering nm_callback_func!\n");
568
569 switch(current)
570 {
571 case LIBNM_ACTIVE_NETWORK_CONNECTION:
572 /* Call res_init in case DNS servers have changed */
573 res_init();
574 if (ui_ops != NULL && ui_ops->network_connected != NULL)
575 ui_ops->network_connected();
576 prev = current;
577 break;
578 case LIBNM_NO_NETWORK_CONNECTION:
579 if (prev != LIBNM_ACTIVE_NETWORK_CONNECTION)
580 break;
581 if (ui_ops != NULL && ui_ops->network_disconnected != NULL)
582 ui_ops->network_disconnected();
583 prev = current;
584 break;
585 case LIBNM_NO_DBUS:
586 case LIBNM_NO_NETWORKMANAGER:
587 case LIBNM_INVALID_CONTEXT:
588 default:
589 break;
590 }
591 }
592 #endif
593
594 void
595 gaim_network_init(void)
596 {
597 #ifdef _WIN32
598 GError *err = NULL;
599 gint cnt = wgaim_get_connected_network_count();
600
601 if (cnt < 0) /* Assume there is a network */
602 current_network_count = 1;
603 /* Don't listen for network changes if we can't tell anyway */
604 else
605 {
606 current_network_count = cnt;
607 if (!g_thread_create(wgaim_network_change_thread, NULL, FALSE, &err))
608 gaim_debug_error("network", "Couldn't create Network Monitor thread: %s\n", err ? err->message : "");
609 }
610 #endif
611
612 gaim_prefs_add_none ("/core/network");
613 gaim_prefs_add_bool ("/core/network/auto_ip", TRUE);
614 gaim_prefs_add_string("/core/network/public_ip", "");
615 gaim_prefs_add_bool ("/core/network/ports_range_use", FALSE);
616 gaim_prefs_add_int ("/core/network/ports_range_start", 1024);
617 gaim_prefs_add_int ("/core/network/ports_range_end", 2048);
618
619 gaim_upnp_discover(NULL, NULL);
620
621 #ifdef HAVE_LIBNM
622 nm_context = libnm_glib_init();
623 if(nm_context)
624 nm_callback_idx = libnm_glib_register_callback(nm_context, nm_callback_func, NULL, g_main_context_default());
625 #endif
626 }
627
628 void
629 gaim_network_uninit(void)
630 {
631 #ifdef HAVE_LIBNM
632 /* FIXME: If anyone can think of a more clever way to shut down libnm without
633 * using a global variable + this function, please do. */
634 if(nm_context && nm_callback_idx)
635 libnm_glib_unregister_callback(nm_context, nm_callback_idx);
636
637 if(nm_context)
638 libnm_glib_shutdown(nm_context);
639 #endif
640 }