KCP based VPN
License: Apache License, Version 2.0
本程序仅用于异地组网,不支持其它用途,使用时请严格遵守当地法律
见本项目的Release页
参考:https://github.com/yzsme/kcpvpn/blob/master/build.bash
git clone https://github.com/yzsme/kcpvpn
pushd kcpvpn
bash ./build.bash # or bash ./build.bash "amd64"
ls -l kcpvpn-build
popd
程序使用到tun/tap,默认情况下仅有root用户可调用相关API,设置以下capabilities后通过非root用户启动程序可监听<1024的端口且可调用tun/tap相关API:
setcap cap_net_admin,cap_net_bind_service,cap_net_raw+ep /usr/bin/kcpvpn
user1@server1:~$ kcpvpn server --help
...
--ip value 服务器模式下监听的IP
--port value 服务器模式下监听的端口
--udp-mtu value UDP数据包MTU (default: 1350)
--kcp-mode value KCP模式: fast3, fast2, fast, normal, manual (default: "fast",参见KCPTUN)
--sndwnd value set send window size(num of packets) (default: 1024)
--rcvwnd value set receive window size(num of packets) (default: 1024)
--datashard value, --ds value set reed-solomon erasure coding - datashard (default: 10)
--parityshard value, --ps value set reed-solomon erasure coding - parityshard (default: 3)
--rapid-fec 增加少量流量消耗,提升低pps情况下fec的效果,从而降低丢包对延迟的影响
--dscp value set DSCP(6bit) (default: 0)
--sockbuf value per-socket buffer in bytes (default: 4194304)
--smuxbuf value the overall de-mux buffer in bytes (default: 4194304)
--keepalive value seconds between heartbeats (default: 10)
--secret value 密钥 [$KCPVPN_KEY]
--crypt value 加密算法:aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none (default: "aes")
--local-ip value 本地IP,服务器仅在TUN模式下适用
--netmask value 子网掩码,仅TAP模式下适用
--vni-mtu value tun/tap虚拟网卡MTU (default: 1500)
--full-frame-mtu value decide automatically if is 0 (default: 0)
--vni-name-prefix value 服务器模式下虚拟网卡名称前缀 (default: "kvs")
--vni-mode value 虚拟网卡模式,tun或tap
--assignable-ips value 用于分配给客户端的IP范围,格式如:192.168.0.0/24 or 192.168.0.0-192.168.0.255
--hook-dir value hook存放文件夹
--bridge value 自动把虚拟网卡加入到所指定名称的桥,仅TAP模式适用
--tcp raw socket模拟TCP模式
user1@client1:~$ kcpvpn client --help
...
--ip value 服务器IP
--port value 服务器端口
--udp-mtu value UDP数据包MTU (default: 1350)
--kcp-mode value KCP模式: fast3, fast2, fast, normal, manual (default: "fast",参见KCPTUN)
--sndwnd value set send window size(num of packets) (default: 1024)
--rcvwnd value set receive window size(num of packets) (default: 1024)
--datashard value, --ds value set reed-solomon erasure coding - datashard (default: 10)
--parityshard value, --ps value set reed-solomon erasure coding - parityshard (default: 3)
--rapid-fec 增加少量流量消耗,提升低pps情况下fec的效果,从而降低丢包对延迟的影响
--dscp value set DSCP(6bit) (default: 0)
--sockbuf value per-socket buffer in bytes (default: 4194304)
--smuxbuf value the overall de-mux buffer in bytes (default: 4194304)
--keepalive value seconds between heartbeats (default: 10)
--secret value 密钥 [$KCPVPN_KEY]
--crypt value 加密算法:aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none (default: "aes")
--local-ip value 指定使用的本地IP,客户端在tun和tap模式下均适用
--vni-mtu value tun/tap虚拟网卡MTU (default: 1500)
--client-id value 客户端ID,用以服务器根据客户端ID调用Hook,最大长度不可超过16字节
--vni-name value tun/tap网卡名称
--persistent-vni 持久型tun/tap网卡,即程序退出后,仍然保留
--auto-reconnect 与服务器链接中断后,自动重连
--bridge value 自动把虚拟网卡加入到所指定名称的桥,仅TAP模式适用
--tcp raw socket模拟TCP模式
--on-connected-hook value 客户端与服务器连接成功后调用的hook
...
如果--local-ip在--assignable-ips范围中,程序会自动排除
user1@server1:~$ KCPVPN_KEY="pre-shared-key" kcpvpn server \
--ip 0.0.0.0 \
--port 1235 \
--kcp-mode normal \
--dscp 46 \
--local-ip 192.168.88.0 \
--vni-mode tun \
--assignable-ips 192.168.88.0/24 \
--hook-dir /kcpvpn-hooks
tap模式需要注意分配的IP范围需要手动排除network与broadcast地址,并且指定netmask;一般使用tap模式会有桥接的需求,可用使用--bridge指定一个现有的桥,程序将会自动把创建的tap网卡加入到桥
user1@server1:~$ KCPVPN_KEY="pre-shared-key" kcpvpn server \
--ip 0.0.0.0 \
--port 1235 \
--kcp-mode normal \
--dscp 46 \
--local-ip 192.168.88.1 \
--vni-mode tap \
--assignable-ips 192.168.88.2-192.168.88.254 \
--netmask 255.255.255.0 \
--hook-dir /kcpvpn-hooks \
--bridge br0
除了KCP协议本身的参数外,客户端无需指定VPN的参数,例如VPN模式(tun或tap),MTU等,这些参数都可以通过握手获取
user1@client1:~$ KCPVPN_KEY="pre-shared-key" kcpvpn client \
--client-id client1 \
--ip server1 \
--port 1235 \
--kcp-mode normal \
--dscp 46
如客户端指定使用固定IP,并且指定期望的MTU值(最终会取服务器与客户端中的最小值):
user1@client1:~$ KCPVPN_KEY="pre-shared-key" kcpvpn client \
--client-id client1 \
--ip server1 \
--port 1235 \
--kcp-mode normal \
--dscp 46 \
--vni-mtu 1200 \
--local-ip 192.168.88.255 \
--vni-name kvc_c1
可参考以下systemd的配置文件
user1@server1:~$ cat /etc/systemd/system/kcpvpn-server.service
[Unit]
Description=KCPVPN Server
After=network-online.target
[Service]
Environment="KCPVPN_KEY=pre-shared-key"
Type=simple
User=nobody
ExecStart=/usr/bin/kcpvpn server --ip 0.0.0.0 --port 1235 --udp-mtu 1480 --kcp-mode fast3 --crypt xor --vni-mode tun --vni-mtu 1400 --local-ip 192.168.88.0 --assignable-ips 192.168.88.0/24
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
user1@server1:~$ systemctl daemon-reload
user1@server1:~$ systemctl enable kcpvpn-server
user1@server1:~$ systemctl start kcpvpn-server
user1@client1:~$ cat /etc/systemd/system/kcpvpn-client.service
[Unit]
Description=KCPVPN Server
After=network-online.target
[Service]
Environment="KCPVPN_KEY=pre-shared-key"
Type=simple
User=nobody
ExecStart=/usr/bin/kcpvpn server --ip server1 --port 1235 --udp-mtu 1480 --kcp-mode fast3 --crypt xor --persistent-vni --auto-reconnect
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
user1@client1:~$ systemctl daemon-reload
user1@client1:~$ systemctl enable kcpvpn-client
user1@client1:~$ systemctl start kcpvpn-client
在服务器模式下,可通过--hook-dir指定hook存放的目录;客户端模式可通过--on-connected-hook指定连接成功后的hook;Hook可以是Script,也可以是二进制可执行程序。
当客户端与服务器握手成功后,服务器会检查客户端是否有传递client id,如果有,检查目录下是否存在文件on_CLIENTID_connected文件,存在则运行(例如客户端传递的Client ID为client1,则hook文件名为on_client1_connected)。
若文件不存在或者客户端没有传递client id,则检查文件on_connected是否存在,存在则调用
服务器通过以下环境变量给Hook程序传递信息:
- KV_CLIENT_ID: 客户端ID
- KV_VNI_INTERFACE_NAME: 客户端的虚拟网卡名称
- KV_CLIENT_IP: 客户端使用的IP,注意如果是0.0.0.0表示没有给客户端分配IP,客户端也没有指定使用特定IP
- KV_CLIENT_IP_MODE:客户端的IP分配模式
- KV_REMOTE_ADDR:客户端连接服务器使用的IP地址与端口
tap模式附带以太网头,可直接通过桥接进行异地组网。
# 客户端连接后,服务器会出现kvs(可通过--vni-name-prefix更改)开头的TAP网卡
user1@server1:~$ ip link show
...
8: kvs0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN mode DEFAULT group default qlen 1000
link/ether de:0b:f7:84:b5:a9 brd ff:ff:ff:ff:ff:ff
...
# 创建桥接网卡
user1@server1:~$ brctl addbr kvsbr0
user1@server1:~$ ip link set kvsbr0 up
user1@server1:~$ ip addr add 192.168.88.1/24 dev kvsbr0
# 把kvs网卡加入桥接
user1@server1:~$ brctl addif kvsbr0 kvs0
user1@server1:~$ brctl show kvsbr0
bridge name bridge id STP enabled interfaces
kvsbr0 8000.de0bf784b5a9 no kvs0
user1@server1:~$ ip addr show
...
8: kvs0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master kvsbr0 state UNKNOWN group default qlen 1000
link/ether de:0b:f7:84:b5:a9 brd ff:ff:ff:ff:ff:ff
inet6 fe80::dc0b:f7ff:fe84:b5a9/64 scope link
valid_lft forever preferred_lft forever
9: kvsbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether de:0b:f7:84:b5:a9 brd ff:ff:ff:ff:ff:ff
inet 192.168.88.1/24 scope global kvsbr0
valid_lft forever preferred_lft forever
inet6 fe80::4e0:28ff:fe79:4663/64 scope link
valid_lft forever preferred_lft forever
...
user1@client1:~$ ip addr show
...
4: kvc0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether b6:ec:1d:b3:e6:90 brd ff:ff:ff:ff:ff:ff
inet 192.168.88.2/24 brd 192.168.88.255 scope global kvc0
valid_lft forever preferred_lft forever
inet6 fe80::b4ec:1dff:feb3:e690/64 scope link
valid_lft forever preferred_lft forever
...
user1@client1:~$ ping 192.168.88.1
PING 192.168.88.1 (192.168.88.1) 56(84) bytes of data.
64 bytes from 192.168.88.1: icmp_seq=1 ttl=64 time=2.76 ms
64 bytes from 192.168.88.1: icmp_seq=2 ttl=64 time=0.881 ms
64 bytes from 192.168.88.1: icmp_seq=3 ttl=64 time=1.88 ms
64 bytes from 192.168.88.1: icmp_seq=4 ttl=64 time=1.75 ms
64 bytes from 192.168.88.1: icmp_seq=5 ttl=64 time=1.88 ms
^C
--- 192.168.88.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4017ms
rtt min/avg/max/mdev = 0.881/1.834/2.764/0.599 ms
user1@client1:~$ cat /proc/net/arp | grep 192.168.88.1
192.168.88.1 0x1 0x2 de:0b:f7:84:b5:a9 * kvc0
-
能否关闭服务器的IP分配功能?
去除--assignable-ips参数即可,这样客户端将可以指定使用任何IP,tap模式下服务器也可使用DHCP为客户端分配IP
-
服务器通过非root权限运行,hook是通过非root身份调用的,导致hook无法调用root才能调用的接口
如果hook是script,考虑通过sudo的NOPASSWD实现,如果hook是二进制可执行程序,还可通过capability或者SUID实现,因此请确保hook目录仅KCPVPN所运行的用户拥有访问权限!
-
如何提高吞吐量?
程序默认使用的虚拟网卡MTU是1500,如果传输大量数据,程序需要频繁调用内核API,因此可尝试添加以下参数启用jumbo frame,把MTU设至9000:
--vni-mtu 9000
9000 / 1500 = 6,这意味着每个数据包可最多减少五次相关的系统调用
-
虚拟网卡MTU小于1500导致部分TCP服务无法正常使用
请使用iptables修改SYN包的MSS值,取值一般为虚拟网卡的MTU - 40,例如--vni-mtu 1450,MSS取值1410:
# -i与-o后面的“kvs”是虚拟网卡名称前缀,即--vni-name-prefix参数所指定的值 iptables -t mangle -A FORWARD -i kvs+ -p tcp --syn -j TCPMSS --set-mss MSS值 iptables -t mangle -A FORWARD -o kvs+ -p tcp --syn -j TCPMSS --set-mss MSS值
-
Rapid FEC?
通过--rapid-fec参数可以启用此模式,会增加少许带宽消耗,但可大幅度降低低pps情况下丢包对延迟的影响。效果如下图,左上未启用,右上已启用,下方裸线路: