├── .clang-format ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── README.zh-CN.md ├── bin ├── client.json └── server.json ├── build_client.bat ├── build_client.sh ├── build_server.bat ├── build_server.sh └── src ├── client.cpp ├── client.h ├── client └── main.cpp ├── cs_message.h ├── handshake.h ├── log.cpp ├── log.h ├── message.cpp ├── message.h ├── prefix.h ├── proxy_socket.cpp ├── proxy_socket.h ├── server.cpp ├── server.h ├── server └── main.cpp ├── session.cpp └── session.h /.clang-format: -------------------------------------------------------------------------------- 1 | UseTab: Never 2 | TabWidth: 4 3 | IndentWidth: 4 4 | AccessModifierOffset: -4 5 | PointerAlignment: Left 6 | NamespaceIndentation: All 7 | ColumnLimit: 0 8 | BreakBeforeBraces: Allman 9 | BreakConstructorInitializers: BeforeColon 10 | BreakBeforeBinaryOperators: None 11 | BreakBeforeTernaryOperators: false 12 | AlwaysBreakTemplateDeclarations: Yes 13 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 14 | AllowShortBlocksOnASingleLine: Always 15 | AllowShortCaseLabelsOnASingleLine: true 16 | AllowShortFunctionsOnASingleLine: All 17 | AllowShortLoopsOnASingleLine: false 18 | AllowShortLambdasOnASingleLine: Empty 19 | Cpp11BracedListStyle: true 20 | IndentCaseLabels: false 21 | SortIncludes: false 22 | ReflowComments: true 23 | AlignConsecutiveAssignments: false 24 | AlignTrailingComments: true 25 | AlignAfterOpenBracket: Align 26 | BinPackArguments: true 27 | BinPackParameters: true 28 | AlwaysBreakAfterReturnType: None 29 | KeepEmptyLinesAtTheStartOfBlocks: true 30 | IndentWrappedFunctionNames: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | *.pdb 34 | 35 | 36 | *.ilk 37 | *.log 38 | 39 | thirdparty/boost 40 | thirdparty/rapidjson 41 | thirdparty/spdlog 42 | 43 | 44 | bin/* 45 | 46 | v2/bin/ 47 | .vscode/settings.json 48 | .vscode/c_cpp_properties.json 49 | build -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/spdlog"] 2 | path = third_party/spdlog 3 | url = https://github.com/gabime/spdlog 4 | [submodule "third_party/asio"] 5 | path = third_party/asio 6 | url = https://github.com/chriskohlhoff/asio 7 | [submodule "third_party/jsonserializer"] 8 | path = third_party/jsonserializer 9 | url = https://github.com/AlpsMonaco/jsonserializer 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) build and launch server", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/bin/server", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}/bin", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "为 gdb 启用整齐打印", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | }, 24 | { 25 | "description": "将反汇编风格设置为 Intel", 26 | "text": "-gdb-set disassembly-flavor intel", 27 | "ignoreFailures": true 28 | } 29 | ], 30 | "preLaunchTask": "(gdb) build server" 31 | }, 32 | { 33 | "name": "(gdb) launch server", 34 | "type": "cppdbg", 35 | "request": "launch", 36 | "program": "${workspaceFolder}/bin/server", 37 | "args": [], 38 | "stopAtEntry": false, 39 | "cwd": "${workspaceFolder}/bin", 40 | "environment": [], 41 | "externalConsole": false, 42 | "MIMode": "gdb", 43 | "setupCommands": [ 44 | { 45 | "description": "为 gdb 启用整齐打印", 46 | "text": "-enable-pretty-printing", 47 | "ignoreFailures": true 48 | }, 49 | { 50 | "description": "将反汇编风格设置为 Intel", 51 | "text": "-gdb-set disassembly-flavor intel", 52 | "ignoreFailures": true 53 | } 54 | ] 55 | }, 56 | { 57 | "name": "(gdb) build and launch client", 58 | "type": "cppdbg", 59 | "request": "launch", 60 | "program": "${workspaceFolder}/bin/client", 61 | "args": [], 62 | "stopAtEntry": false, 63 | "cwd": "${workspaceFolder}/bin", 64 | "environment": [], 65 | "externalConsole": false, 66 | "MIMode": "gdb", 67 | "setupCommands": [ 68 | { 69 | "description": "为 gdb 启用整齐打印", 70 | "text": "-enable-pretty-printing", 71 | "ignoreFailures": true 72 | }, 73 | { 74 | "description": "将反汇编风格设置为 Intel", 75 | "text": "-gdb-set disassembly-flavor intel", 76 | "ignoreFailures": true 77 | } 78 | ], 79 | "preLaunchTask": "(gdb) build client" 80 | }, 81 | { 82 | "name": "(gdb) launch client", 83 | "type": "cppdbg", 84 | "request": "launch", 85 | "program": "${workspaceFolder}/bin/client", 86 | "args": [], 87 | "stopAtEntry": false, 88 | "cwd": "${workspaceFolder}/bin", 89 | "environment": [], 90 | "externalConsole": false, 91 | "MIMode": "gdb", 92 | "setupCommands": [ 93 | { 94 | "description": "为 gdb 启用整齐打印", 95 | "text": "-enable-pretty-printing", 96 | "ignoreFailures": true 97 | }, 98 | { 99 | "description": "将反汇编风格设置为 Intel", 100 | "text": "-gdb-set disassembly-flavor intel", 101 | "ignoreFailures": true 102 | } 103 | ] 104 | }, 105 | { 106 | "name": "(Windows) build and launch server", 107 | "type": "cppvsdbg", 108 | "request": "launch", 109 | "program": "${workspaceFolder}/bin/server.exe", 110 | "args": [], 111 | "stopAtEntry": false, 112 | "cwd": "${workspaceFolder}/bin/", 113 | "environment": [], 114 | "console": "integratedTerminal", 115 | "preLaunchTask": "(windows) build server" 116 | }, 117 | { 118 | "name": "(Windows) launch server", 119 | "type": "cppvsdbg", 120 | "request": "launch", 121 | "program": "${workspaceFolder}/bin/server.exe", 122 | "args": [], 123 | "stopAtEntry": false, 124 | "cwd": "${workspaceFolder}/bin/", 125 | "environment": [], 126 | "console": "integratedTerminal", 127 | }, 128 | { 129 | "name": "(Windows) build and launch client", 130 | "type": "cppvsdbg", 131 | "request": "launch", 132 | "program": "${workspaceFolder}/bin/client.exe", 133 | "args": [], 134 | "stopAtEntry": false, 135 | "cwd": "${workspaceFolder}/bin/", 136 | "environment": [], 137 | "console": "integratedTerminal", 138 | "preLaunchTask": "(windows) build client" 139 | }, 140 | { 141 | "name": "(Windows) launch client", 142 | "type": "cppvsdbg", 143 | "request": "launch", 144 | "program": "${workspaceFolder}/bin/client.exe", 145 | "args": [], 146 | "stopAtEntry": false, 147 | "cwd": "${workspaceFolder}/bin/", 148 | "environment": [], 149 | "console": "integratedTerminal", 150 | }, 151 | ] 152 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cppbuild", 6 | "label": "(gdb) build server", 7 | "command": "/usr/bin/g++-10", 8 | "args": [ 9 | "-fdiagnostics-color=always", 10 | "-g", 11 | "-std=c++2a", 12 | "${workspaceFolder}/src/message.cpp", 13 | "${workspaceFolder}/src/proxy_socket.cpp", 14 | "${workspaceFolder}/src/server.cpp", 15 | "${workspaceFolder}/src/session.cpp", 16 | "${workspaceFolder}/src/log.cpp", 17 | "${workspaceFolder}/src/server/main.cpp", 18 | "-I${workspaceFolder}/third_party/asio/asio/include", 19 | "-I${workspaceFolder}/third_party/spdlog/include/", 20 | "-I${workspaceFolder}/third_party/jsonserializer/include/", 21 | "-o", 22 | "${workspaceFolder}/bin/server", 23 | "-lpthread" 24 | ], 25 | "options": { 26 | "cwd": "${workspaceFolder}/bin" 27 | }, 28 | "problemMatcher": [ 29 | "$gcc" 30 | ], 31 | "group": { 32 | "kind": "build", 33 | "isDefault": true 34 | } 35 | }, 36 | { 37 | "type": "cppbuild", 38 | "label": "(gdb) build client", 39 | "command": "/usr/bin/g++-10", 40 | "args": [ 41 | "-fdiagnostics-color=always", 42 | "-g", 43 | "-std=c++2a", 44 | "${workspaceFolder}/src/message.cpp", 45 | "${workspaceFolder}/src/proxy_socket.cpp", 46 | "${workspaceFolder}/src/client.cpp", 47 | "${workspaceFolder}/src/session.cpp", 48 | "${workspaceFolder}/src/log.cpp", 49 | "${workspaceFolder}/src/client/main.cpp", 50 | "-I${workspaceFolder}/third_party/asio/asio/include", 51 | "-I${workspaceFolder}/third_party/spdlog/include/", 52 | "-I${workspaceFolder}/third_party/jsonserializer/include/", 53 | "-o", 54 | "${workspaceFolder}/bin/client", 55 | "-lpthread" 56 | ], 57 | "options": { 58 | "cwd": "${workspaceFolder}/bin" 59 | }, 60 | "problemMatcher": [ 61 | "$gcc" 62 | ], 63 | "group": { 64 | "kind": "build", 65 | "isDefault": true 66 | } 67 | }, 68 | { 69 | "type": "cppbuild", 70 | "label": "(windows) build server", 71 | "command": "cl.exe", 72 | "args": [ 73 | "/Zi", 74 | "/EHsc", 75 | "/MDd", 76 | "/nologo", 77 | "/std:c++20", 78 | "/D_WIN32_WINNT=0x0601", 79 | "/Fe${workspaceFolder}\\bin\\server.exe", 80 | "${workspaceFolder}\\src\\message.cpp", 81 | "${workspaceFolder}\\src\\proxy_socket.cpp", 82 | "${workspaceFolder}\\src\\server.cpp", 83 | "${workspaceFolder}\\src\\session.cpp", 84 | "${workspaceFolder}\\src\\log.cpp", 85 | "${workspaceFolder}\\src\\server\\main.cpp", 86 | "/I${workspaceFolder}\\third_party\\asio\\asio\\include", 87 | "/I${workspaceFolder}\\third_party\\spdlog\\include", 88 | "/I${workspaceFolder}\\third_party\\jsonserializer\\include", 89 | ], 90 | "options": { 91 | "cwd": "${workspaceFolder}\\bin" 92 | }, 93 | "problemMatcher": [ 94 | "$msCompile" 95 | ], 96 | "group": { 97 | "kind": "build", 98 | "isDefault": true 99 | } 100 | }, 101 | { 102 | "type": "cppbuild", 103 | "label": "(windows) build client", 104 | "command": "cl.exe", 105 | "args": [ 106 | "/Zi", 107 | "/EHsc", 108 | "/MDd", 109 | "/nologo", 110 | "/std:c++20", 111 | "/D_WIN32_WINNT=0x0601", 112 | "/Fe${workspaceFolder}\\bin\\client.exe", 113 | "${workspaceFolder}\\src\\message.cpp", 114 | "${workspaceFolder}\\src\\proxy_socket.cpp", 115 | "${workspaceFolder}\\src\\client.cpp", 116 | "${workspaceFolder}\\src\\session.cpp", 117 | "${workspaceFolder}\\src\\log.cpp", 118 | "${workspaceFolder}\\src\\client\\main.cpp", 119 | "/I${workspaceFolder}\\third_party\\asio\\asio\\include", 120 | "/I${workspaceFolder}\\third_party\\spdlog\\include", 121 | "/I${workspaceFolder}\\third_party\\jsonserializer\\include", 122 | ], 123 | "options": { 124 | "cwd": "${workspaceFolder}\\bin" 125 | }, 126 | "problemMatcher": [ 127 | "$msCompile" 128 | ], 129 | "group": { 130 | "kind": "build", 131 | "isDefault": true 132 | } 133 | } 134 | ] 135 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(cppnat) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | 7 | if(WIN32) 8 | add_compile_definitions(_WIN32_WINNT=0x0601) 9 | endif() 10 | 11 | add_library(CPPNAT_LIBRARY STATIC 12 | src/log.cpp 13 | src/message.cpp 14 | src/proxy_socket.cpp 15 | src/session.cpp 16 | ) 17 | 18 | target_include_directories(CPPNAT_LIBRARY 19 | PUBLIC 20 | ${PROJECT_SOURCE_DIR}/third_party/asio/asio/include 21 | ${PROJECT_SOURCE_DIR}/third_party/spdlog/include/ 22 | ${PROJECT_SOURCE_DIR}/third_party/jsonserializer/include/ 23 | ) 24 | 25 | if (UNIX) 26 | target_link_libraries(CPPNAT_LIBRARY pthread) 27 | endif() 28 | 29 | if (MINGW) 30 | target_link_libraries(CPPNAT_LIBRARY ws2_32 mswsock) 31 | endif() 32 | 33 | add_executable(SERVER src/server.cpp src/server/main.cpp) 34 | 35 | set_target_properties(SERVER PROPERTIES OUTPUT_NAME "server") 36 | if(WIN32) 37 | set_target_properties(SERVER PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY $) 38 | else() 39 | set_target_properties(SERVER PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin/) 40 | endif() 41 | add_custom_command(TARGET SERVER POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/bin/server.json $) 42 | 43 | target_link_libraries(SERVER 44 | PRIVATE 45 | CPPNAT_LIBRARY 46 | ) 47 | 48 | add_executable(CLIENT src/client.cpp src/client/main.cpp) 49 | 50 | target_link_libraries(CLIENT 51 | PRIVATE 52 | CPPNAT_LIBRARY 53 | ) 54 | 55 | set_target_properties(CLIENT PROPERTIES OUTPUT_NAME "client") 56 | if(WIN32) 57 | set_target_properties(CLIENT PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY $) 58 | else() 59 | set_target_properties(CLIENT PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin/) 60 | endif() 61 | add_custom_command(TARGET CLIENT POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/bin/client.json $) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 柯正杰 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cppnat 2 | 3 | writen in cpp,cppnat is a NAT reverse proxy tool (aslo known as a hole punching tool) helps you expose your private IP and port and bind them onto a server with public IP,access your private service anywhere. 4 | 5 | [README](README.md) | [中文文档](README.zh-CN.md) 6 | 7 | ## build (recommended) 8 | 1. Init git submodules in `thirt_party/`. 9 | 2. Use `CMake` to generate platform project file.`CmakeLists.txt` file is in the root path of this repository. 10 | ### Linux 11 | ```sh 12 | mkdir build ; cd build ; cmake .. ; make 13 | ``` 14 | If the compile is successful,you will have both `server` and `client` in the `bin/` directory of this repository. 15 | The default `server.json` and `client.json` are also included in the `bin/` directory,change them to satisfy you needs. 16 | 17 | ### Windows 18 | ```bat 19 | mkdir build & cd build && cmake .. 20 | ``` 21 | Open `cppnat.sln` and compile `cppnat` using `Visual Studio`. 22 | You will have `server.exe` and `client.exe` in `Visual Studio` build output directory, 23 | could be `Release/` or `Debug/` depends on which mode you choose to compile. 24 | Also `server.json` and `client.json` will be copied to the directory, 25 | change them to satisfy you needs. 26 | 27 | 28 | ## build (deprecated) 29 | 1. Init git submodules in `thirt_party/`. 30 | 2. run `build_server.sh` and `build_client.sh` on linux os, requires at least `g++ 10`. 31 | For Windows system with MSVC compiler `cl.exe` has installed,run `build_server.bat` and `build_client.bat`. 32 | 3. If the compile is successful,you will have `server` and `client` in the `bin/` directory. 33 | The default `server.json` and `client.json` are also included in the `bin/` directory,change them to satisfy you needs. 34 | 35 | ## run 36 | There are `bin/server.json` and `bin/client.json` template config file in the `bin/` directory.Simply change them to satisfy you needs. 37 | 38 | ## config 39 | 40 | ### server 41 | `bin/server` is typically dispatched on a server with public ip.Every TCP connection to the ip and the port you have configured will be redirected to nat client. 42 | 43 | #### `server.json` 44 | ```json 45 | { 46 | "bind_ip": "0.0.0.0", 47 | "bind_port": 54432 48 | } 49 | ``` 50 | There are only two keys in the config json file indicates on which ip and which port the nat server should bind. 51 | 52 | 53 | ### client 54 | `bin/client` is typically dispatched on a computer without a public ip but able to connect to the Internet with NAT. 55 | A client helps you expose you service such as ssh or rdp to the Internet that you could access it anywhere. 56 | 57 | #### `client.json` 58 | ```json 59 | { 60 | "server_ip": "127.0.0.1", 61 | "server_port": 54432, 62 | "proxy_ip": "127.0.0.1", 63 | "proxy_port": 33123 64 | } 65 | ``` 66 | configure `server_ip` and `server_port` to a nat server's binded ip and binded port. 67 | configure `proxy_ip` and `proxy_port` to you local private service. 68 | 69 | **Now you could access you private service anywhere** 70 | based on asio,this library performs well under high concurrency as well. 71 | 72 | 73 | ## Project 74 | 75 | This library is written with `Visual Studio Code`,you could open this project of root path and launch it directly. 76 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # cppnat 2 | 3 | cppnat 是一个NAT内网穿透工具,用来帮助你暴露你的内网的服务到公网上。 4 | 5 | ## 编译 (推荐) 6 | 1. 初始化 `thirt_party/` 下的git子模块。 7 | 2. 使用`CMake`来生成项目文件。`CMakeLists.txt`在这个仓库的根目录中。 8 | ### Linux 9 | ```sh 10 | mkdir build ; cd build ; cmake .. ; make 11 | ``` 12 | 如果编译一切顺利,编译会输出 `server` 和 `client` 在这个仓库的 `bin/` 文件夹里。 13 | 默认的 `server.json` 和 `client.json` 也在里面,修改他们来满足你的需要。 14 | 15 | ### Windows 16 | ```bat 17 | mkdir build & cd build && cmake .. 18 | ``` 19 | 打开 `cppnat.sln`,然后使用 `Visual Studio` 来编译 `cppnat` . 20 | 编译成功的话,`Visual Studio` 会输出 `server.exe`和`client.exe`到`Release/`或者`Debug/`.里,取决于你使用什么模式编译。 21 | `server.json` 和 `client.json` 也会被拷贝过去修改他们来满足你的需要。 22 | 23 | ## 编译 (弃用) 24 | 1. 初始化 `thirt_party/` 下的git子模块。 25 | 2. linux系统下执行 `build_server.sh` 和 `build_client.sh` 需要至少`g++ 10`. 26 | Windows需要`Visual Studio`,执行`build_server.bat` 和 `build_client.bat`. 27 | 3. 如果编译顺利,编译会输出 `server` 和 `client` 在这个仓库的 `bin/` 文件夹里。 28 | 默认的 `server.json` 和 `client.json` 也在里面,修改他们来满足你的需要。 29 | 30 | ## 执行 31 | `bin/server.json` 和 `bin/client.json` 模板配置文件在在这个仓库的 `bin/` 文件夹里。修改他们来满足你的需要。 32 | 33 | ## 配置 34 | 35 | ### 服务端 36 | `bin/server` 一般部署在有公网的服务器上。每个TCP请求都会被重定向到cppnat客户端。 37 | 38 | #### `server.json` 39 | ```json 40 | { 41 | "bind_ip": "127.0.0.1", 42 | "bind_port": 54432 43 | } 44 | ``` 45 | 配置文件里配置公网服务器需要绑定的ip和端口 46 | 47 | 48 | ### 客户端 49 | `bin/client`一般部署在没有公网ip但能正常连接互联网的服务器上,通常是经过了网络地址转换。 50 | 51 | 52 | #### `client.json` 53 | ```json 54 | { 55 | "server_ip": "127.0.0.1", 56 | "server_port": 54432, 57 | "proxy_ip": "127.0.0.1", 58 | "proxy_port": 33123 59 | } 60 | ``` 61 | 配置 `server_ip` 和 `server_port` 来指定cppnat的ip和端口 62 | 配置 `proxy_ip` 和 `proxy_port` 来指定需要代理的内网TCP服务 63 | 64 | ## 项目 65 | 66 | 该项目由`Visual Studio Code`编写,可以用`Visual Studio Code`直接打开。 -------------------------------------------------------------------------------- /bin/client.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment_server": "cppnat server endpoint", 3 | "server_ip": "127.0.0.1", 4 | "server_port": 54432, 5 | "_comment_proxy": "endpoint you want to expose", 6 | "proxy_ip": "127.0.0.1", 7 | "proxy_port": 33123 8 | } 9 | -------------------------------------------------------------------------------- /bin/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "bind_ip": "0.0.0.0", 3 | "bind_port": 54432 4 | } 5 | -------------------------------------------------------------------------------- /build_client.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | echo compiling bin\client.exe 4 | 5 | cl.exe ^ 6 | /EHsc ^ 7 | /nologo ^ 8 | /Zi ^ 9 | /MD ^ 10 | /Ox ^ 11 | /std:c++20 ^ 12 | /D_WIN32_WINNT=0x0601 ^ 13 | /Fe:bin\client.exe ^ 14 | src\message.cpp ^ 15 | src\proxy_socket.cpp ^ 16 | src\client.cpp ^ 17 | src\session.cpp ^ 18 | src\log.cpp ^ 19 | src\client\main.cpp ^ 20 | /Ithird_party\asio\asio\include ^ 21 | /Ithird_party\spdlog\include ^ 22 | /Ithird_party\jsonserializer\include 23 | 24 | if %ERRORLEVEL% EQU 0 echo compile bin\client.exe successfully! 25 | pause -------------------------------------------------------------------------------- /build_client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | time=$(date "+%Y-%m-%d %H:%M:%S") 3 | echo "build client start at ${time}" 4 | 5 | g++ \ 6 | -std=c++2a \ 7 | -obin/client \ 8 | -O2 \ 9 | src/message.cpp \ 10 | src/proxy_socket.cpp \ 11 | src/client.cpp \ 12 | src/session.cpp \ 13 | src/log.cpp \ 14 | src/client/main.cpp \ 15 | -Ithird_party/asio/asio/include \ 16 | -Ithird_party/spdlog/include \ 17 | -Ithird_party/jsonserializer/include \ 18 | -lpthread 19 | 20 | time=$(date "+%Y-%m-%d %H:%M:%S") 21 | echo "build client end at ${time}" -------------------------------------------------------------------------------- /build_server.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | echo compiling bin\server.exe... 4 | 5 | cl.exe ^ 6 | /EHsc ^ 7 | /nologo ^ 8 | /Zi ^ 9 | /MD ^ 10 | /Ox ^ 11 | /std:c++20 ^ 12 | /D_WIN32_WINNT=0x0601 ^ 13 | /Fe:bin\server.exe ^ 14 | src\message.cpp ^ 15 | src\proxy_socket.cpp ^ 16 | src\server.cpp ^ 17 | src\session.cpp ^ 18 | src\log.cpp ^ 19 | src\server\main.cpp ^ 20 | /Ithird_party\asio\asio\include ^ 21 | /Ithird_party\spdlog\include ^ 22 | /Ithird_party\jsonserializer\include 23 | 24 | if %ERRORLEVEL% EQU 0 echo compile bin\server.exe successfully! 25 | pause -------------------------------------------------------------------------------- /build_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | time=$(date "+%Y-%m-%d %H:%M:%S") 3 | echo "build server start at ${time}" 4 | 5 | g++ \ 6 | -std=c++2a \ 7 | -obin/server \ 8 | -O2 \ 9 | src/message.cpp \ 10 | src/proxy_socket.cpp \ 11 | src/server.cpp \ 12 | src/session.cpp \ 13 | src/log.cpp \ 14 | src/server/main.cpp \ 15 | -Ithird_party/asio/asio/include \ 16 | -Ithird_party/spdlog/include \ 17 | -Ithird_party/jsonserializer/include \ 18 | -lpthread 19 | 20 | time=$(date "+%Y-%m-%d %H:%M:%S") 21 | echo "build server end at ${time}" 22 | -------------------------------------------------------------------------------- /src/client.cpp: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | 3 | #include "handshake.h" 4 | #include "log.h" 5 | 6 | NAMESPACE_CPPNAT_START 7 | 8 | Client::Client(const std::string& server_addr, std::uint16_t server_port, 9 | const std::string& proxy_addr, std::uint16_t proxy_port) 10 | : ios_(), 11 | server_endpoint_(asio::ip::address::from_string(server_addr), 12 | server_port), 13 | proxy_endpoint_(asio::ip::address::from_string(proxy_addr), proxy_port), 14 | server_socket_ptr_(), 15 | message_handler_(), 16 | message_writer_(), 17 | proxy_socket_map_() {} 18 | 19 | Client::~Client() {} 20 | 21 | void Client::Stop() 22 | { 23 | server_socket_ptr_->close(); 24 | ios_.stop(); 25 | int count = 0; 26 | for (;;) 27 | { 28 | count++; 29 | if (count == 60) return; 30 | if (ios_.stopped()) return; 31 | std::this_thread::sleep_for(std::chrono::seconds(1)); 32 | } 33 | } 34 | 35 | void Client::Start() 36 | { 37 | Log::Info("server {}:{},proxy to {}:{}", 38 | server_endpoint_.address().to_string(), server_endpoint_.port(), 39 | proxy_endpoint_.address().to_string(), proxy_endpoint_.port()); 40 | InitMessageHandler(); 41 | server_socket_ptr_ = std::make_shared(ios_); 42 | Log::Info("connecting to server {}:{}", 43 | server_endpoint_.address().to_string(), server_endpoint_.port()); 44 | server_socket_ptr_->async_connect(server_endpoint_, 45 | [&](const std::error_code& ec) -> void 46 | { 47 | if (ec) 48 | { 49 | HandleErrorCode(ec); 50 | return; 51 | } 52 | Handshake(); 53 | }); 54 | ios_.run(); 55 | } 56 | 57 | void Client::BeginMessageLoop() 58 | { 59 | Log::Info("begin message loop"); 60 | auto session = std::make_shared( 61 | server_socket_ptr_, message_handler_, 62 | [&](const std::error_code& ec) -> void 63 | { 64 | OnServerReadError(ec); 65 | }); 66 | message_writer_ = session->GetMessageWriter(); 67 | session->Start(); 68 | } 69 | 70 | void Client::OnServerReadError(const std::error_code& ec) 71 | { 72 | Log::Error("disconnect from server {} {}", ec.value(), ec.message()); 73 | for (auto& v : proxy_socket_map_) 74 | { 75 | v.second->Close(); 76 | } 77 | proxy_socket_map_.clear(); 78 | } 79 | 80 | void Client::OnServerWriteError(const std::error_code& ec) 81 | { 82 | Log::Error("write to server error {} {}", ec.value(), ec.message()); 83 | } 84 | 85 | void Client::BeginProxy(std::uint16_t id) 86 | { 87 | auto proxy_socket_ptr = proxy_socket_map_[id]; 88 | proxy_socket_ptr->SetOnRecv([proxy_socket_ptr, 89 | this](ProxyData& proxy_data) -> void 90 | { 91 | auto ec = message_writer_.Write(ServerMessage::kDataTransfer, proxy_data); 92 | if (ec) 93 | { 94 | OnServerWriteError(ec); 95 | return; 96 | } 97 | proxy_socket_ptr->ReadOnce(); 98 | }); 99 | proxy_socket_ptr->SetOnReadError( 100 | [proxy_socket_ptr, this](std::uint16_t id, 101 | const std::error_code& ec) -> void 102 | { 103 | HandleErrorCode(ec); 104 | ServerMessage::ClientProxySocketClosed msg{id}; 105 | auto err = 106 | message_writer_.Write(ServerMessage::kClientProxySocketClosed, msg); 107 | if (err) 108 | { 109 | OnServerWriteError(err); 110 | } 111 | }); 112 | proxy_socket_ptr->SetOnWriteError( 113 | [proxy_socket_ptr, this](std::uint16_t id, 114 | const std::error_code& ec) -> void 115 | { 116 | HandleErrorCode(ec); 117 | }); 118 | proxy_socket_ptr->ReadOnce(); 119 | } 120 | 121 | void Client::InitMessageHandler() 122 | { 123 | Log::Info("initialize message handler"); 124 | message_handler_.Bind( 125 | [&](const ClientMessage::NewProxy& msg, MessageWriter& writer) -> void 126 | { 127 | auto socket_ptr = std::make_shared(ios_); 128 | std::uint16_t id = msg.id; 129 | Log::Info("new proxy request,id:{}", id); 130 | socket_ptr->async_connect( 131 | proxy_endpoint_, 132 | [this, socket_ptr, id](const std::error_code& ec) -> void 133 | { 134 | ServerMessage::NewProxyResult result; 135 | result.id = id; 136 | if (ec) 137 | { 138 | HandleErrorCode(ec); 139 | result.code = StatusCode::kError; 140 | } 141 | else { 142 | proxy_socket_map_[id] = 143 | std::make_shared(id, socket_ptr); 144 | result.code = StatusCode::kSuccess; 145 | BeginProxy(id); 146 | } 147 | auto err = 148 | message_writer_.Write(ServerMessage::kNewProxyResult, result); 149 | if (err) OnServerWriteError(err); 150 | }); 151 | }); 152 | message_handler_ 153 | .Bind( 154 | [&](const ClientMessage::DataTransfer& msg, MessageWriter& writer) 155 | { 156 | auto buffer_ptr = msg.Copy(); 157 | proxy_socket_map_[msg.id]->Write(msg.Copy()); 158 | }); 159 | 160 | message_handler_.Bind( 162 | [&](const ServerMessage::ClientProxySocketClosed& msg, 163 | MessageWriter& writer) 164 | { 165 | proxy_socket_map_[msg.id]->Close(); 166 | proxy_socket_map_.erase(msg.id); 167 | }); 168 | } 169 | 170 | void Client::Handshake() 171 | { 172 | Log::Info("handshaking with server"); 173 | asio::async_write( 174 | *server_socket_ptr_, 175 | asio::buffer(Handshake::server_message.data(), 176 | Handshake::server_message.size()), 177 | [&](const std::error_code& ec, size_t) -> void 178 | { 179 | if (ec) 180 | { 181 | HandleErrorCode(ec); 182 | return; 183 | } 184 | std::shared_ptr> buffer_ptr = 185 | std::make_shared>(); 186 | asio::async_read( 187 | *server_socket_ptr_, 188 | asio::buffer(buffer_ptr->Get(), Handshake::client_message.size()), 189 | [&, buffer_ptr](const std::error_code& ec, size_t length) -> void 190 | { 191 | if (ec) 192 | { 193 | HandleErrorCode(ec); 194 | return; 195 | } 196 | std::string_view recv_data(buffer_ptr->Get(), length); 197 | Log::Bytes(recv_data, "handshake recv"); 198 | if (recv_data == Handshake::client_message) 199 | { 200 | BeginMessageLoop(); 201 | Log::Info("handshake successfully"); 202 | } 203 | }); 204 | }); 205 | } 206 | 207 | NAMESPACE_CPPNAT_END -------------------------------------------------------------------------------- /src/client.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_CLIENT_H__ 2 | #define __CPPNAT_CLIENT_H__ 3 | 4 | #include "cs_message.h" 5 | #include "prefix.h" 6 | #include "session.h" 7 | #include "proxy_socket.h" 8 | 9 | NAMESPACE_CPPNAT_START 10 | 11 | class Client 12 | { 13 | public: 14 | Client(const std::string& server_addr, std::uint16_t server_port, 15 | const std::string& proxy_addr, std::uint16_t proxy_port); 16 | ~Client(); 17 | 18 | void Start(); 19 | void Stop(); 20 | 21 | protected: 22 | asio::io_service ios_; 23 | asio::ip::tcp::endpoint server_endpoint_; 24 | asio::ip::tcp::endpoint proxy_endpoint_; 25 | SocketPtr server_socket_ptr_; 26 | MessageHandler message_handler_; 27 | MessageWriter message_writer_; 28 | std::map proxy_socket_map_; 29 | 30 | void Handshake(); 31 | void BeginMessageLoop(); 32 | void InitMessageHandler(); 33 | void OnServerReadError(const std::error_code& ec); 34 | void OnServerWriteError(const std::error_code& ec); 35 | void BeginProxy(std::uint16_t id); 36 | }; 37 | 38 | NAMESPACE_CPPNAT_END 39 | 40 | #endif -------------------------------------------------------------------------------- /src/client/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "../client.h" 7 | #include "../log.h" 8 | 9 | int main(int argc, char** argv) 10 | { 11 | cppnat::Log::SetLogName("nat_client"); 12 | cppnat::Log::Info("cppnat client start"); 13 | std::ifstream ifs("client.json"); 14 | if (!ifs.is_open()) 15 | { 16 | cppnat::Log::Error("open client.json failed"); 17 | return 1; 18 | } 19 | std::string config_file_content((std::istreambuf_iterator(ifs)), 20 | (std::istreambuf_iterator())); 21 | jsr::Json json; 22 | auto err = json.Parse(config_file_content); 23 | if (err) 24 | { 25 | cppnat::Log::Error(err.Message()); 26 | return 1; 27 | } 28 | std::string server_ip; 29 | std::uint16_t server_port; 30 | std::string proxy_ip; 31 | std::uint16_t proxy_port; 32 | err = json.Unmarshal({ 33 | {"server_ip", server_ip}, 34 | {"server_port", server_port}, 35 | {"proxy_ip", proxy_ip}, 36 | {"proxy_port", proxy_port}, 37 | }); 38 | cppnat::Log::Info("proxy to {}:{},connecting to nat server {}:{}", proxy_ip, proxy_port, server_ip, server_port); 39 | if (err) 40 | { 41 | cppnat::Log::Error(err.Message()); 42 | return 1; 43 | } 44 | for (;;) 45 | { 46 | cppnat::Client client(server_ip, server_port, proxy_ip, proxy_port); 47 | client.Start(); 48 | std::this_thread::sleep_for(std::chrono::seconds(5)); 49 | }; 50 | } -------------------------------------------------------------------------------- /src/cs_message.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_CS_MESSAGE_H__ 2 | #define __CPPNAT_CS_MESSAGE_H__ 3 | 4 | #include "message.h" 5 | 6 | NAMESPACE_CPPNAT_START 7 | 8 | enum class StatusCode : std::uint16_t 9 | { 10 | kSuccess, 11 | kError 12 | }; 13 | 14 | class ServerMessage 15 | { 16 | public: 17 | enum MessageEnum 18 | { 19 | kNewProxyResult = 10001, 20 | kClientProxySocketClosed, 21 | kDataTransfer, 22 | }; 23 | 24 | struct NewProxyResult : public Message 25 | { 26 | std::uint16_t id; 27 | StatusCode code; 28 | }; 29 | 30 | struct ClientProxySocketClosed : public Message 31 | { 32 | std::uint16_t id; 33 | 34 | ClientProxySocketClosed(size_t id) : id(id) {} 35 | }; 36 | 37 | struct DataTransfer : public Message 38 | { 39 | std::uint16_t id; 40 | std::uint16_t data_size; 41 | static constexpr size_t body_meta_size = sizeof(id) + sizeof(data_size); 42 | static constexpr size_t writable_size = 43 | Protocol::max_size - Protocol::header_size - body_meta_size; 44 | char data[writable_size]; 45 | 46 | size_t Size() { return body_meta_size + data_size; } 47 | 48 | DynamicBufferPtr Copy() 49 | { 50 | auto buffer_ptr = std::make_shared(data_size); 51 | buffer_ptr->Write(data, data_size); 52 | return buffer_ptr; 53 | } 54 | 55 | DynamicBufferPtr Copy() const 56 | { 57 | return const_cast(*this).Copy(); 58 | } 59 | }; 60 | }; 61 | 62 | class ClientMessage 63 | { 64 | public: 65 | enum MessageEnum 66 | { 67 | kNewProxy = 10001, 68 | kServerProxySocketClosed, 69 | kDataTransfer, 70 | }; 71 | 72 | struct NewProxy : public Message 73 | { 74 | std::uint16_t id; 75 | }; 76 | 77 | using NewProxyResult = ServerMessage::NewProxyResult; 78 | using ServerProxySocketClosed = ServerMessage::ClientProxySocketClosed; 79 | using DataTransfer = ServerMessage::DataTransfer; 80 | }; 81 | 82 | using ProxyData = ClientMessage::DataTransfer; 83 | 84 | NAMESPACE_CPPNAT_END 85 | 86 | #endif -------------------------------------------------------------------------------- /src/handshake.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_HANDSHAKE_H__ 2 | #define __CPPNAT_HANDSHAKE_H__ 3 | 4 | NAMESPACE_CPPNAT_START 5 | 6 | #include "prefix.h" 7 | 8 | class Handshake 9 | { 10 | public: 11 | static constexpr std::string_view server_message = "cpp_nat_hello_server"; 12 | static constexpr std::string_view client_message = "cpp_nat_hello_client"; 13 | }; 14 | 15 | NAMESPACE_CPPNAT_END 16 | 17 | #endif -------------------------------------------------------------------------------- /src/log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | 3 | NAMESPACE_CPPNAT_START 4 | 5 | void Log::SetLogLevel(spdlog::level::level_enum level) 6 | { 7 | Ins().LogLevel(level); 8 | }; 9 | 10 | Log::Log(const std::string& log_name) 11 | : log_ptr_(spdlog::basic_logger_mt(log_name, log_name + ".log")) 12 | { 13 | log_ptr_->set_level(spdlog::level::info); 14 | log_ptr_->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); 15 | log_ptr_->flush_on(spdlog::level::debug); 16 | spdlog::set_level(spdlog::level::info); 17 | } 18 | 19 | Log& Log::Ins(const std::string& log_name) 20 | { 21 | static Log logger(log_name); 22 | return logger; 23 | } 24 | 25 | void Log::LogLevel(spdlog::level::level_enum level) 26 | { 27 | log_ptr_->set_level(level); 28 | spdlog::set_level(level); 29 | } 30 | 31 | void HandleErrorCode(const std::error_code& ec) 32 | { 33 | Log::Error("{} {}", ec.value(), ec.message()); 34 | } 35 | 36 | void Log::SetLogName(const std::string& log_name) { Ins(log_name); } 37 | 38 | NAMESPACE_CPPNAT_END -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPP_NAT_LOG_H__ 2 | #define __CPP_NAT_LOG_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "prefix.h" 9 | 10 | NAMESPACE_CPPNAT_START 11 | 12 | class Log 13 | { 14 | public: 15 | static void SetLogLevel(spdlog::level::level_enum level); 16 | 17 | template 18 | static void Error(spdlog::format_string_t fmt, Args&&... args) 19 | { 20 | Ins().log_ptr_->error(fmt, std::forward(args)...); 21 | spdlog::error(fmt, std::forward(args)...); 22 | } 23 | 24 | template 25 | static void Error(const T& msg) 26 | { 27 | Ins().log_ptr_->error(msg); 28 | spdlog::error(msg); 29 | } 30 | 31 | template 32 | static void Info(spdlog::format_string_t fmt, Args&&... args) 33 | { 34 | Ins().log_ptr_->info(fmt, std::forward(args)...); 35 | spdlog::info(fmt, std::forward(args)...); 36 | } 37 | 38 | static void Bytes(const std::string_view& sv, 39 | const std::string& comment = "") 40 | { 41 | Ins().log_ptr_->debug("{} size:{} {}", comment, sv.size(), 42 | spdlog::to_hex(sv.begin(), sv.end())); 43 | } 44 | 45 | static void Bytes(const char* bytes, size_t size, 46 | const std::string& comment = "") 47 | { 48 | Bytes(std::string_view(bytes, size), comment); 49 | } 50 | 51 | static void SocketEvent(const std::string& event, SocketPtr socket_ptr) 52 | { 53 | Ins().log_ptr_->info("{} {}:{}", event, 54 | socket_ptr->remote_endpoint().address().to_string(), 55 | socket_ptr->remote_endpoint().port()); 56 | } 57 | 58 | static void SocketErrorEvent(const std::string& event, SocketPtr socket_ptr, 59 | const std::error_code& ec) 60 | { 61 | Ins().log_ptr_->error("{} {}:{} {}", event, 62 | socket_ptr->remote_endpoint().address().to_string(), 63 | socket_ptr->remote_endpoint().port(), ec.message()); 64 | } 65 | static void SetLogName(const std::string& log_name); 66 | 67 | protected: 68 | using LogPtr = std::shared_ptr; 69 | Log(const std::string& log_name); 70 | static Log& Ins(const std::string& log_name = "cppnat"); 71 | void LogLevel(spdlog::level::level_enum level); 72 | LogPtr log_ptr_; 73 | }; 74 | 75 | void HandleErrorCode(const std::error_code&); 76 | 77 | NAMESPACE_CPPNAT_END 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/message.cpp: -------------------------------------------------------------------------------- 1 | #include "message.h" 2 | 3 | NAMESPACE_CPPNAT_START 4 | 5 | MessageWriter::MessageWriter(SocketPtr socket_ptr, ErrorHandler error_handler) 6 | : socket_ptr_(socket_ptr), error_handler_(error_handler) {} 7 | MessageWriter::~MessageWriter() {} 8 | 9 | MessageHandler::MessageHandler() : callback_map_() {} 10 | MessageHandler::~MessageHandler() {} 11 | 12 | void MessageHandler::Handle(Protocol::Cmd cmd, const char* data, 13 | SocketPtr socket_ptr) 14 | { 15 | MessageWriter writer(socket_ptr); 16 | callback_map_[cmd](data, writer); 17 | } 18 | 19 | void MessageHandler::Handle(Protocol::Cmd cmd, const char* data, 20 | SocketPtr socket_ptr) const 21 | { 22 | const_cast(*this).Handle(cmd, data, socket_ptr); 23 | } 24 | 25 | NAMESPACE_CPPNAT_END -------------------------------------------------------------------------------- /src/message.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_MESSAGE_H__ 2 | #define __CPPNAT_MESSAGE_H__ 3 | 4 | #include "log.h" 5 | #include "prefix.h" 6 | 7 | NAMESPACE_CPPNAT_START 8 | 9 | template 10 | struct IsMessageType 11 | { 12 | static constexpr bool value = false; 13 | }; 14 | 15 | template 16 | struct IsMessageType().packet_size, 17 | std::declval().cmd, void())> 18 | { 19 | static constexpr bool value = true; 20 | }; 21 | 22 | template 23 | struct MessageSize 24 | { 25 | static size_t Get(const T& t) { return sizeof(T); } 26 | }; 27 | 28 | template 29 | struct MessageSize().Size(), void())> 30 | { 31 | static size_t Get(const T& t) { return const_cast(t).Size() + Protocol::header_size; } 32 | }; 33 | 34 | using Message = Protocol::Header; 35 | 36 | class MessageWriter 37 | { 38 | public: 39 | MessageWriter(SocketPtr socket_ptr = nullptr, 40 | ErrorHandler error_handler = nullptr); 41 | ~MessageWriter(); 42 | 43 | template 44 | std::error_code Write(CmdType cmd, T& t) 45 | { 46 | static_assert(IsMessageType::value, 47 | "message must inherit cppnat::Message"); 48 | t.cmd = cmd; 49 | t.packet_size = MessageSize::Get(t); 50 | std::error_code ec; 51 | const char* data = reinterpret_cast(&t); 52 | Log::Bytes(data, t.packet_size, "socket write event"); 53 | asio::write(*socket_ptr_, asio::buffer(data, t.packet_size), ec); 54 | return ec; 55 | } 56 | 57 | protected: 58 | SocketPtr socket_ptr_; 59 | ErrorHandler error_handler_; 60 | }; 61 | 62 | class MessageHandler 63 | { 64 | public: 65 | MessageHandler(); 66 | ~MessageHandler(); 67 | 68 | template 69 | void Bind(const std::function& callback) 70 | { 71 | callback_map_[cmd] = [callback](const char* raw_data, 72 | MessageWriter& writer) -> void 73 | { 74 | callback(*reinterpret_cast(raw_data), writer); 75 | }; 76 | } 77 | 78 | void Handle(Protocol::Cmd, const char*, SocketPtr socket_ptr); 79 | 80 | void Handle(Protocol::Cmd, const char*, SocketPtr socket_ptr) const; 81 | 82 | protected: 83 | std::map> 84 | callback_map_; 85 | }; 86 | 87 | NAMESPACE_CPPNAT_END 88 | 89 | #endif -------------------------------------------------------------------------------- /src/prefix.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_PREFIX_H__ 2 | #define __CPPNAT_PREFIX_H__ 3 | 4 | #define NAMESPACE_CPPNAT_START \ 5 | namespace cppnat \ 6 | { 7 | #define NAMESPACE_CPPNAT_END } 8 | 9 | // C++ Standard library 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // C++ third party library 19 | #include 20 | 21 | NAMESPACE_CPPNAT_START 22 | 23 | class Protocol 24 | { 25 | public: 26 | using Size = std::uint16_t; 27 | using Cmd = std::uint16_t; 28 | 29 | static constexpr Size header_size = sizeof(Size) + sizeof(Cmd); 30 | static constexpr Size max_size = ~0; 31 | static constexpr Size data_size = max_size - header_size; 32 | 33 | struct Header 34 | { 35 | Size packet_size; 36 | Cmd cmd; 37 | }; 38 | 39 | static Header GetHeader(const char* buffer) 40 | { 41 | return *reinterpret_cast(buffer); 42 | } 43 | }; 44 | 45 | template 46 | class BufferSize 47 | { 48 | public: 49 | BufferSize() {} 50 | ~BufferSize() {} 51 | static constexpr size_t size = buffer_size; 52 | 53 | char* Get() { return buffer_; } 54 | size_t Size() { return buffer_size; } 55 | 56 | protected: 57 | char buffer_[buffer_size]; 58 | }; 59 | 60 | class DynamicBuffer 61 | { 62 | public: 63 | DynamicBuffer(size_t buffer_size) : buffer_size_(buffer_size), p(nullptr) 64 | { 65 | p = new char[buffer_size]; 66 | } 67 | 68 | void Write(char* src, size_t size) { memcpy(p, src, size); } 69 | ~DynamicBuffer() { delete[] p; } 70 | size_t Size() { return buffer_size_; } 71 | char* Get() { return p; } 72 | 73 | protected: 74 | size_t buffer_size_; 75 | char* p; 76 | }; 77 | 78 | using DynamicBufferPtr = std::shared_ptr; 79 | 80 | using SocketPtr = std::shared_ptr; 81 | inline SocketPtr CreateSocket(asio::io_service& ios) 82 | { 83 | return std::make_shared(ios); 84 | } 85 | 86 | using TimerPtr = std::shared_ptr; 87 | inline TimerPtr CreateTimer(asio::io_service& ios) 88 | { 89 | return std::make_shared(ios); 90 | } 91 | 92 | using ErrorHandler = std::function; 93 | 94 | NAMESPACE_CPPNAT_END 95 | 96 | #endif -------------------------------------------------------------------------------- /src/proxy_socket.cpp: -------------------------------------------------------------------------------- 1 | #include "proxy_socket.h" 2 | 3 | NAMESPACE_CPPNAT_START 4 | 5 | ProxySocket::ProxySocket(std::uint16_t id, SocketPtr socket_ptr) 6 | : proxy_data_(), 7 | socket_ptr_(socket_ptr), 8 | on_recv_(), 9 | on_read_error_(), 10 | on_write_error_() 11 | { 12 | proxy_data_.id = id; 13 | proxy_data_.data_size = 0; 14 | } 15 | 16 | ProxySocket::~ProxySocket() {} 17 | 18 | void ProxySocket::ReadOnce() 19 | { 20 | auto self(shared_from_this()); 21 | socket_ptr_->async_read_some( 22 | asio::buffer(proxy_data_.data, proxy_data_.writable_size), 23 | [this, self](const std::error_code& ec, size_t length) -> void 24 | { 25 | if (ec) 26 | { 27 | on_read_error_(proxy_data_.id, ec); 28 | return; 29 | } 30 | Log::Bytes(proxy_data_.data, length, "recv from proxy data"); 31 | proxy_data_.data_size = length; 32 | on_recv_(proxy_data_); 33 | }); 34 | } 35 | 36 | void ProxySocket::SetOnRecv(const OnRecvCallback& callback) 37 | { 38 | on_recv_ = callback; 39 | } 40 | 41 | void ProxySocket::SetOnReadError(const OnReadErrorCallback& callback) 42 | { 43 | on_read_error_ = callback; 44 | } 45 | 46 | void ProxySocket::SetOnWriteError(const OnWriteErrorCallback& callback) 47 | { 48 | on_write_error_ = callback; 49 | } 50 | 51 | void ProxySocket::Write(DynamicBufferPtr buffer_ptr) 52 | { 53 | auto self(shared_from_this()); 54 | Log::Bytes(buffer_ptr->Get(), buffer_ptr->Size(), "write to proxy socket"); 55 | asio::async_write( 56 | *socket_ptr_, asio::buffer(buffer_ptr->Get(), buffer_ptr->Size()), 57 | [buffer_ptr, self, this](const std::error_code& ec, size_t) -> void 58 | { 59 | if (ec) 60 | { 61 | on_write_error_(proxy_data_.id, ec); 62 | return; 63 | } 64 | }); 65 | } 66 | 67 | void ProxySocket::Close() 68 | { 69 | if (socket_ptr_->is_open()) socket_ptr_->close(); 70 | } 71 | 72 | NAMESPACE_CPPNAT_END -------------------------------------------------------------------------------- /src/proxy_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPP_NAT_PROXY_SOCKET_H__ 2 | #define __CPP_NAT_PROXY_SOCKET_H__ 3 | 4 | #include "cs_message.h" 5 | #include "prefix.h" 6 | 7 | NAMESPACE_CPPNAT_START 8 | 9 | class ProxySocket : public std::enable_shared_from_this 10 | { 11 | public: 12 | using OnRecvCallback = std::function; 13 | using OnReadErrorCallback = 14 | std::function; 15 | using OnWriteErrorCallback = 16 | std::function; 17 | 18 | ProxySocket(std::uint16_t id, SocketPtr socket_ptr); 19 | ~ProxySocket(); 20 | 21 | void ReadOnce(); 22 | void Write(DynamicBufferPtr); 23 | void Close(); 24 | 25 | void SetOnRecv(const OnRecvCallback&); 26 | void SetOnReadError(const OnReadErrorCallback&); 27 | void SetOnWriteError(const OnWriteErrorCallback&); 28 | 29 | protected: 30 | SocketPtr socket_ptr_; 31 | ProxyData proxy_data_; 32 | OnRecvCallback on_recv_; 33 | OnReadErrorCallback on_read_error_; 34 | OnWriteErrorCallback on_write_error_; 35 | }; 36 | 37 | using ProxySocketPtr = std::shared_ptr; 38 | 39 | NAMESPACE_CPPNAT_END 40 | 41 | #endif -------------------------------------------------------------------------------- /src/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.h" 2 | 3 | #include "cs_message.h" 4 | #include "handshake.h" 5 | #include "log.h" 6 | 7 | NAMESPACE_CPPNAT_START 8 | 9 | Server::Server(const std::string& listen_addr, std::uint16_t port) 10 | : ios_(), 11 | acceptor_(ios_, asio::ip::tcp::endpoint( 12 | asio::ip::address::from_string(listen_addr), port)), 13 | is_client_connected_(false), 14 | message_handler_(), 15 | message_writer_(), 16 | socket_id_(0), 17 | proxy_socket_map_(), 18 | client_socket_ptr_() {} 19 | 20 | Server::~Server() { Stop(); } 21 | 22 | void Server::InitMessageHandler() 23 | { 24 | Log::Info("initialize message handler"); 25 | message_handler_ 26 | .Bind( 27 | [&](const ServerMessage::NewProxyResult& msg, 28 | MessageWriter& writer) -> void 29 | { 30 | if (msg.code == StatusCode::kSuccess) 31 | BeginProxy(msg.id); 32 | else 33 | proxy_socket_map_.erase(msg.id); 34 | }); 35 | message_handler_ 36 | .Bind( 37 | [&](const ServerMessage::DataTransfer& msg, MessageWriter& writer) 38 | { 39 | auto buffer_ptr = msg.Copy(); 40 | proxy_socket_map_[msg.id]->Write(msg.Copy()); 41 | }); 42 | 43 | message_handler_.Bind( 45 | [&](const ServerMessage::ClientProxySocketClosed& msg, 46 | MessageWriter& writer) 47 | { 48 | proxy_socket_map_[msg.id]->Close(); 49 | proxy_socket_map_.erase(msg.id); 50 | }); 51 | } 52 | 53 | void Server::Stop() 54 | { 55 | client_socket_ptr_->close(); 56 | ios_.stop(); 57 | int count = 0; 58 | for (;;) 59 | { 60 | count++; 61 | if (count == 60) return; 62 | if (ios_.stopped()) return; 63 | std::this_thread::sleep_for(std::chrono::seconds(1)); 64 | } 65 | } 66 | 67 | void Server::Start() 68 | { 69 | InitMessageHandler(); 70 | AcceptSocket(); 71 | Log::Info("wait for cppnat client"); 72 | ios_.run(); 73 | } 74 | 75 | void Server::AcceptSocket() 76 | { 77 | SocketPtr socket_ptr = CreateSocket(ios_); 78 | acceptor_.async_accept( 79 | *socket_ptr, [socket_ptr, this](const asio::error_code& ec) -> void 80 | { 81 | if (ec) 82 | { 83 | HandleErrorCode(ec); 84 | return; 85 | } 86 | if (is_client_connected_) 87 | Proxy(socket_ptr); 88 | else 89 | Handshake(socket_ptr); 90 | AcceptSocket(); 91 | }); 92 | } 93 | 94 | void Server::Proxy(SocketPtr socket_ptr) 95 | { 96 | Log::Info("ready to proxy {}:{}", 97 | socket_ptr->remote_endpoint().address().to_string(), 98 | socket_ptr->remote_endpoint().port()); 99 | ClientMessage::NewProxy msg; 100 | msg.id = socket_id_++; 101 | Log::Info("new proxy id:{}", msg.id); 102 | proxy_socket_map_.emplace(msg.id, 103 | std::make_shared(msg.id, socket_ptr)); 104 | message_writer_.Write(ClientMessage::kNewProxy, msg); 105 | } 106 | 107 | void Server::OnClientWriteError(const std::error_code& ec) 108 | { 109 | Log::Error("write to client error {} {}", ec.value(), ec.message()); 110 | } 111 | 112 | void Server::BeginProxy(std::uint16_t id) 113 | { 114 | Log::Info("id:{},begin proxy", id); 115 | auto proxy_socket_ptr = proxy_socket_map_[id]; 116 | proxy_socket_ptr->SetOnRecv([proxy_socket_ptr, 117 | this](ProxyData& proxy_data) -> void 118 | { 119 | auto ec = message_writer_.Write(ClientMessage::kDataTransfer, proxy_data); 120 | if (ec) 121 | { 122 | OnClientWriteError(ec); 123 | return; 124 | } 125 | proxy_socket_ptr->ReadOnce(); 126 | }); 127 | proxy_socket_ptr->SetOnReadError( 128 | [proxy_socket_ptr, this](std::uint16_t id, 129 | const std::error_code& ec) -> void 130 | { 131 | HandleErrorCode(ec); 132 | if (is_client_connected_) 133 | { 134 | ClientMessage::ServerProxySocketClosed msg{id}; 135 | auto err = message_writer_.Write(ClientMessage::kDataTransfer, msg); 136 | if (err) OnClientWriteError(ec); 137 | } 138 | }); 139 | proxy_socket_ptr->SetOnWriteError( 140 | [proxy_socket_ptr, this](std::uint16_t id, 141 | const std::error_code& ec) -> void 142 | { 143 | HandleErrorCode(ec); 144 | }); 145 | proxy_socket_ptr->ReadOnce(); 146 | } 147 | 148 | void Server::Handshake(SocketPtr socket_ptr) 149 | { 150 | Log::Info("handshaking with {}:{}", 151 | socket_ptr->remote_endpoint().address().to_string(), 152 | socket_ptr->remote_endpoint().port()); 153 | TimerPtr timer_ptr = CreateTimer(ios_); 154 | timer_ptr->expires_from_now(std::chrono::seconds(10)); 155 | timer_ptr->async_wait( 156 | [socket_ptr, timer_ptr, this](const std::error_code& ec) -> void 157 | { 158 | if (!ec) socket_ptr->close(); 159 | }); 160 | std::shared_ptr> buffer_ptr = 161 | std::make_shared>(); 162 | asio::async_read( 163 | *socket_ptr, 164 | asio::buffer(buffer_ptr->Get(), Handshake::server_message.size()), 165 | [buffer_ptr, timer_ptr, socket_ptr, this](const std::error_code& ec, 166 | size_t length) -> void 167 | { 168 | if (is_client_connected_) 169 | { 170 | timer_ptr->cancel(); 171 | socket_ptr->close(); 172 | return; 173 | } 174 | if (ec) 175 | { 176 | HandleErrorCode(ec); 177 | timer_ptr->cancel(); 178 | return; 179 | } 180 | std::string_view recv_data(buffer_ptr->Get(), length); 181 | if (recv_data != Handshake::server_message) 182 | { 183 | timer_ptr->cancel(); 184 | socket_ptr->close(); 185 | return; 186 | } 187 | asio::async_write(*socket_ptr, 188 | asio::buffer(Handshake::client_message.data(), 189 | Handshake::client_message.size()), 190 | [buffer_ptr, timer_ptr, socket_ptr, this]( 191 | const std::error_code& ec, size_t) -> void 192 | { 193 | if (is_client_connected_) 194 | { 195 | timer_ptr->cancel(); 196 | socket_ptr->close(); 197 | return; 198 | } 199 | if (ec) 200 | { 201 | HandleErrorCode(ec); 202 | timer_ptr->cancel(); 203 | socket_ptr->close(); 204 | return; 205 | } 206 | AcceptClient(socket_ptr); 207 | is_client_connected_ = true; 208 | timer_ptr->cancel(); 209 | }); 210 | }); 211 | } 212 | 213 | void Server::OnClientReadError(const std::error_code& ec) 214 | { 215 | Log::Error("disconnect from client {} {}", ec.value(), ec.message()); 216 | is_client_connected_ = false; 217 | for (auto& v : proxy_socket_map_) 218 | { 219 | v.second->Close(); 220 | } 221 | proxy_socket_map_.clear(); 222 | } 223 | 224 | void Server::AcceptClient(SocketPtr socket_ptr) 225 | { 226 | Log::Info("accept client from {}:{}", 227 | socket_ptr->remote_endpoint().address().to_string(), 228 | socket_ptr->remote_endpoint().port()); 229 | auto session = std::make_shared( 230 | socket_ptr, message_handler_, 231 | [this](const std::error_code& ec) -> void 232 | { 233 | OnClientReadError(ec); 234 | }); 235 | message_writer_ = session->GetMessageWriter(); 236 | session->Start(); 237 | } 238 | 239 | NAMESPACE_CPPNAT_END -------------------------------------------------------------------------------- /src/server.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_SERVER_H__ 2 | #define __CPPNAT_SERVER_H__ 3 | 4 | #include "prefix.h" 5 | #include "proxy_socket.h" 6 | #include "session.h" 7 | 8 | NAMESPACE_CPPNAT_START 9 | 10 | class Server 11 | { 12 | public: 13 | Server(const std::string& listen_addr, std::uint16_t port); 14 | ~Server(); 15 | 16 | void Start(); 17 | void Stop(); 18 | 19 | protected: 20 | asio::io_service ios_; 21 | asio::ip::tcp::acceptor acceptor_; 22 | bool is_client_connected_; 23 | MessageHandler message_handler_; 24 | MessageWriter message_writer_; 25 | std::uint16_t socket_id_; 26 | std::map proxy_socket_map_; 27 | SocketPtr client_socket_ptr_; 28 | 29 | void Handshake(SocketPtr socket_ptr); 30 | void Proxy(SocketPtr socket_ptr); 31 | void AcceptClient(SocketPtr socket_ptr); 32 | void OnClientReadError(const std::error_code& ec); 33 | void OnClientWriteError(const std::error_code& ec); 34 | void InitMessageHandler(); 35 | void BeginProxy(std::uint16_t id); 36 | void AcceptSocket(); 37 | }; 38 | 39 | NAMESPACE_CPPNAT_END 40 | 41 | #endif -------------------------------------------------------------------------------- /src/server/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "../log.h" 7 | #include "../server.h" 8 | 9 | int main(int argc, char** argv) 10 | { 11 | cppnat::Log::SetLogName("nat_server"); 12 | cppnat::Log::Info("cppnat server start"); 13 | std::ifstream ifs("server.json"); 14 | if (!ifs.is_open()) 15 | { 16 | cppnat::Log::Error("open server.json failed"); 17 | return 1; 18 | } 19 | std::string config_file_content((std::istreambuf_iterator(ifs)), 20 | (std::istreambuf_iterator())); 21 | jsr::Json json; 22 | auto err = json.Parse(config_file_content); 23 | if (err) 24 | { 25 | cppnat::Log::Error(err.Message()); 26 | return 1; 27 | } 28 | std::string ip; 29 | std::uint16_t port; 30 | err = json.Unmarshal({ 31 | {"bind_ip", ip}, 32 | {"bind_port", port}, 33 | }); 34 | if (err) 35 | { 36 | cppnat::Log::Error(err.Message()); 37 | return 1; 38 | } 39 | cppnat::Log::Info("server listening on {}:{}", ip, port); 40 | cppnat::Server server(ip, port); 41 | server.Start(); 42 | } -------------------------------------------------------------------------------- /src/session.cpp: -------------------------------------------------------------------------------- 1 | #include "session.h" 2 | 3 | NAMESPACE_CPPNAT_START 4 | 5 | Session::Session(SocketPtr socket_ptr, const MessageHandler& message_handler, 6 | const ErrorHandler& error_handler) 7 | : socket_ptr_(socket_ptr), 8 | read_size_(0), 9 | extra_offset_(0), 10 | buffer_(), 11 | message_handler_(message_handler), 12 | error_handler_(error_handler) {} 13 | 14 | Session::~Session() {} 15 | 16 | MessageWriter Session::GetMessageWriter() { return MessageWriter(socket_ptr_); } 17 | 18 | void Session::Start() { MessageLoop(); } 19 | 20 | void Session::MessageLoop() 21 | { 22 | auto self(shared_from_this()); 23 | socket_ptr_->async_read_some( 24 | asio::buffer(buffer_ + read_size_, buffer_size_ - read_size_), 25 | [self, this](const std::error_code& ec, std::size_t length) -> void 26 | { 27 | if (ec) 28 | { 29 | error_handler_(ec); 30 | return; 31 | } 32 | Log::Bytes(buffer_ + read_size_, length, "socket read event"); 33 | read_size_ += length; 34 | ParseBuffer(); 35 | MessageLoop(); 36 | }); 37 | } 38 | 39 | void Session::ParseBuffer() 40 | { 41 | if (read_size_ < Protocol::header_size) return; 42 | auto header = Protocol::GetHeader(buffer_); 43 | if (read_size_ < header.packet_size) return; 44 | if (read_size_ == header.packet_size) 45 | read_size_ = 0; 46 | else 47 | extra_offset_ = header.packet_size; 48 | HandleBuffer(header, buffer_); 49 | if (extra_offset_ > 0) ParseExtraBuffer(); 50 | } 51 | 52 | void Session::ParseExtraBuffer() 53 | { 54 | size_t remain_size = 0; 55 | do 56 | { 57 | remain_size = read_size_ - extra_offset_; 58 | if (remain_size < Protocol::header_size) 59 | { 60 | memcpy(buffer_, buffer_ + extra_offset_, remain_size); 61 | read_size_ = remain_size; 62 | extra_offset_ = 0; 63 | return; 64 | } 65 | auto header = Protocol::GetHeader(buffer_ + extra_offset_); 66 | if (remain_size < header.packet_size) 67 | { 68 | memcpy(buffer_, buffer_ + extra_offset_, remain_size); 69 | read_size_ = remain_size; 70 | extra_offset_ = 0; 71 | return; 72 | } 73 | HandleBuffer(header, buffer_ + extra_offset_); 74 | if (remain_size == header.packet_size) 75 | { 76 | read_size_ = 0; 77 | extra_offset_ = 0; 78 | } 79 | else 80 | extra_offset_ += header.packet_size; 81 | } while (extra_offset_ > 0); 82 | } 83 | 84 | void Session::HandleBuffer(const Protocol::Header& header, const char* body) 85 | { 86 | Log::Bytes(body, header.packet_size, "handle buffer"); 87 | message_handler_.Handle(header.cmd, body, socket_ptr_); 88 | } 89 | 90 | NAMESPACE_CPPNAT_END -------------------------------------------------------------------------------- /src/session.h: -------------------------------------------------------------------------------- 1 | #ifndef __CPPNAT_SESSION_H__ 2 | #define __CPPNAT_SESSION_H__ 3 | 4 | #include "message.h" 5 | 6 | NAMESPACE_CPPNAT_START 7 | 8 | class Session : public std::enable_shared_from_this 9 | { 10 | public: 11 | Session(SocketPtr, const MessageHandler&, const ErrorHandler& error_handler); 12 | ~Session(); 13 | 14 | MessageWriter GetMessageWriter(); 15 | 16 | void Start(); 17 | 18 | protected: 19 | static constexpr size_t buffer_size_ = Protocol::max_size; 20 | 21 | SocketPtr socket_ptr_; 22 | size_t read_size_; 23 | size_t extra_offset_; 24 | char buffer_[buffer_size_]; 25 | const MessageHandler& message_handler_; 26 | ErrorHandler error_handler_; 27 | 28 | void MessageLoop(); 29 | void ParseBuffer(); 30 | void HandleBuffer(const Protocol::Header&, const char*); 31 | void ParseExtraBuffer(); 32 | }; 33 | 34 | NAMESPACE_CPPNAT_END 35 | 36 | #endif --------------------------------------------------------------------------------