diff options
-rw-r--r-- | conf/Makefile.am | 1 | ||||
-rw-r--r-- | conf/plugins/bypass-lan.opt | 8 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | src/libcharon/Makefile.am | 7 | ||||
-rw-r--r-- | src/libcharon/kernel/kernel_interface.c | 11 | ||||
-rw-r--r-- | src/libcharon/kernel/kernel_interface.h | 11 | ||||
-rw-r--r-- | src/libcharon/kernel/kernel_net.h | 11 | ||||
-rw-r--r-- | src/libcharon/plugins/bypass_lan/Makefile.am | 18 | ||||
-rw-r--r-- | src/libcharon/plugins/bypass_lan/bypass_lan_listener.c | 296 | ||||
-rw-r--r-- | src/libcharon/plugins/bypass_lan/bypass_lan_listener.h | 54 | ||||
-rw-r--r-- | src/libcharon/plugins/bypass_lan/bypass_lan_plugin.c | 109 | ||||
-rw-r--r-- | src/libcharon/plugins/bypass_lan/bypass_lan_plugin.h | 42 | ||||
-rw-r--r-- | src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c | 141 | ||||
-rw-r--r-- | src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c | 194 |
14 files changed, 907 insertions, 0 deletions
diff --git a/conf/Makefile.am b/conf/Makefile.am index c4b2c02fd..80fa31e73 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -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 \ diff --git a/conf/plugins/bypass-lan.opt b/conf/plugins/bypass-lan.opt new file mode 100644 index 000000000..8c72facde --- /dev/null +++ b/conf/plugins/bypass-lan.opt @@ -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. diff --git a/configure.ac b/configure.ac index 86562bc70..ddedad184 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 6fa995a30..18f2dee10 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -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 diff --git a/src/libcharon/kernel/kernel_interface.c b/src/libcharon/kernel/kernel_interface.c index 7b39a020c..ea5af9eb8 100644 --- a/src/libcharon/kernel/kernel_interface.c +++ b/src/libcharon/kernel/kernel_interface.c @@ -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, diff --git a/src/libcharon/kernel/kernel_interface.h b/src/libcharon/kernel/kernel_interface.h index 225b40932..96c9ffa62 100644 --- a/src/libcharon/kernel/kernel_interface.h +++ b/src/libcharon/kernel/kernel_interface.h @@ -316,6 +316,17 @@ struct kernel_interface_t { 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. * * Virtual IPs are attached to an interface. If an IP is added multiple diff --git a/src/libcharon/kernel/kernel_net.h b/src/libcharon/kernel/kernel_net.h index 1d78d6edd..12475b123 100644 --- a/src/libcharon/kernel/kernel_net.h +++ b/src/libcharon/kernel/kernel_net.h @@ -119,6 +119,17 @@ struct kernel_net_t { 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. * * Virtual IPs are attached to an interface. If an IP is added multiple diff --git a/src/libcharon/plugins/bypass_lan/Makefile.am b/src/libcharon/plugins/bypass_lan/Makefile.am new file mode 100644 index 000000000..c1313f6ba --- /dev/null +++ b/src/libcharon/plugins/bypass_lan/Makefile.am @@ -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 diff --git a/src/libcharon/plugins/bypass_lan/bypass_lan_listener.c b/src/libcharon/plugins/bypass_lan/bypass_lan_listener.c new file mode 100644 index 000000000..49f7cd3ca --- /dev/null +++ b/src/libcharon/plugins/bypass_lan/bypass_lan_listener.c @@ -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; +} diff --git a/src/libcharon/plugins/bypass_lan/bypass_lan_listener.h b/src/libcharon/plugins/bypass_lan/bypass_lan_listener.h new file mode 100644 index 000000000..737230db9 --- /dev/null +++ b/src/libcharon/plugins/bypass_lan/bypass_lan_listener.h @@ -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_ @}*/ diff --git a/src/libcharon/plugins/bypass_lan/bypass_lan_plugin.c b/src/libcharon/plugins/bypass_lan/bypass_lan_plugin.c new file mode 100644 index 000000000..ccc05f0a7 --- /dev/null +++ b/src/libcharon/plugins/bypass_lan/bypass_lan_plugin.c @@ -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; +} diff --git a/src/libcharon/plugins/bypass_lan/bypass_lan_plugin.h b/src/libcharon/plugins/bypass_lan/bypass_lan_plugin.h new file mode 100644 index 000000000..934bf0cf5 --- /dev/null +++ b/src/libcharon/plugins/bypass_lan/bypass_lan_plugin.h @@ -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_ @}*/ diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index 4ecd97634..b19bbf2f0 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -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, diff --git a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c index 0717a8a2e..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> @@ -1705,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) @@ -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, |