From 6b29fb3a685b7698147e0e09c4b5f897ec7b2c85 Mon Sep 17 00:00:00 2001 From: cnbatch Date: Sun, 27 Nov 2022 02:28:44 +0800 Subject: [PATCH] Initial Upload --- .gitignore | 58 +++ CMakeLists.txt | 36 ++ README.md | 203 +++++++++ service/config_sample.conf | 5 + service/punchnatd | 43 ++ sln/punchnat.sln | 39 ++ sln/punchnat/punchnat.vcxproj | 189 +++++++++ sln/punchnat/punchnat.vcxproj.filters | 69 ++++ sln/punchnat/punchnat.vcxproj.user | 23 ++ src/CMakeLists.txt | 28 ++ src/main.cpp | 77 ++++ src/networks/CMakeLists.txt | 8 + src/networks/connections.cpp | 565 ++++++++++++++++++++++++++ src/networks/connections.hpp | 238 +++++++++++ src/networks/modes.hpp | 144 +++++++ src/networks/stun.cpp | 151 +++++++ src/networks/stun.hpp | 159 ++++++++ src/networks/tcp_mode.cpp | 247 +++++++++++ src/networks/udp_mode.cpp | 261 ++++++++++++ src/shares/CMakeLists.txt | 6 + src/shares/share_defines.cpp | 120 ++++++ src/shares/share_defines.hpp | 39 ++ src/shares/string_utils.hpp | 85 ++++ 23 files changed, 2793 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 service/config_sample.conf create mode 100644 service/punchnatd create mode 100644 sln/punchnat.sln create mode 100644 sln/punchnat/punchnat.vcxproj create mode 100644 sln/punchnat/punchnat.vcxproj.filters create mode 100644 sln/punchnat/punchnat.vcxproj.user create mode 100644 src/CMakeLists.txt create mode 100644 src/main.cpp create mode 100644 src/networks/CMakeLists.txt create mode 100644 src/networks/connections.cpp create mode 100644 src/networks/connections.hpp create mode 100644 src/networks/modes.hpp create mode 100644 src/networks/stun.cpp create mode 100644 src/networks/stun.hpp create mode 100644 src/networks/tcp_mode.cpp create mode 100644 src/networks/udp_mode.cpp create mode 100644 src/shares/CMakeLists.txt create mode 100644 src/shares/share_defines.cpp create mode 100644 src/shares/share_defines.hpp create mode 100644 src/shares/string_utils.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d331f02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeSettings.json +build/ +out/ +obj/ +x64/ +Debug/ +Release/ +arm/ +arm64/ +.vs/ +.idea/ +.vscode/ +cmake-build-debug/ +cmake-build-release/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..801d331 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.15.0 FATAL_ERROR) + +project(punchnat CXX) + +set(CMAKE_C_STANDARD 17) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + include_directories("/usr/local/include") + include_directories("/usr/local/include/botan-2") +endif() +if(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD") + include_directories("/usr/pkg/include") + include_directories("/usr/pkg/include/botan-2") +endif() +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + include_directories("/usr/include/botan-2") +endif() + +find_package(Threads REQUIRED) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") + add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN) + add_compile_options("$<$:/utf-8>") + add_compile_options("$<$:/utf-8>") +endif() + +add_executable(${PROJECT_NAME} src/main.cpp) +add_subdirectory(src) +set_property(TARGET punchnat PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6285b23 --- /dev/null +++ b/README.md @@ -0,0 +1,203 @@ +# PunchNAT +利用 STUN 打洞,同时支持 TCP 与 UDP,同时支持 IPv4 与 IPv6。 + +注意:TCP 打洞需要 STUN 服务器的支持。 + +## 用法 +`punchnat config.conf` + +`config.conf` 示例: +``` +listen_port=50000 +destination_address=192.168.1.10 +destination_port=3389 +stun_server=stun.qq.com +log_path=./ +``` + +如果要指定侦听的网卡,那就指定该网卡的 IP 地址 +``` +listen_on=192.168.1.1 +listen_port=50000 +destination_address=192.168.1.10 +destination_port=3389 +stun_server=stun.qq.com +log_path=./ +``` + +如果想要指定多个端口、多个网卡,那就分开多个配置文件 + +``` +punchnat config1.conf config2.conf +``` + +### Log 文件 +目前只提供输出 IP 地址到指定 Log 目录的功能。 + +在首次获取打洞后的 IP 地址与端口后,以及打洞的 IP 地址与端口发生变化后,会向 Log 目录创建 ip_address.txt 文件(若存在就追加),将 IP 地址与端口写进去。 + +获取到的打洞地址会同时显示在控制台当中。 + +`log_path=` 必须指向目录,不能指向文件本身。 + +如果不需要写入 Log 文件,那就删除 `log_path` 这一行。 + +### STUN Servers +不支持 TCP 的普通 STUN 服务器(来自于[NatTypeTeste](https://github.com/HMBSbige/NatTypeTester)) +- stun.syncthing.net +- stun.qq.com +- stun.miwifi.com +- stun.bige0.com +- stun.stunprotocol.org + +同时支持 TCP 与 UDP 打洞的 STUN 服务器(来自于[Natter](https://github.com/MikeWang000000/Natter)) +- fwa.lifesizecloud.com +- stun.isp.net.au +- stun.freeswitch.org +- stun.voip.blackberry.com +- stun.nextcloud.com +- stun.stunprotocol.org +- stun.sipnet.com +- stun.radiojar.com +- stun.sonetel.com +- stun.voipgate.com + +其它 STUN 服务器:[public-stun-list.txt](https://gist.github.com/mondain/b0ec1cf5f60ae726202e) + +--- + +## 预编译二进制 +为了方便使用,目前已经提供了多个平台的二进制可执行文件: +- Windows +- FreeBSD +- Linux + +FreeBSD 用户可将下载好的二进制文件复制到 `/usr/local/bin/`,然后运行命令 +``` +chmod +x /usr/local/bin/punchnat +``` + +--- + +## 建立服务 +### FreeBSD + +**提示:务必事先做完上一个步骤,将二进制文件复制到 `/usr/local/bin/`** + +本项目的 `service` 目录已经准备好相应服务文件。 + +1. 找到 punchnatd 文件,复制到 `/usr/local/etc/rc.d/` +2. 运行命令 `chmod +x /usr/local/etc/rc.d/punchnatd` +3. 把配置文件复制到 `/usr/local/etc/punchnatd/` + - 记得把配置文件命名为 `config.conf` + - 完整的路径名:`/usr/local/etc/punchnatd/config.conf` +4. 在 `/etc/rc.conf` 加一行 `punchnatd_enable="YES"` + +最后,运行 `service punchnatd start` 即可启动服务 + +--- + +## 编译 +编译器须支持 C++17 + +依赖库:[asio](https://github.com/chriskohlhoff/asio) ≥ 1.18.2 + +### Windows +请事先使用 vcpkg 安装依赖包 `asio`,一句命令即可: + +``` +vcpkg install asio:x64-windows asio:x64-windows-static +``` +(如果需要 ARM 或者 32 位 x86 版本,请自行调整选项) + +然后用 Visual Studio 打开 `sln\punchnat.sln` 自行编译 + +### FreeBSD +同样,请先安装依赖项 asio,另外还需要 cmake,用系统自带 pkg 即可安装: + +``` +pkg install asio cmake +``` +接着在 build 目录当中构建 +``` +mkdir build +cd build +cmake .. +make +``` + +### NetBSD +步骤与 FreeBSD 类似,使用 [pkgin](https://www.netbsd.org/docs/pkgsrc/using.html) 安装依赖项与 cmake: +``` +pkgin install asio +pkgin install cmake +``` +构建步骤请参考上述的 FreeBSD。 + +注意,由于 NetBSD 自带的 GCC 版本较低,未必能成功编译出可用的二进制文件,有可能需要用 pkgin 额外安装高版本 GCC。 + +### Linux +步骤与 FreeBSD 类似,请用发行版自带的包管理器安装 asio 与 cmake。 + +#### Fedora +```` +dnf install asio cmake +```` +接着在 build 目录当中构建 +``` +mkdir build +cd build +cmake .. +make +``` + +如果所使用发行版的 asio 版本过低,需要自行解决。 + +如果不想用 io_ruing,请打开项目内的 src/CMakeLists.txt 删除相关选项,编译时会自动使用 epoll。 + +### macOS +我没苹果电脑,所有步骤请自行解决。 + +--- + +## IPv4 映射 IPv6 +由于该项目内部使用的是 IPv6 单栈 + 开启 IPv4 映射地址(IPv4-mapped IPv6)来使用 IPv4 网络,因此请确保 v6only 选项的值为 0。 + +**正常情况下不需要任何额外设置,FreeBSD 与 Linux 以及 Windows 都默认允许 IPv4 地址映射到 IPv6。** + +如果不放心,那么可以这样做 +### FreeBSD +按照FreeBSD手册 [33.9.5. IPv6 and IPv4 Address Mapping](https://docs.freebsd.org/en/books/handbook/advanced-networking/#_ipv6_and_ipv4_address_mapping) 介绍,在 `/etc/rc.conf` 加一行即可 +``` +ipv6_ipv4mapping="YES" +``` +如果还是不放心,那就运行命令 +``` +sysctl net.inet6.ip6.v6only=0 +``` + +### Linux +可运行命令 +``` +sysctl -w net.ipv6.bindv6only=0 +``` +正常情况下不需要这样做,它的默认值就是 0。 + +## 其它注意事项 +### NetBSD +使用命令 +``` +sysctl -w net.inet6.ip6.v6only=0 +``` +设置后,单栈+映射地址模式可以侦听双栈。 + +但由于未知的原因,它无法主动连接 IPv4 映射地址,因此 `destination_address` 只能使用 IPv6 地址。 + +### OpenBSD +因为 OpenBSD 彻底屏蔽了 IPv4 映射地址,所以在 OpenBSD 平台只能使用 IPv6 单栈模式。 + +## 关于代码 +### 为什么要用两个 asio::io_context +这里用了两个 asio::io_context,其中一个是用于处理 UDP 数据的异步循环,另一个用于处理内部逻辑以及 TCP 数据的收发。 + +之所以要这样做,完全是为了迁就 BSD 系统。如果只用一个 io_context 去做所有的事,由于两次接收之间的延迟过高,在 BSD 平台会导致 UDP 丢包率过高。 \ No newline at end of file diff --git a/service/config_sample.conf b/service/config_sample.conf new file mode 100644 index 0000000..b86a85f --- /dev/null +++ b/service/config_sample.conf @@ -0,0 +1,5 @@ +listen_port=33890 +destination_port=3389 +destination_address=192.168.0.10 +#stun_server=stun.qq.com +#log_path=./ \ No newline at end of file diff --git a/service/punchnatd b/service/punchnatd new file mode 100644 index 0000000..eb7f9af --- /dev/null +++ b/service/punchnatd @@ -0,0 +1,43 @@ +#!/bin/sh +# +# $FreeBSD$ +# + +# PROVIDE: punchnat +# REQUIRE: LOGIN DAEMON +# KEYWORD: shutdown + +# Add these lines to /etc/rc.conf.local or /etc/rc.conf to enable `punchnatd': +# +# punchnatd_enable (bool): Set to "NO" by default. +# Set it to "YES" to enable punchnatd +# punchnatd_config (path): Set to "/usr/local/etc/punchnatd/config.conf" by default +# Set it to the punchnatd server config + +. /etc/rc.subr + +name="punchnatd" +rcvar="${name}_enable" + +eval ": \${${name}_enable:=\"NO\"}" +eval ": \${${name}_config:=\"/usr/local/etc/${name}/config.conf\"}" + +pidfile="/var/run/${name}.pid" +procname="/usr/local/bin/punchnat" +configfile="$(eval echo \${${name}_config})" + +start_precmd="punchnatd_startprecmd" +start_cmd=punchnatd_start + +punchnatd_startprecmd() +{ + touch "${pidfile}" +} + +punchnatd_start() +{ + /usr/sbin/daemon -c -p ${pidfile} ${procname} ${configfile} > /dev/null 2>&1 +} + +load_rc_config "$name" +run_rc_command "$1" diff --git a/sln/punchnat.sln b/sln/punchnat.sln new file mode 100644 index 0000000..5df6b3d --- /dev/null +++ b/sln/punchnat.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32428.217 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "punchnat", "punchnat\punchnat.vcxproj", "{9205A90F-CDDA-49BE-A53B-33F31B7AE94F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Debug|ARM.ActiveCfg = Debug|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Debug|ARM64.ActiveCfg = Debug|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Debug|x64.ActiveCfg = Debug|x64 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Debug|x64.Build.0 = Debug|x64 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Debug|x86.ActiveCfg = Debug|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Debug|x86.Build.0 = Debug|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Release|ARM.ActiveCfg = Release|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Release|ARM64.ActiveCfg = Release|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Release|x64.ActiveCfg = Release|x64 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Release|x64.Build.0 = Release|x64 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Release|x86.ActiveCfg = Release|Win32 + {9205A90F-CDDA-49BE-A53B-33F31B7AE94F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FDBF6160-79CE-47E9-ABAF-4C1053872B31} + EndGlobalSection +EndGlobal diff --git a/sln/punchnat/punchnat.vcxproj b/sln/punchnat/punchnat.vcxproj new file mode 100644 index 0000000..fd9f9a9 --- /dev/null +++ b/sln/punchnat/punchnat.vcxproj @@ -0,0 +1,189 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {9205a90f-cdda-49be-a53b-33f31b7ae94f} + WinMain + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + true + + + true + + + true + + + true + + + + Level3 + true + ASIO_STANDALONE;NOMINMAX;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + stdc17 + MultiThreadedDebug + + + Console + true + mswsock.lib;ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + + + + + Level3 + true + true + true + ASIO_STANDALONE;NOMINMAX;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + stdc17 + MultiThreaded + + + Console + true + true + true + mswsock.lib;ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + true + ASIO_STANDALONE;NOMINMAX;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + stdc17 + MultiThreadedDebug + + + Console + true + mswsock.lib;ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + true + + + + + Level3 + true + true + true + ASIO_STANDALONE;NOMINMAX;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + stdc17 + MultiThreaded + + + Console + true + true + true + mswsock.lib;ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/sln/punchnat/punchnat.vcxproj.filters b/sln/punchnat/punchnat.vcxproj.filters new file mode 100644 index 0000000..6167d4f --- /dev/null +++ b/sln/punchnat/punchnat.vcxproj.filters @@ -0,0 +1,69 @@ + + + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {598ffcc6-7351-4e2d-893f-cbcbfda03241} + + + {938c51d4-c6dd-4288-a1be-0b136460b0ad} + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {5669631a-8b61-4bab-83d7-21982734261d} + + + {780cbf8e-2985-4efa-a490-d141e8b48352} + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files\shares + + + Source Files\networks + + + Source Files\networks + + + Source Files\networks + + + Source Files\networks + + + + + Header Files\shares + + + Header Files\shares + + + Header Files\shares + + + Header Files\networks + + + Header Files\networks + + + Header Files\networks + + + \ No newline at end of file diff --git a/sln/punchnat/punchnat.vcxproj.user b/sln/punchnat/punchnat.vcxproj.user new file mode 100644 index 0000000..531c9a5 --- /dev/null +++ b/sln/punchnat/punchnat.vcxproj.user @@ -0,0 +1,23 @@ + + + + + + WindowsLocalDebugger + + + + + WindowsLocalDebugger + + + + + WindowsLocalDebugger + + + + + WindowsLocalDebugger + + \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..c61cacc --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,28 @@ +add_subdirectory(shares) +add_subdirectory(networks) + +if (WIN32) + target_link_libraries(${PROJECT_NAME} PUBLIC wsock32 ws2_32) +endif() +if (UNIX) + target_link_libraries(${PROJECT_NAME} PUBLIC stdc++) +endif() + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(${PROJECT_NAME} PUBLIC uring) +endif() + +if (${VCPKG_MANIFEST_DIR}) + find_package(asio CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE asio asio::asio) +else() + target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads) +endif() + +target_link_libraries(${PROJECT_NAME} PRIVATE SHAREDEFINES) +target_link_libraries(${PROJECT_NAME} PRIVATE NETCONNECTIONS) + +add_compile_options("$<$:/utf-8>") +add_compile_options("$<$:/utf-8>") +set_property(TARGET ${PROJECT_NAME} PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e45882f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include + +#include "shares/share_defines.hpp" +#include "networks/connections.hpp" +#include "networks/modes.hpp" + +int main(int argc, char *argv[]) +{ + if (argc <= 1) + { + printf("Usage: %s config1.conf\n", argv[0]); + printf(" %s config1.conf config2.conf...\n", argv[0]); + return 0; + } + + asio::io_context ioc; + asio::io_context network_io{ 1 }; + std::vector udp_sessions; + std::vector tcp_sessions; + + bool error_found = false; + + for (int i = 1; i < argc; ++i) + { + std::vector lines; + std::ifstream input(argv[i]); + std::copy( + std::istream_iterator(input), + std::istream_iterator(), + std::back_inserter(lines)); + + std::vector error_msg; + user_settings settings = parse_from_args(lines, error_msg); + if (error_msg.size() > 0) + { + printf("Error(s) found in setting file %s\n", argv[i]); + for (const std::string &each_one : error_msg) + { + std::cerr << "\t" << each_one << "\n"; + } + std::cerr << std::endl; + error_found = true; + continue; + } + + udp_sessions.emplace_back(udp_mode(ioc, network_io, settings)); + tcp_sessions.emplace_back(tcp_mode(ioc, settings)); + } + + std::cout << "error_found: " << (error_found ? "Yes" : "No") << "\n"; + std::cout << "TCP: " << tcp_sessions.size() << "\n"; + std::cout << "UDP: " << udp_sessions.size() << "\n"; + + for (tcp_mode &server : tcp_sessions) + { + server.start(); + } + + for (udp_mode &server : udp_sessions) + { + server.start(); + } + + if(!error_found) + { + std::thread([&] { ioc.run(); }).detach(); + network_io.run(); + } + + printf("bye\n"); + return 0; +} \ No newline at end of file diff --git a/src/networks/CMakeLists.txt b/src/networks/CMakeLists.txt new file mode 100644 index 0000000..7f83dc3 --- /dev/null +++ b/src/networks/CMakeLists.txt @@ -0,0 +1,8 @@ +set(THISLIB_NAME NETCONNECTIONS) + +add_library(${THISLIB_NAME} STATIC "connections.cpp" "stun.cpp" "tcp_mode.cpp" "udp_mode.cpp") +target_link_libraries(${THISLIB_NAME} PRIVATE SHAREDEFINES) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(${THISLIB_NAME} PUBLIC Threads::Threads) +#target_include_directories(${THISLIB_NAME} INTERFACE networks/ PARENT_SCOPE) diff --git a/src/networks/connections.cpp b/src/networks/connections.cpp new file mode 100644 index 0000000..47ac224 --- /dev/null +++ b/src/networks/connections.cpp @@ -0,0 +1,565 @@ +#include +#include +#include +#include +#include +#include +#include "connections.hpp" + +using namespace std::chrono; +using namespace std::literals; + +int64_t right_now() +{ + auto right_now = std::chrono::system_clock::now(); + return std::chrono::duration_cast(right_now.time_since_epoch()).count(); +} + +void empty_tcp_callback(std::shared_ptr input_data, size_t data_size, tcp_session *tmp2) +{ +} + +void empty_udp_callback(std::shared_ptr tmp1, size_t tmpt, udp::endpoint &&tmp2, asio::ip::port_type tmp3) +{ +} + +void empty_tcp_disconnect(tcp_session *tmp) +{ +} + +std::unique_ptr send_stun_3489_request(udp_server &sender, const std::string &stun_host) +{ + asio::error_code ec; + udp::resolver &udp_resolver = sender.get_resolver(); + udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp::v6(), stun_host, "3478", + udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + + if (ec) + return nullptr; + + auto number = generate_random_number(); + std::unique_ptr header = rfc3489::create_stun_header(number); + size_t header_size = sizeof(rfc3489::stun_header); + for (auto &target_address : remote_addresses) + { + std::vector data(header_size); + std::copy_n((uint8_t *)(header.get()), header_size, data.begin()); + sender.async_send_out(std::move(data), target_address); + } + + return header; +} + +std::unique_ptr send_stun_8489_request(udp_server &sender, const std::string &stun_host) +{ + asio::error_code ec; + udp::resolver &udp_resolver = sender.get_resolver(); + udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp::v6(), stun_host, "3478", + udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + + if (ec) + return nullptr; + + auto number = generate_random_number(); + std::unique_ptr header = rfc8489::create_stun_header(number); + size_t header_size = sizeof(rfc8489::stun_header); + + for (auto &target_address : remote_addresses) + { + std::vector data(header_size); + std::copy_n((uint8_t *)header.get(), header_size, data.data()); + sender.async_send_out(std::move(data), target_address); + } + + return header; +} + +void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, rfc8489::stun_header *header) +{ + asio::error_code ec; + udp::resolver &udp_resolver = sender.get_resolver(); + udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp::v6(), stun_host, "3478", + udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + + if (ec) + return; + + size_t header_size = sizeof(rfc8489::stun_header); + for (auto &target_address : remote_addresses) + { + std::vector data(header_size); + std::copy_n((uint8_t *)header, header_size, data.data()); + sender.async_send_out(std::move(data), target_address); + } + + return; +} + +std::unique_ptr send_stun_8489_request(tcp_session &sender, const std::string &stun_host) +{ + auto number = generate_random_number(); + std::unique_ptr header = rfc8489::create_stun_header(number); + size_t header_size = sizeof(rfc8489::stun_header); + + std::vector data(header_size); + std::copy_n((uint8_t *)header.get(), header_size, data.data()); + sender.async_send_data(std::move(data)); + + return header; +} + +void resend_stun_8489_request(tcp_session &sender, const std::string &stun_host, rfc8489::stun_header *header) +{ + size_t header_size = sizeof(rfc8489::stun_header); + std::vector data(header_size); + std::copy_n((uint8_t *)header, header_size, data.data()); + sender.async_send_data(std::move(data)); + + return; +} + + +void tcp_session::start() +{ + async_read_data(); +} + +bool tcp_session::is_open() +{ + return connection_socket.is_open(); +} + +void tcp_session::async_read_data() +{ + if (!stopped.load() && connection_socket.is_open()) + { + std::shared_ptr buffer_cache(new uint8_t[BUFFER_SIZE]()); + asio::async_read(connection_socket, asio::buffer(buffer_cache.get(), BUFFER_SIZE), asio::transfer_at_least(1), + [this, buffer_cache](const asio::error_code &error, size_t bytes_transferred) + { + after_read_completed(buffer_cache, error, bytes_transferred); + }); + } +} + +size_t tcp_session::send_data(const std::vector &buffer_data, asio::error_code &ec) +{ + return connection_socket.send(asio::buffer(buffer_data.data(), buffer_data.size()), 0, ec); +} + +size_t tcp_session::send_data(const std::vector &buffer_data) +{ + return connection_socket.send(asio::buffer(buffer_data)); +} + +size_t tcp_session::send_data(const uint8_t *buffer_data, size_t size_in_bytes) +{ + return connection_socket.send(asio::buffer(buffer_data, size_in_bytes)); +} + +void tcp_session::async_send_data(std::shared_ptr input_data, size_t data_size) +{ + if (stopped.load()) + return; + asio::async_write(connection_socket, asio::buffer(input_data.get(), data_size), + [this, input_data](const asio::error_code &error, size_t bytes_transferred) + { + after_write_completed(error, bytes_transferred); + }); +} + +void tcp_session::async_send_data(std::vector &&data) +{ + if (stopped.load()) + return; + auto asio_buffer = asio::buffer(data); + asio::async_write(connection_socket, asio_buffer, + [this, data_ = std::move(data)](const asio::error_code &error, size_t bytes_transferred) + { after_write_completed(error, bytes_transferred); }); +} + +void tcp_session::async_send_data(const uint8_t *buffer_data, size_t size_in_bytes) +{ + asio::async_write(connection_socket, asio::buffer(buffer_data, size_in_bytes), + std::bind(&tcp_session::after_write_completed, this, + std::placeholders::_1, std::placeholders::_2)); +} + +void tcp_session::when_disconnect(std::function callback_before_disconnect) +{ + std::unique_lock locker{ callback_mutex }; + callback_for_disconnect = callback_before_disconnect; +} + +void tcp_session::stop() +{ + stopped.store(true); +} + +void tcp_session::replace_callback(tcp_callback_t callback_func) +{ + callback = callback_func; +} + +tcp::socket& tcp_session::socket() +{ + return connection_socket; +} + +void tcp_session::after_write_completed(const asio::error_code &error, size_t bytes_transferred) +{ + if (error) + { + return; + } +} + +void tcp_session::after_read_completed(std::shared_ptr buffer_cache, const asio::error_code &error, size_t bytes_transferred) +{ + if (stopped.load()) + return; + + if (error) + { + std::shared_lock locker{ callback_mutex }; + auto callback_before_disconnect = callback_for_disconnect; + locker.unlock(); + callback_before_disconnect(this); + return; + } + + async_read_data(); + callback(buffer_cache, bytes_transferred, this); +} + + + +std::unique_ptr tcp_server::connect(const std::string &remote_address, asio::ip::port_type port_num, tcp_callback_t callback_func, asio::error_code &ec) +{ + return connect(remote_address, std::to_string(port_num), callback_func, ec); +} + +std::unique_ptr tcp_server::connect(const std::string &remote_address, const std::string &port_num, tcp_callback_t callback_func, asio::error_code & ec) +{ + std::unique_ptr new_connection = std::make_unique(internal_io_context, callback_func); + tcp::socket ¤t_socket = new_connection->socket(); + auto remote_endpoints = resolver.resolve(tcp::v6(), remote_address, port_num, + tcp::resolver::numeric_service | tcp::resolver::v4_mapped | tcp::resolver::all_matching, ec); + for (auto &endpoint_entry : remote_endpoints) + { + current_socket.open(endpoint_entry.endpoint().protocol()); + current_socket.set_option(tcp::no_delay(true)); + if (endpoint_entry.endpoint().protocol() == tcp::v6()) + current_socket.set_option(asio::ip::v6_only(false)); + current_socket.connect(endpoint_entry, ec); + if (!ec) + break; + current_socket.close(); + } + return new_connection; +} + +void tcp_server::acceptor_initialise(const tcp::endpoint &ep) +{ + asio::ip::v6_only v6_option(false); + tcp_acceptor.open(ep.protocol()); + tcp_acceptor.set_option(v6_option); + tcp_acceptor.set_option(tcp::no_delay(true)); + tcp_acceptor.bind(ep); + tcp_acceptor.listen(tcp_acceptor.max_connections); +} + +void tcp_server::start_accept() +{ + std::unique_ptr new_connection = std::make_unique(internal_io_context, session_callback); + tcp_session *connection_ptr = new_connection.get(); + + tcp_acceptor.async_accept(connection_ptr->socket(), + [this, tcp_connection = std::move(new_connection)](const asio::error_code &error_code) mutable + { + handle_accept(std::move(tcp_connection), error_code); + }); +} + +void tcp_server::handle_accept(std::unique_ptr &&new_connection, const asio::error_code &error_code) +{ + if (error_code) + { + if (!tcp_acceptor.is_open()) + return; + } + + start_accept(); + acceptor_callback(std::move(new_connection)); +} + +std::unique_ptr tcp_client::connect(tcp_callback_t callback_func, asio::error_code &ec) +{ + std::unique_ptr new_connection = std::make_unique(internal_io_context, callback_func); + tcp::socket ¤t_socket = new_connection->socket(); + for (auto &endpoint_entry : remote_endpoints) + { + current_socket.open(endpoint_entry.endpoint().protocol()); + current_socket.set_option(tcp::no_delay(true)); + if (endpoint_entry.endpoint().protocol() == tcp::v6()) + current_socket.set_option(asio::ip::v6_only(false)); + current_socket.connect(endpoint_entry, ec); + if (!ec) + break; + current_socket.close(); + } + return new_connection; +} + +bool tcp_client::set_remote_hostname(const std::string &remote_address, asio::ip::port_type port_num, asio::error_code &ec) +{ + return set_remote_hostname(remote_address, std::to_string(port_num), ec); +} + +bool tcp_client::set_remote_hostname(const std::string &remote_address, const std::string &port_num, asio::error_code &ec) +{ + remote_endpoints = resolver.resolve(tcp::v6(), remote_address, port_num, + tcp::resolver::numeric_service | tcp::resolver::v4_mapped | tcp::resolver::all_matching, ec); + + return remote_endpoints.size() > 0; +} + +void udp_server::continue_receive() +{ + start_receive(); +} + +void udp_server::async_send_out(std::shared_ptr> data, const udp::endpoint &client_endpoint) +{ + connection_socket.async_send_to(asio::buffer(*data), client_endpoint, + [data](const asio::error_code &error, size_t bytes_transferred) {}); +} + +void udp_server::async_send_out(std::shared_ptr data, size_t data_size, const udp::endpoint &client_endpoint) +{ + connection_socket.async_send_to(asio::buffer(data.get(), data_size), client_endpoint, + [data](const asio::error_code &error, size_t bytes_transferred) {}); +} + +void udp_server::async_send_out(std::vector &&data, const udp::endpoint &client_endpoint) +{ + auto asio_buffer = asio::buffer(data); + connection_socket.async_send_to(asio_buffer, client_endpoint, + [data_ = std::move(data)](const asio::error_code &error, size_t bytes_transferred) {}); +} + +void udp_server::initialise(const udp::endpoint &ep) +{ + asio::ip::v6_only v6_option(false); + connection_socket.open(ep.protocol()); + connection_socket.set_option(v6_option); + connection_socket.bind(ep); +} + +void udp_server::start_receive() +{ + std::shared_ptr buffer_cache(new uint8_t[BUFFER_SIZE]()); + connection_socket.async_receive_from(asio::buffer(buffer_cache.get(), BUFFER_SIZE), incoming_endpoint, + [buffer_cache, this](const asio::error_code &error, std::size_t bytes_transferred) mutable + { + handle_receive(buffer_cache, error, bytes_transferred); + }); +} + +void udp_server::handle_receive(std::shared_ptr buffer_cache, const asio::error_code &error, std::size_t bytes_transferred) +{ + if (error) + { + if (connection_socket.is_open()) + start_receive(); + return; + } + + udp::endpoint copy_of_incoming_endpoint = incoming_endpoint; + start_receive(); + //callback(buffer_cache, bytes_transferred, std::move(copy_of_incoming_endpoint), port_number); + asio::post(task_assigner, [this, buffer_cache, bytes_transferred, peer_ep = std::move(copy_of_incoming_endpoint)]() mutable + { + callback(buffer_cache, bytes_transferred, std::move(peer_ep), port_number); + }); +} + +asio::ip::port_type udp_server::get_port_number() +{ + return port_number; +} + + + + + +void udp_client::pause(bool set_as_pause) +{ + bool expect = set_as_pause; + if (paused.compare_exchange_strong(expect, set_as_pause)) + return; + paused.store(set_as_pause); + start_receive(); +} + +void udp_client::stop() +{ + stopped.store(true); + callback = empty_udp_callback; + this->disconnect(); +} + +bool udp_client::is_pause() +{ + return paused.load(); +} + +bool udp_client::is_stop() +{ + return stopped.load(); +} + +udp::resolver::results_type udp_client::get_remote_hostname(const std::string &remote_address, asio::ip::port_type port_num, asio::error_code &ec) +{ + return get_remote_hostname(remote_address, std::to_string(port_num), ec); +} + +udp::resolver::results_type udp_client::get_remote_hostname(const std::string &remote_address, const std::string &port_num, asio::error_code &ec) +{ + udp::resolver::results_type remote_addresses = resolver.resolve(udp::v6(), remote_address, port_num, + udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + + return remote_addresses; +} + +void udp_client::disconnect() +{ + if (connection_socket.is_open()) + { + asio::error_code ec; + connection_socket.close(ec); + } +} + +void udp_client::async_receive() +{ + if (paused.load() || stopped.load()) + return; + start_receive(); +} + +size_t udp_client::send_out(const std::vector &data, const udp::endpoint &peer_endpoint, asio::error_code &ec) +{ + if (stopped.load()) + return 0; + + size_t sent_size = connection_socket.send_to(asio::buffer(data), peer_endpoint, 0, ec); + last_send_time.store(right_now()); + return sent_size; +} + +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()) + return 0; + + size_t sent_size = connection_socket.send_to(asio::buffer(data, size), peer_endpoint, 0, ec); + last_send_time.store(right_now()); + return sent_size; +} + +void udp_client::async_send_out(std::shared_ptr> data, const udp::endpoint &peer_endpoint) +{ + if (stopped.load()) + return; + + connection_socket.async_send_to(asio::buffer(*data), peer_endpoint, + [data](const asio::error_code &error, size_t bytes_transferred) {}); + last_send_time.store(right_now()); +} + +void udp_client::async_send_out(std::shared_ptr data, size_t data_size, const udp::endpoint &peer_endpoint) +{ + if (stopped.load()) + return; + + connection_socket.async_send_to(asio::buffer(data.get(), data_size), peer_endpoint, + [data](const asio::error_code &error, size_t bytes_transferred) {}); + last_send_time.store(right_now()); +} + +void udp_client::async_send_out(std::vector &&data, const udp::endpoint &peer_endpoint) +{ + if (stopped.load()) + return; + + auto asio_buffer = asio::buffer(data); + connection_socket.async_send_to(asio_buffer, peer_endpoint, + [data_ = std::move(data)](const asio::error_code &error, size_t bytes_transferred) {}); + last_send_time.store(right_now()); +} + +asio::ip::port_type udp_client::local_port_number() +{ + if (connection_socket.is_open()) + return connection_socket.local_endpoint().port(); + return 0; +} + +int64_t udp_client::time_gap_of_receive() +{ + return calculate_difference(right_now(), last_receive_time.load()); +} + +int64_t udp_client::time_gap_of_send() +{ + return calculate_difference(right_now(), last_send_time.load()); +} + +void udp_client::initialise() +{ + asio::ip::v6_only v6_option(false); + connection_socket.open(udp::v6()); + connection_socket.set_option(v6_option); +} + +void udp_client::start_receive() +{ + if (paused.load() || stopped.load()) + return; + + std::shared_ptr buffer_cache(new uint8_t[BUFFER_SIZE]()); + + connection_socket.async_receive_from(asio::buffer(buffer_cache.get(), BUFFER_SIZE), incoming_endpoint, + [buffer_cache, this](const asio::error_code &error, std::size_t bytes_transferred) + { + handle_receive(buffer_cache, error, bytes_transferred); + }); +} + +void udp_client::handle_receive(std::shared_ptr buffer_cache, const asio::error_code &error, std::size_t bytes_transferred) +{ + if (stopped.load()) + return; + + if (error) + { + if (!paused.load() && connection_socket.is_open()) + start_receive(); + return; + } + + last_receive_time.store(right_now()); + asio::error_code ec; + auto local_ep = connection_socket.local_endpoint(ec); + if (ec) + return; + auto local_port = local_ep.port(); + udp::endpoint copy_of_incoming_endpoint = incoming_endpoint; + start_receive(); + //callback(buffer_cache, bytes_transferred, std::move(copy_of_incoming_endpoint), local_port); + asio::post(task_assigner, [this, buffer_cache, bytes_transferred, peer_ep = std::move(copy_of_incoming_endpoint), local_port]() mutable + { + callback(buffer_cache, bytes_transferred, std::move(peer_ep), local_port); + }); +} diff --git a/src/networks/connections.hpp b/src/networks/connections.hpp new file mode 100644 index 0000000..d584354 --- /dev/null +++ b/src/networks/connections.hpp @@ -0,0 +1,238 @@ +#pragma once + +#ifndef __CONNECTIONS__ +#define __CONNECTIONS__ + +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../shares/share_defines.hpp" +#include "stun.hpp" + +using asio::ip::tcp; +using asio::ip::udp; + +constexpr uint8_t TIME_GAP = std::numeric_limits::max(); //seconds +constexpr size_t BUFFER_SIZE = 4096u; +constexpr size_t EMPTY_PACKET_SIZE = 1430u; +constexpr size_t RETRY_TIMES = 5u; +constexpr size_t RETRY_WAITS = 3u; +constexpr size_t TIMEOUT = 180; // second +constexpr size_t CLEANUP_WAITS = 10; // second +constexpr auto STUN_RESEND = std::chrono::seconds(30); +constexpr auto FINDER_TIMEOUT_INTERVAL = std::chrono::seconds(1); +constexpr auto CHANGEPORT_UPDATE_INTERVAL = std::chrono::seconds(1); +constexpr auto EXPRING_UPDATE_INTERVAL = std::chrono::seconds(5); +const asio::ip::udp::endpoint local_empty_target(asio::ip::make_address_v6("::1"), 70); + + +class tcp_session; + +using tcp_callback_t = std::function, size_t, tcp_session*)>; +using udp_callback_t = std::function, size_t, udp::endpoint&&, asio::ip::port_type)>; + +int64_t right_now(); + +void empty_tcp_callback(std::shared_ptr input_data, size_t data_size, tcp_session *tmp2); +void empty_udp_callback(std::shared_ptr tmp1, size_t, udp::endpoint &&tmp2, asio::ip::port_type tmp3); +void empty_tcp_disconnect(tcp_session *tmp); + +int64_t right_now(); + +class tcp_session +{ +public: + + tcp_session(asio::io_context &io_context, tcp_callback_t callback_func) + : connection_socket(io_context), stopped(false), + callback(callback_func), callback_for_disconnect(empty_tcp_disconnect) {} + + void start(); + bool is_open(); + + void async_read_data(); + + size_t send_data(const std::vector &buffer_data, asio::error_code &ec); + size_t send_data(const std::vector &buffer_data); + size_t send_data(const uint8_t *buffer_data, size_t size_in_bytes); + + void async_send_data(std::shared_ptr input_data, size_t data_size); + void async_send_data(std::vector &&data); + void async_send_data(const uint8_t *buffer_data, size_t size_in_bytes); + + void when_disconnect(std::function callback_before_disconnect); + void stop(); + void replace_callback(tcp_callback_t callback_func); + + tcp::socket& socket(); + +private: + void after_write_completed(const asio::error_code &error, size_t bytes_transferred); + + void after_read_completed(std::shared_ptr buffer_cache, const asio::error_code &error, size_t bytes_transferred); + + tcp::socket connection_socket; + tcp_callback_t callback; + std::atomic stopped; + std::shared_mutex callback_mutex; + std::function callback_for_disconnect; +}; + +class tcp_server +{ +public: + using acceptor_callback_t = std::function&&)>; + tcp_server() = delete; + tcp_server(asio::io_context &io_context, const tcp::endpoint &ep, + acceptor_callback_t acceptor_callback_func, tcp_callback_t callback_func) + : internal_io_context(io_context), resolver(io_context), tcp_acceptor(io_context), + acceptor_callback(acceptor_callback_func), session_callback(callback_func) + { + acceptor_initialise(ep); + start_accept(); + } + + std::unique_ptr connect(const std::string &remote_address, asio::ip::port_type port_num, tcp_callback_t callback_func, asio::error_code &ec); + std::unique_ptr connect(const std::string &remote_address, const std::string &port_num, tcp_callback_t callback_func, asio::error_code &ec); + +private: + void acceptor_initialise(const tcp::endpoint &ep); + void start_accept(); + void handle_accept(std::unique_ptr &&new_connection, const asio::error_code &error_code); + + asio::io_context &internal_io_context; + tcp::acceptor tcp_acceptor; + tcp::resolver resolver; + acceptor_callback_t acceptor_callback; + tcp_callback_t session_callback; + bool paused; +}; + +class tcp_client +{ +public: + tcp_client() = delete; + tcp_client(asio::io_context &io_context) + : internal_io_context(io_context), resolver(io_context) + { + } + + std::unique_ptr connect(tcp_callback_t callback_func, asio::error_code &ec); + + bool set_remote_hostname(const std::string &remote_address, asio::ip::port_type port_num, asio::error_code &ec); + bool set_remote_hostname(const std::string &remote_address, const std::string &port_num, asio::error_code &ec); + +private: + + asio::io_context &internal_io_context; + tcp::resolver resolver; + asio::ip::basic_resolver_results remote_endpoints; +}; + + +class udp_server +{ +public: + udp_server() = delete; + udp_server(asio::io_context &io_context, asio::strand &asio_strand, const udp::endpoint &ep, udp_callback_t callback_func) + : port_number(ep.port()), resolver(io_context), connection_socket(io_context), callback(callback_func), task_assigner(asio_strand) + { + initialise(ep); + start_receive(); + } + + void continue_receive(); + + void async_send_out(std::shared_ptr> data, const udp::endpoint &client_endpoint); + void async_send_out(std::shared_ptr data, size_t data_size, const udp::endpoint &client_endpoint); + void async_send_out(std::vector &&data, const udp::endpoint &client_endpoint); + udp::resolver& get_resolver() { return resolver; } + +private: + void initialise(const udp::endpoint &ep); + void start_receive(); + void handle_receive(std::shared_ptr buffer_cache, const asio::error_code &error, std::size_t bytes_transferred); + + asio::ip::port_type get_port_number(); + + asio::ip::port_type port_number; + udp::resolver resolver; + udp::socket connection_socket; + udp::endpoint incoming_endpoint; + udp_callback_t callback; + asio::strand &task_assigner; +}; + +class udp_client +{ +public: + udp_client() = delete; + udp_client(asio::io_context &io_context, asio::strand &asio_strand, udp_callback_t callback_func) + : connection_socket(io_context), resolver(io_context), callback(callback_func), task_assigner(asio_strand), + last_receive_time(right_now()), last_send_time(right_now()), + paused(false), stopped(false) + { + initialise(); + } + + void pause(bool set_as_pause); + void stop(); + bool is_pause(); + bool is_stop(); + + udp::resolver::results_type get_remote_hostname(const std::string &remote_address, asio::ip::port_type port_num, asio::error_code &ec); + udp::resolver::results_type get_remote_hostname(const std::string &remote_address, const std::string &port_num, asio::error_code &ec); + + void disconnect(); + + void async_receive(); + + size_t send_out(const std::vector &data, const udp::endpoint &peer_endpoint, asio::error_code &ec); + size_t send_out(const uint8_t *data, size_t size, const udp::endpoint &peer_endpoint, asio::error_code &ec); + + void async_send_out(std::shared_ptr> data, const udp::endpoint &peer_endpoint); + void async_send_out(std::shared_ptr data, size_t data_size, const udp::endpoint &peer_endpoint); + void async_send_out(std::vector &&data, const udp::endpoint &peer_endpoint); + + asio::ip::port_type local_port_number(); + + int64_t time_gap_of_receive(); + int64_t time_gap_of_send(); + +protected: + void initialise(); + + void start_receive(); + + void handle_receive(std::shared_ptr buffer_cache, const asio::error_code &error, std::size_t bytes_transferred); + + udp::socket connection_socket; + udp::resolver resolver; + udp::endpoint incoming_endpoint; + udp_callback_t callback; + asio::strand &task_assigner; + std::atomic last_receive_time; + std::atomic last_send_time; + std::atomic paused; + std::atomic stopped; +}; + + +std::unique_ptr send_stun_3489_request(udp_server &sender, const std::string &stun_host); +std::unique_ptr send_stun_8489_request(udp_server &sender, const std::string &stun_host); +void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, rfc8489::stun_header *header); +std::unique_ptr send_stun_8489_request(tcp_session &sender, const std::string &stun_host); +void resend_stun_8489_request(tcp_session &sender, const std::string &stun_host, rfc8489::stun_header *header); + +#endif // !__CONNECTIONS__ diff --git a/src/networks/modes.hpp b/src/networks/modes.hpp new file mode 100644 index 0000000..6c70382 --- /dev/null +++ b/src/networks/modes.hpp @@ -0,0 +1,144 @@ +#pragma once +#include "connections.hpp" +#include + +#ifndef __CLIENT_HPP__ +#define __CLIENT_HPP__ + +class tcp_mode +{ + asio::io_context &io_context; + user_settings current_settings; + std::unique_ptr tcp_access_point; + std::unique_ptr stun_keep_alive_session; + std::string keep_alive_host = "www.qq.com"; + + std::unique_ptr stun_header; + std::atomic external_ipv4_port; + std::atomic external_ipv4_address; + std::atomic external_ipv6_port; + std::shared_mutex mutex_ipv6; + std::array external_ipv6_address; + const std::array zero_value_array; + + std::mutex mutex_tcp_sessions; + std::unordered_map> tcp_sessions; + + asio::steady_timer timer_expiring_kcp; + asio::steady_timer timer_change_ports; + asio::steady_timer timer_stun; + asio::strand asio_strand; + + void tcp_server_accept_incoming(std::unique_ptr &&incoming_session); + void tcp_server_incoming(std::shared_ptr input_data, size_t data_size, tcp_session *incoming_session, tcp_session *outcoming_session); + void tcp_client_incoming(std::shared_ptr input_data, size_t data_size, tcp_session *incoming_session, tcp_session *output_session); + void local_disconnect(tcp_session *incoming_session, tcp_session *outcoming_session); + void connect_stun(); + void send_stun_request(const asio::error_code &e); + void stun_keep_alive(tcp_session *incoming_session); + void stun_disconnected(tcp_session *incoming_session); + void save_external_ip_address(uint32_t ipv4_address, uint16_t ipv4_port, const std::array &ipv6_address, uint16_t ipv6_port); + void extract_stun_data(std::shared_ptr input_data, size_t data_size, tcp_session *session); + +public: + tcp_mode() = delete; + tcp_mode(const tcp_mode &) = delete; + tcp_mode& operator=(const tcp_mode &) = delete; + + tcp_mode(asio::io_context &io_context_ref, const user_settings &settings) + : io_context(io_context_ref), timer_stun(io_context), + timer_expiring_kcp(io_context), timer_change_ports(io_context), + asio_strand(asio::make_strand(io_context.get_executor())), + zero_value_array{}, + current_settings(settings) + { + } + + tcp_mode(tcp_mode &&existing_client) noexcept : + io_context(existing_client.io_context), + timer_stun(std::move(existing_client.timer_stun)), + timer_expiring_kcp(std::move(existing_client.timer_expiring_kcp)), + timer_change_ports(std::move(existing_client.timer_change_ports)), + asio_strand(std::move(existing_client.asio_strand)), + zero_value_array{}, + current_settings(std::move(existing_client.current_settings)) + { + } + + ~tcp_mode(); + + bool start(); +}; + +class udp_mode +{ + asio::io_context &io_context; + asio::io_context &network_io; + user_settings current_settings; + std::unique_ptr udp_access_point; + std::unique_ptr stun_header; + std::atomic external_ipv4_port; + std::atomic external_ipv4_address; + std::atomic external_ipv6_port; + std::shared_mutex mutex_ipv6; + std::array external_ipv6_address; + const std::array zero_value_array; + + std::shared_mutex mutex_udp_session_map_to_wrapper; + std::unordered_map> udp_session_map_to_wrapper; + std::shared_mutex mutex_wrapper_session_map_to_udp; + std::unordered_map wrapper_session_map_to_udp; + + std::shared_mutex mutex_udp_target; + std::unique_ptr udp_target; + + asio::steady_timer timer_find_timeout; + asio::steady_timer timer_stun; + asio::strand asio_strand; + + void udp_server_incoming(std::shared_ptr data, size_t data_size, udp::endpoint &&peer, asio::ip::port_type port_number); + void udp_client_incoming_to_udp(std::shared_ptr data, size_t data_size, udp::endpoint &&peer, asio::ip::port_type local_port_number); + udp::endpoint get_remote_address(); + + void loop_timeout_sessions(); + void wrapper_loop_updates(const asio::error_code &e); + void send_stun_request(const asio::error_code &e); + void save_external_ip_address(uint32_t ipv4_address, uint16_t ipv4_port, const std::array &ipv6_address, uint16_t ipv6_port); + +public: + udp_mode() = delete; + udp_mode(const udp_mode &) = delete; + udp_mode& operator=(const udp_mode &) = delete; + + udp_mode(asio::io_context &io_context_ref, asio::io_context &net_io, const user_settings &settings) : + io_context(io_context_ref), + network_io(net_io), + timer_find_timeout(io_context), + timer_stun(io_context), + asio_strand(asio::make_strand(io_context.get_executor())), + external_ipv4_port(0), + external_ipv4_address(0), + external_ipv6_port(0), + external_ipv6_address{}, + zero_value_array{}, + current_settings(settings) {} + + udp_mode(udp_mode &&existing_client) noexcept : + io_context(existing_client.io_context), + network_io(existing_client.network_io), + timer_find_timeout(std::move(existing_client.timer_find_timeout)), + timer_stun(std::move(existing_client.timer_stun)), + asio_strand(std::move(existing_client.asio_strand)), + external_ipv4_port(existing_client.external_ipv4_port.load()), + external_ipv4_address(existing_client.external_ipv4_address.load()), + external_ipv6_port(existing_client.external_ipv6_port.load()), + external_ipv6_address{ existing_client.external_ipv6_address }, + zero_value_array{}, + current_settings(std::move(existing_client.current_settings)) {} + + ~udp_mode(); + + bool start(); +}; + +#endif // !__CLIENT_HPP__ diff --git a/src/networks/stun.cpp b/src/networks/stun.cpp new file mode 100644 index 0000000..9c279c1 --- /dev/null +++ b/src/networks/stun.cpp @@ -0,0 +1,151 @@ +#include +#include +#include +#include "stun.hpp" + +namespace rfc3489 +{ + std::unique_ptr create_stun_header(uint64_t id) + { + std::unique_ptr header_data = std::make_unique(); + header_data->message_type = htons(message_type::binding_request); + header_data->message_length = 0; + header_data->transaction_id_part_1 = std::numeric_limits::max(); + header_data->transaction_id_part_2 = id; + + return header_data; + } + + bool unpack_address_port(const uint8_t *data, uint64_t transaction_id_part_1, uint64_t transaction_id_part_2, uint32_t &ip_address, uint16_t &port) + { + const uint8_t *ptr = data; + const stun_header *header = (const stun_header *)ptr; + if(ntohs(header->message_type) != message_type::binding_response) + return false; + + uint16_t attrbutes_size = ntohs(header->message_length); + if (header->transaction_id_part_1 != transaction_id_part_1 || header->transaction_id_part_2 != transaction_id_part_2) + return false; + + const stun_attributes *attribute_ptr = (const stun_attributes *)(ptr + sizeof(stun_header)); + const stun_attributes *next_attribute_ptr = attribute_ptr; + while ((const uint8_t *)next_attribute_ptr < (const uint8_t *)attribute_ptr + attrbutes_size) + { + const stun_attributes *current_attribute_ptr = next_attribute_ptr; + if (ntohs(current_attribute_ptr->attribute_type) == attributes_type::mapped_address) + { + const uint8_t *ipaddress_ptr = (const uint8_t *)current_attribute_ptr + sizeof(stun_attributes); + stun_mapped_address_ipv4 *ipv4 = (stun_mapped_address_ipv4 *)ipaddress_ptr; + if (ipv4->family == 1) + { + ip_address = ntohl(ipv4->ip_address); + port = ntohs(ipv4->port); + return true; + } + } + + const uint8_t *next_ptr = (const uint8_t *)current_attribute_ptr + sizeof(stun_attributes) + ntohs(current_attribute_ptr->length); + next_attribute_ptr = (const stun_attributes *)next_ptr; + } + + return false; + } +} + +namespace rfc8489 +{ + std::unique_ptr create_stun_header(uint64_t id) + { + std::unique_ptr header_data = std::make_unique(); + header_data->message_type = htons(message_type::class_request | message_type::binding); + header_data->message_length = 0; + header_data->magic_cookie = htonl(magic_cookie_value); + header_data->transaction_id_part_1 = std::numeric_limits::max(); + header_data->transaction_id_part_2 = id; + + return header_data; + } + + bool unpack_address_port(const uint8_t *data, uint32_t transaction_id_part_1, uint64_t transaction_id_part_2, + uint32_t &ipv4_address, uint16_t &ipv4_port, std::array &ipv6_address, uint16_t &ipv6_port) + { + bool address_has_found = false; + const uint8_t *ptr = data; + const stun_header *header = (const stun_header *)ptr; + uint16_t message_type = ntohs(header->message_type); + + if ((message_type & message_type::class_xor_bitset) != message_type::class_success_response) + return false; + + uint16_t attrbutes_size = ntohs(header->message_length); + if (header->transaction_id_part_1 != transaction_id_part_1 || header->transaction_id_part_2 != transaction_id_part_2) + return false; + + const stun_attributes *attribute_ptr = (const stun_attributes *)(ptr + sizeof(stun_header)); + const stun_attributes *next_attribute_ptr = attribute_ptr; + while ((const uint8_t *)next_attribute_ptr < (const uint8_t *)attribute_ptr + attrbutes_size) + { + const stun_attributes *current_attribute_ptr = next_attribute_ptr; + if (ntohs(current_attribute_ptr->attribute_type) == attributes_type::mapped_address) + { + const uint8_t *ipaddress_ptr = (const uint8_t *)current_attribute_ptr + sizeof(stun_attributes); + rfc3489::stun_mapped_address_ipv4 *ipv4 = (rfc3489::stun_mapped_address_ipv4 *)ipaddress_ptr; + if (ipv4->family == 1) + { + ipv4_address = ntohl(ipv4->ip_address); + ipv4_port = ntohs(ipv4->port); + address_has_found = true; + } + } + + if (ntohs(current_attribute_ptr->attribute_type) == attributes_type::xor_mapped_address) + { + const uint8_t *ipaddress_ptr = (const uint8_t *)current_attribute_ptr + sizeof(stun_attributes); + stun_mapped_address_ipv4 *ipv4 = (stun_mapped_address_ipv4 *)ipaddress_ptr; + stun_mapped_address_ipv6 *ipv6 = (stun_mapped_address_ipv6 *)ipaddress_ptr; + if (ipv4->family == ip_family::ipv4) + { + ipv4_address = ntohl(ipv4->x_ip_address) ^ magic_cookie_value; + ipv4_port = ntohs(ipv4->x_port) ^ magic_cookie_front16; + address_has_found = true; + } + + if (ipv6->family == ip_family::ipv6) + { + std::copy(std::begin(ipv6->x_ip_address), std::end(ipv6->x_ip_address), ipv6_address.begin()); + uint8_t *ptr = ipv6_address.data(); + uint32_t n_cookie = htonl(magic_cookie_value); + + for (uint8_t *u8_ptr = (uint8_t *)&n_cookie; + u8_ptr < (uint8_t *)&n_cookie + sizeof(n_cookie); + u8_ptr++, ptr++) + { + *ptr ^= *u8_ptr; + } + + for (uint8_t *u8_ptr = (uint8_t *)&transaction_id_part_1; + u8_ptr < (uint8_t *)&transaction_id_part_1 + sizeof(transaction_id_part_1); + u8_ptr++, ptr++) + { + *ptr ^= *u8_ptr; + } + + for (uint8_t *u8_ptr = (uint8_t *)&transaction_id_part_2; + u8_ptr < (uint8_t *)&transaction_id_part_2 + sizeof(transaction_id_part_2); + u8_ptr++, ptr++) + { + *ptr ^= *u8_ptr; + } + + ipv6_port = ntohs(ipv6->x_port) ^ magic_cookie_front16; + address_has_found = true; + } + } + + const uint8_t *next_ptr = (const uint8_t *)current_attribute_ptr + sizeof(stun_attributes) + ntohs(current_attribute_ptr->length); + next_attribute_ptr = (const stun_attributes *)next_ptr; + } + + return address_has_found; + } +} diff --git a/src/networks/stun.hpp b/src/networks/stun.hpp new file mode 100644 index 0000000..3cc2095 --- /dev/null +++ b/src/networks/stun.hpp @@ -0,0 +1,159 @@ +#pragma once + +#ifndef __STUN_HPP__ +#define __STUN_HPP__ + +#include +#include +#include +#include "../shares/share_defines.hpp" + +namespace rfc3489 +{ + namespace message_type + { + constexpr uint16_t binding_request = 0x0001; + constexpr uint16_t binding_response = 0x0101; + constexpr uint16_t binding_error_response = 0x0111; + constexpr uint16_t shared_secret_request = 0x0002; + constexpr uint16_t shared_secret_response = 0x0102; + constexpr uint16_t shared_secret_error_response = 0x0112; + } + + namespace attributes_type + { + constexpr uint16_t mapped_address = 0x0001; + constexpr uint16_t response_address = 0x0002; + constexpr uint16_t change_request = 0x0003; + constexpr uint16_t source_address = 0x0004; + constexpr uint16_t changed_address = 0x0005; + constexpr uint16_t username = 0x0006; + constexpr uint16_t password = 0x0007; + constexpr uint16_t message_integrity = 0x0008; + constexpr uint16_t error_code = 0x0009; + constexpr uint16_t unknown_attributes = 0x000a; + constexpr uint16_t reflected_from = 0x000b; + } + +#pragma pack (push, 1) + struct stun_header + { + uint16_t message_type; + uint16_t message_length; + uint64_t transaction_id_part_1; + uint64_t transaction_id_part_2; + }; + + struct stun_attributes + { + uint16_t attribute_type; + uint16_t length; + }; + + struct stun_mapped_address_ipv4 + { + uint8_t ignore; + uint8_t family; + uint16_t port; + uint32_t ip_address; + }; +#pragma pack(pop) + + std::unique_ptr create_stun_header(uint64_t id); + bool unpack_address_port(const uint8_t *data, uint64_t transaction_id_part_1, + uint64_t transaction_id_part_2, uint32_t &ip_address, uint16_t &port); +} + +namespace rfc8489 +{ + constexpr uint32_t magic_cookie_value = 0x2112A442; + constexpr uint16_t magic_cookie_front16 = 0x2112; + + namespace message_type + { + constexpr uint16_t class_xor_bitset = 0b00'00000'1'000'1'0000; + constexpr uint16_t class_request = 0b00'00000'0'000'0'0000; + constexpr uint16_t class_indication = 0b00'00000'0'000'1'0000; + constexpr uint16_t class_success_response = 0b00'00000'1'000'0'0000; + constexpr uint16_t class_error_response = 0b00'00000'1'000'1'0000; + + constexpr uint16_t binding = 0x001; + } + + namespace attributes_type + { + constexpr uint16_t mapped_address = 0x0001; + constexpr uint16_t username = 0x0006; + constexpr uint16_t message_integrity = 0x0008; + constexpr uint16_t error_code = 0x0009; + constexpr uint16_t unknown_attributes = 0x000a; + constexpr uint16_t realm = 0x000b; + constexpr uint16_t nonce = 0x000b; + constexpr uint16_t xor_mapped_address = 0x0020; + constexpr uint16_t software = 0x8022; + constexpr uint16_t alternate_server = 0x8023; + constexpr uint16_t fingerprint = 0x8028; + constexpr uint16_t message_integrity_sha256 = 0x001c; + constexpr uint16_t password_algorithm = 0x001d; + constexpr uint16_t userhash = 0x001e; + constexpr uint16_t password_algorithms = 0x8002; + constexpr uint16_t alternate_domain = 0x8003; + } + + namespace password_algorithms + { + constexpr uint16_t md5 = 0x0001; + constexpr uint16_t sha_256 = 0x0002; + } + + namespace ip_family + { + constexpr uint8_t ipv4 = 1; + constexpr uint8_t ipv6 = 2; + } + +#pragma pack (push, 1) + struct stun_header + { + uint16_t message_type; + uint16_t message_length; + uint32_t magic_cookie; + uint32_t transaction_id_part_1; + uint64_t transaction_id_part_2; + }; + + struct stun_attributes + { + uint16_t attribute_type; + uint16_t length; + }; + + struct stun_mapped_address_ipv4 + { + uint8_t ignore; + uint8_t family; + uint16_t x_port; + uint32_t x_ip_address; + }; + + struct stun_mapped_address_ipv6 + { + uint8_t ignore; + uint8_t family; + uint16_t x_port; + uint8_t x_ip_address[16]; + }; +#pragma pack(pop) + + std::unique_ptr create_stun_header(uint64_t id); + bool unpack_address_port(const uint8_t *data, + uint32_t transaction_id_part_1, uint64_t transaction_id_part_2, + uint32_t &ipv4_address, uint16_t &ipv4_port, + std::array &ipv6_address, uint16_t &ipv6_port); + + + +} + +#endif // !__STUN_HPP__ + diff --git a/src/networks/tcp_mode.cpp b/src/networks/tcp_mode.cpp new file mode 100644 index 0000000..ccea387 --- /dev/null +++ b/src/networks/tcp_mode.cpp @@ -0,0 +1,247 @@ +#include +#include +#include +#include +#include "modes.hpp" + +using namespace std::placeholders; +using namespace std::chrono; +using namespace std::literals; + +//std::atomic counter_packet_client; + + +tcp_mode::~tcp_mode() +{ + timer_stun.cancel(); + timer_expiring_kcp.cancel(); + timer_change_ports.cancel(); +} + +bool tcp_mode::start() +{ + printf("start_up() running in client mode (TCP)\n"); + + uint16_t port_number = current_settings.listen_port; + if (port_number == 0) + return false; + + tcp::endpoint listen_on_ep(tcp::v6(), port_number); + if (!current_settings.listen_on.empty()) + { + asio::error_code ec; + asio::ip::address local_address = asio::ip::make_address(current_settings.listen_on, ec); + if (ec) + { + std::cerr << "TCP Mode - Listen Address incorrect - " << current_settings.listen_on << "\n"; + return false; + } + + if (local_address.is_v4()) + listen_on_ep.address(asio::ip::make_address_v6(asio::ip::v4_mapped, local_address.to_v4())); + else + listen_on_ep.address(local_address); + } + + try + { + tcp_server::acceptor_callback_t tcp_func_acceptor = std::bind(&tcp_mode::tcp_server_accept_incoming, this, _1); + tcp_access_point = std::make_unique(io_context, listen_on_ep, tcp_func_acceptor, tcp_callback_t()); + + if (!current_settings.stun_server.empty()) + { + connect_stun(); + } + } + catch (std::exception &ex) + { + std::cerr << ex.what() << std::endl; + return false; + } + + return true; +} + +void tcp_mode::tcp_server_accept_incoming(std::unique_ptr &&incoming_session) +{ + std::scoped_lock locker{ mutex_tcp_sessions }; + if (!incoming_session->is_open()) + return; + + tcp_client target_connector(io_context); + std::string &destination_address = current_settings.destination_address; + uint16_t destination_port = current_settings.destination_port; + asio::error_code ec; + if (target_connector.set_remote_hostname(destination_address, destination_port, ec) && ec) + { + std::cout << ec.message() << "\n"; + return; + } + + auto callback_function = [output_ptr = incoming_session.get(), this](std::shared_ptr input_data, size_t data_size, tcp_session *target_session) + { + tcp_client_incoming(input_data, data_size, target_session, output_ptr); + }; + std::unique_ptr local_session = target_connector.connect(callback_function, ec); + + if (ec) + return; + + local_session->async_read_data(); + local_session->when_disconnect([output_ptr = incoming_session.get(), this](tcp_session *session) { local_disconnect(session, output_ptr); }); + + incoming_session->replace_callback([output_ptr = local_session.get(), this](std::shared_ptr input_data, size_t data_size, tcp_session *incoming_session) + { + tcp_server_incoming(input_data, data_size, incoming_session, output_ptr); + }); + incoming_session->when_disconnect([output_ptr = local_session.get(), this](tcp_session *session) { local_disconnect(session, output_ptr); }); + incoming_session->async_read_data(); + + auto accept_ptr = incoming_session.get(); + auto output_ptr = local_session.get(); + tcp_sessions.insert({ accept_ptr, std::move(incoming_session) }); + tcp_sessions.insert({ output_ptr, std::move(local_session) }); +} + +void tcp_mode::tcp_server_incoming(std::shared_ptr input_data, size_t data_size, tcp_session *incoming_session, tcp_session *outcoming_session) +{ + if (data_size == 0) + return; + + outcoming_session->async_send_data(std::move(input_data), data_size); +} + +void tcp_mode::tcp_client_incoming(std::shared_ptr input_data, size_t data_size, tcp_session *incoming_session, tcp_session *output_session) +{ + if (data_size == 0) + return; + + output_session->async_send_data(input_data, data_size); +} + +void tcp_mode::local_disconnect(tcp_session *incoming_session, tcp_session *outcoming_session) +{ + std::scoped_lock locker{ mutex_tcp_sessions }; + incoming_session->when_disconnect(empty_tcp_disconnect); + if (tcp_sessions.find(outcoming_session) != tcp_sessions.end()) + { + outcoming_session->socket().close(); + } + incoming_session->replace_callback(empty_tcp_callback); + incoming_session->stop(); + tcp_sessions.erase(incoming_session); +} + +void tcp_mode::connect_stun() +{ + asio::error_code ec; + stun_keep_alive_session = tcp_access_point->connect(keep_alive_host, "80", empty_tcp_callback, ec); + if (stun_keep_alive_session == nullptr) + return; + + stun_keep_alive(stun_keep_alive_session.get()); + auto stun_ip_func = [this](std::shared_ptr input_data, size_t data_size, tcp_session *session) { extract_stun_data(input_data, data_size, session); }; + auto stun_session = tcp_access_point->connect(current_settings.stun_server, "3478", stun_ip_func, ec); + if (ec) + { + std::cerr << "TCP Mode - Cannot Complete STUN Punching: " << ec.message() << "\n"; + return; + } + + if (stun_session == nullptr) + { + std::cerr << "TCP Mode - Cannot Connect STUN Server\n"; + return; + } + + stun_session->when_disconnect([this](tcp_session *session) { stun_disconnected(session); }); + stun_header = send_stun_8489_request(*stun_session, current_settings.stun_server); + + timer_stun.expires_after(std::chrono::seconds(1)); + timer_stun.async_wait([this](const asio::error_code &e) { send_stun_request(e); }); +} + + +void tcp_mode::send_stun_request(const asio::error_code &e) +{ + if (e == asio::error::operation_aborted) + return; + + if (current_settings.stun_server.empty()) + return; + + asio::error_code ec; + auto stun_ip_func = [this](std::shared_ptr input_data, size_t data_size, tcp_session *session) { extract_stun_data(input_data, data_size, session); }; + auto stun_session = tcp_access_point->connect(current_settings.stun_server, "3478", stun_ip_func, ec); + if (!ec || stun_session != nullptr) + { + resend_stun_8489_request(*stun_session, current_settings.stun_server, stun_header.get()); + } + + timer_stun.expires_after(STUN_RESEND); + timer_stun.async_wait([this](const asio::error_code &e) { send_stun_request(e); }); +} + +void tcp_mode::stun_keep_alive(tcp_session *incoming_session) +{ + std::string http_text = "GET /~ HTTP/1.1\r\n" + "Host: " + keep_alive_host + "\r\n" + "Connection: keep-alive\r\n\r\n"; + incoming_session->async_send_data((const uint8_t *)http_text.c_str(), http_text.size()); +} + +void tcp_mode::stun_disconnected(tcp_session *incoming_session) +{ + connect_stun(); +} + +void tcp_mode::save_external_ip_address(uint32_t ipv4_address, uint16_t ipv4_port, const std::array& ipv6_address, uint16_t ipv6_port) +{ + if (ipv4_address != 0 && ipv4_port != 0 && (external_ipv4_address.load() != ipv4_address || external_ipv4_port.load() != ipv4_port)) + { + external_ipv4_address.store(ipv4_address); + external_ipv4_port.store(ipv4_port); + std::stringstream ss; + ss << "TCP Mode - External IPv4 Address: " << asio::ip::make_address_v4(ipv4_address) << "\n"; + ss << "TCP Mode - External IPv4 Port: " << ipv4_port << "\n"; + std::string message = ss.str(); + if (!current_settings.log_ip_address.empty()) + asio::post(asio_strand, [message, log_ip_address = current_settings.log_ip_address]() { print_message_to_file(message, log_ip_address); }); + std::cout << message; + } + + std::shared_lock locker(mutex_ipv6); + if (ipv6_address != zero_value_array && ipv6_port != 0 && (external_ipv6_address != ipv6_address || external_ipv6_port != ipv6_port)) + { + locker.unlock(); + std::unique_lock lock_ipv6(mutex_ipv6); + external_ipv6_address = ipv6_address; + lock_ipv6.unlock(); + external_ipv6_port.store(ipv6_port); + std::stringstream ss; + ss << "TCP Mode - External IPv6 Address: " << asio::ip::make_address_v6(ipv6_address) << "\n"; + ss << "TCP Mode - External IPv6 Port: " << ipv6_port << "\n"; + std::string message = ss.str(); + if (!current_settings.log_ip_address.empty()) + asio::post(asio_strand, [message, log_ip_address = current_settings.log_ip_address]() { print_message_to_file(message, log_ip_address); }); + std::cout << message; + } +} + +void tcp_mode::extract_stun_data(std::shared_ptr input_data, size_t data_size, tcp_session * session) +{ + if (stun_header != nullptr) + { + uint32_t ipv4_address = 0; + uint16_t ipv4_port = 0; + std::array ipv6_address{}; + uint16_t ipv6_port = 0; + if (rfc8489::unpack_address_port(input_data.get(), stun_header->transaction_id_part_1, stun_header->transaction_id_part_2, ipv4_address, ipv4_port, ipv6_address, ipv6_port)) + { + save_external_ip_address(ipv4_address, ipv4_port, ipv6_address, ipv6_port); + return; + } + } + +} + diff --git a/src/networks/udp_mode.cpp b/src/networks/udp_mode.cpp new file mode 100644 index 0000000..8faa1c7 --- /dev/null +++ b/src/networks/udp_mode.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include "modes.hpp" + +using namespace std::placeholders; +using namespace std::chrono; +using namespace std::literals; + + +udp_mode::~udp_mode() +{ + timer_find_timeout.cancel(); + timer_stun.cancel(); +} + +bool udp_mode::start() +{ + printf("start_up() running in client mode (UDP)\n"); + + uint16_t port_number = current_settings.listen_port; + if (port_number == 0) + return false; + + udp::endpoint listen_on_ep(udp::v6(), port_number); + if (!current_settings.listen_on.empty()) + { + asio::error_code ec; + asio::ip::address local_address = asio::ip::make_address(current_settings.listen_on, ec); + if (ec) + { + std::cerr << "UDP Mode - Listen Address incorrect - " << current_settings.listen_on << "\n"; + return false; + } + + if (local_address.is_v4()) + listen_on_ep.address(asio::ip::make_address_v6(asio::ip::v4_mapped, local_address.to_v4())); + else + listen_on_ep.address(local_address); + } + + + try + { + udp_callback_t udp_func_ap = std::bind(&udp_mode::udp_server_incoming, this, _1, _2, _3, _4); + udp_access_point = std::make_unique(network_io, asio_strand, listen_on_ep, udp_func_ap); + + timer_find_timeout.expires_after(EXPRING_UPDATE_INTERVAL); + timer_find_timeout.async_wait([this](const asio::error_code &e) { wrapper_loop_updates(e); }); + + if (!current_settings.stun_server.empty()) + { + stun_header = send_stun_8489_request(*udp_access_point, current_settings.stun_server); + timer_stun.expires_after(std::chrono::seconds(1)); + timer_stun.async_wait([this](const asio::error_code &e) { send_stun_request(e); }); + } + } + catch (std::exception &ex) + { + std::cerr << ex.what() << std::endl; + return false; + } + + return true; +} + +void udp_mode::udp_server_incoming(std::shared_ptr data, size_t data_size, udp::endpoint &&peer, asio::ip::port_type port_number) +{ + if (data_size == 0) + return; + + if (stun_header != nullptr) + { + uint32_t ipv4_address = 0; + uint16_t ipv4_port = 0; + std::array ipv6_address{}; + uint16_t ipv6_port = 0; + if (rfc8489::unpack_address_port(data.get(), stun_header->transaction_id_part_1, stun_header->transaction_id_part_2, ipv4_address, ipv4_port, ipv6_address, ipv6_port)) + { + save_external_ip_address(ipv4_address, ipv4_port, ipv6_address, ipv6_port); + return; + } + } + + std::shared_lock share_locker_udp_session_map_to_wrapper{ mutex_udp_session_map_to_wrapper, std::defer_lock }; + std::unique_lock unique_locker_udp_session_map_to_wrapper{ mutex_udp_session_map_to_wrapper, std::defer_lock }; + share_locker_udp_session_map_to_wrapper.lock(); + + auto iter = udp_session_map_to_wrapper.find(peer); + if (iter == udp_session_map_to_wrapper.end()) + { + share_locker_udp_session_map_to_wrapper.unlock(); + unique_locker_udp_session_map_to_wrapper.lock(); + iter = udp_session_map_to_wrapper.find(peer); + if (iter == udp_session_map_to_wrapper.end()) + { + const std::string &destination_address = current_settings.destination_address; + uint16_t destination_port = current_settings.destination_port; + if (destination_port == 0) + return; + + auto udp_func = std::bind(&udp_mode::udp_client_incoming_to_udp, this, _1, _2, _3, _4); + auto udp_forwarder = std::make_unique(network_io, asio_strand, udp_func); + if (udp_forwarder == nullptr) + return; + + auto forwarder_ptr = udp_forwarder.get(); + asio::error_code ec; + for (int i = 0; i < RETRY_TIMES; ++i) + { + udp::resolver::results_type udp_endpoints = forwarder_ptr->get_remote_hostname(destination_address, destination_port, ec); + if (ec) + { + std::cerr << ec.message() << "\n"; + std::this_thread::sleep_for(std::chrono::seconds(RETRY_WAITS)); + } + else if (udp_endpoints.size() == 0) + { + std::cerr << "UDP Mode - destination address not found\n"; + std::this_thread::sleep_for(std::chrono::seconds(RETRY_WAITS)); + } + else + { + std::scoped_lock locker{ mutex_udp_target }; + udp_target = std::make_unique(*udp_endpoints.begin()); + break; + } + } + + if (ec) + return; + + forwarder_ptr->send_out(data.get(), data_size, *udp_target, ec); + if (ec) + return; + + asio::ip::port_type port_number = forwarder_ptr->local_port_number(); + std::unique_lock lock_wrapper_session_map_to_udp{ mutex_wrapper_session_map_to_udp }; + wrapper_session_map_to_udp[port_number] = peer; + lock_wrapper_session_map_to_udp.unlock(); + udp_session_map_to_wrapper.insert({ peer, std::move(udp_forwarder) }); + + forwarder_ptr->async_receive(); + + return; + } + unique_locker_udp_session_map_to_wrapper.unlock(); + share_locker_udp_session_map_to_wrapper.lock(); + } + + udp_client *udp_session = iter->second.get(); + share_locker_udp_session_map_to_wrapper.unlock(); + + udp_session->async_send_out(data, data_size, get_remote_address()); +} + + +void udp_mode::udp_client_incoming_to_udp(std::shared_ptr data, size_t data_size, udp::endpoint &&peer, asio::ip::port_type local_port_number) +{ + if (data_size == 0) + return; + + std::shared_lock lock_wrapper_session_map_to_udp{ mutex_wrapper_session_map_to_udp }; + auto session_iter = wrapper_session_map_to_udp.find(local_port_number); + if (session_iter == wrapper_session_map_to_udp.end()) + return; + udp::endpoint &udp_endpoint = session_iter->second; + lock_wrapper_session_map_to_udp.unlock(); + + udp_access_point->async_send_out(data, data_size, udp_endpoint); +} + +udp::endpoint udp_mode::get_remote_address() +{ + udp::endpoint ep; + std::shared_lock locker{ mutex_udp_target }; + ep = *udp_target; + locker.unlock(); + return ep; +} + +void udp_mode::loop_timeout_sessions() +{ + std::scoped_lock lockers{ mutex_udp_session_map_to_wrapper }; + for (auto iter = udp_session_map_to_wrapper.begin(), next_iter = iter; iter != udp_session_map_to_wrapper.end(); iter = next_iter) + { + ++next_iter; + std::unique_ptr &client_ptr = iter->second; + if (client_ptr->time_gap_of_receive() >= TIMEOUT && client_ptr->time_gap_of_send() >= TIMEOUT) + { + asio::ip::port_type port_number = client_ptr->local_port_number(); + client_ptr->pause(true); + client_ptr->stop(); + std::scoped_lock locker_wrapper_changeport_timestamp{ mutex_wrapper_session_map_to_udp }; + wrapper_session_map_to_udp.erase(port_number); + } + + if (client_ptr->time_gap_of_receive() > TIMEOUT + 5 && client_ptr->time_gap_of_send() > TIMEOUT + 5) + { + udp_session_map_to_wrapper.erase(iter); + } + } +} + + +void udp_mode::wrapper_loop_updates(const asio::error_code &e) +{ + if (e == asio::error::operation_aborted) + return; + + loop_timeout_sessions(); + + timer_find_timeout.expires_after(FINDER_TIMEOUT_INTERVAL); + timer_find_timeout.async_wait([this](const asio::error_code &e) { wrapper_loop_updates(e); }); +} + +void udp_mode::send_stun_request(const asio::error_code &e) +{ + if (e == asio::error::operation_aborted) + return; + + if (!current_settings.stun_server.empty()) + resend_stun_8489_request(*udp_access_point, current_settings.stun_server, stun_header.get()); + + timer_stun.expires_after(STUN_RESEND); + timer_stun.async_wait([this](const asio::error_code &e) { send_stun_request(e); }); +} + +void udp_mode::save_external_ip_address(uint32_t ipv4_address, uint16_t ipv4_port, const std::array& ipv6_address, uint16_t ipv6_port) +{ + if (ipv4_address != 0 && ipv4_port != 0 && (external_ipv4_address.load() != ipv4_address || external_ipv4_port.load() != ipv4_port)) + { + external_ipv4_address.store(ipv4_address); + external_ipv4_port.store(ipv4_port); + std::stringstream ss; + ss << "UDP Mode - External IPv4 Address: " << asio::ip::make_address_v4(ipv4_address) << "\n"; + ss << "UDP Mode - External IPv4 Port: " << ipv4_port << "\n"; + std::string message = ss.str(); + if (!current_settings.log_ip_address.empty()) + asio::post(asio_strand, [message, log_ip_address = current_settings.log_ip_address]() { print_message_to_file(message, log_ip_address); }); + std::cout << message; + } + + std::shared_lock locker(mutex_ipv6); + if (ipv6_address != zero_value_array && ipv6_port != 0 && (external_ipv6_address != ipv6_address || external_ipv6_port != ipv6_port)) + { + locker.unlock(); + std::unique_lock lock_ipv6(mutex_ipv6); + external_ipv6_address = ipv6_address; + lock_ipv6.unlock(); + external_ipv6_port.store(ipv6_port); + std::stringstream ss; + ss << "UDP Mode - External IPv6 Address: " << asio::ip::make_address_v6(ipv6_address) << "\n"; + ss << "UDP Mode - External IPv6 Port: " << ipv6_port << "\n"; + std::string message = ss.str(); + if (!current_settings.log_ip_address.empty()) + asio::post(asio_strand, [message, log_ip_address = current_settings.log_ip_address]() { print_message_to_file(message, log_ip_address); }); + std::cout << message; + } +} diff --git a/src/shares/CMakeLists.txt b/src/shares/CMakeLists.txt new file mode 100644 index 0000000..dd6704c --- /dev/null +++ b/src/shares/CMakeLists.txt @@ -0,0 +1,6 @@ +set(THISLIB_NAME SHAREDEFINES) + +add_library(${THISLIB_NAME} STATIC share_defines.cpp) + +#target_include_directories(${THISLIB_NAME} PUBLIC shares/ PARENT_SCOPE) + diff --git a/src/shares/share_defines.cpp b/src/shares/share_defines.cpp new file mode 100644 index 0000000..1b6a786 --- /dev/null +++ b/src/shares/share_defines.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include "share_defines.hpp" +#include "string_utils.hpp" + +user_settings parse_from_args(const std::vector &args, std::vector &error_msg) +{ + using namespace str_utils; + + user_settings current_user_settings; + error_msg.clear(); + + for (const std::string &arg : args) + { + auto line = trim_copy(arg); + if (line.empty() || line[0] == '#') + continue; + auto eq = line.find_first_of("="); + if (eq == std::string::npos) continue; + + std::string name = line.substr(0, eq); + std::string value = line.substr(eq + 1); + trim(name); + trim(value); + std::string original_value = value; + to_lower(name); + to_lower(value); + + if (value.empty()) + continue; + + switch (strhash(name.c_str())) + { + case strhash("listen_on"): + current_user_settings.listen_on = original_value; + break; + + case strhash("listen_port"): + if (auto port_number = std::stoi(value); port_number > 0 && port_number < 65536) + current_user_settings.listen_port = static_cast(port_number); + else + error_msg.emplace_back("invalid listen_port number: " + value); + break; + + case strhash("destination_port"): + if (auto port_number = std::stoi(value); port_number > 0 && port_number < 65536) + current_user_settings.destination_port = static_cast(port_number); + else + error_msg.emplace_back("invalid listen_port number: " + value); + break; + + + case strhash("destination_address"): + current_user_settings.destination_address = value; + break; + + case strhash("stun_server"): + current_user_settings.stun_server = original_value; + break; + + case strhash("log_path"): + current_user_settings.log_directory = original_value; + break; + + default: + error_msg.emplace_back("unknow option: " + arg); + } + } + + check_settings(current_user_settings, error_msg); + + return current_user_settings; +} + +void check_settings(user_settings ¤t_user_settings, std::vector &error_msg) +{ + if (current_user_settings.destination_address.empty()) + error_msg.emplace_back("invalid destination_address setting"); + + if (0 == current_user_settings.listen_port) + error_msg.emplace_back("listen_port is not set"); + + if (0 == current_user_settings.destination_port) + error_msg.emplace_back("destination_port is not set"); + + if (!current_user_settings.stun_server.empty()) + { + if (0 == current_user_settings.listen_port) + error_msg.emplace_back("do not specify multiple listen ports when STUN Server is set"); + } + + if (!current_user_settings.log_directory.empty()) + { + if (std::filesystem::exists(current_user_settings.log_directory)) + { + if (std::filesystem::is_directory(current_user_settings.log_directory)) + current_user_settings.log_ip_address = current_user_settings.log_directory / "ip_address.log"; + else + error_msg.emplace_back("Log Path is not directory"); + } + else + { + error_msg.emplace_back("Log Path does not exist"); + } + } +} + +int64_t calculate_difference(int64_t number1, int64_t number2) +{ + return abs(number1 - number2); +} + +void print_message_to_file(const std::string &message, const std::filesystem::path &log_file) +{ + std::ofstream output_file; + output_file.open(log_file, std::ios::out | std::ios::app); + output_file << message; +} diff --git a/src/shares/share_defines.hpp b/src/shares/share_defines.hpp new file mode 100644 index 0000000..43f3b6f --- /dev/null +++ b/src/shares/share_defines.hpp @@ -0,0 +1,39 @@ +#pragma once + +#ifndef _SHARE_DEFINES_ +#define _SHARE_DEFINES_ + +#include +#include +#include +#include +#include +#include +#include + +template +T generate_random_number() +{ + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution uniform_dist(std::numeric_limits::min(), std::numeric_limits::max()); + return uniform_dist(mt); +} + +struct user_settings +{ + uint16_t listen_port = 0; + uint16_t destination_port = 0; + std::string listen_on; + std::string destination_address; + std::string stun_server; + std::filesystem::path log_directory; + std::filesystem::path log_ip_address; +}; + +user_settings parse_from_args(const std::vector &args, std::vector &error_msg); +void check_settings(user_settings ¤t_user_settings, std::vector &error_msg); +int64_t calculate_difference(int64_t number1, int64_t number2); +void print_message_to_file(const std::string &message, const std::filesystem::path &log_file); + +#endif // !_SHARE_HEADER_ diff --git a/src/shares/string_utils.hpp b/src/shares/string_utils.hpp new file mode 100644 index 0000000..1b118ff --- /dev/null +++ b/src/shares/string_utils.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include + +namespace str_utils +{ + template + constexpr inline uint64_t strhash(const T* str, int h = 0) + { + return str[h] ? (strhash(str, h + 1) * 5) ^ static_cast(str[h]) : 4096; + } + + // trim from start (in place) + inline void ltrim(std::string &s) + { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](auto ch) + { + return !std::isspace(ch); + })); + } + + // trim from end (in place) + inline void rtrim(std::string &s) + { + s.erase(std::find_if(s.rbegin(), s.rend(), [](auto ch) + { + return !std::isspace(ch); + }).base(), s.end()); + } + + // trim from both ends (in place) + inline void trim(std::string &s) + { + ltrim(s); + rtrim(s); + } + + // trim from start (copying) + inline std::string ltrim_copy(std::string s) + { + ltrim(s); + return s; + } + + // trim from end (copying) + inline std::string rtrim_copy(std::string s) + { + rtrim(s); + return s; + } + + // trim from both ends (copying) + inline std::string trim_copy(std::string s) + { + trim(s); + return s; + } + + inline void to_lower(std::string &s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](auto c) { return tolower(c); }); + } + + inline std::string to_lower_copy(std::string s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](auto c) { return tolower(c); }); + return s; + } + + inline void to_upper(std::string &s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](auto c) { return toupper(c); }); + } + + inline std::string to_upper_copy(std::string s) + { + std::transform(s.begin(), s.end(), s.begin(), + [](auto c) { return tolower(c); }); + return s; + } +} \ No newline at end of file