Compare commits

..

No commits in common. "master" and "v1.0.1" have entirely different histories.

32 changed files with 1300 additions and 1753 deletions

View file

@ -1,6 +0,0 @@
.dockerignore
.github
.gitignore
Dockerfile
LICENSE
README.md

View file

@ -11,6 +11,8 @@ builds:
- linux
- windows
- darwin
- freebsd
- openbsd
goarch:
- arm
- arm64

View file

@ -6,18 +6,17 @@ on:
pull_request:
branches:
- '**'
workflow_dispatch:
jobs:
windowsAmd64Build:
name: Build Windows amd64 Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Windows amd64 Version
run: |
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o WireProxy_amd64.exe -v ./cmd/wireproxy
@ -25,7 +24,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@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_windows_amd64
path: release_windows_amd64
@ -33,11 +32,11 @@ jobs:
name: Build Windows arm64 Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Windows arm64 Version
run: |
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -o WireProxy_arm64.exe -v ./cmd/wireproxy
@ -45,7 +44,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@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_windows_arm64
path: release_windows_arm64
@ -53,11 +52,11 @@ jobs:
name: Build Linux amd64 Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Linux amd64 Version
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o WireProxy_amd64 -v ./cmd/wireproxy
@ -65,7 +64,7 @@ jobs:
mv WireProxy_amd64 wireproxy
cp wireproxy release_linux_amd64/wireproxy
- name: Upload Linux amd64 Version
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_linux_amd64
path: release_linux_amd64
@ -73,11 +72,11 @@ jobs:
name: Build Linux arm64 Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Linux arm64 Version
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o WireProxy_arm64 -v ./cmd/wireproxy
@ -85,7 +84,7 @@ jobs:
mv WireProxy_arm64 wireproxy
cp wireproxy release_linux_arm64/wireproxy
- name: Upload Linux arm64 Version
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_linux_arm64
path: release_linux_arm64
@ -93,11 +92,11 @@ jobs:
name: Build Linux s390x Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Linux s390x Version
run: |
CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -o WireProxy_s390x -v ./cmd/wireproxy
@ -105,7 +104,7 @@ jobs:
mv WireProxy_s390x wireproxy
cp wireproxy release_linux_s390x/wireproxy
- name: Upload Linux s390x Version
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_linux_s390x
path: release_linux_s390x
@ -113,11 +112,11 @@ jobs:
name: Build Darwin amd64 Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Darwin amd64 Version
run: |
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o WireProxy_amd64 -v ./cmd/wireproxy
@ -125,7 +124,7 @@ jobs:
mv WireProxy_amd64 wireproxy
cp wireproxy release_darwin_amd64/wireproxy
- name: Upload Darwin amd64 Version
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_darwin_amd64
path: release_darwin_amd64
@ -133,11 +132,11 @@ jobs:
name: Build Darwin arm64 Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
- name: Setting up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: 1.17
- name: Building Darwin arm64 Version
run: |
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o WireProxy_arm64 -v ./cmd/wireproxy
@ -145,7 +144,7 @@ jobs:
mv WireProxy_arm64 wireproxy
cp wireproxy release_darwin_arm64/wireproxy
- name: Upload Darwin arm64 Version
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v1
with:
name: WireProxy_darwin_arm64
path: release_darwin_arm64

View file

@ -1,72 +0,0 @@
name: Build container
on:
push:
branches:
- master
pull_request:
# Allow for manually running
workflow_dispatch:
inputs:
container_tag:
description: Tag for container
default: "latest"
required: true
permissions:
packages: write
jobs:
container:
runs-on: ubuntu-20.04
env:
CONTAINER_NAME: ghcr.io/${{ github.repository }}
BUILD_PLATFORMS: linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x
RAW_CONTAINER_TAG: ${{ github.event.inputs.container_tag || github.event.pull_request.head.ref || 'latest' }}
RAW_REF_NAME: ${{ github.event.pull_request.head.ref || github.ref }}
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3.0.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
submodules: recursive
# Needed for buildx gha cache to work
- name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v3
- name: Build container
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CONTAINER_TAG=$(echo "$RAW_CONTAINER_TAG" | sed 's/[^a-zA-Z0-9]\+/-/')
REF_NAME=$(echo "$RAW_REF_NAME" | sed -r 's#^refs/(heads|tags)/##')
docker buildx build \
--platform "$BUILD_PLATFORMS" \
--tag "$CONTAINER_NAME:$CONTAINER_TAG" \
--tag "$CONTAINER_NAME:$GITHUB_SHA" \
--label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \
--label "org.opencontainers.image.documentation=${{ github.server_url }}/${{ github.repository }}" \
--label "org.opencontainers.image.url=${{ github.server_url }}/${{ github.repository }}/packages" \
--label "org.opencontainers.image.ref.name=$REF_NAME" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.vendor=${{ github.repository_owner }}" \
--label "org.opencontainers.image.created=$(date -u --rfc-3339=seconds)" \
--cache-from type=gha \
--cache-to type=gha,mode=max \
--pull ${{ github.event_name == 'push' && '--push' || '' }} .

View file

@ -6,8 +6,6 @@ on:
pull_request:
branches:
- '**'
workflow_dispatch:
permissions:
contents: read
jobs:
@ -15,11 +13,7 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: '1.21'
- uses: actions/checkout@v4
- uses: actions/setup-go@v2
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: latest
uses: golangci/golangci-lint-action@v2

View file

@ -1,41 +0,0 @@
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

View file

@ -1,4 +1,4 @@
name: Cross compile WireProxy
name: Cross compile WirePorxy
on:
workflow_dispatch:
@ -7,14 +7,14 @@ on:
- v*
jobs:
WireProxy:
WirePorxy:
name: Cross compile WireProxy
name: Cross compile WirePorxy
runs-on: ubuntu-20.04
env:
workdir: ./WireProxy
workdir: ./WirePorxy
steps:
- name: Checkout code
@ -22,25 +22,23 @@ jobs:
with:
fetch-depth: 0
- name: Git clone WireProxy
- name: Git clone WirePorxy
run: |
git clone https://github.com/pufferffish/wireproxy.git ${{ env.workdir }}
git clone https://github.com/octeep/wireproxy.git ${{ env.workdir }}
cp ./.github/wireproxy-releaser.yml ${{ env.workdir }}/.goreleaser.yml
- name: Set up GoReleaser
uses: actions/setup-go@v5
uses: actions/setup-go@v2
with:
go-version: "1.21"
go-version: "1.17"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
uses: goreleaser/goreleaser-action@v2
with:
distribution: goreleaser
workdir: ${{ env.workdir }}
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
args: release --rm-dist
- name: Release binaries
uses: softprops/action-gh-release@v1
@ -48,4 +46,4 @@ jobs:
tag_name: wireproxy
files: ${{ env.workdir }}/dist/*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

3
.gitignore vendored
View file

@ -1,6 +1,3 @@
/main
/wireproxy
*.sw?
/.idea
.goreleaser.yml
*.conf

View file

@ -1,19 +0,0 @@
# Start by building the application.
FROM docker.io/golang:1.21 as build
WORKDIR /usr/src/wireproxy
COPY . .
RUN make
# Now copy it into our base image.
FROM gcr.io/distroless/static-debian11:nonroot
COPY --from=build /usr/src/wireproxy/wireproxy /usr/bin/wireproxy
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"

View file

@ -1,4 +1,4 @@
Copyright (c) 2024 Wind Wong <me@windtfw.com>
Copyright (c) 2022 Wind T.F. Wong <octeep@pm.me>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View file

@ -1,15 +0,0 @@
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:
${GO} build -trimpath -ldflags "-s -w -X 'main.version=${TAG}'" ./cmd/wireproxy
.PHONY: clean
clean:
${RM} wireproxy

192
README.md
View file

@ -1,20 +1,17 @@
# wireproxy
[![ISC licensed](https://img.shields.io/badge/license-ISC-blue)](./LICENSE)
[![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)
A wireguard client that exposes itself as a socks5/http proxy or tunnels.
A wireguard client that exposes itself as a socks5 proxy or tunnels.
# What is this
`wireproxy` is a completely userspace application that connects to a wireguard peer,
and exposes a socks5/http proxy or tunnels on the machine. This can be useful if you need
and exposes a socks5 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.
# Why you might want this
- You simply want to use wireguard as a way to proxy some traffic.
- You don't want root permission just to change wireguard settings.
@ -23,35 +20,21 @@ 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/artem-russkikh/wireproxy-awg)
of wireproxy by [@artem-russkikh](https://github.com/artem-russkikh).
# Sponsor
This project is supported by [IPRoyal](https://iproyal.com/?r=795836). You can get premium quality proxies at unbeatable prices
with a discount using [this referral link](https://iproyal.com/?r=795836)! 🚀
![IPRoyal](/assets/iproyal.png)
# Feature
- TCP static routing for client and server
- SOCKS5/HTTP proxy (currently only CONNECT is supported)
- SOCKS5 proxy (currently only CONNECT is supported)
# TODO
- UDP Support in SOCKS5
- UDP static routing
# Usage
```bash
./wireproxy [-c path to config]
```
./wireproxy -c [path to config]
```
```bash
usage: wireproxy [-h|--help] [-c|--config "<value>"] [-s|--silent]
[-d|--daemon] [-i|--info "<value>"] [-v|--version]
```
usage: wireproxy [-h|--help] -c|--config "<value>" [-d|--daemon]
[-n|--configtest]
Userspace wireguard client for proxying
@ -60,36 +43,20 @@ Arguments:
-h --help Print help information
-c --config Path of configuration file
Default paths: /etc/wireproxy/wireproxy.conf, $HOME/.config/wireproxy.conf
-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
```bash
```
git clone https://github.com/octeep/wireproxy
cd wireproxy
make
go build ./cmd/wireproxy
```
# Install
```bash
go install github.com/pufferffish/wireproxy/cmd/wireproxy@v1.0.9 # or @latest
```
# 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
```ini
```
# The [Interface] and [Peer] configurations follow the same semantics and meaning
# of a wg-quick configuration. To understand what these fields mean, please refer to:
# https://wiki.archlinux.org/title/WireGuard#Persistent_configuration
@ -98,7 +65,6 @@ Instructions for using wireproxy with Firefox container tabs and auto-start on M
Address = 10.200.200.2/32 # The subnet should be /32 and /128 for IPv4 and v6 respectively
# MTU = 1420 (optional)
PrivateKey = uCTIK+56CPyCvwJxmU5dBfuyJvPuSXAq1FzHdnIxe1Q=
# PrivateKey = $MY_WIREGUARD_PRIVATE_KEY # Alternatively, reference environment variables
DNS = 10.200.200.1
[Peer]
@ -123,16 +89,6 @@ 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
@ -142,22 +98,11 @@ 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
wireproxy config file like this:
```ini
```
WGConfig = <path to the wireguard config>
# Same semantics as above
@ -171,119 +116,10 @@ WGConfig = <path to the wireguard config>
...
```
Having multiple peers is also supported. `AllowedIPs` would need to be specified
such that wireproxy would know which peer to forward to.
```ini
[Interface]
Address = 10.254.254.40/32
PrivateKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
[Peer]
Endpoint = 192.168.0.204:51820
PublicKey = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY=
AllowedIPs = 10.254.254.100/32
PersistentKeepalive = 25
[Peer]
PublicKey = ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ=
AllowedIPs = 10.254.254.1/32, fdee:1337:c000:d00d::1/128
Endpoint = 172.16.0.185:44044
PersistentKeepalive = 25
## Donation
<noscript><a href="https://liberapay.com/octeep/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
[TCPServerTunnel]
ListenPort = 5000
Target = service-one.servicenet:5000
[TCPServerTunnel]
ListenPort = 5001
Target = service-two.servicenet:5001
[TCPServerTunnel]
ListenPort = 5080
Target = service-three.servicenet:80
```
Wireproxy can also allow peers to connect to it:
```ini
[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.
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:
```ini
[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
```text
< 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:
```ini
[Interface]
PrivateKey = censored
Address = 10.2.0.2/32
DNS = 10.2.0.1
CheckAlive = 1.1.1.1
```
`/readyz` would respond with
```text
< 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
## Stargazers over time
[![Stargazers over time](https://starchart.cc/octeep/wireproxy.svg)](https://starchart.cc/octeep/wireproxy)

View file

@ -1,96 +0,0 @@
# 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
```ini
# 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.
```bash
#!/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
<?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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,184 +1,44 @@
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/pufferffish/wireproxy"
"golang.zx2c4.com/wireguard/device"
"github.com/octeep/wireproxy"
"suah.dev/protect"
)
// an argument to denote that this process was spawned by -d
const daemonProcess = "daemon-process"
// default paths for wireproxy config file
var default_config_paths = []string {
"/etc/wireproxy/wireproxy.conf",
os.Getenv("HOME")+"/.config/wireproxy.conf",
}
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) {
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) {
panicIfError(protect.Unveil(path, flags))
}
// get the executable path via syscalls or infer it from argv
func executablePath() string {
programPath, err := os.Executable()
err := protect.Pledge(promises)
if err != nil {
return os.Args[0]
log.Panic(err)
}
return programPath
}
// check if default config file paths exist
func configFilePath() (string, bool) {
for _, path := range default_config_paths {
if _, err := os.Stat(path); err == nil {
return path, true
}
}
return "", false
}
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").IgnoreIfMissing(),
landlock.ROFiles("/dev/fd").IgnoreIfMissing(),
landlock.ROFiles("/dev/zero").IgnoreIfMissing(),
landlock.ROFiles("/dev/urandom").IgnoreIfMissing(),
landlock.ROFiles("/etc/localtime").IgnoreIfMissing(),
landlock.ROFiles("/proc/self/stat").IgnoreIfMissing(),
landlock.ROFiles("/proc/self/status").IgnoreIfMissing(),
landlock.ROFiles("/usr/share/locale").IgnoreIfMissing(),
landlock.ROFiles("/proc/self/cmdline").IgnoreIfMissing(),
landlock.ROFiles("/usr/share/zoneinfo").IgnoreIfMissing(),
landlock.ROFiles("/proc/sys/kernel/version").IgnoreIfMissing(),
landlock.ROFiles("/proc/sys/kernel/ngroups_max").IgnoreIfMissing(),
landlock.ROFiles("/proc/sys/kernel/cap_last_cap").IgnoreIfMissing(),
landlock.ROFiles("/proc/sys/vm/overcommit_memory").IgnoreIfMissing(),
landlock.RWFiles("/dev/log").IgnoreIfMissing(),
landlock.RWFiles("/dev/null").IgnoreIfMissing(),
landlock.RWFiles("/dev/full").IgnoreIfMissing(),
landlock.RWFiles("/proc/self/fd").IgnoreIfMissing(),
))
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")
// only allow standard stdio operation, file reading, networking, and exec
pledgeOrPanic("stdio rpath inet dns proc exec")
isDaemonProcess := len(os.Args) > 1 && os.Args[1] == daemonProcess
args := os.Args
if isDaemonProcess {
lock("boot-daemon")
// remove proc and exec if they are not needed
pledgeOrPanic("stdio rpath inet dns")
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"})
config := parser.String("c", "config", &argparse.Options{Required: true, Help: "Path of configuration file"})
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."})
err := parser.Parse(args)
@ -187,27 +47,14 @@ func main() {
return
}
if *printVerison {
fmt.Printf("wireproxy, version %s\n", version)
return
}
if *config == "" {
if path, config_exist := configFilePath(); config_exist {
*config = path
} else {
fmt.Println("configuration path is required")
return
}
}
if !*daemon {
lock("read-config")
// remove proc and exec if they are not needed
pledgeOrPanic("stdio rpath inet dns")
}
conf, err := wireproxy.ParseConfig(*config)
if err != nil {
log.Fatal(err)
log.Panic(err)
}
if *configTest {
@ -215,8 +62,6 @@ func main() {
return
}
lockNetwork(conf.Routines, info)
if isDaemonProcess {
os.Stdout, _ = os.Open(os.DevNull)
os.Stderr, _ = os.Open(os.DevNull)
@ -224,8 +69,14 @@ func main() {
}
if *daemon {
args[0] = daemonProcess
cmd := exec.Command(exePath, args...)
programPath, err := os.Executable()
if err != nil {
programPath = args[0]
}
newArgs := []string{daemonProcess}
newArgs = append(newArgs, args[1:]...)
cmd := exec.Command(programPath, newArgs...)
err = cmd.Start()
if err != nil {
fmt.Println(err.Error())
@ -233,36 +84,17 @@ func main() {
return
}
// 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
}
// no file access is allowed from now on, only networking
pledgeOrPanic("stdio inet dns")
lock("ready")
tun, err := wireproxy.StartWireguard(conf.Device, logLevel)
tnet, err := wireproxy.StartWireguard(conf.Device)
if err != nil {
log.Fatal(err)
log.Panic(err)
}
for _, spawner := range conf.Routines {
go spawner.SpawnRoutine(tun)
go spawner.SpawnRoutine(tnet)
}
tun.StartPingIPs()
if *info != "" {
go func() {
err := http.ListenAndServe(*info, tun)
if err != nil {
panic(err)
}
}()
}
<-ctx.Done()
select {} // sleep eternally
}

271
config.go
View file

@ -5,32 +5,23 @@ import (
"encoding/hex"
"errors"
"net"
"os"
"strings"
"github.com/go-ini/ini"
"net/netip"
"golang.zx2c4.com/go118/netip"
)
type PeerConfig struct {
PublicKey string
PreSharedKey string
Endpoint *string
KeepAlive int
AllowedIPs []netip.Prefix
}
// DeviceConfig contains the information to initiate a wireguard connection
type DeviceConfig struct {
SecretKey string
Endpoint []netip.Addr
Peers []PeerConfig
DNS []netip.Addr
MTU int
ListenPort *int
CheckAlive []netip.Addr
CheckAliveInterval int
SelfSecretKey string
SelfEndpoint []netip.Addr
PeerPublicKey string
PeerEndpoint string
DNS []netip.Addr
KeepAlive int
PreSharedKey string
MTU int
}
type TCPClientTunnelConfig struct {
@ -38,10 +29,6 @@ type TCPClientTunnelConfig struct {
Target string
}
type STDIOTunnelConfig struct {
Target string
}
type TCPServerTunnelConfig struct {
ListenPort int
Target string
@ -53,12 +40,6 @@ type Socks5Config struct {
Password string
}
type HTTPConfig struct {
BindAddress string
Username string
Password string
}
type Configuration struct {
Device *DeviceConfig
Routines []RoutineSpawner
@ -69,18 +50,6 @@ func parseString(section *ini.Section, keyName string) (string, error) {
if key == nil {
return "", errors.New(keyName + " should not be empty")
}
value := key.String()
if strings.HasPrefix(value, "$") {
if strings.HasPrefix(value, "$$") {
return strings.Replace(value, "$$", "$", 1), nil
}
var ok bool
value, ok = os.LookupEnv(strings.TrimPrefix(value, "$"))
if !ok {
return "", errors.New(keyName + " references unset environment variable " + key.String())
}
return value, nil
}
return key.String(), nil
}
@ -135,21 +104,14 @@ func encodeBase64ToHex(key string) (string, error) {
}
func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
key, err := parseString(section, keyName)
if err != nil {
if strings.Contains(err.Error(), "should not be empty") {
return []netip.Addr{}, nil
}
return nil, err
key := section.Key(keyName)
if key == nil {
return []netip.Addr{}, nil
}
keys := strings.Split(key, ",")
var ips = make([]netip.Addr, 0, len(keys))
for _, str := range keys {
ips := []netip.Addr{}
for _, str := range key.StringsWithShadows(",") {
str = strings.TrimSpace(str)
if len(str) == 0 {
continue
}
ip, err := netip.ParseAddr(str)
if err != nil {
return nil, err
@ -160,59 +122,24 @@ func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
}
func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
key, err := parseString(section, keyName)
if err != nil {
if strings.Contains(err.Error(), "should not be empty") {
return []netip.Addr{}, nil
}
return nil, err
key := section.Key(keyName)
if key == nil {
return []netip.Addr{}, nil
}
keys := strings.Split(key, ",")
var ips = make([]netip.Addr, 0, len(keys))
for _, str := range keys {
str = strings.TrimSpace(str)
if len(str) == 0 {
continue
}
if addr, err := netip.ParseAddr(str); err == nil {
ips = append(ips, addr)
} else {
prefix, err := netip.ParsePrefix(str)
if err != nil {
return nil, err
}
addr := prefix.Addr()
ips = append(ips, addr)
}
}
return ips, nil
}
func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) {
key, err := parseString(section, "AllowedIPs")
if err != nil {
if strings.Contains(err.Error(), "should not be empty") {
return []netip.Prefix{}, nil
}
return nil, err
}
keys := strings.Split(key, ",")
var ips = make([]netip.Prefix, 0, len(keys))
for _, str := range keys {
str = strings.TrimSpace(str)
if len(str) == 0 {
continue
}
ips := []netip.Addr{}
for _, str := range key.StringsWithShadows(",") {
prefix, err := netip.ParsePrefix(str)
if err != nil {
return nil, err
}
ips = append(ips, prefix)
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
}
@ -247,13 +174,13 @@ func ParseInterface(cfg *ini.File, device *DeviceConfig) error {
return err
}
device.Endpoint = address
device.SelfEndpoint = address
privKey, err := parseBase64KeyToHex(section, "PrivateKey")
if err != nil {
return err
}
device.SecretKey = privKey
device.SelfSecretKey = privKey
dns, err := parseNetIP(section, "DNS")
if err != nil {
@ -269,87 +196,49 @@ 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
}
// ParsePeers parses the [Peer] section and extract the information into `peers`
func ParsePeers(cfg *ini.File, peers *[]PeerConfig) error {
// ParsePeer parses the [Peer] section and extract the information into `device`
func ParsePeer(cfg *ini.File, device *DeviceConfig) error {
sections, err := cfg.SectionsByName("Peer")
if len(sections) < 1 || err != nil {
return errors.New("at least one [Peer] is expected")
if len(sections) != 1 || err != nil {
return errors.New("one and only one [Peer] is expected")
}
section := sections[0]
for _, section := range sections {
peer := PeerConfig{
PreSharedKey: "0000000000000000000000000000000000000000000000000000000000000000",
KeepAlive: 0,
}
decoded, err := parseBase64KeyToHex(section, "PublicKey")
if err != nil {
return err
}
device.PeerPublicKey = decoded
decoded, err := parseBase64KeyToHex(section, "PublicKey")
if sectionKey, err := section.GetKey("PreSharedKey"); err == nil {
value, err := encodeBase64ToHex(sectionKey.String())
if err != nil {
return err
}
peer.PublicKey = decoded
device.PreSharedKey = value
}
if sectionKey, err := section.GetKey("PreSharedKey"); err == nil {
value, err := encodeBase64ToHex(sectionKey.String())
if err != nil {
return err
}
peer.PreSharedKey = value
}
decoded, err = parseString(section, "Endpoint")
if err != nil {
return err
}
decoded, err = resolveIPPAndPort(decoded)
if err != nil {
return err
}
device.PeerEndpoint = decoded
if sectionKey, err := section.GetKey("Endpoint"); err == nil {
value := sectionKey.String()
decoded, err = resolveIPPAndPort(strings.ToLower(value))
if err != nil {
return err
}
peer.Endpoint = &decoded
}
if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil {
value, err := sectionKey.Int()
if err != nil {
return err
}
peer.KeepAlive = value
}
peer.AllowedIPs, err = parseAllowedIPs(section)
if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil {
value, err := sectionKey.Int()
if err != nil {
return err
}
*peers = append(*peers, peer)
device.KeepAlive = value
}
return nil
}
@ -370,17 +259,6 @@ 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{}
@ -417,24 +295,6 @@ 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 {
@ -458,9 +318,8 @@ func parseRoutinesConfig(routines *[]RoutineSpawner, cfg *ini.File, sectionName
// ParseConfig takes the path of a configuration file and parses it into Configuration
func ParseConfig(path string) (*Configuration, error) {
iniOpt := ini.LoadOptions{
Insensitive: true,
AllowShadows: true,
AllowNonUniqueSections: true,
Insensitive: true,
AllowShadows: true,
}
cfg, err := ini.LoadSources(iniOpt, path)
@ -469,7 +328,9 @@ func ParseConfig(path string) (*Configuration, error) {
}
device := &DeviceConfig{
MTU: 1420,
PreSharedKey: "0000000000000000000000000000000000000000000000000000000000000000",
KeepAlive: 0,
MTU: 1420,
}
root := cfg.Section("")
@ -487,23 +348,18 @@ func ParseConfig(path string) (*Configuration, error) {
return nil, err
}
err = ParsePeers(wgCfg, &device.Peers)
err = ParsePeer(wgCfg, device)
if err != nil {
return nil, err
}
var routinesSpawners []RoutineSpawner
routinesSpawners := []RoutineSpawner{}
err = parseRoutinesConfig(&routinesSpawners, cfg, "TCPClientTunnel", parseTCPClientTunnelConfig)
if err != nil {
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
@ -514,11 +370,6 @@ 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,

View file

@ -1,87 +0,0 @@
package wireproxy
import (
"github.com/go-ini/ini"
"testing"
)
func loadIniConfig(config string) (*ini.File, error) {
iniOpt := ini.LoadOptions{
Insensitive: true,
AllowShadows: true,
AllowNonUniqueSections: true,
}
return ini.LoadSources(iniOpt, []byte(config))
}
func TestWireguardConfWithoutSubnet(t *testing.T) {
const config = `
[Interface]
PrivateKey = LAr1aNSNF9d0MjwUgAVC4020T0N/E5NUtqVv5EnsSz0=
Address = 10.5.0.2
DNS = 1.1.1.1
[Peer]
PublicKey = e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 94.140.11.15:51820
PersistentKeepalive = 25`
var cfg DeviceConfig
iniData, err := loadIniConfig(config)
if err != nil {
t.Fatal(err)
}
err = ParseInterface(iniData, &cfg)
if err != nil {
t.Fatal(err)
}
}
func TestWireguardConfWithSubnet(t *testing.T) {
const config = `
[Interface]
PrivateKey = LAr1aNSNF9d0MjwUgAVC4020T0N/E5NUtqVv5EnsSz0=
Address = 10.5.0.2/23
DNS = 1.1.1.1
[Peer]
PublicKey = e8LKAc+f9xEzq9Ar7+MfKRrs+gZ/4yzvpRJLRJ/VJ1w=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 94.140.11.15:51820
PersistentKeepalive = 25`
var cfg DeviceConfig
iniData, err := loadIniConfig(config)
if err != nil {
t.Fatal(err)
}
err = ParseInterface(iniData, &cfg)
if err != nil {
t.Fatal(err)
}
}
func TestWireguardConfWithManyAddress(t *testing.T) {
const config = `
[Interface]
PrivateKey = mBsVDahr1XIu9PPd17UmsDdB6E53nvmS47NbNqQCiFM=
Address = 100.96.0.190,2606:B300:FFFF:fe8a:2ac6:c7e8:b021:6f5f/128
DNS = 198.18.0.1,198.18.0.2
[Peer]
PublicKey = SHnh4C2aDXhp1gjIqceGhJrhOLSeNYcqWLKcYnzj00U=
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = 192.200.144.22:51820`
var cfg DeviceConfig
iniData, err := loadIniConfig(config)
if err != nil {
t.Fatal(err)
}
err = ParseInterface(iniData, &cfg)
if err != nil {
t.Fatal(err)
}
}

14
docker/Dockerfile Normal file
View file

@ -0,0 +1,14 @@
FROM golang:alpine AS go-build
RUN apk --no-cache add --update git
RUN git clone https://github.com/octeep/wireproxy.git
RUN cd ./wireproxy && go build ./cmd/wireproxy
FROM alpine:latest
RUN apk upgrade
COPY --from=go-build /go/wireproxy/wireproxy /usr/bin/
VOLUME [ "/etc/wireproxy"]
ENTRYPOINT [ "/usr/bin/wireproxy", "/etc/wireproxy/config" ]

10
docker/Makefile Normal file
View file

@ -0,0 +1,10 @@
build:
docker build -t wireproxy .
run:
docker run \
--rm --tty --interactive \
--name=wireproxy \
--publish 2534:2534 \
--volume "${PWD}/config:/etc/wireproxy/config:ro" \
wireproxy

12
docker/config Normal file
View file

@ -0,0 +1,12 @@
[Interface]
Address = ###Interface - Address###
PrivateKey = ###Interface - PrivateKey###
DNS = ###Interface - DNS###
[Peer]
PublicKey = ###Peer - PublicKey###
Endpoint = ###Peer - Endpoint###
# Socks5 create a socks5 proxy on your LAN, and any traffic would be routed via wireguard
[Socks5]
BindAddress = 0.0.0.0:2534

37
go.mod
View file

@ -1,26 +1,25 @@
module github.com/pufferffish/wireproxy
module github.com/octeep/wireproxy
go 1.21.1
toolchain go1.21.6
go 1.17
require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/akamensky/argparse v1.4.0
github.com/go-ini/ini v1.67.0
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a
github.com/things-go/go-socks5 v0.0.5
golang.org/x/net v0.33.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
suah.dev/protect v1.2.3
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/go-ini/ini v1.66.4
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d
golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178
golang.zx2c4.com/wireguard/tun/netstack v0.0.0-20220310012736-ae6bc4dd64e1
gvisor.dev/gvisor v0.0.0-20211020211948-f76a604701b6
)
require (
github.com/google/btree v1.1.2 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sys v0.28.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
github.com/akamensky/argparse v1.3.1 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
suah.dev/protect v1.2.0 // indirect
)

1039
go.sum

File diff suppressed because it is too large Load diff

166
http.go
View file

@ -1,166 +0,0 @@
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 == "" {
return http.StatusProxyAuthRequired, fmt.Errorf("%s", http.StatusText(http.StatusProxyAuthRequired))
}
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")
}
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 {
resp := responseWith(req, code)
if code == http.StatusProxyAuthRequired {
resp.Header.Set("Proxy-Authenticate", "Basic realm=\"Proxy\"")
}
_ = resp.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() {
defer conn.Close()
defer peer.Close()
_, _ = io.Copy(conn, peer)
}()
go func() {
defer conn.Close()
defer peer.Close()
_, _ = io.Copy(peer, conn)
}()
}
// 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)
}
}

10
net.go
View file

@ -3,8 +3,8 @@
package wireproxy
import (
"golang.zx2c4.com/go118/netip"
"net"
"net/netip"
)
func TCPAddrFromAddrPort(addr netip.AddrPort) *net.TCPAddr {
@ -14,3 +14,11 @@ func TCPAddrFromAddrPort(addr netip.AddrPort) *net.TCPAddr {
Port: int(addr.Port()),
}
}
func UDPAddrFromAddrPort(addr netip.AddrPort) *net.UDPAddr {
return &net.UDPAddr{
IP: addr.Addr().AsSlice(),
Zone: addr.Addr().Zone(),
Port: int(addr.Port()),
}
}

View file

@ -1,21 +0,0 @@
# Running wireproxy with rc.d
If you're on a rc.d-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 `/bin/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` file from this directory to `/usr/local/etc/rc.d`.
2. If necessary, customize the unit.
Edit the parts with `procname`, `command`, `wireproxy_conf` to point to the executable and the configuration file.
4. Add the following lines to `/etc/rc.conf` to enable wireproxy
`wireproxy_enable="YES"`
5. Start wireproxy service and check status
```
sudo service wireproxy start
sudo service wireproxy status
```

View file

@ -1,30 +0,0 @@
#!/bin/sh
#
# PROVIDE: wireproxy
# REQUIRE: DAEMON
# KEYWORD: nojail
#
#
# Add the following lines to /etc/rc.conf to enable wireproxy:
#
#wireproxy_enable="YES"
#
. /etc/rc.subr
name=wireproxy
rcvar=wireproxy_enable
load_rc_config $name
procname="/bin/wireproxy"
wireproxy_enable=${wireproxy_enable:-"NO"}
wireproxy_bin=/bin/wireproxy
wireproxy_conf=/etc/wireproxy.conf
command=${wireproxy_bin}
command_args="-s -d -c ${wireproxy_conf}"
run_rc_command "$1"

View file

@ -1,34 +1,19 @@
package wireproxy
import (
"bytes"
"context"
srand "crypto/rand"
"crypto/subtle"
"encoding/binary"
"encoding/json"
"errors"
"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"
"sync"
"time"
"github.com/things-go/go-socks5"
"github.com/things-go/go-socks5/bufferpool"
"net/netip"
"github.com/armon/go-socks5"
"golang.zx2c4.com/go118/netip"
"golang.zx2c4.com/wireguard/tun/netstack"
)
@ -43,13 +28,8 @@ type CredentialValidator struct {
// 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
PingRecordLock *sync.Mutex
tnet *netstack.Net
systemDNS bool
}
// RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed
@ -57,21 +37,39 @@ type RoutineSpawner interface {
SpawnRoutine(vt *VirtualTun)
}
type addressPort struct {
address string
port uint16
}
// 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.
// ResolveAddrPort resolves a hostname and returns an AddrPort.
// DNS traffic may or may not be routed depending on VirtualTun's setting
func (d VirtualTun) ResolveAddrPort(saddr string) (*netip.AddrPort, error) {
name, sport, err := net.SplitHostPort(saddr)
if err != nil {
return nil, err
}
addr, err := d.ResolveAddrWithContext(context.Background(), name)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errors.New("port must be numeric")}
}
addrPort := netip.AddrPortFrom(*addr, uint16(port))
return &addrPort, nil
}
// ResolveAddrPort resolves a hostname and returns an AddrPort.
// DNS traffic may or may not be routed depending on VirtualTun's setting
func (d VirtualTun) ResolveAddrWithContext(ctx context.Context, name string) (*netip.Addr, error) {
addrs, err := d.LookupAddr(ctx, name)
@ -103,7 +101,7 @@ func (d VirtualTun) ResolveAddrWithContext(ctx context.Context, name string) (*n
return &addr, nil
}
// Resolve resolves a hostname and returns an IP.
// ResolveAddrPort resolves a hostname and returns an IP.
// DNS traffic may or may not be routed depending on VirtualTun's setting
func (d VirtualTun) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
addr, err := d.ResolveAddrWithContext(ctx, name)
@ -114,68 +112,21 @@ func (d VirtualTun) Resolve(ctx context.Context, name string) (context.Context,
return ctx, addr.AsSlice(), nil
}
func parseAddressPort(endpoint string) (*addressPort, error) {
name, sport, err := net.SplitHostPort(endpoint)
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sport)
if err != nil || port < 0 || port > 65535 {
return nil, &net.OpError{Op: "dial", Err: errors.New("port must be numeric")}
}
return &addressPort{address: name, port: uint16(port)}, nil
}
func (d VirtualTun) resolveToAddrPort(endpoint *addressPort) (*netip.AddrPort, error) {
addr, err := d.ResolveAddrWithContext(context.Background(), endpoint.address)
if err != nil {
return nil, err
}
addrPort := netip.AddrPortFrom(*addr, endpoint.port)
return &addrPort, nil
}
// SpawnRoutine spawns a socks5 server.
// Spawns a socks5 server.
func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) {
var authMethods []socks5.Authenticator
conf := &socks5.Config{Dial: vt.tnet.DialContext, Resolver: vt}
if username := config.Username; username != "" {
authMethods = append(authMethods, socks5.UserPassAuthenticator{
Credentials: socks5.StaticCredentials{username: config.Password},
})
} else {
authMethods = append(authMethods, socks5.NoAuthAuthenticator{})
validator := CredentialValidator{username: username}
validator.password = config.Password
conf.Credentials = validator
}
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)
}
}
// 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
server, err := socks5.New(conf)
if err != nil {
log.Panic(err)
}
if err := server.ListenAndServe("tcp", config.BindAddress); err != nil {
log.Fatal(err)
log.Panic(err)
}
}
@ -187,283 +138,82 @@ func (c CredentialValidator) Valid(username, password string) bool {
return u&p == 1
}
// connForward copy data from `from` to `to`
func connForward(from io.ReadWriteCloser, to io.ReadWriteCloser) {
defer from.Close()
defer to.Close()
_, err := io.Copy(to, from)
// 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)
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`
func tcpClientForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
target, err := vt.resolveToAddrPort(raddr)
if err != nil {
errorLogger.Printf("TCP Server Tunnel to %s: %s\n", target, err.Error())
return
}
tcpAddr := TCPAddrFromAddrPort(*target)
sconn, err := vt.Tnet.DialTCP(tcpAddr)
func tcpClientForward(tnet *netstack.Net, target *net.TCPAddr, conn net.Conn) {
sconn, err := tnet.DialTCP(target)
if err != nil {
errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error())
return
}
go connForward(sconn, conn)
go connForward(conn, sconn)
go connForward(1024, sconn, conn)
go connForward(1024, conn, sconn)
}
// 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 connForward(os.Stdin, sconn)
go connForward(sconn, stdout)
}
// SpawnRoutine spawns a local TCP server which acts as a proxy to the specified target
// Spawns a local TCP server which acts as a proxy to the specified target
func (conf *TCPClientTunnelConfig) SpawnRoutine(vt *VirtualTun) {
raddr, err := parseAddressPort(conf.Target)
raddr, err := vt.ResolveAddrPort(conf.Target)
if err != nil {
log.Fatal(err)
log.Panic(err)
}
tcpAddr := TCPAddrFromAddrPort(*raddr)
server, err := net.ListenTCP("tcp", conf.BindAddress)
if err != nil {
log.Fatal(err)
log.Panic(err)
}
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
log.Panic(err)
}
go tcpClientForward(vt, raddr, conn)
go tcpClientForward(vt.tnet, tcpAddr, conn)
}
}
// 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)
func tcpServerForward(target *net.TCPAddr, conn net.Conn) {
sconn, err := net.DialTCP("tcp", nil, target)
if err != nil {
errorLogger.Printf("TCP Server Tunnel to %s: %s\n", target, err.Error())
return
}
tcpAddr := TCPAddrFromAddrPort(*target)
sconn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
errorLogger.Printf("TCP Server Tunnel to %s: %s\n", target, err.Error())
return
}
go connForward(sconn, conn)
go connForward(conn, sconn)
go connForward(1024, sconn, conn)
go connForward(1024, conn, sconn)
}
// SpawnRoutine spawns a TCP server on wireguard which acts as a proxy to the specified target
// Spawns a TCP server on wireguard which acts as a proxy to the specified target
func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
raddr, err := parseAddressPort(conf.Target)
raddr, err := vt.ResolveAddrPort(conf.Target)
if err != nil {
log.Fatal(err)
log.Panic(err)
}
tcpAddr := TCPAddrFromAddrPort(*raddr)
addr := &net.TCPAddr{Port: conf.ListenPort}
server, err := vt.Tnet.ListenTCP(addr)
server, err := vt.tnet.ListenTCP(addr)
if err != nil {
log.Fatal(err)
log.Panic(err)
}
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
log.Panic(err)
}
go tcpServerForward(vt, raddr, conn)
go tcpServerForward(tcpAddr, 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.PingRecordLock.Lock()
d.PingRecord[addr.String()] = uint64(time.Now().Unix())
d.PingRecordLock.Unlock()
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)
}
}()
}

View file

@ -1,35 +0,0 @@
# 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
curl https://raw.githubusercontent.com/pufferffish/wireproxy/master/systemd/wireproxy.service | sudo tee /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).

View file

@ -1,46 +0,0 @@
[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 @sandbox
[Install]
WantedBy=multi-user.target

View file

@ -1,28 +0,0 @@
#!/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

25
util.go
View file

@ -1,25 +0,0 @@
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

@ -1,13 +1,9 @@
package wireproxy
import (
"bytes"
"fmt"
"sync"
"net/netip"
"github.com/MakeNowJust/heredoc/v2"
"golang.zx2c4.com/go118/netip"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun/netstack"
@ -15,63 +11,38 @@ import (
// DeviceSetting contains the parameters for setting up a tun interface
type DeviceSetting struct {
IpcRequest string
DNS []netip.Addr
DeviceAddr []netip.Addr
MTU int
ipcRequest string
dns []netip.Addr
deviceAddr []netip.Addr
mtu int
}
// CreateIPCRequest serialize the config into an IPC request and DeviceSetting
func CreateIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
var request bytes.Buffer
// serialize the config into an IPC request and DeviceSetting
func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
request := fmt.Sprintf(`private_key=%s
public_key=%s
endpoint=%s
persistent_keepalive_interval=%d
preshared_key=%s
allowed_ip=0.0.0.0/0`, conf.SelfSecretKey, conf.PeerPublicKey, conf.PeerEndpoint, conf.KeepAlive, conf.PreSharedKey)
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
persistent_keepalive_interval=%d
preshared_key=%s
`),
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 {
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", ip.String()))
}
} else {
request.WriteString(heredoc.Doc(`
allowed_ip=0.0.0.0/0
allowed_ip=::0/0
`))
}
}
setting := &DeviceSetting{IpcRequest: request.String(), DNS: conf.DNS, DeviceAddr: conf.Endpoint, MTU: conf.MTU}
setting := &DeviceSetting{ipcRequest: request, dns: conf.DNS, deviceAddr: conf.SelfEndpoint, mtu: conf.MTU}
return setting, nil
}
// StartWireguard creates a tun interface on netstack given a configuration
func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
setting, err := CreateIPCRequest(conf)
func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
setting, err := createIPCRequest(conf)
if err != nil {
return nil, err
}
tun, tnet, err := netstack.CreateNetTUN(setting.DeviceAddr, setting.DNS, setting.MTU)
tun, tnet, err := netstack.CreateNetTUN(setting.deviceAddr, setting.dns, setting.mtu)
if err != nil {
return nil, err
}
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, ""))
err = dev.IpcSet(setting.IpcRequest)
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
err = dev.IpcSet(setting.ipcRequest)
if err != nil {
return nil, err
}
@ -82,11 +53,7 @@ func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
}
return &VirtualTun{
Tnet: tnet,
Dev: dev,
Conf: conf,
SystemDNS: len(setting.DNS) == 0,
PingRecord: make(map[string]uint64),
PingRecordLock: new(sync.Mutex),
tnet: tnet,
systemDNS: len(setting.dns) == 0,
}, nil
}