mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2024-12-30 02:37:01 +00:00
Compare commits
152
Commits
v2.4.0
...
refactor-proxy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9f56af25f | ||
|
|
8ef439b8d8 | ||
|
|
601601a1dc | ||
|
|
c8c08cfeea | ||
|
|
776e6470d3 | ||
|
|
66fafd224e | ||
|
|
e083dafcf5 | ||
|
|
24b8cdd96b | ||
|
|
dd791e50c1 | ||
|
|
7555425ab8 | ||
|
|
592517a00d | ||
|
|
63f71e0b84 | ||
|
|
488e5b223c | ||
|
|
0d819e1aec | ||
|
|
60a63db500 | ||
|
|
8c7c9085c2 | ||
|
|
8653c18875 | ||
|
|
7b1d73d86e | ||
|
|
a49ce339b7 | ||
|
|
2334083cf9 | ||
|
|
01d4ac4864 | ||
|
|
c8f8cb5caf | ||
|
|
e5bfa13a3f | ||
|
|
e86b3b7dc5 | ||
|
|
010765138c | ||
|
|
f8bddb162e | ||
|
|
2d80a4ba3b | ||
|
|
00a5f18ebd | ||
|
|
47913b549f | ||
|
|
2283f82bbc | ||
|
|
cbf620b2f8 | ||
|
|
68da4d9997 | ||
|
|
78086193fb | ||
|
|
631fa59182 | ||
|
|
f448baa2ae | ||
|
|
b470006fb6 | ||
|
|
73ea4358cb | ||
|
|
19ba20ffc0 | ||
|
|
f1b7b4745b | ||
|
|
90f77548ed | ||
|
|
fffcbbea10 | ||
|
|
f588b4c731 | ||
|
|
71efe6c7cf | ||
|
|
2067a3129e | ||
|
|
8309fddef3 | ||
|
|
2b494a7517 | ||
|
|
7ab86fd9b0 | ||
|
|
4868427efb | ||
|
|
673a942fb3 | ||
|
|
44ad654d72 | ||
|
|
46c04db29f | ||
|
|
07a6a9f096 | ||
|
|
cd6b118e63 | ||
|
|
9838f57e0d | ||
|
|
9dfea44f48 | ||
|
|
860cf31256 | ||
|
|
8e20770bec | ||
|
|
3320ba46e4 | ||
|
|
38bfbb2c3f | ||
|
|
6c07927bba | ||
|
|
2813b4c581 | ||
|
|
db7c3fd7d2 | ||
|
|
4fc5c03e35 | ||
|
|
3cbbf3068a | ||
|
|
8da083b28e | ||
|
|
dc6eb815da | ||
|
|
3b54548914 | ||
|
|
d061f1c040 | ||
|
|
8e8ccdab89 | ||
|
|
9cbc99e8f5 | ||
|
|
2dbd2caaa9 | ||
|
|
2d0bd1d219 | ||
|
|
61a9d26815 | ||
|
|
4cc02c822c | ||
|
|
20499c6432 | ||
|
|
ad522ebb35 | ||
|
|
06d8bee2af | ||
|
|
b809f89411 | ||
|
|
b8ff1859c1 | ||
|
|
89c37dc156 | ||
|
|
1b8e063485 | ||
|
|
195290884c | ||
|
|
3b343600e7 | ||
|
|
2c51a65685 | ||
|
|
fb9ca95909 | ||
|
|
1e99f2d580 | ||
|
|
ce15b1b2c2 | ||
|
|
c036db2e23 | ||
|
|
007c97fe67 | ||
|
|
7327f2c784 | ||
|
|
66860d3de8 | ||
|
|
c61d7b5a20 | ||
|
|
680feede3b | ||
|
|
22b15f6fab | ||
|
|
f7b4f75ed4 | ||
|
|
29feac8cd4 | ||
|
|
6cfc25309e | ||
|
|
1f63b239c3 | ||
|
|
041bc510ea | ||
|
|
51d8a27289 | ||
|
|
5ffb0186bf | ||
|
|
8a2e5cebeb | ||
|
|
846d3d87a7 | ||
|
|
b491e17bfa | ||
|
|
fbe4c22347 | ||
|
|
4a8bf64cb1 | ||
|
|
059f661862 | ||
|
|
ab05092671 | ||
|
|
6809e7f835 | ||
|
|
bbdc9113d7 | ||
|
|
30608f4925 | ||
|
|
39b1406ffa | ||
|
|
a1edb1c1bb | ||
|
|
ad014648ef | ||
|
|
fa3317a94c | ||
|
|
8d3c28a516 | ||
|
|
35f6888c30 | ||
|
|
3cbc74b1cf | ||
|
|
24a53467f6 | ||
|
|
702ba81ccd | ||
|
|
7fce6e9544 | ||
|
|
553c2f7a17 | ||
|
|
ccfb3a47f6 | ||
|
|
0177157c69 | ||
|
|
82546cd2c5 | ||
|
|
9895b3e048 | ||
|
|
39b50f2bfb | ||
|
|
c45470650b | ||
|
|
cb6408a17a | ||
|
|
77bd119d34 | ||
|
|
595896dfc7 | ||
|
|
55a8d038d6 | ||
|
|
e31ffce0e6 | ||
|
|
267cc6d1a9 | ||
|
|
31468620e7 | ||
|
|
1536735456 | ||
|
|
b329f23a4e | ||
|
|
1ecd587857 | ||
|
|
6076fd9a69 | ||
|
|
3926f86613 | ||
|
|
77e8ad4810 | ||
|
|
a8e8a2dc4c | ||
|
|
f2cfa15945 | ||
|
|
764657d657 | ||
|
|
d65d3b08d0 | ||
|
|
c35f28b3a3 | ||
|
|
23a6e28768 | ||
|
|
102b46e9f6 | ||
|
|
ab728bd8cc | ||
|
|
4aea88c36e | ||
|
|
e4801c3989 | ||
|
|
fd8223e4d0 |
@@ -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.
|
||||||
@@ -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,4 +1,4 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: false
|
||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: tun2socks GitHub Wiki
|
- name: tun2socks GitHub Wiki
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
name: "CodeQL"
|
name: "CodeQL"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.event_name }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
@@ -16,15 +21,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- 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
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
name: Publish Docker Image
|
name: Publish Docker Image
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: docker-${{ github.event_name }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -8,48 +12,46 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
docker:
|
||||||
build:
|
name: Docker
|
||||||
name: Build
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
- name: Check out code into the Go module directory
|
uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
platforms: all
|
platforms: all
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v3
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: xjasonlyu
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.CR_PAT }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: shell
|
id: shell
|
||||||
run: |
|
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)
|
- name: Build and Push (dev)
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
@@ -57,10 +59,12 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
xjasonlyu/tun2socks:dev
|
xjasonlyu/tun2socks:dev
|
||||||
ghcr.io/xjasonlyu/tun2socks:dev
|
ghcr.io/xjasonlyu/tun2socks:dev
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Build and Push (latest)
|
- name: Build and Push (latest)
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
@@ -70,3 +74,5 @@ jobs:
|
|||||||
xjasonlyu/tun2socks:${{ steps.shell.outputs.version }}
|
xjasonlyu/tun2socks:${{ steps.shell.outputs.version }}
|
||||||
ghcr.io/xjasonlyu/tun2socks:latest
|
ghcr.io/xjasonlyu/tun2socks:latest
|
||||||
ghcr.io/xjasonlyu/tun2socks:${{ steps.shell.outputs.version }}
|
ghcr.io/xjasonlyu/tun2socks:${{ steps.shell.outputs.version }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|||||||
@@ -1,22 +1,30 @@
|
|||||||
name: Linter
|
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:
|
jobs:
|
||||||
lint:
|
linter:
|
||||||
|
name: Linter
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
- 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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
check-latest: true
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|||||||
@@ -1,48 +1,46 @@
|
|||||||
name: Publish Go Releases
|
name: Publish Go Releases
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release-${{ github.event_name }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
release:
|
||||||
name: Build
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
check-latest: true
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
~/.cache/go-build
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-go-
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Run test
|
|
||||||
run: |
|
|
||||||
go test ./...
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
run: make -j releases
|
run: make -j releases
|
||||||
|
|
||||||
- name: Upload Releases
|
- name: Upload Releases
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
files: build/*
|
files: build/*
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
name: Mark stale issues and pull requests
|
name: Mark stale issues and pull requests
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 10 * * *"
|
- cron: "0 10 * * *"
|
||||||
@@ -8,9 +13,9 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v3
|
- uses: actions/stale@v9
|
||||||
with:
|
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'
|
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-stale: 60
|
||||||
days-before-close: 7
|
days-before-close: 7
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
*.exe~
|
*.exe~
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.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/
|
build/
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
|
|||||||
+11
-6
@@ -1,16 +1,21 @@
|
|||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- gofumpt
|
|
||||||
- staticcheck
|
|
||||||
- govet
|
|
||||||
- gci
|
- gci
|
||||||
|
- gofumpt
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- staticcheck
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
- usestdlibvars
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gci:
|
gci:
|
||||||
|
custom-order: true
|
||||||
sections:
|
sections:
|
||||||
- standard
|
- standard
|
||||||
- prefix(github.com/xjasonlyu/tun2socks)
|
|
||||||
- default
|
- default
|
||||||
staticcheck:
|
- prefix(github.com/xjasonlyu/tun2socks)
|
||||||
go: '1.18'
|
|
||||||
|
|||||||
+2
-1
@@ -12,7 +12,7 @@ LABEL org.opencontainers.image.source="https://github.com/xjasonlyu/tun2socks"
|
|||||||
COPY docker/entrypoint.sh /entrypoint.sh
|
COPY docker/entrypoint.sh /entrypoint.sh
|
||||||
COPY --from=builder /src/build/tun2socks /usr/bin/tun2socks
|
COPY --from=builder /src/build/tun2socks /usr/bin/tun2socks
|
||||||
|
|
||||||
RUN apk add --update --no-cache iptables iproute2 \
|
RUN apk add --update --no-cache iptables iproute2 tzdata \
|
||||||
&& chmod +x /entrypoint.sh
|
&& chmod +x /entrypoint.sh
|
||||||
|
|
||||||
ENV TUN=tun0
|
ENV TUN=tun0
|
||||||
@@ -25,6 +25,7 @@ ENV UDP_TIMEOUT=
|
|||||||
ENV TCP_SNDBUF=
|
ENV TCP_SNDBUF=
|
||||||
ENV TCP_RCVBUF=
|
ENV TCP_RCVBUF=
|
||||||
ENV TCP_AUTO_TUNING=
|
ENV TCP_AUTO_TUNING=
|
||||||
|
ENV MULTICAST_GROUPS=
|
||||||
ENV EXTRA_COMMANDS=
|
ENV EXTRA_COMMANDS=
|
||||||
ENV TUN_INCLUDED_ROUTES=
|
ENV TUN_INCLUDED_ROUTES=
|
||||||
ENV TUN_EXCLUDED_ROUTES=
|
ENV TUN_EXCLUDED_ROUTES=
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ UNIX_ARCH_LIST = \
|
|||||||
linux-ppc64 \
|
linux-ppc64 \
|
||||||
linux-ppc64le \
|
linux-ppc64le \
|
||||||
linux-s390x \
|
linux-s390x \
|
||||||
openbsd-386 \
|
|
||||||
openbsd-amd64 \
|
openbsd-amd64 \
|
||||||
openbsd-amd64-v3 \
|
openbsd-amd64-v3 \
|
||||||
openbsd-arm64
|
openbsd-arm64
|
||||||
@@ -130,9 +129,6 @@ linux-ppc64le:
|
|||||||
linux-s390x:
|
linux-s390x:
|
||||||
GOARCH=s390x GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
GOARCH=s390x GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
||||||
|
|
||||||
openbsd-386:
|
|
||||||
GOARCH=386 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
|
||||||
|
|
||||||
openbsd-amd64:
|
openbsd-amd64:
|
||||||
GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
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)
|
releases: $(unix_releases) $(windows_releases)
|
||||||
|
|
||||||
lint:
|
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:
|
clean:
|
||||||
rm -rf $(BUILD_DIR)
|
rm -rf $(BUILD_DIR)
|
||||||
|
|||||||
@@ -8,28 +8,27 @@
|
|||||||
[![Docker Pulls][6]](https://hub.docker.com/r/xjasonlyu/tun2socks)
|
[![Docker Pulls][6]](https://hub.docker.com/r/xjasonlyu/tun2socks)
|
||||||
[![Releases][7]](https://github.com/xjasonlyu/tun2socks/releases)
|
[![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
|
[2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks?logo=go
|
||||||
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks
|
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks
|
||||||
[4]: https://api.codeclimate.com/v1/badges/b5b30239174fc6603aca/maintainability
|
[4]: https://api.codeclimate.com/v1/badges/b5b30239174fc6603aca/maintainability
|
||||||
[5]: https://img.shields.io/github/license/xjasonlyu/tun2socks
|
[5]: https://img.shields.io/github/license/xjasonlyu/tun2socks
|
||||||
[6]: https://img.shields.io/docker/pulls/xjasonlyu/tun2socks?logo=docker
|
[6]: https://img.shields.io/docker/pulls/xjasonlyu/tun2socks?logo=docker
|
||||||
[7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks
|
[7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?logo=smartthings
|
||||||
|
|
||||||
English | [简体中文](README_ZH.md)
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Proxy Everything: Handle all network traffic of any internet programs sent by the device through a proxy.
|
- 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.
|
- 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.
|
- 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.
|
- 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
|
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -47,7 +46,6 @@ Welcome and feel free to ask any questions at [Discussions](https://github.com/x
|
|||||||
|
|
||||||
## Credits
|
## 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
|
- [google/gvisor](https://github.com/google/gvisor) - Application Kernel for Containers
|
||||||
- [wireguard-go](https://git.zx2c4.com/wireguard-go) - Go Implementation of WireGuard
|
- [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
|
|||||||
|
|
||||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks?ref=badge_large)
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks?ref=badge_large)
|
||||||
|
|
||||||
## Stargazers over time
|
## Star History
|
||||||
|
|
||||||
[](https://starchart.cc/xjasonlyu/tun2socks)
|
<a href="https://star-history.com/#xjasonlyu/tun2socks&Date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=xjasonlyu/tun2socks&type=Date&theme=dark" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=xjasonlyu/tun2socks&type=Date" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=xjasonlyu/tun2socks&type=Date" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||

|
|
||||||
|
|
||||||
[![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)。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 使用文档
|
|
||||||
|
|
||||||
- [源码安装](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)
|
|
||||||
|
|
||||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks?ref=badge_large)
|
|
||||||
|
|
||||||
## 星星走向
|
|
||||||
|
|
||||||
[](https://starchart.cc/xjasonlyu/tun2socks)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package observable
|
|
||||||
|
|
||||||
type Iterable <-chan any
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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
@@ -1,15 +1,14 @@
|
|||||||
// Package pool provides a pool of []byte.
|
// Package pool provides a pool of []byte.
|
||||||
package pool
|
package pool
|
||||||
|
|
||||||
// Ref: github.com/Dreamacro/clash/common/pool
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MaxSegmentSize is the largest possible UDP datagram size.
|
// MaxSegmentSize is the largest possible UDP datagram size.
|
||||||
MaxSegmentSize = (1 << 16) - 1
|
MaxSegmentSize = (1 << 16) - 1
|
||||||
|
|
||||||
// io.Copy default buffer size is 32 KiB, but the maximum packet
|
// RelayBufferSize is a buffer of 20 KiB to reduce the memory
|
||||||
// size of vmess/shadowsocks is about 16 KiB, so define a buffer
|
// of each TCP relay as io.Copy default buffer size is 32 KiB,
|
||||||
// of 20 KiB to reduce the memory of each TCP relay.
|
// but the maximum packet size of vmess/shadowsocks is about
|
||||||
|
// 16 KiB, so define .
|
||||||
RelayBufferSize = 20 << 10
|
RelayBufferSize = 20 << 10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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{}
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,6 @@ import (
|
|||||||
type Device interface {
|
type Device interface {
|
||||||
stack.LinkEndpoint
|
stack.LinkEndpoint
|
||||||
|
|
||||||
// Close stops and closes the device.
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Name returns the current name of the device.
|
// Name returns the current name of the device.
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
package fdbased
|
package fdbased
|
||||||
|
|
||||||
const Driver = "fd"
|
const Driver = "fd"
|
||||||
|
|
||||||
const defaultMTU = 1500
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !windows
|
//go:build unix
|
||||||
|
|
||||||
package fdbased
|
package fdbased
|
||||||
|
|
||||||
@@ -6,12 +6,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultMTU = 1500
|
||||||
|
|
||||||
type FD struct {
|
type FD struct {
|
||||||
stack.LinkEndpoint
|
stack.LinkEndpoint
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ type FD struct {
|
|||||||
mtu uint32
|
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)
|
fd, err := strconv.Atoi(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot open fd: %s", name)
|
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 {
|
if mtu == 0 {
|
||||||
mtu = defaultMTU
|
mtu = defaultMTU
|
||||||
}
|
}
|
||||||
return open(fd, mtu)
|
return open(fd, mtu, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FD) Type() string {
|
func (f *FD) Type() string {
|
||||||
@@ -38,8 +40,9 @@ func (f *FD) Name() string {
|
|||||||
return strconv.Itoa(f.fd)
|
return strconv.Itoa(f.fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FD) Close() error {
|
func (f *FD) Close() {
|
||||||
return unix.Close(f.fd)
|
defer f.LinkEndpoint.Close()
|
||||||
|
_ = unix.Close(f.fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ device.Device = (*FD)(nil)
|
var _ device.Device = (*FD)(nil)
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ import (
|
|||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Open(name string, mtu uint32) (device.Device, error) {
|
func Open(name string, mtu uint32, offset int) (device.Device, error) {
|
||||||
return nil, errors.New("not supported")
|
return nil, errors.ErrUnsupported
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package fdbased
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
"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}
|
f := &FD{fd: fd, mtu: mtu}
|
||||||
|
|
||||||
ep, err := fdbased.New(&fdbased.Options{
|
ep, err := fdbased.New(&fdbased.Options{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !linux && !windows
|
//go:build unix && !linux
|
||||||
|
|
||||||
package fdbased
|
package fdbased
|
||||||
|
|
||||||
@@ -10,10 +10,10 @@ import (
|
|||||||
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
|
"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}
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create endpoint: %w", err)
|
return nil, fmt.Errorf("create endpoint: %w", err)
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/buffer"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
@@ -110,7 +110,7 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
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:]) {
|
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 {
|
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||||
defer pkt.DecRef()
|
defer pkt.DecRef()
|
||||||
|
|
||||||
size := pkt.Size()
|
buf := pkt.ToBuffer()
|
||||||
views := pkt.Views()
|
defer buf.Release()
|
||||||
if e.offset != 0 {
|
if e.offset != 0 {
|
||||||
views = append([]buffer.View{
|
v := buffer.NewViewWithData(make([]byte, e.offset))
|
||||||
make(buffer.View, e.offset),
|
_ = buf.Prepend(v)
|
||||||
}, views...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vView := buffer.NewVectorisedView(size, views)
|
if _, err := e.rw.Write(buf.Flatten()); err != nil {
|
||||||
if _, err := e.rw.Write(vView.ToView()); err != nil {
|
|
||||||
return &tcpip.ErrInvalidEndpointState{}
|
return &tcpip.ErrInvalidEndpointState{}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ package tun
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
"gvisor.dev/gvisor/pkg/rawfile"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
"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/link/tun"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUN struct {
|
type TUN struct {
|
||||||
@@ -77,11 +76,11 @@ func (t *TUN) Name() string {
|
|||||||
return t.name
|
return t.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TUN) Close() error {
|
func (t *TUN) Close() {
|
||||||
return unix.Close(t.fd)
|
defer t.LinkEndpoint.Close()
|
||||||
|
_ = unix.Close(t.fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ref: wireguard tun/tun_linux.go setMTU.
|
|
||||||
func setMTU(name string, n uint32) error {
|
func setMTU(name string, n uint32) error {
|
||||||
// open datagram socket
|
// open datagram socket
|
||||||
fd, err := unix.Socket(
|
fd, err := unix.Socket(
|
||||||
@@ -95,22 +94,10 @@ func setMTU(name string, n uint32) error {
|
|||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
const ifReqSize = unix.IFNAMSIZ + 64
|
ifr, err := unix.NewIfreq(name)
|
||||||
|
if err != nil {
|
||||||
// do ioctl call
|
return err
|
||||||
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.SetUint32(n)
|
||||||
return nil
|
return unix.IoctlIfreq(fd, unix.SIOCSIFMTU, ifr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ package tun
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
|
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUN struct {
|
type TUN struct {
|
||||||
@@ -18,6 +19,12 @@ type TUN struct {
|
|||||||
mtu uint32
|
mtu uint32
|
||||||
name string
|
name string
|
||||||
offset int
|
offset int
|
||||||
|
|
||||||
|
rSizes []int
|
||||||
|
rBuffs [][]byte
|
||||||
|
wBuffs [][]byte
|
||||||
|
rMutex sync.Mutex
|
||||||
|
wMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(name string, mtu uint32) (_ device.Device, err error) {
|
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
|
forcedMTU := defaultMTU
|
||||||
if t.mtu > 0 {
|
if t.mtu > 0 {
|
||||||
forcedMTU = int(t.mtu)
|
forcedMTU = int(t.mtu)
|
||||||
}
|
}
|
||||||
|
|
||||||
nt, err := tun.CreateTUN(t.name, forcedMTU)
|
nt, err := createTUN(t.name, forcedMTU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create tun: %w", err)
|
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) {
|
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) {
|
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 {
|
func (t *TUN) Name() string {
|
||||||
@@ -68,7 +89,7 @@ func (t *TUN) Name() string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TUN) Close() error {
|
func (t *TUN) Close() {
|
||||||
defer t.Endpoint.Close()
|
defer t.Endpoint.Close()
|
||||||
return t.nt.Close()
|
_ = t.nt.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
//go:build !linux && !windows
|
//go:build unix && !linux
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
offset = 4 /* 4 bytes TUN_PI */
|
offset = 4 /* 4 bytes TUN_PI */
|
||||||
defaultMTU = 1500
|
defaultMTU = 1500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func createTUN(name string, mtu int) (tun.Device, error) {
|
||||||
|
return tun.CreateTUN(name, mtu)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package tun
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
offset = 0
|
offset = 0
|
||||||
defaultMTU = 0 /* auto */
|
defaultMTU = 0 /* auto */
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func createTUN(name string, mtu int) (tun.Device, error) {
|
||||||
|
return tun.CreateTUN(name, mtu)
|
||||||
|
}
|
||||||
|
|||||||
+63
-2
@@ -2,11 +2,14 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"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/stack"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,3 +59,61 @@ func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ const (
|
|||||||
|
|
||||||
// tcpDefaultBufferSize is the default size of the send buffer for
|
// tcpDefaultBufferSize is the default size of the send buffer for
|
||||||
// a transport endpoint.
|
// a transport endpoint.
|
||||||
tcpDefaultSendBufferSize = stack.DefaultBufferSize
|
tcpDefaultSendBufferSize = tcp.DefaultSendBufferSize
|
||||||
|
|
||||||
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
|
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
|
||||||
// for a transport endpoint.
|
// for a transport endpoint.
|
||||||
tcpDefaultReceiveBufferSize = stack.DefaultBufferSize
|
tcpDefaultReceiveBufferSize = tcp.DefaultReceiveBufferSize
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option func(*stack.Stack) error
|
type Option func(*stack.Stack) error
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withRouteTable(nicID tcpip.NICID) option.Option {
|
func withRouteTable(nicID tcpip.NICID) option.Option {
|
||||||
|
|||||||
+13
-13
@@ -1,16 +1,17 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
"net"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
"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.
|
// Config is the configuration to create *stack.Stack.
|
||||||
@@ -23,9 +24,9 @@ type Config struct {
|
|||||||
// stack to set transport handlers.
|
// stack to set transport handlers.
|
||||||
TransportHandler adapter.TransportHandler
|
TransportHandler adapter.TransportHandler
|
||||||
|
|
||||||
// PrintFunc is the function that will be called
|
// MulticastGroups is used by internal stack to add
|
||||||
// to print internal stack events.
|
// nic to given groups.
|
||||||
PrintFunc func(string, ...any)
|
MulticastGroups []net.IP
|
||||||
|
|
||||||
// Options are supplement options to apply settings
|
// Options are supplement options to apply settings
|
||||||
// for the internal stack.
|
// for the internal stack.
|
||||||
@@ -34,10 +35,6 @@ type Config struct {
|
|||||||
|
|
||||||
// CreateStack creates *stack.Stack with given config.
|
// CreateStack creates *stack.Stack with given config.
|
||||||
func CreateStack(cfg *Config) (*stack.Stack, error) {
|
func CreateStack(cfg *Config) (*stack.Stack, error) {
|
||||||
if cfg.PrintFunc == nil {
|
|
||||||
cfg.PrintFunc = func(string, ...any) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := []option.Option{option.WithDefault()}
|
opts := []option.Option{option.WithDefault()}
|
||||||
if len(opts) > 0 {
|
if len(opts) > 0 {
|
||||||
opts = append(opts, cfg.Options...)
|
opts = append(opts, cfg.Options...)
|
||||||
@@ -57,15 +54,15 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Generate unique NIC id.
|
// Generate unique NIC id.
|
||||||
nicID := tcpip.NICID(s.UniqueID())
|
nicID := s.NextNICID()
|
||||||
|
|
||||||
opts = append(opts,
|
opts = append(opts,
|
||||||
// Important: We must initiate transport protocol handlers
|
// Important: We must initiate transport protocol handlers
|
||||||
// before creating NIC, otherwise NIC would dispatch packets
|
// before creating NIC, otherwise NIC would dispatch packets
|
||||||
// to stack and cause race condition.
|
// to stack and cause race condition.
|
||||||
// Initiate transport protocol (TCP/UDP) with given handler.
|
// Initiate transport protocol (TCP/UDP) with given handler.
|
||||||
withTCPHandler(cfg.TransportHandler.HandleTCP, cfg.PrintFunc),
|
withTCPHandler(cfg.TransportHandler.HandleTCP),
|
||||||
withUDPHandler(cfg.TransportHandler.HandleUDP, cfg.PrintFunc),
|
withUDPHandler(cfg.TransportHandler.HandleUDP),
|
||||||
|
|
||||||
// Create stack NIC and then bind link endpoint to it.
|
// Create stack NIC and then bind link endpoint to it.
|
||||||
withCreatingNIC(nicID, cfg.LinkEndpoint),
|
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
|
// Add default route table for IPv4 and IPv6. This will handle
|
||||||
// all incoming ICMP packets.
|
// all incoming ICMP packets.
|
||||||
withRouteTable(nicID),
|
withRouteTable(nicID),
|
||||||
|
|
||||||
|
// Add default NIC to the given multicast groups.
|
||||||
|
withMulticastGroups(nicID, cfg.MulticastGroups),
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
|||||||
+10
-7
@@ -3,15 +3,16 @@ package core
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
glog "gvisor.dev/gvisor/pkg/log"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
"gvisor.dev/gvisor/pkg/waiter"
|
"gvisor.dev/gvisor/pkg/waiter"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -40,18 +41,20 @@ const (
|
|||||||
tcpKeepaliveInterval = 30 * time.Second
|
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 {
|
return func(s *stack.Stack) error {
|
||||||
tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
|
tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
|
||||||
var (
|
var (
|
||||||
wq waiter.Queue
|
wq waiter.Queue
|
||||||
ep tcpip.Endpoint
|
ep tcpip.Endpoint
|
||||||
err tcpip.Error
|
err tcpip.Error
|
||||||
|
id = r.ID()
|
||||||
)
|
)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printf("tcp forwarder request: %s", err)
|
glog.Debugf("forward tcp request: %s:%d->%s:%d: %s",
|
||||||
|
id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -68,7 +71,7 @@ func withTCPHandler(handle func(adapter.TCPConn), printf func(string, ...any)) o
|
|||||||
|
|
||||||
conn := &tcpConn{
|
conn := &tcpConn{
|
||||||
TCPConn: gonet.NewTCPConn(&wq, ep),
|
TCPConn: gonet.NewTCPConn(&wq, ep),
|
||||||
id: r.ID(),
|
id: id,
|
||||||
}
|
}
|
||||||
handle(conn)
|
handle(conn)
|
||||||
})
|
})
|
||||||
@@ -98,7 +101,7 @@ func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
|
|||||||
{ /* TCP recv/send buffer size */
|
{ /* TCP recv/send buffer size */
|
||||||
var ss tcpip.TCPSendBufferSizeRangeOption
|
var ss tcpip.TCPSendBufferSizeRangeOption
|
||||||
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil {
|
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
|
var rs tcpip.TCPReceiveBufferSizeRangeOption
|
||||||
|
|||||||
+13
-8
@@ -1,28 +1,33 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
glog "gvisor.dev/gvisor/pkg/log"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
"gvisor.dev/gvisor/pkg/waiter"
|
"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 {
|
return func(s *stack.Stack) error {
|
||||||
udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
|
udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) {
|
||||||
var wq waiter.Queue
|
var (
|
||||||
|
wq waiter.Queue
|
||||||
|
id = r.ID()
|
||||||
|
)
|
||||||
ep, err := r.CreateEndpoint(&wq)
|
ep, err := r.CreateEndpoint(&wq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printf("udp forwarder request: %s", err)
|
glog.Debugf("forward udp request: %s:%d->%s:%d: %s",
|
||||||
|
id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := &udpConn{
|
conn := &udpConn{
|
||||||
UDPConn: gonet.NewUDPConn(s, &wq, ep),
|
UDPConn: gonet.NewUDPConn(&wq, ep),
|
||||||
id: r.ID(),
|
id: id,
|
||||||
}
|
}
|
||||||
handle(conn)
|
handle(conn)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
package dialer
|
||||||
|
|
||||||
@@ -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
@@ -3,7 +3,7 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -77,10 +77,14 @@ run() {
|
|||||||
ARGS="$ARGS --tcp-rcvbuf $TCP_RCVBUF"
|
ARGS="$ARGS --tcp-rcvbuf $TCP_RCVBUF"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$TCP_AUTO_TUNING" -eq 1 ]; then
|
if [ "$TCP_AUTO_TUNING" = 1 ]; then
|
||||||
ARGS="$ARGS --tcp-auto-tuning"
|
ARGS="$ARGS --tcp-auto-tuning"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -n "$MULTICAST_GROUPS" ]; then
|
||||||
|
ARGS="$ARGS --multicast-groups $MULTICAST_GROUPS"
|
||||||
|
fi
|
||||||
|
|
||||||
exec tun2socks \
|
exec tun2socks \
|
||||||
--loglevel "$LOGLEVEL" \
|
--loglevel "$LOGLEVEL" \
|
||||||
--fwmark "$FWMARK" \
|
--fwmark "$FWMARK" \
|
||||||
|
|||||||
+49
-16
@@ -2,24 +2,25 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
"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/engine/mirror"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/log"
|
"github.com/xjasonlyu/tun2socks/v2/log"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/proxy"
|
"github.com/xjasonlyu/tun2socks/v2/proxy"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/restapi"
|
"github.com/xjasonlyu/tun2socks/v2/restapi"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/tunnel"
|
"github.com/xjasonlyu/tun2socks/v2/tunnel"
|
||||||
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -81,13 +82,25 @@ func start() error {
|
|||||||
func stop() (err error) {
|
func stop() (err error) {
|
||||||
_engineMu.Lock()
|
_engineMu.Lock()
|
||||||
if _defaultDevice != nil {
|
if _defaultDevice != nil {
|
||||||
err = _defaultDevice.Close()
|
_defaultDevice.Close()
|
||||||
}
|
}
|
||||||
if _defaultStack != nil {
|
if _defaultStack != nil {
|
||||||
_defaultStack.Close()
|
_defaultStack.Close()
|
||||||
_defaultStack.Wait()
|
_defaultStack.Wait()
|
||||||
}
|
}
|
||||||
_engineMu.Unlock()
|
_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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +109,7 @@ func general(k *Key) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.SetLevel(level)
|
log.SetLogger(log.Must(log.NewLeveled(level)))
|
||||||
|
|
||||||
if k.Interface != "" {
|
if k.Interface != "" {
|
||||||
iface, err := net.InterfaceByName(k.Interface)
|
iface, err := net.InterfaceByName(k.Interface)
|
||||||
@@ -143,7 +156,7 @@ func restAPI(k *Key) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := restapi.Start(host, token); err != nil {
|
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)
|
log.Infof("[RESTAPI] serve at: %s", u)
|
||||||
@@ -159,15 +172,37 @@ func netstack(k *Key) (err error) {
|
|||||||
return errors.New("empty device")
|
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
|
return
|
||||||
}
|
}
|
||||||
proxy.SetDialer(_defaultProxy)
|
proxy.DefaultProxy = _defaultProxy
|
||||||
|
|
||||||
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
|
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var multicastGroups []net.IP
|
||||||
|
if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var opts []option.Option
|
var opts []option.Option
|
||||||
if k.TCPModerateReceiveBuffer {
|
if k.TCPModerateReceiveBuffer {
|
||||||
opts = append(opts, option.WithTCPModerateReceiveBuffer(true))
|
opts = append(opts, option.WithTCPModerateReceiveBuffer(true))
|
||||||
@@ -192,10 +227,8 @@ func netstack(k *Key) (err error) {
|
|||||||
if _defaultStack, err = core.CreateStack(&core.Config{
|
if _defaultStack, err = core.CreateStack(&core.Config{
|
||||||
LinkEndpoint: _defaultDevice,
|
LinkEndpoint: _defaultDevice,
|
||||||
TransportHandler: &mirror.Tunnel{},
|
TransportHandler: &mirror.Tunnel{},
|
||||||
PrintFunc: func(format string, v ...any) {
|
MulticastGroups: multicastGroups,
|
||||||
log.Warnf("[STACK] %s", fmt.Sprintf(format, v...))
|
Options: opts,
|
||||||
},
|
|
||||||
Options: opts,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -203,7 +236,7 @@ func netstack(k *Key) (err error) {
|
|||||||
log.Infof(
|
log.Infof(
|
||||||
"[STACK] %s://%s <-> %s://%s",
|
"[STACK] %s://%s <-> %s://%s",
|
||||||
_defaultDevice.Type(), _defaultDevice.Name(),
|
_defaultDevice.Type(), _defaultDevice.Name(),
|
||||||
_defaultProxy.Proto(), _defaultProxy.Addr(),
|
_defaultProxy.Protocol(), _defaultProxy.Address(),
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,8 @@ type Key struct {
|
|||||||
TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"`
|
TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"`
|
||||||
TCPSendBufferSize string `yaml:"tcp-send-buffer-size"`
|
TCPSendBufferSize string `yaml:"tcp-send-buffer-size"`
|
||||||
TCPReceiveBufferSize string `yaml:"tcp-receive-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"`
|
UDPTimeout time.Duration `yaml:"udp-timeout"`
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-84
@@ -1,7 +1,6 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -10,8 +9,6 @@ import (
|
|||||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
|
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
|
"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) {
|
func parseRestAPI(s string) (*url.URL, error) {
|
||||||
@@ -51,102 +48,36 @@ func parseDevice(s string, mtu uint32) (device.Device, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name := u.Host
|
|
||||||
driver := strings.ToLower(u.Scheme)
|
driver := strings.ToLower(u.Scheme)
|
||||||
|
|
||||||
switch driver {
|
switch driver {
|
||||||
case fdbased.Driver:
|
case fdbased.Driver:
|
||||||
return fdbased.Open(name, mtu)
|
return parseFD(u, mtu)
|
||||||
case tun.Driver:
|
case tun.Driver:
|
||||||
return tun.Open(name, mtu)
|
return parseTUN(u, mtu)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported driver: %s", driver)
|
return nil, fmt.Errorf("unsupported driver: %s", driver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseProxy(s string) (proxy.Proxy, error) {
|
func parseFD(u *url.URL, mtu uint32) (device.Device, error) {
|
||||||
if !strings.Contains(s, "://") {
|
return fdbased.Open(u.Host, mtu, 0)
|
||||||
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 parseHTTP(u *url.URL) (address, username, password string) {
|
func parseMulticastGroups(s string) (multicastGroups []net.IP, _ error) {
|
||||||
address, username = u.Host, u.User.Username()
|
ipStrings := strings.Split(s, ",")
|
||||||
password, _ = u.User.Password()
|
for _, ipString := range ipStrings {
|
||||||
return
|
if strings.TrimSpace(ipString) == "" {
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := data[0]
|
ip := net.ParseIP(ipString)
|
||||||
value := data[1]
|
if ip == nil {
|
||||||
|
return nil, fmt.Errorf("invalid IP format: %s", ipString)
|
||||||
switch key {
|
|
||||||
case "obfs":
|
|
||||||
obfsMode = value
|
|
||||||
case "obfs-host":
|
|
||||||
obfsHost = value
|
|
||||||
}
|
}
|
||||||
|
if !ip.IsMulticast() {
|
||||||
|
return nil, fmt.Errorf("invalid multicast IP address: %s", ipString)
|
||||||
|
}
|
||||||
|
multicastGroups = append(multicastGroups, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
)
|
||||||
@@ -1,31 +1,37 @@
|
|||||||
module github.com/xjasonlyu/tun2socks/v2
|
module github.com/xjasonlyu/tun2socks/v2
|
||||||
|
|
||||||
go 1.18
|
go 1.22.0
|
||||||
|
|
||||||
|
toolchain go1.22.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/docker/go-units v0.4.0
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/cors v1.2.0
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/go-chi/render v1.0.1
|
github.com/go-gost/relay v0.5.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/gorilla/schema v1.4.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/gorilla/websocket v1.5.3
|
||||||
go.uber.org/atomic v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
go.uber.org/automaxprocs v1.4.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb
|
go.uber.org/automaxprocs v1.5.3
|
||||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
|
go.uber.org/zap v1.27.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6
|
golang.org/x/crypto v0.25.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
golang.org/x/sys v0.22.0
|
||||||
gvisor.dev/gvisor v0.0.0-20220401222657-54142f50892f
|
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 (
|
require (
|
||||||
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b // indirect
|
golang.org/x/net v0.27.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,66 +1,63 @@
|
|||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/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.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.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.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.2.0 h1:tV1g1XENQ8ku4Bq3K9ub2AtgG+p16SmzeMSGTwrOKdE=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.0/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c=
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b h1:vI32FkLJNAWtGD4BwkThwEy6XS7ZLLMHkSkYfF8M0W0=
|
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||||
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
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=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d h1:dFTIljP/5ReqgM7nMR4DauApFatUaSP8r9btX0sd8a8=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gvisor.dev/gvisor v0.0.0-20240713103206-39d6c232e61d/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
|
||||||
gvisor.dev/gvisor v0.0.0-20220401222657-54142f50892f h1:nrRW7+UI7oDyWsuJSZTKMWTisiYUqSG9LU0eageG77c=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20220401222657-54142f50892f/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build debug
|
|
||||||
|
|
||||||
package version
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_debug = true
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
const Name = "tun2socks"
|
const Name = "tun2socks"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_debug = false
|
|
||||||
|
|
||||||
// Version can be set at link time by executing
|
// Version can be set at link time by executing
|
||||||
// the command: `git describe --abbrev=0 --tags HEAD`
|
// the command: `git describe --abbrev=0 --tags HEAD`
|
||||||
Version string
|
Version string
|
||||||
@@ -20,19 +18,8 @@ var (
|
|||||||
GitCommit string
|
GitCommit string
|
||||||
)
|
)
|
||||||
|
|
||||||
func versionize(s string) string {
|
|
||||||
return strings.TrimPrefix(s, "v")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debug() bool {
|
|
||||||
return _debug
|
|
||||||
}
|
|
||||||
|
|
||||||
func String() string {
|
func String() string {
|
||||||
if !Debug() {
|
return fmt.Sprintf("%s-%s", Name, strings.TrimPrefix(Version, "v"))
|
||||||
return fmt.Sprintf("%s-%s", Name, versionize(Version))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s-%s (debug)", Name, versionize(Version))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildString() string {
|
func BuildString() string {
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// Package log is a thin wrapper based on "go.uber.org/zap".
|
||||||
|
package log
|
||||||
@@ -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...)
|
||||||
|
}
|
||||||
@@ -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
@@ -1,72 +1,31 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"go.uber.org/zap/zapcore"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Level uint32
|
// Level is an alias for zapcore.Level.
|
||||||
|
type Level = zapcore.Level
|
||||||
|
|
||||||
|
// Levels are aliases for Level.
|
||||||
const (
|
const (
|
||||||
SilentLevel Level = iota
|
DebugLevel = zapcore.DebugLevel
|
||||||
ErrorLevel
|
InfoLevel = zapcore.InfoLevel
|
||||||
WarnLevel
|
WarnLevel = zapcore.WarnLevel
|
||||||
InfoLevel
|
ErrorLevel = zapcore.ErrorLevel
|
||||||
DebugLevel
|
DPanicLevel = zapcore.DPanicLevel
|
||||||
|
PanicLevel = zapcore.PanicLevel
|
||||||
|
FatalLevel = zapcore.FatalLevel
|
||||||
|
InvalidLevel = zapcore.InvalidLevel
|
||||||
|
SilentLevel = InvalidLevel + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnmarshalJSON deserialize Level with json
|
// ParseLevel is a thin wrapper for zapcore.ParseLevel.
|
||||||
func (level *Level) UnmarshalJSON(data []byte) error {
|
func ParseLevel(text string) (Level, error) {
|
||||||
var lvl string
|
switch text {
|
||||||
if err := json.Unmarshal(data, &lvl); err != nil {
|
case "silent", "SILENT":
|
||||||
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":
|
|
||||||
return SilentLevel, nil
|
return SilentLevel, nil
|
||||||
case "error":
|
|
||||||
return ErrorLevel, nil
|
|
||||||
case "warning":
|
|
||||||
return WarnLevel, nil
|
|
||||||
case "info":
|
|
||||||
return InfoLevel, nil
|
|
||||||
case "debug":
|
|
||||||
return DebugLevel, nil
|
|
||||||
default:
|
default:
|
||||||
return Level(0), fmt.Errorf("not a valid logrus Level: %q", lvl)
|
return zapcore.ParseLevel(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-45
@@ -1,58 +1,71 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// _defaultLevel is package default logging level.
|
// global Logger and SugaredLogger.
|
||||||
var _defaultLevel = atomic.NewUint32(uint32(InfoLevel))
|
var (
|
||||||
|
_globalMu sync.RWMutex
|
||||||
|
_globalL *Logger
|
||||||
|
_globalS *SugaredLogger
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logrus.SetOutput(os.Stdout)
|
SetLogger(zap.Must(zap.NewProduction()))
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetLevel(level Level) {
|
func NewLeveled(l Level, options ...Option) (*Logger, error) {
|
||||||
_defaultLevel.Store(uint32(level))
|
switch l {
|
||||||
}
|
case SilentLevel:
|
||||||
|
return zap.NewNop(), nil
|
||||||
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 {
|
|
||||||
case DebugLevel:
|
case DebugLevel:
|
||||||
logrus.WithTime(event.Time).Debugln(event.Message)
|
return zap.NewDevelopment(options...)
|
||||||
case InfoLevel:
|
case InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel:
|
||||||
logrus.WithTime(event.Time).Infoln(event.Message)
|
cfg := zap.NewProductionConfig()
|
||||||
case WarnLevel:
|
cfg.Level.SetLevel(l)
|
||||||
logrus.WithTime(event.Time).Warnln(event.Message)
|
return cfg.Build(options...)
|
||||||
case ErrorLevel:
|
default:
|
||||||
logrus.WithTime(event.Time).Errorln(event.Message)
|
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
@@ -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,13 +7,13 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
_ "github.com/xjasonlyu/tun2socks/v2/dns"
|
_ "github.com/xjasonlyu/tun2socks/v2/dns"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/engine"
|
"github.com/xjasonlyu/tun2socks/v2/engine"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/internal/version"
|
"github.com/xjasonlyu/tun2socks/v2/internal/version"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/log"
|
"github.com/xjasonlyu/tun2socks/v2/log"
|
||||||
|
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -30,12 +30,15 @@ func init() {
|
|||||||
flag.StringVar(&configFile, "config", "", "YAML format configuration file")
|
flag.StringVar(&configFile, "config", "", "YAML format configuration file")
|
||||||
flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name")
|
flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name")
|
||||||
flag.StringVar(&key.Interface, "interface", "", "Use network INTERFACE (Linux/MacOS only)")
|
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.Proxy, "proxy", "", "Use this proxy [protocol://]host[:port]")
|
||||||
flag.StringVar(&key.RestAPI, "restapi", "", "HTTP statistic server listen address")
|
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.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack")
|
||||||
flag.StringVar(&key.TCPReceiveBufferSize, "tcp-rcvbuf", "", "Set TCP receive 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.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.BoolVar(&versionFlag, "version", false, "Show version and then quit")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
|
||||||
}
|
|
||||||
@@ -1,34 +1,33 @@
|
|||||||
package proxy
|
package direct
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
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 {
|
const protocol = "direct"
|
||||||
*Base
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDirect() *Direct {
|
type Direct struct{ *base.Base }
|
||||||
return &Direct{
|
|
||||||
Base: &Base{
|
func New() *Direct { return &Direct{base.New("", protocol)} }
|
||||||
proto: proto.Direct,
|
|
||||||
},
|
func Parse(*url.URL) (proxy.Proxy, error) { return New(), nil }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(ctx, "tcp", metadata.DestinationAddress())
|
c, err := dialer.DialContext(ctx, "tcp", metadata.DestinationAddress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
setKeepAlive(c)
|
internal.SetKeepAlive(c)
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,3 +54,7 @@ func (pc *directPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
|||||||
}
|
}
|
||||||
return pc.PacketConn.WriteTo(b, udpAddr)
|
return pc.PacketConn.WriteTo(b, udpAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterProtocol(protocol, Parse)
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -7,44 +7,45 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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)
|
DialContext(context.Context, *M.Metadata) (net.Conn, error)
|
||||||
|
|
||||||
|
// DialUDP is used to to dial/listen UDP networks.
|
||||||
DialUDP(*M.Metadata) (net.PacketConn, error)
|
DialUDP(*M.Metadata) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Proxy interface {
|
// Dial uses the DefaultProxy to dial TCP.
|
||||||
Dialer
|
|
||||||
Addr() string
|
|
||||||
Proto() proto.Proto
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDialer sets default Dialer.
|
|
||||||
func SetDialer(d Dialer) {
|
|
||||||
_defaultDialer = d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial uses default Dialer to dial TCP.
|
|
||||||
func Dial(metadata *M.Metadata) (net.Conn, error) {
|
func Dial(metadata *M.Metadata) (net.Conn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), TCPConnectTimeout)
|
||||||
defer cancel()
|
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) {
|
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) {
|
func DialUDP(metadata *M.Metadata) (net.PacketConn, error) {
|
||||||
return _defaultDialer.DialUDP(metadata)
|
return DefaultProxy.DialUDP(metadata)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,30 @@
|
|||||||
package proxy
|
package reject
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
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 {
|
const protocol = "reject"
|
||||||
*Base
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReject() *Reject {
|
type Reject struct{ *base.Base }
|
||||||
return &Reject{
|
|
||||||
Base: &Base{
|
|
||||||
proto: proto.Reject,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
|
func New() *Reject { return &Reject{base.New("", protocol)} }
|
||||||
return &nopConn{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reject) DialUDP(*M.Metadata) (net.PacketConn, error) {
|
func Parse(*url.URL) (proxy.Proxy, error) { return New(), nil }
|
||||||
return &nopPacketConn{}, 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{}
|
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) SetDeadline(time.Time) error { return nil }
|
||||||
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||||
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proxy.RegisterProtocol(protocol, Parse)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package proxy
|
package socks5
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -6,17 +6,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
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"
|
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Proxy = (*Socks5)(nil)
|
var _ proxy.Proxy = (*Socks5)(nil)
|
||||||
|
|
||||||
|
const protocol = "socks5"
|
||||||
|
|
||||||
type Socks5 struct {
|
type Socks5 struct {
|
||||||
*Base
|
*base.Base
|
||||||
|
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
@@ -25,31 +30,41 @@ type Socks5 struct {
|
|||||||
unix bool
|
unix bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5(addr, user, pass string) (*Socks5, error) {
|
func New(addr, user, pass string) (*Socks5, error) {
|
||||||
return &Socks5{
|
return &Socks5{
|
||||||
Base: &Base{
|
Base: base.New(addr, protocol),
|
||||||
addr: addr,
|
|
||||||
proto: proto.Socks5,
|
|
||||||
},
|
|
||||||
user: user,
|
user: user,
|
||||||
pass: pass,
|
pass: pass,
|
||||||
unix: len(addr) > 0 && addr[0] == '/',
|
unix: len(addr) > 0 && addr[0] == '/',
|
||||||
}, nil
|
}, 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) {
|
func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) {
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
if ss.unix {
|
if ss.unix {
|
||||||
network = "unix"
|
network = "unix"
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = dialer.DialContext(ctx, network, ss.Addr())
|
c, err = dialer.DialContext(ctx, network, ss.Address())
|
||||||
if err != nil {
|
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
|
var user *socks5.User
|
||||||
if ss.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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
|
func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
|
||||||
if ss.unix {
|
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()
|
defer cancel()
|
||||||
|
|
||||||
c, err := dialer.DialContext(ctx, "tcp", ss.Addr())
|
c, err := dialer.DialContext(ctx, "tcp", ss.Address())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("connect to %s: %w", ss.Addr(), err)
|
err = fmt.Errorf("connect to %s: %w", ss.Address(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setKeepAlive(c)
|
internal.SetKeepAlive(c)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil && c != nil {
|
if err != nil && c != nil {
|
||||||
@@ -121,10 +136,14 @@ func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
bindAddr := addr.UDPAddr()
|
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 "::" */
|
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 {
|
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
|
bindAddr.IP = udpAddr.IP
|
||||||
}
|
}
|
||||||
@@ -142,7 +161,7 @@ type socksPacketConn struct {
|
|||||||
func (pc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
func (pc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
var packet []byte
|
var packet []byte
|
||||||
if ma, ok := addr.(*M.Addr); ok {
|
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 {
|
} else {
|
||||||
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
|
packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b)
|
||||||
}
|
}
|
||||||
@@ -179,6 +198,6 @@ func (pc *socksPacketConn) Close() error {
|
|||||||
return pc.PacketConn.Close()
|
return pc.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func serializeSocksAddr(m *M.Metadata) socks5.Addr {
|
func init() {
|
||||||
return socks5.SerializeAddr("", m.DstIP, m.DstPort)
|
proxy.RegisterProtocol(protocol, Parse)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,15 +7,19 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultInterval = 1000
|
const defaultInterval = 1000
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerEndpoint("/connections", connectionRouter())
|
||||||
|
}
|
||||||
|
|
||||||
func connectionRouter() http.Handler {
|
func connectionRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getConnections)
|
r.Get("/", getConnections)
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerMountPoint("/debug/pprof/", pprofRouter())
|
registerEndpoint("/debug/pprof/", pprofRouter())
|
||||||
}
|
}
|
||||||
|
|
||||||
func pprofRouter() http.Handler {
|
func pprofRouter() http.Handler {
|
||||||
|
|||||||
+40
-28
@@ -2,9 +2,9 @@ package restapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
@@ -12,26 +12,36 @@ import (
|
|||||||
"gvisor.dev/gvisor/pkg/tcpip"
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _statsFunc func() tcpip.Stats
|
var _stackStatsFunc func() tcpip.Stats
|
||||||
|
|
||||||
func SetStatsFunc(s func() tcpip.Stats) {
|
func SetStatsFunc(s func() tcpip.Stats) {
|
||||||
_statsFunc = s
|
_stackStatsFunc = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registerEndpoint("/netstats", http.HandlerFunc(getNetStats))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNetStats(w http.ResponseWriter, r *http.Request) {
|
func getNetStats(w http.ResponseWriter, r *http.Request) {
|
||||||
if _statsFunc == nil {
|
if _stackStatsFunc == nil {
|
||||||
render.Status(r, http.StatusInternalServerError)
|
render.Status(r, http.StatusInternalServerError)
|
||||||
render.JSON(w, r, ErrUninitialized)
|
render.JSON(w, r, ErrUninitialized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot := func() any {
|
b := &bytes.Buffer{}
|
||||||
s := _statsFunc()
|
snapshot := func() []byte {
|
||||||
return dump(reflect.ValueOf(&s).Elem())
|
s := _stackStatsFunc()
|
||||||
|
b.Reset() /* reset buffer */
|
||||||
|
encodeToJSON(reflect.ValueOf(&s).Elem(), b)
|
||||||
|
return b.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !websocket.IsWebSocketUpgrade(r) {
|
if !websocket.IsWebSocketUpgrade(r) {
|
||||||
render.JSON(w, r, snapshot())
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
render.Status(r, http.StatusOK)
|
||||||
|
w.Write(snapshot())
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,42 +53,44 @@ func getNetStats(w http.ResponseWriter, r *http.Request) {
|
|||||||
tick := time.NewTicker(time.Second)
|
tick := time.NewTicker(time.Second)
|
||||||
defer tick.Stop()
|
defer tick.Stop()
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
for range tick.C {
|
for range tick.C {
|
||||||
buf.Reset()
|
if err = conn.WriteMessage(websocket.TextMessage, snapshot()); err != nil {
|
||||||
|
|
||||||
if err = json.NewEncoder(buf).Encode(snapshot()); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = conn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dump(value reflect.Value) map[string]any {
|
func encodeToJSON(value reflect.Value, b *bytes.Buffer) {
|
||||||
numField := value.NumField()
|
b.WriteByte('{')
|
||||||
structure := make(map[string]any, numField)
|
defer b.WriteByte('}')
|
||||||
|
|
||||||
for i := 0; i < numField; i++ {
|
for i, numField := 0, value.NumField(); i < numField; i++ {
|
||||||
field := value.Type().Field(i)
|
field := value.Type().Field(i)
|
||||||
value := value.Field(i)
|
value := value.Field(i)
|
||||||
|
|
||||||
|
b.WriteString("\"" + field.Name + "\":")
|
||||||
|
|
||||||
switch v := value.Addr().Interface().(type) {
|
switch v := value.Addr().Interface().(type) {
|
||||||
case **tcpip.StatCounter:
|
case **tcpip.StatCounter:
|
||||||
structure[field.Name] = (*v).Value()
|
b.WriteString(strconv.FormatUint((*v).Value(), 10))
|
||||||
case **tcpip.IntegralStatCounterMap:
|
case **tcpip.IntegralStatCounterMap:
|
||||||
counterMap := make(map[uint64]uint64)
|
b.WriteByte('{')
|
||||||
for _, k := range (*v).Keys() {
|
for j, keys := 0, (*v).Keys(); j < len(keys); j++ {
|
||||||
if counter, ok := (*v).Get(k); ok {
|
if counter, ok := (*v).Get(keys[j]); ok {
|
||||||
counterMap[k] = counter.Value()
|
k := strconv.FormatUint(keys[j], 10)
|
||||||
|
v := strconv.FormatUint(counter.Value(), 10)
|
||||||
|
b.WriteString("\"" + k + "\":" + v)
|
||||||
|
if j < len(keys)-1 {
|
||||||
|
b.WriteByte(',')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
structure[field.Name] = counterMap
|
b.WriteByte('}')
|
||||||
default:
|
default:
|
||||||
structure[field.Name] = dump(value)
|
encodeToJSON(value, b)
|
||||||
|
}
|
||||||
|
if i < numField-1 {
|
||||||
|
b.WriteByte(',')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return structure
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-69
@@ -1,7 +1,5 @@
|
|||||||
package restapi
|
package restapi
|
||||||
|
|
||||||
// Ref: github.com/Dreamacro/clash/hub/route
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -10,20 +8,27 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"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/chi/v5"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
V "github.com/xjasonlyu/tun2socks/v2/internal/version"
|
||||||
|
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _upgrader = websocket.Upgrader{
|
var (
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
_upgrader = websocket.Upgrader{
|
||||||
return true
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
},
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_endpoints = make(map[string]http.Handler)
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerEndpoint(pattern string, handler http.Handler) {
|
||||||
|
_endpoints[pattern] = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(addr, token string) error {
|
func Start(addr, token string) error {
|
||||||
@@ -40,11 +45,12 @@ func Start(addr, token string) error {
|
|||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(authenticator(token))
|
r.Use(authenticator(token))
|
||||||
r.Get("/", hello)
|
r.Get("/", hello)
|
||||||
r.Get("/logs", getLogs)
|
|
||||||
r.Get("/traffic", traffic)
|
r.Get("/traffic", traffic)
|
||||||
r.Get("/version", version)
|
r.Get("/version", version)
|
||||||
r.Get("/netstats", getNetStats)
|
// attach HTTP handlers
|
||||||
r.Mount("/connections", connectionRouter())
|
for pattern, handler := range _endpoints {
|
||||||
|
r.Mount(pattern, handler)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", addr)
|
listener, err := net.Listen("tcp", addr)
|
||||||
@@ -95,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) {
|
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
@@ -202,7 +153,6 @@ func version(w http.ResponseWriter, r *http.Request) {
|
|||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"version": V.Version,
|
"version": V.Version,
|
||||||
"commit": V.GitCommit,
|
"commit": V.GitCommit,
|
||||||
"debug": V.Debug(),
|
|
||||||
"modules": V.Info(),
|
"modules": V.Info(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
## Embedded go-shadowsocks2
|
||||||
|
|
||||||
|
origin https://github.com/riobard/go-shadowsocks2
|
||||||
Executable
+164
@@ -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]
|
||||||
|
}
|
||||||
Executable
+94
@@ -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
|
||||||
|
}
|
||||||
Executable
+95
@@ -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
|
||||||
|
}
|
||||||
Executable
+285
@@ -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)
|
||||||
|
}
|
||||||
Executable
+116
@@ -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
|
||||||
|
}
|
||||||
Executable
+79
@@ -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
|
||||||
|
}
|
||||||
Executable
+197
@@ -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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
mRand "math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -64,8 +65,8 @@ func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
|||||||
if ho.firstRequest {
|
if ho.firstRequest {
|
||||||
randBytes := make([]byte, 16)
|
randBytes := make([]byte, 16)
|
||||||
rand.Read(randBytes)
|
rand.Read(randBytes)
|
||||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
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", rand.Int()%54, rand.Int()%2))
|
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("Upgrade", "websocket")
|
||||||
req.Header.Set("Connection", "Upgrade")
|
req.Header.Set("Connection", "Upgrade")
|
||||||
req.Host = ho.host
|
req.Host = ho.host
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
// Package obfs provides obfuscation functionality for Shadowsocks protocol.
|
// Package obfs provides obfuscation functionality for Shadowsocks protocol.
|
||||||
package obfs
|
package obfs
|
||||||
|
|
||||||
// Ref: github.com/Dreamacro/clash/component/simple-obfs
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user