L2TPv3:http://man7.org/linux/man-pages/man8/ip-l2tp.8.html
ip-xfrm:http://man7.org/linux/man-pages/man8/ip-xfrm.8.html
这两个都是kernel内置的功能,通过这两个可以直接构建加密的VPN。
本文尝试使用ip-xfrm创建加密的隧道,并基于此隧道构建L2TPv3 VPN。
1. 生成密钥与ID
不使用StrongSwan,手动配置ip-xfrm时需要用到:
1 2 3 4 |
HASH_KEY=0x`dd if=/dev/urandom count=32 bs=1 2> /dev/null | xxd -p -c 64` ENCRYPT_KEY=0x`dd if=/dev/urandom count=32 bs=1 2> /dev/null | xxd -p -c 64` ID=0x`dd if=/dev/urandom count=4 bs=1 2> /dev/null | xxd -p -c 8` echo -e "HASH_KEY=${HASH_KEY}\nENCRYPT_KEY=${ENCRYPT_KEY}\nID=${ID}" |
复制上面四行命令后输出的内容,粘贴到left和right上运行,用于设置变量
2. 配置ip-xfrm
这里得分两种情况,一种是left和right都不经过NAT直接持有公网IP,另一种是left或者right任何一端经过NAT。
为什么要这样区分?因为ip-xfrm传输加密报文使用的协议ESP是L3的,一般情况下无法NAT,需要使用L4封装才能正常使用;其次,L4被SNAT后的源端口(source port),可能会被改变。
下面以UDP端口1801作为L2TPv3的通讯端口,对ip-xfrm进行配置。
2.1 不经过NAT
拓扑如下:
首先在left配置ip-xfrm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 执行第一步所输出的 root@left:~# HASH_KEY=... root@left:~# ENCRYPT_KEY=... root@left:~# ID=... # 设置IP地址变量 root@left:~# SOURCE_IP="192.168.1.1" root@left:~# DESTINATION_IP="192.168.2.1" root@left:~# L2TPV3_PORT="1801" root@left:~# ip xfrm state add src ${SOURCE_IP} dst ${DESTINATION_IP} \ proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 0 \ auth sha1 ${HASH_KEY} \ enc aes ${ENCRYPT_KEY} root@left:~# ip xfrm state add src ${DESTINATION_IP} dst ${SOURCE_IP} \ proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 32 \ auth sha1 ${HASH_KEY} \ enc aes ${ENCRYPT_KEY} root@left:~# ip xfrm policy add src ${SOURCE_IP} dst ${DESTINATION_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport root@left:~# ip xfrm policy add src ${DESTINATION_IP} dst ${SOURCE_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport |
然后配置right:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 执行第一步所输出的 root@left:~# HASH_KEY=... root@left:~# ENCRYPT_KEY=... root@left:~# ID=... # 设置IP地址变量 SOURCE_IP="192.168.2.1" DESTINATION_IP="192.168.1.1" L2TPV3_PORT="1801" ip xfrm state add src ${SOURCE_IP} dst ${DESTINATION_IP} \ proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 0 \ auth sha1 ${HASH_KEY} \ enc aes ${ENCRYPT_KEY} ip xfrm state add src ${DESTINATION_IP} dst ${SOURCE_IP} \ proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 32 \ auth sha1 ${HASH_KEY} \ enc aes ${ENCRYPT_KEY} ip xfrm policy add src ${SOURCE_IP} dst ${DESTINATION_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport ip xfrm policy add src ${DESTINATION_IP} dst ${SOURCE_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport |
上面的配置命令,state是指定left到right的之间数据封装使用ESP协议,指定使用sha1算法进行签名,aes-128进行加密。policy指定当L2TPv3的UDP数据包在left和right之间传输时,使用刚刚在state里面配置的规则进行封装。
2.2 经过NAT
拓扑:
上图中left和right都被NAT过,这里需要注意:至少要保证left或者right的一个UDP端口能被对方访问,如果双方都被NAT,且路由器都没有映射端口到它们那,那这个VPN就无法构建。
这里假定right的路由把到2.3.5.6的流量都转发给了192.168.2.2。
NAT还有一个比较大的问题,就是SNAT后源端口可能会改变,假定left和right设定了ESP通过双方的UDP端口4500封装,如果left的kernel从源端口4500发送UDP封装的ESP数据包到2.3.5.6:4500,right收到的数据包的来源端口有可能不是4500,这样会内核就找不到对应的xfrm规则,不会对解除该ESP的封装。即使你通过抓包或者其它方法获取到了被SNAT后的端口,如果这个NAT记录一段时间后无任何流量,路由器会清理掉该记录,后面再SNAT的端口可能又是另一个了。所以,这就是为什么IPsec会有NAT-T。
不过,即使解决SNAT和NAT记录超时问题,也还不足,虽然可以直接手动配置espinudp的规则,但是还是得依赖外部程序,内核才会解除ESP的UDP封装。
2.2.1 方式一:使用StrongSwan配置ip-xfrm
StrongSwan的实现其实也是ip-xfrm,使用StrongSwan,需要开放UDP端口500和4500,这里假设right的路由已把这两个端口转发到right。
在left和right安装StrongSwan:
1 |
apt install strongswan |
在left的/etc/ipsec.conf加入记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
conn peer-2.3.5.6-1801 left=192.168.1.2 leftid="@left-id" right=2.3.5.6 rightid="@right-id" leftprotoport=udp/1801 rightprotoport=udp/1801 ike=aes128-sha1-modp2048! keyexchange=ikev2 reauth=no ikelifetime=28800s dpddelay=30s dpdtimeout=120s dpdaction=restart esp=aes128-sha1-modp2048! keylife=3600s rekeymargin=540s type=transport compress=no authby=secret auto=route keyingtries=%forever |
在left的/etc/ipsec.secrets加入记录(把pre-shared-key改成你自己指定的密钥):
1 |
192.168.1.2 2.3.5.6 @left-id @right-id : PSK "pre-shared-key" |
在right的/etc/ipsec.conf加入记录(这里的auto是add不是route,因为只有right的路由器是为right映射了端口的,left没有,right是无法主动向left发起协商请求的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
conn peer-1.2.3.2-1801 left=192.168.2.2 leftid="@right-id" right=1.2.3.2 rightid="@left-id" leftprotoport=udp/1801 rightprotoport=udp/1801 ike=aes128-sha1-modp2048! keyexchange=ikev2 reauth=no ikelifetime=28800s dpddelay=30s dpdtimeout=120s dpdaction=clear esp=aes128-sha1-modp2048! keylife=3600s rekeymargin=540s type=transport compress=no authby=secret auto=add keyingtries=%forever |
在right的/etc/ipsec.secrets加入记录(left和right的PSK要一致):
1 |
192.168.2.2 1.2.3.2 @right-id @left-id : PSK "pre-shared-key" |
注意,在StrongSwan的配置文件中的left和right并不代表拓扑图中的left和right,left是本机,right是对方;另外,ipsec.secrets的格式是:
1 |
left_ip right_ip left_id right_id : PSK "pre-shared-key" |
上面StrongSwan加入的记录,大概意思就是该规则应用于left和right的udp端口1801之间的通讯,并且让内核在遇到匹配left和right 1801端口的通讯时自动配置ip-xfrm,此外,30秒进行一次心跳,120秒无回应则重建连接。
在两边都重启StrongSwan:
1 |
ipsec restart |
正常来说,后面构建L2TPv3后,1801端口有流量,StrongSwan会自动配置ip-xfrm,不过可以手动up试试是否成功(这里只能在left上进行,前面已提过right是无法主动向left发起协商请求的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
root@left:~# ipsec up peer-2.3.5.6-1801 initiating IKE_SA peer-192.168.1.2-1801[4] to 2.3.5.6 generating IKE_SA_INIT request 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) N(HASH_ALG) N(REDIR_SUP) ] sending packet: from 192.168.1.2[500] to 2.3.5.6[500] (464 bytes) received packet: from 2.3.5.6[500] to 192.168.1.2[500] (462 bytes) parsed IKE_SA_INIT response 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) N(FRAG_SUP) N(HASH_ALG) N(MULT_AUTH) ] local host is behind NAT, sending keep alives remote host is behind NAT authentication of 'left' (myself) with pre-shared key establishing CHILD_SA peer-2.3.5.6-1801 generating IKE_AUTH request 1 [ IDi N(INIT_CONTACT) IDr AUTH N(USE_TRANSP) SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(MULT_AUTH) N(EAP_ONLY) ] sending packet: from 192.168.1.2[4500] to 2.3.5.6[4500] (364 bytes) received packet: from 2.3.5.6[4500] to 192.168.1.2[4500] (284 bytes) parsed IKE_AUTH response 1 [ IDr AUTH N(USE_TRANSP) SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_6_ADDR) ] authentication of 'right-id' with pre-shared key successful IKE_SA peer-2.3.5.6-1801[4] established between 192.168.1.2[left]...2.3.5.6[right-id] scheduling rekeying in 27781s maximum IKE_SA lifetime 28321s CHILD_SA peer-2.3.5.6-1801{2} established with SPIs c903d293_i cbbfce0b_o and TS 192.168.1.2/32[udp/1801] === 2.3.5.6/32[udp/1801] connection 'peer-2.3.5.6-1801' established successfully |
2.2.2 方式二:手动配置ip-xfrm
下面的配置步骤,即使全部对了,应该也是无法通的,抓包可以看到封装为UDP的ESP数据包,但是内核不会解除封装,不过如果你left和right两边都运行StrongSwan(不需要进行任何配置),监听着4500端口,就可以通。
left:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 执行第一步所输出的 root@left:~# HASH_KEY=... root@left:~# ENCRYPT_KEY=... root@left:~# ID=... # 设置IP地址变量 root@left:~# SOURCE_IP="192.168.1.2" root@left:~# DESTINATION_IP="2.3.5.6" root@left:~# L2TPV3_PORT="1801" root@left:~# ip xfrm state add src ${SOURCE_IP} dst ${DESTINATION_IP} proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 0 \ encap espinudp 4500 4500 0.0.0.0 \ auth sha1 ${HASH_KEY} enc aes ${ENCRYPT_KEY} root@left:~# ip xfrm state add src ${DESTINATION_IP} dst ${SOURCE_IP} proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 32 \ encap espinudp 4500 4500 0.0.0.0 \ auth sha1 ${HASH_KEY} enc aes ${ENCRYPT_KEY} root@left:~# ip xfrm policy add src ${SOURCE_IP} dst ${DESTINATION_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport root@left:~# ip xfrm policy add src ${DESTINATION_IP} dst ${SOURCE_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport |
right:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
root@right:~# LEFT_ESPINUDP_PORT="4500" # 这里是left的4500端口被路由器SNAT后的端口,不一定是4500,可以先跳到第三步在left配置好L2TPv3并配置IP,产生流量并抓包查看SNAT后的端口,或者自己想办法获取吧 # 执行第一步所输出的 root@right:~# HASH_KEY=... root@right:~# ENCRYPT_KEY=... root@right:~# ID=... # 设置IP地址变量 root@right:~# SOURCE_IP="192.168.2.2" root@right:~# DESTINATION_IP="1.2.3.2" root@right:~# L2TPV3_PORT="1801" root@right:~# ip xfrm state add src ${SOURCE_IP} dst ${DESTINATION_IP} proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 0 \ encap espinudp 4500 ${LEFT_ESPINUDP_PORT} 0.0.0.0 \ auth sha1 ${HASH_KEY} enc aes ${ENCRYPT_KEY} root@right:~# ip xfrm state add src ${DESTINATION_IP} dst ${SOURCE_IP} proto esp spi ${ID} reqid ${ID} mode transport \ replay-window 32 \ encap espinudp ${LEFT_ESPINUDP_PORT} 4500 0.0.0.0 \ auth sha1 ${HASH_KEY} enc aes ${ENCRYPT_KEY} root@right:~# ip xfrm policy add src ${SOURCE_IP} dst ${DESTINATION_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport root@right:~# ip xfrm policy add src ${DESTINATION_IP} dst ${SOURCE_IP} proto udp sport ${L2TPV3_PORT} dport ${L2TPV3_PORT} \ dir out tmpl src 0.0.0.0 dst 0.0.0.0 proto esp reqid ${ID} mode transport |
3. 配置L2TPv3
在left和right上都执行一样的命令配置L2TPv3:
1 2 3 4 5 6 7 8 9 10 11 |
L2TP_ID="1000" # 如果有多个,就换成不同的ID # 下面的三行变量设置,如果前面使用了StrongSwan,未设置的话这里再设置一次,否则直接跳过 SOURCE_IP="本地的IP" # 本地经NAT,是内网IP就填内网IP,无NAT就是公网IP DESTINATION_IP="对方的IP" # 对方的公网IP L2TPV3_PORT="1801" ip l2tp add tunnel tunnel_id ${L2TP_ID} peer_tunnel_id ${L2TP_ID} \ encap udp local ${SOURCE_IP} remote ${DESTINATION_IP} \ udp_sport ${L2TPV3_PORT} udp_dport ${L2TPV3_PORT} ip l2tp add session name l2tpeth100 tunnel_id ${L2TP_ID} session_id ${L2TP_ID} \ peer_session_id ${L2TP_ID} # name后面的l2tpeth100是虚拟网卡的名称 |
无误的话,在left和right上执行ip link show会看到一个名为l2tpeth100的虚拟网卡:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
root@left:~# ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 ... 9: l2tpeth100: <BROADCAST,MULTICAST> mtu 1404 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 86:d4:ea:0c:6a:dc brd ff:ff:ff:ff:ff:ff root@left:~# ip link set l2tpeth100 up root@left:~# ip addr add 192.168.200.1/24 dev l2tpeth100 root@right:~# ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 ... 24: l2tpeth100: <BROADCAST,MULTICAST> mtu 1404 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 92:85:53:77:f1:b6 brd ff:ff:ff:ff:ff:ff root@right:~# ip link set l2tpeth100 up root@right:~# ip addr add 192.168.200.2/24 dev l2tpeth100 root@right:~# ping 192.168.200.1 PING 192.168.200.1 (192.168.200.1) 56(84) bytes of data. 64 bytes from 192.168.200.1: icmp_seq=1 ttl=64 time=1.54 ms 64 bytes from 192.168.200.1: icmp_seq=2 ttl=64 time=0.940 ms 64 bytes from 192.168.200.1: icmp_seq=3 ttl=64 time=1.00 ms 64 bytes from 192.168.200.1: icmp_seq=4 ttl=64 time=0.982 ms 64 bytes from 192.168.200.1: icmp_seq=5 ttl=64 time=1.22 ms 64 bytes from 192.168.200.1: icmp_seq=6 ttl=64 time=1.22 ms ^C --- 192.168.200.1 ping statistics --- 6 packets transmitted, 6 received, 0% packet loss, time 5002ms rtt min/avg/max/mdev = 0.940/1.153/1.548/0.211 ms root@right:~# arp -an | grep l2tpeth100 ? (192.168.200.1) at 86:d4:ea:0c:6a:dc [ether] on l2tpeth100 |
L2TPv3是附带以太网头的,所以还可以桥接组网。