├── .clang-format ├── .github ├── badges │ └── telegram.svg └── workflows │ ├── build.yaml │ └── code-format.yaml ├── .gitignore ├── .gitmodules ├── Android.mk ├── Application.mk ├── License ├── Makefile ├── README.md ├── build.mk └── src ├── hev-conf.c ├── hev-conf.h ├── hev-exec.c ├── hev-exec.h ├── hev-main.c ├── hev-main.h ├── hev-misc.c ├── hev-misc.h ├── hev-sock.c ├── hev-sock.h ├── hev-stun.c ├── hev-stun.h ├── hev-tfwd.c ├── hev-tfwd.h ├── hev-tnsk.c ├── hev-tnsk.h ├── hev-ufwd.c ├── hev-ufwd.h ├── hev-unsk.c ├── hev-unsk.h ├── hev-winc.c ├── hev-winc.h ├── hev-xnsk.c └── hev-xnsk.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Left 8 | AlignOperands: true 9 | AlignTrailingComments: false 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: None 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterDefinitionReturnType: TopLevel 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: MultiLine 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BraceWrapping: 23 | AfterClass: true 24 | AfterControlStatement: false 25 | AfterEnum: true 26 | AfterFunction: true 27 | AfterNamespace: true 28 | AfterObjCDeclaration: false 29 | AfterStruct: true 30 | AfterUnion: true 31 | AfterExternBlock: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | SplitEmptyFunction: true 36 | SplitEmptyRecord: true 37 | SplitEmptyNamespace: true 38 | BreakBeforeBinaryOperators: None 39 | BreakBeforeBraces: Custom 40 | BreakBeforeInheritanceComma: false 41 | BreakInheritanceList: BeforeColon 42 | BreakBeforeTernaryOperators: false 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakAfterJavaFieldAnnotations: false 45 | BreakStringLiterals: false 46 | ColumnLimit: 80 47 | CommentPragmas: '^ IWYU pragma:' 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 50 | ConstructorInitializerIndentWidth: 4 51 | ContinuationIndentWidth: 4 52 | Cpp11BracedListStyle: false 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | ExperimentalAutoDetectBinPacking: false 56 | FixNamespaceComments: false 57 | ForEachMacros: 58 | - foreach 59 | - Q_FOREACH 60 | - BOOST_FOREACH 61 | IncludeBlocks: Preserve 62 | IncludeCategories: 63 | - Regex: '.*' 64 | Priority: 1 65 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 66 | Priority: 3 67 | - Regex: '.*' 68 | Priority: 1 69 | IncludeIsMainRegex: '(Test)?$' 70 | IndentCaseLabels: false 71 | IndentPPDirectives: None 72 | IndentWidth: 4 73 | IndentWrappedFunctionNames: false 74 | JavaScriptQuotes: Leave 75 | JavaScriptWrapImports: true 76 | KeepEmptyLinesAtTheStartOfBlocks: false 77 | MacroBlockBegin: '' 78 | MacroBlockEnd: '' 79 | MaxEmptyLinesToKeep: 1 80 | NamespaceIndentation: Inner 81 | ObjCBinPackProtocolList: Auto 82 | ObjCBlockIndentWidth: 4 83 | ObjCSpaceAfterProperty: true 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 10 86 | PenaltyBreakBeforeFirstCallParameter: 30 87 | PenaltyBreakComment: 10 88 | PenaltyBreakFirstLessLess: 0 89 | PenaltyBreakString: 10 90 | PenaltyBreakTemplateDeclaration: 10 91 | PenaltyExcessCharacter: 100 92 | PenaltyReturnTypeOnItsOwnLine: 60 93 | PointerAlignment: Right 94 | ReflowComments: false 95 | SortIncludes: false 96 | SortUsingDeclarations: false 97 | SpaceAfterCStyleCast: false 98 | SpaceAfterTemplateKeyword: true 99 | SpaceBeforeAssignmentOperators: true 100 | SpaceBeforeCpp11BracedList: false 101 | SpaceBeforeCtorInitializerColon: true 102 | SpaceBeforeInheritanceColon: true 103 | SpaceBeforeParens: Always 104 | SpaceBeforeRangeBasedForLoopColon: true 105 | SpaceInEmptyParentheses: false 106 | SpacesBeforeTrailingComments: 1 107 | SpacesInAngles: false 108 | SpacesInContainerLiterals: false 109 | SpacesInCStyleCastParentheses: false 110 | SpacesInParentheses: false 111 | SpacesInSquareBrackets: false 112 | Standard: Cpp03 113 | TabWidth: 4 114 | UseTab: Never 115 | ... 116 | -------------------------------------------------------------------------------- /.github/badges/telegram.svg: -------------------------------------------------------------------------------- 1 | chatchaton Telegramon Telegram 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: "Build" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | release: 9 | types: 10 | - published 11 | workflow_dispatch: 12 | 13 | jobs: 14 | source: 15 | name: Source 16 | runs-on: ubuntu-latest 17 | env: 18 | BRANCH_NAME: ${{ github.base_ref || github.ref_name }} 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 1 24 | submodules: true 25 | - name: Gen Source 26 | run: | 27 | REV_ID=$(git tag --points-at HEAD) 28 | if [ -z "${REV_ID}" ]; then 29 | REV_ID=$(git rev-parse --short HEAD) 30 | fi 31 | mkdir -p natmap-${{ env.BRANCH_NAME }} 32 | git ls-files --recurse-submodules | tar c -O -T- | tar x -C natmap-${{ env.BRANCH_NAME }} 33 | echo ${REV_ID} > natmap-${{ env.BRANCH_NAME }}/.version 34 | tar cJf natmap-${{ env.BRANCH_NAME }}.tar.xz natmap-${{ env.BRANCH_NAME }} 35 | - name: Upload source 36 | uses: actions/upload-artifact@v4 37 | with: 38 | name: natmap-${{ env.BRANCH_NAME }}.tar.xz 39 | path: natmap-${{ env.BRANCH_NAME }}.tar.xz 40 | if-no-files-found: error 41 | retention-days: 1 42 | 43 | linux: 44 | name: Linux 45 | runs-on: ubuntu-latest 46 | strategy: 47 | matrix: 48 | include: 49 | - name: arm64 50 | tool: aarch64-unknown-linux-musl 51 | - name: arm32 52 | tool: arm-unknown-linux-musleabi 53 | - name: arm32hf 54 | tool: arm-unknown-linux-musleabihf 55 | - name: arm32v7 56 | tool: armv7-unknown-linux-musleabi 57 | - name: arm32v7hf 58 | tool: armv7-unknown-linux-musleabihf 59 | - name: i586 60 | tool: i586-unknown-linux-musl 61 | - name: i686 62 | tool: i686-unknown-linux-musl 63 | - name: loong64 64 | tool: loongarch64-unknown-linux-musl 65 | - name: m68k 66 | tool: m68k-unknown-linux-musl 67 | - name: microblazeel 68 | tool: microblazeel-xilinx-linux-musl 69 | - name: microblaze 70 | tool: microblaze-xilinx-linux-musl 71 | - name: mips64el 72 | tool: mips64el-unknown-linux-musl 73 | - name: mips64 74 | tool: mips64-unknown-linux-musl 75 | - name: mips32el 76 | tool: mipsel-unknown-linux-musl 77 | - name: mips32elsf 78 | tool: mipsel-unknown-linux-muslsf 79 | - name: mips32 80 | tool: mips-unknown-linux-musl 81 | - name: mips32sf 82 | tool: mips-unknown-linux-muslsf 83 | - name: powerpc64 84 | tool: powerpc64-unknown-linux-musl 85 | - name: powerpc 86 | tool: powerpc-unknown-linux-musl 87 | - name: riscv32 88 | tool: riscv32-unknown-linux-musl 89 | - name: riscv64 90 | tool: riscv64-unknown-linux-musl 91 | - name: s390x 92 | tool: s390x-ibm-linux-musl 93 | - name: sh 94 | tool: sh-multilib-linux-musl 95 | - name: sheb 96 | tool: sh-multilib-linux-musl 97 | env: 98 | CFLAGS: "-mb" 99 | - name: x86_64 100 | tool: x86_64-unknown-linux-musl 101 | steps: 102 | - name: Checkout 103 | uses: actions/checkout@v4 104 | with: 105 | fetch-depth: 1 106 | submodules: true 107 | - name: Build ${{ matrix.name }} 108 | run: | 109 | sudo mkdir -p /opt/x-tools 110 | wget https://github.com/cross-tools/musl-cross/releases/download/20250501/${{ matrix.tool }}.tar.xz 111 | sudo tar xf ${{ matrix.tool }}.tar.xz -C /opt/x-tools 112 | make CROSS_PREFIX=/opt/x-tools/${{ matrix.tool }}/bin/${{ matrix.tool }}- CFLAGS=${{ matrix.env.CFLAGS }} ENABLE_STATIC=1 -j`nproc` 113 | cp bin/natmap natmap-linux-${{ matrix.name }} 114 | - name: Upload ${{ matrix.name }} 115 | uses: actions/upload-artifact@v4 116 | with: 117 | name: natmap-linux-${{ matrix.name }} 118 | path: natmap-linux-${{ matrix.name }} 119 | if-no-files-found: error 120 | retention-days: 1 121 | 122 | windows: 123 | name: Windows 124 | runs-on: windows-latest 125 | defaults: 126 | run: 127 | shell: cmd 128 | steps: 129 | - name: Checkout 130 | uses: actions/checkout@v4 131 | with: 132 | fetch-depth: 1 133 | submodules: true 134 | - name: Setup MSYS2 135 | uses: msys2/setup-msys2@v2 136 | with: 137 | msystem: MSYS 138 | location: D:\msys2 139 | update: true 140 | install: >- 141 | gcc 142 | git 143 | make 144 | wget 145 | zip 146 | - name: Build 147 | shell: msys2 {0} 148 | run: | 149 | export MSYS=winsymlinks:native 150 | git clone --depth=1 --recursive file://`pwd` work; cd work 151 | make LFLAGS="-lmsys-2.0 -lws2_32" -j`nproc` 152 | mkdir natmap 153 | cp bin/natmap* natmap 154 | wget -P natmap https://github.com/heiher/msys2/releases/latest/download/msys-2.0.dll 155 | zip -r ../natmap-win64.zip natmap 156 | - name: Upload ${{ matrix.name }} 157 | uses: actions/upload-artifact@v4 158 | with: 159 | name: natmap-win64.zip 160 | path: natmap-win64.zip 161 | if-no-files-found: error 162 | retention-days: 1 163 | 164 | macos: 165 | name: macOS 166 | runs-on: macos-latest 167 | strategy: 168 | matrix: 169 | include: 170 | - name: arm64 171 | flags: -arch arm64 -mmacosx-version-min=11.0 172 | - name: x86_64 173 | flags: -arch x86_64 -mmacosx-version-min=10.6 174 | steps: 175 | - name: Checkout 176 | uses: actions/checkout@v4 177 | with: 178 | fetch-depth: 1 179 | submodules: true 180 | - name: Build ${{ matrix.name }} 181 | run: | 182 | make CC=clang CFLAGS="${{ matrix.flags }}" LFLAGS="${{ matrix.flags }}" -j $(sysctl -n hw.logicalcpu) 183 | cp bin/natmap natmap-darwin-${{ matrix.name }} 184 | - name: Upload ${{ matrix.name }} 185 | uses: actions/upload-artifact@v4 186 | with: 187 | name: natmap-darwin-${{ matrix.name }} 188 | path: natmap-darwin-${{ matrix.name }} 189 | if-no-files-found: error 190 | retention-days: 1 191 | 192 | freebsd: 193 | name: FreeBSD 194 | runs-on: ubuntu-latest 195 | steps: 196 | - name: Checkout 197 | uses: actions/checkout@v4 198 | with: 199 | fetch-depth: 1 200 | submodules: true 201 | - name: Build 202 | uses: vmactions/freebsd-vm@v1 203 | with: 204 | usesh: true 205 | prepare: | 206 | pkg install -y gmake gcc 207 | run: | 208 | gmake 209 | cp bin/natmap natmap-freebsd-x86_64 210 | - name: Upload 211 | uses: actions/upload-artifact@v4 212 | with: 213 | name: natmap-freebsd-x86_64 214 | path: natmap-freebsd-x86_64 215 | if-no-files-found: error 216 | retention-days: 1 217 | 218 | release: 219 | name: Release 220 | runs-on: ubuntu-latest 221 | needs: 222 | - source 223 | - linux 224 | - windows 225 | - macos 226 | - freebsd 227 | if: github.event_name == 'release' 228 | steps: 229 | - name: Checkout 230 | uses: actions/checkout@v4 231 | with: 232 | fetch-depth: 1 233 | - name: Download artifacts 234 | uses: actions/download-artifact@v4 235 | with: 236 | path: release 237 | pattern: "natmap-*" 238 | merge-multiple: true 239 | - name: Upload artifacts 240 | env: 241 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 242 | run: | 243 | for i in release/natmap-*; do 244 | gh release upload ${{ github.event.release.tag_name }} $i 245 | done 246 | 247 | android: 248 | name: Android 249 | runs-on: ubuntu-latest 250 | if: github.event_name != 'release' 251 | steps: 252 | - name: Checkout 253 | uses: actions/checkout@v4 254 | with: 255 | fetch-depth: 1 256 | submodules: true 257 | - name: Prepare 258 | run: | 259 | wget https://dl.google.com/android/repository/android-ndk-r27b-linux.zip 260 | unzip android-ndk-r27b-linux.zip 261 | ln -sf . jni 262 | - name: Build 263 | run: | 264 | ./android-ndk-r27b/ndk-build 265 | 266 | llvm: 267 | name: LLVM 268 | runs-on: ubuntu-latest 269 | if: github.event_name != 'release' 270 | steps: 271 | - name: Checkout 272 | uses: actions/checkout@v4 273 | with: 274 | fetch-depth: 1 275 | submodules: true 276 | - name: Prepare 277 | run: | 278 | sudo apt install -y clang 279 | - name: Build 280 | run: | 281 | make CC=clang ENABLE_STATIC=1 -j`nproc` 282 | -------------------------------------------------------------------------------- /.github/workflows/code-format.yaml: -------------------------------------------------------------------------------- 1 | name: "Check code formatting" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | checker: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 1 17 | - name: Prepare 18 | run: | 19 | sudo apt install -y clang-format 20 | - name: Format 21 | run: | 22 | find src -iname "*.[h,c]" -exec clang-format -i {} \; 23 | - name: Check 24 | run: | 25 | git status 26 | git diff --quiet 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | build 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third-part/hev-task-system"] 2 | path = third-part/hev-task-system 3 | url = https://github.com/heiher/hev-task-system 4 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | TOP_PATH := $(call my-dir) 17 | 18 | ifeq ($(filter $(modules-get-list),hev-task-system),) 19 | include $(TOP_PATH)/third-part/hev-task-system/Android.mk 20 | endif 21 | 22 | LOCAL_PATH = $(TOP_PATH) 23 | SRCDIR := $(LOCAL_PATH)/src 24 | 25 | include $(CLEAR_VARS) 26 | include $(LOCAL_PATH)/build.mk 27 | LOCAL_MODULE := natmap 28 | LOCAL_SRC_FILES := $(patsubst $(SRCDIR)/%,src/%,$(SRCFILES)) 29 | LOCAL_C_INCLUDES := \ 30 | $(LOCAL_PATH)/third-part/hev-task-system/include \ 31 | $(LOCAL_PATH)/third-part/hev-task-system/src/lib/rbtree 32 | LOCAL_CFLAGS += $(VERSION_CFLAGS) 33 | ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) 34 | LOCAL_CFLAGS += -mfpu=neon 35 | endif 36 | LOCAL_STATIC_LIBRARIES := hev-task-system 37 | include $(BUILD_EXECUTABLE) 38 | -------------------------------------------------------------------------------- /Application.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Android Open Source Project 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | APP_OPTIM := release 17 | APP_PLATFORM := android-21 18 | APP_ABI := all 19 | APP_CFLAGS := -O3 20 | NDK_TOOLCHAIN_VERSION := clang 21 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 hev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for natmap 2 | 3 | PROJECT=natmap 4 | 5 | CROSS_PREFIX := 6 | PP=$(CROSS_PREFIX)cpp 7 | CC=$(CROSS_PREFIX)gcc 8 | STRIP=$(CROSS_PREFIX)strip 9 | CCFLAGS=-O3 -pipe -Wall -Werror $(CFLAGS) \ 10 | -I$(THIRDPARTDIR)/hev-task-system/include \ 11 | -I$(THIRDPARTDIR)/hev-task-system/src/lib/rbtree 12 | LDFLAGS=-L$(THIRDPARTDIR)/hev-task-system/bin -lhev-task-system \ 13 | -lpthread $(LFLAGS) 14 | 15 | SRCDIR=src 16 | BINDIR=bin 17 | BUILDDIR=build 18 | THIRDPARTDIR=third-part 19 | 20 | TARGET=$(BINDIR)/natmap 21 | THIRDPARTS=$(THIRDPARTDIR)/hev-task-system 22 | 23 | -include build.mk 24 | CCFLAGS+=$(VERSION_CFLAGS) 25 | CCSRCS=$(filter %.c,$(SRCFILES)) 26 | ASSRCS=$(filter %.S,$(SRCFILES)) 27 | LDOBJS=$(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(CCSRCS)) \ 28 | $(patsubst $(SRCDIR)/%.S,$(BUILDDIR)/%.o,$(ASSRCS)) 29 | DEPEND=$(LDOBJS:.o=.dep) 30 | 31 | BUILDMSG="\e[1;31mBUILD\e[0m %s\n" 32 | LINKMSG="\e[1;34mLINK\e[0m \e[1;32m%s\e[0m\n" 33 | STRIPMSG="\e[1;34mSTRIP\e[0m \e[1;32m%s\e[0m\n" 34 | CLEANMSG="\e[1;34mCLEAN\e[0m %s\n" 35 | 36 | ENABLE_DEBUG := 37 | ifeq ($(ENABLE_DEBUG),1) 38 | CCFLAGS+=-g -O0 -DENABLE_DEBUG 39 | STRIP=true 40 | endif 41 | 42 | ENABLE_STATIC := 43 | ifeq ($(ENABLE_STATIC),1) 44 | CCFLAGS+=-static 45 | endif 46 | 47 | V := 48 | ECHO_PREFIX := @ 49 | ifeq ($(V),1) 50 | undefine ECHO_PREFIX 51 | endif 52 | 53 | .PHONY: all clean tp-build tp-clean 54 | 55 | all : $(TARGET) 56 | 57 | tp-build : $(THIRDPARTS) 58 | @$(foreach dir,$^,$(MAKE) --no-print-directory -C $(dir);) 59 | 60 | tp-clean : $(THIRDPARTS) 61 | @$(foreach dir,$^,$(MAKE) --no-print-directory -C $(dir) clean;) 62 | 63 | clean : tp-clean 64 | $(ECHO_PREFIX) $(RM) -rf $(BINDIR) $(BUILDDIR) 65 | @printf $(CLEANMSG) $(PROJECT) 66 | 67 | $(TARGET) : $(LDOBJS) tp-build 68 | $(ECHO_PREFIX) mkdir -p $(dir $@) 69 | $(ECHO_PREFIX) $(CC) $(CCFLAGS) -o $@ $(LDOBJS) $(LDFLAGS) 70 | @printf $(LINKMSG) $@ 71 | $(ECHO_PREFIX) $(STRIP) $@ 72 | @printf $(STRIPMSG) $@ 73 | 74 | $(BUILDDIR)/%.dep : $(SRCDIR)/%.c 75 | $(ECHO_PREFIX) mkdir -p $(dir $@) 76 | $(ECHO_PREFIX) $(PP) $(CCFLAGS) -MM -MT$(@:.dep=.o) -MF$@ $< 2>/dev/null 77 | 78 | $(BUILDDIR)/%.o : $(SRCDIR)/%.c 79 | $(ECHO_PREFIX) mkdir -p $(dir $@) 80 | $(ECHO_PREFIX) $(CC) $(CCFLAGS) -c -o $@ $< 81 | @printf $(BUILDMSG) $< 82 | 83 | ifneq ($(MAKECMDGOALS),clean) 84 | -include $(DEPEND) 85 | endif 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NATMap 2 | 3 | [![status](https://github.com/heiher/natmap/actions/workflows/build.yaml/badge.svg?branch=master&event=push)](https://github.com/heiher/natmap) 4 | [![chat](https://github.com/heiher/natmap/raw/master/.github/badges/telegram.svg)](https://t.me/hellonatter) 5 | 6 | This project is used to establish a TCP/UDP port mapping from an ISP NAT public 7 | address to local private address. If all layers of NAT are full cones (NAT-1), 8 | any host can access internal services through the mapped public address. In bind 9 | mode, all traffic does not go through this program. 10 | 11 | [中文文档](https://github.com/heiher/natmap/wiki) 12 | 13 | ## How to Build 14 | 15 | ```bash 16 | git clone --recursive https://github.com/heiher/natmap.git 17 | cd natmap 18 | make 19 | 20 | # statically link 21 | make ENABLE_STATIC=1 22 | 23 | # cross compile 24 | make CROSS_PREFIX=${cross-toolchain}/bin/x86_64-unknown-linux- 25 | 26 | # android 27 | mkdir natmap 28 | cd natmap 29 | git clone --recursive https://github.com/heiher/natmap.git jni 30 | ndk-build 31 | 32 | # windows (msys2) 33 | export MSYS=winsymlinks:native 34 | git clone --recursive https://github.com/heiher/natmap.git 35 | cd natmap 36 | make LFLAGS="-lmsys-2.0 -lws2_32" 37 | ``` 38 | 39 | ## How to Use 40 | 41 | ### Usage 42 | 43 | ``` 44 | Usage: 45 | natmap [options] 46 | 47 | Options: 48 | -4 use IPv4 49 | -6 use IPv6 50 | -u UDP mode 51 | -d run as daemon 52 | -i network interface or IP address 53 | -k seconds between each keep-alive 54 | -c UDP STUN check cycle (every intervals) 55 | -s [:port] domain name or address of STUN server 56 | -h [:port] domain name or address of HTTP server 57 | -e script path for notify mapped address 58 | -f fwmark value (hex: 0x1, dec: 1, oct: 01) 59 | 60 | Bind options: 61 | -b [-port] port number range for binding 62 | - <0>: random allocation 63 | - : specified 64 | - -: sequential allocation within the range 65 | 66 | Forward options: 67 | -T port forwarding timeout in seconds 68 | -t
domain name or address of forward target 69 | -p port number of forward target (0: use public port) 70 | ``` 71 | 72 | ### Bind mode 73 | 74 | ```bash 75 | # TCP 76 | natmap -s turn.cloudflare.com -h example.com -b 80 77 | 78 | # UDP 79 | natmap -u -s turn.cloudflare.com -b 443 80 | ``` 81 | 82 | In TCP mode, this program will establishs a TCP port mapping in two steps: 83 | 84 | 1. Establish a connection with the HTTP server from the specified bind port and 85 | keep it alive. 86 | 2. Establish a connection with the STUN server from the same port and obtain the 87 | public address. 88 | 89 | This program will then call the script specified by the argument to inform the 90 | public address after the port mapping is established. The script can update 91 | the DNS record for external access. 92 | 93 | In TCP mode on Windows, make sure your application server is bound to the local 94 | network IP, not to `0.0.0.0` and `::`. 95 | 96 | In UDP mode on Windows, make sure your application server is bound to `0.0.0.0` 97 | or `::`, not to the local network IP. 98 | 99 | Please note that you need to open the firewall to allow access to the bind port. 100 | 101 | #### OpenWrt 102 | 103 | Goto Network -> Firewall -> Traffic Rules 104 | 105 | Add a traffic rule: 106 | 107 | * Protocol: TCP/UDP 108 | * Source zone: wan 109 | * Destination zone: Device (input) 110 | * Destination port: [bind port] 111 | * Action: accept 112 | * Others: keep default values 113 | 114 | If the port binding fails because it is already in use, this program will try 115 | to find out which local service process occupies the port and enable port reuse 116 | remotely. This works in Linux kernel 5.6 and later, and needs to run as root. 117 | 118 | ### Forward mode 119 | 120 | ```bash 121 | # TCP 122 | natmap -s turn.cloudflare.com -h example.com -b 80 -t 10.0.0.2 -p 80 123 | 124 | # UDP 125 | natmap -u -s turn.cloudflare.com -b 443 -t 10.0.0.2 -p 443 126 | ``` 127 | 128 | Similar to bind mode, this program will listen on bound port, accepts incoming 129 | connections, and forward them to target address. 130 | 131 | Another way is to use firewall's DNAT to forward, and this way should uses bind 132 | mode. 133 | 134 | #### OpenWrt 135 | 136 | Goto Network -> Firewall -> Port Forwards 137 | 138 | Add a port forward rule: 139 | 140 | * Protocol: TCP/UDP 141 | * Source zone: wan 142 | * External port: [bind port] 143 | * Destination zone: lan 144 | * Internal IP address: 10.0.0.2 145 | * Internal port: 80 146 | * Others: keep default values 147 | 148 | ### Script arguments 149 | 150 | ``` 151 | {public-addr} {public-port} {ip4p} {private-port} {protocol} {private-addr} 152 | ``` 153 | 154 | * argv[0]: Script path 155 | * argv[1]: Public address (IPv4/IPv6) 156 | * argv[2]: Public port 157 | * argv[3]: IP4P 158 | * argv[4]: Bind port (private port) 159 | * argv[5]: Protocol (TCP/UDP) 160 | * argv[6]: Private address (IPv4/IPv6) 161 | 162 | ### IP4P address 163 | 164 | The IP4P address format uses IPv6 special addresses to encode IPv4 addresses and 165 | ports for easy distribution through DNS AAAA records. 166 | 167 | ``` 168 | 2001::{port}:{ipv4-hi16}:{ipv4-lo16} 169 | ``` 170 | 171 | ## Contributors 172 | * **abgelehnt** - https://github.com/abgelehnt 173 | * **hev** - https://hev.cc 174 | * **mike wang** - https://github.com/mikewang000000 175 | * **muink** - https://github.com/muink 176 | * **tianling shen** - https://github.com/1715173329 177 | * **xhe** - https://github.com/xhebox 178 | 179 | ## License 180 | MIT 181 | -------------------------------------------------------------------------------- /build.mk: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | rwildcard=$(foreach d,$(wildcard $1*), \ 4 | $(call rwildcard,$d/,$2) \ 5 | $(filter $(subst *,%,$2),$d)) 6 | 7 | SRCFILES=$(call rwildcard,$(SRCDIR)/,*.c *.S) 8 | 9 | ifeq ($(REV_ID),) 10 | ifneq (,$(wildcard .version)) 11 | REV_ID=$(shell cat .version) 12 | endif 13 | ifeq ($(REV_ID),) 14 | REV_ID=$(shell git -C $(SRCDIR) tag --points-at HEAD) 15 | endif 16 | ifeq ($(REV_ID),) 17 | REV_ID=$(shell git -C $(SRCDIR) rev-parse --short HEAD) 18 | endif 19 | ifeq ($(REV_ID),) 20 | REV_ID=unknown 21 | endif 22 | endif 23 | VERSION_CFLAGS=-DCOMMIT_ID=\"$(REV_ID)\" 24 | -------------------------------------------------------------------------------- /src/hev-conf.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-conf.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : Conf 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "hev-misc.h" 18 | 19 | #include "hev-conf.h" 20 | 21 | static int mode = SOCK_STREAM; 22 | static int type = AF_INET; 23 | static int keep; 24 | static unsigned int ucount; 25 | static int dmon; 26 | static int tmsec; 27 | static int bport[3]; 28 | static unsigned int mark; 29 | 30 | static char mport[16]; 31 | static char sport[16] = "3478"; 32 | static char hport[16] = "80"; 33 | static char stun[256]; 34 | static char http[256]; 35 | 36 | static const char *path; 37 | static const char *baddr; 38 | static const char *taddr; 39 | static const char *tport; 40 | static const char *iface; 41 | 42 | const char * 43 | hev_conf_help (void) 44 | { 45 | const char *help = 46 | "Usage:\n" 47 | " natmap [options]\n" 48 | "\n" 49 | "Options:\n" 50 | " -4 use IPv4\n" 51 | " -6 use IPv6\n" 52 | " -u UDP mode\n" 53 | " -d run as daemon\n" 54 | " -i network interface or IP address\n" 55 | " -k seconds between each keep-alive\n" 56 | " -c UDP STUN check cycle (every intervals)\n" 57 | " -s [:port] domain name or address of STUN server\n" 58 | " -h [:port] domain name or address of HTTP server\n" 59 | " -e script path for notify mapped address\n" 60 | " -f fwmark value (hex: 0x1, dec: 1, oct: 01)\n" 61 | "\n" 62 | "Bind options:\n" 63 | " -b [-port] port number range for binding\n" 64 | " - <0>: random allocation\n" 65 | " - : specified\n" 66 | " - -: sequential allocation within the range\n" 67 | "\n" 68 | "Forward options:\n" 69 | " -T port forwarding timeout in seconds\n" 70 | " -t
domain name or address of forward target\n" 71 | " -p port number of forward target (0: use public port)\n"; 72 | 73 | return help; 74 | } 75 | 76 | int 77 | hev_conf_init (int argc, char *argv[]) 78 | { 79 | int opt; 80 | struct sockaddr_in6 sa; 81 | 82 | while ((opt = getopt (argc, argv, "46udk:c:s:h:e:f:b:T:t:p:i:")) != -1) { 83 | switch (opt) { 84 | case '4': 85 | type = AF_INET; 86 | break; 87 | case '6': 88 | type = AF_INET6; 89 | break; 90 | case 'u': 91 | mode = SOCK_DGRAM; 92 | break; 93 | case 'd': 94 | dmon = 1; 95 | break; 96 | case 'k': 97 | keep = strtoul (optarg, NULL, 10) * 1000; 98 | break; 99 | case 'c': 100 | ucount = strtoul (optarg, NULL, 10); 101 | break; 102 | case 's': 103 | sscanf (optarg, "%255[^:]:%5[0123456789]", stun, sport); 104 | break; 105 | case 'h': 106 | sscanf (optarg, "%255[^:]:%5[0123456789]", http, hport); 107 | break; 108 | case 'e': 109 | path = optarg; 110 | break; 111 | case 'f': 112 | mark = strtoul (optarg, NULL, 0); 113 | break; 114 | case 'b': 115 | sscanf (optarg, "%u-%u", &bport[0], &bport[1]); 116 | break; 117 | case 'T': 118 | tmsec = strtoul (optarg, NULL, 10) * 1000; 119 | break; 120 | case 't': 121 | taddr = optarg; 122 | break; 123 | case 'p': 124 | tport = optarg; 125 | break; 126 | case 'i': 127 | iface = optarg; 128 | break; 129 | default: 130 | return -1; 131 | } 132 | } 133 | 134 | if (!stun[0]) { 135 | return -1; 136 | } 137 | 138 | if ((mode == SOCK_STREAM) && !http[0]) { 139 | return -1; 140 | } 141 | 142 | if ((taddr && !tport) || (!taddr && tport)) { 143 | return -1; 144 | } 145 | 146 | if (keep <= 0) { 147 | keep = (mode == SOCK_STREAM) ? 30 : 10; 148 | keep *= 1000; 149 | } 150 | 151 | if (ucount <= 0) { 152 | ucount = 10; 153 | } 154 | 155 | if (!bport[0]) { 156 | bport[0] = 0; 157 | } 158 | if (!bport[1]) { 159 | bport[1] = bport[0]; 160 | } 161 | bport[2] = bport[0]; 162 | 163 | if (iface && inet_pton (type, iface, &sa)) { 164 | baddr = iface; 165 | iface = NULL; 166 | } else { 167 | baddr = (type == AF_INET6) ? "::" : "0.0.0.0"; 168 | } 169 | 170 | if (tmsec <= 0) { 171 | tmsec = 120000; 172 | } 173 | 174 | return 0; 175 | } 176 | 177 | int 178 | hev_conf_mode (void) 179 | { 180 | return mode; 181 | } 182 | 183 | int 184 | hev_conf_type (void) 185 | { 186 | return type; 187 | } 188 | 189 | int 190 | hev_conf_keep (void) 191 | { 192 | return keep; 193 | } 194 | 195 | unsigned int 196 | hev_conf_ucount (void) 197 | { 198 | return ucount; 199 | } 200 | 201 | const char * 202 | hev_conf_stun (void) 203 | { 204 | return stun; 205 | } 206 | 207 | const char * 208 | hev_conf_http (void) 209 | { 210 | return http; 211 | } 212 | 213 | const char * 214 | hev_conf_path (void) 215 | { 216 | return path; 217 | } 218 | 219 | unsigned int 220 | hev_conf_mark (void) 221 | { 222 | return mark; 223 | } 224 | 225 | const char * 226 | hev_conf_baddr (void) 227 | { 228 | return baddr; 229 | } 230 | 231 | const char * 232 | hev_conf_bport (void) 233 | { 234 | static char port[16]; 235 | 236 | snprintf (port, sizeof (port) - 1, "%u", bport[2]); 237 | 238 | bport[2] = bport[2] + 1; 239 | if (bport[2] > bport[1]) { 240 | bport[2] = bport[0]; 241 | } 242 | 243 | return port; 244 | } 245 | 246 | const char * 247 | hev_conf_taddr (void) 248 | { 249 | return taddr; 250 | } 251 | 252 | const char * 253 | hev_conf_tport (void) 254 | { 255 | return tport; 256 | } 257 | 258 | int 259 | hev_conf_tmsec (void) 260 | { 261 | return tmsec; 262 | } 263 | 264 | const char * 265 | hev_conf_mport (int port) 266 | { 267 | if (port > 0) 268 | snprintf (mport, sizeof (mport) - 1, "%u", port); 269 | 270 | return mport; 271 | } 272 | 273 | const char * 274 | hev_conf_iface (void) 275 | { 276 | return iface; 277 | } 278 | 279 | int 280 | hev_conf_daemon (void) 281 | { 282 | return dmon; 283 | } 284 | 285 | const char * 286 | hev_conf_sport (void) 287 | { 288 | return sport; 289 | } 290 | 291 | const char * 292 | hev_conf_hport (void) 293 | { 294 | return hport; 295 | } 296 | -------------------------------------------------------------------------------- /src/hev-conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-conf.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : Conf 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_CONF_H__ 11 | #define __HEV_CONF_H__ 12 | 13 | /** 14 | * hev_conf_help: 15 | * 16 | * Get help message of config. 17 | * 18 | * Returns: returns help message string. 19 | */ 20 | const char *hev_conf_help (void); 21 | 22 | /** 23 | * hev_conf_init: 24 | * @argc: argument count 25 | * @argv: argument vector 26 | * 27 | * Initialize config with arguments. 28 | * 29 | * Returns: returns zero on successful, otherwise returns -1. 30 | */ 31 | int hev_conf_init (int argc, char *argv[]); 32 | 33 | /** 34 | * hev_conf_mode: 35 | * 36 | * Get transmission protocol mode. 37 | * 38 | * Returns: returns SOCK_STREAM or SOCK_DGRAM. 39 | */ 40 | int hev_conf_mode (void); 41 | 42 | /** 43 | * hev_conf_type: 44 | * 45 | * Get network family type. 46 | * 47 | * Returns: returns AF_INET or AF_INET6. 48 | */ 49 | int hev_conf_type (void); 50 | 51 | /** 52 | * hev_conf_keep: 53 | * 54 | * Get keep-alive interval in millseconds. 55 | * 56 | * Returns: returns integer number. 57 | */ 58 | int hev_conf_keep (void); 59 | 60 | /** 61 | * hev_conf_ucount: 62 | * 63 | * Get the UDP STUN check cycle value. 64 | * 65 | * Returns: returns integer number. 66 | */ 67 | unsigned int hev_conf_ucount (void); 68 | 69 | /** 70 | * hev_conf_stun: 71 | * 72 | * Get STUN server address. 73 | * 74 | * Returns: returns string. 75 | */ 76 | const char *hev_conf_stun (void); 77 | 78 | /** 79 | * hev_conf_http: 80 | * 81 | * Get HTTP server address. 82 | * 83 | * Returns: returns string. 84 | */ 85 | const char *hev_conf_http (void); 86 | 87 | /** 88 | * hev_conf_path: 89 | * 90 | * Get script path that invoked on port mapping created. 91 | * 92 | * Returns: returns string. 93 | */ 94 | const char *hev_conf_path (void); 95 | 96 | /** 97 | * hev_conf_mark: 98 | * 99 | * Get the fwmark value. 100 | * 101 | * Returns: returns string. 102 | */ 103 | unsigned int hev_conf_mark (void); 104 | 105 | /** 106 | * hev_conf_baddr: 107 | * 108 | * Get bind address for port mapping. 109 | * 110 | * Returns: returns string. 111 | */ 112 | const char *hev_conf_baddr (void); 113 | 114 | /** 115 | * hev_conf_bport: 116 | * 117 | * Get bind port for port mapping. 118 | * 119 | * Returns: returns string. 120 | */ 121 | const char *hev_conf_bport (void); 122 | 123 | /** 124 | * hev_conf_taddr: 125 | * 126 | * Get target address for port forwarding. 127 | * 128 | * Returns: returns string. 129 | */ 130 | const char *hev_conf_taddr (void); 131 | 132 | /** 133 | * hev_conf_tport: 134 | * 135 | * Get target port for port forwarding. 136 | * 137 | * Returns: returns string. 138 | */ 139 | const char *hev_conf_tport (void); 140 | 141 | /** 142 | * hev_conf_tmsec: 143 | * 144 | * Get timeout millseconds for port forwarding. 145 | * 146 | * Returns: returns integer number. 147 | */ 148 | int hev_conf_tmsec (void); 149 | 150 | /** 151 | * hev_conf_mport: 152 | * @port: port number 153 | * 154 | * Get/Set public port of port mapping. 155 | * 156 | * Returns: returns string. 157 | */ 158 | const char *hev_conf_mport (int port); 159 | 160 | /** 161 | * hev_conf_iface: 162 | * 163 | * Get network interface name for binding as source. 164 | * 165 | * Returns: returns string. 166 | */ 167 | const char *hev_conf_iface (void); 168 | 169 | /** 170 | * hev_conf_daemon: 171 | * 172 | * Get is run as daemon. 173 | * 174 | * Returns: returns non-zero on true, otherwise returns zero. 175 | */ 176 | int hev_conf_daemon (void); 177 | 178 | /** 179 | * hev_conf_sport: 180 | * 181 | * Get port of STUN server. 182 | * 183 | * Returns: returns string. 184 | */ 185 | const char *hev_conf_sport (void); 186 | 187 | /** 188 | * hev_conf_hport: 189 | * 190 | * Get port of HTTP server. 191 | * 192 | * Returns: returns string. 193 | */ 194 | const char *hev_conf_hport (void); 195 | 196 | #endif /* __HEV_CONF_H__ */ 197 | -------------------------------------------------------------------------------- /src/hev-exec.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-exec.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Exec 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include "hev-conf.h" 21 | #include "hev-misc.h" 22 | 23 | #include "hev-exec.h" 24 | 25 | static HevTaskCall call; 26 | static const char *mode; 27 | static const char *path; 28 | static char oaddr[INET6_ADDRSTRLEN]; 29 | static char iaddr[INET6_ADDRSTRLEN]; 30 | static char oport[32]; 31 | static char iport[32]; 32 | static char ip4p[32]; 33 | 34 | static void 35 | signal_handler (int signum) 36 | { 37 | waitpid (-1, NULL, WNOHANG); 38 | } 39 | 40 | static void 41 | hev_exec_fork_exec (HevTaskCall *call) 42 | { 43 | pid_t pid; 44 | 45 | pid = fork (); 46 | if (pid < 0) { 47 | LOG (E); 48 | return; 49 | } else if (pid != 0) { 50 | return; 51 | } 52 | 53 | execl (path, path, oaddr, oport, ip4p, iport, mode, iaddr, NULL); 54 | 55 | LOGV (E, "%s", "Run script failed, Please check is it executable?"); 56 | exit (-1); 57 | } 58 | 59 | void 60 | hev_exec_init (void *stack) 61 | { 62 | call.stack_top = stack; 63 | } 64 | 65 | void 66 | hev_exec_run (int family, unsigned int maddr[4], unsigned short mport, 67 | unsigned int baddr[4], unsigned short bport) 68 | { 69 | unsigned char *q; 70 | unsigned char *p; 71 | const char *fmt; 72 | 73 | path = hev_conf_path (); 74 | signal (SIGCHLD, signal_handler); 75 | 76 | q = (unsigned char *)maddr; 77 | p = (unsigned char *)&mport; 78 | 79 | inet_ntop (family, maddr, oaddr, sizeof (oaddr)); 80 | inet_ntop (family, baddr, iaddr, sizeof (iaddr)); 81 | 82 | fmt = "%u"; 83 | snprintf (oport, sizeof (oport), fmt, ntohs (mport)); 84 | snprintf (iport, sizeof (iport), fmt, ntohs (bport)); 85 | 86 | if (family == AF_INET) { 87 | fmt = "2001::%02x%02x:%02x%02x:%02x%02x"; 88 | snprintf (ip4p, sizeof (ip4p), fmt, p[0], p[1], q[0], q[1], q[2], q[3]); 89 | } else { 90 | ip4p[0] = '\0'; 91 | } 92 | 93 | switch (hev_conf_mode ()) { 94 | case SOCK_STREAM: 95 | mode = "tcp"; 96 | break; 97 | case SOCK_DGRAM: 98 | mode = "udp"; 99 | break; 100 | default: 101 | mode = ""; 102 | } 103 | 104 | if (!path) { 105 | printf ("%s %s %s %s %s %s\n", oaddr, oport, ip4p, iport, mode, iaddr); 106 | fflush (stdout); 107 | return; 108 | } 109 | 110 | #ifdef __MSYS__ 111 | hev_task_call_jump (&call, hev_exec_fork_exec); 112 | #else 113 | hev_exec_fork_exec (&call); 114 | #endif 115 | } 116 | -------------------------------------------------------------------------------- /src/hev-exec.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-exec.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Exec 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_EXEC_H__ 11 | #define __HEV_EXEC_H__ 12 | 13 | #ifdef __MSYS__ 14 | #define HEV_EXEC_STACK_SIZE (16384) 15 | #else 16 | #define HEV_EXEC_STACK_SIZE (1) 17 | #endif 18 | 19 | /** 20 | * hev_exec_init: 21 | * 22 | * Initialize exec. 23 | */ 24 | void hev_exec_init (void *stack); 25 | 26 | /** 27 | * hev_exec_run: 28 | * @family: network family 29 | * @maddr: mapped addr 30 | * @mport: mapped port 31 | * @baddr: bound addr 32 | * @bport: bound port 33 | * 34 | * Run script to notify the mapped address is changed. 35 | */ 36 | void hev_exec_run (int family, unsigned int maddr[4], unsigned short mport, 37 | unsigned int baddr[4], unsigned short bport); 38 | 39 | #endif /* __HEV_EXEC_H__ */ 40 | -------------------------------------------------------------------------------- /src/hev-main.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-main.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Main 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "hev-conf.h" 20 | #include "hev-exec.h" 21 | #include "hev-misc.h" 22 | #include "hev-winc.h" 23 | #include "hev-xnsk.h" 24 | 25 | #include "hev-main.h" 26 | 27 | int 28 | main (int argc, char *argv[]) 29 | { 30 | char stack[HEV_EXEC_STACK_SIZE]; 31 | struct rlimit limit = { 32 | .rlim_cur = 65536, 33 | .rlim_max = 65536, 34 | }; 35 | struct timeval tv; 36 | int res; 37 | 38 | #if defined(__MSYS__) 39 | res = hev_winc_setup_ctrlc (); 40 | if (res < 0) { 41 | LOG (E); 42 | return -3; 43 | } 44 | #endif 45 | 46 | res = hev_conf_init (argc, argv); 47 | if (res < 0) { 48 | fprintf (stderr, "%s", hev_conf_help ()); 49 | #ifdef COMMIT_ID 50 | fprintf (stderr, "\nVersion: %s\n", COMMIT_ID); 51 | #endif 52 | return -1; 53 | } 54 | 55 | res = hev_conf_daemon (); 56 | if (res) { 57 | hev_run_daemon (); 58 | } 59 | 60 | gettimeofday (&tv, 0); 61 | srand (tv.tv_sec ^ tv.tv_usec); 62 | 63 | signal (SIGPIPE, SIG_IGN); 64 | setrlimit (RLIMIT_NOFILE, &limit); 65 | hev_exec_init (&stack[HEV_EXEC_STACK_SIZE]); 66 | 67 | res = hev_task_system_init (); 68 | if (res < 0) { 69 | LOG (E); 70 | return -2; 71 | } 72 | 73 | hev_xnsk_run (); 74 | hev_task_system_run (); 75 | 76 | hev_task_system_fini (); 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /src/hev-main.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-main.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : Main 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_MAIN_H__ 11 | #define __HEV_MAIN_H__ 12 | 13 | /** 14 | * main: 15 | * 16 | * Main entry function. 17 | * 18 | * Returns: returns zero on successful, otherwise returns -1. 19 | */ 20 | int main (int argc, char *argv[]); 21 | 22 | #endif /* __HEV_MAIN_H__ */ 23 | -------------------------------------------------------------------------------- /src/hev-misc.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-misc.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Misc 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(__linux__) 19 | #include 20 | #endif 21 | 22 | #include "hev-misc.h" 23 | 24 | #if defined(__mips__) 25 | #if defined(_LP64) 26 | #define NR_Linux 5000 27 | #else 28 | #define NR_Linux 4000 29 | #endif 30 | #else 31 | #define NR_Linux 0 32 | #endif 33 | 34 | #if !defined(SYS_pidfd_open) 35 | #define SYS_pidfd_open (NR_Linux + 434) 36 | #endif 37 | 38 | #if !defined(SYS_pidfd_getfd) 39 | #define SYS_pidfd_getfd (NR_Linux + 438) 40 | #endif 41 | 42 | #if defined(__linux__) 43 | #define run_syscall syscall 44 | #else 45 | static long 46 | run_syscall (long n, ...) 47 | { 48 | return -1; 49 | } 50 | #endif 51 | 52 | int 53 | hev_io_yielder (HevTaskYieldType type, void *data) 54 | { 55 | const int timeout = *(int *)data; 56 | 57 | if (type == HEV_TASK_YIELD) { 58 | hev_task_yield (HEV_TASK_YIELD); 59 | return 0; 60 | } 61 | 62 | if (timeout < 0) { 63 | hev_task_yield (HEV_TASK_WAITIO); 64 | } else { 65 | if (hev_task_sleep (timeout) <= 0) { 66 | return -1; 67 | } 68 | } 69 | 70 | return 0; 71 | } 72 | 73 | static unsigned long 74 | get_inode (int port, int family) 75 | { 76 | const char *paths[] = { 77 | "/proc/net/tcp", 78 | "/proc/net/tcp6", 79 | }; 80 | char *line = NULL; 81 | size_t len = 0; 82 | ssize_t nread; 83 | FILE *fp; 84 | int i; 85 | 86 | i = (family == AF_INET6) ? 1 : 0; 87 | 88 | fp = fopen (paths[i], "r"); 89 | if (!fp) { 90 | return 0; 91 | } 92 | 93 | nread = getline (&line, &len, fp); 94 | if (nread < 0) { 95 | fclose (fp); 96 | return 0; 97 | } 98 | 99 | while ((nread = getline (&line, &len, fp)) != -1) { 100 | int res, local_port, rem_port, d, state, uid, timer_run, timeout; 101 | unsigned long rxq, txq, time_len, retr, inode; 102 | char rem_addr[128], local_addr[128]; 103 | 104 | res = sscanf (line, 105 | "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X " 106 | "%X %lX:%lX %X:%lX %lX %d %d %lu %*s\n", 107 | &d, local_addr, &local_port, rem_addr, &rem_port, &state, 108 | &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, 109 | &inode); 110 | if ((res >= 14) && (state == 10) && (local_port == port)) { 111 | fclose (fp); 112 | return inode; 113 | } 114 | } 115 | 116 | free (line); 117 | fclose (fp); 118 | 119 | return 0; 120 | } 121 | 122 | static int 123 | get_pid_fd (unsigned long inode, pid_t *pid, int *fd) 124 | { 125 | struct dirent *dpe; 126 | char match[256]; 127 | DIR *dp; 128 | 129 | dp = opendir ("/proc"); 130 | if (!dp) { 131 | return -1; 132 | } 133 | 134 | snprintf (match, sizeof (match) - 1, "socket:[%lu]", inode); 135 | 136 | while ((dpe = readdir (dp))) { 137 | char path[1024]; 138 | struct dirent *dfe; 139 | DIR *df; 140 | 141 | if (dpe->d_type != DT_DIR) { 142 | continue; 143 | } 144 | 145 | snprintf (path, sizeof (path) - 1, "/proc/%s/fd", dpe->d_name); 146 | df = opendir (path); 147 | if (!df) { 148 | continue; 149 | } 150 | 151 | while ((dfe = readdir (df))) { 152 | char name[256]; 153 | int len; 154 | 155 | if (dfe->d_type != DT_LNK) { 156 | continue; 157 | } 158 | 159 | snprintf (path, sizeof (path) - 1, "/proc/%s/fd/%s", dpe->d_name, 160 | dfe->d_name); 161 | len = readlink (path, name, sizeof (name) - 1); 162 | if (len < 0) { 163 | continue; 164 | } 165 | 166 | name[len] = '\0'; 167 | if (strcmp (name, match) == 0) { 168 | *fd = strtoul (dfe->d_name, NULL, 10); 169 | *pid = strtoul (dpe->d_name, NULL, 10); 170 | closedir (df); 171 | closedir (dp); 172 | return 0; 173 | } 174 | } 175 | 176 | closedir (df); 177 | } 178 | 179 | closedir (dp); 180 | 181 | return -1; 182 | } 183 | 184 | static int 185 | set_reuse_port (pid_t pid, int fd) 186 | { 187 | const int reuse = 1; 188 | int pfd; 189 | int sfd; 190 | 191 | pfd = run_syscall (SYS_pidfd_open, pid, 0); 192 | if (pfd < 0) { 193 | LOG (E); 194 | return -1; 195 | } 196 | 197 | sfd = run_syscall (SYS_pidfd_getfd, pfd, fd, 0); 198 | if (sfd < 0) { 199 | LOG (E); 200 | close (pfd); 201 | return -1; 202 | } 203 | 204 | setsockopt (sfd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof (int)); 205 | 206 | close (sfd); 207 | close (pfd); 208 | return 0; 209 | } 210 | 211 | int 212 | hev_reuse_port (const char *port) 213 | { 214 | int types[] = { AF_INET, AF_INET6 }; 215 | int result = 0; 216 | int p; 217 | int i; 218 | 219 | p = strtoul (port, NULL, 10); 220 | 221 | for (i = 0; i < ARRAY_SIZE (types); i++) { 222 | unsigned long inode; 223 | 224 | inode = get_inode (p, types[i]); 225 | if (inode > 0) { 226 | pid_t pid; 227 | int res; 228 | int sfd; 229 | 230 | res = get_pid_fd (inode, &pid, &sfd); 231 | if (res == 0) { 232 | result |= set_reuse_port (pid, sfd); 233 | } 234 | } 235 | } 236 | 237 | return result; 238 | } 239 | 240 | int 241 | hev_run_daemon (void) 242 | { 243 | switch (fork ()) { 244 | case -1: 245 | return -1; 246 | case 0: 247 | break; 248 | default: 249 | exit (0); 250 | } 251 | 252 | if (setsid () < 0) { 253 | return -1; 254 | } 255 | 256 | return 0; 257 | } 258 | -------------------------------------------------------------------------------- /src/hev-misc.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-misc.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Misc 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_MISC_H__ 11 | #define __HEV_MISC_H__ 12 | 13 | #include 14 | #include 15 | 16 | #ifndef SO_REUSEPORT 17 | #define SO_REUSEPORT SO_REUSEADDR 18 | #endif 19 | 20 | #define ARRAY_SIZE(x) (sizeof (x) / sizeof (x[0])) 21 | 22 | #define LOG(T) \ 23 | fprintf (stderr, "[" #T "] %s %s:%d\n", __FUNCTION__, __FILE__, __LINE__); 24 | 25 | #define LOGV(T, F, ...) \ 26 | fprintf (stderr, "[" #T "] %s %s:%d " F "\n", __FUNCTION__, __FILE__, \ 27 | __LINE__, __VA_ARGS__); 28 | 29 | #ifndef container_of 30 | #define container_of(ptr, type, member) \ 31 | ({ \ 32 | void *__mptr = (void *)(ptr); \ 33 | ((type *)(__mptr - offsetof (type, member))); \ 34 | }) 35 | #endif 36 | 37 | #define io_yielder hev_io_yielder 38 | 39 | /** 40 | * hev_io_yielder: 41 | * @type: yield type 42 | * @data: data 43 | * 44 | * The task io yielder. 45 | * 46 | * Returns: returns zero on successful, otherwise returns -1. 47 | */ 48 | int hev_io_yielder (HevTaskYieldType type, void *data); 49 | 50 | /** 51 | * hev_reuse_port: 52 | * @port: port number 53 | * 54 | * Set reuse port flag for remote socket file descriptor in other process. 55 | * 56 | * Returns: returns zero on successful, otherwise returns -1. 57 | */ 58 | int hev_reuse_port (const char *port); 59 | 60 | /** 61 | * hev_run_daemon: 62 | * 63 | * Run as daemon. 64 | * 65 | * Returns: returns zero on successful, otherwise returns -1. 66 | */ 67 | int hev_run_daemon (void); 68 | 69 | #endif /* __HEV_MISC_H__ */ 70 | -------------------------------------------------------------------------------- /src/hev-sock.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-sock.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Sock 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "hev-misc.h" 24 | 25 | #include "hev-sock.h" 26 | 27 | typedef struct _SockConnectCtx SockConnectCtx; 28 | 29 | struct _SockConnectCtx 30 | { 31 | int retries; 32 | void *data; 33 | }; 34 | 35 | static int 36 | connect_io_yielder (HevTaskYieldType type, void *data) 37 | { 38 | SockConnectCtx *ctx = data; 39 | 40 | ctx->retries++; 41 | if (ctx->retries >= 100) { 42 | errno = EADDRNOTAVAIL; 43 | return -1; 44 | } 45 | 46 | return io_yielder (type, ctx->data); 47 | } 48 | 49 | static int 50 | sock_connect (int fd, const struct sockaddr *addr, socklen_t addr_len, 51 | SockConnectCtx *ctx) 52 | { 53 | int res; 54 | 55 | ctx->retries = 0; 56 | res = hev_task_io_socket_connect (fd, addr, addr_len, connect_io_yielder, 57 | ctx); 58 | if (res < 0) { 59 | if (errno == EADDRNOTAVAIL) { 60 | LOGV (E, "%s", 61 | "Cannot assign requested address, " 62 | "Please check is another instance exists or wait a minute. " 63 | "More: https://github.com/heiher/natmap/issues/27"); 64 | } else { 65 | LOGV (E, "%s", strerror (errno)); 66 | } 67 | } 68 | 69 | return res; 70 | } 71 | 72 | static struct addrinfo * 73 | get_addr (int family, int type, const char *addr, const char *port, int passive) 74 | { 75 | struct addrinfo *result = NULL; 76 | struct addrinfo hints = { 0 }; 77 | int res; 78 | 79 | hints.ai_family = family; 80 | hints.ai_socktype = type; 81 | hints.ai_flags = passive ? AI_PASSIVE : 0; 82 | 83 | res = hev_task_dns_getaddrinfo (addr, port, &hints, &result); 84 | if (res < 0) { 85 | LOG (E); 86 | return NULL; 87 | } 88 | 89 | return result; 90 | } 91 | 92 | static int 93 | get_sock (struct addrinfo *ai) 94 | { 95 | const int reuse = 1; 96 | int family; 97 | int socktype; 98 | int res; 99 | int fd; 100 | 101 | family = ai->ai_family; 102 | socktype = ai->ai_socktype; 103 | 104 | fd = hev_task_io_socket_socket (family, socktype, 0); 105 | if (fd < 0) { 106 | LOG (E); 107 | return -1; 108 | } 109 | 110 | res = fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC); 111 | if (res < 0) { 112 | LOG (W); 113 | } 114 | 115 | setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (int)); 116 | setsockopt (fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof (int)); 117 | 118 | return fd; 119 | } 120 | 121 | static int 122 | bind_iface (int fd, int family, const char *iface) 123 | { 124 | if (!iface) { 125 | return 0; 126 | } 127 | 128 | #if defined(__linux__) 129 | struct ifreq ifr = { 0 }; 130 | 131 | strncpy (ifr.ifr_name, iface, sizeof (ifr.ifr_name) - 1); 132 | return setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof (ifr)); 133 | #elif defined(__APPLE__) || defined(__MACH__) 134 | int i; 135 | 136 | i = if_nametoindex (iface); 137 | if (i == 0) { 138 | return -1; 139 | } 140 | 141 | switch (family) { 142 | case AF_INET: 143 | return setsockopt (fd, IPPROTO_IP, IP_BOUND_IF, &i, sizeof (i)); 144 | case AF_INET6: 145 | return setsockopt (fd, IPPROTO_IPV6, IPV6_BOUND_IF, &i, sizeof (i)); 146 | } 147 | #endif 148 | 149 | return -1; 150 | } 151 | 152 | static int 153 | bind_fwmark (int fd, unsigned int mark) 154 | { 155 | #if defined(__linux__) 156 | return setsockopt (fd, SOL_SOCKET, SO_MARK, &mark, sizeof (mark)); 157 | #elif defined(__FreeBSD__) 158 | return setsockopt (fd, SOL_SOCKET, SO_USER_COOKIE, &mark, sizeof (mark)); 159 | #endif 160 | return 0; 161 | } 162 | 163 | int 164 | hev_sock_client_base (int family, int type, const char *saddr, 165 | const char *sport, const char *daddr, const char *dport, 166 | const char *iface, unsigned int mark, 167 | struct sockaddr_storage *baddr, 168 | struct sockaddr_storage *paddr) 169 | { 170 | HevTask *task = hev_task_self (); 171 | struct addrinfo *sai; 172 | struct addrinfo *dai; 173 | int timeout = 30000; 174 | SockConnectCtx ctx; 175 | socklen_t addrlen; 176 | int res; 177 | int fd; 178 | 179 | sai = get_addr (family, type, saddr, sport, 0); 180 | if (!sai) { 181 | LOG (E); 182 | return -1; 183 | } 184 | 185 | dai = get_addr (sai->ai_family, type, daddr, dport, 0); 186 | if (!dai) { 187 | LOG (E); 188 | freeaddrinfo (sai); 189 | return -1; 190 | } 191 | 192 | if (paddr) { 193 | memcpy (paddr, dai->ai_addr, dai->ai_addrlen); 194 | } 195 | 196 | fd = get_sock (sai); 197 | if (fd < 0) { 198 | LOG (E); 199 | freeaddrinfo (sai); 200 | freeaddrinfo (dai); 201 | return -1; 202 | } 203 | 204 | res = bind_iface (fd, sai->ai_family, iface); 205 | if (mark) { 206 | res |= bind_fwmark (fd, mark); 207 | } 208 | if (res < 0) { 209 | LOG (E); 210 | freeaddrinfo (sai); 211 | freeaddrinfo (dai); 212 | close (fd); 213 | return -1; 214 | } 215 | 216 | res = bind (fd, sai->ai_addr, sai->ai_addrlen); 217 | if (res < 0) { 218 | hev_reuse_port (sport); 219 | res = bind (fd, sai->ai_addr, sai->ai_addrlen); 220 | if (res < 0) { 221 | LOGV (E, "%s", strerror (errno)); 222 | freeaddrinfo (sai); 223 | freeaddrinfo (dai); 224 | close (fd); 225 | return -1; 226 | } 227 | } 228 | freeaddrinfo (sai); 229 | 230 | hev_task_add_fd (task, fd, POLLIN | POLLOUT); 231 | 232 | ctx.data = &timeout; 233 | res = sock_connect (fd, dai->ai_addr, dai->ai_addrlen, &ctx); 234 | freeaddrinfo (dai); 235 | if (res < 0) { 236 | hev_task_del_fd (task, fd); 237 | close (fd); 238 | return -1; 239 | } 240 | 241 | addrlen = sizeof (struct sockaddr_storage); 242 | res = getsockname (fd, (struct sockaddr *)baddr, &addrlen); 243 | if (res < 0) { 244 | LOG (E); 245 | hev_task_del_fd (task, fd); 246 | close (fd); 247 | return -1; 248 | } 249 | 250 | return fd; 251 | } 252 | 253 | int 254 | hev_sock_client_stun (struct sockaddr *saddr, int type, const char *daddr, 255 | const char *dport, const char *iface, unsigned int mark, 256 | unsigned int baddr[4], int *bport) 257 | { 258 | HevTask *task = hev_task_self (); 259 | struct sockaddr_storage taddr; 260 | struct addrinfo sai, *dai; 261 | int timeout = 30000; 262 | SockConnectCtx ctx; 263 | socklen_t addrlen; 264 | int res; 265 | int fd; 266 | 267 | switch (saddr->sa_family) { 268 | case AF_INET: 269 | addrlen = sizeof (struct sockaddr_in); 270 | break; 271 | case AF_INET6: 272 | addrlen = sizeof (struct sockaddr_in6); 273 | break; 274 | } 275 | 276 | sai.ai_family = saddr->sa_family; 277 | sai.ai_socktype = type; 278 | 279 | dai = get_addr (sai.ai_family, sai.ai_socktype, daddr, dport, 0); 280 | if (!dai) { 281 | LOG (E); 282 | return -1; 283 | } 284 | 285 | fd = get_sock (&sai); 286 | if (fd < 0) { 287 | LOG (E); 288 | freeaddrinfo (dai); 289 | return -1; 290 | } 291 | 292 | res = bind_iface (fd, sai.ai_family, iface); 293 | if (mark) { 294 | res |= bind_fwmark (fd, mark); 295 | } 296 | if (res < 0) { 297 | LOG (E); 298 | freeaddrinfo (dai); 299 | close (fd); 300 | return -1; 301 | } 302 | 303 | res = bind (fd, saddr, addrlen); 304 | if (res < 0) { 305 | LOG (E); 306 | freeaddrinfo (dai); 307 | close (fd); 308 | return -1; 309 | } 310 | 311 | hev_task_add_fd (task, fd, POLLIN | POLLOUT); 312 | 313 | ctx.data = &timeout; 314 | res = sock_connect (fd, dai->ai_addr, dai->ai_addrlen, &ctx); 315 | freeaddrinfo (dai); 316 | if (res < 0) { 317 | hev_task_del_fd (task, fd); 318 | close (fd); 319 | return -1; 320 | } 321 | 322 | res = getsockname (fd, (struct sockaddr *)&taddr, &addrlen); 323 | if (res < 0) { 324 | LOG (E); 325 | hev_task_del_fd (task, fd); 326 | close (fd); 327 | return -1; 328 | } 329 | 330 | if (taddr.ss_family == AF_INET) { 331 | struct sockaddr_in *pa = (struct sockaddr_in *)&taddr; 332 | memcpy (baddr, &pa->sin_addr, 4); 333 | *bport = pa->sin_port; 334 | } else if (taddr.ss_family == AF_INET6) { 335 | struct sockaddr_in6 *pa = (struct sockaddr_in6 *)&taddr; 336 | memcpy (baddr, &pa->sin6_addr, 16); 337 | *bport = pa->sin6_port; 338 | } 339 | 340 | return fd; 341 | } 342 | 343 | int 344 | hev_sock_client_pfwd (int type, const char *addr, const char *port) 345 | { 346 | HevTask *task = hev_task_self (); 347 | struct addrinfo *ai; 348 | int timeout = 30000; 349 | int res; 350 | int fd; 351 | 352 | ai = get_addr (AF_UNSPEC, type, addr, port, 0); 353 | if (!ai) { 354 | LOG (E); 355 | return -1; 356 | } 357 | 358 | fd = get_sock (ai); 359 | if (fd < 0) { 360 | LOG (E); 361 | freeaddrinfo (ai); 362 | return -1; 363 | } 364 | 365 | hev_task_add_fd (task, fd, POLLIN | POLLOUT); 366 | 367 | res = hev_task_io_socket_connect (fd, ai->ai_addr, ai->ai_addrlen, 368 | io_yielder, &timeout); 369 | freeaddrinfo (ai); 370 | if (res < 0) { 371 | LOG (E); 372 | hev_task_del_fd (task, fd); 373 | close (fd); 374 | return -1; 375 | } 376 | 377 | return fd; 378 | } 379 | 380 | int 381 | hev_sock_server_pfwd (struct sockaddr *saddr, int type, const char *iface, 382 | unsigned int mark) 383 | { 384 | struct sockaddr_storage baddr; 385 | struct addrinfo ai; 386 | socklen_t addrlen; 387 | int res; 388 | int fd; 389 | 390 | switch (saddr->sa_family) { 391 | case AF_INET: 392 | addrlen = sizeof (struct sockaddr_in); 393 | break; 394 | case AF_INET6: 395 | default: 396 | addrlen = sizeof (struct sockaddr_in6); 397 | break; 398 | } 399 | 400 | memcpy (&baddr, saddr, addrlen); 401 | ai.ai_family = baddr.ss_family; 402 | ai.ai_socktype = type; 403 | 404 | fd = get_sock (&ai); 405 | if (fd < 0) { 406 | LOG (E); 407 | return -1; 408 | } 409 | 410 | #ifdef __MSYS__ 411 | if (type == SOCK_DGRAM) { 412 | if (baddr.ss_family == AF_INET) { 413 | struct sockaddr_in *pa = (struct sockaddr_in *)&baddr; 414 | memset (&pa->sin_addr, 0, 4); 415 | } else if (baddr.ss_family == AF_INET6) { 416 | struct sockaddr_in6 *pa = (struct sockaddr_in6 *)&baddr; 417 | memset (&pa->sin6_addr, 0, 16); 418 | } 419 | } 420 | #endif 421 | 422 | res = bind (fd, (struct sockaddr *)&baddr, addrlen); 423 | res |= bind_iface (fd, baddr.ss_family, iface); 424 | if (mark) { 425 | res |= bind_fwmark (fd, mark); 426 | } 427 | if (type == SOCK_STREAM) { 428 | res |= listen (fd, 5); 429 | } 430 | if (res < 0) { 431 | LOG (E); 432 | close (fd); 433 | return -1; 434 | } 435 | 436 | hev_task_add_fd (hev_task_self (), fd, POLLIN); 437 | 438 | return fd; 439 | } 440 | -------------------------------------------------------------------------------- /src/hev-sock.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-sock.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Sock 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_SOCK_H__ 11 | #define __HEV_SOCK_H__ 12 | 13 | /** 14 | * hev_sock_client_base: 15 | * @family: network family 16 | * @type: socket type 17 | * @saddr: source addr 18 | * @sport: source port 19 | * @daddr: destination addr 20 | * @dport: destination port 21 | * @iface: network interface 22 | * @mark: fwmark 23 | * @baddr: [out] socket bind addr 24 | * @paddr: [out] socket peer addr 25 | * 26 | * Create a socket for TCP and UDP client. 27 | * 28 | * Returns: returns file descriptor on successful, otherwise returns -1. 29 | */ 30 | int hev_sock_client_base (int family, int type, const char *saddr, 31 | const char *sport, const char *daddr, 32 | const char *dport, const char *iface, 33 | unsigned int mark, struct sockaddr_storage *baddr, 34 | struct sockaddr_storage *paddr); 35 | 36 | /** 37 | * hev_sock_client_stun: 38 | * @saddr: source addr 39 | * @type: socket type 40 | * @daddr: destination addr 41 | * @dport: destination port 42 | * @iface: network interface 43 | * @mark: fwmark 44 | * @baddr: [out] bound addr 45 | * @bport: [out] bound port 46 | * 47 | * Create a socket for STUN client. 48 | * 49 | * Returns: returns file descriptor on successful, otherwise returns -1. 50 | */ 51 | int hev_sock_client_stun (struct sockaddr *saddr, int type, const char *daddr, 52 | const char *dport, const char *iface, 53 | unsigned int mark, unsigned int baddr[4], int *bport); 54 | 55 | /** 56 | * hev_sock_client_pfwd: 57 | * @type: socket type 58 | * @addr: destination addr 59 | * @port: destination port 60 | * 61 | * Create a socket for port forwarding client. 62 | * 63 | * Returns: returns file descriptor on successful, otherwise returns -1. 64 | */ 65 | int hev_sock_client_pfwd (int type, const char *addr, const char *port); 66 | 67 | /** 68 | * hev_sock_server_pfwd: 69 | * @saddr: source addr 70 | * @type: socket type 71 | * @iface: network interface 72 | * @mark: fwmark 73 | * 74 | * Create a socket for port forwarding server. 75 | * 76 | * Returns: returns file descriptor on successful, otherwise returns -1. 77 | */ 78 | int hev_sock_server_pfwd (struct sockaddr *saddr, int type, const char *iface, 79 | unsigned int mark); 80 | 81 | #endif /* __HEV_SOCK_H__ */ 82 | -------------------------------------------------------------------------------- /src/hev-stun.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-stun.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Stun 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "hev-conf.h" 21 | #include "hev-exec.h" 22 | #include "hev-misc.h" 23 | #include "hev-sock.h" 24 | #include "hev-xnsk.h" 25 | 26 | #include "hev-stun.h" 27 | 28 | typedef struct _StunMessage StunMessage; 29 | typedef struct _StunAttribute StunAttribute; 30 | typedef struct _StunMappedAddr StunMappedAddr; 31 | 32 | enum 33 | { 34 | IPV4 = 1, 35 | IPV6 = 2, 36 | }; 37 | 38 | enum 39 | { 40 | MAGIC = 0x2112A442, 41 | MAPPED_ADDR = 0x0001, 42 | XOR_MAPPED_ADDR = 0x0020, 43 | }; 44 | 45 | struct _StunMessage 46 | { 47 | unsigned short type; 48 | unsigned short size; 49 | unsigned int magic; 50 | unsigned int tid[3]; 51 | }; 52 | 53 | struct _StunAttribute 54 | { 55 | unsigned short type; 56 | unsigned short size; 57 | }; 58 | 59 | struct _StunMappedAddr 60 | { 61 | unsigned char reserved; 62 | unsigned char family; 63 | unsigned short port; 64 | union 65 | { 66 | unsigned int addr[0]; 67 | unsigned int ipv4[1]; 68 | unsigned int ipv6[4]; 69 | }; 70 | }; 71 | 72 | static HevTask *task; 73 | static HevStunHandler handler; 74 | static int udp_timeout; 75 | static int udp_retry1; 76 | static int udp_retry2; 77 | static int *udp_retry = &udp_retry1; 78 | 79 | static int 80 | cmp_addr (int family, unsigned int maddr[4], unsigned short mport, 81 | unsigned int baddr[4], unsigned short bport) 82 | { 83 | static unsigned int pmaddr[4]; 84 | static unsigned int pbaddr[4]; 85 | static unsigned short pmport; 86 | static unsigned short pbport; 87 | int res = 0; 88 | 89 | if (pbport != bport) { 90 | res |= -1; 91 | } 92 | pbport = bport; 93 | 94 | if (pmport != mport) { 95 | res |= -1; 96 | } 97 | pmport = mport; 98 | 99 | switch (family) { 100 | case AF_INET: 101 | res |= memcmp (pmaddr, maddr, 4); 102 | memcpy (&pmaddr[0], maddr, 4); 103 | memset (&pmaddr[1], 0, 12); 104 | res |= memcmp (pbaddr, baddr, 4); 105 | memcpy (&pbaddr[0], baddr, 4); 106 | memset (&pbaddr[1], 0, 12); 107 | break; 108 | case AF_INET6: 109 | res |= memcmp (pmaddr, maddr, 16); 110 | memcpy (pmaddr, maddr, 16); 111 | res |= memcmp (pbaddr, baddr, 16); 112 | memcpy (pbaddr, baddr, 16); 113 | break; 114 | } 115 | 116 | return res; 117 | } 118 | 119 | static ssize_t 120 | stun_tcp (int fd, StunMessage *msg, void *buf, size_t size) 121 | { 122 | int timeout = 30000; 123 | ssize_t len; 124 | 125 | len = hev_task_io_socket_send (fd, msg, sizeof (StunMessage), MSG_WAITALL, 126 | io_yielder, &timeout); 127 | if (len <= 0) { 128 | LOGV (E, "%s", "STUN TCP send failed."); 129 | return -1; 130 | } 131 | 132 | len = hev_task_io_socket_recv (fd, msg, sizeof (StunMessage), MSG_WAITALL, 133 | io_yielder, &timeout); 134 | if (len <= 0) { 135 | LOGV (E, "%s", "STUN TCP recv failed."); 136 | return -1; 137 | } 138 | 139 | len = htons (msg->size); 140 | if ((len <= 0) || (len > size)) { 141 | LOG (E); 142 | return -1; 143 | } 144 | 145 | len = hev_task_io_socket_recv (fd, buf, len, MSG_WAITALL, io_yielder, 146 | &timeout); 147 | return len; 148 | } 149 | 150 | static ssize_t 151 | stun_udp (int fd, StunMessage *msg, void *buf, size_t size) 152 | { 153 | ssize_t len = -1; 154 | int i; 155 | 156 | for (i = 0; i < *udp_retry; i++) { 157 | len = hev_task_io_socket_send (fd, msg, sizeof (StunMessage), 0, 158 | io_yielder, &udp_timeout); 159 | if (len <= 0) { 160 | LOGV (E, "%s", "STUN UDP send failed."); 161 | return -1; 162 | } 163 | 164 | len = hev_task_io_socket_recv (fd, buf, size, 0, io_yielder, 165 | &udp_timeout); 166 | if (len > 0) { 167 | break; 168 | } 169 | } 170 | 171 | udp_retry = &udp_retry2; 172 | return len; 173 | } 174 | 175 | static void 176 | stun_pack (StunMessage *msg) 177 | { 178 | msg->type = htons (0x0001); 179 | msg->size = htons (0x0000); 180 | msg->magic = htonl (MAGIC); 181 | msg->tid[0] = rand (); 182 | msg->tid[1] = rand (); 183 | msg->tid[2] = rand (); 184 | } 185 | 186 | static int 187 | stun_unpack (StunMessage *msg, void *body, size_t len, int pos, 188 | unsigned int *addr, unsigned short *port) 189 | { 190 | int family = -1; 191 | int i; 192 | 193 | for (i = pos; i < len;) { 194 | StunAttribute *a = (StunAttribute *)(body + i); 195 | StunMappedAddr *m = (StunMappedAddr *)(a + 1); 196 | int size; 197 | 198 | size = ntohs (a->size); 199 | if (size <= 0) { 200 | LOG (E); 201 | return -1; 202 | } 203 | 204 | if (a->type == htons (MAPPED_ADDR)) { 205 | *port = m->port; 206 | addr[0] = m->addr[0]; 207 | addr[1] = m->addr[1]; 208 | addr[2] = m->addr[2]; 209 | addr[3] = m->addr[3]; 210 | family = m->family; 211 | break; 212 | } else if (a->type == htons (XOR_MAPPED_ADDR)) { 213 | *port = m->port ^ msg->magic; 214 | addr[0] = m->addr[0] ^ msg->magic; 215 | addr[1] = m->addr[1] ^ msg->tid[0]; 216 | addr[2] = m->addr[2] ^ msg->tid[1]; 217 | addr[3] = m->addr[3] ^ msg->tid[2]; 218 | family = m->family; 219 | break; 220 | } 221 | 222 | i += sizeof (StunAttribute) + size; 223 | } 224 | 225 | switch (family) { 226 | case IPV4: 227 | family = AF_INET; 228 | break; 229 | case IPV6: 230 | family = AF_INET6; 231 | break; 232 | default: 233 | LOG (E); 234 | return -1; 235 | } 236 | 237 | return family; 238 | } 239 | 240 | static int 241 | stun_bind (int fd, int mode, unsigned int baddr[4], int bport) 242 | { 243 | const int bufsize = 2048; 244 | char buf[bufsize + 32]; 245 | unsigned int maddr[4]; 246 | unsigned short mport; 247 | StunMessage msg; 248 | int family = 0; 249 | int exec; 250 | int len; 251 | int pos; 252 | 253 | stun_pack (&msg); 254 | 255 | if (mode == SOCK_STREAM) { 256 | len = stun_tcp (fd, &msg, buf, bufsize); 257 | pos = 0; 258 | } else { 259 | len = stun_udp (fd, &msg, buf, bufsize); 260 | pos = sizeof (msg); 261 | } 262 | if (len <= 0) { 263 | LOG (E); 264 | return -1; 265 | } 266 | 267 | family = stun_unpack (&msg, buf, len, pos, maddr, &mport); 268 | if (family < 0) { 269 | LOG (E); 270 | return -1; 271 | } 272 | 273 | handler (); 274 | 275 | exec = cmp_addr (family, maddr, mport, baddr, bport); 276 | if (exec) { 277 | hev_conf_mport (ntohs (mport)); 278 | hev_exec_run (family, maddr, mport, baddr, bport); 279 | } 280 | 281 | return 0; 282 | } 283 | 284 | static void 285 | task_entry (void *data) 286 | { 287 | unsigned int baddr[4]; 288 | const char *iface; 289 | const char *stun; 290 | const char *sport; 291 | unsigned int mark; 292 | int bport; 293 | int loop; 294 | int mode; 295 | int fd; 296 | 297 | mode = hev_conf_mode (); 298 | stun = hev_conf_stun (); 299 | sport = hev_conf_sport (); 300 | iface = hev_conf_iface (); 301 | mark = hev_conf_mark (); 302 | 303 | #ifdef __MSYS__ 304 | udp_timeout = 100; 305 | udp_retry1 = 50; 306 | udp_retry2 = 5; 307 | loop = 0; 308 | #else 309 | udp_timeout = 3000; 310 | udp_retry1 = 10; 311 | udp_retry2 = 10; 312 | loop = (mode == SOCK_DGRAM); 313 | #endif 314 | 315 | fd = hev_sock_client_stun (data, mode, stun, sport, iface, mark, baddr, 316 | &bport); 317 | if (fd < 0) { 318 | LOGV (E, "%s", "Start STUN service failed."); 319 | hev_xnsk_kill (); 320 | goto exit; 321 | } 322 | 323 | for (;;) { 324 | int res = stun_bind (fd, mode, baddr, bport); 325 | if (res < 0) { 326 | LOG (E); 327 | hev_xnsk_kill (); 328 | break; 329 | } 330 | 331 | if (!loop) { 332 | break; 333 | } 334 | 335 | hev_task_yield (HEV_TASK_WAITIO); 336 | } 337 | 338 | hev_task_del_fd (hev_task_self (), fd); 339 | close (fd); 340 | exit: 341 | task = NULL; 342 | } 343 | 344 | void 345 | hev_stun_run (struct sockaddr *saddr, HevStunHandler _handler) 346 | { 347 | if (task) { 348 | hev_task_wakeup (task); 349 | return; 350 | } 351 | 352 | handler = _handler; 353 | task = hev_task_new (-1); 354 | hev_task_run (task, task_entry, saddr); 355 | } 356 | -------------------------------------------------------------------------------- /src/hev-stun.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-stun.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : Stun 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_STUN_H__ 11 | #define __HEV_STUN_H__ 12 | 13 | typedef void (*HevStunHandler) (void); 14 | 15 | /** 16 | * hev_stun_run: 17 | * @saddr: socket source address 18 | * @handler: callback for stun done 19 | * 20 | * Run STUN client to get mapped address. 21 | */ 22 | void hev_stun_run (struct sockaddr *saddr, HevStunHandler handler); 23 | 24 | #endif /* __HEV_STUN_H__ */ 25 | -------------------------------------------------------------------------------- /src/hev-tfwd.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-tfwd.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : TCP forwarder 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "hev-conf.h" 19 | #include "hev-misc.h" 20 | #include "hev-sock.h" 21 | #include "hev-xnsk.h" 22 | 23 | #include "hev-tfwd.h" 24 | 25 | static HevTask *task; 26 | static int quit; 27 | 28 | static int 29 | yielder (HevTaskYieldType type, void *data) 30 | { 31 | hev_task_yield (type); 32 | 33 | return quit; 34 | } 35 | 36 | static void 37 | client_task_entry (void *data) 38 | { 39 | HevTask *task = hev_task_self (); 40 | const char *addr; 41 | const char *port; 42 | int timeout; 43 | int mode; 44 | int sfd; 45 | int dfd; 46 | 47 | sfd = (intptr_t)data; 48 | mode = hev_conf_mode (); 49 | addr = hev_conf_taddr (); 50 | port = hev_conf_tport (); 51 | timeout = hev_conf_tmsec (); 52 | 53 | if (strtoul (port, NULL, 10) == 0) 54 | port = hev_conf_mport (-1); 55 | 56 | dfd = hev_sock_client_pfwd (mode, addr, port); 57 | if (dfd < 0) { 58 | LOG (W); 59 | close (sfd); 60 | return; 61 | } 62 | 63 | hev_task_add_fd (task, sfd, POLLIN | POLLOUT); 64 | hev_task_io_splice (sfd, sfd, dfd, dfd, 8192, io_yielder, &timeout); 65 | 66 | hev_task_del_fd (task, sfd); 67 | hev_task_del_fd (task, dfd); 68 | close (sfd); 69 | close (dfd); 70 | } 71 | 72 | static void 73 | server_task_entry (void *data) 74 | { 75 | const char *iface; 76 | int mode; 77 | int mark; 78 | int fd; 79 | 80 | mode = hev_conf_mode (); 81 | mark = hev_conf_mark (); 82 | iface = hev_conf_iface (); 83 | fd = hev_sock_server_pfwd (data, mode, iface, mark); 84 | if (fd < 0) { 85 | LOGV (E, "%s", "Start TCP forward service failed."); 86 | hev_xnsk_kill (); 87 | task = NULL; 88 | return; 89 | } 90 | 91 | quit = 0; 92 | for (;;) { 93 | HevTask *task; 94 | int nfd; 95 | 96 | nfd = hev_task_io_socket_accept (fd, NULL, NULL, yielder, NULL); 97 | if (nfd < 0) { 98 | if (nfd != -2) { 99 | LOG (E); 100 | } 101 | break; 102 | } 103 | 104 | task = hev_task_new (-1); 105 | hev_task_run (task, client_task_entry, (void *)(intptr_t)nfd); 106 | } 107 | 108 | close (fd); 109 | task = NULL; 110 | } 111 | 112 | void 113 | hev_tfwd_run (struct sockaddr *saddr) 114 | { 115 | if (task) { 116 | return; 117 | } 118 | 119 | task = hev_task_new (-1); 120 | hev_task_run (task, server_task_entry, saddr); 121 | } 122 | 123 | void 124 | hev_tfwd_kill (void) 125 | { 126 | quit = -1; 127 | if (task) { 128 | hev_task_wakeup (task); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/hev-tfwd.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-tfwd.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : TCP forwarder 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_TFWD_H__ 11 | #define __HEV_TFWD_H__ 12 | 13 | /** 14 | * hev_tfwd_run: 15 | * @saddr: source addr 16 | * 17 | * Run server for TCP port forwarding. 18 | */ 19 | void hev_tfwd_run (struct sockaddr *saddr); 20 | 21 | /** 22 | * hev_tfwd_kill: 23 | * 24 | * Force stop TCP port forwarding server. 25 | */ 26 | void hev_tfwd_kill (void); 27 | 28 | #endif /* __HEV_TFWD_H__ */ 29 | -------------------------------------------------------------------------------- /src/hev-tnsk.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-tnsk.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : TCP NAT session keeper 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "hev-conf.h" 19 | #include "hev-misc.h" 20 | #include "hev-sock.h" 21 | #include "hev-stun.h" 22 | #include "hev-tfwd.h" 23 | #include "hev-xnsk.h" 24 | 25 | #include "hev-tnsk.h" 26 | 27 | static struct sockaddr_storage saddr; 28 | static HevTask *task; 29 | static int timeout; 30 | static int fd; 31 | 32 | static void 33 | tnsk_keep_alive (int fd, const char *http) 34 | { 35 | static char buffer[8192]; 36 | struct msghdr mh = { 0 }; 37 | struct iovec iov[3]; 38 | int misscnt = 0; 39 | 40 | mh.msg_iov = iov; 41 | mh.msg_iovlen = 3; 42 | 43 | iov[0].iov_base = "HEAD / HTTP/1.1\r\nHost: "; 44 | iov[0].iov_len = strlen (iov[0].iov_base); 45 | iov[1].iov_base = (void *)http; 46 | iov[1].iov_len = strlen (iov[1].iov_base); 47 | iov[2].iov_base = "\r\nConnection: keep-alive\r\n\r\n"; 48 | iov[2].iov_len = strlen (iov[2].iov_base); 49 | 50 | for (;;) { 51 | int res; 52 | 53 | timeout = 30000; 54 | res = hev_task_io_socket_sendmsg (fd, &mh, MSG_WAITALL, io_yielder, 55 | &timeout); 56 | if (res <= 0) { 57 | return; 58 | } 59 | 60 | timeout = hev_conf_keep (); 61 | for (;;) { 62 | res = hev_task_io_socket_recv (fd, buffer, sizeof (buffer), 0, 63 | io_yielder, &timeout); 64 | if ((res == -2) && (misscnt++ == 0) && timeout) { 65 | break; 66 | } else if (res <= 0) { 67 | return; 68 | } else { 69 | misscnt = 0; 70 | } 71 | } 72 | } 73 | } 74 | 75 | static void 76 | stun_handler (void) 77 | { 78 | const char *tfwd = hev_conf_taddr (); 79 | 80 | if (tfwd) { 81 | hev_tfwd_run ((struct sockaddr *)&saddr); 82 | } 83 | } 84 | 85 | static void 86 | tnsk_run (void) 87 | { 88 | const char *http; 89 | const char *tfwd; 90 | const char *addr; 91 | const char *port; 92 | const char *hport; 93 | const char *iface; 94 | unsigned int mark; 95 | int type; 96 | 97 | type = hev_conf_type (); 98 | http = hev_conf_http (); 99 | tfwd = hev_conf_taddr (); 100 | addr = hev_conf_baddr (); 101 | port = hev_conf_bport (); 102 | hport = hev_conf_hport (); 103 | iface = hev_conf_iface (); 104 | mark = hev_conf_mark (); 105 | 106 | fd = hev_sock_client_base (type, SOCK_STREAM, addr, port, http, hport, 107 | iface, mark, &saddr, NULL); 108 | if (fd < 0) { 109 | LOGV (E, "%s", "Start TCP keep-alive service failed."); 110 | return; 111 | } 112 | 113 | hev_stun_run ((struct sockaddr *)&saddr, stun_handler); 114 | 115 | tnsk_keep_alive (fd, http); 116 | 117 | if (tfwd) { 118 | hev_tfwd_kill (); 119 | } 120 | hev_task_del_fd (hev_task_self (), fd); 121 | close (fd); 122 | } 123 | 124 | static void 125 | task_entry (void *data) 126 | { 127 | for (;;) { 128 | tnsk_run (); 129 | hev_task_sleep (5000); 130 | } 131 | } 132 | 133 | static void 134 | tnsk_kill (void) 135 | { 136 | timeout = 0; 137 | if (task) { 138 | hev_task_wakeup (task); 139 | } 140 | } 141 | 142 | void 143 | hev_tnsk_run (void) 144 | { 145 | hev_xnsk_init (tnsk_kill); 146 | 147 | task = hev_task_new (-1); 148 | hev_task_run (task, task_entry, NULL); 149 | } 150 | -------------------------------------------------------------------------------- /src/hev-tnsk.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-tnsk.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : TCP NAT session keeper 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_TNSK_H__ 11 | #define __HEV_TNSK_H__ 12 | 13 | /** 14 | * hev_tnsk_run: 15 | * 16 | * Run HTTP client to keep-alive. 17 | */ 18 | void hev_tnsk_run (void); 19 | 20 | #endif /* __HEV_TNSK_H__ */ 21 | -------------------------------------------------------------------------------- /src/hev-ufwd.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-ufwd.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : UDP forwarder 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "hev-conf.h" 23 | #include "hev-misc.h" 24 | #include "hev-sock.h" 25 | #include "hev-xnsk.h" 26 | 27 | #include "hev-ufwd.h" 28 | 29 | typedef struct _Session Session; 30 | 31 | struct _Session 32 | { 33 | HevRBTreeNode node; 34 | HevTask *task; 35 | struct sockaddr_in6 addr; 36 | socklen_t alen; 37 | int active; 38 | int fd; 39 | }; 40 | 41 | static HevRBTree sessions; 42 | static HevTask *task; 43 | static int sfd = -1; 44 | static int quit; 45 | 46 | static int 47 | yielder (HevTaskYieldType type, void *data) 48 | { 49 | hev_task_yield (type); 50 | 51 | return quit; 52 | } 53 | 54 | static Session * 55 | session_find (struct sockaddr *saddr, socklen_t len) 56 | { 57 | HevRBTreeNode *node = sessions.root; 58 | 59 | while (node) { 60 | Session *this; 61 | int res; 62 | 63 | this = container_of (node, Session, node); 64 | res = memcmp (&this->addr, saddr, len); 65 | 66 | if (res < 0) 67 | node = node->left; 68 | else if (res > 0) 69 | node = node->right; 70 | else 71 | return this; 72 | } 73 | 74 | return NULL; 75 | } 76 | 77 | static void 78 | session_add (Session *s) 79 | { 80 | HevRBTreeNode **new = &sessions.root, *parent = NULL; 81 | 82 | while (*new) { 83 | Session *this; 84 | int res; 85 | 86 | this = container_of (*new, Session, node); 87 | res = memcmp (&this->addr, &s->addr, s->alen); 88 | 89 | parent = *new; 90 | if (res < 0) 91 | new = &((*new)->left); 92 | else if (res > 0) 93 | new = &((*new)->right); 94 | } 95 | 96 | hev_rbtree_node_link (&s->node, parent, new); 97 | hev_rbtree_insert_color (&sessions, &s->node); 98 | } 99 | 100 | static void 101 | session_del (Session *s) 102 | { 103 | hev_rbtree_erase (&sessions, &s->node); 104 | } 105 | 106 | static Session * 107 | session_new (struct sockaddr *saddr, socklen_t len) 108 | { 109 | const char *addr; 110 | const char *port; 111 | Session *s; 112 | int mode; 113 | int fd; 114 | 115 | mode = hev_conf_mode (); 116 | addr = hev_conf_taddr (); 117 | port = hev_conf_tport (); 118 | 119 | if (strtoul (port, NULL, 10) == 0) 120 | port = hev_conf_mport (-1); 121 | 122 | fd = hev_sock_client_pfwd (mode, addr, port); 123 | if (fd < 0) { 124 | LOG (E); 125 | return NULL; 126 | } 127 | 128 | s = hev_malloc0 (sizeof (Session)); 129 | if (!s) { 130 | LOG (E); 131 | hev_task_del_fd (hev_task_self (), fd); 132 | close (fd); 133 | return NULL; 134 | } 135 | 136 | s->fd = fd; 137 | s->alen = len; 138 | s->task = hev_task_new (-1); 139 | memcpy (&s->addr, saddr, len); 140 | 141 | return s; 142 | } 143 | 144 | static void 145 | session_free (Session *s) 146 | { 147 | close (s->fd); 148 | hev_free (s); 149 | } 150 | 151 | static void 152 | client_task_entry (void *data) 153 | { 154 | HevTask *task = hev_task_self (); 155 | const int bufsize = 2048; 156 | struct sockaddr *pa; 157 | char buf[bufsize]; 158 | Session *s = data; 159 | int timeout; 160 | 161 | pa = (struct sockaddr *)&s->addr; 162 | hev_task_add_fd (task, s->fd, POLLIN); 163 | timeout = hev_conf_tmsec (); 164 | 165 | for (;;) { 166 | int len; 167 | 168 | s->active = 0; 169 | len = hev_task_io_socket_recvfrom (s->fd, buf, bufsize, 0, NULL, NULL, 170 | io_yielder, &timeout); 171 | if (len <= 0) { 172 | if ((len == -2) && s->active) { 173 | continue; 174 | } 175 | break; 176 | } 177 | 178 | len = sendto (sfd, buf, len, 0, pa, s->alen); 179 | if (len <= 0) { 180 | break; 181 | } 182 | } 183 | 184 | hev_task_del_fd (task, s->fd); 185 | session_del (s); 186 | session_free (s); 187 | } 188 | 189 | static void 190 | server_task_entry (void *data) 191 | { 192 | const char *iface; 193 | int mode; 194 | int mark; 195 | 196 | mode = hev_conf_mode (); 197 | mark = hev_conf_mark (); 198 | iface = hev_conf_iface (); 199 | 200 | sfd = hev_sock_server_pfwd (data, mode, iface, mark); 201 | if (sfd < 0) { 202 | LOGV (E, "%s", "Start UDP forward service failed."); 203 | hev_xnsk_kill (); 204 | task = NULL; 205 | return; 206 | } 207 | 208 | quit = 0; 209 | for (;;) { 210 | struct sockaddr_storage addr = { 0 }; 211 | socklen_t alen = sizeof (addr); 212 | const int bufsize = 2048; 213 | struct sockaddr *pa; 214 | char buf[bufsize]; 215 | Session *s; 216 | int len; 217 | 218 | pa = (struct sockaddr *)&addr; 219 | len = hev_task_io_socket_recvfrom (sfd, buf, bufsize, 0, pa, &alen, 220 | yielder, NULL); 221 | if (len < 0) { 222 | break; 223 | } 224 | 225 | s = session_find (pa, alen); 226 | if (!s) { 227 | s = session_new (pa, alen); 228 | if (!s) { 229 | LOG (E); 230 | continue; 231 | } 232 | session_add (s); 233 | hev_task_del_fd (task, s->fd); 234 | hev_task_run (s->task, client_task_entry, s); 235 | } 236 | 237 | send (s->fd, buf, len, 0); 238 | s->active = 1; 239 | } 240 | 241 | hev_task_del_fd (hev_task_self (), sfd); 242 | close (sfd); 243 | task = NULL; 244 | sfd = -1; 245 | } 246 | 247 | void 248 | hev_ufwd_run (struct sockaddr *saddr) 249 | { 250 | if (task) { 251 | return; 252 | } 253 | 254 | task = hev_task_new (-1); 255 | hev_task_run (task, server_task_entry, saddr); 256 | } 257 | 258 | void 259 | hev_ufwd_kill (void) 260 | { 261 | quit = -1; 262 | if (task) { 263 | hev_task_wakeup (task); 264 | } 265 | } 266 | 267 | int 268 | hev_ufwd_fd (void) 269 | { 270 | return sfd; 271 | } 272 | -------------------------------------------------------------------------------- /src/hev-ufwd.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-ufwd.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : UDP forwarder 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_UFWD_H__ 11 | #define __HEV_UFWD_H__ 12 | 13 | /** 14 | * hev_ufwd_run: 15 | * @saddr: source addr 16 | * 17 | * Run server for UDP port forwarding. 18 | */ 19 | void hev_ufwd_run (struct sockaddr *saddr); 20 | 21 | /** 22 | * hev_ufwd_kill: 23 | * 24 | * Force stop UDP port forwarding server. 25 | */ 26 | void hev_ufwd_kill (void); 27 | 28 | /** 29 | * hev_ufwd_fd: 30 | * 31 | * Get socket fd of UDP port forwarding server. 32 | */ 33 | int hev_ufwd_fd (void); 34 | 35 | #endif /* __HEV_UFWD_H__ */ 36 | -------------------------------------------------------------------------------- /src/hev-unsk.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-unsk.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 - 2025 xyz 6 | Description : UDP NAT session keeper 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "hev-conf.h" 19 | #include "hev-misc.h" 20 | #include "hev-sock.h" 21 | #include "hev-stun.h" 22 | #include "hev-ufwd.h" 23 | #include "hev-xnsk.h" 24 | 25 | #include "hev-unsk.h" 26 | 27 | static struct sockaddr_storage saddr; 28 | static struct sockaddr_storage daddr; 29 | static HevTask *task; 30 | static int timeout; 31 | static unsigned int cycle; 32 | 33 | static void 34 | stun_handler (void) 35 | { 36 | const char *ufwd = hev_conf_taddr (); 37 | 38 | if (ufwd) { 39 | hev_ufwd_run ((struct sockaddr *)&saddr); 40 | } 41 | } 42 | 43 | static void 44 | unsk_keep_alive (void) 45 | { 46 | int n = 0; 47 | 48 | do { 49 | socklen_t addrlen; 50 | ssize_t res; 51 | int fd = -1; 52 | 53 | if (hev_task_sleep (timeout) > 0) { 54 | break; 55 | } 56 | 57 | fd = hev_ufwd_fd (); 58 | if ((fd < 0) || (++n >= cycle)) { 59 | hev_stun_run ((struct sockaddr *)&saddr, stun_handler); 60 | n = 0; 61 | continue; 62 | } 63 | 64 | switch (daddr.ss_family) { 65 | case AF_INET: 66 | addrlen = sizeof (struct sockaddr_in); 67 | break; 68 | case AF_INET6: 69 | default: 70 | addrlen = sizeof (struct sockaddr_in6); 71 | break; 72 | } 73 | 74 | res = sendto (fd, "k", 1, 0, (struct sockaddr *)&daddr, addrlen); 75 | if (res < 0) { 76 | LOG (E); 77 | break; 78 | } 79 | } while (timeout); 80 | } 81 | 82 | static void 83 | unsk_run (void) 84 | { 85 | const char *ufwd; 86 | const char *addr; 87 | const char *port; 88 | const char *stun; 89 | const char *sport; 90 | const char *iface; 91 | unsigned int mark; 92 | int type; 93 | int fd; 94 | 95 | type = hev_conf_type (); 96 | ufwd = hev_conf_taddr (); 97 | addr = hev_conf_baddr (); 98 | port = hev_conf_bport (); 99 | stun = hev_conf_stun (); 100 | sport = hev_conf_sport (); 101 | iface = hev_conf_iface (); 102 | mark = hev_conf_mark (); 103 | timeout = hev_conf_keep (); 104 | cycle = hev_conf_ucount (); 105 | 106 | fd = hev_sock_client_base (type, SOCK_DGRAM, addr, port, stun, sport, iface, 107 | mark, &saddr, &daddr); 108 | if (fd < 0) { 109 | LOGV (E, "%s", "Start UDP keep-alive service failed."); 110 | return; 111 | } 112 | hev_task_del_fd (hev_task_self (), fd); 113 | close (fd); 114 | 115 | hev_stun_run ((struct sockaddr *)&saddr, stun_handler); 116 | 117 | unsk_keep_alive (); 118 | 119 | if (ufwd) { 120 | hev_ufwd_kill (); 121 | } 122 | } 123 | 124 | static void 125 | task_entry (void *data) 126 | { 127 | for (;;) { 128 | unsk_run (); 129 | hev_task_sleep (5000); 130 | } 131 | } 132 | 133 | static void 134 | unsk_kill (void) 135 | { 136 | timeout = 0; 137 | if (task) { 138 | hev_task_wakeup (task); 139 | } 140 | } 141 | 142 | void 143 | hev_unsk_run (void) 144 | { 145 | hev_xnsk_init (unsk_kill); 146 | 147 | task = hev_task_new (-1); 148 | hev_task_run (task, task_entry, NULL); 149 | } 150 | -------------------------------------------------------------------------------- /src/hev-unsk.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-unsk.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : UDP NAT session keeper 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_UNSK_H__ 11 | #define __HEV_UNSK_H__ 12 | 13 | /** 14 | * hev_unsk_run: 15 | * 16 | * Run UDP client to keep-alive. 17 | */ 18 | void hev_unsk_run (void); 19 | 20 | #endif /* __HEV_UNSK_H__ */ 21 | -------------------------------------------------------------------------------- /src/hev-winc.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-winc.c 4 | Author : hev 5 | Copyright : Copyright (c) 2025 xyz 6 | Description : Functions for Windows console 7 | ============================================================================ 8 | */ 9 | 10 | #if defined(__MSYS__) 11 | 12 | #include 13 | 14 | static BOOL WINAPI 15 | ctrlHandler (DWORD signal) 16 | { 17 | if (signal == CTRL_C_EVENT) 18 | exit (STATUS_CONTROL_C_EXIT); 19 | return TRUE; 20 | } 21 | 22 | int 23 | hev_winc_setup_ctrlc (void) 24 | { 25 | return SetConsoleCtrlHandler (ctrlHandler, TRUE) ? 0 : -1; 26 | } 27 | 28 | #endif /* defined(__MSYS__) */ 29 | -------------------------------------------------------------------------------- /src/hev-winc.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-winc.h 4 | Author : hev 5 | Copyright : Copyright (c) 2025 xyz 6 | Description : Functions for Windows console 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_WINC_H__ 11 | #define __HEV_WINC_H__ 12 | 13 | /** 14 | * hev_winc_setup_ctrlc: 15 | * 16 | * Setup Ctrl-C handler for Windows console. 17 | */ 18 | int hev_winc_setup_ctrlc (void); 19 | 20 | #endif /* __HEV_WINC_H__ */ 21 | -------------------------------------------------------------------------------- /src/hev-xnsk.c: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-xnsk.c 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : NAT session keeper 7 | ============================================================================ 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include "hev-conf.h" 14 | #include "hev-misc.h" 15 | #include "hev-tnsk.h" 16 | #include "hev-unsk.h" 17 | 18 | #include "hev-xnsk.h" 19 | 20 | static void (*killer) (void); 21 | 22 | void 23 | hev_xnsk_init (void *kill) 24 | { 25 | killer = kill; 26 | } 27 | 28 | void 29 | hev_xnsk_run (void) 30 | { 31 | int mode = hev_conf_mode (); 32 | 33 | switch (mode) { 34 | case SOCK_STREAM: 35 | hev_tnsk_run (); 36 | break; 37 | case SOCK_DGRAM: 38 | hev_unsk_run (); 39 | break; 40 | default: 41 | LOG (E); 42 | } 43 | } 44 | 45 | void 46 | hev_xnsk_kill (void) 47 | { 48 | killer (); 49 | } 50 | -------------------------------------------------------------------------------- /src/hev-xnsk.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================ 3 | Name : hev-xnsk.h 4 | Author : hev 5 | Copyright : Copyright (c) 2022 xyz 6 | Description : NAT session keeper 7 | ============================================================================ 8 | */ 9 | 10 | #ifndef __HEV_XNSK_H__ 11 | #define __HEV_XNSK_H__ 12 | 13 | /** 14 | * hev_xnsk_init: 15 | * 16 | * Initialize keeper. 17 | */ 18 | void hev_xnsk_init (void *kill); 19 | 20 | /** 21 | * hev_xnsk_run: 22 | * 23 | * Run keeper. 24 | */ 25 | void hev_xnsk_run (void); 26 | 27 | /** 28 | * hev_xnsk_kill: 29 | * 30 | * Force stop keeper. 31 | */ 32 | void hev_xnsk_kill (void); 33 | 34 | #endif /* __HEV_XNSK_H__ */ 35 | --------------------------------------------------------------------------------