From 8458319bab375fe2a2fef28739904127cafbf279 Mon Sep 17 00:00:00 2001 From: Mark Pashmfouroush Date: Thu, 2 May 2024 10:43:22 +0100 Subject: [PATCH] tun: windows support (experimental) Signed-off-by: Mark Pashmfouroush --- app/app.go | 30 ++++---- app/tun_others.go | 21 ++++++ app/tun_windows.go | 164 ++++++++++++++++++++++++++++++++++++++++++++ app/wg.go | 17 ++--- example_config.json | 2 +- go.mod | 3 +- go.sum | 18 +++-- main.go | 5 +- 8 files changed, 220 insertions(+), 40 deletions(-) create mode 100644 app/tun_others.go create mode 100644 app/tun_windows.go diff --git a/app/app.go b/app/app.go index d83dc0698..b6fd7f572 100644 --- a/app/app.go +++ b/app/app.go @@ -26,17 +26,14 @@ type WarpOptions struct { Gool bool Scan *wiresocks.ScanOptions CacheDir string - Tun *TunOptions + Tun bool + FwMark uint32 } 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") @@ -46,7 +43,7 @@ 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 { + if opts.Psiphon != nil && opts.Tun { return errors.New("can't use psiphon and tun at the same time") } @@ -121,7 +118,7 @@ func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint str conf.Peers[i] = peer } - if opts.Tun != nil { + if opts.Tun { // Create a new tun interface tunDev, err := newNormalTun() if err != nil { @@ -129,7 +126,7 @@ func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint str } // Establish wireguard tunnel on tun interface - if err := establishWireguard(l, conf, tunDev, opts.Tun.FwMark); err != nil { + if err := establishWireguard(l, conf, tunDev, true, opts.FwMark); err != nil { return err } l.Info("serving tun", "interface", "warp0") @@ -143,7 +140,7 @@ func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint str } // Establish wireguard on userspace stack - if err := establishWireguard(l, conf, tunDev, 0); err != nil { + if err := establishWireguard(l, conf, tunDev, false, opts.FwMark); err != nil { return err } @@ -186,8 +183,8 @@ func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoi return err } - // Establish wireguard on userspace stack - if err := establishWireguard(l.With("gool", "outer"), conf, tunDev, 0); err != nil { + // 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 } @@ -218,15 +215,16 @@ func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoi conf.Peers[i] = peer } - if opts.Tun != nil { + 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.With("gool", "inner"), conf, tunDev, opts.Tun.FwMark); err != nil { + // 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") @@ -240,7 +238,7 @@ func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoi } // Establish wireguard on userspace stack - if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, 0); err != nil { + if err := establishWireguard(l.With("gool", "inner"), conf, tunDev, false, opts.FwMark); err != nil { return err } @@ -283,7 +281,7 @@ func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, opts WarpOptions, e } // Establish wireguard on userspace stack - if err := establishWireguard(l, conf, tunDev, 0); err != nil { + if err := establishWireguard(l, conf, tunDev, false, opts.FwMark); err != nil { return err } 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 index eac3b59df..bf59eaff8 100644 --- a/app/wg.go +++ b/app/wg.go @@ -16,15 +16,6 @@ import ( "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 { @@ -68,7 +59,7 @@ func usermodeTunTest(ctx context.Context, l *slog.Logger, tnet *netstack.Net) er return nil } -func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wgtun.Device, fwmark uint32) error { +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 @@ -103,5 +94,11 @@ func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wg 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 2c3c92a7e..6004cd894 100644 --- a/example_config.json +++ b/example_config.json @@ -9,6 +9,6 @@ "scan": true, "rtt": "1000ms", "cache-dir": "", - "tun": false, + "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 bd41f82b7..5a03f847a 100644 --- a/main.go +++ b/main.go @@ -79,7 +79,7 @@ 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") + 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") @@ -136,6 +136,8 @@ func main() { Endpoint: *endpoint, License: *key, Gool: *gool, + Tun: *tun, + FwMark: uint32(*fwmark), } switch { @@ -161,7 +163,6 @@ func main() { 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