| /* | 
 |  * | 
 |  *  ConnMan VPN daemon | 
 |  * | 
 |  *  Copyright (C) 2012  Intel Corporation. All rights reserved. | 
 |  * | 
 |  *  This program is free software; you can redistribute it and/or modify | 
 |  *  it under the terms of the GNU General Public License version 2 as | 
 |  *  published by the Free Software Foundation. | 
 |  * | 
 |  *  This program is distributed in the hope that it will be useful, | 
 |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  *  GNU General Public License for more details. | 
 |  * | 
 |  *  You should have received a copy of the GNU General Public License | 
 |  *  along with this program; if not, write to the Free Software | 
 |  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
 |  * | 
 |  */ | 
 |  | 
 | #ifdef HAVE_CONFIG_H | 
 | #include <config.h> | 
 | #endif | 
 |  | 
 | #include <errno.h> | 
 | #include <stdio.h> | 
 | #include <net/if.h> | 
 | #include <net/if_arp.h> | 
 | #include <linux/if_link.h> | 
 | #include <string.h> | 
 | #include <stdlib.h> | 
 | #include <stdint.h> | 
 | #include <arpa/inet.h> | 
 |  | 
 | #ifndef IFF_LOWER_UP | 
 | #define IFF_LOWER_UP	0x10000 | 
 | #endif | 
 |  | 
 | #include <gdbus.h> | 
 |  | 
 | #include "../src/connman.h" | 
 |  | 
 | #include "vpn.h" | 
 |  | 
 | struct vpn_ipconfig { | 
 | 	int refcount; | 
 | 	int index; | 
 | 	int family; | 
 | 	bool enabled; | 
 | 	struct connman_ipaddress *address; | 
 | 	struct connman_ipaddress *system; | 
 | }; | 
 |  | 
 | struct vpn_ipdevice { | 
 | 	int index; | 
 | 	char *ifname; | 
 | 	unsigned short type; | 
 | 	unsigned int flags; | 
 | 	char *address; | 
 | 	uint16_t mtu; | 
 |  | 
 | 	GSList *address_list; | 
 | 	char *ipv4_gateway; | 
 | 	char *ipv6_gateway; | 
 |  | 
 | 	char *pac; | 
 | }; | 
 |  | 
 | static GHashTable *ipdevice_hash = NULL; | 
 |  | 
 | struct connman_ipaddress * | 
 | __vpn_ipconfig_get_address(struct vpn_ipconfig *ipconfig) | 
 | { | 
 | 	if (!ipconfig) | 
 | 		return NULL; | 
 |  | 
 | 	return ipconfig->address; | 
 | } | 
 |  | 
 | const char *__vpn_ipconfig_get_peer(struct vpn_ipconfig *ipconfig) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return NULL; | 
 |  | 
 | 	return ipconfig->address->peer; | 
 | } | 
 |  | 
 | unsigned short __vpn_ipconfig_get_type_from_index(int index) | 
 | { | 
 | 	struct vpn_ipdevice *ipdevice; | 
 |  | 
 | 	ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); | 
 | 	if (!ipdevice) | 
 | 		return ARPHRD_VOID; | 
 |  | 
 | 	return ipdevice->type; | 
 | } | 
 |  | 
 | unsigned int __vpn_ipconfig_get_flags_from_index(int index) | 
 | { | 
 | 	struct vpn_ipdevice *ipdevice; | 
 |  | 
 | 	ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); | 
 | 	if (!ipdevice) | 
 | 		return 0; | 
 |  | 
 | 	return ipdevice->flags; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_foreach(void (*function) (int index, | 
 | 					void *user_data), void *user_data) | 
 | { | 
 | 	GList *list, *keys; | 
 |  | 
 | 	keys = g_hash_table_get_keys(ipdevice_hash); | 
 | 	if (!keys) | 
 | 		return; | 
 |  | 
 | 	for (list = g_list_first(keys); list; list = g_list_next(list)) { | 
 | 		int index = GPOINTER_TO_INT(list->data); | 
 |  | 
 | 		function(index, user_data); | 
 | 	} | 
 |  | 
 | 	g_list_free(keys); | 
 | } | 
 |  | 
 | void __vpn_ipconfig_set_local(struct vpn_ipconfig *ipconfig, | 
 | 							const char *address) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return; | 
 |  | 
 | 	g_free(ipconfig->address->local); | 
 | 	ipconfig->address->local = g_strdup(address); | 
 | } | 
 |  | 
 | const char *__vpn_ipconfig_get_local(struct vpn_ipconfig *ipconfig) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return NULL; | 
 |  | 
 | 	return ipconfig->address->local; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_set_peer(struct vpn_ipconfig *ipconfig, | 
 | 							const char *address) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return; | 
 |  | 
 | 	g_free(ipconfig->address->peer); | 
 | 	ipconfig->address->peer = g_strdup(address); | 
 | } | 
 |  | 
 | void __vpn_ipconfig_set_broadcast(struct vpn_ipconfig *ipconfig, | 
 | 					const char *broadcast) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return; | 
 |  | 
 | 	g_free(ipconfig->address->broadcast); | 
 | 	ipconfig->address->broadcast = g_strdup(broadcast); | 
 | } | 
 |  | 
 | void __vpn_ipconfig_set_gateway(struct vpn_ipconfig *ipconfig, | 
 | 							const char *gateway) | 
 | { | 
 | 	DBG(""); | 
 |  | 
 | 	if (!ipconfig->address) | 
 | 		return; | 
 | 	g_free(ipconfig->address->gateway); | 
 | 	ipconfig->address->gateway = g_strdup(gateway); | 
 | } | 
 |  | 
 | const char * | 
 | __vpn_ipconfig_get_gateway(struct vpn_ipconfig *ipconfig) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return NULL; | 
 |  | 
 | 	return ipconfig->address->gateway; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_set_prefixlen(struct vpn_ipconfig *ipconfig, | 
 | 					unsigned char prefixlen) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return; | 
 |  | 
 | 	ipconfig->address->prefixlen = prefixlen; | 
 | } | 
 |  | 
 | unsigned char | 
 | __vpn_ipconfig_get_prefixlen(struct vpn_ipconfig *ipconfig) | 
 | { | 
 | 	if (!ipconfig->address) | 
 | 		return 0; | 
 |  | 
 | 	return ipconfig->address->prefixlen; | 
 | } | 
 |  | 
 | int __vpn_ipconfig_address_add(struct vpn_ipconfig *ipconfig, int family) | 
 | { | 
 | 	DBG("ipconfig %p family %d", ipconfig, family); | 
 |  | 
 | 	if (!ipconfig) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (family == AF_INET) | 
 | 		return connman_inet_set_address(ipconfig->index, | 
 | 						ipconfig->address); | 
 | 	else if (family == AF_INET6) | 
 | 		return connman_inet_set_ipv6_address(ipconfig->index, | 
 | 							ipconfig->address); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int __vpn_ipconfig_gateway_add(struct vpn_ipconfig *ipconfig, int family) | 
 | { | 
 | 	DBG("ipconfig %p family %d", ipconfig, family); | 
 |  | 
 | 	if (!ipconfig || !ipconfig->address) | 
 | 		return -EINVAL; | 
 |  | 
 | 	DBG("family %d gw %s peer %s", family, | 
 | 		ipconfig->address->gateway, ipconfig->address->peer); | 
 |  | 
 | 	if (family == AF_INET) | 
 | 		connman_inet_add_host_route(ipconfig->index, | 
 | 					ipconfig->address->gateway, | 
 | 					ipconfig->address->peer); | 
 | 	else if (family == AF_INET6) | 
 | 		connman_inet_add_ipv6_host_route(ipconfig->index, | 
 | 						ipconfig->address->gateway, | 
 | 						ipconfig->address->peer); | 
 | 	else | 
 | 		return -EINVAL; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_unref_debug(struct vpn_ipconfig *ipconfig, | 
 | 				const char *file, int line, const char *caller) | 
 | { | 
 | 	if (!ipconfig) | 
 | 		return; | 
 |  | 
 | 	DBG("%p ref %d by %s:%d:%s()", ipconfig, ipconfig->refcount - 1, | 
 | 		file, line, caller); | 
 |  | 
 | 	if (__sync_fetch_and_sub(&ipconfig->refcount, 1) != 1) | 
 | 		return; | 
 |  | 
 | 	connman_ipaddress_free(ipconfig->system); | 
 | 	connman_ipaddress_free(ipconfig->address); | 
 | 	g_free(ipconfig); | 
 | } | 
 |  | 
 | static struct vpn_ipconfig *create_ipv6config(int index) | 
 | { | 
 | 	struct vpn_ipconfig *ipv6config; | 
 |  | 
 | 	DBG("index %d", index); | 
 |  | 
 | 	ipv6config = g_try_new0(struct vpn_ipconfig, 1); | 
 | 	if (!ipv6config) | 
 | 		return NULL; | 
 |  | 
 | 	ipv6config->refcount = 1; | 
 |  | 
 | 	ipv6config->index = index; | 
 | 	ipv6config->enabled = false; | 
 | 	ipv6config->family = AF_INET6; | 
 |  | 
 | 	ipv6config->address = connman_ipaddress_alloc(AF_INET6); | 
 | 	if (!ipv6config->address) { | 
 | 		g_free(ipv6config); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	ipv6config->system = connman_ipaddress_alloc(AF_INET6); | 
 |  | 
 | 	DBG("ipconfig %p", ipv6config); | 
 |  | 
 | 	return ipv6config; | 
 | } | 
 |  | 
 | struct vpn_ipconfig *__vpn_ipconfig_create(int index, int family) | 
 | { | 
 | 	struct vpn_ipconfig *ipconfig; | 
 |  | 
 | 	if (family == AF_INET6) | 
 | 		return create_ipv6config(index); | 
 |  | 
 | 	DBG("index %d", index); | 
 |  | 
 | 	ipconfig = g_try_new0(struct vpn_ipconfig, 1); | 
 | 	if (!ipconfig) | 
 | 		return NULL; | 
 |  | 
 | 	ipconfig->refcount = 1; | 
 |  | 
 | 	ipconfig->index = index; | 
 | 	ipconfig->enabled = false; | 
 | 	ipconfig->family = family; | 
 |  | 
 | 	ipconfig->address = connman_ipaddress_alloc(AF_INET); | 
 | 	if (!ipconfig->address) { | 
 | 		g_free(ipconfig); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	ipconfig->system = connman_ipaddress_alloc(AF_INET); | 
 |  | 
 | 	DBG("ipconfig %p", ipconfig); | 
 |  | 
 | 	return ipconfig; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_set_index(struct vpn_ipconfig *ipconfig, int index) | 
 | { | 
 | 	ipconfig->index = index; | 
 | } | 
 |  | 
 | static const char *type2str(unsigned short type) | 
 | { | 
 | 	switch (type) { | 
 | 	case ARPHRD_ETHER: | 
 | 		return "ETHER"; | 
 | 	case ARPHRD_LOOPBACK: | 
 | 		return "LOOPBACK"; | 
 | 	case ARPHRD_PPP: | 
 | 		return "PPP"; | 
 | 	case ARPHRD_NONE: | 
 | 		return "NONE"; | 
 | 	case ARPHRD_VOID: | 
 | 		return "VOID"; | 
 | 	} | 
 |  | 
 | 	return ""; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_newlink(int index, unsigned short type, | 
 | 				unsigned int flags, | 
 | 				const char *address, | 
 | 				unsigned short mtu, | 
 | 				struct rtnl_link_stats *stats) | 
 | { | 
 | 	struct vpn_ipdevice *ipdevice; | 
 | 	GString *str; | 
 |  | 
 | 	if (type == ARPHRD_LOOPBACK) | 
 | 		return; | 
 |  | 
 | 	ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); | 
 | 	if (ipdevice) | 
 | 		goto update; | 
 |  | 
 | 	ipdevice = g_try_new0(struct vpn_ipdevice, 1); | 
 | 	if (!ipdevice) | 
 | 		return; | 
 |  | 
 | 	ipdevice->index = index; | 
 | 	ipdevice->ifname = connman_inet_ifname(index); | 
 | 	ipdevice->type = type; | 
 |  | 
 | 	ipdevice->address = g_strdup(address); | 
 |  | 
 | 	g_hash_table_insert(ipdevice_hash, GINT_TO_POINTER(index), ipdevice); | 
 |  | 
 | 	connman_info("%s {create} index %d type %d <%s>", ipdevice->ifname, | 
 | 						index, type, type2str(type)); | 
 |  | 
 | update: | 
 | 	ipdevice->mtu = mtu; | 
 |  | 
 | 	if (flags == ipdevice->flags) | 
 | 		return; | 
 |  | 
 | 	ipdevice->flags = flags; | 
 |  | 
 | 	str = g_string_new(NULL); | 
 | 	if (!str) | 
 | 		return; | 
 |  | 
 | 	if (flags & IFF_UP) | 
 | 		g_string_append(str, "UP"); | 
 | 	else | 
 | 		g_string_append(str, "DOWN"); | 
 |  | 
 | 	if (flags & IFF_RUNNING) | 
 | 		g_string_append(str, ",RUNNING"); | 
 |  | 
 | 	if (flags & IFF_LOWER_UP) | 
 | 		g_string_append(str, ",LOWER_UP"); | 
 |  | 
 | 	connman_info("%s {update} flags %u <%s>", ipdevice->ifname, | 
 | 							flags, str->str); | 
 |  | 
 | 	g_string_free(str, TRUE); | 
 | } | 
 |  | 
 | void __vpn_ipconfig_dellink(int index, struct rtnl_link_stats *stats) | 
 | { | 
 | 	struct vpn_ipdevice *ipdevice; | 
 |  | 
 | 	DBG("index %d", index); | 
 |  | 
 | 	ipdevice = g_hash_table_lookup(ipdevice_hash, GINT_TO_POINTER(index)); | 
 | 	if (!ipdevice) | 
 | 		return; | 
 |  | 
 | 	g_hash_table_remove(ipdevice_hash, GINT_TO_POINTER(index)); | 
 | } | 
 |  | 
 | static void free_ipdevice(gpointer data) | 
 | { | 
 | 	struct vpn_ipdevice *ipdevice = data; | 
 |  | 
 | 	connman_info("%s {remove} index %d", ipdevice->ifname, | 
 | 							ipdevice->index); | 
 |  | 
 | 	g_free(ipdevice->ipv4_gateway); | 
 | 	g_free(ipdevice->ipv6_gateway); | 
 | 	g_free(ipdevice->pac); | 
 |  | 
 | 	g_free(ipdevice->address); | 
 |  | 
 | 	g_free(ipdevice->ifname); | 
 | 	g_free(ipdevice); | 
 | } | 
 |  | 
 | int __vpn_ipconfig_init(void) | 
 | { | 
 | 	DBG(""); | 
 |  | 
 | 	ipdevice_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, | 
 | 							NULL, free_ipdevice); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void __vpn_ipconfig_cleanup(void) | 
 | { | 
 | 	DBG(""); | 
 |  | 
 | 	g_hash_table_destroy(ipdevice_hash); | 
 | 	ipdevice_hash = NULL; | 
 | } |