diff options
Diffstat (limited to 'src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c')
| -rw-r--r-- | src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c | 197 | 
1 files changed, 196 insertions, 1 deletions
| diff --git a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c index 236e3417f..efcf1c2a7 100644 --- a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -15,6 +15,7 @@  #include <sys/types.h>  #include <sys/socket.h> +#include <sys/sysctl.h>  #include <net/if.h>  #include <net/if_dl.h>  #include <ifaddrs.h> @@ -1448,7 +1449,8 @@ static status_t manage_route(private_kernel_pfroute_net_t *this, int op,  				}  				break;  			case RTAX_GATEWAY: -				if (gateway) +				if (gateway && +					gateway->get_family(gateway) == dst->get_family(dst))  				{  					add_rt_addr(&msg.hdr, RTA_GATEWAY, gateway);  				} @@ -1704,6 +1706,198 @@ METHOD(kernel_net_t, get_nexthop, host_t*,  }  /** + * Get the number of set bits in the given netmask + */ +static uint8_t sockaddr_to_netmask(sockaddr_t *sockaddr, host_t *dst) +{ +	uint8_t len = 0, i, byte, mask = 0; +	struct sockaddr_storage ss; +	char *addr; + +	/* at least some older FreeBSD versions send us shorter sockaddrs +	 * with the family set to -1 (255) */ +	if (sockaddr->sa_family == 255) +	{ +		memset(&ss, 0, sizeof(ss)); +		memcpy(&ss, sockaddr, sockaddr->sa_len); +		/* use the address family and length of the destination as hint */ +		ss.ss_len = *dst->get_sockaddr_len(dst); +		ss.ss_family = dst->get_family(dst); +		sockaddr = (sockaddr_t*)&ss; +	} + +	switch (sockaddr->sa_family) +	{ +		case AF_INET: +			len = 4; +			addr = (char*)&((struct sockaddr_in*)sockaddr)->sin_addr; +			break; +		case AF_INET6: +			len = 16; +			addr = (char*)&((struct sockaddr_in6*)sockaddr)->sin6_addr; +			break; +		default: +			break; +	} + +	for (i = 0; i < len; i++) +	{ +		byte = addr[i]; + +		if (byte == 0x00) +		{ +			break; +		} +		if (byte == 0xff) +		{ +			mask += 8; +		} +		else +		{ +			while (byte & 0x80) +			{ +				mask++; +				byte <<= 1; +			} +		} +	} +	return mask; +} + +/** enumerator over subnets */ +typedef struct { +	enumerator_t public; +	/** sysctl result */ +	char *buf; +	/** length of the complete result */ +	size_t len; +	/** start of the current route entry */ +	char *current; +	/** last subnet enumerated */ +	host_t *net; +	/** interface of current net */ +	char *ifname; +} subnet_enumerator_t; + +METHOD(enumerator_t, destroy_subnet_enumerator, void, +	subnet_enumerator_t *this) +{ +	DESTROY_IF(this->net); +	free(this->ifname); +	free(this->buf); +	free(this); +} + +METHOD(enumerator_t, enumerate_subnets, bool, +	subnet_enumerator_t *this, host_t **net, uint8_t *mask, char **ifname) +{ +	enumerator_t *enumerator; +	struct rt_msghdr *rtm; +	struct sockaddr *addr; +	int type; + +	if (!this->current) +	{ +		this->current = this->buf; +	} +	else +	{ +		rtm = (struct rt_msghdr*)this->current; +		this->current += rtm->rtm_msglen; +		DESTROY_IF(this->net); +		this->net = NULL; +		free(this->ifname); +		this->ifname = NULL; +	} + +	for (; this->current < this->buf + this->len; +		 this->current += rtm->rtm_msglen) +	{ +		struct sockaddr *netmask; +		uint8_t netbits = 0; + +		rtm = (struct rt_msghdr*)this->current; + +		if (rtm->rtm_version != RTM_VERSION) +		{ +			continue; +		} +		if (rtm->rtm_flags & RTF_GATEWAY || +			rtm->rtm_flags & RTF_HOST || +			rtm->rtm_flags & RTF_REJECT) +		{ +			continue; +		} +		enumerator = create_rtmsg_enumerator(rtm); +		while (enumerator->enumerate(enumerator, &type, &addr)) +		{ +			if (type == RTAX_DST) +			{ +				this->net = this->net ?: host_create_from_sockaddr(addr); +			} +			if (type == RTAX_NETMASK) +			{ +				netmask = addr; +			} +			if (type == RTAX_IFP && addr->sa_family == AF_LINK) +			{ +				struct sockaddr_dl *sdl = (struct sockaddr_dl*)addr; +				free(this->ifname); +				this->ifname = strndup(sdl->sdl_data, sdl->sdl_nlen); +			} +		} +		if (this->net) +		{ +			netbits = sockaddr_to_netmask(netmask, this->net); +		} +		enumerator->destroy(enumerator); + +		if (this->net && this->ifname) +		{ +			*net = this->net; +			*mask = netbits ?: this->net->get_address(this->net).len * 8; +			*ifname = this->ifname; +			return TRUE; +		} +	} +	return FALSE; +} + +METHOD(kernel_net_t, create_local_subnet_enumerator, enumerator_t*, +	private_kernel_pfroute_net_t *this) +{ +	subnet_enumerator_t *enumerator; +	char *buf; +	size_t len; +	int mib[7] = { +		CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0, 0 +	}; + +	if (sysctl(mib, countof(mib), NULL, &len, NULL, 0) < 0) +	{ +		DBG2(DBG_KNL, "enumerating local subnets failed"); +		return enumerator_create_empty(); +	} +	buf = malloc(len); +	if (sysctl(mib, countof(mib), buf, &len, NULL, 0) < 0) +	{ +		DBG2(DBG_KNL, "enumerating local subnets failed"); +		free(buf); +		return enumerator_create_empty(); +	} + +	INIT(enumerator, +		.public = { +			.enumerate = (void*)_enumerate_subnets, +			.destroy = _destroy_subnet_enumerator, +		}, +		.buf = buf, +		.len = len, +	); +	return &enumerator->public; +} + +/**   * Initialize a list of local addresses.   */  static status_t init_address_list(private_kernel_pfroute_net_t *this) @@ -1848,6 +2042,7 @@ kernel_pfroute_net_t *kernel_pfroute_net_create()  				.get_features = _get_features,  				.get_interface = _get_interface_name,  				.create_address_enumerator = _create_address_enumerator, +				.create_local_subnet_enumerator = _create_local_subnet_enumerator,  				.get_source_addr = _get_source_addr,  				.get_nexthop = _get_nexthop,  				.add_ip = _add_ip, | 
