12 Commits
udp ... v1.0.6

Author SHA1 Message Date
pufferfish
b0a405a075 Update wireproxy.yml 2023-05-22 18:05:32 +01:00
Wayback Archiver
25e6568f4d Add support for http proxy (#68)
* Add support for http proxy

* add test case for http proxy

---------

Co-authored-by: octeep <github@bandersnatch.anonaddy.com>
Co-authored-by: pufferfish <74378430+pufferffish@users.noreply.github.com>
2023-05-22 17:47:33 +01:00
pufferfish
d9c6eb7143 add test CI (#69) 2023-05-22 17:03:27 +01:00
Wayback Archiver
30d2697f03 Add silent flag to reduce output (#67) 2023-05-20 23:47:19 +01:00
Wayback Archiver
6fcd53d2a0 Fix tag describe in makefile (#65)
* Fix tag describe in makefile

* Use build directive from makefile
2023-05-09 16:11:37 +01:00
octeep
d898e7a931 Merge pull request #60 from octeep/dependabot/go_modules/golang.org/x/net-0.7.0
Bump golang.org/x/net from 0.0.0-20220225172249-27dd8689420f to 0.7.0
2023-02-25 19:30:25 +00:00
dependabot[bot]
2c327f6f76 Bump golang.org/x/net from 0.0.0-20220225172249-27dd8689420f to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.0.0-20220225172249-27dd8689420f to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-25 19:10:00 +00:00
octeep
9afc0f75ff Merge pull request #59 from octeep/dependabot/go_modules/golang.org/x/sys-0.1.0
Bump golang.org/x/sys from 0.0.0-20220315194320-039c03cc5b86 to 0.1.0
2023-02-25 19:09:32 +00:00
dependabot[bot]
d5ec898e57 Bump golang.org/x/sys from 0.0.0-20220315194320-039c03cc5b86 to 0.1.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20220315194320-039c03cc5b86 to 0.1.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/commits/v0.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-25 11:41:30 +00:00
Wind Wong
62f53faa35 Merge pull request #53 from 0ff/feature/allow-embedding
make VirtualTun fields public
2023-01-05 09:56:21 +08:00
Wind Wong
ae453954ea Update LICENSE 2022-12-31 10:17:01 +00:00
Fabian Off
b18b709f84 make VirtualTun fields public 2022-12-23 14:32:36 +01:00
16 changed files with 362 additions and 230 deletions

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

@@ -0,0 +1,40 @@
name: Test
on:
push:
branches:
- '**'
pull_request:
branches:
- '**'
jobs:
test:
name: Test wireproxy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
- name: Install dependencies
run: sudo apt install wireguard curl
- name: Building wireproxy
run: |
git tag dev
make
- name: Generate test config
run: ./test_config.sh
- name: Start wireproxy
run: ./wireproxy -c test.conf & sleep 1
- name: Test socks5
run: curl --proxy socks5://localhost:64423 http://zx2c4.com/ip | grep -q "demo.wireguard.com"
- name: Test http
run: curl --proxy http://localhost:64424 http://zx2c4.com/ip | grep -q "demo.wireguard.com"
- name: Test http with password
run: curl --proxy http://peter:hunter123@localhost:64424 http://zx2c4.com/ip | grep -q "demo.wireguard.com"
- name: Test http with wrong password
run: |
set +e
curl -s --fail --proxy http://peter:wrongpass@localhost:64425 http://zx2c4.com/ip
if [[ $? == 0 ]]; then exit 1; fi

View File

@@ -24,7 +24,7 @@ jobs:
- name: Git clone WireProxy - name: Git clone WireProxy
run: | run: |
git clone https://github.com/octeep/wireproxy.git ${{ env.workdir }} git clone https://github.com/pufferffish/wireproxy.git ${{ env.workdir }}
cp ./.github/wireproxy-releaser.yml ${{ env.workdir }}/.goreleaser.yml cp ./.github/wireproxy-releaser.yml ${{ env.workdir }}/.goreleaser.yml
- name: Set up GoReleaser - name: Set up GoReleaser

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
*.sw? *.sw?
/.idea /.idea
.goreleaser.yml .goreleaser.yml
*.conf

View File

@@ -4,7 +4,7 @@ FROM golang:1.18 as build
WORKDIR /usr/src/wireproxy WORKDIR /usr/src/wireproxy
COPY . . COPY . .
RUN CGO_ENABLED=0 go build ./cmd/wireproxy RUN make
# Now copy it into our base image. # Now copy it into our base image.
FROM gcr.io/distroless/static-debian11:nonroot FROM gcr.io/distroless/static-debian11:nonroot

View File

@@ -1,12 +1,14 @@
export GO ?= go export GO ?= go
export CGO_ENABLED = 0
TAG := $(shell git describe --always --tags $(git rev-list --tags --max-count=1) --match v*)
.PHONY: all .PHONY: all
all: wireproxy all: wireproxy
.PHONY: wireproxy .PHONY: wireproxy
wireproxy: wireproxy:
tag="$$(git describe --tag 2>/dev/null)" && \ ${GO} build -trimpath -ldflags "-s -w -X 'main.version=${TAG}'" ./cmd/wireproxy
${GO} build -ldflags "-X 'main.version=$$tag'" ./cmd/wireproxy
.PHONY: clean .PHONY: clean
clean: clean:

View File

@@ -3,11 +3,11 @@
[![Build status](https://github.com/octeep/wireproxy/actions/workflows/build.yml/badge.svg)](https://github.com/octeep/wireproxy/actions) [![Build status](https://github.com/octeep/wireproxy/actions/workflows/build.yml/badge.svg)](https://github.com/octeep/wireproxy/actions)
[![Documentation](https://img.shields.io/badge/godoc-wireproxy-blue)](https://pkg.go.dev/github.com/octeep/wireproxy) [![Documentation](https://img.shields.io/badge/godoc-wireproxy-blue)](https://pkg.go.dev/github.com/octeep/wireproxy)
A wireguard client that exposes itself as a socks5 proxy or tunnels. A wireguard client that exposes itself as a socks5/http proxy or tunnels.
# What is this # What is this
`wireproxy` is a completely userspace application that connects to a wireguard peer, `wireproxy` is a completely userspace application that connects to a wireguard peer,
and exposes a socks5 proxy or tunnels on the machine. This can be useful if you need and exposes a socks5/http proxy or tunnels on the machine. This can be useful if you need
to connect to certain sites via a wireguard peer, but can't be bothered to setup a new network to connect to certain sites via a wireguard peer, but can't be bothered to setup a new network
interface for whatever reasons. interface for whatever reasons.
@@ -22,7 +22,7 @@ anything.
# Feature # Feature
- TCP static routing for client and server - TCP static routing for client and server
- SOCKS5 proxy (currently only CONNECT is supported) - SOCKS5/HTTP proxy (currently only CONNECT is supported)
# TODO # TODO
- UDP Support in SOCKS5 - UDP Support in SOCKS5
@@ -34,8 +34,8 @@ anything.
``` ```
``` ```
usage: wireproxy [-h|--help] -c|--config "<value>" [-d|--daemon] usage: wireproxy [-h|--help] [-c|--config "<value>"] [-s|--silent]
[-n|--configtest] [-d|--daemon] [-v|--version] [-n|--configtest]
Userspace wireguard client for proxying Userspace wireguard client for proxying
@@ -43,7 +43,9 @@ Arguments:
-h --help Print help information -h --help Print help information
-c --config Path of configuration file -c --config Path of configuration file
-s --silent Silent mode
-d --daemon Make wireproxy run in background -d --daemon Make wireproxy run in background
-v --version Print version
-n --configtest Configtest mode. Only check the configuration file for -n --configtest Configtest mode. Only check the configuration file for
validity. validity.
``` ```
@@ -98,6 +100,16 @@ BindAddress = 127.0.0.1:25344
#Username = ... #Username = ...
# Avoid using spaces in the password field # Avoid using spaces in the password field
#Password = ... #Password = ...
# http creates a http proxy on your LAN, and all traffic would be routed via wireguard.
[http]
BindAddress = 127.0.0.1:25345
# HTTP authentication parameters, specifying username and password enables
# proxy authentication.
#Username = ...
# Avoid using spaces in the password field
#Password = ...
``` ```
Alternatively, if you already have a wireguard config, you can import it in the Alternatively, if you already have a wireguard config, you can import it in the

View File

@@ -8,6 +8,7 @@ import (
"github.com/akamensky/argparse" "github.com/akamensky/argparse"
"github.com/octeep/wireproxy" "github.com/octeep/wireproxy"
"golang.zx2c4.com/wireguard/device"
"suah.dev/protect" "suah.dev/protect"
) )
@@ -63,6 +64,7 @@ func main() {
parser := argparse.NewParser("wireproxy", "Userspace wireguard client for proxying") parser := argparse.NewParser("wireproxy", "Userspace wireguard client for proxying")
config := parser.String("c", "config", &argparse.Options{Help: "Path of configuration file"}) config := parser.String("c", "config", &argparse.Options{Help: "Path of configuration file"})
silent := parser.Flag("s", "silent", &argparse.Options{Help: "Silent mode"})
daemon := parser.Flag("d", "daemon", &argparse.Options{Help: "Make wireproxy run in background"}) daemon := parser.Flag("d", "daemon", &argparse.Options{Help: "Make wireproxy run in background"})
printVerison := parser.Flag("v", "version", &argparse.Options{Help: "Print version"}) printVerison := parser.Flag("v", "version", &argparse.Options{Help: "Print version"})
configTest := parser.Flag("n", "configtest", &argparse.Options{Help: "Configtest mode. Only check the configuration file for validity."}) configTest := parser.Flag("n", "configtest", &argparse.Options{Help: "Configtest mode. Only check the configuration file for validity."})
@@ -114,10 +116,15 @@ func main() {
return return
} }
logLevel := device.LogLevelVerbose
if *silent {
logLevel = device.LogLevelSilent
}
// no file access is allowed from now on, only networking // no file access is allowed from now on, only networking
pledgeOrPanic("stdio inet dns") pledgeOrPanic("stdio inet dns")
tnet, err := wireproxy.StartWireguard(conf.Device) tnet, err := wireproxy.StartWireguard(conf.Device, logLevel)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@@ -45,6 +45,12 @@ type Socks5Config struct {
Password string Password string
} }
type HTTPConfig struct {
BindAddress string
Username string
Password string
}
type Configuration struct { type Configuration struct {
Device *DeviceConfig Device *DeviceConfig
Routines []RoutineSpawner Routines []RoutineSpawner
@@ -330,6 +336,24 @@ func parseSocks5Config(section *ini.Section) (RoutineSpawner, error) {
return config, nil return config, nil
} }
func parseHTTPConfig(section *ini.Section) (RoutineSpawner, error) {
config := &HTTPConfig{}
bindAddress, err := parseString(section, "BindAddress")
if err != nil {
return nil, err
}
config.BindAddress = bindAddress
username, _ := parseString(section, "Username")
config.Username = username
password, _ := parseString(section, "Password")
config.Password = password
return config, nil
}
// Takes a function that parses an individual section into a config, and apply it on all // Takes a function that parses an individual section into a config, and apply it on all
// specified sections // specified sections
func parseRoutinesConfig(routines *[]RoutineSpawner, cfg *ini.File, sectionName string, f func(*ini.Section) (RoutineSpawner, error)) error { func parseRoutinesConfig(routines *[]RoutineSpawner, cfg *ini.File, sectionName string, f func(*ini.Section) (RoutineSpawner, error)) error {
@@ -404,6 +428,11 @@ func ParseConfig(path string) (*Configuration, error) {
return nil, err return nil, err
} }
err = parseRoutinesConfig(&routinesSpawners, cfg, "http", parseHTTPConfig)
if err != nil {
return nil, err
}
return &Configuration{ return &Configuration{
Device: device, Device: device,
Routines: routinesSpawners, Routines: routinesSpawners,

9
go.mod
View File

@@ -5,21 +5,18 @@ go 1.18
require ( require (
github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/akamensky/argparse v1.3.1 github.com/akamensky/argparse v1.3.1
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/go-ini/ini v1.66.4 github.com/go-ini/ini v1.66.4
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b
suah.dev/protect v1.2.0 suah.dev/protect v1.2.0
) )
require ( require (
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/stretchr/testify v1.8.0 // indirect github.com/stretchr/testify v1.8.0 // indirect
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf // indirect
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect

18
go.sum
View File

@@ -2,6 +2,8 @@ github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZ
github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g= github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g=
github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -9,8 +11,6 @@ github.com/go-ini/ini v1.66.4 h1:dKjMqkcbkzfddhIhyglTPgMoJnkvmG+bSLrU9cTHc5M=
github.com/go-ini/ini v1.66.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.66.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -18,19 +18,13 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6 h1:8DkPbOq/EPxbD5VJajKuvssiYZJSrlpeetcGfrBoBVE=
github.com/txthinking/socks5 v0.0.0-20220615051428-39268faee3e6/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=

156
http.go Normal file
View File

@@ -0,0 +1,156 @@
package wireproxy
import (
"bufio"
"bytes"
"encoding/base64"
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
)
const proxyAuthHeaderKey = "Proxy-Authorization"
type HTTPServer struct {
config *HTTPConfig
auth CredentialValidator
dial func(network, address string) (net.Conn, error)
authRequired bool
}
func (s *HTTPServer) authenticate(req *http.Request) (int, error) {
if !s.authRequired {
return 0, nil
}
auth := req.Header.Get(proxyAuthHeaderKey)
if auth != "" {
enc := strings.TrimPrefix(auth, "Basic ")
str, err := base64.StdEncoding.DecodeString(enc)
if err != nil {
return http.StatusNotAcceptable, fmt.Errorf("decode username and password failed: %w", err)
}
pairs := bytes.SplitN(str, []byte(":"), 2)
if len(pairs) != 2 {
return http.StatusLengthRequired, fmt.Errorf("username and password format invalid")
}
if s.auth.Valid(string(pairs[0]), string(pairs[1])) {
return 0, nil
}
return http.StatusUnauthorized, fmt.Errorf("username and password not matching")
}
return http.StatusProxyAuthRequired, fmt.Errorf(http.StatusText(http.StatusProxyAuthRequired))
}
func (s *HTTPServer) handleConn(req *http.Request, conn net.Conn) (peer net.Conn, err error) {
addr := req.Host
if !strings.Contains(addr, ":") {
port := "443"
addr = net.JoinHostPort(addr, port)
}
peer, err = s.dial("tcp", addr)
if err != nil {
return peer, fmt.Errorf("tun tcp dial failed: %w", err)
}
_, err = conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if err != nil {
peer.Close()
peer = nil
}
return
}
func (s *HTTPServer) handle(req *http.Request) (peer net.Conn, err error) {
addr := req.Host
if !strings.Contains(addr, ":") {
port := "80"
addr = net.JoinHostPort(addr, port)
}
peer, err = s.dial("tcp", addr)
if err != nil {
return peer, fmt.Errorf("tun tcp dial failed: %w", err)
}
err = req.Write(peer)
if err != nil {
peer.Close()
peer = nil
return peer, fmt.Errorf("conn write failed: %w", err)
}
return
}
func (s *HTTPServer) serve(conn net.Conn) error {
defer conn.Close()
var rd io.Reader = bufio.NewReader(conn)
req, err := http.ReadRequest(rd.(*bufio.Reader))
if err != nil {
return fmt.Errorf("read request failed: %w", err)
}
code, err := s.authenticate(req)
if err != nil {
_ = responseWith(req, code).Write(conn)
return err
}
var peer net.Conn
switch req.Method {
case http.MethodConnect:
peer, err = s.handleConn(req, conn)
case http.MethodGet:
peer, err = s.handle(req)
default:
_ = responseWith(req, http.StatusMethodNotAllowed).Write(conn)
return fmt.Errorf("unsupported protocol: %s", req.Method)
}
if err != nil {
return fmt.Errorf("dial proxy failed: %w", err)
}
if peer == nil {
return fmt.Errorf("dial proxy failed: peer nil")
}
defer peer.Close()
go func() {
defer peer.Close()
defer conn.Close()
_, _ = io.Copy(conn, peer)
}()
_, err = io.Copy(peer, conn)
return err
}
// ListenAndServe is used to create a listener and serve on it
func (s *HTTPServer) ListenAndServe(network, addr string) error {
server, err := net.Listen("tcp", s.config.BindAddress)
if err != nil {
return fmt.Errorf("listen tcp failed: %w", err)
}
for {
conn, err := server.Accept()
if err != nil {
return fmt.Errorf("accept request failed: %w", err)
}
go func(conn net.Conn) {
err = s.serve(conn)
if err != nil {
log.Println(err)
}
}(conn)
}
}

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"crypto/subtle" "crypto/subtle"
"errors" "errors"
"github.com/txthinking/socks5"
"io" "io"
"log" "log"
"math/rand" "math/rand"
@@ -12,6 +11,9 @@ import (
"os" "os"
"strconv" "strconv"
"github.com/armon/go-socks5"
"golang.zx2c4.com/wireguard/tun/netstack"
"net/netip" "net/netip"
) )
@@ -24,6 +26,12 @@ type CredentialValidator struct {
password string password string
} }
// VirtualTun stores a reference to netstack network and DNS configuration
type VirtualTun struct {
Tnet *netstack.Net
SystemDNS bool
}
// RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed // RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed
type RoutineSpawner interface { type RoutineSpawner interface {
SpawnRoutine(vt *VirtualTun) SpawnRoutine(vt *VirtualTun)
@@ -37,10 +45,10 @@ type addressPort struct {
// LookupAddr lookups a hostname. // LookupAddr lookups a hostname.
// DNS traffic may or may not be routed depending on VirtualTun's setting // DNS traffic may or may not be routed depending on VirtualTun's setting
func (d VirtualTun) LookupAddr(ctx context.Context, name string) ([]string, error) { func (d VirtualTun) LookupAddr(ctx context.Context, name string) ([]string, error) {
if d.systemDNS { if d.SystemDNS {
return net.DefaultResolver.LookupHost(ctx, name) return net.DefaultResolver.LookupHost(ctx, name)
} else { } else {
return d.tnet.LookupContextHost(ctx, name) return d.Tnet.LookupContextHost(ctx, name)
} }
} }
@@ -113,12 +121,34 @@ func (d VirtualTun) resolveToAddrPort(endpoint *addressPort) (*netip.AddrPort, e
// SpawnRoutine spawns a socks5 server. // SpawnRoutine spawns a socks5 server.
func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) { func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) {
server, err := socks5.NewClassicServer(config.BindAddress, "0.0.0.0", config.Username, config.Password, 15, 15) conf := &socks5.Config{Dial: vt.Tnet.DialContext, Resolver: vt}
if username := config.Username; username != "" {
validator := CredentialValidator{username: username}
validator.password = config.Password
conf.Credentials = validator
}
server, err := socks5.New(conf)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
err = server.ListenAndServe(vt)
if err != nil { if err := server.ListenAndServe("tcp", config.BindAddress); err != nil {
log.Fatal(err)
}
}
// SpawnRoutine spawns a http server.
func (config *HTTPConfig) SpawnRoutine(vt *VirtualTun) {
http := &HTTPServer{
config: config,
dial: vt.Tnet.Dial,
auth: CredentialValidator{config.Username, config.Password},
}
if config.Username != "" || config.Password != "" {
http.authRequired = true
}
if err := http.ListenAndServe("tcp", config.BindAddress); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
@@ -152,7 +182,7 @@ func tcpClientForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
tcpAddr := TCPAddrFromAddrPort(*target) tcpAddr := TCPAddrFromAddrPort(*target)
sconn, err := vt.tnet.DialTCP(tcpAddr) sconn, err := vt.Tnet.DialTCP(tcpAddr)
if err != nil { if err != nil {
errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error()) errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error())
return return
@@ -211,7 +241,7 @@ func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
} }
addr := &net.TCPAddr{Port: conf.ListenPort} addr := &net.TCPAddr{Port: conf.ListenPort}
server, err := vt.tnet.ListenTCP(addr) server, err := vt.Tnet.ListenTCP(addr)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

28
test_config.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -e
exec 3<>/dev/tcp/demo.wireguard.com/42912
privatekey="$(wg genkey)"
wg pubkey <<<"$privatekey" >&3
IFS=: read -r status server_pubkey server_port internal_ip <&3
[[ $status == OK ]]
cat >test.conf <<EOL
[Interface]
Address = $internal_ip/32
PrivateKey = $privatekey
DNS = 8.8.8.8
[Peer]
PublicKey = $server_pubkey
Endpoint = demo.wireguard.com:$server_port
[Socks5]
BindAddress = 127.0.0.1:64423
[http]
BindAddress = 127.0.0.1:64424
[http]
BindAddress = 127.0.0.1:64425
Username = peter
Password = hunter123
EOL

179
tunnel.go
View File

@@ -1,179 +0,0 @@
package wireproxy
import (
"fmt"
"github.com/patrickmn/go-cache"
"github.com/txthinking/socks5"
"golang.zx2c4.com/wireguard/tun/netstack"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"io"
"log"
"net"
"time"
)
// VirtualTun stores a reference to netstack network and DNS configuration
type VirtualTun struct {
tnet *netstack.Net
systemDNS bool
mappedPortToNatEntry map[uint16]string
natEntryToMappedPort *cache.Cache
}
type NatEntry struct {
key string
srcAddr net.Addr
mappedPort uint16
conn *gonet.UDPConn
}
func (d *VirtualTun) connect(w io.Writer, r *socks5.Request) (net.Conn, error) {
if socks5.Debug {
log.Println("Call:", r.Address())
}
tmp, err := d.tnet.Dial("tcp", r.Address())
if err != nil {
var p *socks5.Reply
if r.Atyp == socks5.ATYPIPv4 || r.Atyp == socks5.ATYPDomain {
p = socks5.NewReply(socks5.RepHostUnreachable, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
} else {
p = socks5.NewReply(socks5.RepHostUnreachable, socks5.ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00})
}
if _, err := p.WriteTo(w); err != nil {
return nil, err
}
return nil, err
}
a, addr, port, err := socks5.ParseAddress(tmp.LocalAddr().String())
if err != nil {
var p *socks5.Reply
if r.Atyp == socks5.ATYPIPv4 || r.Atyp == socks5.ATYPDomain {
p = socks5.NewReply(socks5.RepHostUnreachable, socks5.ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
} else {
p = socks5.NewReply(socks5.RepHostUnreachable, socks5.ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00})
}
if _, err := p.WriteTo(w); err != nil {
return nil, err
}
return nil, err
}
p := socks5.NewReply(socks5.RepSuccess, a, addr, port)
if _, err := p.WriteTo(w); err != nil {
return nil, err
}
return tmp, nil
}
func (d *VirtualTun) TCPHandle(s *socks5.Server, c *net.TCPConn, r *socks5.Request) error {
if r.Cmd == socks5.CmdConnect {
rc, err := d.connect(c, r)
if err != nil {
return err
}
defer rc.Close()
go func() {
var bf [1024 * 2]byte
for {
if s.TCPTimeout != 0 {
if err := rc.SetDeadline(time.Now().Add(time.Duration(s.TCPTimeout) * time.Second)); err != nil {
return
}
}
i, err := rc.Read(bf[:])
if err != nil {
return
}
if _, err := c.Write(bf[0:i]); err != nil {
return
}
}
}()
var bf [1024 * 2]byte
for {
if s.TCPTimeout != 0 {
if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPTimeout) * time.Second)); err != nil {
return nil
}
}
i, err := c.Read(bf[:])
if err != nil {
return nil
}
if _, err := rc.Write(bf[0:i]); err != nil {
return nil
}
}
}
if r.Cmd == socks5.CmdUDP {
caddr, err := r.UDP(c, s.ServerAddr)
if err != nil {
return err
}
srcAddr := caddr.String()
mappedPort := uint16(caddr.Port)
tries := 0
for _, occupied := d.mappedPortToNatEntry[mappedPort]; occupied; mappedPort++ {
tries++
if tries > 65535 {
return fmt.Errorf("nat table is full")
}
}
laddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", mappedPort))
if err != nil {
return err
}
conn, err := d.tnet.ListenUDP(laddr)
if err != nil {
fmt.Println("fic")
return err
}
entry := &NatEntry{
key: srcAddr,
srcAddr: caddr,
conn: conn,
mappedPort: mappedPort,
}
d.mappedPortToNatEntry[mappedPort] = srcAddr
d.natEntryToMappedPort.Set(srcAddr, entry, 0)
go func() {
buf := make([]byte, 65536)
for n, from, err := conn.ReadFrom(buf); err == nil; n, from, err = conn.ReadFrom(buf) {
a, addr, port, err := socks5.ParseAddress(from.String())
if err != nil {
log.Println(err)
break
}
d.natEntryToMappedPort.Set(srcAddr, entry, 0)
d1 := socks5.NewDatagram(a, addr, port, buf[0:n])
if _, err := s.UDPConn.WriteToUDP(d1.Bytes(), caddr); err != nil {
log.Println(err)
break
}
}
_ = conn.Close()
d.natEntryToMappedPort.Delete(srcAddr)
}()
fmt.Printf("%s udp mapped to port %d\n", srcAddr, mappedPort)
return nil
}
return socks5.ErrUnsupportCmd
}
func (d *VirtualTun) UDPHandle(server *socks5.Server, addr *net.UDPAddr, datagram *socks5.Datagram) error {
srcAddr := addr.String()
entry, ok := d.natEntryToMappedPort.Get(srcAddr)
if !ok {
return fmt.Errorf("this udp address %s is not associated", srcAddr)
}
natEntry := entry.(*NatEntry)
// refresh timeout
d.natEntryToMappedPort.Set(srcAddr, entry, 0)
raddr, err := net.ResolveUDPAddr("udp", datagram.Address())
if err != nil {
return err
}
_, err = natEntry.conn.WriteTo(datagram.Data, raddr)
return err
}

25
util.go Normal file
View File

@@ -0,0 +1,25 @@
package wireproxy
import (
"bytes"
"io"
"net/http"
"strconv"
)
const space = " "
func responseWith(req *http.Request, statusCode int) *http.Response {
statusText := http.StatusText(statusCode)
body := "wireproxy:" + space + req.Proto + space + strconv.Itoa(statusCode) + space + statusText + "\r\n"
return &http.Response{
StatusCode: statusCode,
Status: statusText,
Proto: req.Proto,
ProtoMajor: req.ProtoMajor,
ProtoMinor: req.ProtoMinor,
Header: http.Header{},
Body: io.NopCloser(bytes.NewBufferString(body)),
}
}

View File

@@ -3,9 +3,8 @@ package wireproxy
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/patrickmn/go-cache"
"net/netip" "net/netip"
"time"
"github.com/MakeNowJust/heredoc/v2" "github.com/MakeNowJust/heredoc/v2"
"golang.zx2c4.com/wireguard/conn" "golang.zx2c4.com/wireguard/conn"
@@ -54,7 +53,7 @@ func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
} }
// StartWireguard creates a tun interface on netstack given a configuration // StartWireguard creates a tun interface on netstack given a configuration
func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) { func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
setting, err := createIPCRequest(conf) setting, err := createIPCRequest(conf)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -64,7 +63,7 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, "")) dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, ""))
err = dev.IpcSet(setting.ipcRequest) err = dev.IpcSet(setting.ipcRequest)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -75,17 +74,8 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
return nil, err return nil, err
} }
natTable := cache.New(30*time.Second, time.Minute)
natTableRev := make(map[uint16]string)
natTable.OnEvicted(func(srcAddr string, i interface{}) {
entry := i.(*NatEntry)
_ = entry.conn.Close()
delete(natTableRev, entry.mappedPort)
})
return &VirtualTun{ return &VirtualTun{
tnet: tnet, Tnet: tnet,
systemDNS: len(setting.dns) == 0, SystemDNS: len(setting.dns) == 0,
mappedPortToNatEntry: natTableRev,
natEntryToMappedPort: natTable,
}, nil }, nil
} }