diff --git a/app/app.go b/app/app.go index 290d022f3..d83dc0698 100644 --- a/app/app.go +++ b/app/app.go @@ -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 @@ -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") @@ -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 @@ -94,12 +104,16 @@ 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 @@ -107,103 +121,187 @@ func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint str 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) diff --git a/app/wg.go b/app/wg.go new file mode 100644 index 000000000..eac3b59df --- /dev/null +++ b/app/wg.go @@ -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 +} diff --git a/example_config.json b/example_config.json index 599791482..2c3c92a7e 100644 --- a/example_config.json +++ b/example_config.json @@ -8,5 +8,7 @@ "country": "DE", "scan": true, "rtt": "1000ms", - "cache-dir": "" + "cache-dir": "", + "tun": false, + "fwmark": "0x1375" } diff --git a/main.go b/main.go index a7b994320..bd41f82b7 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,8 @@ func main() { scan = fs.BoolLong("scan", "enable warp scanning") rtt = fs.DurationLong("rtt", 1000*time.Millisecond, "scanner rtt limit") cacheDir = fs.StringLong("cache-dir", "", "directory to store generated profiles") + tun = fs.BoolLong("tun", "enable tun interface") + fwmark = fs.UintLong("fwmark", 0x1375, "set linux firewall mark for tun mode") _ = fs.String('c', "config", "", "path to config file") verFlag = fs.BoolLong("version", "displays version number") ) @@ -157,6 +159,11 @@ func main() { opts.Scan = &wiresocks.ScanOptions{V4: *v4, V6: *v6, MaxRTT: *rtt} } + if *tun { + l.Info("tun mode enabled") + opts.Tun = &app.TunOptions{FwMark: uint32(*fwmark)} + } + // If the endpoint is not set, choose a random warp endpoint if opts.Endpoint == "" { addrPort, err := warp.RandomWarpEndpoint(*v4, *v6) diff --git a/wiresocks/proxy.go b/wiresocks/proxy.go index 53b3801e4..e34fa4cb6 100644 --- a/wiresocks/proxy.go +++ b/wiresocks/proxy.go @@ -26,16 +26,24 @@ type VirtualTun struct { } // StartProxy spawns a socks5 server. -func (vt *VirtualTun) StartProxy(bindAddress netip.AddrPort) (netip.AddrPort, error) { +func StartProxy(ctx context.Context, l *slog.Logger, tnet *netstack.Net, bindAddress netip.AddrPort) (netip.AddrPort, error) { ln, err := net.Listen("tcp", bindAddress.String()) if err != nil { return netip.AddrPort{}, err // Return error if binding was unsuccessful } + vt := VirtualTun{ + Tnet: tnet, + Logger: l.With("subsystem", "vtun"), + Dev: nil, + Ctx: ctx, + pool: bufferpool.NewPool(256 * 1024), + } + proxy := mixed.NewProxy( mixed.WithListener(ln), - mixed.WithLogger(vt.Logger), - mixed.WithContext(vt.Ctx), + mixed.WithLogger(l), + mixed.WithContext(ctx), mixed.WithUserHandler(func(request *statute.ProxyRequest) error { return vt.generalHandler(request) }), diff --git a/wiresocks/udpfw.go b/wiresocks/udpfw.go index a9dde8108..094d693e5 100644 --- a/wiresocks/udpfw.go +++ b/wiresocks/udpfw.go @@ -5,9 +5,11 @@ import ( "net" "net/netip" "sync" + + "github.com/bepass-org/warp-plus/wireguard/tun/netstack" ) -func NewVtunUDPForwarder(ctx context.Context, localBind netip.AddrPort, dest string, vtun *VirtualTun, mtu int) (netip.AddrPort, error) { +func NewVtunUDPForwarder(ctx context.Context, localBind netip.AddrPort, dest string, tnet *netstack.Net, mtu int) (netip.AddrPort, error) { destAddr, err := net.ResolveUDPAddr("udp", dest) if err != nil { return netip.AddrPort{}, err @@ -18,7 +20,7 @@ func NewVtunUDPForwarder(ctx context.Context, localBind netip.AddrPort, dest str return netip.AddrPort{}, err } - rconn, err := vtun.Tnet.DialUDP(nil, destAddr) + rconn, err := tnet.DialUDP(nil, destAddr) if err != nil { return netip.AddrPort{}, err } diff --git a/wiresocks/wiresocks.go b/wiresocks/wiresocks.go deleted file mode 100644 index b50587c42..000000000 --- a/wiresocks/wiresocks.go +++ /dev/null @@ -1,56 +0,0 @@ -package wiresocks - -import ( - "bytes" - "context" - "fmt" - "log/slog" - - "github.com/bepass-org/warp-plus/wireguard/conn" - "github.com/bepass-org/warp-plus/wireguard/device" - "github.com/bepass-org/warp-plus/wireguard/tun/netstack" - "github.com/things-go/go-socks5/bufferpool" -) - -// StartWireguard creates a tun interface on netstack given a configuration -func StartWireguard(ctx context.Context, l *slog.Logger, conf *Configuration) (*VirtualTun, error) { - var request bytes.Buffer - - request.WriteString(fmt.Sprintf("private_key=%s\n", conf.Interface.PrivateKey)) - - 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)) - } - } - - tun, tnet, err := netstack.CreateNetTUN(conf.Interface.Addresses, conf.Interface.DNS, conf.Interface.MTU) - if err != nil { - return nil, err - } - - dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewSLogger(l.With("subsystem", "wireguard-go"))) - err = dev.IpcSet(request.String()) - if err != nil { - return nil, err - } - - err = dev.Up() - if err != nil { - return nil, err - } - - return &VirtualTun{ - Tnet: tnet, - Logger: l.With("subsystem", "vtun"), - Dev: dev, - Ctx: ctx, - pool: bufferpool.NewPool(256 * 1024), - }, nil -}