12 Commits
udp ... v1.0.6

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

* add test case for http proxy

---------

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

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

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

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

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

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

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

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

View File

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

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

4
go.mod
View File

@@ -15,8 +15,8 @@ require (
github.com/google/btree v1.0.1 // indirect
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gvisor.dev/gvisor v0.0.0-20220817001344-846276b3dbc5 // indirect

8
go.sum
View File

@@ -20,11 +20,11 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 h1:A9i04dxx7Cribqbs8jf3FQLogkL/CV2YN7hj9KWJCkc=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=

156
http.go Normal file
View File

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

View File

@@ -28,8 +28,8 @@ type CredentialValidator struct {
// VirtualTun stores a reference to netstack network and DNS configuration
type VirtualTun struct {
tnet *netstack.Net
systemDNS bool
Tnet *netstack.Net
SystemDNS bool
}
// RoutineSpawner spawns a routine (e.g. socks5, tcp static routes) after the configuration is parsed
@@ -45,10 +45,10 @@ type addressPort struct {
// LookupAddr lookups a hostname.
// DNS traffic may or may not be routed depending on VirtualTun's setting
func (d VirtualTun) LookupAddr(ctx context.Context, name string) ([]string, error) {
if d.systemDNS {
if d.SystemDNS {
return net.DefaultResolver.LookupHost(ctx, name)
} else {
return d.tnet.LookupContextHost(ctx, name)
return d.Tnet.LookupContextHost(ctx, name)
}
}
@@ -121,7 +121,7 @@ func (d VirtualTun) resolveToAddrPort(endpoint *addressPort) (*netip.AddrPort, e
// SpawnRoutine spawns a socks5 server.
func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) {
conf := &socks5.Config{Dial: vt.tnet.DialContext, Resolver: vt}
conf := &socks5.Config{Dial: vt.Tnet.DialContext, Resolver: vt}
if username := config.Username; username != "" {
validator := CredentialValidator{username: username}
validator.password = config.Password
@@ -137,6 +137,22 @@ func (config *Socks5Config) SpawnRoutine(vt *VirtualTun) {
}
}
// SpawnRoutine spawns a http server.
func (config *HTTPConfig) SpawnRoutine(vt *VirtualTun) {
http := &HTTPServer{
config: config,
dial: vt.Tnet.Dial,
auth: CredentialValidator{config.Username, config.Password},
}
if config.Username != "" || config.Password != "" {
http.authRequired = true
}
if err := http.ListenAndServe("tcp", config.BindAddress); err != nil {
log.Fatal(err)
}
}
// Valid checks the authentication data in CredentialValidator and compare them
// to username and password in constant time.
func (c CredentialValidator) Valid(username, password string) bool {
@@ -166,7 +182,7 @@ func tcpClientForward(vt *VirtualTun, raddr *addressPort, conn net.Conn) {
tcpAddr := TCPAddrFromAddrPort(*target)
sconn, err := vt.tnet.DialTCP(tcpAddr)
sconn, err := vt.Tnet.DialTCP(tcpAddr)
if err != nil {
errorLogger.Printf("TCP Client Tunnel to %s: %s\n", target, err.Error())
return
@@ -225,7 +241,7 @@ func (conf *TCPServerTunnelConfig) SpawnRoutine(vt *VirtualTun) {
}
addr := &net.TCPAddr{Port: conf.ListenPort}
server, err := vt.tnet.ListenTCP(addr)
server, err := vt.Tnet.ListenTCP(addr)
if err != nil {
log.Fatal(err)
}

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

@@ -53,7 +53,7 @@ func createIPCRequest(conf *DeviceConfig) (*DeviceSetting, error) {
}
// StartWireguard creates a tun interface on netstack given a configuration
func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
func StartWireguard(conf *DeviceConfig, logLevel int) (*VirtualTun, error) {
setting, err := createIPCRequest(conf)
if err != nil {
return nil, err
@@ -63,7 +63,7 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
if err != nil {
return nil, err
}
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(logLevel, ""))
err = dev.IpcSet(setting.ipcRequest)
if err != nil {
return nil, err
@@ -75,7 +75,7 @@ func StartWireguard(conf *DeviceConfig) (*VirtualTun, error) {
}
return &VirtualTun{
tnet: tnet,
systemDNS: len(setting.dns) == 0,
Tnet: tnet,
SystemDNS: len(setting.dns) == 0,
}, nil
}