3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include <arpa/nameser.h>
29 #include <netinet/in.h>
31 #include <sys/ioctl.h>
32 #ifdef HAVE_GETIFADDRS
40 #if defined (__SVR4) && defined (__sun)
41 #include <sys/sockio.h>
53 * Calling sizeof(struct ifreq) isn't always correct on
54 * Mac OS X (and maybe others).
56 #ifdef _SIZEOF_ADDR_IFREQ
57 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
59 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
62 struct _PurpleNetworkListenData
{
67 PurpleNetworkListenCallback cb
;
69 PurpleUPnPMappingAddRemove
*mapping_data
;
73 static gboolean force_online
= FALSE
;
75 /* Cached IP addresses for STUN and TURN servers (set globally in prefs) */
76 static gchar
*stun_ip
= NULL
;
77 static gchar
*turn_ip
= NULL
;
79 /* Keep track of port mappings done with UPnP and NAT-PMP */
80 static GHashTable
*upnp_port_mappings
= NULL
;
81 static GHashTable
*nat_pmp_port_mappings
= NULL
;
84 purple_network_set_public_ip(const char *ip
)
86 g_return_if_fail(ip
!= NULL
);
88 /* XXX - Ensure the IP address is valid */
90 purple_prefs_set_string("/purple/network/public_ip", ip
);
94 purple_network_get_public_ip(void)
96 return purple_prefs_get_string("/purple/network/public_ip");
100 purple_network_get_local_system_ip(int fd
)
102 struct ifreq buffer
[100];
107 struct sockaddr_in
*sinptr
;
108 guint32 lhost
= htonl((127 << 24) + 1); /* 127.0.0.1 */
109 long unsigned int add
;
113 source
= socket(PF_INET
,SOCK_STREAM
, 0);
115 ifc
.ifc_len
= sizeof(buffer
);
116 ifc
.ifc_req
= buffer
;
117 ioctl(source
, SIOCGIFCONF
, &ifc
);
119 if (fd
< 0 && source
>= 0)
122 it
= (guchar
*)buffer
;
123 it_end
= it
+ ifc
.ifc_len
;
124 while (it
< it_end
) {
125 /* in this case "it" is:
126 * a) (struct ifreq)-aligned
127 * b) not aligned, because of OS quirks (see
128 * _SIZEOF_ADDR_IFREQ), so the OS should deal with it.
130 ifr
= (struct ifreq
*)(gpointer
)it
;
131 it
+= HX_SIZE_OF_IFREQ(*ifr
);
133 if (ifr
->ifr_addr
.sa_family
== AF_INET
)
135 sinptr
= (struct sockaddr_in
*)(gpointer
)&ifr
->ifr_addr
;
136 if (sinptr
->sin_addr
.s_addr
!= lhost
)
138 add
= ntohl(sinptr
->sin_addr
.s_addr
);
139 g_snprintf(ip
, 16, "%lu.%lu.%lu.%lu",
154 purple_network_get_all_local_system_ips(void)
156 #if defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP)
157 GList
*result
= NULL
;
158 struct ifaddrs
*start
, *ifa
;
161 ret
= getifaddrs(&start
);
163 purple_debug_warning("network",
164 "getifaddrs() failed: %s\n", g_strerror(errno
));
168 for (ifa
= start
; ifa
; ifa
= ifa
->ifa_next
) {
169 int family
= ifa
->ifa_addr
? ifa
->ifa_addr
->sa_family
: AF_UNSPEC
;
170 char host
[INET6_ADDRSTRLEN
];
171 const char *tmp
= NULL
;
172 common_sockaddr_t
*addr
=
173 (common_sockaddr_t
*)(gpointer
)ifa
->ifa_addr
;
175 if ((family
!= AF_INET
&& family
!= AF_INET6
) || ifa
->ifa_flags
& IFF_LOOPBACK
)
178 if (family
== AF_INET
)
179 tmp
= inet_ntop(family
, &addr
->in
.sin_addr
, host
, sizeof(host
));
181 /* Peer-peer link-local communication is a big TODO. I am not sure
182 * how communicating link-local addresses is supposed to work, and
183 * it seems like it would require attempting the cartesian product
184 * of the local and remote interfaces to see if any match (eww).
186 if (!IN6_IS_ADDR_LINKLOCAL(&addr
->in6
.sin6_addr
))
187 tmp
= inet_ntop(family
, &addr
->in6
.sin6_addr
, host
, sizeof(host
));
190 result
= g_list_prepend(result
, g_strdup(tmp
));
195 return g_list_reverse(result
);
196 #else /* HAVE_GETIFADDRS && HAVE_INET_NTOP */
197 GList
*result
= NULL
;
198 int source
= socket(PF_INET
,SOCK_STREAM
, 0);
199 struct ifreq buffer
[100];
204 ifc
.ifc_len
= sizeof(buffer
);
205 ifc
.ifc_req
= buffer
;
206 ioctl(source
, SIOCGIFCONF
, &ifc
);
209 it
= (guchar
*)buffer
;
210 it_end
= it
+ ifc
.ifc_len
;
211 while (it
< it_end
) {
212 char dst
[INET_ADDRSTRLEN
];
214 /* alignment: see purple_network_get_local_system_ip */
215 ifr
= (struct ifreq
*)(gpointer
)it
;
216 it
+= HX_SIZE_OF_IFREQ(*ifr
);
218 if (ifr
->ifr_addr
.sa_family
== AF_INET
) {
219 struct sockaddr_in
*sinptr
=
220 (struct sockaddr_in
*)(gpointer
)&ifr
->ifr_addr
;
222 inet_ntop(AF_INET
, &sinptr
->sin_addr
, dst
,
224 purple_debug_info("network",
225 "found local i/f with address %s on IPv4\n", dst
);
226 if (!purple_strequal(dst
, "127.0.0.1")) {
227 result
= g_list_append(result
, g_strdup(dst
));
233 #endif /* HAVE_GETIFADDRS && HAVE_INET_NTOP */
237 * purple_network_is_ipv4:
238 * @hostname: The hostname to be verified.
240 * Checks, if specified hostname is valid ipv4 address.
242 * Returns: TRUE, if the hostname is valid.
245 purple_network_is_ipv4(const gchar
*hostname
)
247 g_return_val_if_fail(hostname
!= NULL
, FALSE
);
249 /* We don't accept ipv6 here. */
250 if (strchr(hostname
, ':') != NULL
)
253 return g_hostname_is_ip_address(hostname
);
257 purple_network_get_my_ip(int fd
)
259 const char *ip
= NULL
;
260 PurpleStunNatDiscovery
*stun
;
262 /* Check if the user specified an IP manually */
263 if (!purple_prefs_get_bool("/purple/network/auto_ip")) {
264 ip
= purple_network_get_public_ip();
265 /* Make sure the IP address entered by the user is valid */
266 if ((ip
!= NULL
) && (purple_network_is_ipv4(ip
)))
269 /* Check if STUN discovery was already done */
270 stun
= purple_stun_discover(NULL
);
271 if ((stun
!= NULL
) && (stun
->status
== PURPLE_STUN_STATUS_DISCOVERED
))
272 return stun
->publicip
;
274 /* Attempt to get the IP from a NAT device using UPnP */
275 ip
= purple_upnp_get_public_ip();
279 /* Attempt to get the IP from a NAT device using NAT-PMP */
280 ip
= purple_pmp_get_public_ip();
285 /* Just fetch the IP of the local system */
286 return purple_network_get_local_system_ip(fd
);
291 purple_network_set_upnp_port_mapping_cb(gboolean success
, gpointer data
)
293 PurpleNetworkListenData
*listen_data
;
296 /* TODO: Once we're keeping track of upnp requests... */
297 /* listen_data->pnp_data = NULL; */
300 purple_debug_warning("network", "Couldn't create UPnP mapping\n");
301 if (listen_data
->retry
) {
302 listen_data
->retry
= FALSE
;
303 listen_data
->adding
= FALSE
;
304 listen_data
->mapping_data
= purple_upnp_remove_port_mapping(
305 purple_network_get_port_from_fd(listen_data
->listenfd
),
306 (listen_data
->socket_type
== SOCK_STREAM
) ? "TCP" : "UDP",
307 purple_network_set_upnp_port_mapping_cb
, listen_data
);
310 } else if (!listen_data
->adding
) {
311 /* We've tried successfully to remove the port mapping.
312 * Try to add it again */
313 listen_data
->adding
= TRUE
;
314 listen_data
->mapping_data
= purple_upnp_set_port_mapping(
315 purple_network_get_port_from_fd(listen_data
->listenfd
),
316 (listen_data
->socket_type
== SOCK_STREAM
) ? "TCP" : "UDP",
317 purple_network_set_upnp_port_mapping_cb
, listen_data
);
322 /* add port mapping to hash table */
323 gint key
= purple_network_get_port_from_fd(listen_data
->listenfd
);
324 gint value
= listen_data
->socket_type
;
325 g_hash_table_insert(upnp_port_mappings
, GINT_TO_POINTER(key
), GINT_TO_POINTER(value
));
329 listen_data
->cb(listen_data
->listenfd
, listen_data
->cb_data
);
331 /* Clear the UPnP mapping data, since it's complete and purple_network_listen_cancel() will try to cancel
333 listen_data
->mapping_data
= NULL
;
334 purple_network_listen_cancel(listen_data
);
338 purple_network_finish_pmp_map_cb(gpointer data
)
340 PurpleNetworkListenData
*listen_data
;
345 listen_data
->timer
= 0;
347 /* add port mapping to hash table */
348 key
= purple_network_get_port_from_fd(listen_data
->listenfd
);
349 value
= listen_data
->socket_type
;
350 g_hash_table_insert(nat_pmp_port_mappings
, GINT_TO_POINTER(key
), GINT_TO_POINTER(value
));
353 listen_data
->cb(listen_data
->listenfd
, listen_data
->cb_data
);
355 purple_network_listen_cancel(listen_data
);
360 static PurpleNetworkListenData
*
361 purple_network_do_listen(unsigned short port
, int socket_family
, int socket_type
, gboolean map_external
,
362 PurpleNetworkListenCallback cb
, gpointer cb_data
)
366 PurpleNetworkListenData
*listen_data
;
367 unsigned short actual_port
;
368 #ifdef HAVE_GETADDRINFO
370 struct addrinfo hints
, *res
, *next
;
374 * Get a list of addresses on this machine.
376 g_snprintf(serv
, sizeof(serv
), "%hu", port
);
377 memset(&hints
, 0, sizeof(struct addrinfo
));
378 hints
.ai_flags
= AI_PASSIVE
;
379 hints
.ai_family
= socket_family
;
380 hints
.ai_socktype
= socket_type
;
381 errnum
= getaddrinfo(NULL
/* any IP */, serv
, &hints
, &res
);
384 purple_debug_warning("network", "getaddrinfo: %s\n", purple_gai_strerror(errnum
));
385 if (errnum
== EAI_SYSTEM
)
386 purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno
));
388 purple_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum
);
394 * Go through the list of addresses and attempt to listen on
396 * XXX - Try IPv6 addresses first?
398 for (next
= res
; next
!= NULL
; next
= next
->ai_next
) {
399 listenfd
= socket(next
->ai_family
, next
->ai_socktype
, next
->ai_protocol
);
402 if (setsockopt(listenfd
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) != 0)
403 purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_strerror(errno
));
404 if (bind(listenfd
, next
->ai_addr
, next
->ai_addrlen
) == 0)
406 /* XXX - It is unclear to me (datallah) whether we need to be
407 using a new socket each time */
416 struct sockaddr_in sockin
;
418 if (socket_family
!= AF_INET
&& socket_family
!= AF_UNSPEC
) {
419 purple_debug_warning("network", "Address family %d only "
420 "supported when built with getaddrinfo() "
421 "support\n", socket_family
);
425 if ((listenfd
= socket(AF_INET
, socket_type
, 0)) < 0) {
426 purple_debug_warning("network", "socket: %s\n", g_strerror(errno
));
430 if (setsockopt(listenfd
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) != 0)
431 purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno
));
433 memset(&sockin
, 0, sizeof(struct sockaddr_in
));
434 sockin
.sin_family
= PF_INET
;
435 sockin
.sin_port
= htons(port
);
437 if (bind(listenfd
, (struct sockaddr
*)&sockin
, sizeof(struct sockaddr_in
)) != 0) {
438 purple_debug_warning("network", "bind: %s\n", g_strerror(errno
));
444 if (socket_type
== SOCK_STREAM
&& listen(listenfd
, 4) != 0) {
445 purple_debug_warning("network", "listen: %s\n", g_strerror(errno
));
449 _purple_network_set_common_socket_flags(listenfd
);
450 actual_port
= purple_network_get_port_from_fd(listenfd
);
452 purple_debug_info("network", "Listening on port: %hu\n", actual_port
);
454 listen_data
= g_new0(PurpleNetworkListenData
, 1);
455 listen_data
->listenfd
= listenfd
;
456 listen_data
->adding
= TRUE
;
457 listen_data
->retry
= TRUE
;
458 listen_data
->cb
= cb
;
459 listen_data
->cb_data
= cb_data
;
460 listen_data
->socket_type
= socket_type
;
462 if (!purple_socket_speaks_ipv4(listenfd
) || !map_external
||
463 !purple_prefs_get_bool("/purple/network/map_ports"))
465 purple_debug_info("network", "Skipping external port mapping.\n");
466 /* The pmp_map_cb does what we want to do */
467 listen_data
->timer
= g_timeout_add(0, purple_network_finish_pmp_map_cb
, listen_data
);
469 /* Attempt a NAT-PMP Mapping, which will return immediately */
470 else if (purple_pmp_create_map(((socket_type
== SOCK_STREAM
) ? PURPLE_PMP_TYPE_TCP
: PURPLE_PMP_TYPE_UDP
),
471 actual_port
, actual_port
, PURPLE_PMP_LIFETIME
))
473 purple_debug_info("network", "Created NAT-PMP mapping on port %i\n", actual_port
);
474 /* We want to return listen_data now, and on the next run loop trigger the cb and destroy listen_data */
475 listen_data
->timer
= g_timeout_add(0, purple_network_finish_pmp_map_cb
, listen_data
);
479 /* Attempt a UPnP Mapping */
480 listen_data
->mapping_data
= purple_upnp_set_port_mapping(
482 (socket_type
== SOCK_STREAM
) ? "TCP" : "UDP",
483 purple_network_set_upnp_port_mapping_cb
, listen_data
);
489 PurpleNetworkListenData
*
490 purple_network_listen(unsigned short port
, int socket_family
, int socket_type
,
491 gboolean map_external
, PurpleNetworkListenCallback cb
,
494 g_return_val_if_fail(port
!= 0, NULL
);
496 return purple_network_do_listen(port
, socket_family
, socket_type
, map_external
,
500 PurpleNetworkListenData
*
501 purple_network_listen_range(unsigned short start
, unsigned short end
,
502 int socket_family
, int socket_type
, gboolean map_external
,
503 PurpleNetworkListenCallback cb
,
506 PurpleNetworkListenData
*ret
= NULL
;
508 if (purple_prefs_get_bool("/purple/network/ports_range_use")) {
509 start
= purple_prefs_get_int("/purple/network/ports_range_start");
510 end
= purple_prefs_get_int("/purple/network/ports_range_end");
516 for (; start
<= end
; start
++) {
517 ret
= purple_network_do_listen(start
, AF_UNSPEC
, socket_type
, map_external
, cb
, cb_data
);
525 void purple_network_listen_cancel(PurpleNetworkListenData
*listen_data
)
527 if (listen_data
->mapping_data
!= NULL
)
528 purple_upnp_cancel_port_mapping(listen_data
->mapping_data
);
530 if (listen_data
->timer
> 0)
531 g_source_remove(listen_data
->timer
);
537 purple_network_get_port_from_fd(int fd
)
539 struct sockaddr_in addr
;
542 g_return_val_if_fail(fd
>= 0, 0);
545 if (getsockname(fd
, (struct sockaddr
*) &addr
, &len
) == -1) {
546 purple_debug_warning("network", "getsockname: %s\n", g_strerror(errno
));
550 return ntohs(addr
.sin_port
);
554 purple_network_is_available(void)
560 return g_network_monitor_get_network_available(g_network_monitor_get_default());
564 purple_network_force_online()
570 purple_network_ip_lookup_cb(GObject
*sender
, GAsyncResult
*result
, gpointer data
) {
571 GError
*error
= NULL
;
572 GList
*addresses
= NULL
;
573 GInetAddress
*address
= NULL
;
574 const gchar
**ip_address
= (const gchar
**)data
;
576 addresses
= g_resolver_lookup_by_name_finish(G_RESOLVER(sender
),
579 purple_debug_info("network", "lookup of IP address failed: %s\n", error
->message
);
586 address
= G_INET_ADDRESS(addresses
->data
);
588 *ip_address
= g_inet_address_to_string(address
);
590 g_resolver_free_addresses(addresses
);
594 purple_network_set_stun_server(const gchar
*stun_server
)
596 if (stun_server
&& stun_server
[0] != '\0') {
597 if (purple_network_is_available()) {
598 GResolver
*resolver
= g_resolver_get_default();
599 g_resolver_lookup_by_name_async(resolver
,
602 purple_network_ip_lookup_cb
,
604 g_object_unref(resolver
);
606 purple_debug_info("network",
607 "network is unavailable, don't try to update STUN IP");
616 purple_network_set_turn_server(const gchar
*turn_server
)
618 if (turn_server
&& turn_server
[0] != '\0') {
619 if (purple_network_is_available()) {
620 GResolver
*resolver
= g_resolver_get_default();
621 g_resolver_lookup_by_name_async(resolver
,
624 purple_network_ip_lookup_cb
,
626 g_object_unref(resolver
);
628 purple_debug_info("network",
629 "network is unavailable, don't try to update TURN IP");
639 purple_network_get_stun_ip(void)
645 purple_network_get_turn_ip(void)
651 purple_network_get_handle(void)
659 purple_network_upnp_mapping_remove_cb(gboolean sucess
, gpointer data
)
661 purple_debug_info("network", "done removing UPnP port mapping\n");
664 /* the reason for these functions to have these signatures is to be able to
665 use them for g_hash_table_foreach to clean remaining port mappings, which is
668 purple_network_upnp_mapping_remove(gpointer key
, gpointer value
,
671 gint port
= GPOINTER_TO_INT(key
);
672 gint protocol
= GPOINTER_TO_INT(value
);
673 purple_debug_info("network", "removing UPnP port mapping for port %d\n",
675 purple_upnp_remove_port_mapping(port
,
676 protocol
== SOCK_STREAM
? "TCP" : "UDP",
677 purple_network_upnp_mapping_remove_cb
, NULL
);
678 g_hash_table_remove(upnp_port_mappings
, GINT_TO_POINTER(port
));
682 purple_network_nat_pmp_mapping_remove(gpointer key
, gpointer value
,
685 gint port
= GPOINTER_TO_INT(key
);
686 gint protocol
= GPOINTER_TO_INT(value
);
687 purple_debug_info("network", "removing NAT-PMP port mapping for port %d\n",
689 purple_pmp_destroy_map(
690 protocol
== SOCK_STREAM
? PURPLE_PMP_TYPE_TCP
: PURPLE_PMP_TYPE_UDP
,
692 g_hash_table_remove(nat_pmp_port_mappings
, GINT_TO_POINTER(port
));
696 purple_network_remove_port_mapping(gint fd
)
698 int port
= purple_network_get_port_from_fd(fd
);
699 gint protocol
= GPOINTER_TO_INT(g_hash_table_lookup(upnp_port_mappings
, GINT_TO_POINTER(port
)));
702 purple_network_upnp_mapping_remove(GINT_TO_POINTER(port
), GINT_TO_POINTER(protocol
), NULL
);
704 protocol
= GPOINTER_TO_INT(g_hash_table_lookup(nat_pmp_port_mappings
, GINT_TO_POINTER(port
)));
706 purple_network_nat_pmp_mapping_remove(GINT_TO_POINTER(port
), GINT_TO_POINTER(protocol
), NULL
);
712 _purple_network_set_common_socket_flags(int fd
)
715 gboolean succ
= TRUE
;
717 g_return_val_if_fail(fd
>= 0, FALSE
);
719 flags
= fcntl(fd
, F_GETFL
);
721 if (fcntl(fd
, F_SETFL
, flags
| O_NONBLOCK
) != 0) {
722 purple_debug_warning("network",
723 "Couldn't set O_NONBLOCK flag\n");
728 if (fcntl(fd
, F_SETFD
, FD_CLOEXEC
) != 0) {
729 purple_debug_warning("network",
730 "Couldn't set FD_CLOEXEC flag\n");
739 purple_network_init(void)
741 purple_prefs_add_none ("/purple/network");
742 purple_prefs_add_string("/purple/network/stun_server", "");
743 purple_prefs_add_string("/purple/network/turn_server", "");
744 purple_prefs_add_int ("/purple/network/turn_port", 3478);
745 purple_prefs_add_int ("/purple/network/turn_port_tcp", 3478);
746 purple_prefs_add_string("/purple/network/turn_username", "");
747 purple_prefs_add_string("/purple/network/turn_password", "");
748 purple_prefs_add_bool ("/purple/network/auto_ip", TRUE
);
749 purple_prefs_add_string("/purple/network/public_ip", "");
750 purple_prefs_add_bool ("/purple/network/map_ports", TRUE
);
751 purple_prefs_add_bool ("/purple/network/ports_range_use", FALSE
);
752 purple_prefs_add_int ("/purple/network/ports_range_start", 1024);
753 purple_prefs_add_int ("/purple/network/ports_range_end", 2048);
755 if(purple_prefs_get_bool("/purple/network/map_ports") || purple_prefs_get_bool("/purple/network/auto_ip"))
756 purple_upnp_discover(NULL
, NULL
);
761 purple_network_set_stun_server(
762 purple_prefs_get_string("/purple/network/stun_server"));
763 purple_network_set_turn_server(
764 purple_prefs_get_string("/purple/network/turn_server"));
766 upnp_port_mappings
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
767 nat_pmp_port_mappings
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
773 purple_network_uninit(void)
777 g_hash_table_destroy(upnp_port_mappings
);
778 g_hash_table_destroy(nat_pmp_port_mappings
);
780 /* TODO: clean up remaining port mappings, note calling
781 purple_upnp_remove_port_mapping from here doesn't quite work... */
783 purple_upnp_uninit();