网络命名空间之间移动XFRM虚拟设备

以下根据strongswan代码中的testing/tests/route-based/net2net-xfrmi-netns/中的测试环境,来看一下基于路由和XFRM接口实现的安全连接,以及在两个网络命名空间之间移动XFRM虚拟接口的情况。内核允许XFRM接口的移动,可使得一个命名空间中的strongswan进程为另外一个命名空间中的应用提供安全加密服务,而后者并不需要进行IKE相关协商。拓扑结构如下:

在这里插入图片描述

拓扑图中使用到的设备包括:虚拟网关moon和sun。

sun网关配置

sun的配置文件:/etc/swanctl/swanctl.conf,内容如下。注意其中的net-net子连接配置,if_id_in和if_id_out,都指定了特殊值%unique。

connections {

   gw-gw {
      local_addrs  = PH_IP_SUN
      remote_addrs = PH_IP_MOON

      local {
         auth = pubkey
         certs = sunCert.pem
         id = sun.strongswan.org
      }
      remote {
         auth = pubkey
         id = moon.strongswan.org
      }
      children {
         net-net {
            local_ts  = 0.0.0.0/0
            remote_ts = 0.0.0.0/0

            if_id_out = %unique
            if_id_in = %unique

            updown = /etc/updown

            esp_proposals = aes128gcm128-x25519
         }
      }
      version = 2
      proposals = aes128-sha256-x25519
   }
}

sun的脚本文件:/etc/updown,内容如下。在子连接建立/删除时,执行此脚本。由于在以上swanctl.conf文件中为if_id_in/if_id_out指定了特殊值%unique,在执行此脚本时,将创建xfrmi虚拟接口,添加路由和iptables规则的操作。

在销毁时,删除xfrmi接口,删除添加的iptables规则。此脚本由strongswan的updown插件执行。

#!/bin/bash

IF_NAME="xfrmi-${PLUTO_IF_ID_IN}"

case "${PLUTO_VERB}" in
    up-client)
        /usr/local/libexec/ipsec/xfrmi -n "${IF_NAME}" -i "${PLUTO_IF_ID_IN}" -d eth0
        ip link set "${IF_NAME}" up
        ip route add 10.1.0.0/16 dev "${IF_NAME}"
        iptables -A FORWARD -i "${IF_NAME}" -j ACCEPT
        iptables -A FORWARD -o "${IF_NAME}" -j ACCEPT
        ;;
    down-client)
        iptables -D FORWARD -i "${IF_NAME}" -j ACCEPT
        iptables -D FORWARD -o "${IF_NAME}" -j ACCEPT
        ip link del "${IF_NAME}"
        ;;
esac

xfrmi虚拟接口的名称为:IF_NAME,其由二部分组成:xfrm-前缀,接口ID值。此处由于指定了%unique特殊值,意味着两个方向将使用相同的ID值和xfrm虚拟接口。这点与特殊值%unique-dir不同,后者意味着创建两个ID值不相同的XFRM接口。在文件strongswan-5.8.1/src/libcharon/sa/child_sa.c文件中,函数child_sa_create创建安全关联,其中函数allocate_unique_if_ids将根据配置为XFRM接口分配ID值。

child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config, child_sa_create_t *data)
{
        allocate_unique_if_ids(&this->if_id_in, &this->if_id_out);

对于设置了IF_ID_UNIQUE标志(即%unique)的的ID值,其使用一个静态变量unique_if_id来保存和分配id值,以下代码中,由于未设置%unique-dir选项,布尔变量unique_dir为假,此时将得到一个if_id值,此值将同时用于输入和输出两个方向。ref_get函数在每次调用之后,将静态变量unique_if_id值递增一。

void allocate_unique_if_ids(uint32_t *in, uint32_t *out)
{       
        static refcount_t unique_if_id = 0;
        
        if (IF_ID_IS_UNIQUE(*in) || IF_ID_IS_UNIQUE(*out))
        {       
                refcount_t if_id = 0; 
                bool unique_dir = *in == IF_ID_UNIQUE_DIR || *out == IF_ID_UNIQUE_DIR;
                
                if (!unique_dir)
                {       
                        if_id = ref_get(&unique_if_id);
                }
                if (IF_ID_IS_UNIQUE(*in))
                {       
                        *in = unique_dir ? ref_get(&unique_if_id) : if_id;
                }
                if (IF_ID_IS_UNIQUE(*out))
                {       
                        *out = unique_dir ? ref_get(&unique_if_id) : if_id;
                }

在文件:strongswan-5.8.1/src/libcharon/plugins/updown/updown_listener.c文件中,函数child_updown在子SA创建和删除时调用,其子函数invoke_once如下,在调用/etc/updown脚本之前,设置环境变量PLUTO_IF_ID_IN的值为以上分配的ID值,以便脚本中使用。sun网关的updown脚本根据PLUTO_IF_ID_IN环境变量创建XFRM虚拟接口,而PLUTO_IF_ID_OUT变量的值没有使用。

static void invoke_once(private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, ...)
{

        if_id = child_sa->get_if_id(child_sa, TRUE);
        if (if_id)
        {
                push_env(envp, countof(envp), "PLUTO_IF_ID_IN=%u", if_id);
        }

moon网关配置

moon网关的配置文件:/etc/swanctl/swanctl.conf,内容如下。注意其中的net-net子连接配置,if_id_out和if_id_in指定了相同的值42。与以上sun网关的特殊值(%unique)配置不同,故moon网关也无需特殊的updown脚本文件,以下将为其手动创建XFRM虚拟接口。

connections {

   gw-gw {
      local_addrs  = PH_IP_MOON
      remote_addrs = PH_IP_SUN

      local {
         auth = pubkey
         certs = moonCert.pem
         id = moon.strongswan.org
      }
      remote {
         auth = pubkey
         id = sun.strongswan.org
      }
      children {
         net-net {
            local_ts  = 0.0.0.0/0
            remote_ts = 0.0.0.0/0

            if_id_out = 42
            if_id_in = 42

            esp_proposals = aes128gcm128-x25519
         }
      }
      version = 2
      proposals = aes128-sha256-x25519
   }
}

连接建立流程

操作流程如下,由于在moon网关上明确配置了if_id_in和if_id_out值,此处创建相应ID值的xfrm虚拟接口,这里由于ID值相同仅创建一个XFRM接口,见如下的xfrmi命令。对于sun主机由于指定的为特殊值(%unique),交由strongswan进程调用updown脚本去创建xfrm接口。

随后,在moon网关上配置到sun网关背后网络的路由,经由刚创建的xfrm-moon设备。

moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon -i 42 -d eth0
moon::ip link set xfrm-moon up
moon::ip route add 10.2.0.0/16 dev xfrm-moon
moon::iptables -A FORWARD -i xfrm-moon -j ACCEPT
moon::iptables -A FORWARD -o xfrm-moon -j ACCEPT

最后在两个虚拟网关sun和moon上启动strongswan进程,以及在moon网关上启动名称为net-net的子连接。

moon::systemctl start strongswan
sun::systemctl start strongswan
moon::expect-connection gw-gw
sun::expect-connection gw-gw
moon::swanctl --initiate --child net-net

以上的xfrmi命令将在moon网关上创建虚拟的XFRM接口:xfrm-moon,指定ID值(xfrm_id)为42,其底层物理接口为eth0。可使用xfrmi -list命令进行查看。

moon:~# /usr/local/libexec/ipsec/xfrmi -list
13: xfrm-moon        dev eth0     if_id 0x0000002a [42]
No leaks detected, 2 suppressed by whitelist
moon:~# 

使用ip route命令查看为xfrm-moon虚拟接口添加的路由信息,目的网段10.2.0.0/16(bob主机所在网段)的流量路由到xfrm-moon接口。

moon:~# ip route
default via 192.168.0.254 dev eth0 onlink 
10.1.0.0/16 dev eth1 proto kernel scope link src 10.1.0.1 
10.2.0.0/16 dev xfrm-moon scope link 
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.1 

以下配置的iptables规则,允许xfrm-moon虚拟接口的所有入和出方向的流量。

moon:~# iptables -L -n -v

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    1    84 ACCEPT     all  --  *      xfrm-moon-out  0.0.0.0/0            0.0.0.0/0           
    1    84 ACCEPT     all  --  xfrm-moon-in *       0.0.0.0/0            0.0.0.0/0 

以下为moon网关发起net-net子连接时,sun网关上strongswan的与xfrm有关的日志信息。创建了XFRM虚拟接口xfrmi-1。

sun charon-systemd: 14[IKE] CHILD_SA net-net{1} established with SPIs c1a9266c_i cdb65851_o and TS 0.0.0.0/0 === 0.0.0.0/0
sun charon-systemd: 14[CHD] updown: No leaks detected, 1 suppressed by whitelist
sun charon-systemd: 16[KNL] interface xfrmi-1 activated
sun charon-systemd: 09[KNL] fe80::bd51:119c:aa23:e69c appeared on xfrmi-1
sun charon-systemd: 14[ENC] generating IKE_AUTH response 1 [ IDr CERT AUTH SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(ADD_6_ADDR) N(ADD_6_ADDR) ]

对于sun网关,由于其if_id_in/out指定为值%unique,其xfrmi虚拟接口,相关路由和iptables规则,都是由以上的updown脚本所生成。

sun:~# /usr/local/libexec/ipsec/xfrmi -list
13: xfrmi-1          dev eth0     if_id 0x00000001 [1]
No leaks detected, 2 suppressed by whitelist
sun:~# 

使用ip route命令查看为xfrmi-1虚拟接口添加的路由信息,目的网段10.1.0.0/16(alice主机所在网段)的流量路由到xfrmi-1接口。

sun:~# ip route
default via 192.168.0.254 dev eth0 onlink 
10.1.0.0/16 dev xfrmi-1 scope link 
10.2.0.0/16 dev eth1 proto kernel scope link src 10.2.0.1 
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.2 

以下配置的iptables规则,允许xfrmi-1虚拟接口的所有入和出方向的流量。

sun:~# iptables -L -n -v

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    3   252 ACCEPT     all  --  xfrmi-1 *       0.0.0.0/0            0.0.0.0/0           
    3   252 ACCEPT     all  --  *      xfrmi-1  0.0.0.0/0            0.0.0.0/0 

在moon和sun网关将来连接之后,在alice上ping主机bob,两者是相通的。以下我们将在moon网关上创建名称为xfrmi-test的网络命名空间,之后,将XFRM虚拟接口移动到此命名空间中,由于内核中在不同命名空间中移动网络设备,相当于在前一个命名空间删除设备,在后一个命名空间中新建一个同名设备,因此设备相关配置将丢失,在此重新为xfrm-moon虚拟设备配置IP地址,路由等信息。但是网络设备net_device的私有数据结构没有改变,仍然为xfrm_if结构类型的值,即在新命名空间中的xfrm-moon设备的底层设备依然是旧命名空间中的eth0,并且xfrm_if结构中依然保留指向原网络命名空间的指针。

注意在新建的网络命名空间xfrm-test中没有安全关联信息。但是在此命名空间中,执行ping主机bob的操作,是相通的。由于移走了xfrm-moon接口,从alice到bob不相通了。可见,实际上,在xfrm-test网络命名空间中,仍可访问在默认命名空间中建立的安全连接和策略等信息。

alice::ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES
moon::ip netns add xfrmi-test::.*::NO
moon::ip link set xfrm-moon netns xfrmi-test::.*::NO
moon::ip netns exec xfrmi-test ip addr add 10.1.0.42/32 dev xfrm-moon::.*::NO
moon::ip netns exec xfrmi-test ip link set dev xfrm-moon up::.*::NO
moon::ip netns exec xfrmi-test ip route add 10.2.0.0/16 dev xfrm-moon src 10.1.0.42::.*::NO
moon::ip netns exec xfrmi-test ip xfrm state::.*::NO
moon::ip netns exec xfrmi-test ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES
alice::ping -c 1 -W 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::NO

以下我们看一下在新命名空间xfrm-test中的xfrm-moon设备,我们看到其底层设备显示的为if3,其实这就是eth0设备。if3时iproute工具给出的命名,后缀3为eth0的接口索引。

moon:~# ip netns exec xfrmi-test ip link

13: xfrm-moon@if3: <NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/none 52:54:00:c7:b8:b0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
moon:~# 
moon:~# ip link

3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:c7:b8:b0 brd ff:ff:ff:ff:ff:ff

具体可参见iproute2-4.19.0源码中的print_name_and_link函数,对于带有IFLA_LINK_NETNSID属性的情况,调用函数ll_idx_n2a获取link名称如下,由于eth0接口索引为3,这里将得到if3的link名称。

unsigned int print_name_and_link(const char *fmt, const char *name, struct rtattr *tb[])
{  
    if (tb[IFLA_LINK]) {
        int iflink = rta_getattr_u32(tb[IFLA_LINK]);
        if (iflink) {
            if (tb[IFLA_LINK_NETNSID]) {
                if (is_json_context()) {
                    print_int(PRINT_JSON, "link_index", NULL, iflink);
                } else {
                    link = ll_idx_n2a(iflink);

const char *ll_idx_n2a(unsigned int idx)
{                
    static char buf[IFNAMSIZ];

    snprintf(buf, sizeof(buf), "if%u", idx); 

内核中XFRM虚拟接口与以上ip link命令显示相关的有两个地方。第一个是xfrm接口的IFLA_LINK属性值,其由函数xfrmi_get_iflink实现。以下代码可知,其返回的是底层设备(此处为eth0)的接口索引值。

static const struct net_device_ops xfrmi_netdev_ops = {
    .ndo_get_iflink = xfrmi_get_iflink,
	
static int xfrmi_get_iflink(const struct net_device *dev)
{
    struct xfrm_if *xi = netdev_priv(dev);

    return xi->phydev->ifindex;

第二个地方是,获取IFLA_LINK_NETNSID属性值的函数xfrmi_get_link_net。由代码可见,其获取的是底层设备(此处为eth0)所在的网络命名空间。

static struct rtnl_link_ops xfrmi_link_ops __read_mostly = {
    .kind       = "xfrm",
    .get_link_net   = xfrmi_get_link_net,
	
static struct net *xfrmi_get_link_net(const struct net_device *dev)
{
    struct xfrm_if *xi = netdev_priv(dev);

    return dev_net(xi->phydev);

由以上可见,改变网络命名空间,只是更改了基础的net_device结构相关属性,并没有更改xfrm虚拟接口的属性,故其仍然像在原有命名空间中一样在新命名空间中工作。

如果在moon网关的xfrm-test命名空间上执行ping网关sun后面的主机bob的操作,报文的源地址为xfrm-moon接口地址10.1.0.42,目的地址为bob的eth0接口地址10.2.0.10。根据moon网关上的路由配置,将其由接口xfrm-moon发出。执行其发送函数:xfrmi_xmit,将流结构的成员flowi_oif出接口,替换为XFRM虚拟接口所依据的底层物理接口的索引,此处为以上的eth0接口的索引。

static netdev_tx_t xfrmi_xmit(struct sk_buff *skb, struct net_device *dev)
{  
    struct xfrm_if *xi = netdev_priv(dev);   
    struct flowi fl;                

    fl.flowi_oif = xi->phydev->ifindex;

    ret = xfrmi_xmit2(skb, dev, &fl);

第二阶段的发送函数xfrmi_xmit2如下,注意其中的xfrm_lookup_with_ifid函数,其第一个参数xi->net所指向的为原网络命名空间,而不是xfrm-test。

static int xfrmi_xmit2(struct sk_buff *skb, struct net_device *dev, struct flowi *fl)
{
    struct xfrm_if *xi = netdev_priv(dev);
    struct dst_entry *dst = skb_dst(skb);
    struct net_device *tdev;
    struct xfrm_state *x;

    dst_hold(dst);
    dst = xfrm_lookup_with_ifid(xi->net, dst, fl, NULL, 0, xi->p.if_id);

    x = dst->xfrm;

    if (x->if_id != xi->p.if_id)
        goto tx_err_link_failure;
    ...

    err = dst_output(xi->net, skb->sk, skb);

安全策略和关联

xfrm-test网络命名空间中,并没有相关的安全策略和关联信息,这些信息仍在原有网络命名空间中。如下为默认命名空间中的安全策略,xfrmi-test命名空间中的安全策略为空。

moon:~# ip -s xfrm policy                             
src 0.0.0.0/0 dst 0.0.0.0/0 uid 0
        dir out action allow index 193 priority 399999 ptype main share any flag  (0x00000000)
        tmpl src 192.168.0.1 dst 192.168.0.2
                proto esp spi 0xc63c3382(3325834114) reqid 1(0x00000001) mode tunnel
                level required share any 
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
src 0.0.0.0/0 dst 0.0.0.0/0 uid 0
        dir in action allow index 176 priority 399999 ptype main share any flag  (0x00000000)
        tmpl src 192.168.0.2 dst 192.168.0.1
                proto esp spi 0x00000000(0) reqid 1(0x00000001) mode tunnel
                level required share any 
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
moon:~#
moon:~# ip netns exec xfrmi-test ip -s xfrm policy
moon:~# 

strongswan版本: 5.8.1
内核版本: 5.0

END

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页