基于路由和VTI虚拟接口的IPSec实现

以下根据strongswan代码中的testing/tests/route-based/rw-shared-vti/中的测试环境,来看一下基于路由和VTI接口的安全连接。拓扑结构如下:

在这里插入图片描述

拓扑图中使用到的设备包括:虚拟主机carol和dave,以及虚拟网关moon。

虚拟主机配置

carol的配置文件:/etc/swanctl/swanctl.conf,内容如下。连接home中的字段vips设置为0.0.0.0,标识carol并不请求特定的虚拟IP地址。

connections {

   home {
      local_addrs  = PH_IP_CAROL
      remote_addrs = PH_IP_MOON
      vips = 0.0.0.0

      local {
         auth = pubkey
         certs = carolCert.pem
         id = carol@strongswan.org
      }
      remote {
         auth = pubkey
         id = moon.strongswan.org
      }
      children {
         home {
            remote_ts = 10.1.0.0/16

            updown = /usr/local/libexec/ipsec/_updown iptables
            esp_proposals = aes128gcm128-x25519
         }
      }
      version = 2
      proposals = aes128-sha256-x25519
   }
}

dave主机配置的配置与以上carol主机类似。

网关配置

moon网关的配置文件:/etc/swanctl/swanctl.conf,内容如下。其中为远程用户定义了虚拟地址池rw_pool,地址为10.3.0.0/28网段。字段mark_in和mark_out等于42,其中mark_in指定在inbound方向的policies/SA所使用的Netfilter标记mark值;而mark_out指定在outbound方向上policies/SA设置的Netfilter标记mark值。此处未指定掩码值,即使用默认的0xffffffff。

connections {

   rw {
      local_addrs  = PH_IP_MOON
      pools = rw_pool

      local {
         auth = pubkey
         certs = moonCert.pem
         id = moon.strongswan.org
      }
      remote {
         auth = pubkey
      }
      children {
         net {
            local_ts  = 10.1.0.0/16
            mark_in = 42
            mark_out = 42

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

pools {
    rw_pool {
        addrs = 10.3.0.0/28
    }
}

moon网关的配置文件:/etc/strongswan.conf,内容如下。其中install_routes等于0,表明不为IPSec连接创建路由。

# /etc/strongswan.conf - strongSwan configuration file

swanctl {
  load = pem pkcs1 x509 revocation constraints pubkey openssl random
}

charon-systemd {
  load = random nonce aes sha1 sha2 pem pkcs1 curve25519 gmp x509 curl revocation hmac vici kernel-netlink socket-default updown
}

charon {
  install_routes = 0
}

连接准备

操作流程如下,在两个虚拟主机carol和dave上,以及网关moon上启动strongswan进程。再者,在carol和dave上创建名称为home的子连接。前提是在moon网关上创建名称为vti0的vti虚拟接口,其key指定为42,意味着input和output方向的key值(ikey和okey)都指定为42。

moon::ip tunnel add vti0 local PH_IP_MOON remote 0.0.0.0 mode vti key 42
moon::sysctl -w net.ipv4.conf.vti0.disable_policy=1
moon::ip link set vti0 up
moon::ip route add 10.3.0.0/28 dev vti0
moon::iptables -A FORWARD -i vti0 -j ACCEPT
moon::iptables -A FORWARD -o vti0 -j ACCEPT

以下在moon网关上使用ip tunnel命令查看创建的vti0接口,由于在IPSec连接建立之后,发送了两个ping报文,产生了统计信息:

moon:~# ip -s -d tunnel list 
 
vti0: ip/ip remote any local 192.168.0.1 ttl inherit key 42

RX: Packets    Bytes        Errors CsumErrs OutOfSeq Mcasts
    2          168          0      0        0        0       
TX: Packets    Bytes        Errors DeadLoop NoRoute  NoBufs
    2          168          0      0        0        0     
moon:~# 

内核函数vti_newlink负责创建vti虚拟设备,由于vti属于内核IP隧道的一种,最终使用核心函数ip_tunnel_newlink完成创建。内核使用ip_tunnel结构来表示一个vti设备,其成员parms(ip_tunnel_parm结构)用于保存配置参数,包括以上命令行配置的本地/远端地址,key值等。最后函数ip_tunnel_add将新建隧道添加到网络命名空间中的链表中。

int ip_tunnel_newlink(struct net_device *dev, struct nlattr *tb[], struct ip_tunnel_parm *p, __u32 fwmark)
{  
    struct ip_tunnel *nt;
    struct net *net = dev_net(dev);
    struct ip_tunnel_net *itn;

    nt = netdev_priv(dev);
    itn = net_generic(net, nt->ip_tnl_net_id);

    nt->net = net; 
    nt->parms = *p;
    nt->fwmark = fwmark;
    err = register_netdevice(dev);

    ip_tunnel_add(itn, nt);

使用ip route命令添加目的网段10.3.0.0/28的报文经由vti0虚拟接口。

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.3.0.0/28 dev vti0 scope link 
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.1 
moon:~# 

vti0接口的disable_policy选项的使能,将导致接口相关的路由缓存设置DST_NOPOLICY标志(dst_entry->flags)。在策略检查函数xfrm_policy_check的子函数__xfrm_policy_check2中,可见对于转发的报文(sk为空),如果报文的路由缓存项设置了DST_NOPOLICY标志,将跳过策略检查(不执行函数__xfrm_policy_check)。

static inline int __xfrm_policy_check2(struct sock *sk, int dir, struct sk_buff *skb, unsigned int family, int reverse)
{
    struct net *net = dev_net(skb->dev);
    int ndir = dir | (reverse ? XFRM_POLICY_MASK + 1 : 0);

    if (sk && sk->sk_policy[XFRM_POLICY_IN])
        return __xfrm_policy_check(sk, ndir, skb, family);

    return  (!net->xfrm.policy_count[dir] && !secpath_exists(skb)) ||
        (skb_dst(skb)->flags & DST_NOPOLICY) || __xfrm_policy_check(sk, ndir, skb, family);
}

static inline int xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb, unsigned short family)
{
    return __xfrm_policy_check2(sk, dir, skb, family, 0);
}

iptables命令在filter表的FORWARD链中添加了如下的两条规则,允许进出vti0虚拟接口的所有流量。

moon:~# iptables -L -v

Chain FORWARD (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    2   168 ACCEPT     all  --  vti0   any     anywhere             anywhere            
    2   168 ACCEPT     all  --  any    vti0    anywhere             anywhere            

moon:~# 

数据流程

在IPsec连接建立之后,如果在carol主机上ping主机alice,报文到达moon网关之后,由协议栈的函数xfrm4_rcv_encap或者xfrm4_esp_rcv函数处理,前者处理ESP封装在UDP内部的报文;后者处理ESP协议报文。此环境下没有经过NAT设备,所以使用后者进行处理。其中变量全局链表esp4_handlers链表上注册的xfrm协议,进行处理。

static int xfrm4_esp_rcv(struct sk_buff *skb)
{
    struct xfrm4_protocol *handler;

    XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;

    for_each_protocol_rcu(esp4_handlers, handler)
        if ((ret = handler->handler(skb)) != -EINVAL)
            return ret;

对于VTI虚拟设备,内核注册了vti_esp4_protocol协议结构,其中的handler函数指针设置为:vti_rcv,由其进行接收处理。

static struct xfrm4_protocol vti_esp4_protocol __read_mostly = {
    .handler    =   vti_rcv,
    .input_handler  =   vti_input,
    .cb_handler =   vti_rcv_cb,
    .err_handler    =   vti4_err,
    .priority   =   100,
};

函数vti_rcv实际上调用函数vti_input进行处理。

static int vti_rcv(struct sk_buff *skb)
{  
    XFRM_SPI_SKB_CB(skb)->family = AF_INET;  
    XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct iphdr, daddr);
   
    return vti_input(skb, ip_hdr(skb)->protocol, 0, 0);
} 

函数vti_input如下。子函数ip_tunnel_lookup在网络命名空间中查找匹配的隧道,注意其参数,报文源地址iph->saddr匹配隧道的远端地址;而报文目的地址iph->daddr匹配隧道的本地地址,在以上vti0隧道接口的创建中,指定了本地地址为moon网关的eth0接口地址:192.168.0.1,而隧道远端地址指定为:0.0.0.0,任意匹配。此处可匹配vti0隧道。

对于xfrm4_policy_check检查函数,如上所述,由于指定了DST_NOPOLICY标志,将跳过检查。接下来,为skb关联隧道结构,调用通用的输入处理函数xfrm_input。

static int vti_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
    struct ip_tunnel *tunnel;
    const struct iphdr *iph = ip_hdr(skb);
    struct net *net = dev_net(skb->dev);
    struct ip_tunnel_net *itn = net_generic(net, vti_net_id);

    tunnel = ip_tunnel_lookup(itn, skb->dev->ifindex, TUNNEL_NO_KEY,
                  iph->saddr, iph->daddr, 0);
    if (tunnel) {
        if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
            goto drop;

        XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = tunnel;

        return xfrm_input(skb, nexthdr, spi, encap_type);

在xfrm_input函数中,取出skb的回调结构中保存的隧道的输入key值(i_key),此处为42,使用此值作为参数之一在xfrm状态表中查找匹配的xfrm_state,并将匹配状态结构中的mark值赋予skb->mark。

int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
{
    switch (family) {
    case AF_INET:
        if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4)
            mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4->parms.i_key);
        break;
    case AF_INET6:
        if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6)
            mark = be32_to_cpu(XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6->parms.i_key);
        break;
    }

    do {
        x = xfrm_state_lookup(net, mark, daddr, spi, nexthdr, family); 
        skb->mark = xfrm_smark_get(skb->mark, x);
		
        if (xfrm_tunnel_check(skb, x, family)) {
            XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
            goto drop;
        }
    ....
    } while (!err);

    err = xfrm_rcv_cb(skb, family, x->type->proto, 0);

由以下的函数xfrm_tunnel_check可知,VTI只能与XFRM的隧道模式一同使用。

static inline int xfrm_tunnel_check(struct sk_buff *skb, struct xfrm_state *x, unsigned int family)
{          
    bool tunnel = false;
       
    switch(family) {
    case AF_INET:
        if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4)
            tunnel = true;
        break;
    case AF_INET6:
        if (XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6)
            tunnel = true;
        break;
    }      
    if (tunnel && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL))
        return -EINVAL;
    return 0; 
} 

接下来,看一下alice主机的ping回复报文,其源地址为其eth0接口的IP地址:10.1.0.10,目的地址为carol获取到的虚拟地址:10.3.0.1。在到达moon网关时,通过路由查找,发现路由目的接口为:vti0,执行其发送函数:vti_tunnel_xmit。

如下,函数xfrm_decode_session用于获取报文中的流信息,填充到flowi结构中。之后,将VTI隧道的输出key值(o_key)赋值给流结构中的标记成员flowi_mark。调用vti_xmit函数。

static netdev_tx_t vti_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);
    struct flowi fl;

    switch (skb->protocol) {
    case htons(ETH_P_IP):
        xfrm_decode_session(skb, &fl, AF_INET);
        memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
        break;
    case htons(ETH_P_IPV6):
        xfrm_decode_session(skb, &fl, AF_INET6);
        memset(IP6CB(skb), 0, sizeof(*IP6CB(skb)));
        break;
    default:
        goto tx_err;
    }

    /* override mark with tunnel output key */
    fl.flowi_mark = be32_to_cpu(tunnel->parms.o_key);

    return vti_xmit(skb, dev, &fl);

函数vti_xmit中的xfrm_lookup函数根据流结构fl中的信息查找匹配的安全策略和SA,并关联到路由缓存项中。其中在策略匹配时使用的函数xfrm_policy_match,将检查策略中定义的mark是否与以上在flowi_mark指定的相同(考虑掩码)。

vti_state_check函数确认路由缓存中绑定的安全关联SA中的地址信息是否与隧道中配置的地址信息相符。如果路由缓存中的设备等于隧道设备,说明发生了环路。最后,更新报文skb中的路由缓存,以及更新其中的出口设备,由dst_ouput发送数据包。对于IPv4而言,XFRM路由缓存的出口函数为:xfrm4_output,其负责报文的发送。

static netdev_tx_t vti_xmit(struct sk_buff *skb, struct net_device *dev, struct flowi *fl)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);
    struct ip_tunnel_parm *parms = &tunnel->parms;
    struct dst_entry *dst = skb_dst(skb);

    dst = xfrm_lookup(tunnel->net, dst, fl, NULL, 0);

    if (!vti_state_check(dst->xfrm, parms->iph.daddr, parms->iph.saddr)) {
        goto tx_error_icmp;
    }
    tdev = dst->dev;
    if (tdev == dev) {
        dev->stats.collisions++;
        goto tx_error;
    }
    skb_scrub_packet(skb, !net_eq(tunnel->net, dev_net(dev)));
    skb_dst_set(skb, dst);
    skb->dev = skb_dst(skb)->dev;

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

以下为策略匹配函数xfrm_poliicy_match的实现,可见对mark值的判断。

static int xfrm_policy_match(const struct xfrm_policy *pol, const struct flowi *fl, u8 type, u16 family, int dir, u32 if_id)
{
    const struct xfrm_selector *sel = &pol->selector;
    int ret = -ESRCH;
    bool match;

    if (pol->family != family ||
        pol->if_id != if_id ||
        (fl->flowi_mark & pol->mark.m) != pol->mark.v ||
        pol->type != type)
        return ret;

    match = xfrm_selector_match(sel, fl, family);

最后在moon网关上使用swanctl命令,可看到carol和dave的SA子连接信息在in/out两个方向上设置的mark值,mark-in=0x0000002a(42)和。mark-out=0000002a

moon:~# swanctl --list-sas --raw
list-sa event {rw {uniqueid=2 version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=4500 local-id=moon.strongswan.org remote-host=192.168.0.200 remote-port=4500 remote-id=dave@strongswan.org initiator-spi=9b04b389c96d16f5 responder-spi=83ea374a4c173a36 encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519 established=20 rekey-time=13493 remote-vips=[10.3.0.2] child-sas {net-2 {name=net uniqueid=2 reqid=2 state=INSTALLED mode=TUNNEL protocol=ESP spi-in=c5b7c196 spi-out=c47cc189 
mark-in=0000002a mark-out=0000002a encr-alg=AES_GCM_16 encr-keysize=128 bytes-in=84 packets-in=1 use-in=20 bytes-out=84 packets-out=1 use-out=20 rekey-time=3263 life-time=3940 install-time=20 local-ts=[10.1.0.0/16] remote-ts=[10.3.0.2/32]}}}}
list-sa event {rw {uniqueid=1 version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=4500 local-id=moon.strongswan.org remote-host=192.168.0.100 remote-port=4500 remote-id=carol@strongswan.org initiator-spi=1a69f43457a4e7a3 responder-spi=c996b861d29075e3 encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519 established=20 rekey-time=13105 remote-vips=[10.3.0.1] child-sas {net-1 {name=net uniqueid=1 reqid=1 state=INSTALLED mode=TUNNEL protocol=ESP spi-in=c55ebfd9 spi-out=c9c1ee2b 
mark-in=0000002a mark-out=0000002a encr-alg=AES_GCM_16 encr-keysize=128 bytes-in=84 packets-in=1 use-in=20 bytes-out=84 packets-out=1 use-out=20 rekey-time=3230 life-time=3940 install-time=20 local-ts=[10.1.0.0/16] remote-ts=[10.3.0.1/32]}}}}
list-sas reply {}
No leaks detected, 1442 suppressed by whitelist
moon:~# 

以及两个方向的安全策略中的mark/掩码值: mark 0x2a/0xffffffff。

moon:~# ip -s xfrm policy
src 10.1.0.0/16 dst 10.3.0.2/32 uid 0
        dir out action allow index 857 priority 375423 ptype main share any flag  (0x00000000)
        mark 0x2a/0xffffffff
        tmpl src 192.168.0.1 dst 192.168.0.200
                proto esp spi 0xc5945c6f(3314834543) reqid 2(0x00000002) mode tunnel
                level required share any 
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
src 10.3.0.2/32 dst 10.1.0.0/16 uid 0
        dir in action allow index 840 priority 375423 ptype main share any flag  (0x00000000)
        mark 0x2a/0xffffffff
        tmpl src 192.168.0.200 dst 192.168.0.1
                proto esp spi 0x00000000(0) reqid 2(0x00000002) mode tunnel
                level required share any 
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
src 10.1.0.0/16 dst 10.3.0.1/32 uid 0
        dir out action allow index 833 priority 375423 ptype main share any flag  (0x00000000)
        mark 0x2a/0xffffffff
        tmpl src 192.168.0.1 dst 192.168.0.100
                proto esp spi 0xcb34a737(3409225527) reqid 1(0x00000001) mode tunnel
                level required share any 
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
src 10.3.0.1/32 dst 10.1.0.0/16 uid 0
        dir in action allow index 816 priority 375423 ptype main share any flag  (0x00000000)
        mark 0x2a/0xffffffff
        tmpl src 192.168.0.100 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

strongswan测试版本: 5.8.1

END

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