Compare commits

...

209 Commits

Author SHA1 Message Date
ginuerzh e2447ce578 fix(e2e): use host network for Docker image builds in DinD environments
Docker-in-Docker containers cannot reach the internet via the default
bridge network, causing 'apk add' in the Dockerfile to hang indefinitely
and e2e tests to timeout after 10 minutes.

Add BuildOptionsModifier with NetworkMode=host to all FromDockerfile
definitions so the build step uses the host's network stack. This has
no negative impact on non-DinD environments where bridge networking
already works.

Also move GostBinPath flag and init() from main_test.go to utils.go
(non-test file) so the symbol is accessible across the package.
2026-06-06 20:34:24 +08:00
ginuerzh 811420e923 chore: bump x to include stateless UDP forwarding (#853) 2026-06-05 23:27:22 +08:00
ginuerzh 374b46dfe1 bump x to v0.10.10 2026-06-03 23:30:01 +08:00
ginuerzh 22edb92084 bump x to v0.10.10 2026-06-03 23:25:54 +08:00
ginuerzh 78a0a8c734 fix: disable UPX compression in Dockerfile to resolve #863 startup regression 2026-06-03 00:13:58 +08:00
ZHAO Jin-Xiang 41e1878ebc
feat: add nftables package to Dockerfile (#865)
nftables is a more modern iptables which supports both ipv4 and ipv6.
2026-06-02 23:56:53 +08:00
ginuerzh 5639c90e98 bump x to v0.10.9 2026-06-02 23:54:13 +08:00
ginuerzh 36abf9bcd9 bump x to v0.10.8 2026-06-02 20:07:48 +08:00
ginuerzh d584b7ac61 bump x to v0.10.7 2026-05-31 22:49:10 +08:00
ginuerzh 56e2a1c496 fix: disable UPX compression to resolve #863 startup regression
UPX --best/--lzma/--brute adds ~3s startup time on low-spec systems
(linux/arm) due to in-memory decompression on every invocation of gost -V.
Disabled by default; opt in via GORELEASER_UPX=true for release builds
that need it.
2026-05-31 22:25:57 +08:00
ginuerzh b0bea19275 bump x to v0.10.6 2026-05-31 22:13:42 +08:00
ginuerzh cd64f2edd3 bump x to v0.10.5 2026-05-31 19:50:49 +08:00
ginuerzh 5f04c84e32 bump core to v0.4.1 2026-05-31 19:40:28 +08:00
ginuerzh 600a64e611 bump x to v0.10.4 2026-05-31 17:47:52 +08:00
ginuerzh 3d6b16686b bump x to v0.10.3 2026-05-29 00:29:26 +08:00
ginuerzh 44684d40c3 add CLAUDE.md with build, CLI, and lifecycle documentation 2026-05-23 00:00:56 +08:00
ginuerzh 2be36abe75 bump x to v0.10.1, go-shadowsocks2 to v0.1.3; add gost binary to .gitignore 2026-05-22 23:21:30 +08:00
ginuerzh 8db62785fa Merge pull request #862: Add e2e test cases for shadowsocks
Add comprehensive e2e tests covering TCP and UDP shadowsocks
connections with various cipher modes (aes-128-gcm, aes-256-gcm,
chacha20, 2022-blake3-aes-128, 2022-blake3-aes-256, including
multi-PSK variants).
2026-05-22 23:09:42 +08:00
RMT 8740e6f258
Add e2e test cases for shadowsocks 2026-05-22 14:28:17 +08:00
ginuerzh d4c9ef5056 add masque connector, dialer, and handler registrations; update x to v0.10.0 2026-05-21 23:03:49 +08:00
RMT c06eb0d331 WIP 2026-04-26 22:18:07 +08:00
RMT c8b48dc248 WIP 2026-04-26 22:18:07 +08:00
azoway 45d94cf391 Update install.sh
在alpine服务器上运行安装脚本报错:
curl: (3) URL rejected: Malformed input to a URL function

排查后发现,当前版本部分会匹配出来2个版本的下载链接:
gost_3.2.6_linux_amd64.tar.gz
gost_3.2.6_linux_amd64v3.tar.gz

修改后默认取第一个兼容性更好的版本
2026-04-26 22:16:06 +08:00
nivesh bc96fe3918 streamline download/install process
(get binary only, eliminate unnecessary intermediate files)
2026-04-22 00:25:59 +08:00
nivesh 646d3f906c enhance portability (detect user-preferred bash, replace all greps with awk) 2026-04-22 00:25:59 +08:00
nivesh 4c69940f1e Fix #835 2026-04-22 00:25:59 +08:00
David Manouchehri 3d1f6fcbbb add masque connector, dialer, and handler 2026-04-22 00:23:53 +08:00
ginuerzh e388426ec6 go1.26 2026-04-21 23:59:31 +08:00
ginuerzh 340ba32ef0 v3.2.6 2025-11-22 22:47:02 +08:00
ginuerzh 96551d5fa5 metrics: fix server conn wrapper (#797) 2025-10-11 22:07:41 +08:00
ginuerzh 8d05a6ed93 add multiple entrypoints for tunnel 2025-10-09 22:33:31 +08:00
ginuerzh 0348a16aa9 fix panic for channel close (#779) 2025-09-20 10:06:15 +08:00
ginuerzh 50934e0978 fix sniffing for websocket 2025-09-04 21:32:34 +08:00
ginuerzh c2ed9c6f07 add service option for plugin 2025-08-29 23:38:29 +08:00
ginuerzh 3b9da4e260 go1.25 2025-08-21 22:53:28 +08:00
ginuerzh f0a67a1108 fix bugs 2025-08-21 22:24:42 +08:00
ginuerzh 59c9638ce6 add bypass and admission matcher 2025-08-13 21:27:36 +08:00
ginuerzh 49fa28882f fix issue #760 2025-08-10 19:01:10 +08:00
ginuerzh 08c617b54e v3.2.3 2025-08-09 21:01:25 +08:00
ginuerzh d03b0e2360 v3.2.2 2025-08-05 20:46:21 +08:00
ginuerzh 245d610baf without cancel context 2025-08-05 20:25:24 +08:00
ginuerzh f01f0c9215 fix ws listener 2025-08-05 00:19:51 +08:00
ginuerzh 9b6e9d9cae add context for conn 2025-08-04 19:34:54 +08:00
ginuerzh 1a0e2b06ac add proxyProtocol support for dialer 2025-08-03 15:38:57 +08:00
ginuerzh de8bb498db http2: added non-connect request support #749 2025-07-30 21:44:46 +08:00
ginuerzh 1f735d0649 v3.2.0 2025-07-29 09:15:55 +08:00
ginuerzh d173d167da fix tungo for windows and darwin 2025-07-28 21:21:33 +08:00
ginuerzh b593fdb952 add upx for binary compression 2025-07-26 16:43:49 +08:00
ginuerzh d448628fea add tun2socks (tungo) 2025-07-26 16:43:22 +08:00
ginuerzh 0bfc7f10cd add vtun handle and listener 2025-07-23 22:49:59 +08:00
ginuerzh a24b29c8c2 get real client IP from request 2025-07-15 20:21:52 +08:00
ginuerzh 87af3da1b6 add src field for logger and recorder 2025-07-02 21:49:03 +08:00
ginuerzh dc6fe595e0 read config from stdin #701 2025-06-28 18:20:28 +08:00
ginuerzh db4a0a9259 goreleaser: added linux/loong64 2025-06-28 18:13:45 +08:00
Meng Zhuo 62a097c5e4 enable riscv64 in install.sh 2025-06-27 23:36:31 +08:00
ginuerzh b51cecbbfd add new web APIs 2025-06-25 21:31:22 +08:00
ginuerzh 66a260abd1 fix metrics 2025-06-24 21:23:00 +08:00
ginuerzh 1d3603f9ed
Merge pull request #713 from ravitheg/FixSSHVulnerability
Fix ssh vulnerability
2025-05-30 15:21:37 +08:00
ravitheg 952c61c1a2
Update go.sum 2025-05-27 14:09:22 -07:00
ravitheg 6d61ab82db
Update go.mod 2025-05-27 14:08:56 -07:00
ginuerzh f61bb2fd72 revert to go1.23 2025-02-18 21:43:03 +08:00
ginuerzh 4c2835db04 go1.24 2025-02-18 21:16:47 +08:00
ginuerzh d662b1fd46 router handler: add cache for sd service 2025-02-09 16:19:40 +08:00
ginuerzh 015923fa5d add entrypoint for router handler 2025-02-08 23:11:18 +08:00
ginuerzh f66fce9d49 feat: add router handler & connector 2025-02-07 15:51:51 +08:00
ginuerzh 982a93eee4 add limiter.scope option for service 2025-01-20 23:27:04 +08:00
ginuerzh 758251d3f9 update go.mod 2025-01-10 21:50:18 +08:00
ginuerzh a1e48e164b update config parsing 2025-01-10 21:41:55 +08:00
ginuerzh 581dd74bbb add config hot reload 2025-01-08 23:16:55 +08:00
ginuerzh 3c7a984aa3 v3.0.0 2024-12-31 20:19:49 +08:00
ginuerzh 2a9b7e7d9b fix panic for stats wrapper 2024-12-27 20:43:42 +08:00
ginuerzh ab2b9714ee add traffic reset for stats observer 2024-12-24 20:13:40 +08:00
ginuerzh 6d38c24d88 handle tls for tunnel entrypoint 2024-12-23 21:35:58 +08:00
ginuerzh f376db2000 fix issue #625 2024-12-18 19:06:59 +08:00
ginuerzh 161d96f582 fix observer for http2 2024-12-16 20:49:41 +08:00
ginuerzh 128c0c3117 update go.sum 2024-12-11 22:31:42 +08:00
ginuerzh 66ce980c36 fix webtransport 2024-12-11 22:28:31 +08:00
ginuerzh 829148d3f8 fix ipv6 port for redirect 2024-12-11 22:05:30 +08:00
ginuerzh c45f148a88 add unix domain socket support for api & metrics services 2024-11-22 22:51:16 +08:00
ginuerzh fdc88af4ac fix http keepalive for forwarding 2024-11-17 16:17:31 +08:00
ginuerzh c41c5bf79a add routing rule for node 2024-11-15 20:30:35 +08:00
ginuerzh b3eeb338fb add custom http response header for reverse proxy 2024-11-07 11:08:38 +08:00
ginuerzh 72f185bbf6 add compression flag option for http request 2024-11-02 20:43:44 +08:00
ginuerzh a7599990a8 update deps 2024-11-01 18:19:23 +08:00
ginuerzh 17ca5600d3 update sample rate for websocket recording 2024-10-31 19:16:12 +08:00
ginuerzh 32ccfe483c fix udp port forwarding 2024-10-22 22:57:18 +08:00
ginuerzh 1d0eb06938 sniffing: fix race condition when sniffing websocket frames 2024-10-20 19:24:40 +08:00
ginuerzh 1fa5067d14 sniffing: full websocket frame recording 2024-10-19 20:41:29 +08:00
ginuerzh 7ddfef85a5 sniffing websocket frame 2024-10-19 19:37:21 +08:00
ginuerzh fa98777ab1 fix handler sniffing 2024-10-16 23:03:27 +08:00
ginuerzh e53486efe0 add more fields for log 2024-10-16 22:17:54 +08:00
ginuerzh 881ebfad5b add ClientAddr for websocket conn 2024-10-16 20:48:07 +08:00
ginuerzh d5bb691b38 tunnel: fix http switching protocols 2024-10-15 19:38:43 +08:00
ginuerzh 24c13b861a update tunnel entrypoint 2024-10-15 18:49:21 +08:00
ginuerzh d6daa8bf56 update sniffer for forward & tunnel handler 2024-10-14 23:10:58 +08:00
ginuerzh d18539ef66 recorder: add input/output traffic stats for handler recorder object 2024-10-11 20:39:29 +08:00
ginuerzh 48b0a690c5 update Dockerfile 2024-10-11 13:10:15 +08:00
ginuerzh a7037a27bf update Dockerfile 2024-10-11 11:28:06 +08:00
ginuerzh ac9960de6d update Dockerfile 2024-10-11 00:48:47 +08:00
ginuerzh 404e099907 update github actions 2024-10-11 00:05:00 +08:00
ginuerzh 94cd590142 add keepalive option for http handler 2024-10-10 22:52:04 +08:00
ginuerzh d3809ec634 add sniffer utility for handler traffic sniffing 2024-10-02 22:53:07 +08:00
ginuerzh efb4da6fa6 add traffic sniffing for handler 2024-09-27 20:54:24 +08:00
ginuerzh 2ec8584e96 add route field for reocrder 2024-09-24 20:25:10 +08:00
ginuerzh f05f5cf862 add tls handshake for recorder 2024-09-20 21:28:09 +08:00
ginuerzh bf3018b5b1 add http body for handler recorder 2024-09-16 19:46:24 +08:00
ginuerzh 073af6163e fix recorder clientIP 2024-09-15 18:38:55 +08:00
ginuerzh ef07551e01 add clientIP for handler recorder object 2024-09-15 18:30:02 +08:00
ginuerzh 8077c039f3 update handler recorder object 2024-09-15 12:14:01 +08:00
ginuerzh 33c8f3595a add recorder for handler 2024-09-14 23:24:17 +08:00
ginuerzh 87bff8cac6 add sniffing.fallback option for red handler 2024-09-04 22:46:03 +08:00
ginuerzh d52fcccf06 add scope parameter for traffic limiter 2024-08-31 09:42:03 +08:00
ginuerzh 18610d4608 fix timeout in router 2024-08-06 21:35:19 +08:00
ginuerzh c1de9e4e72 close udp connection when timeout 2024-08-06 18:35:26 +08:00
ginuerzh 1f7c080043 add riscv64 in buildx 2024-08-01 22:05:15 +08:00
ginuerzh b091f72900 fix issue #546 2024-08-01 20:54:21 +08:00
ginuerzh 8e50e9c085 fix ssu handler port exhaustion 2024-07-31 21:03:59 +08:00
ginuerzh 7a35d25c5c add http body rewrite for forward handler 2024-07-19 20:47:01 +08:00
ginuerzh 8e311e7891 add support for icmpv6 2024-07-15 20:42:30 +08:00
ginuerzh 17fc806c2d fix ipv6 for tproxy 2024-07-11 22:39:15 +08:00
ginuerzh b21fd4a22b fix port range parsing 2024-07-10 23:00:06 +08:00
ginuerzh 3e8a025c45 update go.sum 2024-07-08 22:54:09 +08:00
ginuerzh 0d7a6f8091 add port range support for service 2024-07-08 22:52:01 +08:00
ginuerzh 4a2a2a00de update go.sum 2024-07-04 23:12:01 +08:00
ginuerzh c71e128fec bump deps 2024-07-04 23:09:05 +08:00
ginuerzh 1400b5ff5d remove content-length header for http proxy response 2024-06-25 22:26:11 +08:00
ginuerzh cd4fad8c08 add dialTimeout option for service 2024-06-25 21:44:39 +08:00
ginuerzh 56a68ae06f fix netns for socks5 and relay handler 2024-06-24 21:42:37 +08:00
ginuerzh 5d7100f749 add linux network namespace support for listener and dialer 2024-06-21 23:41:27 +08:00
ginuerzh 12871a4ff3 update README.md 2024-06-18 20:59:46 +08:00
ginuerzh 6130bbc075 update go.mod 2024-06-13 23:29:34 +08:00
ginuerzh 85d4fcaf7d add observePeriod option for observer 2024-06-13 22:45:28 +08:00
ginuerzh 7904e571c1 fix #498 2024-06-11 21:58:48 +08:00
ginuerzh 8253c41de6 fix marker for selector 2024-06-07 22:43:48 +08:00
ginuerzh 2148d10def
Merge pull request #488 from cgroschupp/update-mod
remove unnecessary replace and update gost modules
2024-05-31 21:25:47 +08:00
Christian Groschupp 8846c69c05 remove unnecessary replace and update gost modules 2024-05-29 18:40:40 +02:00
ginuerzh 2161b97064 add p2p option for tun handler 2024-04-26 20:58:27 +08:00
ginuerzh e245526b33 update README.md 2024-04-25 22:56:57 +08:00
ginuerzh 60389894c0 update go.mod 2024-04-25 21:50:49 +08:00
dependabot[bot] 9ab142ac99 Bump golang.org/x/net from 0.19.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.19.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.19.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 23:19:10 +08:00
ginuerzh be1b0627d1
Merge pull request #451 from jalalsaberi/master
Add Chinese and English README Button
2024-04-24 23:18:33 +08:00
dependabot[bot] f4115b867d Bump github.com/quic-go/quic-go from 0.40.1 to 0.42.0
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.40.1 to 0.42.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.40.1...v0.42.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 23:11:03 +08:00
kLiHz a513417d7e fixup moving to Docker actions 2024-04-24 23:10:14 +08:00
kLiHz 58bd3d062b bump `setup-go` in workflow 2024-04-24 23:10:14 +08:00
kLiHz d2b79c13a6 move to Docker actions since it is archived 2024-04-24 23:10:14 +08:00
kLiHz fedd7a7b07 fixup bumping `ghaction-docker-buildx` 2024-04-24 23:10:14 +08:00
kLiHz 27f4b6abeb bump `ghaction-docker-buildx` in workflow 2024-04-24 23:10:14 +08:00
kLiHz d4aadbbc14 bump `delete-older-releases` in workflow 2024-04-24 23:10:14 +08:00
kLiHz 4508747635 fix deprecated `set-output` command in workflow 2024-04-24 23:10:14 +08:00
dependabot[bot] 50844821f8 Bump google.golang.org/protobuf from 1.31.0 to 1.33.0
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 23:09:36 +08:00
Jalal Saberi 29ec7c94ad
Add Chinese and English README Button 2024-04-10 19:09:02 +03:30
Jalal Saberi 53d6706b5d
Add Chinese and English README Button 2024-04-10 18:53:53 +03:30
ginuerzh 594418db53 added url path rewriting for forwarder node 2024-01-31 23:20:35 +08:00
ginuerzh 72cd708dfe fix tunnel connector selection 2024-01-28 18:57:34 +08:00
ginuerzh b576bb5645 fix tunnel weight 2024-01-28 18:34:44 +08:00
ginuerzh 8c529e3595 add weight for tunnel connector 2024-01-27 23:32:50 +08:00
ginuerzh b75e133719 fix hop http plugin 2024-01-27 21:33:24 +08:00
dependabot[bot] abfbf23842 Bump github.com/quic-go/quic-go from 0.40.0 to 0.40.1
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.40.0 to 0.40.1.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.40.0...v0.40.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-18 17:33:31 +08:00
ginuerzh 42d62550bf fix deadlock in websocket client conn 2024-01-12 23:50:27 +08:00
ginuerzh 69ba5864ad fix #390 2024-01-08 21:25:41 +08:00
ginuerzh fdd3eb61c3 add auth CLI support for api & metrics 2024-01-07 19:42:14 +08:00
ginuerzh f68bfdb149 add observer 2024-01-03 20:56:24 +08:00
ginuerzh 9e4bca84f1 fix tls handshake sniffing 2023-12-27 20:08:12 +08:00
ginuerzh 925ff89b78 fix issue #376 2023-12-25 22:11:03 +08:00
ginuerzh ca077d418a fix issue #372 2023-12-20 22:00:23 +08:00
ginuerzh 7264fca4f8 parsing config directly from cmd flag -C 2023-12-19 21:45:34 +08:00
ginuerzh bc37fac037 added logger group for service 2023-12-19 21:44:58 +08:00
ginuerzh dc48c4a3b2 fix auth for file handler 2023-12-16 14:33:52 +08:00
ginuerzh 310614663a check date for nightly trigger 2023-12-02 13:03:16 +08:00
ginuerzh 8f094d86b0 fix issue #338 2023-11-30 19:46:29 +08:00
ginuerzh 638c780acd update go.mod 2023-11-29 21:18:54 +08:00
ginuerzh 1f20f199c0 fix tun config 2023-11-20 20:43:27 +08:00
ginuerzh 8dc6146f79 fix go.mod 2023-11-19 20:03:23 +08:00
ginuerzh ed29b34c68 add logger component 2023-11-19 19:57:02 +08:00
ginuerzh b41aea03da add web api for router 2023-11-19 16:17:01 +08:00
ginuerzh 2c17e4417d add logger option for tun router 2023-11-19 14:41:10 +08:00
ginuerzh ea8ff5f77a add router component 2023-11-19 14:24:48 +08:00
ginuerzh 6762957c65 add traffic limiter for proxy handler 2023-11-18 18:30:04 +08:00
ginuerzh 4c934cd737 add more config options for kcp 2023-11-16 22:19:42 +08:00
ginuerzh 0d494f5ef2 fix #335 2023-11-16 20:39:42 +08:00
ginuerzh bf1bab7d84 fix race condition in relay udp conn 2023-11-14 22:37:33 +08:00
ginuerzh b8383b1076 add range port support for forwarder node 2023-11-14 19:43:57 +08:00
ginuerzh 345da28f01 update ingress interface 2023-11-13 20:42:45 +08:00
ginuerzh 907420d18b add webtransport tunnel 2023-11-10 21:30:20 +08:00
ginuerzh c1437794f8 add path option for hop 2023-11-09 20:36:21 +08:00
ginuerzh c2df5650fb add prefer and only options for resolver 2023-11-08 21:09:08 +08:00
ginuerzh 23506579e7 improve udp listener 2023-11-07 23:11:38 +08:00
ginuerzh 969db8691b update README 2023-11-03 17:48:56 +08:00
ginuerzh 3b0a2f1bae update sd 2023-11-02 22:34:11 +08:00
ginuerzh b2ed4ae9fd update sd 2023-11-02 20:54:59 +08:00
ginuerzh b2784011d0 add service discovery for tunnel 2023-10-31 23:00:32 +08:00
ginuerzh d4e00683c3 add tls options 2023-10-29 10:28:46 +08:00
ginuerzh 471513896d update nightly build schedule 2023-10-28 22:48:59 +08:00
ginuerzh 6967278600 add nightly build 2023-10-28 22:41:14 +08:00
ginuerzh 2898732c20 improve tunnel entrypoint 2023-10-27 22:15:53 +08:00
dependabot[bot] bb0c0450e8 Bump google.golang.org/grpc from 1.58.1 to 1.58.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.58.1 to 1.58.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.58.1...v1.58.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-27 22:11:52 +08:00
ginuerzh 6bb7dfea8f fix #315 2023-10-25 23:13:39 +08:00
ginuerzh e27d2bc593 set smux version to 1 2023-10-24 20:49:19 +08:00
ginuerzh 3144d61ecd generate random tunnel ID 2023-10-20 23:57:43 +08:00
ginuerzh e4b68d385a fix race condition 2023-10-19 23:50:27 +08:00
ginuerzh a91300d078 get real client ip for http forwarding 2023-10-18 21:23:38 +08:00
ginuerzh b8785eb71e fix race condition when forwarding http 2023-10-18 19:20:43 +08:00
ginuerzh 67fcd929be fix http traffic forwarding 2023-10-18 14:33:38 +08:00
ginuerzh 48c262bf71 fix websocket for forward handler 2023-10-17 23:26:04 +08:00
ginuerzh 05c35005e9 fix websocket for forward handler 2023-10-17 23:06:02 +08:00
ginuerzh f6112773a1 default to smux version 2 for tunnel 2023-10-17 22:16:28 +08:00
ginuerzh 61002b7c9c add mux config 2023-10-17 21:56:29 +08:00
ginuerzh f8a19e0829 fix http handler for tunnel 2023-10-16 23:56:36 +08:00
ginuerzh b1ace73943 update forward handler 2023-10-16 23:17:51 +08:00
47 changed files with 1917 additions and 1772 deletions

View File

@ -1,89 +0,0 @@
# ref: https://docs.docker.com/ci-cd/github-actions/
# https://blog.oddbit.com/post/2020-09-25-building-multi-architecture-im/
name: docker
on:
push:
branches:
- master
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Prepare
id: prepare
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_IMAGE }}
VERSION=latest
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
echo ::set-output name=docker_platforms::linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/s390x
# https://github.com/crazy-max/ghaction-docker-buildx
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
with:
version: latest
- name: Environment
run: |
echo home=$HOME
echo git_ref=$GITHUB_REF
echo git_sha=$GITHUB_SHA
echo version=${{ steps.prepare.outputs.version }}
echo image=${{ steps.prepare.outputs.docker_image }}
echo platforms=${{ steps.prepare.outputs.docker_platforms }}
echo avail_platforms=${{ steps.buildx.outputs.platforms }}
# https://github.com/actions/checkout
- name: Checkout
uses: actions/checkout@v3
- name: Docker Buildx (no push)
run: |
docker buildx bake \
--set ${{ github.event.repository.name }}.platform=${{ steps.prepare.outputs.docker_platforms }} \
--set ${{ github.event.repository.name }}.output=type=image,push=false \
--set ${{ github.event.repository.name }}.tags="${{ steps.prepare.outputs.tags }}" \
--file docker-compose.yaml
- name: Docker Login
if: success()
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Docker Buildx (push)
if: success()
run: |
docker buildx bake \
--set ${{ github.event.repository.name }}.platform=${{ steps.prepare.outputs.docker_platforms }} \
--set ${{ github.event.repository.name }}.output=type=image,push=true \
--set ${{ github.event.repository.name }}.tags="${{ steps.prepare.outputs.tags }}" \
--file docker-compose.yaml
- name: Clear
if: always()
run: |
rm -f ${HOME}/.docker/config.json

69
.github/workflows/buildx.yml vendored 100644
View File

@ -0,0 +1,69 @@
# ref: https://docs.docker.com/ci-cd/github-actions/
# https://blog.oddbit.com/post/2020-09-25-building-multi-architecture-im/
name: docker
on:
push:
branches:
- master
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Prepare
id: prepare
run: |
DOCKER_IMAGE=${{ secrets.DOCKER_IMAGE }}
VERSION=latest
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/v}
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
MAJOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[1]}'`
MINOR_VERSION=`echo $VERSION | awk '{split($0,a,"."); print a[2]}'`
TAGS="$TAGS,${DOCKER_IMAGE}:${MAJOR_VERSION},${DOCKER_IMAGE}:${MAJOR_VERSION}.${MINOR_VERSION},${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
echo "docker_image=${DOCKER_IMAGE}" >> $GITHUB_OUTPUT
echo "docker_platforms=linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/s390x,linux/riscv64" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Environment
run: |
echo home=$HOME
echo git_ref=$GITHUB_REF
echo git_sha=$GITHUB_SHA
echo image=${{ steps.prepare.outputs.docker_image }}
echo tags=${{ steps.prepare.outputs.tags }}
echo platforms=${{ steps.prepare.outputs.docker_platforms }}
echo avail_platforms=${{ steps.buildx.outputs.platforms }}
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Buildx and push
uses: docker/build-push-action@v6
with:
platforms: ${{ steps.prepare.outputs.docker_platforms }}
push: true
tags: ${{ steps.prepare.outputs.tags }}

View File

@ -15,21 +15,25 @@ jobs:
goreleaser: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- run: git fetch --force --tags - run: git fetch --force --tags
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: '1.21' go-version: '1.26'
cache: true cache: true
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
# More assembly might be required: Docker logins, GPG, etc. It all depends # More assembly might be required: Docker logins, GPG, etc. It all depends
# on your needs. # on your needs.
- uses: goreleaser/goreleaser-action@v4 - uses: goreleaser/goreleaser-action@v6
with: with:
# either 'goreleaser' (default) or 'goreleaser-pro': # either 'goreleaser' (default) or 'goreleaser-pro':
distribution: goreleaser distribution: goreleaser
version: latest version: '~> v2'
args: release --clean args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,61 @@
name: Trigger nightly build
on:
schedule:
# * is a special character in YAML, so you have to quote this string
- cron: '00 15 * * *'
workflow_dispatch:
jobs:
check_date:
runs-on: ubuntu-latest
name: Check latest commit
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@v4
- name: print latest_commit
run: echo ${{ github.sha }}
- id: should_run
continue-on-error: true
name: check latest commit is less than a day
if: ${{ github.event_name == 'schedule' }}
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
trigger-nightly:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
name: Push tag for nightly build
runs-on: ubuntu-latest
steps:
-
name: 'Checkout'
uses: actions/checkout@v4
with:
token: ${{ secrets.NIGHTLY_BUILD_GH_TOKEN }}
fetch-depth: 0
-
name: 'Push new tag'
run: |
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
# A previous release was created using a lightweight tag
# git describe by default includes only annotated tags
# git describe --tags includes lightweight tags as well
DESCRIBE=`git tag -l --sort=-v:refname | grep -v nightly | head -n 1`
MAJOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[1]}'`
MINOR_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[2]}'`
PATCH_VERSION=`echo $DESCRIBE | awk '{split($0,a,"."); print a[3]}'`
PATCH_VERSION="$((${PATCH_VERSION} + 1))"
TAG="${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}-nightly.$(date +'%Y%m%d')"
git tag -a $TAG -m "$TAG: nightly build"
git push origin $TAG
- name: 'Clean up nightly releases'
uses: dev-drprasad/delete-older-releases@v0.3.3
with:
keep_latest: 2
delete_tags: true
delete_tag_pattern: nightly
env:
GITHUB_TOKEN: ${{ secrets.NIGHTLY_BUILD_GH_TOKEN }}

3
.gitignore vendored
View File

@ -32,9 +32,10 @@ _testmain.go
*.bak *.bak
cmd/gost/gost cmd/gost/gost
gost
snap snap
*.pem *.pem
*.yaml /*.yaml
*.txt *.txt
dist/ dist/

View File

@ -28,12 +28,22 @@ builds:
- linux_mips64le - linux_mips64le
- linux_s390x - linux_s390x
- linux_riscv64 - linux_riscv64
- linux_loong64
- freebsd_386 - freebsd_386
- freebsd_amd64 - freebsd_amd64
- windows_386 - windows_386
- windows_amd64 - windows_amd64
- windows_amd64_v3 - windows_amd64_v3
- windows_arm64 - windows_arm64
- android_arm64
ldflags:
- "-s -w -X 'main.version={{ .Tag }}'"
upx:
- # UPX compression. Disabled by default (see #863): upx --best/--lzma/--brute
# adds ~3s startup time on low-spec systems (linux/arm). Release builds can
# opt in via GORELEASER_UPX=true environment variable.
enabled: false
archives: archives:
- format: tar.gz - format: tar.gz

66
CLAUDE.md 100644
View File

@ -0,0 +1,66 @@
# CLAUDE.md — gost/
CLI binary entry point for GOST (GO Simple Tunnel). This module compiles the `gost` command.
## Build & Run
```bash
cd gost && go build ./cmd/gost/...
# Cross-compile
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" ./cmd/gost/...
# Build all platforms
make all
# Run
./gost -L "http://:8080" -F "socks5://:1080"
./gost -C gost.yml
```
## Structure
| File | Purpose |
|------|---------|
| `cmd/gost/main.go` | CLI entry point: flag parsing, multi-worker mode (`--` separator), program launch via `svc.Run` |
| `cmd/gost/program.go` | Service lifecycle (`Init`/`Start`/`Stop`), config loading, API/metrics/profiling servers, SIGHUP reload |
| `cmd/gost/register.go` | Blank imports for all built-in handlers, listeners, dialers, connectors — triggers `init()` registration |
| `cmd/gost/version.go` | Version string (`3.3.0`) |
## CLI Flags
| Flag | Usage |
|------|-------|
| `-L` | Inline service definition (repeatable) |
| `-F` | Inline chain node definition (repeatable) |
| `-C` | Path to config file (YAML/JSON) |
| `-D` / `-DD` | Debug / trace logging |
| `-api` | API service address (e.g. `:8080`) |
| `-metrics` | Prometheus metrics address |
| `-O` | Output merged config as yaml or json, then exit |
| `-V` | Print version and exit |
## Multi-Worker Mode
Arguments separated by ` -- ` spawn multiple worker processes via `exec.CommandContext`. Each worker runs as a child process with `_GOST_ID` set. Any worker's exit cancels all others. This is triggered in `init()` before flag parsing.
## Service Lifecycle
- `Init` → calls `parser.Init()` with all CLI/config inputs
- `Start``parser.Parse()``loader.Load()``p.run()` (starts services, API, metrics, profiling)
- `Stop` → cancels reload context, closes all services
- SIGHUP → `reloadConfig()` re-parses and re-runs without restarting the process
## Key Dependencies
- `github.com/go-gost/core` — interface definitions
- `github.com/go-gost/x` — all implementations, config, registry
- `github.com/judwhite/go-svc` — OS service framework (handles daemon/SIGHUP/SIGTERM)
## Registration Pattern
All components register via `init()` side-effects in their packages. `cmd/gost/register.go` triggers them with blank imports. When adding a new built-in handler/listener/dialer/connector, add a blank import here.
## Tests
Tests are in `tests/e2e/` — integration tests using the built binary. No unit tests. Run with `go test ./tests/e2e/...`.

View File

@ -1,28 +1,37 @@
FROM --platform=$BUILDPLATFORM golang:1.21-alpine as builder FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.6.1 AS xx
# Convert TARGETPLATFORM to GOARCH format FROM --platform=$BUILDPLATFORM golang:1.26-alpine3.23 AS builder
# https://github.com/tonistiigi/xx
COPY --from=tonistiigi/xx:golang / / # UPX compression disabled by default (see #863): upx --best adds ~3s startup
# time on low-spec systems (linux/arm). Builds can opt in by uncommenting the
# upx install line and adding upx --best to the build command below.
# RUN apk add --no-cache upx || echo "upx not found"
COPY --from=xx / /
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN apk add --no-cache musl-dev git gcc RUN xx-info env
ADD . /src ENV CGO_ENABLED=0
WORKDIR /src ENV XX_VERIFY_STATIC=1
ENV GO111MODULE=on WORKDIR /app
RUN cd cmd/gost && go env && go build COPY . .
FROM alpine:latest RUN cd cmd/gost && \
xx-go build -ldflags "-s -w" && \
xx-verify gost
# add iptables for tun/tap FROM alpine:3.23
RUN apk add --no-cache iptables
# add iptables/nftables for tun/tap
RUN apk add --no-cache iptables nftables
WORKDIR /bin/ WORKDIR /bin/
COPY --from=builder /src/cmd/gost/gost . COPY --from=builder /app/cmd/gost/gost .
ENTRYPOINT ["/bin/gost"] ENTRYPOINT ["/bin/gost"]

View File

@ -2,7 +2,7 @@
### GO语言实现的安全隧道 ### GO语言实现的安全隧道
[English README](README_en.md) [![zh](https://img.shields.io/badge/Chinese%20README-green)](README.md) [![en](https://img.shields.io/badge/English%20README-gray)](README_en.md)
## 功能特性 ## 功能特性
@ -10,12 +10,10 @@
- [x] [多级转发链](https://gost.run/concepts/chain/) - [x] [多级转发链](https://gost.run/concepts/chain/)
- [x] [多协议支持](https://gost.run/tutorials/protocols/overview/) - [x] [多协议支持](https://gost.run/tutorials/protocols/overview/)
- [x] [TCP/UDP端口转发](https://gost.run/tutorials/port-forwarding/) - [x] [TCP/UDP端口转发](https://gost.run/tutorials/port-forwarding/)
- [x] [反向代理](https://gost.run/tutorials/reverse-proxy/)和[隧道](https://gost.run/tutorials/reverse-proxy-advanced/) - [x] [反向代理](https://gost.run/tutorials/reverse-proxy/)和[隧道](https://gost.run/tutorials/reverse-proxy-tunnel/)
- [x] [TCP/UDP透明代理](https://gost.run/tutorials/redirect/) - [x] [TCP/UDP透明代理](https://gost.run/tutorials/redirect/)
- [x] DNS[解析](https://gost.run/concepts/resolver/)和[代理](https://gost.run/tutorials/dns/) - [x] DNS[解析](https://gost.run/concepts/resolver/)和[代理](https://gost.run/tutorials/dns/)
- [x] [TUN/TAP设备](https://gost.run/tutorials/tuntap/) - [x] [TUN/TAP设备](https://gost.run/tutorials/tuntap/)与[TUN2SOCKS](https://gost.run/tutorials/tungo/)
- [x] [Unix域套接字重定向](https://gost.run/tutorials/unix/)
- [x] [串口重定向](https://gost.run/tutorials/serial/)
- [x] [负载均衡](https://gost.run/concepts/selector/) - [x] [负载均衡](https://gost.run/concepts/selector/)
- [x] [路由控制](https://gost.run/concepts/bypass/) - [x] [路由控制](https://gost.run/concepts/bypass/)
- [x] [准入控制](https://gost.run/concepts/admission/) - [x] [准入控制](https://gost.run/concepts/admission/)
@ -24,26 +22,40 @@
- [x] [Prometheus监控指标](https://gost.run/tutorials/metrics/) - [x] [Prometheus监控指标](https://gost.run/tutorials/metrics/)
- [x] [动态配置](https://gost.run/tutorials/api/config/) - [x] [动态配置](https://gost.run/tutorials/api/config/)
- [x] [Web API](https://gost.run/tutorials/api/overview/) - [x] [Web API](https://gost.run/tutorials/api/overview/)
- [ ] Web UI - [x] [GUI](https://github.com/go-gost/gostctl)/[WebUI](https://github.com/go-gost/gost-ui)
Wiki站点[https://gost.run](https://gost.run) ## 概览
Telegram讨论群[https://t.me/gogost](https://t.me/gogost) ![Overview](https://gost.run/images/overview.png)
Google讨论组[https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost) GOST作为隧道有三种主要使用方式。
旧版入口:[v2.gost.run](https://v2.gost.run) ### 正向代理
作为代理服务访问网络,可以组合使用多种协议组成转发链进行转发。
![Proxy](https://gost.run/images/proxy.png)
### 端口转发
将一个服务的端口映射到另外一个服务的端口,同样可以组合使用多种协议组成转发链进行转发。
![Forward](https://gost.run/images/forward.png)
### 反向代理
利用隧道和内网穿透将内网服务暴露到公网访问。
![Reverse Proxy](https://gost.run/images/reverse-proxy.png)
## 下载安装 ## 下载安装
### 二进制文件 ### 二进制文件
[https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases) [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
### 安装脚本 ### 安装脚本
```bash ```bash
# 安装最新版本 [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases) # 安装最新版本 [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --install bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --install
@ -53,7 +65,6 @@ bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) --insta
bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh) bash <(curl -fsSL https://github.com/go-gost/gost/raw/master/install.sh)
``` ```
### 源码编译 ### 源码编译
``` ```
@ -68,6 +79,28 @@ go build
docker run --rm gogost/gost -V docker run --rm gogost/gost -V
``` ```
## 工具
### GUI
[go-gost/gostctl](https://github.com/go-gost/gostctl)
### WebUI
[go-gost/gost-ui](https://github.com/go-gost/gost-ui)
### Shadowsocks Android插件 ### Shadowsocks Android插件
[xausky/ShadowsocksGostPlugin](https://github.com/xausky/ShadowsocksGostPlugin) [hamid-nazari/ShadowsocksGostPlugin](https://github.com/hamid-nazari/ShadowsocksGostPlugin)
## 帮助与支持
Wiki站点[https://gost.run](https://gost.run)
YouTube: [https://www.youtube.com/@gost-tunnel](https://www.youtube.com/@gost-tunnel)
Telegram[https://t.me/gogost](https://t.me/gogost)
Google讨论组[https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
旧版入口:[v2.gost.run](https://v2.gost.run)

View File

@ -2,18 +2,18 @@
### A simple security tunnel written in golang ### A simple security tunnel written in golang
[![en](https://img.shields.io/badge/English%20README-green)](README_en.md) [![zh](https://img.shields.io/badge/Chinese%20README-gray)](README.md)
## Features ## Features
- [x] [Listening on multiple ports](https://gost.run/en/getting-started/quick-start/) - [x] [Listening on multiple ports](https://gost.run/en/getting-started/quick-start/)
- [x] [Multi-level forwarding chain](https://gost.run/en/concepts/chain/) - [x] [Multi-level forwarding chain](https://gost.run/en/concepts/chain/)
- [x] Rich protocol - [x] Rich protocol
- [x] [TCP/UDP port forwarding](https://gost.run/en/tutorials/port-forwarding/) - [x] [TCP/UDP port forwarding](https://gost.run/en/tutorials/port-forwarding/)
- [x] [Reverse Proxy](https://gost.run/en/tutorials/reverse-proxy/) and [Tunnel](https://gost.run/en/tutorials/reverse-proxy-advanced/) - [x] [Reverse Proxy](https://gost.run/en/tutorials/reverse-proxy/) and [Tunnel](https://gost.run/en/tutorials/reverse-proxy-tunnel/)
- [x] [TCP/UDP transparent proxy](https://gost.run/en/tutorials/redirect/) - [x] [TCP/UDP transparent proxy](https://gost.run/en/tutorials/redirect/)
- [x] DNS [resolver](https://gost.run/en/concepts/resolver/) and [proxy](https://gost.run/en/tutorials/dns/) - [x] DNS [resolver](https://gost.run/en/concepts/resolver/) and [proxy](https://gost.run/en/tutorials/dns/)
- [x] [TUN/TAP device](https://gost.run/en/tutorials/tuntap/) - [x] [TUN/TAP device](https://gost.run/en/tutorials/tuntap/) and [TUN2SOCKS](https://gost.run/en/tutorials/tungo/)
- [x] [Unix Domain Socket Redirector](https://gost.run/en/tutorials/unix/)
- [x] [Serial Port Redirector](https://gost.run/en/tutorials/serial/)
- [x] [Load balancing](https://gost.run/en/concepts/selector/) - [x] [Load balancing](https://gost.run/en/concepts/selector/)
- [x] [Routing control](https://gost.run/en/concepts/bypass/) - [x] [Routing control](https://gost.run/en/concepts/bypass/)
- [x] [Admission control](https://gost.run/en/concepts/limiter/) - [x] [Admission control](https://gost.run/en/concepts/limiter/)
@ -22,19 +22,34 @@
- [x] [Prometheus metrics](https://gost.run/en/tutorials/metrics/) - [x] [Prometheus metrics](https://gost.run/en/tutorials/metrics/)
- [x] [Dynamic configuration](https://gost.run/en/tutorials/api/config/) - [x] [Dynamic configuration](https://gost.run/en/tutorials/api/config/)
- [x] [Web API](https://gost.run/en/tutorials/api/overview/) - [x] [Web API](https://gost.run/en/tutorials/api/overview/)
- [ ] Web UI - [x] [GUI](https://github.com/go-gost/gostctl)/[WebUI](https://github.com/go-gost/gost-ui)
Wiki: [https://gost.run](https://gost.run/en/) ## Overview
Telegram: [https://t.me/gogost](https://t.me/gogost) ![Overview](https://gost.run/images/overview.png)
Google group: [https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost) There are three main ways to use GOST as a tunnel.
Legacy version: [v2.gost.run](https://v2.gost.run/en/) ### Proxy
As a proxy service to access the network, multiple protocols can be used in combination to form a forwarding chain for traffic forwarding.
![Proxy](https://gost.run/images/proxy.png)
### Port Forwarding
Mapping the port of one service to the port of another service, you can also use a combination of multiple protocols to form a forwarding chain for traffic forwarding.
![Forward](https://gost.run/images/forward.png)
### Reverse Proxy
Use tunnel and intranet penetration to expose local services behind NAT or firewall to public network for access.
![Reverse Proxy](https://gost.run/images/reverse-proxy.png)
## Installation ## Installation
### Binary files ### Binary files
[https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases) [https://github.com/go-gost/gost/releases](https://github.com/go-gost/gost/releases)
@ -64,6 +79,28 @@ go build
docker run --rm gogost/gost -V docker run --rm gogost/gost -V
``` ```
## Tools
### GUI
[go-gost/gostctl](https://github.com/go-gost/gostctl)
### WebUI
[go-gost/gost-ui](https://github.com/go-gost/gost-ui)
### Shadowsocks Android ### Shadowsocks Android
[xausky/ShadowsocksGostPlugin](https://github.com/xausky/ShadowsocksGostPlugin) [hamid-nazari/ShadowsocksGostPlugin](https://github.com/hamid-nazari/ShadowsocksGostPlugin)
## Support
Wiki: [https://gost.run](https://gost.run/en/)
YouTube: [https://www.youtube.com/@gost-tunnel](https://www.youtube.com/@gost-tunnel)
Telegram: [https://t.me/gogost](https://t.me/gogost)
Google group: [https://groups.google.com/d/forum/go-gost](https://groups.google.com/d/forum/go-gost)
Legacy version: [v2.gost.run](https://v2.gost.run/en/)

View File

@ -1,596 +0,0 @@
package main
import (
"encoding/base64"
"errors"
"fmt"
"net/url"
"os"
"strconv"
"strings"
"time"
mdutil "github.com/go-gost/core/metadata/util"
"github.com/go-gost/x/config"
"github.com/go-gost/x/limiter/conn"
"github.com/go-gost/x/limiter/traffic"
mdx "github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
var (
ErrInvalidCmd = errors.New("invalid cmd")
ErrInvalidNode = errors.New("invalid node")
)
type stringList []string
func (l *stringList) String() string {
return fmt.Sprintf("%s", *l)
}
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}
func buildConfigFromCmd(services, nodes stringList) (*config.Config, error) {
namePrefix := ""
cfg := &config.Config{}
var chain *config.ChainConfig
if len(nodes) > 0 {
chain = &config.ChainConfig{
Name: fmt.Sprintf("%schain-0", namePrefix),
}
cfg.Chains = append(cfg.Chains, chain)
}
for i, node := range nodes {
url, err := normCmd(node)
if err != nil {
return nil, err
}
nodeConfig, err := buildNodeConfig(url)
if err != nil {
return nil, err
}
nodeConfig.Name = fmt.Sprintf("%snode-0", namePrefix)
var nodes []*config.NodeConfig
for _, host := range strings.Split(nodeConfig.Addr, ",") {
if host == "" {
continue
}
nodeCfg := &config.NodeConfig{}
*nodeCfg = *nodeConfig
nodeCfg.Name = fmt.Sprintf("%snode-%d", namePrefix, len(nodes))
nodeCfg.Addr = host
nodes = append(nodes, nodeCfg)
}
mc := nodeConfig.Connector.Metadata
md := mdx.NewMetadata(mc)
hopConfig := &config.HopConfig{
Name: fmt.Sprintf("%shop-%d", namePrefix, i),
Selector: parseSelector(mc),
Nodes: nodes,
}
if v := mdutil.GetString(md, "bypass"); v != "" {
bypassCfg := &config.BypassConfig{
Name: fmt.Sprintf("%sbypass-%d", namePrefix, len(cfg.Bypasses)),
}
if v[0] == '~' {
bypassCfg.Whitelist = true
v = v[1:]
}
for _, s := range strings.Split(v, ",") {
if s == "" {
continue
}
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
}
hopConfig.Bypass = bypassCfg.Name
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
delete(mc, "bypass")
}
if v := mdutil.GetString(md, "resolver"); v != "" {
resolverCfg := &config.ResolverConfig{
Name: fmt.Sprintf("%sresolver-%d", namePrefix, len(cfg.Resolvers)),
}
for _, rs := range strings.Split(v, ",") {
if rs == "" {
continue
}
resolverCfg.Nameservers = append(
resolverCfg.Nameservers,
&config.NameserverConfig{
Addr: rs,
},
)
}
hopConfig.Resolver = resolverCfg.Name
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
delete(mc, "resolver")
}
if v := mdutil.GetString(md, "hosts"); v != "" {
hostsCfg := &config.HostsConfig{
Name: fmt.Sprintf("%shosts-%d", namePrefix, len(cfg.Hosts)),
}
for _, s := range strings.Split(v, ",") {
ss := strings.SplitN(s, ":", 2)
if len(ss) != 2 {
continue
}
hostsCfg.Mappings = append(
hostsCfg.Mappings,
&config.HostMappingConfig{
Hostname: ss[0],
IP: ss[1],
},
)
}
hopConfig.Hosts = hostsCfg.Name
cfg.Hosts = append(cfg.Hosts, hostsCfg)
delete(mc, "hosts")
}
if v := mdutil.GetString(md, "interface"); v != "" {
hopConfig.Interface = v
delete(mc, "interface")
}
if v := mdutil.GetInt(md, "so_mark"); v > 0 {
hopConfig.SockOpts = &config.SockOptsConfig{
Mark: v,
}
delete(mc, "so_mark")
}
chain.Hops = append(chain.Hops, hopConfig)
}
for i, svc := range services {
url, err := normCmd(svc)
if err != nil {
return nil, err
}
service, err := buildServiceConfig(url)
if err != nil {
return nil, err
}
service.Name = fmt.Sprintf("%sservice-%d", namePrefix, i)
if chain != nil {
if service.Listener.Type == "rtcp" || service.Listener.Type == "rudp" {
service.Listener.Chain = chain.Name
} else {
service.Handler.Chain = chain.Name
}
}
cfg.Services = append(cfg.Services, service)
mh := service.Handler.Metadata
md := mdx.NewMetadata(mh)
if v := mdutil.GetInt(md, "retries"); v > 0 {
service.Handler.Retries = v
delete(mh, "retries")
}
if v := mdutil.GetString(md, "admission"); v != "" {
admCfg := &config.AdmissionConfig{
Name: fmt.Sprintf("%sadmission-%d", namePrefix, len(cfg.Admissions)),
}
if v[0] == '~' {
admCfg.Whitelist = true
v = v[1:]
}
for _, s := range strings.Split(v, ",") {
if s == "" {
continue
}
admCfg.Matchers = append(admCfg.Matchers, s)
}
service.Admission = admCfg.Name
cfg.Admissions = append(cfg.Admissions, admCfg)
delete(mh, "admission")
}
if v := mdutil.GetString(md, "bypass"); v != "" {
bypassCfg := &config.BypassConfig{
Name: fmt.Sprintf("%sbypass-%d", namePrefix, len(cfg.Bypasses)),
}
if v[0] == '~' {
bypassCfg.Whitelist = true
v = v[1:]
}
for _, s := range strings.Split(v, ",") {
if s == "" {
continue
}
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
}
service.Bypass = bypassCfg.Name
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
delete(mh, "bypass")
}
if v := mdutil.GetString(md, "resolver"); v != "" {
resolverCfg := &config.ResolverConfig{
Name: fmt.Sprintf("%sresolver-%d", namePrefix, len(cfg.Resolvers)),
}
for _, rs := range strings.Split(v, ",") {
if rs == "" {
continue
}
resolverCfg.Nameservers = append(
resolverCfg.Nameservers,
&config.NameserverConfig{
Addr: rs,
Prefer: mdutil.GetString(md, "prefer"),
},
)
}
service.Resolver = resolverCfg.Name
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
delete(mh, "resolver")
}
if v := mdutil.GetString(md, "hosts"); v != "" {
hostsCfg := &config.HostsConfig{
Name: fmt.Sprintf("%shosts-%d", namePrefix, len(cfg.Hosts)),
}
for _, s := range strings.Split(v, ",") {
ss := strings.SplitN(s, ":", 2)
if len(ss) != 2 {
continue
}
hostsCfg.Mappings = append(
hostsCfg.Mappings,
&config.HostMappingConfig{
Hostname: ss[0],
IP: ss[1],
},
)
}
service.Hosts = hostsCfg.Name
cfg.Hosts = append(cfg.Hosts, hostsCfg)
delete(mh, "hosts")
}
in := mdutil.GetString(md, "limiter.in")
out := mdutil.GetString(md, "limiter.out")
cin := mdutil.GetString(md, "limiter.conn.in")
cout := mdutil.GetString(md, "limiter.conn.out")
if in != "" || cin != "" || out != "" || cout != "" {
limiter := &config.LimiterConfig{
Name: fmt.Sprintf("%slimiter-%d", namePrefix, len(cfg.Limiters)),
}
if in != "" || out != "" {
limiter.Limits = append(limiter.Limits,
fmt.Sprintf("%s %s %s", traffic.GlobalLimitKey, in, out))
}
if cin != "" || cout != "" {
limiter.Limits = append(limiter.Limits,
fmt.Sprintf("%s %s %s", traffic.ConnLimitKey, cin, cout))
}
service.Limiter = limiter.Name
cfg.Limiters = append(cfg.Limiters, limiter)
delete(mh, "limiter.in")
delete(mh, "limiter.out")
delete(mh, "limiter.conn.in")
delete(mh, "limiter.conn.out")
}
if climit := mdutil.GetInt(md, "climiter"); climit > 0 {
limiter := &config.LimiterConfig{
Name: fmt.Sprintf("%sclimiter-%d", namePrefix, len(cfg.CLimiters)),
Limits: []string{fmt.Sprintf("%s %d", conn.GlobalLimitKey, climit)},
}
service.CLimiter = limiter.Name
cfg.CLimiters = append(cfg.CLimiters, limiter)
delete(mh, "climiter")
}
if rlimit := mdutil.GetFloat(md, "rlimiter"); rlimit > 0 {
limiter := &config.LimiterConfig{
Name: fmt.Sprintf("%srlimiter-%d", namePrefix, len(cfg.RLimiters)),
Limits: []string{fmt.Sprintf("%s %s", conn.GlobalLimitKey, strconv.FormatFloat(rlimit, 'f', -1, 64))},
}
service.RLimiter = limiter.Name
cfg.RLimiters = append(cfg.RLimiters, limiter)
delete(mh, "rlimiter")
}
}
return cfg, nil
}
func buildServiceConfig(url *url.URL) (*config.ServiceConfig, error) {
namePrefix := ""
if v := os.Getenv("_GOST_ID"); v != "" {
namePrefix = fmt.Sprintf("go-%s@", v)
}
var handler, listener string
schemes := strings.Split(url.Scheme, "+")
if len(schemes) == 1 {
handler = schemes[0]
listener = schemes[0]
}
if len(schemes) == 2 {
handler = schemes[0]
listener = schemes[1]
}
svc := &config.ServiceConfig{
Addr: url.Host,
}
if h := registry.HandlerRegistry().Get(handler); h == nil {
handler = "auto"
}
if ln := registry.ListenerRegistry().Get(listener); ln == nil {
listener = "tcp"
if handler == "ssu" {
listener = "udp"
}
}
// forward mode
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" {
svc.Forwarder = &config.ForwarderConfig{
// Targets: strings.Split(remotes, ","),
}
for i, addr := range strings.Split(remotes, ",") {
svc.Forwarder.Nodes = append(svc.Forwarder.Nodes,
&config.ForwardNodeConfig{
Name: fmt.Sprintf("%starget-%d", namePrefix, i),
Addr: addr,
})
}
if handler != "relay" {
if listener == "tcp" || listener == "udp" ||
listener == "rtcp" || listener == "rudp" ||
listener == "tun" || listener == "tap" ||
listener == "dns" || listener == "unix" ||
listener == "serial" {
handler = listener
} else {
handler = "forward"
}
}
}
var auth *config.AuthConfig
if url.User != nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
}
auth.Password, _ = url.User.Password()
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := mdx.NewMetadata(m)
if sa := mdutil.GetString(md, "auth"); sa != "" {
au, err := parseAuthFromCmd(sa)
if err != nil {
return nil, err
}
auth = au
}
delete(m, "auth")
tlsConfig := &config.TLSConfig{
CertFile: mdutil.GetString(md, "certFile", "cert"),
KeyFile: mdutil.GetString(md, "keyFile", "key"),
CAFile: mdutil.GetString(md, "caFile", "ca"),
}
delete(m, "certFile")
delete(m, "cert")
delete(m, "keyFile")
delete(m, "key")
delete(m, "caFile")
delete(m, "ca")
if tlsConfig.CertFile == "" {
tlsConfig = nil
}
if v := mdutil.GetString(md, "dns"); v != "" {
md.Set("dns", strings.Split(v, ","))
}
if svc.Forwarder != nil {
svc.Forwarder.Selector = parseSelector(m)
}
svc.Handler = &config.HandlerConfig{
Type: handler,
Auth: auth,
Metadata: m,
}
svc.Listener = &config.ListenerConfig{
Type: listener,
TLS: tlsConfig,
Metadata: m,
}
svc.Metadata = m
if svc.Listener.Type == "ssh" || svc.Listener.Type == "sshd" {
svc.Handler.Auth = nil
svc.Listener.Auth = auth
}
return svc, nil
}
func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
var connector, dialer string
schemes := strings.Split(url.Scheme, "+")
if len(schemes) == 1 {
connector = schemes[0]
dialer = schemes[0]
}
if len(schemes) == 2 {
connector = schemes[0]
dialer = schemes[1]
}
node := &config.NodeConfig{
Addr: url.Host,
}
if c := registry.ConnectorRegistry().Get(connector); c == nil {
connector = "http"
}
if d := registry.DialerRegistry().Get(dialer); d == nil {
dialer = "tcp"
if connector == "ssu" {
dialer = "udp"
}
}
var auth *config.AuthConfig
if url.User != nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
}
auth.Password, _ = url.User.Password()
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := mdx.NewMetadata(m)
if sauth := mdutil.GetString(md, "auth"); sauth != "" && auth == nil {
au, err := parseAuthFromCmd(sauth)
if err != nil {
return nil, err
}
auth = au
}
delete(m, "auth")
tlsConfig := &config.TLSConfig{
CertFile: mdutil.GetString(md, "certFile", "cert"),
KeyFile: mdutil.GetString(md, "keyFile", "key"),
CAFile: mdutil.GetString(md, "caFile", "ca"),
Secure: mdutil.GetBool(md, "secure"),
ServerName: mdutil.GetString(md, "serverName"),
}
if tlsConfig.ServerName == "" {
tlsConfig.ServerName = url.Hostname()
}
delete(m, "certFile")
delete(m, "cert")
delete(m, "keyFile")
delete(m, "key")
delete(m, "caFile")
delete(m, "ca")
delete(m, "secure")
delete(m, "serverName")
if !tlsConfig.Secure && tlsConfig.CertFile == "" && tlsConfig.CAFile == "" {
tlsConfig = nil
}
node.Connector = &config.ConnectorConfig{
Type: connector,
Auth: auth,
Metadata: m,
}
node.Dialer = &config.DialerConfig{
Type: dialer,
TLS: tlsConfig,
Metadata: m,
}
if node.Dialer.Type == "ssh" || node.Dialer.Type == "sshd" {
node.Connector.Auth = nil
node.Dialer.Auth = auth
}
return node, nil
}
func normCmd(s string) (*url.URL, error) {
s = strings.TrimSpace(s)
if s == "" {
return nil, ErrInvalidCmd
}
if s[0] == ':' || !strings.Contains(s, "://") {
s = "auto://" + s
}
url, err := url.Parse(s)
if err != nil {
return nil, err
}
if url.Scheme == "https" {
url.Scheme = "http+tls"
}
return url, nil
}
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
v, err := base64.StdEncoding.DecodeString(sa)
if err != nil {
return nil, err
}
cs := string(v)
n := strings.IndexByte(cs, ':')
if n < 0 {
return &config.AuthConfig{
Username: cs,
}, nil
}
return &config.AuthConfig{
Username: cs[:n],
Password: cs[n+1:],
}, nil
}
func parseSelector(m map[string]any) *config.SelectorConfig {
md := mdx.NewMetadata(m)
strategy := mdutil.GetString(md, "strategy")
maxFails := mdutil.GetInt(md, "maxFails", "max_fails")
failTimeout := mdutil.GetDuration(md, "failTimeout", "fail_timeout")
if strategy == "" && maxFails <= 0 && failTimeout <= 0 {
return nil
}
if strategy == "" {
strategy = "round"
}
if maxFails <= 0 {
maxFails = 1
}
if failTimeout <= 0 {
failTimeout = 30 * time.Second
}
delete(m, "strategy")
delete(m, "maxFails")
delete(m, "max_fails")
delete(m, "failTimeout")
delete(m, "fail_timeout")
return &config.SelectorConfig{
Strategy: strategy,
MaxFails: maxFails,
FailTimeout: failTimeout,
}
}

View File

@ -1,221 +0,0 @@
package main
import (
"io"
"os"
"path/filepath"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/service"
"github.com/go-gost/x/api"
"github.com/go-gost/x/config"
admission_parser "github.com/go-gost/x/config/parsing/admission"
auth_parser "github.com/go-gost/x/config/parsing/auth"
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
chain_parser "github.com/go-gost/x/config/parsing/chain"
hop_parser "github.com/go-gost/x/config/parsing/hop"
hosts_parser "github.com/go-gost/x/config/parsing/hosts"
ingress_parser "github.com/go-gost/x/config/parsing/ingress"
limiter_parser "github.com/go-gost/x/config/parsing/limiter"
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
service_parser "github.com/go-gost/x/config/parsing/service"
xlogger "github.com/go-gost/x/logger"
metrics "github.com/go-gost/x/metrics/service"
"github.com/go-gost/x/registry"
"gopkg.in/natefinch/lumberjack.v2"
)
func buildService(cfg *config.Config) (services []service.Service) {
if cfg == nil {
return
}
log := logger.Default()
for _, autherCfg := range cfg.Authers {
if auther := auth_parser.ParseAuther(autherCfg); auther != nil {
if err := registry.AutherRegistry().Register(autherCfg.Name, auther); err != nil {
log.Fatal(err)
}
}
}
for _, admissionCfg := range cfg.Admissions {
if adm := admission_parser.ParseAdmission(admissionCfg); adm != nil {
if err := registry.AdmissionRegistry().Register(admissionCfg.Name, adm); err != nil {
log.Fatal(err)
}
}
}
for _, bypassCfg := range cfg.Bypasses {
if bp := bypass_parser.ParseBypass(bypassCfg); bp != nil {
if err := registry.BypassRegistry().Register(bypassCfg.Name, bp); err != nil {
log.Fatal(err)
}
}
}
for _, resolverCfg := range cfg.Resolvers {
r, err := resolver_parser.ParseResolver(resolverCfg)
if err != nil {
log.Fatal(err)
}
if r != nil {
if err := registry.ResolverRegistry().Register(resolverCfg.Name, r); err != nil {
log.Fatal(err)
}
}
}
for _, hostsCfg := range cfg.Hosts {
if h := hosts_parser.ParseHostMapper(hostsCfg); h != nil {
if err := registry.HostsRegistry().Register(hostsCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, ingressCfg := range cfg.Ingresses {
if h := ingress_parser.ParseIngress(ingressCfg); h != nil {
if err := registry.IngressRegistry().Register(ingressCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, recorderCfg := range cfg.Recorders {
if h := recorder_parser.ParseRecorder(recorderCfg); h != nil {
if err := registry.RecorderRegistry().Register(recorderCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, limiterCfg := range cfg.Limiters {
if h := limiter_parser.ParseTrafficLimiter(limiterCfg); h != nil {
if err := registry.TrafficLimiterRegistry().Register(limiterCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, limiterCfg := range cfg.CLimiters {
if h := limiter_parser.ParseConnLimiter(limiterCfg); h != nil {
if err := registry.ConnLimiterRegistry().Register(limiterCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, limiterCfg := range cfg.RLimiters {
if h := limiter_parser.ParseRateLimiter(limiterCfg); h != nil {
if err := registry.RateLimiterRegistry().Register(limiterCfg.Name, h); err != nil {
log.Fatal(err)
}
}
}
for _, hopCfg := range cfg.Hops {
hop, err := hop_parser.ParseHop(hopCfg)
if err != nil {
log.Fatal(err)
}
if hop != nil {
if err := registry.HopRegistry().Register(hopCfg.Name, hop); err != nil {
log.Fatal(err)
}
}
}
for _, chainCfg := range cfg.Chains {
c, err := chain_parser.ParseChain(chainCfg)
if err != nil {
log.Fatal(err)
}
if c != nil {
if err := registry.ChainRegistry().Register(chainCfg.Name, c); err != nil {
log.Fatal(err)
}
}
}
for _, svcCfg := range cfg.Services {
svc, err := service_parser.ParseService(svcCfg)
if err != nil {
log.Fatal(err)
}
if svc != nil {
if err := registry.ServiceRegistry().Register(svcCfg.Name, svc); err != nil {
log.Fatal(err)
}
}
services = append(services, svc)
}
return
}
func logFromConfig(cfg *config.LogConfig) logger.Logger {
if cfg == nil {
cfg = &config.LogConfig{}
}
opts := []xlogger.LoggerOption{
xlogger.FormatLoggerOption(logger.LogFormat(cfg.Format)),
xlogger.LevelLoggerOption(logger.LogLevel(cfg.Level)),
}
var out io.Writer = os.Stderr
switch cfg.Output {
case "none", "null":
return xlogger.Nop()
case "stdout":
out = os.Stdout
case "stderr", "":
out = os.Stderr
default:
if cfg.Rotation != nil {
out = &lumberjack.Logger{
Filename: cfg.Output,
MaxSize: cfg.Rotation.MaxSize,
MaxAge: cfg.Rotation.MaxAge,
MaxBackups: cfg.Rotation.MaxBackups,
LocalTime: cfg.Rotation.LocalTime,
Compress: cfg.Rotation.Compress,
}
} else {
os.MkdirAll(filepath.Dir(cfg.Output), 0755)
f, err := os.OpenFile(cfg.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logger.Default().Warn(err)
} else {
out = f
}
}
}
opts = append(opts, xlogger.OutputLoggerOption(out))
return xlogger.NewLogger(opts...)
}
func buildAPIService(cfg *config.APIConfig) (service.Service, error) {
auther := auth_parser.ParseAutherFromAuth(cfg.Auth)
if cfg.Auther != "" {
auther = registry.AutherRegistry().Get(cfg.Auther)
}
return api.NewService(
cfg.Addr,
api.PathPrefixOption(cfg.PathPrefix),
api.AccessLogOption(cfg.AccessLog),
api.AutherOption(auther),
)
}
func buildMetricsService(cfg *config.MetricsConfig) (service.Service, error) {
auther := auth_parser.ParseAutherFromAuth(cfg.Auth)
if cfg.Auther != "" {
auther = registry.AutherRegistry().Get(cfg.Auther)
}
return metrics.NewService(
cfg.Addr,
metrics.PathOption(cfg.Path),
metrics.AutherOption(auther),
)
}

View File

@ -17,17 +17,30 @@ import (
"github.com/judwhite/go-svc" "github.com/judwhite/go-svc"
) )
type stringList []string
func (l *stringList) String() string {
return fmt.Sprintf("%s", *l)
}
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}
var ( var (
cfgFile string cfgFile string
outputFormat string outputFormat string
services stringList services stringList
nodes stringList nodes stringList
debug bool debug bool
trace bool
apiAddr string apiAddr string
metricsAddr string metricsAddr string
) )
func init() { func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
args := strings.Join(os.Args[1:], " ") args := strings.Join(os.Args[1:], " ")
if strings.Contains(args, " -- ") { if strings.Contains(args, " -- ") {
@ -61,7 +74,9 @@ func worker(id int, args []string, ctx *context.Context, ret *int) {
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id)) cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id))
cmd.Run() if err := cmd.Run(); err != nil {
log.Fatal(err)
}
if cmd.ProcessState.Exited() { if cmd.ProcessState.Exited() {
*ret = cmd.ProcessState.ExitCode() *ret = cmd.ProcessState.ExitCode()
} }
@ -76,6 +91,7 @@ func init() {
flag.BoolVar(&printVersion, "V", false, "print version") flag.BoolVar(&printVersion, "V", false, "print version")
flag.StringVar(&outputFormat, "O", "", "output format, one of yaml|json format") flag.StringVar(&outputFormat, "O", "", "output format, one of yaml|json format")
flag.BoolVar(&debug, "D", false, "debug mode") flag.BoolVar(&debug, "D", false, "debug mode")
flag.BoolVar(&trace, "DD", false, "trace mode")
flag.StringVar(&apiAddr, "api", "", "api service address") flag.StringVar(&apiAddr, "api", "", "api service address")
flag.StringVar(&metricsAddr, "metrics", "", "metrics service address") flag.StringVar(&metricsAddr, "metrics", "", "metrics service address")
flag.Parse() flag.Parse()
@ -85,13 +101,15 @@ func init() {
version, runtime.Version(), runtime.GOOS, runtime.GOARCH) version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
os.Exit(0) os.Exit(0)
} }
logger.SetDefault(xlogger.NewLogger())
} }
func main() { func main() {
log := xlogger.NewLogger()
logger.SetDefault(log)
p := &program{} p := &program{}
if err := svc.Run(p); err != nil { if err := svc.Run(p); err != nil {
log.Fatal(err) logger.Default().Fatal(err)
} }
} }

View File

@ -1,79 +1,56 @@
package main package main
import ( import (
"context"
"errors"
"net/http" "net/http"
"os" "os"
"os/signal"
"strings"
"syscall"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/service"
api_service "github.com/go-gost/x/api/service"
xauth "github.com/go-gost/x/auth"
"github.com/go-gost/x/config" "github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing" "github.com/go-gost/x/config/loader"
auth_parser "github.com/go-gost/x/config/parsing/auth"
"github.com/go-gost/x/config/parsing/parser"
xmetrics "github.com/go-gost/x/metrics" xmetrics "github.com/go-gost/x/metrics"
metrics "github.com/go-gost/x/metrics/service"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"github.com/judwhite/go-svc" "github.com/judwhite/go-svc"
) )
type program struct { type program struct {
srvApi service.Service
srvMetrics service.Service
srvProfiling *http.Server
cancel context.CancelFunc
} }
func (p *program) Init(env svc.Environment) error { func (p *program) Init(env svc.Environment) error {
cfg := &config.Config{} parser.Init(parser.Args{
if cfgFile != "" { CfgFile: cfgFile,
if err := cfg.ReadFile(cfgFile); err != nil { Services: services,
return err Nodes: nodes,
} Debug: debug,
} Trace: trace,
ApiAddr: apiAddr,
MetricsAddr: metricsAddr,
})
cmdCfg, err := buildConfigFromCmd(services, nodes) return nil
}
func (p *program) Start() error {
cfg, err := parser.Parse()
if err != nil { if err != nil {
return err return err
} }
cfg = p.mergeConfig(cfg, cmdCfg)
if len(cfg.Services) == 0 && apiAddr == "" {
if err := cfg.Load(); err != nil {
return err
}
}
if v := os.Getenv("GOST_API"); v != "" {
cfg.API = &config.APIConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" {
cfg.Log = &config.LogConfig{
Level: v,
}
}
if v := os.Getenv("GOST_PROFILING"); v != "" {
cfg.Profiling = &config.ProfilingConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_METRICS"); v != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: v,
}
}
if apiAddr != "" {
cfg.API = &config.APIConfig{
Addr: apiAddr,
}
}
if debug {
if cfg.Log == nil {
cfg.Log = &config.LogConfig{}
}
cfg.Log.Level = string(logger.DebugLevel)
}
if metricsAddr != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: metricsAddr,
}
}
logger.SetDefault(logFromConfig(cfg.Log))
if outputFormat != "" { if outputFormat != "" {
if err := cfg.Write(os.Stdout, outputFormat); err != nil { if err := cfg.Write(os.Stdout, outputFormat); err != nil {
@ -82,115 +59,216 @@ func (p *program) Init(env svc.Environment) error {
os.Exit(0) os.Exit(0)
} }
parsing.BuildDefaultTLSConfig(cfg.TLS)
config.Set(cfg) config.Set(cfg)
if err := loader.Load(cfg); err != nil {
return err
}
if err := p.run(cfg); err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
go p.reload(ctx)
return nil return nil
} }
func (p *program) Start() error { func (p *program) run(cfg *config.Config) error {
log := logger.Default() for _, svc := range registry.ServiceRegistry().GetAll() {
cfg := config.Global()
if cfg.API != nil {
s, err := buildAPIService(cfg.API)
if err != nil {
return err
}
go func() {
defer s.Close()
log.Info("api service on ", s.Addr())
log.Fatal(s.Serve())
}()
}
if cfg.Profiling != nil {
go func() {
addr := cfg.Profiling.Addr
if addr == "" {
addr = ":6060"
}
log.Info("profiling server on ", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}()
}
if cfg.Metrics != nil {
xmetrics.Init(xmetrics.NewMetrics())
if cfg.Metrics.Addr != "" {
s, err := buildMetricsService(cfg.Metrics)
if err != nil {
log.Fatal(err)
}
go func() {
defer s.Close()
log.Info("metrics service on ", s.Addr())
log.Fatal(s.Serve())
}()
}
}
for _, svc := range buildService(cfg) {
svc := svc svc := svc
go func() { go func() {
svc.Serve() svc.Serve()
}() }()
} }
if p.srvApi != nil {
p.srvApi.Close()
p.srvApi = nil
}
if cfg.API != nil {
s, err := buildApiService(cfg.API)
if err != nil {
return err
}
p.srvApi = s
go func() {
defer s.Close()
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@api"})
log.Info("listening on ", s.Addr())
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}()
}
if p.srvMetrics != nil {
p.srvMetrics.Close()
p.srvMetrics = nil
}
if cfg.Metrics != nil && cfg.Metrics.Addr != "" {
s, err := buildMetricsService(cfg.Metrics)
if err != nil {
return err
}
p.srvMetrics = s
xmetrics.Enable(true)
go func() {
defer s.Close()
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@metrics"})
log.Info("listening on ", s.Addr())
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}()
}
if p.srvProfiling != nil {
p.srvProfiling.Close()
p.srvProfiling = nil
}
if cfg.Profiling != nil {
addr := cfg.Profiling.Addr
if addr == "" {
addr = ":6060"
}
s := &http.Server{
Addr: addr,
}
p.srvProfiling = s
go func() {
defer s.Close()
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@profiling"})
log.Info("listening on ", addr)
if err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Error(err)
}
}()
}
return nil return nil
} }
func (p *program) Stop() error { func (p *program) Stop() error {
if p.cancel != nil {
p.cancel()
}
for name, srv := range registry.ServiceRegistry().GetAll() { for name, srv := range registry.ServiceRegistry().GetAll() {
srv.Close() srv.Close()
logger.Default().Debugf("service %s shutdown", name) logger.Default().Debugf("service %s shutdown", name)
} }
if p.srvApi != nil {
p.srvApi.Close()
logger.Default().Debug("service @api shutdown")
}
if p.srvMetrics != nil {
p.srvMetrics.Close()
logger.Default().Debug("service @metrics shutdown")
}
if p.srvProfiling != nil {
p.srvProfiling.Close()
logger.Default().Debug("service @profiling shutdown")
}
return nil return nil
} }
func (p *program) mergeConfig(cfg1, cfg2 *config.Config) *config.Config { func (p *program) reload(ctx context.Context) {
if cfg1 == nil { c := make(chan os.Signal, 1)
return cfg2 signal.Notify(c, syscall.SIGHUP)
}
if cfg2 == nil {
return cfg1
}
cfg := &config.Config{ for {
Services: append(cfg1.Services, cfg2.Services...), select {
Chains: append(cfg1.Chains, cfg2.Chains...), case <-c:
Hops: append(cfg1.Hops, cfg2.Hops...), if err := p.reloadConfig(); err != nil {
Authers: append(cfg1.Authers, cfg2.Authers...), logger.Default().Error(err)
Admissions: append(cfg1.Admissions, cfg2.Admissions...), } else {
Bypasses: append(cfg1.Bypasses, cfg2.Bypasses...), logger.Default().Info("config reloaded")
Resolvers: append(cfg1.Resolvers, cfg2.Resolvers...), }
Hosts: append(cfg1.Hosts, cfg2.Hosts...),
Ingresses: append(cfg1.Ingresses, cfg2.Ingresses...),
Recorders: append(cfg1.Recorders, cfg2.Recorders...),
Limiters: append(cfg1.Limiters, cfg2.Limiters...),
CLimiters: append(cfg1.CLimiters, cfg2.CLimiters...),
RLimiters: append(cfg1.RLimiters, cfg2.RLimiters...),
TLS: cfg1.TLS,
Log: cfg1.Log,
API: cfg1.API,
Metrics: cfg1.Metrics,
Profiling: cfg1.Profiling,
}
if cfg2.TLS != nil {
cfg.TLS = cfg2.TLS
}
if cfg2.Log != nil {
cfg.Log = cfg2.Log
}
if cfg2.API != nil {
cfg.API = cfg2.API
}
if cfg2.Metrics != nil {
cfg.Metrics = cfg2.Metrics
}
if cfg2.Profiling != nil {
cfg.Profiling = cfg2.Profiling
}
return cfg case <-ctx.Done():
return
}
}
}
func (p *program) reloadConfig() error {
cfg, err := parser.Parse()
if err != nil {
return err
}
config.Set(cfg)
if err := loader.Load(cfg); err != nil {
return err
}
if err := p.run(cfg); err != nil {
return err
}
return nil
}
func buildApiService(cfg *config.APIConfig) (service.Service, error) {
var authers []auth.Authenticator
if auther := auth_parser.ParseAutherFromAuth(cfg.Auth); auther != nil {
authers = append(authers, auther)
}
if cfg.Auther != "" {
authers = append(authers, registry.AutherRegistry().Get(cfg.Auther))
}
var auther auth.Authenticator
if len(authers) > 0 {
auther = xauth.AuthenticatorGroup(authers...)
}
network := "tcp"
addr := cfg.Addr
if strings.HasPrefix(addr, "unix://") {
network = "unix"
addr = strings.TrimPrefix(addr, "unix://")
}
return api_service.NewService(
network, addr,
api_service.PathPrefixOption(cfg.PathPrefix),
api_service.AccessLogOption(cfg.AccessLog),
api_service.AutherOption(auther),
)
}
func buildMetricsService(cfg *config.MetricsConfig) (service.Service, error) {
auther := auth_parser.ParseAutherFromAuth(cfg.Auth)
if cfg.Auther != "" {
auther = registry.AutherRegistry().Get(cfg.Auther)
}
network := "tcp"
addr := cfg.Addr
if strings.HasPrefix(addr, "unix://") {
network = "unix"
addr = strings.TrimPrefix(addr, "unix://")
}
return metrics.NewService(
network, addr,
metrics.PathOption(cfg.Path),
metrics.AutherOption(auther),
)
} }

View File

@ -6,7 +6,9 @@ import (
_ "github.com/go-gost/x/connector/forward" _ "github.com/go-gost/x/connector/forward"
_ "github.com/go-gost/x/connector/http" _ "github.com/go-gost/x/connector/http"
_ "github.com/go-gost/x/connector/http2" _ "github.com/go-gost/x/connector/http2"
_ "github.com/go-gost/x/connector/masque"
_ "github.com/go-gost/x/connector/relay" _ "github.com/go-gost/x/connector/relay"
_ "github.com/go-gost/x/connector/router"
_ "github.com/go-gost/x/connector/serial" _ "github.com/go-gost/x/connector/serial"
_ "github.com/go-gost/x/connector/sni" _ "github.com/go-gost/x/connector/sni"
_ "github.com/go-gost/x/connector/socks/v4" _ "github.com/go-gost/x/connector/socks/v4"
@ -17,6 +19,7 @@ import (
_ "github.com/go-gost/x/connector/tcp" _ "github.com/go-gost/x/connector/tcp"
_ "github.com/go-gost/x/connector/tunnel" _ "github.com/go-gost/x/connector/tunnel"
_ "github.com/go-gost/x/connector/unix" _ "github.com/go-gost/x/connector/unix"
_ "github.com/go-gost/x/connector/masque"
// Register dialers // Register dialers
_ "github.com/go-gost/x/dialer/direct" _ "github.com/go-gost/x/dialer/direct"
@ -26,8 +29,11 @@ import (
_ "github.com/go-gost/x/dialer/http2" _ "github.com/go-gost/x/dialer/http2"
_ "github.com/go-gost/x/dialer/http2/h2" _ "github.com/go-gost/x/dialer/http2/h2"
_ "github.com/go-gost/x/dialer/http3" _ "github.com/go-gost/x/dialer/http3"
_ "github.com/go-gost/x/dialer/http3/masque"
_ "github.com/go-gost/x/dialer/http3/wt"
_ "github.com/go-gost/x/dialer/icmp" _ "github.com/go-gost/x/dialer/icmp"
_ "github.com/go-gost/x/dialer/kcp" _ "github.com/go-gost/x/dialer/kcp"
_ "github.com/go-gost/x/dialer/mtcp"
_ "github.com/go-gost/x/dialer/mtls" _ "github.com/go-gost/x/dialer/mtls"
_ "github.com/go-gost/x/dialer/mws" _ "github.com/go-gost/x/dialer/mws"
_ "github.com/go-gost/x/dialer/obfs/http" _ "github.com/go-gost/x/dialer/obfs/http"
@ -44,16 +50,21 @@ import (
_ "github.com/go-gost/x/dialer/ws" _ "github.com/go-gost/x/dialer/ws"
// Register handlers // Register handlers
_ "github.com/go-gost/x/handler/api"
_ "github.com/go-gost/x/handler/auto" _ "github.com/go-gost/x/handler/auto"
_ "github.com/go-gost/x/handler/dns" _ "github.com/go-gost/x/handler/dns"
_ "github.com/go-gost/x/handler/file"
_ "github.com/go-gost/x/handler/forward/local" _ "github.com/go-gost/x/handler/forward/local"
_ "github.com/go-gost/x/handler/forward/remote" _ "github.com/go-gost/x/handler/forward/remote"
_ "github.com/go-gost/x/handler/http" _ "github.com/go-gost/x/handler/http"
_ "github.com/go-gost/x/handler/http2" _ "github.com/go-gost/x/handler/http2"
_ "github.com/go-gost/x/handler/http3" _ "github.com/go-gost/x/handler/http3"
_ "github.com/go-gost/x/handler/masque"
_ "github.com/go-gost/x/handler/metrics"
_ "github.com/go-gost/x/handler/redirect/tcp" _ "github.com/go-gost/x/handler/redirect/tcp"
_ "github.com/go-gost/x/handler/redirect/udp" _ "github.com/go-gost/x/handler/redirect/udp"
_ "github.com/go-gost/x/handler/relay" _ "github.com/go-gost/x/handler/relay"
_ "github.com/go-gost/x/handler/router"
_ "github.com/go-gost/x/handler/serial" _ "github.com/go-gost/x/handler/serial"
_ "github.com/go-gost/x/handler/sni" _ "github.com/go-gost/x/handler/sni"
_ "github.com/go-gost/x/handler/socks/v4" _ "github.com/go-gost/x/handler/socks/v4"
@ -63,6 +74,7 @@ import (
_ "github.com/go-gost/x/handler/sshd" _ "github.com/go-gost/x/handler/sshd"
_ "github.com/go-gost/x/handler/tap" _ "github.com/go-gost/x/handler/tap"
_ "github.com/go-gost/x/handler/tun" _ "github.com/go-gost/x/handler/tun"
_ "github.com/go-gost/x/handler/tungo"
_ "github.com/go-gost/x/handler/tunnel" _ "github.com/go-gost/x/handler/tunnel"
_ "github.com/go-gost/x/handler/unix" _ "github.com/go-gost/x/handler/unix"
@ -75,8 +87,10 @@ import (
_ "github.com/go-gost/x/listener/http2/h2" _ "github.com/go-gost/x/listener/http2/h2"
_ "github.com/go-gost/x/listener/http3" _ "github.com/go-gost/x/listener/http3"
_ "github.com/go-gost/x/listener/http3/h3" _ "github.com/go-gost/x/listener/http3/h3"
_ "github.com/go-gost/x/listener/http3/wt"
_ "github.com/go-gost/x/listener/icmp" _ "github.com/go-gost/x/listener/icmp"
_ "github.com/go-gost/x/listener/kcp" _ "github.com/go-gost/x/listener/kcp"
_ "github.com/go-gost/x/listener/mtcp"
_ "github.com/go-gost/x/listener/mtls" _ "github.com/go-gost/x/listener/mtls"
_ "github.com/go-gost/x/listener/mws" _ "github.com/go-gost/x/listener/mws"
_ "github.com/go-gost/x/listener/obfs/http" _ "github.com/go-gost/x/listener/obfs/http"
@ -94,6 +108,7 @@ import (
_ "github.com/go-gost/x/listener/tcp" _ "github.com/go-gost/x/listener/tcp"
_ "github.com/go-gost/x/listener/tls" _ "github.com/go-gost/x/listener/tls"
_ "github.com/go-gost/x/listener/tun" _ "github.com/go-gost/x/listener/tun"
_ "github.com/go-gost/x/listener/tungo"
_ "github.com/go-gost/x/listener/udp" _ "github.com/go-gost/x/listener/udp"
_ "github.com/go-gost/x/listener/unix" _ "github.com/go-gost/x/listener/unix"
_ "github.com/go-gost/x/listener/ws" _ "github.com/go-gost/x/listener/ws"

View File

@ -1,5 +1,5 @@
package main package main
const ( var (
version = "3.0.0-rc9" version = "3.3.0"
) )

View File

@ -1,4 +0,0 @@
version: "3.4"
services:
gost:
build: .

213
go.mod
View File

@ -1,119 +1,170 @@
module github.com/go-gost/gost module github.com/go-gost/gost
go 1.21 go 1.26.3
replace github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a
require ( require (
github.com/go-gost/core v0.0.0-20231015073540-f08c81460234 github.com/go-gost/core v0.4.1
github.com/go-gost/x v0.0.0-20231015155552-5ab729b166b4 github.com/go-gost/x v0.10.11-0.20260605152603-e45d9a8cc81a
github.com/judwhite/go-svc v1.2.1 github.com/judwhite/go-svc v1.2.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0 github.com/moby/moby/client v0.4.0
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.42.0
) )
require ( require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-iptables v0.5.0 // indirect github.com/coreos/go-iptables v0.5.0 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/docker/go-connections v0.7.0 // indirect
github.com/gin-contrib/cors v1.3.1 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dunglas/httpsfv v1.1.0 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/cors v1.7.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect github.com/gin-gonic/gin v1.10.1 // indirect
github.com/go-gost/gosocks4 v0.0.1 // indirect github.com/go-gost/go-shadowsocks2 v0.1.3 // indirect
github.com/go-gost/gosocks5 v0.4.0 // indirect github.com/go-gost/gosocks4 v0.1.0 // indirect
github.com/go-gost/plugin v0.0.0-20231015073745-fc558b9a3146 // indirect github.com/go-gost/gosocks5 v0.5.0 // indirect
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 // indirect github.com/go-gost/plugin v0.3.0 // indirect
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 // indirect github.com/go-gost/relay v0.6.0 // indirect
github.com/go-gost/tls-dissector v0.2.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gopacket v1.1.19 // indirect github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/reedsolomon v1.9.9 // indirect github.com/klauspost/reedsolomon v1.11.8 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/magiconair/properties v1.8.10 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.56 // indirect github.com/miekg/dns v1.1.61 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.54.1 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pion/dtls/v3 v3.1.1 // indirect
github.com/pion/dtls/v2 v2.2.6 // indirect github.com/pion/logging v0.2.4 // indirect
github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pion/transport/v2 v2.0.2 // indirect github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pion/udp/v2 v2.0.1 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/procfs v0.11.1 // indirect github.com/prometheus/client_model v0.6.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/prometheus/common v0.48.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/quic-go/quic-go v0.38.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.1 // indirect
github.com/quic-go/webtransport-go v0.10.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rs/xid v1.3.0 // indirect github.com/rs/xid v1.3.0 // indirect
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect github.com/shadowsocks/go-shadowsocks2 v0.1.6-0.20241020092332-e1fe9ea73740 // indirect
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.14.0 // indirect github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/templexxx/cpu v0.0.7 // indirect github.com/templexxx/cpu v0.1.1 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect github.com/templexxx/xorsimd v0.4.3 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/vishvananda/netlink v1.1.0 // indirect github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtaci/kcp-go/v5 v5.6.1 // indirect github.com/vulcand/predicate v1.2.0 // indirect
github.com/xtaci/smux v1.5.24 // indirect github.com/xjasonlyu/tun2socks/v2 v2.6.0 // indirect
github.com/xtaci/kcp-go/v5 v5.6.5 // indirect
github.com/xtaci/smux v1.5.31 // indirect
github.com/xtaci/tcpraw v1.2.25 // indirect github.com/xtaci/tcpraw v1.2.25 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect github.com/yl2chen/cidranger v1.0.2 // indirect
golang.org/x/arch v0.3.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.14.0 // indirect github.com/zalando/go-keyring v0.2.4 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
golang.org/x/mod v0.12.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
golang.org/x/net v0.17.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
golang.org/x/sys v0.13.0 // indirect go.opentelemetry.io/otel v1.41.0 // indirect
golang.org/x/text v0.13.0 // indirect go.opentelemetry.io/otel/metric v1.41.0 // indirect
golang.org/x/time v0.3.0 // indirect go.opentelemetry.io/otel/trace v1.41.0 // indirect
golang.org/x/tools v0.13.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.org/x/arch v0.8.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 // indirect golang.org/x/crypto v0.50.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
google.golang.org/grpc v1.58.1 // indirect golang.org/x/mod v0.34.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect golang.org/x/net v0.53.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.43.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 // indirect
) )

888
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Check Root User # Check Root User
@ -42,7 +42,7 @@ install_gost() {
armv7*) armv7*)
cpu_arch="armv7" cpu_arch="armv7"
;; ;;
aarch64) aarch64|arm64)
cpu_arch="arm64" cpu_arch="arm64"
;; ;;
i686) i686)
@ -57,29 +57,31 @@ install_gost() {
mipsel*) mipsel*)
cpu_arch="mipsle" cpu_arch="mipsle"
;; ;;
riscv64)
cpu_arch="riscv64"
;;
*) *)
echo "Unsupported CPU architecture." echo "Unsupported CPU architecture."
exit 1 exit 1
;; ;;
esac esac
get_download_url="$base_url/tags/$version" get_download_url="$base_url/tags/$version"
download_url=$(curl -s "$get_download_url" | grep -Eo "\"browser_download_url\": \".*${os}.*${cpu_arch}.*\"" | awk -F'["]' '{print $4}') download_url=$(curl -s "$get_download_url" | awk -F'"' -v re=".*${os}.*${cpu_arch}.*" '/"browser_download_url":/ && $4 ~ re { print $4 }' | head -n 1)
# Download the binary # Download and install the binary
echo "Downloading gost version $version..." install_path="/usr/local/bin"
curl -fsSL -o gost.tar.gz $download_url echo "Downloading and installing gost version $version..."
curl -fsSL "$download_url" | tar -xzC "$install_path" gost
chmod +x "$install_path/gost"
# Extract and install the binary # Remove binary from macOS quarantine when installing for first time
echo "Installing gost..." [[ "$os" == "darwin" ]] && { xattr -d com.apple.quarantine "$install_path/gost" 2>&-; }
tar -xzf gost.tar.gz
chmod +x gost
mv gost /usr/local/bin/gost
echo "gost installation completed!" echo "gost installation completed!"
} }
# Retrieve available versions from GitHub API # Retrieve available versions from GitHub API
versions=$(curl -s "$base_url" | grep -oP 'tag_name": "\K[^"]+') versions=$(curl -s "$base_url" | awk -F'"' '/"tag_name":/ {print $4}')
# Check if --install option provided # Check if --install option provided
if [[ "$1" == "--install" ]]; then if [[ "$1" == "--install" ]]; then

View File

@ -0,0 +1,4 @@
FROM alpine:3.22
# add tools needed by e2e containers and health checks
RUN apk add --no-cache iptables curl netcat-openbsd python3

View File

@ -0,0 +1,53 @@
# End-to-End Tests
Integration tests that spin up real gost instances inside Docker containers and verify protocol behavior over the network.
## Prerequisites
- [Docker](https://docs.docker.com/get-docker/) (running daemon)
- Go toolchain (for compiling the gost binary under test)
## Running
From the repository root:
```bash
# Run all e2e tests
go test ./tests/e2e/ -v -timeout 10m
# Run a specific test suite
go test ./tests/e2e/ -v -run TestShadowsocksSuite -timeout 5m
go test ./tests/e2e/ -v -run TestParallelSelectorSuite -timeout 5m
# Use a pre-built gost binary (skips compilation)
go test ./tests/e2e/ -v -gost-bin /path/to/gost
```
## Architecture
```
tests/e2e/
├── Dockerfile # Shared base image (Alpine + curl, python3, etc.)
├── main_test.go # TestMain: compiles gost, creates Docker network
├── utils.go # Helpers: container lifecycle, config rendering
├── scripts/
│ ├── tcp_echo.py # HTTP echo server (responds with "hello-gost")
│ └── udp_echo.py # UDP echo server (reflects payloads)
├── testdata/ # config files or data files for running cases
├── shadowsocks_test.go # Shadowsocks protocol tests
└── parallel_selector_test.go # Parallel node selector tests
```
### How it works
1. **TestMain** (`main_test.go`) compiles the gost binary from `../../cmd/gost` (unless `-gost-bin` is provided) and creates a shared Docker network for all containers.
2. Each test suite starts echo server containers (TCP/UDP), then launches separate gost containers for server and client roles.
3. Client configs use Go template syntax (`{{.ServerAddr}}`) so the server address is injected at runtime.
4. Tests verify end-to-end connectivity by sending traffic through the gost proxy chain and checking that the echo server responds correctly.
## Tips
- Increase `-timeout` for CI or slow networks. Container image builds on first run take extra time.
- Use `-gost-bin` to avoid recompiling when iterating on tests locally.
- Add `-v` to see container log output on failure.
- RunGostContainer will wait for exposedPorts automatically, but it's not reliable for udp ports. So, you should check the readiness of udp ports inside cases.

View File

@ -0,0 +1,50 @@
package e2e
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"testing"
"github.com/testcontainers/testcontainers-go/network"
)
var SharedNetworkName string
func TestMain(m *testing.M) {
flag.Parse()
ctx := context.Background()
shouldCleanup := false
if GostBinPath == "" {
GostBinPath = "/tmp/gost-test-bin"
cmd := exec.Command("go", "build", "-o", GostBinPath, "../../cmd/gost")
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("Failed to compile gost: %v\n", err)
os.Exit(1)
}
shouldCleanup = true
}
net, err := network.New(ctx)
if err != nil {
fmt.Printf("Failed to create network: %v\n", err)
os.Exit(1)
}
SharedNetworkName = net.Name
code := m.Run()
net.Remove(ctx)
if shouldCleanup {
os.Remove(GostBinPath)
}
os.Exit(code)
}

View File

@ -0,0 +1,57 @@
package e2e
import (
"context"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/suite"
"github.com/testcontainers/testcontainers-go"
)
type ParallelSelectorSuite struct {
suite.Suite
ctx context.Context
echoC testcontainers.Container
echoIP string
}
func (s *ParallelSelectorSuite) SetupSuite() {
s.ctx = context.Background()
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.echoC = echoC
echoIP, err := echoC.ContainerIP(s.ctx)
s.Require().NoError(err)
s.echoIP = echoIP
}
func (s *ParallelSelectorSuite) TearDownSuite() {
if s.echoC != nil {
s.echoC.Terminate(s.ctx)
}
}
func (s *ParallelSelectorSuite) TestParallelSelector() {
gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, "testdata/parallel_selector/server.yaml", "8080/tcp")
s.Require().NoError(err)
defer gostC.Terminate(s.ctx)
// Test the proxy by running curl inside the gost container
cmd := []string{"curl", "-v", "-s", "-x", "http://127.0.0.1:8080", fmt.Sprintf("http://%s:5678", s.echoIP)}
code, out, err := gostC.Exec(s.ctx, cmd)
s.Require().NoError(err)
body, err := io.ReadAll(out)
s.Require().NoError(err)
s.Require().Equal(0, code)
s.Require().Contains(string(body), "hello-gost")
}
func TestParallelSelectorSuite(t *testing.T) {
suite.Run(t, new(ParallelSelectorSuite))
}

View File

@ -0,0 +1,16 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b"hello-gost"
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
return
HTTPServer(("0.0.0.0", 5678), Handler).serve_forever()

View File

@ -0,0 +1,9 @@
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 5679))
while True:
data, addr = sock.recvfrom(2048)
sock.sendto(data, addr)

View File

@ -0,0 +1,160 @@
package e2e
import (
"context"
"fmt"
"io"
"net"
"os"
"strings"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/testcontainers/testcontainers-go"
)
type ShadowsocksSuite struct {
suite.Suite
ctx context.Context
echoC testcontainers.Container
echoIP string
udpC testcontainers.Container
}
func (s *ShadowsocksSuite) SetupSuite() {
s.ctx = context.Background()
s.T().Logf("start tcp echo container...")
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.echoC = echoC
echoIP, err := echoC.ContainerIP(s.ctx)
s.Require().NoError(err)
s.echoIP = echoIP
s.T().Logf("start udp echo container...")
udpC, err := RunUDPEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.udpC = udpC
}
func (s *ShadowsocksSuite) TearDownSuite() {
if s.echoC != nil {
s.echoC.Terminate(s.ctx)
}
if s.udpC != nil {
s.udpC.Terminate(s.ctx)
}
}
func (s *ShadowsocksSuite) TestShadowsocksTCP() {
s.runTCPCase("aes256gcm", "testdata/shadowsocks/tcp_server_aes256gcm.yaml", "testdata/shadowsocks/tcp_client_aes256gcm.yaml")
s.runTCPCase("chacha20", "testdata/shadowsocks/tcp_server_chacha20.yaml", "testdata/shadowsocks/tcp_client_chacha20.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocks2022TCP() {
s.runTCPCase("2022-aes128", "testdata/shadowsocks/tcp_server_2022_aes128.yaml", "testdata/shadowsocks/tcp_client_2022_aes128.yaml")
s.runTCPCase("2022-aes256", "testdata/shadowsocks/tcp_server_2022_aes256.yaml", "testdata/shadowsocks/tcp_client_2022_aes256.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocks2022TCPMultiPSK() {
s.runTCPCase("2022-aes128-multipsk", "testdata/shadowsocks/tcp_server_2022_aes128_multipsk.yaml", "testdata/shadowsocks/tcp_client_2022_aes128_multipsk.yaml")
s.runTCPCase("2022-aes256-multipsk", "testdata/shadowsocks/tcp_server_2022_aes256_multipsk.yaml", "testdata/shadowsocks/tcp_client_2022_aes256_multipsk.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocksUDP() {
s.runUDPCase("aes256gcm", "testdata/shadowsocks/udp_server_aes256gcm.yaml", "testdata/shadowsocks/udp_client_aes256gcm.yaml")
}
func (s *ShadowsocksSuite) TestShadowsocks2022UDP() {
s.runUDPCase("2022-aes128", "testdata/shadowsocks/udp_server_2022_aes128.yaml", "testdata/shadowsocks/udp_client_2022_aes128.yaml")
s.runUDPCase("2022-aes256", "testdata/shadowsocks/udp_server_2022_aes256.yaml", "testdata/shadowsocks/udp_client_2022_aes256.yaml")
}
func (s *ShadowsocksSuite) runUDPCase(name, serverConfig, clientConfig string) {
s.T().Run(name, func(t *testing.T) {
serverAlias := name + "-ssu-server"
serverC, err := RunGostContainerWithOptions(s.ctx, SharedNetworkName, serverConfig, []string{serverAlias}, []string{"8389/udp"})
s.Require().NoError(err)
defer serverC.Terminate(s.ctx)
rendered, err := RenderConfig(clientConfig, ConfigData{ServerAddr: serverAlias + ":8389"})
s.Require().NoError(err)
defer os.Remove(rendered)
clientC, err := RunGostContainerWithPorts(
s.ctx,
SharedNetworkName,
rendered,
"9000/udp",
)
s.Require().NoError(err)
defer clientC.Terminate(s.ctx)
host, err := clientC.Host(s.ctx)
s.Require().NoError(err)
port, err := clientC.MappedPort(s.ctx, "9000/udp")
s.Require().NoError(err)
conn, err := net.DialTimeout("udp", net.JoinHostPort(host, port.Port()), 5*time.Second)
s.Require().NoError(err)
defer conn.Close()
payload := []byte("hello-gost-udp")
buf := make([]byte, 2048)
var n int
for i := range 5 {
_, err = conn.Write(payload)
s.Require().NoError(err)
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
n, err = conn.Read(buf)
if err == nil {
break
}
s.T().Logf("udp read attempt %d failed: %v, retrying...", i+1, err)
time.Sleep(2 * time.Second)
}
if err != nil {
DumpLogs(s.T(), s.ctx, name+" udp client logs", clientC)
DumpLogs(s.T(), s.ctx, name+" udp server logs", serverC)
}
s.Require().NoError(err)
s.Require().Contains(string(buf[:n]), "hello-gost")
})
}
func TestShadowsocksSuite(t *testing.T) {
suite.Run(t, new(ShadowsocksSuite))
}
func (s *ShadowsocksSuite) runTCPCase(name, serverConfig, clientConfig string) {
s.T().Run(name, func(t *testing.T) {
serverAlias := name + "-ss-server"
serverC, err := RunGostContainerWithOptions(s.ctx, SharedNetworkName, serverConfig, []string{serverAlias}, []string{"8388/tcp"})
s.Require().NoError(err)
defer serverC.Terminate(s.ctx)
rendered, err := RenderConfig(clientConfig, ConfigData{ServerAddr: serverAlias + ":8388"})
s.Require().NoError(err)
defer os.Remove(rendered)
clientC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, rendered, "8080/tcp")
s.Require().NoError(err)
defer clientC.Terminate(s.ctx)
cmd := []string{"curl", "-v", "-s", "-x", "http://127.0.0.1:8080", fmt.Sprintf("http://%s:5678", s.echoIP)}
code, out, err := clientC.Exec(s.ctx, cmd)
s.Require().NoError(err)
body, err := io.ReadAll(out)
s.Require().NoError(err)
if code != 0 || !strings.Contains(string(body), "hello-gost") {
DumpLogs(s.T(), s.ctx, name+" client logs", clientC)
DumpLogs(s.T(), s.ctx, name+" server logs", serverC)
}
s.Require().Equal(0, code)
s.Require().Contains(string(body), "hello-gost")
})
}

View File

@ -0,0 +1,30 @@
services:
- name: proxy
addr: :8080
handler:
type: http
chain: my-chain
listener:
type: tcp
- name: dummy-1
addr: :18081
handler:
type: http
chains:
- name: my-chain
hops:
- name: hop-1
selector:
strategy: parallel
nodes:
- name: node-1
addr: 127.0.0.1:18081
connector:
type: http
# non existed node
- name: node-2
addr: 127.0.0.1:18082
connector:
type: http

View File

@ -0,0 +1,23 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
dialer:
type: tcp

View File

@ -0,0 +1,23 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==:Vbwi6yqCwvPMPR1bCi32Dg==
dialer:
type: tcp

View File

@ -0,0 +1,23 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
dialer:
type: tcp

View File

@ -0,0 +1,23 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
dialer:
type: tcp

View File

@ -0,0 +1,23 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: aes-256-gcm
password: secret
dialer:
type: tcp

View File

@ -0,0 +1,23 @@
services:
- name: http-proxy
addr: :8080
handler:
type: http
chain: ss-tcp-chain
listener:
type: tcp
chains:
- name: ss-tcp-chain
hops:
- name: ss-hop
nodes:
- name: ss-node
addr: {{.ServerAddr}}
connector:
type: ss
auth:
username: chacha20-ietf-poly1305
password: secret
dialer:
type: tcp

View File

@ -0,0 +1,10 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
listener:
type: tcp

View File

@ -0,0 +1,13 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
metadata:
users:
test: Vbwi6yqCwvPMPR1bCi32Dg==
listener:
type: tcp

View File

@ -0,0 +1,10 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
listener:
type: tcp

View File

@ -0,0 +1,13 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
metadata:
users:
test: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
listener:
type: tcp

View File

@ -0,0 +1,10 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: aes-256-gcm
password: secret
listener:
type: tcp

View File

@ -0,0 +1,10 @@
services:
- name: ss-server
addr: :8388
handler:
type: ss
auth:
username: chacha20-ietf-poly1305
password: secret
listener:
type: tcp

View File

@ -0,0 +1,27 @@
services:
- name: udp-proxy
addr: :9000
handler:
type: udp
chain: ssu-chain
forwarder:
nodes:
- name: udp-echo
addr: udp-echo:5679
listener:
type: udp
chains:
- name: ssu-chain
hops:
- name: ssu-hop
nodes:
- name: ssu-node
addr: {{.ServerAddr}}
connector:
type: ssu
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
dialer:
type: udp

View File

@ -0,0 +1,27 @@
services:
- name: udp-proxy
addr: :9000
handler:
type: udp
chain: ssu-chain
forwarder:
nodes:
- name: udp-echo
addr: udp-echo:5679
listener:
type: udp
chains:
- name: ssu-chain
hops:
- name: ssu-hop
nodes:
- name: ssu-node
addr: {{.ServerAddr}}
connector:
type: ssu
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
dialer:
type: udp

View File

@ -0,0 +1,27 @@
services:
- name: udp-proxy
addr: :9000
handler:
type: udp
chain: ssu-chain
forwarder:
nodes:
- name: udp-echo
addr: udp-echo:5679
listener:
type: udp
chains:
- name: ssu-chain
hops:
- name: ssu-hop
nodes:
- name: ssu-node
addr: {{.ServerAddr}}
connector:
type: ssu
auth:
username: aes-256-gcm
password: secret
dialer:
type: udp

View File

@ -0,0 +1,10 @@
services:
- name: ssu-server
addr: :8389
handler:
type: ssu
auth:
username: 2022-blake3-aes-128-gcm
password: MTIzNDU2Nzg5MDEyMzQ1Ng==
listener:
type: udp

View File

@ -0,0 +1,10 @@
services:
- name: ssu-server
addr: :8389
handler:
type: ssu
auth:
username: 2022-blake3-aes-256-gcm
password: MTIzNDU2Nzg5MDEyMzQ1NjEyMzQ1Njc4OTAxMjM0NTY=
listener:
type: udp

View File

@ -0,0 +1,10 @@
services:
- name: ssu-server
addr: :8389
handler:
type: ssu
auth:
username: aes-256-gcm
password: secret
listener:
type: udp

173
tests/e2e/utils.go 100644
View File

@ -0,0 +1,173 @@
package e2e
import (
"context"
"flag"
"io"
"os"
"testing"
"text/template"
"github.com/moby/moby/client"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
var GostBinPath string
func init() {
flag.StringVar(&GostBinPath, "gost-bin", "", "Path to a pre-built gost binary (skips compilation)")
}
type ConfigData struct {
ServerAddr string
}
func DumpLogs(t *testing.T, ctx context.Context, label string, c testcontainers.Container) {
logs, err := c.Logs(ctx)
if err != nil {
return
}
defer logs.Close()
body, err := io.ReadAll(logs)
if err != nil {
return
}
t.Logf("%s:\n%s", label, string(body))
}
func RenderConfig(tmplPath string, data ConfigData) (string, error) {
tmpl, err := template.ParseFiles(tmplPath)
if err != nil {
return "", err
}
f, err := os.CreateTemp("", "gost-e2e-config-*.yaml")
if err != nil {
return "", err
}
if err := tmpl.Execute(f, data); err != nil {
f.Close()
os.Remove(f.Name())
return "", err
}
if err := f.Close(); err != nil {
os.Remove(f.Name())
return "", err
}
return f.Name(), nil
}
func RunEchoContainer(ctx context.Context, networkName string) (testcontainers.Container, error) {
req := echoContainerRequest(ctx, networkName)
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
}
func echoContainerRequest(_ context.Context, networkName string) testcontainers.ContainerRequest {
return testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: ".",
Dockerfile: "Dockerfile",
Repo: "gost-e2e",
Tag: "latest",
KeepImage: true,
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
opts.NetworkMode = "host"
},
},
Networks: []string{networkName},
NetworkAliases: map[string][]string{
networkName: {"tcp-echo"},
},
Files: []testcontainers.ContainerFile{
{HostFilePath: "scripts/tcp_echo.py", ContainerFilePath: "/scripts/tcp_echo.py", FileMode: 0644},
},
ExposedPorts: []string{"5678/tcp"},
Cmd: []string{"python3", "/scripts/tcp_echo.py"},
WaitingFor: wait.ForExposedPort(),
}
}
func RunUDPEchoContainer(ctx context.Context, networkName string) (testcontainers.Container, error) {
req := udpEchoContainerRequest(ctx, networkName)
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
}
func udpEchoContainerRequest(_ context.Context, networkName string) testcontainers.ContainerRequest {
return testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: ".",
Dockerfile: "Dockerfile",
Repo: "gost-e2e",
Tag: "latest",
KeepImage: true,
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
opts.NetworkMode = "host"
},
},
Networks: []string{networkName},
NetworkAliases: map[string][]string{
networkName: {"udp-echo"},
},
Files: []testcontainers.ContainerFile{
{HostFilePath: "scripts/udp_echo.py", ContainerFilePath: "/scripts/udp_echo.py", FileMode: 0644},
},
ExposedPorts: []string{"5679/udp"},
Cmd: []string{"python3", "/scripts/udp_echo.py"},
WaitingFor: wait.ForExposedPort().SkipInternalCheck(),
}
}
func RunGostContainer(ctx context.Context, networkName, yamlPath string) (testcontainers.Container, error) {
return runGostContainer(ctx, networkName, yamlPath, nil, nil)
}
func RunGostContainerWithPorts(ctx context.Context, networkName, yamlPath string, exposedPorts ...string) (testcontainers.Container, error) {
return runGostContainer(ctx, networkName, yamlPath, nil, exposedPorts)
}
func RunGostContainerWithOptions(ctx context.Context, networkName, yamlPath string, aliases, exposedPorts []string) (testcontainers.Container, error) {
return runGostContainer(ctx, networkName, yamlPath, aliases, exposedPorts)
}
func runGostContainer(ctx context.Context, networkName, yamlPath string, aliases, exposedPorts []string) (testcontainers.Container, error) {
req := testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: ".",
Dockerfile: "Dockerfile",
Repo: "gost-e2e",
Tag: "latest",
KeepImage: true,
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
opts.NetworkMode = "host"
},
},
ExposedPorts: exposedPorts,
// interal check for udp ports will be failed
WaitingFor: wait.ForExposedPort().SkipInternalCheck(),
Networks: []string{networkName},
NetworkAliases: map[string][]string{
networkName: aliases,
},
Files: []testcontainers.ContainerFile{
{HostFilePath: GostBinPath, ContainerFilePath: "/bin/gost", FileMode: 0755},
{HostFilePath: yamlPath, ContainerFilePath: "/config.yaml", FileMode: 0644},
},
Cmd: []string{"/bin/gost", "-C", "/config.yaml"},
}
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
}