IPSec

A real beast.

1. Good reads

(我都没有看过)

"It's just an astonishingly-complex suite of protocols." – An Illustrated Guide to IPsec

Nftables 指南:packet flow ipsec expressions

2. Caveats

2.1. Let's Encrypt

Android 的 strongswan client 似乎需要:

  • x509 下面的 cert 是 fullchain.pem
  • x509ca 下面的 cert 是 ca cert(chain.pem)

iOS 的原生 IKEv2 Client 要求:

3. 安装和配置

可以参考 ../bootstrap/ansible/roles/networking/tasks/strongswan.yml。Archlinux 安装 strongswan ,Debian 安装 charon-systemd

Strongswan 有旧的 ipsec.conf 的配置形式和新的 charon 配置形式,我只会新的。

参考的 Server 配置:

# /etc/swanctl/conf.d/example.conf
pools {
   v4pool {
      addrs = 172.31.2.0/24
   }
   v6pool {
      addrs = fdff:ffff:ffff:fff0::1:2:0/112
   }
}
connections {
  # '-eap' 的后缀是有含义的!换成别的不行,我不知道为什么,或许是和下面 secrets 里的 'eap-' 对应,
  # 也或许是必须就要这么写。
  # 下面的 local 是本机侧, remote 是远端侧。
  rw-eap {
    local_addrs = example.com
    pools = v4pool, v6pool

    local {
      auth = pubkey
      certs = example.com.pem
      id = example.com
    }
    remote {
      # 用户名+密码的登录方式
      auth = eap-mschapv2
      eap_id = %any
    }
    children {
      net {
         local_ts  = 0.0.0.0/0, ::/0
      }
    }
    version = 2
    send_cert = always
    send_certreq = no
  }

  # https://docs.strongswan.org/docs/latest/config/IPv6Ndp.html
  ndp {
    children {
      ns {
        local_ts = ::/0[ipv6-icmp/135]
        remote_ts = ::/0[ipv6-icmp/135]
        mode = pass
        start_action = trap
      }
      na {
        local_ts = ::/0[ipv6-icmp/136]
        remote_ts = ::/0[ipv6-icmp/136]
        mode = pass
        start_action = trap
      }
    }
  }
}
secrets {
   eap-yourname {
      id = yourname
      secret = yourpassword
   }
}

指定 DNS 服务器

# /etc/strongswan.d/charon/attr.conf
# Section to specify arbitrary attributes that are assigned to a peer via
# configuration payload (CP).
attr {
    load = yes
    # <attr> is an attribute name or an integer, values can be an IP address,
    # subnet or arbitrary value.
    # <attr> =
    dns = 8.8.8.8

}

证书文件:

# 私钥
/etc/swanctl/private:
example.com.pem

# 公钥,我选的是 letsecrypt 的 fullchain 证书
/etc/swanctl/x509:
exmpale.comf.pem

# CA 证书。不知道为什么加了 fullchain 还是不够,我从 client 认证失败的例子看说缺这些 CA 就加上了。
# openssl x509 -in x.pem -text -noout 也能用来看最上层的 CA 是谁。
# 从 https://letsencrypt.org/certificates/ 能下载 pem 格式的证书文件。
/etc/swanctl/x509ca:
le-R11.pem  le-R12.pem

4. 使用本地 DHCP 为 roadwarrier 提供 LAN 地址

dhcp + farp plugin 能让远端设备获得如同在本地 LAN 一样的二层接入,仅限 IPv4。这样的好处是能用本地 DHCP 服务为远端设备提供固定地址。

连接配置:v4 地址池改为特殊的 "dhcp" 地址池

connections {
  rw-eap {
    local_addrs = example.com
    pools = dhcp, v6pool
  }
}

dhcp 插件配置:dnsmasq 不会响应本地回环设备来的请求,必须要发往它 LAN 网段的广播地址

# /etc/strongswan.d/charon/dhcp.conf
dhcp {
    load = yes
    # 强行让 strongswan 到广播地址去发请求
    force_server_address = yes
    # DHCP server unicast or broadcast IP address.
    server = 172.31.0.255

    # 虽然可能没有必要,但是让 MAC 地址稳定或许更像一个标准的二层设备
    # Derive user-defined MAC address from hash of IKE identity and send client
    # identity DHCP option.
    identity_lease = yes
}

farp 插件配置:加载就好,我不确定它是否起到关键作用

bypass-lan 插件:先关闭,否则它默认在 route table 220 里增加对每个 netif 的网段的 throw 路由。 这是为了让 roadwarrier 能访问自己本地同网段的设备。我其实不太理解为什么在 router 上加 throw 路由 能影响 roadwarrier 的路由选择。

# /etc/strongswan.d/charon/bypass-lan.conf
bypass-lan {
    load = no
}

Updated: 2025-12-04 Thu 02:42