github.com/labulakalia/water@v0.0.5-0.20231118024244-f351ca6784b6/syscalls_tun_windows.go (about)

     1  package water
     2  
     3  import (
     4  	_ "embed"
     5  	"errors"
     6  	"fmt"
     7  	"golang.org/x/sys/windows"
     8  	"golang.zx2c4.com/wintun"
     9  	"time"
    10  	_ "unsafe"
    11  	"os"
    12  	"sync"
    13  	"sync/atomic"
    14  )
    15  
    16  
    17  const (
    18  	rateMeasurementGranularity = uint64((time.Second / 2) / time.Nanosecond)
    19  	spinloopRateThreshold      = 800000000 / 8                                   // 800mbps
    20  	spinloopDuration           = uint64(time.Millisecond / 80 / time.Nanosecond) // ~1gbit/s
    21  )
    22  type Event int
    23  
    24  const (
    25  	EventUp = 1 << iota
    26  	EventDown
    27  	EventMTUUpdate
    28  )
    29  
    30  type rateJuggler struct {
    31  	current       uint64
    32  	nextByteCount uint64
    33  	nextStartTime int64
    34  	changing      int32
    35  }
    36  
    37  type NativeTun struct {
    38  	wt        *wintun.Adapter
    39  	name      string
    40  	handle    windows.Handle
    41  	rate      rateJuggler
    42  	session   wintun.Session
    43  	readWait  windows.Handle
    44  	events    chan Event
    45  	running   sync.WaitGroup
    46  	closeOnce sync.Once
    47  	close     int32
    48  	forcedMTU int
    49  }
    50  
    51  type WTun struct {
    52  	dev *NativeTun
    53  }
    54  
    55  func (w *WTun) Close() error {
    56  	return w.dev.Close()
    57  }
    58  
    59  func (w *WTun) Write(b []byte) (int, error) {
    60  	return w.dev.Write(b, 0)
    61  }
    62  
    63  func (w *WTun) Read(b []byte) (int, error) {
    64  	return w.dev.Read(b, 0)
    65  }
    66  
    67  var (
    68  	WintunTunnelType          = "Wintun"
    69  	WintunStaticRequestedGUID *windows.GUID
    70  )
    71  
    72  //go:linkname procyield runtime.procyield
    73  func procyield(cycles uint32)
    74  
    75  //go:linkname nanotime runtime.nanotime
    76  func nanotime() int64
    77  
    78  
    79  
    80  func openTunDev(config Config) (ifce *Interface, err error) {
    81  	gUID := &windows.GUID{
    82  		0x0000000,
    83  		0xFFFF,
    84  		0xFFFF,
    85  		[8]byte{0xFF, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
    86  	}
    87  	if config.PlatformSpecificParams.Name == "" {
    88  		config.PlatformSpecificParams.Name = "WaterIface"
    89  	}
    90  	nativeTunDevice, err := CreateTUNWithRequestedGUID(config.PlatformSpecificParams.Name, gUID, 0)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	ifce = &Interface{
    95  		isTAP: config.DeviceType == TAP,
    96  		ReadWriteCloser: &WTun{dev: nativeTunDevice},
    97  		name: config.PlatformSpecificParams.Name,
    98  	}
    99  	return ifce, nil
   100  }
   101  
   102  //
   103  // CreateTUN creates a Wintun interface with the given name. Should a Wintun
   104  // interface with the same name exist, it is reused.
   105  //
   106  func CreateTUN(ifname string, mtu int) (*NativeTun, error) {
   107  	return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu)
   108  }
   109  
   110  //
   111  // CreateTUNWithRequestedGUID creates a Wintun interface with the given name and
   112  // a requested GUID. Should a Wintun interface with the same name exist, it is reused.
   113  //
   114  func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (*NativeTun, error) {
   115  	wt, err := wintun.CreateAdapter(ifname, WintunTunnelType, requestedGUID)
   116  	if err != nil {
   117  		return nil, fmt.Errorf("Error creating interface: %w", err)
   118  	}
   119  
   120  	forcedMTU := 1420
   121  	if mtu > 0 {
   122  		forcedMTU = mtu
   123  	}
   124  
   125  	tun := &NativeTun{
   126  		wt:        wt,
   127  		name:      ifname,
   128  		handle:    windows.InvalidHandle,
   129  		events:    make(chan Event, 10),
   130  		forcedMTU: forcedMTU,
   131  	}
   132  
   133  	tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB
   134  	if err != nil {
   135  		tun.wt.Close()
   136  		close(tun.events)
   137  		return nil, fmt.Errorf("Error starting session: %w", err)
   138  	}
   139  	tun.readWait = tun.session.ReadWaitEvent()
   140  	return tun, nil
   141  }
   142  
   143  func (tun *NativeTun) Name() (string, error) {
   144  	return tun.name, nil
   145  }
   146  
   147  func (tun *NativeTun) File() *os.File {
   148  	return nil
   149  }
   150  
   151  func (tun *NativeTun) Events() chan Event {
   152  	return tun.events
   153  }
   154  
   155  func (tun *NativeTun) Close() error {
   156  	var err error
   157  	tun.closeOnce.Do(func() {
   158  		atomic.StoreInt32(&tun.close, 1)
   159  		windows.SetEvent(tun.readWait)
   160  		tun.running.Wait()
   161  		tun.session.End()
   162  		if tun.wt != nil {
   163  			tun.wt.Close()
   164  		}
   165  		close(tun.events)
   166  	})
   167  	return err
   168  }
   169  
   170  func (tun *NativeTun) MTU() (int, error) {
   171  	return tun.forcedMTU, nil
   172  }
   173  
   174  // TODO: This is a temporary hack. We really need to be monitoring the interface in real time and adapting to MTU changes.
   175  func (tun *NativeTun) ForceMTU(mtu int) {
   176  	update := tun.forcedMTU != mtu
   177  	tun.forcedMTU = mtu
   178  	if update {
   179  		tun.events <- EventMTUUpdate
   180  	}
   181  }
   182  
   183  // Note: Read() and Write() assume the caller comes only from a single thread; there's no locking.
   184  
   185  func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
   186  	tun.running.Add(1)
   187  	defer tun.running.Done()
   188  retry:
   189  	if atomic.LoadInt32(&tun.close) == 1 {
   190  		return 0, os.ErrClosed
   191  	}
   192  	start := nanotime()
   193  	shouldSpin := atomic.LoadUint64(&tun.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&tun.rate.nextStartTime)) <= rateMeasurementGranularity*2
   194  	for {
   195  		if atomic.LoadInt32(&tun.close) == 1 {
   196  			return 0, os.ErrClosed
   197  		}
   198  		packet, err := tun.session.ReceivePacket()
   199  		switch err {
   200  		case nil:
   201  			packetSize := len(packet)
   202  			copy(buff[offset:], packet)
   203  			tun.session.ReleaseReceivePacket(packet)
   204  			tun.rate.update(uint64(packetSize))
   205  			return packetSize, nil
   206  		case windows.ERROR_NO_MORE_ITEMS:
   207  			if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
   208  				windows.WaitForSingleObject(tun.readWait, windows.INFINITE)
   209  				goto retry
   210  			}
   211  			procyield(1)
   212  			continue
   213  		case windows.ERROR_HANDLE_EOF:
   214  			return 0, os.ErrClosed
   215  		case windows.ERROR_INVALID_DATA:
   216  			return 0, errors.New("Send ring corrupt")
   217  		}
   218  		return 0, fmt.Errorf("Read failed: %w", err)
   219  	}
   220  }
   221  
   222  func (tun *NativeTun) Flush() error {
   223  	return nil
   224  }
   225  
   226  func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
   227  	tun.running.Add(1)
   228  	defer tun.running.Done()
   229  	if atomic.LoadInt32(&tun.close) == 1 {
   230  		return 0, os.ErrClosed
   231  	}
   232  
   233  	packetSize := len(buff) - offset
   234  	tun.rate.update(uint64(packetSize))
   235  
   236  	packet, err := tun.session.AllocateSendPacket(packetSize)
   237  	if err == nil {
   238  		copy(packet, buff[offset:])
   239  		tun.session.SendPacket(packet)
   240  		return packetSize, nil
   241  	}
   242  	switch err {
   243  	case windows.ERROR_HANDLE_EOF:
   244  		return 0, os.ErrClosed
   245  	case windows.ERROR_BUFFER_OVERFLOW:
   246  		return 0, nil // Dropping when ring is full.
   247  	}
   248  	return 0, fmt.Errorf("Write failed: %w", err)
   249  }
   250  
   251  // LUID returns Windows interface instance ID.
   252  func (tun *NativeTun) LUID() uint64 {
   253  	tun.running.Add(1)
   254  	defer tun.running.Done()
   255  	if atomic.LoadInt32(&tun.close) == 1 {
   256  		return 0
   257  	}
   258  	return tun.wt.LUID()
   259  }
   260  
   261  // RunningVersion returns the running version of the Wintun driver.
   262  func (tun *NativeTun) RunningVersion() (version uint32, err error) {
   263  	return wintun.RunningVersion()
   264  }
   265  
   266  func (rate *rateJuggler) update(packetLen uint64) {
   267  	now := nanotime()
   268  	total := atomic.AddUint64(&rate.nextByteCount, packetLen)
   269  	period := uint64(now - atomic.LoadInt64(&rate.nextStartTime))
   270  	if period >= rateMeasurementGranularity {
   271  		if !atomic.CompareAndSwapInt32(&rate.changing, 0, 1) {
   272  			return
   273  		}
   274  		atomic.StoreInt64(&rate.nextStartTime, now)
   275  		atomic.StoreUint64(&rate.current, total*uint64(time.Second/time.Nanosecond)/period)
   276  		atomic.StoreUint64(&rate.nextByteCount, 0)
   277  		atomic.StoreInt32(&rate.changing, 0)
   278  	}
   279  }