Compare commits

..
140 Commits
Author SHA1 Message Date
xjasonlyu a9f56af25f refactor proxy/internal
update proxies

refactor

fix sth

up
2024-08-28 19:01:18 -04:00
xjasonlyu 8ef439b8d8 Refactor(proxy): make proxies registerable 2024-08-28 19:01:01 -04:00
Jason LyuandGitHub 601601a1dc Refactor(log): use go.uber.org/zap (#389) 2024-08-29 06:56:35 +08:00
Jason LyuandGitHub c8c08cfeea Chore: adjust markdown layout (#388) 2024-08-21 05:27:56 +08:00
Jason LyuandGitHub 776e6470d3 Feature(actions): add testing (#378) 2024-08-21 05:27:20 +08:00
Jason LyuandGitHub 66fafd224e Chore: update README (#387)
* remove Chinese README
2024-08-21 02:14:22 +08:00
Jason LyuandGitHub e083dafcf5 Chore(.gitignore): use github template (#381) 2024-07-22 08:53:29 +08:00
Jason LyuandGitHub 24b8cdd96b Refactor(debug): simplify version string (#380) 2024-07-22 08:34:24 +08:00
Jason LyuandGitHub dd791e50c1 Feature: bump gVisor to latest version (#376) 2024-07-20 11:31:44 +08:00
Jason LyuandGitHub 7555425ab8 Chore: update golangci-lint settings (#377)
* remove staticcheck config
2024-07-16 10:32:59 +08:00
Jason LyuandGitHub 592517a00d Chore(actions): update versions (#375) 2024-07-16 06:22:25 +08:00
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 LyuandGitHub 488e5b223c Chore(actions): update versions (#362) 2024-05-13 02:34:37 +08:00
Jason LyuandGitHub 0d819e1aec Fix: codeql autobuild error (#355) 2024-04-07 02:53:17 +08:00
Jason LyuandGitHub 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 AngelandGitHub 8c7c9085c2 Fix: socks5 usernames and passwords can BOTH be up to 255 bytes (#343) 2024-02-28 10:27:14 +08:00
Jason LyuandGitHub 8653c18875 Fix: safely split command strings (#340) 2024-02-20 05:41:58 +08:00
Jason LyuandGitHub 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 MooreandGitHub a49ce339b7 Fix: call SetSendBufferSize when setting send buffer size option (#336) 2024-02-07 10:18:53 +08:00
Jason LyuandGitHub 2334083cf9 Chore: update CodeQL (#332) 2024-01-28 07:21:33 +08:00
Jason LyuandGitHub 01d4ac4864 Chore: update go mods (#331) 2024-01-28 07:10:26 +08:00
Jason LyuandGitHub c8f8cb5caf Chore(release/go): use version from go.mod (#317) 2023-11-14 06:21:26 +08:00
Jason LyuandGitHub e5bfa13a3f Chore: update go mod (#316)
* bump gvisor to `v0.0.0-20231113203814-cdee0abd0280`
2023-11-14 06:00:15 +08:00
ctyandGitHub e86b3b7dc5 Perf(SOCKS5): optimize memory footprint with authentication (#315) 2023-11-13 06:28:42 +08:00
Jason LyuandGitHub 010765138c Chore: update README (#314)
* Remove deleted repositories
2023-11-03 23:09:12 +08:00
Jason LyuandGitHub f8bddb162e Improve(proxy/ss): allow explicit none cipher mode (#312) 2023-10-25 09:36:02 +08:00
Jason LyuandGitHub 2d80a4ba3b Chore(actions/docker): use github variables (#311) 2023-10-25 00:21:43 +08:00
Jason LyuandGitHub 00a5f18ebd Feature(proxy): support gost relay protocol (#310) 2023-10-24 10:55:33 +08:00
Jason LyuandGitHub 47913b549f Fix: defer func for safeConnClose (#309) 2023-10-24 09:13:36 +08:00
Jason LyuandGitHub 2283f82bbc Refactor: direct proxy parsing (#308) 2023-10-24 04:44:17 +08:00
Jason LyuandGitHub 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 LyuandGitHub 68da4d9997 Chore: update go mod (#303) 2023-09-29 04:56:49 +08:00
Jason LyuandGitHub 78086193fb Fix: bump versions for actions (#302) 2023-09-29 04:46:13 +08:00
Jason LyuandGitHub 631fa59182 Feature: persistent wintun with GUID option (#301)
Fixes: #300
2023-09-29 04:36:29 +08:00
Jason LyuandGitHub f448baa2ae Chore: use errors.ErrUnsupported (#294) 2023-09-05 01:51:03 +08:00
Jason LyuandGitHub b470006fb6 Chore: bump to go1.21 (#292) 2023-08-29 01:30:11 +08:00
Jason LyuandGitHub 73ea4358cb Docker: add options for multicast groups (#290) 2023-08-28 07:04:22 +08:00
Jason LyuandGitHub 19ba20ffc0 Improve(actions): add concurrency groups (#289) 2023-08-28 03:57:16 +08:00
Jason LyuandGitHub f1b7b4745b Chore: bump go mods (#288) 2023-08-28 03:48:38 +08:00
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 LyuandGitHub 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
yushengandGitHub 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 LyuandGitHub db7c3fd7d2 Chore: create CODE_OF_CONDUCT.md (#259) 2023-05-17 22:16:38 +08:00
Jason LyuandGitHub 4fc5c03e35 Chore: create SECURITY.md (#258) 2023-05-17 22:02:30 +08:00
Vladislav FursovandGitHub 3cbbf3068a Fix(socks5): panic with unassigned reply code (#255) 2023-05-17 10:06:56 +08:00
Jason LyuandGitHub 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 LyuandGitHub 3b54548914 CI: update golangci-lint config (#251) 2023-05-11 10:51:55 +08:00
SolynandGitHub 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
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
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
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
LanceLiandGitHub 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
105 changed files with 3127 additions and 1492 deletions
+128
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
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']
+1 -1
View File
@@ -1,4 +1,4 @@
blank_issues_enabled: true
blank_issues_enabled: false
contact_links:
- name: tun2socks GitHub Wiki
+29
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.
+15 -4
View File
@@ -1,8 +1,13 @@
name: "CodeQL"
concurrency:
group: codeql-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [ main ]
pull_request:
jobs:
analyze:
@@ -16,15 +21,21 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
check-latest: true
go-version-file: 'go.mod'
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3
+21 -15
View File
@@ -1,5 +1,9 @@
name: Publish Docker Image
concurrency:
group: docker-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
@@ -8,48 +12,46 @@ on:
- '*'
jobs:
build:
name: Build
docker:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Checkout code
uses: actions/checkout@v4
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 (dev)
if: github.ref == 'refs/heads/main'
uses: docker/build-push-action@v2
uses: docker/build-push-action@v6
with:
context: .
push: true
@@ -57,10 +59,12 @@ jobs:
tags: |
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
@@ -70,3 +74,5 @@ jobs:
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
+19 -11
View File
@@ -1,22 +1,30 @@
name: Linter
on: [ push, pull_request ]
concurrency:
group: linter-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'main'
pull_request:
jobs:
lint:
linter:
name: Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: ${{ steps.version.outputs.go_version }}
check-latest: true
go-version-file: 'go.mod'
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: latest
+16 -18
View File
@@ -1,48 +1,46 @@
name: Publish Go Releases
concurrency:
group: release-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
tags:
- '*'
jobs:
build:
name: Build
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get latest Go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: ${{ steps.version.outputs.go_version }}
check-latest: true
go-version-file: 'go.mod'
- name: Cache go module
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
path: |
~/go/pkg/mod
~/.cache/go-build
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run test
run: |
go test ./...
- 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/')
with:
files: build/*
+7 -2
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,9 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
- uses: actions/stale@v9
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'
days-before-stale: 60
days-before-close: 7
+31
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@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
check-latest: true
go-version-file: 'go.mod'
- name: Run test
run: |
go test ./...
+19 -1
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
+11 -6
View File
@@ -1,16 +1,21 @@
linters:
disable-all: true
enable:
- gofumpt
- staticcheck
- govet
- gci
- gofumpt
- gosimple
- govet
- ineffassign
- misspell
- staticcheck
- unconvert
- unused
- usestdlibvars
linters-settings:
gci:
custom-order: true
sections:
- standard
- prefix(github.com/xjasonlyu/tun2socks)
- default
staticcheck:
go: '1.18'
- prefix(github.com/xjasonlyu/tun2socks)
+2 -1
View File
@@ -12,7 +12,7 @@ LABEL org.opencontainers.image.source="https://github.com/xjasonlyu/tun2socks"
COPY docker/entrypoint.sh /entrypoint.sh
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
@@ -25,6 +25,7 @@ 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=
+5 -5
View File
@@ -41,7 +41,6 @@ UNIX_ARCH_LIST = \
linux-ppc64 \
linux-ppc64le \
linux-s390x \
openbsd-386 \
openbsd-amd64 \
openbsd-amd64-v3 \
openbsd-arm64
@@ -130,9 +129,6 @@ 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)-$@
openbsd-amd64:
GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
@@ -171,7 +167,11 @@ all-arch: $(UNIX_ARCH_LIST) $(WINDOWS_ARCH_LIST)
releases: $(unix_releases) $(windows_releases)
lint:
golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./...
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)
+14 -10
View File
@@ -8,28 +8,27 @@
[![Docker Pulls][6]](https://hub.docker.com/r/xjasonlyu/tun2socks)
[![Releases][7]](https://github.com/xjasonlyu/tun2socks/releases)
[1]: https://img.shields.io/github/workflow/status/xjasonlyu/tun2socks/Go?logo=github
[1]: https://img.shields.io/github/actions/workflow/status/xjasonlyu/tun2socks/release.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
English | [简体中文](README_ZH.md)
[7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?logo=smartthings
## Features
- Proxy Everything: Handle all network traffic of any internet programs sent by the device through a proxy.
- Proxy Protocols: HTTP/Socks4/Socks5/Shadowsocks with authentication support for remote connections.
- Run Everywhere: Linux/macOS/Windows/FreeBSD/OpenBSD multi-platform support with specific optimization.
- Gateway Mode: Act as a layer three gateway to handle network traffic from other devices in the same network.
- Gateway Mode: Act as a Layer 3 gateway to handle network traffic from other devices on the same network.
- Full IPv6 Support: All functions work in IPv6, tunnel IPv4 connections through IPv6 proxy and vice versa.
- Network Stack: Powered by user-space TCP/IP stack from Google container application kernel **[gVisor](https://github.com/google/gvisor)**.
- Network Stack: Powered by the user-space TCP/IP stack from Google container application kernel **[gVisor](https://github.com/google/gvisor)**.
## Benchmarks
For all scenarios of usage, tun2socks performs best. See [here](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks) for more details.
For all scenarios of usage, tun2socks performs best.
See [benchmarks](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks) for more details.
![benchmark](docs/benchmark.png)
@@ -47,7 +46,6 @@ Welcome and feel free to ask any questions at [Discussions](https://github.com/x
## 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
@@ -57,6 +55,12 @@ Welcome and feel free to ask any questions at [Discussions](https://github.com/x
[![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)
## 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>
-62
View File
@@ -1,62 +0,0 @@
![tun2socks](docs/wordmark.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)
[![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)
[1]: https://img.shields.io/github/workflow/status/xjasonlyu/tun2socks/Go?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
[English](README.md) | 简体中文
## 特性介绍
- 全局代理: 处理来自本设备的任意网络应用的所有网络流量并通过代理转发。
- 代理协议: 通过 HTTP/Socks4/Socks5/Shadowsocks 远程连接且支持鉴权。
- 跨平台性: 具有 Linux/macOS/Windows/FreeBSD/OpenBSD 特定优化的多平台支持。
- 网关模式: 作为第三层网关处理来自同一网络中其他设备的所有网络流量。
- IPv6 支持: 所有功能都可以在 IPv6 中工作,允许通过 IPv6 代理转发 IPv4 连接,反之亦然。
- TCP/IP 栈: 由来自 Google 容器应用程序内核 **[gVisor](https://github.com/google/gvisor)** 的用户空间 TCP/IP 网络栈强力驱动。
## 性能测试
对于任意的使用场景,tun2socks 表现最佳。更多细节看[这里](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks)。
![benchmark](docs/benchmark.png)
## 使用文档
- [源码安装](https://github.com/xjasonlyu/tun2socks/wiki/Install-from-Source)
- [使用例子](https://github.com/xjasonlyu/tun2socks/wiki/Examples)
- [内存优化](https://github.com/xjasonlyu/tun2socks/wiki/Memory-Optimization)
文档以及使用方式可以在 [Wiki](https://github.com/xjasonlyu/tun2socks/wiki) 里找到。
## 交流讨论
欢迎来讨论区 [Discussions](https://github.com/xjasonlyu/tun2socks/discussions) 交流提问。
## 特别感谢
- [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
## 许可协议
[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)
## 星星走向
[![Stargazers over time](https://starchart.cc/xjasonlyu/tun2socks.svg)](https://starchart.cc/xjasonlyu/tun2socks)
-3
View File
@@ -1,3 +0,0 @@
package observable
type Iterable <-chan any
-67
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
}
-148
View File
@@ -1,148 +0,0 @@
package observable
import (
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.uber.org/atomic"
)
func iterator(item []any) chan any {
ch := make(chan any)
go func() {
time.Sleep(100 * time.Millisecond)
for _, elm := range item {
ch <- elm
}
close(ch)
}()
return ch
}
func TestObservable(t *testing.T) {
iter := iterator([]any{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([]any{1, 2, 3, 4, 5})
src := NewObservable(iter)
ch1, _ := src.Subscribe()
ch2, _ := src.Subscribe()
count := atomic.NewInt32(0)
var wg sync.WaitGroup
wg.Add(2)
waitCh := func(ch <-chan any) {
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([]any{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([]any{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 any))
iter := iterator([]any{1})
src := NewObservable(iter)
src.UnSubscribe(sub)
}
func TestObservable_SubscribeGoroutineLeak(t *testing.T) {
iter := iterator([]any{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 any) {
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 any)
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()
}
-33
View File
@@ -1,33 +0,0 @@
package observable
import (
"sync"
)
type Subscription <-chan any
type Subscriber struct {
buffer chan any
once sync.Once
}
func (s *Subscriber) Emit(item any) {
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 any, 200),
}
return sub
}
+4 -5
View File
@@ -1,15 +1,14 @@
// Package pool provides a pool of []byte.
package pool
// Ref: github.com/Dreamacro/clash/common/pool
const (
// MaxSegmentSize is the largest possible UDP datagram size.
MaxSegmentSize = (1 << 16) - 1
// 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 is a buffer of 20 KiB to reduce the memory
// of each TCP relay as io.Copy default buffer size is 32 KiB,
// but the maximum packet size of vmess/shadowsocks is about
// 16 KiB, so define .
RelayBufferSize = 20 << 10
)
-31
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
-35
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{}
}
-3
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
-2
View File
@@ -1,5 +1,3 @@
package fdbased
const Driver = "fd"
const defaultMTU = 1500
+10 -7
View File
@@ -1,4 +1,4 @@
//go:build !windows
//go:build unix
package fdbased
@@ -6,12 +6,14 @@ import (
"fmt"
"strconv"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"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
@@ -19,7 +21,7 @@ type FD struct {
mtu uint32
}
func Open(name string, mtu uint32) (device.Device, error) {
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)
@@ -27,7 +29,7 @@ func Open(name string, mtu uint32) (device.Device, error) {
if mtu == 0 {
mtu = defaultMTU
}
return open(fd, mtu)
return open(fd, mtu, offset)
}
func (f *FD) Type() string {
@@ -38,8 +40,9 @@ func (f *FD) Name() string {
return strconv.Itoa(f.fd)
}
func (f *FD) Close() error {
return unix.Close(f.fd)
func (f *FD) Close() {
defer f.LinkEndpoint.Close()
_ = unix.Close(f.fd)
}
var _ device.Device = (*FD)(nil)
+2 -2
View File
@@ -6,6 +6,6 @@ import (
"github.com/xjasonlyu/tun2socks/v2/core/device"
)
func Open(name string, mtu uint32) (device.Device, error) {
return nil, errors.New("not supported")
func Open(name string, mtu uint32, offset int) (device.Device, error) {
return nil, errors.ErrUnsupported
}
+3 -3
View File
@@ -3,12 +3,12 @@ package fdbased
import (
"fmt"
"github.com/xjasonlyu/tun2socks/v2/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{
@@ -1,4 +1,4 @@
//go:build !linux && !windows
//go:build unix && !linux
package fdbased
@@ -10,10 +10,10 @@ import (
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
)
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 := iobased.New(os.NewFile(uintptr(fd), f.Name()), mtu, 0)
ep, err := iobased.New(os.NewFile(uintptr(fd), f.Name()), mtu, offset)
if err != nil {
return nil, fmt.Errorf("create endpoint: %w", err)
}
+7 -9
View File
@@ -8,8 +8,8 @@ import (
"io"
"sync"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@@ -110,7 +110,7 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
}
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: buffer.View(data[offset : offset+n]).ToVectorisedView(),
Payload: buffer.MakeWithData(data[offset : offset+n]),
})
switch header.IPVersion(data[offset:]) {
@@ -139,16 +139,14 @@ func (e *Endpoint) outboundLoop(ctx context.Context) {
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
defer pkt.DecRef()
size := pkt.Size()
views := pkt.Views()
buf := pkt.ToBuffer()
defer buf.Release()
if e.offset != 0 {
views = append([]buffer.View{
make(buffer.View, e.offset),
}, views...)
v := buffer.NewViewWithData(make([]byte, e.offset))
_ = buf.Prepend(v)
}
vView := buffer.NewVectorisedView(size, views)
if _, err := e.rw.Write(vView.ToView()); err != nil {
if _, err := e.rw.Write(buf.Flatten()); err != nil {
return &tcpip.ErrInvalidEndpointState{}
}
return nil
+11 -24
View File
@@ -4,15 +4,14 @@ package tun
import (
"fmt"
"unsafe"
"github.com/xjasonlyu/tun2socks/v2/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 {
@@ -77,11 +76,11 @@ 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)
}
// Ref: wireguard tun/tun_linux.go setMTU.
func setMTU(name string, n uint32) error {
// open datagram socket
fd, err := unix.Socket(
@@ -95,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)
}
+29 -8
View File
@@ -4,11 +4,12 @@ package tun
import (
"fmt"
"sync"
"golang.zx2c4.com/wireguard/tun"
"github.com/xjasonlyu/tun2socks/v2/core/device"
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
"golang.zx2c4.com/wireguard/tun"
)
type TUN struct {
@@ -18,6 +19,12 @@ type TUN struct {
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, err error) {
@@ -27,14 +34,21 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
}
}()
t := &TUN{name: name, mtu: mtu, offset: offset}
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)
}
@@ -56,11 +70,18 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
}
func (t *TUN) Read(packet []byte) (int, error) {
return t.nt.Read(packet, t.offset)
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) {
return t.nt.Write(packet, t.offset)
t.wMutex.Lock()
defer t.wMutex.Unlock()
t.wBuffs[0] = packet
return t.nt.Write(t.wBuffs, t.offset)
}
func (t *TUN) Name() string {
@@ -68,7 +89,7 @@ func (t *TUN) Name() string {
return name
}
func (t *TUN) Close() error {
func (t *TUN) Close() {
defer t.Endpoint.Close()
return t.nt.Close()
_ = t.nt.Close()
}
+9 -1
View File
@@ -1,8 +1,16 @@
//go:build !linux && !windows
//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)
}
+8
View File
@@ -1,6 +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)
}
+63 -2
View File
@@ -2,11 +2,14 @@ package core
import (
"fmt"
"github.com/xjasonlyu/tun2socks/v2/core/option"
"net"
"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 (
@@ -56,3 +59,61 @@ func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
return nil
}
}
// withMulticastGroups adds a NIC to the given multicast groups.
func withMulticastGroups(nicID tcpip.NICID, multicastGroups []net.IP) 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 {
if ip := multicastGroup.To4(); ip != nil {
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4Slice(ip)); err != nil {
return fmt.Errorf("join multicast group: %s", err)
}
} else {
ip := multicastGroup.To16()
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16Slice(ip)); err != nil {
return fmt.Errorf("join multicast group: %s", err)
}
}
}
return nil
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
package core
import (
"github.com/xjasonlyu/tun2socks/v2/core/option"
"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 withRouteTable(nicID tcpip.NICID) option.Option {
+13 -13
View File
@@ -1,16 +1,17 @@
package core
import (
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
"net"
"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"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
)
// Config is the configuration to create *stack.Stack.
@@ -23,9 +24,9 @@ type Config struct {
// stack to set transport handlers.
TransportHandler adapter.TransportHandler
// PrintFunc is the function that will be called
// to print internal stack events.
PrintFunc func(string, ...any)
// MulticastGroups is used by internal stack to add
// nic to given groups.
MulticastGroups []net.IP
// Options are supplement options to apply settings
// for the internal stack.
@@ -34,10 +35,6 @@ type Config struct {
// CreateStack creates *stack.Stack with given config.
func CreateStack(cfg *Config) (*stack.Stack, error) {
if cfg.PrintFunc == nil {
cfg.PrintFunc = func(string, ...any) {}
}
opts := []option.Option{option.WithDefault()}
if len(opts) > 0 {
opts = append(opts, cfg.Options...)
@@ -57,15 +54,15 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
})
// Generate unique NIC id.
nicID := tcpip.NICID(s.UniqueID())
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, cfg.PrintFunc),
withUDPHandler(cfg.TransportHandler.HandleUDP, cfg.PrintFunc),
withTCPHandler(cfg.TransportHandler.HandleTCP),
withUDPHandler(cfg.TransportHandler.HandleUDP),
// Create stack NIC and then bind link endpoint to it.
withCreatingNIC(nicID, cfg.LinkEndpoint),
@@ -96,6 +93,9 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
// 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 {
+7 -6
View File
@@ -3,15 +3,16 @@ package core
import (
"time"
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
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 (
@@ -40,7 +41,7 @@ const (
tcpKeepaliveInterval = 30 * time.Second
)
func withTCPHandler(handle func(adapter.TCPConn), printf func(string, ...any)) option.Option {
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 (
@@ -52,7 +53,7 @@ func withTCPHandler(handle func(adapter.TCPConn), printf func(string, ...any)) o
defer func() {
if err != nil {
printf("forward tcp request %s:%d->%s:%d: %s",
glog.Debugf("forward tcp request: %s:%d->%s:%d: %s",
id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
}
}()
@@ -100,7 +101,7 @@ func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
{ /* TCP recv/send buffer size */
var ss tcpip.TCPSendBufferSizeRangeOption
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil {
ep.SocketOptions().SetReceiveBufferSize(int64(ss.Default), false)
ep.SocketOptions().SetSendBufferSize(int64(ss.Default), false)
}
var rs tcpip.TCPReceiveBufferSizeRangeOption
+7 -6
View File
@@ -1,16 +1,17 @@
package core
import (
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
"github.com/xjasonlyu/tun2socks/v2/core/option"
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), printf func(string, ...any)) option.Option {
func withUDPHandler(handle func(adapter.UDPConn)) option.Option {
return func(s *stack.Stack) error {
udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
var (
@@ -19,13 +20,13 @@ func withUDPHandler(handle func(adapter.UDPConn), printf func(string, ...any)) o
)
ep, err := r.CreateEndpoint(&wq)
if err != nil {
printf("udp forwarder request %s:%d->%s:%d: %s",
glog.Debugf("forward udp request: %s:%d->%s:%d: %s",
id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
return
}
conn := &udpConn{
UDPConn: gonet.NewUDPConn(s, &wq, ep),
UDPConn: gonet.NewUDPConn(&wq, ep),
id: id,
}
handle(conn)
+33
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
}
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
}
+33
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
}
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
}
@@ -1,4 +1,4 @@
//go:build !linux && !darwin
//go:build !unix && !windows
package dialer
+68
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
}
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
}
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))
}
+1 -1
View File
@@ -3,7 +3,7 @@ package dns
import (
"net"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
"github.com/xjasonlyu/tun2socks/v2/dialer"
)
func init() {
+4
View File
@@ -81,6 +81,10 @@ run() {
ARGS="$ARGS --tcp-auto-tuning"
fi
if [ -n "$MULTICAST_GROUPS" ]; then
ARGS="$ARGS --multicast-groups $MULTICAST_GROUPS"
fi
exec tun2socks \
--loglevel "$LOGLEVEL" \
--fwmark "$FWMARK" \
+49 -16
View File
@@ -2,24 +2,25 @@ package engine
import (
"errors"
"fmt"
"net"
"os/exec"
"sync"
"time"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
"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/engine/mirror"
"github.com/xjasonlyu/tun2socks/v2/log"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/restapi"
"github.com/xjasonlyu/tun2socks/v2/tunnel"
"github.com/docker/go-units"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
var (
@@ -81,13 +82,25 @@ func start() error {
func stop() (err error) {
_engineMu.Lock()
if _defaultDevice != nil {
err = _defaultDevice.Close()
_defaultDevice.Close()
}
if _defaultStack != nil {
_defaultStack.Close()
_defaultStack.Wait()
}
_engineMu.Unlock()
return nil
}
func execCommand(cmd string) error {
parts, err := shlex.Split(cmd)
if err != nil {
return err
}
if len(parts) == 0 {
return errors.New("empty command")
}
_, err = exec.Command(parts[0], parts[1:]...).Output()
return err
}
@@ -96,7 +109,7 @@ func general(k *Key) error {
if err != nil {
return err
}
log.SetLevel(level)
log.SetLogger(log.Must(log.NewLeveled(level)))
if k.Interface != "" {
iface, err := net.InterfaceByName(k.Interface)
@@ -143,7 +156,7 @@ func restAPI(k *Key) error {
go func() {
if err := restapi.Start(host, token); err != nil {
log.Warnf("[RESTAPI] failed to start: %v", err)
log.Errorf("[RESTAPI] failed to start: %v", err)
}
}()
log.Infof("[RESTAPI] serve at: %s", u)
@@ -159,15 +172,37 @@ func netstack(k *Key) (err error) {
return errors.New("empty device")
}
if _defaultProxy, err = parseProxy(k.Proxy); err != nil {
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)
}
}
defer func() {
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)
}
}()
if _defaultProxy, err = proxy.ParseFromURL(k.Proxy); err != nil {
return
}
proxy.SetDialer(_defaultProxy)
proxy.DefaultProxy = _defaultProxy
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
return
}
var multicastGroups []net.IP
if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil {
return err
}
var opts []option.Option
if k.TCPModerateReceiveBuffer {
opts = append(opts, option.WithTCPModerateReceiveBuffer(true))
@@ -192,10 +227,8 @@ func netstack(k *Key) (err error) {
if _defaultStack, err = core.CreateStack(&core.Config{
LinkEndpoint: _defaultDevice,
TransportHandler: &mirror.Tunnel{},
PrintFunc: func(format string, v ...any) {
log.Warnf("[STACK] %s", fmt.Sprintf(format, v...))
},
Options: opts,
MulticastGroups: multicastGroups,
Options: opts,
}); err != nil {
return
}
@@ -203,7 +236,7 @@ func netstack(k *Key) (err error) {
log.Infof(
"[STACK] %s://%s <-> %s://%s",
_defaultDevice.Type(), _defaultDevice.Name(),
_defaultProxy.Proto(), _defaultProxy.Addr(),
_defaultProxy.Protocol(), _defaultProxy.Address(),
)
return nil
}
+3
View File
@@ -13,5 +13,8 @@ type Key struct {
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"`
}
+15 -84
View File
@@ -1,7 +1,6 @@
package engine
import (
"encoding/base64"
"fmt"
"net"
"net/url"
@@ -10,8 +9,6 @@ import (
"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) {
@@ -51,102 +48,36 @@ func parseDevice(s string, mtu uint32) (device.Device, error) {
return nil, err
}
name := u.Host
driver := strings.ToLower(u.Scheme)
switch driver {
case fdbased.Driver:
return fdbased.Open(name, mtu)
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 parseProxy(s string) (proxy.Proxy, error) {
if !strings.Contains(s, "://") {
s = fmt.Sprintf("%s://%s", proto.Socks5 /* default protocol */, s)
}
u, err := url.Parse(s)
if err != nil {
return nil, err
}
protocol := strings.ToLower(u.Scheme)
switch protocol {
case proto.Direct.String():
return proxy.NewDirect(), nil
case proto.Reject.String():
return proxy.NewReject(), nil
case proto.HTTP.String():
return proxy.NewHTTP(parseHTTP(u))
case proto.Socks4.String():
return proxy.NewSocks4(parseSocks4(u))
case proto.Socks5.String():
return proxy.NewSocks5(parseSocks5(u))
case proto.Shadowsocks.String():
return proxy.NewShadowsocks(parseShadowsocks(u))
default:
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
}
func parseFD(u *url.URL, mtu uint32) (device.Device, error) {
return fdbased.Open(u.Host, mtu, 0)
}
func parseHTTP(u *url.URL) (address, username, password string) {
address, username = u.Host, u.User.Username()
password, _ = u.User.Password()
return
}
func parseSocks4(u *url.URL) (address, username string) {
address, username = u.Host, u.User.Username()
return
}
func parseSocks5(u *url.URL) (address, username, password string) {
address, username = u.Host, u.User.Username()
password, _ = u.User.Password()
// Socks5 over UDS
if address == "" {
address = u.Path
}
return
}
func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost string) {
address = u.Host
if pass, set := u.User.Password(); set {
method = u.User.Username()
password = pass
} else {
data, _ := base64.RawURLEncoding.DecodeString(u.User.String())
userInfo := strings.SplitN(string(data), ":", 2)
if len(userInfo) == 2 {
method = userInfo[0]
password = userInfo[1]
}
}
rawQuery, _ := url.QueryUnescape(u.RawQuery)
for _, s := range strings.Split(rawQuery, ";") {
data := strings.SplitN(s, "=", 2)
if len(data) != 2 {
func parseMulticastGroups(s string) (multicastGroups []net.IP, _ error) {
ipStrings := strings.Split(s, ",")
for _, ipString := range ipStrings {
if strings.TrimSpace(ipString) == "" {
continue
}
key := data[0]
value := data[1]
switch key {
case "obfs":
obfsMode = value
case "obfs-host":
obfsHost = value
ip := net.ParseIP(ipString)
if ip == nil {
return nil, fmt.Errorf("invalid IP format: %s", ipString)
}
if !ip.IsMulticast() {
return nil, fmt.Errorf("invalid multicast IP address: %s", ipString)
}
multicastGroups = append(multicastGroups, ip)
}
return
}
+14
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
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)
}
+11
View File
@@ -0,0 +1,11 @@
package engine
import (
_ "github.com/xjasonlyu/tun2socks/v2/proxy/direct"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/http"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/reject"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/relay"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/shadowsocks"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/socks4"
_ "github.com/xjasonlyu/tun2socks/v2/proxy/socks5"
)
+27 -22
View File
@@ -1,32 +1,37 @@
module github.com/xjasonlyu/tun2socks/v2
go 1.18
go 1.22.0
toolchain go1.22.5
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7
github.com/docker/go-units v0.4.0
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/cors v1.2.0
github.com/go-chi/render v1.0.1
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.1
go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.5.1
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gvisor.dev/gvisor v0.0.0-20220405222207-795f4f0139bb
github.com/docker/go-units v0.5.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1
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/automaxprocs v1.5.3
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.25.0
golang.org/x/sys v0.22.0
golang.org/x/time v0.5.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d
)
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/kr/text v0.2.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
)
+53 -53
View File
@@ -1,63 +1,63 @@
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
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/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/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.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/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.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/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/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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
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.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/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=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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-20220405210540-1e041c57c461 h1:kHVeDEnfKn3T238CvrUcz6KeEsFHVaKh4kMTt6Wsysg=
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/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-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 h1:kgBK1EGuTIYbwoKROmsoV0FQp08gnCcVa110A4Unqhk=
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
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/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
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-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220405222207-795f4f0139bb h1:AFXQ8bzBy3bvv18rPD8bjDGzylB0PZrMJWQKuXxvR7Y=
gvisor.dev/gvisor v0.0.0-20220405222207-795f4f0139bb/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
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-20240713103206-39d6c232e61d h1:dFTIljP/5ReqgM7nMR4DauApFatUaSP8r9btX0sd8a8=
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
-7
View File
@@ -1,7 +0,0 @@
//go:build debug
package version
func init() {
_debug = true
}
+1 -14
View File
@@ -9,8 +9,6 @@ import (
const Name = "tun2socks"
var (
_debug = false
// Version can be set at link time by executing
// the command: `git describe --abbrev=0 --tags HEAD`
Version string
@@ -20,19 +18,8 @@ var (
GitCommit string
)
func versionize(s string) string {
return strings.TrimPrefix(s, "v")
}
func Debug() bool {
return _debug
}
func String() string {
if !Debug() {
return fmt.Sprintf("%s-%s", Name, versionize(Version))
}
return fmt.Sprintf("%s-%s (debug)", Name, versionize(Version))
return fmt.Sprintf("%s-%s", Name, strings.TrimPrefix(Version, "v"))
}
func BuildString() string {
+2
View File
@@ -0,0 +1,2 @@
// Package log is a thin wrapper based on "go.uber.org/zap".
package log
+37
View File
@@ -0,0 +1,37 @@
package log
import (
"runtime"
"strings"
"time"
glog "gvisor.dev/gvisor/pkg/log"
)
var _globalE = &emitter{}
func init() {
glog.SetTarget(_globalE)
}
type emitter struct {
logger *SugaredLogger
}
func (e *emitter) setLogger(logger *SugaredLogger) {
e.logger = logger.WithOptions(pkgCallerSkip)
}
func (e *emitter) logf(level glog.Level, format string, args ...any) {
e.logger.Logf(1-Level(level), "[STACK] "+format, args...)
}
func (e *emitter) Emit(depth int, level glog.Level, _ time.Time, format string, args ...any) {
if _, file, line, ok := runtime.Caller(depth + 1); ok {
// Ignore: gvisor.dev/gvisor/pkg/tcpip/adapters/gonet/gonet.go:457
if line == 457 && strings.HasSuffix(file, "gonet/gonet.go") {
return
}
}
e.logf(level, format, args...)
}
-39
View File
@@ -1,39 +0,0 @@
package log
import (
"fmt"
"time"
"github.com/xjasonlyu/tun2socks/v2/common/observable"
)
var (
_logCh = make(chan any)
_source = observable.NewObservable(_logCh)
)
type Event struct {
Level Level `json:"level"`
Message string `json:"msg"`
Time time.Time `json:"time"`
}
func newEvent(level Level, format string, args ...any) *Event {
event := &Event{
Level: level,
Time: time.Now(),
Message: fmt.Sprintf(format, args...),
}
_logCh <- event /* send all events to logCh */
return event
}
func Subscribe() observable.Subscription {
sub, _ := _source.Subscribe()
return sub
}
func UnSubscribe(sub observable.Subscription) {
_source.UnSubscribe(sub)
}
+18 -59
View File
@@ -1,72 +1,31 @@
package log
import (
"encoding/json"
"fmt"
"strings"
"go.uber.org/zap/zapcore"
)
type Level uint32
// Level is an alias for zapcore.Level.
type Level = zapcore.Level
// Levels are aliases for Level.
const (
SilentLevel Level = iota
ErrorLevel
WarnLevel
InfoLevel
DebugLevel
DebugLevel = zapcore.DebugLevel
InfoLevel = zapcore.InfoLevel
WarnLevel = zapcore.WarnLevel
ErrorLevel = zapcore.ErrorLevel
DPanicLevel = zapcore.DPanicLevel
PanicLevel = zapcore.PanicLevel
FatalLevel = zapcore.FatalLevel
InvalidLevel = zapcore.InvalidLevel
SilentLevel = InvalidLevel + 1
)
// UnmarshalJSON deserialize Level with json
func (level *Level) UnmarshalJSON(data []byte) error {
var lvl string
if err := json.Unmarshal(data, &lvl); err != nil {
return err
}
l, err := ParseLevel(lvl)
if err != nil {
return err
}
*level = l
return nil
}
// MarshalJSON serialize Level with json
func (level Level) MarshalJSON() ([]byte, error) {
return json.Marshal(level.String())
}
func (level Level) String() string {
switch level {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case SilentLevel:
return "silent"
default:
return fmt.Sprintf("not a valid level %d", level)
}
}
func ParseLevel(lvl string) (Level, error) {
switch strings.ToLower(lvl) {
case "silent":
// ParseLevel is a thin wrapper for zapcore.ParseLevel.
func ParseLevel(text string) (Level, error) {
switch text {
case "silent", "SILENT":
return SilentLevel, nil
case "error":
return ErrorLevel, nil
case "warning":
return WarnLevel, nil
case "info":
return InfoLevel, nil
case "debug":
return DebugLevel, nil
default:
return Level(0), fmt.Errorf("not a valid logrus Level: %q", lvl)
return zapcore.ParseLevel(text)
}
}
+58 -45
View File
@@ -1,58 +1,71 @@
package log
import (
"os"
"fmt"
"sync"
"github.com/sirupsen/logrus"
"go.uber.org/atomic"
"go.uber.org/zap"
)
// _defaultLevel is package default logging level.
var _defaultLevel = atomic.NewUint32(uint32(InfoLevel))
// global Logger and SugaredLogger.
var (
_globalMu sync.RWMutex
_globalL *Logger
_globalS *SugaredLogger
)
func init() {
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.DebugLevel)
SetLogger(zap.Must(zap.NewProduction()))
}
func SetLevel(level Level) {
_defaultLevel.Store(uint32(level))
}
func Debugf(format string, args ...any) {
logf(DebugLevel, format, args...)
}
func Infof(format string, args ...any) {
logf(InfoLevel, format, args...)
}
func Warnf(format string, args ...any) {
logf(WarnLevel, format, args...)
}
func Errorf(format string, args ...any) {
logf(ErrorLevel, format, args...)
}
func Fatalf(format string, args ...any) {
logrus.Fatalf(format, args...)
}
func logf(level Level, format string, args ...any) {
event := newEvent(level, format, args...)
if uint32(event.Level) > _defaultLevel.Load() {
return
}
switch level {
func NewLeveled(l Level, options ...Option) (*Logger, error) {
switch l {
case SilentLevel:
return zap.NewNop(), nil
case DebugLevel:
logrus.WithTime(event.Time).Debugln(event.Message)
case InfoLevel:
logrus.WithTime(event.Time).Infoln(event.Message)
case WarnLevel:
logrus.WithTime(event.Time).Warnln(event.Message)
case ErrorLevel:
logrus.WithTime(event.Time).Errorln(event.Message)
return zap.NewDevelopment(options...)
case InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel:
cfg := zap.NewProductionConfig()
cfg.Level.SetLevel(l)
return cfg.Build(options...)
default:
return nil, fmt.Errorf("invalid level: %s", l)
}
}
// SetLogger sets the global Logger and SugaredLogger.
func SetLogger(logger *Logger) {
_globalMu.Lock()
defer _globalMu.Unlock()
// apply pkgCallerSkip to global loggers.
_globalL = logger.WithOptions(pkgCallerSkip)
_globalS = _globalL.Sugar()
_globalE.setLogger(_globalS)
}
func logf(lvl Level, template string, args ...any) {
_globalMu.RLock()
s := _globalS
_globalMu.RUnlock()
s.Logf(lvl, template, args...)
}
func Debugf(template string, args ...any) {
logf(DebugLevel, template, args...)
}
func Infof(template string, args ...any) {
logf(InfoLevel, template, args...)
}
func Warnf(template string, args ...any) {
logf(WarnLevel, template, args...)
}
func Errorf(template string, args ...any) {
logf(ErrorLevel, template, args...)
}
func Fatalf(template string, args ...any) {
logf(FatalLevel, template, args...)
}
+22
View File
@@ -0,0 +1,22 @@
package log
import (
"go.uber.org/zap"
)
// Must is an alias for zap.Must.
var Must = zap.Must
// logger aliases for zap.Logger and zap.SugaredLogger.
type (
Logger = zap.Logger
SugaredLogger = zap.SugaredLogger
)
type (
// Option is an alias for zap.Option.
Option = zap.Option
)
// pkgCallerSkip skips the pkg wrapper code as the caller.
var pkgCallerSkip = zap.AddCallerSkip(2)
+7 -4
View File
@@ -7,13 +7,13 @@ import (
"os/signal"
"syscall"
"go.uber.org/automaxprocs/maxprocs"
"gopkg.in/yaml.v3"
_ "github.com/xjasonlyu/tun2socks/v2/dns"
"github.com/xjasonlyu/tun2socks/v2/engine"
"github.com/xjasonlyu/tun2socks/v2/internal/version"
"github.com/xjasonlyu/tun2socks/v2/log"
"go.uber.org/automaxprocs/maxprocs"
"gopkg.in/yaml.v3"
)
var (
@@ -30,12 +30,15 @@ func init() {
flag.StringVar(&configFile, "config", "", "YAML format configuration file")
flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name")
flag.StringVar(&key.Interface, "interface", "", "Use network INTERFACE (Linux/MacOS only)")
flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warning|error|silent]")
flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warn|error|silent]")
flag.StringVar(&key.Proxy, "proxy", "", "Use this proxy [protocol://]host[:port]")
flag.StringVar(&key.RestAPI, "restapi", "", "HTTP statistic server listen address")
flag.StringVar(&key.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack")
flag.StringVar(&key.TCPReceiveBufferSize, "tcp-rcvbuf", "", "Set TCP receive buffer size for netstack")
flag.BoolVar(&key.TCPModerateReceiveBuffer, "tcp-auto-tuning", false, "Enable TCP receive buffer auto-tuning")
flag.StringVar(&key.MulticastGroups, "multicast-groups", "", "Set multicast groups, separated by commas")
flag.StringVar(&key.TUNPreUp, "tun-pre-up", "", "Execute a command before TUN device setup")
flag.StringVar(&key.TUNPostUp, "tun-post-up", "", "Execute a command after TUN device setup")
flag.BoolVar(&versionFlag, "version", false, "Show version and then quit")
flag.Parse()
}
-33
View File
@@ -1,33 +0,0 @@
package proxy
import (
"context"
"errors"
"net"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
var _ Proxy = (*Base)(nil)
type Base struct {
addr string
proto proto.Proto
}
func (b *Base) Addr() string {
return b.addr
}
func (b *Base) Proto() proto.Proto {
return b.proto
}
func (b *Base) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
return nil, errors.New("not supported")
}
func (b *Base) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, errors.New("not supported")
}
+18 -15
View File
@@ -1,34 +1,33 @@
package proxy
package direct
import (
"context"
"net"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ Proxy = (*Direct)(nil)
var _ proxy.Proxy = (*Direct)(nil)
type Direct struct {
*Base
}
const protocol = "direct"
func NewDirect() *Direct {
return &Direct{
Base: &Base{
proto: proto.Direct,
},
}
}
type Direct struct{ *base.Base }
func New() *Direct { return &Direct{base.New("", protocol)} }
func Parse(*url.URL) (proxy.Proxy, error) { return New(), nil }
func (d *Direct) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
c, err := dialer.DialContext(ctx, "tcp", metadata.DestinationAddress())
if err != nil {
return nil, err
}
setKeepAlive(c)
internal.SetKeepAlive(c)
return c, nil
}
@@ -55,3 +54,7 @@ func (pc *directPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
}
return pc.PacketConn.WriteTo(b, udpAddr)
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
-97
View File
@@ -1,97 +0,0 @@
package proxy
// Ref: https://github.com/Dreamacro/clash/adapter/outbound/http
import (
"bufio"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
type HTTP struct {
*Base
user string
pass string
}
func NewHTTP(addr, user, pass string) (*HTTP, error) {
return &HTTP{
Base: &Base{
addr: addr,
proto: proto.HTTP,
},
user: user,
pass: pass,
}, nil
}
func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", h.Addr())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", h.Addr(), err)
}
setKeepAlive(c)
defer safeConnClose(c, err)
err = h.shakeHand(metadata, c)
return
}
func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error {
addr := metadata.DestinationAddress()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
Host: addr,
},
Host: addr,
Header: http.Header{
"Proxy-Connection": []string{"Keep-Alive"},
},
}
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
req.Header.Add("Proxy-Authorization",
fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))))
}
if err := req.Write(rw); err != nil {
return err
}
resp, err := http.ReadResponse(bufio.NewReader(rw), req)
if err != nil {
return err
}
if resp.StatusCode == http.StatusOK {
return nil
}
if resp.StatusCode == http.StatusProxyAuthRequired {
return errors.New("HTTP need auth")
}
if resp.StatusCode == http.StatusMethodNotAllowed {
return errors.New("CONNECT method not allowed by proxy")
}
if resp.StatusCode >= http.StatusInternalServerError {
return errors.New(resp.Status)
}
return fmt.Errorf("HTTP connect status code: %d", resp.StatusCode)
}
+113
View File
@@ -0,0 +1,113 @@
package http
import (
"bufio"
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ proxy.Proxy = (*HTTP)(nil)
const protocol = "http"
type HTTP struct {
*base.Base
user string
pass string
}
func New(addr, user, pass string) (*HTTP, error) {
return &HTTP{
Base: base.New(addr, protocol),
user: user,
pass: pass,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, username := proxyURL.Host, proxyURL.User.Username()
password, _ := proxyURL.User.Password()
return New(address, username, password)
}
func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", h.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", h.Address(), err)
}
internal.SetKeepAlive(c)
defer func(c net.Conn) {
internal.SafeConnClose(c, err)
}(c)
err = h.shakeHand(metadata, c)
return
}
func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error {
addr := metadata.DestinationAddress()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
Host: addr,
},
Host: addr,
Header: http.Header{
"Proxy-Connection": []string{"Keep-Alive"},
},
}
if h.user != "" && h.pass != "" {
req.Header.Set("Proxy-Authorization", fmt.Sprintf("Basic %s", basicAuth(h.user, h.pass)))
}
if err := req.Write(rw); err != nil {
return err
}
resp, err := http.ReadResponse(bufio.NewReader(rw), req)
if err != nil {
return err
}
switch resp.StatusCode {
case http.StatusOK:
return nil
case http.StatusProxyAuthRequired:
return errors.New("HTTP auth required by proxy")
case http.StatusMethodNotAllowed:
return errors.New("CONNECT method not allowed by proxy")
default:
return fmt.Errorf("HTTP connect status: %s", resp.Status)
}
}
// The Basic authentication scheme is based on the model that the client
// needs to authenticate itself with a user-id and a password for each
// protection space ("realm"). The realm value is a free-form string
// that can only be compared for equality with other realms on that
// server. The server will service the request only if it can validate
// the user-id and password for the protection space applying to the
// requested resource.
func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+44
View File
@@ -0,0 +1,44 @@
package base
import (
"context"
"errors"
"fmt"
"net"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
)
var _ proxy.Proxy = (*Base)(nil)
type Base struct {
address, protocol string
}
func New(address, protocol string) *Base {
return &Base{
address: address,
protocol: protocol,
}
}
func (b *Base) Address() string {
return b.address
}
func (b *Base) Protocol() string {
return b.protocol
}
func (b *Base) String() string {
return fmt.Sprintf("%s://%s", b.protocol, b.address)
}
func (b *Base) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
return nil, errors.ErrUnsupported
}
func (b *Base) DialUDP(*M.Metadata) (net.PacketConn, error) {
return nil, errors.ErrUnsupported
}
+31
View File
@@ -0,0 +1,31 @@
package internal
import (
"net"
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
const tcpKeepAlivePeriod = 30 * time.Second
// SetKeepAlive sets the tcp keepalive option for the tcp connection.
func SetKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod)
}
}
// SafeConnClose closes the given tcp connection safely.
func SafeConnClose(c net.Conn, err error) {
if c != nil && err != nil {
c.Close()
}
}
// SerializeSocksAddr serializes *metadata.Metadata to socks5.Addr.
func SerializeSocksAddr(m *M.Metadata) socks5.Addr {
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
}
-33
View File
@@ -1,33 +0,0 @@
package proto
import "fmt"
const (
Direct Proto = iota
Reject
HTTP
Socks4
Socks5
Shadowsocks
)
type Proto uint8
func (proto Proto) String() string {
switch proto {
case Direct:
return "direct"
case Reject:
return "reject"
case HTTP:
return "http"
case Socks4:
return "socks4"
case Socks5:
return "socks5"
case Shadowsocks:
return "ss"
default:
return fmt.Sprintf("proto(%d)", proto)
}
}
+72
View File
@@ -0,0 +1,72 @@
package proxy
import (
"errors"
"fmt"
"net/url"
"sync"
"go.uber.org/atomic"
)
// ErrProtocol indicates that parsing encountered an unknown protocol.
var ErrProtocol = errors.New("proxy: unknown protocol")
// A protocol holds a proxy protocol's name and how to parse it.
type protocol struct {
name string
parse func(*url.URL) (Proxy, error)
}
// Protocols is the list of registered proxy protocols.
var (
protocolsMu sync.Mutex
atomicProtocols atomic.Value
)
// RegisterProtocol registers a proxy protocol for use by [Parse].
// Name is the name of the proxy protocol, like "http" or "socks5".
// [Parse] is the function that parses the proxy url.
func RegisterProtocol(name string, parse func(*url.URL) (Proxy, error)) {
protocolsMu.Lock()
formats, _ := atomicProtocols.Load().([]protocol)
atomicProtocols.Store(append(formats, protocol{name, parse}))
protocolsMu.Unlock()
}
// pick determines the protocol by the given name.
func pick(name string) protocol {
protocols, _ := atomicProtocols.Load().([]protocol)
for _, p := range protocols {
if p.name == name {
return p
}
}
return protocol{}
}
// Parse parses proxy *url.URL that holds the proxy info into Proxy.
// Protocol registration is typically done by an init function in the
// proxy-specific package.
func Parse(proxyURL *url.URL) (Proxy, error) {
if proxyURL == nil {
return nil, errors.New("proxy: nil url")
}
if proxyURL.Scheme == "" {
return nil, errors.New("proxy: protocol not specified")
}
p := pick(proxyURL.Scheme)
if p.parse == nil {
return nil, fmt.Errorf("%w: %s", ErrProtocol, proxyURL.Scheme)
}
return p.parse(proxyURL)
}
// ParseFromURL parses url string that holds the proxy info into Proxy.
func ParseFromURL(proxy string) (Proxy, error) {
proxyURL, err := url.Parse(proxy)
if err != nil {
return nil, err
}
return Parse(proxyURL)
}
+23 -22
View File
@@ -7,44 +7,45 @@ import (
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
)
const (
tcpConnectTimeout = 5 * time.Second
TCPConnectTimeout = 5 * time.Second
)
var _defaultDialer Dialer = &Base{}
// DefaultProxy is the default [Proxy] and is used by [Dial], [DialContext], and [DialUDP].
var DefaultProxy Proxy = nil
type Dialer interface {
type Proxy interface {
// Address returns the address of the proxy.
Address() string
// Protocol returns the protocol of the proxy.
Protocol() string
// String returns the string representation of the proxy.
String() string
// DialContext is used to dial TCP networks with context.
DialContext(context.Context, *M.Metadata) (net.Conn, error)
// DialUDP is used to to dial/listen UDP networks.
DialUDP(*M.Metadata) (net.PacketConn, error)
}
type Proxy interface {
Dialer
Addr() string
Proto() proto.Proto
}
// SetDialer sets default Dialer.
func SetDialer(d Dialer) {
_defaultDialer = d
}
// Dial uses default Dialer to dial TCP.
// Dial uses the DefaultProxy to dial TCP.
func Dial(metadata *M.Metadata) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
ctx, cancel := context.WithTimeout(context.Background(), TCPConnectTimeout)
defer cancel()
return _defaultDialer.DialContext(ctx, metadata)
return DialContext(ctx, metadata)
}
// DialContext uses default Dialer to dial TCP with context.
// DialContext uses the DefaultProxy to dial TCP with context.
func DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
return _defaultDialer.DialContext(ctx, metadata)
return DefaultProxy.DialContext(ctx, metadata)
}
// DialUDP uses default Dialer to dial UDP.
// DialUDP uses the DefaultProxy to dial UDP.
func DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
return _defaultDialer.DialUDP(metadata)
return DefaultProxy.DialUDP(metadata)
}
+17 -19
View File
@@ -1,36 +1,30 @@
package proxy
package reject
import (
"context"
"io"
"net"
"net/url"
"time"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ Proxy = (*Reject)(nil)
var _ proxy.Proxy = (*Reject)(nil)
type Reject struct {
*Base
}
const protocol = "reject"
func NewReject() *Reject {
return &Reject{
Base: &Base{
proto: proto.Reject,
},
}
}
type Reject struct{ *base.Base }
func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
return &nopConn{}, nil
}
func New() *Reject { return &Reject{base.New("", protocol)} }
func (r *Reject) DialUDP(*M.Metadata) (net.PacketConn, error) {
return &nopPacketConn{}, nil
}
func Parse(*url.URL) (proxy.Proxy, error) { return New(), nil }
func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) { return &nopConn{}, nil }
func (r *Reject) DialUDP(*M.Metadata) (net.PacketConn, error) { return &nopPacketConn{}, nil }
type nopConn struct{}
@@ -52,3 +46,7 @@ func (npc *nopPacketConn) LocalAddr() net.Addr { ret
func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+273
View File
@@ -0,0 +1,273 @@
package relay
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"net/url"
"sync"
"github.com/go-gost/relay"
"github.com/gorilla/schema"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
)
var _ proxy.Proxy = (*Relay)(nil)
const protocol = "relay"
type Relay struct {
*base.Base
user string
pass string
noDelay bool
}
func New(addr, user, pass string, noDelay bool) (*Relay, error) {
return &Relay{
Base: base.New(addr, protocol),
user: user,
pass: pass,
noDelay: noDelay,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, username := proxyURL.Host, proxyURL.User.Username()
password, _ := proxyURL.User.Password()
opts := struct {
NoDelay bool
}{}
if err := schema.NewDecoder().Decode(&opts, proxyURL.Query()); err != nil {
return nil, err
}
return New(address, username, password, opts.NoDelay)
}
func (rl *Relay) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
return rl.dialContext(ctx, metadata)
}
func (rl *Relay) DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), proxy.TCPConnectTimeout)
defer cancel()
return rl.dialContext(ctx, metadata)
}
func (rl *Relay) dialContext(ctx context.Context, metadata *M.Metadata) (rc *relayConn, err error) {
var c net.Conn
c, err = dialer.DialContext(ctx, "tcp", rl.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", rl.Address(), err)
}
internal.SetKeepAlive(c)
defer func(c net.Conn) {
internal.SafeConnClose(c, err)
}(c)
req := relay.Request{
Version: relay.Version1,
Cmd: relay.CmdConnect,
}
if metadata.Network == M.UDP {
req.Cmd |= relay.FUDP
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
}
if rl.user != "" {
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: rl.user,
Password: rl.pass,
})
}
req.Features = append(req.Features, serializeRelayAddr(metadata))
if rl.noDelay {
if _, err = req.WriteTo(c); err != nil {
return
}
if err = readRelayResponse(c); err != nil {
return
}
}
switch metadata.Network {
case M.TCP:
rc = newRelayConn(c, metadata.Addr(), rl.noDelay, false)
if !rl.noDelay {
if _, err = req.WriteTo(rc.wbuf); err != nil {
return
}
}
case M.UDP:
rc = newRelayConn(c, metadata.Addr(), rl.noDelay, true)
if !rl.noDelay {
if _, err = req.WriteTo(rc.wbuf); err != nil {
return
}
}
default:
err = fmt.Errorf("network %s is unsupported", metadata.Network)
return
}
return
}
type relayConn struct {
net.Conn
udp bool
addr net.Addr
once sync.Once
wbuf *bytes.Buffer
}
func newRelayConn(c net.Conn, addr net.Addr, noDelay, udp bool) *relayConn {
rc := &relayConn{
Conn: c,
addr: addr,
udp: udp,
}
if !noDelay {
rc.wbuf = &bytes.Buffer{}
}
return rc
}
func (rc *relayConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := rc.Read(b)
return n, rc.addr, err
}
func (rc *relayConn) Read(b []byte) (n int, err error) {
rc.once.Do(func() {
if rc.wbuf != nil {
err = readRelayResponse(rc.Conn)
}
})
if err != nil {
return
}
if !rc.udp {
return rc.Conn.Read(b)
}
var bb [2]byte
_, err = io.ReadFull(rc.Conn, bb[:])
if err != nil {
return
}
dLen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dLen {
return io.ReadFull(rc.Conn, b[:dLen])
}
buf := pool.Get(dLen)
defer pool.Put(buf)
_, err = io.ReadFull(rc.Conn, buf)
n = copy(b, buf)
return
}
func (rc *relayConn) WriteTo(b []byte, _ net.Addr) (int, error) {
return rc.Write(b)
}
func (rc *relayConn) Write(b []byte) (int, error) {
if rc.udp {
return rc.udpWrite(b)
}
return rc.tcpWrite(b)
}
func (rc *relayConn) tcpWrite(b []byte) (n int, err error) {
if rc.wbuf != nil && rc.wbuf.Len() > 0 {
n = len(b)
rc.wbuf.Write(b)
_, err = rc.Conn.Write(rc.wbuf.Bytes())
rc.wbuf.Reset()
return
}
return rc.Conn.Write(b)
}
func (rc *relayConn) udpWrite(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
n = len(b)
if rc.wbuf != nil && rc.wbuf.Len() > 0 {
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
rc.wbuf.Write(bb[:])
rc.wbuf.Write(b)
_, err = rc.wbuf.WriteTo(rc.Conn)
return
}
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
_, err = rc.Conn.Write(bb[:])
if err != nil {
return
}
return rc.Conn.Write(b)
}
func readRelayResponse(r io.Reader) error {
resp := relay.Response{}
if _, err := resp.ReadFrom(r); err != nil {
return err
}
if resp.Version != relay.Version1 {
return relay.ErrBadVersion
}
if resp.Status != relay.StatusOK {
return fmt.Errorf("status %d", resp.Status)
}
return nil
}
func serializeRelayAddr(m *M.Metadata) *relay.AddrFeature {
af := &relay.AddrFeature{
Host: m.DstIP.String(),
Port: m.DstPort,
}
if m.DstIP.To4() != nil {
af.AType = relay.AddrIPv4
} else {
af.AType = relay.AddrIPv6
}
return af
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
-121
View File
@@ -1,121 +0,0 @@
package proxy
import (
"context"
"errors"
"fmt"
"net"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
obfs "github.com/xjasonlyu/tun2socks/v2/component/simple-obfs"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
"github.com/Dreamacro/go-shadowsocks2/core"
)
var _ Proxy = (*Shadowsocks)(nil)
type Shadowsocks struct {
*Base
cipher core.Cipher
// simple-obfs plugin
obfsMode, obfsHost string
}
func NewShadowsocks(addr, method, password, obfsMode, obfsHost string) (*Shadowsocks, error) {
cipher, err := core.PickCipher(method, nil, password)
if err != nil {
return nil, fmt.Errorf("ss initialize: %w", err)
}
return &Shadowsocks{
Base: &Base{
addr: addr,
proto: proto.Shadowsocks,
},
cipher: cipher,
obfsMode: obfsMode,
obfsHost: obfsHost,
}, nil
}
func (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Addr())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
}
setKeepAlive(c)
defer safeConnClose(c, err)
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost)
case "http":
_, port, _ := net.SplitHostPort(ss.addr)
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializeSocksAddr(metadata))
return
}
func (ss *Shadowsocks) DialUDP(*M.Metadata) (net.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
return nil, fmt.Errorf("listen packet: %w", err)
}
udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr())
if err != nil {
return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err)
}
pc = ss.cipher.PacketConn(pc)
return &ssPacketConn{PacketConn: pc, rAddr: udpAddr}, nil
}
type ssPacketConn struct {
net.PacketConn
rAddr net.Addr
}
func (pc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
var packet []byte
if ma, ok := addr.(*M.Addr); ok {
packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
if err != nil {
return
}
return pc.PacketConn.WriteTo(packet[3:], pc.rAddr)
}
func (pc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, err := pc.PacketConn.ReadFrom(b)
if err != nil {
return 0, nil, err
}
addr := socks5.SplitAddr(b[:n])
if addr == nil {
return 0, nil, errors.New("parse addr error")
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse addr error")
}
copy(b, b[len(addr):])
return n - len(addr), udpAddr, err
}
+171
View File
@@ -0,0 +1,171 @@
package shadowsocks
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/url"
"strings"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
"github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/core"
obfs "github.com/xjasonlyu/tun2socks/v2/transport/simple-obfs"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
var _ proxy.Proxy = (*Shadowsocks)(nil)
const protocol = "ss"
type Shadowsocks struct {
*base.Base
cipher core.Cipher
// simple-obfs plugin
obfsMode, obfsHost string
}
func New(addr, method, password, obfsMode, obfsHost string) (*Shadowsocks, error) {
cipher, err := core.PickCipher(method, nil, password)
if err != nil {
return nil, fmt.Errorf("ss initialize: %w", err)
}
return &Shadowsocks{
Base: base.New(addr, protocol),
cipher: cipher,
obfsMode: obfsMode,
obfsHost: obfsHost,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
var (
address = proxyURL.Host
method, password string
obfsMode, obfsHost string
)
if ss := proxyURL.User.String(); ss == "" {
method = "dummy" // none cipher mode
} else if pass, set := proxyURL.User.Password(); set {
method = proxyURL.User.Username()
password = pass
} else {
data, _ := base64.RawURLEncoding.DecodeString(ss)
userInfo := strings.SplitN(string(data), ":", 2)
if len(userInfo) == 2 {
method = userInfo[0]
password = userInfo[1]
}
}
rawQuery, _ := url.QueryUnescape(proxyURL.RawQuery)
for _, s := range strings.Split(rawQuery, ";") {
data := strings.SplitN(s, "=", 2)
if len(data) != 2 {
continue
}
key := data[0]
value := data[1]
switch key {
case "obfs":
obfsMode = value
case "obfs-host":
obfsHost = value
}
}
return New(address, method, password, obfsMode, obfsHost)
}
func (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Address(), err)
}
internal.SetKeepAlive(c)
defer func(c net.Conn) {
internal.SafeConnClose(c, err)
}(c)
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsHost)
case "http":
_, port, _ := net.SplitHostPort(ss.Address())
c = obfs.NewHTTPObfs(c, ss.obfsHost, port)
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(internal.SerializeSocksAddr(metadata))
return
}
func (ss *Shadowsocks) DialUDP(*M.Metadata) (net.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
if err != nil {
return nil, fmt.Errorf("listen packet: %w", err)
}
udpAddr, err := net.ResolveUDPAddr("udp", ss.Address())
if err != nil {
return nil, fmt.Errorf("resolve udp address %s: %w", ss.Address(), err)
}
pc = ss.cipher.PacketConn(pc)
return &ssPacketConn{PacketConn: pc, rAddr: udpAddr}, nil
}
type ssPacketConn struct {
net.PacketConn
rAddr net.Addr
}
func (pc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
var packet []byte
if ma, ok := addr.(*M.Addr); ok {
packet, err = socks5.EncodeUDPPacket(internal.SerializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
if err != nil {
return
}
return pc.PacketConn.WriteTo(packet[3:], pc.rAddr)
}
func (pc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, err := pc.PacketConn.ReadFrom(b)
if err != nil {
return 0, nil, err
}
addr := socks5.SplitAddr(b[:n])
if addr == nil {
return 0, nil, errors.New("parse addr error")
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse addr error")
}
copy(b, b[len(addr):])
return n - len(addr), udpAddr, err
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
-43
View File
@@ -1,43 +0,0 @@
package proxy
import (
"context"
"fmt"
"net"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/transport/socks4"
)
var _ Proxy = (*Socks4)(nil)
type Socks4 struct {
*Base
userID string
}
func NewSocks4(addr, userID string) (*Socks4, error) {
return &Socks4{
Base: &Base{
addr: addr,
proto: proto.Socks4,
},
userID: userID,
}, nil
}
func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Addr())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
}
setKeepAlive(c)
defer safeConnClose(c, err)
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
return
}
+56
View File
@@ -0,0 +1,56 @@
package socks4
import (
"context"
"fmt"
"net"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
"github.com/xjasonlyu/tun2socks/v2/transport/socks4"
)
var _ proxy.Proxy = (*Socks4)(nil)
const protocol = "socks4"
type Socks4 struct {
*base.Base
userID string
}
func New(addr, userID string) (*Socks4, error) {
return &Socks4{
Base: base.New(addr, protocol),
userID: userID,
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, userID := proxyURL.Host, proxyURL.User.Username()
return New(address, userID)
}
func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
c, err = dialer.DialContext(ctx, "tcp", ss.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Address(), err)
}
internal.SetKeepAlive(c)
defer func(c net.Conn) {
internal.SafeConnClose(c, err)
}(c)
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
return
}
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+44 -25
View File
@@ -1,4 +1,4 @@
package proxy
package socks5
import (
"context"
@@ -6,17 +6,22 @@ import (
"fmt"
"io"
"net"
"net/url"
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
"github.com/xjasonlyu/tun2socks/v2/dialer"
M "github.com/xjasonlyu/tun2socks/v2/metadata"
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
"github.com/xjasonlyu/tun2socks/v2/proxy"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal"
"github.com/xjasonlyu/tun2socks/v2/proxy/internal/base"
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
)
var _ Proxy = (*Socks5)(nil)
var _ proxy.Proxy = (*Socks5)(nil)
const protocol = "socks5"
type Socks5 struct {
*Base
*base.Base
user string
pass string
@@ -25,31 +30,41 @@ type Socks5 struct {
unix bool
}
func NewSocks5(addr, user, pass string) (*Socks5, error) {
func New(addr, user, pass string) (*Socks5, error) {
return &Socks5{
Base: &Base{
addr: addr,
proto: proto.Socks5,
},
Base: base.New(addr, protocol),
user: user,
pass: pass,
unix: len(addr) > 0 && addr[0] == '/',
}, nil
}
func Parse(proxyURL *url.URL) (proxy.Proxy, error) {
address, username := proxyURL.Host, proxyURL.User.Username()
password, _ := proxyURL.User.Password()
// Socks5 over UDS
if address == "" {
address = proxyURL.Path
}
return New(address, username, password)
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
network := "tcp"
if ss.unix {
network = "unix"
}
c, err = dialer.DialContext(ctx, network, ss.Addr())
c, err = dialer.DialContext(ctx, network, ss.Address())
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("connect to %s: %w", ss.Address(), err)
}
setKeepAlive(c)
internal.SetKeepAlive(c)
defer safeConnClose(c, err)
defer func(c net.Conn) {
internal.SafeConnClose(c, err)
}(c)
var user *socks5.User
if ss.user != "" {
@@ -59,24 +74,24 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.
}
}
_, err = socks5.ClientHandshake(c, serializeSocksAddr(metadata), socks5.CmdConnect, user)
_, err = socks5.ClientHandshake(c, internal.SerializeSocksAddr(metadata), socks5.CmdConnect, user)
return
}
func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
if ss.unix {
return nil, errors.New("not supported when unix domain socket is enabled")
return nil, fmt.Errorf("%w when unix domain socket is enabled", errors.ErrUnsupported)
}
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
ctx, cancel := context.WithTimeout(context.Background(), proxy.TCPConnectTimeout)
defer cancel()
c, err := dialer.DialContext(ctx, "tcp", ss.Addr())
c, err := dialer.DialContext(ctx, "tcp", ss.Address())
if err != nil {
err = fmt.Errorf("connect to %s: %w", ss.Addr(), err)
err = fmt.Errorf("connect to %s: %w", ss.Address(), err)
return
}
setKeepAlive(c)
internal.SetKeepAlive(c)
defer func() {
if err != nil && c != nil {
@@ -121,10 +136,14 @@ func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
}()
bindAddr := addr.UDPAddr()
if bindAddr == nil {
return nil, fmt.Errorf("invalid UDP binding address: %#v", addr)
}
if bindAddr.IP.IsUnspecified() { /* e.g. "0.0.0.0" or "::" */
udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr())
udpAddr, err := net.ResolveUDPAddr("udp", ss.Address())
if err != nil {
return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err)
return nil, fmt.Errorf("resolve udp address %s: %w", ss.Address(), err)
}
bindAddr.IP = udpAddr.IP
}
@@ -142,7 +161,7 @@ type socksPacketConn struct {
func (pc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
var packet []byte
if ma, ok := addr.(*M.Addr); ok {
packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b)
packet, err = socks5.EncodeUDPPacket(internal.SerializeSocksAddr(ma.Metadata()), b)
} else {
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
}
@@ -179,6 +198,6 @@ func (pc *socksPacketConn) Close() error {
return pc.PacketConn.Close()
}
func serializeSocksAddr(m *M.Metadata) socks5.Addr {
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
func init() {
proxy.RegisterProtocol(protocol, Parse)
}
+45
View File
@@ -0,0 +1,45 @@
package proxy
import (
"errors"
"net/url"
"strings"
)
// URL is the universal representation of the proxy configuration.
type URL url.URL
func (u *URL) Protocol() string {
return u.Scheme
}
func (u *URL) Address() string {
return u.Host
}
func (u *URL) String() string {
return (&url.URL{
Scheme: u.Scheme,
Host: u.Host,
Path: strings.TrimRight(u.Path, "/"),
}).String()
}
func ParseURL(rawURL string) (*URL, error) {
proxyURL, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
if proxyURL.Scheme == "" {
return nil, errors.New("proxy: protocol not specified")
}
return (*URL)(proxyURL), nil
}
func MustParseURL(rawURL string) *URL {
u, err := ParseURL(rawURL)
if err != nil {
panic(err)
}
return u
}
+31
View File
@@ -0,0 +1,31 @@
package proxy
import (
"testing"
"github.com/stretchr/testify/suite"
)
type URLTestSuite struct {
suite.Suite
}
func (s *URLTestSuite) TestAddress() {
tests := []struct {
u *URL
expected string
}{
{
MustParseURL("http://example.com/"),
"http://example.com",
},
}
for _, tt := range tests {
s.Assert().Equal(tt.expected, tt.u.String())
}
}
func TestURLTestSuite(t *testing.T) {
suite.Run(t, new(URLTestSuite))
}
-25
View File
@@ -1,25 +0,0 @@
package proxy
import (
"net"
"time"
)
const (
tcpKeepAlivePeriod = 30 * time.Second
)
// setKeepAlive sets tcp keepalive option for tcp connection.
func setKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod)
}
}
// safeConnClose closes tcp connection safely.
func safeConnClose(c net.Conn, err error) {
if c != nil && err != nil {
c.Close()
}
}
+3 -3
View File
@@ -7,17 +7,17 @@ import (
"strconv"
"time"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/gorilla/websocket"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
const defaultInterval = 1000
func init() {
registerMountPoint("/connections", connectionRouter())
registerEndpoint("/connections", connectionRouter())
}
func connectionRouter() http.Handler {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
)
func init() {
registerMountPoint("/debug/pprof/", pprofRouter())
registerEndpoint("/debug/pprof/", pprofRouter())
}
func pprofRouter() http.Handler {
+1 -2
View File
@@ -19,7 +19,7 @@ func SetStatsFunc(s func() tcpip.Stats) {
}
func init() {
registerMountPoint("/netstats", http.HandlerFunc(getNetStats))
registerEndpoint("/netstats", http.HandlerFunc(getNetStats))
}
func getNetStats(w http.ResponseWriter, r *http.Request) {
@@ -40,7 +40,6 @@ func getNetStats(w http.ResponseWriter, r *http.Request) {
if !websocket.IsWebSocketUpgrade(r) {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
// write and flush.
w.Write(snapshot())
w.(http.Flusher).Flush()
return
+7 -67
View File
@@ -1,7 +1,5 @@
package restapi
// Ref: github.com/Dreamacro/clash/hub/route
import (
"bytes"
"encoding/json"
@@ -10,14 +8,13 @@ import (
"strings"
"time"
V "github.com/xjasonlyu/tun2socks/v2/internal/version"
"github.com/xjasonlyu/tun2socks/v2/log"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
"github.com/go-chi/render"
"github.com/gorilla/websocket"
V "github.com/xjasonlyu/tun2socks/v2/internal/version"
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
)
var (
@@ -27,11 +24,11 @@ var (
},
}
_mountPoints = make(map[string]http.Handler)
_endpoints = make(map[string]http.Handler)
)
func registerMountPoint(pattern string, handler http.Handler) {
_mountPoints[pattern] = handler
func registerEndpoint(pattern string, handler http.Handler) {
_endpoints[pattern] = handler
}
func Start(addr, token string) error {
@@ -48,11 +45,10 @@ func Start(addr, token string) error {
r.Group(func(r chi.Router) {
r.Use(authenticator(token))
r.Get("/", hello)
r.Get("/logs", getLogs)
r.Get("/traffic", traffic)
r.Get("/version", version)
// attach HTTP handlers
for pattern, handler := range _mountPoints {
for pattern, handler := range _endpoints {
r.Mount(pattern, handler)
}
})
@@ -105,61 +101,6 @@ func authenticator(token string) func(http.Handler) http.Handler {
}
}
func getLogs(w http.ResponseWriter, r *http.Request) {
lvl := r.URL.Query().Get("level")
if lvl == "" {
lvl = "info" /* default */
}
level, err := log.ParseLevel(lvl)
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest)
return
}
var wsConn *websocket.Conn
if websocket.IsWebSocketUpgrade(r) {
wsConn, err = _upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
}
if wsConn == nil {
w.Header().Set("Content-Type", "application/json")
render.Status(r, http.StatusOK)
}
sub := log.Subscribe()
defer log.UnSubscribe(sub)
buf := &bytes.Buffer{}
for elm := range sub {
buf.Reset()
e := elm.(*log.Event)
if e.Level > level {
continue
}
if err = json.NewEncoder(buf).Encode(e); err != nil {
break
}
if wsConn == nil {
_, err = w.Write(buf.Bytes())
w.(http.Flusher).Flush()
} else {
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
}
if err != nil {
break
}
}
}
func traffic(w http.ResponseWriter, r *http.Request) {
var (
err error
@@ -212,7 +153,6 @@ func version(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, render.M{
"version": V.Version,
"commit": V.GitCommit,
"debug": V.Debug(),
"modules": V.Info(),
})
}
+3
View File
@@ -0,0 +1,3 @@
## Embedded go-shadowsocks2
origin https://github.com/riobard/go-shadowsocks2
+164
View File
@@ -0,0 +1,164 @@
package core
import (
"crypto/md5"
"errors"
"net"
"sort"
"strings"
"github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/shadowaead"
"github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/shadowstream"
)
type Cipher interface {
StreamConnCipher
PacketConnCipher
}
type StreamConnCipher interface {
StreamConn(net.Conn) net.Conn
}
type PacketConnCipher interface {
PacketConn(net.PacketConn) net.PacketConn
}
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
var ErrCipherNotSupported = errors.New("cipher not supported")
const (
aeadAes128Gcm = "AEAD_AES_128_GCM"
aeadAes192Gcm = "AEAD_AES_192_GCM"
aeadAes256Gcm = "AEAD_AES_256_GCM"
aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305"
aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305"
)
// List of AEAD ciphers: key size in bytes and constructor
var aeadList = map[string]struct {
KeySize int
New func([]byte) (shadowaead.Cipher, error)
}{
aeadAes128Gcm: {16, shadowaead.AESGCM},
aeadAes192Gcm: {24, shadowaead.AESGCM},
aeadAes256Gcm: {32, shadowaead.AESGCM},
aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305},
aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305},
}
// List of stream ciphers: key size in bytes and constructor
var streamList = map[string]struct {
KeySize int
New func(key []byte) (shadowstream.Cipher, error)
}{
"RC4-MD5": {16, shadowstream.RC4MD5},
"AES-128-CTR": {16, shadowstream.AESCTR},
"AES-192-CTR": {24, shadowstream.AESCTR},
"AES-256-CTR": {32, shadowstream.AESCTR},
"AES-128-CFB": {16, shadowstream.AESCFB},
"AES-192-CFB": {24, shadowstream.AESCFB},
"AES-256-CFB": {32, shadowstream.AESCFB},
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
"XCHACHA20": {32, shadowstream.Xchacha20},
}
// ListCipher returns a list of available cipher names sorted alphabetically.
func ListCipher() []string {
var l []string
for k := range aeadList {
l = append(l, k)
}
for k := range streamList {
l = append(l, k)
}
sort.Strings(l)
return l
}
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
func PickCipher(name string, key []byte, password string) (Cipher, error) {
name = strings.ToUpper(name)
switch name {
case "DUMMY":
return &dummy{}, nil
case "CHACHA20-IETF-POLY1305":
name = aeadChacha20Poly1305
case "XCHACHA20-IETF-POLY1305":
name = aeadXChacha20Poly1305
case "AES-128-GCM":
name = aeadAes128Gcm
case "AES-192-GCM":
name = aeadAes192Gcm
case "AES-256-GCM":
name = aeadAes256Gcm
}
if choice, ok := aeadList[name]; ok {
if len(key) == 0 {
key = Kdf(password, choice.KeySize)
}
if len(key) != choice.KeySize {
return nil, shadowaead.KeySizeError(choice.KeySize)
}
aead, err := choice.New(key)
return &AeadCipher{Cipher: aead, Key: key}, err
}
if choice, ok := streamList[name]; ok {
if len(key) == 0 {
key = Kdf(password, choice.KeySize)
}
if len(key) != choice.KeySize {
return nil, shadowstream.KeySizeError(choice.KeySize)
}
ciph, err := choice.New(key)
return &StreamCipher{Cipher: ciph, Key: key}, err
}
return nil, ErrCipherNotSupported
}
type AeadCipher struct {
shadowaead.Cipher
Key []byte
}
func (aead *AeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
func (aead *AeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
return shadowaead.NewPacketConn(c, aead)
}
type StreamCipher struct {
shadowstream.Cipher
Key []byte
}
func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
func (ciph *StreamCipher) PacketConn(c net.PacketConn) net.PacketConn {
return shadowstream.NewPacketConn(c, ciph)
}
// dummy cipher does not encrypt
type dummy struct{}
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
// key-derivation function from original Shadowsocks
func Kdf(password string, keyLen int) []byte {
var b, prev []byte
h := md5.New()
for len(b) < keyLen {
h.Write(prev)
h.Write([]byte(password))
b = h.Sum(b)
prev = b[len(b)-h.Size():]
h.Reset()
}
return b[:keyLen]
}
+94
View File
@@ -0,0 +1,94 @@
package shadowaead
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"io"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
type Cipher interface {
KeySize() int
SaltSize() int
Encrypter(salt []byte) (cipher.AEAD, error)
Decrypter(salt []byte) (cipher.AEAD, error)
}
type KeySizeError int
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
func hkdfSHA1(secret, salt, info, outkey []byte) {
r := hkdf.New(sha1.New, secret, salt, info)
if _, err := io.ReadFull(r, outkey); err != nil {
panic(err) // should never happen
}
}
type metaCipher struct {
psk []byte
makeAEAD func(key []byte) (cipher.AEAD, error)
}
func (a *metaCipher) KeySize() int { return len(a.psk) }
func (a *metaCipher) SaltSize() int {
if ks := a.KeySize(); ks > 16 {
return ks
}
return 16
}
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
subkey := make([]byte, a.KeySize())
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
return a.makeAEAD(subkey)
}
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
subkey := make([]byte, a.KeySize())
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
return a.makeAEAD(subkey)
}
func aesGCM(key []byte) (cipher.AEAD, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(blk)
}
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
func AESGCM(psk []byte) (Cipher, error) {
switch l := len(psk); l {
case 16, 24, 32: // AES 128/196/256
default:
return nil, aes.KeySizeError(l)
}
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
}
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
// must be 32.
func Chacha20Poly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
}
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
// must be 32.
func XChacha20Poly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
}
+95
View File
@@ -0,0 +1,95 @@
package shadowaead
import (
"crypto/rand"
"errors"
"io"
"net"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
var ErrShortPacket = errors.New("short packet")
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
// Pack encrypts plaintext using Cipher with a randomly generated salt and
// returns a slice of dst containing the encrypted packet and any error occurred.
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
saltSize := ciph.SaltSize()
salt := dst[:saltSize]
if _, err := rand.Read(salt); err != nil {
return nil, err
}
aead, err := ciph.Encrypter(salt)
if err != nil {
return nil, err
}
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
return nil, io.ErrShortBuffer
}
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
return dst[:saltSize+len(b)], nil
}
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
saltSize := ciph.SaltSize()
if len(pkt) < saltSize {
return nil, ErrShortPacket
}
salt := pkt[:saltSize]
aead, err := ciph.Decrypter(salt)
if err != nil {
return nil, err
}
if len(pkt) < saltSize+aead.Overhead() {
return nil, ErrShortPacket
}
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
return nil, io.ErrShortBuffer
}
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
return b, err
}
type PacketConn struct {
net.PacketConn
Cipher
}
const maxPacketSize = 64 * 1024
// NewPacketConn wraps a net.PacketConn with cipher
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
return &PacketConn{PacketConn: c, Cipher: ciph}
}
// WriteTo encrypts b and write to addr using the embedded PacketConn.
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.Get(maxPacketSize)
defer pool.Put(buf)
buf, err := Pack(buf, b, c)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
// ReadFrom reads from the embedded PacketConn and decrypts into b.
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
if err != nil {
return n, addr, err
}
copy(b, bb)
return len(bb), addr, err
}
+285
View File
@@ -0,0 +1,285 @@
package shadowaead
import (
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"net"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
const (
// payloadSizeMask is the maximum size of payload in bytes.
payloadSizeMask = 0x3FFF // 16*1024 - 1
bufSize = 17 * 1024 // >= 2+aead.Overhead()+payloadSizeMask+aead.Overhead()
)
var ErrZeroChunk = errors.New("zero chunk")
type Writer struct {
io.Writer
cipher.AEAD
nonce [32]byte // should be sufficient for most nonce sizes
}
// NewWriter wraps an io.Writer with authenticated encryption.
func NewWriter(w io.Writer, aead cipher.AEAD) *Writer { return &Writer{Writer: w, AEAD: aead} }
// Write encrypts p and writes to the embedded io.Writer.
func (w *Writer) Write(p []byte) (n int, err error) {
buf := pool.Get(bufSize)
defer pool.Put(buf)
nonce := w.nonce[:w.NonceSize()]
tag := w.Overhead()
off := 2 + tag
// compatible with snell
if len(p) == 0 {
buf = buf[:off]
buf[0], buf[1] = byte(0), byte(0)
w.Seal(buf[:0], nonce, buf[:2], nil)
increment(nonce)
_, err = w.Writer.Write(buf)
return
}
for nr := 0; n < len(p) && err == nil; n += nr {
nr = payloadSizeMask
if n+nr > len(p) {
nr = len(p) - n
}
buf = buf[:off+nr+tag]
buf[0], buf[1] = byte(nr>>8), byte(nr) // big-endian payload size
w.Seal(buf[:0], nonce, buf[:2], nil)
increment(nonce)
w.Seal(buf[:off], nonce, p[n:n+nr], nil)
increment(nonce)
_, err = w.Writer.Write(buf)
}
return
}
// ReadFrom reads from the given io.Reader until EOF or error, encrypts and
// writes to the embedded io.Writer. Returns number of bytes read from r and
// any error encountered.
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
buf := pool.Get(bufSize)
defer pool.Put(buf)
nonce := w.nonce[:w.NonceSize()]
tag := w.Overhead()
off := 2 + tag
for {
nr, er := r.Read(buf[off : off+payloadSizeMask])
n += int64(nr)
buf[0], buf[1] = byte(nr>>8), byte(nr)
w.Seal(buf[:0], nonce, buf[:2], nil)
increment(nonce)
w.Seal(buf[:off], nonce, buf[off:off+nr], nil)
increment(nonce)
if _, ew := w.Writer.Write(buf[:off+nr+tag]); ew != nil {
err = ew
return
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
err = er
}
return
}
}
}
type Reader struct {
io.Reader
cipher.AEAD
nonce [32]byte // should be sufficient for most nonce sizes
buf []byte // to be put back into bufPool
off int // offset to unconsumed part of buf
}
// NewReader wraps an io.Reader with authenticated decryption.
func NewReader(r io.Reader, aead cipher.AEAD) *Reader { return &Reader{Reader: r, AEAD: aead} }
// Read and decrypt a record into p. len(p) >= max payload size + AEAD overhead.
func (r *Reader) read(p []byte) (int, error) {
nonce := r.nonce[:r.NonceSize()]
tag := r.Overhead()
// decrypt payload size
p = p[:2+tag]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
_, err := r.Open(p[:0], nonce, p, nil)
increment(nonce)
if err != nil {
return 0, err
}
// decrypt payload
size := (int(p[0])<<8 + int(p[1])) & payloadSizeMask
if size == 0 {
return 0, ErrZeroChunk
}
p = p[:size+tag]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
_, err = r.Open(p[:0], nonce, p, nil)
increment(nonce)
if err != nil {
return 0, err
}
return size, nil
}
// Read reads from the embedded io.Reader, decrypts and writes to p.
func (r *Reader) Read(p []byte) (int, error) {
if r.buf == nil {
if len(p) >= payloadSizeMask+r.Overhead() {
return r.read(p)
}
b := pool.Get(bufSize)
n, err := r.read(b)
if err != nil {
return 0, err
}
r.buf = b[:n]
r.off = 0
}
n := copy(p, r.buf[r.off:])
r.off += n
if r.off == len(r.buf) {
pool.Put(r.buf[:cap(r.buf)])
r.buf = nil
}
return n, nil
}
// WriteTo reads from the embedded io.Reader, decrypts and writes to w until
// there's no more data to write or when an error occurs. Return number of
// bytes written to w and any error encountered.
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
if r.buf == nil {
r.buf = pool.Get(bufSize)
r.off = len(r.buf)
}
for {
for r.off < len(r.buf) {
nw, ew := w.Write(r.buf[r.off:])
r.off += nw
n += int64(nw)
if ew != nil {
if r.off == len(r.buf) {
pool.Put(r.buf[:cap(r.buf)])
r.buf = nil
}
err = ew
return
}
}
nr, er := r.read(r.buf)
if er != nil {
if er != io.EOF {
err = er
}
return
}
r.buf = r.buf[:nr]
r.off = 0
}
}
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
func increment(b []byte) {
for i := range b {
b[i]++
if b[i] != 0 {
return
}
}
}
type Conn struct {
net.Conn
Cipher
r *Reader
w *Writer
}
// NewConn wraps a stream-oriented net.Conn with cipher.
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
func (c *Conn) initReader() error {
salt := make([]byte, c.SaltSize())
if _, err := io.ReadFull(c.Conn, salt); err != nil {
return err
}
aead, err := c.Decrypter(salt)
if err != nil {
return err
}
c.r = NewReader(c.Conn, aead)
return nil
}
func (c *Conn) Read(b []byte) (int, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.Read(b)
}
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.WriteTo(w)
}
func (c *Conn) initWriter() error {
salt := make([]byte, c.SaltSize())
if _, err := rand.Read(salt); err != nil {
return err
}
aead, err := c.Encrypter(salt)
if err != nil {
return err
}
_, err = c.Conn.Write(salt)
if err != nil {
return err
}
c.w = NewWriter(c.Conn, aead)
return nil
}
func (c *Conn) Write(b []byte) (int, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.Write(b)
}
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.ReadFrom(r)
}
+116
View File
@@ -0,0 +1,116 @@
package shadowstream
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rc4"
"strconv"
"golang.org/x/crypto/chacha20"
)
// Cipher generates a pair of stream ciphers for encryption and decryption.
type Cipher interface {
IVSize() int
Encrypter(iv []byte) cipher.Stream
Decrypter(iv []byte) cipher.Stream
}
type KeySizeError int
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
// CTR mode
type ctrStream struct{ cipher.Block }
func (b *ctrStream) IVSize() int { return b.BlockSize() }
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
func AESCTR(key []byte) (Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &ctrStream{blk}, nil
}
// CFB mode
type cfbStream struct{ cipher.Block }
func (b *cfbStream) IVSize() int { return b.BlockSize() }
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
func AESCFB(key []byte) (Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &cfbStream{blk}, nil
}
// IETF-variant of chacha20
type chacha20ietfkey []byte
func (k chacha20ietfkey) IVSize() int { return chacha20.NonceSize }
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Chacha20IETF(key []byte) (Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, KeySizeError(chacha20.KeySize)
}
return chacha20ietfkey(key), nil
}
type xchacha20key []byte
func (k xchacha20key) IVSize() int { return chacha20.NonceSizeX }
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewUnauthenticatedCipher(k, iv)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Xchacha20(key []byte) (Cipher, error) {
if len(key) != chacha20.KeySize {
return nil, KeySizeError(chacha20.KeySize)
}
return xchacha20key(key), nil
}
type rc4Md5Key []byte
func (k rc4Md5Key) IVSize() int {
return 16
}
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
h := md5.New()
h.Write([]byte(k))
h.Write(iv)
rc4key := h.Sum(nil)
c, _ := rc4.NewCipher(rc4key)
return c
}
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream {
return k.Encrypter(iv)
}
func RC4MD5(key []byte) (Cipher, error) {
return rc4Md5Key(key), nil
}
+79
View File
@@ -0,0 +1,79 @@
package shadowstream
import (
"crypto/rand"
"errors"
"io"
"net"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
var ErrShortPacket = errors.New("short packet")
// Pack encrypts plaintext using stream cipher s and a random IV.
// Returns a slice of dst containing random IV and ciphertext.
// Ensure len(dst) >= s.IVSize() + len(plaintext).
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
if len(dst) < s.IVSize()+len(plaintext) {
return nil, io.ErrShortBuffer
}
iv := dst[:s.IVSize()]
_, err := rand.Read(iv)
if err != nil {
return nil, err
}
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
return dst[:len(iv)+len(plaintext)], nil
}
// Unpack decrypts pkt using stream cipher s.
// Returns a slice of dst containing decrypted plaintext.
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
if len(pkt) < s.IVSize() {
return nil, ErrShortPacket
}
if len(dst) < len(pkt)-s.IVSize() {
return nil, io.ErrShortBuffer
}
iv := pkt[:s.IVSize()]
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
return dst[:len(pkt)-len(iv)], nil
}
type PacketConn struct {
net.PacketConn
Cipher
}
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn {
return &PacketConn{PacketConn: c, Cipher: ciph}
}
const maxPacketSize = 64 * 1024
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
buf := pool.Get(maxPacketSize)
defer pool.Put(buf)
buf, err := Pack(buf, b, c.Cipher)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
if err != nil {
return n, addr, err
}
copy(b, bb)
return len(bb), addr, err
}
+197
View File
@@ -0,0 +1,197 @@
package shadowstream
import (
"crypto/cipher"
"crypto/rand"
"io"
"net"
)
const bufSize = 2048
type Writer struct {
io.Writer
cipher.Stream
buf [bufSize]byte
}
// NewWriter wraps an io.Writer with stream cipher encryption.
func NewWriter(w io.Writer, s cipher.Stream) *Writer { return &Writer{Writer: w, Stream: s} }
func (w *Writer) Write(p []byte) (n int, err error) {
buf := w.buf[:]
for nw := 0; n < len(p) && err == nil; n += nw {
end := n + len(buf)
if end > len(p) {
end = len(p)
}
w.XORKeyStream(buf, p[n:end])
nw, err = w.Writer.Write(buf[:end-n])
}
return
}
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
buf := w.buf[:]
for {
nr, er := r.Read(buf)
n += int64(nr)
b := buf[:nr]
w.XORKeyStream(b, b)
if _, err = w.Writer.Write(b); err != nil {
return
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
err = er
}
return
}
}
}
type Reader struct {
io.Reader
cipher.Stream
buf [bufSize]byte
}
// NewReader wraps an io.Reader with stream cipher decryption.
func NewReader(r io.Reader, s cipher.Stream) *Reader { return &Reader{Reader: r, Stream: s} }
func (r *Reader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
if err != nil {
return 0, err
}
r.XORKeyStream(p, p[:n])
return
}
func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
buf := r.buf[:]
for {
nr, er := r.Reader.Read(buf)
if nr > 0 {
r.XORKeyStream(buf, buf[:nr])
nw, ew := w.Write(buf[:nr])
n += int64(nw)
if ew != nil {
err = ew
return
}
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut)
err = er
}
return
}
}
}
// A Conn represents a Shadowsocks connection. It implements the net.Conn interface.
type Conn struct {
net.Conn
Cipher
r *Reader
w *Writer
readIV []byte
writeIV []byte
}
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} }
func (c *Conn) initReader() error {
if c.r == nil {
iv, err := c.ObtainReadIV()
if err != nil {
return err
}
c.r = NewReader(c.Conn, c.Decrypter(iv))
}
return nil
}
func (c *Conn) Read(b []byte) (int, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.Read(b)
}
func (c *Conn) WriteTo(w io.Writer) (int64, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.WriteTo(w)
}
func (c *Conn) initWriter() error {
if c.w == nil {
iv, err := c.ObtainWriteIV()
if err != nil {
return err
}
if _, err := c.Conn.Write(iv); err != nil {
return err
}
c.w = NewWriter(c.Conn, c.Encrypter(iv))
}
return nil
}
func (c *Conn) Write(b []byte) (int, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.Write(b)
}
func (c *Conn) ReadFrom(r io.Reader) (int64, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.ReadFrom(r)
}
func (c *Conn) ObtainWriteIV() ([]byte, error) {
if len(c.writeIV) == c.IVSize() {
return c.writeIV, nil
}
iv := make([]byte, c.IVSize())
if _, err := rand.Read(iv); err != nil {
return nil, err
}
c.writeIV = iv
return iv, nil
}
func (c *Conn) ObtainReadIV() ([]byte, error) {
if len(c.readIV) == c.IVSize() {
return c.readIV, nil
}
iv := make([]byte, c.IVSize())
if _, err := io.ReadFull(c.Conn, iv); err != nil {
return nil, err
}
c.readIV = iv
return iv, nil
}
@@ -2,10 +2,11 @@ package obfs
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"math/rand"
mRand "math/rand"
"net"
"net/http"
@@ -64,8 +65,8 @@ func (ho *HTTPObfs) Write(b []byte) (int, error) {
if ho.firstRequest {
randBytes := make([]byte, 16)
rand.Read(randBytes)
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", mRand.Int()%54, mRand.Int()%2))
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Connection", "Upgrade")
req.Host = ho.host
@@ -1,4 +1,2 @@
// Package obfs provides obfuscation functionality for Shadowsocks protocol.
package obfs
// Ref: github.com/Dreamacro/clash/component/simple-obfs
@@ -2,19 +2,15 @@ package obfs
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"math/rand"
"net"
"time"
"github.com/xjasonlyu/tun2socks/v2/common/pool"
)
func init() {
rand.Seed(time.Now().Unix())
}
const (
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
)

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