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 }