乐趣区

XFRM-IPsec协议的内核实现框架

IPsec协议帮助 IP 层建立安全可信的数据包传输通道。当前已经有了如 StrongSwan、OpenSwan 等比较成熟的解决方案,而它们都使用了 Linux 内核中的 XFRM 框架进行报文接收发送。


XFRM的正确读音是 transform(转换), 这表示内核协议栈收到的IPsec 报文需要经过 转换 才能还原为原始报文;同样地,要发送的原始报文也需要转换为 IPsec 报文才能发送出去。

Overview

XFRM 实例

IPsec 中有两个重要概念:安全关联 (Security Association) 和安全策略 (Security Policy),这两类信息都需要存放在内核XFRM。核XFRM 使用 netns_xfrm 这个结构来组织这些信息,它也被称为 xfrm instance(实例)。从它的名字也可以看出来,这个实例是与 network namespace 相关的,每个命名空间都有这样的一个实例,实例间彼此独立。所以同一台主机上的不同容器可以互不干扰地使用XFRM

struct net
{
    ......
   #ifdef CONFIG_XFRM
    struct netns_xfrm    xfrm;
    #endif 
    ......
}

Netlink 通道

上面提到了 Security Association 和 Security Policy 信息,这些信息一般是由用户态 IPsec 进程 (eg. StrongSwan) 下发到内核 XFRM 的,这个下发的通道在 network namespace 初始化时创建。

static int __net_init xfrm_user_net_init(struct net *net)
{
    struct sock *nlsk;
    struct netlink_kernel_cfg cfg = {
        .groups    = XFRMNLGRP_MAX,
        .input    = xfrm_netlink_rcv,
    };

    nlsk = netlink_kernel_create(net, NETLINK_XFRM, &cfg);
    ......
    return 0;
}

这样,当用户下发 IPsec 配置时,内核便可以调用 xfrm_netlink_rcv() 来接收

XFRM State

XFRM使用 xfrm_state 表示 IPsec 协议栈中的 Security Association,它表示了一条单方向的 IPsec 流量所需的一切信息,包括模式 (Transport 或 Tunnel)、密钥、replay 参数等信息。用户态 IPsec 进程通过发送一个 XFRM_MSG_NEWSA 请求,可以让XFRM 创建一个 xfrm_state 结构

xfrm_state包含的字段很多,这里就不贴了,仅仅列出其中最重要的字段:

  • id: 它是一个 xfrm_id 结构,包含该 SA 的目的地址、SPI、和协议(AH/ESP)
  • props:表示该 SA 的其他属性,包括 IPsec Mode(Transport/Tunnel)、源地址等信息

每个 xfrm_state 在内核中会加入多个哈希表,因此,内核可以从多个特征查找到同一个个 SA:

  • xfrm_state_lookup():通过指定的 SPI 信息查找 SA
  • xfrm_state_lookup_byaddr(): 通过源地址查找 SA
  • xfrm_state_find(): 通过目的地址查找 SA

用户可以通过 ip xfrm state ls 命令列出当前主机上的xfrm_state

src 192.168.0.1 dst 192.168.0.2
    proto esp spi 0xc420a5ed(3290473965) reqid 1(0x00000001) mode tunnel
    replay-window 0 seq 0x00000000 flag af-unspec (0x00100000)
    auth-trunc hmac(sha256) 0xa65e95de83369bd9f3be3afafc5c363ea5e5e3e12c3017837a7b9dd40fe1901f (256 bits) 128
    enc cbc(aes) 0x61cd9e16bb8c1d9757852ce1ff46791f (128 bits)
    anti-replay context: seq 0x0, oseq 0x1, bitmap 0x00000000
    lifetime config:
      limit: soft (INF)(bytes), hard (INF)(bytes)
      limit: soft (INF)(packets), hard (INF)(packets)
      expire add: soft 1004(sec), hard 1200(sec)
      expire use: soft 0(sec), hard 0(sec)
    lifetime current:
      84(bytes), 1(packets)
      add 2019-09-02 10:25:39 use 2019-09-02 10:25:39
    stats:
      replay-window 0 replay 0 failed 0

XFRM Policy

XFRM使用 xfrm_policy 表示 IPsec 协议栈中的 Security Policy,用户通过下发这样的规则,可以让 XFRM 允许或者禁止某些特征的流的发送和接收。用户态 IPsec 进程通过发送一个 XFRM_MSG_POLICY 请求,可以让 XFRM 创建一个 xfrm_state 结构

struct xfrm_policy {
    ......
    struct hlist_node    bydst;
    struct hlist_node    byidx;

    /* This lock only affects elements except for entry. */
    rwlock_t        lock;
    atomic_t        refcnt;
    struct timer_list    timer;

    struct flow_cache_object flo;
    atomic_t        genid;
    u32            priority;
    u32            index;
    struct xfrm_mark    mark;
    struct xfrm_selector    selector;
    struct xfrm_lifetime_cfg lft;
    struct xfrm_lifetime_cur curlft;
    struct xfrm_policy_walk_entry walk;
    struct xfrm_policy_queue polq;
    u8            type;
    u8            action;
    u8            flags;
    u8            xfrm_nr;
    u16            family;
    struct xfrm_sec_ctx    *security;
    struct xfrm_tmpl           xfrm_vec[XFRM_MAX_DEPTH];
    struct rcu_head        rcu;
};

这个结构的字段很多,但大部分并不用关心,我们重点关注下面列举出的这几个字段就行:

  • selector:表示该 Policy 匹配的流的特征
  • action:取值为 XFRM_POLICY_ALLOW(0)或 XFRM_POLICY_BLOCK(1),前者表示允许该流量,后者表示不允许。
  • xfrm_nr: 表示与这条 Policy 关联的 template 的数量,template 可以理解为 xfrm_state 的简化版本,xfrm_nr 决定了流量进行 转换 的次数,通常这个值为 1
  • xfrm_vec: 表示与这条 Policy 关联的 template,数组的每个元素是 xfrm_tmpl, 一个xfrm_tmpl 可以还原 (resolve) 成一个完成state

xfrm_state 类似,用户可以通过 ip xfrm policy ls 命令列出当前主机上的xfrm_policy

src 10.1.0.0/16 dst 10.2.0.0/16 uid 0
    dir out action allow index 5025 priority 383615 ptype main share any flag  (0x00000000)
    lifetime config:
      limit: soft (INF)(bytes), hard (INF)(bytes)
      limit: soft (INF)(packets), hard (INF)(packets)
      expire add: soft 0(sec), hard 0(sec)
      expire use: soft 0(sec), hard 0(sec)
    lifetime current:
      0(bytes), 0(packets)
      add 2019-09-02 10:25:39 use 2019-09-02 10:25:39
    tmpl src 192.168.0.1 dst 192.168.0.2
        proto esp spi 0xc420a5ed(3290473965) reqid 1(0x00000001) mode tunnel
        level required share any 
        enc-mask ffffffff auth-mask ffffffff comp-mask ffffffff

接收发送 IPsec 报文

接收

下图展示了 XFRM 框架接收 IPsec 报文的流程:

从整体上看,IPsec 报文的接收是一个 迂回 的过程,IP 层接收时,根据报文的 protocol 字段,如果它是 IPsec 类型 (AH、ESP),则会进入XFRM 框架进行接收,在此过程里,比较重要的过程是xfrm_state_lookup(), 该函数查找 SA,如果找到之后,再根据不同的协议和模式进入不同的处理过程,最终,将原始报文的信息获取出来,重入ip_local_deliver(). 然后,还需经历 XFRM Policy 的过滤,最后再上送到应用层。

发送

下图展示了 XFRM 框架发送 IPsec 报文的流程:

XFRM在报文路由查找后查找是否有满足条件的 SA,如果没有,则直接走 ip_output(), 否则进入XFRM 的处理过程,根据模式和协议做相应处理,最后殊途同归到ip_output()

REF

  • Linux Kernel Networing Implementation and Throry
退出移动版