Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6797166eb | ||
|
|
eccf83a0cf | ||
|
|
54cedea2e4 | ||
|
|
bbde9cd266 | ||
|
|
4f066d050a | ||
|
|
c710def46d | ||
|
|
a2d7aecb6f | ||
|
|
49f568810c | ||
|
|
af973227e9 | ||
|
|
0f4d79dea8 | ||
|
|
d1a455e87d | ||
|
|
5f76f777d5 | ||
|
|
d2f7768c51 | ||
|
|
05c8493981 | ||
|
|
31e6afd75d | ||
|
|
aa207764b2 | ||
|
|
edf63253df | ||
|
|
b0a405a075 | ||
|
|
25e6568f4d | ||
|
|
d9c6eb7143 | ||
|
|
30d2697f03 | ||
|
|
6fcd53d2a0 | ||
|
|
d898e7a931 | ||
|
|
2c327f6f76 | ||
|
|
9afc0f75ff | ||
|
|
d5ec898e57 | ||
|
|
62f53faa35 | ||
|
|
ae453954ea | ||
|
|
b18b709f84 |
57
.github/workflows/build.yml
vendored
57
.github/workflows/build.yml
vendored
@@ -6,17 +6,18 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
windowsAmd64Build:
|
||||
name: Build Windows amd64 Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Windows amd64 Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o WireProxy_amd64.exe -v ./cmd/wireproxy
|
||||
@@ -24,7 +25,7 @@ jobs:
|
||||
mv WireProxy_amd64.exe wireproxy.exe
|
||||
cp wireproxy.exe release_windows_amd64/wireproxy.exe
|
||||
- name: Upload Windows amd64 Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_windows_amd64
|
||||
path: release_windows_amd64
|
||||
@@ -32,11 +33,11 @@ jobs:
|
||||
name: Build Windows arm64 Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Windows arm64 Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -o WireProxy_arm64.exe -v ./cmd/wireproxy
|
||||
@@ -44,7 +45,7 @@ jobs:
|
||||
mv WireProxy_arm64.exe wireproxy.exe
|
||||
cp wireproxy.exe release_windows_arm64/wireproxy.exe
|
||||
- name: Upload Windows arm64 Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_windows_arm64
|
||||
path: release_windows_arm64
|
||||
@@ -52,11 +53,11 @@ jobs:
|
||||
name: Build Linux amd64 Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Linux amd64 Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o WireProxy_amd64 -v ./cmd/wireproxy
|
||||
@@ -64,7 +65,7 @@ jobs:
|
||||
mv WireProxy_amd64 wireproxy
|
||||
cp wireproxy release_linux_amd64/wireproxy
|
||||
- name: Upload Linux amd64 Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_linux_amd64
|
||||
path: release_linux_amd64
|
||||
@@ -72,11 +73,11 @@ jobs:
|
||||
name: Build Linux arm64 Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Linux arm64 Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o WireProxy_arm64 -v ./cmd/wireproxy
|
||||
@@ -84,7 +85,7 @@ jobs:
|
||||
mv WireProxy_arm64 wireproxy
|
||||
cp wireproxy release_linux_arm64/wireproxy
|
||||
- name: Upload Linux arm64 Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_linux_arm64
|
||||
path: release_linux_arm64
|
||||
@@ -92,11 +93,11 @@ jobs:
|
||||
name: Build Linux s390x Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Linux s390x Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -o WireProxy_s390x -v ./cmd/wireproxy
|
||||
@@ -104,7 +105,7 @@ jobs:
|
||||
mv WireProxy_s390x wireproxy
|
||||
cp wireproxy release_linux_s390x/wireproxy
|
||||
- name: Upload Linux s390x Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_linux_s390x
|
||||
path: release_linux_s390x
|
||||
@@ -112,11 +113,11 @@ jobs:
|
||||
name: Build Darwin amd64 Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Darwin amd64 Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o WireProxy_amd64 -v ./cmd/wireproxy
|
||||
@@ -124,7 +125,7 @@ jobs:
|
||||
mv WireProxy_amd64 wireproxy
|
||||
cp wireproxy release_darwin_amd64/wireproxy
|
||||
- name: Upload Darwin amd64 Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_darwin_amd64
|
||||
path: release_darwin_amd64
|
||||
@@ -132,11 +133,11 @@ jobs:
|
||||
name: Build Darwin arm64 Version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.21"
|
||||
- name: Building Darwin arm64 Version
|
||||
run: |
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o WireProxy_arm64 -v ./cmd/wireproxy
|
||||
@@ -144,7 +145,7 @@ jobs:
|
||||
mv WireProxy_arm64 wireproxy
|
||||
cp wireproxy release_darwin_arm64/wireproxy
|
||||
- name: Upload Darwin arm64 Version
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: WireProxy_darwin_arm64
|
||||
path: release_darwin_arm64
|
||||
|
||||
10
.github/workflows/container.yml
vendored
10
.github/workflows/container.yml
vendored
@@ -28,26 +28,26 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2.0.0
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Needed for buildx gha cache to work
|
||||
- name: Expose GitHub Runtime
|
||||
uses: crazy-max/ghaction-github-runtime@v2
|
||||
uses: crazy-max/ghaction-github-runtime@v3
|
||||
|
||||
- name: Build container
|
||||
env:
|
||||
|
||||
12
.github/workflows/golangci-lint.yml
vendored
12
.github/workflows/golangci-lint.yml
vendored
@@ -6,6 +6,8 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
@@ -13,9 +15,11 @@ jobs:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.19'
|
||||
- uses: actions/checkout@v3
|
||||
go-version: '1.21'
|
||||
- uses: actions/checkout@v4
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
version: latest
|
||||
41
.github/workflows/test.yml
vendored
Normal file
41
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test wireproxy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setting up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- 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
|
||||
10
.github/workflows/wireproxy.yml
vendored
10
.github/workflows/wireproxy.yml
vendored
@@ -24,21 +24,21 @@ jobs:
|
||||
|
||||
- name: Git clone WireProxy
|
||||
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
|
||||
|
||||
- name: Set up GoReleaser
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.19"
|
||||
go-version: "1.21"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
distribution: goreleaser
|
||||
workdir: ${{ env.workdir }}
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
||||
*.sw?
|
||||
/.idea
|
||||
.goreleaser.yml
|
||||
*.conf
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,10 +1,10 @@
|
||||
# Start by building the application.
|
||||
FROM golang:1.18 as build
|
||||
FROM docker.io/golang:1.21 as build
|
||||
|
||||
WORKDIR /usr/src/wireproxy
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build ./cmd/wireproxy
|
||||
RUN make
|
||||
|
||||
# Now copy it into our base image.
|
||||
FROM gcr.io/distroless/static-debian11:nonroot
|
||||
@@ -14,6 +14,6 @@ VOLUME [ "/etc/wireproxy"]
|
||||
ENTRYPOINT [ "/usr/bin/wireproxy" ]
|
||||
CMD [ "--config", "/etc/wireproxy/config" ]
|
||||
|
||||
LABEL org.opencontainers.image.title wireproxy
|
||||
LABEL org.opencontainers.image.description "Wireguard client that exposes itself as a socks5 proxy"
|
||||
LABEL org.opencontainers.image.licenses ISC
|
||||
LABEL org.opencontainers.image.title="wireproxy"
|
||||
LABEL org.opencontainers.image.description="Wireguard client that exposes itself as a socks5 proxy"
|
||||
LABEL org.opencontainers.image.licenses="ISC"
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2023 octeep <github@bandersnatch.anonaddy.com>
|
||||
Copyright (c) 2024 Wind Wong <me@windtfw.com>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,12 +1,14 @@
|
||||
export GO ?= go
|
||||
export CGO_ENABLED = 0
|
||||
|
||||
TAG := $(shell git describe --always --tags $(git rev-list --tags --max-count=1) --match v*)
|
||||
|
||||
.PHONY: all
|
||||
all: wireproxy
|
||||
|
||||
.PHONY: wireproxy
|
||||
wireproxy:
|
||||
tag="$$(git describe --tag 2>/dev/null)" && \
|
||||
${GO} build -ldflags "-X 'main.version=$$tag'" ./cmd/wireproxy
|
||||
${GO} build -trimpath -ldflags "-s -w -X 'main.version=${TAG}'" ./cmd/wireproxy
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
||||
110
README.md
110
README.md
@@ -3,11 +3,11 @@
|
||||
[](https://github.com/octeep/wireproxy/actions)
|
||||
[](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
|
||||
`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
|
||||
interface for whatever reasons.
|
||||
|
||||
@@ -20,9 +20,12 @@ and configured my browser to use wireproxy for certain sites. It's pretty useful
|
||||
wireproxy is completely isolated from my network interfaces, and I don't need root to configure
|
||||
anything.
|
||||
|
||||
Users who want something similar but for Amnezia VPN can use [this fork](https://github.com/juev/wireproxy/tree/feature/amnezia-go)
|
||||
of wireproxy by [@juev](https://github.com/juev).
|
||||
|
||||
# Feature
|
||||
- TCP static routing for client and server
|
||||
- SOCKS5 proxy (currently only CONNECT is supported)
|
||||
- SOCKS5/HTTP proxy (currently only CONNECT is supported)
|
||||
|
||||
# TODO
|
||||
- UDP Support in SOCKS5
|
||||
@@ -34,7 +37,8 @@ anything.
|
||||
```
|
||||
|
||||
```
|
||||
usage: wireproxy [-h|--help] -c|--config "<value>" [-d|--daemon]
|
||||
usage: wireproxy [-h|--help] [-c|--config "<value>"] [-s|--silent]
|
||||
[-d|--daemon] [-i|--info "<value>"] [-v|--version]
|
||||
[-n|--configtest]
|
||||
|
||||
Userspace wireguard client for proxying
|
||||
@@ -43,9 +47,13 @@ Arguments:
|
||||
|
||||
-h --help Print help information
|
||||
-c --config Path of configuration file
|
||||
-s --silent Silent mode
|
||||
-d --daemon Make wireproxy run in background
|
||||
-i --info Specify the address and port for exposing health status
|
||||
-v --version Print version
|
||||
-n --configtest Configtest mode. Only check the configuration file for
|
||||
validity.
|
||||
|
||||
```
|
||||
|
||||
# Build instruction
|
||||
@@ -55,6 +63,9 @@ cd wireproxy
|
||||
make
|
||||
```
|
||||
|
||||
# Use with VPN
|
||||
Instructions for using wireproxy with Firefox container tabs and auto-start on MacOS can be found [here](/UseWithVPN.md).
|
||||
|
||||
# Sample config file
|
||||
```
|
||||
# The [Interface] and [Peer] configurations follow the same semantics and meaning
|
||||
@@ -89,6 +100,16 @@ Target = play.cubecraft.net:25565
|
||||
ListenPort = 3422
|
||||
Target = localhost:25545
|
||||
|
||||
# STDIOTunnel is a tunnel connecting the standard input and output of the wireproxy
|
||||
# process to the specified TCP target via wireguard.
|
||||
# This is especially useful to use wireproxy as a ProxyCommand parameter in openssh
|
||||
# For example:
|
||||
# ssh -o ProxyCommand='wireproxy -c myconfig.conf' ssh.myserver.net
|
||||
# Flow:
|
||||
# Piped command -->(wireguard)--> ssh.myserver.net:22
|
||||
[STDIOTunnel]
|
||||
Target = ssh.myserver.net:22
|
||||
|
||||
# Socks5 creates a socks5 proxy on your LAN, and all traffic would be routed via wireguard.
|
||||
[Socks5]
|
||||
BindAddress = 127.0.0.1:25344
|
||||
@@ -98,6 +119,16 @@ BindAddress = 127.0.0.1:25344
|
||||
#Username = ...
|
||||
# Avoid using spaces in the password field
|
||||
#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
|
||||
@@ -149,10 +180,75 @@ ListenPort = 5080
|
||||
Target = service-three.servicenet:80
|
||||
```
|
||||
|
||||
## Donation
|
||||
<noscript><a href="https://liberapay.com/octeep/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
|
||||
Wireproxy can also allow peers to connect to it:
|
||||
```
|
||||
[Interface]
|
||||
ListenPort = 5400
|
||||
...
|
||||
|
||||
[Peer]
|
||||
PublicKey = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY=
|
||||
AllowedIPs = 10.254.254.100/32
|
||||
# Note there is no Endpoint defined here.
|
||||
```
|
||||
# Health endpoint
|
||||
Wireproxy supports exposing a health endpoint for monitoring purposes.
|
||||
The argument `--info/-i` specifies an address and port (e.g. `localhost:9080`), which exposes a HTTP server that provides health status metric of the server.
|
||||
|
||||
## Stargazers over time
|
||||
Currently two endpoints are implemented:
|
||||
|
||||
`/metrics`: Exposes information of the wireguard daemon, this provides the same information you would get with `wg show`. [This](https://www.wireguard.com/xplatform/#example-dialog) shows an example of what the response would look like.
|
||||
|
||||
`/readyz`: This responds with a json which shows the last time a pong is received from an IP specified with `CheckAlive`. When `CheckAlive` is set, a ping is sent out to addresses in `CheckAlive` per `CheckAliveInterval` seconds (defaults to 5) via wireguard. If a pong has not been received from one of the addresses within the last `CheckAliveInterval` seconds (+2 seconds for some leeway to account for latency), then it would respond with a 503, otherwise a 200.
|
||||
|
||||
For example:
|
||||
```
|
||||
[Interface]
|
||||
PrivateKey = censored
|
||||
Address = 10.2.0.2/32
|
||||
DNS = 10.2.0.1
|
||||
CheckAlive = 1.1.1.1, 3.3.3.3
|
||||
CheckAliveInterval = 3
|
||||
|
||||
[Peer]
|
||||
PublicKey = censored
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = 149.34.244.174:51820
|
||||
|
||||
[Socks5]
|
||||
BindAddress = 127.0.0.1:25344
|
||||
```
|
||||
`/readyz` would respond with
|
||||
```
|
||||
< HTTP/1.1 503 Service Unavailable
|
||||
< Date: Thu, 11 Apr 2024 00:54:59 GMT
|
||||
< Content-Length: 35
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
{"1.1.1.1":1712796899,"3.3.3.3":0}
|
||||
```
|
||||
|
||||
And for:
|
||||
```
|
||||
[Interface]
|
||||
PrivateKey = censored
|
||||
Address = 10.2.0.2/32
|
||||
DNS = 10.2.0.1
|
||||
CheckAlive = 1.1.1.1
|
||||
```
|
||||
`/readyz` would respond with
|
||||
```
|
||||
< HTTP/1.1 200 OK
|
||||
< Date: Thu, 11 Apr 2024 00:56:21 GMT
|
||||
< Content-Length: 23
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
{"1.1.1.1":1712796979}
|
||||
```
|
||||
|
||||
If nothing is set for `CheckAlive`, an empty JSON object with 200 will be the response.
|
||||
|
||||
The peer which the ICMP ping packet is routed to depends on the `AllowedIPs` set for each peers.
|
||||
|
||||
# Stargazers over time
|
||||
[](https://starchart.cc/octeep/wireproxy)
|
||||
|
||||
89
UseWithVPN.md
Normal file
89
UseWithVPN.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Getting a Wireguard Server
|
||||
You can create your own wireguard server using a host service like DigitalOcean,
|
||||
or you can get a VPN service that provides WireGuard configs.
|
||||
|
||||
I recommend ProtonVPN, because it is highly secure and has a great WireGuard
|
||||
config generator.
|
||||
|
||||
Simply go to https://account.protonvpn.com/downloads and scroll down to the
|
||||
wireguard section to generate your configs, then paste into the appropriate
|
||||
section below.
|
||||
|
||||
# Simple Setup for multiple SOCKS configs for firefox
|
||||
|
||||
Create a folder for your configs and startup scripts. Can be the same place as
|
||||
this code. That path you will use below. For reference this text uses
|
||||
`/Users/jonny/vpntabs`
|
||||
|
||||
For each VPN you want to run, you will download your wireguard config and name
|
||||
it appropriately (e.g. `ProtonUS.adblock.server.conf`) and then create two new
|
||||
files from those below with similar names (e.g. `ProtonUS.adblock.conf` and
|
||||
`ProtonUS.adblock.sh`)
|
||||
|
||||
You will also create a launch script, the reference below is only for macOS. The
|
||||
naming should also be similar (e.g.
|
||||
`/Users/jonny/Library/LaunchAgents/com.ProtonUS.adblock.plist`)
|
||||
|
||||
## Config File
|
||||
Make sure you use a unique port for every separate server
|
||||
I recommend you set proxy authentication, you can use the same user/pass for all
|
||||
```
|
||||
# Link to the Downloaded config
|
||||
WGConfig = /Users/jonny/vpntabs/ProtonUS.adblock.server.conf
|
||||
|
||||
# Used for firefox containers
|
||||
[Socks5]
|
||||
BindAddress = 127.0.0.1:25344 # Update the port here for each new server
|
||||
|
||||
# Socks5 authentication parameters, specifying username and password enables
|
||||
# proxy authentication.
|
||||
#Username = ...
|
||||
# Avoid using spaces in the password field
|
||||
#Password = ...
|
||||
```
|
||||
|
||||
## Startup Script File
|
||||
This is a bash script to facilitate startup, not strictly essential, but adds
|
||||
ease.
|
||||
Note, you MUST update the first path to wherever you installed this code to.
|
||||
Make sure you use the path for the config file above, not the one you downloaded
|
||||
from e.g. protonvpn.
|
||||
```
|
||||
#!/bin/bash
|
||||
/Users/jonny/wireproxy/wireproxy -c /Users/jonny/vpntabs/ProtonUS.adblock.conf
|
||||
```
|
||||
|
||||
## MacOS LaunchAgent
|
||||
To make it run every time you start your computer, you can create a launch agent
|
||||
in `$HOME/Library/LaunchAgents`. Name reference above.
|
||||
|
||||
That file should contain the following, the label should be the same as the file
|
||||
name and the paths should be set correctly:
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.ProtonUS.adblock</string>
|
||||
<key>Program</key>
|
||||
<string>/Users/jonny/vpntabs/ProtonUS.adblock.sh</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
To enable it, run
|
||||
`launchctl load ~/Library/LaunchAgents/com.ProtonUS.adblock.plist` and
|
||||
`launchtl start ~/Library/LaunchAgents/com.PortonUS.adblock.plist`
|
||||
|
||||
# Firefox Setup
|
||||
You will need to enable the Multi Account Container Tabs extension and a proxy extension, I
|
||||
recommend Sideberry, but Container Proxy also works.
|
||||
|
||||
Create a container to be dedicated to this VPN, and then add the IP, port,
|
||||
username, and password from above.
|
||||
@@ -1,37 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/landlock-lsm/go-landlock/landlock"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
"github.com/octeep/wireproxy"
|
||||
"github.com/pufferffish/wireproxy"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"suah.dev/protect"
|
||||
)
|
||||
|
||||
// an argument to denote that this process was spawned by -d
|
||||
const daemonProcess = "daemon-process"
|
||||
|
||||
var version = "1.0.5-dev"
|
||||
var version = "1.0.8-dev"
|
||||
|
||||
func panicIfError(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// attempts to pledge and panic if it fails
|
||||
// this does nothing on non-OpenBSD systems
|
||||
func pledgeOrPanic(promises string) {
|
||||
err := protect.Pledge(promises)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
panicIfError(protect.Pledge(promises))
|
||||
}
|
||||
|
||||
// attempts to unveil and panic if it fails
|
||||
// this does nothing on non-OpenBSD systems
|
||||
func unveilOrPanic(path string, flags string) {
|
||||
err := protect.Unveil(path, flags)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
panicIfError(protect.Unveil(path, flags))
|
||||
}
|
||||
|
||||
// get the executable path via syscalls or infer it from argv
|
||||
@@ -43,27 +51,117 @@ func executablePath() string {
|
||||
return programPath
|
||||
}
|
||||
|
||||
func main() {
|
||||
func lock(stage string) {
|
||||
switch stage {
|
||||
case "boot":
|
||||
exePath := executablePath()
|
||||
// OpenBSD
|
||||
unveilOrPanic("/", "r")
|
||||
unveilOrPanic(exePath, "x")
|
||||
|
||||
// only allow standard stdio operation, file reading, networking, and exec
|
||||
// also remove unveil permission to lock unveil
|
||||
pledgeOrPanic("stdio rpath inet dns proc exec")
|
||||
// Linux
|
||||
panicIfError(landlock.V1.BestEffort().RestrictPaths(
|
||||
landlock.RODirs("/"),
|
||||
))
|
||||
case "boot-daemon":
|
||||
case "read-config":
|
||||
// OpenBSD
|
||||
pledgeOrPanic("stdio rpath inet dns")
|
||||
case "ready":
|
||||
// no file access is allowed from now on, only networking
|
||||
// OpenBSD
|
||||
pledgeOrPanic("stdio inet dns")
|
||||
// Linux
|
||||
net.DefaultResolver.PreferGo = true // needed to lock down dependencies
|
||||
panicIfError(landlock.V1.BestEffort().RestrictPaths(
|
||||
landlock.ROFiles("/etc/resolv.conf"),
|
||||
landlock.ROFiles("/dev/fd"),
|
||||
landlock.ROFiles("/dev/zero"),
|
||||
landlock.ROFiles("/dev/urandom"),
|
||||
landlock.ROFiles("/etc/localtime"),
|
||||
landlock.ROFiles("/proc/self/stat"),
|
||||
landlock.ROFiles("/proc/self/status"),
|
||||
landlock.ROFiles("/usr/share/locale"),
|
||||
landlock.ROFiles("/proc/self/cmdline"),
|
||||
landlock.ROFiles("/usr/share/zoneinfo"),
|
||||
landlock.ROFiles("/proc/sys/kernel/version"),
|
||||
landlock.ROFiles("/proc/sys/kernel/ngroups_max"),
|
||||
landlock.ROFiles("/proc/sys/kernel/cap_last_cap"),
|
||||
landlock.ROFiles("/proc/sys/vm/overcommit_memory"),
|
||||
landlock.RWFiles("/dev/log"),
|
||||
landlock.RWFiles("/dev/null"),
|
||||
landlock.RWFiles("/dev/full"),
|
||||
landlock.RWFiles("/proc/self/fd"),
|
||||
))
|
||||
default:
|
||||
panic("invalid stage")
|
||||
}
|
||||
}
|
||||
|
||||
func extractPort(addr string) uint16 {
|
||||
_, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to extract port from %s: %w", addr, err))
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to extract port from %s: %w", addr, err))
|
||||
}
|
||||
|
||||
return uint16(port)
|
||||
}
|
||||
|
||||
func lockNetwork(sections []wireproxy.RoutineSpawner, infoAddr *string) {
|
||||
var rules []landlock.Rule
|
||||
if infoAddr != nil && *infoAddr != "" {
|
||||
rules = append(rules, landlock.BindTCP(extractPort(*infoAddr)))
|
||||
}
|
||||
|
||||
for _, section := range sections {
|
||||
switch section := section.(type) {
|
||||
case *wireproxy.TCPServerTunnelConfig:
|
||||
rules = append(rules, landlock.ConnectTCP(extractPort(section.Target)))
|
||||
case *wireproxy.HTTPConfig:
|
||||
rules = append(rules, landlock.BindTCP(extractPort(section.BindAddress)))
|
||||
case *wireproxy.TCPClientTunnelConfig:
|
||||
rules = append(rules, landlock.ConnectTCP(uint16(section.BindAddress.Port)))
|
||||
case *wireproxy.Socks5Config:
|
||||
rules = append(rules, landlock.BindTCP(extractPort(section.BindAddress)))
|
||||
}
|
||||
}
|
||||
|
||||
panicIfError(landlock.V4.BestEffort().RestrictNet(rules...))
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, syscall.SIGINT, syscall.SIGQUIT)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go func() {
|
||||
<-s
|
||||
cancel()
|
||||
}()
|
||||
|
||||
exePath := executablePath()
|
||||
lock("boot")
|
||||
|
||||
isDaemonProcess := len(os.Args) > 1 && os.Args[1] == daemonProcess
|
||||
args := os.Args
|
||||
if isDaemonProcess {
|
||||
// remove proc and exec if they are not needed
|
||||
pledgeOrPanic("stdio rpath inet dns")
|
||||
lock("boot-daemon")
|
||||
args = []string{args[0]}
|
||||
args = append(args, os.Args[2:]...)
|
||||
}
|
||||
parser := argparse.NewParser("wireproxy", "Userspace wireguard client for proxying")
|
||||
|
||||
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"})
|
||||
info := parser.String("i", "info", &argparse.Options{Help: "Specify the address and port for exposing health status"})
|
||||
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."})
|
||||
|
||||
@@ -84,8 +182,7 @@ func main() {
|
||||
}
|
||||
|
||||
if !*daemon {
|
||||
// remove proc and exec if they are not needed
|
||||
pledgeOrPanic("stdio rpath inet dns")
|
||||
lock("read-config")
|
||||
}
|
||||
|
||||
conf, err := wireproxy.ParseConfig(*config)
|
||||
@@ -98,6 +195,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
lockNetwork(conf.Routines, info)
|
||||
|
||||
if isDaemonProcess {
|
||||
os.Stdout, _ = os.Open(os.DevNull)
|
||||
os.Stderr, _ = os.Open(os.DevNull)
|
||||
@@ -114,17 +213,36 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
// no file access is allowed from now on, only networking
|
||||
pledgeOrPanic("stdio inet dns")
|
||||
// Wireguard doesn't allow configuring which FD to use for logging
|
||||
// https://github.com/WireGuard/wireguard-go/blob/master/device/logger.go#L39
|
||||
// so redirect STDOUT to STDERR, we don't want to print anything to STDOUT anyways
|
||||
os.Stdout = os.NewFile(uintptr(syscall.Stderr), "/dev/stderr")
|
||||
logLevel := device.LogLevelVerbose
|
||||
if *silent {
|
||||
logLevel = device.LogLevelSilent
|
||||
}
|
||||
|
||||
tnet, err := wireproxy.StartWireguard(conf.Device)
|
||||
lock("ready")
|
||||
|
||||
tun, err := wireproxy.StartWireguard(conf.Device, logLevel)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, spawner := range conf.Routines {
|
||||
go spawner.SpawnRoutine(tnet)
|
||||
go spawner.SpawnRoutine(tun)
|
||||
}
|
||||
|
||||
select {} // sleep eternally
|
||||
tun.StartPingIPs()
|
||||
|
||||
if *info != "" {
|
||||
go func() {
|
||||
err := http.ListenAndServe(*info, tun)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
111
config.go
111
config.go
@@ -15,7 +15,7 @@ import (
|
||||
type PeerConfig struct {
|
||||
PublicKey string
|
||||
PreSharedKey string
|
||||
Endpoint string
|
||||
Endpoint *string
|
||||
KeepAlive int
|
||||
AllowedIPs []netip.Prefix
|
||||
}
|
||||
@@ -27,6 +27,9 @@ type DeviceConfig struct {
|
||||
Peers []PeerConfig
|
||||
DNS []netip.Addr
|
||||
MTU int
|
||||
ListenPort *int
|
||||
CheckAlive []netip.Addr
|
||||
CheckAliveInterval int
|
||||
}
|
||||
|
||||
type TCPClientTunnelConfig struct {
|
||||
@@ -34,6 +37,10 @@ type TCPClientTunnelConfig struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
type STDIOTunnelConfig struct {
|
||||
Target string
|
||||
}
|
||||
|
||||
type TCPServerTunnelConfig struct {
|
||||
ListenPort int
|
||||
Target string
|
||||
@@ -45,6 +52,12 @@ type Socks5Config struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
BindAddress string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type Configuration struct {
|
||||
Device *DeviceConfig
|
||||
Routines []RoutineSpawner
|
||||
@@ -114,8 +127,9 @@ func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
|
||||
return []netip.Addr{}, nil
|
||||
}
|
||||
|
||||
var ips []netip.Addr
|
||||
for _, str := range key.StringsWithShadows(",") {
|
||||
keys := key.StringsWithShadows(",")
|
||||
var ips = make([]netip.Addr, 0, len(keys))
|
||||
for _, str := range keys {
|
||||
str = strings.TrimSpace(str)
|
||||
ip, err := netip.ParseAddr(str)
|
||||
if err != nil {
|
||||
@@ -132,18 +146,15 @@ func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error)
|
||||
return []netip.Addr{}, nil
|
||||
}
|
||||
|
||||
var ips []netip.Addr
|
||||
for _, str := range key.StringsWithShadows(",") {
|
||||
keys := key.StringsWithShadows(",")
|
||||
var ips = make([]netip.Addr, 0, len(keys))
|
||||
for _, str := range keys {
|
||||
prefix, err := netip.ParsePrefix(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := prefix.Addr()
|
||||
if prefix.Bits() != addr.BitLen() {
|
||||
return nil, errors.New("interface address subnet should be /32 for IPv4 and /128 for IPv6")
|
||||
}
|
||||
|
||||
ips = append(ips, addr)
|
||||
}
|
||||
return ips, nil
|
||||
@@ -155,8 +166,9 @@ func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) {
|
||||
return []netip.Prefix{}, nil
|
||||
}
|
||||
|
||||
var ips []netip.Prefix
|
||||
for _, str := range key.StringsWithShadows(",") {
|
||||
keys := key.StringsWithShadows(",")
|
||||
var ips = make([]netip.Prefix, 0, len(keys))
|
||||
for _, str := range keys {
|
||||
prefix, err := netip.ParsePrefix(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -219,10 +231,37 @@ func ParseInterface(cfg *ini.File, device *DeviceConfig) error {
|
||||
device.MTU = value
|
||||
}
|
||||
|
||||
if sectionKey, err := section.GetKey("ListenPort"); err == nil {
|
||||
value, err := sectionKey.Int()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
device.ListenPort = &value
|
||||
}
|
||||
|
||||
checkAlive, err := parseNetIP(section, "CheckAlive")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
device.CheckAlive = checkAlive
|
||||
|
||||
device.CheckAliveInterval = 5
|
||||
if sectionKey, err := section.GetKey("CheckAliveInterval"); err == nil {
|
||||
value, err := sectionKey.Int()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(checkAlive) == 0 {
|
||||
return errors.New("CheckAliveInterval is only valid when CheckAlive is set")
|
||||
}
|
||||
|
||||
device.CheckAliveInterval = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParsePeer parses the [Peer] section and extract the information into `peers`
|
||||
// ParsePeers parses the [Peer] section and extract the information into `peers`
|
||||
func ParsePeers(cfg *ini.File, peers *[]PeerConfig) error {
|
||||
sections, err := cfg.SectionsByName("Peer")
|
||||
if len(sections) < 1 || err != nil {
|
||||
@@ -249,15 +288,14 @@ func ParsePeers(cfg *ini.File, peers *[]PeerConfig) error {
|
||||
peer.PreSharedKey = value
|
||||
}
|
||||
|
||||
decoded, err = parseString(section, "Endpoint")
|
||||
if sectionKey, err := section.GetKey("Endpoint"); err == nil {
|
||||
value := sectionKey.String()
|
||||
decoded, err = resolveIPPAndPort(strings.ToLower(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decoded, err = resolveIPPAndPort(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
peer.Endpoint = &decoded
|
||||
}
|
||||
peer.Endpoint = decoded
|
||||
|
||||
if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil {
|
||||
value, err := sectionKey.Int()
|
||||
@@ -294,6 +332,17 @@ func parseTCPClientTunnelConfig(section *ini.Section) (RoutineSpawner, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseSTDIOTunnelConfig(section *ini.Section) (RoutineSpawner, error) {
|
||||
config := &STDIOTunnelConfig{}
|
||||
targetSection, err := parseString(section, "Target")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Target = targetSection
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseTCPServerTunnelConfig(section *ini.Section) (RoutineSpawner, error) {
|
||||
config := &TCPServerTunnelConfig{}
|
||||
|
||||
@@ -330,6 +379,24 @@ func parseSocks5Config(section *ini.Section) (RoutineSpawner, error) {
|
||||
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
|
||||
// specified sections
|
||||
func parseRoutinesConfig(routines *[]RoutineSpawner, cfg *ini.File, sectionName string, f func(*ini.Section) (RoutineSpawner, error)) error {
|
||||
@@ -394,6 +461,11 @@ func ParseConfig(path string) (*Configuration, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parseRoutinesConfig(&routinesSpawners, cfg, "STDIOTunnel", parseSTDIOTunnelConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parseRoutinesConfig(&routinesSpawners, cfg, "TCPServerTunnel", parseTCPServerTunnelConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -404,6 +476,11 @@ func ParseConfig(path string) (*Configuration, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = parseRoutinesConfig(&routinesSpawners, cfg, "http", parseHTTPConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Configuration{
|
||||
Device: device,
|
||||
Routines: routinesSpawners,
|
||||
|
||||
37
go.mod
37
go.mod
@@ -1,26 +1,27 @@
|
||||
module github.com/octeep/wireproxy
|
||||
module github.com/pufferffish/wireproxy
|
||||
|
||||
go 1.18
|
||||
go 1.21.1
|
||||
|
||||
toolchain go1.21.6
|
||||
|
||||
require (
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/akamensky/argparse v1.3.1
|
||||
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
|
||||
suah.dev/protect v1.2.0
|
||||
github.com/akamensky/argparse v1.4.0
|
||||
github.com/go-ini/ini v1.67.0
|
||||
github.com/things-go/go-socks5 v0.0.5
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
suah.dev/protect v1.2.3
|
||||
)
|
||||
|
||||
require (
|
||||
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/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/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
|
||||
)
|
||||
|
||||
72
go.sum
72
go.sum
@@ -1,47 +1,41 @@
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A=
|
||||
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/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/akamensky/argparse v1.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
|
||||
github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
|
||||
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/go-ini/ini v1.66.4 h1:dKjMqkcbkzfddhIhyglTPgMoJnkvmG+bSLrU9cTHc5M=
|
||||
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/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/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a h1:dz+a1MiMQksVhejeZwqJuzPawYQBwug74J8PPtkLl9U=
|
||||
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a/go.mod h1:1NY/VPO8xm3hXw3f+M65z+PJDLUaZA5cu7OfanxoUzY=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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/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/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
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.0.0-20220315194320-039c03cc5b86/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/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/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b/go.mod h1:6y4CqPAy54NwiN4nC8K+R1eMpQDB1P2d25qmunh2RSA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
|
||||
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 h1:cv/zaNV0nr1mJzaeo4S5mHIm5va1W0/9J3/5prlsuRM=
|
||||
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
||||
suah.dev/protect v1.2.0 h1:4G4V43yVYXCjLFzaE9QJR0fLo3rf5vNBS9YxyoI19DU=
|
||||
suah.dev/protect v1.2.0/go.mod h1:Ocn1yqUskqe/is6N2bxQxtT+fegbvQsOFyHbJAQu9XE=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
|
||||
suah.dev/protect v1.2.3 h1:aHeoNwZ9YPp64hrYaN0g0djNE1eRujgH63CrfRrUKdc=
|
||||
suah.dev/protect v1.2.3/go.mod h1:n1R3XIbsnryKX7C1PO88i5Wgo0v8OTXm9K9FIKt4rfs=
|
||||
|
||||
162
http.go
Normal file
162
http.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package wireproxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/sourcegraph/conc"
|
||||
)
|
||||
|
||||
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) {
|
||||
var rd = bufio.NewReader(conn)
|
||||
req, err := http.ReadRequest(rd)
|
||||
if err != nil {
|
||||
log.Printf("read request failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
code, err := s.authenticate(req)
|
||||
if err != nil {
|
||||
_ = responseWith(req, code).Write(conn)
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
log.Printf("unsupported protocol: %s\n", req.Method)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("dial proxy failed: %s\n", err)
|
||||
return
|
||||
}
|
||||
if peer == nil {
|
||||
log.Println("dial proxy failed: peer nil")
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
wg := conc.NewWaitGroup()
|
||||
wg.Go(func() {
|
||||
_, err = io.Copy(conn, peer)
|
||||
_ = conn.Close()
|
||||
})
|
||||
wg.Go(func() {
|
||||
_, err = io.Copy(peer, conn)
|
||||
_ = peer.Close()
|
||||
})
|
||||
wg.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
// ListenAndServe is used to create a listener and serve on it
|
||||
func (s *HTTPServer) ListenAndServe(network, addr string) error {
|
||||
server, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen tcp failed: %w", err)
|
||||
}
|
||||
defer func(server net.Listener) {
|
||||
_ = server.Close()
|
||||
}(server)
|
||||
for {
|
||||
conn, err := server.Accept()
|
||||
if err != nil {
|
||||
return fmt.Errorf("accept request failed: %w", err)
|
||||
}
|
||||
go func(conn net.Conn) {
|
||||
s.serve(conn)
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
310
routine.go
310
routine.go
@@ -1,18 +1,35 @@
|
||||
package wireproxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
srand "crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/txthinking/socks5"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/conc"
|
||||
"github.com/things-go/go-socks5"
|
||||
"github.com/things-go/go-socks5/bufferpool"
|
||||
|
||||
"net/netip"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
)
|
||||
|
||||
// errorLogger is the logger to print error message
|
||||
@@ -24,6 +41,16 @@ type CredentialValidator struct {
|
||||
password string
|
||||
}
|
||||
|
||||
// VirtualTun stores a reference to netstack network and DNS configuration
|
||||
type VirtualTun struct {
|
||||
Tnet *netstack.Net
|
||||
Dev *device.Device
|
||||
SystemDNS bool
|
||||
Conf *DeviceConfig
|
||||
// PingRecord stores the last time an IP was pinged
|
||||
PingRecord map[string]uint64
|
||||
}
|
||||
|
||||
// RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed
|
||||
type RoutineSpawner interface {
|
||||
SpawnRoutine(vt *VirtualTun)
|
||||
@@ -37,11 +64,10 @@ type addressPort struct {
|
||||
// LookupAddr lookups a hostname.
|
||||
// DNS traffic may or may not be routed depending on VirtualTun's setting
|
||||
func (d VirtualTun) LookupAddr(ctx context.Context, name string) ([]string, error) {
|
||||
if d.systemDNS {
|
||||
if d.SystemDNS {
|
||||
return net.DefaultResolver.LookupHost(ctx, name)
|
||||
} else {
|
||||
return d.tnet.LookupContextHost(ctx, name)
|
||||
}
|
||||
return d.Tnet.LookupContextHost(ctx, name)
|
||||
}
|
||||
|
||||
// ResolveAddrWithContext resolves a hostname and returns an AddrPort.
|
||||
@@ -113,12 +139,41 @@ func (d VirtualTun) resolveToAddrPort(endpoint *addressPort) (*netip.AddrPort, e
|
||||
|
||||
// SpawnRoutine spawns a socks5 server.
|
||||
func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) {
|
||||
server, err := socks5.NewClassicServer(config.BindAddress, "0.0.0.0", config.Username, config.Password, 15, 15)
|
||||
if err != nil {
|
||||
var authMethods []socks5.Authenticator
|
||||
if username := config.Username; username != "" {
|
||||
authMethods = append(authMethods, socks5.UserPassAuthenticator{
|
||||
Credentials: socks5.StaticCredentials{username: config.Password},
|
||||
})
|
||||
} else {
|
||||
authMethods = append(authMethods, socks5.NoAuthAuthenticator{})
|
||||
}
|
||||
|
||||
options := []socks5.Option{
|
||||
socks5.WithDial(vt.Tnet.DialContext),
|
||||
socks5.WithResolver(vt),
|
||||
socks5.WithAuthMethods(authMethods),
|
||||
socks5.WithBufferPool(bufferpool.NewPool(256 * 1024)),
|
||||
}
|
||||
|
||||
server := socks5.NewServer(options...)
|
||||
|
||||
if err := server.ListenAndServe("tcp", config.BindAddress); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = server.ListenAndServe(vt)
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
// SpawnRoutine spawns a http server.
|
||||
func (config *HTTPConfig) SpawnRoutine(vt *VirtualTun) {
|
||||
server := &HTTPServer{
|
||||
config: config,
|
||||
dial: vt.Tnet.Dial,
|
||||
auth: CredentialValidator{config.Username, config.Password},
|
||||
}
|
||||
if config.Username != "" || config.Password != "" {
|
||||
server.authRequired = true
|
||||
}
|
||||
|
||||
if err := server.ListenAndServe("tcp", config.BindAddress); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -131,15 +186,12 @@ func (c CredentialValidator) Valid(username, password string) bool {
|
||||
return u&p == 1
|
||||
}
|
||||
|
||||
// connForward copy data from `from` to `to`, then close both stream.
|
||||
func connForward(bufSize int, from io.ReadWriteCloser, to io.ReadWriteCloser) {
|
||||
buf := make([]byte, bufSize)
|
||||
_, err := io.CopyBuffer(to, from, buf)
|
||||
// connForward copy data from `from` to `to`
|
||||
func connForward(from io.ReadWriteCloser, to io.ReadWriteCloser) {
|
||||
_, err := io.Copy(to, from)
|
||||
if err != nil {
|
||||
errorLogger.Printf("Cannot forward traffic: %s\n", err.Error())
|
||||
}
|
||||
_ = from.Close()
|
||||
_ = to.Close()
|
||||
}
|
||||
|
||||
// tcpClientForward starts a new connection via wireguard and forward traffic from `conn`
|
||||
@@ -152,14 +204,62 @@ func tcpClientForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
|
||||
|
||||
tcpAddr := TCPAddrFromAddrPort(*target)
|
||||
|
||||
sconn, err := vt.tnet.DialTCP(tcpAddr)
|
||||
sconn, err := vt.Tnet.DialTCP(tcpAddr)
|
||||
if err != nil {
|
||||
errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
go connForward(1024, sconn, conn)
|
||||
go connForward(1024, conn, sconn)
|
||||
go func() {
|
||||
wg := conc.NewWaitGroup()
|
||||
wg.Go(func() {
|
||||
connForward(sconn, conn)
|
||||
})
|
||||
wg.Go(func() {
|
||||
connForward(conn, sconn)
|
||||
})
|
||||
wg.Wait()
|
||||
_ = sconn.Close()
|
||||
_ = conn.Close()
|
||||
sconn = nil
|
||||
conn = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// STDIOTcpForward starts a new connection via wireguard and forward traffic from `conn`
|
||||
func STDIOTcpForward(vt *VirtualTun, raddr *addressPort) {
|
||||
target, err := vt.resolveToAddrPort(raddr)
|
||||
if err != nil {
|
||||
errorLogger.Printf("Name resolution error for %s: %s\n", raddr.address, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// os.Stdout has previously been remapped to stderr, se we can't use it
|
||||
stdout, err := os.OpenFile("/dev/stdout", os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to open /dev/stdout: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
tcpAddr := TCPAddrFromAddrPort(*target)
|
||||
sconn, err := vt.Tnet.DialTCP(tcpAddr)
|
||||
if err != nil {
|
||||
errorLogger.Printf("TCP Client Tunnel to %s (%s): %s\n", target, tcpAddr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg := conc.NewWaitGroup()
|
||||
wg.Go(func() {
|
||||
connForward(os.Stdin, sconn)
|
||||
})
|
||||
wg.Go(func() {
|
||||
connForward(sconn, stdout)
|
||||
})
|
||||
wg.Wait()
|
||||
_ = sconn.Close()
|
||||
sconn = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// SpawnRoutine spawns a local TCP server which acts as a proxy to the specified target
|
||||
@@ -183,6 +283,16 @@ func (conf *TCPClientTunnelConfig) SpawnRoutine(vt *VirtualTun) {
|
||||
}
|
||||
}
|
||||
|
||||
// SpawnRoutine connects to the specified target and plumbs it to STDIN / STDOUT
|
||||
func (conf *STDIOTunnelConfig) SpawnRoutine(vt *VirtualTun) {
|
||||
raddr, err := parseAddressPort(conf.Target)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
go STDIOTcpForward(vt, raddr)
|
||||
}
|
||||
|
||||
// tcpServerForward starts a new connection locally and forward traffic from `conn`
|
||||
func tcpServerForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
|
||||
target, err := vt.resolveToAddrPort(raddr)
|
||||
@@ -199,8 +309,20 @@ func tcpServerForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
go connForward(1024, sconn, conn)
|
||||
go connForward(1024, conn, sconn)
|
||||
go func() {
|
||||
gr := conc.NewWaitGroup()
|
||||
gr.Go(func() {
|
||||
connForward(sconn, conn)
|
||||
})
|
||||
gr.Go(func() {
|
||||
connForward(conn, sconn)
|
||||
})
|
||||
gr.Wait()
|
||||
_ = sconn.Close()
|
||||
_ = conn.Close()
|
||||
sconn = nil
|
||||
conn = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// SpawnRoutine spawns a TCP server on wireguard which acts as a proxy to the specified target
|
||||
@@ -211,7 +333,7 @@ func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
|
||||
}
|
||||
|
||||
addr := &net.TCPAddr{Port: conf.ListenPort}
|
||||
server, err := vt.tnet.ListenTCP(addr)
|
||||
server, err := vt.Tnet.ListenTCP(addr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -224,3 +346,151 @@ func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
|
||||
go tcpServerForward(vt, raddr, conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (d VirtualTun) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Health metric request: %s\n", r.URL.Path)
|
||||
switch path.Clean(r.URL.Path) {
|
||||
case "/readyz":
|
||||
body, err := json.Marshal(d.PingRecord)
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to get device metrics: %s\n", err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
status := http.StatusOK
|
||||
for _, record := range d.PingRecord {
|
||||
lastPong := time.Unix(int64(record), 0)
|
||||
// +2 seconds to account for the time it takes to ping the IP
|
||||
if time.Since(lastPong) > time.Duration(d.Conf.CheckAliveInterval+2)*time.Second {
|
||||
status = http.StatusServiceUnavailable
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(body)
|
||||
_, _ = w.Write([]byte("\n"))
|
||||
case "/metrics":
|
||||
get, err := d.Dev.IpcGet()
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to get device metrics: %s\n", err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, peer := range strings.Split(get, "\n") {
|
||||
pair := strings.SplitN(peer, "=", 2)
|
||||
if len(pair) != 2 {
|
||||
buf.WriteString(peer)
|
||||
continue
|
||||
}
|
||||
if pair[0] == "private_key" || pair[0] == "preshared_key" {
|
||||
pair[1] = "REDACTED"
|
||||
}
|
||||
buf.WriteString(pair[0])
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(pair[1])
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (d VirtualTun) pingIPs() {
|
||||
for _, addr := range d.Conf.CheckAlive {
|
||||
socket, err := d.Tnet.Dial("ping", addr.String())
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to ping %s: %s\n", addr, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
data := make([]byte, 16)
|
||||
_, _ = srand.Read(data)
|
||||
|
||||
requestPing := icmp.Echo{
|
||||
Seq: rand.Intn(1 << 16),
|
||||
Data: data,
|
||||
}
|
||||
|
||||
var icmpBytes []byte
|
||||
if addr.Is4() {
|
||||
icmpBytes, _ = (&icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &requestPing}).Marshal(nil)
|
||||
} else if addr.Is6() {
|
||||
icmpBytes, _ = (&icmp.Message{Type: ipv6.ICMPTypeEchoRequest, Code: 0, Body: &requestPing}).Marshal(nil)
|
||||
} else {
|
||||
errorLogger.Printf("Failed to ping %s: invalid address: %s\n", addr, addr.String())
|
||||
continue
|
||||
}
|
||||
|
||||
_ = socket.SetReadDeadline(time.Now().Add(time.Duration(d.Conf.CheckAliveInterval) * time.Second))
|
||||
_, err = socket.Write(icmpBytes)
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to ping %s: %s\n", addr, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
addr := addr
|
||||
go func() {
|
||||
n, err := socket.Read(icmpBytes[:])
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to read ping response from %s: %s\n", addr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
replyPacket, err := icmp.ParseMessage(1, icmpBytes[:n])
|
||||
if err != nil {
|
||||
errorLogger.Printf("Failed to parse ping response from %s: %s\n", addr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if addr.Is4() {
|
||||
replyPing, ok := replyPacket.Body.(*icmp.Echo)
|
||||
if !ok {
|
||||
errorLogger.Printf("Failed to parse ping response from %s: invalid reply type: %s\n", addr, replyPacket.Type)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(replyPing.Data, requestPing.Data) || replyPing.Seq != requestPing.Seq {
|
||||
errorLogger.Printf("Failed to parse ping response from %s: invalid ping reply: %v\n", addr, replyPing)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if addr.Is6() {
|
||||
replyPing, ok := replyPacket.Body.(*icmp.RawBody)
|
||||
if !ok {
|
||||
errorLogger.Printf("Failed to parse ping response from %s: invalid reply type: %s\n", addr, replyPacket.Type)
|
||||
return
|
||||
}
|
||||
|
||||
seq := binary.BigEndian.Uint16(replyPing.Data[2:4])
|
||||
pongBody := replyPing.Data[4:]
|
||||
if !bytes.Equal(pongBody, requestPing.Data) || int(seq) != requestPing.Seq {
|
||||
errorLogger.Printf("Failed to parse ping response from %s: invalid ping reply: %v\n", addr, replyPing)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
d.PingRecord[addr.String()] = uint64(time.Now().Unix())
|
||||
|
||||
defer socket.Close()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (d VirtualTun) StartPingIPs() {
|
||||
for _, addr := range d.Conf.CheckAlive {
|
||||
d.PingRecord[addr.String()] = 0
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
d.pingIPs()
|
||||
time.Sleep(time.Duration(d.Conf.CheckAliveInterval) * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
35
systemd/README.md
Normal file
35
systemd/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Running wireproxy with systemd
|
||||
|
||||
If you're on a systemd-based distro, you'll most likely want to run Wireproxy as a systemd unit.
|
||||
|
||||
The provided systemd unit assumes you have the wireproxy executable installed on `/opt/wireproxy/wireproxy` and a configuration file stored at `/etc/wireproxy.conf`. These paths can be customized by editing the unit file.
|
||||
|
||||
# Setting up the unit
|
||||
|
||||
1. Copy the `wireproxy.service` file from this directory to `/etc/systemd/system/`, or use the following cURL command to download it:
|
||||
```bash
|
||||
sudo curl https://raw.githubusercontent.com/pufferffish/wireproxy/master/systemd/wireproxy.service > /etc/systemd/system/wireproxy.service
|
||||
```
|
||||
|
||||
2. If necessary, customize the unit.
|
||||
|
||||
Edit the parts with `LoadCredential`, `ExecStartPre=` and `ExecStart=` to point to the executable and the configuration file. For example, if wireproxy is installed on `/usr/bin` and the configuration file is located in `/opt/myfiles/wireproxy.conf` do the following change:
|
||||
```service
|
||||
LoadCredential=conf:/opt/myfiles/wireproxy.conf
|
||||
ExecStartPre=/usr/bin/wireproxy -n -c ${CREDENTIALS_DIRECTORY}/conf
|
||||
ExecStart=/usr/bin/wireproxy -c ${CREDENTIALS_DIRECTORY}/conf
|
||||
```
|
||||
|
||||
4. Reload systemd and enable the unit.
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now wireproxy.service
|
||||
```
|
||||
|
||||
5. Make sure it's working correctly.
|
||||
|
||||
Finally, check out the unit status to confirm `wireproxy.service` has started without problems. You can use commands like `systemctl status wireproxy.service` and/or `sudo journalctl -u wireproxy.service`.
|
||||
|
||||
# Additional notes
|
||||
|
||||
If you want to disable the extensive logging that's done by Wireproxy, simply add `-s` parameter to `ExecStart=`. This will enable the silent mode that was implemented with [pull/67](https://github.com/pufferffish/wireproxy/pull/67).
|
||||
46
systemd/wireproxy.service
Normal file
46
systemd/wireproxy.service
Normal file
@@ -0,0 +1,46 @@
|
||||
[Unit]
|
||||
Description=Wireproxy socks5/http tunnel
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
User=wireproxy
|
||||
Group=wireproxy
|
||||
SyslogIdentifier=wireproxy
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
RestartSec=30s
|
||||
|
||||
DynamicUser=yes
|
||||
LoadCredential=conf:/etc/wireproxy.conf
|
||||
ExecStartPre=/opt/wireproxy/wireproxy -n -c ${CREDENTIALS_DIRECTORY}/conf
|
||||
ExecStart=/opt/wireproxy/wireproxy -c ${CREDENTIALS_DIRECTORY}/conf
|
||||
|
||||
# Required if <1024 port
|
||||
#AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
LimitNPROC=64
|
||||
LockPersonality=true
|
||||
MemoryDenyWriteExecute=true
|
||||
NoNewPrivileges=true
|
||||
PrivateDevices=true
|
||||
PrivateTmp=true
|
||||
PrivateUsers=true
|
||||
ProcSubset=pid
|
||||
ProtectClock=true
|
||||
ProtectControlGroups=true
|
||||
ProtectHome=true
|
||||
ProtectHostname=true
|
||||
ProtectKernelLogs=true
|
||||
ProtectKernelModules=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectProc=invisible
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK
|
||||
RestrictNamespaces=true
|
||||
RestrictRealtime=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
28
test_config.sh
Executable file
28
test_config.sh
Executable 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
179
tunnel.go
@@ -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
25
util.go
Normal 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)),
|
||||
}
|
||||
}
|
||||
33
wireguard.go
33
wireguard.go
@@ -3,9 +3,8 @@ package wireproxy
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/patrickmn/go-cache"
|
||||
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc/v2"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
@@ -27,15 +26,21 @@ func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
|
||||
|
||||
request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey))
|
||||
|
||||
if conf.ListenPort != nil {
|
||||
request.WriteString(fmt.Sprintf("listen_port=%d\n", *conf.ListenPort))
|
||||
}
|
||||
|
||||
for _, peer := range conf.Peers {
|
||||
request.WriteString(fmt.Sprintf(heredoc.Doc(`
|
||||
public_key=%s
|
||||
endpoint=%s
|
||||
persistent_keepalive_interval=%d
|
||||
preshared_key=%s
|
||||
`),
|
||||
peer.PublicKey, peer.Endpoint, peer.KeepAlive, peer.PreSharedKey,
|
||||
peer.PublicKey, peer.KeepAlive, peer.PreSharedKey,
|
||||
))
|
||||
if peer.Endpoint != nil {
|
||||
request.WriteString(fmt.Sprintf("endpoint=%s\n", *peer.Endpoint))
|
||||
}
|
||||
|
||||
if len(peer.AllowedIPs) > 0 {
|
||||
for _, ip := range peer.AllowedIPs {
|
||||
@@ -54,7 +59,7 @@ func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -64,7 +69,7 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -75,17 +80,11 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
|
||||
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{
|
||||
tnet: tnet,
|
||||
systemDNS: len(setting.dns) == 0,
|
||||
mappedPortToNatEntry: natTableRev,
|
||||
natEntryToMappedPort: natTable,
|
||||
Tnet: tnet,
|
||||
Dev: dev,
|
||||
Conf: conf,
|
||||
SystemDNS: len(setting.dns) == 0,
|
||||
PingRecord: make(map[string]uint64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user