GRE协议与传输模式下IPSec隧道

以下根据strongswan代码中的testing/tests/route-based/net2net-gre/中的测试环境,来看一下GRE报文通过IPSec隧道的情况。拓扑结构如下:

在这里插入图片描述

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

sun网关配置

sun的配置文件:/etc/swanctl/swanctl.conf,内容如下。注意其中的gre子连接配置,local_ts和remote_ts,都指定了特殊值dynamic[gre],其由两部分组成,dynamic关键字表明此地址运行时将被隧道或者虚拟IP的地址所替换,方括号中的gre指明使用的协议。

connections {

   gw-gw {
      local_addrs  = PH_IP_SUN
      remote_addrs = PH_IP_MOON

      children {
         gre {
            local_ts  = dynamic[gre]
            remote_ts = dynamic[gre]
            mode = transport

            updown = /usr/local/libexec/ipsec/_updown iptables
            esp_proposals = aes128gcm128-x25519
         }

文件strongswan-5.8.1/src/libcharon/plugins/vici/vici_config.c负责处理vici接口配置,其中函数parse_ts处理以上提到的流选择器的解析。通过解析左右两个方括号,得到协议和端口字符串protoport,这里由于没有使用斜杆指定端口号,仅有协议字符串,即gre。随后使用getprotobyname获取对应的协议号,getprotobyname函数实际上有系统文件/etc/protocols中获取协议值,对于gre,其协议值为47。

另外,说明一点,也可以直接在方括号内指定协议数值,协议值应大于等于0,并且小于256,参见如下判断代码。函数最后,由子函数traffic_selector_create_dynamic创建动态的流选择器,在这里,第一个参数proto,为以上获得的gre协议值47,由于未指定端口值,这里的from和to分别为0和65535。

CALLBACK(parse_ts, bool, linked_list_t *out, chunk_t v) 
{    
    protoport = strchr(buf, '[');                       
    if (protoport) {
            *(protoport++) = '\0';
            sep = strrchr(protoport, ']');
            if (!sep) { return FALSE; }
            *sep = '\0';

            if (streq(protoport, "any")) { }
            else
            {
                    protoent = getprotobyname(protoport);
                    if (protoent) {
                            proto = protoent->p_proto;
                    } else {
                            p = strtol(protoport, &end, 0);
                            if ((*protoport && *end) || p < 0 || p > 0xff) {
                                    return FALSE;
                            }
                            proto = (uint8_t)p;
							
    if (streq(buf, "dynamic"))
    {       
            ts = traffic_selector_create_dynamic(proto, from, to);
    }

函数traffic_selector_create_dynamic位于文件:strongswan-5.8.1/src/libstrongswan/selectors/traffic_selector.c。其中在子函数traffic_selector_create中将流量选择器结构的protocol协议设置为47(gre),将端口范围设置为0到65535,之后退出子函数。将流量选择器的地址范围设置为0到全F,并且置位dynamic成员变量。流量选择器的类型为:TS_IPV4_ADDR_RANGE,这些信息将在稍后的IKE_AUTH报文的Initiator请求中看到。

但是需要注意,strongswan进程将在连接建立过程中,更新流量选择器结构中的地址范围信息。

traffic_selector_t *traffic_selector_create_dynamic(uint8_t protocol, uint16_t from_port, uint16_t to_port)
{        
        private_traffic_selector_t *this = traffic_selector_create(
                                                 protocol, TS_IPV4_ADDR_RANGE, from_port, to_port);

        memset(this->from, 0, sizeof(this->from));
        memset(this->to, 0xFF, sizeof(this->to));
        this->netbits = 0;
        this->dynamic = TRUE; 
        return &this->public; 
} 
static private_traffic_selector_t *traffic_selector_create(uint8_t protocol, ts_type_t type, uint16_t from_port, uint16_t to_port)
{
        private_traffic_selector_t *this;
        INIT(this,
                .public = { ... },
                .from_port = from_port,
                .to_port = to_port,
                .protocol = protocol,
                .type = type,
        );

sun网关的/etc/strongswan.conf文件,如下所示,使用start-scripts开头的段,updown脚本指定为python文件:/etc/updown.py。


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
}

由于在swanctl.conf文件中,XFRM接口ID指定为特殊值%unique-dir,将使用脚本文件:/etc/updown.py,创建两个xfrm虚拟接口(每个方向单独一个接口),和添加路由信息。

moon网关配置

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

这里为主机alice和venus分别配置了不同的子连接alice-net和venus-net,二者的区别在于流量选择器的本地地址不同,alice的local_ts值为其eth0接口的地址10.1.0.10/32;而venus的local_ts值为其eth0接口的地址10.1.0.20。对比以上的sun网关的配置,其流量选择器的远端地址指定的为网段10.1.0.0/16,包括了alice和venus主机。

connections {

   gw-gw {
      local_addrs  = PH_IP_MOON
      remote_addrs = PH_IP_SUN

      children {
         gre {
            local_ts  = dynamic[gre]
            remote_ts = dynamic[gre]
            mode = transport

            updown = /usr/local/libexec/ipsec/_updown iptables
            esp_proposals = aes128gcm128-x25519
         }
      }

连接建立流程

操作流程如下,在moon和sun网关上分别创建GRE隧道设备:gre-moon和gre-sun,对于gre-moon设备,其本地隧道地质为moon网关eth0接口的地址:192.168.0.1,远端隧道地址为sun主机的eth0接口地址:192.168.0.2。对于后者gre-sun设备而言,其本地和远端隧道地址与gre-moon这好相反。

两个GRE隧道设备使用相同的key值42,意味值ikey和okey的值都指定为42。

moon::ip tunnel add gre-moon local PH_IP_MOON remote PH_IP_SUN mode gre key 42
moon::ip link set gre-moon up
moon::ip route add 10.2.0.0/16 dev gre-moon
moon::iptables -A FORWARD -i gre-moon -j ACCEPT
moon::iptables -A FORWARD -o gre-moon -j ACCEPT
sun::ip tunnel add gre-sun local PH_IP_SUN remote PH_IP_MOON mode gre key 42
sun::ip link set gre-sun up
sun::ip route add 10.1.0.0/16 dev gre-sun
sun::iptables -A FORWARD -i gre-sun -j ACCEPT
sun::iptables -A FORWARD -o gre-sun -j ACCEPT

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

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

使用ip命令在moon和sun网关上查看创建的GRE隧道设备,ikey和okey两个方向的值都为42。

moon:~# ip -d link show gre-moon
24: gre-moon@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1472 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/gre 192.168.0.1 peer 192.168.0.2 promiscuity 0 
    gre remote 192.168.0.2 local 192.168.0.1 ttl inherit ikey 0.0.0.42 okey 0.0.0.42 addrgenmode eui64 numtxqueues 1 gso_max_size 65536 gso_max_segs 65535 
moon:~#

sun:~# ip -d link show gre-sun
26: gre-sun@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1472 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/gre 192.168.0.2 peer 192.168.0.1 promiscuity 0 
    gre remote 192.168.0.1 local 192.168.0.2 ttl inherit ikey 0.0.0.42 okey 0.0.0.42 addrgenmode eui64 numtxqueues 1 gso_max_size 65536 gso_max_segs 65535 
sun:~# 

使用ip route命令查看为gre-moon虚拟接口添加的路由信息,目的网段10.2.0.0/16(bob主机所在网段)的流量路由到gre-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 gre-moon scope link 
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.1 
moon:~# 

以下为连接建立之后,_updown脚本配置的iptables规则,在FORWARD规则链上允许转发入接口或者出接口为gre-moon隧道接口的所有流量。对于INPUT和OUTPUT规则链,使用policy扩展匹配ESP(50)协议的IPSEC报文,及其封装的协议号为47的GRE报文,两个规则链分别匹配两个方向。

moon:~# iptables -L -n -v

Chain INPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    1   112 ACCEPT     47   --  eth0   *       192.168.0.2          192.168.0.1          policy match dir in pol ipsec reqid 1 proto 50

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

Chain OUTPUT (policy DROP 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    1   112 ACCEPT     47   --  *      eth0    192.168.0.1          192.168.0.2          policy match dir out pol ipsec reqid 1 proto 50  

默认的_updown脚本为:strongswan-5.8.1/src/_updown/_updown.in,其中的操作取决于case之后的关键变量值:"$PLUTO_VERB:$1",如上swanctl.conf文件所述参数 1 的 值 为 i p t a b l e s , 而 变 量 1的值为iptables,而变量 1iptablesPLUTO_VERB的值由strongswan的updown插件设置,其由两部分组成,第一是表示连接状态的up/down;第二部分是host/client。host对应本机流量,相应规则添加到INPUT/OUTPUT链。client对应其它的包含流量,相应规则要添加到FORWARD链。对于本处的情况,虽然是保护两个子网(alice到bob)的流量,但是经过本机的GRE隧道处理流量相当于本机的流量,所以对应于host角色。

IPSEC_POLICY="-m policy --pol ipsec --proto $PLUTO_PROTO --reqid $PLUTO_REQID"
IPSEC_POLICY_IN="$IPSEC_POLICY --dir in"
IPSEC_POLICY_OUT="$IPSEC_POLICY --dir out"

case "$PLUTO_VERB:$1" in

up-host:iptables)
        # connection to me
        iptables -I INPUT 1 -i $PLUTO_INTERFACE -p $PLUTO_MY_PROTOCOL \
            -s $PLUTO_PEER_CLIENT $S_PEER_PORT \
            -d $PLUTO_ME $D_MY_PORT $IPSEC_POLICY_IN -j ACCEPT
        iptables -I OUTPUT 1 -o $PLUTO_INTERFACE -p $PLUTO_PEER_PROTOCOL \
            -s $PLUTO_ME $S_MY_PORT $IPSEC_POLICY_OUT \
            -d $PLUTO_PEER_CLIENT $D_PEER_PORT -j ACCEPT

文件strongswan-5.8.1/src/libcharon/plugins/updown/updown_listener.c中的函数invoke_once负责启动updown脚本,并且提前准备相应的环境变量。根据以上updown脚本,需要使用的变量有:

  • 接口名称 PLUTO_INTERFACE (eth0)
  • 协议号 PLUTO_MY_PROTOCOL (47) 和 PLUTO_PEER_PROTOCOL (47)
  • 对端地址 PLUTO_PEER_CLIENT (192.168.0.2/32) 和本IP地地址 PLUTO_ME (192.168.0.1/32)

目前,updown插件不支持针对以下4个端口环境变量的设置。

  • 对端的源端口 S_PEER_PORT 和目的端口 D_PEER_PORT
  • 本端的源端口 S_MY_PORT 和目的端口 D_MY_PORT。

环境变量PLUTO_PROTO的值为ESP,变量PLUTO_REQID的值为1。

  • IPSEC策略匹配扩展的变量: PLUTO_PROTO 和 PLUTO_REQID
  • 环境变量 PLUTO_VERB
static void invoke_once(private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, child_cfg_t *config, bool up,
                                                traffic_selector_t *my_ts, traffic_selector_t *other_ts)
{
        me = ike_sa->get_my_host(ike_sa);
        other = ike_sa->get_other_host(ike_sa);

        is_host = my_ts->is_host(my_ts, me);
        if (is_host) {
                is_ipv6 = me->get_family(me) == AF_INET6;
        } else {
                is_ipv6 = my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE;
        }
        push_env(envp, countof(envp), "PLUTO_VERB=%s%s%s", up ? "up" : "down", is_host ? "-host" : "-client", is_ipv6 ? "-v6" : "");
        push_env(envp, countof(envp), "PLUTO_INTERFACE=%s", iface ? iface : "unknown");
        push_env(envp, countof(envp), "PLUTO_REQID=%u", child_sa->get_reqid(child_sa));
        push_env(envp, countof(envp), "PLUTO_PROTO=%s", child_sa->get_protocol(child_sa) == PROTO_ESP ? "esp" : "ah");
        push_env(envp, countof(envp), "PLUTO_ME=%H", me);
        push_env(envp, countof(envp), "PLUTO_MY_PROTOCOL=%u", my_ts->get_protocol(my_ts));
        push_env(envp, countof(envp), "PLUTO_PEER_CLIENT=%+H/%u", host, mask);
        push_env(envp, countof(envp), "PLUTO_PEER_PROTOCOL=%u", other_ts->get_protocol(other_ts));

更新流量选择器TS

文件:strongswan-5.8.1/src/libcharon/sa/ikev2/tasks/child_create.c中的函数build_i负责构建Initiator端的消息,看一下以下的TSi和TSr的赋值。涉及到两个子函数get_dynamic_hosts和get_traffic_selectors。对于前者,get_dynamic_hosts的第二个参数指明要获取的是本地还是远端的host值。后一个子函数负责将获取的host主机的值赋予相应的流量选择器。

METHOD(task_t, build_i, status_t, private_child_create_t *this, message_t *message)
{
        if (list->get_count(list)) {
                ...
        } else
        {       /* no virtual IPs configured */
                list->destroy(list);
                list = get_dynamic_hosts(this->ike_sa, TRUE);
                this->tsi = this->config->get_traffic_selectors(this->config, TRUE, NULL, list, TRUE);
                list->destroy(list);
        }
        list = get_dynamic_hosts(this->ike_sa, FALSE);
        this->tsr = this->config->get_traffic_selectors(this->config, FALSE, NULL, list, TRUE);

对于在配置中TS指定为dynamic动态的请求,本地TS使用本机的IKE端点地址(get_my_host),此处为moon网关地址。远端TS使用远端主机IKE端点地址(get_other_host),即sun网关地址。

/* Get hosts to use for dynamic traffic selectors
 */
static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local)
{
        if (list->get_count(list) == 0)
        {       /* no virtual IPs assigned */
                if (local) {
                        host = ike_sa->get_my_host(ike_sa);
                        list->insert_last(list, host);
                }
                else if (!have_pool(ike_sa))
                {       /* use host only if we don't have a pool configured */
                        host = ike_sa->get_other_host(ike_sa);
                        list->insert_last(list, host);
                }

以下的get_traffic_selectors函数将get_dynamic_hosts获取到的主机地址设置到流量选择器TS中,见最后的函数调用set_address。条件是TS配置了dynamic; 或者为工作在传输模式(MODE_TRANSPORT)的连接发起者Initiator。

METHOD(child_cfg_t, get_traffic_selectors, linked_list_t*, private_child_cfg_t *this, bool local, linked_list_t *supplied,
        linked_list_t *hosts, bool log)
{       
        enumerator_t *e1, *e2;
        traffic_selector_t *ts1, *ts2, *selected;

        if (local) {
                e1 = this->my_ts->create_enumerator(this->my_ts);
        } else {                                                                                          
                e1 = this->other_ts->create_enumerator(this->other_ts);
        }
        while (e1->enumerate(e1, &ts1))  /* in a first step, replace "dynamic" TS with the host list */
        {       
                if (hosts && hosts->get_count(hosts))
                {       /* set hosts if TS is dynamic or as initiator in transport mode */
                        bool dynamic = ts1->is_dynamic(ts1), proxy_mode = has_option(this, OPT_PROXY_MODE);
                        if (dynamic || (this->mode == MODE_TRANSPORT && !proxy_mode && !supplied))
                        {
                                e2 = hosts->create_enumerator(hosts);
                                while (e2->enumerate(e2, &host)) {
                                        ts2 = ts1->clone(ts1);
                                        if (dynamic || !host->is_anyaddr(host)) {       
                                                ts2->set_address(ts2, host); /* don't make regular TS larger than they were */
                                        }

文件:strongswan-5.8.1/src/libstrongswan/selectors/traffic_selector.c中的函数set_address如下。除了将出入的主机地址拷贝为流量选择器TS的开始(from)和结束(to)地址外,还将清除流量选择器的dynamic标志。在以下涉及到IKE报文时,将看到流量选择器的最终值。

METHOD(traffic_selector_t, set_address, void, private_traffic_selector_t *this, host_t *host)
{
        this->type = host->get_family(host) == AF_INET ? TS_IPV4_ADDR_RANGE : TS_IPV6_ADDR_RANGE;

        if (host->is_anyaddr(host))
        {
                ...
        } else {
                chunk_t from = host->get_address(host);
                memcpy(this->from, from.ptr, from.len);
                memcpy(this->to, from.ptr, from.len);
                this->netbits = from.len * 8;
        }
        this->dynamic = FALSE;

报文流程

alice主机pingbob主机的报文,目的地址为10.2.0.10,到达moon网关之后,查询路由得到出口设备gre-moon,以下为此设备的发送函数__gre_xmit。函数gre_build_header将为报文增加GRE头部,其中参数o_key为配置中的值42。函数ip_tunnel_xmit执行发送。

static void __gre_xmit(struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params, __be16 proto)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);

    /* Push GRE header. */
    gre_build_header(skb, tunnel->tun_hlen,
             tunnel->parms.o_flags, proto, tunnel->parms.o_key,
             htonl(tunnel->o_seqno));

    ip_tunnel_xmit(skb, dev, tnl_params, tnl_params->protocol);
}

注意发送函数ip_tunnel_xmit中的ip_route_output_key出口路由查询函数,其flowi4结构参数的成员flowi4_proto在函数ip_tunnel_init_flow中被赋予了protocol变量的值,在这里为GRE的协议值47。

void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params, u8 protocol)
{
    ip_tunnel_init_flow(&fl4, protocol, dst, tnl_params->saddr,
                tunnel->parms.o_key, RT_TOS(tos), tunnel->parms.link, tunnel->fwmark);

    if (ip_tunnel_encap(skb, tunnel, &protocol, &fl4) < 0)
        goto tx_error;

    rt = connected ? dst_cache_get_ip4(&tunnel->dst_cache, &fl4.saddr) : NULL;
    if (!rt) {
        rt = ip_route_output_key(tunnel->net, &fl4);

函数ip_route_output_key将调用ip_route_output_flow查询路由,其中__ip_route_output_key执行通用的路由查询,之后,检查flowi4结构成员flowi4_proto如果有值,调用xfrm_lookup_route函数查询XFRM路由信息,最终得到一个xfrm_dst类型路由缓存。

struct rtable *ip_route_output_flow(struct net *net, struct flowi4 *flp4, const struct sock *sk)
{
    struct rtable *rt = __ip_route_output_key(net, flp4);

    if (IS_ERR(rt))
        return rt;
                  
    if (flp4->flowi4_proto)
        rt = (struct rtable *)xfrm_lookup_route(net, &rt->dst,  flowi4_to_flowi(flp4), sk, 0);

以下为处理函数xfrm_bundle_lookup,如下所示。调用路径为:xfrm_lookup_route->xfrm_lookup->xfrm_lookup_with_ifid,以下也列出了函数xfrm_lookup_with_ifid的部分内容。首先先根据flowi4等参数查找安全策略,即函数xfrm_policy_lookup。其次使用函数xfrm_resolve_and_create_bundle创建XFRM路由xfrm_dst,并为其绑定安全关联和安全策略。

static struct xfrm_dst *xfrm_bundle_lookup(struct net *net, const struct flowi *fl,
                       u16 family, u8 dir, struct xfrm_flo *xflo, u32 if_id)
{
    struct xfrm_policy *pols[XFRM_POLICY_TYPE_MAX];
    struct xfrm_dst *xdst;

    /* Resolve policies to use if we couldn't get them from
     * previous cache entry */
    num_pols = 1;
    pols[0] = xfrm_policy_lookup(net, fl, family, dir, if_id);
    err = xfrm_expand_policies(fl, family, pols, &num_pols, &num_xfrms);

    if (err < 0) goto inc_error;
    if (num_pols == 0) return NULL;
    if (num_xfrms <= 0) goto make_dummy_bundle;

    xdst = xfrm_resolve_and_create_bundle(pols, num_pols, fl, family, xflo->dst_orig);
    return xdst;
	
struct dst_entry *xfrm_lookup_with_ifid(struct net *net, struct dst_entry *dst_orig, const struct flowi *fl, ...)
{
    if (xdst == NULL) {
        struct xfrm_flo xflo;
		
        xflo.dst_orig = dst_orig;
        xflo.flags = flags;
        xdst = xfrm_bundle_lookup(net, fl, family, dir, &xflo, if_id);
    }
    dst = &xdst->u.dst;
    return dst;

安全策略的查找在函数xfrm_policy_lookup_bytype中完成,注意在匹配函数xfrm_policy_match中,除了流量选择器的源和目的地址、端口号等匹配之外,对于此处的情况,还比对策略中的协议类型,这里应当为GRE协议(47)。

static struct xfrm_policy *xfrm_policy_lookup_bytype(struct net *net, u8 type, const struct flowi *fl, u16 family, u8 dir, u32 if_id)
{
    hlist_for_each_entry_rcu(pol, chain, bydst) {
        err = xfrm_policy_match(pol, fl, type, family, dir, if_id);
        if (err) {
            if (err == -ESRCH)
                continue;
            else {
                ret = ERR_PTR(err);
                goto fail;
            }
        } else {
            ret = pol;
            break;
        }
    }
    return ret;

函数xfrm_resolve_and_create_bundle负责解析和创建xfrm_dst结构,其由两个核心子函数组成。函数xfrm_tmpl_resolve更加以上得到的安全策略中的管理模板查找安全关联。另一个函数xfrm_bundle_create负责分配合初始化xfrm_dst结构,并去绑定安全关联。

在函数xfrm_resolve_and_create_bundle最后,为xfrm_dst绑定安全策略。

static struct xfrm_dst *xfrm_resolve_and_create_bundle(struct xfrm_policy **pols, int num_pols,
                   const struct flowi *fl, u16 family, struct dst_entry *dst_orig)
{
    struct net *net = xp_net(pols[0]);
    struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
    struct xfrm_dst *bundle[XFRM_MAX_DEPTH];
    struct xfrm_dst *xdst;
    struct dst_entry *dst;

    /* Try to instantiate a bundle */
    err = xfrm_tmpl_resolve(pols, num_pols, fl, xfrm, family);
    if (err <= 0) {
        ...
        return ERR_PTR(err);
    }
    dst = xfrm_bundle_create(pols[0], xfrm, bundle, err, fl, dst_orig);
    if (IS_ERR(dst)) {
        XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTBUNDLEGENERROR);
        return ERR_CAST(dst);
    }

    xdst = (struct xfrm_dst *)dst;
    xdst->num_xfrms = err;
    xdst->num_pols = num_pols;
    memcpy(xdst->pols, pols, sizeof(struct xfrm_policy *) * num_pols);
    xdst->policy_genid = atomic_read(&pols[0]->genid);

    return xdst;
}

路由bundle创建函数xfrm_bundle_create如下,根据所对应的安全关联的个数将生成一个xfrm_dst结构链表,每个安全关联xfrm对应一个xfrm_dst结构,彼此之间通过成员child连接成单向的链表。意味着相关的数据流根据链表可进行多重的变换,在每个xfrm_dst结构中,成员xfrm保存了对应的安全关联结构,成员output为此安全关联的输出函数,对于此处IPv4协议与传输模式而言,此函数为:xfrm4_output。

static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm,
                        struct xfrm_dst **bundle, int nx, const struct flowi *fl, struct dst_entry *dst)
{
    struct xfrm_mode *inner_mode;

    for (; i < nx; i++) {
        struct xfrm_dst *xdst = xfrm_alloc_dst(net, family);
        struct dst_entry *dst1 = &xdst->u.dst;

        if (!xdst_prev)  xdst0 = xdst;
        else xfrm_dst_set_child(xdst_prev, &xdst->u.dst); 

        inner_mode = xfrm[i]->inner_mode;

        xdst->route = dst;
        dst_copy_metrics(dst1, dst);

        dst1->xfrm = xfrm[i];
        xdst->xfrm_genid = xfrm[i]->genid;
        dst1->obsolete = DST_OBSOLETE_FORCE_CHK;
        dst1->flags |= DST_HOST;
        dst1->lastuse = now;
        dst1->input = dst_discard;
        dst1->output = inner_mode->afinfo->output;
        xdst_prev = xdst;
    }
    xfrm_dst_set_child(xdst_prev, dst);
    xdst0->path = dst;

    return &xdst0->u.dst;

本小节开始之前GRE的发送函数ip_tunnel_xmit在查完路由之后,最后由ip_local_out函数执行发送,最终执行dst_output函数,调用以上的output函数指针(xfrm4_output),进行IPSEC变换处理。

int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{  
    int err;
       
    err = __ip_local_out(net, sk, skb);
    if (likely(err == 1))
        err = dst_output(net, sk, skb);

    return err;
}

报文解析

以下为sun网关回复给moon的IKE_AUTH报文中的使用传输模式的通知信息。

在这里插入图片描述

以下为IKE_AUTH报文中的Initiator和Responder的流选择器载荷信息,可见其中的协议值Protocol ID为47(Generic Routing Encapsulation)。

在这里插入图片描述

以下为alice到bob的ping报文,可见其中的GRE协议Key值为:2a(42)。

在这里插入图片描述

安全策略和关联

在moon网关上使用ip xfrm查看安全策略信息,可见协议proto为gre,关联模板为传输模式(mode transport)。

src 192.168.0.1/32 dst 192.168.0.2/32 proto gre uid 0
        dir out action allow index 881 priority 366975 ptype main share any flag  (0x00000000)

        tmpl src 0.0.0.0 dst 0.0.0.0
                proto esp spi 0xc4202f7b(3290443643) reqid 1(0x00000001) mode transport
                level required share any
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff
src 192.168.0.2/32 dst 192.168.0.1/32 proto gre uid 0
        dir in action allow index 872 priority 366975 ptype main share any flag  (0x00000000)

        tmpl src 0.0.0.0 dst 0.0.0.0
                proto esp spi 0x00000000(0) reqid 1(0x00000001) mode transport
                level required share any
                enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff

在moon网关上使用swanctl工具查看安全关联信息,可见子连接gre的local和remote流选择器,分别为192.168.0.1/32[gre]和192.168.0.2/32[gre]。

gw-gw: #1, ESTABLISHED, IKEv2, db0ab87fcef07746_i* edfab5049f135556_r
  local  'moon.strongswan.org' @ 192.168.0.1[4500]
  remote 'sun.strongswan.org' @ 192.168.0.2[4500]
  AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/CURVE_25519
  established 1s ago, rekeying in 13346s
  gre: #1, reqid 1, INSTALLED, TRANSPORT, ESP:AES_GCM_16-128
    installed 1s ago, rekeying in 3512s, expires in 3959s
    in  cbf4b273,     92 bytes,     1 packets,     1s ago
    out c4202f7b,     92 bytes,     1 packets,     1s ago
    local  192.168.0.1/32[gre]
    remote 192.168.0.2/32[gre]

strongswan版本: 5.8.1
内核版本: 5.0

END

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