Skip to content

Commit

Permalink
tun: add tun support (linux)
Browse files Browse the repository at this point in the history
Signed-off-by: Mark Pashmfouroush <[email protected]>
  • Loading branch information
markpash committed May 6, 2024
1 parent 9123492 commit a6ada4e
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 93 deletions.
160 changes: 129 additions & 31 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

const singleMTU = 1330
const doubleMTU = 1280 // minimum mtu for IPv6, may cause frag reassembly somewhere
const connTestEndpoint = "http://1.1.1.1:80/"

type WarpOptions struct {
Bind netip.AddrPort
Expand All @@ -25,12 +26,17 @@ type WarpOptions struct {
Gool bool
Scan *wiresocks.ScanOptions
CacheDir string
Tun *TunOptions
}

type PsiphonOptions struct {
Country string
}

type TunOptions struct {
FwMark uint32
}

func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error {
if opts.Psiphon != nil && opts.Gool {
return errors.New("can't use psiphon and gool at the same time")
Expand All @@ -40,6 +46,10 @@ func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error {
return errors.New("must provide country for psiphon")
}

if opts.Psiphon != nil && opts.Tun != nil {
return errors.New("can't use psiphon and tun at the same time")
}

// create identities
if err := createPrimaryAndSecondaryIdentities(l.With("subsystem", "warp/account"), opts); err != nil {
return err
Expand Down Expand Up @@ -94,116 +104,204 @@ func RunWarp(ctx context.Context, l *slog.Logger, opts WarpOptions) error {
}

func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint string) error {
// Set up primary/outer warp config
conf, err := wiresocks.ParseConfig(path.Join(opts.CacheDir, "primary", "wgcf-profile.ini"))
if err != nil {
return err
}

// Set up MTU
conf.Interface.MTU = singleMTU

// Enable trick and keepalive on all peers in config
for i, peer := range conf.Peers {
peer.Endpoint = endpoint
peer.Trick = true
peer.KeepAlive = 3
conf.Peers[i] = peer
}

tnet, err := wiresocks.StartWireguard(ctx, l, conf)
if opts.Tun != nil {
// Create a new tun interface
tunDev, err := newNormalTun()
if err != nil {
return err
}

// Establish wireguard tunnel on tun interface
if err := establishWireguard(l, conf, tunDev, opts.Tun.FwMark); err != nil {
return err
}
l.Info("serving tun", "interface", "warp0")
return nil
}

// Create userspace tun network stack
tunDev, tnet, err := newUsermodeTun(conf)
if err != nil {
return err
}

_, err = tnet.StartProxy(opts.Bind)
// Establish wireguard on userspace stack
if err := establishWireguard(l, conf, tunDev, 0); err != nil {
return err
}

// Test wireguard connectivity
if err := usermodeTunTest(ctx, l, tnet); err != nil {
return err
}

// Run a proxy on the userspace stack
_, err = wiresocks.StartProxy(ctx, l, tnet, opts.Bind)
if err != nil {
return err
}

l.Info("serving proxy", "address", opts.Bind)

return nil
}

func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint string) error {
func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoints []string) error {
// Set up primary/outer warp config
conf, err := wiresocks.ParseConfig(path.Join(opts.CacheDir, "primary", "wgcf-profile.ini"))
if err != nil {
return err
}

// Set up MTU
conf.Interface.MTU = singleMTU

// Enable trick and keepalive on all peers in config
for i, peer := range conf.Peers {
peer.Endpoint = endpoint
peer.Endpoint = endpoints[0]
peer.Trick = true
peer.KeepAlive = 3
conf.Peers[i] = peer
}

tnet, err := wiresocks.StartWireguard(ctx, l, conf)
// Create userspace tun network stack
tunDev, tnet, err := newUsermodeTun(conf)
if err != nil {
return err
}

warpBind, err := tnet.StartProxy(netip.MustParseAddrPort("127.0.0.1:0"))
// Establish wireguard on userspace stack
if err := establishWireguard(l.With("gool", "outer"), conf, tunDev, 0); err != nil {
return err
}

// Test wireguard connectivity
if err := usermodeTunTest(ctx, l, tnet); err != nil {
return err
}

// Create a UDP port forward between localhost and the remote endpoint
addr, err := wiresocks.NewVtunUDPForwarder(ctx, netip.MustParseAddrPort("127.0.0.1:0"), endpoints[0], tnet, singleMTU)
if err != nil {
return err
}

// run psiphon
err = psiphon.RunPsiphon(ctx, l.With("subsystem", "psiphon"), warpBind.String(), opts.CacheDir, opts.Bind.String(), opts.Psiphon.Country)
// Set up secondary/inner warp config
conf, err = wiresocks.ParseConfig(path.Join(opts.CacheDir, "secondary", "wgcf-profile.ini"))
if err != nil {
return fmt.Errorf("unable to run psiphon %w", err)
return err
}

l.Info("serving proxy", "address", opts.Bind)
// Set up MTU
conf.Interface.MTU = doubleMTU

// Enable keepalive on all peers in config
for i, peer := range conf.Peers {
peer.Endpoint = addr.String()
peer.KeepAlive = 10
conf.Peers[i] = peer
}

if opts.Tun != nil {
// Create a new tun interface
tunDev, err := newNormalTun()
if err != nil {
return err
}

// Establish wireguard tunnel on tun interface
if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, opts.Tun.FwMark); err != nil {
return err
}
l.Info("serving tun", "interface", "warp0")
return nil
}

// Create userspace tun network stack
tunDev, tnet, err = newUsermodeTun(conf)
if err != nil {
return err
}

// Establish wireguard on userspace stack
if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, 0); err != nil {
return err
}

// Test wireguard connectivity
if err := usermodeTunTest(ctx, l, tnet); err != nil {
return err
}

_, err = wiresocks.StartProxy(ctx, l, tnet, opts.Bind)
if err != nil {
return err
}

l.Info("serving proxy", "address", opts.Bind)
return nil
}

func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoints []string) error {
// Run outer warp
func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint string) error {
// Set up primary/outer warp config
conf, err := wiresocks.ParseConfig(path.Join(opts.CacheDir, "primary", "wgcf-profile.ini"))
if err != nil {
return err
}

// Set up MTU
conf.Interface.MTU = singleMTU

// Enable trick and keepalive on all peers in config
for i, peer := range conf.Peers {
peer.Endpoint = endpoints[0]
peer.Endpoint = endpoint
peer.Trick = true
peer.KeepAlive = 3
conf.Peers[i] = peer
}

tnet, err := wiresocks.StartWireguard(ctx, l.With("gool", "outer"), conf)
// Create userspace tun network stack
tunDev, tnet, err := newUsermodeTun(conf)
if err != nil {
return err
}

// Create a UDP port forward between localhost and the remote endpoint
addr, err := wiresocks.NewVtunUDPForwarder(ctx, netip.MustParseAddrPort("127.0.0.1:0"), endpoints[1], tnet, singleMTU)
if err != nil {
// Establish wireguard on userspace stack
if err := establishWireguard(l, conf, tunDev, 0); err != nil {
return err
}

// Run inner warp
conf, err = wiresocks.ParseConfig(path.Join(opts.CacheDir, "secondary", "wgcf-profile.ini"))
if err != nil {
// Test wireguard connectivity
if err := usermodeTunTest(ctx, l, tnet); err != nil {
return err
}
conf.Interface.MTU = doubleMTU

for i, peer := range conf.Peers {
peer.Endpoint = addr.String()
peer.KeepAlive = 10
conf.Peers[i] = peer
}

tnet, err = wiresocks.StartWireguard(ctx, l.With("gool", "inner"), conf)
// Run a proxy on the userspace stack
warpBind, err := wiresocks.StartProxy(ctx, l, tnet, netip.MustParseAddrPort("127.0.0.1:0"))
if err != nil {
return err
}

_, err = tnet.StartProxy(opts.Bind)
// run psiphon
err = psiphon.RunPsiphon(ctx, l.With("subsystem", "psiphon"), warpBind.String(), opts.CacheDir, opts.Bind.String(), opts.Psiphon.Country)
if err != nil {
return err
return fmt.Errorf("unable to run psiphon %w", err)
}

l.Info("serving proxy", "address", opts.Bind)
Expand Down
107 changes: 107 additions & 0 deletions app/wg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package app

import (
"bytes"
"context"
"fmt"
"io"
"log/slog"
"net/http"
"time"

"github.com/bepass-org/warp-plus/wireguard/conn"
"github.com/bepass-org/warp-plus/wireguard/device"
wgtun "github.com/bepass-org/warp-plus/wireguard/tun"
"github.com/bepass-org/warp-plus/wireguard/tun/netstack"
"github.com/bepass-org/warp-plus/wiresocks"
)

func newNormalTun() (wgtun.Device, error) {
tunDev, err := wgtun.CreateTUN("warp0", 1280)
if err != nil {
return nil, err
}

return tunDev, nil
}

func newUsermodeTun(conf *wiresocks.Configuration) (wgtun.Device, *netstack.Net, error) {
tunDev, tnet, err := netstack.CreateNetTUN(conf.Interface.Addresses, conf.Interface.DNS, conf.Interface.MTU)
if err != nil {
return nil, nil, err
}

return tunDev, tnet, nil
}

func usermodeTunTest(ctx context.Context, l *slog.Logger, tnet *netstack.Net) error {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
defer cancel()

for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}

client := http.Client{Transport: &http.Transport{
DialContext: tnet.DialContext,
ResponseHeaderTimeout: 5 * time.Second,
}}
resp, err := client.Get(connTestEndpoint)
if err != nil {
l.Error("connection test failed", "error", err.Error())
continue
}
_, err = io.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
l.Error("connection test failed", "error", err.Error())
continue
}

l.Info("connection test successful")
break
}

return nil
}

func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wgtun.Device, fwmark uint32) error {
// create the IPC message to establish the wireguard conn
var request bytes.Buffer

request.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey))
if fwmark != 0 {
request.WriteString(fmt.Sprintf("fwmark=%d\n", fwmark))
}

for _, peer := range conf.Peers {
request.WriteString(fmt.Sprintf("public_key=%s\n", peer.PublicKey))
request.WriteString(fmt.Sprintf("persistent_keepalive_interval=%d\n", peer.KeepAlive))
request.WriteString(fmt.Sprintf("preshared_key=%s\n", peer.PreSharedKey))
request.WriteString(fmt.Sprintf("endpoint=%s\n", peer.Endpoint))
request.WriteString(fmt.Sprintf("trick=%t\n", peer.Trick))

for _, cidr := range peer.AllowedIPs {
request.WriteString(fmt.Sprintf("allowed_ip=%s\n", cidr))
}
}

dev := device.NewDevice(
tunDev,
conn.NewDefaultBind(),
device.NewSLogger(l.With("subsystem", "wireguard-go")),
)

if err := dev.IpcSet(request.String()); err != nil {
return err
}

if err := dev.Up(); err != nil {
return err
}

return nil
}
4 changes: 3 additions & 1 deletion example_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
"country": "DE",
"scan": true,
"rtt": "1000ms",
"cache-dir": ""
"cache-dir": "",
"tun": false,
"fwmark": "0x1375"
}
Loading

0 comments on commit a6ada4e

Please sign in to comment.