• VPP和StrongSwan搭建IPSec


    1、strongswan+vpp简介

    使用VPP 20.01 版本 + strongswan 5.8.3版本编译。

    目前strongswan+VPP方案主要是使用strongswan的插件机制,替换strongswan的两个默认插件。

    1. socket-default 该插件是IKE报文的socket backend。
    2. kernel-netlink 该插件是IPSec 数通backend

    将默认的socket-default连接替换为VPP的punt socket方式,punt socket会将ike协议报文通过VPP上送到strongswan中,strongswan也会将回应的报文通过punt socket传输回vpp,IKE的协商层面是通过strongswan完成。
    ike协商完成之后,strongswan通过vpp的C语言 API向VPP下发IPSEC的配置,sa spd 路由等等,下发完成配置之后,VPP的IPSEC隧道就完成了建立。
    用strongswan替换VPP自身的IKE功能,是因为VPP本身的IKE只支持IKE V2而且功能的丰富度不如strongswan完善。

    已有的开源项目简介

    作者matfabia

    https://github.com/matfabia/strongswan/tree/vpp
    该项目是strongswan+vpp这个方案的最初的项目,确定了strongswan与vpp结合的大致方向,完成度也比较高,后续的其他开源项目都是在此基础上修修补补。
    该项目在上传最初代码后,就停止更新了,代码基于的VPP版本应该是v18.01左右。

    作者mestery

    https://github.com/mestery/strongswan
    该项目是基于上面原始项目进行修改,支持VPP 的1810版本,进行了小幅度的API适配整体和上面项目相差不大。随后该项目也停止更新了,但是该项目中有一个pull request比较关键,在4500 UDP端口上支持了NAT-T IKE,增加了一些VPP新支持的加密算法例如GCM的支持。但是该pull request并没有合入到该项目,因为作者可能已经忘记这个项目了。

    作者rayshi-10

    https://github.com/rayshi-10/Strongswan-Vpp2001
    该项目是基于第二个项目做的,而且把第二个项目中pull request合入了进来,支持了VPP后来加入的更多加密和认证算法,而且支持NAT-T IKE。然后支持了VPP v20.01版本。这个版本的代码修改量还是比较大的。因为VPP v20.01版本API和数据结构的改动是相当大的,大部分原有的IPSEC配置API都发生的变化,进行了多次重构,但是而且设置还删除了一些配置属性,导致原有的流程可以需要改动比较大才能适配。
    v20.01的VPP ipv4 ipv6的配置需要显示下发两条,而以前的版本是使用any属性标志下发一条就可以了。这部分的改动需要特别关注下,该项目目测这部分可能会有BUG。可以特别关注下该项目的manage_policy函数,例如下面的部分,is_anyaddr的情况只下发了一条policy。可能会出现问题

    if (src->is_anyaddr(src) && dst->is_anyaddr(dst))
        {
            memset(mp->entry.local_address_stop.un.ip6, 0xFF, 16);
            memset(mp->entry.remote_address_stop.un.ip6, 0xFF, 16);
        } 

    2、基于rayshi-10的代码和strongswan release5.8.3进行修改

    下载源码

    下载strongswan主线代码,切换到5.8.3分支

    git clone https://github.com/strongswan/strongswan.git
    git checkout 5.8.3

    下载rayshi-10 strongswan + vpp 20.01代码

    git clone https://github.com/rayshi-10/Strongswan-Vpp2001.git

    替换文件

    将该项目的

    src/libcharon/plugins/kernel_vpp/
    src/libcharon/plugins/socket_vpp/

    两个目录替换到strongswan 5.8.3对应目录下,然后将该项目configure.ac目录下kernel-vpp socket-vpp相关的内存,添加到strongswan 5.8.3对应的文件里。
    注,该项目的configure.ac里面缺少下面两条配置

    ADD_PLUGIN([kernel-vpp],           [c charon])
    ADD_PLUGIN([socket-vpp],           [c charon])

    需要将这两条配置自行添加到configure.ac中的合适位置,
    例如向下面的方式添加

    ADD_PLUGIN([kernel-iph],           [c charon])
    ADD_PLUGIN([kernel-vpp],           [c charon])
    ADD_PLUGIN([kernel-pfkey],         [c charon starter nm cmd])
    ADD_PLUGIN([kernel-pfroute],       [c charon starter nm cmd])
    ADD_PLUGIN([kernel-netlink],       [c charon starter nm cmd])
    ADD_PLUGIN([resolve],              [c charon cmd])
    ADD_PLUGIN([save-keys],            [c])
    ADD_PLUGIN([socket-default],       [c charon nm cmd])
    ADD_PLUGIN([socket-dynamic],       [c charon cmd])
    ADD_PLUGIN([socket-win],           [c charon])
    ADD_PLUGIN([socket-vpp],           [c charon])
    ADD_PLUGIN([bypass-lan],           [c charon nm cmd])

    注意dnssec_status_t的修改

    dnssec_status_t枚举变量在strongswan vpp中进行了重命名,将这个枚举中的变量全都加了DNSS前缀,可能是因为这个枚举里面的变量和VPP里面的内容重名了,我们在替换时,如果编译失败了,可能是忘记重命名该名称导致
    重命名后的效果如下

    enum dnssec_status_t {
        /**
         * The validating resolver has a trust anchor, has a chain of
         * trust, and is able to verify all the signatures in the response.
         * [RFC4033]
         */
        DNSS_SECURE,
        /**
         * The validating resolver has a trust anchor, a chain of
         * trust, and, at some delegation point, signed proof of the
         * non-existence of a DS record.  This indicates that subsequent
         * branches in the tree are provably insecure.  A validating resolver
         * may have a local policy to mark parts of the domain space as
         * insecure. [RFC4033]
         */
        DNSS_INSECURE,
        /**
         * The validating resolver has a trust anchor and a secure
         * delegation indicating that subsidiary data is signed, but the
         * response fails to validate for some reason: missing signatures,
         * expired signatures, signatures with unsupported algorithms, data
         * missing that the relevant NSEC RR says should be present, and so
         * forth. [RFC4033]
         */
        DNSS_BOGUS,
        /**
         * There is no trust anchor that would indicate that a
         * specific portion of the tree is secure.  This is the default
         * operation mode. [RFC4033]
         */
        DNSS_INDETERMINATE,
    };

    修改PUNT read socket path

    在src/libcharon/plugins/socket_vpp/socket_vpp_socket.c中该项目中vpp的punt read path是/tmp目录,该地址可以自行设定,例如我将该地址进行了下面的修改,和VPP其他unix socket放置在同一目录。

    #define READ_PATH "/var/run/vpp/ike-punt-read.sock"

    3、编译项目

    下载依赖

    Centos7,使用下面的命令下载编译中的依赖项。

    yum install gperf
    yum install python3
    yum install gmp
    yum install gmp-devel

    编译vpp

    git clone https://github.com/FDio/vpp.git
    git checkout v20.01
    make install-dep
    make build-release

    将编译好的VPP安装到系统中

    cp build-root/install-vpp-native/vpp/include/* /usr/include/ -r
    cp build-root/install-vpp-native/vpp/lib/* /lib64/ -r
    cp build-root/install-vpp-native/vpp/lib/vpp_plugins /lib/ -r
    cp build-root/install-vpp-native/vpp/bin/vpp /usr/bin/
    cp build-root/install-vpp-native/vpp/bin/vppctl /usr/bin/

    编译strongswan

    预处理

    最新版本的strongswan在centos下可能编译不过,pkgconfig版本低,缺少PKG_CHECK_VAR
    需要在configure.ac前面添加下面的定义

    # backwards compat with older pkg-config
    # - pull in AC_DEFUN from pkg.m4
    m4_ifndef([PKG_CHECK_VAR], [
    # PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
    # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
    # -------------------------------------------
    # Retrieves the value of the pkg-config variable for the given module.
    AC_DEFUN([PKG_CHECK_VAR],
    [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
    AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
    _PKG_CONFIG([$1], [variable="][$3]["], [$2])
    AS_VAR_COPY([$1], [pkg_cv_][$1])
    AS_VAR_IF([$1], [""], [$5], [$4])dnl
    ])# PKG_CHECK_VAR
    ])

    执行编译

    ./autogen.sh
    ./configure --enable-socket-vpp --enable-kernel-vpp --enable-libipsec --enable-openssl
    make -j 8

    将编译好的strongswan安装到系统中

    make install

    安装好的默认目录是/usr/local/,主要文件和目录如下所示

    /usr/local/bin/pki
    /usr/local/sbin/ipsec
    /usr/local/sbin/swanctl
    /usr/local/sbin/etc/

    4、运行测试

    搭建方式

    我是通过Vmware虚拟机跑了两个vpp + strongswan的环境,两者的接口使用vmnet连接。
    strongswan+vpp的启动顺序,需要首先启动VPP,然后配置好接口之后,启动strongswan,然后启动协商隧道。

    vpp启动配置

    startup.conf

    statseg
    {
      default
      per-node-counters on
    }
    socksvr
    {
        socket-name /var/run/vpp/vpp-api.sock
    }
    unix
    {
      cli-listen /run/vpp/cli.sock
      log /tmp/vpe.log
      nodaemon
      coredump-size 1M
    }
    punt
    {
        socket /var/run/vpp/ike-punt-write.sock
    }
    api-trace { on }
    heapsize 1G
    buffers
    {
        buffers-per-numa 40000
    }
    plugins
    {
      plugin dpdk_plugin.so
      {
        enable
      }
    }
    cpu
    {
      # Dynamic Create Option
      main-core 0
      corelist-workers 1
    }
    dpdk
    {
      log-level debug
      huge-dir /dev/hugepages
      no-tx-checksum-offload
      #vdev crypto_aesni_mb
      dev 0000:02:05.0 { name eth1 }
      dev 0000:02:06.0 { name eth2 }
    }

    上面的配置CPU部分需要根据自己的环境编写,绑定工作线程和主线程到某些CPU核。dpdk部分的接口PCI号,也需要根据实际的情况填写,上面的配置ipsec加解密使用了openssl的能力,没有使用dpdk的加解密套件,使用dpdk加解密套件请看最后一节。
    上面配置中比较重要的一点是punt这一部分,该配置必须填写。strongswan使用到了两个punt socket,其中一个是VPP startup.conf中指定,是write socket,strongswan写报文使用该unix socket。还有一个是punt读接口,该unix socket在strongswan的socket-vpp插件中启动时,动态向vpp注册,接口的路径在代码中写死。上面已经说过这个问题了。
    dev 0000:02:05.0 { name G1/1 }中02:05:0通过lspci查询网卡信息获得。,G1/1仅是别名,后续vppctl指令设置时使用。dev 0000:02:06.0同理。需要额外注意的是,corelist-workers的值需要视情况而定,可以适当增加虚拟机的核数或减少该值。
    heapsize也许关注。

    使用vdev crypto_aesni_mb方式,若/var/log/messages下报错如下:

    crypto_create_session_drv_pool: failed to create session drv mempool

    则可能是DPDK大页内存不足引起。扩大DPDK的大页内存数:

    cd /boot/grub2/
    grubby --update-kernel=ALL --args="default_hugepagesz=2M hugepagesz=2M hugepages=512"
    reboot

    vpp运行配置

    VPP成功启动后,需要配置接口的IP信息,这一部分信息就根据测试例的拓扑来配置就可以。
    下面是我的环境中site-to-site中配置CLI命令。
    moon的配置

    vppctl set int state eth1 up
    vppctl set int state eth2 up
    vppctl set int state local0 up
    vppctl set int ip addr eth1 192.168.0.3/24
    vppctl set int ip addr eth2 10.1.0.3/16

    sun的配置

    vppctl set int state eth1 up
    vppctl set int state eth2 up
    vppctl set int state local0 up
    vppctl set int ip addr eth1 192.168.0.2/24
    vppctl set int ip addr eth2 10.1.0.2/16

    若配置时,无法找到eth1和eth2设备,则可能需要编译DPDK加载相关驱动。

    DPDK编译后(我选择的x86_64-native-linux-gcc),需要ifdown掉相关网卡,进行网卡绑定。

    cd x86_64-native-linux-gcc/kmod/
    modprobe uio_pci_generic
    modprobe uio
    insmod igb_uio.ko
    ./dpdk-devbind.py -b igb_uio 02:05.0 ./dpdk-devbind.py -b igb_uio 02:06.0

    配置strongswan

    开启vpp插件

    在进行配置之前,需要先启用我们的kernel-vpp和socket-vpp插件。首先我们将

    /usr/local/etc/strongswan.d/charon/kernel-netlink.conf 
    /usr/local/etc/strongswan.d/charon/socket-default.conf 

    两个默认插件的内容修改一下将默认加载变成不加载
    load = no
    然后将我们新增的两个插件加载状态变为yes

    /usr/local/etc/strongswan.d/charon/socket-vpp.conf 
    /usr/local/etc/strongswan.d/charon/kernel-vpp.conf

    修改为load = yes
    strongswan新版本,我们配置的内容主要是/usr/local/etc/swanctl/swanctl.conf文件,具体的场景和配置可以参考上面给出的官方测试例的配置。
    注意:按上述编译strongswan后,未生成/usr/local/etc/strongswan.d/charon/socket-vpp.conf 和/usr/local/etc/strongswan.d/charon/kernel-vpp.conf。
    通过对比strongswan和Strongswan-Vpp2001的configue.ac,作出修改后,重新编译才生成上述两个文件。

    site-to-site配置

    | 192.168.0.3 | === | 192.168.0.2 | moon                 sun         
    site1 moon配置

    swanctl配置:/usr/local/etc/swanctl/swanctl.conf

    connections {
    
       host-host {
          local_addrs  = 192.168.0.3
          remote_addrs = 192.168.0.2
    
          local {
             auth = psk
             id = moon.strongswan.org
          }
          remote {
             auth = psk
             id = sun.strongswan.org
          }
          children {
             host-host {
                local_ts  = dynamic[udp/81-65535]
                remote_ts = 192.168.0.0/24[udp/8887-8888]
    
                updown = /usr/local/libexec/ipsec/_updown iptables
                esp_proposals = aes128-sha256-x25519
             }
          }
          version = 2
          mobike = no
          proposals = aes128-sha256-x25519
       }
    }
    
    secrets {
          ike-host-host {
            id = sun.strongswan.org
            secret = simplepsk
          }
    }
    site1 sun配置

    swanctl配置:/usr/local/etc/swanctl/swanctl.conf

    connections {
    
       host-host {
          local_addrs  = 192.168.0.2
          remote_addrs = 192.168.0.3
    
          local {
             auth = psk
             id = sun.strongswan.org
          }
          remote {
             auth = psk
             id = moon.strongswan.org
          }
          children {
             host-host {
                local_ts  = dynamic[udp/8887-8888]
                remote_ts = 192.168.0.0/24[udp/81-65535]
    
                updown = /usr/local/libexec/ipsec/_updown iptables
                esp_proposals = aes128-sha256-x25519
             }
          }
          version = 2
          mobike = no
          proposals = aes128-sha256-x25519
       }
    }
    
    secrets {
          ike-host-host {
            id = moon.strongswan.org
            secret = simplepsk
          }
    }
    开始运行

    首先启动VPP,配置好strongswan的配置和VPP的配置,然后两端都使用systemctl start strongswan-starter.service启动strongswan
    可以使用swanctl --stats命令查看一下vpp的插件加载是否正确,在/var/log/messages文件中查看日志是否有报错等等。
    然后查看一下VPP端,strongswan是否已经建立好了连接,如果建立成功之后,vpp中应该会有如下的显示:

    [root@localhost centos126]# vppctl show api clients
    Shared memory clients
                    Name      PID   Queue Length           Queue VA Health
              strongswan    10488              0 0x00000001301ce9c0 OK
    [root@localhost centos126]# vppctl show udp punt
    IPV4 UDP ports punt : 500, 4500
    IPV6 UDP ports punt : 500, 4500

    注意:我在执行systemctl start strongswan-starter.service后,查看/var/log/messages,发现报错如下:

    Jul 28 23:12:58 localhost charon: 00[LIB] feature CUSTOM:libcharon-receiver in critical plugin 'charon' has unmet dependency: CUSTOM:socket 
    Jul 28 23:12:58 localhost charon: 00[LIB] feature CUSTOM:libcharon in critical plugin 'charon' has unmet dependency: CUSTOM:libcharon-receiver

    依据博主冰封飞飞的提示,我也怀疑socket-vpp未编译进来,但是strongswan编译未报错,而且已经生成了相应插件的配置文件。于是,我编译了Strongswan-Vpp2001源码,但是编译报错,socket-vpp插件编译失败。通过find指令找到所引头文件位置,执行如下指令后再次编译成功。

    export C_INCLUDE_PATH=/home/centos119/Ipsec/vpp/src/

    其中/home/centos119/Ipsec/vpp/src/是,依赖头文件所属父目录。

    在两端执行swanctl --load-all加载所有的配置和证书。
    在协商的发起端执行初始化命令,这个net-net是根据当前的swanctl.conf配置文件中children字段里面的内容填写的。

    swanctl --load-all
    swanctl --initiate --child host-host

    查看日志/var/log/messages是否成功。

    最终,重新编译strongswan目录(执行至make install),并单独编译socket_vpp和kernel_vpp目录,执行make; make install。使用5.8.3版本,和上述配置,连接成功。

    [root@localhost Tools]# swanctl --list-conns
    host-host: IKEv2, no reauthentication, rekeying every 14400s
      local:  192.168.0.2
      remote: 192.168.0.3
      local pre-shared key authentication:
        id: sun.strongswan.org
      remote pre-shared key authentication:
        id: moon.strongswan.org
      host-host: TUNNEL, rekeying every 3600s
        local:  dynamic[udp/8887-8888]
        remote: 192.168.0.0/24[udp/81-65535]
    [root@localhost Tools]# vppctl show ipsec all
    [0] sa 1 (0x1) spi 2938187978 (0xaf2130ca) protocol:esp flags:[tunnel ]
    [1] sa 2 (0x2) spi 955807424 (0x38f876c0) protocol:esp flags:[tunnel ]
    spd 1
     ip4-outbound:
       [1] priority 2147483647 action bypass type ip4-outbound protocol IPSEC_AH
         local addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [3] priority 2147483647 action bypass type ip4-outbound protocol IPSEC_ESP
         local addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [5] priority 2147483647 action bypass type ip4-outbound protocol UDP
         local addr range 0.0.0.0 - 255.255.255.255 port range 500 - 500
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [7] priority 2147483647 action bypass type ip4-outbound protocol UDP
         local addr range 0.0.0.0 - 255.255.255.255 port range 4500 - 4500
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [10] priority 2147480829 action protect type ip4-outbound protocol UDP sa 2
         local addr range 192.168.0.2 - 192.168.0.2 port range 8887 - 8888
         remote addr range 192.168.0.3 - 192.168.0.3 port range 81 - 65535
         packets 0 bytes 0
    ip6-outbound:
     ip4-inbound-protect:
       [8] priority 2147480829 action protect type ip4-inbound-protect protocol UDP sa 1
         local addr range 192.168.0.2 - 192.168.0.2 port range 81 - 65535
         remote addr range 192.168.0.3 - 192.168.0.3 port range 8887 - 8888
         packets 0 bytes 0
       [9] priority 2147480829 action protect type ip4-inbound-protect protocol UDP sa 1
         local addr range 192.168.0.2 - 192.168.0.2 port range 81 - 65535
         remote addr range 192.168.0.3 - 192.168.0.3 port range 8887 - 8888
         packets 0 bytes 0
     ip6-inbound-protect:
     ip4-inbound-bypass:
       [0] priority 2147483647 action bypass type ip4-inbound-bypass protocol IPSEC_AH
         local addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [2] priority 2147483647 action bypass type ip4-inbound-bypass protocol IPSEC_ESP
         local addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [4] priority 2147483647 action bypass type ip4-inbound-bypass protocol UDP
         local addr range 0.0.0.0 - 255.255.255.255 port range 500 - 500
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
       [6] priority 2147483647 action bypass type ip4-inbound-bypass protocol UDP
         local addr range 0.0.0.0 - 255.255.255.255 port range 4500 - 4500
         remote addr range 0.0.0.0 - 255.255.255.255 port range 0 - 65535
         packets 0 bytes 0
     ip6-inbound-bypass:
    SPD Bindings:
      1 -> eth1

    参考链接:https://blog.csdn.net/a363344923/article/details/105417015

    锄禾日当午,不如coding苦~
  • 相关阅读:
    使用SQLite做本地数据缓存的思考
    毕业后第一次跳槽面试的点滴记录
    Nancy基于JwtBearer认证的使用与实现
    谈谈Nancy中让人又爱又恨的Diagnostics【上篇】
    CentOS 7.x 防火墙开放端口相关用法记录
    浅析如何在Nancy中使用Swagger生成API文档
    浅析如何在Nancy中生成API文档
    初探CSRF在ASP.NET Core中的处理方式
    微信小程序支付简单小结与梳理
    浅析Content Negotation在Nancy的实现和使用
  • 原文地址:https://www.cnblogs.com/wangzhigang/p/13401739.html
Copyright © 2020-2023  润新知