/*
- * Copyright (C) 2006-2017 Tobias Brunner
+ * Copyright (C) 2006-2019 Tobias Brunner
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2008-2016 Andreas Steffen
* Copyright (C) 2006-2007 Fabian Hartmann, Noah Heusser
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
+/*
+ * Copyright (C) 2018 Mellanox Technologies.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/ioctl.h>
#include <stdint.h>
#include <linux/ipsec.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/xfrm.h>
#include <linux/udp.h>
+#include <linux/ethtool.h>
+#include <linux/sockios.h>
#include <net/if.h>
#include <unistd.h>
#include <time.h>
/** Base priority for installed policies */
#define PRIO_BASE 200000
-/** Default lifetime of an acquire XFRM state (in seconds) */
-#define DEFAULT_ACQUIRE_LIFETIME 165
-
/**
* Map the limit for bytes and packets to XFRM_INF by default
*/
"XFRM_MSG_MAPPING"
);
-ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_REPLAY_ESN_VAL,
+ENUM(xfrm_attr_type_names, XFRMA_UNSPEC, XFRMA_OFFLOAD_DEV,
"XFRMA_UNSPEC",
"XFRMA_ALG_AUTH",
"XFRMA_ALG_CRYPT",
"XFRMA_MARK",
"XFRMA_TFCPAD",
"XFRMA_REPLAY_ESN_VAL",
+ "XFRMA_SA_EXTRA_FLAGS",
+ "XFRMA_PROTO",
+ "XFRMA_ADDRESS_FILTER",
+ "XFRMA_PAD",
+ "XFRMA_OFFLOAD_DEV",
);
/**
kernel_ipsec_manage_policy_t *data);
};
-typedef struct route_entry_t route_entry_t;
-
-/**
- * Installed routing entry
- */
-struct route_entry_t {
- /** Name of the interface the route is bound to */
- char *if_name;
-
- /** Source ip of the route */
- host_t *src_ip;
-
- /** Gateway for this route */
- host_t *gateway;
-
- /** Destination net */
- chunk_t dst_net;
-
- /** Destination net prefixlen */
- uint8_t prefixlen;
-};
-
-/**
- * Destroy a route_entry_t object
- */
-static void route_entry_destroy(route_entry_t *this)
-{
- free(this->if_name);
- this->src_ip->destroy(this->src_ip);
- DESTROY_IF(this->gateway);
- chunk_free(&this->dst_net);
- free(this);
-}
-
-/**
- * Compare two route_entry_t objects
- */
-static bool route_entry_equals(route_entry_t *a, route_entry_t *b)
-{
- return a->if_name && b->if_name && streq(a->if_name, b->if_name) &&
- a->src_ip->ip_equals(a->src_ip, b->src_ip) &&
- a->gateway->ip_equals(a->gateway, b->gateway) &&
- chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen;
-}
-
typedef struct ipsec_sa_t ipsec_sa_t;
/**
/** Optional mark */
mark_t mark;
+ /** Optional mark */
+ uint32_t if_id;
+
/** Description of this SA */
ipsec_sa_cfg_t cfg;
return chunk_hash_inc(sa->src->get_address(sa->src),
chunk_hash_inc(sa->dst->get_address(sa->dst),
chunk_hash_inc(chunk_from_thing(sa->mark),
- chunk_hash(chunk_from_thing(sa->cfg)))));
+ chunk_hash_inc(chunk_from_thing(sa->if_id),
+ chunk_hash(chunk_from_thing(sa->cfg))))));
}
/**
sa->dst->ip_equals(sa->dst, other_sa->dst) &&
sa->mark.value == other_sa->mark.value &&
sa->mark.mask == other_sa->mark.mask &&
+ sa->if_id == other_sa->if_id &&
ipsec_sa_cfg_equals(&sa->cfg, &other_sa->cfg);
}
*/
static ipsec_sa_t *ipsec_sa_create(private_kernel_netlink_ipsec_t *this,
host_t *src, host_t *dst, mark_t mark,
- ipsec_sa_cfg_t *cfg)
+ uint32_t if_id, ipsec_sa_cfg_t *cfg)
{
ipsec_sa_t *sa, *found;
INIT(sa,
.src = src,
.dst = dst,
.mark = mark,
+ .if_id = if_id,
.cfg = *cfg,
);
found = this->sas->get(this->sas, sa);
static policy_sa_t *policy_sa_create(private_kernel_netlink_ipsec_t *this,
policy_dir_t dir, policy_type_t type, host_t *src, host_t *dst,
traffic_selector_t *src_ts, traffic_selector_t *dst_ts, mark_t mark,
- ipsec_sa_cfg_t *cfg)
+ uint32_t if_id, ipsec_sa_cfg_t *cfg)
{
policy_sa_t *policy;
INIT(policy, .priority = 0);
}
policy->type = type;
- policy->sa = ipsec_sa_create(this, src, dst, mark, cfg);
+ policy->sa = ipsec_sa_create(this, src, dst, mark, if_id, cfg);
return policy;
}
/**
* Destroy a policy_sa(_in)_t object
*/
-static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t *dir,
+static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t dir,
private_kernel_netlink_ipsec_t *this)
{
- if (*dir == POLICY_OUT)
+ if (dir == POLICY_OUT)
{
policy_sa_out_t *out = (policy_sa_out_t*)policy;
out->src_ts->destroy(out->src_ts);
free(policy);
}
+CALLBACK(policy_sa_destroy_cb, void,
+ policy_sa_t *policy, va_list args)
+{
+ private_kernel_netlink_ipsec_t *this;
+ policy_dir_t dir;
+
+ VA_ARGS_VGET(args, dir, this);
+ policy_sa_destroy(policy, dir, this);
+}
+
typedef struct policy_entry_t policy_entry_t;
/**
/** Optional mark */
uint32_t mark;
+ /** Optional interface ID */
+ uint32_t if_id;
+
/** Associated route installed for this policy */
route_entry_t *route;
}
if (policy->used_by)
{
- policy->used_by->invoke_function(policy->used_by,
- (linked_list_invoke_t)policy_sa_destroy,
- &policy->direction, this);
+ policy->used_by->invoke_function(policy->used_by, policy_sa_destroy_cb,
+ policy->direction, this);
policy->used_by->destroy(policy->used_by);
}
free(policy);
static u_int policy_hash(policy_entry_t *key)
{
chunk_t chunk = chunk_from_thing(key->sel);
- return chunk_hash_inc(chunk, chunk_hash(chunk_from_thing(key->mark)));
+ return chunk_hash_inc(chunk, chunk_hash_inc(chunk_from_thing(key->mark),
+ chunk_hash(chunk_from_thing(key->if_id))));
}
/**
{
return memeq(&key->sel, &other_key->sel, sizeof(struct xfrm_selector)) &&
key->mark == other_key->mark &&
+ key->if_id == other_key->if_id &&
key->direction == other_key->direction;
}
static bool receive_events(private_kernel_netlink_ipsec_t *this, int fd,
watcher_event_t event)
{
- char response[1024];
+ char response[netlink_get_buflen()];
struct nlmsghdr *hdr = (struct nlmsghdr*)response;
struct sockaddr_nl addr;
socklen_t addr_len = sizeof(addr);
METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_netlink_ipsec_t *this)
{
- return KERNEL_ESP_V3_TFC;
+ return KERNEL_ESP_V3_TFC | KERNEL_POLICY_SPI;
}
/**
return TRUE;
}
+/**
+ * Add a uint32 attribute to message
+ */
+static bool add_uint32(struct nlmsghdr *hdr, int buflen,
+ enum xfrm_attr_type_t type, uint32_t value)
+{
+ uint32_t *xvalue;
+
+ xvalue = netlink_reserve(hdr, buflen, type, sizeof(*xvalue));
+ if (!xvalue)
+ {
+ return FALSE;
+ }
+ *xvalue = value;
+ return TRUE;
+}
+
+/* ETHTOOL_GSSET_INFO is available since 2.6.34 and ETH_SS_FEATURES (enum) and
+ * ETHTOOL_GFEATURES since 2.6.39, so check for the latter */
+#ifdef ETHTOOL_GFEATURES
+
+/**
+ * Global metadata used for IPsec HW offload
+ */
+static struct {
+ /** determined HW offload support */
+ bool supported;
+ /** bit in feature set */
+ u_int bit;
+ /** total number of device feature blocks */
+ u_int total_blocks;
+} netlink_hw_offload;
+
+/**
+ * Check if kernel supports HW offload and determine feature flag
+ */
+static void netlink_find_offload_feature(const char *ifname)
+{
+ struct ethtool_sset_info *sset_info;
+ struct ethtool_gstrings *cmd = NULL;
+ struct ifreq ifr;
+ uint32_t sset_len, i;
+ char *str;
+ int err, query_socket;
+
+ query_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
+ if (query_socket < 0)
+ {
+ return;
+ }
+
+ /* determine number of device features */
+ INIT_EXTRA(sset_info, sizeof(uint32_t),
+ .cmd = ETHTOOL_GSSET_INFO,
+ .sset_mask = 1ULL << ETH_SS_FEATURES,
+ );
+ strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+ ifr.ifr_name[IFNAMSIZ-1] = '\0';
+ ifr.ifr_data = (void*)sset_info;
+
+ err = ioctl(query_socket, SIOCETHTOOL, &ifr);
+ if (err || sset_info->sset_mask != 1ULL << ETH_SS_FEATURES)
+ {
+ goto out;
+ }
+ sset_len = sset_info->data[0];
+
+ /* retrieve names of device features */
+ INIT_EXTRA(cmd, ETH_GSTRING_LEN * sset_len,
+ .cmd = ETHTOOL_GSTRINGS,
+ .string_set = ETH_SS_FEATURES,
+ );
+ strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+ ifr.ifr_name[IFNAMSIZ-1] = '\0';
+ ifr.ifr_data = (void*)cmd;
+
+ err = ioctl(query_socket, SIOCETHTOOL, &ifr);
+ if (err)
+ {
+ goto out;
+ }
+
+ /* look for the ESP_HW feature bit */
+ str = (char*)cmd->data;
+ for (i = 0; i < cmd->len; i++)
+ {
+ if (strneq(str, "esp-hw-offload", ETH_GSTRING_LEN))
+ {
+ netlink_hw_offload.supported = TRUE;
+ netlink_hw_offload.bit = i;
+ netlink_hw_offload.total_blocks = (sset_len + 31) / 32;
+ break;
+ }
+ str += ETH_GSTRING_LEN;
+ }
+
+out:
+ free(sset_info);
+ free(cmd);
+ close(query_socket);
+}
+
+/**
+ * Check if interface supported HW offload
+ */
+static bool netlink_detect_offload(const char *ifname)
+{
+ struct ethtool_gfeatures *cmd;
+ uint32_t feature_bit;
+ struct ifreq ifr;
+ int query_socket;
+ int block;
+ bool ret = FALSE;
+
+ if (!netlink_hw_offload.supported)
+ {
+ DBG1(DBG_KNL, "HW offload is not supported by kernel");
+ return FALSE;
+ }
+
+ query_socket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_XFRM);
+ if (query_socket < 0)
+ {
+ return FALSE;
+ }
+
+ /* feature is supported by kernel, query device features */
+ INIT_EXTRA(cmd, sizeof(cmd->features[0]) * netlink_hw_offload.total_blocks,
+ .cmd = ETHTOOL_GFEATURES,
+ .size = netlink_hw_offload.total_blocks,
+ );
+ strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
+ ifr.ifr_name[IFNAMSIZ-1] = '\0';
+ ifr.ifr_data = (void*)cmd;
+
+ if (!ioctl(query_socket, SIOCETHTOOL, &ifr))
+ {
+ block = netlink_hw_offload.bit / 32;
+ feature_bit = 1U << (netlink_hw_offload.bit % 32);
+ if (cmd->features[block].active & feature_bit)
+ {
+ ret = TRUE;
+ }
+ }
+
+ if (!ret)
+ {
+ DBG1(DBG_KNL, "HW offload is not supported by device");
+ }
+ free(cmd);
+ close(query_socket);
+ return ret;
+}
+
+#else
+
+static void netlink_find_offload_feature(const char *ifname)
+{
+}
+
+static bool netlink_detect_offload(const char *ifname)
+{
+ return FALSE;
+}
+
+#endif
+
+/**
+ * There are 3 HW offload configuration values:
+ * 1. HW_OFFLOAD_NO : Do not configure HW offload.
+ * 2. HW_OFFLOAD_YES : Configure HW offload.
+ * Fail SA addition if offload is not supported.
+ * 3. HW_OFFLOAD_AUTO : Configure HW offload if supported by the kernel
+ * and device.
+ * Do not fail SA addition otherwise.
+ */
+static bool config_hw_offload(kernel_ipsec_sa_id_t *id,
+ kernel_ipsec_add_sa_t *data, struct nlmsghdr *hdr,
+ int buflen)
+{
+ host_t *local = data->inbound ? id->dst : id->src;
+ struct xfrm_user_offload *offload;
+ bool hw_offload_yes, ret = FALSE;
+ char *ifname;
+
+ /* do Ipsec configuration without offload */
+ if (data->hw_offload == HW_OFFLOAD_NO)
+ {
+ return TRUE;
+ }
+
+ hw_offload_yes = (data->hw_offload == HW_OFFLOAD_YES);
+
+ if (!charon->kernel->get_interface(charon->kernel, local, &ifname))
+ {
+ return !hw_offload_yes;
+ }
+
+ /* check if interface supports hw_offload */
+ if (!netlink_detect_offload(ifname))
+ {
+ ret = !hw_offload_yes;
+ goto out;
+ }
+
+ /* activate HW offload */
+ offload = netlink_reserve(hdr, buflen,
+ XFRMA_OFFLOAD_DEV, sizeof(*offload));
+ if (!offload)
+ {
+ ret = !hw_offload_yes;
+ goto out;
+ }
+ offload->ifindex = if_nametoindex(ifname);
+ if (local->get_family(local) == AF_INET6)
+ {
+ offload->flags |= XFRM_OFFLOAD_IPV6;
+ }
+ offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0;
+
+ ret = TRUE;
+
+out:
+ free(ifname);
+ return ret;
+}
+
METHOD(kernel_ipsec_t, add_sa, status_t,
private_kernel_netlink_ipsec_t *this, kernel_ipsec_sa_id_t *id,
kernel_ipsec_add_sa_t *data)
.spi = htonl(ntohs(data->cpi)),
.proto = IPPROTO_COMP,
.mark = id->mark,
+ .if_id = id->if_id,
};
kernel_ipsec_add_sa_t ipcomp_sa = {
.reqid = data->reqid,
sa->id.proto = id->proto;
sa->family = id->src->get_family(id->src);
sa->mode = mode2kernel(mode);
+
+ if (!data->copy_df)
+ {
+ sa->flags |= XFRM_STATE_NOPMTUDISC;
+ }
+
+ if (!data->copy_ecn)
+ {
+ sa->flags |= XFRM_STATE_NOECN;
+ }
+
+ if (data->inbound)
+ {
+ switch (data->copy_dscp)
+ {
+ case DSCP_COPY_YES:
+ case DSCP_COPY_IN_ONLY:
+ sa->flags |= XFRM_STATE_DECAP_DSCP;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch (data->copy_dscp)
+ {
+ case DSCP_COPY_IN_ONLY:
+ case DSCP_COPY_NO:
+ {
+ /* currently the only extra flag */
+ if (!add_uint32(hdr, sizeof(request), XFRMA_SA_EXTRA_FLAGS,
+ XFRM_SA_XFLAG_DONT_ENCAP_DSCP))
+ {
+ goto failed;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
switch (mode)
{
case MODE_TUNNEL:
goto failed;
}
+ if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+ {
+ goto failed;
+ }
+
+ if (ipcomp == IPCOMP_NONE && (data->mark.value | data->mark.mask))
+ {
+ if (!add_uint32(hdr, sizeof(request), XFRMA_SET_MARK,
+ data->mark.value) ||
+ !add_uint32(hdr, sizeof(request), XFRMA_SET_MARK_MASK,
+ data->mark.mask))
+ {
+ goto failed;
+ }
+ }
+
if (data->tfc && id->proto == IPPROTO_ESP && mode == MODE_TUNNEL)
{ /* the kernel supports TFC padding only for tunnel mode ESP SAs */
- uint32_t *tfcpad;
-
- tfcpad = netlink_reserve(hdr, sizeof(request), XFRMA_TFCPAD,
- sizeof(*tfcpad));
- if (!tfcpad)
+ if (!add_uint32(hdr, sizeof(request), XFRMA_TFCPAD, data->tfc))
{
goto failed;
}
- *tfcpad = data->tfc;
}
if (id->proto != IPPROTO_COMP)
data->replay_window);
sa->replay_window = data->replay_window;
}
- if (data->hw_offload)
- {
- host_t *local = data->inbound ? id->dst : id->src;
- char *ifname;
-
- if (charon->kernel->get_interface(charon->kernel, local, &ifname))
- {
- struct xfrm_user_offload *offload;
- offload = netlink_reserve(hdr, sizeof(request),
- XFRMA_OFFLOAD_DEV, sizeof(*offload));
- if (!offload)
- {
- free(ifname);
- goto failed;
- }
- offload->ifindex = if_nametoindex(ifname);
- if (local->get_family(local) == AF_INET6)
- {
- offload->flags |= XFRM_OFFLOAD_IPV6;
- }
- offload->flags |= data->inbound ? XFRM_OFFLOAD_INBOUND : 0;
- free(ifname);
- }
+ DBG2(DBG_KNL, " HW offload: %N", hw_offload_names, data->hw_offload);
+ if (!config_hw_offload(id, data, hdr, sizeof(request)))
+ {
+ DBG1(DBG_KNL, "failed to configure HW offload");
+ goto failed;
}
}
- if (this->socket_xfrm->send_ack(this->socket_xfrm, hdr) != SUCCESS)
+ status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+ if (status == NOT_FOUND && data->update)
{
- DBG1(DBG_KNL, "unable to add SAD entry with SPI %.8x%s", ntohl(id->spi),
- markstr);
+ DBG1(DBG_KNL, "allocated SPI not found anymore, try to add SAD entry");
+ hdr->nlmsg_type = XFRM_MSG_NEWSA;
+ status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
+ }
+
+ if (status != SUCCESS)
+ {
+ DBG1(DBG_KNL, "unable to add SAD entry with SPI %.8x%s (%N)", ntohl(id->spi),
+ markstr, status_names, status);
+ status = FAILED;
goto failed;
}
{
return;
}
+ if (sa->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, sa->if_id))
+ {
+ return;
+ }
if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
{
{
return FAILED;
}
+ if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+ {
+ return FAILED;
+ }
if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
{
{
return FAILED;
}
+ if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+ {
+ return FAILED;
+ }
switch (this->socket_xfrm->send_ack(this->socket_xfrm, hdr))
{
uint32_t replay_esn_len = 0;
kernel_ipsec_del_sa_t del = { 0 };
status_t status = FAILED;
+ traffic_selector_t *ts;
char markstr[32] = "";
/* if IPComp is used, we first update the IPComp SA */
.spi = htonl(ntohs(data->cpi)),
.proto = IPPROTO_COMP,
.mark = id->mark,
+ .if_id = id->if_id,
};
kernel_ipsec_update_sa_t ipcomp = {
.new_src = data->new_src,
{
return FAILED;
}
+ if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+ {
+ return FAILED;
+ }
if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
{
if (!id->src->ip_equals(id->src, data->new_src))
{
host2xfrm(data->new_src, &sa->saddr);
+
+ ts = selector2ts(&sa->sel, TRUE);
+ if (ts && ts->is_host(ts, id->src))
+ {
+ ts->set_address(ts, data->new_src);
+ ts2subnet(ts, &sa->sel.saddr, &sa->sel.prefixlen_s);
+ }
+ DESTROY_IF(ts);
}
if (!id->dst->ip_equals(id->dst, data->new_dst))
{
host2xfrm(data->new_dst, &sa->id.daddr);
+
+ ts = selector2ts(&sa->sel, FALSE);
+ if (ts && ts->is_host(ts, id->dst))
+ {
+ ts->set_address(ts, data->new_dst);
+ ts2subnet(ts, &sa->sel.daddr, &sa->sel.prefixlen_d);
+ }
+ DESTROY_IF(ts);
}
rta = XFRM_RTA(out_hdr, struct xfrm_usersa_info);
INIT(route,
.prefixlen = policy->sel.prefixlen_d,
+ .pass = mapping->type == POLICY_PASS,
);
if (charon->kernel->get_address_by_ts(charon->kernel, out->src_ts,
- &route->src_ip, NULL) == SUCCESS)
+ &route->src_ip, NULL) != SUCCESS)
{
- if (!ipsec->dst->is_anyaddr(ipsec->dst))
+ if (!route->pass)
{
- route->gateway = charon->kernel->get_nexthop(charon->kernel,
- ipsec->dst, -1, ipsec->src,
- &route->if_name);
+ free(route);
+ return;
}
- else
- { /* for shunt policies */
- iface = xfrm2host(policy->sel.family, &policy->sel.daddr, 0);
- route->gateway = charon->kernel->get_nexthop(charon->kernel,
- iface, policy->sel.prefixlen_d,
- route->src_ip, &route->if_name);
- iface->destroy(iface);
- }
- route->dst_net = chunk_alloc(policy->sel.family == AF_INET ? 4 : 16);
- memcpy(route->dst_net.ptr, &policy->sel.daddr, route->dst_net.len);
-
- /* get the interface to install the route for, if we haven't one yet.
- * If we have a local address, use it. Otherwise (for shunt policies)
- * use the route's source address. */
- if (!route->if_name)
- {
- iface = ipsec->src;
- if (iface->is_anyaddr(iface))
- {
- iface = route->src_ip;
- }
- if (!charon->kernel->get_interface(charon->kernel, iface,
- &route->if_name))
- {
- route_entry_destroy(route);
- return;
- }
+ /* allow blank source IP for passthrough policies */
+ route->src_ip = host_create_any(policy->sel.family);
+ }
+
+ if (!ipsec->dst->is_anyaddr(ipsec->dst))
+ {
+ route->gateway = charon->kernel->get_nexthop(charon->kernel,
+ ipsec->dst, -1, ipsec->src,
+ &route->if_name);
+ }
+ else
+ { /* for shunt policies */
+ iface = xfrm2host(policy->sel.family, &policy->sel.daddr, 0);
+ route->gateway = charon->kernel->get_nexthop(charon->kernel,
+ iface, policy->sel.prefixlen_d,
+ route->src_ip, &route->if_name);
+ iface->destroy(iface);
+ }
+ route->dst_net = chunk_alloc(policy->sel.family == AF_INET ? 4 : 16);
+ memcpy(route->dst_net.ptr, &policy->sel.daddr, route->dst_net.len);
+
+ /* get the interface to install the route for, if we haven't one yet.
+ * If we have a local address, use it. Otherwise (for shunt policies)
+ * use the route's source address. */
+ if (!route->if_name)
+ {
+ iface = ipsec->src;
+ if (iface->is_anyaddr(iface))
+ {
+ iface = route->src_ip;
}
- if (policy->route)
+ if (!charon->kernel->get_interface(charon->kernel, iface,
+ &route->if_name) &&
+ !route->pass)
+ { /* don't require an interface for passthrough policies */
+ route_entry_destroy(route);
+ return;
+ }
+ }
+ if (policy->route)
+ {
+ route_entry_t *old = policy->route;
+ if (route_entry_equals(old, route))
{
- route_entry_t *old = policy->route;
- if (route_entry_equals(old, route))
- {
- route_entry_destroy(route);
- return;
- }
- /* uninstall previously installed route */
- if (charon->kernel->del_route(charon->kernel, old->dst_net,
- old->prefixlen, old->gateway,
- old->src_ip, old->if_name) != SUCCESS)
- {
- DBG1(DBG_KNL, "error uninstalling route installed with policy "
- "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names,
- policy->direction);
- }
- route_entry_destroy(old);
- policy->route = NULL;
+ route_entry_destroy(route);
+ return;
}
-
- DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", out->dst_ts,
- route->gateway, route->src_ip, route->if_name);
- switch (charon->kernel->add_route(charon->kernel, route->dst_net,
- route->prefixlen, route->gateway,
- route->src_ip, route->if_name))
+ /* uninstall previously installed route */
+ if (charon->kernel->del_route(charon->kernel, old->dst_net,
+ old->prefixlen, old->gateway,
+ old->src_ip, old->if_name,
+ old->pass) != SUCCESS)
{
- default:
- DBG1(DBG_KNL, "unable to install source route for %H",
- route->src_ip);
- /* FALL */
- case ALREADY_DONE:
- /* route exists, do not uninstall */
- route_entry_destroy(route);
- break;
- case SUCCESS:
- /* cache the installed route */
- policy->route = route;
- break;
+ DBG1(DBG_KNL, "error uninstalling route installed with policy "
+ "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names,
+ policy->direction);
}
+ route_entry_destroy(old);
+ policy->route = NULL;
}
- else
+
+ DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", out->dst_ts,
+ route->gateway, route->src_ip, route->if_name);
+ switch (charon->kernel->add_route(charon->kernel, route->dst_net,
+ route->prefixlen, route->gateway,
+ route->src_ip, route->if_name,
+ route->pass))
{
- free(route);
+ default:
+ DBG1(DBG_KNL, "unable to install source route for %H",
+ route->src_ip);
+ /* FALL */
+ case ALREADY_DONE:
+ /* route exists, do not uninstall */
+ route_entry_destroy(route);
+ break;
+ case SUCCESS:
+ /* cache the installed route */
+ policy->route = route;
+ break;
}
}
struct xfrm_user_tmpl *tmpl;
struct {
uint8_t proto;
+ uint32_t spi;
bool use;
} protos[] = {
- { IPPROTO_COMP, ipsec->cfg.ipcomp.transform != IPCOMP_NONE },
- { IPPROTO_ESP, ipsec->cfg.esp.use },
- { IPPROTO_AH, ipsec->cfg.ah.use },
+ { IPPROTO_COMP, htonl(ntohs(ipsec->cfg.ipcomp.cpi)),
+ ipsec->cfg.ipcomp.transform != IPCOMP_NONE },
+ { IPPROTO_ESP, ipsec->cfg.esp.spi, ipsec->cfg.esp.use },
+ { IPPROTO_AH, ipsec->cfg.ah.spi, ipsec->cfg.ah.use },
};
ipsec_mode_t proto_mode = ipsec->cfg.mode;
int count = 0;
}
tmpl->reqid = ipsec->cfg.reqid;
tmpl->id.proto = protos[i].proto;
+ if (policy->direction == POLICY_OUT)
+ {
+ tmpl->id.spi = protos[i].spi;
+ }
tmpl->aalgos = tmpl->ealgos = tmpl->calgos = ~0;
tmpl->mode = mode2kernel(proto_mode);
tmpl->optional = protos[i].proto == IPPROTO_COMP &&
policy_change_done(this, policy);
return FAILED;
}
+ if (ipsec->if_id &&
+ !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, ipsec->if_id))
+ {
+ policy_change_done(this, policy);
+ return FAILED;
+ }
this->mutex->unlock(this->mutex);
status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr);
* - this is an outbound policy (to just get one for each child)
* - routing is not disabled via strongswan.conf
* - the selector is not for a specific protocol/port
+ * - no XFRM interface ID is configured
* - we are in tunnel/BEET mode or install a bypass policy
*/
if (policy->direction == POLICY_OUT && this->install_routes &&
- !policy->sel.proto && !policy->sel.dport && !policy->sel.sport)
+ !policy->sel.proto && !policy->sel.dport && !policy->sel.sport &&
+ !policy->if_id)
{
if (mapping->type == POLICY_PASS ||
(mapping->type == POLICY_IPSEC && ipsec->cfg.mode != MODE_TRANSPORT))
INIT(policy,
.sel = ts2selector(id->src_ts, id->dst_ts, id->interface),
.mark = id->mark.value & id->mark.mask,
+ .if_id = id->if_id,
.direction = id->dir,
.reqid = data->sa->reqid,
);
/* cache the assigned IPsec SA */
assigned_sa = policy_sa_create(this, id->dir, data->type, data->src,
- data->dst, id->src_ts, id->dst_ts, id->mark, data->sa);
+ data->dst, id->src_ts, id->dst_ts, id->mark,
+ id->if_id, data->sa);
assigned_sa->auto_priority = get_priority(policy, data->prio, id->interface);
assigned_sa->priority = this->get_priority ? this->get_priority(id, data)
: data->manual_prio;
{
return FAILED;
}
+ if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+ {
+ return FAILED;
+ }
if (this->socket_xfrm->send(this->socket_xfrm, hdr, &out, &len) == SUCCESS)
{
.src = data->src,
.dst = data->dst,
.mark = id->mark,
+ .if_id = id->if_id,
.cfg = *data->sa,
};
char markstr[32] = "";
memset(&policy, 0, sizeof(policy_entry_t));
policy.sel = ts2selector(id->src_ts, id->dst_ts, id->interface);
policy.mark = id->mark.value & id->mark.mask;
+ policy.if_id = id->if_id;
policy.direction = id->dir;
/* find the policy */
ipsec_sa_equals(mapping->sa, &assigned_sa))
{
current->used_by->remove_at(current->used_by, enumerator);
- policy_sa_destroy(mapping, &id->dir, this);
+ policy_sa_destroy(mapping, id->dir, this);
break;
}
if (is_installed)
policy_change_done(this, current);
return FAILED;
}
+ if (id->if_id && !add_uint32(hdr, sizeof(request), XFRMA_IF_ID, id->if_id))
+ {
+ policy_change_done(this, current);
+ return FAILED;
+ }
if (current->route)
{
route_entry_t *route = current->route;
if (charon->kernel->del_route(charon->kernel, route->dst_net,
route->prefixlen, route->gateway,
- route->src_ip, route->if_name) != SUCCESS)
+ route->src_ip, route->if_name,
+ route->pass) != SUCCESS)
{
DBG1(DBG_KNL, "error uninstalling route installed with policy "
"%R === %R %N%s", id->src_ts, id->dst_ts, policy_dir_names,
{
private_kernel_netlink_ipsec_t *this;
bool register_for_events = TRUE;
- FILE *f;
INIT(this,
.public = {
register_for_events = FALSE;
}
- f = fopen("/proc/sys/net/core/xfrm_acq_expires", "w");
- if (f)
- {
- fprintf(f, "%u", lib->settings->get_int(lib->settings,
- "%s.plugins.kernel-netlink.xfrm_acq_expires",
- DEFAULT_ACQUIRE_LIFETIME, lib->ns));
- fclose(f);
- }
-
this->socket_xfrm = netlink_socket_create(NETLINK_XFRM, xfrm_msg_names,
lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.parallel_xfrm", FALSE, lib->ns));
(watcher_cb_t)receive_events, this);
}
+ netlink_find_offload_feature(lib->settings->get_str(lib->settings,
+ "%s.plugins.kernel-netlink.hw_offload_feature_interface",
+ "lo", lib->ns));
+
return &this->public;
}