Click Here for English Version
但凡使用過三大業者的家用寬頻,並且需要家寬互聯,那麼幾乎都會體驗到UDP 被限速的情況。為了躲避三大業者針對UDP 的QoS,我製作了另一個工具,叫做UDP Hop。原理是定期建立新連線(更換連接埠號碼及連接到新位址)。
只不過,UDP Hop 只支援轉送UDP 流量。為了能夠利用UDP 轉送TCP 流量,因此就有了KCP Tube。利用KCP 的可靠重傳保證轉送的TCP 不會丟包。
製作KCP Tube 的另一個原因是,其它KCP 轉送工具只能轉送TCP 流量,但我又需要用KCP 轉送UDP 流量。主要是為了方便玩遊戲。
當然了,其實udphop 以及kcptube 都是同時構想出來的。所以為了方便起見,先做好了KCP Tube 搭好框架,接著再在KCP Tube 的基礎上裁剪成UDP Hop。然後再把UDP Hop 的修補程式碼反向合併回KCP Tube。
為了方便家寬Full Cone NAT 用戶使用,KCP Tube 以服務端基本模式運作的時候可以利用STUN 打洞,同時支援IPv4 與IPv6。
正如KCP 本身的用途一樣,KCP Tube 的主要目標是降低延遲,而不是偏向傳輸超大流量。那麼能不能傳輸超大流量呢?能,只是效果未必比得上現有的TCP-KCP 轉送工具。
目前支援3 種模式:
注意,客戶端的時間與服務端的時間務必同步,時間相差不能大於255 秒。
請前往Wiki 頁面,或前往文件頁面。
若需要設定檔產生器,請前往此處:KCPTube Generator
kcptube config.conf
客戶端模式範例:
mode=client
kcp=regular3
inbound_bandwidth=500M
outbound_bandwidth=50M
listen_port=59000
destination_port=3000
destination_address=123.45.67.89
encryption_password=qwerty1234
encryption_algorithm=AES-GCM
服務端模式範例:
mode=server
kcp=regular3
inbound_bandwidth=1G
outbound_bandwidth=1G
listen_port=3000
destination_port=59000
destination_address=::1
encryption_password=qwerty1234
encryption_algorithm=AES-GCM
stun_server=stun.qq.com
log_path=./
備註:初次連線時,服務端會向客戶端告知自己的連接埠範圍,因此用戶端模式的listen_port
不一定要等於服務端模式的destination_port
,兩邊的連接埠可以不一致,但用戶端所寫的連接埠號碼範圍不能超出服務端的範圍,以免客戶端選錯連接埠連線不上。
如果要指定偵聽的網路卡,那就指定該網卡的IP 位址,加一行即可
listen_on=192.168.1.1
如果想要偵聽多個連接埠、多個網路卡,那就分開多個設定檔
kcptube config1.conf config2.conf
如果您想在連線前測試一下連線是否暢通,可以加上--try
選項
kcptube --try config1.conf
或
kcptube config1.conf --try
使用--check-config
選項即可驗證設定檔是否正確:
kcptube --check-config config1.conf
或
kcptube config1.conf --check-config
客戶端模式範例:
mode=client
kcp=regular3
inbound_bandwidth=500M
outbound_bandwidth=50M
listen_port=6000
destination_port=3000-4000
destination_address=123.45.67.89
dport_refresh=600
encryption_password=qwerty1234
encryption_algorithm=AES-GCM
服務端模式範例:
mode=server
kcp=regular3
inbound_bandwidth=1G
outbound_bandwidth=1G
listen_port=3000-4000
destination_port=6000
destination_address=::1
encryption_password=qwerty1234
encryption_algorithm=AES-GCM
客戶端模式範例:
mode=client
kcp=manual
kcp_mtu=1400
kcp_sndwnd=512
kcp_rcvwnd=2048
kcp_nodelay=1
kcp_interval=10
kcp_resend=2
kcp_nc=true
udp_timeout=300
listen_port=6000
destination_port=3000-4000
destination_address=123.45.67.89
dport_refresh=600
encryption_password=qwerty1234
encryption_algorithm=AES-GCM
服務端模式範例:
mode=server
kcp=manual
kcp_mtu=1400
kcp_sndwnd=512
kcp_rcvwnd=2048
kcp_nodelay=1
kcp_interval=10
kcp_resend=2
kcp_nc=true
udp_timeout=300
listen_port=3000-4000
destination_port=6000
destination_address=::1
encryption_password=qwerty1234
encryption_algorithm=AES-GCM
名稱 | 可設定值 | 必填 | 備註 |
---|---|---|---|
mode | client server relay | 是 | 客戶端服務端中繼節點 |
listen_on | 網域名稱或IP 位址 | 否 | 只能填寫網域名稱或IP 位址。多個地址請用逗號分隔 |
listen_port | 1 - 65535 | 是 | 以服務端運作時可以指定連接埠範圍 |
destination_port | 1 - 65535 | 是 | 以客戶端運作時可以指定連接埠範圍。多個地址請用逗號分隔 |
destination_address | IP位址、網域名稱 | 是 | 填入IPv6 位址時不需要中括號 |
dport_refresh | 0 - 32767 | 否 | 單位“秒”。不填寫表示使用預設值60 秒。 1 至20 按20 秒算,大於32767 按32767 秒算。 設為0 表示停用。 |
encryption_algorithm | AES-GCM AES-OCB chacha20 xchacha20 none | 否 | AES-256-GCM-AEAD AES-256-OCB-AEAD ChaCha20-Poly1305 XChaCha20-Poly1305 不加密 |
encryption_password | 任意字元 | 視情況 | 設定了encryption_algorithm 且不為none 時必填 |
udp_timeout | 0 - 65535 | 否 | 單位“秒”。預設值180 秒,設為0 則使用預設值該選項表示的是,UDP 應用程式↔ kcptube 之間的逾時設定 |
keep_alive | 0 - 65535 | 否 | 單位“秒”。預設值為0,等於停用Keep Alive 此選項是指兩個KCP端之間的Keep Alive 可單方面啟用,用於偵測通道是否停止回應。若超過30秒仍未回應,就關閉通道。 |
mux_tunnels | 0 - 65535 | 否 | 預設值為0,等於不使用多工通道此選項是指兩個KCP端之間的多工通道數僅限客戶端啟用 |
stun_server | STUN 伺服器位址 | 否 | listen_port 為連接埠範圍模式時不可使用 |
log_path | 存放Log 的目錄 | 否 | 不能指向文件本身 |
fec | uint8:uint8 | 否 | 格式為fec=D:R ,例如可填入fec=20:3 。注意:D + R 的總數最大值為255,不能超過這個數。 冒號兩側任一個值為0 表示不使用該選項。兩端的設定必須相同。 詳情請參考FEC使用介紹 |
mtu | 正整數 | 否 | 目前網路MTU 數值,用以自動計算kcp_mtu |
kcp_mtu | 正整數 | 否 | 預設值1440。呼叫ikcp_setmtu() 設定的值,亦即UDP 封包內資料內容的長度 |
kcp | manual fast1 - 6 regular1 - 5 | 是 | 手動設定快速常速 (末尾數字:數值越小,速度越快) |
kcp_sndwnd | 正整數 | 否 | 預設值見下表,可單獨覆寫 |
kcp_rcvwnd | 正整數 | 否 | 預設值見下表,可單獨覆寫 |
kcp_nodelay | 正整數 | 視情況 | kcp=manual 時必填,預設值請見下表 |
kcp_interval | 正整數 | 視情況 | kcp=manual 時必填,預設值請見下表 |
kcp_resend | 正整數 | 視情況 | kcp=manual 時必填,預設值請見下表 |
kcp_nc | yes true 1 no false 0 | 視情況 | kcp=manual 時必填,預設值請見下表 |
outbound_bandwidth | 正整數 | 否 | 出站頻寬,用於通訊過程中動態更新kcp_sndwnd 的值 |
inbound_bandwidth | 正整數 | 否 | 入站頻寬,用於通訊過程中動態更新kcp_rcvwnd 的值 |
ipv4_only | yes true 1 no false 0 | 否 | 若係統停用了IPv6,須啟用該選項並設為yes 或true 或1 |
ipv6_only | yes true 1 no false 0 | 否 | 忽略IPv4 位址 |
blast | yes true 1 no false 0 | 否 | 嘗試忽略KCP 流控設置,盡可能迅速地轉送封包。可能會導致負載過大 |
[listener] | N/A | 是 (僅限中繼模式) | 中繼模式的標籤,用於指定監聽模式的KCP 設定該標籤表示與客戶端互動數據 |
[forwarder] | N/A | 是 (僅限中繼模式) | 中繼模式的標籤,用於指定轉運模式的KCP 設定該標籤表示與服務端互動數據 |
[custom_input] | N/A | 否 | 自訂映射模式的標籤,使用方法請參考自訂映射使用方法 |
[custom_input_tcp] | N/A | 否 | 自訂映射模式的標籤,使用方法請參考自訂映射使用方法 |
[custom_input_udp] | N/A | 否 | 自訂映射模式的標籤,使用方法請參考自訂映射使用方法 |
其中, encryption_algorithm
以及encryption_password
在通訊的兩端必須保持一致。
名稱 | 可設定值 | 必填 | 備註 |
---|---|---|---|
fib_ingress | 0 - 65535 | 否 | 入站連線使用的FIB |
fib_egress | 0 - 65535 | 否 | 出站連線使用的FIB |
可用字尾:K / M / G
後綴區分大小寫,大寫以二進位(1024) 計算,小寫以十進位(1000) 計算。
填入1000,表示頻寬為1000 bps
填入100k,表示頻寬為100 kbps (100000 bps)
填入100K,表示頻寬為100 Kbps (102400 bps)
填入100M,表示頻寬為100 Mbps (102400 Kbps)
填入1G,表示頻寬為1 Gbps (1024 Mbps)
注意,是bps (Bits Per Second),不是Bps (Bytes Per Second)。
需要提醒的是,填寫的頻寬值不應超出實際頻寬,以免造成發送視窗擁塞導致阻塞。
重要提示:
KCPTube 會在KCP 連結建立後的5 秒左右,根據握手包的延遲值以及outbound_bandwidth 與inbound_bandwidth 的數值,計算並設定KCP 的傳送視窗大小。設定完成後的一段時間內,有很大機率出現流量大幅波動的情況,甚至會出現流量突然降至0,需要好幾秒才能恢復。
快速模式 | kcp_sndwnd | kcp_rcvwnd | kcp_nodelay | kcp_interval | kcp_resend | kcp_nc |
---|---|---|---|---|---|---|
fast1 | 2048 | 2048 | 1 | 1 | 2 | 1 |
fast2 | 2048 | 2048 | 2 | 1 | 2 | 1 |
fast3 | 2048 | 2048 | 1 | 1 | 3 | 1 |
fast4 | 2048 | 2048 | 2 | 1 | 3 | 1 |
fast5 | 2048 | 2048 | 1 | 1 | 4 | 1 |
fast6 | 2048 | 2048 | 2 | 1 | 4 | 1 |
常速模式 | kcp_sndwnd | kcp_rcvwnd | kcp_nodelay | kcp_interval | kcp_resend | kcp_nc |
---|---|---|---|---|---|---|
regular1 | 1024 | 1024 | 1 | 1 | 5 | 1 |
regular2 | 1024 | 1024 | 2 | 1 | 5 | 1 |
regular3 | 1024 | 1024 | 0 | 1 | 2 | 1 |
regular4 | 1024 | 1024 | 0 | 15 | 2 | 1 |
regular5 | 1024 | 1024 | 0 | 30 | 2 | 1 |
其中,丟包率越高(高於10%),kcp_nodelay=1 就比kcp_nodelay=2 越有優勢。在丟包率不特別高的情況下,kcp_nodelay=2 可使延遲抖動更為平滑。
對於低丟包環境,每個模式都適合使用,差異只在於浪費的流量是多還是少,以及最高速的上限有所不同。其中regular3 浪費的流量沒那麼多。
建議同時開啟blast=1
設定。
對於高丟包環境,請考慮疊加使用FEC 設定。詳情請參考FEC使用介紹
更多詳解,請見參數列表。
在首次取得打洞後的IP 位址與連接埠後,以及打洞的IP 位址與連接埠變更後,會向Log 目錄建立ip_address.txt 檔案(若存在就覆蓋),將IP 位址與連接埠寫進去。
取得到的打洞位址會同時顯示在控制台當中。
log_path=
必須指向目錄,不能指向檔案本身。
如果不需要寫入Log 文件,那就刪除log_path
這一行。
從NatTypeTeste找到的普通STUN 伺服器:
從Natter找到的STUN 伺服器:
其它STUN 伺服器:public-stun-list.txt
為了方便使用,目前已經提供了多個平台的二進位可執行檔:
預編譯的二進位檔全部都是靜態編譯。 Linux 版本基本上都是靜態編譯,但libc 除外,因此準備了兩個版本,一個用於glibc (2.31),另一個用於musl。
對於Linux 環境,另有提供Docker 映像(目前僅限x64),下載kcptube_docker_image.zip 並解壓縮,再使用docker load -i kcptube_docker.tar
導入。
導入後,使用方式為:
docker run -v /path/to/config_file.conf:/config_file.conf kcptube config_file.conf
例如:
docker run -v /home/someone/config1.conf:/config1.conf kcptube config1.conf
FreeBSD 使用者可將下載好的二進位檔案複製到/usr/local/bin/
,然後執行命令
chmod +x /usr/local/bin/kcptube
本項目的service
目錄已經準備好相應服務文件。
/usr/local/etc/rc.d/
chmod +x /usr/local/etc/rc.d/kcptube
/usr/local/etc/kcptube/
config.conf
/usr/local/etc/kcptube/config.conf
/etc/rc.conf
加一行kcptube_enable="YES"
最後,執行service kcptube start
即可啟動服務
編譯器須支援C++20
依賴函式庫:
請事先使用vcpkg 安裝依賴套件asio
與botan
,指令如下:
vcpkg install asio:x64-windows asio:x64-windows-static
vcpkg install botan:x64-windows botan:x64-windows-static
(如果需要ARM 或32 位元x86 版本,請自行調整選項)
然後用Visual Studio 開啟slnkcptube.sln
自行編譯
同樣,請先安裝依賴項asio 以及botan3,另外還需要cmake,用系統自備pkg 即可安裝:
pkg install asio botan3 cmake
接著在build 目錄中構建
mkdir build
cd build
cmake ..
make
步驟與FreeBSD 類似,NetBSD 請使用pkgin 安裝依賴項與cmake:
pkgin install asio
pkgin install cmake
OpenBSD 請使用pkg_add
安裝上述兩個相依性。 DragonflyBSD 請使用pkg
,用法與FreeBSD 相同。
由於botan-3 仍未被這幾個BSD 系統收錄,須自行編譯botan-3。
剩餘的建置步驟請參考上述的FreeBSD。
請注意,由於這幾個BSD 自帶的編譯器版本較低,請事先額外安裝高版本GCC。
步驟與FreeBSD 類似,請用發行版自備的套件管理器安裝asio 與botan3 以及cmake。
apk add asio botan3-libs cmake
接著在build 目錄當中構建
mkdir build
cd build
cmake ..
make
有兩種做法
做法1
依照正常流程編譯好,刪除剛產生的kcptube 二進位文件,並執行指令
make VERBOSE=1
再從輸出的內容提取出最後一條C++ 連結指令,把中間的-lbotan-3
改成libbotan-3.a 的完整路徑,例如/usr/lib/x86_64-linux-gnu/libbotan-3.a
。
做法2
開啟src/CMakeLists.txt,把target_link_libraries(${PROJECT_NAME} PRIVATE botan-3)
改成target_link_libraries(${PROJECT_NAME} PRIVATE botan-3 -static)
然後即可正常編譯。請注意,如果系統使用glibc 的話,這樣會連同glibc 一併靜態編譯,從而會跳出有關getaddrinfo 的警告。
我沒蘋果電腦,所有步驟請自行解決。
增加接收快取可以改善UDP 傳輸效能
可以使用指令sysctl kern.ipc.maxsockbuf
查看快取大小。如果需要調整,請執行指令(數字改為想要的數值):
sysctl -w kern.ipc.maxsockbuf=33554434
或在/etc/sysctl.conf
寫入
kern.ipc.maxsockbuf=33554434
可以使用指令sysctl net.inet.udp.recvspace
查看接收快取大小。如果需要調整,請執行指令(數字改為想要的數值):
sysctl -w net.inet.udp.recvspace=33554434
或在/etc/sysctl.conf
寫入
net.inet.udp.recvspace=33554434
如有需要,可同時調整net.inet.udp.sendspace
的數值。這是發送快取的設定。
對於接收快取,可以使用指令sysctl net.core.rmem_max
及sysctl net.core.rmem_default
查看接收快取大小。
如果需要調整,請執行指令(數字改為想要的數值):
sysctl -w net.core.rmem_max=33554434
sysctl -w net.core.rmem_default=33554434
或在/etc/sysctl.conf
寫入
net.core.rmem_max=33554434
net.core.rmem_default=33554434
如有需要,可同時調整net.core.wmem_max
及net.core.wmem_default
的數值。這是發送快取的設定。
由於kcptube 內部使用的是IPv6 單棧+ 開啟IPv4 映射位址(IPv4-mapped IPv6)來同時使用IPv4 與IPv6 網絡,因此請確保v6only 選項的值為0。
正常情況下不需要任何額外設置,FreeBSD 與Linux 以及Windows 都預設允許IPv4 位址映射到IPv6。
如果系統不支援IPv6,或停用了IPv6,請在設定檔中設定ipv4_only=true,這樣kcptube 會退回到使用IPv4 單棧模式。
使用命令
sysctl -w net.inet6.ip6.v6only=0
設定後,單棧+映射位址模式可以偵聽雙棧。
但由於未知的原因,可能無法主動連接IPv4 映射位址。
因為OpenBSD 徹底屏蔽了IPv4 映射位址,所以在OpenBSD 平台使用雙堆疊的話,需要將設定檔儲存成兩個,其中一個啟用ipv4_only=1,然後在使用kcptube 時同時載入兩個設定檔。
大多數情況下,這種提示只會在伺服器端遇到,不會在客戶端遇到。
如果確實在客戶端遇到了,請檢查mux_tunnels
的數值是否過高(請順便參考「多路復用(mux_tunnels=N)」段落)。
一般情況下,絕大多數BSD 系統都不會遇到這種事,只有2023 年下半年更新後的GhostBSD 才會遇到這種現象。
這是因為GhostBSD 在/etc/sysctl.conf
當中加了這一行:
kern.maxfiles=100000
這一行縮減了上限,遠低於原始FreeBSD 的對應數值。
解決辦法很簡單,刪掉這一行即可。註解掉也可以。
也可以使用指令sysctl kern.maxfiles=300000
暫時修改上限值。
由於Linux 系統的Open Files 數量限制為1024,所以很容易就會遇到這種問題。
臨時解決辦法:
ulimit -n
,查看輸出的數值ulimit -n 300000
永久解決方法:
編輯/etc/security/limits.conf,在末尾加上
* hard nofile 300000
* soft nofile 300000
root hard nofile 300000
root soft nofile 300000
由於需要傳送TCP 數據,因此數據校驗是不可忽略的,正如TCP 本身。
無論是否加密,kcptube 都會將MTU 縮小2 個位元組,尾附2 個位元組的資料。
如果已經使用了加密選項,那麼尾附的2 位元組資料就是臨時產生的IV。
如果選擇不使用加密功能,那麼尾附的2 位元組資料就是校驗碼,是由CRC32 高低位異或而成。
需要提醒的是,使用校驗碼仍然無法100% 避免內容錯誤,TCP 本身也是一樣。如果確實需要精確無誤,請啟用加密選項。
KCP Tube 雖然有「多工」的功能,但預設並不主動開啟。在不使用該功能的情況下,每接受一個入站連接,就會建立一個對應的出站連接。
原因是為了躲避業者的QoS。多路復用狀態下,一旦某個連接埠號碼被QoS,就會導致共用連接埠號碼的其它會話同時受阻,直到更換連接埠號碼為止。
連接之間相互獨立,即使某個連接埠號碼被QoS,受影響的僅只是這一路會話,不影響其他會話。
除非被承載的程式會產生大量獨立連線。在這種情況下,KCP Tube 會建立大量KCP 通道,在通訊過程中會消耗較多的CPU資源。
如果確實要用「多重化」功能,可以參考以下分類:
適合使用「多路復用」的場景:
不必使用「多路復用」的場景:
啟用「多路復用」後,KCPTube 會預先建立N 條連結,所有入站新連線都會從已有連結傳送資料,而不再單獨建立新連結。此時KCP 通道的超時時間為30 秒。
一般來說,`mux_tunnels 設定成3 ~ 10 就夠用了,不需要設定過高的數值。
為了降低延遲,kcptube 啟用了TCP_NODELAY 選項。對於某些大流量應用場景,可能會造成TCP 資料傳輸量減少。
在原版KCP 的基礎上,作了以下修改:
flush()
是先把待發送資料轉移到發送佇列後,在同一個迴圈重做完「傳送新資料包」、「封包重發」、「ACK包發送」三件事。修改後的版本變為先做“資料包重發”、“ACK包發送”,然後再做“待發送資料轉移到發送佇列”,在轉移期間順便發送。check()
每次都會重新遍歷一遍發送佇列,尋找已到點的重傳時間戳記。修改後的版本變成:從已經排好序的映射表讀取第一個時間戳,免除查找步驟。除此之外,原版的存在“bug”,kcptube 也會有。例如:
於是kcptube 設定了較明顯的暫停方案。對於TCP 數據,在達到接收限制時(佇列滿額),會暫停接收TCP 數據,直到有空位再恢復;對於UDP 數據,在達到接收限制時就直接丟包。
這個限制對於傳輸量不大的應用場景基本上不會造成影響。
kcptube 使用的執行緒池來自於BS::thread_pool,另外再做了些許修改,用於多連接時的平行加解密處理。
程式碼寫得很隨意,想到哪寫到哪,因此版面混亂。準確來說,是十分混亂。
其中有些程式碼行長得像竹竿,主要是寫的時候為了順著思路所以懶得換行。畢竟我又不用vim / emacs。當我用IDE 時,IDE 的程式碼區設定的文字大小不同於其他區域的文字大小,甚至字體都不一樣,幫我緩解了混亂問題。
至於閱讀者的感受嘛…… 那一定會不爽。不關我事,不管了。