From 3a9504a5f864a01d7c616a895bc7cf19b5b0a296 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Mon, 13 May 2024 23:44:50 +0100 Subject: [PATCH] tun: add tun support (experimental) (#79) * tun: add tun support (linux) * tun: windows support (experimental) Signed-off-by: Mark Pashmfouroush --- app/app.go | 158 +++++++++++++++++++++++++++++++-------- app/tun_others.go | 21 ++++++ app/tun_windows.go | 164 +++++++++++++++++++++++++++++++++++++++++ app/wg.go | 104 ++++++++++++++++++++++++++ example_config.json | 4 +- go.mod | 3 +- go.sum | 18 ++--- main.go | 8 ++ wiresocks/proxy.go | 14 +++- wiresocks/udpfw.go | 6 +- wiresocks/wiresocks.go | 56 -------------- 11 files changed, 452 insertions(+), 104 deletions(-) create mode 100644 app/tun_others.go create mode 100644 app/tun_windows.go create mode 100644 app/wg.go delete mode 100644 wiresocks/wiresocks.go diff --git a/app/app.go b/app/app.go index 290d022f3..b6fd7f572 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,6 +26,8 @@ type WarpOptions struct { Gool bool Scan *wiresocks.ScanOptions CacheDir string + Tun bool + FwMark uint32 } type PsiphonOptions struct { @@ -40,6 +43,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 { + 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 +101,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 +118,188 @@ 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 { + // 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, true, opts.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, false, opts.FwMark); 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 and bind the wireguard sockets to the default interface and apply + if err := establishWireguard(l.With("gool", "outer"), conf, tunDev, opts.Tun, opts.FwMark); 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 { + // Create a new tun interface + tunDev, err := newNormalTun() + if err != nil { + return err + } + + // Establish wireguard tunnel on tun interface but don't bind + // wireguard sockets to default interface and don't apply fwmark. + if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, false, opts.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, false, opts.FwMark); 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, false, opts.FwMark); 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/tun_others.go b/app/tun_others.go new file mode 100644 index 000000000..0985ab72a --- /dev/null +++ b/app/tun_others.go @@ -0,0 +1,21 @@ +//go:build !windows + +package app + +import ( + "github.com/bepass-org/warp-plus/wireguard/device" + wgtun "github.com/bepass-org/warp-plus/wireguard/tun" +) + +func newNormalTun() (wgtun.Device, error) { + tunDev, err := wgtun.CreateTUN("warp0", 1280) + if err != nil { + return nil, err + } + return tunDev, nil + +} + +func bindToIface(_ *device.Device) error { + return nil +} diff --git a/app/tun_windows.go b/app/tun_windows.go new file mode 100644 index 000000000..e4e94457c --- /dev/null +++ b/app/tun_windows.go @@ -0,0 +1,164 @@ +package app + +import ( + "errors" + "fmt" + "net" + "net/netip" + + "github.com/bepass-org/warp-plus/wireguard/conn" + "github.com/bepass-org/warp-plus/wireguard/device" + "github.com/bepass-org/warp-plus/wireguard/tun" + wgtun "github.com/bepass-org/warp-plus/wireguard/tun" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" +) + +const wintunGUID = "c33d325f-20cd-44e5-998c-19b0c15b4df1" +const family4 = winipcfg.AddressFamily(windows.AF_INET) +const family6 = winipcfg.AddressFamily(windows.AF_INET6) + +func newNormalTun() (wgtun.Device, error) { + guid, _ := windows.GUIDFromString(wintunGUID) + tunDev, err := wgtun.CreateTUNWithRequestedGUID("warp0", &guid, 1280) + if err != nil { + return nil, err + } + + nativeTunDevice := tunDev.(*tun.NativeTun) + luid := winipcfg.LUID(nativeTunDevice.LUID()) + + err = luid.SetIPAddressesForFamily(family4, []netip.Prefix{netip.MustParsePrefix("172.16.0.2/24")}) + if err != nil { + return nil, err + } + + // Set this to break IPv6 and prevent leaks. TODO: fix windows ipv6 tun + err = luid.SetIPAddressesForFamily(family6, []netip.Prefix{netip.MustParsePrefix("fd12:3456:789a:1::1/128")}) + if err != nil { + return nil, err + } + +tryAgain4: + err = luid.SetRoutesForFamily(family4, []*winipcfg.RouteData{{Destination: netip.MustParsePrefix("0.0.0.0/0"), NextHop: netip.IPv4Unspecified(), Metric: 0}}) + if err != nil && err == windows.ERROR_NOT_FOUND { + goto tryAgain4 + } else if err != nil { + return nil, err + } + + var ipif *winipcfg.MibIPInterfaceRow + ipif, err = luid.IPInterface(family4) + if err != nil { + return nil, err + } + ipif.ForwardingEnabled = true + ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled + ipif.DadTransmits = 0 + ipif.ManagedAddressConfigurationSupported = false + ipif.OtherStatefulConfigurationSupported = false + ipif.NLMTU = uint32(1280) + ipif.UseAutomaticMetric = false + ipif.Metric = 0 + + err = ipif.Set() + if err != nil && err == windows.ERROR_NOT_FOUND { + goto tryAgain4 + } else if err != nil { + return nil, fmt.Errorf("unable to set metric and MTU: %w", err) + } + +tryAgain6: + err = luid.SetRoutesForFamily(family6, []*winipcfg.RouteData{{Destination: netip.MustParsePrefix("::/0"), NextHop: netip.IPv6Unspecified(), Metric: 0}}) + if err != nil && err == windows.ERROR_NOT_FOUND { + goto tryAgain6 + } else if err != nil { + return nil, err + } + + var ipif6 *winipcfg.MibIPInterfaceRow + ipif6, err = luid.IPInterface(family6) + if err != nil { + return nil, err + } + ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled + ipif6.DadTransmits = 0 + ipif6.ManagedAddressConfigurationSupported = false + ipif6.OtherStatefulConfigurationSupported = false + ipif6.NLMTU = uint32(1280) + ipif6.UseAutomaticMetric = false + ipif6.Metric = 0 + + err = ipif6.Set() + if err != nil && err == windows.ERROR_NOT_FOUND { + goto tryAgain6 + } else if err != nil { + return nil, fmt.Errorf("unable to set metric and MTU: %w", err) + } + + return tunDev, nil + +} + +func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) { + interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways) + if err != nil { + return "", fmt.Errorf("get default interface failure. %w", err) + } + + var destination netip.Prefix + if family == family4 { + destination = netip.PrefixFrom(netip.IPv4Unspecified(), 0) + } else { + destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0) + } + + for _, ifaceM := range interfaces { + if ifaceM.OperStatus != winipcfg.IfOperStatusUp { + continue + } + + ifname := ifaceM.FriendlyName() + + if ifname == "warp0" { + continue + } + + for gatewayAddress := ifaceM.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next { + nextHop, _ := netip.AddrFromSlice(gatewayAddress.Address.IP()) + + if _, err = ifaceM.LUID.Route(destination, nextHop.Unmap()); err == nil { + return ifname, nil + } + } + } + + return "", errors.New("interface not found") +} + +func bindToIface(dev *device.Device) error { + ifaceName, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(family4)) + if err != nil { + return err + } + + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return err + } + + bind, ok := dev.Bind().(conn.BindSocketToInterface) + if !ok { + return errors.New("failed to cast to bindsockettointerface") + } + + if err := bind.BindSocketToInterface4(uint32(iface.Index), false); err != nil { + return err + } + + if err := bind.BindSocketToInterface6(uint32(iface.Index), false); err != nil { + return err + } + + return nil +} diff --git a/app/wg.go b/app/wg.go new file mode 100644 index 000000000..bf59eaff8 --- /dev/null +++ b/app/wg.go @@ -0,0 +1,104 @@ +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 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, bind bool, 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 + } + + if bind { + if err := bindToIface(dev); err != nil { + return err + } + } + + return nil +} diff --git a/example_config.json b/example_config.json index 599791482..6004cd894 100644 --- a/example_config.json +++ b/example_config.json @@ -8,5 +8,7 @@ "country": "DE", "scan": true, "rtt": "1000ms", - "cache-dir": "" + "cache-dir": "", + "tun-experimental": false, + "fwmark": "0x1375" } diff --git a/go.mod b/go.mod index 1b7706c0c..3266d9359 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( golang.org/x/net v0.24.0 golang.org/x/sys v0.19.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 + golang.zx2c4.com/wireguard/windows v0.5.3 gvisor.dev/gvisor v0.0.0-20240503213918-b7c924bc64f8 ) @@ -55,6 +56,7 @@ require ( github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300 // indirect github.com/mroth/weightedrand v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect @@ -79,7 +81,6 @@ require ( gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.5.0 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect - golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index 94d54fbbd..8868253bb 100644 --- a/go.sum +++ b/go.sum @@ -95,8 +95,8 @@ github.com/grafov/m3u8 v0.0.0-20171211212457-6ab8f28ed427/go.mod h1:PdjzaU/pJUo4 github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= @@ -120,10 +120,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mdlayher/netlink v1.4.2-0.20210930205308-a81a8c23d40a h1:yk5OmRew64lWdeNanQ3l0hDgUt1E8MfipPhh/GO9Tuw= -github.com/mdlayher/netlink v1.4.2-0.20210930205308-a81a8c23d40a/go.mod h1:qw8F9IVzxa0GpqhVAfOw8DNyo7ec/jxI6bPWPEg1MV4= -github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84 h1:L1jnQ6o+K3M574eez7eTxbsia6H1SfJaVpaXY33L37Q= -github.com/mdlayher/socket v0.0.0-20210624160740-9dbe287ded84/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300 h1:cpzamikkKRyu3TZF14CsVFf/CmhlrqZ+7P9aVZYtXz8= github.com/miekg/dns v1.1.44-0.20210804161652-ab67aa642300/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E= @@ -220,8 +220,6 @@ golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM= -golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= @@ -287,6 +285,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= +golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= +golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -304,5 +304,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20240503213918-b7c924bc64f8 h1:DzvThIeaqCvbufowKcGjJ04qrViggGUg6zEcGFC4i3k= gvisor.dev/gvisor v0.0.0-20240503213918-b7c924bc64f8/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= -honnef.co/go/tools v0.4.2 h1:6qXr+R5w+ktL5UkwEbPp+fEvfyoMPche6GkOpGHZcLc= -honnef.co/go/tools v0.4.2/go.mod h1:36ZgoUOrqOk1GxwHhyryEkq8FQWkUO2xGuSMhUCcdvA= diff --git a/main.go b/main.go index a7b994320..5a03f847a 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-experimental", "enable tun interface (experimental)") + 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") ) @@ -134,6 +136,8 @@ func main() { Endpoint: *endpoint, License: *key, Gool: *gool, + Tun: *tun, + FwMark: uint32(*fwmark), } switch { @@ -157,6 +161,10 @@ func main() { opts.Scan = &wiresocks.ScanOptions{V4: *v4, V6: *v6, MaxRTT: *rtt} } + if *tun { + l.Info("tun mode enabled") + } + // 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 -}