Skip to content

Commit 178d0f4

Browse files
authored
Extended tunnel and added a cli subcommand for it (#5)
* feat: Extended tunnel and added a cli subcommand for it - Added `aesgcm_conn.go` to provide AES-GCM encryption for net.Conn. - Introduced `NewAESGCMConn` function for creating encrypted connections. - Implemented Read and Write methods for encrypted data transmission. - Added tests in `aesgcm_conn_test.go` to validate encryption and decryption functionality. - Created a command-line tool in `cmd/netx` for establishing secure tunnels with chainable transforms. - Implemented UDP and TCP echo servers and clients for end-to-end testing in `internal/tools/e2e`. - Enhanced logging and error handling throughout the codebase. - Updated `.gitignore` to exclude build artifacts and temporary files. * feat: add SSH and uTLS tun support in cli feat: Implemented SSH connection management in ssh_conn.go, allowing for direct channel handling over SSH. * feat: Implement URI handling with layered transport options - Added `listener` struct to manage connections with URI layers. - Introduced `Layers` and `Layer` types to support multiple connection layers. - Implemented `Wrap` method for `Layers` to wrap connections with specified layers. - Created `Scheme` type to encapsulate transport and layers for URIs. - Defined `Transport` type with TCP and UDP options. - Developed `URI` type to represent a URI with scheme and address. - Implemented marshaling and unmarshaling for `Layers`, `Scheme`, `Transport`, and `URI`. - Added support for various connection layers including SSH, TLS, DTLS, and PSK. - Included error handling for invalid parameters and missing keys in layer configurations. * fix: lint * feat: Add JSON marshaling and unmarshaling for Layers and update URI and Scheme structs * fix: Update parameter names and improve documentation in README and URI format * fix: Update parameter names in README and URI format for consistency
1 parent 57dab31 commit 178d0f4

File tree

25 files changed

+1989
-47
lines changed

25 files changed

+1989
-47
lines changed

.github/workflows/lint_and_test.yml renamed to .github/workflows/lint_test_and_build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ jobs:
2525
- run: task deps
2626
- run: task lint
2727
- run: task test
28+
- run: task e2e:tun

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/.task/
2+
/build/
3+
4+
# e2e working directory created by Task e2e:tun
5+
/.e2e/

README.md

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Notes:
4444
rawClient, rawServer := net.Pipe()
4545
defer rawClient.Close(); defer rawServer.Close()
4646

47-
client := netx.NewFramedConn(rawClient) // default max frame size 16KiB
47+
client := netx.NewFramedConn(rawClient) // default max frame size 32KiB
4848
server := netx.NewFramedConn(rawServer, netx.WithMaxFrameSize(64<<10))
4949

5050
msg := []byte("hello frame")
@@ -153,10 +153,84 @@ If `Logger` is nil, the server/tunnel use `slog.Default()`.
153153
- Unhandled connections are dropped immediately after all routes decline.
154154
- `Shutdown(ctx)` will close listeners, then wait for tracked connections until `ctx` is done, after which remaining connections are force‑closed.
155155

156-
## Testing
156+
## CLI
157157

158-
The repository includes unit and end‑to‑end tests (UDP over TCP, TLS routing, graceful shutdown). Run:
158+
An extendable CLI is available at `cmd/netx` with an initial `tun` subcommand to relay between chainable endpoints.
159+
160+
Build:
159161

160162
```bash
161-
go test ./...
163+
task build
162164
```
165+
166+
Install and use:
167+
168+
```bash
169+
go install github.com/pedramktb/go-netx/cmd/netx@latest
170+
171+
# Show help
172+
netx tun -h
173+
174+
# Example: TCP TLS server to TCP TLS+buffered+framed+aesgcm client
175+
netx tun \
176+
--from tcp+tls[cert=server.crt,key=server.key]://:9000 \
177+
--to tcp+tls[cert=client.crt]+buffered[size=8192]+framed[maxsize=4096]+aesgcm[key=00112233445566778899aabbccddeeff]://example.com:9443
178+
179+
# Example: UDP DTLS server to UDP aesgcm client
180+
netx tun \
181+
--from udp+dtls[cert=server.crt,key=server.key]://:4444 \
182+
--to udp+aesgcm[key=00112233445566778899aabbccddeeff]://10.0.0.10:5555
183+
```
184+
185+
Options:
186+
187+
- `--from <chain>://listenAddr` - Incoming side chain URI (required)
188+
- `--to <chain>://connectAddr` - Peer side chain URI (required)
189+
- `--log <level>` - Log level: debug|info|warn|error (default: info)
190+
- `-h` - Show help
191+
192+
Chain syntax:
193+
194+
Chains use the form `<chain>://host:port` where `<chain>` is a `+`-separated list starting with a base transport (`tcp` or `udp`), optionally followed by wrappers with parameters in brackets.
195+
196+
**Supported base transports:**
197+
198+
- `tcp` - TCP listener or dialer
199+
- `udp` - UDP listener or dialer
200+
201+
**Supported wrappers:**
202+
203+
- `tls` - Transport Layer Security
204+
- Server params: `cert`, `key`
205+
- Client params: `cert` (optional, for SPKI pinning), `servername` (required if cert not provided)
206+
207+
- `utls` - TLS with client fingerprint camouflage via uTLS
208+
- Client-side only
209+
- Params: `cert` (optional, for SPKI pinning), `servername` (required if cert not provided), `hello` (optional: chrome, firefox, ios, android, safari, edge, randomized, randomizednoalpn; default: chrome)
210+
211+
- `dtls` - Datagram Transport Layer Security
212+
- Server params: `cert`, `key`
213+
- Client params: `cert` (optional, for SPKI pinning), `servername` (required if cert not provided)
214+
215+
- `tlspsk` - TLS with pre-shared key (TLS 1.2, cipher: TLS_PSK_WITH_AES_256_CBC_SHA)
216+
- Params: `key`, `identity`
217+
218+
- `dtlspsk` - DTLS with pre-shared key (cipher: TLS_PSK_WITH_AES_128_GCM_SHA256)
219+
- Params: `key`, `identity`
220+
221+
- `aesgcm` - AES-GCM encryption with passive IV exchange
222+
- Params: `key`, `maxpacket` (optional, default: 32768)
223+
224+
- `buffered` - Buffered read/write for better performance
225+
- Params: `size` (optional, default: 4096)
226+
227+
- `framed` - Length-prefixed frames for packet semantics over streams
228+
- Params: `maxsize` (optional, default: 32768)
229+
230+
- `ssh` - SSH tunneling via "direct-tcpip" channels
231+
- Server params: `key` (optional, required with pass), `pass` (optional), `pubkey` (optional, required if no pass)
232+
- Client params: `pubkey`, `pass` (optional), `key` (optional, required if no pass)
233+
234+
**Notes:**
235+
- All passwords, keys and certificates must be provided as hex-encoded strings.
236+
- When using `cert` for client-side `tls`/`utls`/`dtls`, default validation is disabled and a manual SPKI (SubjectPublicKeyInfo) hash comparison is performed against the provided certificate. This is certificate pinning and will fail if the server presents a different key.

Taskfile.yml

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ version: '3'
22

33
tasks:
44
default:
5-
cmds:
5+
cmds:
66
- task --list
77

88
deps:
@@ -20,3 +20,154 @@ tasks:
2020
desc: Run tests
2121
cmds:
2222
- go test ./...
23+
24+
build:
25+
desc: Build binaries and libraries
26+
cmds:
27+
# Linux binaries and shared libraries
28+
- env GOOS=linux GOARCH=amd64 go build -o build/netx_linux_x64 cmd/netx/*.go
29+
- env GOOS=linux GOARCH=arm64 go build -o build/netx_linux_arm64 cmd/netx/*.go
30+
# - env GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-gnu-gcc go build -buildmode=c-shared -o build/libnetx_linux_x64.so cmd/netx/lib/main.go
31+
# - env GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build -buildmode=c-shared -o build/libnetx_linux_arm64.so cmd/netx/lib/main.go
32+
# # Windows binaries and shared libraries
33+
- env GOOS=windows GOARCH=amd64 go build -o build/netx_windows_x64.exe cmd/netx/*.go
34+
- env GOOS=windows GOARCH=arm64 go build -o build/netx_windows_arm64.exe cmd/netx/*.go
35+
# - env GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -buildmode=c-shared -o build/libnetx_windows_x64.dll cmd/netx/lib/main.go
36+
# # aarch64-w64-mingw32-gcc is experimental and not available
37+
# # - env GOOS=windows GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-w64-mingw32-gcc go build -buildmode=c-shared -o build/libnetx_windows_arm64.dll cmd/lib/main.go
38+
# # macOS binaries
39+
- env GOOS=darwin GOARCH=amd64 go build -o build/netx_macos_x64 cmd/netx/*.go
40+
- env GOOS=darwin GOARCH=arm64 go build -o build/netx_macos_arm64 cmd/netx/*.go
41+
# # Android shared libraries
42+
# - env GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android26-clang go build -buildmode=c-shared -o build/libnetx_android_x64.so cmd/netx/lib/main.go
43+
# - env GOOS=android GOARCH=arm64 CGO_ENABLED=1 CC=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang go build -buildmode=c-shared -o build/libnetx_android_arm64.so cmd/netx/lib/main.go
44+
sources:
45+
- '**/*.go'
46+
- go.mod
47+
- go.sum
48+
generates:
49+
- build/netx_linux_x64
50+
- build/netx_linux_arm64
51+
- build/libnetx_linux_x64.so
52+
- build/libnetx_linux_arm64.so
53+
- build/netx_windows_x64.exe
54+
- build/netx_windows_arm64.exe
55+
- build/libnetx_windows_x64.dll
56+
- build/netx_macos_x64
57+
- build/netx_macos_arm64
58+
- build/libnetx_android_x64.so
59+
- build/libnetx_android_arm64.so
60+
61+
build-apple-libs:
62+
desc: Build Apple libraries (optional, requires mac toolchains)
63+
cmds:
64+
- env GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -buildmode=c-shared -o build/libnetx_macos_x64.dylib cmd/netx/lib/main.go
65+
- env GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -buildmode=c-shared -o build/libnetx_macos_arm64.dylib cmd/netx/lib/main.go
66+
- |
67+
mkdir -p build
68+
export CC=$(xcrun -find -sdk iphonesimulator clang)
69+
export CXX=$(xcrun -find -sdk iphonesimulator clang++)
70+
export SDKROOT=$(xcrun --sdk iphonesimulator --show-sdk-path)
71+
export CFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=10.0"
72+
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -mios-simulator-version-min=10.0"
73+
env GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -buildmode=c-archive -o build/libnetx_ios_x64.a cmd/netx/lib/main.go
74+
- |
75+
mkdir -p build
76+
export CC=$(xcrun -find -sdk iphoneos clang)
77+
export CXX=$(xcrun -find -sdk iphoneos clang++)
78+
export SDKROOT=$(xcrun --sdk iphoneos --show-sdk-path)
79+
export CFLAGS="-arch arm64 -isysroot $SDKROOT -mios-version-min=10.0"
80+
export LDFLAGS="-arch arm64 -isysroot $SDKROOT -mios-version-min=10.0"
81+
env GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build -buildmode=c-archive -o build/libnetx_ios_arm64.a cmd/netx/lib/main.go
82+
sources:
83+
- '**/*.go'
84+
- go.mod
85+
- go.sum
86+
generates:
87+
- build/libnetx_macos_x64.dylib
88+
- build/libnetx_macos_arm64.dylib
89+
- build/libnetx_ios_x64.a
90+
- build/libnetx_ios_arm64.a
91+
92+
e2e:tun:
93+
desc: Run CLI end-to-end tun tests locally (uses .e2e working dir).
94+
cmds:
95+
- |
96+
set -euo pipefail
97+
ROOT=$(pwd)
98+
WORK=.e2e
99+
mkdir -p "$WORK"
100+
101+
echo "Building netx binary..."
102+
go build -o "$WORK/netx" ./cmd/netx
103+
chmod +x "$WORK/netx"
104+
cd "$WORK"
105+
106+
echo "Generating certs and keys..."
107+
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 1 -nodes -subj "/CN=localhost" >/dev/null 2>&1
108+
openssl rand -hex 32 > psk.hex
109+
cp psk.hex aes.hex
110+
echo y | ssh-keygen -t ed25519 -f ssh_server_key -N "" -C "e2e-server" >/dev/null 2>&1
111+
echo y | ssh-keygen -t ed25519 -f ssh_client_key -N "" -C "e2e-client" >/dev/null 2>&1
112+
113+
echo "Building echo servers and clients..."
114+
go build -tags e2e -o tcp_echo "$ROOT/internal/tools/e2e/tcp_echo"
115+
go build -tags e2e -o udp_echo "$ROOT/internal/tools/e2e/udp_echo"
116+
go build -tags e2e -o tcp_client "$ROOT/internal/tools/e2e/tcp_client"
117+
go build -tags e2e -o udp_client "$ROOT/internal/tools/e2e/udp_client"
118+
119+
# Use isolated ports to avoid conflicts with external demos
120+
TE=48080; UE=48081
121+
STLS=49000; SDTLS=49100; SDTLSP=49300; SAESCT=49400; SAESCU=49500; SFR=49600; STLSPSK=49200; SSSH=49700; SUTLS=49800
122+
CTLS=50000; CDTLS=50010; CDTLSP=50011; CAESCT=50002; CAESCU=50012; CFR=50003; CTLSPSK=50001; CSSH=50004; CUTLS=50005
123+
124+
echo "Starting echo servers..."
125+
./tcp_echo 127.0.0.1:${TE} > tcp_echo.log 2>&1 &
126+
./udp_echo 127.0.0.1:${UE} > udp_echo.log 2>&1 &
127+
128+
echo "Starting server tunnels..."
129+
./netx tun --from "tcp+tls[cert=$(xxd -p server.crt | tr -d '\n'),key=$(xxd -p server.key | tr -d '\n')]://127.0.0.1:${STLS}" --to "tcp://127.0.0.1:${TE}" --log info > tls_server.log 2>&1 &
130+
./netx tun --from "udp+dtls[cert=$(xxd -p server.crt | tr -d '\n'),key=$(xxd -p server.key | tr -d '\n')]://127.0.0.1:${SDTLS}" --to "udp://127.0.0.1:${UE}" --log info > dtls_server.log 2>&1 &
131+
./netx tun --from "udp+dtlspsk[identity=i,key=$(cat psk.hex)]://127.0.0.1:${SDTLSP}" --to "udp://127.0.0.1:${UE}" --log info > dtlspsk_server.log 2>&1 &
132+
./netx tun --from "tcp+buffered[size=8192]+framed[maxsize=4096]+aesgcm[key=$(cat aes.hex)]://127.0.0.1:${SAESCT}" --to "tcp://127.0.0.1:${TE}" --log info > aesgcm_tcp_server.log 2>&1 &
133+
./netx tun --from "udp+aesgcm[key=$(cat aes.hex)]://127.0.0.1:${SAESCU}" --to "udp://127.0.0.1:${UE}" --log info > aesgcm_udp_server.log 2>&1 &
134+
./netx tun --from "tcp+framed[maxsize=4096]://127.0.0.1:${SFR}" --to "udp://127.0.0.1:${UE}" --log info > framed_tcp_server.log 2>&1 &
135+
./netx tun --from "tcp+tlspsk[identity=i,key=$(cat psk.hex)]://127.0.0.1:${STLSPSK}" --to "tcp://127.0.0.1:${TE}" --log info > tlspsk_server.log 2>&1 &
136+
./netx tun --from "tcp+ssh[key=$(xxd -p ssh_server_key | tr -d '\n'),pubkey=$(xxd -p ssh_client_key.pub | tr -d '\n')]://127.0.0.1:${SSSH}" --to "tcp://127.0.0.1:${TE}" --log info > ssh_server.log 2>&1 &
137+
./netx tun --from "tcp+tls[cert=$(xxd -p server.crt | tr -d '\n'),key=$(xxd -p server.key | tr -d '\n')]://127.0.0.1:${SUTLS}" --to "tcp://127.0.0.1:${TE}" --log info > utls_server.log 2>&1 &
138+
139+
echo "Starting client tunnels..."
140+
./netx tun --from "tcp://127.0.0.1:${CTLS}" --to "tcp+tls[cert=$(xxd -p server.crt | tr -d '\n')]://127.0.0.1:${STLS}" --log info > tls_client.log 2>&1 &
141+
./netx tun --from "udp://127.0.0.1:${CDTLS}" --to "udp+dtls[cert=$(xxd -p server.crt | tr -d '\n')]://127.0.0.1:${SDTLS}" --log info > dtls_client.log 2>&1 &
142+
./netx tun --from "udp://127.0.0.1:${CDTLSP}" --to "udp+dtlspsk[identity=i,key=$(cat psk.hex)]://127.0.0.1:${SDTLSP}" --log info > dtlspsk_client.log 2>&1 &
143+
./netx tun --from "tcp://127.0.0.1:${CAESCT}" --to "tcp+buffered[size=8192]+framed[maxsize=4096]+aesgcm[key=$(cat aes.hex)]://127.0.0.1:${SAESCT}" --log info > aesgcm_tcp_client.log 2>&1 &
144+
./netx tun --from "udp://127.0.0.1:${CAESCU}" --to "udp+aesgcm[key=$(cat aes.hex)]://127.0.0.1:${SAESCU}" --log info > aesgcm_udp_client.log 2>&1 &
145+
./netx tun --from "udp://127.0.0.1:${CFR}" --to "tcp+framed[maxsize=4096]://127.0.0.1:${SFR}" --log info > framed_tcp_client.log 2>&1 &
146+
./netx tun --from "tcp://127.0.0.1:${CTLSPSK}" --to "tcp+tlspsk[identity=i,key=$(cat psk.hex)]://127.0.0.1:${STLSPSK}" --log info > tlspsk_client.log 2>&1 &
147+
./netx tun --from "tcp://127.0.0.1:${CSSH}" --to "tcp+ssh[pubkey=$(xxd -p ssh_server_key.pub | tr -d '\n'),key=$(xxd -p ssh_client_key | tr -d '\n')]://127.0.0.1:${SSSH}" --log info > ssh_client.log 2>&1 &
148+
./netx tun --from "tcp://127.0.0.1:${CUTLS}" --to "tcp+utls[cert=$(xxd -p server.crt | tr -d '\n'),hello=chrome]://127.0.0.1:${SUTLS}" --log info > utls_client.log 2>&1 &
149+
150+
sleep 2
151+
152+
echo "Running tests..."
153+
pass=0; fail=0
154+
run_tcp(){ name=$1; addr=$2; msg=$3; out=$(./tcp_client "$addr" "$msg" || true); if [ "$out" = "$msg" ]; then echo "PASS $name"; pass=$((pass+1)); else echo "FAIL $name -> got: $out"; fail=$((fail+1)); fi }
155+
run_udp(){ name=$1; addr=$2; msg=$3; out=$(./udp_client "$addr" "$msg" || true); if [ "$out" = "$msg" ]; then echo "PASS $name"; pass=$((pass+1)); else echo "FAIL $name -> got: $out"; fail=$((fail+1)); fi }
156+
run_tcp TLS 127.0.0.1:${CTLS} hello_tls
157+
run_udp DTLS 127.0.0.1:${CDTLS} hello_dtls
158+
run_udp DTLSPSK 127.0.0.1:${CDTLSP} hello_dtlspsk
159+
run_tcp AESGCM_TCP 127.0.0.1:${CAESCT} hello_aesgcm_tcp
160+
run_udp AESGCM_UDP 127.0.0.1:${CAESCU} hello_aesgcm_udp
161+
run_udp FRAMED_TCP_BR 127.0.0.1:${CFR} hello_udp_over_tcp
162+
run_tcp SSH 127.0.0.1:${CSSH} hello_ssh
163+
run_tcp UTLS 127.0.0.1:${CUTLS} hello_utls
164+
run_tcp TLSPSK 127.0.0.1:${CTLSPSK} hello_tlspsk
165+
echo "RESULTS: pass=$pass fail=$fail"
166+
167+
echo "Cleaning up..."
168+
pkill netx || true
169+
pkill tcp_echo || true
170+
pkill udp_echo || true
171+
172+
echo "Done."
173+
[ "$fail" -eq 0 ]

0 commit comments

Comments
 (0)