Merge branch 'bypass-lan'

Adds a new plugin that automatically installs and updates bypass policies
for locally attached subnets.  This is useful for laptops etc. that are
used in different networks and prefer maintaining access to local hosts
(e.g. network printers or NAS) while connected to a VPN.
This commit is contained in:
Tobias Brunner 2017-02-08 10:47:33 +01:00
commit e9d13476cb
14 changed files with 907 additions and 0 deletions

View File

@ -32,6 +32,7 @@ plugins = \
plugins/attr.opt \
plugins/attr-sql.opt \
plugins/bliss.opt \
plugins/bypass-lan.opt \
plugins/certexpire.opt \
plugins/coupling.opt \
plugins/dhcp.opt \

View File

@ -0,0 +1,8 @@
charon.plugins.bypass-lan.interfaces_ignore
A comma-separated list of network interfaces for which connected subnets
should be ignored, if **interfaces_use** is specified this option has no
effect.
charon.plugins.bypass-lan.interfaces_use
A comma-separated list of network interfaces for which connected subnets
should be considered. All other interfaces are ignored.

View File

@ -254,6 +254,7 @@ ARG_ENABL_SET([tnccs-20], [enable TNCCS 2.0 protocol module.])
ARG_ENABL_SET([tnccs-dynamic], [enable dynamic TNCCS protocol discovery module.])
# misc plugins
ARG_ENABL_SET([android-log], [enable Android specific logger plugin.])
ARG_ENABL_SET([bypass-lan], [enable plugin to install bypass policies for local subnets.])
ARG_ENABL_SET([certexpire], [enable CSV export of expiration dates of used certificates.])
ARG_ENABL_SET([connmark], [enable connmark plugin using conntrack based marks to select return path SA.])
ARG_ENABL_SET([forecast], [enable forecast plugin forwarding broadcast/multicast messages.])
@ -1395,6 +1396,7 @@ ADD_PLUGIN([resolve], [c charon cmd])
ADD_PLUGIN([socket-default], [c charon nm cmd])
ADD_PLUGIN([socket-dynamic], [c charon cmd])
ADD_PLUGIN([socket-win], [c charon])
ADD_PLUGIN([bypass-lan], [c charon nm cmd])
ADD_PLUGIN([connmark], [c charon])
ADD_PLUGIN([forecast], [c charon])
ADD_PLUGIN([farp], [c charon])
@ -1616,6 +1618,7 @@ AM_CONDITIONAL(USE_IMV_HCD, test x$imv_hcd = xtrue)
AM_CONDITIONAL(USE_SOCKET_DEFAULT, test x$socket_default = xtrue)
AM_CONDITIONAL(USE_SOCKET_DYNAMIC, test x$socket_dynamic = xtrue)
AM_CONDITIONAL(USE_SOCKET_WIN, test x$socket_win = xtrue)
AM_CONDITIONAL(USE_BYPASS_LAN, test x$bypass_lan = xtrue)
AM_CONDITIONAL(USE_CONNMARK, test x$connmark = xtrue)
AM_CONDITIONAL(USE_FORECAST, test x$forecast = xtrue)
AM_CONDITIONAL(USE_FARP, test x$farp = xtrue)
@ -1864,6 +1867,7 @@ AC_CONFIG_FILES([
src/libcharon/plugins/socket_default/Makefile
src/libcharon/plugins/socket_dynamic/Makefile
src/libcharon/plugins/socket_win/Makefile
src/libcharon/plugins/bypass_lan/Makefile
src/libcharon/plugins/connmark/Makefile
src/libcharon/plugins/forecast/Makefile
src/libcharon/plugins/farp/Makefile

View File

@ -227,6 +227,13 @@ if MONOLITHIC
endif
endif
if USE_BYPASS_LAN
SUBDIRS += plugins/bypass_lan
if MONOLITHIC
libcharon_la_LIBADD += plugins/bypass_lan/libstrongswan-bypass-lan.la
endif
endif
if USE_FORECAST
SUBDIRS += plugins/forecast
if MONOLITHIC

View File

@ -554,6 +554,16 @@ METHOD(kernel_interface_t, create_address_enumerator, enumerator_t*,
return this->net->create_address_enumerator(this->net, which);
}
METHOD(kernel_interface_t, create_local_subnet_enumerator, enumerator_t*,
private_kernel_interface_t *this)
{
if (!this->net || !this->net->create_local_subnet_enumerator)
{
return enumerator_create_empty();
}
return this->net->create_local_subnet_enumerator(this->net);
}
METHOD(kernel_interface_t, add_ip, status_t,
private_kernel_interface_t *this, host_t *virtual_ip, int prefix,
char *iface)
@ -1005,6 +1015,7 @@ kernel_interface_t *kernel_interface_create()
.get_nexthop = _get_nexthop,
.get_interface = _get_interface,
.create_address_enumerator = _create_address_enumerator,
.create_local_subnet_enumerator = _create_local_subnet_enumerator,
.add_ip = _add_ip,
.del_ip = _del_ip,
.add_route = _add_route,

View File

@ -315,6 +315,17 @@ struct kernel_interface_t {
enumerator_t *(*create_address_enumerator) (kernel_interface_t *this,
kernel_address_type_t which);
/**
* Creates an enumerator over all local subnets.
*
* Local subnets are subnets the host is directly connected to.
*
* The enumerator returns the network, subnet mask and interface.
*
* @return enumerator over host_t*, uint8_t, char*
*/
enumerator_t *(*create_local_subnet_enumerator)(kernel_interface_t *this);
/**
* Add a virtual IP to an interface.
*

View File

@ -118,6 +118,17 @@ struct kernel_net_t {
enumerator_t *(*create_address_enumerator) (kernel_net_t *this,
kernel_address_type_t which);
/**
* Creates an enumerator over all local subnets.
*
* Local subnets are subnets the host is directly connected to.
*
* The enumerator returns the network, subnet mask and interface.
*
* @return enumerator over host_t*, uint8_t, char*
*/
enumerator_t *(*create_local_subnet_enumerator)(kernel_net_t *this);
/**
* Add a virtual IP to an interface.
*

View File

@ -0,0 +1,18 @@
AM_CPPFLAGS = \
-I$(top_srcdir)/src/libstrongswan \
-I$(top_srcdir)/src/libcharon
AM_CFLAGS = \
$(PLUGIN_CFLAGS)
if MONOLITHIC
noinst_LTLIBRARIES = libstrongswan-bypass-lan.la
else
plugin_LTLIBRARIES = libstrongswan-bypass-lan.la
endif
libstrongswan_bypass_lan_la_SOURCES = \
bypass_lan_plugin.h bypass_lan_plugin.c \
bypass_lan_listener.h bypass_lan_listener.c
libstrongswan_bypass_lan_la_LDFLAGS = -module -avoid-version

View File

@ -0,0 +1,296 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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.
*/
#include "bypass_lan_listener.h"
#include <collections/hashtable.h>
#include <collections/linked_list.h>
#include <threading/mutex.h>
#include <processing/jobs/callback_job.h>
#include <daemon.h>
typedef struct private_bypass_lan_listener_t private_bypass_lan_listener_t;
/**
* Private data
*/
struct private_bypass_lan_listener_t {
/**
* Public interface.
*/
bypass_lan_listener_t public;
/**
* Currently installed bypass policies, bypass_policy_t*.
*/
hashtable_t *policies;
/**
* Mutex to access list of policies.
*/
mutex_t *mutex;
/**
* List of interface names to include or exclude (char*), NULL if interfaces
* are not filtered.
*/
linked_list_t *ifaces_filter;
/**
* TRUE to exclude interfaces listed in ifaces_filter, FALSE to consider
* only those listed there.
*/
bool ifaces_exclude;
};
/**
* Data for bypass policies
*/
typedef struct {
private_bypass_lan_listener_t *listener;
host_t *net;
uint8_t mask;
char *iface;
child_cfg_t *cfg;
} bypass_policy_t;
/**
* Destroy a bypass policy
*/
static void bypass_policy_destroy(bypass_policy_t *this)
{
traffic_selector_t *ts;
if (this->cfg)
{
ts = traffic_selector_create_from_subnet(this->net->clone(this->net),
this->mask, 0, 0, 65535);
DBG1(DBG_IKE, "uninstalling bypass policy for %R", ts);
charon->shunts->uninstall(charon->shunts,
this->cfg->get_name(this->cfg));
this->cfg->destroy(this->cfg);
ts->destroy(ts);
}
this->net->destroy(this->net);
free(this->iface);
free(this);
}
/**
* Hash a bypass policy
*/
static u_int policy_hash(bypass_policy_t *policy)
{
return chunk_hash_inc(policy->net->get_address(policy->net),
chunk_hash(chunk_from_thing(policy->mask)));
}
/**
* Compare bypass policy
*/
static bool policy_equals(bypass_policy_t *a, bypass_policy_t *b)
{
return a->mask == b->mask && a->net->equals(a->net, b->net);
}
/**
* Check if an interface should be considered
*/
static bool consider_interface(private_bypass_lan_listener_t *this, char *iface)
{
status_t expected;
if (!iface || !this->ifaces_filter)
{
return TRUE;
}
expected = this->ifaces_exclude ? NOT_FOUND : SUCCESS;
return this->ifaces_filter->find_first(this->ifaces_filter, (void*)streq,
NULL, iface) == expected;
}
/**
* Job updating bypass policies
*/
static job_requeue_t update_bypass(private_bypass_lan_listener_t *this)
{
enumerator_t *enumerator;
hashtable_t *seen;
bypass_policy_t *found, *lookup;
host_t *net;
uint8_t mask;
char *iface;
seen = hashtable_create((hashtable_hash_t)policy_hash,
(hashtable_equals_t)policy_equals, 4);
this->mutex->lock(this->mutex);
enumerator = charon->kernel->create_local_subnet_enumerator(charon->kernel);
while (enumerator->enumerate(enumerator, &net, &mask, &iface))
{
if (!consider_interface(this, iface))
{
continue;
}
INIT(lookup,
.net = net->clone(net),
.mask = mask,
.iface = strdupnull(iface),
);
seen->put(seen, lookup, lookup);
found = this->policies->get(this->policies, lookup);
if (!found)
{
child_cfg_create_t child = {
.mode = MODE_PASS,
.interface = iface,
};
child_cfg_t *cfg;
traffic_selector_t *ts;
char name[128];
ts = traffic_selector_create_from_subnet(net->clone(net), mask,
0, 0, 65535);
snprintf(name, sizeof(name), "Bypass LAN %R [%s]", ts, iface ?: "");
cfg = child_cfg_create(name, &child);
cfg->add_traffic_selector(cfg, FALSE, ts->clone(ts));
cfg->add_traffic_selector(cfg, TRUE, ts);
charon->shunts->install(charon->shunts, cfg);
DBG1(DBG_IKE, "installed bypass policy for %R", ts);
INIT(found,
.net = net->clone(net),
.mask = mask,
.iface = strdupnull(iface),
.cfg = cfg,
);
this->policies->put(this->policies, found, found);
}
}
enumerator->destroy(enumerator);
enumerator = this->policies->create_enumerator(this->policies);
while (enumerator->enumerate(enumerator, NULL, &lookup))
{
if (!seen->get(seen, lookup))
{
this->policies->remove_at(this->policies, enumerator);
bypass_policy_destroy(lookup);
}
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
seen->destroy_function(seen, (void*)bypass_policy_destroy);
return JOB_REQUEUE_NONE;
}
METHOD(kernel_listener_t, roam, bool,
private_bypass_lan_listener_t *this, bool address)
{
lib->processor->queue_job(lib->processor,
(job_t*)callback_job_create((callback_job_cb_t)update_bypass, this,
NULL, (callback_job_cancel_t)return_false));
return TRUE;
}
METHOD(bypass_lan_listener_t, reload_interfaces, void,
private_bypass_lan_listener_t *this)
{
char *ifaces;
this->mutex->lock(this->mutex);
DESTROY_FUNCTION_IF(this->ifaces_filter, (void*)free);
this->ifaces_filter = NULL;
this->ifaces_exclude = FALSE;
ifaces = lib->settings->get_str(lib->settings,
"%s.plugins.bypass-lan.interfaces_use", NULL, lib->ns);
if (!ifaces)
{
this->ifaces_exclude = TRUE;
ifaces = lib->settings->get_str(lib->settings,
"%s.plugins.bypass-lan.interfaces_ignore", NULL, lib->ns);
}
if (ifaces)
{
enumerator_t *enumerator;
char *iface;
enumerator = enumerator_create_token(ifaces, ",", " ");
while (enumerator->enumerate(enumerator, &iface))
{
if (!this->ifaces_filter)
{
this->ifaces_filter = linked_list_create();
}
this->ifaces_filter->insert_last(this->ifaces_filter,
strdup(iface));
}
enumerator->destroy(enumerator);
}
this->mutex->unlock(this->mutex);
lib->processor->queue_job(lib->processor,
(job_t*)callback_job_create((callback_job_cb_t)update_bypass, this,
NULL, (callback_job_cancel_t)return_false));
}
METHOD(bypass_lan_listener_t, destroy, void,
private_bypass_lan_listener_t *this)
{
enumerator_t *enumerator;
bypass_policy_t *policy;
enumerator = this->policies->create_enumerator(this->policies);
while (enumerator->enumerate(enumerator, NULL, &policy))
{
bypass_policy_destroy(policy);
}
enumerator->destroy(enumerator);
DESTROY_FUNCTION_IF(this->ifaces_filter, (void*)free);
this->policies->destroy(this->policies);
this->mutex->destroy(this->mutex);
free(this);
}
/*
* See header
*/
bypass_lan_listener_t *bypass_lan_listener_create()
{
private_bypass_lan_listener_t *this;
INIT(this,
.public = {
.listener = {
.roam = _roam,
},
.reload_interfaces = _reload_interfaces,
.destroy = _destroy,
},
.policies = hashtable_create((hashtable_hash_t)policy_hash,
(hashtable_equals_t)policy_equals, 4),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
);
reload_interfaces(this);
return &this->public;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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.
*/
/**
* @defgroup bypass_lan_listener bypass_lan_listener
* @{ @ingroup bypass_lan
*/
#ifndef BYPASS_LAN_LISTENER_H_
#define BYPASS_LAN_LISTENER_H_
#include <bus/listeners/listener.h>
typedef struct bypass_lan_listener_t bypass_lan_listener_t;
/**
* Listener to install bypass policies
*/
struct bypass_lan_listener_t {
/**
* Implements kernel_listener_t interface.
*/
kernel_listener_t listener;
/**
* Reload ignored/used interface names from config.
*/
void (*reload_interfaces)(bypass_lan_listener_t *this);
/**
* Destroy a bypass_lan_listener_t.
*/
void (*destroy)(bypass_lan_listener_t *this);
};
/**
* Create a bypass_lan_listener instance.
*/
bypass_lan_listener_t *bypass_lan_listener_create();
#endif /** BYPASS_LAN_LISTENER_H_ @}*/

View File

@ -0,0 +1,109 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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.
*/
#include "bypass_lan_plugin.h"
#include "bypass_lan_listener.h"
#include <daemon.h>
typedef struct private_bypass_lan_plugin_t private_bypass_lan_plugin_t;
/**
* Private data
*/
struct private_bypass_lan_plugin_t {
/**
* Public interface
*/
bypass_lan_plugin_t public;
/**
* Listener installing bypass policies
*/
bypass_lan_listener_t *listener;
};
METHOD(plugin_t, get_name, char*,
private_bypass_lan_plugin_t *this)
{
return "bypass-lan";
}
/**
* Register listener
*/
static bool plugin_cb(private_bypass_lan_plugin_t *this,
plugin_feature_t *feature, bool reg, void *cb_data)
{
if (reg)
{
charon->kernel->add_listener(charon->kernel,
&this->listener->listener);
}
else
{
charon->kernel->remove_listener(charon->kernel,
&this->listener->listener);
}
return TRUE;
}
METHOD(plugin_t, get_features, int,
private_bypass_lan_plugin_t *this, plugin_feature_t *features[])
{
static plugin_feature_t f[] = {
PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL),
PLUGIN_PROVIDE(CUSTOM, "bypass-lan"),
};
*features = f;
return countof(f);
}
METHOD(plugin_t, reload, bool,
private_bypass_lan_plugin_t *this)
{
this->listener->reload_interfaces(this->listener);
return TRUE;
}
METHOD(plugin_t, destroy, void,
private_bypass_lan_plugin_t *this)
{
this->listener->destroy(this->listener);
free(this);
}
/**
* Plugin constructor
*/
plugin_t *bypass_lan_plugin_create()
{
private_bypass_lan_plugin_t *this;
INIT(this,
.public = {
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
.reload = _reload,
.destroy = _destroy,
},
},
.listener = bypass_lan_listener_create(),
);
return &this->public.plugin;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2016 Tobias Brunner
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* 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.
*/
/**
* @defgroup bypass_lan bypass_lan
* @ingroup cplugins
*
* @defgroup bypass_lan_plugin bypass_lan_plugin
* @{ @ingroup bypass_lan
*/
#ifndef BYPASS_LAN_PLUGIN_H_
#define BYPASS_LAN_PLUGIN_H_
#include <plugins/plugin.h>
typedef struct bypass_lan_plugin_t bypass_lan_plugin_t;
/**
* Plugin installing bypass policies for locally attached subnets.
*/
struct bypass_lan_plugin_t {
/**
* Implements plugin interface
*/
plugin_t plugin;
};
#endif /** BYPASS_LAN_PLUGIN_H_ @}*/

View File

@ -2125,6 +2125,146 @@ METHOD(kernel_net_t, get_nexthop, host_t*,
return get_route(this, dest, prefix, TRUE, src, iface, 0);
}
/** enumerator over subnets */
typedef struct {
enumerator_t public;
private_kernel_netlink_net_t *private;
/** message from the kernel */
struct nlmsghdr *msg;
/** current message from the kernel */
struct nlmsghdr *current;
/** remaining length */
size_t len;
/** last subnet enumerated */
host_t *net;
/** interface of current net */
char ifname[IFNAMSIZ];
} subnet_enumerator_t;
METHOD(enumerator_t, destroy_subnet_enumerator, void,
subnet_enumerator_t *this)
{
DESTROY_IF(this->net);
free(this->msg);
free(this);
}
METHOD(enumerator_t, enumerate_subnets, bool,
subnet_enumerator_t *this, host_t **net, uint8_t *mask, char **ifname)
{
if (!this->current)
{
this->current = this->msg;
}
else
{
this->current = NLMSG_NEXT(this->current, this->len);
DESTROY_IF(this->net);
this->net = NULL;
}
while (NLMSG_OK(this->current, this->len))
{
switch (this->current->nlmsg_type)
{
case NLMSG_DONE:
break;
case RTM_NEWROUTE:
{
struct rtmsg *msg;
struct rtattr *rta;
size_t rtasize;
chunk_t dst = chunk_empty;
uint32_t oif = 0;
msg = NLMSG_DATA(this->current);
if (!route_usable(this->current))
{
break;
}
else if (msg->rtm_table && (
msg->rtm_table == RT_TABLE_LOCAL ||
msg->rtm_table == this->private->routing_table))
{ /* ignore our own and the local routing tables */
break;
}
rta = RTM_RTA(msg);
rtasize = RTM_PAYLOAD(this->current);
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
case RTA_DST:
dst = chunk_create(RTA_DATA(rta), RTA_PAYLOAD(rta));
break;
case RTA_OIF:
if (RTA_PAYLOAD(rta) == sizeof(oif))
{
oif = *(uint32_t*)RTA_DATA(rta);
}
break;
}
rta = RTA_NEXT(rta, rtasize);
}
if (dst.ptr && oif && if_indextoname(oif, this->ifname))
{
this->net = host_create_from_chunk(msg->rtm_family, dst, 0);
*net = this->net;
*mask = msg->rtm_dst_len;
*ifname = this->ifname;
return TRUE;
}
break;
}
default:
break;
}
this->current = NLMSG_NEXT(this->current, this->len);
}
return FALSE;
}
METHOD(kernel_net_t, create_local_subnet_enumerator, enumerator_t*,
private_kernel_netlink_net_t *this)
{
netlink_buf_t request;
struct nlmsghdr *hdr, *out;
struct rtmsg *msg;
size_t len;
subnet_enumerator_t *enumerator;
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST;
hdr->nlmsg_type = RTM_GETROUTE;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
hdr->nlmsg_flags |= NLM_F_DUMP;
msg = NLMSG_DATA(hdr);
msg->rtm_scope = RT_SCOPE_LINK;
if (this->socket->send(this->socket, hdr, &out, &len) != SUCCESS)
{
DBG2(DBG_KNL, "enumerating local subnets failed");
return enumerator_create_empty();
}
INIT(enumerator,
.public = {
.enumerate = (void*)_enumerate_subnets,
.destroy = _destroy_subnet_enumerator,
},
.private = this,
.msg = out,
.len = len,
);
return &enumerator->public;
}
/**
* Manages the creation and deletion of ip addresses on an interface.
* By setting the appropriate nlmsg_type, the ip will be set or unset.
@ -2761,6 +2901,7 @@ kernel_netlink_net_t *kernel_netlink_net_create()
.interface = {
.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,

View File

@ -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>
@ -1704,6 +1705,198 @@ METHOD(kernel_net_t, get_nexthop, host_t*,
return get_route(this, TRUE, dest, src, iface);
}
/**
* 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.
*/
@ -1849,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,