Compare commits

..

16 commits

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
16 changed files with 354 additions and 139 deletions

View file

@ -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

@ -1,4 +1,5 @@
# 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)
@ -6,12 +7,14 @@
A wireguard client that exposes itself as a socks5/http 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/http 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,23 +23,33 @@ 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/juev/wireproxy/tree/feature/amnezia-go) Users who want something similar but for Amnezia VPN can use [this fork](https://github.com/artem-russkikh/wireproxy-awg)
of wireproxy by [@juev](https://github.com/juev). 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/HTTP 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>"] [-s|--silent] usage: wireproxy [-h|--help] [-c|--config "<value>"] [-s|--silent]
[-d|--daemon] [-i|--info "<value>"] [-v|--version] [-d|--daemon] [-i|--info "<value>"] [-v|--version]
[-n|--configtest] [-n|--configtest]
@ -47,27 +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 -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 -i --info Specify the address and port for exposing health status
-v --version Print version -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
``` ```
# Install
```bash
go install github.com/pufferffish/wireproxy/cmd/wireproxy@v1.0.9 # or @latest
```
# Use with VPN # Use with VPN
Instructions for using wireproxy with Firefox container tabs and auto-start on MacOS can be found [here](/UseWithVPN.md). Instructions for using wireproxy with Firefox container tabs and auto-start on MacOS can be found [here](/UseWithVPN.md).
# Sample config file # 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
@ -76,6 +98,7 @@ 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 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]
@ -133,7 +156,8 @@ BindAddress = 127.0.0.1:25345
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
@ -149,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=
@ -181,7 +206,8 @@ Target = service-three.servicenet:80
``` ```
Wireproxy can also allow peers to connect to it: Wireproxy can also allow peers to connect to it:
```
```ini
[Interface] [Interface]
ListenPort = 5400 ListenPort = 5400
... ...
@ -191,7 +217,9 @@ PublicKey = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY=
AllowedIPs = 10.254.254.100/32 AllowedIPs = 10.254.254.100/32
# Note there is no Endpoint defined here. # Note there is no Endpoint defined here.
``` ```
# Health endpoint # Health endpoint
Wireproxy supports exposing a health endpoint for monitoring purposes. 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. 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.
@ -202,7 +230,8 @@ Currently two endpoints are implemented:
`/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. `/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: For example:
```
```ini
[Interface] [Interface]
PrivateKey = censored PrivateKey = censored
Address = 10.2.0.2/32 Address = 10.2.0.2/32
@ -218,8 +247,10 @@ Endpoint = 149.34.244.174:51820
[Socks5] [Socks5]
BindAddress = 127.0.0.1:25344 BindAddress = 127.0.0.1:25344
``` ```
`/readyz` would respond with `/readyz` would respond with
```
```text
< HTTP/1.1 503 Service Unavailable < HTTP/1.1 503 Service Unavailable
< Date: Thu, 11 Apr 2024 00:54:59 GMT < Date: Thu, 11 Apr 2024 00:54:59 GMT
< Content-Length: 35 < Content-Length: 35
@ -229,15 +260,18 @@ BindAddress = 127.0.0.1:25344
``` ```
And for: And for:
```
```ini
[Interface] [Interface]
PrivateKey = censored PrivateKey = censored
Address = 10.2.0.2/32 Address = 10.2.0.2/32
DNS = 10.2.0.1 DNS = 10.2.0.1
CheckAlive = 1.1.1.1 CheckAlive = 1.1.1.1
``` ```
`/readyz` would respond with `/readyz` would respond with
```
```text
< HTTP/1.1 200 OK < HTTP/1.1 200 OK
< Date: Thu, 11 Apr 2024 00:56:21 GMT < Date: Thu, 11 Apr 2024 00:56:21 GMT
< Content-Length: 23 < Content-Length: 23
@ -251,4 +285,5 @@ If nothing is set for `CheckAlive`, an empty JSON object with 200 will be the re
The peer which the ICMP ping packet is routed to depends on the `AllowedIPs` set for each peers. 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) [![Stargazers over time](https://starchart.cc/octeep/wireproxy.svg)](https://starchart.cc/octeep/wireproxy)

View file

@ -1,11 +1,12 @@
# Getting a Wireguard Server # Getting a Wireguard Server
You can create your own wireguard server using a host service like DigitalOcean, You can create your own wireguard server using a host service like DigitalOcean,
or you can get a VPN service that provides WireGuard configs. or you can get a VPN service that provides WireGuard configs.
I recommend ProtonVPN, because it is highly secure and has a great WireGuard I recommend ProtonVPN, because it is highly secure and has a great WireGuard
config generator. config generator.
Simply go to https://account.protonvpn.com/downloads and scroll down to the Simply go to <https://account.protonvpn.com/downloads> and scroll down to the
wireguard section to generate your configs, then paste into the appropriate wireguard section to generate your configs, then paste into the appropriate
section below. section below.
@ -25,9 +26,11 @@ naming should also be similar (e.g.
`/Users/jonny/Library/LaunchAgents/com.ProtonUS.adblock.plist`) `/Users/jonny/Library/LaunchAgents/com.ProtonUS.adblock.plist`)
## Config File ## Config File
Make sure you use a unique port for every separate server 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 I recommend you set proxy authentication, you can use the same user/pass for all
```
```ini
# Link to the Downloaded config # Link to the Downloaded config
WGConfig = /Users/jonny/vpntabs/ProtonUS.adblock.server.conf WGConfig = /Users/jonny/vpntabs/ProtonUS.adblock.server.conf
@ -43,24 +46,27 @@ BindAddress = 127.0.0.1:25344 # Update the port here for each new server
``` ```
## Startup Script File ## Startup Script File
This is a bash script to facilitate startup, not strictly essential, but adds This is a bash script to facilitate startup, not strictly essential, but adds
ease. ease.
Note, you MUST update the first path to wherever you installed this code to. 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 Make sure you use the path for the config file above, not the one you downloaded
from e.g. protonvpn. from e.g. protonvpn.
```
```bash
#!/bin/bash #!/bin/bash
/Users/jonny/wireproxy/wireproxy -c /Users/jonny/vpntabs/ProtonUS.adblock.conf /Users/jonny/wireproxy/wireproxy -c /Users/jonny/vpntabs/ProtonUS.adblock.conf
``` ```
## MacOS LaunchAgent ## MacOS LaunchAgent
To make it run every time you start your computer, you can create a launch agent To make it run every time you start your computer, you can create a launch agent
in `$HOME/Library/LaunchAgents`. Name reference above. in `$HOME/Library/LaunchAgents`. Name reference above.
That file should contain the following, the label should be the same as the file That file should contain the following, the label should be the same as the file
name and the paths should be set correctly: name and the paths should be set correctly:
``` ```xml
<?xml version="1.0" encoding="UTF-8"?> <?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"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@ -82,6 +88,7 @@ To enable it, run
`launchtl start ~/Library/LaunchAgents/com.PortonUS.adblock.plist` `launchtl start ~/Library/LaunchAgents/com.PortonUS.adblock.plist`
# Firefox Setup # Firefox Setup
You will need to enable the Multi Account Container Tabs extension and a proxy extension, I You will need to enable the Multi Account Container Tabs extension and a proxy extension, I
recommend Sideberry, but Container Proxy also works. recommend Sideberry, but Container Proxy also works.

BIN
assets/iproyal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -22,6 +22,12 @@ import (
// 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"
// 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" var version = "1.0.8-dev"
func panicIfError(err error) { func panicIfError(err error) {
@ -51,6 +57,16 @@ func executablePath() string {
return programPath 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) { func lock(stage string) {
switch stage { switch stage {
case "boot": case "boot":
@ -177,9 +193,13 @@ 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 {
lock("read-config") lock("read-config")

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"
@ -68,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
} }
@ -122,15 +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
}
keys := key.StringsWithShadows(",") keys := strings.Split(key, ",")
var ips = make([]netip.Addr, 0, len(keys)) var ips = make([]netip.Addr, 0, len(keys))
for _, str := range 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
@ -141,14 +160,25 @@ 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
}
keys := key.StringsWithShadows(",") keys := strings.Split(key, ",")
var ips = make([]netip.Addr, 0, len(keys)) var ips = make([]netip.Addr, 0, len(keys))
for _, str := range 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
@ -157,18 +187,26 @@ func parseCIDRNetIP(section *ini.Section, keyName string) ([]netip.Addr, error)
addr := prefix.Addr() addr := prefix.Addr()
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
}
keys := key.StringsWithShadows(",") keys := strings.Split(key, ",")
var ips = make([]netip.Prefix, 0, len(keys)) var ips = make([]netip.Prefix, 0, len(keys))
for _, str := range 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

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)
}
}

7
go.mod
View file

@ -9,17 +9,16 @@ require (
github.com/akamensky/argparse v1.4.0 github.com/akamensky/argparse v1.4.0
github.com/go-ini/ini v1.67.0 github.com/go-ini/ini v1.67.0
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a
github.com/sourcegraph/conc v0.3.0
github.com/things-go/go-socks5 v0.0.5 github.com/things-go/go-socks5 v0.0.5
golang.org/x/net v0.23.0 golang.org/x/net v0.33.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
suah.dev/protect v1.2.3 suah.dev/protect v1.2.3
) )
require ( require (
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect

14
go.sum
View file

@ -12,19 +12,17 @@ github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a h1:dz+a1M
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a/go.mod h1:1NY/VPO8xm3hXw3f+M65z+PJDLUaZA5cu7OfanxoUzY= 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/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8= github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ= github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=

38
http.go
View file

@ -10,8 +10,6 @@ import (
"net" "net"
"net/http" "net/http"
"strings" "strings"
"github.com/sourcegraph/conc"
) )
const proxyAuthHeaderKey = "Proxy-Authorization" const proxyAuthHeaderKey = "Proxy-Authorization"
@ -31,7 +29,10 @@ func (s *HTTPServer) authenticate(req *http.Request) (int, error) {
} }
auth := req.Header.Get(proxyAuthHeaderKey) auth := req.Header.Get(proxyAuthHeaderKey)
if auth != "" { if auth == "" {
return http.StatusProxyAuthRequired, fmt.Errorf("%s", http.StatusText(http.StatusProxyAuthRequired))
}
enc := strings.TrimPrefix(auth, "Basic ") enc := strings.TrimPrefix(auth, "Basic ")
str, err := base64.StdEncoding.DecodeString(enc) str, err := base64.StdEncoding.DecodeString(enc)
if err != nil { if err != nil {
@ -45,9 +46,6 @@ func (s *HTTPServer) authenticate(req *http.Request) (int, error) {
return 0, nil return 0, nil
} }
return http.StatusUnauthorized, fmt.Errorf("username and password not matching") return http.StatusUnauthorized, fmt.Errorf("username and password not matching")
}
return http.StatusProxyAuthRequired, fmt.Errorf(http.StatusText(http.StatusProxyAuthRequired))
} }
func (s *HTTPServer) handleConn(req *http.Request, conn net.Conn) (peer net.Conn, err error) { func (s *HTTPServer) handleConn(req *http.Request, conn net.Conn) (peer net.Conn, err error) {
@ -103,7 +101,11 @@ func (s *HTTPServer) serve(conn net.Conn) {
code, err := s.authenticate(req) code, err := s.authenticate(req)
if err != nil { if err != nil {
_ = responseWith(req, code).Write(conn) resp := responseWith(req, code)
if code == http.StatusProxyAuthRequired {
resp.Header.Set("Proxy-Authenticate", "Basic realm=\"Proxy\"")
}
_ = resp.Write(conn)
log.Println(err) log.Println(err)
return return
} }
@ -127,17 +129,19 @@ func (s *HTTPServer) serve(conn net.Conn) {
log.Println("dial proxy failed: peer nil") log.Println("dial proxy failed: peer nil")
return return
} }
go func() { go func() {
wg := conc.NewWaitGroup() defer conn.Close()
wg.Go(func() { defer peer.Close()
_, err = io.Copy(conn, peer)
_ = conn.Close() _, _ = io.Copy(conn, peer)
}) }()
wg.Go(func() {
_, err = io.Copy(peer, conn) go func() {
_ = peer.Close() defer conn.Close()
}) defer peer.Close()
wg.Wait()
_, _ = io.Copy(peer, 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

@ -21,9 +21,9 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/sourcegraph/conc"
"github.com/things-go/go-socks5" "github.com/things-go/go-socks5"
"github.com/things-go/go-socks5/bufferpool" "github.com/things-go/go-socks5/bufferpool"
@ -49,6 +49,7 @@ type VirtualTun struct {
Conf *DeviceConfig Conf *DeviceConfig
// PingRecord stores the last time an IP was pinged // PingRecord stores the last time an IP was pinged
PingRecord map[string]uint64 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
@ -188,6 +189,9 @@ func (c CredentialValidator) Valid(username, password string) bool {
// connForward copy data from `from` to `to` // connForward copy data from `from` to `to`
func connForward(from io.ReadWriteCloser, to io.ReadWriteCloser) { func connForward(from io.ReadWriteCloser, to io.ReadWriteCloser) {
defer from.Close()
defer to.Close()
_, err := io.Copy(to, from) _, 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())
@ -210,20 +214,8 @@ func tcpClientForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
return return
} }
go func() { go connForward(sconn, conn)
wg := conc.NewWaitGroup() go connForward(conn, sconn)
wg.Go(func() {
connForward(sconn, conn)
})
wg.Go(func() {
connForward(conn, sconn)
})
wg.Wait()
_ = sconn.Close()
_ = conn.Close()
sconn = nil
conn = nil
}()
} }
// STDIOTcpForward starts a new connection via wireguard and forward traffic from `conn` // STDIOTcpForward starts a new connection via wireguard and forward traffic from `conn`
@ -248,18 +240,8 @@ func STDIOTcpForward(vt *VirtualTun, raddr *addressPort) {
return return
} }
go func() { go connForward(os.Stdin, sconn)
wg := conc.NewWaitGroup() go connForward(sconn, stdout)
wg.Go(func() {
connForward(os.Stdin, sconn)
})
wg.Go(func() {
connForward(sconn, stdout)
})
wg.Wait()
_ = sconn.Close()
sconn = nil
}()
} }
// SpawnRoutine spawns a local TCP server which acts as a proxy to the specified target // SpawnRoutine spawns a local TCP server which acts as a proxy to the specified target
@ -309,20 +291,9 @@ func tcpServerForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
return return
} }
go func() { go connForward(sconn, conn)
gr := conc.NewWaitGroup() go connForward(conn, sconn)
gr.Go(func() {
connForward(sconn, conn)
})
gr.Go(func() {
connForward(conn, sconn)
})
gr.Wait()
_ = sconn.Close()
_ = conn.Close()
sconn = nil
conn = nil
}()
} }
// SpawnRoutine spawns a TCP server on wireguard which acts as a proxy to the specified target // SpawnRoutine spawns a TCP server on wireguard which acts as a proxy to the specified target
@ -475,7 +446,9 @@ func (d VirtualTun) pingIPs() {
} }
} }
d.PingRecordLock.Lock()
d.PingRecord[addr.String()] = uint64(time.Now().Unix()) d.PingRecord[addr.String()] = uint64(time.Now().Unix())
d.PingRecordLock.Unlock()
defer socket.Close() defer socket.Close()
}() }()

View file

@ -8,7 +8,7 @@ The provided systemd unit assumes you have the wireproxy executable installed on
1. Copy the `wireproxy.service` file from this directory to `/etc/systemd/system/`, or use the following cURL command to download it: 1. Copy the `wireproxy.service` file from this directory to `/etc/systemd/system/`, or use the following cURL command to download it:
```bash ```bash
sudo curl https://raw.githubusercontent.com/pufferffish/wireproxy/master/systemd/wireproxy.service > /etc/systemd/system/wireproxy.service curl https://raw.githubusercontent.com/pufferffish/wireproxy/master/systemd/wireproxy.service | sudo tee /etc/systemd/system/wireproxy.service
``` ```
2. If necessary, customize the unit. 2. If necessary, customize the unit.

View file

@ -40,7 +40,7 @@ RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=true RestrictNamespaces=true
RestrictRealtime=true RestrictRealtime=true
SystemCallArchitectures=native SystemCallArchitectures=native
SystemCallFilter=@system-service SystemCallFilter=@system-service @sandbox
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -3,6 +3,7 @@ package wireproxy
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"sync"
"net/netip" "net/netip"
@ -14,14 +15,14 @@ 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))
@ -54,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, logLevel int) (*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(logLevel, "")) 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
} }
@ -84,7 +85,8 @@ func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
Tnet: tnet, Tnet: tnet,
Dev: dev, Dev: dev,
Conf: conf, Conf: conf,
SystemDNS: len(setting.dns) == 0, SystemDNS: len(setting.DNS) == 0,
PingRecord: make(map[string]uint64), PingRecord: make(map[string]uint64),
PingRecordLock: new(sync.Mutex),
}, nil }, nil
} }