311 Commits
v2.3.2 ... main

Author SHA1 Message Date
dependabot[bot]
57550350c8 Chore(deps): Bump golang.org/x/crypto from 0.41.0 to 0.45.0 (#504)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.41.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.41.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-19 22:19:59 -05:00
Jason Lyu
a51a483b93 Chore: bump action versions (#502) 2025-11-01 15:26:16 -04:00
sz
8fe7561186 Fix: crash on multiple start/stop due to FD being closed twice (#495) 2025-10-08 08:18:31 -04:00
Jason Lyu
ef4516522f Chore: apply gofumpt rules (#498) 2025-10-08 08:12:34 -04:00
Jason Lyu
a1a64030c4 Chore: bump to go1.25 (#490)
* bump go mods
* bump actions
* remove go.uber.org/automaxprocs
2025-09-03 23:38:43 -04:00
铃木和花
61d826947e Fix: update gVisor dependency & modify UDP handler to return a bool (#487) 2025-08-10 14:14:49 -04:00
dependabot[bot]
0c6fa80b94 Chore(deps): Bump github.com/go-chi/chi/v5 from 5.2.1 to 5.2.2 (#480)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-10 14:13:24 -04:00
Jason Lyu
4127937ea7 Chore: bump go mods (#473) 2025-05-26 05:22:28 +08:00
Jason Lyu
44f7044450 CI(release): auto set prerelease flag (#466) 2025-04-16 05:10:16 +08:00
Jason Lyu
d1eabcd312 Refactor(dialer): use DefaultDialer (#465) 2025-04-16 04:35:59 +08:00
Jason Lyu
f135a13b33 LICENSE: switch to MIT (#462)
* Change license from GPL-3.0 to MIT
2025-04-16 03:59:57 +08:00
Jason Lyu
59057d186a Chore: update README (#464) 2025-04-16 03:09:50 +08:00
Jason Lyu
551f5e8a37 Chore: bump go mods (#463) 2025-04-16 02:41:29 +08:00
Jason Lyu
3599d45714 Chore: regular go.mod bump (#459) 2025-03-18 04:55:35 +08:00
dependabot[bot]
af5d9bd0d2 Chore(deps): Bump golang.org/x/net from 0.35.0 to 0.36.0 (#458)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 10:15:20 +08:00
Petya Petrov
c834bec3eb Fix(engine): mutex never unlocked on error (#456)
* fix error when mutex never unlock

---------

Co-authored-by: devil666face <artem1999k@gmail.com>
Co-authored-by: Jason Lyu <xjasonlyu@gmail.com>
2025-03-02 06:55:54 +08:00
Jason Lyu
feedf264d2 Docs(README): use docker build status (#455) 2025-02-25 03:20:05 +08:00
Jason Lyu
7a1711d51b Chore: bump go mods (#454) 2025-02-25 02:47:22 +08:00
铃木和花
302622f84b feat: support Linux abstract namespace (#436) 2025-01-28 11:53:02 +08:00
Jason Lyu
ed948608cc Chore: bump go mods (#447) 2025-01-28 11:38:17 +08:00
dependabot[bot]
bff979b469 Chore(deps): Bump golang.org/x/net from 0.31.0 to 0.33.0 (#446)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.31.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.31.0...v0.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-28 10:42:01 +08:00
Jason Lyu
ec85410144 Chore(stale): add exempt-pr-labels (#438) 2024-12-21 04:12:05 +08:00
dependabot[bot]
43f0ba892f Chore(deps): Bump golang.org/x/crypto from 0.29.0 to 0.31.0 (#434)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.29.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.29.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 21:53:43 -05:00
寻觅
e5009398d5 Feature: add Loong64 arch (#427) 2024-11-12 07:29:06 -05:00
Jason Lyu
54ef0500d3 Chore: bump go mods (#426) 2024-11-11 04:15:24 +08:00
Hossin Asaadi
b65d23180c Feature: set open FD offset for iOS (#418)
Co-authored-by: Jason Lyu <xjasonlyu@gmail.com>
2024-10-13 03:51:27 +08:00
Jason Lyu
a821cc483c Chore: bump go mods (#413) 2024-09-30 04:44:55 +08:00
Jason Lyu
56786517dc Refactor: bufferpool new function (#407) 2024-09-23 02:39:36 +08:00
Jason Lyu
428f82694a Chore: add README credits (#408) 2024-09-23 02:39:24 +08:00
Jason Lyu
bf745d0e0e Chore: update .dockerignore (#401) 2024-09-02 06:06:38 +08:00
Jason Lyu
391d3d9f89 Refactor(pool): use generic buffer/pool (#399) 2024-09-02 05:57:22 +08:00
Jason Lyu
fc4c5c4c55 Refactor(pool): move to internal (#398) 2024-09-01 04:23:25 +08:00
Jason Lyu
978803cdf8 Refactor(socks): replace net.IP with netip.Addr (#397) 2024-09-01 04:04:01 +08:00
Jason Lyu
bd37a1a4c6 Refactor(metadata): replace net.IP with netip.Addr (#396) 2024-09-01 02:57:09 +08:00
Jason Lyu
1f09b4d42d Refactor(core): replace net.IP with netip.Addr (#395) 2024-08-31 12:00:38 +08:00
Jason Lyu
fd98f65994 Refactor(tunnel): modularize tunnel pkg (#393) 2024-08-31 11:31:18 +08:00
Jason Lyu
71c45ef87e Chore(mod): bump to go1.23 (#394) 2024-08-31 11:21:07 +08:00
Jason Lyu
601601a1dc Refactor(log): use go.uber.org/zap (#389) 2024-08-29 06:56:35 +08:00
Jason Lyu
c8c08cfeea Chore: adjust markdown layout (#388) 2024-08-21 05:27:56 +08:00
Jason Lyu
776e6470d3 Feature(actions): add testing (#378) 2024-08-21 05:27:20 +08:00
Jason Lyu
66fafd224e Chore: update README (#387)
* remove Chinese README
2024-08-21 02:14:22 +08:00
Jason Lyu
e083dafcf5 Chore(.gitignore): use github template (#381) 2024-07-22 08:53:29 +08:00
Jason Lyu
24b8cdd96b Refactor(debug): simplify version string (#380) 2024-07-22 08:34:24 +08:00
Jason Lyu
dd791e50c1 Feature: bump gVisor to latest version (#376) 2024-07-20 11:31:44 +08:00
Jason Lyu
7555425ab8 Chore: update golangci-lint settings (#377)
* remove staticcheck config
2024-07-16 10:32:59 +08:00
Jason Lyu
592517a00d Chore(actions): update versions (#375) 2024-07-16 06:22:25 +08:00
dependabot[bot]
63f71e0b84 Chore(deps): Bump github.com/gorilla/schema from 1.3.0 to 1.4.1 (#370)
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.3.0 to 1.4.1.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.3.0...v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/schema
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-12 05:19:58 +08:00
Jason Lyu
488e5b223c Chore(actions): update versions (#362) 2024-05-13 02:34:37 +08:00
Jason Lyu
0d819e1aec Fix: codeql autobuild error (#355) 2024-04-07 02:53:17 +08:00
Jason Lyu
60a63db500 Chore: bump dependency (#354)
* gVisor: `v0.0.0-20240215211334-a66ecfdd829a` => `v0.0.0-20240405191320-0878b34101b5`
2024-04-07 02:33:43 +08:00
Yawning Angel
8c7c9085c2 Fix: socks5 usernames and passwords can BOTH be up to 255 bytes (#343) 2024-02-28 10:27:14 +08:00
Jason Lyu
8653c18875 Fix: safely split command strings (#340) 2024-02-20 05:41:58 +08:00
Jason Lyu
7b1d73d86e Chore: bump to go1.22 (#337)
* Chore: update go mods
* Chore: bump to go1.22
* Chore: fix lint trigger
2024-02-16 06:47:17 +08:00
Nicolai Moore
a49ce339b7 Fix: call SetSendBufferSize when setting send buffer size option (#336) 2024-02-07 10:18:53 +08:00
Jason Lyu
2334083cf9 Chore: update CodeQL (#332) 2024-01-28 07:21:33 +08:00
Jason Lyu
01d4ac4864 Chore: update go mods (#331) 2024-01-28 07:10:26 +08:00
Jason Lyu
c8f8cb5caf Chore(release/go): use version from go.mod (#317) 2023-11-14 06:21:26 +08:00
Jason Lyu
e5bfa13a3f Chore: update go mod (#316)
* bump gvisor to `v0.0.0-20231113203814-cdee0abd0280`
2023-11-14 06:00:15 +08:00
cty
e86b3b7dc5 Perf(SOCKS5): optimize memory footprint with authentication (#315) 2023-11-13 06:28:42 +08:00
Jason Lyu
010765138c Chore: update README (#314)
* Remove deleted repositories
2023-11-03 23:09:12 +08:00
Jason Lyu
f8bddb162e Improve(proxy/ss): allow explicit none cipher mode (#312) 2023-10-25 09:36:02 +08:00
Jason Lyu
2d80a4ba3b Chore(actions/docker): use github variables (#311) 2023-10-25 00:21:43 +08:00
Jason Lyu
00a5f18ebd Feature(proxy): support gost relay protocol (#310) 2023-10-24 10:55:33 +08:00
Jason Lyu
47913b549f Fix: defer func for safeConnClose (#309) 2023-10-24 09:13:36 +08:00
Jason Lyu
2283f82bbc Refactor: direct proxy parsing (#308) 2023-10-24 04:44:17 +08:00
Jason Lyu
cbf620b2f8 Improve: use embedded go-shadowsocks2 (#306)
* Improve: use embedded go-shadowsocks2
* Chore: update go mod
2023-10-23 02:52:26 +08:00
Jason Lyu
68da4d9997 Chore: update go mod (#303) 2023-09-29 04:56:49 +08:00
Jason Lyu
78086193fb Fix: bump versions for actions (#302) 2023-09-29 04:46:13 +08:00
Jason Lyu
631fa59182 Feature: persistent wintun with GUID option (#301)
Fixes: #300
2023-09-29 04:36:29 +08:00
Jason Lyu
f448baa2ae Chore: use errors.ErrUnsupported (#294) 2023-09-05 01:51:03 +08:00
Jason Lyu
b470006fb6 Chore: bump to go1.21 (#292) 2023-08-29 01:30:11 +08:00
Jason Lyu
73ea4358cb Docker: add options for multicast groups (#290) 2023-08-28 07:04:22 +08:00
Jason Lyu
19ba20ffc0 Improve(actions): add concurrency groups (#289) 2023-08-28 03:57:16 +08:00
Jason Lyu
f1b7b4745b Chore: bump go mods (#288) 2023-08-28 03:48:38 +08:00
Amaindex
90f77548ed Feature: add support for multicast (#245)
* add support for multicast (#243)

* adjust setup

---------

Co-authored-by: xjasonlyu <xjasonlyu@gmail.com>
2023-08-28 03:40:49 +08:00
xjasonlyu
fffcbbea10 Chore: set up sponsor button 2023-08-27 14:14:27 -04:00
xjasonlyu
f588b4c731 Chore: star chart with dark theme supported 2023-08-06 13:44:24 +08:00
Jason Lyu
71efe6c7cf Chore: do not mark stale for help wanted issues (#280) 2023-07-27 10:21:20 +08:00
xjasonlyu
2067a3129e Chore: update badge links 2023-07-20 15:01:18 +08:00
yusheng
8309fddef3 Feature: fdbased open fd with offset (#272) 2023-06-30 13:18:53 +08:00
xjasonlyu
2b494a7517 Fix: HTTP proxy basic auth error (#266) 2023-06-20 14:40:54 +08:00
xjasonlyu
7ab86fd9b0 Chore: use renamed buffer pkg 2023-06-05 14:54:58 +08:00
xjasonlyu
4868427efb Chore: update go modules 2023-06-05 14:54:29 +08:00
xjasonlyu
673a942fb3 Improve: set mtu with unix ioctl 2023-05-30 17:40:46 +08:00
xjasonlyu
44ad654d72 Fix: revert to classic TUN creation (#254) 2023-05-30 17:40:45 +08:00
xjasonlyu
46c04db29f Fix: tun issue with virtio_net_hdr (#254) 2023-05-26 16:23:01 +08:00
xjasonlyu
07a6a9f096 Chore: bump gVisor version 2023-05-26 14:45:37 +08:00
xjasonlyu
cd6b118e63 Refactor: relocate simple-obfs pkg 2023-05-20 15:10:06 +08:00
xjasonlyu
9838f57e0d Refactor: relocate dialer pkg 2023-05-20 14:53:08 +08:00
xjasonlyu
9dfea44f48 Refactor: remove nat pkg 2023-05-20 14:37:49 +08:00
xjasonlyu
860cf31256 Fix: relocate unused const 2023-05-20 14:37:02 +08:00
xjasonlyu
8e20770bec Refactor: optimize http proxy 2023-05-20 14:32:16 +08:00
xjasonlyu
3320ba46e4 Fix: update tcpip.Address api 2023-05-18 21:38:29 +08:00
xjasonlyu
38bfbb2c3f Chore: update go mod 2023-05-18 21:16:44 +08:00
xjasonlyu
6c07927bba Chore: remove ref annotations 2023-05-18 21:15:04 +08:00
xjasonlyu
2813b4c581 Chore: move files to .github 2023-05-17 22:19:57 +08:00
Jason Lyu
db7c3fd7d2 Chore: create CODE_OF_CONDUCT.md (#259) 2023-05-17 22:16:38 +08:00
Jason Lyu
4fc5c03e35 Chore: create SECURITY.md (#258) 2023-05-17 22:02:30 +08:00
Vladislav Fursov
3cbbf3068a Fix(socks5): panic with unassigned reply code (#255) 2023-05-17 10:06:56 +08:00
Jason Lyu
8da083b28e Chore: nolint in tun_wireguard_unix.go (#253) 2023-05-15 15:05:46 +08:00
xjasonlyu
dc6eb815da CI(linter): enable new linters 2023-05-11 10:58:19 +08:00
Jason Lyu
3b54548914 CI: update golangci-lint config (#251) 2023-05-11 10:51:55 +08:00
Solyn
d061f1c040 Feature: support setting log output (#248)
Signed-off-by: yusheng <yusheng@tencent.com>
2023-05-06 20:49:49 +08:00
xjasonlyu
8e8ccdab89 Chore: use Star History 2023-04-25 15:25:48 +08:00
xjasonlyu
9cbc99e8f5 Chore: bump golang.org/x/sys to v0.7.0 2023-04-06 12:54:12 +08:00
xjasonlyu
2dbd2caaa9 Fix: netstats JSON response 2023-04-03 19:59:37 +08:00
LanceLi
2d0bd1d219 Improve: tunnel/tcp pipe (#219)
Co-authored-by: xjasonlyu <xjasonlyu@gmail.com>
2023-04-03 19:15:24 +08:00
xjasonlyu
61a9d26815 Minor: UDP unidirectionalPacketStream 2023-04-03 19:13:03 +08:00
xjasonlyu
4cc02c822c Minor: improve tunnel/udp 2023-04-03 18:43:02 +08:00
xjasonlyu
20499c6432 Revert: cli tcp-wait-timeout option
This reverts commit 2c51a65685.
2023-04-03 17:57:13 +08:00
xjasonlyu
ad522ebb35 Chore: minor renames 2023-04-03 17:43:19 +08:00
xjasonlyu
06d8bee2af Ignore: (*gonet.TCPConn).RemoteAddr() warning 2023-04-03 13:52:28 +08:00
xjasonlyu
b809f89411 Revert: udp-rlybuf option
As a low-level networking tool, tun2socks should be able to handle UDP packets of all possible sizes. This reverts commit fb9ca95909.
2023-04-03 12:24:07 +08:00
xjasonlyu
b8ff1859c1 Chore: update go mods 2023-04-02 20:20:56 +08:00
xjasonlyu
89c37dc156 Improve: deprecate isIgnorable
All errors should be returned by copyBuffer and handled by higher level functions.
2023-04-02 16:43:55 +08:00
xjasonlyu
1b8e063485 Fix: log message typo 2023-03-30 18:11:08 +08:00
xjasonlyu
195290884c Fix: potential crash by bad UDP address (#238) 2023-03-30 14:11:43 +08:00
xjasonlyu
3b343600e7 Chore: core loglevel to debug 2023-03-29 23:19:08 +08:00
Solyn
2c51a65685 Feature: cli tcp-wait-timeout option (#156)
Co-authored-by: xjasonlyu <xjasonlyu@gmail.com>
2023-03-29 17:41:01 +08:00
xjasonlyu
fb9ca95909 Feature: add udp-rlybuf option
* Default UDP relay buffer size: 16KiB
2023-03-29 16:56:03 +08:00
xjasonlyu
1e99f2d580 Chore: debug level log 2023-03-28 18:04:46 +08:00
xjasonlyu
ce15b1b2c2 Improve: with default tracker 2023-03-28 18:00:05 +08:00
xjasonlyu
c036db2e23 Chore: rm unused code 2023-03-27 19:11:56 +08:00
Jack
007c97fe67 Feature: support pre&post-up exec (#233)
* Add tun pre/post script
* Improve: pre & post tun

---------

Co-authored-by: xjasonlyu <xjasonlyu@gmail.com>
2023-03-27 13:26:39 +08:00
xjasonlyu
7327f2c784 Improve: with gvisor internal log 2023-03-27 13:21:10 +08:00
xjasonlyu
66860d3de8 Improve: enhance error handling 2023-03-27 00:43:39 +08:00
xjasonlyu
c61d7b5a20 Chore: error is ignorable 2023-03-26 23:07:13 +08:00
xjasonlyu
680feede3b Improve: add tun io lock 2023-03-26 22:56:01 +08:00
xjasonlyu
22b15f6fab Improve: redirect stack log 2023-03-26 22:48:28 +08:00
xjasonlyu
f7b4f75ed4 Fix: align with wireguard io 2023-03-26 22:10:21 +08:00
xjasonlyu
29feac8cd4 Chore: bump go mods 2023-03-26 14:40:39 +08:00
xjasonlyu
6cfc25309e Fix: rand linter
`math/rand.Read` has been deprecated since Go 1.20
2023-02-21 15:06:05 +08:00
xjasonlyu
1f63b239c3 Chore: update linter 2023-02-21 14:55:01 +08:00
xjasonlyu
041bc510ea Chore: bump go modules 2023-02-21 14:05:25 +08:00
xjasonlyu
51d8a27289 Chore: bump gvisor
v0.0.0-20230219185229-4f1045309c43
2023-02-21 14:03:17 +08:00
xjasonlyu
5ffb0186bf Improve: do not stale labeled issues 2023-02-12 14:04:35 +08:00
xjasonlyu
8a2e5cebeb Fix: deprecate rand.Seed 2023-02-06 15:39:37 +08:00
xjasonlyu
846d3d87a7 Chore: bump go.mod to 1.20 2023-02-06 15:39:15 +08:00
xjasonlyu
b491e17bfa Chore: add v2 to prefix 2023-02-06 15:38:46 +08:00
xjasonlyu
fbe4c22347 Revert: undo crypto/rand
This reverts commit 4a8bf64cb1.
2023-02-06 15:31:05 +08:00
xjasonlyu
4a8bf64cb1 Improve: use crypto/rand 2023-02-04 14:26:41 +08:00
xjasonlyu
059f661862 Chore: bump to go1.20 2023-02-04 13:52:29 +08:00
xjasonlyu
ab05092671 Chore: update go mod 2023-02-04 13:52:19 +08:00
xjasonlyu
6809e7f835 Chore: bump gVisor 2023-01-22 13:27:11 +08:00
xjasonlyu
bbdc9113d7 Chore: update go mod 2023-01-13 13:40:20 +08:00
xjasonlyu
30608f4925 Chore: bump gVisor 2023-01-01 15:55:12 +08:00
xjasonlyu
39b1406ffa Chore: update mods 2022-12-17 16:57:57 +08:00
xjasonlyu
a1edb1c1bb Chore: update workflows 2022-12-17 16:53:30 +08:00
xjasonlyu
ad014648ef Fix: build badges 2022-12-16 16:54:54 +08:00
xjasonlyu
fa3317a94c Chore: bump go mods 2022-12-01 15:57:08 +08:00
xjasonlyu
8d3c28a516 Feature: support mark on FreeBSD & OpenBSD 2022-12-01 15:46:49 +08:00
LanceLi
35f6888c30 Feature: support bind interface on windows (#192) 2022-11-26 23:10:23 +08:00
xjasonlyu
3cbc74b1cf Chore: update gvisor 2022-11-08 15:21:45 +08:00
xjasonlyu
24a53467f6 Chore: deprecate set-output command 2022-10-15 15:33:20 +08:00
xjasonlyu
702ba81ccd Chore: update workflows 2022-10-14 23:26:59 +08:00
xjasonlyu
7fce6e9544 Fix: PacketBufferPtr.IsNil 2022-10-14 22:54:12 +08:00
xjasonlyu
553c2f7a17 Chore: bump modules 2022-10-14 22:36:41 +08:00
xjasonlyu
ccfb3a47f6 Fix: make build work
This commit may be reverted if gVisor updates its fdbased pkg.
2022-09-25 16:31:15 +08:00
xjasonlyu
0177157c69 Chore: deprecate openbsd-386 2022-09-25 16:23:47 +08:00
xjasonlyu
82546cd2c5 Chore: with unix build constraint 2022-09-25 15:49:39 +08:00
xjasonlyu
9895b3e048 Chore: update go mod 2022-09-25 15:08:23 +08:00
xjasonlyu
39b50f2bfb Chore: update go mod 2022-09-18 19:31:29 +08:00
xjasonlyu
c45470650b Chore: reorder imports 2022-08-07 00:25:59 +08:00
xjasonlyu
cb6408a17a Fix: update to bufferv2 2022-08-07 00:05:11 +08:00
xjasonlyu
77bd119d34 Chore: update go mod 2022-08-07 00:04:03 +08:00
xjasonlyu
595896dfc7 Chore: bump to go1.19 2022-08-06 23:51:15 +08:00
xjasonlyu
55a8d038d6 Improve: ignore tcp errors 2022-07-15 21:08:11 +08:00
xjasonlyu
e31ffce0e6 Fix: adjust endpoint change 2022-07-15 20:59:37 +08:00
xjasonlyu
267cc6d1a9 Chore: update go mod 2022-07-15 20:17:27 +08:00
xjasonlyu
31468620e7 Chore: update badge logo 2022-06-01 00:10:41 +08:00
xjasonlyu
1536735456 Chore: disable blank issues 2022-05-24 15:30:51 +08:00
xjasonlyu
b329f23a4e Chore: auth with GITHUB_TOKEN 2022-05-21 16:27:52 +08:00
xjasonlyu
1ecd587857 Fix(udp): ignore EOF 2022-04-26 16:06:01 +08:00
xjasonlyu
6076fd9a69 Chore(netstats): just render.JSON 2022-04-21 23:30:54 +08:00
xjasonlyu
3926f86613 Feature(docker): add tzdata 2022-04-07 10:56:24 +08:00
xjasonlyu
77e8ad4810 Chore: update modules 2022-04-06 23:33:47 +08:00
xjasonlyu
a8e8a2dc4c Fix(tunnel/udp): NAT source verification (#112) 2022-04-06 23:21:18 +08:00
xjasonlyu
f2cfa15945 Chore(tunnel): minify log message 2022-04-06 17:26:02 +08:00
xjasonlyu
764657d657 Chore: ignore io timeout 2022-04-06 16:07:59 +08:00
xjasonlyu
d65d3b08d0 Change: use default tcp buffer size 2022-04-06 15:48:51 +08:00
xjasonlyu
c35f28b3a3 Improve(restapi/netstats): JSON encoding 2022-04-06 15:23:02 +08:00
xjasonlyu
23a6e28768 Chore(core): format request ID 2022-04-05 23:40:57 +08:00
xjasonlyu
102b46e9f6 Refactor(tunnel): udp packet relay 2022-04-05 23:14:16 +08:00
xjasonlyu
ab728bd8cc Chore(tunnel): log tcp error 2022-04-05 23:13:38 +08:00
xjasonlyu
4aea88c36e Chore(core): print TransportEndpointID 2022-04-04 22:32:50 +08:00
xjasonlyu
e4801c3989 Fix(restapi): debug missing mountpoints 2022-04-04 22:05:47 +08:00
xjasonlyu
fd8223e4d0 Fix(docker): sh out of range warning 2022-04-04 21:15:06 +08:00
xjasonlyu
883915ab2c Chore: update README 2022-04-04 17:14:03 +08:00
xjasonlyu
4ca3c90b8c Chore: update modules 2022-04-04 17:06:11 +08:00
xjasonlyu
2e758d1960 Chore(core/tcp): add a comment 2022-04-04 17:06:06 +08:00
xjasonlyu
9797cb31c0 Refactor: return metadata.Addr only 2022-04-03 23:16:36 +08:00
xjasonlyu
8f97bda4f5 Chore: amend log messages 2022-04-03 22:31:59 +08:00
xjasonlyu
6a53c52167 Chore: add udp-timeout constraint 2022-04-03 22:09:41 +08:00
xjasonlyu
992e716216 Improve: use interface index for macos 2022-04-02 16:08:55 +08:00
xjasonlyu
289ea82829 Feature(docker): add tcp options env 2022-04-02 15:15:06 +08:00
xjasonlyu
a0d31261b9 Chore: remove redundant alias 2022-04-02 15:06:30 +08:00
xjasonlyu
31e19a0690 Change: udp timeout = time.Duration 2022-04-02 15:05:47 +08:00
xjasonlyu
596056676c Chore: rename to netstack 2022-04-02 00:23:49 +08:00
xjasonlyu
bff32beb73 Chore: add/update modules 2022-04-02 00:03:28 +08:00
xjasonlyu
551e2c345c Feature: cli tcp options 2022-04-01 23:40:17 +08:00
xjasonlyu
dc2c555865 Fix: correctly apply recv buffer size 2022-04-01 22:34:44 +08:00
xjasonlyu
81c2d6963b Chore: reorder const and func 2022-04-01 22:26:05 +08:00
xjasonlyu
b7c3c9001b Chore: add benchmark.png 2022-04-01 22:10:09 +08:00
xjasonlyu
9f239d146b Chore: separate send/recv buffer 2022-04-01 21:48:17 +08:00
xjasonlyu
3c326c01ed Change: disable tcp recv buffer auto-tuning by default 2022-04-01 21:11:33 +08:00
xjasonlyu
21eb99a37e Fix: should apply default option firstly 2022-04-01 14:28:12 +08:00
xjasonlyu
e0b0a1e94b Chore(core): amend message 2022-03-31 23:56:06 +08:00
xjasonlyu
096117dda1 Chore: minor declaration 2022-03-31 23:12:11 +08:00
xjasonlyu
21232703af Chore(core): use config.PrintFunc 2022-03-31 17:48:52 +08:00
xjasonlyu
ccf53dcb88 Chore: use mirror.Tunnel 2022-03-31 17:28:12 +08:00
xjasonlyu
8d3f8d7631 Fix: potential panic on windows (#110)
This is only a temporary solution.
2022-03-31 15:11:34 +08:00
xjasonlyu
9f7989a9d0 Fix: import missing 2022-03-31 14:06:56 +08:00
xjasonlyu
b5f61c0999 Fix(core): race cond when init 2022-03-31 00:04:12 +08:00
xjasonlyu
e6fc4adccd Feature(restapi): add netstats 2022-03-30 23:38:31 +08:00
xjasonlyu
abdbaa6b83 Refactor(engine): remove struct 2022-03-30 23:09:50 +08:00
xjasonlyu
cba7e19d22 Chore(restapi): minor adjustment 2022-03-30 17:56:18 +08:00
xjasonlyu
2aea811072 Chore: with engine 2022-03-29 19:42:44 +08:00
xjasonlyu
b166ed5e66 Refactor: stats -> restapi 2022-03-29 19:25:45 +08:00
xjasonlyu
e6911cb6fb Chore: remove config example 2022-03-29 18:45:38 +08:00
xjasonlyu
3999c5d66b Refactor(core): with error callback 2022-03-29 17:19:54 +08:00
xjasonlyu
0a9f7f123c Chore: use tcpip.Error 2022-03-29 16:18:52 +08:00
xjasonlyu
ba7a7ddc95 Chore(core/adapter): rename to TransportHandler 2022-03-29 15:26:00 +08:00
xjasonlyu
20fe2e4cd8 Chore: minor rename 2022-03-29 14:12:25 +08:00
xjasonlyu
201e79ac71 Fix: nil options panic 2022-03-29 13:39:18 +08:00
xjasonlyu
a4bedf6080 Refactor: new dialer impl 2022-03-29 13:39:18 +08:00
xjasonlyu
42d6c96b6b Fix: wrong wait logic 2022-03-29 13:39:18 +08:00
xjasonlyu
9d7dacbea1 Chore(core/device): tun constants 2022-03-29 13:39:18 +08:00
xjasonlyu
0e8b16f9a1 Fix: decrease packet reference (#106)
ref: f375784d83
2022-03-29 13:39:18 +08:00
xjasonlyu
c2af4c0c7c Fix: wait for device to close 2022-03-29 13:39:18 +08:00
xjasonlyu
ba0a4acbda Refactor: core.CreateStackWithOptions 2022-03-29 13:39:18 +08:00
xjasonlyu
898e648cb5 Chore: close stack when stop 2022-03-29 13:39:18 +08:00
xjasonlyu
620d5ac834 Chore: put log in engine 2022-03-29 13:39:18 +08:00
xjasonlyu
7df522a91f Revert: GOAMD64v1 as default (#104)
GOAMD64=v3 will not run on older x86 processors that don't support AVX2.
2022-03-29 13:39:18 +08:00
xjasonlyu
376a1eac2c Fix: early stopped engine (#105) 2022-03-29 13:39:18 +08:00
xjasonlyu
584c9c7805 Chore: explicit resolver 2022-03-29 13:39:18 +08:00
xjasonlyu
c6ca52326a Refactor: main.go 2022-03-29 13:39:18 +08:00
xjasonlyu
ae07fbdb68 Chore: delete common/automaxprocs 2022-03-29 13:39:18 +08:00
xjasonlyu
531125ee1f Chore: minor refactor 2022-03-29 13:39:18 +08:00
xjasonlyu
7ac97016fe Chore: move all to version 2022-03-29 13:39:18 +08:00
xjasonlyu
d3fc3abbb7 Migration: go 1.18 2022-03-29 13:38:50 +08:00
xjasonlyu
8bb8423e50 Chore: fix lint 2022-03-26 15:06:42 +08:00
xjasonlyu
82fd2f91c0 Chore: update action workflows 2022-03-26 14:49:57 +08:00
xjasonlyu
7bbae5549d Chore: adjust Dockerfile 2022-03-26 14:34:26 +08:00
xjasonlyu
c68dd0771e Chore: make tcpKeepaliveCount explicit 2022-02-13 14:57:31 +08:00
xjasonlyu
5679d15442 Fix: nil addr panic (#99) 2022-02-12 21:16:43 +08:00
xjasonlyu
4be2734b19 Chore: make internal/version 2022-02-09 17:29:10 +08:00
xjasonlyu
283008536b Feature: more fields in stats version 2022-02-09 17:25:55 +08:00
xjasonlyu
2f21e10be6 Feature: show debug in version 2022-02-09 17:21:20 +08:00
xjasonlyu
8d2170832c Chore(engine): improve log message 2022-02-09 16:49:54 +08:00
xjasonlyu
211831b3e6 Fix(iobased): exit outboundLoop to prevent goroutine leak 2022-02-08 20:17:30 +08:00
xjasonlyu
6a8bc0fd79 Chore(socks5): revise handshake error message 2022-02-08 18:55:03 +08:00
xjasonlyu
9d8251ac43 Fix: google/gvisor@f33d034fec (google/gvisor#7125) 2022-02-08 14:38:24 +08:00
xjasonlyu
bf35298289 Fix: suppress panic when wintun.dll not found 2022-02-07 17:31:28 +08:00
xjasonlyu
b5794661b5 Chore: update go modules 2022-02-06 21:39:56 +08:00
xjasonlyu
dc2794ae1e Improve(statistic): use google/uuid 2022-02-06 21:02:49 +08:00
xjasonlyu
6cfbf4d0e6 Chore: add TODOs 2022-02-06 20:41:49 +08:00
xjasonlyu
edec658cd0 Refactor: use core/adapter 2022-02-06 20:26:09 +08:00
xjasonlyu
93a5ff5d86 Improve(stack): add TCP recovery option 2022-02-06 20:17:45 +08:00
xjasonlyu
1b38ce2d25 Improve(socks5): unify addr parsing 2022-02-06 19:48:12 +08:00
xjasonlyu
cc56100f15 Chore(metadata): add TCPAddr method 2022-02-05 20:14:05 +08:00
xjasonlyu
575a2a66ac Chore(socks5): add rfc annotation 2022-02-05 19:16:57 +08:00
xjasonlyu
d552de237f Fix: reset timeout in udp tunnel 2022-02-05 16:59:23 +08:00
xjasonlyu
6603c1f334 Refactor: improve metadata structure 2022-02-05 16:51:17 +08:00
xjasonlyu
dd0cde04b4 Refactor: optimize UDP module
Symmetric NAT support for now.
2022-02-05 15:49:03 +08:00
xjasonlyu
14c663c40e Improve: Makefile debug target 2022-02-03 21:45:16 +08:00
xjasonlyu
6547625688 Fix: UDP packet buffer leak (#82) 2022-02-02 21:29:27 +08:00
xjasonlyu
bdf85afa3e Chore: use versionize 2022-02-02 20:29:43 +08:00
xjasonlyu
c85cf60a45 Improve: reduce bytes copy 2022-02-02 19:48:38 +08:00
xjasonlyu
47e74ed8c2 Fix: release packet buffer (#82) 2022-02-02 17:17:59 +08:00
xjasonlyu
e36c2eb226 Improve: use *channel.Endpoint for underlying I/O 2022-02-02 17:17:43 +08:00
xjasonlyu
b28349235f Chore(stack): update opts.go 2022-02-01 20:34:43 +08:00
xjasonlyu
faed47da40 Chore: fix lint 2022-02-01 17:30:34 +08:00
xjasonlyu
13b5cc71d7 Improve(stack): specify NIC options 2022-02-01 17:19:51 +08:00
xjasonlyu
d415ed35d7 Improve(iobased): impl stack.GSOEndpoint 2022-02-01 17:18:17 +08:00
xjasonlyu
cd5b9e8954 Chore: rename to fdbased 2022-02-01 17:17:09 +08:00
xjasonlyu
c2ec509cfa Fix(core): update gVisor (#82, #95) 2022-02-01 17:16:39 +08:00
xjasonlyu
b581c2e877 Style(workflows): auto reformat 2022-02-01 15:47:10 +08:00
xjasonlyu
5d81f455bd Chore(stack): update comments 2022-02-01 13:18:25 +08:00
xjasonlyu
7e268cfc7a Chore: update issue template 2022-01-31 23:18:32 +08:00
xjasonlyu
3480680806 Chore: chmod -x .go files 2022-01-31 18:32:11 +08:00
xjasonlyu
fd000c6617 Refactor: standalone metadata module 2022-01-31 16:03:10 +08:00
xjasonlyu
830c231c43 Refactor: standalone version module 2022-01-31 16:00:17 +08:00
xjasonlyu
0e5dafd36b Chore(docker): re-tag to dev 2022-01-31 14:26:31 +08:00
xjasonlyu
40dfe8807a Chore(iobased): use MTU() 2022-01-29 21:00:43 +08:00
xjasonlyu
e3007f0498 Chore: import with newline 2022-01-29 21:00:19 +08:00
xjasonlyu
e23837aa6d Style: use gofumpt for fmt 2022-01-29 17:26:24 +08:00
xjasonlyu
1d229ac859 Chore: update workflows 2022-01-29 17:06:01 +08:00
Jason Lyu
300401ad76 Chore: create codeql-analysis.yml 2022-01-29 17:04:54 +08:00
xjasonlyu
e3b57bb8d8 Chore: update issue template 2022-01-29 16:56:03 +08:00
xjasonlyu
f114b435d3 Chore: issue template config 2022-01-29 16:34:00 +08:00
xjasonlyu
b394b09790 Chore: adjust entrypoint.sh 2022-01-29 14:09:59 +08:00
xjasonlyu
59f682dfab Chore: rename to iobased 2022-01-28 21:10:10 +08:00
xjasonlyu
3b82a085d7 Chore: rename to config.yml 2022-01-28 19:59:32 +08:00
xjasonlyu
4b4b01a507 Improve: use bytes.Buffer pool 2022-01-28 15:30:23 +08:00
xjasonlyu
1bbb51b332 Chore: use automaxprocs 2022-01-28 15:01:04 +08:00
xjasonlyu
2a2420f89d Fix: revert module
Temporarily revert to previous gVisor version due to unstable bugs.
2022-01-20 15:07:42 +08:00
xjasonlyu
95f6464174 Chore: impl interface 2022-01-19 19:52:45 +08:00
xjasonlyu
3fa0820552 Chore: go mod update 2022-01-19 19:52:03 +08:00
Fan Lin
c068fbd626 Fix: set major version to v2 (#88) 2022-01-03 16:06:18 +08:00
xjasonlyu
9370983c63 Chore: upgrade go-chi/chi to v5 2021-12-24 11:31:55 +08:00
xjasonlyu
5fe7c9a5bb Chore: update README_ZH 2021-12-23 20:01:50 +08:00
xjasonlyu
c1ba254957 Improve: add pprof debug 2021-12-08 17:00:28 +08:00
xjasonlyu
4ffe32fd89 Improve: add default mtu for fd device 2021-12-08 16:27:45 +08:00
xjasonlyu
8e00168914 Chore: regular module updating 2021-12-08 12:32:23 +08:00
xjasonlyu
0d51d2db54 Chore: update go modules 2021-11-27 13:36:13 +08:00
xjasonlyu
cf557f0eb1 Fix: control should return error 2021-11-27 13:27:44 +08:00
xjasonlyu
6bb44f9eb1 Change: use nop for reject 2021-11-27 13:15:01 +08:00
Jason Lyu
40824d8350 Update issue templates 2021-11-22 11:09:06 +08:00
xjasonlyu
5ee4c676a1 Feature: YAML config support 2021-11-10 13:38:20 +08:00
xjasonlyu
733c4cb779 Chore: grammar correction 2021-11-08 21:11:35 +08:00
xjasonlyu
af55e8517c Chore: rename to SerializeSocksAddr 2021-11-08 21:06:24 +08:00
151 changed files with 4954 additions and 3297 deletions

View File

@@ -1,6 +1,8 @@
.github
.github/
.gitignore
.golangci.yaml
# Other
build/*
docs/*
docs/
build/
tests/

128
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
xjasonlyu@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [ xjasonlyu ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,33 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Environment**
- OS: [e.g. `Ubuntu-20.04`]
- Version: [e.g. `v2.1.0`]
- Network: [e.g. route tables, iptables rules]
**Log**
Paste the tun2socks log below with the log level set to `DEBUG`.
```
```
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. [First Step]
2. [Second Step]
3. ……
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.

55
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Bug report
description: Create a report to help us improve
title: "[Bug] "
body:
- type: checkboxes
id: ensure
attributes:
label: Verify steps
description: Please verify that you've followed these steps
options:
- label: Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
required: true
- label: I have searched on the [issue tracker](……/) for a related issue.
required: true
- type: input
attributes:
label: Version
validations:
required: true
- type: dropdown
id: os
attributes:
label: What OS are you seeing the problem on?
multiple: true
options:
- Windows
- Linux
- macOS
- OpenBSD/FreeBSD
- Other
- type: textarea
attributes:
label: Description
validations:
required: true
- type: textarea
attributes:
label: CLI or Config
description: Paste the command line parameters or configuration below.
- type: textarea
attributes:
render: shell
label: Logs
description: Paste the logs below with the log level set to `DEBUG`.
- type: textarea
attributes:
label: How to Reproduce
description: Steps to reproduce the behavior, if any.

9
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
blank_issues_enabled: false
contact_links:
- name: tun2socks GitHub Wiki
url: https://github.com/xjasonlyu/tun2socks/wiki
about: Please see the wiki for common configurations
- name: tun2socks GitHub Discussions
url: https://github.com/xjasonlyu/tun2socks/discussions
about: Ask questions and get help on GitHub Discussions

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem?**
A clear and concise description of what the problem is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,25 @@
name: Feature request
description: Suggest an idea or improvement
title: "[Feature] "
body:
- type: textarea
id: description
attributes:
label: Description
placeholder: A clear description of the feature or enhancement.
validations:
required: true
- type: textarea
id: related
attributes:
label: Is this feature related to a specific bug?
description: Please include a bug references if yes.
- type: textarea
id: solution
attributes:
label: Do you have a specific solution in mind?
description: >
Please include any details about a solution that you have in mind,
including any alternatives considered.

29
.github/SECURITY.md vendored Normal file
View File

@@ -0,0 +1,29 @@
# Security Policy
## Supported Versions
| Version | Supported |
|:-------:|:------------------:|
| 2.x | :white_check_mark: |
| 1.x | :x: |
## Reporting a Vulnerability
If you believe you have found a security vulnerability in this repository, please report it to me through coordinated
disclosure.
**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
Instead, please email to xjasonlyu[@]gmail.com.
Please include as much of the information listed below as you can to help me better understand and resolve the issue:
* The type of issue (e.g., buffer overflow, payload attack)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help me triage your report more quickly.

41
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: "CodeQL"
concurrency:
group: codeql-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [ main ]
pull_request:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v6
with:
check-latest: true
go-version-file: 'go.mod'
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4

View File

@@ -1,75 +1,78 @@
name: Publish Docker Image
concurrency:
group: docker-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'main'
tags:
- '*'
paths-ignore:
- '**.md'
- 'docs/**'
jobs:
build:
name: Build
docker:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
with:
platforms: all
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
with:
version: latest
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ghcr.io
username: xjasonlyu
password: ${{ secrets.CR_PAT }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: shell
run: |
echo ::set-output name=version::$(git describe --tags --abbrev=0)
echo "version=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT
- name: Build and Push (nightly)
- name: Build and Push (dev)
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
tags: |
xjasonlyu/tun2socks:nightly
ghcr.io/xjasonlyu/tun2socks:nightly
xjasonlyu/tun2socks:dev
ghcr.io/xjasonlyu/tun2socks:dev
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build and Push (latest)
if: startsWith(github.ref, 'refs/tags/')
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
tags: |
xjasonlyu/tun2socks:latest
xjasonlyu/tun2socks:${{ steps.shell.outputs.version }}
ghcr.io/xjasonlyu/tun2socks:latest
ghcr.io/xjasonlyu/tun2socks:${{ steps.shell.outputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -1,42 +0,0 @@
name: Go Static Check
on:
push:
branches:
- '*'
tags-ignore:
- '*'
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.17.x
- name: Cache go module
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Get dependencies, run test and static check
run: |
go test ./...
go vet ./...
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck -- $(go list ./...)

30
.github/workflows/linter.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Linter
concurrency:
group: linter-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'main'
pull_request:
jobs:
linter:
name: Linter
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v6
with:
check-latest: true
go-version-file: 'go.mod'
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: latest

View File

@@ -1,38 +1,59 @@
name: Publish Go Releases
concurrency:
group: release-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
tags:
- '*'
paths-ignore:
- '**.md'
- 'docs/**'
- 'docker/**'
jobs:
build:
name: Build
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: 1.17.x
check-latest: true
go-version-file: 'go.mod'
- name: Cache go module
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Set prerelease flag
if: startsWith(github.ref, 'refs/tags/')
id: pre
run: |
TAG="$GITHUB_REF_NAME"
if [[ "$TAG" =~ -(beta|alpha|rc) ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
- name: Build
if: startsWith(github.ref, 'refs/tags/')
run: make -j releases
- name: Upload Releases
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: build/*
draft: true
prerelease: false
prerelease: ${{ steps.pre.outputs.is_prerelease }}

View File

@@ -1,5 +1,10 @@
name: Mark stale issues and pull requests
permissions:
contents: write
issues: write
pull-requests: write
on:
schedule:
- cron: "0 10 * * *"
@@ -8,9 +13,10 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
exempt-issue-labels: 'question,bug,enhancement,help wanted'
exempt-pr-labels: 'pending,WIP,help wanted'
days-before-stale: 60
days-before-close: 7

31
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Test
concurrency:
group: test-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'main'
pull_request:
jobs:
build-test:
name: Build Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v6
with:
check-latest: true
go-version-file: 'go.mod'
- name: Run test
run: |
go test ./...

20
.gitignore vendored
View File

@@ -1,9 +1,27 @@
# Binaries
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# Build directory
build/
# IDE

34
.golangci.yaml Normal file
View File

@@ -0,0 +1,34 @@
version: "2"
linters:
default: none
enable:
- govet
- ineffassign
- misspell
- staticcheck
- unconvert
- unused
- usestdlibvars
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- transport/shadowsocks
formatters:
enable:
- gci
- gofumpt
settings:
gci:
sections:
- standard
- default
- prefix(github.com/xjasonlyu/tun2socks)
custom-order: true
exclusions:
generated: lax
paths: []

View File

@@ -1,19 +1,18 @@
FROM golang:alpine AS builder
WORKDIR /tun2socks-src
COPY . /tun2socks-src
WORKDIR /src
COPY . /src
RUN apk add --no-cache make git \
&& make tun2socks \
&& mv ./build/tun2socks /tun2socks
RUN apk add --update --no-cache make git \
&& make tun2socks
FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/xjasonlyu/tun2socks"
COPY docker/entrypoint.sh /entrypoint.sh
COPY --from=builder /tun2socks /usr/bin/tun2socks
COPY --from=builder /src/build/tun2socks /usr/bin/tun2socks
RUN apk add --update --no-cache iptables iproute2 \
RUN apk add --update --no-cache iptables iproute2 tzdata \
&& chmod +x /entrypoint.sh
ENV TUN=tun0
@@ -21,9 +20,12 @@ ENV ADDR=198.18.0.1/15
ENV LOGLEVEL=info
ENV PROXY=direct://
ENV MTU=9000
ENV STATS=
ENV TOKEN=
ENV RESTAPI=
ENV UDP_TIMEOUT=
ENV TCP_SNDBUF=
ENV TCP_RCVBUF=
ENV TCP_AUTO_TUNING=
ENV MULTICAST_GROUPS=
ENV EXTRA_COMMANDS=
ENV TUN_INCLUDED_ROUTES=
ENV TUN_EXCLUDED_ROUTES=

695
LICENSE
View File

@@ -1,674 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
MIT License
Copyright (c) 2019 Jason Lyu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +1,5 @@
BINARY := tun2socks
MODULE := github.com/xjasonlyu/tun2socks
MODULE := github.com/xjasonlyu/tun2socks/v2
BUILD_DIR := build
BUILD_TAGS :=
@@ -11,20 +11,23 @@ CGO_ENABLED := 0
GO111MODULE := on
LDFLAGS += -w -s -buildid=
LDFLAGS += -X "$(MODULE)/constant.Version=$(BUILD_VERSION)"
LDFLAGS += -X "$(MODULE)/constant.GitCommit=$(BUILD_COMMIT)"
LDFLAGS += -X "$(MODULE)/internal/version.Version=$(BUILD_VERSION)"
LDFLAGS += -X "$(MODULE)/internal/version.GitCommit=$(BUILD_COMMIT)"
GO_BUILD = GO111MODULE=$(GO111MODULE) CGO_ENABLED=$(CGO_ENABLED) \
go build $(BUILD_FLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -trimpath
UNIX_ARCH_LIST = \
darwin-amd64 \
darwin-amd64-v3 \
darwin-arm64 \
freebsd-386 \
freebsd-amd64 \
freebsd-amd64-v3 \
freebsd-arm64 \
linux-386 \
linux-amd64 \
linux-amd64-v3 \
linux-arm64 \
linux-armv5 \
linux-armv6 \
@@ -38,17 +41,22 @@ UNIX_ARCH_LIST = \
linux-ppc64 \
linux-ppc64le \
linux-s390x \
openbsd-386 \
linux-loong64 \
openbsd-amd64 \
openbsd-amd64-v3 \
openbsd-arm64
WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64 \
windows-amd64-v3 \
windows-arm64 \
windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64
all: linux-amd64 linux-arm64 darwin-amd64 darwin-arm64 windows-amd64
debug: BUILD_TAGS += debug
debug: all
tun2socks:
$(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)
@@ -56,6 +64,9 @@ tun2socks:
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
darwin-amd64-v3:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
@@ -65,6 +76,9 @@ freebsd-386:
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
freebsd-amd64-v3:
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
@@ -74,6 +88,9 @@ linux-386:
linux-amd64:
GOARCH=amd64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
linux-amd64-v3:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
linux-arm64:
GOARCH=arm64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
@@ -113,12 +130,15 @@ linux-ppc64le:
linux-s390x:
GOARCH=s390x GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
openbsd-386:
GOARCH=386 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
linux-loong64:
GOARCH=loong64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
openbsd-amd64:
GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
openbsd-amd64-v3:
GOARCH=amd64 GOOS=openbsd GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
openbsd-arm64:
GOARCH=arm64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
@@ -128,6 +148,9 @@ windows-386:
windows-amd64:
GOARCH=amd64 GOOS=windows $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe
windows-amd64-v3:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe
@@ -147,5 +170,12 @@ all-arch: $(UNIX_ARCH_LIST) $(WINDOWS_ARCH_LIST)
releases: $(unix_releases) $(windows_releases)
lint:
GOOS=darwin golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=linux golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
GOOS=openbsd golangci-lint run ./...
clean:
rm -rf $(BUILD_DIR)

View File

@@ -3,61 +3,72 @@
[![GitHub Workflow][1]](https://github.com/xjasonlyu/tun2socks/actions)
[![Go Version][2]](https://github.com/xjasonlyu/tun2socks/blob/main/go.mod)
[![Go Report][3]](https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks)
[![GitHub License][4]](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE)
[![Releases][5]](https://github.com/xjasonlyu/tun2socks/releases)
[1]: https://img.shields.io/github/workflow/status/xjasonlyu/tun2socks/Go?style=flat-square
[2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks/main?style=flat-square
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks?style=flat-square
[4]: https://img.shields.io/github/license/xjasonlyu/tun2socks?style=flat-square
[5]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?include_prereleases&style=flat-square
English | [简体中文](README_ZH.md)
[![Maintainability][4]](https://codeclimate.com/github/xjasonlyu/tun2socks/maintainability)
[![GitHub License][5]](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE)
[![Docker Pulls][6]](https://hub.docker.com/r/xjasonlyu/tun2socks)
[![Releases][7]](https://github.com/xjasonlyu/tun2socks/releases)
## Features
- **Network Support**
- Dualstack: `IPv4/IPv6`
- Forwarder: `TCP/UDP`
- Ping Echo: `ICMP`
- **Platform Support**
- Linux
- MacOS
- Windows
- FreeBSD
- OpenBSD
- **Proxy Protocol**
- HTTP
- Socks4
- Socks5
- Shadowsocks
- **Extra Feature**
- Improved stability without CGO
- Optimized UDP transmission for game
- Performed with >2.5Gbps throughput
- TCP/IP stack powered by **[gVisor](https://github.com/google/gvisor)**
- **Universal Proxying**: Transparently routes all network traffic from any application through a proxy.
- **Multi-Protocol**: Supports HTTP/SOCKS4/SOCKS5/Shadowsocks proxies with optional authentication.
- **Cross-Platform**: Runs on Linux/macOS/Windows/FreeBSD/OpenBSD with platform-specific optimizations.
- **Gateway Mode**: Acts as a Layer 3 gateway to route traffic from other devices on the same network.
- **Full IPv6 Compatibility**: Natively supports IPv6; seamlessly tunnels IPv4 over IPv6 and vice versa.
- **User-Space Networking**: Leverages the **[gVisor](https://github.com/google/gvisor)** network stack for enhanced
performance and flexibility.
## Benchmarks
![benchmark](docs/benchmark.png)
For all scenarios of usage, tun2socks performs best.
See [benchmarks](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks) for more details.
## Documentation
Docs and quick start guides can be found at [Github Wiki](https://github.com/xjasonlyu/tun2socks/wiki).
- [Install from Source](https://github.com/xjasonlyu/tun2socks/wiki/Install-from-Source)
- [Quickstart Examples](https://github.com/xjasonlyu/tun2socks/wiki/Examples)
- [Memory Optimization](https://github.com/xjasonlyu/tun2socks/wiki/Memory-Optimization)
Full documentation and technical guides can be found at [Wiki](https://github.com/xjasonlyu/tun2socks/wiki).
## Community
Welcome and feel free to ask any questions at [Github Discussions](https://github.com/xjasonlyu/tun2socks/discussions).
Welcome and feel free to ask any questions at [Discussions](https://github.com/xjasonlyu/tun2socks/discussions).
## Credits
- [Dreamacro/clash](https://github.com/Dreamacro/clash) - A rule-based tunnel in Go
- [google/gvisor](https://github.com/google/gvisor) - Application Kernel for Containers
- [wireguard-go](https://git.zx2c4.com/wireguard-go) - Go Implementation of WireGuard
- [wintun](https://git.zx2c4.com/wintun/) - Layer 3 TUN Driver for Windows
## License
[GPL-3.0](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks?ref=badge_large)
All versions starting from `v2.6.0` are available under the terms of the [MIT License](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE).
## Stargazers over time
## Star History
[![Stargazers over time](https://starchart.cc/xjasonlyu/tun2socks.svg)](https://starchart.cc/xjasonlyu/tun2socks)
<a href="https://star-history.com/#xjasonlyu/tun2socks&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xjasonlyu/tun2socks&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xjasonlyu/tun2socks&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xjasonlyu/tun2socks&type=Date" />
</picture>
</a>
[1]: https://img.shields.io/github/actions/workflow/status/xjasonlyu/tun2socks/docker.yml?logo=github
[2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks?logo=go
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks
[4]: https://api.codeclimate.com/v1/badges/b5b30239174fc6603aca/maintainability
[5]: https://img.shields.io/github/license/xjasonlyu/tun2socks
[6]: https://img.shields.io/docker/pulls/xjasonlyu/tun2socks?logo=docker
[7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?logo=smartthings

View File

@@ -1,68 +0,0 @@
![tun2socks](docs/logo.png)
[![GitHub Workflow][1]](https://github.com/xjasonlyu/tun2socks/actions)
[![Go Version][2]](https://github.com/xjasonlyu/tun2socks/blob/main/go.mod)
[![Go Report][3]](https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks)
[![GitHub License][4]](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE)
[![Releases][5]](https://github.com/xjasonlyu/tun2socks/releases)
[1]: https://img.shields.io/github/workflow/status/xjasonlyu/tun2socks/Go?style=flat-square
[2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks/main?style=flat-square
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks?style=flat-square
[4]: https://img.shields.io/github/license/xjasonlyu/tun2socks?style=flat-square
[5]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?include_prereleases&style=flat-square
[English](README.md) | 简体中文
## 为什么使用 tun2socks
通过在主机上运行`tun2socks`,可以轻松地接管所有的`TCP/UDP`流量,同时提供诸多专业的功能特性,这包括:
- 强制使不支持代理的程序走代理
- 配合Clash、V2Ray等工具实现全局代理上网
- 配合Burp、Charles等工具进行应用层数据的调试
- 配合DHCP、CoreDNS等工具部署路由模式代理局域网流量
## 特性介绍
- **全面支持:** IPv4/IPv6/ICMP/TCP/UDP
- **代理协议:** HTTP/Socks4/Socks5/Shadowsocks
- **游戏加速:** 针对UDP传输的优化
- **纯Go实现** 无需CGO稳定性提升
- **路由模式:** 转发代理局域网内所有流量
- **TCP/IP栈** 由 **[gVisor](https://github.com/google/gvisor)** 强力驱动
- **高性能:** >2.5Gbps 的带宽吞吐量
## 硬件需求
| 目标 | 最小 | 建议 |
| :--- | :---: | :---: |
| 系统 | Linux MacOS Freebsd OpenBSD Windows | Linux or MacOS |
| 内存 | >20MB | >128MB |
| 架构 | ANY | AMD64 or ARM64 |
## 使用文档
文档以及使用方式,请看 [Github Wiki](https://github.com/xjasonlyu/tun2socks/wiki)。
## 交流讨论
欢迎来讨论区交流提问,[Github Discussions](https://github.com/xjasonlyu/tun2socks/discussions)。
## 注意事项
1. 由于采用了纯Go实现所以这一版本的`tun2socks`在有大量连接时内存消耗通常较多。如果您的需求对内存消耗极为敏感,请继续使用 [v1](https://github.com/xjasonlyu/tun2socks/tree/v1) 版本。
2. `tun2socks`只应该专注于将网络层的TCP/UDP流量转发给SOCKS服务器其他的如DNSDoH、DHCP等模块功能应该交由第三方应用实现所以弃用了DNS模块。
3. 因为是通过用户空间的网络栈接管所有流量并处理转发在高吞吐时CPU的使用量会剧增所以CPU的性能直接与可以达到的最大带宽挂钩。
## 特别感谢
- [Dreamacro/clash](https://github.com/Dreamacro/clash) - A rule-based tunnel in Go
- [google/gvisor](https://github.com/google/gvisor) - Application Kernel for Containers
- [wireguard-go](https://git.zx2c4.com/wireguard-go) - Go Implementation of WireGuard
## License
[GPL-3.0](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks?ref=badge_large)

28
common/pool/alloc.go → buffer/allocator/allocator.go Executable file → Normal file
View File

@@ -1,30 +1,29 @@
package pool
package allocator
import (
"errors"
"math/bits"
"sync"
)
var _allocator = NewAllocator()
"github.com/xjasonlyu/tun2socks/v2/internal/pool"
)
// Allocator for incoming frames, optimized to prevent overwriting
// after zeroing.
type Allocator struct {
buffers []sync.Pool
buffers []*pool.Pool[[]byte]
}
// NewAllocator initiates a []byte allocator for frames less than
// 65536 bytes, the waste(memory fragmentation) of space allocation
// is guaranteed to be no more than 50%.
func NewAllocator() *Allocator {
// New initiates a []byte allocator for frames less than 65536 bytes,
// the waste(memory fragmentation) of space allocation is guaranteed
// to be no more than 50%.
func New() *Allocator {
alloc := &Allocator{}
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
alloc.buffers = make([]*pool.Pool[[]byte], 17) // 1B -> 64K
for k := range alloc.buffers {
i := k
alloc.buffers[k].New = func() interface{} {
alloc.buffers[k] = pool.New(func() []byte {
return make([]byte, 1<<uint32(i))
}
})
}
return alloc
}
@@ -37,10 +36,10 @@ func (alloc *Allocator) Get(size int) []byte {
b := msb(size)
if size == 1<<b {
return alloc.buffers[b].Get().([]byte)[:size]
return alloc.buffers[b].Get()[:size]
}
return alloc.buffers[b+1].Get().([]byte)[:size]
return alloc.buffers[b+1].Get()[:size]
}
// Put returns a []byte to pool for future use,
@@ -51,7 +50,6 @@ func (alloc *Allocator) Put(buf []byte) error {
return errors.New("allocator Put() incorrect buffer size")
}
//lint:ignore SA6002 ignore temporarily
alloc.buffers[b].Put(buf)
return nil
}

View File

@@ -1,4 +1,4 @@
package pool
package allocator
import (
"math/rand"
@@ -8,7 +8,7 @@ import (
)
func TestAllocGet(t *testing.T) {
alloc := NewAllocator()
alloc := New()
assert.Nil(t, alloc.Get(0))
assert.Equal(t, 1, len(alloc.Get(1)))
assert.Equal(t, 2, len(alloc.Get(2)))
@@ -23,7 +23,7 @@ func TestAllocGet(t *testing.T) {
}
func TestAllocPut(t *testing.T) {
alloc := NewAllocator()
alloc := New()
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
@@ -33,7 +33,7 @@ func TestAllocPut(t *testing.T) {
}
func TestAllocPutThenGet(t *testing.T) {
alloc := NewAllocator()
alloc := New()
data := alloc.Get(4)
_ = alloc.Put(data)
newData := alloc.Get(4)

11
common/pool/pool.go → buffer/pool.go Executable file → Normal file
View File

@@ -1,18 +1,23 @@
// Package pool provides a pool of []byte.
package pool
// Package buffer provides a pool of []byte.
package buffer
// Ref: github.com/Dreamacro/clash/common/pool
import (
"github.com/xjasonlyu/tun2socks/v2/buffer/allocator"
)
const (
// MaxSegmentSize is the largest possible UDP datagram size.
MaxSegmentSize = (1 << 16) - 1
// RelayBufferSize is the default buffer size for TCP relays.
// io.Copy default buffer size is 32 KiB, but the maximum packet
// size of vmess/shadowsocks is about 16 KiB, so define a buffer
// of 20 KiB to reduce the memory of each TCP relay.
RelayBufferSize = 20 << 10
)
var _allocator = allocator.New()
// Get gets a []byte from default allocator with most appropriate cap.
func Get(size int) []byte {
return _allocator.Get(size)

View File

@@ -1,3 +0,0 @@
package observable
type Iterable <-chan interface{}

View File

@@ -1,67 +0,0 @@
package observable
// Ref: github.com/Dreamacro/clash/common/observable
import (
"errors"
"sync"
)
type Observable struct {
iterable Iterable
listener map[Subscription]*Subscriber
mux sync.Mutex
done bool
}
func (o *Observable) process() {
for item := range o.iterable {
o.mux.Lock()
for _, sub := range o.listener {
sub.Emit(item)
}
o.mux.Unlock()
}
o.close()
}
func (o *Observable) close() {
o.mux.Lock()
defer o.mux.Unlock()
o.done = true
for _, sub := range o.listener {
sub.Close()
}
}
func (o *Observable) Subscribe() (Subscription, error) {
o.mux.Lock()
defer o.mux.Unlock()
if o.done {
return nil, errors.New("observable is closed")
}
subscriber := newSubscriber()
o.listener[subscriber.Out()] = subscriber
return subscriber.Out(), nil
}
func (o *Observable) UnSubscribe(sub Subscription) {
o.mux.Lock()
defer o.mux.Unlock()
subscriber, exist := o.listener[sub]
if !exist {
return
}
delete(o.listener, sub)
subscriber.Close()
}
func NewObservable(any Iterable) *Observable {
observable := &Observable{
iterable: any,
listener: map[Subscription]*Subscriber{},
}
go observable.process()
return observable
}

View File

@@ -1,148 +0,0 @@
package observable
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func iterator(item []interface{}) chan interface{} {
ch := make(chan interface{})
go func() {
time.Sleep(100 * time.Millisecond)
for _, elm := range item {
ch <- elm
}
close(ch)
}()
return ch
}
func TestObservable(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
data, err := src.Subscribe()
assert.Nil(t, err)
count := 0
for range data {
count++
}
assert.Equal(t, count, 5)
}
func TestObservable_MultiSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe()
var count = atomic.NewInt32(0)
var wg sync.WaitGroup
wg.Add(2)
waitCh := func(ch <-chan interface{}) {
for range ch {
count.Inc()
}
wg.Done()
}
go waitCh(ch1)
go waitCh(ch2)
wg.Wait()
assert.Equal(t, int32(10), count.Load())
}
func TestObservable_UnSubscribe(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
data, err := src.Subscribe()
assert.Nil(t, err)
src.UnSubscribe(data)
_, open := <-data
assert.False(t, open)
}
func TestObservable_SubscribeClosedSource(t *testing.T) {
iter := iterator([]interface{}{1})
src := NewObservable(iter)
data, _ := src.Subscribe()
<-data
_, closed := src.Subscribe()
assert.NotNil(t, closed)
}
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
sub := Subscription(make(chan interface{}))
iter := iterator([]interface{}{1})
src := NewObservable(iter)
src.UnSubscribe(sub)
}
func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
iter := iterator([]interface{}{1, 2, 3, 4, 5})
src := NewObservable(iter)
max := 100
var list []Subscription
for i := 0; i < max; i++ {
ch, _ := src.Subscribe()
list = append(list, ch)
}
var wg sync.WaitGroup
wg.Add(max)
waitCh := func(ch <-chan interface{}) {
for range ch {
}
wg.Done()
}
for _, ch := range list {
go waitCh(ch)
}
wg.Wait()
for _, sub := range list {
_, more := <-sub
assert.False(t, more)
}
if len(list) > 0 {
_, more := <-list[0]
assert.False(t, more)
}
}
func Benchmark_Observable_1000(b *testing.B) {
ch := make(chan interface{})
o := NewObservable(ch)
num := 1000
var subs []Subscription
for i := 0; i < num; i++ {
sub, _ := o.Subscribe()
subs = append(subs, sub)
}
wg := sync.WaitGroup{}
wg.Add(num)
b.ResetTimer()
for _, sub := range subs {
go func(s Subscription) {
for range s {
}
wg.Done()
}(sub)
}
for i := 0; i < b.N; i++ {
ch <- i
}
close(ch)
wg.Wait()
}

View File

@@ -1,33 +0,0 @@
package observable
import (
"sync"
)
type Subscription <-chan interface{}
type Subscriber struct {
buffer chan interface{}
once sync.Once
}
func (s *Subscriber) Emit(item interface{}) {
s.buffer <- item
}
func (s *Subscriber) Out() Subscription {
return s.buffer
}
func (s *Subscriber) Close() {
s.once.Do(func() {
close(s.buffer)
})
}
func newSubscriber() *Subscriber {
sub := &Subscriber{
buffer: make(chan interface{}, 200),
}
return sub
}

View File

@@ -1,21 +0,0 @@
package dialer
import (
"net"
"sync"
)
var _bindOnce sync.Once
// BindToInterface binds dialer to specific interface.
func BindToInterface(name string) error {
i, err := net.InterfaceByName(name)
if err != nil {
return err
}
_bindOnce.Do(func() {
addControl(bindToInterface(i))
})
return nil
}

View File

@@ -1,26 +0,0 @@
package dialer
import (
"net"
"syscall"
"golang.org/x/sys/unix"
)
func bindToInterface(i *net.Interface) controlFunc {
return func(network, address string, c syscall.RawConn) error {
ipStr, _, _ := net.SplitHostPort(address)
if ip := net.ParseIP(ipStr); ip != nil && !ip.IsGlobalUnicast() {
return nil
}
return c.Control(func(fd uintptr) {
switch network {
case "tcp4", "udp4":
unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, i.Index)
case "tcp6", "udp6":
unix.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, i.Index)
}
})
}
}

View File

@@ -1,21 +0,0 @@
package dialer
import (
"net"
"syscall"
"golang.org/x/sys/unix"
)
func bindToInterface(i *net.Interface) controlFunc {
return func(network, address string, c syscall.RawConn) error {
ipStr, _, _ := net.SplitHostPort(address)
if ip := net.ParseIP(ipStr); ip != nil && !ip.IsGlobalUnicast() {
return nil
}
return c.Control(func(fd uintptr) {
unix.BindToDevice(int(fd), i.Name)
})
}
}

View File

@@ -1,15 +0,0 @@
//go:build !linux && !darwin
package dialer
import (
"errors"
"net"
"syscall"
)
func bindToInterface(_ *net.Interface) controlFunc {
return func(string, string, syscall.RawConn) error {
return errors.New("unsupported platform")
}
}

View File

@@ -1,37 +0,0 @@
package dialer
import (
"errors"
"net"
"syscall"
)
type controlFunc func(string, string, syscall.RawConn) error
var (
_controlPool = make([]controlFunc, 0, 2)
)
func addControl(f controlFunc) {
_controlPool = append(_controlPool, f)
}
func setControl(i interface{}) {
control := func(address, network string, c syscall.RawConn) error {
for _, f := range _controlPool {
if err := f(address, network, c); err != nil {
return err
}
}
return nil
}
switch v := i.(type) {
case *net.Dialer:
v.Control = control
case *net.ListenConfig:
v.Control = control
default:
panic(errors.New("wrong type"))
}
}

View File

@@ -1,22 +0,0 @@
package dialer
import (
"context"
"net"
)
func Dial(network, address string) (net.Conn, error) {
return DialContext(context.Background(), network, address)
}
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
d := &net.Dialer{}
setControl(d)
return d.DialContext(ctx, network, address)
}
func ListenPacket(network, address string) (net.PacketConn, error) {
lc := &net.ListenConfig{}
setControl(lc)
return lc.ListenPacket(context.Background(), network, address)
}

View File

@@ -1,14 +0,0 @@
package dialer
import (
"sync"
)
var _setOnce sync.Once
// SetMark sets the mark for each packet sent through this dialer(socket).
func SetMark(i int) {
_setOnce.Do(func() {
addControl(setMark(i))
})
}

View File

@@ -1,15 +0,0 @@
package dialer
import (
"syscall"
"golang.org/x/sys/unix"
)
func setMark(i int) controlFunc {
return func(_, _ string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, i)
})
}
}

View File

@@ -1,14 +0,0 @@
//go:build !linux
package dialer
import (
"errors"
"syscall"
)
func setMark(_ int) controlFunc {
return func(string, string, syscall.RawConn) error {
return errors.New("fwmark: linux only")
}
}

View File

@@ -1,31 +0,0 @@
/*
Package nat provides simple NAT table implements.
* Normal (Full Cone) NAT
A full cone NAT is one where all requests from the same internal IP address
and port are mapped to the same external IP address and port. Furthermore,
any external host can send a packet to the internal host, by sending a packet
to the mapped external address.
* Restricted Cone NAT
A restricted cone NAT is one where all requests from the same internal IP
address and port are mapped to the same external IP address and port.
Unlike a full cone NAT, an external host (with IP address X) can send a
packet to the internal host only if the internal host had previously sent
a packet to IP address X.
* Port Restricted Cone NAT
A port restricted cone NAT is like a restricted cone NAT, but the restriction
includes port numbers. Specifically, an external host can send a packet, with
source IP address X and source port P, to the internal host only if the internal
host had previously sent a packet to IP address X and port P.
* Symmetric NAT
A symmetric NAT is one where all requests from the same internal IP address
and port, to a specific destination IP address and port, are mapped to the
same external IP address and port. If the same host sends a packet with the
same source address and port, but to a different destination, a different mapping
is used. Furthermore, only the external host that receives a packet can send a
UDP packet back to the internal host.
*/
package nat

View File

@@ -1,35 +0,0 @@
package nat
import (
"net"
"sync"
)
type Table struct {
mapping sync.Map
}
func (t *Table) Set(key string, pc net.PacketConn) {
t.mapping.Store(key, pc)
}
func (t *Table) Get(key string) net.PacketConn {
item, exist := t.mapping.Load(key)
if !exist {
return nil
}
return item.(net.PacketConn)
}
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item.(*sync.Cond), loaded
}
func (t *Table) Delete(key string) {
t.mapping.Delete(key)
}
func NewTable() *Table {
return &Table{}
}

View File

@@ -1,89 +0,0 @@
package constant
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"strconv"
"github.com/xjasonlyu/tun2socks/transport/socks5"
)
const (
TCP Network = iota
UDP
)
type Network uint8
func (n Network) String() string {
switch n {
case TCP:
return "tcp"
case UDP:
return "udp"
default:
return fmt.Sprintf("network(%d)", n)
}
}
func (n Network) MarshalText() ([]byte, error) {
return []byte(n.String()), nil
}
// Metadata implements the net.Addr interface.
type Metadata struct {
Net Network `json:"network"`
SrcIP net.IP `json:"sourceIP"`
MidIP net.IP `json:"dialerIP"`
DstIP net.IP `json:"destinationIP"`
SrcPort uint16 `json:"sourcePort"`
MidPort uint16 `json:"dialerPort"`
DstPort uint16 `json:"destinationPort"`
}
func (m *Metadata) DestinationAddress() string {
return net.JoinHostPort(m.DstIP.String(), strconv.FormatUint(uint64(m.DstPort), 10))
}
func (m *Metadata) SourceAddress() string {
return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10))
}
func (m *Metadata) UDPAddr() *net.UDPAddr {
if m.Net != UDP || m.DstIP == nil {
return nil
}
return &net.UDPAddr{
IP: m.DstIP,
Port: int(m.DstPort),
}
}
func (m *Metadata) SerializesSocksAddr() socks5.Addr {
var (
buf [][]byte
port [2]byte
)
binary.BigEndian.PutUint16(port[:], m.DstPort)
if m.DstIP.To4() != nil /* IPv4 */ {
aType := socks5.AtypIPv4
buf = [][]byte{{aType}, m.DstIP.To4(), port[:]}
} else /* IPv6 */ {
aType := socks5.AtypIPv6
buf = [][]byte{{aType}, m.DstIP.To16(), port[:]}
}
return bytes.Join(buf, nil)
}
func (m *Metadata) Network() string {
return m.Net.String()
}
// String returns destination address of this metadata.
// Also for implementing net.Addr interface.
func (m *Metadata) String() string {
return m.DestinationAddress()
}

View File

@@ -1,10 +0,0 @@
package constant
const (
Name = "tun2socks"
)
var (
Version string
GitCommit string
)

View File

@@ -1,34 +0,0 @@
package core
import (
"net"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type TCPConn interface {
net.Conn
ID() *stack.TransportEndpointID
}
type UDPPacket interface {
// Data get the payload of UDP Packet.
Data() []byte
// Drop call after packet is used, could release resources in this function.
Drop()
// ID returns the transport endpoint id of packet.
ID() *stack.TransportEndpointID
// LocalAddr returns the source IP/Port of packet.
LocalAddr() net.Addr
// RemoteAddr returns the destination IP/Port of packet.
RemoteAddr() net.Addr
// WriteBack writes the payload with source IP/Port equals addr
// - variable source IP/Port is important to STUN
// - if addr is not provided, WriteBack will write out UDP packet with SourceIP/Port equals to original Target.
WriteBack([]byte, net.Addr) (int, error)
}

24
core/adapter/adapter.go Normal file
View File

@@ -0,0 +1,24 @@
package adapter
import (
"net"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// TCPConn implements the net.Conn interface.
type TCPConn interface {
net.Conn
// ID returns the transport endpoint id of TCPConn.
ID() *stack.TransportEndpointID
}
// UDPConn implements net.Conn and net.PacketConn.
type UDPConn interface {
net.Conn
net.PacketConn
// ID returns the transport endpoint id of UDPConn.
ID() *stack.TransportEndpointID
}

8
core/adapter/handler.go Normal file
View File

@@ -0,0 +1,8 @@
package adapter
// TransportHandler is a TCP/UDP connection handler that implements
// HandleTCP and HandleUDP methods.
type TransportHandler interface {
HandleTCP(TCPConn)
HandleUDP(UDPConn)
}

3
core/device/device.go Executable file → Normal file
View File

@@ -9,9 +9,6 @@ import (
type Device interface {
stack.LinkEndpoint
// Close stops and closes the device.
Close() error
// Name returns the current name of the device.
Name() string

View File

@@ -1,41 +0,0 @@
//go:build !windows
package fd
import (
"fmt"
"strconv"
"github.com/xjasonlyu/tun2socks/core/device"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type FD struct {
stack.LinkEndpoint
fd int
mtu uint32
}
func Open(name string, mtu uint32) (device.Device, error) {
fd, err := strconv.Atoi(name)
if err != nil {
return nil, fmt.Errorf("cannot open fd: %s", name)
}
return open(fd, mtu)
}
func (f *FD) Type() string {
return Driver
}
func (f *FD) Name() string {
return strconv.Itoa(f.fd)
}
func (f *FD) Close() error {
return unix.Close(f.fd)
}
var _ device.Device = (*FD)(nil)

View File

@@ -1,11 +0,0 @@
package fd
import (
"errors"
"github.com/xjasonlyu/tun2socks/core/device"
)
func Open(name string, mtu uint32) (device.Device, error) {
return nil, errors.New("not supported")
}

View File

@@ -1,23 +0,0 @@
//go:build !linux && !windows
package fd
import (
"fmt"
"os"
"github.com/xjasonlyu/tun2socks/core/device"
"github.com/xjasonlyu/tun2socks/core/device/rwbased"
)
func open(fd int, mtu uint32) (device.Device, error) {
f := &FD{fd: fd, mtu: mtu}
ep, err := rwbased.New(os.NewFile(uintptr(fd), f.Name()), mtu)
if err != nil {
return nil, fmt.Errorf("create endpoint: %w", err)
}
f.LinkEndpoint = ep
return f, nil
}

View File

@@ -1,3 +1,3 @@
package fd
package fdbased
const Driver = "fd"

View File

@@ -0,0 +1,52 @@
//go:build unix
package fdbased
import (
"fmt"
"strconv"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"github.com/xjasonlyu/tun2socks/v2/core/device"
)
const defaultMTU = 1500
type FD struct {
stack.LinkEndpoint
fd int
mtu uint32
closed bool
}
func Open(name string, mtu uint32, offset int) (device.Device, error) {
fd, err := strconv.Atoi(name)
if err != nil {
return nil, fmt.Errorf("cannot open fd: %s", name)
}
if mtu == 0 {
mtu = defaultMTU
}
return open(fd, mtu, offset)
}
func (f *FD) Type() string {
return Driver
}
func (f *FD) Name() string {
return strconv.Itoa(f.fd)
}
func (f *FD) Close() {
if !f.closed {
defer f.LinkEndpoint.Close()
_ = unix.Close(f.fd)
f.closed = true
}
}
var _ device.Device = (*FD)(nil)

View File

@@ -0,0 +1,11 @@
package fdbased
import (
"errors"
"github.com/xjasonlyu/tun2socks/v2/core/device"
)
func Open(name string, mtu uint32, offset int) (device.Device, error) {
return nil, errors.ErrUnsupported
}

View File

@@ -1,18 +1,20 @@
package fd
package fdbased
import (
"fmt"
"github.com/xjasonlyu/tun2socks/core/device"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"github.com/xjasonlyu/tun2socks/v2/core/device"
)
func open(fd int, mtu uint32) (device.Device, error) {
func open(fd int, mtu uint32, offset int) (device.Device, error) {
f := &FD{fd: fd, mtu: mtu}
ep, err := fdbased.New(&fdbased.Options{
MTU: mtu,
FDs: []int{fd},
FDs: []int{fd},
MTU: mtu,
// TUN only, ignore ethernet header.
EthernetHeader: false,
})
if err != nil {

View File

@@ -0,0 +1,22 @@
//go:build unix && !linux
package fdbased
import (
"fmt"
"os"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
)
func open(fd int, mtu uint32, offset int) (device.Device, error) {
f := &FD{fd: fd, mtu: mtu}
ep, err := iobased.New(os.NewFile(uintptr(fd), f.Name()), mtu, offset)
if err != nil {
return nil, fmt.Errorf("create endpoint: %w", err)
}
f.LinkEndpoint = ep
return f, nil
}

View File

@@ -0,0 +1,153 @@
// Package iobased provides the implementation of io.ReadWriter
// based data-link layer endpoints.
package iobased
import (
"context"
"errors"
"io"
"sync"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const (
// Queue length for outbound packet, arriving for read. Overflow
// causes packet drops.
defaultOutQueueLen = 1 << 10
)
// Endpoint implements the interface of stack.LinkEndpoint from io.ReadWriter.
type Endpoint struct {
*channel.Endpoint
// rw is the io.ReadWriter for reading and writing packets.
rw io.ReadWriter
// mtu (maximum transmission unit) is the maximum size of a packet.
mtu uint32
// offset can be useful when perform TUN device I/O with TUN_PI enabled.
offset int
// once is used to perform the init action once when attaching.
once sync.Once
// wg keeps track of running goroutines.
wg sync.WaitGroup
}
// New returns stack.LinkEndpoint(.*Endpoint) and error.
func New(rw io.ReadWriter, mtu uint32, offset int) (*Endpoint, error) {
if mtu == 0 {
return nil, errors.New("MTU size is zero")
}
if rw == nil {
return nil, errors.New("RW interface is nil")
}
if offset < 0 {
return nil, errors.New("offset must be non-negative")
}
return &Endpoint{
Endpoint: channel.New(defaultOutQueueLen, mtu, ""),
rw: rw,
mtu: mtu,
offset: offset,
}, nil
}
// Attach launches the goroutine that reads packets from io.Reader and
// dispatches them via the provided dispatcher.
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.Endpoint.Attach(dispatcher)
e.once.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
e.wg.Add(2)
go func() {
e.outboundLoop(ctx)
e.wg.Done()
}()
go func() {
e.dispatchLoop(cancel)
e.wg.Done()
}()
})
}
func (e *Endpoint) Wait() {
e.wg.Wait()
}
// dispatchLoop dispatches packets to upper layer.
func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
// Call cancel() to ensure (*Endpoint).outboundLoop(context.Context) exits
// gracefully after (*Endpoint).dispatchLoop(context.CancelFunc) returns.
defer cancel()
offset, mtu := e.offset, int(e.mtu)
for {
data := make([]byte, offset+mtu)
n, err := e.rw.Read(data)
if err != nil {
break
}
if n == 0 || n > mtu {
continue
}
if !e.IsAttached() {
continue /* unattached, drop packet */
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(data[offset : offset+n]),
})
switch header.IPVersion(data[offset:]) {
case header.IPv4Version:
e.InjectInbound(header.IPv4ProtocolNumber, pkt)
case header.IPv6Version:
e.InjectInbound(header.IPv6ProtocolNumber, pkt)
}
pkt.DecRef()
}
}
// outboundLoop reads outbound packets from channel, and then it calls
// writePacket to send those packets back to lower layer.
func (e *Endpoint) outboundLoop(ctx context.Context) {
for {
pkt := e.ReadContext(ctx)
if pkt == nil {
break
}
e.writePacket(pkt)
}
}
// writePacket writes outbound packets to the io.Writer.
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
defer pkt.DecRef()
buf := pkt.ToBuffer()
defer buf.Release()
if e.offset != 0 {
v := buffer.NewViewWithData(make([]byte, e.offset))
_ = buf.Prepend(v)
}
if _, err := e.rw.Write(buf.Flatten()); err != nil {
return &tcpip.ErrInvalidEndpointState{}
}
return nil
}

View File

@@ -1,144 +0,0 @@
// Package rwbased provides the implementation of io.ReadWriter
// based data-link layer endpoints.
package rwbased
import (
"errors"
"io"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
var _ stack.LinkEndpoint = (*Endpoint)(nil)
// Endpoint implements the interface of stack.LinkEndpoint from io.ReadWriter.
type Endpoint struct {
// rw is the io.ReadWriter for reading and writing packets.
rw io.ReadWriter
// mtu (maximum transmission unit) is the maximum size of a packet.
mtu uint32
dispatcher stack.NetworkDispatcher
}
// New returns stack.LinkEndpoint(.*Endpoint) and error.
func New(rw io.ReadWriter, mtu uint32) (*Endpoint, error) {
if mtu == 0 {
return nil, errors.New("MTU size is zero")
}
if rw == nil {
return nil, errors.New("RW interface is nil")
}
return &Endpoint{
rw: rw,
mtu: mtu,
}, nil
}
// Attach launches the goroutine that reads packets from io.ReadWriter and
// dispatches them via the provided dispatcher.
func (e *Endpoint) Attach(dispatcher stack.NetworkDispatcher) {
go e.dispatchLoop()
e.dispatcher = dispatcher
}
// IsAttached implements stack.LinkEndpoint.IsAttached.
func (e *Endpoint) IsAttached() bool {
return e.dispatcher != nil
}
// dispatchLoop dispatches packets to upper layer.
func (e *Endpoint) dispatchLoop() {
for {
packet := make([]byte, e.mtu)
n, err := e.rw.Read(packet)
if err != nil {
break
}
if !e.IsAttached() {
continue
}
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: buffer.NewVectorisedView(n, []buffer.View{buffer.NewViewFromBytes(packet)}),
})
switch header.IPVersion(packet) {
case header.IPv4Version:
e.dispatcher.DeliverNetworkPacket("", "", header.IPv4ProtocolNumber, pkb)
case header.IPv6Version:
e.dispatcher.DeliverNetworkPacket("", "", header.IPv6ProtocolNumber, pkb)
}
}
}
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
vView := buffer.NewVectorisedView(pkt.Size(), pkt.Views())
if _, err := e.rw.Write(vView.ToView()); err != nil {
return &tcpip.ErrInvalidEndpointState{}
}
return nil
}
// WritePacket writes packet back into io.ReadWriter.
func (e *Endpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
return e.writePacket(pkt)
}
// WritePackets writes packets back into io.ReadWriter.
func (e *Endpoint) WritePackets(_ stack.RouteInfo, pkts stack.PacketBufferList, _ tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
n := 0
for pkt := pkts.Front(); pkt != nil; pkt = pkt.Next() {
if err := e.writePacket(pkt); err != nil {
break
}
n++
}
return n, nil
}
func (e *Endpoint) WriteRawPacket(packetBuffer *stack.PacketBuffer) tcpip.Error {
return &tcpip.ErrNotSupported{}
}
// MTU implements stack.LinkEndpoint.MTU.
func (e *Endpoint) MTU() uint32 {
return e.mtu
}
// Capabilities implements stack.LinkEndpoint.Capabilities.
func (e *Endpoint) Capabilities() stack.LinkEndpointCapabilities {
return stack.CapabilityNone
}
// MaxHeaderLength returns the maximum size of the link layer header. Given it
// doesn't have a header, it just returns 0.
func (*Endpoint) MaxHeaderLength() uint16 {
return 0
}
// LinkAddress returns the link address of this endpoint.
func (*Endpoint) LinkAddress() tcpip.LinkAddress {
return ""
}
// ARPHardwareType implements stack.LinkEndpoint.ARPHardwareType.
func (*Endpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareNone
}
// AddHeader implements stack.LinkEndpoint.AddHeader.
func (e *Endpoint) AddHeader(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
}
// Wait implements stack.LinkEndpoint.Wait.
func (e *Endpoint) Wait() {}

View File

@@ -1,33 +0,0 @@
//go:build darwin || freebsd || openbsd
package tun
import (
"github.com/xjasonlyu/tun2socks/common/pool"
)
const (
offset = 4 /* 4 bytes TUN_PI */
defaultMTU = 1500
)
func (t *TUN) Read(packet []byte) (n int, err error) {
buf := pool.Get(offset + len(packet))
defer pool.Put(buf)
if n, err = t.nt.Read(buf, offset); err != nil {
return
}
copy(packet, buf[offset:offset+n])
return
}
func (t *TUN) Write(packet []byte) (int, error) {
buf := pool.Get(offset + len(packet))
defer pool.Put(buf)
copy(buf[offset:], packet)
return t.nt.Write(buf[:offset+len(packet)], offset)
}

View File

@@ -1,15 +0,0 @@
package tun
const (
offset = 0
defaultMTU = 0 /* auto */
)
func (t *TUN) Read(packet []byte) (int, error) {
return t.nt.Read(packet, offset)
}
func (t *TUN) Write(packet []byte) (int, error) {
return t.nt.Write(packet, offset)
}

2
core/device/tun/tun.go Executable file → Normal file
View File

@@ -2,7 +2,7 @@
package tun
import (
"github.com/xjasonlyu/tun2socks/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device"
)
const Driver = "tun"

View File

@@ -4,15 +4,14 @@ package tun
import (
"fmt"
"unsafe"
"github.com/xjasonlyu/tun2socks/core/device"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/rawfile"
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"github.com/xjasonlyu/tun2socks/v2/core/device"
)
type TUN struct {
@@ -49,10 +48,21 @@ func Open(name string, mtu uint32) (device.Device, error) {
t.mtu = _mtu
ep, err := fdbased.New(&fdbased.Options{
MTU: t.mtu,
FDs: []int{fd},
// TUN only
MTU: t.mtu,
// TUN only, ignore ethernet header.
EthernetHeader: false,
// SYS_READV support only for TUN fd.
PacketDispatchMode: fdbased.Readv,
// TAP/TUN fd's are not sockets and using the WritePackets calls results
// in errors as it always defaults to using SendMMsg which is not supported
// for tap/tun device fds.
//
// This CL changes WritePackets to gracefully degrade to using writev instead
// of sendmmsg if the underlying fd is not a socket.
//
// Fixed: https://github.com/google/gvisor/commit/f33d034fecd7723a1e560ccc62aeeba328454fd0
MaxSyscallHeaderBytes: 0x00,
})
if err != nil {
return nil, fmt.Errorf("create endpoint: %w", err)
@@ -66,8 +76,9 @@ func (t *TUN) Name() string {
return t.name
}
func (t *TUN) Close() error {
return unix.Close(t.fd)
func (t *TUN) Close() {
defer t.LinkEndpoint.Close()
_ = unix.Close(t.fd)
}
func setMTU(name string, n uint32) error {
@@ -83,22 +94,10 @@ func setMTU(name string, n uint32) error {
defer unix.Close(fd)
const ifReqSize = unix.IFNAMSIZ + 64
// do ioctl call
var ifr [ifReqSize]byte
copy(ifr[:], name)
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = n
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCSIFMTU),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return fmt.Errorf("failed to set MTU: %w", errno)
ifr, err := unix.NewIfreq(name)
if err != nil {
return err
}
return nil
ifr.SetUint32(n)
return unix.IoctlIfreq(fd, unix.SIOCSIFMTU, ifr)
}

67
core/device/tun/tun_wireguard.go Executable file → Normal file
View File

@@ -4,42 +4,63 @@ package tun
import (
"fmt"
"github.com/xjasonlyu/tun2socks/core/device"
"github.com/xjasonlyu/tun2socks/core/device/rwbased"
"sync"
"golang.zx2c4.com/wireguard/tun"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
)
type TUN struct {
*rwbased.Endpoint
*iobased.Endpoint
nt *tun.NativeTun
mtu uint32
name string
nt *tun.NativeTun
mtu uint32
name string
offset int
rSizes []int
rBuffs [][]byte
wBuffs [][]byte
rMutex sync.Mutex
wMutex sync.Mutex
}
func Open(name string, mtu uint32) (device.Device, error) {
t := &TUN{name: name, mtu: mtu}
func Open(name string, mtu uint32) (_ device.Device, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("open tun: %v", r)
}
}()
t := &TUN{
name: name,
mtu: mtu,
offset: offset,
rSizes: make([]int, 1),
rBuffs: make([][]byte, 1),
wBuffs: make([][]byte, 1),
}
forcedMTU := defaultMTU
if t.mtu > 0 {
forcedMTU = int(t.mtu)
}
nt, err := tun.CreateTUN(t.name, forcedMTU)
nt, err := createTUN(t.name, forcedMTU)
if err != nil {
return nil, fmt.Errorf("create tun: %w", err)
}
t.nt = nt.(*tun.NativeTun)
_mtu, err := nt.MTU()
tunMTU, err := nt.MTU()
if err != nil {
return nil, fmt.Errorf("get mtu: %w", err)
}
t.mtu = uint32(_mtu)
t.mtu = uint32(tunMTU)
ep, err := rwbased.New(t, t.mtu)
ep, err := iobased.New(t, t.mtu, offset)
if err != nil {
return nil, fmt.Errorf("create endpoint: %w", err)
}
@@ -48,11 +69,27 @@ func Open(name string, mtu uint32) (device.Device, error) {
return t, nil
}
func (t *TUN) Read(packet []byte) (int, error) {
t.rMutex.Lock()
defer t.rMutex.Unlock()
t.rBuffs[0] = packet
_, err := t.nt.Read(t.rBuffs, t.rSizes, t.offset)
return t.rSizes[0], err
}
func (t *TUN) Write(packet []byte) (int, error) {
t.wMutex.Lock()
defer t.wMutex.Unlock()
t.wBuffs[0] = packet
return t.nt.Write(t.wBuffs, t.offset)
}
func (t *TUN) Name() string {
name, _ := t.nt.Name()
return name
}
func (t *TUN) Close() error {
return t.nt.Close()
func (t *TUN) Close() {
defer t.Endpoint.Close()
_ = t.nt.Close()
}

View File

@@ -0,0 +1,16 @@
//go:build unix && !linux
package tun
import (
"golang.zx2c4.com/wireguard/tun"
)
const (
offset = 4 /* 4 bytes TUN_PI */
defaultMTU = 1500
)
func createTUN(name string, mtu int) (tun.Device, error) {
return tun.CreateTUN(name, mtu)
}

View File

@@ -0,0 +1,14 @@
package tun
import (
"golang.zx2c4.com/wireguard/tun"
)
const (
offset = 0
defaultMTU = 0 /* auto */
)
func createTUN(name string, mtu int) (tun.Device, error) {
return tun.CreateTUN(name, mtu)
}

View File

@@ -1,6 +0,0 @@
package core
type Handler interface {
Add(TCPConn)
AddPacket(UDPPacket)
}

119
core/nic.go Normal file
View File

@@ -0,0 +1,119 @@
package core
import (
"fmt"
"net/netip"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"github.com/xjasonlyu/tun2socks/v2/core/option"
)
const (
// nicPromiscuousModeEnabled is the value used by stack to enable
// or disable NIC's promiscuous mode.
nicPromiscuousModeEnabled = true
// nicSpoofingEnabled is the value used by stack to enable or disable
// NIC's spoofing.
nicSpoofingEnabled = true
)
// withCreatingNIC creates NIC for stack.
func withCreatingNIC(nicID tcpip.NICID, ep stack.LinkEndpoint) option.Option {
return func(s *stack.Stack) error {
if err := s.CreateNICWithOptions(nicID, ep,
stack.NICOptions{
Disabled: false,
// If no queueing discipline was specified
// provide a stub implementation that just
// delegates to the lower link endpoint.
QDisc: nil,
}); err != nil {
return fmt.Errorf("create NIC: %s", err)
}
return nil
}
}
// withPromiscuousMode sets promiscuous mode in the given NICs.
func withPromiscuousMode(nicID tcpip.NICID, v bool) option.Option {
return func(s *stack.Stack) error {
if err := s.SetPromiscuousMode(nicID, v); err != nil {
return fmt.Errorf("set promiscuous mode: %s", err)
}
return nil
}
}
// withSpoofing sets address spoofing in the given NICs, allowing
// endpoints to bind to any address in the NIC.
func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
return func(s *stack.Stack) error {
if err := s.SetSpoofing(nicID, v); err != nil {
return fmt.Errorf("set spoofing: %s", err)
}
return nil
}
}
// withMulticastGroups adds a NIC to the given multicast groups.
func withMulticastGroups(nicID tcpip.NICID, multicastGroups []netip.Addr) option.Option {
return func(s *stack.Stack) error {
if len(multicastGroups) == 0 {
return nil
}
// The default NIC of tun2socks is working on Spoofing mode. When the UDP Endpoint
// tries to use a non-local address to connect, the network stack will
// generate a temporary addressState to build the route, which can be primary
// but is ephemeral. Nevertheless, when the UDP Endpoint tries to use a
// multicast address to connect, the network stack will select an available
// primary addressState to build the route. However, when tun2socks is in the
// just-initialized or idle state, there will be no available primary addressState,
// and the connect operation will fail. Therefore, we need to add permanent addresses,
// e.g. 10.0.0.1/8 and fd00:1/8, to the default NIC, which are only used to build
// routes for multicast response and do not affect other connections.
//
// In fact, for multicast, the sender normally does not expect a response.
// So, the ep.net.Connect is unnecessary. If we implement a custom UDP Forwarder
// and ForwarderRequest in the future, we can remove these code.
s.AddProtocolAddress(
nicID,
tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.AddrFrom4([4]byte{0x0a, 0, 0, 0x01}),
PrefixLen: 8,
},
},
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
)
s.AddProtocolAddress(
nicID,
tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: tcpip.AddrFrom16([16]byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}),
PrefixLen: 8,
},
},
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
)
for _, multicastGroup := range multicastGroups {
var err tcpip.Error
switch {
case multicastGroup.Is4():
err = s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4(multicastGroup.As4()))
case multicastGroup.Is6():
err = s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16(multicastGroup.As16()))
}
if err != nil {
return fmt.Errorf("join multicast group: %s", err)
}
}
return nil
}
}

127
core/stack/opts.go → core/option/option.go Executable file → Normal file
View File

@@ -1,4 +1,4 @@
package stack
package option
import (
"fmt"
@@ -7,23 +7,18 @@ import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
)
const (
// maxBufferSize is the maximum permitted size of a send/receive buffer.
maxBufferSize = 4 << 20 // 4 MiB
// minBufferSize is the smallest size of a receive or send buffer.
minBufferSize = 4 << 10 // 4 KiB
// defaultBufferSize is the default size of the send/recv buffer for
// a transport endpoint.
defaultBufferSize = 212 << 10 // 212 KiB
// defaultTimeToLive specifies the default TTL used by stack.
defaultTimeToLive uint8 = 64
// ipForwardingEnabled is the value used by stack to enable packet
// forwarding between NICs.
ipForwardingEnabled = true
// icmpBurst is the default number of ICMP messages that can be sent in
// a single burst.
icmpBurst = 50
@@ -32,10 +27,6 @@ const (
// by this rate limiter.
icmpLimit rate.Limit = 1000
// ipForwardingEnabled is the value used by stack to enable packet
// forwarding between NICs.
ipForwardingEnabled = true
// tcpCongestionControl is the congestion control algorithm used by
// stack. ccReno is the default option in gVisor stack.
tcpCongestionControlAlgorithm = "reno" // "reno" or "cubic"
@@ -46,18 +37,35 @@ const (
// tcpModerateReceiveBufferEnabled is the value used by stack to
// enable or disable tcp receive buffer auto-tuning option.
tcpModerateReceiveBufferEnabled = true
tcpModerateReceiveBufferEnabled = false
// tcpSACKEnabled is the value used by stack to enable or disable
// tcp selective ACK.
tcpSACKEnabled = true
// tcpRecovery is the loss detection algorithm used by TCP.
tcpRecovery = tcpip.TCPRACKLossDetection
// tcpMinBufferSize is the smallest size of a send/recv buffer.
tcpMinBufferSize = tcp.MinBufferSize
// tcpMaxBufferSize is the maximum permitted size of a send/recv buffer.
tcpMaxBufferSize = tcp.MaxBufferSize
// tcpDefaultBufferSize is the default size of the send buffer for
// a transport endpoint.
tcpDefaultSendBufferSize = tcp.DefaultSendBufferSize
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
// for a transport endpoint.
tcpDefaultReceiveBufferSize = tcp.DefaultReceiveBufferSize
)
type Option func(*Stack) error
type Option func(*stack.Stack) error
// WithDefault sets all default values for stack.
func WithDefault() Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
opts := []Option{
WithDefaultTTL(defaultTimeToLive),
WithForwarding(ipForwardingEnabled),
@@ -69,8 +77,9 @@ func WithDefault() Option {
// Too large buffers thrash cache, so there is little point
// in too large buffers.
//
// Ref: https://github.com/majek/slirpnetstack/blob/master/stack.go
WithTCPBufferSizeRange(minBufferSize, defaultBufferSize, maxBufferSize),
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
WithTCPSendBufferSizeRange(tcpMinBufferSize, tcpDefaultSendBufferSize, tcpMaxBufferSize),
WithTCPReceiveBufferSizeRange(tcpMinBufferSize, tcpDefaultReceiveBufferSize, tcpMaxBufferSize),
WithTCPCongestionControl(tcpCongestionControlAlgorithm),
WithTCPDelay(tcpDelayEnabled),
@@ -82,6 +91,17 @@ func WithDefault() Option {
// TCP selective ACK Option, see:
// https://tools.ietf.org/html/rfc2018
WithTCPSACKEnabled(tcpSACKEnabled),
// TCPRACKLossDetection: indicates RACK is used for loss detection and
// recovery.
//
// TCPRACKStaticReoWnd: indicates the reordering window should not be
// adjusted when DSACK is received.
//
// TCPRACKNoDupTh: indicates RACK should not consider the classic three
// duplicate acknowledgements rule to mark the segments as lost. This
// is used when reordering is not detected.
WithTCPRecovery(tcpRecovery),
}
for _, opt := range opts {
@@ -96,7 +116,7 @@ func WithDefault() Option {
// WithDefaultTTL sets the default TTL used by stack.
func WithDefaultTTL(ttl uint8) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
opt := tcpip.DefaultTTLOption(ttl)
if err := s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &opt); err != nil {
return fmt.Errorf("set ipv4 default TTL: %s", err)
@@ -110,7 +130,7 @@ func WithDefaultTTL(ttl uint8) Option {
// WithForwarding sets packet forwarding between NICs for IPv4 & IPv6.
func WithForwarding(v bool) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
if err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, v); err != nil {
return fmt.Errorf("set ipv4 forwarding: %s", err)
}
@@ -124,7 +144,7 @@ func WithForwarding(v bool) Option {
// WithICMPBurst sets the number of ICMP messages that can be sent
// in a single burst.
func WithICMPBurst(burst int) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
s.SetICMPBurst(burst)
return nil
}
@@ -133,19 +153,26 @@ func WithICMPBurst(burst int) Option {
// WithICMPLimit sets the maximum number of ICMP messages permitted
// by rate limiter.
func WithICMPLimit(limit rate.Limit) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
s.SetICMPLimit(limit)
return nil
}
}
// WithTCPBufferSizeRange sets the receive and send buffer size range for TCP.
func WithTCPBufferSizeRange(a, b, c int) Option {
return func(s *Stack) error {
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
return fmt.Errorf("set TCP receive buffer size range: %s", err)
// WithTCPSendBufferSize sets default the send buffer size for TCP.
func WithTCPSendBufferSize(size int) Option {
return func(s *stack.Stack) error {
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
return fmt.Errorf("set TCP send buffer size range: %s", err)
}
return nil
}
}
// WithTCPSendBufferSizeRange sets the send buffer size range for TCP.
func WithTCPSendBufferSizeRange(a, b, c int) Option {
return func(s *stack.Stack) error {
sndOpt := tcpip.TCPSendBufferSizeRangeOption{Min: a, Default: b, Max: c}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sndOpt); err != nil {
return fmt.Errorf("set TCP send buffer size range: %s", err)
@@ -154,9 +181,31 @@ func WithTCPBufferSizeRange(a, b, c int) Option {
}
}
// WithTCPReceiveBufferSize sets the default receive buffer size for TCP.
func WithTCPReceiveBufferSize(size int) Option {
return func(s *stack.Stack) error {
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcpMinBufferSize, Default: size, Max: tcpMaxBufferSize}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
return fmt.Errorf("set TCP receive buffer size range: %s", err)
}
return nil
}
}
// WithTCPReceiveBufferSizeRange sets the receive buffer size range for TCP.
func WithTCPReceiveBufferSizeRange(a, b, c int) Option {
return func(s *stack.Stack) error {
rcvOpt := tcpip.TCPReceiveBufferSizeRangeOption{Min: a, Default: b, Max: c}
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &rcvOpt); err != nil {
return fmt.Errorf("set TCP receive buffer size range: %s", err)
}
return nil
}
}
// WithTCPCongestionControl sets the current congestion control algorithm.
func WithTCPCongestionControl(cc string) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
opt := tcpip.CongestionControlOption(cc)
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
return fmt.Errorf("set TCP congestion control algorithm: %s", err)
@@ -167,7 +216,7 @@ func WithTCPCongestionControl(cc string) Option {
// WithTCPDelay enables or disables Nagle's algorithm in TCP.
func WithTCPDelay(v bool) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
opt := tcpip.TCPDelayEnabled(v)
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
return fmt.Errorf("set TCP delay: %s", err)
@@ -178,7 +227,7 @@ func WithTCPDelay(v bool) Option {
// WithTCPModerateReceiveBuffer sets receive buffer moderation for TCP.
func WithTCPModerateReceiveBuffer(v bool) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
opt := tcpip.TCPModerateReceiveBufferOption(v)
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
return fmt.Errorf("set TCP moderate receive buffer: %s", err)
@@ -189,7 +238,7 @@ func WithTCPModerateReceiveBuffer(v bool) Option {
// WithTCPSACKEnabled sets the SACK option for TCP.
func WithTCPSACKEnabled(v bool) Option {
return func(s *Stack) error {
return func(s *stack.Stack) error {
opt := tcpip.TCPSACKEnabled(v)
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &opt); err != nil {
return fmt.Errorf("set TCP SACK: %s", err)
@@ -197,3 +246,13 @@ func WithTCPSACKEnabled(v bool) Option {
return nil
}
}
// WithTCPRecovery sets the recovery option for TCP.
func WithTCPRecovery(v tcpip.TCPRecovery) Option {
return func(s *stack.Stack) error {
if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &v); err != nil {
return fmt.Errorf("set TCP Recovery: %s", err)
}
return nil
}
}

15
core/stack/icmp.go → core/route.go Executable file → Normal file
View File

@@ -1,22 +1,23 @@
package stack
package core
import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"github.com/xjasonlyu/tun2socks/v2/core/option"
)
func withICMPHandler() Option {
return func(s *Stack) error {
// Add default route table for IPv4 and IPv6.
// This will handle all incoming ICMP packets.
func withRouteTable(nicID tcpip.NICID) option.Option {
return func(s *stack.Stack) error {
s.SetRouteTable([]tcpip.Route{
{
Destination: header.IPv4EmptySubnet,
NIC: s.nicID,
NIC: nicID,
},
{
Destination: header.IPv6EmptySubnet,
NIC: s.nicID,
NIC: nicID,
},
})
return nil

107
core/stack.go Normal file
View File

@@ -0,0 +1,107 @@
package core
import (
"net/netip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
)
// Config is the configuration to create *stack.Stack.
type Config struct {
// LinkEndpoints is the interface implemented by
// data link layer protocols.
LinkEndpoint stack.LinkEndpoint
// TransportHandler is the handler used by internal
// stack to set transport handlers.
TransportHandler adapter.TransportHandler
// MulticastGroups is used by internal stack to add
// nic to given groups.
MulticastGroups []netip.Addr
// Options are supplement options to apply settings
// for the internal stack.
Options []option.Option
}
// CreateStack creates *stack.Stack with given config.
func CreateStack(cfg *Config) (*stack.Stack, error) {
opts := []option.Option{option.WithDefault()}
if len(opts) > 0 {
opts = append(opts, cfg.Options...)
}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocol,
ipv6.NewProtocol,
},
TransportProtocols: []stack.TransportProtocolFactory{
tcp.NewProtocol,
udp.NewProtocol,
icmp.NewProtocol4,
icmp.NewProtocol6,
},
})
// Generate unique NIC id.
nicID := s.NextNICID()
opts = append(opts,
// Important: We must initiate transport protocol handlers
// before creating NIC, otherwise NIC would dispatch packets
// to stack and cause race condition.
// Initiate transport protocol (TCP/UDP) with given handler.
withTCPHandler(cfg.TransportHandler.HandleTCP),
withUDPHandler(cfg.TransportHandler.HandleUDP),
// Create stack NIC and then bind link endpoint to it.
withCreatingNIC(nicID, cfg.LinkEndpoint),
// In the past we did s.AddAddressRange to assign 0.0.0.0/0
// onto the interface. We need that to be able to terminate
// all the incoming connections - to any ip. AddressRange API
// has been removed and the suggested workaround is to use
// Promiscuous mode. https://github.com/google/gvisor/issues/3876
//
// Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go
withPromiscuousMode(nicID, nicPromiscuousModeEnabled),
// Enable spoofing if a stack may send packets from unowned
// addresses. This change required changes to some netgophers
// since previously, promiscuous mode was enough to let the
// netstack respond to all incoming packets regardless of the
// packet's destination address. Now that a stack.Route is not
// held for each incoming packet, finding a route may fail with
// local addresses we don't own but accepted packets for while
// in promiscuous mode. Since we also want to be able to send
// from any address (in response the received promiscuous mode
// packets), we need to enable spoofing.
//
// Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127
withSpoofing(nicID, nicSpoofingEnabled),
// Add default route table for IPv4 and IPv6. This will handle
// all incoming ICMP packets.
withRouteTable(nicID),
// Add default NIC to the given multicast groups.
withMulticastGroups(nicID, cfg.MulticastGroups),
)
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
return s, nil
}

View File

@@ -1,52 +0,0 @@
package stack
import (
"fmt"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
const (
// defaultNICID is the ID of default NIC used by DefaultStack.
defaultNICID tcpip.NICID = 0x01
// nicPromiscuousModeEnabled is the value used by stack to enable
// or disable NIC's promiscuous mode.
nicPromiscuousModeEnabled = true
// nicSpoofingEnabled is the value used by stack to enable or disable
// NIC's spoofing.
nicSpoofingEnabled = true
)
// withCreatingNIC creates NIC for stack.
func withCreatingNIC(ep stack.LinkEndpoint) Option {
return func(s *Stack) error {
if err := s.CreateNIC(s.nicID, ep); err != nil {
return fmt.Errorf("create NIC: %s", err)
}
return nil
}
}
// withPromiscuousMode sets promiscuous mode in the given NIC.
func withPromiscuousMode(v bool) Option {
return func(s *Stack) error {
if err := s.SetPromiscuousMode(s.nicID, v); err != nil {
return fmt.Errorf("set promiscuous mode: %s", err)
}
return nil
}
}
// withSpoofing sets address spoofing in the given NIC, allowing
// endpoints to bind to any address in the NIC.
func withSpoofing(v bool) Option {
return func(s *Stack) error {
if err := s.SetSpoofing(s.nicID, v); err != nil {
return fmt.Errorf("set spoofing: %s", err)
}
return nil
}
}

View File

@@ -1,82 +0,0 @@
// Package stack provides a thin wrapper around a gVisor's stack.
package stack
import (
"github.com/xjasonlyu/tun2socks/core"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
type Stack struct {
*stack.Stack
handler core.Handler
nicID tcpip.NICID
}
// New allocates a new *Stack with given options.
func New(ep stack.LinkEndpoint, handler core.Handler, opts ...Option) (*Stack, error) {
s := &Stack{
Stack: stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocol,
ipv6.NewProtocol,
},
TransportProtocols: []stack.TransportProtocolFactory{
tcp.NewProtocol,
udp.NewProtocol,
icmp.NewProtocol4,
icmp.NewProtocol6,
},
}),
handler: handler,
nicID: defaultNICID,
}
opts = append(opts,
// Important: We must initiate transport protocol handlers
// before creating NIC, otherwise NIC would dispatch packets
// to stack and cause race condition.
withICMPHandler(), withTCPHandler(), withUDPHandler(),
// Create stack NIC and then bind link endpoint.
withCreatingNIC(ep),
// In past we did s.AddAddressRange to assign 0.0.0.0/0 onto
// the interface. We need that to be able to terminate all the
// incoming connections - to any ip. AddressRange API has been
// removed and the suggested workaround is to use Promiscuous
// mode. https://github.com/google/gvisor/issues/3876
//
// Ref: https://github.com/majek/slirpnetstack/blob/master/stack.go
withPromiscuousMode(nicPromiscuousModeEnabled),
// Enable spoofing if a stack may send packets from unowned addresses.
// This change required changes to some netgophers since previously,
// promiscuous mode was enough to let the netstack respond to all
// incoming packets regardless of the packet's destination address. Now
// that a stack.Route is not held for each incoming packet, finding a route
// may fail with local addresses we don't own but accepted packets for
// while in promiscuous mode. Since we also want to be able to send from
// any address (in response the received promiscuous mode packets), we need
// to enable spoofing.
//
// Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127
withSpoofing(nicSpoofingEnabled),
)
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
return s, nil
}

View File

@@ -1,83 +0,0 @@
package stack
import (
"fmt"
"net"
"time"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/waiter"
)
const (
// defaultWndSize if set to zero, the default
// receive window buffer size is used instead.
defaultWndSize = 0
// maxConnAttempts specifies the maximum number
// of in-flight tcp connection attempts.
maxConnAttempts = 2 << 10
// tcpKeepaliveIdle specifies the time a connection
// must remain idle before the first TCP keepalive
// packet is sent. Once this time is reached,
// tcpKeepaliveInterval option is used instead.
tcpKeepaliveIdle = 60 * time.Second
// tcpKeepaliveInterval specifies the interval
// time between sending TCP keepalive packets.
tcpKeepaliveInterval = 30 * time.Second
)
func withTCPHandler() Option {
return func(s *Stack) error {
tcpForwarder := tcp.NewForwarder(s.Stack, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
var wq waiter.Queue
id := r.ID()
ep, err := r.CreateEndpoint(&wq)
if err != nil {
// prevent potential half-open TCP connection leak.
r.Complete(true)
return
}
r.Complete(false)
setKeepalive(ep)
conn := &tcpConn{
Conn: gonet.NewTCPConn(&wq, ep),
id: &id,
}
s.handler.Add(conn)
})
s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
return nil
}
}
func setKeepalive(ep tcpip.Endpoint) error {
ep.SocketOptions().SetKeepAlive(true)
idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
if err := ep.SetSockOpt(&idle); err != nil {
return fmt.Errorf("set keepalive idle: %s", err)
}
interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
if err := ep.SetSockOpt(&interval); err != nil {
return fmt.Errorf("set keepalive interval: %s", err)
}
return nil
}
type tcpConn struct {
net.Conn
id *stack.TransportEndpointID
}
func (c *tcpConn) ID() *stack.TransportEndpointID {
return c.id
}

View File

@@ -1,181 +0,0 @@
package stack
import (
"fmt"
"net"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
const (
// udpNoChecksum disables UDP checksum.
udpNoChecksum = true
)
func withUDPHandler() Option {
return func(s *Stack) error {
udpHandlePacket := func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
// Ref: gVisor pkg/tcpip/transport/udp/endpoint.go HandlePacket
udpHdr := header.UDP(pkt.TransportHeader().View())
if int(udpHdr.Length()) > pkt.Data().Size()+header.UDPMinimumSize {
// Malformed packet.
s.Stats().UDP.MalformedPacketsReceived.Increment()
return true
}
if !verifyChecksum(udpHdr, pkt) {
// Checksum error.
s.Stats().UDP.ChecksumErrors.Increment()
return true
}
s.Stats().UDP.PacketsReceived.Increment()
packet := &udpPacket{
s: s,
id: &id,
data: pkt.Data().ExtractVV(),
nicID: pkt.NICID,
netHdr: pkt.Network(),
netProto: pkt.NetworkProtocolNumber,
}
s.handler.AddPacket(packet)
return true
}
s.SetTransportProtocolHandler(udp.ProtocolNumber, udpHandlePacket)
return nil
}
}
type udpPacket struct {
s *Stack
id *stack.TransportEndpointID
data buffer.VectorisedView
nicID tcpip.NICID
netHdr header.Network
netProto tcpip.NetworkProtocolNumber
}
func (p *udpPacket) Data() []byte {
return p.data.ToView()
}
func (p *udpPacket) Drop() {}
func (p *udpPacket) ID() *stack.TransportEndpointID {
return p.id
}
func (p *udpPacket) LocalAddr() net.Addr {
return &net.UDPAddr{IP: net.IP(p.id.LocalAddress), Port: int(p.id.LocalPort)}
}
func (p *udpPacket) RemoteAddr() net.Addr {
return &net.UDPAddr{IP: net.IP(p.id.RemoteAddress), Port: int(p.id.RemotePort)}
}
func (p *udpPacket) WriteBack(b []byte, addr net.Addr) (int, error) {
v := buffer.View(b)
if len(v) > header.UDPMaximumPacketSize {
// Payload can't possibly fit in a packet.
return 0, fmt.Errorf("%s", &tcpip.ErrMessageTooLong{})
}
var (
localAddress tcpip.Address
localPort uint16
)
if udpAddr, ok := addr.(*net.UDPAddr); !ok {
localAddress = p.netHdr.DestinationAddress()
localPort = p.id.LocalPort
} else if ipv4 := udpAddr.IP.To4(); ipv4 != nil {
localAddress = tcpip.Address(ipv4)
localPort = uint16(udpAddr.Port)
} else {
localAddress = tcpip.Address(udpAddr.IP)
localPort = uint16(udpAddr.Port)
}
route, err := p.s.FindRoute(p.nicID, localAddress, p.netHdr.SourceAddress(), p.netProto, false /* multicastLoop */)
if err != nil {
return 0, fmt.Errorf("%#v find route: %s", p.id, err)
}
defer route.Release()
data := v.ToVectorisedView()
if err = sendUDP(route, data, localPort, p.id.RemotePort, udpNoChecksum); err != nil {
return 0, fmt.Errorf("%v", err)
}
return data.Size(), nil
}
// sendUDP sends a UDP segment via the provided network endpoint and under the
// provided identity.
func sendUDP(r *stack.Route, data buffer.VectorisedView, localPort, remotePort uint16, noChecksum bool) tcpip.Error {
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: header.UDPMinimumSize + int(r.MaxHeaderLength()),
Data: data,
})
// Initialize the UDP header.
udpHdr := header.UDP(pkt.TransportHeader().Push(header.UDPMinimumSize))
pkt.TransportProtocolNumber = udp.ProtocolNumber
length := uint16(pkt.Size())
udpHdr.Encode(&header.UDPFields{
SrcPort: localPort,
DstPort: remotePort,
Length: length,
})
// Set the checksum field unless TX checksum offload is enabled.
// On IPv4, UDP checksum is optional, and a zero value indicates the
// transmitter skipped the checksum generation (RFC768).
// On IPv6, UDP checksum is not optional (RFC2460 Section 8.1).
if r.RequiresTXTransportChecksum() &&
(!noChecksum || r.NetProto() == header.IPv6ProtocolNumber) {
xsum := r.PseudoHeaderChecksum(udp.ProtocolNumber, length)
for _, v := range data.Views() {
xsum = header.Checksum(v, xsum)
}
udpHdr.SetChecksum(^udpHdr.CalculateChecksum(xsum))
}
ttl := r.DefaultTTL()
if err := r.WritePacket(stack.NetworkHeaderParams{
Protocol: udp.ProtocolNumber,
TTL: ttl,
TOS: 0, /* default */
}, pkt); err != nil {
r.Stats().UDP.PacketSendErrors.Increment()
return err
}
// Track count of packets sent.
r.Stats().UDP.PacketsSent.Increment()
return nil
}
// verifyChecksum verifies the checksum unless RX checksum offload is enabled.
// On IPv4, UDP checksum is optional, and a zero value means the transmitter
// omitted the checksum generation (RFC768).
// On IPv6, UDP checksum is not optional (RFC2460 Section 8.1).
func verifyChecksum(hdr header.UDP, pkt *stack.PacketBuffer) bool {
if !pkt.RXTransportChecksumValidated &&
(hdr.Checksum() != 0 || pkt.NetworkProtocolNumber == header.IPv6ProtocolNumber) {
netHdr := pkt.Network()
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, netHdr.DestinationAddress(), netHdr.SourceAddress(), hdr.Length())
for _, v := range pkt.Data().Views() {
xsum = header.Checksum(v, xsum)
}
return hdr.CalculateChecksum(xsum) == 0xffff
}
return true
}

122
core/tcp.go Normal file
View File

@@ -0,0 +1,122 @@
package core
import (
"time"
glog "gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/waiter"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
)
const (
// defaultWndSize if set to zero, the default
// receive window buffer size is used instead.
defaultWndSize = 0
// maxConnAttempts specifies the maximum number
// of in-flight tcp connection attempts.
maxConnAttempts = 2 << 10
// tcpKeepaliveCount is the maximum number of
// TCP keep-alive probes to send before giving up
// and killing the connection if no response is
// obtained from the other end.
tcpKeepaliveCount = 9
// tcpKeepaliveIdle specifies the time a connection
// must remain idle before the first TCP keepalive
// packet is sent. Once this time is reached,
// tcpKeepaliveInterval option is used instead.
tcpKeepaliveIdle = 60 * time.Second
// tcpKeepaliveInterval specifies the interval
// time between sending TCP keepalive packets.
tcpKeepaliveInterval = 30 * time.Second
)
func withTCPHandler(handle func(adapter.TCPConn)) option.Option {
return func(s *stack.Stack) error {
tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
var (
wq waiter.Queue
ep tcpip.Endpoint
err tcpip.Error
id = r.ID()
)
defer func() {
if err != nil {
glog.Debugf("forward tcp request: %s:%d->%s:%d: %s",
id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
}
}()
// Perform a TCP three-way handshake.
ep, err = r.CreateEndpoint(&wq)
if err != nil {
// RST: prevent potential half-open TCP connection leak.
r.Complete(true)
return
}
defer r.Complete(false)
err = setSocketOptions(s, ep)
conn := &tcpConn{
TCPConn: gonet.NewTCPConn(&wq, ep),
id: id,
}
handle(conn)
})
s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
return nil
}
}
func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
{ /* TCP keepalive options */
ep.SocketOptions().SetKeepAlive(true)
idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
if err := ep.SetSockOpt(&idle); err != nil {
return err
}
interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
if err := ep.SetSockOpt(&interval); err != nil {
return err
}
if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil {
return err
}
}
{ /* TCP recv/send buffer size */
var ss tcpip.TCPSendBufferSizeRangeOption
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil {
ep.SocketOptions().SetSendBufferSize(int64(ss.Default), false)
}
var rs tcpip.TCPReceiveBufferSizeRangeOption
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &rs); err == nil {
ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false)
}
}
return nil
}
type tcpConn struct {
*gonet.TCPConn
id stack.TransportEndpointID
}
func (c *tcpConn) ID() *stack.TransportEndpointID {
return &c.id
}

47
core/udp.go Normal file
View File

@@ -0,0 +1,47 @@
package core
import (
glog "gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
)
func withUDPHandler(handle func(adapter.UDPConn)) option.Option {
return func(s *stack.Stack) error {
udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) bool {
var (
wq waiter.Queue
id = r.ID()
)
ep, err := r.CreateEndpoint(&wq)
if err != nil {
glog.Debugf("forward udp request: %s:%d->%s:%d: %s",
id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
return false
}
conn := &udpConn{
UDPConn: gonet.NewUDPConn(&wq, ep),
id: id,
}
handle(conn)
return true
})
s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
return nil
}
}
type udpConn struct {
*gonet.UDPConn
id stack.TransportEndpointID
}
func (c *udpConn) ID() *stack.TransportEndpointID {
return &c.id
}

83
dialer/dialer.go Normal file
View File

@@ -0,0 +1,83 @@
package dialer
import (
"context"
"net"
"syscall"
"go.uber.org/atomic"
)
// DefaultDialer is the default Dialer and is used by DialContext and ListenPacket.
var DefaultDialer = &Dialer{
InterfaceName: atomic.NewString(""),
InterfaceIndex: atomic.NewInt32(0),
RoutingMark: atomic.NewInt32(0),
}
type Dialer struct {
InterfaceName *atomic.String
InterfaceIndex *atomic.Int32
RoutingMark *atomic.Int32
}
type Options struct {
// InterfaceName is the name of interface/device to bind.
// If a socket is bound to an interface, only packets received
// from that particular interface are processed by the socket.
InterfaceName string
// InterfaceIndex is the index of interface/device to bind.
// It is almost the same as InterfaceName except it uses the
// index of the interface instead of the name.
InterfaceIndex int
// RoutingMark is the mark for each packet sent through this
// socket. Changing the mark can be used for mark-based routing
// without netfilter or for packet filtering.
RoutingMark int
}
// DialContext is a wrapper around DefaultDialer.DialContext.
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return DefaultDialer.DialContext(ctx, network, address)
}
// ListenPacket is a wrapper around DefaultDialer.ListenPacket.
func ListenPacket(network, address string) (net.PacketConn, error) {
return DefaultDialer.ListenPacket(network, address)
}
func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return d.DialContextWithOptions(ctx, network, address, &Options{
InterfaceName: d.InterfaceName.Load(),
InterfaceIndex: int(d.InterfaceIndex.Load()),
RoutingMark: int(d.RoutingMark.Load()),
})
}
func (*Dialer) DialContextWithOptions(ctx context.Context, network, address string, opts *Options) (net.Conn, error) {
d := &net.Dialer{
Control: func(network, address string, c syscall.RawConn) error {
return setSocketOptions(network, address, c, opts)
},
}
return d.DialContext(ctx, network, address)
}
func (d *Dialer) ListenPacket(network, address string) (net.PacketConn, error) {
return d.ListenPacketWithOptions(network, address, &Options{
InterfaceName: d.InterfaceName.Load(),
InterfaceIndex: int(d.InterfaceIndex.Load()),
RoutingMark: int(d.RoutingMark.Load()),
})
}
func (*Dialer) ListenPacketWithOptions(network, address string, opts *Options) (net.PacketConn, error) {
lc := &net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return setSocketOptions(network, address, c, opts)
},
}
return lc.ListenPacket(context.Background(), network, address)
}

19
dialer/sockopt.go Normal file
View File

@@ -0,0 +1,19 @@
package dialer
func isTCPSocket(network string) bool {
switch network {
case "tcp", "tcp4", "tcp6":
return true
default:
return false
}
}
func isUDPSocket(network string) bool {
switch network {
case "udp", "udp4", "udp6":
return true
default:
return false
}
}

45
dialer/sockopt_darwin.go Normal file
View File

@@ -0,0 +1,45 @@
package dialer
import (
"net"
"syscall"
"golang.org/x/sys/unix"
)
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
return err
}
var innerErr error
err = c.Control(func(fd uintptr) {
host, _, _ := net.SplitHostPort(address)
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
return
}
if opts.InterfaceIndex == 0 && opts.InterfaceName != "" {
if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil {
opts.InterfaceIndex = iface.Index
}
}
if opts.InterfaceIndex != 0 {
switch network {
case "tcp4", "udp4":
innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, opts.InterfaceIndex)
case "tcp6", "udp6":
innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, opts.InterfaceIndex)
}
if innerErr != nil {
return
}
}
})
if innerErr != nil {
err = innerErr
}
return err
}

33
dialer/sockopt_freebsd.go Normal file
View File

@@ -0,0 +1,33 @@
package dialer
import (
"net"
"syscall"
"golang.org/x/sys/unix"
)
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
return err
}
var innerErr error
err = c.Control(func(fd uintptr) {
host, _, _ := net.SplitHostPort(address)
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
return
}
if opts.RoutingMark != 0 {
if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_USER_COOKIE, opts.RoutingMark); innerErr != nil {
return
}
}
})
if innerErr != nil {
err = innerErr
}
return err
}

44
dialer/sockopt_linux.go Normal file
View File

@@ -0,0 +1,44 @@
package dialer
import (
"net"
"syscall"
"golang.org/x/sys/unix"
)
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
return err
}
var innerErr error
err = c.Control(func(fd uintptr) {
host, _, _ := net.SplitHostPort(address)
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
return
}
if opts.InterfaceName == "" && opts.InterfaceIndex != 0 {
if iface, err := net.InterfaceByIndex(opts.InterfaceIndex); err == nil {
opts.InterfaceName = iface.Name
}
}
if opts.InterfaceName != "" {
if innerErr = unix.BindToDevice(int(fd), opts.InterfaceName); innerErr != nil {
return
}
}
if opts.RoutingMark != 0 {
if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, opts.RoutingMark); innerErr != nil {
return
}
}
})
if innerErr != nil {
err = innerErr
}
return err
}

33
dialer/sockopt_openbsd.go Normal file
View File

@@ -0,0 +1,33 @@
package dialer
import (
"net"
"syscall"
"golang.org/x/sys/unix"
)
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
return err
}
var innerErr error
err = c.Control(func(fd uintptr) {
host, _, _ := net.SplitHostPort(address)
if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() {
return
}
if opts.RoutingMark != 0 {
if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RTABLE, opts.RoutingMark); innerErr != nil {
return
}
}
})
if innerErr != nil {
err = innerErr
}
return err
}

9
dialer/sockopt_others.go Normal file
View File

@@ -0,0 +1,9 @@
//go:build !unix && !windows
package dialer
import "syscall"
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) error {
return nil
}

68
dialer/sockopt_windows.go Normal file
View File

@@ -0,0 +1,68 @@
package dialer
import (
"encoding/binary"
"net"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31
)
func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) {
if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) {
return err
}
var innerErr error
err = c.Control(func(fd uintptr) {
host, _, _ := net.SplitHostPort(address)
ip := net.ParseIP(host)
if ip != nil && !ip.IsGlobalUnicast() {
return
}
if opts.InterfaceIndex == 0 && opts.InterfaceName != "" {
if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil {
opts.InterfaceIndex = iface.Index
}
}
if opts.InterfaceIndex != 0 {
switch network {
case "tcp4", "udp4":
innerErr = bindSocketToInterface4(windows.Handle(fd), uint32(opts.InterfaceIndex))
case "tcp6", "udp6":
innerErr = bindSocketToInterface6(windows.Handle(fd), uint32(opts.InterfaceIndex))
if network == "udp6" && ip == nil {
// The underlying IP net maybe IPv4 even if the `network` param is `udp6`,
// so we should bind socket to interface4 at the same time.
innerErr = bindSocketToInterface4(windows.Handle(fd), uint32(opts.InterfaceIndex))
}
}
}
})
if innerErr != nil {
err = innerErr
}
return err
}
func bindSocketToInterface4(handle windows.Handle, index uint32) error {
// For IPv4, this parameter must be an interface index in network byte order.
// Ref: https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
var bytes [4]byte
binary.BigEndian.PutUint32(bytes[:], index)
index = *(*uint32)(unsafe.Pointer(&bytes[0]))
return windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(index))
}
func bindSocketToInterface6(handle windows.Handle, index uint32) error {
return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, int(index))
}

10
component/dialer/resolver.go → dns/resolver.go Executable file → Normal file
View File

@@ -1,10 +1,14 @@
package dialer
package dns
import "net"
import (
"net"
"github.com/xjasonlyu/tun2socks/v2/dialer"
)
func init() {
// We must use this DialContext to query DNS
// when using net default resolver.
net.DefaultResolver.PreferGo = true
net.DefaultResolver.Dial = DialContext
net.DefaultResolver.Dial = dialer.DefaultDialer.DialContext
}

View File

@@ -47,7 +47,7 @@ config_route() {
done
}
main() {
run() {
create_tun
create_table
config_route
@@ -58,27 +58,39 @@ main() {
fi
if [ -n "$MTU" ]; then
ARGS="-mtu $MTU"
ARGS="--mtu $MTU"
fi
if [ -n "$STATS" ]; then
ARGS="$ARGS -stats $STATS"
fi
if [ -n "$TOKEN" ]; then
ARGS="$ARGS -token $TOKEN"
if [ -n "$RESTAPI" ]; then
ARGS="$ARGS --restapi $RESTAPI"
fi
if [ -n "$UDP_TIMEOUT" ]; then
ARGS="$ARGS -udp-timeout $UDP_TIMEOUT"
ARGS="$ARGS --udp-timeout $UDP_TIMEOUT"
fi
if [ -n "$TCP_SNDBUF" ]; then
ARGS="$ARGS --tcp-sndbuf $TCP_SNDBUF"
fi
if [ -n "$TCP_RCVBUF" ]; then
ARGS="$ARGS --tcp-rcvbuf $TCP_RCVBUF"
fi
if [ "$TCP_AUTO_TUNING" = 1 ]; then
ARGS="$ARGS --tcp-auto-tuning"
fi
if [ -n "$MULTICAST_GROUPS" ]; then
ARGS="$ARGS --multicast-groups $MULTICAST_GROUPS"
fi
exec tun2socks \
-loglevel "$LOGLEVEL" \
-fwmark "$FWMARK" \
-device "$TUN" \
-proxy "$PROXY" \
--loglevel "$LOGLEVEL" \
--fwmark "$FWMARK" \
--device "$TUN" \
--proxy "$PROXY" \
$ARGS
}
main || exit 1
run || exit 1

BIN
docs/benchmark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
docs/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 109 KiB

287
engine/engine.go Executable file → Normal file
View File

@@ -2,167 +2,242 @@ package engine
import (
"errors"
"os"
"net"
"net/netip"
"os/exec"
"sync"
"time"
"github.com/xjasonlyu/tun2socks/component/dialer"
"github.com/xjasonlyu/tun2socks/core/device"
"github.com/xjasonlyu/tun2socks/core/stack"
"github.com/xjasonlyu/tun2socks/log"
"github.com/xjasonlyu/tun2socks/proxy"
"github.com/xjasonlyu/tun2socks/stats"
"github.com/xjasonlyu/tun2socks/tunnel"
"github.com/docker/go-units"
"github.com/google/shlex"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"github.com/xjasonlyu/tun2socks/v2/core"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/option"
"github.com/xjasonlyu/tun2socks/v2/dialer"
"github.com/xjasonlyu/tun2socks/v2/log"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/restapi"
"github.com/xjasonlyu/tun2socks/v2/tunnel"
)
var _engine = &engine{}
var (
_engineMu sync.Mutex
// _defaultKey holds the default key for the engine.
_defaultKey *Key
// _defaultProxy holds the default proxy for the engine.
_defaultProxy proxy.Proxy
// _defaultDevice holds the default device for the engine.
_defaultDevice device.Device
// _defaultStack holds the default stack for the engine.
_defaultStack *stack.Stack
)
// Start starts the default engine up.
func Start() error {
return _engine.start()
func Start() {
if err := start(); err != nil {
log.Fatalf("[ENGINE] failed to start: %v", err)
}
}
// Stop shuts the default engine down.
func Stop() error {
return _engine.stop()
func Stop() {
if err := stop(); err != nil {
log.Fatalf("[ENGINE] failed to stop: %v", err)
}
}
// Insert loads *Key to the default engine.
func Insert(k *Key) {
_engine.insert(k)
_engineMu.Lock()
_defaultKey = k
_engineMu.Unlock()
}
type Key struct {
MTU int
Mark int
UDPTimeout int
Proxy string
Stats string
Token string
Device string
LogLevel string
Interface string
Version bool
}
func start() error {
_engineMu.Lock()
defer _engineMu.Unlock()
type engine struct {
*Key
stack *stack.Stack
proxy proxy.Proxy
device device.Device
}
func (e *engine) start() error {
if e.Key == nil {
if _defaultKey == nil {
return errors.New("empty key")
}
if e.Version {
showVersion()
os.Exit(0)
}
for _, f := range []func() error{
e.setLogLevel,
e.setMark,
e.setInterface,
e.setStats,
e.setUDPTimeout,
e.setProxy,
e.setDevice,
e.setStack,
for _, f := range []func(*Key) error{
general,
restAPI,
netstack,
} {
if err := f(); err != nil {
if err := f(_defaultKey); err != nil {
return err
}
}
return nil
}
func (e *engine) stop() error {
if e.device != nil {
return e.device.Close()
func stop() (err error) {
_engineMu.Lock()
if _defaultDevice != nil {
_defaultDevice.Close()
}
if _defaultStack != nil {
_defaultStack.Close()
_defaultStack.Wait()
}
_engineMu.Unlock()
return nil
}
func (e *engine) insert(k *Key) {
e.Key = k
}
func (e *engine) setLogLevel() error {
level, err := log.ParseLevel(e.LogLevel)
func execCommand(cmd string) error {
parts, err := shlex.Split(cmd)
if err != nil {
return err
}
log.SetLevel(level)
return nil
}
func (e *engine) setMark() error {
if e.Mark != 0 {
dialer.SetMark(e.Mark)
log.Infof("[DIALER] set fwmark: %#x", e.Mark)
if len(parts) == 0 {
return errors.New("empty command")
}
return nil
_, err = exec.Command(parts[0], parts[1:]...).Output()
return err
}
func (e *engine) setInterface() error {
if e.Interface != "" {
if err := dialer.BindToInterface(e.Interface); err != nil {
func general(k *Key) error {
level, err := log.ParseLevel(k.LogLevel)
if err != nil {
return err
}
log.SetLogger(log.Must(log.NewLeveled(level)))
if k.Interface != "" {
iface, err := net.InterfaceByName(k.Interface)
if err != nil {
return err
}
log.Infof("[DIALER] use interface: %s", e.Interface)
dialer.DefaultDialer.InterfaceName.Store(iface.Name)
dialer.DefaultDialer.InterfaceIndex.Store(int32(iface.Index))
log.Infof("[DIALER] bind to interface: %s", k.Interface)
}
if k.Mark != 0 {
dialer.DefaultDialer.RoutingMark.Store(int32(k.Mark))
log.Infof("[DIALER] set fwmark: %#x", k.Mark)
}
if k.UDPTimeout > 0 {
if k.UDPTimeout < time.Second {
return errors.New("invalid udp timeout value")
}
tunnel.T().SetUDPTimeout(k.UDPTimeout)
}
return nil
}
func (e *engine) setStats() error {
if e.Stats != "" {
func restAPI(k *Key) error {
if k.RestAPI != "" {
u, err := parseRestAPI(k.RestAPI)
if err != nil {
return err
}
host, token := u.Host, u.User.String()
restapi.SetStatsFunc(func() tcpip.Stats {
_engineMu.Lock()
defer _engineMu.Unlock()
// default stack is not initialized.
if _defaultStack == nil {
return tcpip.Stats{}
}
return _defaultStack.Stats()
})
go func() {
_ = stats.Start(e.Stats, e.Token)
if err := restapi.Start(host, token); err != nil {
log.Errorf("[RESTAPI] failed to start: %v", err)
}
}()
log.Infof("[STATS] serve at: http://%s", e.Stats)
log.Infof("[RESTAPI] serve at: %s", u)
}
return nil
}
func (e *engine) setUDPTimeout() error {
if e.UDPTimeout > 0 {
tunnel.SetUDPTimeout(e.UDPTimeout)
}
return nil
}
func (e *engine) setProxy() (err error) {
if e.Proxy == "" {
func netstack(k *Key) (err error) {
if k.Proxy == "" {
return errors.New("empty proxy")
}
e.proxy, err = parseProxy(e.Proxy)
proxy.SetDialer(e.proxy)
return
}
func (e *engine) setDevice() (err error) {
if e.Device == "" {
if k.Device == "" {
return errors.New("empty device")
}
e.device, err = parseDevice(e.Device, uint32(e.MTU))
return
}
if k.TUNPreUp != "" {
log.Infof("[TUN] pre-execute command: `%s`", k.TUNPreUp)
if preUpErr := execCommand(k.TUNPreUp); preUpErr != nil {
log.Errorf("[TUN] failed to pre-execute: %s: %v", k.TUNPreUp, preUpErr)
}
}
func (e *engine) setStack() (err error) {
defer func() {
if err == nil {
log.Infof(
"[STACK] %s://%s <-> %s://%s",
e.device.Type(), e.device.Name(),
e.proxy.Proto(), e.proxy.Addr(),
)
if k.TUNPostUp == "" || err != nil {
return
}
log.Infof("[TUN] post-execute command: `%s`", k.TUNPostUp)
if postUpErr := execCommand(k.TUNPostUp); postUpErr != nil {
log.Errorf("[TUN] failed to post-execute: %s: %v", k.TUNPostUp, postUpErr)
}
}()
e.stack, err = stack.New(e.device, &fakeTunnel{}, stack.WithDefault())
return
if _defaultProxy, err = parseProxy(k.Proxy); err != nil {
return err
}
tunnel.T().SetDialer(_defaultProxy)
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
return err
}
var multicastGroups []netip.Addr
if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil {
return err
}
var opts []option.Option
if k.TCPModerateReceiveBuffer {
opts = append(opts, option.WithTCPModerateReceiveBuffer(true))
}
if k.TCPSendBufferSize != "" {
size, err := units.RAMInBytes(k.TCPSendBufferSize)
if err != nil {
return err
}
opts = append(opts, option.WithTCPSendBufferSize(int(size)))
}
if k.TCPReceiveBufferSize != "" {
size, err := units.RAMInBytes(k.TCPReceiveBufferSize)
if err != nil {
return err
}
opts = append(opts, option.WithTCPReceiveBufferSize(int(size)))
}
if _defaultStack, err = core.CreateStack(&core.Config{
LinkEndpoint: _defaultDevice,
TransportHandler: tunnel.T(),
MulticastGroups: multicastGroups,
Options: opts,
}); err != nil {
return err
}
log.Infof(
"[STACK] %s://%s <-> %s://%s",
_defaultDevice.Type(), _defaultDevice.Name(),
_defaultProxy.Proto(), _defaultProxy.Addr(),
)
return nil
}

20
engine/key.go Normal file
View File

@@ -0,0 +1,20 @@
package engine
import "time"
type Key struct {
MTU int `yaml:"mtu"`
Mark int `yaml:"fwmark"`
Proxy string `yaml:"proxy"`
RestAPI string `yaml:"restapi"`
Device string `yaml:"device"`
LogLevel string `yaml:"loglevel"`
Interface string `yaml:"interface"`
TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"`
TCPSendBufferSize string `yaml:"tcp-send-buffer-size"`
TCPReceiveBufferSize string `yaml:"tcp-receive-buffer-size"`
MulticastGroups string `yaml:"multicast-groups"`
TUNPreUp string `yaml:"tun-pre-up"`
TUNPostUp string `yaml:"tun-post-up"`
UDPTimeout time.Duration `yaml:"udp-timeout"`
}

View File

@@ -3,16 +3,48 @@ package engine
import (
"encoding/base64"
"fmt"
"net"
"net/netip"
"net/url"
"runtime"
"strings"
"github.com/xjasonlyu/tun2socks/core/device"
"github.com/xjasonlyu/tun2socks/core/device/fd"
"github.com/xjasonlyu/tun2socks/core/device/tun"
"github.com/xjasonlyu/tun2socks/proxy"
"github.com/xjasonlyu/tun2socks/proxy/proto"
"github.com/gorilla/schema"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
func parseRestAPI(s string) (*url.URL, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", "http", s)
}
u, err := url.Parse(s)
if err != nil {
return nil, err
}
addr, err := net.ResolveTCPAddr("tcp", u.Host)
if err != nil {
return nil, err
}
if addr.IP == nil {
addr.IP = net.IPv4zero /* default: 0.0.0.0 */
}
u.Host = addr.String()
switch u.Scheme {
case "http":
return u, nil
default:
return nil, fmt.Errorf("unsupported scheme: %s", u.Scheme)
}
}
func parseDevice(s string, mtu uint32) (device.Device, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", tun.Driver /* default driver */, s)
@@ -23,19 +55,28 @@ func parseDevice(s string, mtu uint32) (device.Device, error) {
return nil, err
}
name := u.Host
driver := strings.ToLower(u.Scheme)
switch driver {
case fd.Driver:
return fd.Open(name, mtu)
case fdbased.Driver:
return parseFD(u, mtu)
case tun.Driver:
return tun.Open(name, mtu)
return parseTUN(u, mtu)
default:
return nil, fmt.Errorf("unsupported driver: %s", driver)
}
}
func parseFD(u *url.URL, mtu uint32) (device.Device, error) {
offset := 0
// fd offset in ios
// https://stackoverflow.com/questions/69260852/ios-network-extension-packet-parsing/69487795#69487795
if runtime.GOOS == "ios" {
offset = 4
}
return fdbased.Open(u.Host, mtu, offset)
}
func parseProxy(s string) (proxy.Proxy, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", proto.Socks5 /* default protocol */, s)
@@ -54,48 +95,56 @@ func parseProxy(s string) (proxy.Proxy, error) {
case proto.Reject.String():
return proxy.NewReject(), nil
case proto.HTTP.String():
return proxy.NewHTTP(parseHTTP(u))
return parseHTTP(u)
case proto.Socks4.String():
return proxy.NewSocks4(parseSocks4(u))
return parseSocks4(u)
case proto.Socks5.String():
return proxy.NewSocks5(parseSocks5(u))
return parseSocks5(u)
case proto.Shadowsocks.String():
return proxy.NewShadowsocks(parseShadowsocks(u))
return parseShadowsocks(u)
case proto.Relay.String():
return parseRelay(u)
default:
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
}
}
func parseHTTP(u *url.URL) (address, username, password string) {
address, username = u.Host, u.User.Username()
password, _ = u.User.Password()
return
func parseHTTP(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
return proxy.NewHTTP(address, username, password)
}
func parseSocks4(u *url.URL) (address, username string) {
address, username = u.Host, u.User.Username()
return
func parseSocks4(u *url.URL) (proxy.Proxy, error) {
address, userID := u.Host, u.User.Username()
return proxy.NewSocks4(address, userID)
}
func parseSocks5(u *url.URL) (address, username, password string) {
address, username = u.Host, u.User.Username()
password, _ = u.User.Password()
func parseSocks5(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
// Socks5 over UDS
if address == "" {
address = u.Path
}
return
return proxy.NewSocks5(address, username, password)
}
func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost string) {
address = u.Host
func parseShadowsocks(u *url.URL) (proxy.Proxy, error) {
var (
address = u.Host
method, password string
obfsMode, obfsHost string
)
if pass, set := u.User.Password(); set {
if ss := u.User.String(); ss == "" {
method = "dummy" // none cipher mode
} else if pass, set := u.User.Password(); set {
method = u.User.Username()
password = pass
} else {
data, _ := base64.RawURLEncoding.DecodeString(u.User.String())
data, _ := base64.RawURLEncoding.DecodeString(ss)
userInfo := strings.SplitN(string(data), ":", 2)
if len(userInfo) == 2 {
method = userInfo[0]
@@ -120,5 +169,36 @@ func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost
}
}
return proxy.NewShadowsocks(address, method, password, obfsMode, obfsHost)
}
func parseRelay(u *url.URL) (proxy.Proxy, error) {
address, username := u.Host, u.User.Username()
password, _ := u.User.Password()
opts := struct {
NoDelay bool
}{}
if err := schema.NewDecoder().Decode(&opts, u.Query()); err != nil {
return nil, err
}
return proxy.NewRelay(address, username, password, opts.NoDelay)
}
func parseMulticastGroups(s string) (multicastGroups []netip.Addr, _ error) {
for _, ip := range strings.Split(s, ",") {
if ip = strings.TrimSpace(ip); ip == "" {
continue
}
addr, err := netip.ParseAddr(ip)
if err != nil {
return nil, err
}
if !addr.IsMulticast() {
return nil, fmt.Errorf("invalid multicast IP: %s", addr)
}
multicastGroups = append(multicastGroups, addr)
}
return
}

14
engine/parse_unix.go Normal file
View File

@@ -0,0 +1,14 @@
//go:build unix
package engine
import (
"net/url"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
)
func parseTUN(u *url.URL, mtu uint32) (device.Device, error) {
return tun.Open(u.Host, mtu)
}

34
engine/parse_windows.go Normal file
View File

@@ -0,0 +1,34 @@
package engine
import (
"net/url"
"github.com/gorilla/schema"
"golang.org/x/sys/windows"
wun "golang.zx2c4.com/wireguard/tun"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
"github.com/xjasonlyu/tun2socks/v2/internal/version"
)
func init() {
wun.WintunTunnelType = version.Name
}
func parseTUN(u *url.URL, mtu uint32) (device.Device, error) {
opts := struct {
GUID string
}{}
if err := schema.NewDecoder().Decode(&opts, u.Query()); err != nil {
return nil, err
}
if opts.GUID != "" {
guid, err := windows.GUIDFromString(opts.GUID)
if err != nil {
return nil, err
}
wun.WintunStaticRequestedGUID = &guid
}
return tun.Open(u.Host, mtu)
}

View File

@@ -1,18 +0,0 @@
package engine
import (
"github.com/xjasonlyu/tun2socks/core"
"github.com/xjasonlyu/tun2socks/tunnel"
)
var _ core.Handler = (*fakeTunnel)(nil)
type fakeTunnel struct{}
func (*fakeTunnel) Add(conn core.TCPConn) {
tunnel.Add(conn)
}
func (*fakeTunnel) AddPacket(packet core.UDPPacket) {
tunnel.AddPacket(packet)
}

View File

@@ -1,22 +0,0 @@
package engine
import (
"fmt"
"runtime"
"strings"
V "github.com/xjasonlyu/tun2socks/constant"
)
func showVersion() {
fmt.Print(versionString())
fmt.Print(releaseString())
}
func versionString() string {
return fmt.Sprintf("%s-%s\n", V.Name, strings.TrimPrefix(V.Version, "v"))
}
func releaseString() string {
return fmt.Sprintf("%s/%s, %s, %s\n", runtime.GOOS, runtime.GOARCH, runtime.Version(), V.GitCommit)
}

46
go.mod
View File

@@ -1,28 +1,36 @@
module github.com/xjasonlyu/tun2socks
module github.com/xjasonlyu/tun2socks/v2
go 1.17
go 1.25
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/go-chi/chi v4.1.2+incompatible
github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1
github.com/gofrs/uuid v4.1.0+incompatible
github.com/gorilla/websocket v1.4.2
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.9.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
golang.org/x/sys v0.0.0-20211031064116-611d5d643895
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
golang.zx2c4.com/wireguard v0.0.0-20211030003956-52704c4b9288
gvisor.dev/gvisor v0.0.0-20211029210705-806fa5c3235c
github.com/docker/go-units v0.5.0
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/cors v1.2.2
github.com/go-chi/render v1.0.3
github.com/go-gost/relay v0.5.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
github.com/gorilla/schema v1.4.1
github.com/gorilla/websocket v1.5.3
github.com/stretchr/testify v1.9.0
go.uber.org/atomic v1.11.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.45.0
golang.org/x/sys v0.38.0
golang.org/x/time v0.12.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20250828211149-1f30edfbb5d4
)
require (
github.com/ajg/form v1.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

102
go.sum
View File

@@ -1,51 +1,61 @@
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/gofrs/uuid v4.1.0+incompatible h1:sIa2eCvUTwgjbqXrPLfNwUf9S3i3mpH1O1atV+iL/Wk=
github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c=
github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wireguard v0.0.0-20211030003956-52704c4b9288 h1:v3PAPzkDfgUxCCHa2ygoesDpj490YbYB4Q3ZuE6lFKk=
golang.zx2c4.com/wireguard v0.0.0-20211030003956-52704c4b9288/go.mod h1:RTjaYEQboNk7+2qfPGBotaMEh/5HIvmPZ6DIe10lTqI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20211029210705-806fa5c3235c h1:S4L2OVKs48LB9vVbPaiEfRqon+dn0wm3MABd2cyvjkc=
gvisor.dev/gvisor v0.0.0-20211029210705-806fa5c3235c/go.mod h1:btyTBPTxT8AFMvW7yctFJ2nPCEDWZLpmKQEZ0gG+bbQ=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20250828211149-1f30edfbb5d4 h1:dYE7x98x3StwL1Ezt6vtZmfIMupziz7CPhivLZxAFA8=
gvisor.dev/gvisor v0.0.0-20250828211149-1f30edfbb5d4/go.mod h1:K16uJjZ+hSqDVsXhU2Rg2FpMN7kBvjZp/Ibt5BYZJjw=

38
internal/pool/pool.go Normal file
View File

@@ -0,0 +1,38 @@
// Package pool provides internal pool utilities.
package pool
import (
"sync"
)
// A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed
// object pooling.
//
// Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will
// not be detected, so all internal pool use must take care to only store
// pointer types.
type Pool[T any] struct {
pool sync.Pool
}
// New returns a new [Pool] for T, and will use fn to construct new Ts when
// the pool is empty.
func New[T any](fn func() T) *Pool[T] {
return &Pool[T]{
pool: sync.Pool{
New: func() any {
return fn()
},
},
}
}
// Get gets a T from the pool, or creates a new one if the pool is empty.
func (p *Pool[T]) Get() T {
return p.pool.Get().(T)
}
// Put returns x into the pool.
func (p *Pool[T]) Put(x T) {
p.pool.Put(x)
}

View File

@@ -0,0 +1,85 @@
package pool
import (
"runtime/debug"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
type pooledValue[T any] struct {
value T
}
func TestNew(t *testing.T) {
// Disable GC to avoid the victim cache during the test.
defer debug.SetGCPercent(debug.SetGCPercent(-1))
p := New(func() *pooledValue[string] {
return &pooledValue[string]{
value: "new",
}
})
// Probabilistically, 75% of sync.Pool.Put calls will succeed when -race
// is enabled (see ref below); attempt to make this quasi-deterministic by
// brute force (i.e., put significantly more objects in the pool than we
// will need for the test) in order to avoid testing without race enabled.
//
// ref: https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/sync/pool.go;l=100-103
for i := 0; i < 1_000; i++ {
p.Put(&pooledValue[string]{
value: t.Name(),
})
}
// Ensure that we always get the expected value. Note that this must only
// run a fraction of the number of times that Put is called above.
for i := 0; i < 10; i++ {
func() {
x := p.Get()
defer p.Put(x)
require.Equal(t, t.Name(), x.value)
}()
}
// Depool all objects that might be in the pool to ensure that it's empty.
for i := 0; i < 1_000; i++ {
p.Get()
}
// Now that the pool is empty, it should use the value specified in the
// underlying sync.Pool.New func.
require.Equal(t, "new", p.Get().value)
}
func TestNew_Race(t *testing.T) {
p := New(func() *pooledValue[int] {
return &pooledValue[int]{
value: -1,
}
})
var wg sync.WaitGroup
defer wg.Wait()
// Run a number of goroutines that read and write pool object fields to
// tease out races.
for i := 0; i < 1_000; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
x := p.Get()
defer p.Put(x)
// Must both read and write the field.
if n := x.value; n >= -1 {
x.value = i
}
}()
}
}

View File

@@ -0,0 +1,11 @@
package version
import (
"runtime/debug"
)
// Info returns project dependencies as []*debug.Module.
func Info() []*debug.Module {
bi, _ := debug.ReadBuildInfo()
return bi.Deps
}

Some files were not shown because too many files have changed in this diff Show More