47 Commits
udp ... master

Author SHA1 Message Date
Emilien Devos
9dad356bee fix: issue with omiting endpoint (#157)
fixes #156
2025-02-19 17:21:17 +00:00
lexandr0s
288687b873 Add hint to run Wireproxy as system daemon in rc.d-based system (#164)
* Add hint for rc.d service

* Update README.md

---------

Co-authored-by: root <root@prox1.example.com>
2025-02-18 12:27:18 +00:00
pufferffish
f17557487d add IPRoyal referral link 2025-02-18 12:26:13 +00:00
dependabot[bot]
a57972e756 Bump golang.org/x/net from 0.23.0 to 0.33.0 (#160)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-31 16:26:26 +00:00
Christian Speckner
7bb1be2d20 Make sure that closing one direction closes the other, too. (#159)
* Make sure that closing one direction closes the other, too.

* Pacify linter.
2025-01-31 16:09:16 +00:00
Lars Gerber
47cd451c80 docs: add syntax highlighting and Go install command (#158)
* docs: add syntax language for codeblocks

* docs: add install instructions for Go
2025-01-31 15:59:57 +00:00
Takanori Hirano
d710683181 Fix PingRecord race condition (#149) 2024-12-26 17:20:01 +00:00
Yaroslav
3098c397e7 Update README.md (#150)
Fixed curl example command to work well with sudo
2024-12-26 17:19:42 +00:00
dependabot[bot]
3e6e5a61f0 Bump golang.org/x/crypto from 0.21.0 to 0.31.0 (#146)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.21.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.21.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 00:24:48 +00:00
Luiz Henrique Gomes Palácio
4a564b5ea2 Fix HTTP proxy authentication to support both preemptive and challenge-response auth (#134) 2024-09-04 04:21:40 +08:00
Niko
5b7f822f17 Fix broken sandboxing resulting in SIGABRT (#136) 2024-09-04 04:08:52 +08:00
Artem Russkikh
3729bced93 Update README (#137) 2024-09-04 04:08:20 +08:00
Nicholas
cb1f39b3e5 Support env lookup for some values (#122)
Co-authored-by: pufferfish <74378430+pufferffish@users.noreply.github.com>
2024-07-22 15:38:19 +01:00
pufferffish
f8a5d70c71 make device setting fields public 2024-07-22 15:38:07 +01:00
Amirhossein Shaerpour
42a097d490 change - add default configuration paths (#121) 2024-07-22 15:11:26 +01:00
pufferffish
ff99bfd4a6 fix config parsing 2024-07-22 15:10:12 +01:00
dependabot[bot]
e749217090 Bump golang.org/x/net from 0.21.0 to 0.23.0 (#113)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 15:21:34 +01:00
pufferfish
6ab7069908 fix landlock restriction while files do not exists (#114) 2024-04-19 15:15:09 +01:00
pufferfish
a6797166eb Limit wireproxy's permissions with landlock (#108)
* Limit wireproxy's permissions with landlock

* Show better debug message

* Fix crash when info is null

* Fix crash when landlock ABI is outdated

* remove /dev/std{in,out,err} from landlock restriction
2024-04-13 02:38:48 +01:00
pufferfish
eccf83a0cf Add health status endpoint (#107)
* implement metric endpoint

* implement ICMP ping

* fix linting

* fix IPv6 pings

* Add documentation for --info
2024-04-12 05:24:16 +01:00
pufferfish
54cedea2e4 Update LICENSE 2024-04-10 03:42:15 +01:00
pufferffish
bbde9cd266 bump version 2024-04-06 20:21:37 +01:00
J. Dekker
4f066d050a systemd: tight sandboxing (#103)
wireproxy needs very little permissions, we can restrict it to basically
nothing. DynamicUser means the system will generate a UID on demand
for service, also CAP_NET_BIND_SERVICE can be used to allow this user
to bind to a port < 1024 if desired. Also LoadCredential lets us read
a file with tight permissions i.e. root:root 0400 and pass it to only
wireproxy in an ephemeral and constrained manner.

Signed-off-by: J. Dekker <jdek@itanimul.li>
2024-03-18 16:42:03 +00:00
pufferfish
c710def46d Update README.md to mention Amnezia fork 2024-02-20 19:40:19 +00:00
Evsyukov Denis
a2d7aecb6f fix: improvements in memory consumption (#100)
* fix: must close the connection after processing

I think it should help to close #80

* feat: migration to github.com/things-go/go-socks5

- preallocate config slices
- not used interfaces in consumer
- do not allocate new variables in loops

* feat: close connection after full processing

* feat: correct process sigint signal

* feat: improve build system

* fix: http proxy

* feat: update golangci-lint-action to v3.7.0

* feat: correct process routines

* fix: close http conn correctly

* feat: update golangci-lint-action to v4

* fix: goreleaser used clean now
2024-02-11 12:45:43 +00:00
guangwu
49f568810c fix: typo (#93)
Signed-off-by: guoguangwu <guoguangwu@magic-shield.com>
2024-02-09 10:57:11 +00:00
dependabot[bot]
af973227e9 Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#98)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 11:33:21 +08:00
Jonah Brüchert
0f4d79dea8 Fix build on go 1.21 by updating wireguard-go (#91) 2023-10-18 03:21:11 +01:00
Johan
d1a455e87d Remove interface address subnet length check (#86)
Resolves #82
2023-08-17 23:10:38 +01:00
François Guerraz
5f76f777d5 Use io.Copy instead of io.CopyBuffer (#85) 2023-08-17 22:40:19 +01:00
Jonah Brüchert
d2f7768c51 Update dependencies (#79)
* Update dependencies

gvisor was not updated to the latest version, because it did not
compile.

* actions: Update go version to 1.20

* docker: Update go version to 1.20
2023-08-17 11:42:52 +01:00
villepeh
05c8493981 Provide an example systemd service file (#77)
* Create README.md

* Create wireproxy.service

* Update README.md
2023-07-15 20:39:33 +01:00
Jonah Brüchert
31e6afd75d Allow to use wireproxy as endpoint (#74)
This is useful for running wireguard endpoints in unprivileged
containers.
2023-07-15 20:39:24 +01:00
François Guerraz
aa207764b2 Implement STDIOTunnel (#70) 2023-06-14 03:17:32 +01:00
Mike Dacre
edf63253df Update README with Firefox VPN tabs instructions (#73)
* Update README with Firefox VPN tabs instructions

* move instructions to another file

---------

Co-authored-by: octeep <github@bandersnatch.anonaddy.com>
2023-06-14 03:04:06 +01:00
pufferfish
b0a405a075 Update wireproxy.yml 2023-05-22 18:05:32 +01:00
Wayback Archiver
25e6568f4d Add support for http proxy (#68)
* Add support for http proxy

* add test case for http proxy

---------

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

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

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

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

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

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

View File

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

View File

@@ -28,26 +28,26 @@ jobs:
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: 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 - name: Login to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
submodules: recursive submodules: recursive
# Needed for buildx gha cache to work # Needed for buildx gha cache to work
- name: Expose GitHub Runtime - name: Expose GitHub Runtime
uses: crazy-max/ghaction-github-runtime@v2 uses: crazy-max/ghaction-github-runtime@v3
- name: Build container - name: Build container
env: env:
@@ -59,6 +59,7 @@ jobs:
docker buildx build \ docker buildx build \
--platform "$BUILD_PLATFORMS" \ --platform "$BUILD_PLATFORMS" \
--tag "$CONTAINER_NAME:$CONTAINER_TAG" \ --tag "$CONTAINER_NAME:$CONTAINER_TAG" \
--tag "$CONTAINER_NAME:$GITHUB_SHA" \
--label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \ --label "org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}" \
--label "org.opencontainers.image.documentation=${{ 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.url=${{ github.server_url }}/${{ github.repository }}/packages" \

View File

@@ -6,6 +6,8 @@ on:
pull_request: pull_request:
branches: branches:
- '**' - '**'
workflow_dispatch:
permissions: permissions:
contents: read contents: read
jobs: jobs:
@@ -13,9 +15,11 @@ jobs:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
with: with:
go-version: '1.19' go-version: '1.21'
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: golangci-lint - 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
View 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

View File

@@ -24,21 +24,21 @@ jobs:
- name: Git clone WireProxy - name: Git clone WireProxy
run: | run: |
git clone https://github.com/octeep/wireproxy.git ${{ env.workdir }} git clone https://github.com/pufferffish/wireproxy.git ${{ env.workdir }}
cp ./.github/wireproxy-releaser.yml ${{ env.workdir }}/.goreleaser.yml cp ./.github/wireproxy-releaser.yml ${{ env.workdir }}/.goreleaser.yml
- name: Set up GoReleaser - name: Set up GoReleaser
uses: actions/setup-go@v2 uses: actions/setup-go@v5
with: with:
go-version: "1.19" go-version: "1.21"
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2 uses: goreleaser/goreleaser-action@v5
with: with:
distribution: goreleaser distribution: goreleaser
workdir: ${{ env.workdir }} workdir: ${{ env.workdir }}
version: latest version: latest
args: release --rm-dist args: release --clean
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

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

View File

@@ -1,10 +1,10 @@
# Start by building the application. # Start by building the application.
FROM golang:1.18 as build FROM docker.io/golang:1.21 as build
WORKDIR /usr/src/wireproxy WORKDIR /usr/src/wireproxy
COPY . . COPY . .
RUN CGO_ENABLED=0 go build ./cmd/wireproxy RUN make
# Now copy it into our base image. # Now copy it into our base image.
FROM gcr.io/distroless/static-debian11:nonroot FROM gcr.io/distroless/static-debian11:nonroot
@@ -14,6 +14,6 @@ VOLUME [ "/etc/wireproxy"]
ENTRYPOINT [ "/usr/bin/wireproxy" ] ENTRYPOINT [ "/usr/bin/wireproxy" ]
CMD [ "--config", "/etc/wireproxy/config" ] CMD [ "--config", "/etc/wireproxy/config" ]
LABEL org.opencontainers.image.title wireproxy LABEL org.opencontainers.image.title="wireproxy"
LABEL org.opencontainers.image.description "Wireguard client that exposes itself as a socks5 proxy" LABEL org.opencontainers.image.description="Wireguard client that exposes itself as a socks5 proxy"
LABEL org.opencontainers.image.licenses ISC LABEL org.opencontainers.image.licenses="ISC"

View File

@@ -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 Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above

View File

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

159
README.md
View File

@@ -1,17 +1,20 @@
# wireproxy # wireproxy
[![ISC licensed](https://img.shields.io/badge/license-ISC-blue)](./LICENSE) [![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) [![Build status](https://github.com/octeep/wireproxy/actions/workflows/build.yml/badge.svg)](https://github.com/octeep/wireproxy/actions)
[![Documentation](https://img.shields.io/badge/godoc-wireproxy-blue)](https://pkg.go.dev/github.com/octeep/wireproxy) [![Documentation](https://img.shields.io/badge/godoc-wireproxy-blue)](https://pkg.go.dev/github.com/octeep/wireproxy)
A wireguard client that exposes itself as a socks5 proxy or tunnels. A wireguard client that exposes itself as a socks5/http proxy or tunnels.
# What is this # What is this
`wireproxy` is a completely userspace application that connects to a wireguard peer, `wireproxy` is a completely userspace application that connects to a wireguard peer,
and exposes a socks5 proxy or tunnels on the machine. This can be useful if you need and exposes a socks5/http proxy or tunnels on the machine. This can be useful if you need
to connect to certain sites via a wireguard peer, but can't be bothered to setup a new network to connect to certain sites via a wireguard peer, but can't be bothered to setup a new network
interface for whatever reasons. interface for whatever reasons.
# Why you might want this # Why you might want this
- You simply want to use wireguard as a way to proxy some traffic. - You simply want to use wireguard as a way to proxy some traffic.
- You don't want root permission just to change wireguard settings. - You don't want root permission just to change wireguard settings.
@@ -20,21 +23,35 @@ 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 wireproxy is completely isolated from my network interfaces, and I don't need root to configure
anything. 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 # Feature
- TCP static routing for client and server - TCP static routing for client and server
- SOCKS5 proxy (currently only CONNECT is supported) - SOCKS5/HTTP proxy (currently only CONNECT is supported)
# TODO # TODO
- UDP Support in SOCKS5 - UDP Support in SOCKS5
- UDP static routing - UDP static routing
# Usage # Usage
```
./wireproxy -c [path to config] ```bash
./wireproxy [-c path to config]
``` ```
``` ```bash
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] [-n|--configtest]
Userspace wireguard client for proxying Userspace wireguard client for proxying
@@ -43,20 +60,36 @@ Arguments:
-h --help Print help information -h --help Print help information
-c --config Path of configuration file -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 -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 -n --configtest Configtest mode. Only check the configuration file for
validity. validity.
``` ```
# Build instruction # Build instruction
```
```bash
git clone https://github.com/octeep/wireproxy git clone https://github.com/octeep/wireproxy
cd wireproxy cd wireproxy
make make
``` ```
# Sample config file # 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 # 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: # of a wg-quick configuration. To understand what these fields mean, please refer to:
# https://wiki.archlinux.org/title/WireGuard#Persistent_configuration # https://wiki.archlinux.org/title/WireGuard#Persistent_configuration
@@ -65,6 +98,7 @@ make
Address = 10.200.200.2/32 # The subnet should be /32 and /128 for IPv4 and v6 respectively Address = 10.200.200.2/32 # The subnet should be /32 and /128 for IPv4 and v6 respectively
# MTU = 1420 (optional) # MTU = 1420 (optional)
PrivateKey = uCTIK+56CPyCvwJxmU5dBfuyJvPuSXAq1FzHdnIxe1Q= PrivateKey = uCTIK+56CPyCvwJxmU5dBfuyJvPuSXAq1FzHdnIxe1Q=
# PrivateKey = $MY_WIREGUARD_PRIVATE_KEY # Alternatively, reference environment variables
DNS = 10.200.200.1 DNS = 10.200.200.1
[Peer] [Peer]
@@ -89,6 +123,16 @@ Target = play.cubecraft.net:25565
ListenPort = 3422 ListenPort = 3422
Target = localhost:25545 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 creates a socks5 proxy on your LAN, and all traffic would be routed via wireguard.
[Socks5] [Socks5]
BindAddress = 127.0.0.1:25344 BindAddress = 127.0.0.1:25344
@@ -98,11 +142,22 @@ BindAddress = 127.0.0.1:25344
#Username = ... #Username = ...
# Avoid using spaces in the password field # Avoid using spaces in the password field
#Password = ... #Password = ...
# http creates a http proxy on your LAN, and all traffic would be routed via wireguard.
[http]
BindAddress = 127.0.0.1:25345
# HTTP authentication parameters, specifying username and password enables
# proxy authentication.
#Username = ...
# Avoid using spaces in the password field
#Password = ...
``` ```
Alternatively, if you already have a wireguard config, you can import it in the Alternatively, if you already have a wireguard config, you can import it in the
wireproxy config file like this: wireproxy config file like this:
```
```ini
WGConfig = <path to the wireguard config> WGConfig = <path to the wireguard config>
# Same semantics as above # Same semantics as above
@@ -118,7 +173,8 @@ WGConfig = <path to the wireguard config>
Having multiple peers is also supported. `AllowedIPs` would need to be specified Having multiple peers is also supported. `AllowedIPs` would need to be specified
such that wireproxy would know which peer to forward to. such that wireproxy would know which peer to forward to.
```
```ini
[Interface] [Interface]
Address = 10.254.254.40/32 Address = 10.254.254.40/32
PrivateKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX= PrivateKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=
@@ -149,10 +205,85 @@ ListenPort = 5080
Target = service-three.servicenet:80 Target = service-three.servicenet:80
``` ```
## Donation Wireproxy can also allow peers to connect to it:
<noscript><a href="https://liberapay.com/octeep/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
```ini
[Interface]
ListenPort = 5400
...
## Stargazers over time [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](https://starchart.cc/octeep/wireproxy.svg)](https://starchart.cc/octeep/wireproxy) [![Stargazers over time](https://starchart.cc/octeep/wireproxy.svg)](https://starchart.cc/octeep/wireproxy)

96
UseWithVPN.md Normal file
View File

@@ -0,0 +1,96 @@
# 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.

BIN
assets/iproyal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,37 +1,51 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"github.com/landlock-lsm/go-landlock/landlock"
"log" "log"
"net"
"net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"strconv"
"syscall"
"github.com/akamensky/argparse" "github.com/akamensky/argparse"
"github.com/octeep/wireproxy" "github.com/pufferffish/wireproxy"
"golang.zx2c4.com/wireguard/device"
"suah.dev/protect" "suah.dev/protect"
) )
// an argument to denote that this process was spawned by -d // an argument to denote that this process was spawned by -d
const daemonProcess = "daemon-process" const daemonProcess = "daemon-process"
var version = "1.0.5-dev" // default paths for wireproxy config file
var default_config_paths = []string {
"/etc/wireproxy/wireproxy.conf",
os.Getenv("HOME")+"/.config/wireproxy.conf",
}
// attempts to pledge and panic if it fails var version = "1.0.8-dev"
// this does nothing on non-OpenBSD systems
func pledgeOrPanic(promises string) { func panicIfError(err error) {
err := protect.Pledge(promises)
if err != nil { if err != nil {
log.Fatal(err) 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 // attempts to unveil and panic if it fails
// this does nothing on non-OpenBSD systems // this does nothing on non-OpenBSD systems
func unveilOrPanic(path string, flags string) { func unveilOrPanic(path string, flags string) {
err := protect.Unveil(path, flags) panicIfError(protect.Unveil(path, flags))
if err != nil {
log.Fatal(err)
}
} }
// get the executable path via syscalls or infer it from argv // get the executable path via syscalls or infer it from argv
@@ -43,27 +57,127 @@ func executablePath() string {
return programPath return programPath
} }
func main() { // 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() exePath := executablePath()
// OpenBSD
unveilOrPanic("/", "r") unveilOrPanic("/", "r")
unveilOrPanic(exePath, "x") unveilOrPanic(exePath, "x")
// only allow standard stdio operation, file reading, networking, and exec // only allow standard stdio operation, file reading, networking, and exec
// also remove unveil permission to lock unveil // also remove unveil permission to lock unveil
pledgeOrPanic("stdio rpath inet dns proc exec") 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")
isDaemonProcess := len(os.Args) > 1 && os.Args[1] == daemonProcess isDaemonProcess := len(os.Args) > 1 && os.Args[1] == daemonProcess
args := os.Args args := os.Args
if isDaemonProcess { if isDaemonProcess {
// remove proc and exec if they are not needed lock("boot-daemon")
pledgeOrPanic("stdio rpath inet dns")
args = []string{args[0]} args = []string{args[0]}
args = append(args, os.Args[2:]...) args = append(args, os.Args[2:]...)
} }
parser := argparse.NewParser("wireproxy", "Userspace wireguard client for proxying") parser := argparse.NewParser("wireproxy", "Userspace wireguard client for proxying")
config := parser.String("c", "config", &argparse.Options{Help: "Path of configuration file"}) config := parser.String("c", "config", &argparse.Options{Help: "Path of configuration file"})
silent := parser.Flag("s", "silent", &argparse.Options{Help: "Silent mode"})
daemon := parser.Flag("d", "daemon", &argparse.Options{Help: "Make wireproxy run in background"}) daemon := parser.Flag("d", "daemon", &argparse.Options{Help: "Make wireproxy run in background"})
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"}) printVerison := parser.Flag("v", "version", &argparse.Options{Help: "Print version"})
configTest := parser.Flag("n", "configtest", &argparse.Options{Help: "Configtest mode. Only check the configuration file for validity."}) configTest := parser.Flag("n", "configtest", &argparse.Options{Help: "Configtest mode. Only check the configuration file for validity."})
@@ -79,13 +193,16 @@ func main() {
} }
if *config == "" { if *config == "" {
if path, config_exist := configFilePath(); config_exist {
*config = path
} else {
fmt.Println("configuration path is required") fmt.Println("configuration path is required")
return return
} }
}
if !*daemon { if !*daemon {
// remove proc and exec if they are not needed lock("read-config")
pledgeOrPanic("stdio rpath inet dns")
} }
conf, err := wireproxy.ParseConfig(*config) conf, err := wireproxy.ParseConfig(*config)
@@ -98,6 +215,8 @@ func main() {
return return
} }
lockNetwork(conf.Routines, info)
if isDaemonProcess { if isDaemonProcess {
os.Stdout, _ = os.Open(os.DevNull) os.Stdout, _ = os.Open(os.DevNull)
os.Stderr, _ = os.Open(os.DevNull) os.Stderr, _ = os.Open(os.DevNull)
@@ -114,17 +233,36 @@ func main() {
return return
} }
// no file access is allowed from now on, only networking // Wireguard doesn't allow configuring which FD to use for logging
pledgeOrPanic("stdio inet dns") // 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 { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
for _, spawner := range conf.Routines { 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()
} }

161
config.go
View File

@@ -5,6 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"net" "net"
"os"
"strings" "strings"
"github.com/go-ini/ini" "github.com/go-ini/ini"
@@ -15,7 +16,7 @@ import (
type PeerConfig struct { type PeerConfig struct {
PublicKey string PublicKey string
PreSharedKey string PreSharedKey string
Endpoint string Endpoint *string
KeepAlive int KeepAlive int
AllowedIPs []netip.Prefix AllowedIPs []netip.Prefix
} }
@@ -27,6 +28,9 @@ type DeviceConfig struct {
Peers []PeerConfig Peers []PeerConfig
DNS []netip.Addr DNS []netip.Addr
MTU int MTU int
ListenPort *int
CheckAlive []netip.Addr
CheckAliveInterval int
} }
type TCPClientTunnelConfig struct { type TCPClientTunnelConfig struct {
@@ -34,6 +38,10 @@ type TCPClientTunnelConfig struct {
Target string Target string
} }
type STDIOTunnelConfig struct {
Target string
}
type TCPServerTunnelConfig struct { type TCPServerTunnelConfig struct {
ListenPort int ListenPort int
Target string Target string
@@ -45,6 +53,12 @@ type Socks5Config struct {
Password string Password string
} }
type HTTPConfig struct {
BindAddress string
Username string
Password string
}
type Configuration struct { type Configuration struct {
Device *DeviceConfig Device *DeviceConfig
Routines []RoutineSpawner Routines []RoutineSpawner
@@ -55,6 +69,18 @@ func parseString(section *ini.Section, keyName string) (string, error) {
if key == nil { if key == nil {
return "", errors.New(keyName + " should not be empty") 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 return key.String(), nil
} }
@@ -109,14 +135,21 @@ func encodeBase64ToHex(key string) (string, error) {
} }
func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) { func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
key := section.Key(keyName) key, err := parseString(section, keyName)
if key == nil { if err != nil {
if strings.Contains(err.Error(), "should not be empty") {
return []netip.Addr{}, nil return []netip.Addr{}, nil
} }
return nil, err
}
var ips []netip.Addr keys := strings.Split(key, ",")
for _, str := range key.StringsWithShadows(",") { var ips = make([]netip.Addr, 0, len(keys))
for _, str := range keys {
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
if len(str) == 0 {
continue
}
ip, err := netip.ParseAddr(str) ip, err := netip.ParseAddr(str)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -127,36 +160,53 @@ func parseNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
} }
func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) { func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error) {
key := section.Key(keyName) key, err := parseString(section, keyName)
if key == nil { if err != nil {
if strings.Contains(err.Error(), "should not be empty") {
return []netip.Addr{}, nil return []netip.Addr{}, nil
} }
return nil, err
}
var ips []netip.Addr keys := strings.Split(key, ",")
for _, str := range key.StringsWithShadows(",") { 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) prefix, err := netip.ParsePrefix(str)
if err != nil { if err != nil {
return nil, err return nil, err
} }
addr := prefix.Addr() 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) ips = append(ips, addr)
} }
}
return ips, nil return ips, nil
} }
func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) { func parseAllowedIPs(section *ini.Section) ([]netip.Prefix, error) {
key := section.Key("AllowedIPs") key, err := parseString(section, "AllowedIPs")
if key == nil { if err != nil {
if strings.Contains(err.Error(), "should not be empty") {
return []netip.Prefix{}, nil return []netip.Prefix{}, nil
} }
return nil, err
}
var ips []netip.Prefix keys := strings.Split(key, ",")
for _, str := range key.StringsWithShadows(",") { var ips = make([]netip.Prefix, 0, len(keys))
for _, str := range keys {
str = strings.TrimSpace(str)
if len(str) == 0 {
continue
}
prefix, err := netip.ParsePrefix(str) prefix, err := netip.ParsePrefix(str)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -219,10 +269,37 @@ func ParseInterface(cfg *ini.File, device *DeviceConfig) error {
device.MTU = value 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 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 { func ParsePeers(cfg *ini.File, peers *[]PeerConfig) error {
sections, err := cfg.SectionsByName("Peer") sections, err := cfg.SectionsByName("Peer")
if len(sections) < 1 || err != nil { if len(sections) < 1 || err != nil {
@@ -249,15 +326,14 @@ func ParsePeers(cfg *ini.File, peers *[]PeerConfig) error {
peer.PreSharedKey = value 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 { if err != nil {
return err return err
} }
decoded, err = resolveIPPAndPort(decoded) peer.Endpoint = &decoded
if err != nil {
return err
} }
peer.Endpoint = decoded
if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil { if sectionKey, err := section.GetKey("PersistentKeepalive"); err == nil {
value, err := sectionKey.Int() value, err := sectionKey.Int()
@@ -294,6 +370,17 @@ func parseTCPClientTunnelConfig(section *ini.Section) (RoutineSpawner, error) {
return config, nil 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) { func parseTCPServerTunnelConfig(section *ini.Section) (RoutineSpawner, error) {
config := &TCPServerTunnelConfig{} config := &TCPServerTunnelConfig{}
@@ -330,6 +417,24 @@ func parseSocks5Config(section *ini.Section) (RoutineSpawner, error) {
return config, nil return config, nil
} }
func parseHTTPConfig(section *ini.Section) (RoutineSpawner, error) {
config := &HTTPConfig{}
bindAddress, err := parseString(section, "BindAddress")
if err != nil {
return nil, err
}
config.BindAddress = bindAddress
username, _ := parseString(section, "Username")
config.Username = username
password, _ := parseString(section, "Password")
config.Password = password
return config, nil
}
// Takes a function that parses an individual section into a config, and apply it on all // Takes a function that parses an individual section into a config, and apply it on all
// specified sections // specified sections
func parseRoutinesConfig(routines *[]RoutineSpawner, cfg *ini.File, sectionName string, f func(*ini.Section) (RoutineSpawner, error)) error { func parseRoutinesConfig(routines *[]RoutineSpawner, cfg *ini.File, sectionName string, f func(*ini.Section) (RoutineSpawner, error)) error {
@@ -394,6 +499,11 @@ func ParseConfig(path string) (*Configuration, error) {
return nil, err return nil, err
} }
err = parseRoutinesConfig(&routinesSpawners, cfg, "STDIOTunnel", parseSTDIOTunnelConfig)
if err != nil {
return nil, err
}
err = parseRoutinesConfig(&routinesSpawners, cfg, "TCPServerTunnel", parseTCPServerTunnelConfig) err = parseRoutinesConfig(&routinesSpawners, cfg, "TCPServerTunnel", parseTCPServerTunnelConfig)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -404,6 +514,11 @@ func ParseConfig(path string) (*Configuration, error) {
return nil, err return nil, err
} }
err = parseRoutinesConfig(&routinesSpawners, cfg, "http", parseHTTPConfig)
if err != nil {
return nil, err
}
return &Configuration{ return &Configuration{
Device: device, Device: device,
Routines: routinesSpawners, Routines: routinesSpawners,

87
config_test.go Normal file
View File

@@ -0,0 +1,87 @@
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)
}
}

33
go.mod
View File

@@ -1,23 +1,26 @@
module github.com/octeep/wireproxy module github.com/pufferffish/wireproxy
go 1.18 go 1.21.1
toolchain go1.21.6
require ( require (
github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/akamensky/argparse v1.3.1 github.com/akamensky/argparse v1.4.0
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/go-ini/ini v1.67.0
github.com/go-ini/ini v1.66.4 github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b github.com/things-go/go-socks5 v0.0.5
suah.dev/protect v1.2.0 golang.org/x/net v0.33.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
suah.dev/protect v1.2.3
) )
require ( require (
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.1.2 // indirect
github.com/stretchr/testify v1.8.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect
) )

64
go.sum
View File

@@ -1,41 +1,39 @@
github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= 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/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.4.0 h1:YGzvsTqCvbEZhL8zZu2AiA5nq805NZh75JNj4ajn1xc=
github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA= github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ini/ini v1.66.4 h1:dKjMqkcbkzfddhIhyglTPgMoJnkvmG+bSLrU9cTHc5M= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.66.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
suah.dev/protect v1.2.0 h1:4G4V43yVYXCjLFzaE9QJR0fLo3rf5vNBS9YxyoI19DU= kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs=
suah.dev/protect v1.2.0/go.mod h1:Ocn1yqUskqe/is6N2bxQxtT+fegbvQsOFyHbJAQu9XE= 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=

166
http.go Normal file
View File

@@ -0,0 +1,166 @@
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)
}
}

21
rc.d/README.md Normal file
View File

@@ -0,0 +1,21 @@
# 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
```

30
rc.d/wireproxy Normal file
View File

@@ -0,0 +1,30 @@
#!/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,20 +1,35 @@
package wireproxy package wireproxy
import ( import (
"bytes"
"context" "context"
srand "crypto/rand"
"crypto/subtle" "crypto/subtle"
"encoding/binary"
"encoding/json"
"errors" "errors"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"golang.zx2c4.com/wireguard/device"
"io" "io"
"log" "log"
"math/rand" "math/rand"
"net" "net"
"net/http"
"os" "os"
"path"
"strconv" "strconv"
"strings"
"sync"
"time"
"github.com/armon/go-socks5" "github.com/things-go/go-socks5"
"github.com/things-go/go-socks5/bufferpool"
"net/netip"
"golang.zx2c4.com/wireguard/tun/netstack" "golang.zx2c4.com/wireguard/tun/netstack"
"net/netip"
) )
// errorLogger is the logger to print error message // errorLogger is the logger to print error message
@@ -28,8 +43,13 @@ type CredentialValidator struct {
// VirtualTun stores a reference to netstack network and DNS configuration // VirtualTun stores a reference to netstack network and DNS configuration
type VirtualTun struct { type VirtualTun struct {
tnet *netstack.Net Tnet *netstack.Net
systemDNS bool Dev *device.Device
SystemDNS bool
Conf *DeviceConfig
// PingRecord stores the last time an IP was pinged
PingRecord map[string]uint64
PingRecordLock *sync.Mutex
} }
// RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed // RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed
@@ -45,11 +65,10 @@ type addressPort struct {
// LookupAddr lookups a hostname. // LookupAddr lookups a hostname.
// DNS traffic may or may not be routed depending on VirtualTun's setting // DNS traffic may or may not be routed depending on VirtualTun's setting
func (d VirtualTun) LookupAddr(ctx context.Context, name string) ([]string, error) { func (d VirtualTun) LookupAddr(ctx context.Context, name string) ([]string, error) {
if d.systemDNS { if d.SystemDNS {
return net.DefaultResolver.LookupHost(ctx, name) return net.DefaultResolver.LookupHost(ctx, name)
} else {
return d.tnet.LookupContextHost(ctx, name)
} }
return d.Tnet.LookupContextHost(ctx, name)
} }
// ResolveAddrWithContext resolves a hostname and returns an AddrPort. // ResolveAddrWithContext resolves a hostname and returns an AddrPort.
@@ -121,16 +140,39 @@ func (d VirtualTun) resolveToAddrPort(endpoint *addressPort) (*netip.AddrPort, e
// SpawnRoutine spawns a socks5 server. // SpawnRoutine spawns a socks5 server.
func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) { func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) {
conf := &socks5.Config{Dial: vt.tnet.DialContext, Resolver: vt} var authMethods []socks5.Authenticator
if username := config.Username; username != "" { if username := config.Username; username != "" {
validator := CredentialValidator{username: username} authMethods = append(authMethods, socks5.UserPassAuthenticator{
validator.password = config.Password Credentials: socks5.StaticCredentials{username: config.Password},
conf.Credentials = validator })
} else {
authMethods = append(authMethods, socks5.NoAuthAuthenticator{})
} }
server, err := socks5.New(conf)
if err != nil { 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) 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
}
if err := server.ListenAndServe("tcp", config.BindAddress); err != nil { if err := server.ListenAndServe("tcp", config.BindAddress); err != nil {
log.Fatal(err) log.Fatal(err)
@@ -145,15 +187,15 @@ func (c CredentialValidator) Valid(username, password string) bool {
return u&p == 1 return u&p == 1
} }
// connForward copy data from `from` to `to`, then close both stream. // connForward copy data from `from` to `to`
func connForward(bufSize int, from io.ReadWriteCloser, to io.ReadWriteCloser) { func connForward(from io.ReadWriteCloser, to io.ReadWriteCloser) {
buf := make([]byte, bufSize) defer from.Close()
_, err := io.CopyBuffer(to, from, buf) defer to.Close()
_, err := io.Copy(to, from)
if err != nil { if err != nil {
errorLogger.Printf("Cannot forward traffic: %s\n", err.Error()) 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` // tcpClientForward starts a new connection via wireguard and forward traffic from `conn`
@@ -166,14 +208,40 @@ func tcpClientForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
tcpAddr := TCPAddrFromAddrPort(*target) tcpAddr := TCPAddrFromAddrPort(*target)
sconn, err := vt.tnet.DialTCP(tcpAddr) sconn, err := vt.Tnet.DialTCP(tcpAddr)
if err != nil { if err != nil {
errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error()) errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error())
return return
} }
go connForward(1024, sconn, conn) go connForward(sconn, conn)
go connForward(1024, conn, sconn) go connForward(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 // SpawnRoutine spawns a local TCP server which acts as a proxy to the specified target
@@ -197,6 +265,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` // tcpServerForward starts a new connection locally and forward traffic from `conn`
func tcpServerForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) { func tcpServerForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
target, err := vt.resolveToAddrPort(raddr) target, err := vt.resolveToAddrPort(raddr)
@@ -213,8 +291,9 @@ func tcpServerForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
return return
} }
go connForward(1024, sconn, conn) go connForward(sconn, conn)
go connForward(1024, conn, sconn) go connForward(conn, sconn)
} }
// SpawnRoutine spawns a TCP server on wireguard which acts as a proxy to the specified target // SpawnRoutine spawns a TCP server on wireguard which acts as a proxy to the specified target
@@ -225,7 +304,7 @@ func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
} }
addr := &net.TCPAddr{Port: conf.ListenPort} addr := &net.TCPAddr{Port: conf.ListenPort}
server, err := vt.tnet.ListenTCP(addr) server, err := vt.Tnet.ListenTCP(addr)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -238,3 +317,153 @@ func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
go tcpServerForward(vt, raddr, conn) 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.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)
}
}()
}

35
systemd/README.md Normal file
View 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
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).

46
systemd/wireproxy.service Normal file
View 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 @sandbox
[Install]
WantedBy=multi-user.target

28
test_config.sh Executable file
View File

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

25
util.go Normal file
View File

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

View File

@@ -3,6 +3,7 @@ package wireproxy
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"sync"
"net/netip" "net/netip"
@@ -14,27 +15,33 @@ import (
// DeviceSetting contains the parameters for setting up a tun interface // DeviceSetting contains the parameters for setting up a tun interface
type DeviceSetting struct { type DeviceSetting struct {
ipcRequest string IpcRequest string
dns []netip.Addr DNS []netip.Addr
deviceAddr []netip.Addr DeviceAddr []netip.Addr
mtu int MTU int
} }
// serialize the config into an IPC request and DeviceSetting // CreateIPCRequest serialize the config into an IPC request and DeviceSetting
func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) { func CreateIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
var request bytes.Buffer var request bytes.Buffer
request.WriteString(fmt.Sprintf("private_key=%s\n", conf.SecretKey)) 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 { for _, peer := range conf.Peers {
request.WriteString(fmt.Sprintf(heredoc.Doc(` request.WriteString(fmt.Sprintf(heredoc.Doc(`
public_key=%s public_key=%s
endpoint=%s
persistent_keepalive_interval=%d persistent_keepalive_interval=%d
preshared_key=%s 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 { if len(peer.AllowedIPs) > 0 {
for _, ip := range peer.AllowedIPs { for _, ip := range peer.AllowedIPs {
@@ -48,23 +55,23 @@ func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
} }
} }
setting := &DeviceSetting{ipcRequest: request.String(), dns: conf.DNS, deviceAddr: conf.Endpoint, mtu: conf.MTU} setting := &DeviceSetting{IpcRequest: request.String(), DNS: conf.DNS, DeviceAddr: conf.Endpoint, MTU: conf.MTU}
return setting, nil return setting, nil
} }
// StartWireguard creates a tun interface on netstack given a configuration // StartWireguard creates a tun interface on netstack given a configuration
func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) { func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
setting, err := createIPCRequest(conf) setting, err := CreateIPCRequest(conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
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 { if err != nil {
return nil, err return nil, err
} }
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, "")) dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, ""))
err = dev.IpcSet(setting.ipcRequest) err = dev.IpcSet(setting.IpcRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -75,7 +82,11 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
} }
return &VirtualTun{ return &VirtualTun{
tnet: tnet, Tnet: tnet,
systemDNS: len(setting.dns) == 0, Dev: dev,
Conf: conf,
SystemDNS: len(setting.DNS) == 0,
PingRecord: make(map[string]uint64),
PingRecordLock: new(sync.Mutex),
}, nil }, nil
} }