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>
57 * Calling sizeof(struct ifreq) isn't always correct on
58 * Mac OS X (and maybe others).
60 #ifdef _SIZEOF_ADDR_IFREQ
61 # define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
63 # define HX_SIZE_OF_IFREQ(a) sizeof(a)
66 struct _PurpleNetworkListenData
{
71 PurpleNetworkListenCallback cb
;
73 PurpleUPnPMappingAddRemove
*mapping_data
;
77 static gboolean force_online
= FALSE
;
79 /* Cached IP addresses for STUN and TURN servers (set globally in prefs) */
80 static gchar
*stun_ip
= NULL
;
81 static gchar
*turn_ip
= NULL
;
83 /* Keep track of port mappings done with UPnP and NAT-PMP */
84 static GHashTable
*upnp_port_mappings
= NULL
;
85 static GHashTable
*nat_pmp_port_mappings
= NULL
;
88 purple_network_set_public_ip(const char *ip
)
90 g_return_if_fail(ip
!= NULL
);
92 /* XXX - Ensure the IP address is valid */
94 purple_prefs_set_string("/purple/network/public_ip", ip
);
98 purple_network_get_public_ip(void)
100 return purple_prefs_get_string("/purple/network/public_ip");
104 purple_network_get_local_system_ip(int fd
)
106 struct ifreq buffer
[100];
111 struct sockaddr_in
*sinptr
;
112 guint32 lhost
= htonl((127 << 24) + 1); /* 127.0.0.1 */
113 long unsigned int add
;
117 source
= socket(PF_INET
,SOCK_STREAM
, 0);
119 ifc
.ifc_len
= sizeof(buffer
);
120 ifc
.ifc_req
= buffer
;
121 ioctl(source
, SIOCGIFCONF
, &ifc
);
123 if (fd
< 0 && source
>= 0)
126 it
= (guchar
*)buffer
;
127 it_end
= it
+ ifc
.ifc_len
;
128 while (it
< it_end
) {
129 /* in this case "it" is:
130 * a) (struct ifreq)-aligned
131 * b) not aligned, because of OS quirks (see
132 * _SIZEOF_ADDR_IFREQ), so the OS should deal with it.
134 ifr
= (struct ifreq
*)(gpointer
)it
;
135 it
+= HX_SIZE_OF_IFREQ(*ifr
);
137 if (ifr
->ifr_addr
.sa_family
== AF_INET
)
139 sinptr
= (struct sockaddr_in
*)(gpointer
)&ifr
->ifr_addr
;
140 if (sinptr
->sin_addr
.s_addr
!= lhost
)
142 add
= ntohl(sinptr
->sin_addr
.s_addr
);
143 g_snprintf(ip
, 16, "%lu.%lu.%lu.%lu",
158 purple_network_get_all_local_system_ips(void)
160 #if defined(HAVE_GETIFADDRS) && defined(HAVE_INET_NTOP)
161 GList
*result
= NULL
;
162 struct ifaddrs
*start
, *ifa
;
165 ret
= getifaddrs(&start
);
167 purple_debug_warning("network",
168 "getifaddrs() failed: %s\n", g_strerror(errno
));
172 for (ifa
= start
; ifa
; ifa
= ifa
->ifa_next
) {
173 int family
= ifa
->ifa_addr
? ifa
->ifa_addr
->sa_family
: AF_UNSPEC
;
174 char host
[INET6_ADDRSTRLEN
];
175 const char *tmp
= NULL
;
176 common_sockaddr_t
*addr
=
177 (common_sockaddr_t
*)(gpointer
)ifa
->ifa_addr
;
179 if ((family
!= AF_INET
&& family
!= AF_INET6
) || ifa
->ifa_flags
& IFF_LOOPBACK
)
182 if (family
== AF_INET
)
183 tmp
= inet_ntop(family
, &addr
->in
.sin_addr
, host
, sizeof(host
));
185 /* Peer-peer link-local communication is a big TODO. I am not sure
186 * how communicating link-local addresses is supposed to work, and
187 * it seems like it would require attempting the cartesian product
188 * of the local and remote interfaces to see if any match (eww).
190 if (!IN6_IS_ADDR_LINKLOCAL(&addr
->in6
.sin6_addr
))
191 tmp
= inet_ntop(family
, &addr
->in6
.sin6_addr
, host
, sizeof(host
));
194 result
= g_list_prepend(result
, g_strdup(tmp
));
199 return g_list_reverse(result
);
200 #else /* HAVE_GETIFADDRS && HAVE_INET_NTOP */
201 GList
*result
= NULL
;
202 int source
= socket(PF_INET
,SOCK_STREAM
, 0);
203 struct ifreq buffer
[100];
208 ifc
.ifc_len
= sizeof(buffer
);
209 ifc
.ifc_req
= buffer
;
210 ioctl(source
, SIOCGIFCONF
, &ifc
);
213 it
= (guchar
*)buffer
;
214 it_end
= it
+ ifc
.ifc_len
;
215 while (it
< it_end
) {
216 char dst
[INET_ADDRSTRLEN
];
218 /* alignment: see purple_network_get_local_system_ip */
219 ifr
= (struct ifreq
*)(gpointer
)it
;
220 it
+= HX_SIZE_OF_IFREQ(*ifr
);
222 if (ifr
->ifr_addr
.sa_family
== AF_INET
) {
223 struct sockaddr_in
*sinptr
=
224 (struct sockaddr_in
*)(gpointer
)&ifr
->ifr_addr
;
226 inet_ntop(AF_INET
, &sinptr
->sin_addr
, dst
,
228 purple_debug_info("network",
229 "found local i/f with address %s on IPv4\n", dst
);
230 if (!purple_strequal(dst
, "127.0.0.1")) {
231 result
= g_list_append(result
, g_strdup(dst
));
237 #endif /* HAVE_GETIFADDRS && HAVE_INET_NTOP */
241 * purple_network_is_ipv4:
242 * @hostname: The hostname to be verified.
244 * Checks, if specified hostname is valid ipv4 address.
246 * Returns: TRUE, if the hostname is valid.
249 purple_network_is_ipv4(const gchar
*hostname
)
251 g_return_val_if_fail(hostname
!= NULL
, FALSE
);
253 /* We don't accept ipv6 here. */
254 if (strchr(hostname
, ':') != NULL
)
257 return g_hostname_is_ip_address(hostname
);
261 purple_network_get_my_ip(int fd
)
263 const char *ip
= NULL
;
264 PurpleStunNatDiscovery
*stun
;
266 /* Check if the user specified an IP manually */
267 if (!purple_prefs_get_bool("/purple/network/auto_ip")) {
268 ip
= purple_network_get_public_ip();
269 /* Make sure the IP address entered by the user is valid */
270 if ((ip
!= NULL
) && (purple_network_is_ipv4(ip
)))
273 /* Check if STUN discovery was already done */
274 stun
= purple_stun_discover(NULL
);
275 if ((stun
!= NULL
) && (stun
->status
== PURPLE_STUN_STATUS_DISCOVERED
))
276 return stun
->publicip
;
278 /* Attempt to get the IP from a NAT device using UPnP */
279 ip
= purple_upnp_get_public_ip();
283 /* Attempt to get the IP from a NAT device using NAT-PMP */
284 ip
= purple_pmp_get_public_ip();
289 /* Just fetch the IP of the local system */
290 return purple_network_get_local_system_ip(fd
);
295 purple_network_set_upnp_port_mapping_cb(gboolean success
, gpointer data
)
297 PurpleNetworkListenData
*listen_data
;
300 /* TODO: Once we're keeping track of upnp requests... */
301 /* listen_data->pnp_data = NULL; */
304 purple_debug_warning("network", "Couldn't create UPnP mapping\n");
305 if (listen_data
->retry
) {
306 listen_data
->retry
= FALSE
;
307 listen_data
->adding
= FALSE
;
308 listen_data
->mapping_data
= purple_upnp_remove_port_mapping(
309 purple_network_get_port_from_fd(listen_data
->listenfd
),
310 (listen_data
->socket_type
== SOCK_STREAM
) ? "TCP" : "UDP",
311 purple_network_set_upnp_port_mapping_cb
, listen_data
);
314 } else if (!listen_data
->adding
) {
315 /* We've tried successfully to remove the port mapping.
316 * Try to add it again */
317 listen_data
->adding
= TRUE
;
318 listen_data
->mapping_data
= purple_upnp_set_port_mapping(
319 purple_network_get_port_from_fd(listen_data
->listenfd
),
320 (listen_data
->socket_type
== SOCK_STREAM
) ? "TCP" : "UDP",
321 purple_network_set_upnp_port_mapping_cb
, listen_data
);
326 /* add port mapping to hash table */
327 gint key
= purple_network_get_port_from_fd(listen_data
->listenfd
);
328 gint value
= listen_data
->socket_type
;
329 g_hash_table_insert(upnp_port_mappings
, GINT_TO_POINTER(key
), GINT_TO_POINTER(value
));
333 listen_data
->cb(listen_data
->listenfd
, listen_data
->cb_data
);
335 /* Clear the UPnP mapping data, since it's complete and purple_network_listen_cancel() will try to cancel
337 listen_data
->mapping_data
= NULL
;
338 purple_network_listen_cancel(listen_data
);
342 purple_network_finish_pmp_map_cb(gpointer data
)
344 PurpleNetworkListenData
*listen_data
;
349 listen_data
->timer
= 0;
351 /* add port mapping to hash table */
352 key
= purple_network_get_port_from_fd(listen_data
->listenfd
);
353 value
= listen_data
->socket_type
;
354 g_hash_table_insert(nat_pmp_port_mappings
, GINT_TO_POINTER(key
), GINT_TO_POINTER(value
));
357 listen_data
->cb(listen_data
->listenfd
, listen_data
->cb_data
);
359 purple_network_listen_cancel(listen_data
);
364 static PurpleNetworkListenData
*
365 purple_network_do_listen(unsigned short port
, int socket_family
, int socket_type
, gboolean map_external
,
366 PurpleNetworkListenCallback cb
, gpointer cb_data
)
370 PurpleNetworkListenData
*listen_data
;
371 unsigned short actual_port
;
372 #ifdef HAVE_GETADDRINFO
374 struct addrinfo hints
, *res
, *next
;
378 * Get a list of addresses on this machine.
380 g_snprintf(serv
, sizeof(serv
), "%hu", port
);
381 memset(&hints
, 0, sizeof(struct addrinfo
));
382 hints
.ai_flags
= AI_PASSIVE
;
383 hints
.ai_family
= socket_family
;
384 hints
.ai_socktype
= socket_type
;
385 errnum
= getaddrinfo(NULL
/* any IP */, serv
, &hints
, &res
);
388 purple_debug_warning("network", "getaddrinfo: %s\n", purple_gai_strerror(errnum
));
389 if (errnum
== EAI_SYSTEM
)
390 purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno
));
392 purple_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum
);
398 * Go through the list of addresses and attempt to listen on
400 * XXX - Try IPv6 addresses first?
402 for (next
= res
; next
!= NULL
; next
= next
->ai_next
) {
403 listenfd
= socket(next
->ai_family
, next
->ai_socktype
, next
->ai_protocol
);
406 if (setsockopt(listenfd
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) != 0)
407 purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_strerror(errno
));
408 if (bind(listenfd
, next
->ai_addr
, next
->ai_addrlen
) == 0)
410 /* XXX - It is unclear to me (datallah) whether we need to be
411 using a new socket each time */
420 struct sockaddr_in sockin
;
422 if (socket_family
!= AF_INET
&& socket_family
!= AF_UNSPEC
) {
423 purple_debug_warning("network", "Address family %d only "
424 "supported when built with getaddrinfo() "
425 "support\n", socket_family
);
429 if ((listenfd
= socket(AF_INET
, socket_type
, 0)) < 0) {
430 purple_debug_warning("network", "socket: %s\n", g_strerror(errno
));
434 if (setsockopt(listenfd
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) != 0)
435 purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno
));
437 memset(&sockin
, 0, sizeof(struct sockaddr_in
));
438 sockin
.sin_family
= PF_INET
;
439 sockin
.sin_port
= htons(port
);
441 if (bind(listenfd
, (struct sockaddr
*)&sockin
, sizeof(struct sockaddr_in
)) != 0) {
442 purple_debug_warning("network", "bind: %s\n", g_strerror(errno
));
448 if (socket_type
== SOCK_STREAM
&& listen(listenfd
, 4) != 0) {
449 purple_debug_warning("network", "listen: %s\n", g_strerror(errno
));
453 _purple_network_set_common_socket_flags(listenfd
);
454 actual_port
= purple_network_get_port_from_fd(listenfd
);
456 purple_debug_info("network", "Listening on port: %hu\n", actual_port
);
458 listen_data
= g_new0(PurpleNetworkListenData
, 1);
459 listen_data
->listenfd
= listenfd
;
460 listen_data
->adding
= TRUE
;
461 listen_data
->retry
= TRUE
;
462 listen_data
->cb
= cb
;
463 listen_data
->cb_data
= cb_data
;
464 listen_data
->socket_type
= socket_type
;
466 if (!purple_socket_speaks_ipv4(listenfd
) || !map_external
||
467 !purple_prefs_get_bool("/purple/network/map_ports"))
469 purple_debug_info("network", "Skipping external port mapping.\n");
470 /* The pmp_map_cb does what we want to do */
471 listen_data
->timer
= g_timeout_add(0, purple_network_finish_pmp_map_cb
, listen_data
);
473 /* Attempt a NAT-PMP Mapping, which will return immediately */
474 else if (purple_pmp_create_map(((socket_type
== SOCK_STREAM
) ? PURPLE_PMP_TYPE_TCP
: PURPLE_PMP_TYPE_UDP
),
475 actual_port
, actual_port
, PURPLE_PMP_LIFETIME
))
477 purple_debug_info("network", "Created NAT-PMP mapping on port %i\n", actual_port
);
478 /* We want to return listen_data now, and on the next run loop trigger the cb and destroy listen_data */
479 listen_data
->timer
= g_timeout_add(0, purple_network_finish_pmp_map_cb
, listen_data
);
483 /* Attempt a UPnP Mapping */
484 listen_data
->mapping_data
= purple_upnp_set_port_mapping(
486 (socket_type
== SOCK_STREAM
) ? "TCP" : "UDP",
487 purple_network_set_upnp_port_mapping_cb
, listen_data
);
493 PurpleNetworkListenData
*
494 purple_network_listen(unsigned short port
, int socket_family
, int socket_type
,
495 gboolean map_external
, PurpleNetworkListenCallback cb
,
498 g_return_val_if_fail(port
!= 0, NULL
);
500 return purple_network_do_listen(port
, socket_family
, socket_type
, map_external
,
504 PurpleNetworkListenData
*
505 purple_network_listen_range(unsigned short start
, unsigned short end
,
506 int socket_family
, int socket_type
, gboolean map_external
,
507 PurpleNetworkListenCallback cb
,
510 PurpleNetworkListenData
*ret
= NULL
;
512 if (purple_prefs_get_bool("/purple/network/ports_range_use")) {
513 start
= purple_prefs_get_int("/purple/network/ports_range_start");
514 end
= purple_prefs_get_int("/purple/network/ports_range_end");
520 for (; start
<= end
; start
++) {
521 ret
= purple_network_do_listen(start
, AF_UNSPEC
, socket_type
, map_external
, cb
, cb_data
);
529 void purple_network_listen_cancel(PurpleNetworkListenData
*listen_data
)
531 if (listen_data
->mapping_data
!= NULL
)
532 purple_upnp_cancel_port_mapping(listen_data
->mapping_data
);
534 if (listen_data
->timer
> 0)
535 g_source_remove(listen_data
->timer
);
541 purple_network_get_port_from_fd(int fd
)
543 struct sockaddr_in addr
;
546 g_return_val_if_fail(fd
>= 0, 0);
549 if (getsockname(fd
, (struct sockaddr
*) &addr
, &len
) == -1) {
550 purple_debug_warning("network", "getsockname: %s\n", g_strerror(errno
));
554 return ntohs(addr
.sin_port
);
558 purple_network_is_available(void)
564 return g_network_monitor_get_network_available(g_network_monitor_get_default());
568 purple_network_force_online()
574 purple_network_ip_lookup_cb(GObject
*sender
, GAsyncResult
*result
, gpointer data
) {
575 GError
*error
= NULL
;
576 GList
*addresses
= NULL
;
577 GInetAddress
*address
= NULL
;
578 const gchar
**ip_address
= (const gchar
**)data
;
580 addresses
= g_resolver_lookup_by_name_finish(G_RESOLVER(sender
),
583 purple_debug_info("network", "lookup of IP address failed: %s\n", error
->message
);
590 address
= G_INET_ADDRESS(addresses
->data
);
592 *ip_address
= g_inet_address_to_string(address
);
594 g_resolver_free_addresses(addresses
);
598 purple_network_set_stun_server(const gchar
*stun_server
)
600 if (stun_server
&& stun_server
[0] != '\0') {
601 if (purple_network_is_available()) {
602 GResolver
*resolver
= g_resolver_get_default();
603 g_resolver_lookup_by_name_async(resolver
,
606 purple_network_ip_lookup_cb
,
608 g_object_unref(resolver
);
610 purple_debug_info("network",
611 "network is unavailable, don't try to update STUN IP");
620 purple_network_set_turn_server(const gchar
*turn_server
)
622 if (turn_server
&& turn_server
[0] != '\0') {
623 if (purple_network_is_available()) {
624 GResolver
*resolver
= g_resolver_get_default();
625 g_resolver_lookup_by_name_async(resolver
,
628 purple_network_ip_lookup_cb
,
630 g_object_unref(resolver
);
632 purple_debug_info("network",
633 "network is unavailable, don't try to update TURN IP");
643 purple_network_get_stun_ip(void)
649 purple_network_get_turn_ip(void)
655 purple_network_get_handle(void)
663 purple_network_upnp_mapping_remove_cb(gboolean sucess
, gpointer data
)
665 purple_debug_info("network", "done removing UPnP port mapping\n");
668 /* the reason for these functions to have these signatures is to be able to
669 use them for g_hash_table_foreach to clean remaining port mappings, which is
672 purple_network_upnp_mapping_remove(gpointer key
, gpointer value
,
675 gint port
= GPOINTER_TO_INT(key
);
676 gint protocol
= GPOINTER_TO_INT(value
);
677 purple_debug_info("network", "removing UPnP port mapping for port %d\n",
679 purple_upnp_remove_port_mapping(port
,
680 protocol
== SOCK_STREAM
? "TCP" : "UDP",
681 purple_network_upnp_mapping_remove_cb
, NULL
);
682 g_hash_table_remove(upnp_port_mappings
, GINT_TO_POINTER(port
));
686 purple_network_nat_pmp_mapping_remove(gpointer key
, gpointer value
,
689 gint port
= GPOINTER_TO_INT(key
);
690 gint protocol
= GPOINTER_TO_INT(value
);
691 purple_debug_info("network", "removing NAT-PMP port mapping for port %d\n",
693 purple_pmp_destroy_map(
694 protocol
== SOCK_STREAM
? PURPLE_PMP_TYPE_TCP
: PURPLE_PMP_TYPE_UDP
,
696 g_hash_table_remove(nat_pmp_port_mappings
, GINT_TO_POINTER(port
));
700 purple_network_remove_port_mapping(gint fd
)
702 int port
= purple_network_get_port_from_fd(fd
);
703 gint protocol
= GPOINTER_TO_INT(g_hash_table_lookup(upnp_port_mappings
, GINT_TO_POINTER(port
)));
706 purple_network_upnp_mapping_remove(GINT_TO_POINTER(port
), GINT_TO_POINTER(protocol
), NULL
);
708 protocol
= GPOINTER_TO_INT(g_hash_table_lookup(nat_pmp_port_mappings
, GINT_TO_POINTER(port
)));
710 purple_network_nat_pmp_mapping_remove(GINT_TO_POINTER(port
), GINT_TO_POINTER(protocol
), NULL
);
715 int purple_network_convert_idn_to_ascii(const gchar
*in
, gchar
**out
)
721 g_return_val_if_fail(out
!= NULL
, -1);
723 ret
= idna_to_ascii_8z(in
, &tmp
, IDNA_USE_STD3_ASCII_RULES
);
724 if (ret
!= IDNA_SUCCESS
) {
729 *out
= g_strdup(tmp
);
730 /* This *MUST* be freed with free, not g_free */
734 g_return_val_if_fail(out
!= NULL
, -1);
742 _purple_network_set_common_socket_flags(int fd
)
745 gboolean succ
= TRUE
;
747 g_return_val_if_fail(fd
>= 0, FALSE
);
749 flags
= fcntl(fd
, F_GETFL
);
751 if (fcntl(fd
, F_SETFL
, flags
| O_NONBLOCK
) != 0) {
752 purple_debug_warning("network",
753 "Couldn't set O_NONBLOCK flag\n");
758 if (fcntl(fd
, F_SETFD
, FD_CLOEXEC
) != 0) {
759 purple_debug_warning("network",
760 "Couldn't set FD_CLOEXEC flag\n");
769 purple_network_init(void)
771 purple_prefs_add_none ("/purple/network");
772 purple_prefs_add_string("/purple/network/stun_server", "");
773 purple_prefs_add_string("/purple/network/turn_server", "");
774 purple_prefs_add_int ("/purple/network/turn_port", 3478);
775 purple_prefs_add_int ("/purple/network/turn_port_tcp", 3478);
776 purple_prefs_add_string("/purple/network/turn_username", "");
777 purple_prefs_add_string("/purple/network/turn_password", "");
778 purple_prefs_add_bool ("/purple/network/auto_ip", TRUE
);
779 purple_prefs_add_string("/purple/network/public_ip", "");
780 purple_prefs_add_bool ("/purple/network/map_ports", TRUE
);
781 purple_prefs_add_bool ("/purple/network/ports_range_use", FALSE
);
782 purple_prefs_add_int ("/purple/network/ports_range_start", 1024);
783 purple_prefs_add_int ("/purple/network/ports_range_end", 2048);
785 if(purple_prefs_get_bool("/purple/network/map_ports") || purple_prefs_get_bool("/purple/network/auto_ip"))
786 purple_upnp_discover(NULL
, NULL
);
791 purple_network_set_stun_server(
792 purple_prefs_get_string("/purple/network/stun_server"));
793 purple_network_set_turn_server(
794 purple_prefs_get_string("/purple/network/turn_server"));
796 upnp_port_mappings
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
797 nat_pmp_port_mappings
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
803 purple_network_uninit(void)
807 g_hash_table_destroy(upnp_port_mappings
);
808 g_hash_table_destroy(nat_pmp_port_mappings
);
810 /* TODO: clean up remaining port mappings, note calling
811 purple_upnp_remove_port_mapping from here doesn't quite work... */