├── .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 |
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 | [](https://github.com/heiher/natmap)
4 | [](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 |
--------------------------------------------------------------------------------