From ef8e2f6f8a240abcefb03267b61c05f525544c80 Mon Sep 17 00:00:00 2001 From: cnbatch Date: Tue, 3 Oct 2023 01:16:21 +0800 Subject: [PATCH] Security Update and KCP Performance Update --- README.md | 58 +- README_EN.md | 58 +- docs/mtu_en.md | 25 + docs/mtu_zh-hans.md | 24 + docs/parameters_en.md | 11 +- docs/parameters_zh-hans.md | 10 +- sln/kcptube/kcptube.vcxproj | 4 +- sln/kcptube/kcptube.vcxproj.filters | 4 +- src/3rd_party/CMakeLists.txt | 2 +- src/3rd_party/ikcp.c | 1319 --------------------------- src/3rd_party/ikcp.cpp | 1283 ++++++++++++++++++++++++++ src/3rd_party/ikcp.h | 417 --------- src/3rd_party/ikcp.hpp | 217 +++++ src/main.cpp | 2 +- src/networks/client.cpp | 17 +- src/networks/connections.cpp | 36 +- src/networks/kcp.cpp | 140 +-- src/networks/kcp.hpp | 29 +- src/networks/relay.cpp | 14 +- src/networks/server.cpp | 14 +- src/shares/aead.hpp | 80 +- src/shares/configurations.cpp | 35 +- src/shares/data_operations.cpp | 205 +++-- src/shares/data_operations.hpp | 4 - src/shares/share_defines.hpp | 13 +- 25 files changed, 2024 insertions(+), 1997 deletions(-) create mode 100644 docs/mtu_en.md create mode 100644 docs/mtu_zh-hans.md delete mode 100644 src/3rd_party/ikcp.c create mode 100644 src/3rd_party/ikcp.cpp delete mode 100644 src/3rd_party/ikcp.h create mode 100644 src/3rd_party/ikcp.hpp diff --git a/README.md b/README.md index 2f014df..794a518 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ encryption_algorithm=AES-GCM | stun_server | STUN 服务器地址 |否|listen_port 为端口范围模式时不可使用| | log_path | 存放 Log 的目录 |否|不能指向文件本身| | kcp_mtu | 正整数 |否|预设值1440| +| kcp_conserve | yes
true
1
no
false
0 |否|使“快速ACK”稍微缓和| | kcp | manual
fast1 - 6
regular1 - 5
  |是|手动设置
快速
常速
(末尾数字:数值越小,速度越快)| | kcp_sndwnd | 正整数 |否|预设值见下表,可以单独覆盖| | kcp_rcvwnd | 正整数 |否|预设值见下表,可以单独覆盖| @@ -218,13 +219,14 @@ encryption_algorithm=AES-GCM | ---- | :----: | :----: | :----: | :----: | :----: |:----: | | 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 | 1 | 3 | 1 | -| regular5 | 1024 | 1024 | 0 | 1 | 0 | 1 | +| regular3* | 1024 | 1024 | 1 | 1 | 5 | 1 | +| regular4* | 1024 | 1024 | 2 | 1 | 5 | 1 | +| regular5* | 1024 | 1024 | 0 | 1 | 3 | 1 | 其中,丢包率越高(高于 10%),kcp_nodelay=1 就比 kcp_nodelay=2 越有优势。在丢包率不特别高的情况下,kcp_nodelay=2 可使延迟抖动更为平滑。 -如果想减少流量浪费、不介意延迟稍微增加,可以选择 regular 模式。需要注意的是,高延迟并且丢包率高于 4% 的环境下使用 regular4 或 regular5 可能会遇到 TCP 断流的情况。 +如果想减少流量浪费、不介意延迟稍微增加,可以选择 regular 模式。
+标记了星号的模式 (regular3 ~ 5) 启用了 kcp_conserve 选项,丢包造成的延迟稍微高一些,浪费的流量稍微少一点。 ### Log 文件 在首次获取打洞后的 IP 地址与端口后,以及打洞的 IP 地址与端口发生变化后,会向 Log 目录创建 ip_address.txt 文件(若存在就覆盖),将 IP 地址与端口写进去。 @@ -397,6 +399,36 @@ make 如果系统不支持 IPv6,或者禁用了 IPv6,请在配置文件中设置 ipv4_only=true,这样 kcptube 会退回到使用 IPv4 单栈模式。 ## 其它注意事项 +### 多种系统都遇到的 Too Many Open Files +#### GhostBSD +一般情况下,绝大多数 BSD 系统都不会遇到这种事,只有 2023 年下半年更新后的 GhostBSD 才会遇到这种现象。 + +这是因为 GhostBSD 在 `/etc/sysctl.conf` 当中加了这一行: +``` +kern.maxfiles=100000 +``` +这一行缩减了上限,远低于原版 FreeBSD 的对应数值。 + +解决办法很简单,删掉这一行即可。注释掉也可以。
+还可以使用命令 `sysctl kern.maxfiles=300000` 临时修改上限值。 + +#### Linux +由于 Linux 系统的 Open Files 数量限制为 1024,所以很容易会遇到这种问题。 + +临时解决办法: +1. 运行命令 `ulimit -n`,查看输出的数值 +2. 如果数值确实只有 1024,请运行命令 `ulimit -n 300000` + +永久解决办法:
+编辑 /etc/security/limits.conf,在末尾加上 + +``` +* hard nofile 300000 +* soft nofile 300000 +root hard nofile 300000 +root soft nofile 300000 +``` + ### NetBSD 使用命令 ``` @@ -409,17 +441,20 @@ sysctl -w net.inet6.ip6.v6only=0 ### OpenBSD 因为 OpenBSD 彻底屏蔽了 IPv4 映射地址,所以在 OpenBSD 平台使用双栈的话,需要将配置文件保存成两个,其中一个启用 ipv4_only=1,然后在使用 kcptube 时同时载入两个配置文件。 -## 数据校验 +## 加密与数据校验 由于需要传送 TCP 数据,因此数据校验是不可忽略的,正如 TCP 本身那样。 -如果已经使用了加密选项,那么就可以忽略本节内容。kcptube 选择的加解密算法已经附带验证能力,可以顺便保证传送内容不出错。 +无论是否加密,kcptube 都会将 MTU 缩小 2 个字节,尾附 2 字节的数据。 + +如果已经使用了加密选项,那么尾附的 2 字节数据就是临时生成的IV。 -如果选择不使用加密功能,那么 kcptube 就会将 MTU 缩小 2 个字节,以便尾附 2 字节的校验码。 +如果选择不使用加密功能,那么尾附的 2 字节数据就是校验码,分别为两种 8-bit 校验码: -然而 kcptube 使用的 Botan 库并不附带 16-bit 校验算法,因此 kcptube 同时使用了两种 8-bit 校验码: - 纵向冗余校验 (LRC, Longitudinal Redundancy Check) - 8-bit checksum +这是因为 kcptube 使用的 Botan 库并不附带 16-bit 校验算法,因此 kcptube 同时使用了这两种 8-bit 校验码。 + 这两种校验码的计算速度都足够快,简明又实用,并不是偏门的计算方式。例如 Modbus 就用到了 LRC。 需要提醒的是,使用两种校验码仍然无法 100% 避免内容错误,TCP 本身也是一样。如果确实需要精确无误,请启用加密选项。 @@ -450,10 +485,11 @@ KCP Tube 虽然有“多路复用”的功能,但默认并不主动打开。 为了降低延迟,kcptube 启用了 TCP_NODELAY 选项。对于某些大流量应用场景,可能会造成 TCP 数据传输量减少。 ### KCP -kcptube 用的是原版 [KCP](https://github.com/skywind3000/kcp),作了些许修改: +在原版 [KCP](https://github.com/skywind3000/kcp) 的基础上,作了以下修改: -1. 原版的 `flush()` 是先把待发送数据转移到发送队列后,在同一个循环重做完“发送新数据包”、“数据包重发”、“ACK包发送”三件事。修改后的版本变为先做“数据包重发”、“ACK包发送”,然后再做“待发送数据转移到发送队列”,在转移期间顺便发送。 -2. 原版的 `check()` 每次都会重新遍历一遍发送队列,查找已到点的重传时间戳。修改后的版本变为直接在KCP结构体中新增 `min_resendts` 变量,该变量在 `flush()` 的发送循环当中顺便找出最小的时间戳,`check()` 就不再需要每次都重新遍历,直接读取 `min_resendts` 变量的值即可。 +1. 原版的“已发送数据包缓存”采用的是队列,修改后的版本改成使用 std::map,共三个映射表:总队列,按包号排序;两个待重发队列,一个按时间排序,另一个按丢包跳跃次数排序。 +2. 原版的 `flush()` 是先把待发送数据转移到发送队列后,在同一个循环重做完“发送新数据包”、“数据包重发”、“ACK包发送”三件事。修改后的版本变为先做“数据包重发”、“ACK包发送”,然后再做“待发送数据转移到发送队列”,在转移期间顺便发送。 +3. 原版的 `check()` 每次都会重新遍历一遍发送队列,查找已到点的重传时间戳。修改后的版本变为:从已经排好序的映射表读取第一个时间戳,免除查找步骤。 除此之外,原版的存在“bug”,kcptube 也会有。例如: diff --git a/README_EN.md b/README_EN.md index d0987ad..3dcc699 100644 --- a/README_EN.md +++ b/README_EN.md @@ -169,6 +169,7 @@ encryption_algorithm=AES-GCM | stun_server | STUN Server's address |No| Cannot be used if listen_port option is port range mode| | log_path | The directory where the Logs are stored |No|Cannot point to the file itself| | kcp_mtu | Positive Integer |No|Default value is 1440| +| kcp_conserve | yes
true
1
no
false
0 |No|Ease up on the 'fast ACK' a little bit.| | kcp | manual
fast1 - 6
regular1 - 5
  |Yes|Setup Manually
Fast Modes
Regular Speeds
(the number at the end: the smaller the value, the faster the speed)| | kcp_sndwnd | Positive Integer |No|See the table below for default values, which can be overridden individually| | kcp_rcvwnd | Positive Integer |No|See the table below for default values, which can be overridden individually| @@ -218,13 +219,15 @@ This bandwidth values should not larger than your actual bandwidth, otherwise th | ---- | :----: | :----: | :----: | :----: | :----: |:----: | | 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 | 1 | 3 | 1 | -| regular5 | 1024 | 1024 | 0 | 1 | 0 | 1 | +| regular3* | 1024 | 1024 | 1 | 1 | 5 | 1 | +| regular4* | 1024 | 1024 | 2 | 1 | 5 | 1 | +| regular5* | 1024 | 1024 | 0 | 1 | 3 | 1 | Note: If the packet loss rate is high enough (higner than 10%), kcp_nodelay=1 may better than kcp_nodelay=2. If the packet loss rate is not too high, kcp_nodelay=2 can make the network latency smoother. -If you want to reduce traffic waste and also accept a little bit more latency increase, please try choosing regular modes. However, using regular 4 or regular 5 in the envirnment of high latency with packet loss rate high than 4% may encounter the situation of TCP disconnection. +If you want to reduce traffic waste and also accept a little bit more latency increase, please try choosing regular modes.
+The star-marked modes (regular3 ~ 5) have enabled the kcp_conserve option, which leads to slightly higher latency caused by packet loss, but reduces wastage of traffic to some extent. + ### Log File After obtaining the IP address and port after NAT hole punching for the first time, and after the IP address and port of NAT hole punching change, an ip_address.txt file will be created in the Log directory (overwrite if it exists), and the IP address and port will be written in. @@ -398,6 +401,36 @@ As kcptube uses IPv6 single-stack + enabled IPv4 mapped addresses (IPv4-mapped I If the system does not support IPv6 or IPv6 is disabled, please set ipv4_only=true in the configuration file, so that kcptube will fall back to using IPv4 single-stack mode. ## Other Considerations +### ‘Too Many Open Files’ of multiple Operation Systems +#### GhostBSD +In general, most BSD systems will not encounter this issue, only GhostBSD updated in the second half of 2023 will encounter it. + +This is because GhostBSD has added this line in `/etc/sysctl.conf`: +``` +kern.maxfiles=100000 +``` +This line reduces the upper limit, far below the corresponding value of the original FreeBSD. + +The solution is simple, just delete or comment out this line.
+Alternatively, use the command `sysctl kern.maxfiles=300000` to temporarily change the limit value. + +#### Linux +Since the Open Files limit for Linux systems is 1024, it's easy to encounter such problems. + +Temporary solution: +1. Run the command `ulimit -n` to check the output value. +2. If the value is indeed only 1024, run the command `ulimit -n 300000`. + +Permanent solution:
+Edit /etc/security/limits.conf and add at the end: + +``` +* hard nofile 300000 +* soft nofile 300000 +root hard nofile 300000 +root soft nofile 300000 +``` + ### NetBSD After running command ``` @@ -410,18 +443,20 @@ However, for unknown reasons, it may not be possible to actively connect to an I ### OpenBSD OpenBSD completely blocks IPv4-mapped IPv6, if you use dual-stack on the OpenBSD platform, you need to save the configuration file as two files, one of which enables ipv4_only=1, and then load both configuration files when using kcptube. -## Data verification +## Encryption and Data verification Since TCP data transmission is required, data verification cannot be ignored, just like TCP itself. -If the encryption option has been used, then the content of this section can be ignored. The encryption algorithm selected by kcptube has verification capabilities built in, which can also ensure that the transmitted content is error-free. +Regardless of whether encryption is enabled or not, this program will reduce the MTU by 2 bytes and append 2-byte data at the end. -If the encryption function is not used, kcptube will reduce the MTU by 2 bytes so that a 2-byte checksum can be attached to the end. +If the encryption option is used, then the 2-byte data appended at the end will be a temporarily generated IV. -However, the Botan library used by kcptube does not include a 16-bit verification algorithm, so kcptube uses two 8-bit checksums at the same time: +If the encryption feature is not selected, the 2-byte data appended at the end will be the checksum, consisting of two different 8-bit checksums: - Longitudinal Redundancy Check (LRC) - 8-bit checksum +This is because the Botan library used in this program does not come with a 16-bit checksum algorithm. Therefore, this program simultaneously utilizes these two 8-bit checksums. + The calculation speed of these two checksums is fast enough, concise and practical, and is not an obscure calculation method. For example, Modbus uses LRC. It should be reminded that using two checksums still cannot completely avoid content errors, just like TCP itself. If you really need accuracy, please enable the encryption option. @@ -452,10 +487,11 @@ The timeout period for KCP channel is 30 seconds after enabling the multiplexing To reduce latency, kcptube has enabled the TCP_NODELAY option. For some high-traffic application scenarios, this may result in a reduction in the amount of TCP data transmitted. ### KCP -KCP Tube uses the original version of the [KCP](https://github.com/skywind3000/kcp), with some modifications: +KCP Tube uses a modified version of [KCP](https://github.com/skywind3000/kcp): -1. The original `flush()` function first moves the data to be sent to the sending queue, and then completes ‘sending new packet’, ‘packet retransmission’, and ‘ACK packet sending’ in the same loop. In the modified version, ‘packet retransmission’ and ‘ACK packet sending’ are done first, and then ‘move the data to be sent to the sending queue’ is done, sending it during the transfer. -2. The original `check()` function would traverse the sending queue every time to find the retransmission timestamp for packets that have reached their timeout. In the modified version, a new variable `min_resendts` is added to the KCP struct. During the `flush()` sending loop, the minimum timestamp is found and stored in min_resendts. `check()` no longer needs to traverse the queue every time. It can directly read the value of `min_resendts`. +1. The original ‘sent data packet cache’ used a queue, and the modified version changed to use std::map, with three mapping tables: a total queue sorted by packet number, and two wait-for-resend queues, one sorted by time and the other sorted by the number of lost packets. +2. The original `flush()` function first transfers the data to be sent to the sending queue, and then completes the three things of ‘sending new data packet’, ‘resending data packet’, and ‘sending ACK packet’ in the same loop. The modified version changes to first do ‘resend data packet’ and ‘send ACK packet’, and then do ‘transfer data to be sent to sending queue’, while sending it during the transfer. +3. The original `check()` function would traverse the sending queue every time to look for the timestamp of the already arrived resend. In the modified version: the first timestamp is read from the already sorted mapping table, eliminating the searching step. And other ‘bugs’ in the original version, will also exist in kcptube. For example: diff --git a/docs/mtu_en.md b/docs/mtu_en.md new file mode 100644 index 0000000..0152ae7 --- /dev/null +++ b/docs/mtu_en.md @@ -0,0 +1,25 @@ +# Set MTU Value + +If the MTU value needs to be specified, it can be calculated by subtracting the IP header size from the MTU value of the network link where the device is located. + +For example, the network for IPoE may have an MTU value of 1500, while the network for PPPoE may have an MTU value of 1492. The size of the IP header is approximately 40 bytes. Thus, KCPTube's MTU value can be calculated as follows: + +- IPoE + - MTU = 1500 - 40 = 1460 +- PPPoE + - MTU = 1492 - 40 = 1452 + +## Transfer VPN data in KCPTube tunnel + +To avoid traffic fragmentation and ensure that each packet transmitted matches the MTU value exactly, the following calculation can be used: + +VPN MTU = KCPTube MTU - KCP Header - KCPTube Header - 2 bytes (tail) + +If encryption options are enabled: + +> VPN MTU = 1440 - 24 - 5 - 2 = 1409 + +If encryption options are disabled: + +> VPN MTU = 1440 - 24 - 9 - 2 = 1405 + diff --git a/docs/mtu_zh-hans.md b/docs/mtu_zh-hans.md new file mode 100644 index 0000000..422494f --- /dev/null +++ b/docs/mtu_zh-hans.md @@ -0,0 +1,24 @@ +# 设置 MTU 值 + +如果想自行指定 MTU 值,计算的方法是,把设备所在网络链路的 MTU 值减去 IP 头的大小。 + +比如,IPoE 的网络可能是 1500,PPPoE 的网络可能是 1492。然后 IP 头大小约为 40 字节。那么,KCPTube 的 MTU 值可以如下计算: + +- IPoE + - MTU = 1500 - 40 = 1460 +- PPPoE + - MTU = 1492 - 40 = 1452 + +## KCPTube 通道内转发 VPN + +如果想减少流量拆分,使每次发送的数据刚好匹配 MTU 值,那么可以如下计算: + +VPN MTU = KCPTube MTU - KCP Header - KCPTube Header - 2 bytes (tail) + +如果启用了加密选项: + +> VPN MTU = 1440 - 24 - 5 - 2 = 1409 + +如果不启用加密选项: + +> VPN MTU = 1440 - 24 - 9 - 2 = 1405 diff --git a/docs/parameters_en.md b/docs/parameters_en.md index 4de622a..81153f7 100644 --- a/docs/parameters_en.md +++ b/docs/parameters_en.md @@ -16,6 +16,7 @@ | stun_server | STUN Server's address |No| Cannot be used if listen_port option is port range mode| | log_path | The directory where the Logs are stored |No|Cannot point to the file itself| | kcp_mtu | Positive Integer |No|Default value is 1440| +| kcp_conserve | yes
true
1
no
false
0 |No|Ease up on the 'fast ACK' a little bit.| | kcp | manual
fast1 - 6
regular1 - 5
  |Yes|Setup Manually
Fast Modes
Regular Speeds
(the number at the end: the smaller the value, the faster the speed)| | kcp_sndwnd | Positive Integer |No|See the table below for default values, which can be overridden individually| | kcp_rcvwnd | Positive Integer |No|See the table below for default values, which can be overridden individually| @@ -65,13 +66,15 @@ This bandwidth values should not larger than your actual bandwidth, otherwise th | ---- | :----: | :----: | :----: | :----: | :----: |:----: | | 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 | 1 | 3 | 1 | -| regular5 | 1024 | 1024 | 0 | 1 | 0 | 1 | +| regular3* | 1024 | 1024 | 1 | 1 | 5 | 1 | +| regular4* | 1024 | 1024 | 2 | 1 | 5 | 1 | +| regular5* | 1024 | 1024 | 0 | 1 | 3 | 1 | Note: If the packet loss rate is high enough (higner than 10%), kcp_nodelay=1 may better than kcp_nodelay=2. If the packet loss rate is not too high, kcp_nodelay=2 can make the network latency smoother. -If you want to reduce traffic waste and also accept a little bit more latency increase, please try choosing regular modes. However, using regular 4 or regular 5 in the envirnment of high latency with packet loss rate high than 4% may encounter the situation of TCP disconnection. +If you want to reduce traffic waste and also accept a little bit more latency increase, please try choosing regular modes.
+The star-marked modes (regular3 ~ 5) have enabled the kcp_conserve option, which leads to slightly higher latency caused by packet loss, but reduces wastage of traffic to some extent. + # Log File After obtaining the IP address and port after NAT hole punching for the first time, and after the IP address and port of NAT hole punching change, an ip_address.txt file will be created in the Log directory (overwrite if it exists), and the IP address and port will be written in. diff --git a/docs/parameters_zh-hans.md b/docs/parameters_zh-hans.md index c50e270..0b5b8cc 100644 --- a/docs/parameters_zh-hans.md +++ b/docs/parameters_zh-hans.md @@ -16,6 +16,7 @@ | stun_server | STUN 服务器地址 |否|listen_port 为端口范围模式时不可使用| | log_path | 存放 Log 的目录 |否|不能指向文件本身| | kcp_mtu | 正整数 |否|预设值1440| +| kcp_conserve | yes
true
1
no
false
0 |否|使“快速ACK”稍微缓和| | kcp | manual
fast1 - 6
regular1 - 5
  |是|手动设置
快速
常速
(末尾数字:数值越小,速度越快)| | kcp_sndwnd | 正整数 |否|预设值见下表,可以单独覆盖| | kcp_rcvwnd | 正整数 |否|预设值见下表,可以单独覆盖| @@ -65,13 +66,14 @@ | ---- | :----: | :----: | :----: | :----: | :----: |:----: | | 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 | 1 | 3 | 1 | -| regular5 | 1024 | 1024 | 0 | 1 | 0 | 1 | +| regular3* | 1024 | 1024 | 1 | 1 | 5 | 1 | +| regular4* | 1024 | 1024 | 2 | 1 | 5 | 1 | +| regular5* | 1024 | 1024 | 0 | 1 | 3 | 1 | 其中,丢包率越高(高于 10%),kcp_nodelay=1 就比 kcp_nodelay=2 越有优势。在丢包率不特别高的情况下,kcp_nodelay=2 可使延迟抖动更为平滑。 -如果想减少流量浪费、不介意延迟稍微增加,可以选择 regular 模式。需要注意的是,高延迟并且丢包率高于 4% 的环境下使用 regular4 或 regular5 可能会遇到 TCP 断流的情况。 +如果想减少流量浪费、不介意延迟稍微增加,可以选择 regular 模式。
+标记了星号的模式 (regular3 ~ 5) 启用了 kcp_conserve 选项,丢包造成的延迟稍微高一些,浪费的流量稍微少一点。 # Log 文件 在首次获取打洞后的 IP 地址与端口后,以及打洞的 IP 地址与端口发生变化后,会向 Log 目录创建 ip_address.txt 文件(若存在就覆盖),将 IP 地址与端口写进去。 diff --git a/sln/kcptube/kcptube.vcxproj b/sln/kcptube/kcptube.vcxproj index da9f01e..18a37d7 100644 --- a/sln/kcptube/kcptube.vcxproj +++ b/sln/kcptube/kcptube.vcxproj @@ -27,7 +27,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/sln/kcptube/kcptube.vcxproj.filters b/sln/kcptube/kcptube.vcxproj.filters index 3bff5e5..5699aee 100644 --- a/sln/kcptube/kcptube.vcxproj.filters +++ b/sln/kcptube/kcptube.vcxproj.filters @@ -63,7 +63,7 @@ Source Files\networks - + Source Files\3rd_party @@ -110,7 +110,7 @@ Header Files\networks - + Header Files\3rd_party diff --git a/src/3rd_party/CMakeLists.txt b/src/3rd_party/CMakeLists.txt index b8c4dbe..1742c4c 100644 --- a/src/3rd_party/CMakeLists.txt +++ b/src/3rd_party/CMakeLists.txt @@ -1,6 +1,6 @@ set(THISLIB_NAME THRID_PARTIES) -add_library(${THISLIB_NAME} STATIC "ikcp.c") +add_library(${THISLIB_NAME} STATIC "ikcp.cpp") target_link_libraries(${THISLIB_NAME} PRIVATE SHAREDEFINES) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) diff --git a/src/3rd_party/ikcp.c b/src/3rd_party/ikcp.c deleted file mode 100644 index e5ce627..0000000 --- a/src/3rd_party/ikcp.c +++ /dev/null @@ -1,1319 +0,0 @@ -//===================================================================== -// -// KCP - A Better ARQ Protocol Implementation -// skywind3000 (at) gmail.com, 2010-2011 -// -// Features: -// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. -// + Maximum RTT reduce three times vs tcp. -// + Lightweight, distributed as a single source file. -// -//===================================================================== -#include "ikcp.h" - -#include -#include -#include -#include -#include - - - -//===================================================================== -// KCP BASIC -//===================================================================== -const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto -const IUINT32 IKCP_RTO_MIN = 100; // normal min rto -const IUINT32 IKCP_RTO_DEF = 200; -const IUINT32 IKCP_RTO_MAX = 60000; -const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data -const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack -const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) -const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) -const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK -const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS -const IUINT32 IKCP_WND_SND = 32; -const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size -const IUINT32 IKCP_MTU_DEF = 1400; -const IUINT32 IKCP_ACK_FAST = 3; -const IUINT32 IKCP_INTERVAL = 100; -const IUINT32 IKCP_OVERHEAD = 24; -const IUINT32 IKCP_DEADLINK = 20; -const IUINT32 IKCP_THRESH_INIT = 2; -const IUINT32 IKCP_THRESH_MIN = 2; -const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size -const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window -const IUINT32 IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack - - -//--------------------------------------------------------------------- -// encode / decode -//--------------------------------------------------------------------- - -/* encode 8 bits unsigned int */ -static inline char *ikcp_encode8u(char *p, unsigned char c) -{ - *(unsigned char*)p++ = c; - return p; -} - -/* decode 8 bits unsigned int */ -static inline const char *ikcp_decode8u(const char *p, unsigned char *c) -{ - *c = *(unsigned char*)p++; - return p; -} - -/* encode 16 bits unsigned int (lsb) */ -static inline char *ikcp_encode16u(char *p, unsigned short w) -{ -#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN - *(unsigned char*)(p + 0) = (w & 255); - *(unsigned char*)(p + 1) = (w >> 8); -#else - memcpy(p, &w, 2); -#endif - p += 2; - return p; -} - -/* decode 16 bits unsigned int (lsb) */ -static inline const char *ikcp_decode16u(const char *p, unsigned short *w) -{ -#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN - *w = *(const unsigned char*)(p + 1); - *w = *(const unsigned char*)(p + 0) + (*w << 8); -#else - memcpy(w, p, 2); -#endif - p += 2; - return p; -} - -/* encode 32 bits unsigned int (lsb) */ -static inline char *ikcp_encode32u(char *p, IUINT32 l) -{ -#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN - *(unsigned char*)(p + 0) = (unsigned char)((l >> 0) & 0xff); - *(unsigned char*)(p + 1) = (unsigned char)((l >> 8) & 0xff); - *(unsigned char*)(p + 2) = (unsigned char)((l >> 16) & 0xff); - *(unsigned char*)(p + 3) = (unsigned char)((l >> 24) & 0xff); -#else - memcpy(p, &l, 4); -#endif - p += 4; - return p; -} - -/* decode 32 bits unsigned int (lsb) */ -static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) -{ -#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN - *l = *(const unsigned char*)(p + 3); - *l = *(const unsigned char*)(p + 2) + (*l << 8); - *l = *(const unsigned char*)(p + 1) + (*l << 8); - *l = *(const unsigned char*)(p + 0) + (*l << 8); -#else - memcpy(l, p, 4); -#endif - p += 4; - return p; -} - -static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) { - return a <= b ? a : b; -} - -static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) { - return a >= b ? a : b; -} - -static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) -{ - return _imin_(_imax_(lower, middle), upper); -} - -static inline long _itimediff(IUINT32 later, IUINT32 earlier) -{ - return ((IINT32)(later - earlier)); -} - -//--------------------------------------------------------------------- -// manage segment -//--------------------------------------------------------------------- -typedef struct IKCPSEG IKCPSEG; - -static void* (*ikcp_malloc_hook)(size_t) = NULL; -static void (*ikcp_free_hook)(void *) = NULL; - -// internal malloc -static void* ikcp_malloc(size_t size) { - if (ikcp_malloc_hook) - return ikcp_malloc_hook(size); - return malloc(size); -} - -// internal free -static void ikcp_free(void *ptr) { - if (ikcp_free_hook) { - ikcp_free_hook(ptr); - } else { - free(ptr); - } -} - -// redefine allocator -void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)) -{ - ikcp_malloc_hook = new_malloc; - ikcp_free_hook = new_free; -} - -// allocate a new kcp segment -static IKCPSEG* ikcp_segment_new(ikcpcb *kcp, int size) -{ - return (IKCPSEG*)ikcp_malloc(sizeof(IKCPSEG) + size); -} - -// delete a segment -static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) -{ - ikcp_free(seg); -} - -// write log -void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) -{ - char buffer[1024]; - va_list argptr; - if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) return; - va_start(argptr, fmt); - vsprintf(buffer, fmt, argptr); - va_end(argptr); - kcp->writelog(buffer, kcp, kcp->user); -} - -// check log mask -static int ikcp_canlog(const ikcpcb *kcp, int mask) -{ - if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) return 0; - return 1; -} - -// output segment -static int ikcp_output(ikcpcb *kcp, const void *data, int size) -{ - assert(kcp); - assert(kcp->output); - if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { - ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); - } - if (size == 0) return 0; - return kcp->output((const char*)data, size, kcp, kcp->user); -} - -// output queue -void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) -{ -#if 0 - const struct IQUEUEHEAD *p; - printf("<%s>: [", name); - for (p = head->next; p != head; p = p->next) { - const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); - printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); - if (p->next != head) printf(","); - } - printf("]\n"); -#endif -} - - -//--------------------------------------------------------------------- -// create a new kcpcb -//--------------------------------------------------------------------- -ikcpcb* ikcp_create(IUINT32 conv, void *user) -{ - ikcpcb *kcp = (ikcpcb*)ikcp_malloc(sizeof(struct IKCPCB)); - if (kcp == NULL) return NULL; - kcp->conv = conv; - kcp->user = user; - kcp->snd_una = 0; - kcp->snd_nxt = 0; - kcp->rcv_nxt = 0; - kcp->ts_recent = 0; - kcp->ts_lastack = 0; - kcp->ts_probe = 0; - kcp->probe_wait = 0; - kcp->snd_wnd = IKCP_WND_SND; - kcp->rcv_wnd = IKCP_WND_RCV; - kcp->rmt_wnd = IKCP_WND_RCV; - kcp->cwnd = 0; - kcp->incr = 0; - kcp->probe = 0; - kcp->mtu = IKCP_MTU_DEF; - kcp->mss = kcp->mtu - IKCP_OVERHEAD; - kcp->stream = 0; - - kcp->buffer = (char*)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); - if (kcp->buffer == NULL) { - ikcp_free(kcp); - return NULL; - } - - iqueue_init(&kcp->snd_queue); - iqueue_init(&kcp->rcv_queue); - iqueue_init(&kcp->snd_buf); - iqueue_init(&kcp->rcv_buf); - kcp->nrcv_buf = 0; - kcp->nsnd_buf = 0; - kcp->nrcv_que = 0; - kcp->nsnd_que = 0; - kcp->state = 0; - kcp->acklist = NULL; - kcp->ackblock = 0; - kcp->ackcount = 0; - kcp->rx_srtt = 0; - kcp->rx_rttval = 0; - kcp->rx_rto = IKCP_RTO_DEF; - kcp->rx_minrto = IKCP_RTO_MIN; - kcp->current = 0; - kcp->interval = IKCP_INTERVAL; - kcp->ts_flush = IKCP_INTERVAL; - kcp->nodelay = 0; - kcp->updated = 0; - kcp->logmask = 0; - kcp->ssthresh = IKCP_THRESH_INIT; - kcp->fastresend = 0; - kcp->fastlimit = IKCP_FASTACK_LIMIT; - kcp->nocwnd = 0; - kcp->xmit = 0; - kcp->dead_link = IKCP_DEADLINK; - kcp->min_resendts = 0; - kcp->output = NULL; - kcp->writelog = NULL; - - return kcp; -} - - -//--------------------------------------------------------------------- -// release a new kcpcb -//--------------------------------------------------------------------- -void ikcp_release(ikcpcb *kcp) -{ - assert(kcp); - if (kcp) { - IKCPSEG *seg; - while (!iqueue_is_empty(&kcp->snd_buf)) { - seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); - iqueue_del(&seg->node); - ikcp_segment_delete(kcp, seg); - } - while (!iqueue_is_empty(&kcp->rcv_buf)) { - seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); - iqueue_del(&seg->node); - ikcp_segment_delete(kcp, seg); - } - while (!iqueue_is_empty(&kcp->snd_queue)) { - seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); - iqueue_del(&seg->node); - ikcp_segment_delete(kcp, seg); - } - while (!iqueue_is_empty(&kcp->rcv_queue)) { - seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); - iqueue_del(&seg->node); - ikcp_segment_delete(kcp, seg); - } - if (kcp->buffer) { - ikcp_free(kcp->buffer); - } - if (kcp->acklist) { - ikcp_free(kcp->acklist); - } - - kcp->nrcv_buf = 0; - kcp->nsnd_buf = 0; - kcp->nrcv_que = 0; - kcp->nsnd_que = 0; - kcp->ackcount = 0; - kcp->buffer = NULL; - kcp->acklist = NULL; - ikcp_free(kcp); - } -} - - -//--------------------------------------------------------------------- -// set output callback, which will be invoked by kcp -//--------------------------------------------------------------------- -void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, - ikcpcb *kcp, void *user)) -{ - kcp->output = output; -} - - -//--------------------------------------------------------------------- -// user/upper level recv: returns size, returns below zero for EAGAIN -//--------------------------------------------------------------------- -int ikcp_recv(ikcpcb *kcp, char *buffer, int len) -{ - struct IQUEUEHEAD *p; - int ispeek = (len < 0)? 1 : 0; - int peeksize; - int recover = 0; - IKCPSEG *seg; - assert(kcp); - - if (iqueue_is_empty(&kcp->rcv_queue)) - return -1; - - if (len < 0) len = -len; - - peeksize = ikcp_peeksize(kcp); - - if (peeksize < 0) - return -2; - - if (peeksize > len) - return -3; - - if (kcp->nrcv_que >= kcp->rcv_wnd) - recover = 1; - - // merge fragment - for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue; ) { - int fragment; - seg = iqueue_entry(p, IKCPSEG, node); - p = p->next; - - if (buffer) { - memcpy(buffer, seg->data, seg->len); - buffer += seg->len; - } - - len += seg->len; - fragment = seg->frg; - - if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { - ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); - } - - if (ispeek == 0) { - iqueue_del(&seg->node); - ikcp_segment_delete(kcp, seg); - kcp->nrcv_que--; - } - - if (fragment == 0) - break; - } - - assert(len == peeksize); - - // move available data from rcv_buf -> rcv_queue - while (! iqueue_is_empty(&kcp->rcv_buf)) { - seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); - if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { - iqueue_del(&seg->node); - kcp->nrcv_buf--; - iqueue_add_tail(&seg->node, &kcp->rcv_queue); - kcp->nrcv_que++; - kcp->rcv_nxt++; - } else { - break; - } - } - - // fast recover - if (kcp->nrcv_que < kcp->rcv_wnd && recover) { - // ready to send back IKCP_CMD_WINS in ikcp_flush - // tell remote my window size - kcp->probe |= IKCP_ASK_TELL; - } - - return len; -} - - -//--------------------------------------------------------------------- -// peek data size -//--------------------------------------------------------------------- -int ikcp_peeksize(const ikcpcb *kcp) -{ - struct IQUEUEHEAD *p; - IKCPSEG *seg; - int length = 0; - - assert(kcp); - - if (iqueue_is_empty(&kcp->rcv_queue)) return -1; - - seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); - if (seg->frg == 0) return seg->len; - - if (kcp->nrcv_que < seg->frg + 1) return -1; - - for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { - seg = iqueue_entry(p, IKCPSEG, node); - length += seg->len; - if (seg->frg == 0) break; - } - - return length; -} - - -//--------------------------------------------------------------------- -// user/upper level send, returns below zero for error -//--------------------------------------------------------------------- -int ikcp_send(ikcpcb *kcp, const char *buffer, int len) -{ - IKCPSEG *seg; - int count, i; - int sent = 0; - - assert(kcp->mss > 0); - if (len < 0) return -1; - - // append to previous segment in streaming mode (if possible) - if (kcp->stream != 0) { - if (!iqueue_is_empty(&kcp->snd_queue)) { - IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); - if (old->len < kcp->mss) { - int capacity = kcp->mss - old->len; - int extend = (len < capacity)? len : capacity; - seg = ikcp_segment_new(kcp, old->len + extend); - assert(seg); - if (seg == NULL) { - return -2; - } - iqueue_add_tail(&seg->node, &kcp->snd_queue); - memcpy(seg->data, old->data, old->len); - if (buffer) { - memcpy(seg->data + old->len, buffer, extend); - buffer += extend; - } - seg->len = old->len + extend; - seg->frg = 0; - len -= extend; - iqueue_del_init(&old->node); - ikcp_segment_delete(kcp, old); - sent = extend; - } - } - if (len <= 0) { - return sent; - } - } - - if (len <= (int)kcp->mss) count = 1; - else count = (len + kcp->mss - 1) / kcp->mss; - - if (count >= (int)IKCP_WND_RCV) { - if (kcp->stream != 0 && sent > 0) - return sent; - return -2; - } - - if (count == 0) count = 1; - - // fragment - for (i = 0; i < count; i++) { - int size = len > (int)kcp->mss ? (int)kcp->mss : len; - seg = ikcp_segment_new(kcp, size); - assert(seg); - if (seg == NULL) { - return -2; - } - if (buffer && len > 0) { - memcpy(seg->data, buffer, size); - } - seg->len = size; - seg->frg = (kcp->stream == 0)? (count - i - 1) : 0; - iqueue_init(&seg->node); - iqueue_add_tail(&seg->node, &kcp->snd_queue); - kcp->nsnd_que++; - if (buffer) { - buffer += size; - } - len -= size; - sent += size; - } - - return sent; -} - - -//--------------------------------------------------------------------- -// parse ack -//--------------------------------------------------------------------- -static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) -{ - IINT32 rto = 0; - if (kcp->rx_srtt == 0) { - kcp->rx_srtt = rtt; - kcp->rx_rttval = rtt / 2; - } else { - long delta = rtt - kcp->rx_srtt; - if (delta < 0) delta = -delta; - kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; - kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; - if (kcp->rx_srtt < 1) kcp->rx_srtt = 1; - } - rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); - kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); -} - -static void ikcp_shrink_buf(ikcpcb *kcp) -{ - struct IQUEUEHEAD *p = kcp->snd_buf.next; - if (p != &kcp->snd_buf) { - IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); - kcp->snd_una = seg->sn; - } else { - kcp->snd_una = kcp->snd_nxt; - } -} - -static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) -{ - struct IQUEUEHEAD *p, *next; - - if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) - return; - - for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { - IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); - next = p->next; - if (sn == seg->sn) { - iqueue_del(p); - ikcp_segment_delete(kcp, seg); - kcp->nsnd_buf--; - break; - } - if (_itimediff(sn, seg->sn) < 0) { - break; - } - } -} - -static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) -{ - struct IQUEUEHEAD *p, *next; - for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { - IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); - next = p->next; - if (_itimediff(una, seg->sn) > 0) { - iqueue_del(p); - ikcp_segment_delete(kcp, seg); - kcp->nsnd_buf--; - } else { - break; - } - } -} - -static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) -{ - struct IQUEUEHEAD *p, *next; - - if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) - return; - - for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { - IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); - next = p->next; - if (_itimediff(sn, seg->sn) < 0) { - break; - } - else if (sn != seg->sn) { - #ifndef IKCP_FASTACK_CONSERVE - seg->fastack++; - #else - if (_itimediff(ts, seg->ts) >= 0) - seg->fastack++; - #endif - } - } -} - - -//--------------------------------------------------------------------- -// ack append -//--------------------------------------------------------------------- -static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) -{ - IUINT32 newsize = kcp->ackcount + 1; - IUINT32 *ptr; - - if (newsize > kcp->ackblock) { - IUINT32 *acklist; - IUINT32 newblock; - - for (newblock = 8; newblock < newsize; newblock <<= 1); - acklist = (IUINT32*)ikcp_malloc(newblock * sizeof(IUINT32) * 2); - - if (acklist == NULL) { - assert(acklist != NULL); - abort(); - } - - if (kcp->acklist != NULL) { - IUINT32 x; - for (x = 0; x < kcp->ackcount; x++) { - acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; - acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; - } - ikcp_free(kcp->acklist); - } - - kcp->acklist = acklist; - kcp->ackblock = newblock; - } - - ptr = &kcp->acklist[kcp->ackcount * 2]; - ptr[0] = sn; - ptr[1] = ts; - kcp->ackcount++; -} - -static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) -{ - if (sn) sn[0] = kcp->acklist[p * 2 + 0]; - if (ts) ts[0] = kcp->acklist[p * 2 + 1]; -} - - -//--------------------------------------------------------------------- -// parse data -//--------------------------------------------------------------------- -void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg) -{ - struct IQUEUEHEAD *p, *prev; - IUINT32 sn = newseg->sn; - int repeat = 0; - - if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || - _itimediff(sn, kcp->rcv_nxt) < 0) { - ikcp_segment_delete(kcp, newseg); - return; - } - - for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { - IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); - prev = p->prev; - if (seg->sn == sn) { - repeat = 1; - break; - } - if (_itimediff(sn, seg->sn) > 0) { - break; - } - } - - if (repeat == 0) { - iqueue_init(&newseg->node); - iqueue_add(&newseg->node, p); - kcp->nrcv_buf++; - } else { - ikcp_segment_delete(kcp, newseg); - } - -#if 0 - ikcp_qprint("rcvbuf", &kcp->rcv_buf); - printf("rcv_nxt=%lu\n", kcp->rcv_nxt); -#endif - - // move available data from rcv_buf -> rcv_queue - while (! iqueue_is_empty(&kcp->rcv_buf)) { - IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); - if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { - iqueue_del(&seg->node); - kcp->nrcv_buf--; - iqueue_add_tail(&seg->node, &kcp->rcv_queue); - kcp->nrcv_que++; - kcp->rcv_nxt++; - } else { - break; - } - } - -#if 0 - ikcp_qprint("queue", &kcp->rcv_queue); - printf("rcv_nxt=%lu\n", kcp->rcv_nxt); -#endif - -#if 1 -// printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); -// printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); -#endif -} - - -//--------------------------------------------------------------------- -// input data -//--------------------------------------------------------------------- -int ikcp_input(ikcpcb *kcp, const char *data, long size) -{ - IUINT32 prev_una = kcp->snd_una; - IUINT32 maxack = 0, latest_ts = 0; - int flag = 0; - - if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { - ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); - } - - if (data == NULL || (int)size < (int)IKCP_OVERHEAD) return -1; - - while (1) { - IUINT32 ts, sn, len, una, conv; - IUINT16 wnd; - IUINT8 cmd, frg; - IKCPSEG *seg; - - if (size < (int)IKCP_OVERHEAD) break; - - data = ikcp_decode32u(data, &conv); - if (conv != kcp->conv) return -1; - - data = ikcp_decode8u(data, &cmd); - data = ikcp_decode8u(data, &frg); - data = ikcp_decode16u(data, &wnd); - data = ikcp_decode32u(data, &ts); - data = ikcp_decode32u(data, &sn); - data = ikcp_decode32u(data, &una); - data = ikcp_decode32u(data, &len); - - size -= IKCP_OVERHEAD; - - if ((long)size < (long)len || (int)len < 0) return -2; - - if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && - cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) - return -3; - - kcp->rmt_wnd = wnd; - ikcp_parse_una(kcp, una); - ikcp_shrink_buf(kcp); - - if (cmd == IKCP_CMD_ACK) { - if (_itimediff(kcp->current, ts) >= 0) { - ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); - } - ikcp_parse_ack(kcp, sn); - ikcp_shrink_buf(kcp); - if (flag == 0) { - flag = 1; - maxack = sn; - latest_ts = ts; - } else { - if (_itimediff(sn, maxack) > 0) { - #ifndef IKCP_FASTACK_CONSERVE - maxack = sn; - latest_ts = ts; - #else - if (_itimediff(ts, latest_ts) > 0) { - maxack = sn; - latest_ts = ts; - } - #endif - } - } - if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { - ikcp_log(kcp, IKCP_LOG_IN_ACK, - "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, - (long)_itimediff(kcp->current, ts), - (long)kcp->rx_rto); - } - } - else if (cmd == IKCP_CMD_PUSH) { - if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { - ikcp_log(kcp, IKCP_LOG_IN_DATA, - "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); - } - if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { - ikcp_ack_push(kcp, sn, ts); - if (_itimediff(sn, kcp->rcv_nxt) >= 0) { - seg = ikcp_segment_new(kcp, len); - seg->conv = conv; - seg->cmd = cmd; - seg->frg = frg; - seg->wnd = wnd; - seg->ts = ts; - seg->sn = sn; - seg->una = una; - seg->len = len; - - if (len > 0) { - memcpy(seg->data, data, len); - } - - ikcp_parse_data(kcp, seg); - } - } - } - else if (cmd == IKCP_CMD_WASK) { - // ready to send back IKCP_CMD_WINS in ikcp_flush - // tell remote my window size - kcp->probe |= IKCP_ASK_TELL; - if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { - ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); - } - } - else if (cmd == IKCP_CMD_WINS) { - // do nothing - if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { - ikcp_log(kcp, IKCP_LOG_IN_WINS, - "input wins: %lu", (unsigned long)(wnd)); - } - } - else { - return -3; - } - - data += len; - size -= len; - } - - if (flag != 0) { - ikcp_parse_fastack(kcp, maxack, latest_ts); - } - - if (_itimediff(kcp->snd_una, prev_una) > 0) { - if (kcp->cwnd < kcp->rmt_wnd) { - IUINT32 mss = kcp->mss; - if (kcp->cwnd < kcp->ssthresh) { - kcp->cwnd++; - kcp->incr += mss; - } else { - if (kcp->incr < mss) kcp->incr = mss; - kcp->incr += (mss * mss) / kcp->incr + (mss / 16); - if ((kcp->cwnd + 1) * mss <= kcp->incr) { - #if 1 - kcp->cwnd = (kcp->incr + mss - 1) / ((mss > 0)? mss : 1); - #else - kcp->cwnd++; - #endif - } - } - if (kcp->cwnd > kcp->rmt_wnd) { - kcp->cwnd = kcp->rmt_wnd; - kcp->incr = kcp->rmt_wnd * mss; - } - } - } - - return 0; -} - - -//--------------------------------------------------------------------- -// ikcp_encode_seg -//--------------------------------------------------------------------- -static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) -{ - ptr = ikcp_encode32u(ptr, seg->conv); - ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); - ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); - ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); - ptr = ikcp_encode32u(ptr, seg->ts); - ptr = ikcp_encode32u(ptr, seg->sn); - ptr = ikcp_encode32u(ptr, seg->una); - ptr = ikcp_encode32u(ptr, seg->len); - return ptr; -} - -static int ikcp_wnd_unused(const ikcpcb *kcp) -{ - if (kcp->nrcv_que < kcp->rcv_wnd) { - return kcp->rcv_wnd - kcp->nrcv_que; - } - return 0; -} - - -//--------------------------------------------------------------------- -// ikcp_flush -//--------------------------------------------------------------------- -void ikcp_flush(ikcpcb *kcp) -{ - IUINT32 current = kcp->current; - char *buffer = kcp->buffer; - char *ptr = buffer; - int count, size, i; - IUINT32 resent, cwnd; - IUINT32 rtomin; - struct IQUEUEHEAD *p; - int change = 0; - int lost = 0; - IKCPSEG seg; - kcp->min_resendts = 0xFFFFFFFF; - - // 'ikcp_update' haven't been called. - if (kcp->updated == 0) return; - - seg.conv = kcp->conv; - seg.cmd = IKCP_CMD_ACK; - seg.frg = 0; - seg.wnd = ikcp_wnd_unused(kcp); - seg.una = kcp->rcv_nxt; - seg.len = 0; - seg.sn = 0; - seg.ts = 0; - - // flush acknowledges - count = kcp->ackcount; - for (i = 0; i < count; i++) { - size = (int)(ptr - buffer); - if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { - ikcp_output(kcp, buffer, size); - ptr = buffer; - } - ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); - ptr = ikcp_encode_seg(ptr, &seg); - } - - kcp->ackcount = 0; - - // probe window size (if remote window size equals zero) - if (kcp->rmt_wnd == 0) { - if (kcp->probe_wait == 0) { - kcp->probe_wait = IKCP_PROBE_INIT; - kcp->ts_probe = kcp->current + kcp->probe_wait; - } - else { - if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { - if (kcp->probe_wait < IKCP_PROBE_INIT) - kcp->probe_wait = IKCP_PROBE_INIT; - kcp->probe_wait += kcp->probe_wait / 2; - if (kcp->probe_wait > IKCP_PROBE_LIMIT) - kcp->probe_wait = IKCP_PROBE_LIMIT; - kcp->ts_probe = kcp->current + kcp->probe_wait; - kcp->probe |= IKCP_ASK_SEND; - } - } - } else { - kcp->ts_probe = 0; - kcp->probe_wait = 0; - } - - // flush window probing commands - if (kcp->probe & IKCP_ASK_SEND) { - seg.cmd = IKCP_CMD_WASK; - size = (int)(ptr - buffer); - if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { - ikcp_output(kcp, buffer, size); - ptr = buffer; - } - ptr = ikcp_encode_seg(ptr, &seg); - } - - // flush window probing commands - if (kcp->probe & IKCP_ASK_TELL) { - seg.cmd = IKCP_CMD_WINS; - size = (int)(ptr - buffer); - if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { - ikcp_output(kcp, buffer, size); - ptr = buffer; - } - ptr = ikcp_encode_seg(ptr, &seg); - } - - kcp->probe = 0; - - // calculate window size - cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); - if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); - - // calculate resent - resent = (kcp->fastresend > 0)? (IUINT32)kcp->fastresend : 0xffffffff; - rtomin = (kcp->nodelay == 0)? (kcp->rx_rto >> 3) : 0; - - // flush data segments - for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { - IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); - int needsend = 0; - if (_itimediff(current, segment->resendts) >= 0) { - needsend = 1; - segment->xmit++; - kcp->xmit++; - if (kcp->nodelay == 0) { - segment->rto += _imax_(segment->rto, (IUINT32)kcp->rx_rto); - } else { - IINT32 step = (kcp->nodelay < 2)? - ((IINT32)(segment->rto)) : kcp->rx_rto; - segment->rto += step / 2; - } - segment->resendts = current + segment->rto; - lost = 1; - kcp->min_resendts = _imin_(kcp->min_resendts, segment->resendts); - } - else if (segment->fastack >= resent) { - if ((int)segment->xmit <= kcp->fastlimit || - kcp->fastlimit <= 0) { - needsend = 1; - segment->xmit++; - segment->fastack = 0; - segment->resendts = current + segment->rto; - change++; - kcp->min_resendts = _imin_(kcp->min_resendts, segment->resendts); - } - } - - if (needsend) { - int need; - segment->ts = current; - segment->wnd = seg.wnd; - segment->una = kcp->rcv_nxt; - - size = (int)(ptr - buffer); - need = IKCP_OVERHEAD + segment->len; - - if (size + need > (int)kcp->mtu) { - ikcp_output(kcp, buffer, size); - ptr = buffer; - } - - ptr = ikcp_encode_seg(ptr, segment); - - if (segment->len > 0) { - memcpy(ptr, segment->data, segment->len); - ptr += segment->len; - } - - if (segment->xmit >= kcp->dead_link) { - kcp->state = (IUINT32)-1; - } - } - } - - // move data from snd_queue to snd_buf - while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { - IKCPSEG *newseg; - if (iqueue_is_empty(&kcp->snd_queue)) break; - - newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); - - iqueue_del(&newseg->node); - iqueue_add_tail(&newseg->node, &kcp->snd_buf); - kcp->nsnd_que--; - kcp->nsnd_buf++; - - newseg->conv = kcp->conv; - newseg->cmd = IKCP_CMD_PUSH; - newseg->wnd = seg.wnd; - newseg->ts = current; - newseg->sn = kcp->snd_nxt++; - newseg->una = kcp->rcv_nxt; - newseg->resendts = current + kcp->rx_rto + rtomin; - newseg->rto = kcp->rx_rto; - newseg->fastack = 0; - newseg->xmit = 1; - - kcp->min_resendts = _imin_(kcp->min_resendts, newseg->resendts); - - size = (int)(ptr - buffer); - int need = IKCP_OVERHEAD + newseg->len; - - if (size + need > (int)kcp->mtu) { - ikcp_output(kcp, buffer, size); - ptr = buffer; - } - - ptr = ikcp_encode_seg(ptr, newseg); - - if (newseg->len > 0) { - memcpy(ptr, newseg->data, newseg->len); - ptr += newseg->len; - } - } - - // flash remain segments - size = (int)(ptr - buffer); - if (size > 0) { - ikcp_output(kcp, buffer, size); - } - - // update ssthresh - if (change) { - IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; - kcp->ssthresh = inflight / 2; - if (kcp->ssthresh < IKCP_THRESH_MIN) - kcp->ssthresh = IKCP_THRESH_MIN; - kcp->cwnd = kcp->ssthresh + resent; - kcp->incr = kcp->cwnd * kcp->mss; - } - - if (lost) { - kcp->ssthresh = cwnd / 2; - if (kcp->ssthresh < IKCP_THRESH_MIN) - kcp->ssthresh = IKCP_THRESH_MIN; - kcp->cwnd = 1; - kcp->incr = kcp->mss; - } - - if (kcp->cwnd < 1) { - kcp->cwnd = 1; - kcp->incr = kcp->mss; - } -} - - -//--------------------------------------------------------------------- -// update state (call it repeatedly, every 10ms-100ms), or you can ask -// ikcp_check when to call it again (without ikcp_input/_send calling). -// 'current' - current timestamp in millisec. -//--------------------------------------------------------------------- -void ikcp_update(ikcpcb *kcp, IUINT32 current) -{ - IINT32 slap; - - kcp->current = current; - - if (kcp->updated == 0) { - kcp->updated = 1; - kcp->ts_flush = kcp->current; - } - - slap = _itimediff(kcp->current, kcp->ts_flush); - - if (slap >= 10000 || slap < -10000) { - kcp->ts_flush = kcp->current; - slap = 0; - } - - if (slap >= 0) { - kcp->ts_flush += kcp->interval; - if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { - kcp->ts_flush = kcp->current + kcp->interval; - } - ikcp_flush(kcp); - } -} - - -//--------------------------------------------------------------------- -// Determine when should you invoke ikcp_update: -// returns when you should invoke ikcp_update in millisec, if there -// is no ikcp_input/_send calling. you can call ikcp_update in that -// time, instead of call update repeatly. -// Important to reduce unnacessary ikcp_update invoking. use it to -// schedule ikcp_update (eg. implementing an epoll-like mechanism, -// or optimize ikcp_update when handling massive kcp connections) -//--------------------------------------------------------------------- -IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) -{ - IUINT32 ts_flush = kcp->ts_flush; - IINT32 tm_flush = 0x7fffffff; - IINT32 tm_packet = 0x7fffffff; - IUINT32 minimal = 0; - - if (kcp->updated == 0) { - return current; - } - - if (_itimediff(current, ts_flush) >= 10000 || - _itimediff(current, ts_flush) < -10000) { - ts_flush = current; - } - - if (_itimediff(current, ts_flush) >= 0) { - return current; - } - - tm_flush = _itimediff(ts_flush, current); - - if (!iqueue_is_empty(&kcp->snd_buf)) { - IINT32 diff = _itimediff(kcp->min_resendts, current); - if (diff <= 0) { - return current; - } - if (diff < tm_packet) tm_packet = diff; - } - - minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); - if (minimal >= kcp->interval) minimal = kcp->interval; - - return current + minimal; -} - - - -int ikcp_setmtu(ikcpcb *kcp, int mtu) -{ - char *buffer; - if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) - return -1; - buffer = (char*)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); - if (buffer == NULL) - return -2; - kcp->mtu = mtu; - kcp->mss = kcp->mtu - IKCP_OVERHEAD; - ikcp_free(kcp->buffer); - kcp->buffer = buffer; - return 0; -} - -int ikcp_interval(ikcpcb *kcp, int interval) -{ - if (interval > 5000) interval = 5000; - else if (interval < 10) interval = 10; - kcp->interval = interval; - return 0; -} - -int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) -{ - if (nodelay >= 0) { - kcp->nodelay = nodelay; - if (nodelay) { - kcp->rx_minrto = IKCP_RTO_NDL; - } - else { - kcp->rx_minrto = IKCP_RTO_MIN; - } - } - if (interval >= 0) { - if (interval > 5000) interval = 5000; - else if (interval < 10) interval = 10; - kcp->interval = interval; - } - if (resend >= 0) { - kcp->fastresend = resend; - } - if (nc >= 0) { - kcp->nocwnd = nc; - } - return 0; -} - - -int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) -{ - if (kcp) { - if (sndwnd > 0) { - kcp->snd_wnd = sndwnd; - } - if (rcvwnd > 0) { // must >= max fragment size - kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); - } - } - return 0; -} - -int ikcp_waitsnd(const ikcpcb *kcp) -{ - return kcp->nsnd_buf + kcp->nsnd_que; -} - - -// read conv -IUINT32 ikcp_getconv(const void *ptr) -{ - IUINT32 conv; - ikcp_decode32u((const char*)ptr, &conv); - return conv; -} - - diff --git a/src/3rd_party/ikcp.cpp b/src/3rd_party/ikcp.cpp new file mode 100644 index 0000000..55b8aee --- /dev/null +++ b/src/3rd_party/ikcp.cpp @@ -0,0 +1,1283 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#include "ikcp.hpp" + +#include +#include +#include +#include +#include + + +//--------------------------------------------------------------------- +// BYTE ORDER & ALIGNMENT +//--------------------------------------------------------------------- +#ifndef IWORDS_BIG_ENDIAN +#ifdef _BIG_ENDIAN_ +#if _BIG_ENDIAN_ +#define IWORDS_BIG_ENDIAN 1 +#endif +#endif +#ifndef IWORDS_BIG_ENDIAN +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MIPSEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) || defined(__powerpc__) || \ + defined(__mc68000__) || defined(__s390x__) || defined(__s390__) +#define IWORDS_BIG_ENDIAN 1 +#endif +#endif +#ifndef IWORDS_BIG_ENDIAN +#define IWORDS_BIG_ENDIAN 0 +#endif +#endif + +#ifndef IWORDS_MUST_ALIGN +#if defined(__i386__) || defined(__i386) || defined(_i386_) +#define IWORDS_MUST_ALIGN 0 +#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) +#define IWORDS_MUST_ALIGN 0 +#elif defined(__amd64) || defined(__amd64__) +#define IWORDS_MUST_ALIGN 0 +#else +#define IWORDS_MUST_ALIGN 1 +#endif +#endif + + +//===================================================================== +// KCP BASIC +//===================================================================== +constexpr uint32_t IKCP_RTO_NDL = 30; // no delay min rto +constexpr uint32_t IKCP_RTO_MIN = 100; // normal min rto +constexpr uint32_t IKCP_RTO_DEF = 200; +constexpr uint32_t IKCP_RTO_MAX = 60000; +constexpr uint32_t IKCP_CMD_PUSH = 81; // cmd: push data +constexpr uint32_t IKCP_CMD_ACK = 82; // cmd: ack +constexpr uint32_t IKCP_CMD_WASK = 83; // cmd: window probe (ask) +constexpr uint32_t IKCP_CMD_WINS = 84; // cmd: window size (tell) +constexpr uint32_t IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK +constexpr uint32_t IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS +constexpr uint32_t IKCP_WND_SND = 32; +constexpr uint32_t IKCP_WND_RCV = 128; // must >= max fragment size +constexpr uint32_t IKCP_MTU_DEF = 1400; +constexpr uint32_t IKCP_ACK_FAST = 3; +constexpr uint32_t IKCP_INTERVAL = 100; +constexpr uint32_t IKCP_OVERHEAD = 24; +constexpr uint32_t IKCP_DEADLINK = 20; +constexpr uint32_t IKCP_THRESH_INIT = 2; +constexpr uint32_t IKCP_THRESH_MIN = 2; +constexpr uint32_t IKCP_PROBE_INIT = 7000; // 7 secs to probe window size +constexpr uint32_t IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window +constexpr uint32_t IKCP_FASTACK_LIMIT = 5; // max times to trigger fastack + + +//--------------------------------------------------------------------- +// encode / decode +//--------------------------------------------------------------------- + +/* encode 8 bits unsigned int */ +static inline char *ikcp_encode8u(char *p, unsigned char c) +{ + *(unsigned char*)p++ = c; + return p; +} + +/* decode 8 bits unsigned int */ +static inline const char *ikcp_decode8u(const char *p, unsigned char *c) +{ + *c = *(unsigned char*)p++; + return p; +} + +/* encode 16 bits unsigned int (lsb) */ +static inline char *ikcp_encode16u(char *p, unsigned short w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (w & 255); + *(unsigned char*)(p + 1) = (w >> 8); +#else + memcpy(p, &w, 2); +#endif + p += 2; + return p; +} + +/* decode 16 bits unsigned int (lsb) */ +static inline const char *ikcp_decode16u(const char *p, unsigned short *w) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *w = *(const unsigned char*)(p + 1); + *w = *(const unsigned char*)(p + 0) + (*w << 8); +#else + memcpy(w, p, 2); +#endif + p += 2; + return p; +} + +/* encode 32 bits unsigned int (lsb) */ +static inline char *ikcp_encode32u(char *p, uint32_t l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *(unsigned char*)(p + 0) = (unsigned char)((l >> 0) & 0xff); + *(unsigned char*)(p + 1) = (unsigned char)((l >> 8) & 0xff); + *(unsigned char*)(p + 2) = (unsigned char)((l >> 16) & 0xff); + *(unsigned char*)(p + 3) = (unsigned char)((l >> 24) & 0xff); +#else + memcpy(p, &l, 4); +#endif + p += 4; + return p; +} + +/* decode 32 bits unsigned int (lsb) */ +static inline const char *ikcp_decode32u(const char *p, uint32_t *l) +{ +#if IWORDS_BIG_ENDIAN || IWORDS_MUST_ALIGN + *l = *(const unsigned char*)(p + 3); + *l = *(const unsigned char*)(p + 2) + (*l << 8); + *l = *(const unsigned char*)(p + 1) + (*l << 8); + *l = *(const unsigned char*)(p + 0) + (*l << 8); +#else + memcpy(l, p, 4); +#endif + p += 4; + return p; +} + +static inline uint32_t _imin_(uint32_t a, uint32_t b) +{ + return a <= b ? a : b; +} + +static inline uint32_t _imax_(uint32_t a, uint32_t b) +{ + return a >= b ? a : b; +} + +static inline uint32_t _ibound_(uint32_t lower, uint32_t middle, uint32_t upper) +{ + return _imin_(_imax_(lower, middle), upper); +} + +static inline long _itimediff(uint32_t later, uint32_t earlier) +{ + return ((int32_t)(later - earlier)); +} + +// output queue +void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) +{ +#if 0 + const struct IQUEUEHEAD *p; + printf("<%s>: [", name); + for (p = head->next; p != head; p = p->next) + { + const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); + printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); + if (p->next != head) printf(","); + } + printf("]\n"); +#endif +} + +namespace KCP +{ + // write log + void kcp_core::ikcp_log(int mask, const char *fmt, ...) + { + char buffer[1024]; + va_list argptr; + if ((mask & this->logmask) == 0 || this->writelog == 0) return; + va_start(argptr, fmt); + vsprintf(buffer, fmt, argptr); + va_end(argptr); + this->writelog(buffer, this->user); + } + + // check log mask + int kcp_core::ikcp_canlog(int mask) + { + if ((mask & this->logmask) == 0 || this->writelog == nullptr) return 0; + return 1; + } + + // output segment + int kcp_core::call_output(const void *data, int size) + { + if (ikcp_canlog(IKCP_LOG_OUTPUT)) + { + ikcp_log(IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); + } + if (size == 0) return 0; + return this->output_callback((const char*)data, size, this->user); + } + + //--------------------------------------------------------------------- + // create a new kcpcb + //--------------------------------------------------------------------- + bool kcp_core::initialise(uint32_t conv, void *user) + { + this->conv = conv; + this->user = user; + this->snd_una = 0; + this->snd_nxt = 0; + this->rcv_nxt = 0; + this->ts_recent = 0; + this->ts_lastack = 0; + this->ts_probe = 0; + this->probe_wait = 0; + this->snd_wnd = IKCP_WND_SND; + this->rcv_wnd = IKCP_WND_RCV; + this->rmt_wnd = IKCP_WND_RCV; + this->cwnd = 0; + this->incr = 0; + this->probe = 0; + this->mtu = IKCP_MTU_DEF; + this->mss = this->mtu - IKCP_OVERHEAD; + this->stream = 0; + + this->buffer = std::make_unique((this->mtu + IKCP_OVERHEAD) * 3); + if (this->buffer == nullptr) + return false; + + this->state = 0; + this->rx_srtt = 0; + this->rx_rttval = 0; + this->rx_rto = IKCP_RTO_DEF; + this->rx_minrto = IKCP_RTO_MIN; + this->current = 0; + this->interval = IKCP_INTERVAL; + this->ts_flush = IKCP_INTERVAL; + this->nodelay = 0; + this->updated = 0; + this->logmask = 0; + this->ssthresh = IKCP_THRESH_INIT; + this->fastresend = 0; + this->fastlimit = IKCP_FASTACK_LIMIT; + this->nocwnd = 0; + this->xmit = 0; + this->dead_link = IKCP_DEADLINK; + this->fastack_conserve = false; + + return true; + } + + void kcp_core::move_kcp(kcp_core &other) + { + this->conv = other.conv; + this->user = other.user; + this->snd_una = other.snd_una; + this->snd_nxt = other.snd_nxt; + this->rcv_nxt = other.rcv_nxt; + this->ts_recent = other.ts_recent; + this->ts_lastack = other.ts_lastack; + this->ts_probe = other.ts_probe; + this->probe_wait = other.probe_wait; + this->snd_wnd = other.snd_wnd; + this->rcv_wnd = other.rcv_wnd; + this->rmt_wnd = other.rmt_wnd; + this->cwnd = other.cwnd; + this->incr = other.incr; + this->probe = other.probe; + this->mtu = other.mtu; + this->mss = other.mss; + this->stream = other.stream; + this->buffer = std::move(other.buffer); + this->state = other.state; + this->rx_srtt = other.rx_srtt; + this->rx_rttval = other.rx_rttval; + this->rx_rto = other.rx_rto; + this->rx_minrto = other.rx_minrto; + this->current = other.current; + this->interval = other.interval; + this->ts_flush = other.ts_flush; + this->nodelay = other.nodelay; + this->updated = other.updated; + this->logmask = other.logmask; + this->ssthresh = other.ssthresh; + this->fastresend = other.fastresend; + this->fastlimit = other.fastlimit; + this->nocwnd = other.nocwnd; + this->xmit = other.xmit; + this->dead_link = other.dead_link; + this->fastack_conserve = other.fastack_conserve; + } + + + + //--------------------------------------------------------------------- + // set output callback, which will be invoked by kcp + //--------------------------------------------------------------------- + void kcp_core::set_output(std::function output_callback) + { + this->output_callback = output_callback; + } + + + //--------------------------------------------------------------------- + // user/upper level recv: returns size, returns below zero for EAGAIN + //--------------------------------------------------------------------- + int kcp_core::receive(char *buffer, int len) + { + int ispeek = (len < 0) ? 1 : 0; + int peeksize; + int recover = 0; + + if (this->rcv_queue.empty()) + return -1; + + if (len < 0) len = -len; + + peeksize = peek_size(); + + if (peeksize < 0) + return -2; + + if (peeksize > len) + return -3; + + if (this->rcv_queue.size() >= this->rcv_wnd) + recover = 1; + + len = 0; + // merge fragment + for (auto seg = rcv_queue.begin(), next = seg; seg != this->rcv_queue.end(); seg = next) + { + int fragment; + ++next; + + if (buffer) + { + std::copy_n(seg->data.get(), seg->len, buffer); + buffer += seg->len; + } + + len += (int)seg->len; + fragment = seg->frg; + + if (ikcp_canlog(IKCP_LOG_RECV)) + { + ikcp_log(IKCP_LOG_RECV, "recv sn=%lu", (unsigned long)seg->sn); + } + + if (ispeek == 0) + this->rcv_queue.erase(seg); + + if (fragment == 0) + break; + } + + assert(len == peeksize); + + // move available data from rcv_buf -> rcv_queue + while (!this->rcv_buf.empty()) + { + auto seg = this->rcv_buf.begin(); + if (seg->sn == this->rcv_nxt && this->rcv_queue.size() < this->rcv_wnd) + { + this->rcv_queue.splice(this->rcv_queue.end(), this->rcv_buf, seg); + this->rcv_nxt++; + } + else break; + } + + // fast recover + if (this->rcv_queue.size() < this->rcv_wnd && recover) { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + this->probe |= IKCP_ASK_TELL; + } + + return len; + } + + + //--------------------------------------------------------------------- + // peek data size + //--------------------------------------------------------------------- + int kcp_core::peek_size() + { + int length = 0; + + if (this->rcv_queue.empty()) return -1; + + auto seg = this->rcv_queue.begin(); + if (seg->frg == 0) return (int)seg->len; + + if (this->rcv_queue.size() < (size_t)(seg->frg) + 1) return -1; + + for (seg = this->rcv_queue.begin(); seg != this->rcv_queue.end(); ++seg) + { + length += (int)seg->len; + if (seg->frg == 0) break; + } + + return length; + } + + + //--------------------------------------------------------------------- + // user/upper level send, returns below zero for error + //--------------------------------------------------------------------- + int kcp_core::send(const char *buffer, int len) + { + int count, i; + int sent = 0; + + assert(this->mss > 0); + if (len < 0) return -1; + + // append to previous segment in streaming mode (if possible) + if (this->stream != 0) + { + if (!this->snd_queue.empty()) + { + auto &seg = this->snd_queue.back(); + if (seg->len < this->mss) + { + int capacity = (int)((int64_t)this->mss - (int64_t)seg->len); + int extend = (len < capacity) ? len : capacity; + uint32_t old_size = seg->len; + bool resized = seg->resize(old_size + (uint32_t)extend); + if (!resized) + return -2; + + if (buffer) + { + std::copy_n(buffer, extend, seg->data.get() + old_size); + buffer += extend; + } + seg->len = old_size + extend; + seg->frg = 0; + len -= extend; + sent = extend; + } + } + if (len <= 0) + return sent; + } + + if (len <= (int)this->mss) count = 1; + else count = (len + this->mss - 1) / this->mss; + + if (count >= (int)IKCP_WND_RCV) + { + if (this->stream != 0 && sent > 0) + return sent; + return -2; + } + + if (count == 0) count = 1; + + // fragment + for (i = 0; i < count; i++) + { + int size = len > (int)this->mss ? (int)this->mss : len; + std::unique_ptr seg = std::make_unique(size); + if (seg == nullptr) + return -2; + + if (buffer && len > 0) + std::copy_n(buffer, size, seg->data.get()); + + seg->len = size; + seg->frg = (this->stream == 0) ? (count - i - 1) : 0; + this->snd_queue.emplace_back(std::move(seg)); + if (buffer) + buffer += size; + + len -= size; + sent += size; + } + + return sent; + } + + + //--------------------------------------------------------------------- + // parse ack + //--------------------------------------------------------------------- + void kcp_core::update_ack(int32_t rtt) + { + int32_t rto = 0; + if (this->rx_srtt == 0) + { + this->rx_srtt = rtt; + this->rx_rttval = rtt / 2; + } + else + { + long delta = rtt - this->rx_srtt; + if (delta < 0) delta = -delta; + this->rx_rttval = (3 * this->rx_rttval + delta) / 4; + this->rx_srtt = (7 * this->rx_srtt + rtt) / 8; + if (this->rx_srtt < 1) this->rx_srtt = 1; + } + rto = this->rx_srtt + _imax_(this->interval, 4 * this->rx_rttval); + this->rx_rto = _ibound_(this->rx_minrto, rto, IKCP_RTO_MAX); + } + + void kcp_core::shrink_buf() + { + if (!this->snd_buf.empty()) + this->snd_una = this->snd_buf.begin()->first; + else + this->snd_una = this->snd_nxt; + } + + void kcp_core::parse_ack(uint32_t sn) + { + if (sn < this->snd_una || sn >= this->snd_nxt) + return; + + this->snd_buf.erase(sn); + } + + void kcp_core::parse_una(uint32_t una) + { + for (auto seg = this->snd_buf.begin(), next = seg; seg != this->snd_buf.end(); seg = next) + { + ++next; + if (una > seg->first) + this->snd_buf.erase(seg); + else break; + } + } + + void kcp_core::parse_fastack(uint32_t sn, uint32_t ts) + { + if (sn < this->snd_una || sn >= this->snd_nxt) + return; + + for (auto &[seg_sn, seg] : this->snd_buf) + { + if (sn < seg_sn) + break; + else if (sn != seg->sn) + { + if (fastack_conserve && ts < seg->ts) + continue; + + this->fastack_buf[seg->fastack].erase(seg); + seg->fastack++; + this->fastack_buf[seg->fastack].insert(seg); + } + } + } + + //--------------------------------------------------------------------- + // parse data + //--------------------------------------------------------------------- + void kcp_core::parse_data(segment &newseg) + { + uint32_t sn = newseg.sn; + bool repeat = false; + + if (sn >= this->rcv_nxt + this->rcv_wnd || sn < this->rcv_nxt) + return; + + decltype(this->rcv_buf.rbegin()) seg_riter; + for (seg_riter = this->rcv_buf.rbegin(); seg_riter != this->rcv_buf.rend(); ++seg_riter) + { + if (seg_riter->sn == sn) + { + repeat = true; + break; + } + if (sn > seg_riter->sn) + break; + } + + if (!repeat) + this->rcv_buf.insert(seg_riter.base(), std::move(newseg)); + +#if 0 + PrintQueue("rcvbuf", &this->rcv_buf); + printf("rcv_nxt=%lu\n", this->rcv_nxt); +#endif + + // move available data from rcv_buf -> rcv_queue + while (!this->rcv_buf.empty()) + { + auto seg = this->rcv_buf.begin(); + if (seg->sn == this->rcv_nxt && this->rcv_queue.size() < this->rcv_wnd) + { + this->rcv_queue.splice(this->rcv_queue.end(), this->rcv_buf, seg); + this->rcv_nxt++; + } + else break; + } + +#if 0 + PrintQueue("queue", &this->rcv_queue); + printf("rcv_nxt=%lu\n", this->rcv_nxt); +#endif + +#if 1 + // printf("snd(buf=%d, queue=%d)\n", this->nsnd_buf, this->nsnd_que); + // printf("rcv(buf=%d, queue=%d)\n", this->nrcv_buf, this->nrcv_que); +#endif + } + + + //--------------------------------------------------------------------- + // input data + //--------------------------------------------------------------------- + int kcp_core::input(const char *data, long size) + { + uint32_t prev_una = this->snd_una; + uint32_t maxack = 0, latest_ts = 0; + int flag = 0; + + if (ikcp_canlog(IKCP_LOG_INPUT)) + ikcp_log(IKCP_LOG_INPUT, "[RI] %d bytes", (int)size); + + if (data == nullptr || size < (long)IKCP_OVERHEAD) return -1; + + while (size > (long)IKCP_OVERHEAD) + { + uint32_t ts, sn, len, una, conv; + uint16_t wnd; + uint8_t cmd, frg; + + data = ikcp_decode32u(data, &conv); + if (conv != this->conv) return -1; + + data = ikcp_decode8u(data, &cmd); + data = ikcp_decode8u(data, &frg); + data = ikcp_decode16u(data, &wnd); + data = ikcp_decode32u(data, &ts); + data = ikcp_decode32u(data, &sn); + data = ikcp_decode32u(data, &una); + data = ikcp_decode32u(data, &len); + + size -= IKCP_OVERHEAD; + + if (size < (long)len || (int)len < 0) return -2; + + if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && + cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) + return -3; + + this->rmt_wnd = wnd; + parse_una(una); + shrink_buf(); + + if (cmd == IKCP_CMD_ACK) + { + if (this->current >= ts) + update_ack(_itimediff(this->current, ts)); + + parse_ack(sn); + shrink_buf(); + if (flag == 0) + { + flag = 1; + maxack = sn; + latest_ts = ts; + } + else + { + if (sn > maxack) + { + if (!fastack_conserve) + { + maxack = sn; + latest_ts = ts; + } + else if (ts > latest_ts) + { + maxack = sn; + latest_ts = ts; + } + } + } + if (ikcp_canlog(IKCP_LOG_IN_ACK)) + { + ikcp_log(IKCP_LOG_IN_ACK, + "input ack: sn=%lu rtt=%ld rto=%ld", (unsigned long)sn, + (long)_itimediff(this->current, ts), + (long)this->rx_rto); + } + } + else if (cmd == IKCP_CMD_PUSH) + { + if (ikcp_canlog(IKCP_LOG_IN_DATA)) + ikcp_log(IKCP_LOG_IN_DATA, "input psh: sn=%lu ts=%lu", (unsigned long)sn, (unsigned long)ts); + + if (sn < this->rcv_nxt + this->rcv_wnd) + { + this->acklist.push_back({ sn , ts }); + if (sn >= this->rcv_nxt) + { + segment seg(len); + seg.conv = conv; + seg.cmd = cmd; + seg.frg = frg; + seg.wnd = wnd; + seg.ts = ts; + seg.sn = sn; + seg.una = una; + seg.len = len; + + if (len > 0) + std::copy_n(data, len, seg.data.get()); + + parse_data(seg); + } + } + } + else if (cmd == IKCP_CMD_WASK) + { + // ready to send back IKCP_CMD_WINS in ikcp_flush + // tell remote my window size + this->probe |= IKCP_ASK_TELL; + if (ikcp_canlog(IKCP_LOG_IN_PROBE)) + ikcp_log(IKCP_LOG_IN_PROBE, "input probe"); + } + else if (cmd == IKCP_CMD_WINS) + { + // do nothing + if (ikcp_canlog(IKCP_LOG_IN_WINS)) + ikcp_log(IKCP_LOG_IN_WINS, "input wins: %lu", (unsigned long)(wnd)); + } + else + return -3; + + data += len; + size -= len; + } + + if (flag != 0) + parse_fastack(maxack, latest_ts); + + if (this->snd_una > prev_una) + { + if (this->cwnd < this->rmt_wnd) + { + uint32_t mss = this->mss; + if (this->cwnd < this->ssthresh) + { + this->cwnd++; + this->incr += mss; + } + else + { + if (this->incr < mss) this->incr = mss; + this->incr += (mss * mss) / this->incr + (mss / 16); + if ((this->cwnd + 1) * mss <= this->incr) + { +#if 1 + this->cwnd = (this->incr + mss - 1) / ((mss > 0) ? mss : 1); +#else + this->cwnd++; +#endif + } + } + if (this->cwnd > this->rmt_wnd) + { + this->cwnd = this->rmt_wnd; + this->incr = this->rmt_wnd * mss; + } + } + } + + return 0; + } + + + //--------------------------------------------------------------------- + // ikcp_encode_seg + //--------------------------------------------------------------------- + static char *ikcp_encode_seg(char *ptr, const segment *seg) + { + ptr = ikcp_encode32u(ptr, seg->conv); + ptr = ikcp_encode8u(ptr, (uint8_t)seg->cmd); + ptr = ikcp_encode8u(ptr, (uint8_t)seg->frg); + ptr = ikcp_encode16u(ptr, (uint16_t)seg->wnd); + ptr = ikcp_encode32u(ptr, seg->ts); + ptr = ikcp_encode32u(ptr, seg->sn); + ptr = ikcp_encode32u(ptr, seg->una); + ptr = ikcp_encode32u(ptr, seg->len); + return ptr; + } + + static char *ikcp_encode_seg(char *ptr, const segment &seg) + { + ptr = ikcp_encode32u(ptr, seg.conv); + ptr = ikcp_encode8u(ptr, (uint8_t)seg.cmd); + ptr = ikcp_encode8u(ptr, (uint8_t)seg.frg); + ptr = ikcp_encode16u(ptr, (uint16_t)seg.wnd); + ptr = ikcp_encode32u(ptr, seg.ts); + ptr = ikcp_encode32u(ptr, seg.sn); + ptr = ikcp_encode32u(ptr, seg.una); + ptr = ikcp_encode32u(ptr, seg.len); + return ptr; + } + + int kcp_core::get_wnd_unused() + { + if (this->rcv_queue.size() < this->rcv_wnd) + return (int)((int64_t)this->rcv_wnd - (int64_t)this->rcv_queue.size()); + + return 0; + } + + + //--------------------------------------------------------------------- + // ikcp_flush + //--------------------------------------------------------------------- + void kcp_core::flush(uint32_t current) + { + // 'ikcp_update' haven't been called. + if (this->updated == 0) return; + + if (current == 0) + current = this->current; + else + this->current = current; + + char *buffer = this->buffer.get(); + char *ptr = buffer; + uint32_t resent, cwnd; + uint32_t rtomin; + int change = 0; + int lost = 0; + segment seg; + + seg.conv = this->conv; + seg.cmd = IKCP_CMD_ACK; + seg.frg = 0; + seg.wnd = get_wnd_unused(); + seg.una = this->rcv_nxt; + seg.sn = 0; + seg.ts = 0; + + // flush acknowledges + for (auto &[ack_sn, ack_ts] : this->acklist) + { + int size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)this->mtu) + { + call_output(buffer, size); + ptr = buffer; + } + seg.sn = ack_sn; + seg.ts = ack_ts; + ptr = ikcp_encode_seg(ptr, seg); + } + + this->acklist.clear(); + + // probe window size (if remote window size equals zero) + if (this->rmt_wnd == 0) + { + if (this->probe_wait == 0) + { + this->probe_wait = IKCP_PROBE_INIT; + this->ts_probe = this->current + this->probe_wait; + } + else + { + if (this->current >= this->ts_probe) + { + if (this->probe_wait < IKCP_PROBE_INIT) + this->probe_wait = IKCP_PROBE_INIT; + this->probe_wait += this->probe_wait / 2; + if (this->probe_wait > IKCP_PROBE_LIMIT) + this->probe_wait = IKCP_PROBE_LIMIT; + this->ts_probe = this->current + this->probe_wait; + this->probe |= IKCP_ASK_SEND; + } + } + } + else + { + this->ts_probe = 0; + this->probe_wait = 0; + } + + // flush window probing commands + if (this->probe & IKCP_ASK_SEND) + { + seg.cmd = IKCP_CMD_WASK; + int size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)this->mtu) + { + call_output(buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, seg); + } + + // flush window probing commands + if (this->probe & IKCP_ASK_TELL) + { + seg.cmd = IKCP_CMD_WINS; + int size = (int)(ptr - buffer); + if (size + (int)IKCP_OVERHEAD > (int)this->mtu) + { + call_output(buffer, size); + ptr = buffer; + } + ptr = ikcp_encode_seg(ptr, seg); + } + + this->probe = 0; + + // calculate window size + cwnd = _imin_(this->snd_wnd, this->rmt_wnd); + if (this->nocwnd == 0) cwnd = _imin_(this->cwnd, cwnd); + + // calculate resent + resent = (this->fastresend > 0) ? (uint32_t)this->fastresend : 0xffffffff; + rtomin = (this->nodelay == 0) ? (this->rx_rto >> 3) : 0; + + // flush data segments + + for (auto iter = this->resendts_buf.begin(), next = iter; iter != this->resendts_buf.end(); iter = next) + { + ++next; + auto &[resend_ts, seg_list] = *iter; + if (seg_list.empty()) + { + this->resendts_buf.erase(iter); + continue; + } + + if (current < resend_ts) break; + + for (auto seg_iter = seg_list.begin(), seg_next = seg_iter; + seg_iter != seg_list.end(); + seg_iter = seg_next) + { + ++seg_next; + std::shared_ptr segptr = seg_iter->lock(); + if (segptr == nullptr) + { + seg_list.erase(seg_iter); + continue; + } + + segptr->xmit++; + this->xmit++; + if (this->nodelay == 0) + { + segptr->rto += _imax_(segptr->rto, (uint32_t)this->rx_rto); + } + else + { + int32_t step = (this->nodelay < 2) ? + ((int32_t)(segptr->rto)) : this->rx_rto; + segptr->rto += step / 2; + } + segptr->resendts = current + segptr->rto; + lost = 1; + + seg_list.erase(seg_iter); + this->resendts_buf[segptr->resendts].insert(segptr); + + segptr->ts = current; + segptr->wnd = seg.wnd; + segptr->una = this->rcv_nxt; + ptr = send_out(ptr, buffer, segptr.get()); + } + + if (seg_list.empty()) + this->resendts_buf.erase(iter); + } + + for (auto iter = this->fastack_buf.rbegin(), next = iter; iter != this->fastack_buf.rend(); iter = next) + { + ++next; + auto &[fast_ack, seg_list] = *iter; + if (seg_list.empty()) + continue; + + if (fast_ack < resent) break; + + for (auto seg_iter = seg_list.begin(), seg_next = seg_iter; + seg_iter != seg_list.end(); + seg_iter = seg_next) + { + ++seg_next; + std::shared_ptr segptr = seg_iter->lock(); + if (segptr == nullptr) + { + seg_list.erase(seg_iter); + continue; + } + + if ((int)segptr->xmit <= this->fastlimit || this->fastlimit <= 0) + { + uint32_t old_resendtrs = segptr->resendts; + segptr->xmit++; + segptr->fastack = 0; + segptr->resendts = current + segptr->rto; + change++; + + seg_list.erase(seg_iter); + this->fastack_buf[segptr->fastack].insert(segptr); + this->resendts_buf[old_resendtrs].erase(segptr); + this->resendts_buf[segptr->resendts].insert(segptr); + + segptr->ts = current; + segptr->wnd = seg.wnd; + segptr->una = this->rcv_nxt; + ptr = send_out(ptr, buffer, segptr.get()); + } + } + } + + // move data from snd_queue to snd_buf + while (this->snd_nxt < this->snd_una + cwnd && !this->snd_queue.empty()) + { + auto iter = this->snd_queue.begin(); + std::shared_ptr newseg = std::move(*iter); + + newseg->conv = this->conv; + newseg->cmd = IKCP_CMD_PUSH; + newseg->wnd = seg.wnd; + newseg->ts = current; + newseg->sn = this->snd_nxt++; + newseg->una = this->rcv_nxt; + newseg->resendts = current + this->rx_rto + rtomin; + newseg->rto = this->rx_rto; + newseg->fastack = 0; + newseg->xmit = 1; + + this->snd_buf[newseg->sn] = newseg; + this->snd_queue.pop_front(); + resendts_buf[newseg->resendts].insert(newseg); + fastack_buf[newseg->fastack].insert(newseg); + + ptr = send_out(ptr, buffer, newseg.get()); + } + + // flash remain segments + if (int size = (int)(ptr - buffer); size > 0) + call_output(buffer, size); + + + // update ssthresh + if (change) + { + uint32_t inflight = this->snd_nxt - this->snd_una; + this->ssthresh = inflight / 2; + if (this->ssthresh < IKCP_THRESH_MIN) + this->ssthresh = IKCP_THRESH_MIN; + this->cwnd = this->ssthresh + resent; + this->incr = this->cwnd * this->mss; + } + + if (lost) + { + this->ssthresh = cwnd / 2; + if (this->ssthresh < IKCP_THRESH_MIN) + this->ssthresh = IKCP_THRESH_MIN; + this->cwnd = 1; + this->incr = this->mss; + } + + if (this->cwnd < 1) + { + this->cwnd = 1; + this->incr = this->mss; + } + } + + + //--------------------------------------------------------------------- + // update state (call it repeatedly, every 10ms-100ms), or you can ask + // ikcp_check when to call it again (without ikcp_input/_send calling). + // 'current' - current timestamp in millisec. + //--------------------------------------------------------------------- + void kcp_core::update(uint32_t current) + { + int32_t slap; + + this->current = current; + + if (this->updated == 0) + { + this->updated = 1; + this->ts_flush = this->current; + } + + slap = _itimediff(this->current, this->ts_flush); + + if (slap >= 10000 || slap < -10000) + { + this->ts_flush = this->current; + slap = 0; + } + + if (slap >= 0) + { + this->ts_flush += this->interval; + if (this->current >= this->ts_flush) + this->ts_flush = this->current + this->interval; + + flush(); + } + } + + + //--------------------------------------------------------------------- + // Determine when should you invoke ikcp_update: + // returns when you should invoke ikcp_update in millisec, if there + // is no ikcp_input/_send calling. you can call ikcp_update in that + // time, instead of call update repeatly. + // Important to reduce unnacessary ikcp_update invoking. use it to + // schedule ikcp_update (eg. implementing an epoll-like mechanism, + // or optimize ikcp_update when handling massive kcp connections) + //--------------------------------------------------------------------- + uint32_t kcp_core::check(uint32_t current) + { + uint32_t ts_flush = this->ts_flush; + int32_t tm_flush = 0x7fffffff; + int32_t tm_packet = 0x7fffffff; + uint32_t minimal = 0; + + if (this->updated == 0) + return current; + + if (_itimediff(current, ts_flush) >= 10000 || _itimediff(current, ts_flush) < -10000) + ts_flush = current; + + if (current >= ts_flush) + return current; + + tm_flush = _itimediff(ts_flush, current); + + if (!this->resendts_buf.empty()) + { + auto &[resend_ts, seg_list] = *this->resendts_buf.begin(); + + int32_t diff = _itimediff(resend_ts, current); + if (diff <= 0) + return current; + + if (diff < tm_packet) + tm_packet = diff; + } + + minimal = (uint32_t)(tm_packet < tm_flush ? tm_packet : tm_flush); + if (minimal >= this->interval) minimal = this->interval; + + return current + minimal; + } + + int kcp_core::set_mtu(int mtu) + { + if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) + return -1; + std::unique_ptr buffer = std::make_unique((mtu + IKCP_OVERHEAD) * 3); + if (buffer == nullptr) + return -2; + this->mtu = mtu; + this->mss = this->mtu - IKCP_OVERHEAD; + this->buffer = std::move(buffer); + return 0; + } + + int kcp_core::set_interval(int interval) + { + if (interval > 5000) interval = 5000; + else if (interval <= 0) interval = 1; + this->interval = interval; + return 0; + } + + int kcp_core::set_nodelay(int nodelay, int interval, int resend, int nc) + { + if (nodelay >= 0) + { + this->nodelay = nodelay; + if (nodelay) + this->rx_minrto = IKCP_RTO_NDL; + else + this->rx_minrto = IKCP_RTO_MIN; + } + if (interval >= 0) + { + if (interval > 5000) interval = 5000; + else if (interval <= 0) interval = 1; + this->interval = interval; + } + if (resend >= 0) + this->fastresend = resend; + + if (nc >= 0) + this->nocwnd = nc; + + return 0; + } + + int kcp_core::set_wndsize(int sndwnd, int rcvwnd) + { + if (sndwnd > 0) + this->snd_wnd = sndwnd; + + if (rcvwnd > 0) // must >= max fragment size + this->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); + + return 0; + } + + int kcp_core::get_waitsnd() + { + return (int)(this->snd_buf.size() + this->snd_queue.size()); + } + + // read conv + uint32_t kcp_core::get_conv(const void *ptr) + { + uint32_t kcp_conv; + ikcp_decode32u((const char*)ptr, &kcp_conv); + return kcp_conv; + } + + uint32_t kcp_core::get_conv() + { + return conv; + } + + char* KCP::kcp_core::send_out(char *ptr, char *buffer, segment *segptr) + { + int size = (int)(ptr - buffer); + int need = (int)IKCP_OVERHEAD + (int)segptr->len; + + if (size + need > (int)this->mtu) + { + call_output(buffer, size); + ptr = buffer; + } + + ptr = ikcp_encode_seg(ptr, segptr); + + if (segptr->len > 0) + { + std::copy_n(segptr->data.get(), segptr->len, ptr); + ptr += segptr->len; + } + + if (segptr->xmit >= this->dead_link) + this->state = (uint32_t)-1; + + return ptr; + } +} + diff --git a/src/3rd_party/ikcp.h b/src/3rd_party/ikcp.h deleted file mode 100644 index 2822df7..0000000 --- a/src/3rd_party/ikcp.h +++ /dev/null @@ -1,417 +0,0 @@ -//===================================================================== -// -// KCP - A Better ARQ Protocol Implementation -// skywind3000 (at) gmail.com, 2010-2011 -// -// Features: -// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. -// + Maximum RTT reduce three times vs tcp. -// + Lightweight, distributed as a single source file. -// -//===================================================================== -#ifndef __IKCP_H__ -#define __IKCP_H__ - -#include -#include -#include - - -//===================================================================== -// 32BIT INTEGER DEFINITION -//===================================================================== -#ifndef __INTEGER_32_BITS__ -#define __INTEGER_32_BITS__ -#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ - defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ - defined(_M_AMD64) - typedef unsigned int ISTDUINT32; - typedef int ISTDINT32; -#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ - defined(__i386) || defined(_M_X86) - typedef unsigned long ISTDUINT32; - typedef long ISTDINT32; -#elif defined(__MACOS__) - typedef UInt32 ISTDUINT32; - typedef SInt32 ISTDINT32; -#elif defined(__APPLE__) && defined(__MACH__) - #include - typedef u_int32_t ISTDUINT32; - typedef int32_t ISTDINT32; -#elif defined(__BEOS__) - #include - typedef u_int32_t ISTDUINT32; - typedef int32_t ISTDINT32; -#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) - typedef unsigned __int32 ISTDUINT32; - typedef __int32 ISTDINT32; -#elif defined(__GNUC__) - #include - typedef uint32_t ISTDUINT32; - typedef int32_t ISTDINT32; -#else - typedef unsigned long ISTDUINT32; - typedef long ISTDINT32; -#endif -#endif - - -//===================================================================== -// Integer Definition -//===================================================================== -#ifndef __IINT8_DEFINED -#define __IINT8_DEFINED -typedef char IINT8; -#endif - -#ifndef __IUINT8_DEFINED -#define __IUINT8_DEFINED -typedef unsigned char IUINT8; -#endif - -#ifndef __IUINT16_DEFINED -#define __IUINT16_DEFINED -typedef unsigned short IUINT16; -#endif - -#ifndef __IINT16_DEFINED -#define __IINT16_DEFINED -typedef short IINT16; -#endif - -#ifndef __IINT32_DEFINED -#define __IINT32_DEFINED -typedef ISTDINT32 IINT32; -#endif - -#ifndef __IUINT32_DEFINED -#define __IUINT32_DEFINED -typedef ISTDUINT32 IUINT32; -#endif - -#ifndef __IINT64_DEFINED -#define __IINT64_DEFINED -#if defined(_MSC_VER) || defined(__BORLANDC__) -typedef __int64 IINT64; -#else -typedef long long IINT64; -#endif -#endif - -#ifndef __IUINT64_DEFINED -#define __IUINT64_DEFINED -#if defined(_MSC_VER) || defined(__BORLANDC__) -typedef unsigned __int64 IUINT64; -#else -typedef unsigned long long IUINT64; -#endif -#endif - -#ifndef INLINE -#if defined(__GNUC__) - -#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) -#define INLINE __inline__ __attribute__((always_inline)) -#else -#define INLINE __inline__ -#endif - -#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) -#define INLINE __inline -#else -#define INLINE -#endif -#endif - -#if (!defined(__cplusplus)) && (!defined(inline)) -#define inline INLINE -#endif - - -//===================================================================== -// QUEUE DEFINITION -//===================================================================== -#ifndef __IQUEUE_DEF__ -#define __IQUEUE_DEF__ - -struct IQUEUEHEAD { - struct IQUEUEHEAD *next, *prev; -}; - -typedef struct IQUEUEHEAD iqueue_head; - - -//--------------------------------------------------------------------- -// queue init -//--------------------------------------------------------------------- -#define IQUEUE_HEAD_INIT(name) { &(name), &(name) } -#define IQUEUE_HEAD(name) \ - struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) - -#define IQUEUE_INIT(ptr) ( \ - (ptr)->next = (ptr), (ptr)->prev = (ptr)) - -#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) - -#define ICONTAINEROF(ptr, type, member) ( \ - (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) - -#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) - - -//--------------------------------------------------------------------- -// queue operation -//--------------------------------------------------------------------- -#define IQUEUE_ADD(node, head) ( \ - (node)->prev = (head), (node)->next = (head)->next, \ - (head)->next->prev = (node), (head)->next = (node)) - -#define IQUEUE_ADD_TAIL(node, head) ( \ - (node)->prev = (head)->prev, (node)->next = (head), \ - (head)->prev->next = (node), (head)->prev = (node)) - -#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) - -#define IQUEUE_DEL(entry) (\ - (entry)->next->prev = (entry)->prev, \ - (entry)->prev->next = (entry)->next, \ - (entry)->next = 0, (entry)->prev = 0) - -#define IQUEUE_DEL_INIT(entry) do { \ - IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) - -#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) - -#define iqueue_init IQUEUE_INIT -#define iqueue_entry IQUEUE_ENTRY -#define iqueue_add IQUEUE_ADD -#define iqueue_add_tail IQUEUE_ADD_TAIL -#define iqueue_del IQUEUE_DEL -#define iqueue_del_init IQUEUE_DEL_INIT -#define iqueue_is_empty IQUEUE_IS_EMPTY - -#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ - for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ - &((iterator)->MEMBER) != (head); \ - (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) - -#define iqueue_foreach(iterator, head, TYPE, MEMBER) \ - IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) - -#define iqueue_foreach_entry(pos, head) \ - for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) - - -#define __iqueue_splice(list, head) do { \ - iqueue_head *first = (list)->next, *last = (list)->prev; \ - iqueue_head *at = (head)->next; \ - (first)->prev = (head), (head)->next = (first); \ - (last)->next = (at), (at)->prev = (last); } while (0) - -#define iqueue_splice(list, head) do { \ - if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) - -#define iqueue_splice_init(list, head) do { \ - iqueue_splice(list, head); iqueue_init(list); } while (0) - - -#ifdef _MSC_VER -#pragma warning(disable:4311) -#pragma warning(disable:4312) -#pragma warning(disable:4996) -#endif - -#endif - - -//--------------------------------------------------------------------- -// BYTE ORDER & ALIGNMENT -//--------------------------------------------------------------------- -#ifndef IWORDS_BIG_ENDIAN - #ifdef _BIG_ENDIAN_ - #if _BIG_ENDIAN_ - #define IWORDS_BIG_ENDIAN 1 - #endif - #endif - #ifndef IWORDS_BIG_ENDIAN - #if defined(__hppa__) || \ - defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ - (defined(__MIPS__) && defined(__MIPSEB__)) || \ - defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ - defined(__sparc__) || defined(__powerpc__) || \ - defined(__mc68000__) || defined(__s390x__) || defined(__s390__) - #define IWORDS_BIG_ENDIAN 1 - #endif - #endif - #ifndef IWORDS_BIG_ENDIAN - #define IWORDS_BIG_ENDIAN 0 - #endif -#endif - -#ifndef IWORDS_MUST_ALIGN - #if defined(__i386__) || defined(__i386) || defined(_i386_) - #define IWORDS_MUST_ALIGN 0 - #elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__) - #define IWORDS_MUST_ALIGN 0 - #elif defined(__amd64) || defined(__amd64__) - #define IWORDS_MUST_ALIGN 0 - #else - #define IWORDS_MUST_ALIGN 1 - #endif -#endif - - -//===================================================================== -// SEGMENT -//===================================================================== -struct IKCPSEG -{ - struct IQUEUEHEAD node; - IUINT32 conv; - IUINT32 cmd; - IUINT32 frg; - IUINT32 wnd; - IUINT32 ts; - IUINT32 sn; - IUINT32 una; - IUINT32 len; - IUINT32 resendts; - IUINT32 rto; - IUINT32 fastack; - IUINT32 xmit; - char data[1]; -}; - - -//--------------------------------------------------------------------- -// IKCPCB -//--------------------------------------------------------------------- -struct IKCPCB -{ - IUINT32 conv, mtu, mss, state; - IUINT32 snd_una, snd_nxt, rcv_nxt; - IUINT32 ts_recent, ts_lastack, ssthresh; - IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; - IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; - IUINT32 current, interval, ts_flush, xmit; - IUINT32 min_resendts; - IUINT32 nrcv_buf, nsnd_buf; - IUINT32 nrcv_que, nsnd_que; - IUINT32 nodelay, updated; - IUINT32 ts_probe, probe_wait; - IUINT32 dead_link, incr; - struct IQUEUEHEAD snd_queue; - struct IQUEUEHEAD rcv_queue; - struct IQUEUEHEAD snd_buf; - struct IQUEUEHEAD rcv_buf; - IUINT32 *acklist; - IUINT32 ackcount; - IUINT32 ackblock; - void *user; - char *buffer; - int fastresend; - int fastlimit; - int nocwnd, stream; - int logmask; - int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); - void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); -}; - - -typedef struct IKCPCB ikcpcb; - -#define IKCP_LOG_OUTPUT 1 -#define IKCP_LOG_INPUT 2 -#define IKCP_LOG_SEND 4 -#define IKCP_LOG_RECV 8 -#define IKCP_LOG_IN_DATA 16 -#define IKCP_LOG_IN_ACK 32 -#define IKCP_LOG_IN_PROBE 64 -#define IKCP_LOG_IN_WINS 128 -#define IKCP_LOG_OUT_DATA 256 -#define IKCP_LOG_OUT_ACK 512 -#define IKCP_LOG_OUT_PROBE 1024 -#define IKCP_LOG_OUT_WINS 2048 - -#ifdef __cplusplus -extern "C" { -#endif - -//--------------------------------------------------------------------- -// interface -//--------------------------------------------------------------------- - -// create a new kcp control object, 'conv' must equal in two endpoint -// from the same connection. 'user' will be passed to the output callback -// output callback can be setup like this: 'kcp->output = my_udp_output' -ikcpcb* ikcp_create(IUINT32 conv, void *user); - -// release kcp control object -void ikcp_release(ikcpcb *kcp); - -// set output callback, which will be invoked by kcp -void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, - ikcpcb *kcp, void *user)); - -// user/upper level recv: returns size, returns below zero for EAGAIN -int ikcp_recv(ikcpcb *kcp, char *buffer, int len); - -// user/upper level send, returns below zero for error -int ikcp_send(ikcpcb *kcp, const char *buffer, int len); - -// update state (call it repeatedly, every 10ms-100ms), or you can ask -// ikcp_check when to call it again (without ikcp_input/_send calling). -// 'current' - current timestamp in millisec. -void ikcp_update(ikcpcb *kcp, IUINT32 current); - -// Determine when should you invoke ikcp_update: -// returns when you should invoke ikcp_update in millisec, if there -// is no ikcp_input/_send calling. you can call ikcp_update in that -// time, instead of call update repeatly. -// Important to reduce unnacessary ikcp_update invoking. use it to -// schedule ikcp_update (eg. implementing an epoll-like mechanism, -// or optimize ikcp_update when handling massive kcp connections) -IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); - -// when you received a low level packet (eg. UDP packet), call it -int ikcp_input(ikcpcb *kcp, const char *data, long size); - -// flush pending data -void ikcp_flush(ikcpcb *kcp); - -// check the size of next message in the recv queue -int ikcp_peeksize(const ikcpcb *kcp); - -// change MTU size, default is 1400 -int ikcp_setmtu(ikcpcb *kcp, int mtu); - -// set maximum window size: sndwnd=32, rcvwnd=32 by default -int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); - -// get how many packet is waiting to be sent -int ikcp_waitsnd(const ikcpcb *kcp); - -// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) -// nodelay: 0:disable(default), 1:enable -// interval: internal update timer interval in millisec, default is 100ms -// resend: 0:disable fast resend(default), 1:enable fast resend -// nc: 0:normal congestion control(default), 1:disable congestion control -int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); - - -void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); - -// setup allocator -void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); - -// read conv -IUINT32 ikcp_getconv(const void *ptr); - - -#ifdef __cplusplus -} -#endif - -#endif - - diff --git a/src/3rd_party/ikcp.hpp b/src/3rd_party/ikcp.hpp new file mode 100644 index 0000000..c8243a9 --- /dev/null +++ b/src/3rd_party/ikcp.hpp @@ -0,0 +1,217 @@ +//===================================================================== +// +// KCP - A Better ARQ Protocol Implementation +// skywind3000 (at) gmail.com, 2010-2011 +// Modifier: cnbatch, 2023 +// +// Features: +// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. +// + Maximum RTT reduce three times vs tcp. +// + Lightweight, distributed as a single source file. +// +//===================================================================== +#ifndef __IKCP_HPP__ +#define __IKCP_HPP__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef _MSC_VER +#pragma warning(disable:4311) +#pragma warning(disable:4312) +#pragma warning(disable:4996) +#endif + + +namespace KCP +{ + //===================================================================== + // SEGMENT + //===================================================================== + struct segment + { + uint32_t conv = 0; + uint32_t cmd = 0; + uint32_t frg = 0; + uint32_t wnd = 0; + uint32_t ts = 0; + uint32_t sn = 0; + uint32_t una = 0; + uint32_t len = 0; + uint32_t resendts = 0; + uint32_t rto = 0; + uint32_t fastack = 0; + uint32_t xmit = 0; + std::unique_ptr data; + + segment() = default; + segment(const segment &other) = delete; + segment(segment &&other) = default; + segment(uint32_t new_size) + { + data = std::make_unique(new_size); + if (data != nullptr) + len = new_size; + } + + bool resize(uint32_t new_size) + { + std::unique_ptr new_data = std::make_unique(new_size); + if (new_data == nullptr) return false; + if (data != nullptr) + std::copy_n(data.get(), len, new_data.get()); + data = std::move(new_data); + return true; + } + }; + + + //--------------------------------------------------------------------- + // IKCPCB + //--------------------------------------------------------------------- + struct kcp_core + { + uint32_t conv, mtu, mss, state; + uint32_t snd_una, snd_nxt, rcv_nxt; + uint32_t ts_recent, ts_lastack, ssthresh; + int32_t rx_rttval, rx_srtt, rx_rto, rx_minrto; + uint32_t snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; + uint32_t current, interval, ts_flush, xmit; + uint32_t nodelay, updated; + uint32_t ts_probe, probe_wait; + uint32_t dead_link, incr; + std::list> snd_queue; + std::list rcv_queue; + //std::list> snd_buf; + std::map> snd_buf; // SN -> segment + std::map, std::owner_less<>>> resendts_buf; // resendts -> segment + std::map, std::owner_less<>>> fastack_buf; // fastack -> segment + std::list rcv_buf; + std::vector> acklist; + void *user; + std::unique_ptr buffer; + int fastresend; + int fastlimit; + int nocwnd, stream; + int logmask; + std::function output_callback; // int(*output)(const char *buf, int len, void *user) + std::function writelog; //void(*writelog)(const char *log, void *user) + bool fastack_conserve; + + //--------------------------------------------------------------------- + // interface + //--------------------------------------------------------------------- + + kcp_core() = default; + // create a new kcp control object, 'conv' must equal in two endpoint + // from the same connection. 'user' will be passed to the output callback + // output callback can be setup like this: 'kcp->output = my_udp_output' + bool initialise(uint32_t conv, void *user); + void move_kcp(kcp_core &other); + + kcp_core(const kcp_core&) = delete; + kcp_core(kcp_core &&other) noexcept { move_kcp(other); } + kcp_core operator=(const kcp_core&) = delete; + kcp_core& operator=(kcp_core &&other) noexcept { move_kcp(other); return *this; } + + // release kcp control object + ~kcp_core() = default; + + // set output callback, which will be invoked by kcp + void set_output(std::function output_callback); + + // user/upper level recv: returns size, returns below zero for EAGAIN + int receive(char *buffer, int len); + + // user/upper level send, returns below zero for error + int send(const char *buffer, int len); + + // update state (call it repeatedly, every 10ms-100ms), or you can ask + // ikcp_check when to call it again (without ikcp_input/_send calling). + // 'current' - current timestamp in millisec. + void update(uint32_t current); + + // Determine when should you invoke ikcp_update: + // returns when you should invoke ikcp_update in millisec, if there + // is no ikcp_input/_send calling. you can call ikcp_update in that + // time, instead of call update repeatly. + // Important to reduce unnacessary ikcp_update invoking. use it to + // schedule ikcp_update (eg. implementing an epoll-like mechanism, + // or optimize ikcp_update when handling massive kcp connections) + uint32_t check(uint32_t current); + + // when you received a low level packet (eg. UDP packet), call it + int input(const char *data, long size); + + // flush pending data + void flush(uint32_t current = 0); + + // check the size of next message in the recv queue + int peek_size(); + + // change MTU size, default is 1400 + int set_mtu(int mtu); + + // set maximum window size: sndwnd=32, rcvwnd=32 by default + int set_wndsize(int sndwnd, int rcvwnd); + + // get how many packet is waiting to be sent + int get_waitsnd(); + + int set_interval(int interval); + + // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) + // nodelay: 0:disable(default), 1:enable + // interval: internal update timer interval in millisec, default is 100ms + // resend: 0:disable fast resend(default), 1:enable fast resend + // nc: 0:normal congestion control(default), 1:disable congestion control + int set_nodelay(int nodelay, int interval, int resend, int nc); + + + void ikcp_log(int mask, const char *fmt, ...); + + // read conv + static uint32_t get_conv(const void *ptr); + uint32_t get_conv(); + + protected: + void update_ack(int32_t rtt); + void shrink_buf(); + void parse_ack(uint32_t sn); + void parse_una(uint32_t una); + void parse_fastack(uint32_t sn, uint32_t ts); + int get_wnd_unused(); + void parse_data(segment &newseg); + int ikcp_canlog(int mask); + int call_output(const void *data, int size); + char* send_out(char *ptr, char *buffer, segment *newseg); + }; +} + +#define IKCP_LOG_OUTPUT 1 +#define IKCP_LOG_INPUT 2 +#define IKCP_LOG_SEND 4 +#define IKCP_LOG_RECV 8 +#define IKCP_LOG_IN_DATA 16 +#define IKCP_LOG_IN_ACK 32 +#define IKCP_LOG_IN_PROBE 64 +#define IKCP_LOG_IN_WINS 128 +#define IKCP_LOG_OUT_DATA 256 +#define IKCP_LOG_OUT_ACK 512 +#define IKCP_LOG_OUT_PROBE 1024 +#define IKCP_LOG_OUT_WINS 2048 + + + +#endif + + diff --git a/src/main.cpp b/src/main.cpp index ac5adc0..a1bc1ed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,7 @@ int main(int argc, char *argv[]) { char app_name[] = "kcptube"; - printf("%s version 20230924\n", app_name); + printf("%s version 20231002\n", app_name); if (argc <= 1) { diff --git a/src/networks/client.cpp b/src/networks/client.cpp index 96b6e20..f06c245 100644 --- a/src/networks/client.cpp +++ b/src/networks/client.cpp @@ -258,7 +258,7 @@ void client_mode::udp_listener_incoming(std::unique_ptr data, size_t uint32_t next_refresh_time = current_settings.blast ? kcp_session->Refresh() : kcp_session->Check(); kcp_updater.submit(kcp_session, next_refresh_time); - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_session->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_session->GetUserData(); kcp_mappings_ptr->last_data_transfer_time.store(packet::right_now()); } @@ -305,7 +305,7 @@ void client_mode::udp_forwarder_incoming_unpack(std::shared_ptr kcp_pt if (kcp_ptr->Input((const char *)data_ptr, (long)packet_data_size) < 0) return; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); if (kcp_mappings_ptr == nullptr) return; @@ -462,7 +462,7 @@ void client_mode::udp_forwarder_to_disconnecting_tcp(std::shared_ptr k continue; } - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); tcp_session *tcp_channel = kcp_mappings_ptr->local_tcp.get(); switch (ftr) @@ -1200,7 +1200,7 @@ bool client_mode::handshake_timeout_detection(kcp_mappings *kcp_mappings_ptr) handshakes[new_kcp_mappings_ptr.get()] = new_kcp_mappings_ptr; locker.unlock(); - kcp_mappings_ptr->egress_kcp->custom_data.store(nullptr); + kcp_mappings_ptr->egress_kcp->SetUserData(nullptr); kcp_updater.remove(kcp_mappings_ptr->egress_kcp); uint32_t next_update_time = new_kcp_mappings_ptr->egress_kcp->Check(); kcp_updater.submit(new_kcp_mappings_ptr->egress_kcp, next_update_time); @@ -1497,7 +1497,7 @@ void client_mode::loop_keep_alive() continue; timestamp += current_settings.keep_alive; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); std::vector keep_alive_packet = packet::create_keep_alive_packet(kcp_mappings_ptr->connection_protocol); kcp_ptr->Send((const char*)keep_alive_packet.data(), keep_alive_packet.size()); @@ -1563,7 +1563,7 @@ std::shared_ptr client_mode::create_handshake(feature ftr, protoco { std::shared_ptr handshake_kcp = std::make_shared(); std::shared_ptr handshake_kcp_mappings = std::make_shared(); - handshake_kcp->custom_data.store(handshake_kcp_mappings.get()); + handshake_kcp->SetUserData(handshake_kcp_mappings.get()); handshake_kcp_mappings->egress_kcp = handshake_kcp; handshake_kcp_mappings->connection_protocol = prtcl; handshake_kcp_mappings->changeport_timestamp.store(LLONG_MAX); @@ -1722,7 +1722,7 @@ void client_mode::on_handshake_success(kcp_mappings *handshake_ptr, const packet return; udp_forwarder->async_receive(); - kcp_ptr->custom_data.store(kcp_mappings_ptr.get()); + kcp_ptr->SetUserData(kcp_mappings_ptr.get()); kcp_ptr->keep_alive_send_time.store(timestamp); kcp_ptr->keep_alive_response_time.store(timestamp); kcp_ptr->SetMTU(current_settings.kcp_mtu); @@ -1730,6 +1730,7 @@ void client_mode::on_handshake_success(kcp_mappings *handshake_ptr, const packet kcp_ptr->NoDelay(current_settings.kcp_nodelay, current_settings.kcp_interval, current_settings.kcp_resend, current_settings.kcp_nc); kcp_ptr->RxMinRTO() = 10; kcp_ptr->SetBandwidth(outbound_bandwidth, current_settings.inbound_bandwidth); + kcp_ptr->SetAsConserve(current_settings.kcp_conserve); std::weak_ptr handshake_kcp_weak = handshake_ptr->egress_kcp; std::weak_ptr data_ptr_weak = kcp_ptr; handshake_ptr->mapping_function = [this, handshake_kcp_weak, data_ptr_weak]() { set_kcp_windows(handshake_kcp_weak, data_ptr_weak); }; @@ -1920,7 +1921,7 @@ void client_mode::handle_handshake(std::shared_ptr kcp_ptr, std::uniqu if (kcp_ptr->Input((const char *)data_ptr, (long)packet_data_size) < 0) return; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); if (kcp_mappings_ptr == nullptr) return; diff --git a/src/networks/connections.cpp b/src/networks/connections.cpp index e732f50..4900b57 100644 --- a/src/networks/connections.cpp +++ b/src/networks/connections.cpp @@ -113,8 +113,7 @@ void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, uint16_t generate_new_port_number(uint16_t start_port_num, uint16_t end_port_num) { - std::random_device rd; - std::mt19937 mt(rd()); + thread_local std::mt19937 mt(std::random_device{}()); std::uniform_int_distribution uniform_dist(start_port_num, end_port_num); return uniform_dist(mt); } @@ -483,7 +482,8 @@ void tcp_session::stop() { stopped.store(true); callback = empty_tcp_callback; - disconnect(); + if (is_open()) + disconnect(); } bool tcp_session::is_pause() const @@ -525,7 +525,7 @@ void tcp_session::async_read_data() size_t tcp_session::send_data(const std::vector &buffer_data) { - if (stopped.load()) + if (stopped.load() || !connection_socket.is_open() || buffer_data.empty()) return 0; size_t sent_size = connection_socket.send(asio::buffer(buffer_data)); @@ -535,7 +535,7 @@ size_t tcp_session::send_data(const std::vector &buffer_data) size_t tcp_session::send_data(const uint8_t *buffer_data, size_t size_in_bytes) { - if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr) + if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr || size_in_bytes == 0) return 0; size_t sent_size = connection_socket.send(asio::buffer(buffer_data, size_in_bytes)); @@ -545,7 +545,7 @@ size_t tcp_session::send_data(const uint8_t *buffer_data, size_t size_in_bytes) size_t tcp_session::send_data(const uint8_t *buffer_data, size_t size_in_bytes, asio::error_code &ec) { - if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr) + if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr || size_in_bytes == 0) return 0; size_t sent_size = connection_socket.send(asio::buffer(buffer_data, size_in_bytes), 0, ec); @@ -555,7 +555,7 @@ size_t tcp_session::send_data(const uint8_t *buffer_data, size_t size_in_bytes, void tcp_session::async_send_data(std::unique_ptr> data) { - if (stopped.load() || !connection_socket.is_open() || data == nullptr) + if (stopped.load() || !connection_socket.is_open() || data == nullptr || data->empty()) return; auto asio_buffer = asio::buffer(*data); @@ -568,7 +568,7 @@ void tcp_session::async_send_data(std::unique_ptr> data) void tcp_session::async_send_data(std::vector &&data) { - if (stopped.load() || !connection_socket.is_open()) + if (stopped.load() || !connection_socket.is_open() || data.empty()) return; auto asio_buffer = asio::buffer(data); @@ -579,7 +579,7 @@ void tcp_session::async_send_data(std::vector &&data) void tcp_session::async_send_data(std::unique_ptr buffer_data, size_t size_in_bytes) { - if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr) + if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr || size_in_bytes == 0) return; auto asio_buffer = asio::buffer(buffer_data.get(), size_in_bytes); @@ -590,7 +590,7 @@ void tcp_session::async_send_data(std::unique_ptr buffer_data, size_t void tcp_session::async_send_data(std::unique_ptr buffer_data, uint8_t *start_pos, size_t size_in_bytes) { - if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr || start_pos == nullptr) + if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr || start_pos == nullptr || size_in_bytes == 0) return; asio::async_write(connection_socket, asio::buffer(start_pos, size_in_bytes), @@ -600,7 +600,7 @@ void tcp_session::async_send_data(std::unique_ptr buffer_data, uint8_ void tcp_session::async_send_data(const uint8_t *buffer_data, size_t size_in_bytes) { - if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr) + if (stopped.load() || !connection_socket.is_open() || buffer_data == nullptr || size_in_bytes == 0) return; asio::async_write(connection_socket, asio::buffer(buffer_data, size_in_bytes), @@ -949,7 +949,7 @@ void udp_client::async_receive() size_t udp_client::send_out(const std::vector &data, const udp::endpoint &peer_endpoint, asio::error_code &ec) { - if (stopped.load()) + if (stopped.load() || data.empty()) return 0; size_t sent_size = connection_socket.send_to(asio::buffer(data), peer_endpoint, 0, ec); @@ -959,7 +959,7 @@ size_t udp_client::send_out(const std::vector &data, const udp::endpoin size_t udp_client::send_out(const uint8_t *data, size_t size, const udp::endpoint &peer_endpoint, asio::error_code &ec) { - if (stopped.load() || data == nullptr) + if (stopped.load() || data == nullptr || size == 0) return 0; size_t sent_size = connection_socket.send_to(asio::buffer(data, size), peer_endpoint, 0, ec); @@ -969,7 +969,7 @@ size_t udp_client::send_out(const uint8_t *data, size_t size, const udp::endpoin void udp_client::async_send_out(std::unique_ptr> data, const udp::endpoint &peer_endpoint) { - if (stopped.load() || data == nullptr) + if (stopped.load() || data == nullptr || data->empty()) return; auto asio_buffer = asio::buffer(*data); @@ -980,7 +980,7 @@ void udp_client::async_send_out(std::unique_ptr> data, cons void udp_client::async_send_out(std::unique_ptr data, size_t data_size, const udp::endpoint &peer_endpoint) { - if (stopped.load() || data == nullptr) + if (stopped.load() || data == nullptr || data_size == 0) return; auto asio_buffer = asio::buffer(data.get(), data_size); @@ -991,7 +991,7 @@ void udp_client::async_send_out(std::unique_ptr data, size_t data_siz void udp_client::async_send_out(std::unique_ptr data, uint8_t *start_pos, size_t data_size, const udp::endpoint &peer_endpoint) { - if (stopped.load() || data == nullptr) + if (stopped.load() || data == nullptr || data_size == 0) return; connection_socket.async_send_to(asio::buffer(start_pos, data_size), peer_endpoint, @@ -1001,7 +1001,7 @@ void udp_client::async_send_out(std::unique_ptr data, uint8_t *start_ void udp_client::async_send_out(std::vector &&data, const udp::endpoint &peer_endpoint) { - if (stopped.load()) + if (stopped.load() || data.empty()) return; auto asio_buffer = asio::buffer(data); @@ -1067,7 +1067,7 @@ void udp_client::handle_receive(std::unique_ptr buffer_cache, const a start_receive(); - if (buffer_cache == nullptr || bytes_transferred == 0) + if (bytes_transferred == 0) return; if (gbv_buffer_size - bytes_transferred < gbv_buffer_expand_size) diff --git a/src/networks/kcp.cpp b/src/networks/kcp.cpp index 97c1c42..bef8934 100644 --- a/src/networks/kcp.cpp +++ b/src/networks/kcp.cpp @@ -23,13 +23,12 @@ #endif // __unix__ #include "kcp.hpp" -#include "../3rd_party/ikcp.h" using namespace std::chrono; using namespace std::literals; -int middle_layer_output(const char *buf, int len, IKCPCB *kcp, void *user); -void middle_layer_writelog(const char *buf, IKCPCB *kcp, void *user); +//int middle_layer_output(const char *buf, int len, IKCPCB *kcp, void *user); +//void middle_layer_writelog(const char *buf, IKCPCB *kcp, void *user); int64_t right_now(); namespace KCP @@ -43,36 +42,30 @@ namespace KCP void KCP::Initialise(uint32_t conv) { - ikcp_ptr = ikcp_create(conv, this); - custom_data.store(nullptr); + kcp_ptr = std::make_unique(); + kcp_ptr->initialise(conv, this); last_input_time.store(right_now()); post_update = empty_function; } void KCP::MoveKCP(KCP &other) noexcept { - ikcp_ptr = other.ikcp_ptr; - ((ikcpcb *)ikcp_ptr)->user = this; - custom_data.store(other.custom_data.load()); - other.ikcp_ptr = nullptr; - other.custom_data.store(nullptr); + kcp_ptr = std::move(other.kcp_ptr); last_input_time.store(other.last_input_time.load()); post_update = other.post_update; } - KCP::KCP(const KCP &other) noexcept - { - ikcp_ptr = other.ikcp_ptr; - ((ikcpcb *)ikcp_ptr)->user = this; - custom_data.store(other.custom_data.load()); - last_input_time.store(other.last_input_time.load()); - post_update = other.post_update; - } + //KCP::KCP(const KCP &other) noexcept + //{ + // ikcp_ptr = other.ikcp_ptr; + // ((ikcpcb *)ikcp_ptr)->user = this; + // custom_data.store(other.custom_data.load()); + // last_input_time.store(other.last_input_time.load()); + // post_update = other.post_update; + //} KCP::~KCP() { - ikcp_release((ikcpcb *)ikcp_ptr); - custom_data.store(nullptr); post_update = empty_function; } @@ -80,7 +73,6 @@ namespace KCP { if (outbound_bandwidth == 0 && inbound_bandwidth == 0) return; - ikcpcb *kcp_ptr = (ikcpcb *)ikcp_ptr; int32_t max_srtt = std::max(kcp_ptr->rx_srtt, srtt); int32_t min_srtt = std::min(kcp_ptr->rx_srtt, srtt); srtt = min_srtt <= 0 ? max_srtt : min_srtt; @@ -104,13 +96,12 @@ namespace KCP int32_t KCP::GetRxSRTT() { - return ((ikcpcb *)ikcp_ptr)->rx_srtt; + return kcp_ptr->rx_srtt; } void KCP::SetOutput(std::function output_func) { - this->output = output_func; - ((ikcpcb *)ikcp_ptr)->output = middle_layer_output; + kcp_ptr->set_output(output_func); } void KCP::SetPostUpdate(std::function post_update_func) @@ -121,61 +112,61 @@ namespace KCP int KCP::Receive(char *buffer, int len) { std::scoped_lock locker{ mtx }; - return ikcp_recv((ikcpcb *)ikcp_ptr, buffer, len); + return kcp_ptr->receive(buffer, len); } int KCP::Receive(std::vector &buffer) { std::scoped_lock locker{ mtx }; - return ikcp_recv((ikcpcb *)ikcp_ptr, buffer.data(), (int)buffer.size()); + return kcp_ptr->receive(buffer.data(), (int)buffer.size()); } int KCP::Send(const char *buffer, size_t len) { std::scoped_lock locker{ mtx }; - return ikcp_send((ikcpcb *)ikcp_ptr, buffer, (int)len); + return kcp_ptr->send(buffer, (int)len); } void KCP::Update(uint32_t current) { std::unique_lock locker{ mtx }; - ikcp_update((ikcpcb *)ikcp_ptr, current); + kcp_ptr->update(current); locker.unlock(); - post_update(custom_data.load()); + post_update(kcp_ptr->user); } void KCP::Update() { std::unique_lock locker{ mtx }; - ikcp_update((ikcpcb *)ikcp_ptr, TimeNowForKCP()); + kcp_ptr->update(TimeNowForKCP()); locker.unlock(); - post_update(custom_data.load()); + post_update(kcp_ptr->user); } uint32_t KCP::Check(uint32_t current) { std::shared_lock locker{ mtx }; - return ikcp_check((ikcpcb *)ikcp_ptr, current); + return kcp_ptr->check(current); } uint32_t KCP::Check() { std::shared_lock locker{ mtx }; - return ikcp_check((ikcpcb *)ikcp_ptr, TimeNowForKCP()); + return kcp_ptr->check(TimeNowForKCP()); } uint32_t KCP::Refresh() { std::unique_lock unique_locker{ mtx }; - ikcp_flush((ikcpcb *)ikcp_ptr); - return ikcp_check((ikcpcb *)ikcp_ptr, TimeNowForKCP()); + kcp_ptr->flush(TimeNowForKCP()); + return kcp_ptr->check(TimeNowForKCP()); } // when you received a low level packet (eg. UDP packet), call it int KCP::Input(const char *data, long size) { std::unique_lock locker{ mtx }; - auto ret = ikcp_input((ikcpcb *)ikcp_ptr, data, size); + auto ret = kcp_ptr->input(data, size); locker.unlock(); last_input_time.store(right_now()); return ret; @@ -185,50 +176,50 @@ namespace KCP void KCP::Flush() { std::scoped_lock locker{ mtx }; - ikcp_flush((ikcpcb *)ikcp_ptr); + kcp_ptr->flush(TimeNowForKCP()); } // check the size of next message in the recv queue int KCP::PeekSize() { - return ikcp_peeksize((ikcpcb *)ikcp_ptr); + return kcp_ptr->peek_size(); } // change MTU size, default is 1400 int KCP::SetMTU(int mtu) { - return ikcp_setmtu((ikcpcb *)ikcp_ptr, mtu); + return kcp_ptr->set_mtu(mtu); } int KCP::GetMTU() { - return ((ikcpcb *)ikcp_ptr)->mtu; + return kcp_ptr->mtu; } // set maximum window size: sndwnd=32, rcvwnd=32 by default void KCP::SetWindowSize(uint32_t sndwnd, uint32_t rcvwnd) { - ikcp_wndsize((ikcpcb *)ikcp_ptr, sndwnd, rcvwnd); + kcp_ptr->set_wndsize(sndwnd, rcvwnd); } void KCP::GetWindowSize(uint32_t &sndwnd, uint32_t &rcvwnd) { - sndwnd = ((ikcpcb *)ikcp_ptr)->snd_wnd; - rcvwnd = ((ikcpcb *)ikcp_ptr)->rcv_wnd; + sndwnd = kcp_ptr->snd_wnd; + rcvwnd = kcp_ptr->rcv_wnd; } std::pair KCP::GetWindowSizes() { - return std::pair{ ((ikcpcb *)ikcp_ptr)->snd_wnd, ((ikcpcb *)ikcp_ptr)->rcv_wnd }; + return std::pair{ kcp_ptr->snd_wnd, kcp_ptr->rcv_wnd }; } uint32_t KCP::GetSendWindowSize() { - return ((ikcpcb *)ikcp_ptr)->snd_wnd; + return kcp_ptr->snd_wnd; } uint32_t KCP::GetReceiveWindowSize() { - return ((ikcpcb *)ikcp_ptr)->rcv_wnd; + return kcp_ptr->rcv_wnd; } //uint32_t KCP::GetRemoteWindowSize() @@ -239,7 +230,7 @@ namespace KCP // get how many packet is waiting to be sent int KCP::WaitingForSend() { - return ikcp_waitsnd((ikcpcb *)ikcp_ptr); + return kcp_ptr->get_waitsnd(); } // fastest: NoDelay(1, 20, 2, 1) @@ -249,29 +240,29 @@ namespace KCP // nc: 0:normal congestion control(default), 1:disable congestion control int KCP::NoDelay(int nodelay, int interval, int resend, bool nc) { - int ret = ikcp_nodelay((ikcpcb *)ikcp_ptr, nodelay, interval, resend, nc); - ((ikcpcb *)ikcp_ptr)->interval = interval; + int ret = kcp_ptr->set_nodelay(nodelay, interval, resend, nc); + kcp_ptr->interval = interval; return ret; } uint32_t KCP::GetConv(const void *ptr) { - return ikcp_getconv(ptr); + return kcp_core::get_conv(ptr); } uint32_t KCP::GetConv() { - return ikcp_getconv(ikcp_ptr); + return kcp_ptr->get_conv(); } void KCP::SetStreamMode(bool enable) { - ((IKCPCB *)ikcp_ptr)->stream = enable; + kcp_ptr->stream = enable; } int32_t& KCP::RxMinRTO() { - return ((IKCPCB *)ikcp_ptr)->rx_minrto; + return kcp_ptr->rx_minrto; } void KCP::SetBandwidth(uint64_t out_bw, uint64_t in_bw) @@ -285,30 +276,45 @@ namespace KCP return last_input_time.load(); } - int proxy_output(KCP *kcp, const char *buf, int len) + void* KCP::GetUserData() { - return kcp->output(buf, len, kcp->custom_data.load()); + return kcp_ptr->user; } - void proxy_writelog(KCP *kcp, const char *buf) + void KCP::SetUserData(void *user_data) { - kcp->writelog(buf, kcp->custom_data.load()); + kcp_ptr->user = user_data; } -} + void KCP::SetAsConserve(bool conserve) + { + kcp_ptr->fastack_conserve = conserve; + } -int middle_layer_output(const char *buf, int len, struct IKCPCB *kcp, void *user) -{ - KCP::KCP *kcp_ptr = (KCP::KCP*)kcp->user; - return KCP::proxy_output(kcp_ptr, buf, len); -} + //int proxy_output(KCP *kcp, const char *buf, int len) + //{ + // return kcp->output(buf, len, kcp->custom_data.load()); + //} + + //void proxy_writelog(KCP *kcp, const char *buf) + //{ + // kcp->writelog(buf, kcp->custom_data.load()); + //} -void middle_layer_writelog(const char *buf, struct IKCPCB *kcp, void *user) -{ - KCP::KCP *kcp_ptr = (KCP::KCP*)kcp->user; - return KCP::proxy_writelog(kcp_ptr, buf); } +//int middle_layer_output(const char *buf, int len, struct IKCPCB *kcp, void *user) +//{ +// KCP::KCP *kcp_ptr = (KCP::KCP*)kcp->user; +// return KCP::proxy_output(kcp_ptr, buf, len); +//} +// +//void middle_layer_writelog(const char *buf, struct IKCPCB *kcp, void *user) +//{ +// KCP::KCP *kcp_ptr = (KCP::KCP*)kcp->user; +// return KCP::proxy_writelog(kcp_ptr, buf); +//} + int64_t right_now() { auto right_now = system_clock::now(); diff --git a/src/networks/kcp.hpp b/src/networks/kcp.hpp index 52e0606..42aef82 100644 --- a/src/networks/kcp.hpp +++ b/src/networks/kcp.hpp @@ -5,17 +5,20 @@ #include #include +#include #include #include #include #include #include +#include "../3rd_party/ikcp.hpp" + namespace KCP { - class KCP; - int proxy_output(KCP *kcp, const char *buf, int len); - void proxy_writelog(KCP *kcp, const char *buf); + //class KCP; + //int proxy_output(KCP *kcp, const char *buf, int len); + //void proxy_writelog(KCP *kcp, const char *buf); constexpr uint32_t five_minutes_in_ms = 5 * 60 * 1000; uint32_t TimeNowForKCP(); @@ -24,31 +27,30 @@ namespace KCP //--------------------------------------------------------------------- class KCP { - friend int proxy_output(KCP *kcp, const char *buf, int len); - friend void proxy_writelog(KCP *kcp, const char *buf); + //friend int proxy_output(KCP *kcp, const char *buf, int len); + //friend void proxy_writelog(KCP *kcp, const char *buf); public: - std::atomic custom_data; + //std::atomic custom_data; std::atomic keep_alive_send_time; std::atomic keep_alive_response_time; private: - void *ikcp_ptr; + std::unique_ptr kcp_ptr; uint64_t outbound_bandwidth = 0; uint64_t inbound_bandwidth = 0; std::atomic last_input_time{0}; mutable std::shared_mutex mtx; - std::function output; // int(*output)(const char *buf, int len, void *user) - std::function writelog; //void(*writelog)(const char *log, void *user) + //std::function output; // int(*output)(const char *buf, int len, void *user) + //std::function writelog; //void(*writelog)(const char *log, void *user) std::function post_update; void Initialise(uint32_t conv); void MoveKCP(KCP &other) noexcept; public: - KCP() { Initialise(0); } - KCP(const KCP &other) noexcept; + KCP(const KCP &other) = delete; KCP(KCP &&other) noexcept { MoveKCP(other); } @@ -139,6 +141,11 @@ namespace KCP int32_t& RxMinRTO(); void SetBandwidth(uint64_t out_bw, uint64_t in_bw); int64_t LastInputTime(); + + void* GetUserData(); + void SetUserData(void *user_data); + + void SetAsConserve(bool conserve); }; } diff --git a/src/networks/relay.cpp b/src/networks/relay.cpp index 14b84a5..21a00ca 100644 --- a/src/networks/relay.cpp +++ b/src/networks/relay.cpp @@ -308,7 +308,7 @@ void relay_mode::udp_listener_incoming_new_connection(std::unique_ptr handshake_kcp_mappings->ingress_kcp = handshake_kcp_ingress; handshake_kcp_mappings->connection_protocol = prtcl; - handshake_kcp_ingress->custom_data.store(handshake_kcp_mappings); + handshake_kcp_ingress->SetUserData(handshake_kcp_mappings); std::shared_ptr handshake_kcp_egress = std::make_shared(0); auto udp_func = std::bind(&relay_mode::udp_forwarder_incoming, this, _1, _2, _3, _4, _5); @@ -321,7 +321,7 @@ void relay_mode::udp_listener_incoming_new_connection(std::unique_ptr handshake_kcp_mappings->egress_kcp = handshake_kcp_egress; handshake_kcp_mappings->egress_forwarder = udp_forwarder; handshake_kcp_mappings->changeport_timestamp.store(LLONG_MAX); - handshake_kcp_egress->custom_data.store(handshake_kcp_mappings); + handshake_kcp_egress->SetUserData(handshake_kcp_mappings); handshake_kcp_egress->SetMTU(current_settings.egress->kcp_mtu); handshake_kcp_egress->NoDelay(0, 2, 0, 1); @@ -444,7 +444,7 @@ void relay_mode::udp_forwarder_incoming_unpack(std::shared_ptr kcp_ptr if (kcp_ptr->Input((const char *)data_ptr, (long)packet_data_size) < 0) return; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); while (true) { @@ -612,7 +612,7 @@ void relay_mode::create_kcp_bidirections(uint32_t new_id, kcp_mappings *handshak { return kcp_sender_via_listener(buf, len, user); }); - kcp_ptr_ingress->custom_data.store(kcp_mappings_ptr); + kcp_ptr_ingress->SetUserData(kcp_mappings_ptr); kcp_ptr_ingress->keep_alive_send_time.store(timestamp); kcp_ptr_ingress->keep_alive_response_time.store(timestamp); @@ -646,7 +646,7 @@ void relay_mode::create_kcp_bidirections(uint32_t new_id, kcp_mappings *handshak return kcp_sender_via_forwarder(buf, len, user); }); kcp_ptr_egress->Update(); - kcp_ptr_egress->custom_data.store(kcp_mappings_ptr); + kcp_ptr_egress->SetUserData(kcp_mappings_ptr); kcp_ptr_ingress->keep_alive_send_time.store(timestamp); kcp_ptr_ingress->keep_alive_response_time.store(timestamp); @@ -1041,7 +1041,7 @@ void relay_mode::loop_keep_alive_ingress() continue; timestamp += current_settings.ingress->keep_alive; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); protocol_type ptype = kcp_mappings_ptr->connection_protocol; std::vector keep_alive_packet = packet::create_keep_alive_packet(ptype); kcp_ptr->Send((const char*)keep_alive_packet.data(), keep_alive_packet.size()); @@ -1067,7 +1067,7 @@ void relay_mode::loop_keep_alive_egress() continue; timestamp += current_settings.egress->keep_alive; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); protocol_type ptype = kcp_mappings_ptr->connection_protocol; std::vector keep_alive_packet = packet::create_keep_alive_packet(ptype); kcp_ptr->Send((const char*)keep_alive_packet.data(), keep_alive_packet.size()); diff --git a/src/networks/server.cpp b/src/networks/server.cpp index 418f9ee..430e872 100644 --- a/src/networks/server.cpp +++ b/src/networks/server.cpp @@ -12,8 +12,7 @@ using namespace std::literals; uint32_t server_mode::generate_token_number() { - std::random_device rd; - std::mt19937 mt(rd()); + thread_local std::mt19937 mt(std::random_device{}()); std::uniform_int_distribution uniform_dist(32, std::numeric_limits::max() - 1); return uniform_dist(mt); } @@ -420,7 +419,7 @@ void server_mode::udp_listener_incoming_new_connection(std::unique_ptringress_kcp = handshake_kcp; handshake_kcp_mappings_ptr->ingress_source_endpoint = peer; handshake_kcp_mappings_ptr->ingress_listener.store(udp_servers[port_number].get()); - handshake_kcp->custom_data.store(handshake_kcp_mappings_ptr); + handshake_kcp->SetUserData(handshake_kcp_mappings_ptr); handshake_kcp->SetMTU(current_settings.kcp_mtu); handshake_kcp->NoDelay(1, 1, 3, 1); handshake_kcp->Update(); @@ -468,7 +467,7 @@ void server_mode::udp_listener_incoming_new_connection(std::unique_ptringress_kcp = data_kcp; data_kcp_mappings_ptr->connection_protocol = prtcl; data_kcp_mappings_ptr->ingress_listener.store(udp_servers[port_number].get()); - data_kcp->custom_data.store(data_kcp_mappings_ptr); + data_kcp->SetUserData(data_kcp_mappings_ptr); data_kcp->keep_alive_send_time.store(timestamp); data_kcp->keep_alive_response_time.store(timestamp); data_kcp->SetMTU(current_settings.kcp_mtu); @@ -477,6 +476,7 @@ void server_mode::udp_listener_incoming_new_connection(std::unique_ptrUpdate(); data_kcp->RxMinRTO() = 10; data_kcp->SetBandwidth(outbound_bandwidth, current_settings.inbound_bandwidth); + data_kcp->SetAsConserve(current_settings.kcp_conserve); bool connect_success = false; @@ -704,7 +704,7 @@ bool server_mode::create_new_tcp_connection(std::shared_ptr handshake_ data_kcp->SetOutput([this](const char *buf, int len, void *user) -> int { return kcp_sender(buf, len, user); }); data_kcp->SetPostUpdate([this](void *user) { resume_tcp((kcp_mappings*)user); }); - kcp_mappings *kcp_mappings_ptr = (kcp_mappings*)data_kcp->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings*)data_kcp->GetUserData(); kcp_mappings_ptr->local_tcp = local_session; local_session->async_read_data(); } @@ -772,7 +772,7 @@ bool server_mode::create_new_udp_connection(std::shared_ptr handshake_ if (udp_target != nullptr || update_local_udp_target(target_connector)) { target_connector->async_receive(); - kcp_mappings *kcp_mappings_ptr = (kcp_mappings*)data_kcp->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings*)data_kcp->GetUserData(); kcp_mappings_ptr->ingress_source_endpoint = peer; kcp_mappings_ptr->local_udp = target_connector; data_kcp->Flush(); @@ -1486,7 +1486,7 @@ void server_mode::loop_keep_alive() continue; timestamp += current_settings.keep_alive; - kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->custom_data.load(); + kcp_mappings *kcp_mappings_ptr = (kcp_mappings *)kcp_ptr->GetUserData(); protocol_type ptype = kcp_mappings_ptr->connection_protocol; std::vector keep_alive_packet = packet::create_keep_alive_packet(ptype); kcp_ptr->Send((const char*)keep_alive_packet.data(), keep_alive_packet.size()); diff --git a/src/shares/aead.hpp b/src/shares/aead.hpp index 2d704a2..ba0fb8e 100644 --- a/src/shares/aead.hpp +++ b/src/shares/aead.hpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,9 @@ class encryption_base } public: + virtual std::array change_iv() = 0; + virtual void change_iv(std::array iv_raw) = 0; + template T encrypt(const T &input_plain_data, std::string &error_message) { @@ -175,7 +179,7 @@ class encryption_base output_length = 0; return empty_error_message; } - + std::string error_message; try { @@ -204,7 +208,7 @@ class encryption_base { return empty_error_message; } - + std::string error_message; try { @@ -284,6 +288,24 @@ class aes_256_gcm : public encryption_base decoder = std::move(other.decoder); return *this; } + + std::array change_iv() override + { + std::array iv_raw{}; + thread_local std::mt19937 mt(std::random_device{}()); + std::uniform_int_distribution uniform_dist(0, std::numeric_limits::max()); + iv_raw[0] = uniform_dist(mt); + iv_raw[1] = uniform_dist(mt); + iv[0] = iv[2] = iv[4] = iv[6] = iv[8] = iv[10] = iv[12] = iv[14] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv[9] = iv[11] = iv[13] = iv[15] = iv_raw[1]; + return iv_raw; + } + + void change_iv(std::array iv_raw) override + { + iv[0] = iv[2] = iv[4] = iv[6] = iv[8] = iv[10] = iv[12] = iv[14] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv[9] = iv[11] = iv[13] = iv[15] = iv_raw[1]; + } }; class aes_256_ocb : public encryption_base @@ -350,6 +372,24 @@ class aes_256_ocb : public encryption_base decoder = std::move(other.decoder); return *this; } + + std::array change_iv() override + { + std::array iv_raw{}; + thread_local std::mt19937 mt(std::random_device{}()); + std::uniform_int_distribution uniform_dist(0, std::numeric_limits::max()); + iv_raw[0] = uniform_dist(mt); + iv_raw[1] = uniform_dist(mt); + iv[0] = iv[2] = iv[4] = iv[6] = iv[8] = iv[10] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv[9] = iv[11] = iv_raw[1]; + return iv_raw; + } + + void change_iv(std::array iv_raw) override + { + iv[0] = iv[2] = iv[4] = iv[6] = iv[8] = iv[10] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv[9] = iv[11] = iv_raw[1]; + } }; class chacha20 : public encryption_base @@ -413,6 +453,24 @@ class chacha20 : public encryption_base decoder = std::move(other.decoder); return *this; } + + std::array change_iv() override + { + std::array iv_raw{}; + thread_local std::mt19937 mt(std::random_device{}()); + std::uniform_int_distribution uniform_dist(0, std::numeric_limits::max()); + iv_raw[0] = uniform_dist(mt); + iv_raw[1] = uniform_dist(mt); + iv[0] = iv[2] = iv[4] = iv[6] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv_raw[1]; + return iv_raw; + } + + void change_iv(std::array iv_raw) override + { + iv[0] = iv[2] = iv[4] = iv[6] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv_raw[1]; + } }; class xchacha20 : public encryption_base @@ -471,6 +529,24 @@ class xchacha20 : public encryption_base decoder = std::move(other.decoder); return *this; } + + std::array change_iv() override + { + std::array iv_raw{}; + thread_local std::mt19937 mt(std::random_device{}()); + std::uniform_int_distribution uniform_dist(0, std::numeric_limits::max()); + iv_raw[0] = uniform_dist(mt); + iv_raw[1] = uniform_dist(mt); + iv[0] = iv[2] = iv[4] = iv[6] = iv[8] = iv[10] = iv[12] = iv[14] = iv[16] = iv[18] = iv[20] = iv[22] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv[9] = iv[11] = iv[13] = iv[15] = iv[17] = iv[19] = iv[21] = iv[23] = iv_raw[1]; + return iv_raw; + } + + void change_iv(std::array iv_raw) override + { + iv[0] = iv[2] = iv[4] = iv[6] = iv[8] = iv[10] = iv[12] = iv[14] = iv[16] = iv[18] = iv[20] = iv[22] = iv_raw[0]; + iv[1] = iv[3] = iv[5] = iv[7] = iv[9] = iv[11] = iv[13] = iv[15] = iv[17] = iv[19] = iv[21] = iv[23] = iv_raw[1]; + } }; #endif // !__AEAD_HPP__ \ No newline at end of file diff --git a/src/shares/configurations.cpp b/src/shares/configurations.cpp index 190dde6..d80bfae 100644 --- a/src/shares/configurations.cpp +++ b/src/shares/configurations.cpp @@ -346,6 +346,13 @@ std::vector parse_the_rest(const std::vector &args, us current_settings->inbound_bandwidth = bandwidth_from_string(original_value, error_msg); break; + case strhash("kcp_conserve"): + { + bool yes = value == "yes" || value == "true" || value == "1"; + current_settings->kcp_conserve = yes; + break; + } + case strhash("log_path"): current_settings->log_directory = original_value; break; @@ -458,13 +465,10 @@ void check_settings(user_settings ¤t_user_settings, std::vectorsecond.decrypt(std::move(cipher_data), error_message); } + + std::array change_iv(const std::string &password) + { + auto iter = core.find(password); + if (iter == core.end()) + { + core.insert({ password, T(password) }); + iter = core.find(password); + } + + return iter->second.change_iv(); + } + + void change_iv(const std::string &password, std::array iv_raw) + { + auto iter = core.find(password); + if (iter == core.end()) + { + core.insert({ password, T(password) }); + iter = core.find(password); + } + + return iter->second.change_iv(iv_raw); + } }; std::pair encrypt_data(const std::string &password, encryption_mode mode, uint8_t *data_ptr, int length) { + if (length <= 0) + return { "empty data", 0 }; + size_t cipher_length = 0; + std::array iv_raw{}; std::string error_message; switch (mode) { case encryption_mode::aes_gcm: { thread_local encrypt_decrypt gcm; + iv_raw = gcm.change_iv(password); error_message = gcm.encrypt(password, data_ptr, length, data_ptr, cipher_length); break; } case encryption_mode::aes_ocb: { thread_local encrypt_decrypt ocb; + iv_raw = ocb.change_iv(password); error_message = ocb.encrypt(password, data_ptr, length, data_ptr, cipher_length); break; } case encryption_mode::chacha20: { thread_local encrypt_decrypt cc20; + iv_raw = cc20.change_iv(password); error_message = cc20.encrypt(password, data_ptr, length, data_ptr, cipher_length); break; } case encryption_mode::xchacha20: { thread_local encrypt_decrypt xcc20; + iv_raw = xcc20.change_iv(password); error_message = xcc20.encrypt(password, data_ptr, length, data_ptr, cipher_length); break; } @@ -123,16 +155,19 @@ std::pair encrypt_data(const std::string &password, encrypt cipher_length = length; uint8_t first_hash = simple_hashing::xor_u8(data_ptr, length); uint8_t second_hash = simple_hashing::checksum8(data_ptr, length); - data_ptr[cipher_length] = first_hash; - cipher_length++; - data_ptr[cipher_length] = second_hash; - cipher_length++; + iv_raw[0] = ~first_hash; + iv_raw[1] = ~second_hash; bitwise_not(data_ptr, cipher_length); break; } }; - xor_backward(data_ptr, cipher_length); + if (cipher_length + constant_values::iv_checksum_block_size > iv_raw.size()) + { + data_ptr[cipher_length] = iv_raw[0]; + data_ptr[cipher_length + 1] = iv_raw[1]; + cipher_length += constant_values::iv_checksum_block_size; + } return { std::move(error_message), cipher_length }; } @@ -140,40 +175,45 @@ std::pair encrypt_data(const std::string &password, encrypt std::vector encrypt_data(const std::string &password, encryption_mode mode, const void *data_ptr, int length, std::string &error_message) { size_t cipher_length = length; - std::vector cipher_cache(length + 48); + std::array iv_raw{}; + std::vector cipher_cache(length + constant_values::encryption_block_reserve + constant_values::iv_checksum_block_size); switch (mode) { case encryption_mode::aes_gcm: { thread_local encrypt_decrypt gcm; + iv_raw = gcm.change_iv(password); error_message = gcm.encrypt(password, (const uint8_t *)data_ptr, length, cipher_cache.data(), cipher_length); if (error_message.empty() && cipher_length > 0) - cipher_cache.resize(cipher_length); + cipher_cache.resize(cipher_length + constant_values::iv_checksum_block_size); break; } case encryption_mode::aes_ocb: { thread_local encrypt_decrypt ocb; + iv_raw = ocb.change_iv(password); error_message = ocb.encrypt(password, (const uint8_t *)data_ptr, length, cipher_cache.data(), cipher_length); if (error_message.empty() && cipher_length > 0) - cipher_cache.resize(cipher_length); + cipher_cache.resize(cipher_length + constant_values::iv_checksum_block_size); break; } case encryption_mode::chacha20: { thread_local encrypt_decrypt cc20; + iv_raw = cc20.change_iv(password); error_message = cc20.encrypt(password, (const uint8_t *)data_ptr, length, cipher_cache.data(), cipher_length); if (error_message.empty() && cipher_length > 0) - cipher_cache.resize(cipher_length); + cipher_cache.resize(cipher_length + constant_values::iv_checksum_block_size); break; } case encryption_mode::xchacha20: { thread_local encrypt_decrypt xcc20; + iv_raw = xcc20.change_iv(password); error_message = xcc20.encrypt(password, (const uint8_t *)data_ptr, length, cipher_cache.data(), cipher_length); if (error_message.empty() && cipher_length > 0) - cipher_cache.resize(cipher_length); + cipher_cache.resize(cipher_length + constant_values::iv_checksum_block_size); break; } default: @@ -181,28 +221,32 @@ std::vector encrypt_data(const std::string &password, encryption_mode m cipher_length = length; uint8_t first_hash = simple_hashing::xor_u8(data_ptr, length); uint8_t second_hash = simple_hashing::checksum8(data_ptr, length); - cipher_cache[cipher_length] = ~first_hash; - cipher_length++; - cipher_cache[cipher_length] = ~second_hash; - cipher_length++; - cipher_cache.resize(cipher_length); + iv_raw[0] = ~first_hash; + iv_raw[1] = ~second_hash; + cipher_cache.resize(cipher_length + constant_values::iv_checksum_block_size); std::transform((const uint8_t *)data_ptr, (const uint8_t *)data_ptr + length, cipher_cache.begin(), [](auto ch) { return ~ch; }); break; } }; - xor_backward(cipher_cache); + if (cipher_cache.size() > iv_raw.size()) + { + cipher_cache[cipher_length] = iv_raw[0]; + cipher_cache[cipher_length + 1] = iv_raw[1]; + } return cipher_cache; } std::vector encrypt_data(const std::string &password, encryption_mode mode, std::vector &&input_data, std::string &error_message) { + std::array iv_raw{}; switch (mode) { case encryption_mode::aes_gcm: { thread_local encrypt_decrypt gcm; + iv_raw = gcm.change_iv(password); input_data = gcm.encrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -211,6 +255,7 @@ std::vector encrypt_data(const std::string &password, encryption_mode m case encryption_mode::aes_ocb: { thread_local encrypt_decrypt ocb; + iv_raw = ocb.change_iv(password); input_data = ocb.encrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -219,6 +264,7 @@ std::vector encrypt_data(const std::string &password, encryption_mode m case encryption_mode::chacha20: { thread_local encrypt_decrypt cc20; + iv_raw = cc20.change_iv(password); input_data = cc20.encrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -227,6 +273,7 @@ std::vector encrypt_data(const std::string &password, encryption_mode m case encryption_mode::xchacha20: { thread_local encrypt_decrypt xcc20; + iv_raw = xcc20.change_iv(password); input_data = xcc20.encrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -234,61 +281,70 @@ std::vector encrypt_data(const std::string &password, encryption_mode m } default: { - const size_t cipher_length = input_data.size(); uint8_t first_hash = simple_hashing::xor_u8(input_data.data(), input_data.size()); uint8_t second_hash = simple_hashing::checksum8(input_data.data(), input_data.size()); - - size_t pos = input_data.size(); - input_data.resize(cipher_length + constant_values::checksum_block_size); - input_data[pos] = first_hash; - pos++; - input_data[pos] = second_hash; - pos++; + iv_raw[0] = ~first_hash; + iv_raw[1] = ~second_hash; std::transform(input_data.begin(), input_data.end(), input_data.begin(), [](auto ch) { return ~ch; }); break; } }; - xor_backward(input_data); + const size_t cipher_length = input_data.size(); + input_data.resize(cipher_length + constant_values::iv_checksum_block_size); + input_data[cipher_length] = iv_raw[0]; + input_data[cipher_length + 1] = iv_raw[1]; + return input_data; } std::pair decrypt_data(const std::string &password, encryption_mode mode, uint8_t *data_ptr, int length) { - xor_forward(data_ptr, length); + if (length <= constant_values::iv_checksum_block_size) + return { "incorrect data length", 0 }; size_t data_length = 0; + int input_length = length - constant_values::iv_checksum_block_size; + std::array iv_raw{}; std::string error_message; + + iv_raw[0] = data_ptr[length - 2]; + iv_raw[1] = data_ptr[length - 1]; + switch (mode) { case encryption_mode::aes_gcm: { thread_local encrypt_decrypt gcm; - error_message = gcm.decrypt(password, data_ptr, length, data_ptr, data_length); + gcm.change_iv(password, iv_raw); + error_message = gcm.decrypt(password, data_ptr, input_length, data_ptr, data_length); break; } case encryption_mode::aes_ocb: { thread_local encrypt_decrypt ocb; - error_message = ocb.decrypt(password, data_ptr, length, data_ptr, data_length); + ocb.change_iv(password, iv_raw); + error_message = ocb.decrypt(password, data_ptr, input_length, data_ptr, data_length); break; } case encryption_mode::chacha20: { thread_local encrypt_decrypt cc20; - error_message = cc20.decrypt(password, data_ptr, length, data_ptr, data_length); + cc20.change_iv(password, iv_raw); + error_message = cc20.decrypt(password, data_ptr, input_length, data_ptr, data_length); break; } case encryption_mode::xchacha20: { thread_local encrypt_decrypt xcc20; - error_message = xcc20.decrypt(password, data_ptr, length, data_ptr, data_length); + xcc20.change_iv(password, iv_raw); + error_message = xcc20.decrypt(password, data_ptr, input_length, data_ptr, data_length); break; } default: { bitwise_not(data_ptr, length); - data_length = length - constant_values::checksum_block_size; + data_length = length - constant_values::iv_checksum_block_size; uint8_t first_hash = simple_hashing::xor_u8(data_ptr, data_length); uint8_t second_hash = simple_hashing::checksum8(data_ptr, data_length); @@ -303,42 +359,56 @@ std::pair decrypt_data(const std::string &password, encrypt std::vector decrypt_data(const std::string &password, encryption_mode mode, const void *data_ptr, int length, std::string &error_message) { - std::vector data_cache((const uint8_t *)data_ptr, (const uint8_t *)data_ptr + length); - xor_forward(data_cache); + if (length <= constant_values::iv_checksum_block_size) + { + error_message = "Incorrect Data Size."; + return std::vector{}; + } + + int data_length = length - constant_values::iv_checksum_block_size; + std::vector data_cache((const uint8_t *)data_ptr, (const uint8_t *)data_ptr + data_length); + std::array iv_raw{}; + iv_raw[0] = ((const uint8_t *)data_ptr)[length - 2]; + iv_raw[1] = ((const uint8_t *)data_ptr)[length - 1]; switch (mode) { case encryption_mode::aes_gcm: { thread_local encrypt_decrypt gcm; + gcm.change_iv(password, iv_raw); data_cache = gcm.decrypt(password, std::move(data_cache), error_message); break; } case encryption_mode::aes_ocb: { thread_local encrypt_decrypt ocb; + ocb.change_iv(password, iv_raw); data_cache = ocb.decrypt(password, std::move(data_cache), error_message); break; } case encryption_mode::chacha20: { thread_local encrypt_decrypt cc20; + cc20.change_iv(password, iv_raw); data_cache = cc20.decrypt(password, std::move(data_cache), error_message); break; } case encryption_mode::xchacha20: { thread_local encrypt_decrypt xcc20; + xcc20.change_iv(password, iv_raw); data_cache = xcc20.decrypt(password, std::move(data_cache), error_message); break; } default: { std::transform(data_cache.begin(), data_cache.end(), data_cache.begin(), [](auto ch) { return ~ch; }); - int data_length = length - constant_values::checksum_block_size; + iv_raw[0] = ~iv_raw[0]; + iv_raw[1] = ~iv_raw[1]; uint8_t first_hash = simple_hashing::xor_u8(data_ptr, data_length); uint8_t second_hash = simple_hashing::checksum8(data_ptr, data_length); - if (first_hash != data_cache[data_length] || second_hash != data_cache[data_length + 1]) + if (first_hash != iv_raw[0] || second_hash != iv_raw[1]) error_message = "Checksum incorrect"; data_cache.resize(data_length); break; @@ -350,13 +420,23 @@ std::vector decrypt_data(const std::string &password, encryption_mode m std::vector decrypt_data(const std::string &password, encryption_mode mode, std::vector &&input_data, std::string &error_message) { - xor_forward(input_data); + if (input_data.size() <= constant_values::iv_checksum_block_size) + { + error_message = "Incorrect Data Length."; + return std::vector{}; + } + + std::array iv_raw{}; + iv_raw[0] = input_data[input_data.size() - 2]; + iv_raw[1] = input_data[input_data.size() - 1]; + input_data.resize(input_data.size() - constant_values::iv_checksum_block_size); switch (mode) { case encryption_mode::aes_gcm: { thread_local encrypt_decrypt gcm; + gcm.change_iv(password, iv_raw); input_data = gcm.decrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -365,6 +445,7 @@ std::vector decrypt_data(const std::string &password, encryption_mode m case encryption_mode::aes_ocb: { thread_local encrypt_decrypt ocb; + ocb.change_iv(password, iv_raw); input_data = ocb.decrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -373,6 +454,7 @@ std::vector decrypt_data(const std::string &password, encryption_mode m case encryption_mode::chacha20: { thread_local encrypt_decrypt cc20; + cc20.change_iv(password, iv_raw); input_data = cc20.decrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -381,6 +463,7 @@ std::vector decrypt_data(const std::string &password, encryption_mode m case encryption_mode::xchacha20: { thread_local encrypt_decrypt xcc20; + xcc20.change_iv(password, iv_raw); input_data = xcc20.decrypt(password, std::move(input_data), error_message); if (!error_message.empty() || input_data.size() == 0) return input_data; @@ -389,12 +472,12 @@ std::vector decrypt_data(const std::string &password, encryption_mode m default: { std::transform(input_data.begin(), input_data.end(), input_data.begin(), [](auto ch) { return ~ch; }); - size_t data_length = input_data.size() - constant_values::checksum_block_size; - uint8_t first_hash = simple_hashing::xor_u8(input_data.data(), data_length); - uint8_t second_hash = simple_hashing::checksum8(input_data.data(), data_length); - if (first_hash != input_data[data_length] || second_hash != input_data[data_length + 1]) + iv_raw[0] = ~iv_raw[0]; + iv_raw[1] = ~iv_raw[1]; + uint8_t first_hash = simple_hashing::xor_u8(input_data.data(), input_data.size()); + uint8_t second_hash = simple_hashing::checksum8(input_data.data(), input_data.size()); + if (first_hash != iv_raw[0] || second_hash != iv_raw[1]) error_message = "Checksum Incorrect"; - input_data.resize(data_length); break; } }; @@ -402,46 +485,6 @@ std::vector decrypt_data(const std::string &password, encryption_mode m return input_data; } -void xor_forward(uint8_t *data, size_t data_size) -{ - for (auto ptr = data, next = ptr + 1; - next < data + data_size; - ++ptr, ++next) - { - *ptr ^= *next; - } -} - -void xor_forward(std::vector &data) -{ - for (auto iter = data.begin(), next = iter + 1; - next != data.end(); - ++iter, ++next) - { - *iter ^= *next; - } -} - -void xor_backward(uint8_t *data, size_t data_size) -{ - for (auto ptr = data + data_size - 1, next = ptr - 1; - next >= data; - --ptr, --next) - { - *next ^= *ptr; - } -} - -void xor_backward(std::vector &data) -{ - for (auto iter = data.rbegin(), next = iter + 1; - next != data.rend(); - ++iter, ++next) - { - *next ^= *iter; - } -} - void bitwise_not(uint8_t *input_data, size_t length) { if (length < sizeof(uint64_t) * 2) diff --git a/src/shares/data_operations.hpp b/src/shares/data_operations.hpp index 95dc0ce..3ae599d 100644 --- a/src/shares/data_operations.hpp +++ b/src/shares/data_operations.hpp @@ -11,10 +11,6 @@ std::vector encrypt_data(const std::string &password, encryption_mode m std::pair decrypt_data(const std::string &password, encryption_mode mode, uint8_t *data_ptr, int length); std::vector decrypt_data(const std::string &password, encryption_mode mode, const void *data_ptr, int length, std::string &error_message); std::vector decrypt_data(const std::string &password, encryption_mode mode, std::vector &&cipher_data, std::string &error_message); -void xor_forward(uint8_t *data, size_t data_size); -void xor_forward(std::vector &data); -void xor_backward(uint8_t *data, size_t data_size); -void xor_backward(std::vector &data); void bitwise_not(uint8_t *input_data, size_t length); #endif // !__DATA_OPERATIONS_HPP__ \ No newline at end of file diff --git a/src/shares/share_defines.hpp b/src/shares/share_defines.hpp index 25197a0..797e442 100644 --- a/src/shares/share_defines.hpp +++ b/src/shares/share_defines.hpp @@ -26,16 +26,17 @@ namespace constant_values constexpr int16_t dport_refresh_minimal = 20; constexpr int kcp_send_window = 1024; constexpr int kcp_receive_window = 1024; - constexpr int kcp_mtu = 1420; - constexpr int checksum_block_size = 2; + constexpr int packet_length = 1420; + constexpr int iv_checksum_block_size = 2; + constexpr int kcp_mtu = packet_length - iv_checksum_block_size; + constexpr int encryption_block_reserve = 48; }; template T generate_random_number() { - std::random_device rd; - std::mt19937 mt(rd()); + thread_local std::mt19937 mt(std::random_device{}()); std::uniform_int_distribution uniform_dist(std::numeric_limits::min(), std::numeric_limits::max()); return uniform_dist(mt); } @@ -43,8 +44,7 @@ T generate_random_number() template T generate_random_number(T start_num, T end_num) { - std::random_device rd; - std::mt19937 mt(rd()); + thread_local std::mt19937 mt(std::random_device{}()); std::uniform_int_distribution uniform_dist(start_num, end_num); return uniform_dist(mt); } @@ -79,6 +79,7 @@ struct user_settings uint32_t kcp_rcvwnd = 0; uint64_t outbound_bandwidth = 0; uint64_t inbound_bandwidth = 0; + bool kcp_conserve = false; bool ipv4_only = false; bool test_only = false; bool blast = false;