mirror of
https://github.com/xjasonlyu/tun2socks.git
synced 2024-12-30 02:37:01 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec85410144 | ||
|
|
43f0ba892f | ||
|
|
e5009398d5 | ||
|
|
54ef0500d3 | ||
|
|
b65d23180c | ||
|
|
a821cc483c | ||
|
|
56786517dc | ||
|
|
428f82694a | ||
|
|
bf745d0e0e | ||
|
|
391d3d9f89 | ||
|
|
fc4c5c4c55 | ||
|
|
978803cdf8 | ||
|
|
bd37a1a4c6 | ||
|
|
1f09b4d42d | ||
|
|
fd98f65994 | ||
|
|
71c45ef87e | ||
|
|
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 |
+5
-3
@@ -1,6 +1,8 @@
|
||||
.github
|
||||
.github/
|
||||
.gitignore
|
||||
.golangci.yaml
|
||||
|
||||
# Other
|
||||
build/*
|
||||
docs/*
|
||||
docs/
|
||||
build/
|
||||
tests/
|
||||
|
||||
@@ -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']
|
||||
@@ -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"
|
||||
|
||||
concurrency:
|
||||
group: codeql-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -16,15 +21,21 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
check-latest: true
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
name: Publish Docker Image
|
||||
|
||||
concurrency:
|
||||
group: docker-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -8,38 +12,36 @@ on:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: xjasonlyu
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get Version
|
||||
@@ -49,7 +51,7 @@ jobs:
|
||||
|
||||
- name: Build and Push (dev)
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
@@ -62,7 +64,7 @@ jobs:
|
||||
|
||||
- name: Build and Push (latest)
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
name: Linter
|
||||
on: [ push, pull_request ]
|
||||
|
||||
concurrency:
|
||||
group: linter-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
linter:
|
||||
name: Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
check-latest: true
|
||||
go-version: '1.20'
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
name: Publish Go Releases
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
check-latest: true
|
||||
go-version: '1.20'
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
|
||||
- name: Run test
|
||||
run: |
|
||||
go test ./...
|
||||
|
||||
- name: Build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: make -j releases
|
||||
|
||||
- name: Upload Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: build/*
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 10 * * *"
|
||||
@@ -8,9 +13,10 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v7
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
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'
|
||||
exempt-issue-labels: 'question,bug,enhancement,help wanted'
|
||||
exempt-pr-labels: 'pending,WIP,help wanted'
|
||||
days-before-stale: 60
|
||||
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~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# Build directory
|
||||
build/
|
||||
|
||||
# IDE
|
||||
|
||||
+11
-6
@@ -1,16 +1,21 @@
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- gofumpt
|
||||
- staticcheck
|
||||
- govet
|
||||
- gci
|
||||
- gofumpt
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unused
|
||||
- usestdlibvars
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
custom-order: true
|
||||
sections:
|
||||
- standard
|
||||
- prefix(github.com/xjasonlyu/tun2socks)
|
||||
- default
|
||||
staticcheck:
|
||||
go: '1.20'
|
||||
- prefix(github.com/xjasonlyu/tun2socks)
|
||||
|
||||
@@ -25,6 +25,7 @@ ENV UDP_TIMEOUT=
|
||||
ENV TCP_SNDBUF=
|
||||
ENV TCP_RCVBUF=
|
||||
ENV TCP_AUTO_TUNING=
|
||||
ENV MULTICAST_GROUPS=
|
||||
ENV EXTRA_COMMANDS=
|
||||
ENV TUN_INCLUDED_ROUTES=
|
||||
ENV TUN_EXCLUDED_ROUTES=
|
||||
|
||||
@@ -41,6 +41,7 @@ UNIX_ARCH_LIST = \
|
||||
linux-ppc64 \
|
||||
linux-ppc64le \
|
||||
linux-s390x \
|
||||
linux-loong64 \
|
||||
openbsd-amd64 \
|
||||
openbsd-amd64-v3 \
|
||||
openbsd-arm64
|
||||
@@ -129,6 +130,9 @@ linux-ppc64le:
|
||||
linux-s390x:
|
||||
GOARCH=s390x GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
||||
|
||||
linux-loong64:
|
||||
GOARCH=loong64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
||||
|
||||
openbsd-amd64:
|
||||
GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@
|
||||
|
||||
@@ -167,9 +171,9 @@ all-arch: $(UNIX_ARCH_LIST) $(WINDOWS_ARCH_LIST)
|
||||
releases: $(unix_releases) $(windows_releases)
|
||||
|
||||
lint:
|
||||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=darwin golangci-lint run ./...
|
||||
GOOS=windows golangci-lint run ./...
|
||||
GOOS=linux golangci-lint run ./...
|
||||
GOOS=linux golangci-lint run ./...
|
||||
GOOS=freebsd golangci-lint run ./...
|
||||
GOOS=openbsd golangci-lint run ./...
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[![Docker Pulls][6]](https://hub.docker.com/r/xjasonlyu/tun2socks)
|
||||
[![Releases][7]](https://github.com/xjasonlyu/tun2socks/releases)
|
||||
|
||||
[1]: https://img.shields.io/github/actions/workflow/status/xjasonlyu/tun2socks/release.yml?branch=main&logo=github
|
||||
[1]: https://img.shields.io/github/actions/workflow/status/xjasonlyu/tun2socks/release.yml?logo=github
|
||||
[2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks?logo=go
|
||||
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks
|
||||
[4]: https://api.codeclimate.com/v1/badges/b5b30239174fc6603aca/maintainability
|
||||
@@ -16,20 +16,19 @@
|
||||
[6]: https://img.shields.io/docker/pulls/xjasonlyu/tun2socks?logo=docker
|
||||
[7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?logo=smartthings
|
||||
|
||||
English | [简体中文](README_ZH.md)
|
||||
|
||||
## Features
|
||||
|
||||
- Proxy Everything: Handle all network traffic of any internet programs sent by the device through a proxy.
|
||||
- Proxy Protocols: HTTP/Socks4/Socks5/Shadowsocks with authentication support for remote connections.
|
||||
- Run Everywhere: Linux/macOS/Windows/FreeBSD/OpenBSD multi-platform support with specific optimization.
|
||||
- Gateway Mode: Act as a layer three gateway to handle network traffic from other devices in the same network.
|
||||
- Gateway Mode: Act as a Layer 3 gateway to handle network traffic from other devices on the same network.
|
||||
- Full IPv6 Support: All functions work in IPv6, tunnel IPv4 connections through IPv6 proxy and vice versa.
|
||||
- Network Stack: Powered by user-space TCP/IP stack from Google container application kernel **[gVisor](https://github.com/google/gvisor)**.
|
||||
- Network Stack: Powered by the user-space TCP/IP stack from Google container application kernel **[gVisor](https://github.com/google/gvisor)**.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
For all scenarios of usage, tun2socks performs best. See [here](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks) for more details.
|
||||
For all scenarios of usage, tun2socks performs best.
|
||||
See [benchmarks](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks) for more details.
|
||||
|
||||

|
||||
|
||||
@@ -47,9 +46,9 @@ Welcome and feel free to ask any questions at [Discussions](https://github.com/x
|
||||
|
||||
## Credits
|
||||
|
||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash) - A rule-based tunnel in Go
|
||||
- [google/gvisor](https://github.com/google/gvisor) - Application Kernel for Containers
|
||||
- [wireguard-go](https://git.zx2c4.com/wireguard-go) - Go Implementation of WireGuard
|
||||
- [wintun](https://git.zx2c4.com/wintun/) - Layer 3 TUN Driver for Windows
|
||||
|
||||
## License
|
||||
|
||||
@@ -57,6 +56,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)
|
||||
|
||||
## 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/actions/workflow/status/xjasonlyu/tun2socks/release.yml?branch=main&logo=github
|
||||
[2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks?logo=go
|
||||
[3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks
|
||||
[4]: https://api.codeclimate.com/v1/badges/b5b30239174fc6603aca/maintainability
|
||||
[5]: https://img.shields.io/github/license/xjasonlyu/tun2socks
|
||||
[6]: https://img.shields.io/docker/pulls/xjasonlyu/tun2socks?logo=docker
|
||||
[7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?logo=smartthings
|
||||
|
||||
[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,30 +1,29 @@
|
||||
package pool
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/bits"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _allocator = NewAllocator()
|
||||
"github.com/xjasonlyu/tun2socks/v2/internal/pool"
|
||||
)
|
||||
|
||||
// Allocator for incoming frames, optimized to prevent overwriting
|
||||
// after zeroing.
|
||||
type Allocator struct {
|
||||
buffers []sync.Pool
|
||||
buffers []*pool.Pool[[]byte]
|
||||
}
|
||||
|
||||
// NewAllocator initiates a []byte allocator for frames less than
|
||||
// 65536 bytes, the waste(memory fragmentation) of space allocation
|
||||
// is guaranteed to be no more than 50%.
|
||||
func NewAllocator() *Allocator {
|
||||
// New initiates a []byte allocator for frames less than 65536 bytes,
|
||||
// the waste(memory fragmentation) of space allocation is guaranteed
|
||||
// to be no more than 50%.
|
||||
func New() *Allocator {
|
||||
alloc := &Allocator{}
|
||||
alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K
|
||||
alloc.buffers = make([]*pool.Pool[[]byte], 17) // 1B -> 64K
|
||||
for k := range alloc.buffers {
|
||||
i := k
|
||||
alloc.buffers[k].New = func() any {
|
||||
alloc.buffers[k] = pool.New(func() []byte {
|
||||
return make([]byte, 1<<uint32(i))
|
||||
}
|
||||
})
|
||||
}
|
||||
return alloc
|
||||
}
|
||||
@@ -37,10 +36,10 @@ func (alloc *Allocator) Get(size int) []byte {
|
||||
|
||||
b := msb(size)
|
||||
if size == 1<<b {
|
||||
return alloc.buffers[b].Get().([]byte)[:size]
|
||||
return alloc.buffers[b].Get()[:size]
|
||||
}
|
||||
|
||||
return alloc.buffers[b+1].Get().([]byte)[:size]
|
||||
return alloc.buffers[b+1].Get()[:size]
|
||||
}
|
||||
|
||||
// Put returns a []byte to pool for future use,
|
||||
@@ -51,8 +50,6 @@ func (alloc *Allocator) Put(buf []byte) error {
|
||||
return errors.New("allocator Put() incorrect buffer size")
|
||||
}
|
||||
|
||||
//lint:ignore SA6002 ignore temporarily
|
||||
//nolint
|
||||
alloc.buffers[b].Put(buf)
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package pool
|
||||
package allocator
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestAllocGet(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
alloc := New()
|
||||
assert.Nil(t, alloc.Get(0))
|
||||
assert.Equal(t, 1, len(alloc.Get(1)))
|
||||
assert.Equal(t, 2, len(alloc.Get(2)))
|
||||
@@ -23,7 +23,7 @@ func TestAllocGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAllocPut(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
alloc := New()
|
||||
assert.NotNil(t, alloc.Put(nil), "put nil misbehavior")
|
||||
assert.NotNil(t, alloc.Put(make([]byte, 3)), "put elem:3 []bytes misbehavior")
|
||||
assert.Nil(t, alloc.Put(make([]byte, 4)), "put elem:4 []bytes misbehavior")
|
||||
@@ -33,7 +33,7 @@ func TestAllocPut(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAllocPutThenGet(t *testing.T) {
|
||||
alloc := NewAllocator()
|
||||
alloc := New()
|
||||
data := alloc.Get(4)
|
||||
_ = alloc.Put(data)
|
||||
newData := alloc.Get(4)
|
||||
@@ -0,0 +1,29 @@
|
||||
// Package buffer provides a pool of []byte.
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/xjasonlyu/tun2socks/v2/buffer/allocator"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxSegmentSize is the largest possible UDP datagram size.
|
||||
MaxSegmentSize = (1 << 16) - 1
|
||||
|
||||
// RelayBufferSize is the default buffer size for TCP relays.
|
||||
// io.Copy default buffer size is 32 KiB, but the maximum packet
|
||||
// size of vmess/shadowsocks is about 16 KiB, so define a buffer
|
||||
// of 20 KiB to reduce the memory of each TCP relay.
|
||||
RelayBufferSize = 20 << 10
|
||||
)
|
||||
|
||||
var _allocator = allocator.New()
|
||||
|
||||
// Get gets a []byte from default allocator with most appropriate cap.
|
||||
func Get(size int) []byte {
|
||||
return _allocator.Get(size)
|
||||
}
|
||||
|
||||
// Put returns a []byte to default allocator for future use.
|
||||
func Put(buf []byte) error {
|
||||
return _allocator.Put(buf)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }}
|
||||
|
||||
func GetBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func PutBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// Package pool provides a pool of []byte.
|
||||
package pool
|
||||
|
||||
// Ref: github.com/Dreamacro/clash/common/pool
|
||||
|
||||
const (
|
||||
// MaxSegmentSize is the largest possible UDP datagram size.
|
||||
MaxSegmentSize = (1 << 16) - 1
|
||||
|
||||
// RelayBufferSize is a buffer of 20 KiB to reduce the memory
|
||||
// of each TCP relay as io.Copy default buffer size is 32 KiB,
|
||||
// but the maximum packet size of vmess/shadowsocks is about
|
||||
// 16 KiB, so define .
|
||||
RelayBufferSize = 20 << 10
|
||||
)
|
||||
|
||||
// Get gets a []byte from default allocator with most appropriate cap.
|
||||
func Get(size int) []byte {
|
||||
return _allocator.Get(size)
|
||||
}
|
||||
|
||||
// Put returns a []byte to default allocator for future use.
|
||||
func Put(buf []byte) error {
|
||||
return _allocator.Put(buf)
|
||||
}
|
||||
@@ -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 {
|
||||
stack.LinkEndpoint
|
||||
|
||||
// Close stops and closes the device.
|
||||
Close() error
|
||||
|
||||
// Name returns the current name of the device.
|
||||
Name() string
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
package fdbased
|
||||
|
||||
const Driver = "fd"
|
||||
|
||||
const defaultMTU = 1500
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !windows
|
||||
//go:build unix
|
||||
|
||||
package fdbased
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||
)
|
||||
|
||||
const defaultMTU = 1500
|
||||
|
||||
type FD struct {
|
||||
stack.LinkEndpoint
|
||||
|
||||
@@ -19,7 +21,7 @@ type FD struct {
|
||||
mtu uint32
|
||||
}
|
||||
|
||||
func Open(name string, mtu uint32) (device.Device, error) {
|
||||
func Open(name string, mtu uint32, offset int) (device.Device, error) {
|
||||
fd, err := strconv.Atoi(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot open fd: %s", name)
|
||||
@@ -27,7 +29,7 @@ func Open(name string, mtu uint32) (device.Device, error) {
|
||||
if mtu == 0 {
|
||||
mtu = defaultMTU
|
||||
}
|
||||
return open(fd, mtu)
|
||||
return open(fd, mtu, offset)
|
||||
}
|
||||
|
||||
func (f *FD) Type() string {
|
||||
@@ -38,8 +40,9 @@ func (f *FD) Name() string {
|
||||
return strconv.Itoa(f.fd)
|
||||
}
|
||||
|
||||
func (f *FD) Close() error {
|
||||
return unix.Close(f.fd)
|
||||
func (f *FD) Close() {
|
||||
defer f.LinkEndpoint.Close()
|
||||
_ = unix.Close(f.fd)
|
||||
}
|
||||
|
||||
var _ device.Device = (*FD)(nil)
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||
)
|
||||
|
||||
func Open(name string, mtu uint32) (device.Device, error) {
|
||||
return nil, errors.New("not supported")
|
||||
func Open(name string, mtu uint32, offset int) (device.Device, error) {
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build (linux && amd64) || (linux && arm64)
|
||||
|
||||
package fdbased
|
||||
|
||||
import (
|
||||
@@ -10,7 +8,7 @@ import (
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||
)
|
||||
|
||||
func open(fd int, mtu uint32) (device.Device, error) {
|
||||
func open(fd int, mtu uint32, offset int) (device.Device, error) {
|
||||
f := &FD{fd: fd, mtu: mtu}
|
||||
|
||||
ep, err := fdbased.New(&fdbased.Options{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(linux && amd64) && !(linux && arm64) && !windows
|
||||
//go:build unix && !linux
|
||||
|
||||
package fdbased
|
||||
|
||||
@@ -10,10 +10,9 @@ import (
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device/iobased"
|
||||
)
|
||||
|
||||
func open(fd int, mtu uint32) (device.Device, error) {
|
||||
func open(fd int, mtu uint32, offset int) (device.Device, error) {
|
||||
f := &FD{fd: fd, mtu: mtu}
|
||||
|
||||
ep, err := iobased.New(os.NewFile(uintptr(fd), f.Name()), mtu, 0)
|
||||
ep, err := iobased.New(os.NewFile(uintptr(fd), f.Name()), mtu, offset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create endpoint: %w", err)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/bufferv2"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
@@ -110,7 +110,7 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
||||
}
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: bufferv2.MakeWithData(data[offset : offset+n]),
|
||||
Payload: buffer.MakeWithData(data[offset : offset+n]),
|
||||
})
|
||||
|
||||
switch header.IPVersion(data[offset:]) {
|
||||
@@ -128,7 +128,7 @@ func (e *Endpoint) dispatchLoop(cancel context.CancelFunc) {
|
||||
func (e *Endpoint) outboundLoop(ctx context.Context) {
|
||||
for {
|
||||
pkt := e.ReadContext(ctx)
|
||||
if pkt.IsNil() {
|
||||
if pkt == nil {
|
||||
break
|
||||
}
|
||||
e.writePacket(pkt)
|
||||
@@ -136,13 +136,13 @@ func (e *Endpoint) outboundLoop(ctx context.Context) {
|
||||
}
|
||||
|
||||
// writePacket writes outbound packets to the io.Writer.
|
||||
func (e *Endpoint) writePacket(pkt stack.PacketBufferPtr) tcpip.Error {
|
||||
func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error {
|
||||
defer pkt.DecRef()
|
||||
|
||||
buf := pkt.ToBuffer()
|
||||
defer buf.Release()
|
||||
if e.offset != 0 {
|
||||
v := bufferv2.NewViewWithData(make([]byte, e.offset))
|
||||
v := buffer.NewViewWithData(make([]byte, e.offset))
|
||||
_ = buf.Prepend(v)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
//go:build (linux && amd64) || (linux && arm64)
|
||||
//go:build linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/rawfile"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/tun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
|
||||
@@ -77,11 +76,11 @@ func (t *TUN) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
return unix.Close(t.fd)
|
||||
func (t *TUN) Close() {
|
||||
defer t.LinkEndpoint.Close()
|
||||
_ = unix.Close(t.fd)
|
||||
}
|
||||
|
||||
// Ref: wireguard tun/tun_linux.go setMTU.
|
||||
func setMTU(name string, n uint32) error {
|
||||
// open datagram socket
|
||||
fd, err := unix.Socket(
|
||||
@@ -95,22 +94,10 @@ func setMTU(name string, n uint32) error {
|
||||
|
||||
defer unix.Close(fd)
|
||||
|
||||
const ifReqSize = unix.IFNAMSIZ + 64
|
||||
|
||||
// do ioctl call
|
||||
var ifr [ifReqSize]byte
|
||||
copy(ifr[:], name)
|
||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = n
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(unix.SIOCSIFMTU),
|
||||
uintptr(unsafe.Pointer(&ifr[0])),
|
||||
)
|
||||
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to set MTU: %w", errno)
|
||||
ifr, err := unix.NewIfreq(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
ifr.SetUint32(n)
|
||||
return unix.IoctlIfreq(fd, unix.SIOCSIFMTU, ifr)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !(linux && amd64) && !(linux && arm64)
|
||||
//go:build !linux
|
||||
|
||||
package tun
|
||||
|
||||
@@ -48,7 +48,7 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
|
||||
forcedMTU = int(t.mtu)
|
||||
}
|
||||
|
||||
nt, err := tun.CreateTUN(t.name, forcedMTU)
|
||||
nt, err := createTUN(t.name, forcedMTU)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create tun: %w", err)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (t *TUN) Name() string {
|
||||
return name
|
||||
}
|
||||
|
||||
func (t *TUN) Close() error {
|
||||
func (t *TUN) Close() {
|
||||
defer t.Endpoint.Close()
|
||||
return t.nt.Close()
|
||||
_ = t.nt.Close()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
//go:build unix
|
||||
//go:build unix && !linux
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
const (
|
||||
offset = 4 /* 4 bytes TUN_PI */
|
||||
defaultMTU = 1500
|
||||
)
|
||||
|
||||
func createTUN(name string, mtu int) (tun.Device, error) {
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
const (
|
||||
offset = 0
|
||||
defaultMTU = 0 /* auto */
|
||||
)
|
||||
|
||||
func createTUN(name string, mtu int) (tun.Device, error) {
|
||||
return tun.CreateTUN(name, mtu)
|
||||
}
|
||||
|
||||
+61
@@ -2,8 +2,11 @@ package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
||||
@@ -56,3 +59,61 @@ func withSpoofing(nicID tcpip.NICID, v bool) option.Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// withMulticastGroups adds a NIC to the given multicast groups.
|
||||
func withMulticastGroups(nicID tcpip.NICID, multicastGroups []netip.Addr) option.Option {
|
||||
return func(s *stack.Stack) error {
|
||||
if len(multicastGroups) == 0 {
|
||||
return nil
|
||||
}
|
||||
// The default NIC of tun2socks is working on Spoofing mode. When the UDP Endpoint
|
||||
// tries to use a non-local address to connect, the network stack will
|
||||
// generate a temporary addressState to build the route, which can be primary
|
||||
// but is ephemeral. Nevertheless, when the UDP Endpoint tries to use a
|
||||
// multicast address to connect, the network stack will select an available
|
||||
// primary addressState to build the route. However, when tun2socks is in the
|
||||
// just-initialized or idle state, there will be no available primary addressState,
|
||||
// and the connect operation will fail. Therefore, we need to add permanent addresses,
|
||||
// e.g. 10.0.0.1/8 and fd00:1/8, to the default NIC, which are only used to build
|
||||
// routes for multicast response and do not affect other connections.
|
||||
//
|
||||
// In fact, for multicast, the sender normally does not expect a response.
|
||||
// So, the ep.net.Connect is unnecessary. If we implement a custom UDP Forwarder
|
||||
// and ForwarderRequest in the future, we can remove these code.
|
||||
s.AddProtocolAddress(
|
||||
nicID,
|
||||
tcpip.ProtocolAddress{
|
||||
Protocol: ipv4.ProtocolNumber,
|
||||
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||
Address: tcpip.AddrFrom4([4]byte{0x0a, 0, 0, 0x01}),
|
||||
PrefixLen: 8,
|
||||
},
|
||||
},
|
||||
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
|
||||
)
|
||||
s.AddProtocolAddress(
|
||||
nicID,
|
||||
tcpip.ProtocolAddress{
|
||||
Protocol: ipv6.ProtocolNumber,
|
||||
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||
Address: tcpip.AddrFrom16([16]byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}),
|
||||
PrefixLen: 8,
|
||||
},
|
||||
},
|
||||
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
|
||||
)
|
||||
for _, multicastGroup := range multicastGroups {
|
||||
var err tcpip.Error
|
||||
switch {
|
||||
case multicastGroup.Is4():
|
||||
err = s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4(multicastGroup.As4()))
|
||||
case multicastGroup.Is6():
|
||||
err = s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16(multicastGroup.As16()))
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("join multicast group: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+10
-2
@@ -1,7 +1,8 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"net/netip"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
@@ -23,6 +24,10 @@ type Config struct {
|
||||
// stack to set transport handlers.
|
||||
TransportHandler adapter.TransportHandler
|
||||
|
||||
// MulticastGroups is used by internal stack to add
|
||||
// nic to given groups.
|
||||
MulticastGroups []netip.Addr
|
||||
|
||||
// Options are supplement options to apply settings
|
||||
// for the internal stack.
|
||||
Options []option.Option
|
||||
@@ -49,7 +54,7 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
|
||||
})
|
||||
|
||||
// Generate unique NIC id.
|
||||
nicID := tcpip.NICID(s.UniqueID())
|
||||
nicID := s.NextNICID()
|
||||
|
||||
opts = append(opts,
|
||||
// Important: We must initiate transport protocol handlers
|
||||
@@ -88,6 +93,9 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
|
||||
// Add default route table for IPv4 and IPv6. This will handle
|
||||
// all incoming ICMP packets.
|
||||
withRouteTable(nicID),
|
||||
|
||||
// Add default NIC to the given multicast groups.
|
||||
withMulticastGroups(nicID, cfg.MulticastGroups),
|
||||
)
|
||||
|
||||
for _, opt := range opts {
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
|
||||
{ /* TCP recv/send buffer size */
|
||||
var ss tcpip.TCPSendBufferSizeRangeOption
|
||||
if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil {
|
||||
ep.SocketOptions().SetReceiveBufferSize(int64(ss.Default), false)
|
||||
ep.SocketOptions().SetSendBufferSize(int64(ss.Default), false)
|
||||
}
|
||||
|
||||
var rs tcpip.TCPReceiveBufferSizeRangeOption
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ func withUDPHandler(handle func(adapter.UDPConn)) option.Option {
|
||||
}
|
||||
|
||||
conn := &udpConn{
|
||||
UDPConn: gonet.NewUDPConn(s, &wq, ep),
|
||||
UDPConn: gonet.NewUDPConn(&wq, ep),
|
||||
id: id,
|
||||
}
|
||||
handle(conn)
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package dns
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -81,6 +81,10 @@ run() {
|
||||
ARGS="$ARGS --tcp-auto-tuning"
|
||||
fi
|
||||
|
||||
if [ -n "$MULTICAST_GROUPS" ]; then
|
||||
ARGS="$ARGS --multicast-groups $MULTICAST_GROUPS"
|
||||
fi
|
||||
|
||||
exec tun2socks \
|
||||
--loglevel "$LOGLEVEL" \
|
||||
--fwmark "$FWMARK" \
|
||||
|
||||
+25
-14
@@ -3,20 +3,20 @@ package engine
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/option"
|
||||
"github.com/xjasonlyu/tun2socks/v2/engine/mirror"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/log"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy"
|
||||
"github.com/xjasonlyu/tun2socks/v2/restapi"
|
||||
@@ -82,22 +82,25 @@ func start() error {
|
||||
func stop() (err error) {
|
||||
_engineMu.Lock()
|
||||
if _defaultDevice != nil {
|
||||
err = _defaultDevice.Close()
|
||||
_defaultDevice.Close()
|
||||
}
|
||||
if _defaultStack != nil {
|
||||
_defaultStack.Close()
|
||||
_defaultStack.Wait()
|
||||
}
|
||||
_engineMu.Unlock()
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func execCommand(cmd string) error {
|
||||
parts := strings.Fields(cmd)
|
||||
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()
|
||||
_, err = exec.Command(parts[0], parts[1:]...).Output()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -106,7 +109,7 @@ func general(k *Key) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.SetLevel(level)
|
||||
log.SetLogger(log.Must(log.NewLeveled(level)))
|
||||
|
||||
if k.Interface != "" {
|
||||
iface, err := net.InterfaceByName(k.Interface)
|
||||
@@ -127,7 +130,7 @@ func general(k *Key) error {
|
||||
if k.UDPTimeout < time.Second {
|
||||
return errors.New("invalid udp timeout value")
|
||||
}
|
||||
tunnel.SetUDPTimeout(k.UDPTimeout)
|
||||
tunnel.T().SetUDPTimeout(k.UDPTimeout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -153,7 +156,7 @@ func restAPI(k *Key) error {
|
||||
|
||||
go func() {
|
||||
if err := restapi.Start(host, token); err != nil {
|
||||
log.Warnf("[RESTAPI] failed to start: %v", err)
|
||||
log.Errorf("[RESTAPI] failed to start: %v", err)
|
||||
}
|
||||
}()
|
||||
log.Infof("[RESTAPI] serve at: %s", u)
|
||||
@@ -170,8 +173,9 @@ func netstack(k *Key) (err error) {
|
||||
}
|
||||
|
||||
if k.TUNPreUp != "" {
|
||||
log.Infof("[TUN] pre-execute command: `%s`", k.TUNPreUp)
|
||||
if preUpErr := execCommand(k.TUNPreUp); preUpErr != nil {
|
||||
log.Warnf("[TUN] failed to pre-execute: %s: %v", k.TUNPreUp, preUpErr)
|
||||
log.Errorf("[TUN] failed to pre-execute: %s: %v", k.TUNPreUp, preUpErr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,20 +183,26 @@ func netstack(k *Key) (err error) {
|
||||
if k.TUNPostUp == "" || err != nil {
|
||||
return
|
||||
}
|
||||
log.Infof("[TUN] post-execute command: `%s`", k.TUNPostUp)
|
||||
if postUpErr := execCommand(k.TUNPostUp); postUpErr != nil {
|
||||
log.Warnf("[TUN] failed to post-execute: %s: %v", k.TUNPostUp, postUpErr)
|
||||
log.Errorf("[TUN] failed to post-execute: %s: %v", k.TUNPostUp, postUpErr)
|
||||
}
|
||||
}()
|
||||
|
||||
if _defaultProxy, err = parseProxy(k.Proxy); err != nil {
|
||||
return
|
||||
}
|
||||
proxy.SetDialer(_defaultProxy)
|
||||
tunnel.T().SetDialer(_defaultProxy)
|
||||
|
||||
if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var multicastGroups []netip.Addr
|
||||
if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var opts []option.Option
|
||||
if k.TCPModerateReceiveBuffer {
|
||||
opts = append(opts, option.WithTCPModerateReceiveBuffer(true))
|
||||
@@ -216,7 +226,8 @@ func netstack(k *Key) (err error) {
|
||||
|
||||
if _defaultStack, err = core.CreateStack(&core.Config{
|
||||
LinkEndpoint: _defaultDevice,
|
||||
TransportHandler: &mirror.Tunnel{},
|
||||
TransportHandler: tunnel.T(),
|
||||
MulticastGroups: multicastGroups,
|
||||
Options: opts,
|
||||
}); err != nil {
|
||||
return
|
||||
|
||||
@@ -13,6 +13,7 @@ type Key struct {
|
||||
TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"`
|
||||
TCPSendBufferSize string `yaml:"tcp-send-buffer-size"`
|
||||
TCPReceiveBufferSize string `yaml:"tcp-receive-buffer-size"`
|
||||
MulticastGroups string `yaml:"multicast-groups"`
|
||||
TUNPreUp string `yaml:"tun-pre-up"`
|
||||
TUNPostUp string `yaml:"tun-post-up"`
|
||||
UDPTimeout time.Duration `yaml:"udp-timeout"`
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package mirror
|
||||
|
||||
import (
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/adapter"
|
||||
"github.com/xjasonlyu/tun2socks/v2/tunnel"
|
||||
)
|
||||
|
||||
var _ adapter.TransportHandler = (*Tunnel)(nil)
|
||||
|
||||
type Tunnel struct{}
|
||||
|
||||
func (*Tunnel) HandleTCP(conn adapter.TCPConn) {
|
||||
tunnel.TCPIn() <- conn
|
||||
}
|
||||
|
||||
func (*Tunnel) HandleUDP(conn adapter.UDPConn) {
|
||||
tunnel.UDPIn() <- conn
|
||||
}
|
||||
+74
-22
@@ -4,9 +4,13 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device/fdbased"
|
||||
"github.com/xjasonlyu/tun2socks/v2/core/device/tun"
|
||||
@@ -51,19 +55,28 @@ func parseDevice(s string, mtu uint32) (device.Device, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := u.Host
|
||||
driver := strings.ToLower(u.Scheme)
|
||||
|
||||
switch driver {
|
||||
case fdbased.Driver:
|
||||
return fdbased.Open(name, mtu)
|
||||
return parseFD(u, mtu)
|
||||
case tun.Driver:
|
||||
return tun.Open(name, mtu)
|
||||
return parseTUN(u, mtu)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported driver: %s", driver)
|
||||
}
|
||||
}
|
||||
|
||||
func parseFD(u *url.URL, mtu uint32) (device.Device, error) {
|
||||
offset := 0
|
||||
// fd offset in ios
|
||||
// https://stackoverflow.com/questions/69260852/ios-network-extension-packet-parsing/69487795#69487795
|
||||
if runtime.GOOS == "ios" {
|
||||
offset = 4
|
||||
}
|
||||
return fdbased.Open(u.Host, mtu, offset)
|
||||
}
|
||||
|
||||
func parseProxy(s string) (proxy.Proxy, error) {
|
||||
if !strings.Contains(s, "://") {
|
||||
s = fmt.Sprintf("%s://%s", proto.Socks5 /* default protocol */, s)
|
||||
@@ -82,48 +95,56 @@ func parseProxy(s string) (proxy.Proxy, error) {
|
||||
case proto.Reject.String():
|
||||
return proxy.NewReject(), nil
|
||||
case proto.HTTP.String():
|
||||
return proxy.NewHTTP(parseHTTP(u))
|
||||
return parseHTTP(u)
|
||||
case proto.Socks4.String():
|
||||
return proxy.NewSocks4(parseSocks4(u))
|
||||
return parseSocks4(u)
|
||||
case proto.Socks5.String():
|
||||
return proxy.NewSocks5(parseSocks5(u))
|
||||
return parseSocks5(u)
|
||||
case proto.Shadowsocks.String():
|
||||
return proxy.NewShadowsocks(parseShadowsocks(u))
|
||||
return parseShadowsocks(u)
|
||||
case proto.Relay.String():
|
||||
return parseRelay(u)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHTTP(u *url.URL) (address, username, password string) {
|
||||
address, username = u.Host, u.User.Username()
|
||||
password, _ = u.User.Password()
|
||||
return
|
||||
func parseHTTP(u *url.URL) (proxy.Proxy, error) {
|
||||
address, username := u.Host, u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
return proxy.NewHTTP(address, username, password)
|
||||
}
|
||||
|
||||
func parseSocks4(u *url.URL) (address, username string) {
|
||||
address, username = u.Host, u.User.Username()
|
||||
return
|
||||
func parseSocks4(u *url.URL) (proxy.Proxy, error) {
|
||||
address, userID := u.Host, u.User.Username()
|
||||
return proxy.NewSocks4(address, userID)
|
||||
}
|
||||
|
||||
func parseSocks5(u *url.URL) (address, username, password string) {
|
||||
address, username = u.Host, u.User.Username()
|
||||
password, _ = u.User.Password()
|
||||
func parseSocks5(u *url.URL) (proxy.Proxy, error) {
|
||||
address, username := u.Host, u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
|
||||
// Socks5 over UDS
|
||||
if address == "" {
|
||||
address = u.Path
|
||||
}
|
||||
return
|
||||
return proxy.NewSocks5(address, username, password)
|
||||
}
|
||||
|
||||
func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost string) {
|
||||
address = u.Host
|
||||
func parseShadowsocks(u *url.URL) (proxy.Proxy, error) {
|
||||
var (
|
||||
address = u.Host
|
||||
method, password string
|
||||
obfsMode, obfsHost string
|
||||
)
|
||||
|
||||
if pass, set := u.User.Password(); set {
|
||||
if ss := u.User.String(); ss == "" {
|
||||
method = "dummy" // none cipher mode
|
||||
} else if pass, set := u.User.Password(); set {
|
||||
method = u.User.Username()
|
||||
password = pass
|
||||
} else {
|
||||
data, _ := base64.RawURLEncoding.DecodeString(u.User.String())
|
||||
data, _ := base64.RawURLEncoding.DecodeString(ss)
|
||||
userInfo := strings.SplitN(string(data), ":", 2)
|
||||
if len(userInfo) == 2 {
|
||||
method = userInfo[0]
|
||||
@@ -148,5 +169,36 @@ func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost
|
||||
}
|
||||
}
|
||||
|
||||
return proxy.NewShadowsocks(address, method, password, obfsMode, obfsHost)
|
||||
}
|
||||
|
||||
func parseRelay(u *url.URL) (proxy.Proxy, error) {
|
||||
address, username := u.Host, u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
|
||||
opts := struct {
|
||||
NoDelay bool
|
||||
}{}
|
||||
if err := schema.NewDecoder().Decode(&opts, u.Query()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proxy.NewRelay(address, username, password, opts.NoDelay)
|
||||
}
|
||||
|
||||
func parseMulticastGroups(s string) (multicastGroups []netip.Addr, _ error) {
|
||||
for _, ip := range strings.Split(s, ",") {
|
||||
if ip = strings.TrimSpace(ip); ip == "" {
|
||||
continue
|
||||
}
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !addr.IsMulticast() {
|
||||
return nil, fmt.Errorf("invalid multicast IP: %s", addr)
|
||||
}
|
||||
multicastGroups = append(multicastGroups, addr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,33 +1,35 @@
|
||||
module github.com/xjasonlyu/tun2socks/v2
|
||||
|
||||
go 1.20
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
go.uber.org/atomic v1.10.0
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/time v0.3.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-gost/relay v0.5.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/schema v1.4.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/time v0.8.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20230401011607-0333bf067633
|
||||
gvisor.dev/gvisor v0.0.0-20241108012608-5901531b85ba
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.31.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
)
|
||||
|
||||
@@ -1,58 +1,63 @@
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8 h1:Ixejp5JscEc866gAvm/l6TFd7BOBvDviKgwb1quWw3g=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.8/go.mod h1:51y4Q6tJoCE7e8TmYXcQRqfoxPfE9Cvn79V6pB6Df7Y=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c=
|
||||
github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
|
||||
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20230401011607-0333bf067633 h1:trSjCkJT04PkOY8WayMOlk9MV6SK2LqvMBRnoi9jy5E=
|
||||
gvisor.dev/gvisor v0.0.0-20230401011607-0333bf067633/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
|
||||
gvisor.dev/gvisor v0.0.0-20241108012608-5901531b85ba h1:ZXzGZ6Md+Vspe9x45C6QlpCdpwYC6nHFtWIfZzTqB0k=
|
||||
gvisor.dev/gvisor v0.0.0-20241108012608-5901531b85ba/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Package pool provides internal pool utilities.
|
||||
package pool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed
|
||||
// object pooling.
|
||||
//
|
||||
// Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will
|
||||
// not be detected, so all internal pool use must take care to only store
|
||||
// pointer types.
|
||||
type Pool[T any] struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// New returns a new [Pool] for T, and will use fn to construct new Ts when
|
||||
// the pool is empty.
|
||||
func New[T any](fn func() T) *Pool[T] {
|
||||
return &Pool[T]{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return fn()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets a T from the pool, or creates a new one if the pool is empty.
|
||||
func (p *Pool[T]) Get() T {
|
||||
return p.pool.Get().(T)
|
||||
}
|
||||
|
||||
// Put returns x into the pool.
|
||||
func (p *Pool[T]) Put(x T) {
|
||||
p.pool.Put(x)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type pooledValue[T any] struct {
|
||||
value T
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
// Disable GC to avoid the victim cache during the test.
|
||||
defer debug.SetGCPercent(debug.SetGCPercent(-1))
|
||||
|
||||
p := New(func() *pooledValue[string] {
|
||||
return &pooledValue[string]{
|
||||
value: "new",
|
||||
}
|
||||
})
|
||||
|
||||
// Probabilistically, 75% of sync.Pool.Put calls will succeed when -race
|
||||
// is enabled (see ref below); attempt to make this quasi-deterministic by
|
||||
// brute force (i.e., put significantly more objects in the pool than we
|
||||
// will need for the test) in order to avoid testing without race enabled.
|
||||
//
|
||||
// ref: https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/sync/pool.go;l=100-103
|
||||
for i := 0; i < 1_000; i++ {
|
||||
p.Put(&pooledValue[string]{
|
||||
value: t.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure that we always get the expected value. Note that this must only
|
||||
// run a fraction of the number of times that Put is called above.
|
||||
for i := 0; i < 10; i++ {
|
||||
func() {
|
||||
x := p.Get()
|
||||
defer p.Put(x)
|
||||
require.Equal(t, t.Name(), x.value)
|
||||
}()
|
||||
}
|
||||
|
||||
// Depool all objects that might be in the pool to ensure that it's empty.
|
||||
for i := 0; i < 1_000; i++ {
|
||||
p.Get()
|
||||
}
|
||||
|
||||
// Now that the pool is empty, it should use the value specified in the
|
||||
// underlying sync.Pool.New func.
|
||||
require.Equal(t, "new", p.Get().value)
|
||||
}
|
||||
|
||||
func TestNew_Race(t *testing.T) {
|
||||
p := New(func() *pooledValue[int] {
|
||||
return &pooledValue[int]{
|
||||
value: -1,
|
||||
}
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
// Run a number of goroutines that read and write pool object fields to
|
||||
// tease out races.
|
||||
for i := 0; i < 1_000; i++ {
|
||||
i := i
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
x := p.Get()
|
||||
defer p.Put(x)
|
||||
|
||||
// Must both read and write the field.
|
||||
if n := x.value; n >= -1 {
|
||||
x.value = i
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
//go:build debug
|
||||
|
||||
package version
|
||||
|
||||
func init() {
|
||||
_debug = true
|
||||
}
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
const Name = "tun2socks"
|
||||
|
||||
var (
|
||||
_debug = false
|
||||
|
||||
// Version can be set at link time by executing
|
||||
// the command: `git describe --abbrev=0 --tags HEAD`
|
||||
Version string
|
||||
@@ -20,19 +18,8 @@ var (
|
||||
GitCommit string
|
||||
)
|
||||
|
||||
func versionize(s string) string {
|
||||
return strings.TrimPrefix(s, "v")
|
||||
}
|
||||
|
||||
func Debug() bool {
|
||||
return _debug
|
||||
}
|
||||
|
||||
func String() string {
|
||||
if !Debug() {
|
||||
return fmt.Sprintf("%s-%s", Name, versionize(Version))
|
||||
}
|
||||
return fmt.Sprintf("%s-%s (debug)", Name, versionize(Version))
|
||||
return fmt.Sprintf("%s-%s", Name, strings.TrimPrefix(Version, "v"))
|
||||
}
|
||||
|
||||
func BuildString() string {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// Package log is a thin wrapper based on "go.uber.org/zap".
|
||||
package log
|
||||
+16
-13
@@ -1,7 +1,6 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -9,26 +8,30 @@ import (
|
||||
glog "gvisor.dev/gvisor/pkg/log"
|
||||
)
|
||||
|
||||
var _globalE = &emitter{}
|
||||
|
||||
func init() {
|
||||
EnableStackLog(true)
|
||||
glog.SetTarget(_globalE)
|
||||
}
|
||||
|
||||
func EnableStackLog(v bool) {
|
||||
if v {
|
||||
glog.SetTarget(&emitter{}) // built-in logger
|
||||
} else {
|
||||
glog.SetTarget(&glog.Writer{Next: io.Discard})
|
||||
}
|
||||
type emitter struct {
|
||||
logger *SugaredLogger
|
||||
}
|
||||
|
||||
type emitter struct{}
|
||||
func (e *emitter) setLogger(logger *SugaredLogger) {
|
||||
e.logger = logger.WithOptions(pkgCallerSkip)
|
||||
}
|
||||
|
||||
func (emitter) Emit(depth int, level glog.Level, _ time.Time, format string, args ...any) {
|
||||
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 (*gonet.TCPConn).RemoteAddr() warning: `ep.GetRemoteAddress() failed`.
|
||||
if line == 457 && strings.HasSuffix(file, "/pkg/tcpip/adapters/gonet/gonet.go") {
|
||||
// Ignore: gvisor.dev/gvisor/pkg/tcpip/adapters/gonet/gonet.go:457
|
||||
if line == 457 && strings.HasSuffix(file, "gonet/gonet.go") {
|
||||
return
|
||||
}
|
||||
}
|
||||
logf(Level(level)+2, "[STACK] "+format, args...)
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
type Level uint32
|
||||
// Level is an alias for zapcore.Level.
|
||||
type Level = zapcore.Level
|
||||
|
||||
// Levels are aliases for Level.
|
||||
const (
|
||||
SilentLevel Level = iota
|
||||
ErrorLevel
|
||||
WarnLevel
|
||||
InfoLevel
|
||||
DebugLevel
|
||||
DebugLevel = zapcore.DebugLevel
|
||||
InfoLevel = zapcore.InfoLevel
|
||||
WarnLevel = zapcore.WarnLevel
|
||||
ErrorLevel = zapcore.ErrorLevel
|
||||
DPanicLevel = zapcore.DPanicLevel
|
||||
PanicLevel = zapcore.PanicLevel
|
||||
FatalLevel = zapcore.FatalLevel
|
||||
InvalidLevel = zapcore.InvalidLevel
|
||||
SilentLevel = InvalidLevel + 1
|
||||
)
|
||||
|
||||
// UnmarshalJSON deserialize Level with json
|
||||
func (level *Level) UnmarshalJSON(data []byte) error {
|
||||
var lvl string
|
||||
if err := json.Unmarshal(data, &lvl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l, err := ParseLevel(lvl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*level = l
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialize Level with json
|
||||
func (level Level) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(level.String())
|
||||
}
|
||||
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case SilentLevel:
|
||||
return "silent"
|
||||
default:
|
||||
return fmt.Sprintf("not a valid level %d", level)
|
||||
}
|
||||
}
|
||||
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "silent":
|
||||
// ParseLevel is a thin wrapper for zapcore.ParseLevel.
|
||||
func ParseLevel(text string) (Level, error) {
|
||||
switch text {
|
||||
case "silent", "SILENT":
|
||||
return SilentLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
default:
|
||||
return Level(0), fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
return zapcore.ParseLevel(text)
|
||||
}
|
||||
}
|
||||
|
||||
+58
-45
@@ -1,58 +1,71 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// _defaultLevel is package default logging level.
|
||||
var _defaultLevel = atomic.NewUint32(uint32(InfoLevel))
|
||||
// global Logger and SugaredLogger.
|
||||
var (
|
||||
_globalMu sync.RWMutex
|
||||
_globalL *Logger
|
||||
_globalS *SugaredLogger
|
||||
)
|
||||
|
||||
func init() {
|
||||
logrus.SetOutput(os.Stdout)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
SetLogger(zap.Must(zap.NewProduction()))
|
||||
}
|
||||
|
||||
func SetLevel(level Level) {
|
||||
_defaultLevel.Store(uint32(level))
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...any) {
|
||||
logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...any) {
|
||||
logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...any) {
|
||||
logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...any) {
|
||||
logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...any) {
|
||||
logrus.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func logf(level Level, format string, args ...any) {
|
||||
event := newEvent(level, format, args...)
|
||||
if uint32(event.Level) > _defaultLevel.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
switch level {
|
||||
func NewLeveled(l Level, options ...Option) (*Logger, error) {
|
||||
switch l {
|
||||
case SilentLevel:
|
||||
return zap.NewNop(), nil
|
||||
case DebugLevel:
|
||||
logrus.WithTime(event.Time).Debugln(event.Message)
|
||||
case InfoLevel:
|
||||
logrus.WithTime(event.Time).Infoln(event.Message)
|
||||
case WarnLevel:
|
||||
logrus.WithTime(event.Time).Warnln(event.Message)
|
||||
case ErrorLevel:
|
||||
logrus.WithTime(event.Time).Errorln(event.Message)
|
||||
return zap.NewDevelopment(options...)
|
||||
case InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel:
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.Level.SetLevel(l)
|
||||
return cfg.Build(options...)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid level: %s", l)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogger sets the global Logger and SugaredLogger.
|
||||
func SetLogger(logger *Logger) {
|
||||
_globalMu.Lock()
|
||||
defer _globalMu.Unlock()
|
||||
// apply pkgCallerSkip to global loggers.
|
||||
_globalL = logger.WithOptions(pkgCallerSkip)
|
||||
_globalS = _globalL.Sugar()
|
||||
_globalE.setLogger(_globalS)
|
||||
}
|
||||
|
||||
func logf(lvl Level, template string, args ...any) {
|
||||
_globalMu.RLock()
|
||||
s := _globalS
|
||||
_globalMu.RUnlock()
|
||||
s.Logf(lvl, template, args...)
|
||||
}
|
||||
|
||||
func Debugf(template string, args ...any) {
|
||||
logf(DebugLevel, template, args...)
|
||||
}
|
||||
|
||||
func Infof(template string, args ...any) {
|
||||
logf(InfoLevel, template, args...)
|
||||
}
|
||||
|
||||
func Warnf(template string, args ...any) {
|
||||
logf(WarnLevel, template, args...)
|
||||
}
|
||||
|
||||
func Errorf(template string, args ...any) {
|
||||
logf(ErrorLevel, template, args...)
|
||||
}
|
||||
|
||||
func Fatalf(template string, args ...any) {
|
||||
logf(FatalLevel, template, args...)
|
||||
}
|
||||
|
||||
+22
@@ -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)
|
||||
@@ -30,12 +30,13 @@ func init() {
|
||||
flag.StringVar(&configFile, "config", "", "YAML format configuration file")
|
||||
flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name")
|
||||
flag.StringVar(&key.Interface, "interface", "", "Use network INTERFACE (Linux/MacOS only)")
|
||||
flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warning|error|silent]")
|
||||
flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warn|error|silent]")
|
||||
flag.StringVar(&key.Proxy, "proxy", "", "Use this proxy [protocol://]host[:port]")
|
||||
flag.StringVar(&key.RestAPI, "restapi", "", "HTTP statistic server listen address")
|
||||
flag.StringVar(&key.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack")
|
||||
flag.StringVar(&key.TCPReceiveBufferSize, "tcp-rcvbuf", "", "Set TCP receive buffer size for netstack")
|
||||
flag.BoolVar(&key.TCPModerateReceiveBuffer, "tcp-auto-tuning", false, "Enable TCP receive buffer auto-tuning")
|
||||
flag.StringVar(&key.MulticastGroups, "multicast-groups", "", "Set multicast groups, separated by commas")
|
||||
flag.StringVar(&key.TUNPreUp, "tun-pre-up", "", "Execute a command before TUN device setup")
|
||||
flag.StringVar(&key.TUNPostUp, "tun-post-up", "", "Execute a command after TUN device setup")
|
||||
flag.BoolVar(&versionFlag, "version", false, "Show version and then quit")
|
||||
|
||||
+22
-20
@@ -2,26 +2,34 @@ package metadata
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// Metadata contains metadata of transport protocol sessions.
|
||||
type Metadata struct {
|
||||
Network Network `json:"network"`
|
||||
SrcIP net.IP `json:"sourceIP"`
|
||||
MidIP net.IP `json:"dialerIP"`
|
||||
DstIP net.IP `json:"destinationIP"`
|
||||
SrcPort uint16 `json:"sourcePort"`
|
||||
MidPort uint16 `json:"dialerPort"`
|
||||
DstPort uint16 `json:"destinationPort"`
|
||||
Network Network `json:"network"`
|
||||
SrcIP netip.Addr `json:"sourceIP"`
|
||||
MidIP netip.Addr `json:"dialerIP"`
|
||||
DstIP netip.Addr `json:"destinationIP"`
|
||||
SrcPort uint16 `json:"sourcePort"`
|
||||
MidPort uint16 `json:"dialerPort"`
|
||||
DstPort uint16 `json:"destinationPort"`
|
||||
}
|
||||
|
||||
func (m *Metadata) DestinationAddrPort() netip.AddrPort {
|
||||
return netip.AddrPortFrom(m.DstIP, m.DstPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) DestinationAddress() string {
|
||||
return net.JoinHostPort(m.DstIP.String(), strconv.FormatUint(uint64(m.DstPort), 10))
|
||||
return m.DestinationAddrPort().String()
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceAddrPort() netip.AddrPort {
|
||||
return netip.AddrPortFrom(m.SrcIP, m.SrcPort)
|
||||
}
|
||||
|
||||
func (m *Metadata) SourceAddress() string {
|
||||
return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10))
|
||||
return m.SourceAddrPort().String()
|
||||
}
|
||||
|
||||
func (m *Metadata) Addr() net.Addr {
|
||||
@@ -29,23 +37,17 @@ func (m *Metadata) Addr() net.Addr {
|
||||
}
|
||||
|
||||
func (m *Metadata) TCPAddr() *net.TCPAddr {
|
||||
if m.Network != TCP || m.DstIP == nil {
|
||||
if m.Network != TCP || !m.DstIP.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return &net.TCPAddr{
|
||||
IP: m.DstIP,
|
||||
Port: int(m.DstPort),
|
||||
}
|
||||
return net.TCPAddrFromAddrPort(m.DestinationAddrPort())
|
||||
}
|
||||
|
||||
func (m *Metadata) UDPAddr() *net.UDPAddr {
|
||||
if m.Network != UDP || m.DstIP == nil {
|
||||
if m.Network != UDP || !m.DstIP.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return &net.UDPAddr{
|
||||
IP: m.DstIP,
|
||||
Port: int(m.DstPort),
|
||||
}
|
||||
return net.UDPAddrFromAddrPort(m.DestinationAddrPort())
|
||||
}
|
||||
|
||||
// Addr implements the net.Addr interface.
|
||||
|
||||
+2
-2
@@ -25,9 +25,9 @@ func (b *Base) Proto() proto.Proto {
|
||||
}
|
||||
|
||||
func (b *Base) DialContext(context.Context, *M.Metadata) (net.Conn, error) {
|
||||
return nil, errors.New("not supported")
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
||||
func (b *Base) DialUDP(*M.Metadata) (net.PacketConn, error) {
|
||||
return nil, errors.New("not supported")
|
||||
return nil, errors.ErrUnsupported
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
||||
)
|
||||
|
||||
+23
-20
@@ -1,7 +1,5 @@
|
||||
package proxy
|
||||
|
||||
// Ref: https://github.com/Dreamacro/clash/adapter/outbound/http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
@@ -13,7 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
||||
)
|
||||
@@ -43,7 +41,9 @@ func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Con
|
||||
}
|
||||
setKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
err = h.shakeHand(metadata, c)
|
||||
return
|
||||
@@ -63,9 +63,7 @@ func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error {
|
||||
}
|
||||
|
||||
if h.user != "" && h.pass != "" {
|
||||
auth := h.user + ":" + h.pass
|
||||
req.Header.Add("Proxy-Authorization",
|
||||
fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(auth))))
|
||||
req.Header.Set("Proxy-Authorization", fmt.Sprintf("Basic %s", basicAuth(h.user, h.pass)))
|
||||
}
|
||||
|
||||
if err := req.Write(rw); err != nil {
|
||||
@@ -77,21 +75,26 @@ func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusProxyAuthRequired {
|
||||
return errors.New("HTTP need auth")
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusMethodNotAllowed {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusInternalServerError {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
return fmt.Errorf("HTTP connect status code: %d", resp.StatusCode)
|
||||
// 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))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
Socks4
|
||||
Socks5
|
||||
Shadowsocks
|
||||
Relay
|
||||
)
|
||||
|
||||
type Proto uint8
|
||||
@@ -27,6 +28,8 @@ func (proto Proto) String() string {
|
||||
return "socks5"
|
||||
case Shadowsocks:
|
||||
return "ss"
|
||||
case Relay:
|
||||
return "relay"
|
||||
default:
|
||||
return fmt.Sprintf("proto(%d)", proto)
|
||||
}
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/relay"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/buffer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
||||
)
|
||||
|
||||
var _ Proxy = (*Relay)(nil)
|
||||
|
||||
type Relay struct {
|
||||
*Base
|
||||
|
||||
user string
|
||||
pass string
|
||||
|
||||
noDelay bool
|
||||
}
|
||||
|
||||
func NewRelay(addr, user, pass string, noDelay bool) (*Relay, error) {
|
||||
return &Relay{
|
||||
Base: &Base{
|
||||
addr: addr,
|
||||
proto: proto.Relay,
|
||||
},
|
||||
user: user,
|
||||
pass: pass,
|
||||
noDelay: noDelay,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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(), 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.Addr())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect to %s: %w", rl.Addr(), err)
|
||||
}
|
||||
setKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
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 := buffer.Get(dLen)
|
||||
defer buffer.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.Is4() {
|
||||
af.AType = relay.AddrIPv4
|
||||
} else {
|
||||
af.AType = relay.AddrIPv6
|
||||
}
|
||||
return af
|
||||
}
|
||||
@@ -6,12 +6,11 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/go-shadowsocks2/core"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||
obfs "github.com/xjasonlyu/tun2socks/v2/component/simple-obfs"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/core"
|
||||
obfs "github.com/xjasonlyu/tun2socks/v2/transport/simple-obfs"
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
|
||||
)
|
||||
|
||||
@@ -50,7 +49,9 @@ func (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c
|
||||
}
|
||||
setKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
switch ss.obfsMode {
|
||||
case "tls":
|
||||
|
||||
+4
-2
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/socks4"
|
||||
@@ -36,7 +36,9 @@ func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.
|
||||
}
|
||||
setKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID)
|
||||
return
|
||||
|
||||
+5
-3
@@ -7,7 +7,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/component/dialer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/dialer"
|
||||
M "github.com/xjasonlyu/tun2socks/v2/metadata"
|
||||
"github.com/xjasonlyu/tun2socks/v2/proxy/proto"
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/socks5"
|
||||
@@ -49,7 +49,9 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.
|
||||
}
|
||||
setKeepAlive(c)
|
||||
|
||||
defer safeConnClose(c, err)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
@@ -65,7 +67,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.
|
||||
|
||||
func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) {
|
||||
if ss.unix {
|
||||
return nil, errors.New("not supported when unix domain socket is enabled")
|
||||
return nil, fmt.Errorf("%w when unix domain socket is enabled", errors.ErrUnsupported)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout)
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
const defaultInterval = 1000
|
||||
|
||||
func init() {
|
||||
registerMountPoint("/connections", connectionRouter())
|
||||
registerEndpoint("/connections", connectionRouter())
|
||||
}
|
||||
|
||||
func connectionRouter() http.Handler {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerMountPoint("/debug/pprof/", pprofRouter())
|
||||
registerEndpoint("/debug/pprof/", pprofRouter())
|
||||
}
|
||||
|
||||
func pprofRouter() http.Handler {
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ func SetStatsFunc(s func() tcpip.Stats) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerMountPoint("/netstats", http.HandlerFunc(getNetStats))
|
||||
registerEndpoint("/netstats", http.HandlerFunc(getNetStats))
|
||||
}
|
||||
|
||||
func getNetStats(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
+4
-64
@@ -1,7 +1,5 @@
|
||||
package restapi
|
||||
|
||||
// Ref: github.com/Dreamacro/clash/hub/route
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
@@ -16,7 +14,6 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
V "github.com/xjasonlyu/tun2socks/v2/internal/version"
|
||||
"github.com/xjasonlyu/tun2socks/v2/log"
|
||||
"github.com/xjasonlyu/tun2socks/v2/tunnel/statistic"
|
||||
)
|
||||
|
||||
@@ -27,11 +24,11 @@ var (
|
||||
},
|
||||
}
|
||||
|
||||
_mountPoints = make(map[string]http.Handler)
|
||||
_endpoints = make(map[string]http.Handler)
|
||||
)
|
||||
|
||||
func registerMountPoint(pattern string, handler http.Handler) {
|
||||
_mountPoints[pattern] = handler
|
||||
func registerEndpoint(pattern string, handler http.Handler) {
|
||||
_endpoints[pattern] = handler
|
||||
}
|
||||
|
||||
func Start(addr, token string) error {
|
||||
@@ -48,11 +45,10 @@ func Start(addr, token string) error {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(authenticator(token))
|
||||
r.Get("/", hello)
|
||||
r.Get("/logs", getLogs)
|
||||
r.Get("/traffic", traffic)
|
||||
r.Get("/version", version)
|
||||
// attach HTTP handlers
|
||||
for pattern, handler := range _mountPoints {
|
||||
for pattern, handler := range _endpoints {
|
||||
r.Mount(pattern, handler)
|
||||
}
|
||||
})
|
||||
@@ -105,61 +101,6 @@ func authenticator(token string) func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
func getLogs(w http.ResponseWriter, r *http.Request) {
|
||||
lvl := r.URL.Query().Get("level")
|
||||
if lvl == "" {
|
||||
lvl = "info" /* default */
|
||||
}
|
||||
|
||||
level, err := log.ParseLevel(lvl)
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, ErrBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var wsConn *websocket.Conn
|
||||
if websocket.IsWebSocketUpgrade(r) {
|
||||
wsConn, err = _upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
render.Status(r, http.StatusOK)
|
||||
}
|
||||
|
||||
sub := log.Subscribe()
|
||||
defer log.UnSubscribe(sub)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for elm := range sub {
|
||||
buf.Reset()
|
||||
|
||||
e := elm.(*log.Event)
|
||||
if e.Level > level {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = json.NewEncoder(buf).Encode(e); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if wsConn == nil {
|
||||
_, err = w.Write(buf.Bytes())
|
||||
w.(http.Flusher).Flush()
|
||||
} else {
|
||||
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traffic(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
err error
|
||||
@@ -212,7 +153,6 @@ func version(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, render.M{
|
||||
"version": V.Version,
|
||||
"commit": V.GitCommit,
|
||||
"debug": V.Debug(),
|
||||
"modules": V.Info(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package bufferpool
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/internal/pool"
|
||||
)
|
||||
|
||||
const _size = 1024 // by default, create 1 KiB buffers
|
||||
|
||||
var _pool = pool.New(func() *bytes.Buffer {
|
||||
return bytes.NewBuffer(make([]byte, 0, _size))
|
||||
})
|
||||
|
||||
func Get() *bytes.Buffer {
|
||||
buf := _pool.Get()
|
||||
buf.Reset()
|
||||
return buf
|
||||
}
|
||||
|
||||
func Put(b *bytes.Buffer) {
|
||||
_pool.Put(b)
|
||||
}
|
||||
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/buffer"
|
||||
)
|
||||
|
||||
// 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 := buffer.Get(maxPacketSize)
|
||||
defer buffer.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/buffer"
|
||||
)
|
||||
|
||||
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 := buffer.Get(bufSize)
|
||||
defer buffer.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 := buffer.Get(bufSize)
|
||||
defer buffer.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 := buffer.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) {
|
||||
buffer.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 = buffer.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) {
|
||||
buffer.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/buffer"
|
||||
)
|
||||
|
||||
// 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 := buffer.Get(maxPacketSize)
|
||||
defer buffer.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
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/common/pool"
|
||||
"github.com/xjasonlyu/tun2socks/v2/buffer"
|
||||
)
|
||||
|
||||
// HTTPObfs is shadowsocks http simple-obfs implementation
|
||||
@@ -29,22 +29,22 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
||||
n := copy(b, ho.buf[ho.offset:])
|
||||
ho.offset += n
|
||||
if ho.offset == len(ho.buf) {
|
||||
pool.Put(ho.buf)
|
||||
buffer.Put(ho.buf)
|
||||
ho.buf = nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if ho.firstResponse {
|
||||
buf := pool.Get(pool.RelayBufferSize)
|
||||
buf := buffer.Get(buffer.RelayBufferSize)
|
||||
n, err := ho.Conn.Read(buf)
|
||||
if err != nil {
|
||||
pool.Put(buf)
|
||||
buffer.Put(buf)
|
||||
return 0, err
|
||||
}
|
||||
idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
pool.Put(buf)
|
||||
buffer.Put(buf)
|
||||
return 0, io.EOF
|
||||
}
|
||||
ho.firstResponse = false
|
||||
@@ -54,7 +54,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
||||
ho.buf = buf[:idx+4+length]
|
||||
ho.offset = idx + 4 + n
|
||||
} else {
|
||||
pool.Put(buf)
|
||||
buffer.Put(buf)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
||||
if ho.firstRequest {
|
||||
randBytes := make([]byte, 16)
|
||||
rand.Read(randBytes)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
||||
req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
||||
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", mRand.Int()%54, mRand.Int()%2))
|
||||
req.Header.Set("Upgrade", "websocket")
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
@@ -1,4 +1,2 @@
|
||||
// Package obfs provides obfuscation functionality for Shadowsocks protocol.
|
||||
package obfs
|
||||
|
||||
// Ref: github.com/Dreamacro/clash/component/simple-obfs
|
||||
@@ -1,14 +1,14 @@
|
||||
package obfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/common/pool"
|
||||
"github.com/xjasonlyu/tun2socks/v2/buffer"
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,12 +25,12 @@ type TLSObfs struct {
|
||||
}
|
||||
|
||||
func (to *TLSObfs) read(b []byte, discardN int) (int, error) {
|
||||
buf := pool.Get(discardN)
|
||||
buf := buffer.Get(discardN)
|
||||
_, err := io.ReadFull(to.Conn, buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
pool.Put(buf)
|
||||
buffer.Put(buf)
|
||||
|
||||
sizeBuf := make([]byte, 2)
|
||||
_, err = io.ReadFull(to.Conn, sizeBuf)
|
||||
@@ -99,8 +99,8 @@ func (to *TLSObfs) write(b []byte) (int, error) {
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
buf := bufferpool.Get()
|
||||
defer bufferpool.Put(buf)
|
||||
buf.Write([]byte{0x17, 0x03, 0x03})
|
||||
binary.Write(buf, binary.BigEndian, uint16(len(b)))
|
||||
buf.Write(b)
|
||||
@@ -124,7 +124,8 @@ func makeClientHelloMsg(data []byte, server string) []byte {
|
||||
rand.Read(random)
|
||||
rand.Read(sessionID)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
buf := bufferpool.Get()
|
||||
defer bufferpool.Put(buf)
|
||||
|
||||
// handshake, TLS 1.0 version, length
|
||||
buf.WriteByte(22)
|
||||
+25
-19
@@ -2,12 +2,14 @@
|
||||
package socks4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool"
|
||||
)
|
||||
|
||||
const Version = 0x04
|
||||
@@ -31,6 +33,7 @@ const (
|
||||
var (
|
||||
errVersionMismatched = errors.New("version code mismatched")
|
||||
errIPv6NotSupported = errors.New("IPv6 not supported")
|
||||
errCmdNotSupported = errors.New("command not supported")
|
||||
|
||||
ErrRequestRejected = errors.New("request rejected or failed")
|
||||
ErrRequestIdentdFailed = errors.New("request rejected because SOCKS server cannot connect to identd on the client")
|
||||
@@ -39,6 +42,10 @@ var (
|
||||
)
|
||||
|
||||
func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
|
||||
if command == CmdBind {
|
||||
return errCmdNotSupported
|
||||
}
|
||||
|
||||
var (
|
||||
host string
|
||||
port uint16
|
||||
@@ -47,24 +54,27 @@ func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID stri
|
||||
return err
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil /* HOST */ {
|
||||
ip = net.IPv4(0, 0, 0, 1)
|
||||
} else if ip.To4() == nil /* IPv6 */ {
|
||||
ip, _ := netip.ParseAddr(host)
|
||||
switch {
|
||||
case !ip.IsValid(): /* HOST */
|
||||
ip = netip.AddrFrom4([4]byte{0, 0, 0, 1})
|
||||
case ip.Is4In6(): /* IPv4-mapped IPv6 */
|
||||
ip = netip.AddrFrom4(ip.As4())
|
||||
case ip.Is4(): /* IPv4 */
|
||||
case ip.Is6(): /* IPv6 */
|
||||
return errIPv6NotSupported
|
||||
}
|
||||
|
||||
dstIP := /* [4]byte */ ip.To4()
|
||||
|
||||
req := &bytes.Buffer{}
|
||||
req := bufferpool.Get()
|
||||
defer bufferpool.Put(req)
|
||||
req.WriteByte(Version)
|
||||
req.WriteByte(command)
|
||||
binary.Write(req, binary.BigEndian, port)
|
||||
req.Write(dstIP)
|
||||
_ = binary.Write(req, binary.BigEndian, port)
|
||||
req.Write(ip.AsSlice())
|
||||
req.WriteString(userID)
|
||||
req.WriteByte(0) /* NULL */
|
||||
req.WriteByte(0x00) /* NULL */
|
||||
|
||||
if isReservedIP(dstIP) /* SOCKS4A */ {
|
||||
if isReservedIP(ip) /* SOCKS4A */ {
|
||||
req.WriteString(host)
|
||||
req.WriteByte(0) /* NULL */
|
||||
}
|
||||
@@ -103,13 +113,9 @@ func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID stri
|
||||
// Internet Assigned Numbers Authority -- such an address is inadmissible
|
||||
// as a destination IP address and thus should never occur if the client
|
||||
// can resolve the domain name.)
|
||||
func isReservedIP(ip net.IP) bool {
|
||||
subnet := net.IPNet{
|
||||
IP: net.IPv4zero,
|
||||
Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
|
||||
}
|
||||
|
||||
return !ip.IsUnspecified() && subnet.Contains(ip)
|
||||
func isReservedIP(ip netip.Addr) bool {
|
||||
prefix := netip.PrefixFrom(netip.IPv4Unspecified(), 24)
|
||||
return !ip.IsUnspecified() && prefix.Contains(ip)
|
||||
}
|
||||
|
||||
func splitHostPort(addr string) (string, uint16, error) {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package socks4
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsReservedIP(t *testing.T) {
|
||||
reservedIPs := []string{
|
||||
"0.0.0.1",
|
||||
"0.0.0.2",
|
||||
"0.0.0.50",
|
||||
"0.0.0.100",
|
||||
"0.0.0.255",
|
||||
}
|
||||
for _, ip := range reservedIPs {
|
||||
assert.True(t, isReservedIP(netip.MustParseAddr(ip)))
|
||||
}
|
||||
|
||||
unReservedIPs := []string{
|
||||
"0.0.0.0",
|
||||
"0.0.1.0",
|
||||
"1.1.1.1",
|
||||
"10.0.0.0",
|
||||
"255.255.255.255",
|
||||
}
|
||||
for _, ip := range unReservedIPs {
|
||||
assert.False(t, isReservedIP(netip.MustParseAddr(ip)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitHostPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
addr string
|
||||
host string
|
||||
port uint16
|
||||
}{
|
||||
{
|
||||
"1.1.1.1:80",
|
||||
"1.1.1.1",
|
||||
80,
|
||||
},
|
||||
{
|
||||
"1.1.1.1:0",
|
||||
"1.1.1.1",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"0.0.0.0:0",
|
||||
"0.0.0.0",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"[::1]:443",
|
||||
"::1",
|
||||
443,
|
||||
},
|
||||
{
|
||||
"example.com:80",
|
||||
"example.com",
|
||||
80,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
host, port, err := splitHostPort(tt.addr)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.host, host)
|
||||
assert.Equal(t, tt.port, port)
|
||||
}
|
||||
|
||||
addrs := []string{
|
||||
"1.1.1.1:-80",
|
||||
"1.1.1.1:abcd",
|
||||
"::1:80",
|
||||
"[::1]",
|
||||
"example.com",
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
_, _, err := splitHostPort(addr)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
+54
-30
@@ -1,8 +1,6 @@
|
||||
// Package socks5 provides SOCKS5 client functionalities.
|
||||
package socks5
|
||||
|
||||
// Ref: github.com/Dreamacro/clash/component/socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
@@ -10,7 +8,19 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool"
|
||||
)
|
||||
|
||||
// AuthMethod is the authentication method as defined in RFC 1928 section 3.
|
||||
type AuthMethod = uint8
|
||||
|
||||
// SOCKS authentication methods as defined in RFC 1928 section 3.
|
||||
const (
|
||||
MethodNoAuth AuthMethod = 0x00
|
||||
MethodUserPass AuthMethod = 0x02
|
||||
)
|
||||
|
||||
// Version is the protocol version as defined in RFC 1928 section 4.
|
||||
@@ -72,7 +82,7 @@ func (r Reply) String() string {
|
||||
case 0x08:
|
||||
return "address type not supported"
|
||||
default:
|
||||
return fmt.Sprintf("unassigned <%#02x>", r)
|
||||
return fmt.Sprintf("unassigned <%#02x>", uint8(r))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +174,9 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
|
||||
|
||||
var method uint8
|
||||
if user != nil {
|
||||
method = 0x02 /* USERNAME/PASSWORD */
|
||||
method = MethodUserPass /* USERNAME/PASSWORD */
|
||||
} else {
|
||||
method = 0x00 /* NO AUTHENTICATION REQUIRED */
|
||||
method = MethodNoAuth /* NO AUTHENTICATION REQUIRED */
|
||||
}
|
||||
|
||||
// VER, NMETHODS, METHODS
|
||||
@@ -183,23 +193,30 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
|
||||
return nil, errors.New("socks version mismatched")
|
||||
}
|
||||
|
||||
if buf[1] == 0x02 /* USERNAME/PASSWORD */ {
|
||||
if buf[1] == MethodUserPass /* USERNAME/PASSWORD */ {
|
||||
if user == nil {
|
||||
return nil, errors.New("auth required")
|
||||
}
|
||||
|
||||
// password protocol version
|
||||
authMsg := &bytes.Buffer{}
|
||||
authMsg.WriteByte(0x01 /* VER */)
|
||||
authMsg.WriteByte(byte(len(user.Username)) /* ULEN */)
|
||||
authMsg.WriteString(user.Username /* UNAME */)
|
||||
authMsg.WriteByte(byte(len(user.Password)) /* PLEN */)
|
||||
authMsg.WriteString(user.Password /* PASSWD */)
|
||||
uLen := len(user.Username)
|
||||
pLen := len(user.Password)
|
||||
|
||||
if len(authMsg.Bytes()) > MaxAuthLen {
|
||||
return nil, errors.New("auth message too long")
|
||||
// Both ULEN and PLEN are limited to the range [1, 255].
|
||||
if uLen == 0 || pLen == 0 {
|
||||
return nil, errors.New("auth username/password empty")
|
||||
} else if uLen > MaxAuthLen || pLen > MaxAuthLen {
|
||||
return nil, errors.New("auth username/password too long")
|
||||
}
|
||||
|
||||
// password protocol version
|
||||
authMsg := bufferpool.Get()
|
||||
defer bufferpool.Put(authMsg)
|
||||
authMsg.WriteByte(0x01 /* VER */)
|
||||
authMsg.WriteByte(byte(uLen) /* ULEN */)
|
||||
authMsg.WriteString(user.Username /* UNAME */)
|
||||
authMsg.WriteByte(byte(pLen) /* PLEN */)
|
||||
authMsg.WriteString(user.Password /* PASSWD */)
|
||||
|
||||
if _, err := rw.Write(authMsg.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -212,12 +229,20 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (
|
||||
return nil, errors.New("rejected username/password")
|
||||
}
|
||||
|
||||
} else if buf[1] != 0x00 /* NO AUTHENTICATION REQUIRED */ {
|
||||
} else if buf[1] != MethodNoAuth /* NO AUTHENTICATION REQUIRED */ {
|
||||
return nil, errors.New("unsupported method")
|
||||
}
|
||||
|
||||
// VER, CMD, RSV, ADDR
|
||||
if _, err := rw.Write(bytes.Join([][]byte{{Version, byte(command), 0x00 /* RSV */}, addr}, nil)); err != nil {
|
||||
req := bufferpool.Get()
|
||||
defer bufferpool.Put(req)
|
||||
req.Grow(3 + MaxAddrLen)
|
||||
req.WriteByte(Version)
|
||||
req.WriteByte(byte(command))
|
||||
req.WriteByte(0x00 /* RSV */)
|
||||
req.Write(addr)
|
||||
|
||||
if _, err := rw.Write(req.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -293,7 +318,7 @@ func SplitAddr(b []byte) Addr {
|
||||
|
||||
// SerializeAddr serializes destination address and port to Addr.
|
||||
// If a domain name is provided, AtypDomainName would be used first.
|
||||
func SerializeAddr(domainName string, dstIP net.IP, dstPort uint16) Addr {
|
||||
func SerializeAddr(domainName string, dstIP netip.Addr, dstPort uint16) Addr {
|
||||
var (
|
||||
buf [][]byte
|
||||
port [2]byte
|
||||
@@ -303,10 +328,10 @@ func SerializeAddr(domainName string, dstIP net.IP, dstPort uint16) Addr {
|
||||
if domainName != "" /* Domain Name */ {
|
||||
length := len(domainName)
|
||||
buf = [][]byte{{AtypDomainName, uint8(length)}, []byte(domainName), port[:]}
|
||||
} else if dstIP.To4() != nil /* IPv4 */ {
|
||||
buf = [][]byte{{AtypIPv4}, dstIP.To4(), port[:]}
|
||||
} else if dstIP.Is4() /* IPv4 */ {
|
||||
buf = [][]byte{{AtypIPv4}, dstIP.AsSlice(), port[:]}
|
||||
} else /* IPv6 */ {
|
||||
buf = [][]byte{{AtypIPv6}, dstIP.To16(), port[:]}
|
||||
buf = [][]byte{{AtypIPv6}, dstIP.AsSlice(), port[:]}
|
||||
}
|
||||
return bytes.Join(buf, nil)
|
||||
}
|
||||
@@ -314,14 +339,13 @@ func SerializeAddr(domainName string, dstIP net.IP, dstPort uint16) Addr {
|
||||
// ParseAddr parses a socks addr from net.Addr.
|
||||
// This is a fast path of ParseAddrString(addr.String())
|
||||
func ParseAddr(addr net.Addr) Addr {
|
||||
switch v := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return SerializeAddr("", v.IP, uint16(v.Port))
|
||||
case *net.UDPAddr:
|
||||
return SerializeAddr("", v.IP, uint16(v.Port))
|
||||
default:
|
||||
return ParseAddrString(addr.String())
|
||||
if v, ok := addr.(interface {
|
||||
AddrPort() netip.AddrPort
|
||||
}); ok {
|
||||
ap := v.AddrPort()
|
||||
return SerializeAddr("", ap.Addr(), ap.Port())
|
||||
}
|
||||
return ParseAddrString(addr.String())
|
||||
}
|
||||
|
||||
// ParseAddrString parses the address in string s to Addr. Returns nil if failed.
|
||||
@@ -336,10 +360,10 @@ func ParseAddrString(s string) Addr {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if ip, _ := netip.ParseAddr(host); ip.IsValid() {
|
||||
return SerializeAddr("", ip, uint16(dstPort))
|
||||
}
|
||||
return SerializeAddr(host, nil, uint16(dstPort))
|
||||
return SerializeAddr(host, netip.Addr{}, uint16(dstPort))
|
||||
}
|
||||
|
||||
// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet`
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSocks5ClientHandshake(t *testing.T) {
|
||||
// Mock server responses
|
||||
readBuffer := &bytes.Buffer{}
|
||||
readBuffer.Write([]byte{Version, MethodUserPass})
|
||||
readBuffer.Write([]byte{Version, 0x00 /* STATUS of SUCCESS */})
|
||||
readBuffer.Write([]byte{Version, 0x00 /* STATUS of SUCCESS */, 0x00 /* RSV */})
|
||||
readBuffer.Write([]byte{AtypIPv4, 0x1, 0x2, 0x3, 0x4, 0x0, 0x0 /* IPv4: 1.2.3.4:0 */})
|
||||
reader := bufio.NewReader(bytes.NewReader(readBuffer.Bytes()))
|
||||
|
||||
writeBuffer := &bytes.Buffer{}
|
||||
writer := bufio.NewWriter(writeBuffer)
|
||||
|
||||
io := bufio.NewReadWriter(reader, writer)
|
||||
|
||||
addr, err := ClientHandshake(io, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, CmdConnect, &User{
|
||||
Username: "test",
|
||||
Password: "6ab49d8b-a009-44e4-bd53-fbdb48fbe7eb",
|
||||
})
|
||||
|
||||
assert.Nil(t, err, "Failed to perform SOCKS5 client handshake: %v", err)
|
||||
assert.Equal(t, "1.2.3.4:0", addr.String(), "Incorrect address obtained from SOCKS5 client handshake")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user