diff --git a/.github/workflows/go-build.yaml b/.github/workflows/go-build.yaml index 141d41937..3e2a90d90 100644 --- a/.github/workflows/go-build.yaml +++ b/.github/workflows/go-build.yaml @@ -83,30 +83,6 @@ jobs: go-version: '1.22' check-latest: true - - name: Cache wintun - id: cache - uses: actions/cache@v4 - if: matrix.goos == 'windows' - env: - cache-name: cache-wintun - with: - path: ~/wintun-0.14.1.zip - key: wintun-0.14.1.zip - - - name: Download wintun - if: matrix.goos == 'windows' && steps.cache.outputs.cache-hit != 'true' - run: | - curl -LO https://www.wintun.net/builds/wintun-0.14.1.zip --output-dir ~/ - ls -lah ~/ - - - name: Validate wintun - if: matrix.goos == 'windows' - run: | - pushd ~/ - echo "07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51 wintun-0.14.1.zip" | sha256sum --check --status - unzip ~/wintun-0.14.1.zip -d ~/wintun - popd - - name: Build warp-plus run: | go build -v -o warp-plus_${{ env.ASSET_NAME }}/ -trimpath -ldflags "-s -w -buildid= -X main.version=${{ github.ref }}" ./cmd/warp-plus @@ -117,17 +93,6 @@ jobs: cp ${GITHUB_WORKSPACE}/README.md ./warp-plus_${{ env.ASSET_NAME }}/README.md cp ${GITHUB_WORKSPACE}/LICENSE ./warp-plus_${{ env.ASSET_NAME }}/LICENSE - - name: Redistribute wintun.dll - if: matrix.goos == 'windows' - run: | - if [ "$GOARCH" = "amd64" ]; then - mv ~/wintun/wintun/bin/amd64/wintun.dll ./warp-plus_${{ env.ASSET_NAME }}/ - elif [ "$GOARCH" = "arm64" ]; then - mv ~/wintun/wintun/bin/arm64/wintun.dll ./warp-plus_${{ env.ASSET_NAME }}/ - elif [ "$GOARCH" = "386" ]; then - mv ~/wintun/wintun/bin/x86/wintun.dll ./warp-plus_${{ env.ASSET_NAME }}/ - fi - - name: Create ZIP archive shell: bash run: | diff --git a/.gitignore b/.gitignore index 6d2c16dee..28fd42feb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ /warp-scan.exe .idea stuff/ -wintun.dll diff --git a/README.md b/README.md index a22ec9515..dfa903519 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ FLAGS --scan enable warp scanning --rtt DURATION scanner rtt limit (default: 1s) --cache-dir STRING directory to store generated profiles - --tun-experimental enable tun interface (experimental) --fwmark UINT set linux firewall mark for tun mode (default: 4981) --reserved STRING override wireguard reserved value (format: '1,2,3') --wgconf STRING path to a normal wireguard config diff --git a/app/app.go b/app/app.go index 4fe5b1c5b..c13914aeb 100644 --- a/app/app.go +++ b/app/app.go @@ -29,7 +29,6 @@ type WarpOptions struct { Gool bool Scan *wiresocks.ScanOptions CacheDir string - Tun bool FwMark uint32 WireguardConfig string Reserved string @@ -56,10 +55,6 @@ 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") - } - // Decide Working Scenario endpoints := []string{opts.Endpoint, opts.Endpoint} @@ -135,31 +130,6 @@ func runWireguard(ctx context.Context, l *slog.Logger, opts WarpOptions) error { conf.Peers[i] = peer } - if opts.Tun { - // Establish wireguard tunnel on tun interface - var werr error - var tunDev tun.Device - for _, t := range []string{"t1", "t2"} { - // Create a new tun interface - tunDev, werr = newNormalTun([]netip.Addr{opts.DnsAddr}) - if werr != nil { - continue - } - - werr = establishWireguard(l, conf, tunDev, true, opts.FwMark, t) - if werr != nil { - continue - } - break - } - if werr != nil { - return werr - } - - l.Info("serving tun", "interface", "warp0") - return nil - } - // Establish wireguard on userspace stack var werr error var tnet *netstack.Net @@ -167,11 +137,11 @@ func runWireguard(ctx context.Context, l *slog.Logger, opts WarpOptions) error { for _, t := range []string{"t1", "t2"} { // Create userspace tun network stack tunDev, tnet, werr = netstack.CreateNetTUN(conf.Interface.Addresses, conf.Interface.DNS, conf.Interface.MTU) - if err != nil { + if werr != nil { continue } - werr = establishWireguard(l, conf, tunDev, false, opts.FwMark, t) + werr = establishWireguard(l, conf, tunDev, opts.FwMark, t) if werr != nil { continue } @@ -230,31 +200,6 @@ func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint str conf.Peers[i] = peer } - if opts.Tun { - // Establish wireguard tunnel on tun interface - var werr error - var tunDev tun.Device - for _, t := range []string{"t1", "t2"} { - // Create a new tun interface - tunDev, werr = newNormalTun([]netip.Addr{opts.DnsAddr}) - if werr != nil { - continue - } - - // Create userspace tun network stack - werr = establishWireguard(l, &conf, tunDev, true, opts.FwMark, t) - if werr != nil { - continue - } - break - } - if werr != nil { - return werr - } - l.Info("serving tun", "interface", "warp0") - return nil - } - // Establish wireguard on userspace stack var werr error var tnet *netstack.Net @@ -265,7 +210,7 @@ func runWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoint str continue } - werr = establishWireguard(l, &conf, tunDev, false, opts.FwMark, t) + werr = establishWireguard(l, &conf, tunDev, opts.FwMark, t) if werr != nil { continue } @@ -334,7 +279,7 @@ func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoi continue } - werr = establishWireguard(l.With("gool", "outer"), &conf, tunDev, opts.Tun, opts.FwMark, t) + werr = establishWireguard(l.With("gool", "outer"), &conf, tunDev, opts.FwMark, t) if werr != nil { continue } @@ -386,22 +331,6 @@ func runWarpInWarp(ctx context.Context, l *slog.Logger, opts WarpOptions, endpoi conf.Peers[i] = peer } - if opts.Tun { - // Create a new tun interface - tunDev, err := newNormalTun([]netip.Addr{opts.DnsAddr}) - 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, "t0"); err != nil { - return err - } - l.Info("serving tun", "interface", "warp0") - return nil - } - // Create userspace tun network stack tunDev, tnet2, err := netstack.CreateNetTUN(conf.Interface.Addresses, conf.Interface.DNS, conf.Interface.MTU) if err != nil { @@ -409,7 +338,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, false, opts.FwMark, "t0"); err != nil { + if err := establishWireguard(l.With("gool", "inner"), &conf, tunDev, opts.FwMark, "t0"); err != nil { return err } @@ -470,7 +399,7 @@ func runWarpWithPsiphon(ctx context.Context, l *slog.Logger, opts WarpOptions, e continue } - werr = establishWireguard(l, &conf, tunDev, false, opts.FwMark, t) + werr = establishWireguard(l, &conf, tunDev, opts.FwMark, t) if werr != nil { continue } diff --git a/app/tun_others.go b/app/tun_others.go deleted file mode 100644 index 78e5f1703..000000000 --- a/app/tun_others.go +++ /dev/null @@ -1,22 +0,0 @@ -//go:build !windows - -package app - -import ( - "net/netip" - - "github.com/bepass-org/warp-plus/wireguard/device" - wgtun "github.com/bepass-org/warp-plus/wireguard/tun" -) - -func newNormalTun(_ []netip.Addr) (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 deleted file mode 100644 index 63790a7fb..000000000 --- a/app/tun_windows.go +++ /dev/null @@ -1,171 +0,0 @@ -//go:build windows - -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(dns []netip.Addr) (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) - } - - err = luid.SetDNS(family4, dns, nil) - if err != nil { - return nil, fmt.Errorf("unable to set DNS: %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 e6fb88352..ea442b0b8 100644 --- a/app/wg.go +++ b/app/wg.go @@ -93,12 +93,12 @@ func waitHandshake(ctx context.Context, l *slog.Logger, dev *device.Device) erro return nil } -func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wgtun.Device, bind bool, fwmark uint32, t string) error { +func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wgtun.Device, fwmark uint32, t string) 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 bind && fwmark != 0 { + if fwmark != 0 { request.WriteString(fmt.Sprintf("fwmark=%d\n", fwmark)) } @@ -129,12 +129,6 @@ func establishWireguard(l *slog.Logger, conf *wiresocks.Configuration, tunDev wg return err } - if bind { - if err := bindToIface(dev); err != nil { - return err - } - } - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(15*time.Second)) defer cancel() if err := waitHandshake(ctx, l, dev); err != nil { diff --git a/cmd/warp-plus/main.go b/cmd/warp-plus/main.go index 1ff5d601d..7b3d069de 100644 --- a/cmd/warp-plus/main.go +++ b/cmd/warp-plus/main.go @@ -44,8 +44,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-experimental", "enable tun interface (experimental)") - fwmark = fs.UintLong("fwmark", 0x1375, "set linux firewall mark for tun mode") + fwmark = fs.UintLong("fwmark", 0x0, "set linux firewall mark for tun mode (requires sudo/root/CAP_NET_ADMIN)") reserved = fs.StringLong("reserved", "", "override wireguard reserved value (format: '1,2,3')") wgConf = fs.StringLong("wgconf", "", "path to a normal wireguard config") _ = fs.String('c', "config", "", "path to config file") @@ -109,7 +108,6 @@ func main() { License: *key, DnsAddr: dnsAddr, Gool: *gool, - Tun: *tun, FwMark: uint32(*fwmark), WireguardConfig: *wgConf, Reserved: *reserved, @@ -136,10 +134,6 @@ 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/example_config.json b/example_config.json index bb7fa098d..7e3e0bf8c 100644 --- a/example_config.json +++ b/example_config.json @@ -10,7 +10,6 @@ "scan": true, "rtt": "1000ms", "cache-dir": "", - "tun-experimental": false, "fwmark": "0x1375", "wgconf": "", "reserved": "", diff --git a/go.mod b/go.mod index f779e1a27..4ea099dc0 100644 --- a/go.mod +++ b/go.mod @@ -22,8 +22,6 @@ require ( golang.org/x/crypto v0.32.0 golang.org/x/net v0.34.0 golang.org/x/sys v0.29.0 - golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 - golang.zx2c4.com/wireguard/windows v0.5.3 ) require ( @@ -123,6 +121,7 @@ require ( golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect tailscale.com v1.58.2 // indirect diff --git a/go.sum b/go.sum index b9e1d6f47..a7702fce9 100644 --- a/go.sum +++ b/go.sum @@ -403,8 +403,6 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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 v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= diff --git a/wireguard/tun/tun_windows.go b/wireguard/tun/tun_windows.go deleted file mode 100644 index 2af8e3e92..000000000 --- a/wireguard/tun/tun_windows.go +++ /dev/null @@ -1,241 +0,0 @@ -/* SPDX-License-Identifier: MIT - * - * Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved. - */ - -package tun - -import ( - "errors" - "fmt" - "os" - "sync" - "sync/atomic" - "time" - _ "unsafe" - - "golang.org/x/sys/windows" - "golang.zx2c4.com/wintun" -) - -const ( - rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond) - spinloopRateThreshold = 800000000 / 8 // 800mbps - spinloopDuration = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s -) - -type rateJuggler struct { - current atomic.Uint64 - nextByteCount atomic.Uint64 - nextStartTime atomic.Int64 - changing atomic.Bool -} - -type NativeTun struct { - wt *wintun.Adapter - name string - handle windows.Handle - rate rateJuggler - session wintun.Session - readWait windows.Handle - events chan Event - running sync.WaitGroup - closeOnce sync.Once - close atomic.Bool - forcedMTU int - outSizes []int -} - -var ( - WintunTunnelType = "WireGuard" - WintunStaticRequestedGUID *windows.GUID -) - -//go:linkname procyield runtime.procyield -func procyield(cycles uint32) - -//go:linkname nanotime runtime.nanotime -func nanotime() int64 - -// CreateTUN creates a Wintun interface with the given name. Should a Wintun -// interface with the same name exist, it is reused. -func CreateTUN(ifname string, mtu int) (Device, error) { - return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu) -} - -// CreateTUNWithRequestedGUID creates a Wintun interface with the given name and -// a requested GUID. Should a Wintun interface with the same name exist, it is reused. -func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (Device, error) { - wt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID) - if err != nil { - return nil, fmt.Errorf("Error creating interface: %w", err) - } - - forcedMTU := 1420 - if mtu > 0 { - forcedMTU = mtu - } - - tun := &NativeTun{ - wt: wt, - name: ifname, - handle: windows.InvalidHandle, - events: make(chan Event, 10), - forcedMTU: forcedMTU, - } - - tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB - if err != nil { - tun.wt.Close() - close(tun.events) - return nil, fmt.Errorf("Error starting session: %w", err) - } - tun.readWait = tun.session.ReadWaitEvent() - return tun, nil -} - -func (tun *NativeTun) Name() (string, error) { - return tun.name, nil -} - -func (tun *NativeTun) File() *os.File { - return nil -} - -func (tun *NativeTun) Events() <-chan Event { - return tun.events -} - -func (tun *NativeTun) Close() error { - var err error - tun.closeOnce.Do(func() { - tun.close.Store(true) - windows.SetEvent(tun.readWait) - tun.running.Wait() - tun.session.End() - if tun.wt != nil { - tun.wt.Close() - } - close(tun.events) - }) - return err -} - -func (tun *NativeTun) MTU() (int, error) { - return tun.forcedMTU, nil -} - -// TODO: This is a temporary hack. We really need to be monitoring the interface in real time and adapting to MTU changes. -func (tun *NativeTun) ForceMTU(mtu int) { - if tun.close.Load() { - return - } - update := tun.forcedMTU != mtu - tun.forcedMTU = mtu - if update { - tun.events <- EventMTUUpdate - } -} - -func (tun *NativeTun) BatchSize() int { - // TODO: implement batching with wintun - return 1 -} - -// Note: Read() and Write() assume the caller comes only from a single thread; there's no locking. - -func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) { - tun.running.Add(1) - defer tun.running.Done() -retry: - if tun.close.Load() { - return 0, os.ErrClosed - } - start := nanotime() - shouldSpin := tun.rate.current.Load() >= spinloopRateThreshold && uint64(start-tun.rate.nextStartTime.Load()) <= rateMeasurementGranularity*2 - for { - if tun.close.Load() { - return 0, os.ErrClosed - } - packet, err := tun.session.ReceivePacket() - switch err { - case nil: - n := copy(bufs[0][offset:], packet) - sizes[0] = n - tun.session.ReleaseReceivePacket(packet) - tun.rate.update(uint64(n)) - return 1, nil - case windows.ERROR_NO_MORE_ITEMS: - if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration { - windows.WaitForSingleObject(tun.readWait, windows.INFINITE) - goto retry - } - procyield(1) - continue - case windows.ERROR_HANDLE_EOF: - return 0, os.ErrClosed - case windows.ERROR_INVALID_DATA: - return 0, errors.New("Send ring corrupt") - } - return 0, fmt.Errorf("Read failed: %w", err) - } -} - -func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) { - tun.running.Add(1) - defer tun.running.Done() - if tun.close.Load() { - return 0, os.ErrClosed - } - - for i, buf := range bufs { - packetSize := len(buf) - offset - tun.rate.update(uint64(packetSize)) - - packet, err := tun.session.AllocateSendPacket(packetSize) - switch err { - case nil: - // TODO: Explore options to eliminate this copy. - copy(packet, buf[offset:]) - tun.session.SendPacket(packet) - continue - case windows.ERROR_HANDLE_EOF: - return i, os.ErrClosed - case windows.ERROR_BUFFER_OVERFLOW: - continue // Dropping when ring is full. - default: - return i, fmt.Errorf("Write failed: %w", err) - } - } - return len(bufs), nil -} - -// LUID returns Windows interface instance ID. -func (tun *NativeTun) LUID() uint64 { - tun.running.Add(1) - defer tun.running.Done() - if tun.close.Load() { - return 0 - } - return tun.wt.LUID() -} - -// RunningVersion returns the running version of the Wintun driver. -func (tun *NativeTun) RunningVersion() (version uint32, err error) { - return wintun.RunningVersion() -} - -func (rate *rateJuggler) update(packetLen uint64) { - now := nanotime() - total := rate.nextByteCount.Add(packetLen) - period := uint64(now - rate.nextStartTime.Load()) - if period >= rateMeasurementGranularity { - if !rate.changing.CompareAndSwap(false, true) { - return - } - rate.nextStartTime.Store(now) - rate.current.Store(total * uint64(time.Second/time.Nanosecond) / period) - rate.nextByteCount.Store(0) - rate.changing.Store(false) - } -}