/src/pidgin/libpurple/network.c
Line | Count | Source |
1 | | /** |
2 | | * @file network.c Network Implementation |
3 | | * @ingroup core |
4 | | */ |
5 | | |
6 | | /* purple |
7 | | * |
8 | | * Purple is the legal property of its developers, whose names are too numerous |
9 | | * to list here. Please refer to the COPYRIGHT file distributed with this |
10 | | * source distribution. |
11 | | * |
12 | | * This program is free software; you can redistribute it and/or modify |
13 | | * it under the terms of the GNU General Public License as published by |
14 | | * the Free Software Foundation; either version 2 of the License, or |
15 | | * (at your option) any later version. |
16 | | * |
17 | | * This program is distributed in the hope that it will be useful, |
18 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | | * GNU General Public License for more details. |
21 | | * |
22 | | * You should have received a copy of the GNU General Public License |
23 | | * along with this program; if not, write to the Free Software |
24 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
25 | | */ |
26 | | |
27 | | #include "internal.h" |
28 | | |
29 | | #ifndef _WIN32 |
30 | | #include <arpa/nameser.h> |
31 | | #include <resolv.h> |
32 | | #include <netinet/in.h> |
33 | | #include <net/if.h> |
34 | | #include <sys/ioctl.h> |
35 | | #ifdef HAVE_GETIFADDRS |
36 | | #include <ifaddrs.h> |
37 | | #endif |
38 | | #else |
39 | | #include <nspapi.h> |
40 | | #endif |
41 | | |
42 | | /* Solaris */ |
43 | | #if defined (__SVR4) && defined (__sun) |
44 | | #include <sys/sockio.h> |
45 | | #endif |
46 | | |
47 | | #include "debug.h" |
48 | | #include "account.h" |
49 | | #include "nat-pmp.h" |
50 | | #include "network.h" |
51 | | #include "prefs.h" |
52 | | #include "stun.h" |
53 | | #include "upnp.h" |
54 | | #include "dnsquery.h" |
55 | | |
56 | | #ifdef USE_IDN |
57 | | #include <idna.h> |
58 | | #endif |
59 | | |
60 | | #ifdef __HAIKU__ |
61 | | # ifndef SIOCGIFCONF |
62 | | # include <sys/sockio.h> |
63 | | # endif |
64 | | #endif |
65 | | |
66 | | /* |
67 | | * Calling sizeof(struct ifreq) isn't always correct on |
68 | | * Mac OS X (and maybe others). |
69 | | */ |
70 | | #ifdef _SIZEOF_ADDR_IFREQ |
71 | | # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a) |
72 | | #else |
73 | 0 | # define HX_SIZE_OF_IFREQ(a) sizeof(a) |
74 | | #endif |
75 | | |
76 | | #ifdef HAVE_NETWORKMANAGER |
77 | | #include <dbus/dbus-glib.h> |
78 | | #include <NetworkManager.h> |
79 | | |
80 | | #if !defined(NM_CHECK_VERSION) |
81 | | #define NM_CHECK_VERSION(x,y,z) 0 |
82 | | #endif |
83 | | |
84 | | static DBusGConnection *nm_conn = NULL; |
85 | | static DBusGProxy *nm_proxy = NULL; |
86 | | static DBusGProxy *dbus_proxy = NULL; |
87 | | static NMState nm_state = NM_STATE_UNKNOWN; |
88 | | static gboolean have_nm_state = FALSE; |
89 | | |
90 | | #elif defined _WIN32 |
91 | | static int current_network_count; |
92 | | |
93 | | /* Mutex for the other global vars */ |
94 | | static GStaticMutex mutex = G_STATIC_MUTEX_INIT; |
95 | | static gboolean network_initialized = FALSE; |
96 | | static HANDLE network_change_handle = NULL; |
97 | | static int (WSAAPI *MyWSANSPIoctl) ( |
98 | | HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer, |
99 | | DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer, |
100 | | LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL; |
101 | | #endif |
102 | | |
103 | | struct _PurpleNetworkListenData { |
104 | | int listenfd; |
105 | | int socket_type; |
106 | | gboolean retry; |
107 | | gboolean adding; |
108 | | PurpleNetworkListenCallback cb; |
109 | | gpointer cb_data; |
110 | | UPnPMappingAddRemove *mapping_data; |
111 | | int timer; |
112 | | }; |
113 | | |
114 | | #ifdef HAVE_NETWORKMANAGER |
115 | | static NMState nm_get_network_state(void); |
116 | | #endif |
117 | | |
118 | | #if defined(HAVE_NETWORKMANAGER) || defined(_WIN32) |
119 | | static gboolean force_online; |
120 | | #endif |
121 | | |
122 | | /* Cached IP addresses for STUN and TURN servers (set globally in prefs) */ |
123 | | static gchar *stun_ip = NULL; |
124 | | static gchar *turn_ip = NULL; |
125 | | |
126 | | /* Keep track of port mappings done with UPnP and NAT-PMP */ |
127 | | static GHashTable *upnp_port_mappings = NULL; |
128 | | static GHashTable *nat_pmp_port_mappings = NULL; |
129 | | |
130 | | const unsigned char * |
131 | | purple_network_ip_atoi(const char *ip) |
132 | 0 | { |
133 | 0 | static unsigned char ret[4]; |
134 | 0 | gchar *delimiter = "."; |
135 | 0 | gchar **split; |
136 | 0 | int i; |
137 | |
|
138 | 0 | g_return_val_if_fail(ip != NULL, NULL); |
139 | | |
140 | 0 | split = g_strsplit(ip, delimiter, 4); |
141 | 0 | for (i = 0; split[i] != NULL; i++) |
142 | 0 | ret[i] = atoi(split[i]); |
143 | 0 | g_strfreev(split); |
144 | | |
145 | | /* i should always be 4 */ |
146 | 0 | if (i != 4) |
147 | 0 | return NULL; |
148 | | |
149 | 0 | return ret; |
150 | 0 | } |
151 | | |
152 | | void |
153 | | purple_network_set_public_ip(const char *ip) |
154 | 0 | { |
155 | 0 | g_return_if_fail(ip != NULL); |
156 | | |
157 | | /* XXX - Ensure the IP address is valid */ |
158 | | |
159 | 0 | purple_prefs_set_string("/purple/network/public_ip", ip); |
160 | 0 | } |
161 | | |
162 | | const char * |
163 | | purple_network_get_public_ip(void) |
164 | 0 | { |
165 | 0 | return purple_prefs_get_string("/purple/network/public_ip"); |
166 | 0 | } |
167 | | |
168 | | const char * |
169 | | purple_network_get_local_system_ip(int fd) |
170 | 0 | { |
171 | 0 | char buffer[1024]; |
172 | 0 | static char ip[16]; |
173 | 0 | char *tmp; |
174 | 0 | struct ifconf ifc; |
175 | 0 | struct ifreq *ifr; |
176 | 0 | struct sockaddr_in *sinptr; |
177 | 0 | guint32 lhost = htonl((127 << 24) + 1); /* 127.0.0.1 */ |
178 | 0 | long unsigned int add; |
179 | 0 | int source = fd; |
180 | |
|
181 | 0 | if (fd < 0) |
182 | 0 | source = socket(PF_INET,SOCK_STREAM, 0); |
183 | |
|
184 | 0 | ifc.ifc_len = sizeof(buffer); |
185 | 0 | ifc.ifc_req = (struct ifreq *)buffer; |
186 | 0 | ioctl(source, SIOCGIFCONF, &ifc); |
187 | |
|
188 | 0 | if (fd < 0 && source >= 0) |
189 | 0 | close(source); |
190 | |
|
191 | 0 | tmp = buffer; |
192 | 0 | while (tmp < buffer + ifc.ifc_len) |
193 | 0 | { |
194 | 0 | ifr = (struct ifreq *)tmp; |
195 | 0 | tmp += HX_SIZE_OF_IFREQ(*ifr); |
196 | |
|
197 | 0 | if (ifr->ifr_addr.sa_family == AF_INET) |
198 | 0 | { |
199 | 0 | sinptr = (struct sockaddr_in *)&ifr->ifr_addr; |
200 | 0 | if (sinptr->sin_addr.s_addr != lhost) |
201 | 0 | { |
202 | 0 | add = ntohl(sinptr->sin_addr.s_addr); |
203 | 0 | g_snprintf(ip, 16, "%lu.%lu.%lu.%lu", |
204 | 0 | ((add >> 24) & 255), |
205 | 0 | ((add >> 16) & 255), |
206 | 0 | ((add >> 8) & 255), |
207 | 0 | add & 255); |
208 | |
|
209 | 0 | return ip; |
210 | 0 | } |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | 0 | return "0.0.0.0"; |
215 | 0 | } |
216 | | |
217 | | GList * |
218 | | purple_network_get_all_local_system_ips(void) |
219 | 0 | { |
220 | 0 | #if defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP) |
221 | 0 | GList *result = NULL; |
222 | 0 | struct ifaddrs *start, *ifa; |
223 | 0 | int ret; |
224 | |
|
225 | 0 | ret = getifaddrs(&start); |
226 | 0 | if (ret < 0) { |
227 | 0 | purple_debug_warning("network", |
228 | 0 | "getifaddrs() failed: %s\n", g_strerror(errno)); |
229 | 0 | return NULL; |
230 | 0 | } |
231 | | |
232 | 0 | for (ifa = start; ifa; ifa = ifa->ifa_next) { |
233 | 0 | int family = ifa->ifa_addr ? ifa->ifa_addr->sa_family : AF_UNSPEC; |
234 | 0 | char host[INET6_ADDRSTRLEN]; |
235 | 0 | const char *tmp = NULL; |
236 | |
|
237 | 0 | if ((family != AF_INET && family != AF_INET6) || ifa->ifa_flags & IFF_LOOPBACK) |
238 | 0 | continue; |
239 | | |
240 | 0 | if (family == AF_INET) |
241 | 0 | tmp = inet_ntop(family, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, host, sizeof(host)); |
242 | 0 | else { |
243 | 0 | struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)ifa->ifa_addr; |
244 | | /* Peer-peer link-local communication is a big TODO. I am not sure |
245 | | * how communicating link-local addresses is supposed to work, and |
246 | | * it seems like it would require attempting the cartesian product |
247 | | * of the local and remote interfaces to see if any match (eww). |
248 | | */ |
249 | 0 | if (!IN6_IS_ADDR_LINKLOCAL(&sockaddr->sin6_addr)) |
250 | 0 | tmp = inet_ntop(family, &sockaddr->sin6_addr, host, sizeof(host)); |
251 | 0 | } |
252 | 0 | if (tmp != NULL) |
253 | 0 | result = g_list_prepend(result, g_strdup(tmp)); |
254 | 0 | } |
255 | |
|
256 | 0 | freeifaddrs(start); |
257 | |
|
258 | 0 | return g_list_reverse(result); |
259 | | #else /* HAVE_GETIFADDRS && HAVE_INET_NTOP */ |
260 | | GList *result = NULL; |
261 | | int source = socket(PF_INET,SOCK_STREAM, 0); |
262 | | char buffer[1024]; |
263 | | char *tmp; |
264 | | struct ifconf ifc; |
265 | | struct ifreq *ifr; |
266 | | |
267 | | ifc.ifc_len = sizeof(buffer); |
268 | | ifc.ifc_req = (struct ifreq *)buffer; |
269 | | ioctl(source, SIOCGIFCONF, &ifc); |
270 | | close(source); |
271 | | |
272 | | tmp = buffer; |
273 | | while (tmp < buffer + ifc.ifc_len) { |
274 | | char dst[INET_ADDRSTRLEN]; |
275 | | |
276 | | ifr = (struct ifreq *)tmp; |
277 | | tmp += HX_SIZE_OF_IFREQ(*ifr); |
278 | | |
279 | | if (ifr->ifr_addr.sa_family == AF_INET) { |
280 | | struct sockaddr_in *sinptr = (struct sockaddr_in *)&ifr->ifr_addr; |
281 | | |
282 | | inet_ntop(AF_INET, &sinptr->sin_addr, dst, |
283 | | sizeof(dst)); |
284 | | purple_debug_info("network", |
285 | | "found local i/f with address %s on IPv4\n", dst); |
286 | | if (!purple_strequal(dst, "127.0.0.1")) { |
287 | | result = g_list_append(result, g_strdup(dst)); |
288 | | } |
289 | | } |
290 | | } |
291 | | |
292 | | return result; |
293 | | #endif /* HAVE_GETIFADDRS && HAVE_INET_NTOP */ |
294 | 0 | } |
295 | | |
296 | | const char * |
297 | | purple_network_get_my_ip(int fd) |
298 | 0 | { |
299 | 0 | const char *ip = NULL; |
300 | 0 | PurpleStunNatDiscovery *stun; |
301 | | |
302 | | /* Check if the user specified an IP manually */ |
303 | 0 | if (!purple_prefs_get_bool("/purple/network/auto_ip")) { |
304 | 0 | ip = purple_network_get_public_ip(); |
305 | | /* Make sure the IP address entered by the user is valid */ |
306 | 0 | if ((ip != NULL) && (purple_network_ip_atoi(ip) != NULL)) |
307 | 0 | return ip; |
308 | 0 | } else { |
309 | | /* Check if STUN discovery was already done */ |
310 | 0 | stun = purple_stun_discover(NULL); |
311 | 0 | if ((stun != NULL) && (stun->status == PURPLE_STUN_STATUS_DISCOVERED)) |
312 | 0 | return stun->publicip; |
313 | | |
314 | | /* Attempt to get the IP from a NAT device using UPnP */ |
315 | 0 | ip = purple_upnp_get_public_ip(); |
316 | 0 | if (ip != NULL) |
317 | 0 | return ip; |
318 | | |
319 | | /* Attempt to get the IP from a NAT device using NAT-PMP */ |
320 | 0 | ip = purple_pmp_get_public_ip(); |
321 | 0 | if (ip != NULL) |
322 | 0 | return ip; |
323 | 0 | } |
324 | | |
325 | | /* Just fetch the IP of the local system */ |
326 | 0 | return purple_network_get_local_system_ip(fd); |
327 | 0 | } |
328 | | |
329 | | |
330 | | static void |
331 | | purple_network_set_upnp_port_mapping_cb(gboolean success, gpointer data) |
332 | 0 | { |
333 | 0 | PurpleNetworkListenData *listen_data; |
334 | |
|
335 | 0 | listen_data = data; |
336 | | /* TODO: Once we're keeping track of upnp requests... */ |
337 | | /* listen_data->pnp_data = NULL; */ |
338 | |
|
339 | 0 | if (!success) { |
340 | 0 | purple_debug_warning("network", "Couldn't create UPnP mapping\n"); |
341 | 0 | if (listen_data->retry) { |
342 | 0 | listen_data->retry = FALSE; |
343 | 0 | listen_data->adding = FALSE; |
344 | 0 | listen_data->mapping_data = purple_upnp_remove_port_mapping( |
345 | 0 | purple_network_get_port_from_fd(listen_data->listenfd), |
346 | 0 | (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP", |
347 | 0 | purple_network_set_upnp_port_mapping_cb, listen_data); |
348 | 0 | return; |
349 | 0 | } |
350 | 0 | } else if (!listen_data->adding) { |
351 | | /* We've tried successfully to remove the port mapping. |
352 | | * Try to add it again */ |
353 | 0 | listen_data->adding = TRUE; |
354 | 0 | listen_data->mapping_data = purple_upnp_set_port_mapping( |
355 | 0 | purple_network_get_port_from_fd(listen_data->listenfd), |
356 | 0 | (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP", |
357 | 0 | purple_network_set_upnp_port_mapping_cb, listen_data); |
358 | 0 | return; |
359 | 0 | } |
360 | | |
361 | 0 | if (success) { |
362 | | /* add port mapping to hash table */ |
363 | 0 | gint key = purple_network_get_port_from_fd(listen_data->listenfd); |
364 | 0 | gint value = listen_data->socket_type; |
365 | 0 | g_hash_table_insert(upnp_port_mappings, GINT_TO_POINTER(key), GINT_TO_POINTER(value)); |
366 | 0 | } |
367 | |
|
368 | 0 | if (listen_data->cb) |
369 | 0 | listen_data->cb(listen_data->listenfd, listen_data->cb_data); |
370 | | |
371 | | /* Clear the UPnP mapping data, since it's complete and purple_network_listen_cancel() will try to cancel |
372 | | * it otherwise. */ |
373 | 0 | listen_data->mapping_data = NULL; |
374 | 0 | purple_network_listen_cancel(listen_data); |
375 | 0 | } |
376 | | |
377 | | static gboolean |
378 | | purple_network_finish_pmp_map_cb(gpointer data) |
379 | 0 | { |
380 | 0 | PurpleNetworkListenData *listen_data; |
381 | 0 | gint key; |
382 | 0 | gint value; |
383 | |
|
384 | 0 | listen_data = data; |
385 | 0 | listen_data->timer = 0; |
386 | | |
387 | | /* add port mapping to hash table */ |
388 | 0 | key = purple_network_get_port_from_fd(listen_data->listenfd); |
389 | 0 | value = listen_data->socket_type; |
390 | 0 | g_hash_table_insert(nat_pmp_port_mappings, GINT_TO_POINTER(key), GINT_TO_POINTER(value)); |
391 | |
|
392 | 0 | if (listen_data->cb) |
393 | 0 | listen_data->cb(listen_data->listenfd, listen_data->cb_data); |
394 | |
|
395 | 0 | purple_network_listen_cancel(listen_data); |
396 | |
|
397 | 0 | return FALSE; |
398 | 0 | } |
399 | | |
400 | | static gboolean listen_map_external = TRUE; |
401 | | void purple_network_listen_map_external(gboolean map_external) |
402 | 0 | { |
403 | 0 | listen_map_external = map_external; |
404 | 0 | } |
405 | | |
406 | | static PurpleNetworkListenData * |
407 | | purple_network_do_listen(unsigned short port, int socket_family, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data) |
408 | 0 | { |
409 | 0 | int listenfd = -1; |
410 | 0 | const int on = 1; |
411 | 0 | PurpleNetworkListenData *listen_data; |
412 | 0 | unsigned short actual_port; |
413 | 0 | #ifdef HAVE_GETADDRINFO |
414 | 0 | int errnum; |
415 | 0 | struct addrinfo hints, *res, *next; |
416 | 0 | char serv[6]; |
417 | | |
418 | | /* |
419 | | * Get a list of addresses on this machine. |
420 | | */ |
421 | 0 | g_snprintf(serv, sizeof(serv), "%hu", port); |
422 | 0 | memset(&hints, 0, sizeof(struct addrinfo)); |
423 | 0 | hints.ai_flags = AI_PASSIVE; |
424 | 0 | hints.ai_family = socket_family; |
425 | 0 | hints.ai_socktype = socket_type; |
426 | 0 | errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res); |
427 | 0 | if (errnum != 0) { |
428 | 0 | #ifndef _WIN32 |
429 | 0 | purple_debug_warning("network", "getaddrinfo: %s\n", purple_gai_strerror(errnum)); |
430 | 0 | if (errnum == EAI_SYSTEM) |
431 | 0 | purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno)); |
432 | | #else |
433 | | purple_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum); |
434 | | #endif |
435 | 0 | return NULL; |
436 | 0 | } |
437 | | |
438 | | /* |
439 | | * Go through the list of addresses and attempt to listen on |
440 | | * one of them. |
441 | | * XXX - Try IPv6 addresses first? |
442 | | */ |
443 | 0 | for (next = res; next != NULL; next = next->ai_next) { |
444 | | #if _WIN32 |
445 | | /* |
446 | | * On Windows, the address family for the transport |
447 | | * address should always be set to AF_INET. |
448 | | */ |
449 | | if(next->ai_family != AF_INET) |
450 | | continue; |
451 | | #endif |
452 | 0 | listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol); |
453 | 0 | if (listenfd < 0) |
454 | 0 | continue; |
455 | 0 | if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) |
456 | 0 | purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_strerror(errno)); |
457 | 0 | if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0) |
458 | 0 | break; /* success */ |
459 | | /* XXX - It is unclear to me (datallah) whether we need to be |
460 | | using a new socket each time */ |
461 | 0 | close(listenfd); |
462 | 0 | } |
463 | |
|
464 | 0 | freeaddrinfo(res); |
465 | |
|
466 | 0 | if (next == NULL) |
467 | 0 | return NULL; |
468 | | #else |
469 | | struct sockaddr_in sockin; |
470 | | |
471 | | if (socket_family != AF_INET && socket_family != AF_UNSPEC) { |
472 | | purple_debug_warning("network", "Address family %d only " |
473 | | "supported when built with getaddrinfo() " |
474 | | "support\n", socket_family); |
475 | | return NULL; |
476 | | } |
477 | | |
478 | | if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) { |
479 | | purple_debug_warning("network", "socket: %s\n", g_strerror(errno)); |
480 | | return NULL; |
481 | | } |
482 | | |
483 | | if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) |
484 | | purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno)); |
485 | | |
486 | | memset(&sockin, 0, sizeof(struct sockaddr_in)); |
487 | | sockin.sin_family = PF_INET; |
488 | | sockin.sin_port = htons(port); |
489 | | |
490 | | if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) { |
491 | | purple_debug_warning("network", "bind: %s\n", g_strerror(errno)); |
492 | | close(listenfd); |
493 | | return NULL; |
494 | | } |
495 | | #endif |
496 | | |
497 | 0 | if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) { |
498 | 0 | purple_debug_warning("network", "listen: %s\n", g_strerror(errno)); |
499 | 0 | close(listenfd); |
500 | 0 | return NULL; |
501 | 0 | } |
502 | 0 | _purple_network_set_common_socket_flags(listenfd); |
503 | 0 | actual_port = purple_network_get_port_from_fd(listenfd); |
504 | |
|
505 | 0 | purple_debug_info("network", "Listening on port: %hu\n", actual_port); |
506 | |
|
507 | 0 | listen_data = g_new0(PurpleNetworkListenData, 1); |
508 | 0 | listen_data->listenfd = listenfd; |
509 | 0 | listen_data->adding = TRUE; |
510 | 0 | listen_data->retry = TRUE; |
511 | 0 | listen_data->cb = cb; |
512 | 0 | listen_data->cb_data = cb_data; |
513 | 0 | listen_data->socket_type = socket_type; |
514 | |
|
515 | 0 | if (!purple_socket_speaks_ipv4(listenfd) || !listen_map_external || |
516 | 0 | !purple_prefs_get_bool("/purple/network/map_ports")) |
517 | 0 | { |
518 | 0 | purple_debug_info("network", "Skipping external port mapping.\n"); |
519 | | /* The pmp_map_cb does what we want to do */ |
520 | 0 | listen_data->timer = purple_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data); |
521 | 0 | } |
522 | | /* Attempt a NAT-PMP Mapping, which will return immediately */ |
523 | 0 | else if (purple_pmp_create_map(((socket_type == SOCK_STREAM) ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP), |
524 | 0 | actual_port, actual_port, PURPLE_PMP_LIFETIME)) |
525 | 0 | { |
526 | 0 | purple_debug_info("network", "Created NAT-PMP mapping on port %i\n", actual_port); |
527 | | /* We want to return listen_data now, and on the next run loop trigger the cb and destroy listen_data */ |
528 | 0 | listen_data->timer = purple_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data); |
529 | 0 | } |
530 | 0 | else |
531 | 0 | { |
532 | | /* Attempt a UPnP Mapping */ |
533 | 0 | listen_data->mapping_data = purple_upnp_set_port_mapping( |
534 | 0 | actual_port, |
535 | 0 | (socket_type == SOCK_STREAM) ? "TCP" : "UDP", |
536 | 0 | purple_network_set_upnp_port_mapping_cb, listen_data); |
537 | 0 | } |
538 | |
|
539 | 0 | return listen_data; |
540 | 0 | } |
541 | | |
542 | | PurpleNetworkListenData * |
543 | | purple_network_listen_family(unsigned short port, int socket_family, |
544 | | int socket_type, PurpleNetworkListenCallback cb, |
545 | | gpointer cb_data) |
546 | 0 | { |
547 | 0 | g_return_val_if_fail(port != 0, NULL); |
548 | | |
549 | 0 | return purple_network_do_listen(port, socket_family, socket_type, |
550 | 0 | cb, cb_data); |
551 | 0 | } |
552 | | |
553 | | PurpleNetworkListenData * |
554 | | purple_network_listen(unsigned short port, int socket_type, |
555 | | PurpleNetworkListenCallback cb, gpointer cb_data) |
556 | 0 | { |
557 | 0 | return purple_network_listen_family(port, AF_UNSPEC, socket_type, |
558 | 0 | cb, cb_data); |
559 | 0 | } |
560 | | |
561 | | PurpleNetworkListenData * |
562 | | purple_network_listen_range_family(unsigned short start, unsigned short end, |
563 | | int socket_family, int socket_type, |
564 | | PurpleNetworkListenCallback cb, |
565 | | gpointer cb_data) |
566 | 0 | { |
567 | 0 | PurpleNetworkListenData *ret = NULL; |
568 | |
|
569 | 0 | if (purple_prefs_get_bool("/purple/network/ports_range_use")) { |
570 | 0 | start = purple_prefs_get_int("/purple/network/ports_range_start"); |
571 | 0 | end = purple_prefs_get_int("/purple/network/ports_range_end"); |
572 | 0 | } else { |
573 | 0 | if (end < start) |
574 | 0 | end = start; |
575 | 0 | } |
576 | |
|
577 | 0 | for (; start <= end; start++) { |
578 | 0 | ret = purple_network_do_listen(start, AF_UNSPEC, socket_type, cb, cb_data); |
579 | 0 | if (ret != NULL) |
580 | 0 | break; |
581 | 0 | } |
582 | |
|
583 | 0 | return ret; |
584 | 0 | } |
585 | | |
586 | | PurpleNetworkListenData * |
587 | | purple_network_listen_range(unsigned short start, unsigned short end, |
588 | | int socket_type, PurpleNetworkListenCallback cb, |
589 | | gpointer cb_data) |
590 | 0 | { |
591 | 0 | return purple_network_listen_range_family(start, end, AF_UNSPEC, |
592 | 0 | socket_type, cb, cb_data); |
593 | 0 | } |
594 | | |
595 | | void purple_network_listen_cancel(PurpleNetworkListenData *listen_data) |
596 | 0 | { |
597 | 0 | if (listen_data->mapping_data != NULL) |
598 | 0 | purple_upnp_cancel_port_mapping(listen_data->mapping_data); |
599 | |
|
600 | 0 | if (listen_data->timer > 0) |
601 | 0 | purple_timeout_remove(listen_data->timer); |
602 | |
|
603 | 0 | g_free(listen_data); |
604 | 0 | } |
605 | | |
606 | | unsigned short |
607 | | purple_network_get_port_from_fd(int fd) |
608 | 0 | { |
609 | 0 | common_sockaddr_t addr; |
610 | 0 | socklen_t len; |
611 | |
|
612 | 0 | g_return_val_if_fail(fd >= 0, 0); |
613 | | |
614 | 0 | len = sizeof(addr); |
615 | 0 | if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) { |
616 | 0 | purple_debug_warning("network", "getsockname: %s\n", g_strerror(errno)); |
617 | 0 | return 0; |
618 | 0 | } |
619 | | |
620 | 0 | return ntohs(addr.in.sin_port); |
621 | 0 | } |
622 | | |
623 | | #ifdef _WIN32 |
624 | | #ifndef NS_NLA |
625 | | #define NS_NLA 15 |
626 | | #endif |
627 | | static gint |
628 | | wpurple_get_connected_network_count(void) |
629 | | { |
630 | | gint net_cnt = 0; |
631 | | |
632 | | WSAQUERYSET qs; |
633 | | HANDLE h; |
634 | | gint retval; |
635 | | int errorid; |
636 | | |
637 | | memset(&qs, 0, sizeof(WSAQUERYSET)); |
638 | | qs.dwSize = sizeof(WSAQUERYSET); |
639 | | qs.dwNameSpace = NS_NLA; |
640 | | |
641 | | retval = WSALookupServiceBeginA(&qs, LUP_RETURN_ALL, &h); |
642 | | if (retval != ERROR_SUCCESS) { |
643 | | gchar *msg; |
644 | | errorid = WSAGetLastError(); |
645 | | msg = g_win32_error_message(errorid); |
646 | | purple_debug_warning("network", "Couldn't retrieve NLA SP lookup handle. " |
647 | | "NLA service is probably not running. Message: %s (%d).\n", |
648 | | msg, errorid); |
649 | | g_free(msg); |
650 | | |
651 | | return -1; |
652 | | } else { |
653 | | gchar *buf = NULL; |
654 | | WSAQUERYSET *res = (LPWSAQUERYSET) buf; |
655 | | DWORD current_size = 0; |
656 | | int iteration_count = 0; |
657 | | while (iteration_count++ < 100) { |
658 | | DWORD size = current_size; |
659 | | retval = WSALookupServiceNextA(h, 0, &size, res); |
660 | | if (retval == ERROR_SUCCESS) { |
661 | | net_cnt++; |
662 | | purple_debug_info("network", "found network '%s'\n", |
663 | | res->lpszServiceInstanceName ? res->lpszServiceInstanceName : "(NULL)"); |
664 | | } else { |
665 | | errorid = WSAGetLastError(); |
666 | | if (errorid == WSAEFAULT) { |
667 | | if (size == 0 || size > 102400) { |
668 | | purple_debug_warning("network", "Got unexpected NLA buffer size %" G_GUINT32_FORMAT ".\n", (guint32) size); |
669 | | break; |
670 | | } |
671 | | buf = g_realloc(buf, size); |
672 | | res = (LPWSAQUERYSET) buf; |
673 | | current_size = size; |
674 | | } else { |
675 | | break; |
676 | | } |
677 | | } |
678 | | } |
679 | | g_free(buf); |
680 | | |
681 | | if (!(errorid == WSA_E_NO_MORE || errorid == WSAENOMORE)) { |
682 | | gchar *msg = g_win32_error_message(errorid); |
683 | | purple_debug_error("network", "got unexpected NLA response %s (%d)\n", msg, errorid); |
684 | | g_free(msg); |
685 | | |
686 | | net_cnt = -1; |
687 | | } |
688 | | |
689 | | retval = WSALookupServiceEnd(h); |
690 | | } |
691 | | |
692 | | return net_cnt; |
693 | | |
694 | | } |
695 | | |
696 | | static gboolean wpurple_network_change_thread_cb(gpointer data) |
697 | | { |
698 | | gint new_count; |
699 | | PurpleConnectionUiOps *ui_ops = purple_connections_get_ui_ops(); |
700 | | |
701 | | new_count = wpurple_get_connected_network_count(); |
702 | | |
703 | | if (new_count < 0) |
704 | | return FALSE; |
705 | | |
706 | | purple_debug_info("network", "Received Network Change Notification. Current network count is %d, previous count was %d.\n", new_count, current_network_count); |
707 | | |
708 | | purple_signal_emit(purple_network_get_handle(), "network-configuration-changed", NULL); |
709 | | |
710 | | if (new_count > 0 && ui_ops != NULL && ui_ops->network_connected != NULL) { |
711 | | ui_ops->network_connected(); |
712 | | } else if (new_count == 0 && current_network_count > 0 && |
713 | | ui_ops != NULL && ui_ops->network_disconnected != NULL) { |
714 | | ui_ops->network_disconnected(); |
715 | | } |
716 | | |
717 | | current_network_count = new_count; |
718 | | |
719 | | return FALSE; |
720 | | } |
721 | | |
722 | | static gboolean _print_debug_msg(gpointer data) { |
723 | | gchar *msg = data; |
724 | | purple_debug_warning("network", "%s", msg); |
725 | | g_free(msg); |
726 | | return FALSE; |
727 | | } |
728 | | |
729 | | static gpointer wpurple_network_change_thread(gpointer data) |
730 | | { |
731 | | WSAQUERYSET qs; |
732 | | WSAEVENT *nla_event; |
733 | | time_t last_trigger = time(NULL) - 31; |
734 | | gchar *buf = NULL; |
735 | | WSAQUERYSET *res = (LPWSAQUERYSET) buf; |
736 | | DWORD current_size = 0; |
737 | | |
738 | | if ((nla_event = WSACreateEvent()) == WSA_INVALID_EVENT) { |
739 | | int errorid = WSAGetLastError(); |
740 | | gchar *msg = g_win32_error_message(errorid); |
741 | | purple_timeout_add(0, _print_debug_msg, |
742 | | g_strdup_printf("Couldn't create WSA event. " |
743 | | "Message: %s (%d).\n", msg, errorid)); |
744 | | g_free(msg); |
745 | | g_thread_exit(NULL); |
746 | | return NULL; |
747 | | } |
748 | | |
749 | | while (TRUE) { |
750 | | int retval; |
751 | | int iteration_count; |
752 | | DWORD retLen = 0; |
753 | | WSACOMPLETION completion; |
754 | | WSAOVERLAPPED overlapped; |
755 | | |
756 | | g_static_mutex_lock(&mutex); |
757 | | if (network_initialized == FALSE) { |
758 | | /* purple_network_uninit has been called */ |
759 | | WSACloseEvent(nla_event); |
760 | | g_static_mutex_unlock(&mutex); |
761 | | g_thread_exit(NULL); |
762 | | return NULL; |
763 | | } |
764 | | |
765 | | if (network_change_handle == NULL) { |
766 | | memset(&qs, 0, sizeof(WSAQUERYSET)); |
767 | | qs.dwSize = sizeof(WSAQUERYSET); |
768 | | qs.dwNameSpace = NS_NLA; |
769 | | if (WSALookupServiceBeginA(&qs, 0, &network_change_handle) == SOCKET_ERROR) { |
770 | | int errorid = WSAGetLastError(); |
771 | | gchar *msg = g_win32_error_message(errorid); |
772 | | purple_timeout_add(0, _print_debug_msg, |
773 | | g_strdup_printf("Couldn't retrieve NLA SP lookup handle. " |
774 | | "NLA service is probably not running. Message: %s (%d).\n", |
775 | | msg, errorid)); |
776 | | g_free(msg); |
777 | | WSACloseEvent(nla_event); |
778 | | g_static_mutex_unlock(&mutex); |
779 | | g_thread_exit(NULL); |
780 | | return NULL; |
781 | | } |
782 | | } |
783 | | g_static_mutex_unlock(&mutex); |
784 | | |
785 | | memset(&completion, 0, sizeof(WSACOMPLETION)); |
786 | | completion.Type = NSP_NOTIFY_EVENT; |
787 | | overlapped.hEvent = nla_event; |
788 | | completion.Parameters.Event.lpOverlapped = &overlapped; |
789 | | |
790 | | if (MyWSANSPIoctl(network_change_handle, SIO_NSP_NOTIFY_CHANGE, NULL, 0, NULL, 0, &retLen, &completion) == SOCKET_ERROR) { |
791 | | int errorid = WSAGetLastError(); |
792 | | if (errorid == WSA_INVALID_HANDLE) { |
793 | | purple_timeout_add(0, _print_debug_msg, |
794 | | g_strdup("Invalid NLA handle; resetting.\n")); |
795 | | g_static_mutex_lock(&mutex); |
796 | | retval = WSALookupServiceEnd(network_change_handle); |
797 | | network_change_handle = NULL; |
798 | | g_static_mutex_unlock(&mutex); |
799 | | continue; |
800 | | /* WSA_IO_PENDING indicates successful async notification will happen */ |
801 | | } else if (errorid != WSA_IO_PENDING) { |
802 | | gchar *msg = g_win32_error_message(errorid); |
803 | | purple_timeout_add(0, _print_debug_msg, |
804 | | g_strdup_printf("Unable to wait for changes. Message: %s (%d).\n", |
805 | | msg, errorid)); |
806 | | g_free(msg); |
807 | | } |
808 | | } |
809 | | |
810 | | /* Make sure at least 30 seconds have elapsed since the last |
811 | | * notification so we don't peg the cpu if this keeps changing. */ |
812 | | if ((time(NULL) - last_trigger) < 30) |
813 | | Sleep(30000); |
814 | | |
815 | | /* This will block until NLA notifies us */ |
816 | | retval = WaitForSingleObjectEx(nla_event, WSA_INFINITE, TRUE); |
817 | | |
818 | | last_trigger = time(NULL); |
819 | | |
820 | | g_static_mutex_lock(&mutex); |
821 | | if (network_initialized == FALSE) { |
822 | | /* Time to die */ |
823 | | WSACloseEvent(nla_event); |
824 | | g_static_mutex_unlock(&mutex); |
825 | | g_thread_exit(NULL); |
826 | | return NULL; |
827 | | } |
828 | | |
829 | | iteration_count = 0; |
830 | | while (iteration_count++ < 100) { |
831 | | DWORD size = current_size; |
832 | | retval = WSALookupServiceNextA(network_change_handle, 0, &size, res); |
833 | | if (retval == ERROR_SUCCESS) { |
834 | | /*purple_timeout_add(0, _print_debug_msg, |
835 | | g_strdup_printf("thread found network '%s'\n", |
836 | | res->lpszServiceInstanceName ? res->lpszServiceInstanceName : "(NULL)"));*/ |
837 | | } else { |
838 | | int errorid = WSAGetLastError(); |
839 | | if (errorid == WSAEFAULT) { |
840 | | if (size == 0 || size > 102400) { |
841 | | purple_timeout_add(0, _print_debug_msg, |
842 | | g_strdup_printf("Thread got unexpected NLA buffer size %" G_GUINT32_FORMAT ".\n", (guint32) size)); |
843 | | break; |
844 | | } |
845 | | buf = g_realloc(buf, size); |
846 | | res = (LPWSAQUERYSET) buf; |
847 | | current_size = size; |
848 | | } else { |
849 | | break; |
850 | | } |
851 | | } |
852 | | |
853 | | } |
854 | | g_free(buf); |
855 | | buf = NULL; |
856 | | current_size = 0; |
857 | | |
858 | | WSAResetEvent(nla_event); |
859 | | g_static_mutex_unlock(&mutex); |
860 | | |
861 | | purple_timeout_add(0, wpurple_network_change_thread_cb, NULL); |
862 | | } |
863 | | |
864 | | g_thread_exit(NULL); |
865 | | return NULL; |
866 | | } |
867 | | #endif |
868 | | |
869 | | gboolean |
870 | | purple_network_is_available(void) |
871 | 0 | { |
872 | | #ifdef HAVE_NETWORKMANAGER |
873 | | if (force_online) |
874 | | return TRUE; |
875 | | |
876 | | if (!have_nm_state) |
877 | | { |
878 | | have_nm_state = TRUE; |
879 | | nm_state = nm_get_network_state(); |
880 | | if (nm_state == NM_STATE_UNKNOWN) |
881 | | purple_debug_warning("network", "NetworkManager not active. Assuming connection exists.\n"); |
882 | | } |
883 | | |
884 | | switch (nm_state) |
885 | | { |
886 | | case NM_STATE_UNKNOWN: |
887 | | #if NM_CHECK_VERSION(0,8,992) |
888 | | case NM_STATE_CONNECTED_LOCAL: |
889 | | case NM_STATE_CONNECTED_SITE: |
890 | | case NM_STATE_CONNECTED_GLOBAL: |
891 | | #else |
892 | | case NM_STATE_CONNECTED: |
893 | | #endif |
894 | | return TRUE; |
895 | | default: |
896 | | break; |
897 | | } |
898 | | |
899 | | return FALSE; |
900 | | |
901 | | #elif defined _WIN32 |
902 | | return (current_network_count > 0 || force_online); |
903 | | #else |
904 | 0 | return TRUE; |
905 | 0 | #endif |
906 | 0 | } |
907 | | |
908 | | void |
909 | | purple_network_force_online() |
910 | 0 | { |
911 | | #if defined(HAVE_NETWORKMANAGER) || defined(_WIN32) |
912 | | force_online = TRUE; |
913 | | #endif |
914 | 0 | } |
915 | | |
916 | | #ifdef HAVE_NETWORKMANAGER |
917 | | static void |
918 | | nm_update_state(NMState state) |
919 | | { |
920 | | NMState prev = nm_state; |
921 | | PurpleConnectionUiOps *ui_ops = purple_connections_get_ui_ops(); |
922 | | |
923 | | have_nm_state = TRUE; |
924 | | nm_state = state; |
925 | | |
926 | | purple_signal_emit(purple_network_get_handle(), "network-configuration-changed", NULL); |
927 | | |
928 | | switch(state) |
929 | | { |
930 | | #if NM_CHECK_VERSION(0,8,992) |
931 | | case NM_STATE_CONNECTED_LOCAL: |
932 | | case NM_STATE_CONNECTED_SITE: |
933 | | case NM_STATE_CONNECTED_GLOBAL: |
934 | | #else |
935 | | case NM_STATE_CONNECTED: |
936 | | #endif |
937 | | /* Call res_init in case DNS servers have changed */ |
938 | | res_init(); |
939 | | /* update STUN IP in case we it changed (theoretically we could |
940 | | have gone from IPv4 to IPv6, f.ex. or we were previously |
941 | | offline */ |
942 | | purple_network_set_stun_server( |
943 | | purple_prefs_get_string("/purple/network/stun_server")); |
944 | | purple_network_set_turn_server( |
945 | | purple_prefs_get_string("/purple/network/turn_server")); |
946 | | |
947 | | if (ui_ops != NULL && ui_ops->network_connected != NULL) |
948 | | ui_ops->network_connected(); |
949 | | break; |
950 | | case NM_STATE_ASLEEP: |
951 | | case NM_STATE_CONNECTING: |
952 | | case NM_STATE_DISCONNECTED: |
953 | | #if NM_CHECK_VERSION(0,8,992) |
954 | | case NM_STATE_DISCONNECTING: |
955 | | #endif |
956 | | #if NM_CHECK_VERSION(1,0,0) |
957 | | if (prev != NM_STATE_CONNECTED_GLOBAL && prev != NM_STATE_UNKNOWN) |
958 | | break; |
959 | | #else |
960 | | if (prev != NM_STATE_CONNECTED && prev != NM_STATE_UNKNOWN) |
961 | | break; |
962 | | #endif |
963 | | if (ui_ops != NULL && ui_ops->network_disconnected != NULL) |
964 | | ui_ops->network_disconnected(); |
965 | | break; |
966 | | case NM_STATE_UNKNOWN: |
967 | | default: |
968 | | break; |
969 | | } |
970 | | } |
971 | | |
972 | | static void |
973 | | nm_state_change_cb(DBusGProxy *proxy, NMState state, gpointer user_data) |
974 | | { |
975 | | purple_debug_info("network", "Got StateChange from NetworkManager: %d.\n", state); |
976 | | nm_update_state(state); |
977 | | } |
978 | | |
979 | | static NMState |
980 | | nm_get_network_state(void) |
981 | | { |
982 | | GError *err = NULL; |
983 | | NMState state = NM_STATE_UNKNOWN; |
984 | | |
985 | | if (!nm_proxy) |
986 | | return NM_STATE_UNKNOWN; |
987 | | |
988 | | if (!dbus_g_proxy_call(nm_proxy, "state", &err, G_TYPE_INVALID, G_TYPE_UINT, &state, G_TYPE_INVALID)) { |
989 | | g_error_free(err); |
990 | | return NM_STATE_UNKNOWN; |
991 | | } |
992 | | |
993 | | return state; |
994 | | } |
995 | | |
996 | | static void |
997 | | nm_dbus_name_owner_changed_cb(DBusGProxy *proxy, char *service, char *old_owner, char *new_owner, gpointer user_data) |
998 | | { |
999 | | if (purple_strequal(service, NM_DBUS_SERVICE)) { |
1000 | | gboolean old_owner_good = old_owner && (old_owner[0] != '\0'); |
1001 | | gboolean new_owner_good = new_owner && (new_owner[0] != '\0'); |
1002 | | |
1003 | | purple_debug_info("network", "Got NameOwnerChanged signal, service = '%s', old_owner = '%s', new_owner = '%s'\n", service, old_owner, new_owner); |
1004 | | if (!old_owner_good && new_owner_good) { /* Equivalent to old ServiceCreated signal */ |
1005 | | purple_debug_info("network", "NetworkManager has started.\n"); |
1006 | | nm_update_state(nm_get_network_state()); |
1007 | | } else if (old_owner_good && !new_owner_good) { /* Equivalent to old ServiceDeleted signal */ |
1008 | | purple_debug_info("network", "NetworkManager has gone away.\n"); |
1009 | | nm_update_state(NM_STATE_UNKNOWN); |
1010 | | } |
1011 | | } |
1012 | | } |
1013 | | |
1014 | | #endif |
1015 | | |
1016 | | static void |
1017 | | purple_network_ip_lookup_cb(GSList *hosts, gpointer data, |
1018 | | const char *error_message) |
1019 | 0 | { |
1020 | 0 | const gchar **ip = (const gchar **) data; |
1021 | |
|
1022 | 0 | if (error_message) { |
1023 | 0 | purple_debug_error("network", "lookup of IP address failed: %s\n", |
1024 | 0 | error_message); |
1025 | 0 | g_slist_free(hosts); |
1026 | 0 | return; |
1027 | 0 | } |
1028 | | |
1029 | 0 | if (hosts && g_slist_next(hosts)) { |
1030 | 0 | struct sockaddr *addr = g_slist_next(hosts)->data; |
1031 | 0 | char dst[INET6_ADDRSTRLEN]; |
1032 | |
|
1033 | 0 | if (addr->sa_family == AF_INET6) { |
1034 | 0 | inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, |
1035 | 0 | dst, sizeof(dst)); |
1036 | 0 | } else { |
1037 | 0 | inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, |
1038 | 0 | dst, sizeof(dst)); |
1039 | 0 | } |
1040 | |
|
1041 | 0 | *ip = g_strdup(dst); |
1042 | 0 | purple_debug_info("network", "set IP address: %s\n", *ip); |
1043 | 0 | } |
1044 | |
|
1045 | 0 | while (hosts != NULL) { |
1046 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
1047 | | /* Free the address */ |
1048 | 0 | g_free(hosts->data); |
1049 | 0 | hosts = g_slist_delete_link(hosts, hosts); |
1050 | 0 | } |
1051 | 0 | } |
1052 | | |
1053 | | void |
1054 | | purple_network_set_stun_server(const gchar *stun_server) |
1055 | 0 | { |
1056 | 0 | if (stun_server && stun_server[0] != '\0') { |
1057 | 0 | if (purple_network_is_available()) { |
1058 | 0 | purple_debug_info("network", "running DNS query for STUN server\n"); |
1059 | 0 | purple_dnsquery_a_account(NULL, stun_server, 3478, purple_network_ip_lookup_cb, |
1060 | 0 | &stun_ip); |
1061 | 0 | } else { |
1062 | 0 | purple_debug_info("network", |
1063 | 0 | "network is unavailable, don't try to update STUN IP"); |
1064 | 0 | } |
1065 | 0 | } else if (stun_ip) { |
1066 | 0 | g_free(stun_ip); |
1067 | 0 | stun_ip = NULL; |
1068 | 0 | } |
1069 | 0 | } |
1070 | | |
1071 | | void |
1072 | | purple_network_set_turn_server(const gchar *turn_server) |
1073 | 0 | { |
1074 | 0 | if (turn_server && turn_server[0] != '\0') { |
1075 | 0 | if (purple_network_is_available()) { |
1076 | 0 | purple_debug_info("network", "running DNS query for TURN server\n"); |
1077 | 0 | purple_dnsquery_a_account(NULL, turn_server, |
1078 | 0 | purple_prefs_get_int("/purple/network/turn_port"), |
1079 | 0 | purple_network_ip_lookup_cb, &turn_ip); |
1080 | 0 | } else { |
1081 | 0 | purple_debug_info("network", |
1082 | 0 | "network is unavailable, don't try to update TURN IP"); |
1083 | 0 | } |
1084 | 0 | } else if (turn_ip) { |
1085 | 0 | g_free(turn_ip); |
1086 | 0 | turn_ip = NULL; |
1087 | 0 | } |
1088 | 0 | } |
1089 | | |
1090 | | |
1091 | | const gchar * |
1092 | | purple_network_get_stun_ip(void) |
1093 | 0 | { |
1094 | 0 | return stun_ip; |
1095 | 0 | } |
1096 | | |
1097 | | const gchar * |
1098 | | purple_network_get_turn_ip(void) |
1099 | 0 | { |
1100 | 0 | return turn_ip; |
1101 | 0 | } |
1102 | | |
1103 | | void * |
1104 | | purple_network_get_handle(void) |
1105 | 0 | { |
1106 | 0 | static int handle; |
1107 | |
|
1108 | 0 | return &handle; |
1109 | 0 | } |
1110 | | |
1111 | | static void |
1112 | | purple_network_upnp_mapping_remove_cb(gboolean sucess, gpointer data) |
1113 | 0 | { |
1114 | 0 | purple_debug_info("network", "done removing UPnP port mapping\n"); |
1115 | 0 | } |
1116 | | |
1117 | | /* the reason for these functions to have these signatures is to be able to |
1118 | | use them for g_hash_table_foreach to clean remaining port mappings, which is |
1119 | | not yet done */ |
1120 | | static void |
1121 | | purple_network_upnp_mapping_remove(gpointer key, gpointer value, |
1122 | | gpointer user_data) |
1123 | 0 | { |
1124 | 0 | gint port = GPOINTER_TO_INT(key); |
1125 | 0 | gint protocol = GPOINTER_TO_INT(value); |
1126 | 0 | purple_debug_info("network", "removing UPnP port mapping for port %d\n", |
1127 | 0 | port); |
1128 | 0 | purple_upnp_remove_port_mapping(port, |
1129 | 0 | protocol == SOCK_STREAM ? "TCP" : "UDP", |
1130 | 0 | purple_network_upnp_mapping_remove_cb, NULL); |
1131 | 0 | g_hash_table_remove(upnp_port_mappings, GINT_TO_POINTER(port)); |
1132 | 0 | } |
1133 | | |
1134 | | static void |
1135 | | purple_network_nat_pmp_mapping_remove(gpointer key, gpointer value, |
1136 | | gpointer user_data) |
1137 | 0 | { |
1138 | 0 | gint port = GPOINTER_TO_INT(key); |
1139 | 0 | gint protocol = GPOINTER_TO_INT(value); |
1140 | 0 | purple_debug_info("network", "removing NAT-PMP port mapping for port %d\n", |
1141 | 0 | port); |
1142 | 0 | purple_pmp_destroy_map( |
1143 | 0 | protocol == SOCK_STREAM ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP, |
1144 | 0 | port); |
1145 | 0 | g_hash_table_remove(nat_pmp_port_mappings, GINT_TO_POINTER(port)); |
1146 | 0 | } |
1147 | | |
1148 | | void |
1149 | | purple_network_remove_port_mapping(gint fd) |
1150 | 0 | { |
1151 | 0 | int port = purple_network_get_port_from_fd(fd); |
1152 | 0 | gint protocol = GPOINTER_TO_INT(g_hash_table_lookup(upnp_port_mappings, GINT_TO_POINTER(port))); |
1153 | |
|
1154 | 0 | if (protocol) { |
1155 | 0 | purple_network_upnp_mapping_remove(GINT_TO_POINTER(port), GINT_TO_POINTER(protocol), NULL); |
1156 | 0 | } else { |
1157 | 0 | protocol = GPOINTER_TO_INT(g_hash_table_lookup(nat_pmp_port_mappings, GINT_TO_POINTER(port))); |
1158 | 0 | if (protocol) { |
1159 | 0 | purple_network_nat_pmp_mapping_remove(GINT_TO_POINTER(port), GINT_TO_POINTER(protocol), NULL); |
1160 | 0 | } |
1161 | 0 | } |
1162 | 0 | } |
1163 | | |
1164 | | int purple_network_convert_idn_to_ascii(const gchar *in, gchar **out) |
1165 | 0 | { |
1166 | | #ifdef USE_IDN |
1167 | | char *tmp; |
1168 | | int ret; |
1169 | | |
1170 | | g_return_val_if_fail(out != NULL, -1); |
1171 | | |
1172 | | ret = idna_to_ascii_8z(in, &tmp, IDNA_USE_STD3_ASCII_RULES); |
1173 | | if (ret != IDNA_SUCCESS) { |
1174 | | *out = NULL; |
1175 | | return ret; |
1176 | | } |
1177 | | |
1178 | | *out = g_strdup(tmp); |
1179 | | /* This *MUST* be freed with free, not g_free */ |
1180 | | free(tmp); |
1181 | | return 0; |
1182 | | #else |
1183 | 0 | g_return_val_if_fail(out != NULL, -1); |
1184 | | |
1185 | 0 | *out = g_strdup(in); |
1186 | 0 | return 0; |
1187 | 0 | #endif |
1188 | 0 | } |
1189 | | |
1190 | | gboolean |
1191 | | _purple_network_set_common_socket_flags(int fd) |
1192 | 0 | { |
1193 | 0 | int flags; |
1194 | 0 | gboolean succ = TRUE; |
1195 | |
|
1196 | 0 | g_return_val_if_fail(fd >= 0, FALSE); |
1197 | | |
1198 | 0 | flags = fcntl(fd, F_GETFL); |
1199 | |
|
1200 | 0 | if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != 0) { |
1201 | 0 | purple_debug_warning("network", |
1202 | 0 | "Couldn't set O_NONBLOCK flag\n"); |
1203 | 0 | succ = FALSE; |
1204 | 0 | } |
1205 | |
|
1206 | 0 | #ifndef _WIN32 |
1207 | 0 | if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) { |
1208 | 0 | purple_debug_warning("network", |
1209 | 0 | "Couldn't set FD_CLOEXEC flag\n"); |
1210 | 0 | succ = FALSE; |
1211 | 0 | } |
1212 | 0 | #endif |
1213 | |
|
1214 | 0 | return succ; |
1215 | 0 | } |
1216 | | |
1217 | | void |
1218 | | purple_network_init(void) |
1219 | 0 | { |
1220 | | #ifdef HAVE_NETWORKMANAGER |
1221 | | GError *error = NULL; |
1222 | | #endif |
1223 | | #ifdef _WIN32 |
1224 | | GError *err = NULL; |
1225 | | gint cnt = wpurple_get_connected_network_count(); |
1226 | | |
1227 | | network_initialized = TRUE; |
1228 | | if (cnt < 0) /* Assume there is a network */ |
1229 | | current_network_count = 1; |
1230 | | /* Don't listen for network changes if we can't tell anyway */ |
1231 | | else { |
1232 | | current_network_count = cnt; |
1233 | | if ((MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) { |
1234 | | if (!g_thread_create(wpurple_network_change_thread, NULL, FALSE, &err)) |
1235 | | purple_debug_error("network", "Couldn't create Network Monitor thread: %s\n", err ? err->message : ""); |
1236 | | } |
1237 | | } |
1238 | | #endif |
1239 | |
|
1240 | 0 | purple_prefs_add_none ("/purple/network"); |
1241 | 0 | purple_prefs_add_string("/purple/network/stun_server", ""); |
1242 | 0 | purple_prefs_add_string("/purple/network/turn_server", ""); |
1243 | 0 | purple_prefs_add_int ("/purple/network/turn_port", 3478); |
1244 | 0 | purple_prefs_add_int ("/purple/network/turn_port_tcp", 3478); |
1245 | 0 | purple_prefs_add_string("/purple/network/turn_username", ""); |
1246 | 0 | purple_prefs_add_string("/purple/network/turn_password", ""); |
1247 | 0 | purple_prefs_add_bool ("/purple/network/auto_ip", FALSE); |
1248 | 0 | purple_prefs_add_string("/purple/network/public_ip", ""); |
1249 | 0 | purple_prefs_add_bool ("/purple/network/map_ports", FALSE); |
1250 | 0 | purple_prefs_add_bool ("/purple/network/ports_range_use", FALSE); |
1251 | 0 | purple_prefs_add_int ("/purple/network/ports_range_start", 1024); |
1252 | 0 | purple_prefs_add_int ("/purple/network/ports_range_end", 2048); |
1253 | |
|
1254 | 0 | if(purple_prefs_get_bool("/purple/network/map_ports") || purple_prefs_get_bool("/purple/network/auto_ip")) |
1255 | 0 | purple_upnp_discover(NULL, NULL); |
1256 | |
|
1257 | | #ifdef HAVE_NETWORKMANAGER |
1258 | | nm_conn = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error); |
1259 | | if (!nm_conn) { |
1260 | | purple_debug_warning("network", "Error connecting to DBus System service: %s.\n", error->message); |
1261 | | } else { |
1262 | | nm_proxy = dbus_g_proxy_new_for_name(nm_conn, |
1263 | | NM_DBUS_SERVICE, |
1264 | | NM_DBUS_PATH, |
1265 | | NM_DBUS_INTERFACE); |
1266 | | /* NM 0.6 signal */ |
1267 | | dbus_g_proxy_add_signal(nm_proxy, "StateChange", G_TYPE_UINT, G_TYPE_INVALID); |
1268 | | dbus_g_proxy_connect_signal(nm_proxy, "StateChange", |
1269 | | G_CALLBACK(nm_state_change_cb), NULL, NULL); |
1270 | | /* NM 0.7 and later signal */ |
1271 | | dbus_g_proxy_add_signal(nm_proxy, "StateChanged", G_TYPE_UINT, G_TYPE_INVALID); |
1272 | | dbus_g_proxy_connect_signal(nm_proxy, "StateChanged", |
1273 | | G_CALLBACK(nm_state_change_cb), NULL, NULL); |
1274 | | |
1275 | | dbus_proxy = dbus_g_proxy_new_for_name(nm_conn, |
1276 | | DBUS_SERVICE_DBUS, |
1277 | | DBUS_PATH_DBUS, |
1278 | | DBUS_INTERFACE_DBUS); |
1279 | | dbus_g_proxy_add_signal(dbus_proxy, "NameOwnerChanged", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID); |
1280 | | dbus_g_proxy_connect_signal(dbus_proxy, "NameOwnerChanged", |
1281 | | G_CALLBACK(nm_dbus_name_owner_changed_cb), NULL, NULL); |
1282 | | } |
1283 | | #endif |
1284 | |
|
1285 | 0 | purple_signal_register(purple_network_get_handle(), "network-configuration-changed", |
1286 | 0 | purple_marshal_VOID, NULL, 0); |
1287 | |
|
1288 | 0 | purple_pmp_init(); |
1289 | 0 | purple_upnp_init(); |
1290 | |
|
1291 | 0 | purple_network_set_stun_server( |
1292 | 0 | purple_prefs_get_string("/purple/network/stun_server")); |
1293 | 0 | purple_network_set_turn_server( |
1294 | 0 | purple_prefs_get_string("/purple/network/turn_server")); |
1295 | |
|
1296 | 0 | upnp_port_mappings = g_hash_table_new(g_direct_hash, g_direct_equal); |
1297 | 0 | nat_pmp_port_mappings = g_hash_table_new(g_direct_hash, g_direct_equal); |
1298 | 0 | } |
1299 | | |
1300 | | |
1301 | | |
1302 | | void |
1303 | | purple_network_uninit(void) |
1304 | 0 | { |
1305 | | #ifdef HAVE_NETWORKMANAGER |
1306 | | if (nm_proxy) { |
1307 | | dbus_g_proxy_disconnect_signal(nm_proxy, "StateChange", G_CALLBACK(nm_state_change_cb), NULL); |
1308 | | dbus_g_proxy_disconnect_signal(nm_proxy, "StateChanged", G_CALLBACK(nm_state_change_cb), NULL); |
1309 | | g_object_unref(G_OBJECT(nm_proxy)); |
1310 | | } |
1311 | | if (dbus_proxy) { |
1312 | | dbus_g_proxy_disconnect_signal(dbus_proxy, "NameOwnerChanged", G_CALLBACK(nm_dbus_name_owner_changed_cb), NULL); |
1313 | | g_object_unref(G_OBJECT(dbus_proxy)); |
1314 | | } |
1315 | | if (nm_conn) |
1316 | | dbus_g_connection_unref(nm_conn); |
1317 | | #endif |
1318 | |
|
1319 | | #ifdef _WIN32 |
1320 | | g_static_mutex_lock(&mutex); |
1321 | | network_initialized = FALSE; |
1322 | | if (network_change_handle != NULL) { |
1323 | | int retval; |
1324 | | /* Trigger the NLA thread to stop waiting for network changes. Not |
1325 | | * doing this can cause hangs on WSACleanup. */ |
1326 | | purple_debug_warning("network", "Terminating the NLA thread\n"); |
1327 | | if ((retval = WSALookupServiceEnd(network_change_handle)) == SOCKET_ERROR) { |
1328 | | int errorid = WSAGetLastError(); |
1329 | | gchar *msg = g_win32_error_message(errorid); |
1330 | | purple_debug_warning("network", "Unable to kill NLA thread. Message: %s (%d).\n", |
1331 | | msg, errorid); |
1332 | | g_free(msg); |
1333 | | } |
1334 | | network_change_handle = NULL; |
1335 | | |
1336 | | } |
1337 | | g_static_mutex_unlock(&mutex); |
1338 | | |
1339 | | #endif |
1340 | 0 | purple_signal_unregister(purple_network_get_handle(), |
1341 | 0 | "network-configuration-changed"); |
1342 | |
|
1343 | 0 | if (stun_ip) |
1344 | 0 | g_free(stun_ip); |
1345 | |
|
1346 | 0 | g_hash_table_destroy(upnp_port_mappings); |
1347 | 0 | g_hash_table_destroy(nat_pmp_port_mappings); |
1348 | | |
1349 | | /* TODO: clean up remaining port mappings, note calling |
1350 | | purple_upnp_remove_port_mapping from here doesn't quite work... */ |
1351 | 0 | } |