├── .dockerignore ├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── SECURITY.md └── workflows │ ├── codeql-analysis.yml │ ├── docker.yml │ ├── linter.yml │ ├── release.yml │ ├── stale.yml │ └── test.yml ├── .gitignore ├── .golangci.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── buffer ├── allocator │ ├── allocator.go │ └── allocator_test.go └── pool.go ├── core ├── adapter │ ├── adapter.go │ └── handler.go ├── device │ ├── device.go │ ├── fdbased │ │ ├── fd.go │ │ ├── fd_unix.go │ │ ├── fd_windows.go │ │ ├── open_linux.go │ │ └── open_unix.go │ ├── iobased │ │ └── endpoint.go │ └── tun │ │ ├── tun.go │ │ ├── tun_netstack.go │ │ ├── tun_wireguard.go │ │ ├── tun_wireguard_unix.go │ │ └── tun_wireguard_windows.go ├── nic.go ├── option │ └── option.go ├── route.go ├── stack.go ├── tcp.go └── udp.go ├── dialer ├── dialer.go ├── sockopt.go ├── sockopt_darwin.go ├── sockopt_freebsd.go ├── sockopt_linux.go ├── sockopt_openbsd.go ├── sockopt_others.go └── sockopt_windows.go ├── dns └── resolver.go ├── docker └── entrypoint.sh ├── docs ├── benchmark.png ├── icon.png └── logo.png ├── engine ├── engine.go ├── key.go ├── parse.go ├── parse_unix.go └── parse_windows.go ├── go.mod ├── go.sum ├── internal ├── pool │ ├── pool.go │ └── pool_test.go └── version │ ├── module.go │ └── version.go ├── log ├── doc.go ├── emitter.go ├── level.go ├── log.go └── zap.go ├── main.go ├── metadata ├── metadata.go └── network.go ├── proxy ├── base.go ├── direct.go ├── http.go ├── proto │ └── proto.go ├── proxy.go ├── reject.go ├── relay.go ├── shadowsocks.go ├── socks4.go ├── socks5.go └── util.go ├── restapi ├── connections.go ├── debug.go ├── errors.go ├── netstats.go └── server.go ├── transport ├── internal │ └── bufferpool │ │ └── bufferpool.go ├── shadowsocks │ ├── README.md │ ├── core │ │ └── cipher.go │ ├── shadowaead │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go │ └── shadowstream │ │ ├── cipher.go │ │ ├── packet.go │ │ └── stream.go ├── simple-obfs │ ├── http.go │ ├── obfs.go │ └── tls.go ├── socks4 │ ├── socks4.go │ ├── socks4.txt │ ├── socks4_test.go │ └── socks4a.txt └── socks5 │ ├── rfc1928.txt │ ├── rfc1929.txt │ ├── socks5.go │ └── socks5_test.go └── tunnel ├── addr.go ├── global.go ├── statistic ├── manager.go └── tracker.go ├── tcp.go ├── tunnel.go └── udp.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .gitignore 3 | .golangci.yaml 4 | 5 | # Other 6 | docs/ 7 | build/ 8 | tests/ 9 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | xjasonlyu@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [ xjasonlyu ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | title: "[Bug] " 4 | body: 5 | - type: checkboxes 6 | id: ensure 7 | attributes: 8 | label: Verify steps 9 | description: Please verify that you've followed these steps 10 | options: 11 | - label: Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome. 12 | required: true 13 | 14 | - label: I have searched on the [issue tracker](……/) for a related issue. 15 | required: true 16 | 17 | - type: input 18 | attributes: 19 | label: Version 20 | validations: 21 | required: true 22 | 23 | - type: dropdown 24 | id: os 25 | attributes: 26 | label: What OS are you seeing the problem on? 27 | multiple: true 28 | options: 29 | - Windows 30 | - Linux 31 | - macOS 32 | - OpenBSD/FreeBSD 33 | - Other 34 | 35 | - type: textarea 36 | attributes: 37 | label: Description 38 | validations: 39 | required: true 40 | 41 | - type: textarea 42 | attributes: 43 | label: CLI or Config 44 | description: Paste the command line parameters or configuration below. 45 | 46 | - type: textarea 47 | attributes: 48 | render: shell 49 | label: Logs 50 | description: Paste the logs below with the log level set to `DEBUG`. 51 | 52 | - type: textarea 53 | attributes: 54 | label: How to Reproduce 55 | description: Steps to reproduce the behavior, if any. 56 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: tun2socks GitHub Wiki 5 | url: https://github.com/xjasonlyu/tun2socks/wiki 6 | about: Please see the wiki for common configurations 7 | - name: tun2socks GitHub Discussions 8 | url: https://github.com/xjasonlyu/tun2socks/discussions 9 | about: Ask questions and get help on GitHub Discussions 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea or improvement 3 | title: "[Feature] " 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Description 9 | placeholder: A clear description of the feature or enhancement. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | id: related 15 | attributes: 16 | label: Is this feature related to a specific bug? 17 | description: Please include a bug references if yes. 18 | 19 | - type: textarea 20 | id: solution 21 | attributes: 22 | label: Do you have a specific solution in mind? 23 | description: > 24 | Please include any details about a solution that you have in mind, 25 | including any alternatives considered. 26 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | |:-------:|:------------------:| 7 | | 2.x | :white_check_mark: | 8 | | 1.x | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | If you believe you have found a security vulnerability in this repository, please report it to me through coordinated 13 | disclosure. 14 | 15 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 16 | 17 | Instead, please email to xjasonlyu[@]gmail.com. 18 | 19 | Please include as much of the information listed below as you can to help me better understand and resolve the issue: 20 | 21 | * The type of issue (e.g., buffer overflow, payload attack) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help me triage your report more quickly. 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | concurrency: 4 | group: codeql-${{ github.event_name }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'go' ] 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup Go 27 | uses: actions/setup-go@v5 28 | with: 29 | check-latest: true 30 | go-version-file: 'go.mod' 31 | 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v3 34 | with: 35 | languages: ${{ matrix.language }} 36 | 37 | - name: Autobuild 38 | uses: github/codeql-action/autobuild@v3 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v3 42 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | 3 | concurrency: 4 | group: docker-${{ github.event_name }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'main' 11 | tags: 12 | - '*' 13 | 14 | jobs: 15 | docker: 16 | name: Docker 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Set up QEMU 25 | uses: docker/setup-qemu-action@v3 26 | with: 27 | platforms: all 28 | 29 | - name: Set up Docker Buildx 30 | uses: docker/setup-buildx-action@v3 31 | with: 32 | version: latest 33 | 34 | - name: Login to DockerHub 35 | uses: docker/login-action@v3 36 | with: 37 | username: ${{ secrets.DOCKER_USERNAME }} 38 | password: ${{ secrets.DOCKER_PASSWORD }} 39 | 40 | - name: Login to GitHub Container Registry 41 | uses: docker/login-action@v3 42 | with: 43 | registry: ghcr.io 44 | username: ${{ github.repository_owner }} 45 | password: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Get Version 48 | id: shell 49 | run: | 50 | echo "version=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT 51 | 52 | - name: Build and Push (dev) 53 | if: github.ref == 'refs/heads/main' 54 | uses: docker/build-push-action@v6 55 | with: 56 | context: . 57 | push: true 58 | platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 59 | tags: | 60 | xjasonlyu/tun2socks:dev 61 | ghcr.io/xjasonlyu/tun2socks:dev 62 | cache-from: type=gha 63 | cache-to: type=gha,mode=max 64 | 65 | - name: Build and Push (latest) 66 | if: startsWith(github.ref, 'refs/tags/') 67 | uses: docker/build-push-action@v6 68 | with: 69 | context: . 70 | push: true 71 | platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 72 | tags: | 73 | xjasonlyu/tun2socks:latest 74 | xjasonlyu/tun2socks:${{ steps.shell.outputs.version }} 75 | ghcr.io/xjasonlyu/tun2socks:latest 76 | ghcr.io/xjasonlyu/tun2socks:${{ steps.shell.outputs.version }} 77 | cache-from: type=gha 78 | cache-to: type=gha,mode=max 79 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | 3 | concurrency: 4 | group: linter-${{ github.event_name }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'main' 11 | pull_request: 12 | 13 | jobs: 14 | linter: 15 | name: Linter 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Go 22 | uses: actions/setup-go@v5 23 | with: 24 | check-latest: true 25 | go-version-file: 'go.mod' 26 | 27 | - name: golangci-lint 28 | uses: golangci/golangci-lint-action@v6 29 | with: 30 | version: latest 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Go Releases 2 | 3 | concurrency: 4 | group: release-${{ github.event_name }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | tags: 10 | - '*' 11 | 12 | jobs: 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v5 24 | with: 25 | check-latest: true 26 | go-version-file: 'go.mod' 27 | 28 | - name: Cache go module 29 | uses: actions/cache@v4 30 | with: 31 | path: | 32 | ~/go/pkg/mod 33 | ~/.cache/go-build 34 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 35 | restore-keys: | 36 | ${{ runner.os }}-go- 37 | 38 | - name: Set prerelease flag 39 | if: startsWith(github.ref, 'refs/tags/') 40 | id: pre 41 | run: | 42 | TAG="$GITHUB_REF_NAME" 43 | if [[ "$TAG" =~ -(beta|alpha|rc) ]]; then 44 | echo "is_prerelease=true" >> $GITHUB_OUTPUT 45 | else 46 | echo "is_prerelease=false" >> $GITHUB_OUTPUT 47 | fi 48 | 49 | - name: Build 50 | if: startsWith(github.ref, 'refs/tags/') 51 | run: make -j releases 52 | 53 | - name: Upload Releases 54 | uses: softprops/action-gh-release@v2 55 | if: startsWith(github.ref, 'refs/tags/') 56 | with: 57 | files: build/* 58 | draft: true 59 | prerelease: ${{ steps.pre.outputs.is_prerelease }} 60 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | permissions: 4 | contents: write 5 | issues: write 6 | pull-requests: write 7 | 8 | on: 9 | schedule: 10 | - cron: "0 10 * * *" 11 | 12 | jobs: 13 | stale: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/stale@v9 17 | with: 18 | 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' 19 | exempt-issue-labels: 'question,bug,enhancement,help wanted' 20 | exempt-pr-labels: 'pending,WIP,help wanted' 21 | days-before-stale: 60 22 | days-before-close: 7 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | concurrency: 4 | group: test-${{ github.event_name }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'main' 11 | pull_request: 12 | 13 | jobs: 14 | build-test: 15 | name: Build Test 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Setup Go 24 | uses: actions/setup-go@v5 25 | with: 26 | check-latest: true 27 | go-version-file: 'go.mod' 28 | 29 | - name: Run test 30 | run: | 31 | go test ./... 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # Build directory 25 | build/ 26 | 27 | # IDE 28 | .idea/ 29 | .vscode/ 30 | 31 | # Misc 32 | .DS_Store 33 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gci 5 | - gofumpt 6 | - gosimple 7 | - govet 8 | - ineffassign 9 | - misspell 10 | - staticcheck 11 | - unconvert 12 | - unused 13 | - usestdlibvars 14 | 15 | linters-settings: 16 | gci: 17 | custom-order: true 18 | sections: 19 | - standard 20 | - default 21 | - prefix(github.com/xjasonlyu/tun2socks) 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | 3 | WORKDIR /src 4 | COPY . /src 5 | 6 | RUN apk add --update --no-cache make git \ 7 | && make tun2socks 8 | 9 | FROM alpine:latest 10 | LABEL org.opencontainers.image.source="https://github.com/xjasonlyu/tun2socks" 11 | 12 | COPY docker/entrypoint.sh /entrypoint.sh 13 | COPY --from=builder /src/build/tun2socks /usr/bin/tun2socks 14 | 15 | RUN apk add --update --no-cache iptables iproute2 tzdata \ 16 | && chmod +x /entrypoint.sh 17 | 18 | ENV TUN=tun0 19 | ENV ADDR=198.18.0.1/15 20 | ENV LOGLEVEL=info 21 | ENV PROXY=direct:// 22 | ENV MTU=9000 23 | ENV RESTAPI= 24 | ENV UDP_TIMEOUT= 25 | ENV TCP_SNDBUF= 26 | ENV TCP_RCVBUF= 27 | ENV TCP_AUTO_TUNING= 28 | ENV MULTICAST_GROUPS= 29 | ENV EXTRA_COMMANDS= 30 | ENV TUN_INCLUDED_ROUTES= 31 | ENV TUN_EXCLUDED_ROUTES= 32 | 33 | ENTRYPOINT ["/entrypoint.sh"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jason Lyu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINARY := tun2socks 2 | MODULE := github.com/xjasonlyu/tun2socks/v2 3 | 4 | BUILD_DIR := build 5 | BUILD_TAGS := 6 | BUILD_FLAGS := -v 7 | BUILD_COMMIT := $(shell git rev-parse --short HEAD) 8 | BUILD_VERSION := $(shell git describe --abbrev=0 --tags HEAD) 9 | 10 | CGO_ENABLED := 0 11 | GO111MODULE := on 12 | 13 | LDFLAGS += -w -s -buildid= 14 | LDFLAGS += -X "$(MODULE)/internal/version.Version=$(BUILD_VERSION)" 15 | LDFLAGS += -X "$(MODULE)/internal/version.GitCommit=$(BUILD_COMMIT)" 16 | 17 | GO_BUILD = GO111MODULE=$(GO111MODULE) CGO_ENABLED=$(CGO_ENABLED) \ 18 | go build $(BUILD_FLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' -trimpath 19 | 20 | UNIX_ARCH_LIST = \ 21 | darwin-amd64 \ 22 | darwin-amd64-v3 \ 23 | darwin-arm64 \ 24 | freebsd-386 \ 25 | freebsd-amd64 \ 26 | freebsd-amd64-v3 \ 27 | freebsd-arm64 \ 28 | linux-386 \ 29 | linux-amd64 \ 30 | linux-amd64-v3 \ 31 | linux-arm64 \ 32 | linux-armv5 \ 33 | linux-armv6 \ 34 | linux-armv7 \ 35 | linux-mips-softfloat \ 36 | linux-mips-hardfloat \ 37 | linux-mipsle-softfloat \ 38 | linux-mipsle-hardfloat \ 39 | linux-mips64 \ 40 | linux-mips64le \ 41 | linux-ppc64 \ 42 | linux-ppc64le \ 43 | linux-s390x \ 44 | linux-loong64 \ 45 | openbsd-amd64 \ 46 | openbsd-amd64-v3 \ 47 | openbsd-arm64 48 | 49 | WINDOWS_ARCH_LIST = \ 50 | windows-386 \ 51 | windows-amd64 \ 52 | windows-amd64-v3 \ 53 | windows-arm64 \ 54 | windows-arm32v7 55 | 56 | all: linux-amd64 linux-arm64 darwin-amd64 darwin-arm64 windows-amd64 57 | 58 | debug: BUILD_TAGS += debug 59 | debug: all 60 | 61 | tun2socks: 62 | $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY) 63 | 64 | darwin-amd64: 65 | GOARCH=amd64 GOOS=darwin $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 66 | 67 | darwin-amd64-v3: 68 | GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 69 | 70 | darwin-arm64: 71 | GOARCH=arm64 GOOS=darwin $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 72 | 73 | freebsd-386: 74 | GOARCH=386 GOOS=freebsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 75 | 76 | freebsd-amd64: 77 | GOARCH=amd64 GOOS=freebsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 78 | 79 | freebsd-amd64-v3: 80 | GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 81 | 82 | freebsd-arm64: 83 | GOARCH=arm64 GOOS=freebsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 84 | 85 | linux-386: 86 | GOARCH=386 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 87 | 88 | linux-amd64: 89 | GOARCH=amd64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 90 | 91 | linux-amd64-v3: 92 | GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 93 | 94 | linux-arm64: 95 | GOARCH=arm64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 96 | 97 | linux-armv5: 98 | GOARCH=arm GOARM=5 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 99 | 100 | linux-armv6: 101 | GOARCH=arm GOARM=6 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 102 | 103 | linux-armv7: 104 | GOARCH=arm GOARM=7 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 105 | 106 | linux-mips-softfloat: 107 | GOARCH=mips GOMIPS=softfloat GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 108 | 109 | linux-mips-hardfloat: 110 | GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 111 | 112 | linux-mipsle-softfloat: 113 | GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 114 | 115 | linux-mipsle-hardfloat: 116 | GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 117 | 118 | linux-mips64: 119 | GOARCH=mips64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 120 | 121 | linux-mips64le: 122 | GOARCH=mips64le GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 123 | 124 | linux-ppc64: 125 | GOARCH=ppc64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 126 | 127 | linux-ppc64le: 128 | GOARCH=ppc64le GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 129 | 130 | linux-s390x: 131 | GOARCH=s390x GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 132 | 133 | linux-loong64: 134 | GOARCH=loong64 GOOS=linux $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 135 | 136 | openbsd-amd64: 137 | GOARCH=amd64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 138 | 139 | openbsd-amd64-v3: 140 | GOARCH=amd64 GOOS=openbsd GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 141 | 142 | openbsd-arm64: 143 | GOARCH=arm64 GOOS=openbsd $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@ 144 | 145 | windows-386: 146 | GOARCH=386 GOOS=windows $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe 147 | 148 | windows-amd64: 149 | GOARCH=amd64 GOOS=windows $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe 150 | 151 | windows-amd64-v3: 152 | GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe 153 | 154 | windows-arm64: 155 | GOARCH=arm64 GOOS=windows $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe 156 | 157 | windows-arm32v7: 158 | GOARCH=arm GOARM=7 GOOS=windows $(GO_BUILD) -o $(BUILD_DIR)/$(BINARY)-$@.exe 159 | 160 | unix_releases := $(addsuffix .zip, $(UNIX_ARCH_LIST)) 161 | windows_releases := $(addsuffix .zip, $(WINDOWS_ARCH_LIST)) 162 | 163 | $(unix_releases): %.zip: % 164 | @zip -qmj $(BUILD_DIR)/$(BINARY)-$(basename $@).zip $(BUILD_DIR)/$(BINARY)-$(basename $@) 165 | 166 | $(windows_releases): %.zip: % 167 | @zip -qmj $(BUILD_DIR)/$(BINARY)-$(basename $@).zip $(BUILD_DIR)/$(BINARY)-$(basename $@).exe 168 | 169 | all-arch: $(UNIX_ARCH_LIST) $(WINDOWS_ARCH_LIST) 170 | 171 | releases: $(unix_releases) $(windows_releases) 172 | 173 | lint: 174 | GOOS=darwin golangci-lint run ./... 175 | GOOS=windows golangci-lint run ./... 176 | GOOS=linux golangci-lint run ./... 177 | GOOS=freebsd golangci-lint run ./... 178 | GOOS=openbsd golangci-lint run ./... 179 | 180 | clean: 181 | rm -rf $(BUILD_DIR) 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![tun2socks](docs/logo.png) 2 | 3 | [![GitHub Workflow][1]](https://github.com/xjasonlyu/tun2socks/actions) 4 | [![Go Version][2]](https://github.com/xjasonlyu/tun2socks/blob/main/go.mod) 5 | [![Go Report][3]](https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks) 6 | [![Maintainability][4]](https://codeclimate.com/github/xjasonlyu/tun2socks/maintainability) 7 | [![GitHub License][5]](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE) 8 | [![Docker Pulls][6]](https://hub.docker.com/r/xjasonlyu/tun2socks) 9 | [![Releases][7]](https://github.com/xjasonlyu/tun2socks/releases) 10 | 11 | ## Features 12 | 13 | - **Universal Proxying**: Transparently routes all network traffic from any application through a proxy. 14 | - **Multi-Protocol**: Supports HTTP/SOCKS4/SOCKS5/Shadowsocks proxies with optional authentication. 15 | - **Cross-Platform**: Runs on Linux/macOS/Windows/FreeBSD/OpenBSD with platform-specific optimizations. 16 | - **Gateway Mode**: Acts as a Layer 3 gateway to route traffic from other devices on the same network. 17 | - **Full IPv6 Compatibility**: Natively supports IPv6; seamlessly tunnels IPv4 over IPv6 and vice versa. 18 | - **User-Space Networking**: Leverages the **[gVisor](https://github.com/google/gvisor)** network stack for enhanced 19 | performance and flexibility. 20 | 21 | ## Benchmarks 22 | 23 | ![benchmark](docs/benchmark.png) 24 | 25 | For all scenarios of usage, tun2socks performs best. 26 | See [benchmarks](https://github.com/xjasonlyu/tun2socks/wiki/Benchmarks) for more details. 27 | 28 | ## Documentation 29 | 30 | - [Install from Source](https://github.com/xjasonlyu/tun2socks/wiki/Install-from-Source) 31 | - [Quickstart Examples](https://github.com/xjasonlyu/tun2socks/wiki/Examples) 32 | - [Memory Optimization](https://github.com/xjasonlyu/tun2socks/wiki/Memory-Optimization) 33 | 34 | Full documentation and technical guides can be found at [Wiki](https://github.com/xjasonlyu/tun2socks/wiki). 35 | 36 | ## Community 37 | 38 | Welcome and feel free to ask any questions at [Discussions](https://github.com/xjasonlyu/tun2socks/discussions). 39 | 40 | ## Credits 41 | 42 | - [google/gvisor](https://github.com/google/gvisor) - Application Kernel for Containers 43 | - [wireguard-go](https://git.zx2c4.com/wireguard-go) - Go Implementation of WireGuard 44 | - [wintun](https://git.zx2c4.com/wintun/) - Layer 3 TUN Driver for Windows 45 | 46 | ## License 47 | 48 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fxjasonlyu%2Ftun2socks?ref=badge_large) 49 | 50 | All versions starting from `v2.6.0` are available under the terms of the [MIT License](https://github.com/xjasonlyu/tun2socks/blob/main/LICENSE). 51 | 52 | ## Star History 53 | 54 | 55 | 56 | 57 | 58 | Star History Chart 59 | 60 | 61 | 62 | [1]: https://img.shields.io/github/actions/workflow/status/xjasonlyu/tun2socks/docker.yml?logo=github 63 | 64 | [2]: https://img.shields.io/github/go-mod/go-version/xjasonlyu/tun2socks?logo=go 65 | 66 | [3]: https://goreportcard.com/badge/github.com/xjasonlyu/tun2socks 67 | 68 | [4]: https://api.codeclimate.com/v1/badges/b5b30239174fc6603aca/maintainability 69 | 70 | [5]: https://img.shields.io/github/license/xjasonlyu/tun2socks 71 | 72 | [6]: https://img.shields.io/docker/pulls/xjasonlyu/tun2socks?logo=docker 73 | 74 | [7]: https://img.shields.io/github/v/release/xjasonlyu/tun2socks?logo=smartthings 75 | -------------------------------------------------------------------------------- /buffer/allocator/allocator.go: -------------------------------------------------------------------------------- 1 | package allocator 2 | 3 | import ( 4 | "errors" 5 | "math/bits" 6 | 7 | "github.com/xjasonlyu/tun2socks/v2/internal/pool" 8 | ) 9 | 10 | // Allocator for incoming frames, optimized to prevent overwriting 11 | // after zeroing. 12 | type Allocator struct { 13 | buffers []*pool.Pool[[]byte] 14 | } 15 | 16 | // New initiates a []byte allocator for frames less than 65536 bytes, 17 | // the waste(memory fragmentation) of space allocation is guaranteed 18 | // to be no more than 50%. 19 | func New() *Allocator { 20 | alloc := &Allocator{} 21 | alloc.buffers = make([]*pool.Pool[[]byte], 17) // 1B -> 64K 22 | for k := range alloc.buffers { 23 | i := k 24 | alloc.buffers[k] = pool.New(func() []byte { 25 | return make([]byte, 1< 65536 { 34 | return nil 35 | } 36 | 37 | b := msb(size) 38 | if size == 1< 65536 || cap(buf) != 1< mtu { 105 | continue 106 | } 107 | 108 | if !e.IsAttached() { 109 | continue /* unattached, drop packet */ 110 | } 111 | 112 | pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ 113 | Payload: buffer.MakeWithData(data[offset : offset+n]), 114 | }) 115 | 116 | switch header.IPVersion(data[offset:]) { 117 | case header.IPv4Version: 118 | e.InjectInbound(header.IPv4ProtocolNumber, pkt) 119 | case header.IPv6Version: 120 | e.InjectInbound(header.IPv6ProtocolNumber, pkt) 121 | } 122 | pkt.DecRef() 123 | } 124 | } 125 | 126 | // outboundLoop reads outbound packets from channel, and then it calls 127 | // writePacket to send those packets back to lower layer. 128 | func (e *Endpoint) outboundLoop(ctx context.Context) { 129 | for { 130 | pkt := e.ReadContext(ctx) 131 | if pkt == nil { 132 | break 133 | } 134 | e.writePacket(pkt) 135 | } 136 | } 137 | 138 | // writePacket writes outbound packets to the io.Writer. 139 | func (e *Endpoint) writePacket(pkt *stack.PacketBuffer) tcpip.Error { 140 | defer pkt.DecRef() 141 | 142 | buf := pkt.ToBuffer() 143 | defer buf.Release() 144 | if e.offset != 0 { 145 | v := buffer.NewViewWithData(make([]byte, e.offset)) 146 | _ = buf.Prepend(v) 147 | } 148 | 149 | if _, err := e.rw.Write(buf.Flatten()); err != nil { 150 | return &tcpip.ErrInvalidEndpointState{} 151 | } 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /core/device/tun/tun.go: -------------------------------------------------------------------------------- 1 | // Package tun provides TUN which implemented device.Device interface. 2 | package tun 3 | 4 | import ( 5 | "github.com/xjasonlyu/tun2socks/v2/core/device" 6 | ) 7 | 8 | const Driver = "tun" 9 | 10 | func (t *TUN) Type() string { 11 | return Driver 12 | } 13 | 14 | var _ device.Device = (*TUN)(nil) 15 | -------------------------------------------------------------------------------- /core/device/tun/tun_netstack.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | 3 | package tun 4 | 5 | import ( 6 | "fmt" 7 | 8 | "golang.org/x/sys/unix" 9 | "gvisor.dev/gvisor/pkg/rawfile" 10 | "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" 11 | "gvisor.dev/gvisor/pkg/tcpip/link/tun" 12 | "gvisor.dev/gvisor/pkg/tcpip/stack" 13 | 14 | "github.com/xjasonlyu/tun2socks/v2/core/device" 15 | ) 16 | 17 | type TUN struct { 18 | stack.LinkEndpoint 19 | 20 | fd int 21 | mtu uint32 22 | name string 23 | } 24 | 25 | func Open(name string, mtu uint32) (device.Device, error) { 26 | t := &TUN{name: name, mtu: mtu} 27 | 28 | if len(t.name) >= unix.IFNAMSIZ { 29 | return nil, fmt.Errorf("interface name too long: %s", t.name) 30 | } 31 | 32 | fd, err := tun.Open(t.name) 33 | if err != nil { 34 | return nil, fmt.Errorf("create tun: %w", err) 35 | } 36 | t.fd = fd 37 | 38 | if t.mtu > 0 { 39 | if err := setMTU(t.name, t.mtu); err != nil { 40 | return nil, fmt.Errorf("set mtu: %w", err) 41 | } 42 | } 43 | 44 | _mtu, err := rawfile.GetMTU(t.name) 45 | if err != nil { 46 | return nil, fmt.Errorf("get mtu: %w", err) 47 | } 48 | t.mtu = _mtu 49 | 50 | ep, err := fdbased.New(&fdbased.Options{ 51 | FDs: []int{fd}, 52 | MTU: t.mtu, 53 | // TUN only, ignore ethernet header. 54 | EthernetHeader: false, 55 | // SYS_READV support only for TUN fd. 56 | PacketDispatchMode: fdbased.Readv, 57 | // TAP/TUN fd's are not sockets and using the WritePackets calls results 58 | // in errors as it always defaults to using SendMMsg which is not supported 59 | // for tap/tun device fds. 60 | // 61 | // This CL changes WritePackets to gracefully degrade to using writev instead 62 | // of sendmmsg if the underlying fd is not a socket. 63 | // 64 | // Fixed: https://github.com/google/gvisor/commit/f33d034fecd7723a1e560ccc62aeeba328454fd0 65 | MaxSyscallHeaderBytes: 0x00, 66 | }) 67 | if err != nil { 68 | return nil, fmt.Errorf("create endpoint: %w", err) 69 | } 70 | t.LinkEndpoint = ep 71 | 72 | return t, nil 73 | } 74 | 75 | func (t *TUN) Name() string { 76 | return t.name 77 | } 78 | 79 | func (t *TUN) Close() { 80 | defer t.LinkEndpoint.Close() 81 | _ = unix.Close(t.fd) 82 | } 83 | 84 | func setMTU(name string, n uint32) error { 85 | // open datagram socket 86 | fd, err := unix.Socket( 87 | unix.AF_INET, 88 | unix.SOCK_DGRAM, 89 | 0, 90 | ) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | defer unix.Close(fd) 96 | 97 | ifr, err := unix.NewIfreq(name) 98 | if err != nil { 99 | return err 100 | } 101 | ifr.SetUint32(n) 102 | return unix.IoctlIfreq(fd, unix.SIOCSIFMTU, ifr) 103 | } 104 | -------------------------------------------------------------------------------- /core/device/tun/tun_wireguard.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package tun 4 | 5 | import ( 6 | "fmt" 7 | "sync" 8 | 9 | "golang.zx2c4.com/wireguard/tun" 10 | 11 | "github.com/xjasonlyu/tun2socks/v2/core/device" 12 | "github.com/xjasonlyu/tun2socks/v2/core/device/iobased" 13 | ) 14 | 15 | type TUN struct { 16 | *iobased.Endpoint 17 | 18 | nt *tun.NativeTun 19 | mtu uint32 20 | name string 21 | offset int 22 | 23 | rSizes []int 24 | rBuffs [][]byte 25 | wBuffs [][]byte 26 | rMutex sync.Mutex 27 | wMutex sync.Mutex 28 | } 29 | 30 | func Open(name string, mtu uint32) (_ device.Device, err error) { 31 | defer func() { 32 | if r := recover(); r != nil { 33 | err = fmt.Errorf("open tun: %v", r) 34 | } 35 | }() 36 | 37 | t := &TUN{ 38 | name: name, 39 | mtu: mtu, 40 | offset: offset, 41 | rSizes: make([]int, 1), 42 | rBuffs: make([][]byte, 1), 43 | wBuffs: make([][]byte, 1), 44 | } 45 | 46 | forcedMTU := defaultMTU 47 | if t.mtu > 0 { 48 | forcedMTU = int(t.mtu) 49 | } 50 | 51 | nt, err := createTUN(t.name, forcedMTU) 52 | if err != nil { 53 | return nil, fmt.Errorf("create tun: %w", err) 54 | } 55 | t.nt = nt.(*tun.NativeTun) 56 | 57 | tunMTU, err := nt.MTU() 58 | if err != nil { 59 | return nil, fmt.Errorf("get mtu: %w", err) 60 | } 61 | t.mtu = uint32(tunMTU) 62 | 63 | ep, err := iobased.New(t, t.mtu, offset) 64 | if err != nil { 65 | return nil, fmt.Errorf("create endpoint: %w", err) 66 | } 67 | t.Endpoint = ep 68 | 69 | return t, nil 70 | } 71 | 72 | func (t *TUN) Read(packet []byte) (int, error) { 73 | t.rMutex.Lock() 74 | defer t.rMutex.Unlock() 75 | t.rBuffs[0] = packet 76 | _, err := t.nt.Read(t.rBuffs, t.rSizes, t.offset) 77 | return t.rSizes[0], err 78 | } 79 | 80 | func (t *TUN) Write(packet []byte) (int, error) { 81 | t.wMutex.Lock() 82 | defer t.wMutex.Unlock() 83 | t.wBuffs[0] = packet 84 | return t.nt.Write(t.wBuffs, t.offset) 85 | } 86 | 87 | func (t *TUN) Name() string { 88 | name, _ := t.nt.Name() 89 | return name 90 | } 91 | 92 | func (t *TUN) Close() { 93 | defer t.Endpoint.Close() 94 | _ = t.nt.Close() 95 | } 96 | -------------------------------------------------------------------------------- /core/device/tun/tun_wireguard_unix.go: -------------------------------------------------------------------------------- 1 | //go:build unix && !linux 2 | 3 | package tun 4 | 5 | import ( 6 | "golang.zx2c4.com/wireguard/tun" 7 | ) 8 | 9 | const ( 10 | offset = 4 /* 4 bytes TUN_PI */ 11 | defaultMTU = 1500 12 | ) 13 | 14 | func createTUN(name string, mtu int) (tun.Device, error) { 15 | return tun.CreateTUN(name, mtu) 16 | } 17 | -------------------------------------------------------------------------------- /core/device/tun/tun_wireguard_windows.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "golang.zx2c4.com/wireguard/tun" 5 | ) 6 | 7 | const ( 8 | offset = 0 9 | defaultMTU = 0 /* auto */ 10 | ) 11 | 12 | func createTUN(name string, mtu int) (tun.Device, error) { 13 | return tun.CreateTUN(name, mtu) 14 | } 15 | -------------------------------------------------------------------------------- /core/nic.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net/netip" 6 | 7 | "gvisor.dev/gvisor/pkg/tcpip" 8 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 9 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" 10 | "gvisor.dev/gvisor/pkg/tcpip/stack" 11 | 12 | "github.com/xjasonlyu/tun2socks/v2/core/option" 13 | ) 14 | 15 | const ( 16 | // nicPromiscuousModeEnabled is the value used by stack to enable 17 | // or disable NIC's promiscuous mode. 18 | nicPromiscuousModeEnabled = true 19 | 20 | // nicSpoofingEnabled is the value used by stack to enable or disable 21 | // NIC's spoofing. 22 | nicSpoofingEnabled = true 23 | ) 24 | 25 | // withCreatingNIC creates NIC for stack. 26 | func withCreatingNIC(nicID tcpip.NICID, ep stack.LinkEndpoint) option.Option { 27 | return func(s *stack.Stack) error { 28 | if err := s.CreateNICWithOptions(nicID, ep, 29 | stack.NICOptions{ 30 | Disabled: false, 31 | // If no queueing discipline was specified 32 | // provide a stub implementation that just 33 | // delegates to the lower link endpoint. 34 | QDisc: nil, 35 | }); err != nil { 36 | return fmt.Errorf("create NIC: %s", err) 37 | } 38 | return nil 39 | } 40 | } 41 | 42 | // withPromiscuousMode sets promiscuous mode in the given NICs. 43 | func withPromiscuousMode(nicID tcpip.NICID, v bool) option.Option { 44 | return func(s *stack.Stack) error { 45 | if err := s.SetPromiscuousMode(nicID, v); err != nil { 46 | return fmt.Errorf("set promiscuous mode: %s", err) 47 | } 48 | return nil 49 | } 50 | } 51 | 52 | // withSpoofing sets address spoofing in the given NICs, allowing 53 | // endpoints to bind to any address in the NIC. 54 | func withSpoofing(nicID tcpip.NICID, v bool) option.Option { 55 | return func(s *stack.Stack) error { 56 | if err := s.SetSpoofing(nicID, v); err != nil { 57 | return fmt.Errorf("set spoofing: %s", err) 58 | } 59 | return nil 60 | } 61 | } 62 | 63 | // withMulticastGroups adds a NIC to the given multicast groups. 64 | func withMulticastGroups(nicID tcpip.NICID, multicastGroups []netip.Addr) option.Option { 65 | return func(s *stack.Stack) error { 66 | if len(multicastGroups) == 0 { 67 | return nil 68 | } 69 | // The default NIC of tun2socks is working on Spoofing mode. When the UDP Endpoint 70 | // tries to use a non-local address to connect, the network stack will 71 | // generate a temporary addressState to build the route, which can be primary 72 | // but is ephemeral. Nevertheless, when the UDP Endpoint tries to use a 73 | // multicast address to connect, the network stack will select an available 74 | // primary addressState to build the route. However, when tun2socks is in the 75 | // just-initialized or idle state, there will be no available primary addressState, 76 | // and the connect operation will fail. Therefore, we need to add permanent addresses, 77 | // e.g. 10.0.0.1/8 and fd00:1/8, to the default NIC, which are only used to build 78 | // routes for multicast response and do not affect other connections. 79 | // 80 | // In fact, for multicast, the sender normally does not expect a response. 81 | // So, the ep.net.Connect is unnecessary. If we implement a custom UDP Forwarder 82 | // and ForwarderRequest in the future, we can remove these code. 83 | s.AddProtocolAddress( 84 | nicID, 85 | tcpip.ProtocolAddress{ 86 | Protocol: ipv4.ProtocolNumber, 87 | AddressWithPrefix: tcpip.AddressWithPrefix{ 88 | Address: tcpip.AddrFrom4([4]byte{0x0a, 0, 0, 0x01}), 89 | PrefixLen: 8, 90 | }, 91 | }, 92 | stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint}, 93 | ) 94 | s.AddProtocolAddress( 95 | nicID, 96 | tcpip.ProtocolAddress{ 97 | Protocol: ipv6.ProtocolNumber, 98 | AddressWithPrefix: tcpip.AddressWithPrefix{ 99 | Address: tcpip.AddrFrom16([16]byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), 100 | PrefixLen: 8, 101 | }, 102 | }, 103 | stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint}, 104 | ) 105 | for _, multicastGroup := range multicastGroups { 106 | var err tcpip.Error 107 | switch { 108 | case multicastGroup.Is4(): 109 | err = s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4(multicastGroup.As4())) 110 | case multicastGroup.Is6(): 111 | err = s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16(multicastGroup.As16())) 112 | } 113 | if err != nil { 114 | return fmt.Errorf("join multicast group: %s", err) 115 | } 116 | } 117 | return nil 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /core/route.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "gvisor.dev/gvisor/pkg/tcpip" 5 | "gvisor.dev/gvisor/pkg/tcpip/header" 6 | "gvisor.dev/gvisor/pkg/tcpip/stack" 7 | 8 | "github.com/xjasonlyu/tun2socks/v2/core/option" 9 | ) 10 | 11 | func withRouteTable(nicID tcpip.NICID) option.Option { 12 | return func(s *stack.Stack) error { 13 | s.SetRouteTable([]tcpip.Route{ 14 | { 15 | Destination: header.IPv4EmptySubnet, 16 | NIC: nicID, 17 | }, 18 | { 19 | Destination: header.IPv6EmptySubnet, 20 | NIC: nicID, 21 | }, 22 | }) 23 | return nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/stack.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "net/netip" 5 | 6 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 7 | "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" 8 | "gvisor.dev/gvisor/pkg/tcpip/stack" 9 | "gvisor.dev/gvisor/pkg/tcpip/transport/icmp" 10 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 11 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 12 | 13 | "github.com/xjasonlyu/tun2socks/v2/core/adapter" 14 | "github.com/xjasonlyu/tun2socks/v2/core/option" 15 | ) 16 | 17 | // Config is the configuration to create *stack.Stack. 18 | type Config struct { 19 | // LinkEndpoints is the interface implemented by 20 | // data link layer protocols. 21 | LinkEndpoint stack.LinkEndpoint 22 | 23 | // TransportHandler is the handler used by internal 24 | // stack to set transport handlers. 25 | TransportHandler adapter.TransportHandler 26 | 27 | // MulticastGroups is used by internal stack to add 28 | // nic to given groups. 29 | MulticastGroups []netip.Addr 30 | 31 | // Options are supplement options to apply settings 32 | // for the internal stack. 33 | Options []option.Option 34 | } 35 | 36 | // CreateStack creates *stack.Stack with given config. 37 | func CreateStack(cfg *Config) (*stack.Stack, error) { 38 | opts := []option.Option{option.WithDefault()} 39 | if len(opts) > 0 { 40 | opts = append(opts, cfg.Options...) 41 | } 42 | 43 | s := stack.New(stack.Options{ 44 | NetworkProtocols: []stack.NetworkProtocolFactory{ 45 | ipv4.NewProtocol, 46 | ipv6.NewProtocol, 47 | }, 48 | TransportProtocols: []stack.TransportProtocolFactory{ 49 | tcp.NewProtocol, 50 | udp.NewProtocol, 51 | icmp.NewProtocol4, 52 | icmp.NewProtocol6, 53 | }, 54 | }) 55 | 56 | // Generate unique NIC id. 57 | nicID := s.NextNICID() 58 | 59 | opts = append(opts, 60 | // Important: We must initiate transport protocol handlers 61 | // before creating NIC, otherwise NIC would dispatch packets 62 | // to stack and cause race condition. 63 | // Initiate transport protocol (TCP/UDP) with given handler. 64 | withTCPHandler(cfg.TransportHandler.HandleTCP), 65 | withUDPHandler(cfg.TransportHandler.HandleUDP), 66 | 67 | // Create stack NIC and then bind link endpoint to it. 68 | withCreatingNIC(nicID, cfg.LinkEndpoint), 69 | 70 | // In the past we did s.AddAddressRange to assign 0.0.0.0/0 71 | // onto the interface. We need that to be able to terminate 72 | // all the incoming connections - to any ip. AddressRange API 73 | // has been removed and the suggested workaround is to use 74 | // Promiscuous mode. https://github.com/google/gvisor/issues/3876 75 | // 76 | // Ref: https://github.com/cloudflare/slirpnetstack/blob/master/stack.go 77 | withPromiscuousMode(nicID, nicPromiscuousModeEnabled), 78 | 79 | // Enable spoofing if a stack may send packets from unowned 80 | // addresses. This change required changes to some netgophers 81 | // since previously, promiscuous mode was enough to let the 82 | // netstack respond to all incoming packets regardless of the 83 | // packet's destination address. Now that a stack.Route is not 84 | // held for each incoming packet, finding a route may fail with 85 | // local addresses we don't own but accepted packets for while 86 | // in promiscuous mode. Since we also want to be able to send 87 | // from any address (in response the received promiscuous mode 88 | // packets), we need to enable spoofing. 89 | // 90 | // Ref: https://github.com/google/gvisor/commit/8c0701462a84ff77e602f1626aec49479c308127 91 | withSpoofing(nicID, nicSpoofingEnabled), 92 | 93 | // Add default route table for IPv4 and IPv6. This will handle 94 | // all incoming ICMP packets. 95 | withRouteTable(nicID), 96 | 97 | // Add default NIC to the given multicast groups. 98 | withMulticastGroups(nicID, cfg.MulticastGroups), 99 | ) 100 | 101 | for _, opt := range opts { 102 | if err := opt(s); err != nil { 103 | return nil, err 104 | } 105 | } 106 | return s, nil 107 | } 108 | -------------------------------------------------------------------------------- /core/tcp.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "time" 5 | 6 | glog "gvisor.dev/gvisor/pkg/log" 7 | "gvisor.dev/gvisor/pkg/tcpip" 8 | "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" 9 | "gvisor.dev/gvisor/pkg/tcpip/header" 10 | "gvisor.dev/gvisor/pkg/tcpip/stack" 11 | "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 12 | "gvisor.dev/gvisor/pkg/waiter" 13 | 14 | "github.com/xjasonlyu/tun2socks/v2/core/adapter" 15 | "github.com/xjasonlyu/tun2socks/v2/core/option" 16 | ) 17 | 18 | const ( 19 | // defaultWndSize if set to zero, the default 20 | // receive window buffer size is used instead. 21 | defaultWndSize = 0 22 | 23 | // maxConnAttempts specifies the maximum number 24 | // of in-flight tcp connection attempts. 25 | maxConnAttempts = 2 << 10 26 | 27 | // tcpKeepaliveCount is the maximum number of 28 | // TCP keep-alive probes to send before giving up 29 | // and killing the connection if no response is 30 | // obtained from the other end. 31 | tcpKeepaliveCount = 9 32 | 33 | // tcpKeepaliveIdle specifies the time a connection 34 | // must remain idle before the first TCP keepalive 35 | // packet is sent. Once this time is reached, 36 | // tcpKeepaliveInterval option is used instead. 37 | tcpKeepaliveIdle = 60 * time.Second 38 | 39 | // tcpKeepaliveInterval specifies the interval 40 | // time between sending TCP keepalive packets. 41 | tcpKeepaliveInterval = 30 * time.Second 42 | ) 43 | 44 | func withTCPHandler(handle func(adapter.TCPConn)) option.Option { 45 | return func(s *stack.Stack) error { 46 | tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) { 47 | var ( 48 | wq waiter.Queue 49 | ep tcpip.Endpoint 50 | err tcpip.Error 51 | id = r.ID() 52 | ) 53 | 54 | defer func() { 55 | if err != nil { 56 | glog.Debugf("forward tcp request: %s:%d->%s:%d: %s", 57 | id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) 58 | } 59 | }() 60 | 61 | // Perform a TCP three-way handshake. 62 | ep, err = r.CreateEndpoint(&wq) 63 | if err != nil { 64 | // RST: prevent potential half-open TCP connection leak. 65 | r.Complete(true) 66 | return 67 | } 68 | defer r.Complete(false) 69 | 70 | err = setSocketOptions(s, ep) 71 | 72 | conn := &tcpConn{ 73 | TCPConn: gonet.NewTCPConn(&wq, ep), 74 | id: id, 75 | } 76 | handle(conn) 77 | }) 78 | s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket) 79 | return nil 80 | } 81 | } 82 | 83 | func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error { 84 | { /* TCP keepalive options */ 85 | ep.SocketOptions().SetKeepAlive(true) 86 | 87 | idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle) 88 | if err := ep.SetSockOpt(&idle); err != nil { 89 | return err 90 | } 91 | 92 | interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval) 93 | if err := ep.SetSockOpt(&interval); err != nil { 94 | return err 95 | } 96 | 97 | if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil { 98 | return err 99 | } 100 | } 101 | { /* TCP recv/send buffer size */ 102 | var ss tcpip.TCPSendBufferSizeRangeOption 103 | if err := s.TransportProtocolOption(header.TCPProtocolNumber, &ss); err == nil { 104 | ep.SocketOptions().SetSendBufferSize(int64(ss.Default), false) 105 | } 106 | 107 | var rs tcpip.TCPReceiveBufferSizeRangeOption 108 | if err := s.TransportProtocolOption(header.TCPProtocolNumber, &rs); err == nil { 109 | ep.SocketOptions().SetReceiveBufferSize(int64(rs.Default), false) 110 | } 111 | } 112 | return nil 113 | } 114 | 115 | type tcpConn struct { 116 | *gonet.TCPConn 117 | id stack.TransportEndpointID 118 | } 119 | 120 | func (c *tcpConn) ID() *stack.TransportEndpointID { 121 | return &c.id 122 | } 123 | -------------------------------------------------------------------------------- /core/udp.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | glog "gvisor.dev/gvisor/pkg/log" 5 | "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" 6 | "gvisor.dev/gvisor/pkg/tcpip/stack" 7 | "gvisor.dev/gvisor/pkg/tcpip/transport/udp" 8 | "gvisor.dev/gvisor/pkg/waiter" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/core/adapter" 11 | "github.com/xjasonlyu/tun2socks/v2/core/option" 12 | ) 13 | 14 | func withUDPHandler(handle func(adapter.UDPConn)) option.Option { 15 | return func(s *stack.Stack) error { 16 | udpForwarder := udp.NewForwarder(s, func(r *udp.ForwarderRequest) { 17 | var ( 18 | wq waiter.Queue 19 | id = r.ID() 20 | ) 21 | ep, err := r.CreateEndpoint(&wq) 22 | if err != nil { 23 | glog.Debugf("forward udp request: %s:%d->%s:%d: %s", 24 | id.RemoteAddress, id.RemotePort, id.LocalAddress, id.LocalPort, err) 25 | return 26 | } 27 | 28 | conn := &udpConn{ 29 | UDPConn: gonet.NewUDPConn(&wq, ep), 30 | id: id, 31 | } 32 | handle(conn) 33 | }) 34 | s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket) 35 | return nil 36 | } 37 | } 38 | 39 | type udpConn struct { 40 | *gonet.UDPConn 41 | id stack.TransportEndpointID 42 | } 43 | 44 | func (c *udpConn) ID() *stack.TransportEndpointID { 45 | return &c.id 46 | } 47 | -------------------------------------------------------------------------------- /dialer/dialer.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "syscall" 7 | 8 | "go.uber.org/atomic" 9 | ) 10 | 11 | // DefaultDialer is the default Dialer and is used by DialContext and ListenPacket. 12 | var DefaultDialer = &Dialer{ 13 | InterfaceName: atomic.NewString(""), 14 | InterfaceIndex: atomic.NewInt32(0), 15 | RoutingMark: atomic.NewInt32(0), 16 | } 17 | 18 | type Dialer struct { 19 | InterfaceName *atomic.String 20 | InterfaceIndex *atomic.Int32 21 | RoutingMark *atomic.Int32 22 | } 23 | 24 | type Options struct { 25 | // InterfaceName is the name of interface/device to bind. 26 | // If a socket is bound to an interface, only packets received 27 | // from that particular interface are processed by the socket. 28 | InterfaceName string 29 | 30 | // InterfaceIndex is the index of interface/device to bind. 31 | // It is almost the same as InterfaceName except it uses the 32 | // index of the interface instead of the name. 33 | InterfaceIndex int 34 | 35 | // RoutingMark is the mark for each packet sent through this 36 | // socket. Changing the mark can be used for mark-based routing 37 | // without netfilter or for packet filtering. 38 | RoutingMark int 39 | } 40 | 41 | // DialContext is a wrapper around DefaultDialer.DialContext. 42 | func DialContext(ctx context.Context, network, address string) (net.Conn, error) { 43 | return DefaultDialer.DialContext(ctx, network, address) 44 | } 45 | 46 | // ListenPacket is a wrapper around DefaultDialer.ListenPacket. 47 | func ListenPacket(network, address string) (net.PacketConn, error) { 48 | return DefaultDialer.ListenPacket(network, address) 49 | } 50 | 51 | func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 52 | return d.DialContextWithOptions(ctx, network, address, &Options{ 53 | InterfaceName: d.InterfaceName.Load(), 54 | InterfaceIndex: int(d.InterfaceIndex.Load()), 55 | RoutingMark: int(d.RoutingMark.Load()), 56 | }) 57 | } 58 | 59 | func (_ *Dialer) DialContextWithOptions(ctx context.Context, network, address string, opts *Options) (net.Conn, error) { 60 | d := &net.Dialer{ 61 | Control: func(network, address string, c syscall.RawConn) error { 62 | return setSocketOptions(network, address, c, opts) 63 | }, 64 | } 65 | return d.DialContext(ctx, network, address) 66 | } 67 | 68 | func (d *Dialer) ListenPacket(network, address string) (net.PacketConn, error) { 69 | return d.ListenPacketWithOptions(network, address, &Options{ 70 | InterfaceName: d.InterfaceName.Load(), 71 | InterfaceIndex: int(d.InterfaceIndex.Load()), 72 | RoutingMark: int(d.RoutingMark.Load()), 73 | }) 74 | } 75 | 76 | func (_ *Dialer) ListenPacketWithOptions(network, address string, opts *Options) (net.PacketConn, error) { 77 | lc := &net.ListenConfig{ 78 | Control: func(network, address string, c syscall.RawConn) error { 79 | return setSocketOptions(network, address, c, opts) 80 | }, 81 | } 82 | return lc.ListenPacket(context.Background(), network, address) 83 | } 84 | -------------------------------------------------------------------------------- /dialer/sockopt.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | func isTCPSocket(network string) bool { 4 | switch network { 5 | case "tcp", "tcp4", "tcp6": 6 | return true 7 | default: 8 | return false 9 | } 10 | } 11 | 12 | func isUDPSocket(network string) bool { 13 | switch network { 14 | case "udp", "udp4", "udp6": 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dialer/sockopt_darwin.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.InterfaceIndex == 0 && opts.InterfaceName != "" { 23 | if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil { 24 | opts.InterfaceIndex = iface.Index 25 | } 26 | } 27 | 28 | if opts.InterfaceIndex != 0 { 29 | switch network { 30 | case "tcp4", "udp4": 31 | innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, opts.InterfaceIndex) 32 | case "tcp6", "udp6": 33 | innerErr = unix.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, opts.InterfaceIndex) 34 | } 35 | if innerErr != nil { 36 | return 37 | } 38 | } 39 | }) 40 | 41 | if innerErr != nil { 42 | err = innerErr 43 | } 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /dialer/sockopt_freebsd.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.RoutingMark != 0 { 23 | if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_USER_COOKIE, opts.RoutingMark); innerErr != nil { 24 | return 25 | } 26 | } 27 | }) 28 | 29 | if innerErr != nil { 30 | err = innerErr 31 | } 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /dialer/sockopt_linux.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.InterfaceName == "" && opts.InterfaceIndex != 0 { 23 | if iface, err := net.InterfaceByIndex(opts.InterfaceIndex); err == nil { 24 | opts.InterfaceName = iface.Name 25 | } 26 | } 27 | 28 | if opts.InterfaceName != "" { 29 | if innerErr = unix.BindToDevice(int(fd), opts.InterfaceName); innerErr != nil { 30 | return 31 | } 32 | } 33 | if opts.RoutingMark != 0 { 34 | if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, opts.RoutingMark); innerErr != nil { 35 | return 36 | } 37 | } 38 | }) 39 | 40 | if innerErr != nil { 41 | err = innerErr 42 | } 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /dialer/sockopt_openbsd.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 11 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 12 | return 13 | } 14 | 15 | var innerErr error 16 | err = c.Control(func(fd uintptr) { 17 | host, _, _ := net.SplitHostPort(address) 18 | if ip := net.ParseIP(host); ip != nil && !ip.IsGlobalUnicast() { 19 | return 20 | } 21 | 22 | if opts.RoutingMark != 0 { 23 | if innerErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RTABLE, opts.RoutingMark); innerErr != nil { 24 | return 25 | } 26 | } 27 | }) 28 | 29 | if innerErr != nil { 30 | err = innerErr 31 | } 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /dialer/sockopt_others.go: -------------------------------------------------------------------------------- 1 | //go:build !unix && !windows 2 | 3 | package dialer 4 | 5 | import "syscall" 6 | 7 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) error { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /dialer/sockopt_windows.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | "syscall" 7 | "unsafe" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | const ( 13 | IP_UNICAST_IF = 31 14 | IPV6_UNICAST_IF = 31 15 | ) 16 | 17 | func setSocketOptions(network, address string, c syscall.RawConn, opts *Options) (err error) { 18 | if opts == nil || !isTCPSocket(network) && !isUDPSocket(network) { 19 | return 20 | } 21 | 22 | var innerErr error 23 | err = c.Control(func(fd uintptr) { 24 | host, _, _ := net.SplitHostPort(address) 25 | ip := net.ParseIP(host) 26 | if ip != nil && !ip.IsGlobalUnicast() { 27 | return 28 | } 29 | 30 | if opts.InterfaceIndex == 0 && opts.InterfaceName != "" { 31 | if iface, err := net.InterfaceByName(opts.InterfaceName); err == nil { 32 | opts.InterfaceIndex = iface.Index 33 | } 34 | } 35 | 36 | if opts.InterfaceIndex != 0 { 37 | switch network { 38 | case "tcp4", "udp4": 39 | innerErr = bindSocketToInterface4(windows.Handle(fd), uint32(opts.InterfaceIndex)) 40 | case "tcp6", "udp6": 41 | innerErr = bindSocketToInterface6(windows.Handle(fd), uint32(opts.InterfaceIndex)) 42 | if network == "udp6" && ip == nil { 43 | // The underlying IP net maybe IPv4 even if the `network` param is `udp6`, 44 | // so we should bind socket to interface4 at the same time. 45 | innerErr = bindSocketToInterface4(windows.Handle(fd), uint32(opts.InterfaceIndex)) 46 | } 47 | } 48 | } 49 | }) 50 | 51 | if innerErr != nil { 52 | err = innerErr 53 | } 54 | return 55 | } 56 | 57 | func bindSocketToInterface4(handle windows.Handle, index uint32) error { 58 | // For IPv4, this parameter must be an interface index in network byte order. 59 | // Ref: https://learn.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options 60 | var bytes [4]byte 61 | binary.BigEndian.PutUint32(bytes[:], index) 62 | index = *(*uint32)(unsafe.Pointer(&bytes[0])) 63 | return windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(index)) 64 | } 65 | 66 | func bindSocketToInterface6(handle windows.Handle, index uint32) error { 67 | return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, int(index)) 68 | } 69 | -------------------------------------------------------------------------------- /dns/resolver.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/xjasonlyu/tun2socks/v2/dialer" 7 | ) 8 | 9 | func init() { 10 | // We must use this DialContext to query DNS 11 | // when using net default resolver. 12 | net.DefaultResolver.PreferGo = true 13 | net.DefaultResolver.Dial = dialer.DefaultDialer.DialContext 14 | } 15 | -------------------------------------------------------------------------------- /docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TUN="${TUN:-tun0}" 4 | ADDR="${ADDR:-198.18.0.1/15}" 5 | LOGLEVEL="${LOGLEVEL:-info}" 6 | 7 | # default values 8 | TABLE="${TABLE:-0x22b}" 9 | FWMARK="${FWMARK:-0x22b}" 10 | CLONE_MAIN="${CLONE_MAIN:-1}" 11 | 12 | create_tun() { 13 | # create tun device 14 | ip tuntap add mode tun dev "$TUN" 15 | ip addr add "$ADDR" dev "$TUN" 16 | ip link set dev "$TUN" up 17 | } 18 | 19 | create_table() { 20 | if [ "$CLONE_MAIN" -ne 0 ]; then 21 | # clone main route table 22 | ip route show table main | 23 | while read -r route; do 24 | ip route add ${route%linkdown*} table "$TABLE" 25 | done 26 | # replace default route 27 | ip route replace default dev "$TUN" table "$TABLE" 28 | else 29 | # just add default route 30 | ip route add default dev "$TUN" table "$TABLE" 31 | fi 32 | } 33 | 34 | config_route() { 35 | # policy routing 36 | ip rule add not fwmark "$FWMARK" table "$TABLE" 37 | ip rule add fwmark "$FWMARK" to "$ADDR" prohibit 38 | 39 | # add tun included routes 40 | for addr in $(echo "$TUN_INCLUDED_ROUTES" | tr ',' '\n'); do 41 | ip rule add to "$addr" table "$TABLE" 42 | done 43 | 44 | # add tun excluded routes 45 | for addr in $(echo "$TUN_EXCLUDED_ROUTES" | tr ',' '\n'); do 46 | ip rule add to "$addr" table main 47 | done 48 | } 49 | 50 | run() { 51 | create_tun 52 | create_table 53 | config_route 54 | 55 | # execute extra commands 56 | if [ -n "$EXTRA_COMMANDS" ]; then 57 | sh -c "$EXTRA_COMMANDS" 58 | fi 59 | 60 | if [ -n "$MTU" ]; then 61 | ARGS="--mtu $MTU" 62 | fi 63 | 64 | if [ -n "$RESTAPI" ]; then 65 | ARGS="$ARGS --restapi $RESTAPI" 66 | fi 67 | 68 | if [ -n "$UDP_TIMEOUT" ]; then 69 | ARGS="$ARGS --udp-timeout $UDP_TIMEOUT" 70 | fi 71 | 72 | if [ -n "$TCP_SNDBUF" ]; then 73 | ARGS="$ARGS --tcp-sndbuf $TCP_SNDBUF" 74 | fi 75 | 76 | if [ -n "$TCP_RCVBUF" ]; then 77 | ARGS="$ARGS --tcp-rcvbuf $TCP_RCVBUF" 78 | fi 79 | 80 | if [ "$TCP_AUTO_TUNING" = 1 ]; then 81 | ARGS="$ARGS --tcp-auto-tuning" 82 | fi 83 | 84 | if [ -n "$MULTICAST_GROUPS" ]; then 85 | ARGS="$ARGS --multicast-groups $MULTICAST_GROUPS" 86 | fi 87 | 88 | exec tun2socks \ 89 | --loglevel "$LOGLEVEL" \ 90 | --fwmark "$FWMARK" \ 91 | --device "$TUN" \ 92 | --proxy "$PROXY" \ 93 | $ARGS 94 | } 95 | 96 | run || exit 1 97 | -------------------------------------------------------------------------------- /docs/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjasonlyu/tun2socks/4127937ea7c450a5230b273f406c9410acec2be7/docs/benchmark.png -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjasonlyu/tun2socks/4127937ea7c450a5230b273f406c9410acec2be7/docs/icon.png -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjasonlyu/tun2socks/4127937ea7c450a5230b273f406c9410acec2be7/docs/logo.png -------------------------------------------------------------------------------- /engine/engine.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/netip" 7 | "os/exec" 8 | "sync" 9 | "time" 10 | 11 | "github.com/docker/go-units" 12 | "github.com/google/shlex" 13 | "gvisor.dev/gvisor/pkg/tcpip" 14 | "gvisor.dev/gvisor/pkg/tcpip/stack" 15 | 16 | "github.com/xjasonlyu/tun2socks/v2/core" 17 | "github.com/xjasonlyu/tun2socks/v2/core/device" 18 | "github.com/xjasonlyu/tun2socks/v2/core/option" 19 | "github.com/xjasonlyu/tun2socks/v2/dialer" 20 | "github.com/xjasonlyu/tun2socks/v2/log" 21 | "github.com/xjasonlyu/tun2socks/v2/proxy" 22 | "github.com/xjasonlyu/tun2socks/v2/restapi" 23 | "github.com/xjasonlyu/tun2socks/v2/tunnel" 24 | ) 25 | 26 | var ( 27 | _engineMu sync.Mutex 28 | 29 | // _defaultKey holds the default key for the engine. 30 | _defaultKey *Key 31 | 32 | // _defaultProxy holds the default proxy for the engine. 33 | _defaultProxy proxy.Proxy 34 | 35 | // _defaultDevice holds the default device for the engine. 36 | _defaultDevice device.Device 37 | 38 | // _defaultStack holds the default stack for the engine. 39 | _defaultStack *stack.Stack 40 | ) 41 | 42 | // Start starts the default engine up. 43 | func Start() { 44 | if err := start(); err != nil { 45 | log.Fatalf("[ENGINE] failed to start: %v", err) 46 | } 47 | } 48 | 49 | // Stop shuts the default engine down. 50 | func Stop() { 51 | if err := stop(); err != nil { 52 | log.Fatalf("[ENGINE] failed to stop: %v", err) 53 | } 54 | } 55 | 56 | // Insert loads *Key to the default engine. 57 | func Insert(k *Key) { 58 | _engineMu.Lock() 59 | _defaultKey = k 60 | _engineMu.Unlock() 61 | } 62 | 63 | func start() error { 64 | _engineMu.Lock() 65 | defer _engineMu.Unlock() 66 | 67 | if _defaultKey == nil { 68 | return errors.New("empty key") 69 | } 70 | 71 | for _, f := range []func(*Key) error{ 72 | general, 73 | restAPI, 74 | netstack, 75 | } { 76 | if err := f(_defaultKey); err != nil { 77 | return err 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func stop() (err error) { 84 | _engineMu.Lock() 85 | if _defaultDevice != nil { 86 | _defaultDevice.Close() 87 | } 88 | if _defaultStack != nil { 89 | _defaultStack.Close() 90 | _defaultStack.Wait() 91 | } 92 | _engineMu.Unlock() 93 | return nil 94 | } 95 | 96 | func execCommand(cmd string) error { 97 | parts, err := shlex.Split(cmd) 98 | if err != nil { 99 | return err 100 | } 101 | if len(parts) == 0 { 102 | return errors.New("empty command") 103 | } 104 | _, err = exec.Command(parts[0], parts[1:]...).Output() 105 | return err 106 | } 107 | 108 | func general(k *Key) error { 109 | level, err := log.ParseLevel(k.LogLevel) 110 | if err != nil { 111 | return err 112 | } 113 | log.SetLogger(log.Must(log.NewLeveled(level))) 114 | 115 | if k.Interface != "" { 116 | iface, err := net.InterfaceByName(k.Interface) 117 | if err != nil { 118 | return err 119 | } 120 | dialer.DefaultDialer.InterfaceName.Store(iface.Name) 121 | dialer.DefaultDialer.InterfaceIndex.Store(int32(iface.Index)) 122 | log.Infof("[DIALER] bind to interface: %s", k.Interface) 123 | } 124 | 125 | if k.Mark != 0 { 126 | dialer.DefaultDialer.RoutingMark.Store(int32(k.Mark)) 127 | log.Infof("[DIALER] set fwmark: %#x", k.Mark) 128 | } 129 | 130 | if k.UDPTimeout > 0 { 131 | if k.UDPTimeout < time.Second { 132 | return errors.New("invalid udp timeout value") 133 | } 134 | tunnel.T().SetUDPTimeout(k.UDPTimeout) 135 | } 136 | return nil 137 | } 138 | 139 | func restAPI(k *Key) error { 140 | if k.RestAPI != "" { 141 | u, err := parseRestAPI(k.RestAPI) 142 | if err != nil { 143 | return err 144 | } 145 | host, token := u.Host, u.User.String() 146 | 147 | restapi.SetStatsFunc(func() tcpip.Stats { 148 | _engineMu.Lock() 149 | defer _engineMu.Unlock() 150 | 151 | // default stack is not initialized. 152 | if _defaultStack == nil { 153 | return tcpip.Stats{} 154 | } 155 | return _defaultStack.Stats() 156 | }) 157 | 158 | go func() { 159 | if err := restapi.Start(host, token); err != nil { 160 | log.Errorf("[RESTAPI] failed to start: %v", err) 161 | } 162 | }() 163 | log.Infof("[RESTAPI] serve at: %s", u) 164 | } 165 | return nil 166 | } 167 | 168 | func netstack(k *Key) (err error) { 169 | if k.Proxy == "" { 170 | return errors.New("empty proxy") 171 | } 172 | if k.Device == "" { 173 | return errors.New("empty device") 174 | } 175 | 176 | if k.TUNPreUp != "" { 177 | log.Infof("[TUN] pre-execute command: `%s`", k.TUNPreUp) 178 | if preUpErr := execCommand(k.TUNPreUp); preUpErr != nil { 179 | log.Errorf("[TUN] failed to pre-execute: %s: %v", k.TUNPreUp, preUpErr) 180 | } 181 | } 182 | 183 | defer func() { 184 | if k.TUNPostUp == "" || err != nil { 185 | return 186 | } 187 | log.Infof("[TUN] post-execute command: `%s`", k.TUNPostUp) 188 | if postUpErr := execCommand(k.TUNPostUp); postUpErr != nil { 189 | log.Errorf("[TUN] failed to post-execute: %s: %v", k.TUNPostUp, postUpErr) 190 | } 191 | }() 192 | 193 | if _defaultProxy, err = parseProxy(k.Proxy); err != nil { 194 | return 195 | } 196 | tunnel.T().SetDialer(_defaultProxy) 197 | 198 | if _defaultDevice, err = parseDevice(k.Device, uint32(k.MTU)); err != nil { 199 | return 200 | } 201 | 202 | var multicastGroups []netip.Addr 203 | if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil { 204 | return err 205 | } 206 | 207 | var opts []option.Option 208 | if k.TCPModerateReceiveBuffer { 209 | opts = append(opts, option.WithTCPModerateReceiveBuffer(true)) 210 | } 211 | 212 | if k.TCPSendBufferSize != "" { 213 | size, err := units.RAMInBytes(k.TCPSendBufferSize) 214 | if err != nil { 215 | return err 216 | } 217 | opts = append(opts, option.WithTCPSendBufferSize(int(size))) 218 | } 219 | 220 | if k.TCPReceiveBufferSize != "" { 221 | size, err := units.RAMInBytes(k.TCPReceiveBufferSize) 222 | if err != nil { 223 | return err 224 | } 225 | opts = append(opts, option.WithTCPReceiveBufferSize(int(size))) 226 | } 227 | 228 | if _defaultStack, err = core.CreateStack(&core.Config{ 229 | LinkEndpoint: _defaultDevice, 230 | TransportHandler: tunnel.T(), 231 | MulticastGroups: multicastGroups, 232 | Options: opts, 233 | }); err != nil { 234 | return 235 | } 236 | 237 | log.Infof( 238 | "[STACK] %s://%s <-> %s://%s", 239 | _defaultDevice.Type(), _defaultDevice.Name(), 240 | _defaultProxy.Proto(), _defaultProxy.Addr(), 241 | ) 242 | return nil 243 | } 244 | -------------------------------------------------------------------------------- /engine/key.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import "time" 4 | 5 | type Key struct { 6 | MTU int `yaml:"mtu"` 7 | Mark int `yaml:"fwmark"` 8 | Proxy string `yaml:"proxy"` 9 | RestAPI string `yaml:"restapi"` 10 | Device string `yaml:"device"` 11 | LogLevel string `yaml:"loglevel"` 12 | Interface string `yaml:"interface"` 13 | TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"` 14 | TCPSendBufferSize string `yaml:"tcp-send-buffer-size"` 15 | TCPReceiveBufferSize string `yaml:"tcp-receive-buffer-size"` 16 | MulticastGroups string `yaml:"multicast-groups"` 17 | TUNPreUp string `yaml:"tun-pre-up"` 18 | TUNPostUp string `yaml:"tun-post-up"` 19 | UDPTimeout time.Duration `yaml:"udp-timeout"` 20 | } 21 | -------------------------------------------------------------------------------- /engine/parse.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net" 7 | "net/netip" 8 | "net/url" 9 | "runtime" 10 | "strings" 11 | 12 | "github.com/gorilla/schema" 13 | 14 | "github.com/xjasonlyu/tun2socks/v2/core/device" 15 | "github.com/xjasonlyu/tun2socks/v2/core/device/fdbased" 16 | "github.com/xjasonlyu/tun2socks/v2/core/device/tun" 17 | "github.com/xjasonlyu/tun2socks/v2/proxy" 18 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 19 | ) 20 | 21 | func parseRestAPI(s string) (*url.URL, error) { 22 | if !strings.Contains(s, "://") { 23 | s = fmt.Sprintf("%s://%s", "http", s) 24 | } 25 | 26 | u, err := url.Parse(s) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | addr, err := net.ResolveTCPAddr("tcp", u.Host) 32 | if err != nil { 33 | return nil, err 34 | } 35 | if addr.IP == nil { 36 | addr.IP = net.IPv4zero /* default: 0.0.0.0 */ 37 | } 38 | u.Host = addr.String() 39 | 40 | switch u.Scheme { 41 | case "http": 42 | return u, nil 43 | default: 44 | return nil, fmt.Errorf("unsupported scheme: %s", u.Scheme) 45 | } 46 | } 47 | 48 | func parseDevice(s string, mtu uint32) (device.Device, error) { 49 | if !strings.Contains(s, "://") { 50 | s = fmt.Sprintf("%s://%s", tun.Driver /* default driver */, s) 51 | } 52 | 53 | u, err := url.Parse(s) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | driver := strings.ToLower(u.Scheme) 59 | 60 | switch driver { 61 | case fdbased.Driver: 62 | return parseFD(u, mtu) 63 | case tun.Driver: 64 | return parseTUN(u, mtu) 65 | default: 66 | return nil, fmt.Errorf("unsupported driver: %s", driver) 67 | } 68 | } 69 | 70 | func parseFD(u *url.URL, mtu uint32) (device.Device, error) { 71 | offset := 0 72 | // fd offset in ios 73 | // https://stackoverflow.com/questions/69260852/ios-network-extension-packet-parsing/69487795#69487795 74 | if runtime.GOOS == "ios" { 75 | offset = 4 76 | } 77 | return fdbased.Open(u.Host, mtu, offset) 78 | } 79 | 80 | func parseProxy(s string) (proxy.Proxy, error) { 81 | if !strings.Contains(s, "://") { 82 | s = fmt.Sprintf("%s://%s", proto.Socks5 /* default protocol */, s) 83 | } 84 | 85 | u, err := url.Parse(s) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | protocol := strings.ToLower(u.Scheme) 91 | 92 | switch protocol { 93 | case proto.Direct.String(): 94 | return proxy.NewDirect(), nil 95 | case proto.Reject.String(): 96 | return proxy.NewReject(), nil 97 | case proto.HTTP.String(): 98 | return parseHTTP(u) 99 | case proto.Socks4.String(): 100 | return parseSocks4(u) 101 | case proto.Socks5.String(): 102 | return parseSocks5(u) 103 | case proto.Shadowsocks.String(): 104 | return parseShadowsocks(u) 105 | case proto.Relay.String(): 106 | return parseRelay(u) 107 | default: 108 | return nil, fmt.Errorf("unsupported protocol: %s", protocol) 109 | } 110 | } 111 | 112 | func parseHTTP(u *url.URL) (proxy.Proxy, error) { 113 | address, username := u.Host, u.User.Username() 114 | password, _ := u.User.Password() 115 | return proxy.NewHTTP(address, username, password) 116 | } 117 | 118 | func parseSocks4(u *url.URL) (proxy.Proxy, error) { 119 | address, userID := u.Host, u.User.Username() 120 | return proxy.NewSocks4(address, userID) 121 | } 122 | 123 | func parseSocks5(u *url.URL) (proxy.Proxy, error) { 124 | address, username := u.Host, u.User.Username() 125 | password, _ := u.User.Password() 126 | 127 | // Socks5 over UDS 128 | if address == "" { 129 | address = u.Path 130 | } 131 | return proxy.NewSocks5(address, username, password) 132 | } 133 | 134 | func parseShadowsocks(u *url.URL) (proxy.Proxy, error) { 135 | var ( 136 | address = u.Host 137 | method, password string 138 | obfsMode, obfsHost string 139 | ) 140 | 141 | if ss := u.User.String(); ss == "" { 142 | method = "dummy" // none cipher mode 143 | } else if pass, set := u.User.Password(); set { 144 | method = u.User.Username() 145 | password = pass 146 | } else { 147 | data, _ := base64.RawURLEncoding.DecodeString(ss) 148 | userInfo := strings.SplitN(string(data), ":", 2) 149 | if len(userInfo) == 2 { 150 | method = userInfo[0] 151 | password = userInfo[1] 152 | } 153 | } 154 | 155 | rawQuery, _ := url.QueryUnescape(u.RawQuery) 156 | for _, s := range strings.Split(rawQuery, ";") { 157 | data := strings.SplitN(s, "=", 2) 158 | if len(data) != 2 { 159 | continue 160 | } 161 | key := data[0] 162 | value := data[1] 163 | 164 | switch key { 165 | case "obfs": 166 | obfsMode = value 167 | case "obfs-host": 168 | obfsHost = value 169 | } 170 | } 171 | 172 | return proxy.NewShadowsocks(address, method, password, obfsMode, obfsHost) 173 | } 174 | 175 | func parseRelay(u *url.URL) (proxy.Proxy, error) { 176 | address, username := u.Host, u.User.Username() 177 | password, _ := u.User.Password() 178 | 179 | opts := struct { 180 | NoDelay bool 181 | }{} 182 | if err := schema.NewDecoder().Decode(&opts, u.Query()); err != nil { 183 | return nil, err 184 | } 185 | 186 | return proxy.NewRelay(address, username, password, opts.NoDelay) 187 | } 188 | 189 | func parseMulticastGroups(s string) (multicastGroups []netip.Addr, _ error) { 190 | for _, ip := range strings.Split(s, ",") { 191 | if ip = strings.TrimSpace(ip); ip == "" { 192 | continue 193 | } 194 | addr, err := netip.ParseAddr(ip) 195 | if err != nil { 196 | return nil, err 197 | } 198 | if !addr.IsMulticast() { 199 | return nil, fmt.Errorf("invalid multicast IP: %s", addr) 200 | } 201 | multicastGroups = append(multicastGroups, addr) 202 | } 203 | return 204 | } 205 | -------------------------------------------------------------------------------- /engine/parse_unix.go: -------------------------------------------------------------------------------- 1 | //go:build unix 2 | 3 | package engine 4 | 5 | import ( 6 | "net/url" 7 | 8 | "github.com/xjasonlyu/tun2socks/v2/core/device" 9 | "github.com/xjasonlyu/tun2socks/v2/core/device/tun" 10 | ) 11 | 12 | func parseTUN(u *url.URL, mtu uint32) (device.Device, error) { 13 | return tun.Open(u.Host, mtu) 14 | } 15 | -------------------------------------------------------------------------------- /engine/parse_windows.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/gorilla/schema" 7 | "golang.org/x/sys/windows" 8 | wun "golang.zx2c4.com/wireguard/tun" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/core/device" 11 | "github.com/xjasonlyu/tun2socks/v2/core/device/tun" 12 | "github.com/xjasonlyu/tun2socks/v2/internal/version" 13 | ) 14 | 15 | func init() { 16 | wun.WintunTunnelType = version.Name 17 | } 18 | 19 | func parseTUN(u *url.URL, mtu uint32) (device.Device, error) { 20 | opts := struct { 21 | GUID string 22 | }{} 23 | if err := schema.NewDecoder().Decode(&opts, u.Query()); err != nil { 24 | return nil, err 25 | } 26 | if opts.GUID != "" { 27 | guid, err := windows.GUIDFromString(opts.GUID) 28 | if err != nil { 29 | return nil, err 30 | } 31 | wun.WintunStaticRequestedGUID = &guid 32 | } 33 | return tun.Open(u.Host, mtu) 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xjasonlyu/tun2socks/v2 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/docker/go-units v0.5.0 7 | github.com/go-chi/chi/v5 v5.2.1 8 | github.com/go-chi/cors v1.2.1 9 | github.com/go-chi/render v1.0.3 10 | github.com/go-gost/relay v0.5.0 11 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 12 | github.com/google/uuid v1.6.0 13 | github.com/gorilla/schema v1.4.1 14 | github.com/gorilla/websocket v1.5.3 15 | github.com/stretchr/testify v1.9.0 16 | go.uber.org/atomic v1.11.0 17 | go.uber.org/automaxprocs v1.6.0 18 | go.uber.org/zap v1.27.0 19 | golang.org/x/crypto v0.38.0 20 | golang.org/x/sys v0.33.0 21 | golang.org/x/time v0.11.0 22 | golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb 23 | gopkg.in/yaml.v3 v3.0.1 24 | gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 25 | ) 26 | 27 | require ( 28 | github.com/ajg/form v1.5.1 // indirect 29 | github.com/davecgh/go-spew v1.1.1 // indirect 30 | github.com/google/btree v1.1.3 // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | go.uber.org/multierr v1.11.0 // indirect 33 | golang.org/x/net v0.40.0 // indirect 34 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= 2 | github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 6 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 7 | github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= 8 | github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 9 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 10 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 11 | github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= 12 | github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= 13 | github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c= 14 | github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8= 15 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 16 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 17 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 18 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 19 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 20 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 21 | github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= 22 | github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= 23 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 24 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 25 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 26 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 27 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 28 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 32 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 33 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 34 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 35 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 36 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 37 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 38 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 39 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 40 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 41 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 42 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 43 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 44 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 45 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 46 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 47 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 48 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 49 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 50 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 51 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 52 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 53 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= 54 | golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 55 | golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= 56 | golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= 57 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 58 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 59 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 61 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 62 | gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20 h1:0DxLu8hxI1OGp1qVRPqNd+2k1a7hMNUNqbZG0IrtKlM= 63 | gvisor.dev/gvisor v0.0.0-20250523182742-eede7a881b20/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= 64 | -------------------------------------------------------------------------------- /internal/pool/pool.go: -------------------------------------------------------------------------------- 1 | // Package pool provides internal pool utilities. 2 | package pool 3 | 4 | import ( 5 | "sync" 6 | ) 7 | 8 | // A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed 9 | // object pooling. 10 | // 11 | // Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will 12 | // not be detected, so all internal pool use must take care to only store 13 | // pointer types. 14 | type Pool[T any] struct { 15 | pool sync.Pool 16 | } 17 | 18 | // New returns a new [Pool] for T, and will use fn to construct new Ts when 19 | // the pool is empty. 20 | func New[T any](fn func() T) *Pool[T] { 21 | return &Pool[T]{ 22 | pool: sync.Pool{ 23 | New: func() any { 24 | return fn() 25 | }, 26 | }, 27 | } 28 | } 29 | 30 | // Get gets a T from the pool, or creates a new one if the pool is empty. 31 | func (p *Pool[T]) Get() T { 32 | return p.pool.Get().(T) 33 | } 34 | 35 | // Put returns x into the pool. 36 | func (p *Pool[T]) Put(x T) { 37 | p.pool.Put(x) 38 | } 39 | -------------------------------------------------------------------------------- /internal/pool/pool_test.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "runtime/debug" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | type pooledValue[T any] struct { 12 | value T 13 | } 14 | 15 | func TestNew(t *testing.T) { 16 | // Disable GC to avoid the victim cache during the test. 17 | defer debug.SetGCPercent(debug.SetGCPercent(-1)) 18 | 19 | p := New(func() *pooledValue[string] { 20 | return &pooledValue[string]{ 21 | value: "new", 22 | } 23 | }) 24 | 25 | // Probabilistically, 75% of sync.Pool.Put calls will succeed when -race 26 | // is enabled (see ref below); attempt to make this quasi-deterministic by 27 | // brute force (i.e., put significantly more objects in the pool than we 28 | // will need for the test) in order to avoid testing without race enabled. 29 | // 30 | // ref: https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/sync/pool.go;l=100-103 31 | for i := 0; i < 1_000; i++ { 32 | p.Put(&pooledValue[string]{ 33 | value: t.Name(), 34 | }) 35 | } 36 | 37 | // Ensure that we always get the expected value. Note that this must only 38 | // run a fraction of the number of times that Put is called above. 39 | for i := 0; i < 10; i++ { 40 | func() { 41 | x := p.Get() 42 | defer p.Put(x) 43 | require.Equal(t, t.Name(), x.value) 44 | }() 45 | } 46 | 47 | // Depool all objects that might be in the pool to ensure that it's empty. 48 | for i := 0; i < 1_000; i++ { 49 | p.Get() 50 | } 51 | 52 | // Now that the pool is empty, it should use the value specified in the 53 | // underlying sync.Pool.New func. 54 | require.Equal(t, "new", p.Get().value) 55 | } 56 | 57 | func TestNew_Race(t *testing.T) { 58 | p := New(func() *pooledValue[int] { 59 | return &pooledValue[int]{ 60 | value: -1, 61 | } 62 | }) 63 | 64 | var wg sync.WaitGroup 65 | defer wg.Wait() 66 | 67 | // Run a number of goroutines that read and write pool object fields to 68 | // tease out races. 69 | for i := 0; i < 1_000; i++ { 70 | i := i 71 | 72 | wg.Add(1) 73 | go func() { 74 | defer wg.Done() 75 | 76 | x := p.Get() 77 | defer p.Put(x) 78 | 79 | // Must both read and write the field. 80 | if n := x.value; n >= -1 { 81 | x.value = i 82 | } 83 | }() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /internal/version/module.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "runtime/debug" 5 | ) 6 | 7 | // Info returns project dependencies as []*debug.Module. 8 | func Info() []*debug.Module { 9 | bi, _ := debug.ReadBuildInfo() 10 | return bi.Deps 11 | } 12 | -------------------------------------------------------------------------------- /internal/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | const Name = "tun2socks" 10 | 11 | var ( 12 | // Version can be set at link time by executing 13 | // the command: `git describe --abbrev=0 --tags HEAD` 14 | Version string 15 | 16 | // GitCommit can be set at link time by executing 17 | // the command: `git rev-parse --short HEAD` 18 | GitCommit string 19 | ) 20 | 21 | func String() string { 22 | return fmt.Sprintf("%s-%s", Name, strings.TrimPrefix(Version, "v")) 23 | } 24 | 25 | func BuildString() string { 26 | return fmt.Sprintf("%s/%s, %s, %s", runtime.GOOS, runtime.GOARCH, runtime.Version(), GitCommit) 27 | } 28 | -------------------------------------------------------------------------------- /log/doc.go: -------------------------------------------------------------------------------- 1 | // Package log is a thin wrapper based on "go.uber.org/zap". 2 | package log 3 | -------------------------------------------------------------------------------- /log/emitter.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "runtime" 5 | "strings" 6 | "time" 7 | 8 | glog "gvisor.dev/gvisor/pkg/log" 9 | ) 10 | 11 | var _globalE = &emitter{} 12 | 13 | func init() { 14 | glog.SetTarget(_globalE) 15 | } 16 | 17 | type emitter struct { 18 | logger *SugaredLogger 19 | } 20 | 21 | func (e *emitter) setLogger(logger *SugaredLogger) { 22 | e.logger = logger.WithOptions(pkgCallerSkip) 23 | } 24 | 25 | func (e *emitter) logf(level glog.Level, format string, args ...any) { 26 | e.logger.Logf(1-Level(level), "[STACK] "+format, args...) 27 | } 28 | 29 | func (e *emitter) Emit(depth int, level glog.Level, _ time.Time, format string, args ...any) { 30 | if _, file, line, ok := runtime.Caller(depth + 1); ok { 31 | // Ignore: gvisor.dev/gvisor/pkg/tcpip/adapters/gonet/gonet.go:457 32 | if line == 457 && strings.HasSuffix(file, "gonet/gonet.go") { 33 | return 34 | } 35 | } 36 | e.logf(level, format, args...) 37 | } 38 | -------------------------------------------------------------------------------- /log/level.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "go.uber.org/zap/zapcore" 5 | ) 6 | 7 | // Level is an alias for zapcore.Level. 8 | type Level = zapcore.Level 9 | 10 | // Levels are aliases for Level. 11 | const ( 12 | DebugLevel = zapcore.DebugLevel 13 | InfoLevel = zapcore.InfoLevel 14 | WarnLevel = zapcore.WarnLevel 15 | ErrorLevel = zapcore.ErrorLevel 16 | DPanicLevel = zapcore.DPanicLevel 17 | PanicLevel = zapcore.PanicLevel 18 | FatalLevel = zapcore.FatalLevel 19 | InvalidLevel = zapcore.InvalidLevel 20 | SilentLevel = InvalidLevel + 1 21 | ) 22 | 23 | // ParseLevel is a thin wrapper for zapcore.ParseLevel. 24 | func ParseLevel(text string) (Level, error) { 25 | switch text { 26 | case "silent", "SILENT": 27 | return SilentLevel, nil 28 | default: 29 | return zapcore.ParseLevel(text) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "go.uber.org/zap" 8 | ) 9 | 10 | // global Logger and SugaredLogger. 11 | var ( 12 | _globalMu sync.RWMutex 13 | _globalL *Logger 14 | _globalS *SugaredLogger 15 | ) 16 | 17 | func init() { 18 | SetLogger(zap.Must(zap.NewProduction())) 19 | } 20 | 21 | func NewLeveled(l Level, options ...Option) (*Logger, error) { 22 | switch l { 23 | case SilentLevel: 24 | return zap.NewNop(), nil 25 | case DebugLevel: 26 | return zap.NewDevelopment(options...) 27 | case InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel: 28 | cfg := zap.NewProductionConfig() 29 | cfg.Level.SetLevel(l) 30 | return cfg.Build(options...) 31 | default: 32 | return nil, fmt.Errorf("invalid level: %s", l) 33 | } 34 | } 35 | 36 | // SetLogger sets the global Logger and SugaredLogger. 37 | func SetLogger(logger *Logger) { 38 | _globalMu.Lock() 39 | defer _globalMu.Unlock() 40 | // apply pkgCallerSkip to global loggers. 41 | _globalL = logger.WithOptions(pkgCallerSkip) 42 | _globalS = _globalL.Sugar() 43 | _globalE.setLogger(_globalS) 44 | } 45 | 46 | func logf(lvl Level, template string, args ...any) { 47 | _globalMu.RLock() 48 | s := _globalS 49 | _globalMu.RUnlock() 50 | s.Logf(lvl, template, args...) 51 | } 52 | 53 | func Debugf(template string, args ...any) { 54 | logf(DebugLevel, template, args...) 55 | } 56 | 57 | func Infof(template string, args ...any) { 58 | logf(InfoLevel, template, args...) 59 | } 60 | 61 | func Warnf(template string, args ...any) { 62 | logf(WarnLevel, template, args...) 63 | } 64 | 65 | func Errorf(template string, args ...any) { 66 | logf(ErrorLevel, template, args...) 67 | } 68 | 69 | func Fatalf(template string, args ...any) { 70 | logf(FatalLevel, template, args...) 71 | } 72 | -------------------------------------------------------------------------------- /log/zap.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | ) 6 | 7 | // Must is an alias for zap.Must. 8 | var Must = zap.Must 9 | 10 | // logger aliases for zap.Logger and zap.SugaredLogger. 11 | type ( 12 | Logger = zap.Logger 13 | SugaredLogger = zap.SugaredLogger 14 | ) 15 | 16 | type ( 17 | // Option is an alias for zap.Option. 18 | Option = zap.Option 19 | ) 20 | 21 | // pkgCallerSkip skips the pkg wrapper code as the caller. 22 | var pkgCallerSkip = zap.AddCallerSkip(2) 23 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "go.uber.org/automaxprocs/maxprocs" 11 | "gopkg.in/yaml.v3" 12 | 13 | _ "github.com/xjasonlyu/tun2socks/v2/dns" 14 | "github.com/xjasonlyu/tun2socks/v2/engine" 15 | "github.com/xjasonlyu/tun2socks/v2/internal/version" 16 | "github.com/xjasonlyu/tun2socks/v2/log" 17 | ) 18 | 19 | var ( 20 | key = new(engine.Key) 21 | 22 | configFile string 23 | versionFlag bool 24 | ) 25 | 26 | func init() { 27 | flag.IntVar(&key.Mark, "fwmark", 0, "Set firewall MARK (Linux only)") 28 | flag.IntVar(&key.MTU, "mtu", 0, "Set device maximum transmission unit (MTU)") 29 | flag.DurationVar(&key.UDPTimeout, "udp-timeout", 0, "Set timeout for each UDP session") 30 | flag.StringVar(&configFile, "config", "", "YAML format configuration file") 31 | flag.StringVar(&key.Device, "device", "", "Use this device [driver://]name") 32 | flag.StringVar(&key.Interface, "interface", "", "Use network INTERFACE (Linux/MacOS only)") 33 | flag.StringVar(&key.LogLevel, "loglevel", "info", "Log level [debug|info|warn|error|silent]") 34 | flag.StringVar(&key.Proxy, "proxy", "", "Use this proxy [protocol://]host[:port]") 35 | flag.StringVar(&key.RestAPI, "restapi", "", "HTTP statistic server listen address") 36 | flag.StringVar(&key.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack") 37 | flag.StringVar(&key.TCPReceiveBufferSize, "tcp-rcvbuf", "", "Set TCP receive buffer size for netstack") 38 | flag.BoolVar(&key.TCPModerateReceiveBuffer, "tcp-auto-tuning", false, "Enable TCP receive buffer auto-tuning") 39 | flag.StringVar(&key.MulticastGroups, "multicast-groups", "", "Set multicast groups, separated by commas") 40 | flag.StringVar(&key.TUNPreUp, "tun-pre-up", "", "Execute a command before TUN device setup") 41 | flag.StringVar(&key.TUNPostUp, "tun-post-up", "", "Execute a command after TUN device setup") 42 | flag.BoolVar(&versionFlag, "version", false, "Show version and then quit") 43 | flag.Parse() 44 | } 45 | 46 | func main() { 47 | maxprocs.Set(maxprocs.Logger(func(string, ...any) {})) 48 | 49 | if versionFlag { 50 | fmt.Println(version.String()) 51 | fmt.Println(version.BuildString()) 52 | os.Exit(0) 53 | } 54 | 55 | if configFile != "" { 56 | data, err := os.ReadFile(configFile) 57 | if err != nil { 58 | log.Fatalf("Failed to read config file '%s': %v", configFile, err) 59 | } 60 | if err = yaml.Unmarshal(data, key); err != nil { 61 | log.Fatalf("Failed to unmarshal config file '%s': %v", configFile, err) 62 | } 63 | } 64 | 65 | engine.Insert(key) 66 | 67 | engine.Start() 68 | defer engine.Stop() 69 | 70 | sigCh := make(chan os.Signal, 1) 71 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 72 | <-sigCh 73 | } 74 | -------------------------------------------------------------------------------- /metadata/metadata.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | ) 7 | 8 | // Metadata contains metadata of transport protocol sessions. 9 | type Metadata struct { 10 | Network Network `json:"network"` 11 | SrcIP netip.Addr `json:"sourceIP"` 12 | MidIP netip.Addr `json:"dialerIP"` 13 | DstIP netip.Addr `json:"destinationIP"` 14 | SrcPort uint16 `json:"sourcePort"` 15 | MidPort uint16 `json:"dialerPort"` 16 | DstPort uint16 `json:"destinationPort"` 17 | } 18 | 19 | func (m *Metadata) DestinationAddrPort() netip.AddrPort { 20 | return netip.AddrPortFrom(m.DstIP, m.DstPort) 21 | } 22 | 23 | func (m *Metadata) DestinationAddress() string { 24 | return m.DestinationAddrPort().String() 25 | } 26 | 27 | func (m *Metadata) SourceAddrPort() netip.AddrPort { 28 | return netip.AddrPortFrom(m.SrcIP, m.SrcPort) 29 | } 30 | 31 | func (m *Metadata) SourceAddress() string { 32 | return m.SourceAddrPort().String() 33 | } 34 | 35 | func (m *Metadata) Addr() net.Addr { 36 | return &Addr{metadata: m} 37 | } 38 | 39 | func (m *Metadata) TCPAddr() *net.TCPAddr { 40 | if m.Network != TCP || !m.DstIP.IsValid() { 41 | return nil 42 | } 43 | return net.TCPAddrFromAddrPort(m.DestinationAddrPort()) 44 | } 45 | 46 | func (m *Metadata) UDPAddr() *net.UDPAddr { 47 | if m.Network != UDP || !m.DstIP.IsValid() { 48 | return nil 49 | } 50 | return net.UDPAddrFromAddrPort(m.DestinationAddrPort()) 51 | } 52 | 53 | // Addr implements the net.Addr interface. 54 | type Addr struct { 55 | metadata *Metadata 56 | } 57 | 58 | func (a *Addr) Metadata() *Metadata { 59 | return a.metadata 60 | } 61 | 62 | func (a *Addr) Network() string { 63 | return a.metadata.Network.String() 64 | } 65 | 66 | func (a *Addr) String() string { 67 | return a.metadata.DestinationAddress() 68 | } 69 | -------------------------------------------------------------------------------- /metadata/network.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | TCP Network = iota 9 | UDP 10 | ) 11 | 12 | type Network uint8 13 | 14 | func (n Network) String() string { 15 | switch n { 16 | case TCP: 17 | return "tcp" 18 | case UDP: 19 | return "udp" 20 | default: 21 | return fmt.Sprintf("network(%d)", n) 22 | } 23 | } 24 | 25 | func (n Network) MarshalText() ([]byte, error) { 26 | return []byte(n.String()), nil 27 | } 28 | -------------------------------------------------------------------------------- /proxy/base.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | 8 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 9 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 10 | ) 11 | 12 | var _ Proxy = (*Base)(nil) 13 | 14 | type Base struct { 15 | addr string 16 | proto proto.Proto 17 | } 18 | 19 | func (b *Base) Addr() string { 20 | return b.addr 21 | } 22 | 23 | func (b *Base) Proto() proto.Proto { 24 | return b.proto 25 | } 26 | 27 | func (b *Base) DialContext(context.Context, *M.Metadata) (net.Conn, error) { 28 | return nil, errors.ErrUnsupported 29 | } 30 | 31 | func (b *Base) DialUDP(*M.Metadata) (net.PacketConn, error) { 32 | return nil, errors.ErrUnsupported 33 | } 34 | -------------------------------------------------------------------------------- /proxy/direct.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/xjasonlyu/tun2socks/v2/dialer" 8 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 9 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 10 | ) 11 | 12 | var _ Proxy = (*Direct)(nil) 13 | 14 | type Direct struct { 15 | *Base 16 | } 17 | 18 | func NewDirect() *Direct { 19 | return &Direct{ 20 | Base: &Base{ 21 | proto: proto.Direct, 22 | }, 23 | } 24 | } 25 | 26 | func (d *Direct) DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) { 27 | c, err := dialer.DialContext(ctx, "tcp", metadata.DestinationAddress()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | setKeepAlive(c) 32 | return c, nil 33 | } 34 | 35 | func (d *Direct) DialUDP(*M.Metadata) (net.PacketConn, error) { 36 | pc, err := dialer.ListenPacket("udp", "") 37 | if err != nil { 38 | return nil, err 39 | } 40 | return &directPacketConn{PacketConn: pc}, nil 41 | } 42 | 43 | type directPacketConn struct { 44 | net.PacketConn 45 | } 46 | 47 | func (pc *directPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 48 | if udpAddr, ok := addr.(*net.UDPAddr); ok { 49 | return pc.PacketConn.WriteTo(b, udpAddr) 50 | } 51 | 52 | udpAddr, err := net.ResolveUDPAddr("udp", addr.String()) 53 | if err != nil { 54 | return 0, err 55 | } 56 | return pc.PacketConn.WriteTo(b, udpAddr) 57 | } 58 | -------------------------------------------------------------------------------- /proxy/http.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "encoding/base64" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | 14 | "github.com/xjasonlyu/tun2socks/v2/dialer" 15 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 16 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 17 | ) 18 | 19 | type HTTP struct { 20 | *Base 21 | 22 | user string 23 | pass string 24 | } 25 | 26 | func NewHTTP(addr, user, pass string) (*HTTP, error) { 27 | return &HTTP{ 28 | Base: &Base{ 29 | addr: addr, 30 | proto: proto.HTTP, 31 | }, 32 | user: user, 33 | pass: pass, 34 | }, nil 35 | } 36 | 37 | func (h *HTTP) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { 38 | c, err = dialer.DialContext(ctx, "tcp", h.Addr()) 39 | if err != nil { 40 | return nil, fmt.Errorf("connect to %s: %w", h.Addr(), err) 41 | } 42 | setKeepAlive(c) 43 | 44 | defer func(c net.Conn) { 45 | safeConnClose(c, err) 46 | }(c) 47 | 48 | err = h.shakeHand(metadata, c) 49 | return 50 | } 51 | 52 | func (h *HTTP) shakeHand(metadata *M.Metadata, rw io.ReadWriter) error { 53 | addr := metadata.DestinationAddress() 54 | req := &http.Request{ 55 | Method: http.MethodConnect, 56 | URL: &url.URL{ 57 | Host: addr, 58 | }, 59 | Host: addr, 60 | Header: http.Header{ 61 | "Proxy-Connection": []string{"Keep-Alive"}, 62 | }, 63 | } 64 | 65 | if h.user != "" && h.pass != "" { 66 | req.Header.Set("Proxy-Authorization", fmt.Sprintf("Basic %s", basicAuth(h.user, h.pass))) 67 | } 68 | 69 | if err := req.Write(rw); err != nil { 70 | return err 71 | } 72 | 73 | resp, err := http.ReadResponse(bufio.NewReader(rw), req) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | switch resp.StatusCode { 79 | case http.StatusOK: 80 | return nil 81 | case http.StatusProxyAuthRequired: 82 | return errors.New("HTTP auth required by proxy") 83 | case http.StatusMethodNotAllowed: 84 | return errors.New("CONNECT method not allowed by proxy") 85 | default: 86 | return fmt.Errorf("HTTP connect status: %s", resp.Status) 87 | } 88 | } 89 | 90 | // The Basic authentication scheme is based on the model that the client 91 | // needs to authenticate itself with a user-id and a password for each 92 | // protection space ("realm"). The realm value is a free-form string 93 | // that can only be compared for equality with other realms on that 94 | // server. The server will service the request only if it can validate 95 | // the user-id and password for the protection space applying to the 96 | // requested resource. 97 | func basicAuth(username, password string) string { 98 | auth := username + ":" + password 99 | return base64.StdEncoding.EncodeToString([]byte(auth)) 100 | } 101 | -------------------------------------------------------------------------------- /proxy/proto/proto.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import "fmt" 4 | 5 | const ( 6 | Direct Proto = iota 7 | Reject 8 | HTTP 9 | Socks4 10 | Socks5 11 | Shadowsocks 12 | Relay 13 | ) 14 | 15 | type Proto uint8 16 | 17 | func (proto Proto) String() string { 18 | switch proto { 19 | case Direct: 20 | return "direct" 21 | case Reject: 22 | return "reject" 23 | case HTTP: 24 | return "http" 25 | case Socks4: 26 | return "socks4" 27 | case Socks5: 28 | return "socks5" 29 | case Shadowsocks: 30 | return "ss" 31 | case Relay: 32 | return "relay" 33 | default: 34 | return fmt.Sprintf("proto(%d)", proto) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | // Package proxy provides implementations of proxy protocols. 2 | package proxy 3 | 4 | import ( 5 | "context" 6 | "net" 7 | "time" 8 | 9 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 10 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 11 | ) 12 | 13 | const ( 14 | tcpConnectTimeout = 5 * time.Second 15 | ) 16 | 17 | var _defaultDialer Dialer = &Base{} 18 | 19 | type Dialer interface { 20 | DialContext(context.Context, *M.Metadata) (net.Conn, error) 21 | DialUDP(*M.Metadata) (net.PacketConn, error) 22 | } 23 | 24 | type Proxy interface { 25 | Dialer 26 | Addr() string 27 | Proto() proto.Proto 28 | } 29 | 30 | // SetDialer sets default Dialer. 31 | func SetDialer(d Dialer) { 32 | _defaultDialer = d 33 | } 34 | 35 | // Dial uses default Dialer to dial TCP. 36 | func Dial(metadata *M.Metadata) (net.Conn, error) { 37 | ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout) 38 | defer cancel() 39 | return _defaultDialer.DialContext(ctx, metadata) 40 | } 41 | 42 | // DialContext uses default Dialer to dial TCP with context. 43 | func DialContext(ctx context.Context, metadata *M.Metadata) (net.Conn, error) { 44 | return _defaultDialer.DialContext(ctx, metadata) 45 | } 46 | 47 | // DialUDP uses default Dialer to dial UDP. 48 | func DialUDP(metadata *M.Metadata) (net.PacketConn, error) { 49 | return _defaultDialer.DialUDP(metadata) 50 | } 51 | -------------------------------------------------------------------------------- /proxy/reject.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "time" 8 | 9 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 10 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 11 | ) 12 | 13 | var _ Proxy = (*Reject)(nil) 14 | 15 | type Reject struct { 16 | *Base 17 | } 18 | 19 | func NewReject() *Reject { 20 | return &Reject{ 21 | Base: &Base{ 22 | proto: proto.Reject, 23 | }, 24 | } 25 | } 26 | 27 | func (r *Reject) DialContext(context.Context, *M.Metadata) (net.Conn, error) { 28 | return &nopConn{}, nil 29 | } 30 | 31 | func (r *Reject) DialUDP(*M.Metadata) (net.PacketConn, error) { 32 | return &nopPacketConn{}, nil 33 | } 34 | 35 | type nopConn struct{} 36 | 37 | func (rw *nopConn) Read([]byte) (int, error) { return 0, io.EOF } 38 | func (rw *nopConn) Write([]byte) (int, error) { return 0, io.EOF } 39 | func (rw *nopConn) Close() error { return nil } 40 | func (rw *nopConn) LocalAddr() net.Addr { return nil } 41 | func (rw *nopConn) RemoteAddr() net.Addr { return nil } 42 | func (rw *nopConn) SetDeadline(time.Time) error { return nil } 43 | func (rw *nopConn) SetReadDeadline(time.Time) error { return nil } 44 | func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil } 45 | 46 | type nopPacketConn struct{} 47 | 48 | func (npc *nopPacketConn) WriteTo(b []byte, _ net.Addr) (n int, err error) { return len(b), nil } 49 | func (npc *nopPacketConn) ReadFrom([]byte) (int, net.Addr, error) { return 0, nil, io.EOF } 50 | func (npc *nopPacketConn) Close() error { return nil } 51 | func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} } 52 | func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil } 53 | func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil } 54 | func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil } 55 | -------------------------------------------------------------------------------- /proxy/relay.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/binary" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "math" 11 | "net" 12 | "sync" 13 | 14 | "github.com/go-gost/relay" 15 | 16 | "github.com/xjasonlyu/tun2socks/v2/buffer" 17 | "github.com/xjasonlyu/tun2socks/v2/dialer" 18 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 19 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 20 | ) 21 | 22 | var _ Proxy = (*Relay)(nil) 23 | 24 | type Relay struct { 25 | *Base 26 | 27 | user string 28 | pass string 29 | 30 | noDelay bool 31 | } 32 | 33 | func NewRelay(addr, user, pass string, noDelay bool) (*Relay, error) { 34 | return &Relay{ 35 | Base: &Base{ 36 | addr: addr, 37 | proto: proto.Relay, 38 | }, 39 | user: user, 40 | pass: pass, 41 | noDelay: noDelay, 42 | }, nil 43 | } 44 | 45 | func (rl *Relay) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { 46 | return rl.dialContext(ctx, metadata) 47 | } 48 | 49 | func (rl *Relay) DialUDP(metadata *M.Metadata) (net.PacketConn, error) { 50 | ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout) 51 | defer cancel() 52 | 53 | return rl.dialContext(ctx, metadata) 54 | } 55 | 56 | func (rl *Relay) dialContext(ctx context.Context, metadata *M.Metadata) (rc *relayConn, err error) { 57 | var c net.Conn 58 | 59 | c, err = dialer.DialContext(ctx, "tcp", rl.Addr()) 60 | if err != nil { 61 | return nil, fmt.Errorf("connect to %s: %w", rl.Addr(), err) 62 | } 63 | setKeepAlive(c) 64 | 65 | defer func(c net.Conn) { 66 | safeConnClose(c, err) 67 | }(c) 68 | 69 | req := relay.Request{ 70 | Version: relay.Version1, 71 | Cmd: relay.CmdConnect, 72 | } 73 | 74 | if metadata.Network == M.UDP { 75 | req.Cmd |= relay.FUDP 76 | req.Features = append(req.Features, &relay.NetworkFeature{ 77 | Network: relay.NetworkUDP, 78 | }) 79 | } 80 | 81 | if rl.user != "" { 82 | req.Features = append(req.Features, &relay.UserAuthFeature{ 83 | Username: rl.user, 84 | Password: rl.pass, 85 | }) 86 | } 87 | 88 | req.Features = append(req.Features, serializeRelayAddr(metadata)) 89 | 90 | if rl.noDelay { 91 | if _, err = req.WriteTo(c); err != nil { 92 | return 93 | } 94 | if err = readRelayResponse(c); err != nil { 95 | return 96 | } 97 | } 98 | 99 | switch metadata.Network { 100 | case M.TCP: 101 | rc = newRelayConn(c, metadata.Addr(), rl.noDelay, false) 102 | if !rl.noDelay { 103 | if _, err = req.WriteTo(rc.wbuf); err != nil { 104 | return 105 | } 106 | } 107 | case M.UDP: 108 | rc = newRelayConn(c, metadata.Addr(), rl.noDelay, true) 109 | if !rl.noDelay { 110 | if _, err = req.WriteTo(rc.wbuf); err != nil { 111 | return 112 | } 113 | } 114 | default: 115 | err = fmt.Errorf("network %s is unsupported", metadata.Network) 116 | return 117 | } 118 | 119 | return 120 | } 121 | 122 | type relayConn struct { 123 | net.Conn 124 | udp bool 125 | addr net.Addr 126 | once sync.Once 127 | wbuf *bytes.Buffer 128 | } 129 | 130 | func newRelayConn(c net.Conn, addr net.Addr, noDelay, udp bool) *relayConn { 131 | rc := &relayConn{ 132 | Conn: c, 133 | addr: addr, 134 | udp: udp, 135 | } 136 | if !noDelay { 137 | rc.wbuf = &bytes.Buffer{} 138 | } 139 | return rc 140 | } 141 | 142 | func (rc *relayConn) ReadFrom(b []byte) (int, net.Addr, error) { 143 | n, err := rc.Read(b) 144 | return n, rc.addr, err 145 | } 146 | 147 | func (rc *relayConn) Read(b []byte) (n int, err error) { 148 | rc.once.Do(func() { 149 | if rc.wbuf != nil { 150 | err = readRelayResponse(rc.Conn) 151 | } 152 | }) 153 | if err != nil { 154 | return 155 | } 156 | 157 | if !rc.udp { 158 | return rc.Conn.Read(b) 159 | } 160 | 161 | var bb [2]byte 162 | _, err = io.ReadFull(rc.Conn, bb[:]) 163 | if err != nil { 164 | return 165 | } 166 | 167 | dLen := int(binary.BigEndian.Uint16(bb[:])) 168 | if len(b) >= dLen { 169 | return io.ReadFull(rc.Conn, b[:dLen]) 170 | } 171 | 172 | buf := buffer.Get(dLen) 173 | defer buffer.Put(buf) 174 | _, err = io.ReadFull(rc.Conn, buf) 175 | n = copy(b, buf) 176 | 177 | return 178 | } 179 | 180 | func (rc *relayConn) WriteTo(b []byte, _ net.Addr) (int, error) { 181 | return rc.Write(b) 182 | } 183 | 184 | func (rc *relayConn) Write(b []byte) (int, error) { 185 | if rc.udp { 186 | return rc.udpWrite(b) 187 | } 188 | return rc.tcpWrite(b) 189 | } 190 | 191 | func (rc *relayConn) tcpWrite(b []byte) (n int, err error) { 192 | if rc.wbuf != nil && rc.wbuf.Len() > 0 { 193 | n = len(b) 194 | rc.wbuf.Write(b) 195 | _, err = rc.Conn.Write(rc.wbuf.Bytes()) 196 | rc.wbuf.Reset() 197 | return 198 | } 199 | return rc.Conn.Write(b) 200 | } 201 | 202 | func (rc *relayConn) udpWrite(b []byte) (n int, err error) { 203 | if len(b) > math.MaxUint16 { 204 | err = errors.New("write: data maximum exceeded") 205 | return 206 | } 207 | 208 | n = len(b) 209 | if rc.wbuf != nil && rc.wbuf.Len() > 0 { 210 | var bb [2]byte 211 | binary.BigEndian.PutUint16(bb[:], uint16(len(b))) 212 | rc.wbuf.Write(bb[:]) 213 | rc.wbuf.Write(b) 214 | _, err = rc.wbuf.WriteTo(rc.Conn) 215 | return 216 | } 217 | 218 | var bb [2]byte 219 | binary.BigEndian.PutUint16(bb[:], uint16(len(b))) 220 | _, err = rc.Conn.Write(bb[:]) 221 | if err != nil { 222 | return 223 | } 224 | return rc.Conn.Write(b) 225 | } 226 | 227 | func readRelayResponse(r io.Reader) error { 228 | resp := relay.Response{} 229 | if _, err := resp.ReadFrom(r); err != nil { 230 | return err 231 | } 232 | if resp.Version != relay.Version1 { 233 | return relay.ErrBadVersion 234 | } 235 | if resp.Status != relay.StatusOK { 236 | return fmt.Errorf("status %d", resp.Status) 237 | } 238 | return nil 239 | } 240 | 241 | func serializeRelayAddr(m *M.Metadata) *relay.AddrFeature { 242 | af := &relay.AddrFeature{ 243 | Host: m.DstIP.String(), 244 | Port: m.DstPort, 245 | } 246 | if m.DstIP.Is4() { 247 | af.AType = relay.AddrIPv4 248 | } else { 249 | af.AType = relay.AddrIPv6 250 | } 251 | return af 252 | } 253 | -------------------------------------------------------------------------------- /proxy/shadowsocks.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net" 8 | 9 | "github.com/xjasonlyu/tun2socks/v2/dialer" 10 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 11 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 12 | "github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/core" 13 | obfs "github.com/xjasonlyu/tun2socks/v2/transport/simple-obfs" 14 | "github.com/xjasonlyu/tun2socks/v2/transport/socks5" 15 | ) 16 | 17 | var _ Proxy = (*Shadowsocks)(nil) 18 | 19 | type Shadowsocks struct { 20 | *Base 21 | 22 | cipher core.Cipher 23 | 24 | // simple-obfs plugin 25 | obfsMode, obfsHost string 26 | } 27 | 28 | func NewShadowsocks(addr, method, password, obfsMode, obfsHost string) (*Shadowsocks, error) { 29 | cipher, err := core.PickCipher(method, nil, password) 30 | if err != nil { 31 | return nil, fmt.Errorf("ss initialize: %w", err) 32 | } 33 | 34 | return &Shadowsocks{ 35 | Base: &Base{ 36 | addr: addr, 37 | proto: proto.Shadowsocks, 38 | }, 39 | cipher: cipher, 40 | obfsMode: obfsMode, 41 | obfsHost: obfsHost, 42 | }, nil 43 | } 44 | 45 | func (ss *Shadowsocks) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { 46 | c, err = dialer.DialContext(ctx, "tcp", ss.Addr()) 47 | if err != nil { 48 | return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err) 49 | } 50 | setKeepAlive(c) 51 | 52 | defer func(c net.Conn) { 53 | safeConnClose(c, err) 54 | }(c) 55 | 56 | switch ss.obfsMode { 57 | case "tls": 58 | c = obfs.NewTLSObfs(c, ss.obfsHost) 59 | case "http": 60 | _, port, _ := net.SplitHostPort(ss.addr) 61 | c = obfs.NewHTTPObfs(c, ss.obfsHost, port) 62 | } 63 | 64 | c = ss.cipher.StreamConn(c) 65 | _, err = c.Write(serializeSocksAddr(metadata)) 66 | return 67 | } 68 | 69 | func (ss *Shadowsocks) DialUDP(*M.Metadata) (net.PacketConn, error) { 70 | pc, err := dialer.ListenPacket("udp", "") 71 | if err != nil { 72 | return nil, fmt.Errorf("listen packet: %w", err) 73 | } 74 | 75 | udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr()) 76 | if err != nil { 77 | return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err) 78 | } 79 | 80 | pc = ss.cipher.PacketConn(pc) 81 | return &ssPacketConn{PacketConn: pc, rAddr: udpAddr}, nil 82 | } 83 | 84 | type ssPacketConn struct { 85 | net.PacketConn 86 | 87 | rAddr net.Addr 88 | } 89 | 90 | func (pc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { 91 | var packet []byte 92 | if ma, ok := addr.(*M.Addr); ok { 93 | packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b) 94 | } else { 95 | packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b) 96 | } 97 | 98 | if err != nil { 99 | return 100 | } 101 | return pc.PacketConn.WriteTo(packet[3:], pc.rAddr) 102 | } 103 | 104 | func (pc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 105 | n, _, err := pc.PacketConn.ReadFrom(b) 106 | if err != nil { 107 | return 0, nil, err 108 | } 109 | 110 | addr := socks5.SplitAddr(b[:n]) 111 | if addr == nil { 112 | return 0, nil, errors.New("parse addr error") 113 | } 114 | 115 | udpAddr := addr.UDPAddr() 116 | if udpAddr == nil { 117 | return 0, nil, errors.New("parse addr error") 118 | } 119 | 120 | copy(b, b[len(addr):]) 121 | return n - len(addr), udpAddr, err 122 | } 123 | -------------------------------------------------------------------------------- /proxy/socks4.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/xjasonlyu/tun2socks/v2/dialer" 9 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 10 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 11 | "github.com/xjasonlyu/tun2socks/v2/transport/socks4" 12 | ) 13 | 14 | var _ Proxy = (*Socks4)(nil) 15 | 16 | type Socks4 struct { 17 | *Base 18 | 19 | userID string 20 | } 21 | 22 | func NewSocks4(addr, userID string) (*Socks4, error) { 23 | return &Socks4{ 24 | Base: &Base{ 25 | addr: addr, 26 | proto: proto.Socks4, 27 | }, 28 | userID: userID, 29 | }, nil 30 | } 31 | 32 | func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { 33 | c, err = dialer.DialContext(ctx, "tcp", ss.Addr()) 34 | if err != nil { 35 | return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err) 36 | } 37 | setKeepAlive(c) 38 | 39 | defer func(c net.Conn) { 40 | safeConnClose(c, err) 41 | }(c) 42 | 43 | err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID) 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /proxy/socks5.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/dialer" 11 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 12 | "github.com/xjasonlyu/tun2socks/v2/proxy/proto" 13 | "github.com/xjasonlyu/tun2socks/v2/transport/socks5" 14 | ) 15 | 16 | var _ Proxy = (*Socks5)(nil) 17 | 18 | type Socks5 struct { 19 | *Base 20 | 21 | user string 22 | pass string 23 | 24 | // unix indicates if socks5 over UDS is enabled. 25 | unix bool 26 | } 27 | 28 | func NewSocks5(addr, user, pass string) (*Socks5, error) { 29 | unix := len(addr) > 0 && addr[0] == '/' 30 | 31 | // For support Linux abstract namespace 32 | if len(addr) > 2 && addr[1] == '@' || addr[1] == 0x00 { 33 | addr = addr[1:] 34 | } 35 | 36 | return &Socks5{ 37 | Base: &Base{ 38 | addr: addr, 39 | proto: proto.Socks5, 40 | }, 41 | user: user, 42 | pass: pass, 43 | unix: unix, 44 | }, nil 45 | } 46 | 47 | func (ss *Socks5) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { 48 | network := "tcp" 49 | if ss.unix { 50 | network = "unix" 51 | } 52 | 53 | c, err = dialer.DialContext(ctx, network, ss.Addr()) 54 | if err != nil { 55 | return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err) 56 | } 57 | setKeepAlive(c) 58 | 59 | defer func(c net.Conn) { 60 | safeConnClose(c, err) 61 | }(c) 62 | 63 | var user *socks5.User 64 | if ss.user != "" { 65 | user = &socks5.User{ 66 | Username: ss.user, 67 | Password: ss.pass, 68 | } 69 | } 70 | 71 | _, err = socks5.ClientHandshake(c, serializeSocksAddr(metadata), socks5.CmdConnect, user) 72 | return 73 | } 74 | 75 | func (ss *Socks5) DialUDP(*M.Metadata) (_ net.PacketConn, err error) { 76 | if ss.unix { 77 | return nil, fmt.Errorf("%w when unix domain socket is enabled", errors.ErrUnsupported) 78 | } 79 | 80 | ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout) 81 | defer cancel() 82 | 83 | c, err := dialer.DialContext(ctx, "tcp", ss.Addr()) 84 | if err != nil { 85 | err = fmt.Errorf("connect to %s: %w", ss.Addr(), err) 86 | return 87 | } 88 | setKeepAlive(c) 89 | 90 | defer func() { 91 | if err != nil && c != nil { 92 | c.Close() 93 | } 94 | }() 95 | 96 | var user *socks5.User 97 | if ss.user != "" { 98 | user = &socks5.User{ 99 | Username: ss.user, 100 | Password: ss.pass, 101 | } 102 | } 103 | 104 | // The UDP ASSOCIATE request is used to establish an association within 105 | // the UDP relay process to handle UDP datagrams. The DST.ADDR and 106 | // DST.PORT fields contain the address and port that the client expects 107 | // to use to send UDP datagrams on for the association. The server MAY 108 | // use this information to limit access to the association. If the 109 | // client is not in possession of the information at the time of the UDP 110 | // ASSOCIATE, the client MUST use a port number and address of all 111 | // zeros. RFC1928 112 | var targetAddr socks5.Addr = []byte{socks5.AtypIPv4, 0, 0, 0, 0, 0, 0} 113 | 114 | addr, err := socks5.ClientHandshake(c, targetAddr, socks5.CmdUDPAssociate, user) 115 | if err != nil { 116 | return nil, fmt.Errorf("client handshake: %w", err) 117 | } 118 | 119 | pc, err := dialer.ListenPacket("udp", "") 120 | if err != nil { 121 | return nil, fmt.Errorf("listen packet: %w", err) 122 | } 123 | 124 | go func() { 125 | io.Copy(io.Discard, c) 126 | c.Close() 127 | // A UDP association terminates when the TCP connection that the UDP 128 | // ASSOCIATE request arrived on terminates. RFC1928 129 | pc.Close() 130 | }() 131 | 132 | bindAddr := addr.UDPAddr() 133 | if bindAddr == nil { 134 | return nil, fmt.Errorf("invalid UDP binding address: %#v", addr) 135 | } 136 | 137 | if bindAddr.IP.IsUnspecified() { /* e.g. "0.0.0.0" or "::" */ 138 | udpAddr, err := net.ResolveUDPAddr("udp", ss.Addr()) 139 | if err != nil { 140 | return nil, fmt.Errorf("resolve udp address %s: %w", ss.Addr(), err) 141 | } 142 | bindAddr.IP = udpAddr.IP 143 | } 144 | 145 | return &socksPacketConn{PacketConn: pc, rAddr: bindAddr, tcpConn: c}, nil 146 | } 147 | 148 | type socksPacketConn struct { 149 | net.PacketConn 150 | 151 | rAddr net.Addr 152 | tcpConn net.Conn 153 | } 154 | 155 | func (pc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { 156 | var packet []byte 157 | if ma, ok := addr.(*M.Addr); ok { 158 | packet, err = socks5.EncodeUDPPacket(serializeSocksAddr(ma.Metadata()), b) 159 | } else { 160 | packet, err = socks5.EncodeUDPPacket(socks5.ParseAddr(addr), b) 161 | } 162 | 163 | if err != nil { 164 | return 165 | } 166 | return pc.PacketConn.WriteTo(packet, pc.rAddr) 167 | } 168 | 169 | func (pc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 170 | n, _, err := pc.PacketConn.ReadFrom(b) 171 | if err != nil { 172 | return 0, nil, err 173 | } 174 | 175 | addr, payload, err := socks5.DecodeUDPPacket(b) 176 | if err != nil { 177 | return 0, nil, err 178 | } 179 | 180 | udpAddr := addr.UDPAddr() 181 | if udpAddr == nil { 182 | return 0, nil, fmt.Errorf("convert %s to UDPAddr is nil", addr) 183 | } 184 | 185 | // due to DecodeUDPPacket is mutable, record addr length 186 | copy(b, payload) 187 | return n - len(addr) - 3, udpAddr, nil 188 | } 189 | 190 | func (pc *socksPacketConn) Close() error { 191 | pc.tcpConn.Close() 192 | return pc.PacketConn.Close() 193 | } 194 | 195 | func serializeSocksAddr(m *M.Metadata) socks5.Addr { 196 | return socks5.SerializeAddr("", m.DstIP, m.DstPort) 197 | } 198 | -------------------------------------------------------------------------------- /proxy/util.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | const ( 9 | tcpKeepAlivePeriod = 30 * time.Second 10 | ) 11 | 12 | // setKeepAlive sets tcp keepalive option for tcp connection. 13 | func setKeepAlive(c net.Conn) { 14 | if tcp, ok := c.(*net.TCPConn); ok { 15 | tcp.SetKeepAlive(true) 16 | tcp.SetKeepAlivePeriod(tcpKeepAlivePeriod) 17 | } 18 | } 19 | 20 | // safeConnClose closes tcp connection safely. 21 | func safeConnClose(c net.Conn, err error) { 22 | if c != nil && err != nil { 23 | c.Close() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /restapi/connections.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/go-chi/render" 12 | "github.com/gorilla/websocket" 13 | 14 | "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" 15 | ) 16 | 17 | const defaultInterval = 1000 18 | 19 | func init() { 20 | registerEndpoint("/connections", connectionRouter()) 21 | } 22 | 23 | func connectionRouter() http.Handler { 24 | r := chi.NewRouter() 25 | r.Get("/", getConnections) 26 | r.Delete("/", closeAllConnections) 27 | r.Delete("/{id}", closeConnection) 28 | return r 29 | } 30 | 31 | func getConnections(w http.ResponseWriter, r *http.Request) { 32 | if !websocket.IsWebSocketUpgrade(r) { 33 | render.JSON(w, r, statistic.DefaultManager.Snapshot()) 34 | return 35 | } 36 | 37 | conn, err := _upgrader.Upgrade(w, r, nil) 38 | if err != nil { 39 | return 40 | } 41 | 42 | intervalStr := r.URL.Query().Get("interval") 43 | interval := defaultInterval 44 | if intervalStr != "" { 45 | t, err := strconv.Atoi(intervalStr) 46 | if err != nil { 47 | render.Status(r, http.StatusBadRequest) 48 | render.JSON(w, r, ErrBadRequest) 49 | return 50 | } 51 | 52 | interval = t 53 | } 54 | 55 | buf := &bytes.Buffer{} 56 | sendSnapshot := func() error { 57 | buf.Reset() 58 | if err := json.NewEncoder(buf).Encode(statistic.DefaultManager.Snapshot()); err != nil { 59 | return err 60 | } 61 | 62 | return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) 63 | } 64 | 65 | if err := sendSnapshot(); err != nil { 66 | return 67 | } 68 | 69 | tick := time.NewTicker(time.Millisecond * time.Duration(interval)) 70 | defer tick.Stop() 71 | for range tick.C { 72 | if err := sendSnapshot(); err != nil { 73 | break 74 | } 75 | } 76 | } 77 | 78 | func closeConnection(w http.ResponseWriter, r *http.Request) { 79 | id := chi.URLParam(r, "id") 80 | snapshot := statistic.DefaultManager.Snapshot() 81 | for _, c := range snapshot.Connections { 82 | if id == c.ID() { 83 | _ = c.Close() 84 | break 85 | } 86 | } 87 | render.NoContent(w, r) 88 | } 89 | 90 | func closeAllConnections(w http.ResponseWriter, r *http.Request) { 91 | snapshot := statistic.DefaultManager.Snapshot() 92 | for _, c := range snapshot.Connections { 93 | _ = c.Close() 94 | } 95 | render.NoContent(w, r) 96 | } 97 | -------------------------------------------------------------------------------- /restapi/debug.go: -------------------------------------------------------------------------------- 1 | //go:build debug 2 | 3 | package restapi 4 | 5 | import ( 6 | "net/http" 7 | "net/http/pprof" 8 | 9 | "github.com/go-chi/chi/v5" 10 | ) 11 | 12 | func init() { 13 | registerEndpoint("/debug/pprof/", pprofRouter()) 14 | } 15 | 16 | func pprofRouter() http.Handler { 17 | r := chi.NewRouter() 18 | r.HandleFunc("/", pprof.Index) 19 | r.HandleFunc("/cmdline", pprof.Cmdline) 20 | r.HandleFunc("/profile", pprof.Profile) 21 | r.HandleFunc("/symbol", pprof.Symbol) 22 | r.HandleFunc("/trace", pprof.Trace) 23 | r.HandleFunc("/{name}", pprofHandler) 24 | return r 25 | } 26 | 27 | func pprofHandler(w http.ResponseWriter, r *http.Request) { 28 | name := chi.URLParam(r, "name") 29 | pprof.Handler(name).ServeHTTP(w, r) 30 | } 31 | -------------------------------------------------------------------------------- /restapi/errors.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | var ( 4 | ErrBadRequest = newError("Body invalid") 5 | ErrUnauthorized = newError("Unauthorized") 6 | ErrUninitialized = newError("Uninitialized") 7 | ) 8 | 9 | var _ error = (*HTTPError)(nil) 10 | 11 | // HTTPError is custom HTTP error for API 12 | type HTTPError struct { 13 | Message string `json:"message"` 14 | } 15 | 16 | func (e *HTTPError) Error() string { 17 | return e.Message 18 | } 19 | 20 | func newError(msg string) *HTTPError { 21 | return &HTTPError{Message: msg} 22 | } 23 | -------------------------------------------------------------------------------- /restapi/netstats.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "reflect" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/go-chi/render" 11 | "github.com/gorilla/websocket" 12 | "gvisor.dev/gvisor/pkg/tcpip" 13 | ) 14 | 15 | var _stackStatsFunc func() tcpip.Stats 16 | 17 | func SetStatsFunc(s func() tcpip.Stats) { 18 | _stackStatsFunc = s 19 | } 20 | 21 | func init() { 22 | registerEndpoint("/netstats", http.HandlerFunc(getNetStats)) 23 | } 24 | 25 | func getNetStats(w http.ResponseWriter, r *http.Request) { 26 | if _stackStatsFunc == nil { 27 | render.Status(r, http.StatusInternalServerError) 28 | render.JSON(w, r, ErrUninitialized) 29 | return 30 | } 31 | 32 | b := &bytes.Buffer{} 33 | snapshot := func() []byte { 34 | s := _stackStatsFunc() 35 | b.Reset() /* reset buffer */ 36 | encodeToJSON(reflect.ValueOf(&s).Elem(), b) 37 | return b.Bytes() 38 | } 39 | 40 | if !websocket.IsWebSocketUpgrade(r) { 41 | w.Header().Set("Content-Type", "application/json") 42 | render.Status(r, http.StatusOK) 43 | w.Write(snapshot()) 44 | w.(http.Flusher).Flush() 45 | return 46 | } 47 | 48 | conn, err := _upgrader.Upgrade(w, r, nil) 49 | if err != nil { 50 | return 51 | } 52 | 53 | tick := time.NewTicker(time.Second) 54 | defer tick.Stop() 55 | 56 | for range tick.C { 57 | if err = conn.WriteMessage(websocket.TextMessage, snapshot()); err != nil { 58 | break 59 | } 60 | } 61 | } 62 | 63 | func encodeToJSON(value reflect.Value, b *bytes.Buffer) { 64 | b.WriteByte('{') 65 | defer b.WriteByte('}') 66 | 67 | for i, numField := 0, value.NumField(); i < numField; i++ { 68 | field := value.Type().Field(i) 69 | value := value.Field(i) 70 | 71 | b.WriteString("\"" + field.Name + "\":") 72 | 73 | switch v := value.Addr().Interface().(type) { 74 | case **tcpip.StatCounter: 75 | b.WriteString(strconv.FormatUint((*v).Value(), 10)) 76 | case **tcpip.IntegralStatCounterMap: 77 | b.WriteByte('{') 78 | for j, keys := 0, (*v).Keys(); j < len(keys); j++ { 79 | if counter, ok := (*v).Get(keys[j]); ok { 80 | k := strconv.FormatUint(keys[j], 10) 81 | v := strconv.FormatUint(counter.Value(), 10) 82 | b.WriteString("\"" + k + "\":" + v) 83 | if j < len(keys)-1 { 84 | b.WriteByte(',') 85 | } 86 | } 87 | } 88 | b.WriteByte('}') 89 | default: 90 | encodeToJSON(value, b) 91 | } 92 | if i < numField-1 { 93 | b.WriteByte(',') 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /restapi/server.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/go-chi/cors" 13 | "github.com/go-chi/render" 14 | "github.com/gorilla/websocket" 15 | 16 | V "github.com/xjasonlyu/tun2socks/v2/internal/version" 17 | "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" 18 | ) 19 | 20 | var ( 21 | _upgrader = websocket.Upgrader{ 22 | CheckOrigin: func(r *http.Request) bool { 23 | return true 24 | }, 25 | } 26 | 27 | _endpoints = make(map[string]http.Handler) 28 | ) 29 | 30 | func registerEndpoint(pattern string, handler http.Handler) { 31 | _endpoints[pattern] = handler 32 | } 33 | 34 | func Start(addr, token string) error { 35 | r := chi.NewRouter() 36 | 37 | c := cors.New(cors.Options{ 38 | AllowedOrigins: []string{"*"}, 39 | AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, 40 | AllowedHeaders: []string{"Content-Type", "Authorization"}, 41 | MaxAge: 300, 42 | }) 43 | 44 | r.Use(c.Handler) 45 | r.Group(func(r chi.Router) { 46 | r.Use(authenticator(token)) 47 | r.Get("/", hello) 48 | r.Get("/traffic", traffic) 49 | r.Get("/version", version) 50 | // attach HTTP handlers 51 | for pattern, handler := range _endpoints { 52 | r.Mount(pattern, handler) 53 | } 54 | }) 55 | 56 | listener, err := net.Listen("tcp", addr) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return http.Serve(listener, r) 62 | } 63 | 64 | func hello(w http.ResponseWriter, r *http.Request) { 65 | render.JSON(w, r, render.M{"hello": V.Name}) 66 | } 67 | 68 | func authenticator(token string) func(http.Handler) http.Handler { 69 | return func(next http.Handler) http.Handler { 70 | fn := func(w http.ResponseWriter, r *http.Request) { 71 | if token == "" { 72 | next.ServeHTTP(w, r) 73 | return 74 | } 75 | 76 | // Browser websocket not support custom header 77 | if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" { 78 | t := r.URL.Query().Get("token") 79 | if t != token { 80 | render.Status(r, http.StatusUnauthorized) 81 | render.JSON(w, r, ErrUnauthorized) 82 | return 83 | } 84 | next.ServeHTTP(w, r) 85 | return 86 | } 87 | 88 | header := r.Header.Get("Authorization") 89 | text := strings.SplitN(header, " ", 2) 90 | 91 | hasInvalidHeader := text[0] != "Bearer" 92 | hasInvalidToken := len(text) != 2 || text[1] != token 93 | if hasInvalidHeader || hasInvalidToken { 94 | render.Status(r, http.StatusUnauthorized) 95 | render.JSON(w, r, ErrUnauthorized) 96 | return 97 | } 98 | next.ServeHTTP(w, r) 99 | } 100 | return http.HandlerFunc(fn) 101 | } 102 | } 103 | 104 | func traffic(w http.ResponseWriter, r *http.Request) { 105 | var ( 106 | err error 107 | wsConn *websocket.Conn 108 | ) 109 | if websocket.IsWebSocketUpgrade(r) { 110 | wsConn, err = _upgrader.Upgrade(w, r, nil) 111 | if err != nil { 112 | return 113 | } 114 | } 115 | 116 | if wsConn == nil { 117 | w.Header().Set("Content-Type", "application/json") 118 | render.Status(r, http.StatusOK) 119 | } 120 | 121 | tick := time.NewTicker(time.Second) 122 | defer tick.Stop() 123 | 124 | buf := &bytes.Buffer{} 125 | for range tick.C { 126 | buf.Reset() 127 | 128 | up, down := statistic.DefaultManager.Now() 129 | if err = json.NewEncoder(buf).Encode(struct { 130 | Up int64 `json:"up"` 131 | Down int64 `json:"down"` 132 | }{ 133 | Up: up, 134 | Down: down, 135 | }); err != nil { 136 | break 137 | } 138 | 139 | if wsConn == nil { 140 | _, err = w.Write(buf.Bytes()) 141 | w.(http.Flusher).Flush() 142 | } else { 143 | err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) 144 | } 145 | 146 | if err != nil { 147 | break 148 | } 149 | } 150 | } 151 | 152 | func version(w http.ResponseWriter, r *http.Request) { 153 | render.JSON(w, r, render.M{ 154 | "version": V.Version, 155 | "commit": V.GitCommit, 156 | "modules": V.Info(), 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /transport/internal/bufferpool/bufferpool.go: -------------------------------------------------------------------------------- 1 | package bufferpool 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/xjasonlyu/tun2socks/v2/internal/pool" 7 | ) 8 | 9 | const _size = 1024 // by default, create 1 KiB buffers 10 | 11 | var _pool = pool.New(func() *bytes.Buffer { 12 | return bytes.NewBuffer(make([]byte, 0, _size)) 13 | }) 14 | 15 | func Get() *bytes.Buffer { 16 | buf := _pool.Get() 17 | buf.Reset() 18 | return buf 19 | } 20 | 21 | func Put(b *bytes.Buffer) { 22 | _pool.Put(b) 23 | } 24 | -------------------------------------------------------------------------------- /transport/shadowsocks/README.md: -------------------------------------------------------------------------------- 1 | ## Embedded go-shadowsocks2 2 | 3 | origin https://github.com/riobard/go-shadowsocks2 4 | -------------------------------------------------------------------------------- /transport/shadowsocks/core/cipher.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/md5" 5 | "errors" 6 | "net" 7 | "sort" 8 | "strings" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/shadowaead" 11 | "github.com/xjasonlyu/tun2socks/v2/transport/shadowsocks/shadowstream" 12 | ) 13 | 14 | type Cipher interface { 15 | StreamConnCipher 16 | PacketConnCipher 17 | } 18 | 19 | type StreamConnCipher interface { 20 | StreamConn(net.Conn) net.Conn 21 | } 22 | 23 | type PacketConnCipher interface { 24 | PacketConn(net.PacketConn) net.PacketConn 25 | } 26 | 27 | // ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns). 28 | var ErrCipherNotSupported = errors.New("cipher not supported") 29 | 30 | const ( 31 | aeadAes128Gcm = "AEAD_AES_128_GCM" 32 | aeadAes192Gcm = "AEAD_AES_192_GCM" 33 | aeadAes256Gcm = "AEAD_AES_256_GCM" 34 | aeadChacha20Poly1305 = "AEAD_CHACHA20_POLY1305" 35 | aeadXChacha20Poly1305 = "AEAD_XCHACHA20_POLY1305" 36 | ) 37 | 38 | // List of AEAD ciphers: key size in bytes and constructor 39 | var aeadList = map[string]struct { 40 | KeySize int 41 | New func([]byte) (shadowaead.Cipher, error) 42 | }{ 43 | aeadAes128Gcm: {16, shadowaead.AESGCM}, 44 | aeadAes192Gcm: {24, shadowaead.AESGCM}, 45 | aeadAes256Gcm: {32, shadowaead.AESGCM}, 46 | aeadChacha20Poly1305: {32, shadowaead.Chacha20Poly1305}, 47 | aeadXChacha20Poly1305: {32, shadowaead.XChacha20Poly1305}, 48 | } 49 | 50 | // List of stream ciphers: key size in bytes and constructor 51 | var streamList = map[string]struct { 52 | KeySize int 53 | New func(key []byte) (shadowstream.Cipher, error) 54 | }{ 55 | "RC4-MD5": {16, shadowstream.RC4MD5}, 56 | "AES-128-CTR": {16, shadowstream.AESCTR}, 57 | "AES-192-CTR": {24, shadowstream.AESCTR}, 58 | "AES-256-CTR": {32, shadowstream.AESCTR}, 59 | "AES-128-CFB": {16, shadowstream.AESCFB}, 60 | "AES-192-CFB": {24, shadowstream.AESCFB}, 61 | "AES-256-CFB": {32, shadowstream.AESCFB}, 62 | "CHACHA20-IETF": {32, shadowstream.Chacha20IETF}, 63 | "XCHACHA20": {32, shadowstream.Xchacha20}, 64 | } 65 | 66 | // ListCipher returns a list of available cipher names sorted alphabetically. 67 | func ListCipher() []string { 68 | var l []string 69 | for k := range aeadList { 70 | l = append(l, k) 71 | } 72 | for k := range streamList { 73 | l = append(l, k) 74 | } 75 | sort.Strings(l) 76 | return l 77 | } 78 | 79 | // PickCipher returns a Cipher of the given name. Derive key from password if given key is empty. 80 | func PickCipher(name string, key []byte, password string) (Cipher, error) { 81 | name = strings.ToUpper(name) 82 | 83 | switch name { 84 | case "DUMMY": 85 | return &dummy{}, nil 86 | case "CHACHA20-IETF-POLY1305": 87 | name = aeadChacha20Poly1305 88 | case "XCHACHA20-IETF-POLY1305": 89 | name = aeadXChacha20Poly1305 90 | case "AES-128-GCM": 91 | name = aeadAes128Gcm 92 | case "AES-192-GCM": 93 | name = aeadAes192Gcm 94 | case "AES-256-GCM": 95 | name = aeadAes256Gcm 96 | } 97 | 98 | if choice, ok := aeadList[name]; ok { 99 | if len(key) == 0 { 100 | key = Kdf(password, choice.KeySize) 101 | } 102 | if len(key) != choice.KeySize { 103 | return nil, shadowaead.KeySizeError(choice.KeySize) 104 | } 105 | aead, err := choice.New(key) 106 | return &AeadCipher{Cipher: aead, Key: key}, err 107 | } 108 | 109 | if choice, ok := streamList[name]; ok { 110 | if len(key) == 0 { 111 | key = Kdf(password, choice.KeySize) 112 | } 113 | if len(key) != choice.KeySize { 114 | return nil, shadowstream.KeySizeError(choice.KeySize) 115 | } 116 | ciph, err := choice.New(key) 117 | return &StreamCipher{Cipher: ciph, Key: key}, err 118 | } 119 | 120 | return nil, ErrCipherNotSupported 121 | } 122 | 123 | type AeadCipher struct { 124 | shadowaead.Cipher 125 | 126 | Key []byte 127 | } 128 | 129 | func (aead *AeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) } 130 | func (aead *AeadCipher) PacketConn(c net.PacketConn) net.PacketConn { 131 | return shadowaead.NewPacketConn(c, aead) 132 | } 133 | 134 | type StreamCipher struct { 135 | shadowstream.Cipher 136 | 137 | Key []byte 138 | } 139 | 140 | func (ciph *StreamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) } 141 | func (ciph *StreamCipher) PacketConn(c net.PacketConn) net.PacketConn { 142 | return shadowstream.NewPacketConn(c, ciph) 143 | } 144 | 145 | // dummy cipher does not encrypt 146 | 147 | type dummy struct{} 148 | 149 | func (dummy) StreamConn(c net.Conn) net.Conn { return c } 150 | func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c } 151 | 152 | // key-derivation function from original Shadowsocks 153 | func Kdf(password string, keyLen int) []byte { 154 | var b, prev []byte 155 | h := md5.New() 156 | for len(b) < keyLen { 157 | h.Write(prev) 158 | h.Write([]byte(password)) 159 | b = h.Sum(b) 160 | prev = b[len(b)-h.Size():] 161 | h.Reset() 162 | } 163 | return b[:keyLen] 164 | } 165 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowaead/cipher.go: -------------------------------------------------------------------------------- 1 | package shadowaead 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/sha1" 7 | "io" 8 | "strconv" 9 | 10 | "golang.org/x/crypto/chacha20poly1305" 11 | "golang.org/x/crypto/hkdf" 12 | ) 13 | 14 | type Cipher interface { 15 | KeySize() int 16 | SaltSize() int 17 | Encrypter(salt []byte) (cipher.AEAD, error) 18 | Decrypter(salt []byte) (cipher.AEAD, error) 19 | } 20 | 21 | type KeySizeError int 22 | 23 | func (e KeySizeError) Error() string { 24 | return "key size error: need " + strconv.Itoa(int(e)) + " bytes" 25 | } 26 | 27 | func hkdfSHA1(secret, salt, info, outkey []byte) { 28 | r := hkdf.New(sha1.New, secret, salt, info) 29 | if _, err := io.ReadFull(r, outkey); err != nil { 30 | panic(err) // should never happen 31 | } 32 | } 33 | 34 | type metaCipher struct { 35 | psk []byte 36 | makeAEAD func(key []byte) (cipher.AEAD, error) 37 | } 38 | 39 | func (a *metaCipher) KeySize() int { return len(a.psk) } 40 | func (a *metaCipher) SaltSize() int { 41 | if ks := a.KeySize(); ks > 16 { 42 | return ks 43 | } 44 | return 16 45 | } 46 | 47 | func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) { 48 | subkey := make([]byte, a.KeySize()) 49 | hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey) 50 | return a.makeAEAD(subkey) 51 | } 52 | 53 | func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) { 54 | subkey := make([]byte, a.KeySize()) 55 | hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey) 56 | return a.makeAEAD(subkey) 57 | } 58 | 59 | func aesGCM(key []byte) (cipher.AEAD, error) { 60 | blk, err := aes.NewCipher(key) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return cipher.NewGCM(blk) 65 | } 66 | 67 | // AESGCM creates a new Cipher with a pre-shared key. len(psk) must be 68 | // one of 16, 24, or 32 to select AES-128/196/256-GCM. 69 | func AESGCM(psk []byte) (Cipher, error) { 70 | switch l := len(psk); l { 71 | case 16, 24, 32: // AES 128/196/256 72 | default: 73 | return nil, aes.KeySizeError(l) 74 | } 75 | return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil 76 | } 77 | 78 | // Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk) 79 | // must be 32. 80 | func Chacha20Poly1305(psk []byte) (Cipher, error) { 81 | if len(psk) != chacha20poly1305.KeySize { 82 | return nil, KeySizeError(chacha20poly1305.KeySize) 83 | } 84 | return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil 85 | } 86 | 87 | // XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk) 88 | // must be 32. 89 | func XChacha20Poly1305(psk []byte) (Cipher, error) { 90 | if len(psk) != chacha20poly1305.KeySize { 91 | return nil, KeySizeError(chacha20poly1305.KeySize) 92 | } 93 | return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil 94 | } 95 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowaead/packet.go: -------------------------------------------------------------------------------- 1 | package shadowaead 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "io" 7 | "net" 8 | 9 | "github.com/xjasonlyu/tun2socks/v2/buffer" 10 | ) 11 | 12 | // ErrShortPacket means that the packet is too short for a valid encrypted packet. 13 | var ErrShortPacket = errors.New("short packet") 14 | 15 | var _zerononce [128]byte // read-only. 128 bytes is more than enough. 16 | 17 | // Pack encrypts plaintext using Cipher with a randomly generated salt and 18 | // returns a slice of dst containing the encrypted packet and any error occurred. 19 | // Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead(). 20 | func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) { 21 | saltSize := ciph.SaltSize() 22 | salt := dst[:saltSize] 23 | if _, err := rand.Read(salt); err != nil { 24 | return nil, err 25 | } 26 | aead, err := ciph.Encrypter(salt) 27 | if err != nil { 28 | return nil, err 29 | } 30 | if len(dst) < saltSize+len(plaintext)+aead.Overhead() { 31 | return nil, io.ErrShortBuffer 32 | } 33 | b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil) 34 | return dst[:saltSize+len(b)], nil 35 | } 36 | 37 | // Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred. 38 | // Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead(). 39 | func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) { 40 | saltSize := ciph.SaltSize() 41 | if len(pkt) < saltSize { 42 | return nil, ErrShortPacket 43 | } 44 | salt := pkt[:saltSize] 45 | aead, err := ciph.Decrypter(salt) 46 | if err != nil { 47 | return nil, err 48 | } 49 | if len(pkt) < saltSize+aead.Overhead() { 50 | return nil, ErrShortPacket 51 | } 52 | if saltSize+len(dst)+aead.Overhead() < len(pkt) { 53 | return nil, io.ErrShortBuffer 54 | } 55 | b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil) 56 | return b, err 57 | } 58 | 59 | type PacketConn struct { 60 | net.PacketConn 61 | Cipher 62 | } 63 | 64 | const maxPacketSize = 64 * 1024 65 | 66 | // NewPacketConn wraps a net.PacketConn with cipher 67 | func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn { 68 | return &PacketConn{PacketConn: c, Cipher: ciph} 69 | } 70 | 71 | // WriteTo encrypts b and write to addr using the embedded PacketConn. 72 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 73 | buf := buffer.Get(maxPacketSize) 74 | defer buffer.Put(buf) 75 | buf, err := Pack(buf, b, c) 76 | if err != nil { 77 | return 0, err 78 | } 79 | _, err = c.PacketConn.WriteTo(buf, addr) 80 | return len(b), err 81 | } 82 | 83 | // ReadFrom reads from the embedded PacketConn and decrypts into b. 84 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 85 | n, addr, err := c.PacketConn.ReadFrom(b) 86 | if err != nil { 87 | return n, addr, err 88 | } 89 | bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c) 90 | if err != nil { 91 | return n, addr, err 92 | } 93 | copy(b, bb) 94 | return len(bb), addr, err 95 | } 96 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowaead/stream.go: -------------------------------------------------------------------------------- 1 | package shadowaead 2 | 3 | import ( 4 | "crypto/cipher" 5 | "crypto/rand" 6 | "errors" 7 | "io" 8 | "net" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/buffer" 11 | ) 12 | 13 | const ( 14 | // payloadSizeMask is the maximum size of payload in bytes. 15 | payloadSizeMask = 0x3FFF // 16*1024 - 1 16 | bufSize = 17 * 1024 // >= 2+aead.Overhead()+payloadSizeMask+aead.Overhead() 17 | ) 18 | 19 | var ErrZeroChunk = errors.New("zero chunk") 20 | 21 | type Writer struct { 22 | io.Writer 23 | cipher.AEAD 24 | nonce [32]byte // should be sufficient for most nonce sizes 25 | } 26 | 27 | // NewWriter wraps an io.Writer with authenticated encryption. 28 | func NewWriter(w io.Writer, aead cipher.AEAD) *Writer { return &Writer{Writer: w, AEAD: aead} } 29 | 30 | // Write encrypts p and writes to the embedded io.Writer. 31 | func (w *Writer) Write(p []byte) (n int, err error) { 32 | buf := buffer.Get(bufSize) 33 | defer buffer.Put(buf) 34 | nonce := w.nonce[:w.NonceSize()] 35 | tag := w.Overhead() 36 | off := 2 + tag 37 | 38 | // compatible with snell 39 | if len(p) == 0 { 40 | buf = buf[:off] 41 | buf[0], buf[1] = byte(0), byte(0) 42 | w.Seal(buf[:0], nonce, buf[:2], nil) 43 | increment(nonce) 44 | _, err = w.Writer.Write(buf) 45 | return 46 | } 47 | 48 | for nr := 0; n < len(p) && err == nil; n += nr { 49 | nr = payloadSizeMask 50 | if n+nr > len(p) { 51 | nr = len(p) - n 52 | } 53 | buf = buf[:off+nr+tag] 54 | buf[0], buf[1] = byte(nr>>8), byte(nr) // big-endian payload size 55 | w.Seal(buf[:0], nonce, buf[:2], nil) 56 | increment(nonce) 57 | w.Seal(buf[:off], nonce, p[n:n+nr], nil) 58 | increment(nonce) 59 | _, err = w.Writer.Write(buf) 60 | } 61 | return 62 | } 63 | 64 | // ReadFrom reads from the given io.Reader until EOF or error, encrypts and 65 | // writes to the embedded io.Writer. Returns number of bytes read from r and 66 | // any error encountered. 67 | func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) { 68 | buf := buffer.Get(bufSize) 69 | defer buffer.Put(buf) 70 | nonce := w.nonce[:w.NonceSize()] 71 | tag := w.Overhead() 72 | off := 2 + tag 73 | for { 74 | nr, er := r.Read(buf[off : off+payloadSizeMask]) 75 | n += int64(nr) 76 | buf[0], buf[1] = byte(nr>>8), byte(nr) 77 | w.Seal(buf[:0], nonce, buf[:2], nil) 78 | increment(nonce) 79 | w.Seal(buf[:off], nonce, buf[off:off+nr], nil) 80 | increment(nonce) 81 | if _, ew := w.Writer.Write(buf[:off+nr+tag]); ew != nil { 82 | err = ew 83 | return 84 | } 85 | if er != nil { 86 | if er != io.EOF { // ignore EOF as per io.ReaderFrom contract 87 | err = er 88 | } 89 | return 90 | } 91 | } 92 | } 93 | 94 | type Reader struct { 95 | io.Reader 96 | cipher.AEAD 97 | nonce [32]byte // should be sufficient for most nonce sizes 98 | buf []byte // to be put back into bufPool 99 | off int // offset to unconsumed part of buf 100 | } 101 | 102 | // NewReader wraps an io.Reader with authenticated decryption. 103 | func NewReader(r io.Reader, aead cipher.AEAD) *Reader { return &Reader{Reader: r, AEAD: aead} } 104 | 105 | // Read and decrypt a record into p. len(p) >= max payload size + AEAD overhead. 106 | func (r *Reader) read(p []byte) (int, error) { 107 | nonce := r.nonce[:r.NonceSize()] 108 | tag := r.Overhead() 109 | 110 | // decrypt payload size 111 | p = p[:2+tag] 112 | if _, err := io.ReadFull(r.Reader, p); err != nil { 113 | return 0, err 114 | } 115 | _, err := r.Open(p[:0], nonce, p, nil) 116 | increment(nonce) 117 | if err != nil { 118 | return 0, err 119 | } 120 | 121 | // decrypt payload 122 | size := (int(p[0])<<8 + int(p[1])) & payloadSizeMask 123 | if size == 0 { 124 | return 0, ErrZeroChunk 125 | } 126 | 127 | p = p[:size+tag] 128 | if _, err := io.ReadFull(r.Reader, p); err != nil { 129 | return 0, err 130 | } 131 | _, err = r.Open(p[:0], nonce, p, nil) 132 | increment(nonce) 133 | if err != nil { 134 | return 0, err 135 | } 136 | return size, nil 137 | } 138 | 139 | // Read reads from the embedded io.Reader, decrypts and writes to p. 140 | func (r *Reader) Read(p []byte) (int, error) { 141 | if r.buf == nil { 142 | if len(p) >= payloadSizeMask+r.Overhead() { 143 | return r.read(p) 144 | } 145 | b := buffer.Get(bufSize) 146 | n, err := r.read(b) 147 | if err != nil { 148 | return 0, err 149 | } 150 | r.buf = b[:n] 151 | r.off = 0 152 | } 153 | 154 | n := copy(p, r.buf[r.off:]) 155 | r.off += n 156 | if r.off == len(r.buf) { 157 | buffer.Put(r.buf[:cap(r.buf)]) 158 | r.buf = nil 159 | } 160 | return n, nil 161 | } 162 | 163 | // WriteTo reads from the embedded io.Reader, decrypts and writes to w until 164 | // there's no more data to write or when an error occurs. Return number of 165 | // bytes written to w and any error encountered. 166 | func (r *Reader) WriteTo(w io.Writer) (n int64, err error) { 167 | if r.buf == nil { 168 | r.buf = buffer.Get(bufSize) 169 | r.off = len(r.buf) 170 | } 171 | 172 | for { 173 | for r.off < len(r.buf) { 174 | nw, ew := w.Write(r.buf[r.off:]) 175 | r.off += nw 176 | n += int64(nw) 177 | if ew != nil { 178 | if r.off == len(r.buf) { 179 | buffer.Put(r.buf[:cap(r.buf)]) 180 | r.buf = nil 181 | } 182 | err = ew 183 | return 184 | } 185 | } 186 | 187 | nr, er := r.read(r.buf) 188 | if er != nil { 189 | if er != io.EOF { 190 | err = er 191 | } 192 | return 193 | } 194 | r.buf = r.buf[:nr] 195 | r.off = 0 196 | } 197 | } 198 | 199 | // increment little-endian encoded unsigned integer b. Wrap around on overflow. 200 | func increment(b []byte) { 201 | for i := range b { 202 | b[i]++ 203 | if b[i] != 0 { 204 | return 205 | } 206 | } 207 | } 208 | 209 | type Conn struct { 210 | net.Conn 211 | Cipher 212 | r *Reader 213 | w *Writer 214 | } 215 | 216 | // NewConn wraps a stream-oriented net.Conn with cipher. 217 | func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} } 218 | 219 | func (c *Conn) initReader() error { 220 | salt := make([]byte, c.SaltSize()) 221 | if _, err := io.ReadFull(c.Conn, salt); err != nil { 222 | return err 223 | } 224 | 225 | aead, err := c.Decrypter(salt) 226 | if err != nil { 227 | return err 228 | } 229 | 230 | c.r = NewReader(c.Conn, aead) 231 | return nil 232 | } 233 | 234 | func (c *Conn) Read(b []byte) (int, error) { 235 | if c.r == nil { 236 | if err := c.initReader(); err != nil { 237 | return 0, err 238 | } 239 | } 240 | return c.r.Read(b) 241 | } 242 | 243 | func (c *Conn) WriteTo(w io.Writer) (int64, error) { 244 | if c.r == nil { 245 | if err := c.initReader(); err != nil { 246 | return 0, err 247 | } 248 | } 249 | return c.r.WriteTo(w) 250 | } 251 | 252 | func (c *Conn) initWriter() error { 253 | salt := make([]byte, c.SaltSize()) 254 | if _, err := rand.Read(salt); err != nil { 255 | return err 256 | } 257 | aead, err := c.Encrypter(salt) 258 | if err != nil { 259 | return err 260 | } 261 | _, err = c.Conn.Write(salt) 262 | if err != nil { 263 | return err 264 | } 265 | c.w = NewWriter(c.Conn, aead) 266 | return nil 267 | } 268 | 269 | func (c *Conn) Write(b []byte) (int, error) { 270 | if c.w == nil { 271 | if err := c.initWriter(); err != nil { 272 | return 0, err 273 | } 274 | } 275 | return c.w.Write(b) 276 | } 277 | 278 | func (c *Conn) ReadFrom(r io.Reader) (int64, error) { 279 | if c.w == nil { 280 | if err := c.initWriter(); err != nil { 281 | return 0, err 282 | } 283 | } 284 | return c.w.ReadFrom(r) 285 | } 286 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowstream/cipher.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/md5" 7 | "crypto/rc4" 8 | "strconv" 9 | 10 | "golang.org/x/crypto/chacha20" 11 | ) 12 | 13 | // Cipher generates a pair of stream ciphers for encryption and decryption. 14 | type Cipher interface { 15 | IVSize() int 16 | Encrypter(iv []byte) cipher.Stream 17 | Decrypter(iv []byte) cipher.Stream 18 | } 19 | 20 | type KeySizeError int 21 | 22 | func (e KeySizeError) Error() string { 23 | return "key size error: need " + strconv.Itoa(int(e)) + " bytes" 24 | } 25 | 26 | // CTR mode 27 | type ctrStream struct{ cipher.Block } 28 | 29 | func (b *ctrStream) IVSize() int { return b.BlockSize() } 30 | func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) } 31 | func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) } 32 | 33 | func AESCTR(key []byte) (Cipher, error) { 34 | blk, err := aes.NewCipher(key) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &ctrStream{blk}, nil 39 | } 40 | 41 | // CFB mode 42 | type cfbStream struct{ cipher.Block } 43 | 44 | func (b *cfbStream) IVSize() int { return b.BlockSize() } 45 | func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) } 46 | func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) } 47 | 48 | func AESCFB(key []byte) (Cipher, error) { 49 | blk, err := aes.NewCipher(key) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &cfbStream{blk}, nil 54 | } 55 | 56 | // IETF-variant of chacha20 57 | type chacha20ietfkey []byte 58 | 59 | func (k chacha20ietfkey) IVSize() int { return chacha20.NonceSize } 60 | func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } 61 | func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream { 62 | ciph, err := chacha20.NewUnauthenticatedCipher(k, iv) 63 | if err != nil { 64 | panic(err) // should never happen 65 | } 66 | return ciph 67 | } 68 | 69 | func Chacha20IETF(key []byte) (Cipher, error) { 70 | if len(key) != chacha20.KeySize { 71 | return nil, KeySizeError(chacha20.KeySize) 72 | } 73 | return chacha20ietfkey(key), nil 74 | } 75 | 76 | type xchacha20key []byte 77 | 78 | func (k xchacha20key) IVSize() int { return chacha20.NonceSizeX } 79 | func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) } 80 | func (k xchacha20key) Encrypter(iv []byte) cipher.Stream { 81 | ciph, err := chacha20.NewUnauthenticatedCipher(k, iv) 82 | if err != nil { 83 | panic(err) // should never happen 84 | } 85 | return ciph 86 | } 87 | 88 | func Xchacha20(key []byte) (Cipher, error) { 89 | if len(key) != chacha20.KeySize { 90 | return nil, KeySizeError(chacha20.KeySize) 91 | } 92 | return xchacha20key(key), nil 93 | } 94 | 95 | type rc4Md5Key []byte 96 | 97 | func (k rc4Md5Key) IVSize() int { 98 | return 16 99 | } 100 | 101 | func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream { 102 | h := md5.New() 103 | h.Write([]byte(k)) 104 | h.Write(iv) 105 | rc4key := h.Sum(nil) 106 | c, _ := rc4.NewCipher(rc4key) 107 | return c 108 | } 109 | 110 | func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream { 111 | return k.Encrypter(iv) 112 | } 113 | 114 | func RC4MD5(key []byte) (Cipher, error) { 115 | return rc4Md5Key(key), nil 116 | } 117 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowstream/packet.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | "io" 7 | "net" 8 | 9 | "github.com/xjasonlyu/tun2socks/v2/buffer" 10 | ) 11 | 12 | // ErrShortPacket means the packet is too short to be a valid encrypted packet. 13 | var ErrShortPacket = errors.New("short packet") 14 | 15 | // Pack encrypts plaintext using stream cipher s and a random IV. 16 | // Returns a slice of dst containing random IV and ciphertext. 17 | // Ensure len(dst) >= s.IVSize() + len(plaintext). 18 | func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { 19 | if len(dst) < s.IVSize()+len(plaintext) { 20 | return nil, io.ErrShortBuffer 21 | } 22 | iv := dst[:s.IVSize()] 23 | _, err := rand.Read(iv) 24 | if err != nil { 25 | return nil, err 26 | } 27 | s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext) 28 | return dst[:len(iv)+len(plaintext)], nil 29 | } 30 | 31 | // Unpack decrypts pkt using stream cipher s. 32 | // Returns a slice of dst containing decrypted plaintext. 33 | func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { 34 | if len(pkt) < s.IVSize() { 35 | return nil, ErrShortPacket 36 | } 37 | if len(dst) < len(pkt)-s.IVSize() { 38 | return nil, io.ErrShortBuffer 39 | } 40 | iv := pkt[:s.IVSize()] 41 | s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):]) 42 | return dst[:len(pkt)-len(iv)], nil 43 | } 44 | 45 | type PacketConn struct { 46 | net.PacketConn 47 | Cipher 48 | } 49 | 50 | // NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption. 51 | func NewPacketConn(c net.PacketConn, ciph Cipher) *PacketConn { 52 | return &PacketConn{PacketConn: c, Cipher: ciph} 53 | } 54 | 55 | const maxPacketSize = 64 * 1024 56 | 57 | func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { 58 | buf := buffer.Get(maxPacketSize) 59 | defer buffer.Put(buf) 60 | buf, err := Pack(buf, b, c.Cipher) 61 | if err != nil { 62 | return 0, err 63 | } 64 | _, err = c.PacketConn.WriteTo(buf, addr) 65 | return len(b), err 66 | } 67 | 68 | func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { 69 | n, addr, err := c.PacketConn.ReadFrom(b) 70 | if err != nil { 71 | return n, addr, err 72 | } 73 | bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher) 74 | if err != nil { 75 | return n, addr, err 76 | } 77 | copy(b, bb) 78 | return len(bb), addr, err 79 | } 80 | -------------------------------------------------------------------------------- /transport/shadowsocks/shadowstream/stream.go: -------------------------------------------------------------------------------- 1 | package shadowstream 2 | 3 | import ( 4 | "crypto/cipher" 5 | "crypto/rand" 6 | "io" 7 | "net" 8 | ) 9 | 10 | const bufSize = 2048 11 | 12 | type Writer struct { 13 | io.Writer 14 | cipher.Stream 15 | buf [bufSize]byte 16 | } 17 | 18 | // NewWriter wraps an io.Writer with stream cipher encryption. 19 | func NewWriter(w io.Writer, s cipher.Stream) *Writer { return &Writer{Writer: w, Stream: s} } 20 | 21 | func (w *Writer) Write(p []byte) (n int, err error) { 22 | buf := w.buf[:] 23 | for nw := 0; n < len(p) && err == nil; n += nw { 24 | end := n + len(buf) 25 | if end > len(p) { 26 | end = len(p) 27 | } 28 | w.XORKeyStream(buf, p[n:end]) 29 | nw, err = w.Writer.Write(buf[:end-n]) 30 | } 31 | return 32 | } 33 | 34 | func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) { 35 | buf := w.buf[:] 36 | for { 37 | nr, er := r.Read(buf) 38 | n += int64(nr) 39 | b := buf[:nr] 40 | w.XORKeyStream(b, b) 41 | if _, err = w.Writer.Write(b); err != nil { 42 | return 43 | } 44 | if er != nil { 45 | if er != io.EOF { // ignore EOF as per io.ReaderFrom contract 46 | err = er 47 | } 48 | return 49 | } 50 | } 51 | } 52 | 53 | type Reader struct { 54 | io.Reader 55 | cipher.Stream 56 | buf [bufSize]byte 57 | } 58 | 59 | // NewReader wraps an io.Reader with stream cipher decryption. 60 | func NewReader(r io.Reader, s cipher.Stream) *Reader { return &Reader{Reader: r, Stream: s} } 61 | 62 | func (r *Reader) Read(p []byte) (n int, err error) { 63 | n, err = r.Reader.Read(p) 64 | if err != nil { 65 | return 0, err 66 | } 67 | r.XORKeyStream(p, p[:n]) 68 | return 69 | } 70 | 71 | func (r *Reader) WriteTo(w io.Writer) (n int64, err error) { 72 | buf := r.buf[:] 73 | for { 74 | nr, er := r.Reader.Read(buf) 75 | if nr > 0 { 76 | r.XORKeyStream(buf, buf[:nr]) 77 | nw, ew := w.Write(buf[:nr]) 78 | n += int64(nw) 79 | if ew != nil { 80 | err = ew 81 | return 82 | } 83 | } 84 | if er != nil { 85 | if er != io.EOF { // ignore EOF as per io.Copy contract (using src.WriteTo shortcut) 86 | err = er 87 | } 88 | return 89 | } 90 | } 91 | } 92 | 93 | // A Conn represents a Shadowsocks connection. It implements the net.Conn interface. 94 | type Conn struct { 95 | net.Conn 96 | Cipher 97 | r *Reader 98 | w *Writer 99 | readIV []byte 100 | writeIV []byte 101 | } 102 | 103 | // NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption. 104 | func NewConn(c net.Conn, ciph Cipher) *Conn { return &Conn{Conn: c, Cipher: ciph} } 105 | 106 | func (c *Conn) initReader() error { 107 | if c.r == nil { 108 | iv, err := c.ObtainReadIV() 109 | if err != nil { 110 | return err 111 | } 112 | c.r = NewReader(c.Conn, c.Decrypter(iv)) 113 | } 114 | return nil 115 | } 116 | 117 | func (c *Conn) Read(b []byte) (int, error) { 118 | if c.r == nil { 119 | if err := c.initReader(); err != nil { 120 | return 0, err 121 | } 122 | } 123 | return c.r.Read(b) 124 | } 125 | 126 | func (c *Conn) WriteTo(w io.Writer) (int64, error) { 127 | if c.r == nil { 128 | if err := c.initReader(); err != nil { 129 | return 0, err 130 | } 131 | } 132 | return c.r.WriteTo(w) 133 | } 134 | 135 | func (c *Conn) initWriter() error { 136 | if c.w == nil { 137 | iv, err := c.ObtainWriteIV() 138 | if err != nil { 139 | return err 140 | } 141 | if _, err := c.Conn.Write(iv); err != nil { 142 | return err 143 | } 144 | c.w = NewWriter(c.Conn, c.Encrypter(iv)) 145 | } 146 | return nil 147 | } 148 | 149 | func (c *Conn) Write(b []byte) (int, error) { 150 | if c.w == nil { 151 | if err := c.initWriter(); err != nil { 152 | return 0, err 153 | } 154 | } 155 | return c.w.Write(b) 156 | } 157 | 158 | func (c *Conn) ReadFrom(r io.Reader) (int64, error) { 159 | if c.w == nil { 160 | if err := c.initWriter(); err != nil { 161 | return 0, err 162 | } 163 | } 164 | return c.w.ReadFrom(r) 165 | } 166 | 167 | func (c *Conn) ObtainWriteIV() ([]byte, error) { 168 | if len(c.writeIV) == c.IVSize() { 169 | return c.writeIV, nil 170 | } 171 | 172 | iv := make([]byte, c.IVSize()) 173 | 174 | if _, err := rand.Read(iv); err != nil { 175 | return nil, err 176 | } 177 | 178 | c.writeIV = iv 179 | 180 | return iv, nil 181 | } 182 | 183 | func (c *Conn) ObtainReadIV() ([]byte, error) { 184 | if len(c.readIV) == c.IVSize() { 185 | return c.readIV, nil 186 | } 187 | 188 | iv := make([]byte, c.IVSize()) 189 | 190 | if _, err := io.ReadFull(c.Conn, iv); err != nil { 191 | return nil, err 192 | } 193 | 194 | c.readIV = iv 195 | 196 | return iv, nil 197 | } 198 | -------------------------------------------------------------------------------- /transport/simple-obfs/http.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "fmt" 8 | "io" 9 | mRand "math/rand" 10 | "net" 11 | "net/http" 12 | 13 | "github.com/xjasonlyu/tun2socks/v2/buffer" 14 | ) 15 | 16 | // HTTPObfs is shadowsocks http simple-obfs implementation 17 | type HTTPObfs struct { 18 | net.Conn 19 | host string 20 | port string 21 | buf []byte 22 | offset int 23 | firstRequest bool 24 | firstResponse bool 25 | } 26 | 27 | func (ho *HTTPObfs) Read(b []byte) (int, error) { 28 | if ho.buf != nil { 29 | n := copy(b, ho.buf[ho.offset:]) 30 | ho.offset += n 31 | if ho.offset == len(ho.buf) { 32 | buffer.Put(ho.buf) 33 | ho.buf = nil 34 | } 35 | return n, nil 36 | } 37 | 38 | if ho.firstResponse { 39 | buf := buffer.Get(buffer.RelayBufferSize) 40 | n, err := ho.Conn.Read(buf) 41 | if err != nil { 42 | buffer.Put(buf) 43 | return 0, err 44 | } 45 | idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) 46 | if idx == -1 { 47 | buffer.Put(buf) 48 | return 0, io.EOF 49 | } 50 | ho.firstResponse = false 51 | length := n - (idx + 4) 52 | n = copy(b, buf[idx+4:n]) 53 | if length > n { 54 | ho.buf = buf[:idx+4+length] 55 | ho.offset = idx + 4 + n 56 | } else { 57 | buffer.Put(buf) 58 | } 59 | return n, nil 60 | } 61 | return ho.Conn.Read(b) 62 | } 63 | 64 | func (ho *HTTPObfs) Write(b []byte) (int, error) { 65 | if ho.firstRequest { 66 | randBytes := make([]byte, 16) 67 | rand.Read(randBytes) 68 | req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:])) 69 | req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", mRand.Int()%54, mRand.Int()%2)) 70 | req.Header.Set("Upgrade", "websocket") 71 | req.Header.Set("Connection", "Upgrade") 72 | req.Host = ho.host 73 | if ho.port != "80" { 74 | req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) 75 | } 76 | req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) 77 | req.ContentLength = int64(len(b)) 78 | err := req.Write(ho.Conn) 79 | ho.firstRequest = false 80 | return len(b), err 81 | } 82 | 83 | return ho.Conn.Write(b) 84 | } 85 | 86 | // NewHTTPObfs return a HTTPObfs 87 | func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { 88 | return &HTTPObfs{ 89 | Conn: conn, 90 | firstRequest: true, 91 | firstResponse: true, 92 | host: host, 93 | port: port, 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /transport/simple-obfs/obfs.go: -------------------------------------------------------------------------------- 1 | // Package obfs provides obfuscation functionality for Shadowsocks protocol. 2 | package obfs 3 | -------------------------------------------------------------------------------- /transport/simple-obfs/tls.go: -------------------------------------------------------------------------------- 1 | package obfs 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "io" 7 | "net" 8 | "time" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/buffer" 11 | "github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool" 12 | ) 13 | 14 | const ( 15 | chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 16 | ) 17 | 18 | // TLSObfs is shadowsocks tls simple-obfs implementation 19 | type TLSObfs struct { 20 | net.Conn 21 | server string 22 | remain int 23 | firstRequest bool 24 | firstResponse bool 25 | } 26 | 27 | func (to *TLSObfs) read(b []byte, discardN int) (int, error) { 28 | buf := buffer.Get(discardN) 29 | _, err := io.ReadFull(to.Conn, buf) 30 | if err != nil { 31 | return 0, err 32 | } 33 | buffer.Put(buf) 34 | 35 | sizeBuf := make([]byte, 2) 36 | _, err = io.ReadFull(to.Conn, sizeBuf) 37 | if err != nil { 38 | return 0, nil 39 | } 40 | 41 | length := int(binary.BigEndian.Uint16(sizeBuf)) 42 | if length > len(b) { 43 | n, err := to.Conn.Read(b) 44 | if err != nil { 45 | return n, err 46 | } 47 | to.remain = length - n 48 | return n, nil 49 | } 50 | 51 | return io.ReadFull(to.Conn, b[:length]) 52 | } 53 | 54 | func (to *TLSObfs) Read(b []byte) (int, error) { 55 | if to.remain > 0 { 56 | length := to.remain 57 | if length > len(b) { 58 | length = len(b) 59 | } 60 | 61 | n, err := io.ReadFull(to.Conn, b[:length]) 62 | to.remain -= n 63 | return n, err 64 | } 65 | 66 | if to.firstResponse { 67 | // type + ver + lensize + 91 = 96 68 | // type + ver + lensize + 1 = 6 69 | // type + ver = 3 70 | to.firstResponse = false 71 | return to.read(b, 105) 72 | } 73 | 74 | // type + ver = 3 75 | return to.read(b, 3) 76 | } 77 | 78 | func (to *TLSObfs) Write(b []byte) (int, error) { 79 | length := len(b) 80 | for i := 0; i < length; i += chunkSize { 81 | end := i + chunkSize 82 | if end > length { 83 | end = length 84 | } 85 | 86 | n, err := to.write(b[i:end]) 87 | if err != nil { 88 | return n, err 89 | } 90 | } 91 | return length, nil 92 | } 93 | 94 | func (to *TLSObfs) write(b []byte) (int, error) { 95 | if to.firstRequest { 96 | helloMsg := makeClientHelloMsg(b, to.server) 97 | _, err := to.Conn.Write(helloMsg) 98 | to.firstRequest = false 99 | return len(b), err 100 | } 101 | 102 | buf := bufferpool.Get() 103 | defer bufferpool.Put(buf) 104 | buf.Write([]byte{0x17, 0x03, 0x03}) 105 | binary.Write(buf, binary.BigEndian, uint16(len(b))) 106 | buf.Write(b) 107 | _, err := to.Conn.Write(buf.Bytes()) 108 | return len(b), err 109 | } 110 | 111 | // NewTLSObfs return a SimpleObfs 112 | func NewTLSObfs(conn net.Conn, server string) net.Conn { 113 | return &TLSObfs{ 114 | Conn: conn, 115 | server: server, 116 | firstRequest: true, 117 | firstResponse: true, 118 | } 119 | } 120 | 121 | func makeClientHelloMsg(data []byte, server string) []byte { 122 | random := make([]byte, 28) 123 | sessionID := make([]byte, 32) 124 | rand.Read(random) 125 | rand.Read(sessionID) 126 | 127 | buf := bufferpool.Get() 128 | defer bufferpool.Put(buf) 129 | 130 | // handshake, TLS 1.0 version, length 131 | buf.WriteByte(22) 132 | buf.Write([]byte{0x03, 0x01}) 133 | length := uint16(212 + len(data) + len(server)) 134 | buf.WriteByte(byte(length >> 8)) 135 | buf.WriteByte(byte(length & 0xff)) 136 | 137 | // clientHello, length, TLS 1.2 version 138 | buf.WriteByte(1) 139 | buf.WriteByte(0) 140 | binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server))) 141 | buf.Write([]byte{0x03, 0x03}) 142 | 143 | // random with timestamp, sid len, sid 144 | binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) 145 | buf.Write(random) 146 | buf.WriteByte(32) 147 | buf.Write(sessionID) 148 | 149 | // cipher suites 150 | buf.Write([]byte{0x00, 0x38}) 151 | buf.Write([]byte{ 152 | 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 153 | 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 154 | 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, 155 | 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, 156 | }) 157 | 158 | // compression 159 | buf.Write([]byte{0x01, 0x00}) 160 | 161 | // extension length 162 | binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server))) 163 | 164 | // session ticket 165 | buf.Write([]byte{0x00, 0x23}) 166 | binary.Write(buf, binary.BigEndian, uint16(len(data))) 167 | buf.Write(data) 168 | 169 | // server name 170 | buf.Write([]byte{0x00, 0x00}) 171 | binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) 172 | binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) 173 | buf.WriteByte(0) 174 | binary.Write(buf, binary.BigEndian, uint16(len(server))) 175 | buf.Write([]byte(server)) 176 | 177 | // ec_point 178 | buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02}) 179 | 180 | // groups 181 | buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) 182 | 183 | // signature 184 | buf.Write([]byte{ 185 | 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 186 | 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 187 | 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 188 | }) 189 | 190 | // encrypt then mac 191 | buf.Write([]byte{0x00, 0x16, 0x00, 0x00}) 192 | 193 | // extended master secret 194 | buf.Write([]byte{0x00, 0x17, 0x00, 0x00}) 195 | 196 | return buf.Bytes() 197 | } 198 | -------------------------------------------------------------------------------- /transport/socks4/socks4.go: -------------------------------------------------------------------------------- 1 | // Package socks4 provides SOCKS4/SOCKS4A client functionalities. 2 | package socks4 3 | 4 | import ( 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "net" 9 | "net/netip" 10 | "strconv" 11 | 12 | "github.com/xjasonlyu/tun2socks/v2/transport/internal/bufferpool" 13 | ) 14 | 15 | const Version = 0x04 16 | 17 | type Command = uint8 18 | 19 | const ( 20 | CmdConnect Command = 0x01 21 | CmdBind Command = 0x02 22 | ) 23 | 24 | type Code = uint8 25 | 26 | const ( 27 | RequestGranted Code = 90 28 | RequestRejected Code = 91 29 | RequestIdentdFailed Code = 92 30 | RequestIdentdMismatched Code = 93 31 | ) 32 | 33 | var ( 34 | errVersionMismatched = errors.New("version code mismatched") 35 | errIPv6NotSupported = errors.New("IPv6 not supported") 36 | errCmdNotSupported = errors.New("command not supported") 37 | 38 | ErrRequestRejected = errors.New("request rejected or failed") 39 | ErrRequestIdentdFailed = errors.New("request rejected because SOCKS server cannot connect to identd on the client") 40 | ErrRequestIdentdMismatched = errors.New("request rejected because the client program and identd report different user-ids") 41 | ErrRequestUnknownCode = errors.New("request failed with unknown code") 42 | ) 43 | 44 | func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) { 45 | if command == CmdBind { 46 | return errCmdNotSupported 47 | } 48 | 49 | var ( 50 | host string 51 | port uint16 52 | ) 53 | if host, port, err = splitHostPort(addr); err != nil { 54 | return err 55 | } 56 | 57 | ip, _ := netip.ParseAddr(host) 58 | switch { 59 | case !ip.IsValid(): /* HOST */ 60 | ip = netip.AddrFrom4([4]byte{0, 0, 0, 1}) 61 | case ip.Is4In6(): /* IPv4-mapped IPv6 */ 62 | ip = netip.AddrFrom4(ip.As4()) 63 | case ip.Is4(): /* IPv4 */ 64 | case ip.Is6(): /* IPv6 */ 65 | return errIPv6NotSupported 66 | } 67 | 68 | req := bufferpool.Get() 69 | defer bufferpool.Put(req) 70 | req.WriteByte(Version) 71 | req.WriteByte(command) 72 | _ = binary.Write(req, binary.BigEndian, port) 73 | req.Write(ip.AsSlice()) 74 | req.WriteString(userID) 75 | req.WriteByte(0x00) /* NULL */ 76 | 77 | if isReservedIP(ip) /* SOCKS4A */ { 78 | req.WriteString(host) 79 | req.WriteByte(0) /* NULL */ 80 | } 81 | 82 | if _, err = rw.Write(req.Bytes()); err != nil { 83 | return err 84 | } 85 | 86 | var resp [8]byte 87 | if _, err = io.ReadFull(rw, resp[:]); err != nil { 88 | return err 89 | } 90 | 91 | if resp[0] != 0x00 { 92 | return errVersionMismatched 93 | } 94 | 95 | switch resp[1] { 96 | case RequestGranted: 97 | return nil 98 | case RequestRejected: 99 | return ErrRequestRejected 100 | case RequestIdentdFailed: 101 | return ErrRequestIdentdFailed 102 | case RequestIdentdMismatched: 103 | return ErrRequestIdentdMismatched 104 | default: 105 | return ErrRequestUnknownCode 106 | } 107 | } 108 | 109 | // For version 4A, if the client cannot resolve the destination host's 110 | // domain name to find its IP address, it should set the first three bytes 111 | // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds 112 | // to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The 113 | // Internet Assigned Numbers Authority -- such an address is inadmissible 114 | // as a destination IP address and thus should never occur if the client 115 | // can resolve the domain name.) 116 | func isReservedIP(ip netip.Addr) bool { 117 | prefix := netip.PrefixFrom(netip.IPv4Unspecified(), 24) 118 | return !ip.IsUnspecified() && prefix.Contains(ip) 119 | } 120 | 121 | func splitHostPort(addr string) (string, uint16, error) { 122 | host, portStr, err := net.SplitHostPort(addr) 123 | if err != nil { 124 | return "", 0, err 125 | } 126 | 127 | portInt, err := strconv.ParseUint(portStr, 10, 16) 128 | if err != nil { 129 | return "", 0, err 130 | } 131 | 132 | return host, uint16(portInt), nil 133 | } 134 | -------------------------------------------------------------------------------- /transport/socks4/socks4_test.go: -------------------------------------------------------------------------------- 1 | package socks4 2 | 3 | import ( 4 | "net/netip" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestIsReservedIP(t *testing.T) { 11 | reservedIPs := []string{ 12 | "0.0.0.1", 13 | "0.0.0.2", 14 | "0.0.0.50", 15 | "0.0.0.100", 16 | "0.0.0.255", 17 | } 18 | for _, ip := range reservedIPs { 19 | assert.True(t, isReservedIP(netip.MustParseAddr(ip))) 20 | } 21 | 22 | unReservedIPs := []string{ 23 | "0.0.0.0", 24 | "0.0.1.0", 25 | "1.1.1.1", 26 | "10.0.0.0", 27 | "255.255.255.255", 28 | } 29 | for _, ip := range unReservedIPs { 30 | assert.False(t, isReservedIP(netip.MustParseAddr(ip))) 31 | } 32 | } 33 | 34 | func TestSplitHostPort(t *testing.T) { 35 | tests := []struct { 36 | addr string 37 | host string 38 | port uint16 39 | }{ 40 | { 41 | "1.1.1.1:80", 42 | "1.1.1.1", 43 | 80, 44 | }, 45 | { 46 | "1.1.1.1:0", 47 | "1.1.1.1", 48 | 0, 49 | }, 50 | { 51 | "0.0.0.0:0", 52 | "0.0.0.0", 53 | 0, 54 | }, 55 | { 56 | "[::1]:443", 57 | "::1", 58 | 443, 59 | }, 60 | { 61 | "example.com:80", 62 | "example.com", 63 | 80, 64 | }, 65 | } 66 | for _, tt := range tests { 67 | host, port, err := splitHostPort(tt.addr) 68 | assert.NoError(t, err) 69 | assert.Equal(t, tt.host, host) 70 | assert.Equal(t, tt.port, port) 71 | } 72 | 73 | addrs := []string{ 74 | "1.1.1.1:-80", 75 | "1.1.1.1:abcd", 76 | "::1:80", 77 | "[::1]", 78 | "example.com", 79 | } 80 | for _, addr := range addrs { 81 | _, _, err := splitHostPort(addr) 82 | assert.Error(t, err) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /transport/socks4/socks4a.txt: -------------------------------------------------------------------------------- 1 | SOCKS 4A: A Simple Extension to SOCKS 4 Protocol 2 | 3 | Ying-Da Lee 4 | yingda@best.com or yingda@esd.sgi.com 5 | 6 | Please read SOCKS4.protocol first for an description of the version 4 7 | protocol. This extension is intended to allow the use of SOCKS on hosts 8 | which are not capable of resolving all domain names. 9 | 10 | In version 4, the client sends the following packet to the SOCKS server 11 | to request a CONNECT or a BIND operation: 12 | 13 | +----+----+----+----+----+----+----+----+----+----+....+----+ 14 | | VN | CD | DSTPORT | DSTIP | USERID |NULL| 15 | +----+----+----+----+----+----+----+----+----+----+....+----+ 16 | # of bytes: 1 1 2 4 variable 1 17 | 18 | VN is the SOCKS protocol version number and should be 4. CD is the 19 | SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL 20 | is a byte of all zero bits. 21 | 22 | For version 4A, if the client cannot resolve the destination host's 23 | domain name to find its IP address, it should set the first three bytes 24 | of DSTIP to NULL and the last byte to a non-zero value. (This corresponds 25 | to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The 26 | Internet Assigned Numbers Authority -- such an address is inadmissible 27 | as a destination IP address and thus should never occur if the client 28 | can resolve the domain name.) Following the NULL byte terminating 29 | USERID, the client must sends the destination domain name and termiantes 30 | it with another NULL byte. This is used for both CONNECT and BIND requests. 31 | 32 | A server using protocol 4A must check the DSTIP in the request packet. 33 | If it represent address 0.0.0.x with nonzero x, the server must read 34 | in the domain name that the client sends in the packet. The server 35 | should resolve the domain name and make connection to the destination 36 | host if it can. 37 | 38 | SOCKSified sockd may pass domain names that it cannot resolve to 39 | the next-hop SOCKS server. 40 | -------------------------------------------------------------------------------- /transport/socks5/rfc1929.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group M. Leech 8 | Request for Comments: 1929 Bell-Northern Research Ltd 9 | Category: Standards Track March 1996 10 | 11 | 12 | Username/Password Authentication for SOCKS V5 13 | 14 | Status of this Memo 15 | 16 | This document specifies an Internet standards track protocol for the 17 | Internet community, and requests discussion and suggestions for 18 | improvements. Please refer to the current edition of the "Internet 19 | Official Protocol Standards" (STD 1) for the standardization state 20 | and status of this protocol. Distribution of this memo is unlimited. 21 | 22 | 1. Introduction 23 | 24 | The protocol specification for SOCKS Version 5 specifies a 25 | generalized framework for the use of arbitrary authentication 26 | protocols in the initial socks connection setup. This document 27 | describes one of those protocols, as it fits into the SOCKS Version 5 28 | authentication "subnegotiation". 29 | 30 | Note: 31 | 32 | Unless otherwise noted, the decimal numbers appearing in packet- 33 | format diagrams represent the length of the corresponding field, in 34 | octets. Where a given octet must take on a specific value, the 35 | syntax X'hh' is used to denote the value of the single octet in that 36 | field. When the word 'Variable' is used, it indicates that the 37 | corresponding field has a variable length defined either by an 38 | associated (one or two octet) length field, or by a data type field. 39 | 40 | 2. Initial negotiation 41 | 42 | Once the SOCKS V5 server has started, and the client has selected the 43 | Username/Password Authentication protocol, the Username/Password 44 | subnegotiation begins. This begins with the client producing a 45 | Username/Password request: 46 | 47 | +----+------+----------+------+----------+ 48 | |VER | ULEN | UNAME | PLEN | PASSWD | 49 | +----+------+----------+------+----------+ 50 | | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 51 | +----+------+----------+------+----------+ 52 | 53 | 54 | 55 | 56 | 57 | 58 | Leech Standards Track [Page 1] 59 | 60 | RFC 1929 Username Authentication for SOCKS V5 March 1996 61 | 62 | 63 | The VER field contains the current version of the subnegotiation, 64 | which is X'01'. The ULEN field contains the length of the UNAME field 65 | that follows. The UNAME field contains the username as known to the 66 | source operating system. The PLEN field contains the length of the 67 | PASSWD field that follows. The PASSWD field contains the password 68 | association with the given UNAME. 69 | 70 | The server verifies the supplied UNAME and PASSWD, and sends the 71 | following response: 72 | 73 | +----+--------+ 74 | |VER | STATUS | 75 | +----+--------+ 76 | | 1 | 1 | 77 | +----+--------+ 78 | 79 | A STATUS field of X'00' indicates success. If the server returns a 80 | `failure' (STATUS value other than X'00') status, it MUST close the 81 | connection. 82 | 83 | 3. Security Considerations 84 | 85 | This document describes a subnegotiation that provides authentication 86 | services to the SOCKS protocol. Since the request carries the 87 | password in cleartext, this subnegotiation is not recommended for 88 | environments where "sniffing" is possible and practical. 89 | 90 | 4. Author's Address 91 | 92 | Marcus Leech 93 | Bell-Northern Research Ltd 94 | P.O. Box 3511, Station C 95 | Ottawa, ON 96 | CANADA K1Y 4H7 97 | 98 | Phone: +1 613 763 9145 99 | EMail: mleech@bnr.ca 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Leech Standards Track [Page 2] 115 | 116 | -------------------------------------------------------------------------------- /transport/socks5/socks5_test.go: -------------------------------------------------------------------------------- 1 | package socks5 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestSocks5ClientHandshake(t *testing.T) { 12 | // Mock server responses 13 | readBuffer := &bytes.Buffer{} 14 | readBuffer.Write([]byte{Version, MethodUserPass}) 15 | readBuffer.Write([]byte{Version, 0x00 /* STATUS of SUCCESS */}) 16 | readBuffer.Write([]byte{Version, 0x00 /* STATUS of SUCCESS */, 0x00 /* RSV */}) 17 | readBuffer.Write([]byte{AtypIPv4, 0x1, 0x2, 0x3, 0x4, 0x0, 0x0 /* IPv4: 1.2.3.4:0 */}) 18 | reader := bufio.NewReader(bytes.NewReader(readBuffer.Bytes())) 19 | 20 | writeBuffer := &bytes.Buffer{} 21 | writer := bufio.NewWriter(writeBuffer) 22 | 23 | io := bufio.NewReadWriter(reader, writer) 24 | 25 | addr, err := ClientHandshake(io, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, CmdConnect, &User{ 26 | Username: "test", 27 | Password: "6ab49d8b-a009-44e4-bd53-fbdb48fbe7eb", 28 | }) 29 | 30 | assert.Nil(t, err, "Failed to perform SOCKS5 client handshake: %v", err) 31 | assert.Equal(t, "1.2.3.4:0", addr.String(), "Incorrect address obtained from SOCKS5 client handshake") 32 | } 33 | -------------------------------------------------------------------------------- /tunnel/addr.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "net" 5 | "net/netip" 6 | 7 | "gvisor.dev/gvisor/pkg/tcpip" 8 | ) 9 | 10 | // parseNetAddr parses net.Addr to IP and port. 11 | func parseNetAddr(addr net.Addr) (netip.Addr, uint16) { 12 | if addr == nil { 13 | return netip.Addr{}, 0 14 | } 15 | if v, ok := addr.(interface { 16 | AddrPort() netip.AddrPort 17 | }); ok { 18 | ap := v.AddrPort() 19 | return ap.Addr(), ap.Port() 20 | } 21 | return parseAddrString(addr.String()) 22 | } 23 | 24 | // parseAddrString parses address string to IP and port. 25 | // It doesn't do any name resolution. 26 | func parseAddrString(s string) (netip.Addr, uint16) { 27 | ap, err := netip.ParseAddrPort(s) 28 | if err != nil { 29 | return netip.Addr{}, 0 30 | } 31 | return ap.Addr(), ap.Port() 32 | } 33 | 34 | // parseTCPIPAddress parses tcpip.Address to netip.Addr. 35 | func parseTCPIPAddress(addr tcpip.Address) netip.Addr { 36 | ip, _ := netip.AddrFromSlice(addr.AsSlice()) 37 | return ip 38 | } 39 | -------------------------------------------------------------------------------- /tunnel/global.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/xjasonlyu/tun2socks/v2/proxy" 7 | "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" 8 | ) 9 | 10 | var ( 11 | _globalMu sync.RWMutex 12 | _globalT *Tunnel 13 | ) 14 | 15 | func init() { 16 | ReplaceGlobal(New(&proxy.Base{}, statistic.DefaultManager)) 17 | T().ProcessAsync() 18 | } 19 | 20 | // T returns the global Tunnel, which can be reconfigured with 21 | // ReplaceGlobal. It's safe for concurrent use. 22 | func T() *Tunnel { 23 | _globalMu.RLock() 24 | t := _globalT 25 | _globalMu.RUnlock() 26 | return t 27 | } 28 | 29 | // ReplaceGlobal replaces the global Tunnel, and returns a function 30 | // to restore the original values. It's safe for concurrent use. 31 | func ReplaceGlobal(t *Tunnel) func() { 32 | _globalMu.Lock() 33 | prev := _globalT 34 | _globalT = t 35 | _globalMu.Unlock() 36 | return func() { ReplaceGlobal(prev) } 37 | } 38 | -------------------------------------------------------------------------------- /tunnel/statistic/manager.go: -------------------------------------------------------------------------------- 1 | package statistic 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "go.uber.org/atomic" 8 | ) 9 | 10 | var DefaultManager *Manager 11 | 12 | func init() { 13 | DefaultManager = &Manager{ 14 | uploadTemp: atomic.NewInt64(0), 15 | downloadTemp: atomic.NewInt64(0), 16 | uploadBlip: atomic.NewInt64(0), 17 | downloadBlip: atomic.NewInt64(0), 18 | uploadTotal: atomic.NewInt64(0), 19 | downloadTotal: atomic.NewInt64(0), 20 | } 21 | go DefaultManager.handle() 22 | } 23 | 24 | type Manager struct { 25 | connections sync.Map 26 | uploadTemp *atomic.Int64 27 | downloadTemp *atomic.Int64 28 | uploadBlip *atomic.Int64 29 | downloadBlip *atomic.Int64 30 | uploadTotal *atomic.Int64 31 | downloadTotal *atomic.Int64 32 | } 33 | 34 | func (m *Manager) Join(c tracker) { 35 | m.connections.Store(c.ID(), c) 36 | } 37 | 38 | func (m *Manager) Leave(c tracker) { 39 | m.connections.Delete(c.ID()) 40 | } 41 | 42 | func (m *Manager) PushUploaded(size int64) { 43 | m.uploadTemp.Add(size) 44 | m.uploadTotal.Add(size) 45 | } 46 | 47 | func (m *Manager) PushDownloaded(size int64) { 48 | m.downloadTemp.Add(size) 49 | m.downloadTotal.Add(size) 50 | } 51 | 52 | func (m *Manager) Now() (up int64, down int64) { 53 | return m.uploadBlip.Load(), m.downloadBlip.Load() 54 | } 55 | 56 | func (m *Manager) Snapshot() *Snapshot { 57 | var connections []tracker 58 | m.connections.Range(func(key, value any) bool { 59 | connections = append(connections, value.(tracker)) 60 | return true 61 | }) 62 | 63 | return &Snapshot{ 64 | UploadTotal: m.uploadTotal.Load(), 65 | DownloadTotal: m.downloadTotal.Load(), 66 | Connections: connections, 67 | } 68 | } 69 | 70 | func (m *Manager) ResetStatistic() { 71 | m.uploadTemp.Store(0) 72 | m.uploadBlip.Store(0) 73 | m.uploadTotal.Store(0) 74 | m.downloadTemp.Store(0) 75 | m.downloadBlip.Store(0) 76 | m.downloadTotal.Store(0) 77 | } 78 | 79 | func (m *Manager) handle() { 80 | ticker := time.NewTicker(time.Second) 81 | 82 | for range ticker.C { 83 | m.uploadBlip.Store(m.uploadTemp.Load()) 84 | m.uploadTemp.Store(0) 85 | m.downloadBlip.Store(m.downloadTemp.Load()) 86 | m.downloadTemp.Store(0) 87 | } 88 | } 89 | 90 | type Snapshot struct { 91 | DownloadTotal int64 `json:"downloadTotal"` 92 | UploadTotal int64 `json:"uploadTotal"` 93 | Connections []tracker `json:"connections"` 94 | } 95 | -------------------------------------------------------------------------------- /tunnel/statistic/tracker.go: -------------------------------------------------------------------------------- 1 | package statistic 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | "go.uber.org/atomic" 10 | 11 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 12 | ) 13 | 14 | type tracker interface { 15 | ID() string 16 | Close() error 17 | } 18 | 19 | type trackerInfo struct { 20 | Start time.Time `json:"start"` 21 | UUID uuid.UUID `json:"id"` 22 | Metadata *M.Metadata `json:"metadata"` 23 | UploadTotal *atomic.Int64 `json:"upload"` 24 | DownloadTotal *atomic.Int64 `json:"download"` 25 | } 26 | 27 | type tcpTracker struct { 28 | net.Conn `json:"-"` 29 | 30 | *trackerInfo 31 | manager *Manager 32 | } 33 | 34 | func NewTCPTracker(conn net.Conn, metadata *M.Metadata, manager *Manager) net.Conn { 35 | id, _ := uuid.NewRandom() 36 | 37 | tt := &tcpTracker{ 38 | Conn: conn, 39 | manager: manager, 40 | trackerInfo: &trackerInfo{ 41 | UUID: id, 42 | Start: time.Now(), 43 | Metadata: metadata, 44 | UploadTotal: atomic.NewInt64(0), 45 | DownloadTotal: atomic.NewInt64(0), 46 | }, 47 | } 48 | 49 | manager.Join(tt) 50 | return tt 51 | } 52 | 53 | func (tt *tcpTracker) ID() string { 54 | return tt.UUID.String() 55 | } 56 | 57 | func (tt *tcpTracker) Read(b []byte) (int, error) { 58 | n, err := tt.Conn.Read(b) 59 | download := int64(n) 60 | tt.manager.PushDownloaded(download) 61 | tt.DownloadTotal.Add(download) 62 | return n, err 63 | } 64 | 65 | func (tt *tcpTracker) Write(b []byte) (int, error) { 66 | n, err := tt.Conn.Write(b) 67 | upload := int64(n) 68 | tt.manager.PushUploaded(upload) 69 | tt.UploadTotal.Add(upload) 70 | return n, err 71 | } 72 | 73 | func (tt *tcpTracker) Close() error { 74 | tt.manager.Leave(tt) 75 | return tt.Conn.Close() 76 | } 77 | 78 | func (tt *tcpTracker) CloseRead() error { 79 | if cr, ok := tt.Conn.(interface{ CloseRead() error }); ok { 80 | return cr.CloseRead() 81 | } 82 | return errors.New("CloseRead is not implemented") 83 | } 84 | 85 | func (tt *tcpTracker) CloseWrite() error { 86 | if cw, ok := tt.Conn.(interface{ CloseWrite() error }); ok { 87 | return cw.CloseWrite() 88 | } 89 | return errors.New("CloseWrite is not implemented") 90 | } 91 | 92 | type udpTracker struct { 93 | net.PacketConn `json:"-"` 94 | 95 | *trackerInfo 96 | manager *Manager 97 | } 98 | 99 | func NewUDPTracker(conn net.PacketConn, metadata *M.Metadata, manager *Manager) net.PacketConn { 100 | id, _ := uuid.NewRandom() 101 | 102 | ut := &udpTracker{ 103 | PacketConn: conn, 104 | manager: manager, 105 | trackerInfo: &trackerInfo{ 106 | UUID: id, 107 | Start: time.Now(), 108 | Metadata: metadata, 109 | UploadTotal: atomic.NewInt64(0), 110 | DownloadTotal: atomic.NewInt64(0), 111 | }, 112 | } 113 | 114 | manager.Join(ut) 115 | return ut 116 | } 117 | 118 | func (ut *udpTracker) ID() string { 119 | return ut.UUID.String() 120 | } 121 | 122 | func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { 123 | n, addr, err := ut.PacketConn.ReadFrom(b) 124 | download := int64(n) 125 | ut.manager.PushDownloaded(download) 126 | ut.DownloadTotal.Add(download) 127 | return n, addr, err 128 | } 129 | 130 | func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { 131 | n, err := ut.PacketConn.WriteTo(b, addr) 132 | upload := int64(n) 133 | ut.manager.PushUploaded(upload) 134 | ut.UploadTotal.Add(upload) 135 | return n, err 136 | } 137 | 138 | func (ut *udpTracker) Close() error { 139 | ut.manager.Leave(ut) 140 | return ut.PacketConn.Close() 141 | } 142 | -------------------------------------------------------------------------------- /tunnel/tcp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/buffer" 11 | "github.com/xjasonlyu/tun2socks/v2/core/adapter" 12 | "github.com/xjasonlyu/tun2socks/v2/log" 13 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 14 | "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" 15 | ) 16 | 17 | func (t *Tunnel) handleTCPConn(originConn adapter.TCPConn) { 18 | defer originConn.Close() 19 | 20 | id := originConn.ID() 21 | metadata := &M.Metadata{ 22 | Network: M.TCP, 23 | SrcIP: parseTCPIPAddress(id.RemoteAddress), 24 | SrcPort: id.RemotePort, 25 | DstIP: parseTCPIPAddress(id.LocalAddress), 26 | DstPort: id.LocalPort, 27 | } 28 | 29 | ctx, cancel := context.WithTimeout(context.Background(), tcpConnectTimeout) 30 | defer cancel() 31 | 32 | remoteConn, err := t.Dialer().DialContext(ctx, metadata) 33 | if err != nil { 34 | log.Warnf("[TCP] dial %s: %v", metadata.DestinationAddress(), err) 35 | return 36 | } 37 | metadata.MidIP, metadata.MidPort = parseNetAddr(remoteConn.LocalAddr()) 38 | 39 | remoteConn = statistic.NewTCPTracker(remoteConn, metadata, t.manager) 40 | defer remoteConn.Close() 41 | 42 | log.Infof("[TCP] %s <-> %s", metadata.SourceAddress(), metadata.DestinationAddress()) 43 | pipe(originConn, remoteConn) 44 | } 45 | 46 | // pipe copies data to & from provided net.Conn(s) bidirectionally. 47 | func pipe(origin, remote net.Conn) { 48 | wg := sync.WaitGroup{} 49 | wg.Add(2) 50 | 51 | go unidirectionalStream(remote, origin, "origin->remote", &wg) 52 | go unidirectionalStream(origin, remote, "remote->origin", &wg) 53 | 54 | wg.Wait() 55 | } 56 | 57 | func unidirectionalStream(dst, src net.Conn, dir string, wg *sync.WaitGroup) { 58 | defer wg.Done() 59 | buf := buffer.Get(buffer.RelayBufferSize) 60 | if _, err := io.CopyBuffer(dst, src, buf); err != nil { 61 | log.Debugf("[TCP] copy data for %s: %v", dir, err) 62 | } 63 | buffer.Put(buf) 64 | // Do the upload/download side TCP half-close. 65 | if cr, ok := src.(interface{ CloseRead() error }); ok { 66 | cr.CloseRead() 67 | } 68 | if cw, ok := dst.(interface{ CloseWrite() error }); ok { 69 | cw.CloseWrite() 70 | } 71 | // Set TCP half-close timeout. 72 | dst.SetReadDeadline(time.Now().Add(tcpWaitTimeout)) 73 | } 74 | -------------------------------------------------------------------------------- /tunnel/tunnel.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "go.uber.org/atomic" 9 | 10 | "github.com/xjasonlyu/tun2socks/v2/core/adapter" 11 | "github.com/xjasonlyu/tun2socks/v2/proxy" 12 | "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" 13 | ) 14 | 15 | const ( 16 | // tcpConnectTimeout is the default timeout for TCP handshakes. 17 | tcpConnectTimeout = 5 * time.Second 18 | // tcpWaitTimeout implements a TCP half-close timeout. 19 | tcpWaitTimeout = 60 * time.Second 20 | // udpSessionTimeout is the default timeout for UDP sessions. 21 | udpSessionTimeout = 60 * time.Second 22 | ) 23 | 24 | var _ adapter.TransportHandler = (*Tunnel)(nil) 25 | 26 | type Tunnel struct { 27 | // Unbuffered TCP/UDP queues. 28 | tcpQueue chan adapter.TCPConn 29 | udpQueue chan adapter.UDPConn 30 | 31 | // UDP session timeout. 32 | udpTimeout *atomic.Duration 33 | 34 | // Internal proxy.Dialer for Tunnel. 35 | dialerMu sync.RWMutex 36 | dialer proxy.Dialer 37 | 38 | // Where the Tunnel statistics are sent to. 39 | manager *statistic.Manager 40 | 41 | procOnce sync.Once 42 | procCancel context.CancelFunc 43 | } 44 | 45 | func New(dialer proxy.Dialer, manager *statistic.Manager) *Tunnel { 46 | return &Tunnel{ 47 | tcpQueue: make(chan adapter.TCPConn), 48 | udpQueue: make(chan adapter.UDPConn), 49 | udpTimeout: atomic.NewDuration(udpSessionTimeout), 50 | dialer: dialer, 51 | manager: manager, 52 | procCancel: func() { /* nop */ }, 53 | } 54 | } 55 | 56 | // TCPIn return fan-in TCP queue. 57 | func (t *Tunnel) TCPIn() chan<- adapter.TCPConn { 58 | return t.tcpQueue 59 | } 60 | 61 | // UDPIn return fan-in UDP queue. 62 | func (t *Tunnel) UDPIn() chan<- adapter.UDPConn { 63 | return t.udpQueue 64 | } 65 | 66 | func (t *Tunnel) HandleTCP(conn adapter.TCPConn) { 67 | t.TCPIn() <- conn 68 | } 69 | 70 | func (t *Tunnel) HandleUDP(conn adapter.UDPConn) { 71 | t.UDPIn() <- conn 72 | } 73 | 74 | func (t *Tunnel) process(ctx context.Context) { 75 | for { 76 | select { 77 | case conn := <-t.tcpQueue: 78 | go t.handleTCPConn(conn) 79 | case conn := <-t.udpQueue: 80 | go t.handleUDPConn(conn) 81 | case <-ctx.Done(): 82 | return 83 | } 84 | } 85 | } 86 | 87 | // ProcessAsync can be safely called multiple times, but will only be effective once. 88 | func (t *Tunnel) ProcessAsync() { 89 | t.procOnce.Do(func() { 90 | ctx, cancel := context.WithCancel(context.Background()) 91 | t.procCancel = cancel 92 | go t.process(ctx) 93 | }) 94 | } 95 | 96 | // Close closes the Tunnel and releases its resources. 97 | func (t *Tunnel) Close() { 98 | t.procCancel() 99 | } 100 | 101 | func (t *Tunnel) Dialer() proxy.Dialer { 102 | t.dialerMu.RLock() 103 | d := t.dialer 104 | t.dialerMu.RUnlock() 105 | return d 106 | } 107 | 108 | func (t *Tunnel) SetDialer(dialer proxy.Dialer) { 109 | t.dialerMu.Lock() 110 | t.dialer = dialer 111 | t.dialerMu.Unlock() 112 | } 113 | 114 | func (t *Tunnel) SetUDPTimeout(timeout time.Duration) { 115 | t.udpTimeout.Store(timeout) 116 | } 117 | -------------------------------------------------------------------------------- /tunnel/udp.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync" 7 | "time" 8 | 9 | "github.com/xjasonlyu/tun2socks/v2/buffer" 10 | "github.com/xjasonlyu/tun2socks/v2/core/adapter" 11 | "github.com/xjasonlyu/tun2socks/v2/log" 12 | M "github.com/xjasonlyu/tun2socks/v2/metadata" 13 | "github.com/xjasonlyu/tun2socks/v2/tunnel/statistic" 14 | ) 15 | 16 | // TODO: Port Restricted NAT support. 17 | func (t *Tunnel) handleUDPConn(uc adapter.UDPConn) { 18 | defer uc.Close() 19 | 20 | id := uc.ID() 21 | metadata := &M.Metadata{ 22 | Network: M.UDP, 23 | SrcIP: parseTCPIPAddress(id.RemoteAddress), 24 | SrcPort: id.RemotePort, 25 | DstIP: parseTCPIPAddress(id.LocalAddress), 26 | DstPort: id.LocalPort, 27 | } 28 | 29 | pc, err := t.Dialer().DialUDP(metadata) 30 | if err != nil { 31 | log.Warnf("[UDP] dial %s: %v", metadata.DestinationAddress(), err) 32 | return 33 | } 34 | metadata.MidIP, metadata.MidPort = parseNetAddr(pc.LocalAddr()) 35 | 36 | pc = statistic.NewUDPTracker(pc, metadata, t.manager) 37 | defer pc.Close() 38 | 39 | var remote net.Addr 40 | if udpAddr := metadata.UDPAddr(); udpAddr != nil { 41 | remote = udpAddr 42 | } else { 43 | remote = metadata.Addr() 44 | } 45 | pc = newSymmetricNATPacketConn(pc, metadata) 46 | 47 | log.Infof("[UDP] %s <-> %s", metadata.SourceAddress(), metadata.DestinationAddress()) 48 | pipePacket(uc, pc, remote, t.udpTimeout.Load()) 49 | } 50 | 51 | func pipePacket(origin, remote net.PacketConn, to net.Addr, timeout time.Duration) { 52 | wg := sync.WaitGroup{} 53 | wg.Add(2) 54 | 55 | go unidirectionalPacketStream(remote, origin, to, "origin->remote", &wg, timeout) 56 | go unidirectionalPacketStream(origin, remote, nil, "remote->origin", &wg, timeout) 57 | 58 | wg.Wait() 59 | } 60 | 61 | func unidirectionalPacketStream(dst, src net.PacketConn, to net.Addr, dir string, wg *sync.WaitGroup, timeout time.Duration) { 62 | defer wg.Done() 63 | if err := copyPacketData(dst, src, to, timeout); err != nil { 64 | log.Debugf("[UDP] copy data for %s: %v", dir, err) 65 | } 66 | } 67 | 68 | func copyPacketData(dst, src net.PacketConn, to net.Addr, timeout time.Duration) error { 69 | buf := buffer.Get(buffer.MaxSegmentSize) 70 | defer buffer.Put(buf) 71 | 72 | for { 73 | src.SetReadDeadline(time.Now().Add(timeout)) 74 | n, _, err := src.ReadFrom(buf) 75 | if ne, ok := err.(net.Error); ok && ne.Timeout() { 76 | return nil /* ignore I/O timeout */ 77 | } else if err == io.EOF { 78 | return nil /* ignore EOF */ 79 | } else if err != nil { 80 | return err 81 | } 82 | 83 | if _, err = dst.WriteTo(buf[:n], to); err != nil { 84 | return err 85 | } 86 | dst.SetReadDeadline(time.Now().Add(timeout)) 87 | } 88 | } 89 | 90 | type symmetricNATPacketConn struct { 91 | net.PacketConn 92 | src string 93 | dst string 94 | } 95 | 96 | func newSymmetricNATPacketConn(pc net.PacketConn, metadata *M.Metadata) *symmetricNATPacketConn { 97 | return &symmetricNATPacketConn{ 98 | PacketConn: pc, 99 | src: metadata.SourceAddress(), 100 | dst: metadata.DestinationAddress(), 101 | } 102 | } 103 | 104 | func (pc *symmetricNATPacketConn) ReadFrom(p []byte) (int, net.Addr, error) { 105 | for { 106 | n, from, err := pc.PacketConn.ReadFrom(p) 107 | 108 | if from != nil && from.String() != pc.dst { 109 | log.Warnf("[UDP] symmetric NAT %s->%s: drop packet from %s", pc.src, pc.dst, from) 110 | continue 111 | } 112 | 113 | return n, from, err 114 | } 115 | } 116 | --------------------------------------------------------------------------------