├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENCE ├── adapter.go ├── adapter_test.go ├── consts.go ├── embed.go ├── embed ├── wintun_amd64.dll ├── wintun_arm.dll ├── wintun_arm64.dll └── wintun_x86.dll ├── embed_windows_386.go ├── embed_windows_amd64.go ├── embed_windows_arm.go ├── embed_windows_arm64.go ├── error.go ├── go.mod ├── go.sum ├── logger.go ├── option.go ├── readme.md ├── wintun.go └── wintun_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | test: 14 | name: go-test 15 | runs-on: windows-latest 16 | env: 17 | CGO_ENABLED: 0 18 | steps: 19 | - name: disable-auto-crlf 20 | run: | 21 | git config --global core.autocrlf false 22 | git config --global core.eol lf 23 | 24 | - name: clone-repo 25 | uses: actions/checkout@v4 26 | 27 | - name: setup-go 28 | uses: actions/setup-go@v4 29 | with: 30 | go-version: '1.21' 31 | 32 | - name: go-vet-fmt-test # fmt check see Test_Gofmt 33 | run : | 34 | go vet 35 | go test -v -timeout 120s -tags "-race" ./... 36 | 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 lysShub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /adapter.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package wintun 5 | 6 | import ( 7 | "context" 8 | "sync" 9 | "syscall" 10 | 11 | "github.com/pkg/errors" 12 | 13 | "unsafe" 14 | 15 | "golang.org/x/sys/windows" 16 | "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" 17 | ) 18 | 19 | type Adapter struct { 20 | // when the handle/session is being used(recv/send etc.), can't Stop/Close, 21 | // reference uint test Test_Recving_Close 22 | mu sync.RWMutex 23 | 24 | handle uintptr 25 | session uintptr 26 | } 27 | 28 | func (a *Adapter) sessionLocked(trap uintptr, args ...uintptr) (r1, r2 uintptr, err error) { 29 | if a.handle == 0 { 30 | return 0, 0, errors.WithStack(ErrAdapterClosed{}) 31 | } else if a.session == 0 { 32 | return 0, 0, errors.WithStack(ErrAdapterStoped{}) 33 | } 34 | r1, r2, err = syscall.SyscallN(trap, append([]uintptr{a.session}, args...)...) 35 | if err == windows.ERROR_SUCCESS { 36 | err = nil 37 | } 38 | return r1, r2, errors.WithStack(err) 39 | } 40 | 41 | func (a *Adapter) Start(capacity uint32) (err error) { 42 | if capacity < MinRingCapacity || MaxRingCapacity < capacity { 43 | return errors.New("invalid ring buff capacity") 44 | } 45 | a.mu.Lock() 46 | defer a.mu.Unlock() 47 | 48 | if a.handle == 0 { 49 | return errors.WithStack(ErrAdapterClosed{}) 50 | } 51 | fd, _, err := syscall.SyscallN( 52 | procStartSession.Addr(), 53 | a.handle, 54 | uintptr(capacity), 55 | ) 56 | if err != windows.ERROR_SUCCESS { 57 | return err 58 | } 59 | a.session = fd 60 | return nil 61 | } 62 | 63 | func (a *Adapter) Stop() error { 64 | a.mu.Lock() 65 | defer a.mu.Unlock() 66 | return a.stopLocked() 67 | } 68 | 69 | func (a *Adapter) stopLocked() error { 70 | if a.session > 0 { 71 | _, _, err := syscall.SyscallN(procEndSession.Addr(), uintptr(a.session)) 72 | if err != windows.ERROR_SUCCESS { 73 | return err 74 | } 75 | a.session = 0 76 | } 77 | return nil 78 | } 79 | 80 | func (a *Adapter) Close() error { 81 | a.mu.Lock() 82 | defer a.mu.Unlock() 83 | 84 | if a.handle > 0 { 85 | err := a.stopLocked() 86 | if err != nil { 87 | return err 88 | } 89 | 90 | _, _, err = syscall.SyscallN(procCloseAdapter.Addr(), a.handle) 91 | if err != windows.ERROR_SUCCESS { 92 | return err 93 | } 94 | a.handle = 0 95 | } 96 | return nil 97 | } 98 | 99 | func (a *Adapter) GetAdapterLuid() (winipcfg.LUID, error) { 100 | a.mu.RLock() 101 | defer a.mu.RUnlock() 102 | 103 | if a.handle == 0 { 104 | return 0, errors.WithStack(ErrAdapterClosed{}) 105 | } 106 | var luid uint64 107 | _, _, err := syscall.SyscallN( 108 | procGetAdapterLUID.Addr(), 109 | a.handle, 110 | uintptr(unsafe.Pointer(&luid)), 111 | ) 112 | if err != windows.ERROR_SUCCESS { 113 | return 0, err 114 | } 115 | return winipcfg.LUID(luid), nil 116 | } 117 | 118 | func (a *Adapter) Index() (int, error) { 119 | luid, err := a.GetAdapterLuid() 120 | if err != nil { 121 | return 0, err 122 | } 123 | 124 | row, err := luid.Interface() 125 | if err != nil { 126 | return 0, err 127 | } 128 | return int(row.InterfaceIndex), nil 129 | } 130 | 131 | func (a *Adapter) getReadWaitEvent() (windows.Handle, error) { 132 | r0, _, err := a.sessionLocked(procGetReadWaitEvent.Addr()) 133 | if r0 == 0 { 134 | return 0, err 135 | } 136 | return windows.Handle(r0), nil 137 | } 138 | 139 | type rpack []byte 140 | 141 | // Recv receive outbound(income adapter) ip packet, after must call ap.Release(p) 142 | func (a *Adapter) Recv(ctx context.Context) (ip rpack, err error) { 143 | a.mu.RLock() 144 | defer a.mu.RUnlock() 145 | 146 | var size uint32 147 | for { 148 | r0, _, err := a.sessionLocked( 149 | procReceivePacket.Addr(), 150 | (uintptr)(unsafe.Pointer(&size)), 151 | ) 152 | 153 | if r0 > 0 { 154 | ptr := unsafe.Add(nil, r0) 155 | return unsafe.Slice((*byte)(ptr), size), nil 156 | } else { 157 | if errors.Is(err, windows.ERROR_NO_MORE_ITEMS) { 158 | 159 | var event uint32 160 | if w, err := a.getReadWaitEvent(); err != nil { 161 | return nil, errors.WithStack(err) 162 | } else { 163 | event, err = windows.WaitForSingleObject(w, 100) 164 | if err != nil { 165 | return nil, errors.WithStack(err) 166 | } 167 | } 168 | 169 | switch event { 170 | case windows.WAIT_OBJECT_0: 171 | case uint32(windows.WAIT_TIMEOUT): 172 | select { 173 | case <-ctx.Done(): 174 | return nil, errors.WithStack(ctx.Err()) 175 | default: 176 | } 177 | default: 178 | return nil, errors.Errorf("invalid WaitForSingleObject event %d", event) 179 | } 180 | } else { 181 | return nil, err 182 | } 183 | } 184 | } 185 | } 186 | 187 | func (a *Adapter) Release(p rpack) error { 188 | if len(p) == 0 { 189 | return nil 190 | } 191 | a.mu.RLock() 192 | defer a.mu.RUnlock() 193 | 194 | _, _, err := a.sessionLocked( 195 | procReleaseReceivePacket.Addr(), 196 | uintptr(unsafe.Pointer(&p[0])), 197 | ) 198 | return err 199 | } 200 | 201 | type spack []byte 202 | 203 | func (a *Adapter) Alloc(size int) (spack, error) { 204 | if size == 0 { 205 | return spack{}, nil 206 | } 207 | a.mu.RLock() 208 | defer a.mu.RUnlock() 209 | 210 | r0, _, err := a.sessionLocked( 211 | procAllocateSendPacket.Addr(), 212 | uintptr(size), 213 | ) 214 | if r0 == 0 { 215 | return nil, err 216 | } 217 | 218 | p := (*byte)(unsafe.Add(*new(unsafe.Pointer), r0)) 219 | return unsafe.Slice(p, size), nil 220 | } 221 | 222 | // Send send inbound(outgoing adapter) ip packet, ip must alloc by AllocPacket 223 | func (a *Adapter) Send(ip spack) error { 224 | if len(ip) == 0 { 225 | return nil 226 | } 227 | a.mu.RLock() 228 | defer a.mu.RUnlock() 229 | 230 | _, _, err := a.sessionLocked( 231 | procSendPacket.Addr(), 232 | uintptr(unsafe.Pointer(&ip[0])), 233 | ) 234 | return err 235 | } 236 | -------------------------------------------------------------------------------- /adapter_test.go: -------------------------------------------------------------------------------- 1 | package wintun_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/netip" 9 | "os/exec" 10 | "strconv" 11 | "strings" 12 | "testing" 13 | "time" 14 | 15 | "github.com/lysShub/wintun-go" 16 | "github.com/stretchr/testify/require" 17 | "golang.org/x/sys/windows" 18 | "gvisor.dev/gvisor/pkg/tcpip/header" 19 | ) 20 | 21 | func Test_Invalid_Ring_Capacity(t *testing.T) { 22 | wintun.MustLoad(wintun.DLL) 23 | 24 | t.Run("lesser", func(t *testing.T) { 25 | ap, err := wintun.CreateAdapter("testinvalidringlesser") 26 | require.NoError(t, err) 27 | defer ap.Close() 28 | 29 | err = ap.Stop() 30 | require.NoError(t, err) 31 | 32 | err = ap.Start(wintun.MinRingCapacity - 1) 33 | require.Error(t, err) 34 | }) 35 | t.Run("greater", func(t *testing.T) { 36 | ap, err := wintun.CreateAdapter("testinvalidringgreater") 37 | require.NoError(t, err) 38 | defer ap.Close() 39 | 40 | err = ap.Stop() 41 | require.NoError(t, err) 42 | 43 | err = ap.Start(wintun.MaxRingCapacity + 1) 44 | require.Error(t, err) 45 | }) 46 | } 47 | 48 | func Test_Adapter_Create(t *testing.T) { 49 | wintun.MustLoad(wintun.DLL) 50 | 51 | t.Run("create/start", func(t *testing.T) { 52 | ap, err := wintun.CreateAdapter("createstart") 53 | require.NoError(t, err) 54 | defer ap.Close() 55 | 56 | err = ap.Start(wintun.MinRingCapacity) 57 | require.True(t, errors.Is(err, windows.ERROR_ALREADY_INITIALIZED)) 58 | }) 59 | t.Run("create/stop/stop", func(t *testing.T) { 60 | ap, err := wintun.CreateAdapter("createstopstop") 61 | require.NoError(t, err) 62 | defer ap.Close() 63 | 64 | err = ap.Stop() 65 | require.NoError(t, err) 66 | 67 | err = ap.Stop() 68 | require.NoError(t, err) 69 | }) 70 | t.Run("create/close/close", func(t *testing.T) { 71 | ap, err := wintun.CreateAdapter("createcloseclose") 72 | require.NoError(t, err) 73 | 74 | err = ap.Close() 75 | require.NoError(t, err) 76 | 77 | err = ap.Close() 78 | require.NoError(t, err) 79 | }) 80 | } 81 | 82 | func Test_Adapter_Stoped_Recv(t *testing.T) { 83 | wintun.MustLoad(wintun.DLL) 84 | 85 | ap, err := wintun.CreateAdapter("testadapterrwstoped") 86 | require.NoError(t, err) 87 | defer ap.Close() 88 | 89 | err = ap.Stop() 90 | require.NoError(t, err) 91 | 92 | _, err = ap.Recv(context.Background()) 93 | require.Error(t, err) 94 | } 95 | 96 | func Test_Adapter_Index(t *testing.T) { 97 | wintun.MustLoad(wintun.DLL) 98 | 99 | name := "testadapterindex" 100 | 101 | a, err := wintun.CreateAdapter(name) 102 | require.NoError(t, err) 103 | defer a.Close() 104 | 105 | ifIdx, err := a.Index() 106 | require.NoError(t, err) 107 | 108 | b, err := exec.Command("netsh", "int", "ipv4", "show", "interfaces").CombinedOutput() 109 | require.NoError(t, err) 110 | 111 | for _, line := range strings.Split(string(b), "\n") { 112 | if strings.Contains(line, name) { 113 | require.True(t, strings.Contains(line, strconv.Itoa(ifIdx))) 114 | return 115 | } 116 | } 117 | t.Errorf("can't found nic: \n %s", string(b)) 118 | } 119 | 120 | func Test_Recv(t *testing.T) { 121 | wintun.MustLoad(wintun.DLL) 122 | 123 | var ( 124 | ip = netip.AddrFrom4([4]byte{10, 1, 1, 11}) 125 | laddr = &net.UDPAddr{IP: ip.AsSlice(), Port: randPort()} 126 | raddr = &net.UDPAddr{IP: []byte{10, 1, 1, 13}, Port: randPort()} 127 | ) 128 | 129 | ap, err := wintun.CreateAdapter("recvoutboundudp") 130 | require.NoError(t, err) 131 | defer ap.Close() 132 | 133 | luid, err := ap.GetAdapterLuid() 134 | require.NoError(t, err) 135 | addr := netip.PrefixFrom(ip, 24) 136 | err = luid.AddIPAddress(addr) 137 | require.NoError(t, err) 138 | 139 | // send udp packet 140 | var ret = make(chan struct{}) 141 | msg := "fqwfnpina" 142 | go func() { 143 | conn, err := net.DialUDP("udp", laddr, raddr) 144 | require.NoError(t, err) 145 | for { 146 | select { 147 | case <-ret: 148 | return 149 | default: 150 | } 151 | 152 | n, err := conn.Write([]byte(msg)) 153 | require.NoError(t, err) 154 | require.Equal(t, len(msg), n) 155 | 156 | time.Sleep(time.Second) 157 | } 158 | }() 159 | 160 | for { 161 | p, err := ap.Recv(context.Background()) 162 | require.NoError(t, err) 163 | 164 | if header.IPVersion(p) == 4 { 165 | iphdr := header.IPv4(p) 166 | if iphdr.TransportProtocol() == header.UDPProtocolNumber { 167 | udphdr := header.UDP(iphdr.Payload()) 168 | 169 | ok := iphdr.SourceAddress().As4() == laddr.AddrPort().Addr().As4() && 170 | iphdr.DestinationAddress().As4() == raddr.AddrPort().Addr().As4() && 171 | udphdr.SourcePort() == laddr.AddrPort().Port() && 172 | udphdr.DestinationPort() == raddr.AddrPort().Port() 173 | 174 | if ok { 175 | require.Equal(t, string(udphdr.Payload()), msg) 176 | break 177 | } 178 | } 179 | } 180 | 181 | err = ap.Release(p) 182 | require.NoError(t, err) 183 | } 184 | ret <- struct{}{} 185 | } 186 | 187 | func Test_RecvCtx(t *testing.T) { 188 | wintun.MustLoad(wintun.DLL) 189 | 190 | ap, err := wintun.CreateAdapter("rcecvctx") 191 | require.NoError(t, err) 192 | defer ap.Close() 193 | 194 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 195 | defer cancel() 196 | 197 | for { 198 | p, err := ap.Recv(ctx) 199 | if err == nil { 200 | require.NoError(t, ap.Release(p)) 201 | } else { 202 | require.True(t, errors.Is(err, context.DeadlineExceeded)) 203 | return 204 | } 205 | } 206 | } 207 | 208 | func Test_Recving_Close(t *testing.T) { 209 | // if remove Close and Recv mutex, will fatal Exception 210 | wintun.MustLoad(wintun.DLL) 211 | 212 | for i := 0; i < 0xf; i++ { 213 | func() { 214 | ap, err := wintun.CreateAdapter("testrecvingclose") 215 | require.NoError(t, err) 216 | defer ap.Close() 217 | 218 | go func() { 219 | time.Sleep(time.Second) 220 | err := ap.Close() 221 | require.NoError(t, err) 222 | }() 223 | 224 | for { 225 | p, err := ap.Recv(context.Background()) 226 | if err == nil { 227 | ap.Release(p) 228 | } else { 229 | require.True(t, errors.Is(err, wintun.ErrAdapterClosed{})) 230 | break 231 | } 232 | } 233 | }() 234 | } 235 | } 236 | 237 | func Test_Echo_UDP_Adapter(t *testing.T) { 238 | wintun.MustLoad(wintun.DLL) 239 | 240 | var ( 241 | ip = netip.AddrFrom4([4]byte{10, 0, 1, 3}) 242 | laddr = &net.UDPAddr{IP: ip.AsSlice(), Port: randPort()} 243 | raddr = &net.UDPAddr{IP: []byte{10, 0, 1, 4}, Port: randPort()} 244 | ) 245 | 246 | ap, err := wintun.CreateAdapter("testechoudpadapter") 247 | require.NoError(t, err) 248 | defer ap.Close() 249 | 250 | luid, err := ap.GetAdapterLuid() 251 | require.NoError(t, err) 252 | addr := netip.PrefixFrom(ip, 24) 253 | err = luid.SetIPAddresses([]netip.Prefix{addr}) 254 | require.NoError(t, err) 255 | 256 | var ch = make(chan struct{}) 257 | defer func() { <-ch }() 258 | go func() { 259 | defer close(ch) 260 | 261 | for { 262 | rp, err := ap.Recv(context.Background()) 263 | if errors.Is(err, wintun.ErrAdapterClosed{}) { 264 | return 265 | } 266 | require.NoError(t, err) 267 | 268 | if header.IPVersion(rp) == 4 { 269 | iphdr := header.IPv4(rp) 270 | src := iphdr.SourceAddress() 271 | dst := iphdr.DestinationAddress() 272 | // not need update checksum 273 | iphdr.SetSourceAddress(dst) 274 | iphdr.SetDestinationAddress(src) 275 | 276 | if iphdr.TransportProtocol() == header.UDPProtocolNumber { 277 | udp := header.UDP(iphdr.Payload()) 278 | src, dst := udp.SourcePort(), udp.DestinationPort() 279 | udp.SetSourcePort(dst) 280 | udp.SetDestinationPort(src) 281 | 282 | sp, _ := ap.Alloc(len(rp)) 283 | copy(sp, rp) 284 | 285 | ap.Send(sp) 286 | } 287 | } 288 | ap.Release(rp) 289 | } 290 | }() 291 | 292 | conn, err := net.DialUDP("udp", laddr, raddr) 293 | require.NoError(t, err) 294 | defer conn.Close() 295 | 296 | msg := "fqwfnpina" 297 | n, err := conn.Write([]byte(msg)) 298 | require.NoError(t, err) 299 | require.Equal(t, len(msg), n) 300 | 301 | var b = make([]byte, 1536) 302 | n, err = conn.Read(b) 303 | require.NoError(t, err) 304 | require.Equal(t, msg, string(b[:n])) 305 | 306 | require.NoError(t, ap.Close()) 307 | } 308 | 309 | func Test_Packet_Sniffing(t *testing.T) { 310 | t.Skip("todo:maybe not route") 311 | // route add 0.0.0.0 mask 0.0.0.0 10.0.1.3 metric 5 if 116 312 | 313 | wintun.MustLoad(wintun.DLL) 314 | 315 | var ( 316 | ip = netip.AddrFrom4([4]byte{10, 0, 1, 3}) 317 | laddr = &net.UDPAddr{IP: ip.AsSlice(), Port: randPort()} 318 | raddr = &net.UDPAddr{IP: []byte{8, 8, 8, 8}, Port: randPort()} 319 | ) 320 | 321 | ap, err := wintun.CreateAdapter("testechoudpadapter") 322 | require.NoError(t, err) 323 | defer ap.Close() 324 | 325 | luid, err := ap.GetAdapterLuid() 326 | require.NoError(t, err) 327 | defer ap.Close() 328 | addr := netip.PrefixFrom(ip, 24) 329 | err = luid.AddIPAddress(addr) 330 | require.NoError(t, err) 331 | 332 | go func() { 333 | for { 334 | 335 | rp, err := ap.Recv(context.Background()) 336 | require.NoError(t, err) 337 | 338 | if header.IPVersion(rp) == 4 { 339 | iphdr := header.IPv4(rp) 340 | src := iphdr.SourceAddress() 341 | dst := iphdr.DestinationAddress() 342 | 343 | fmt.Println(iphdr.TransportProtocol(), src.String(), "-->", dst.String()) 344 | 345 | sp, err := ap.Alloc(len(rp)) 346 | require.NoError(t, err) 347 | copy(sp, rp) 348 | err = ap.Send(sp) 349 | require.NoError(t, err) 350 | } else { 351 | fmt.Println("ipv6") 352 | } 353 | 354 | err = ap.Release(rp) 355 | require.NoError(t, err) 356 | } 357 | }() 358 | 359 | conn, err := net.DialUDP("udp", laddr, raddr) 360 | require.NoError(t, err) 361 | defer conn.Close() 362 | 363 | msg := "fqwfnpina" 364 | n, err := conn.Write([]byte(msg)) 365 | require.NoError(t, err) 366 | require.Equal(t, len(msg), n) 367 | 368 | var b = make([]byte, 1536) 369 | n, err = conn.Read(b) 370 | require.NoError(t, err) 371 | require.Equal(t, msg, string(b[:n])) 372 | } 373 | 374 | func Test_Session_Restart(t *testing.T) { 375 | wintun.MustLoad(wintun.DLL) 376 | 377 | ap, err := wintun.CreateAdapter("testsessionrestart") 378 | require.NoError(t, err) 379 | defer ap.Close() 380 | 381 | err = ap.Stop() 382 | require.NoError(t, err) 383 | 384 | err = ap.Start(wintun.MinRingCapacity) 385 | require.NoError(t, err) 386 | } 387 | 388 | func Test_Auto_Handle_DF(t *testing.T) { 389 | t.Skip("todo") 390 | } 391 | -------------------------------------------------------------------------------- /consts.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package wintun 5 | 6 | const ( 7 | // minimum ring capacity 8 | MinRingCapacity = 0x20000 /* 128kiB */ 9 | 10 | // maximum ring capacity 11 | MaxRingCapacity = 0x4000000 /* 64MiB */ 12 | ) 13 | -------------------------------------------------------------------------------- /embed.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | type Mem []byte 4 | -------------------------------------------------------------------------------- /embed/wintun_amd64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lysShub/wintun-go/1acbbbe408f32daec073cc909ca86dceea6ac48c/embed/wintun_amd64.dll -------------------------------------------------------------------------------- /embed/wintun_arm.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lysShub/wintun-go/1acbbbe408f32daec073cc909ca86dceea6ac48c/embed/wintun_arm.dll -------------------------------------------------------------------------------- /embed/wintun_arm64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lysShub/wintun-go/1acbbbe408f32daec073cc909ca86dceea6ac48c/embed/wintun_arm64.dll -------------------------------------------------------------------------------- /embed/wintun_x86.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lysShub/wintun-go/1acbbbe408f32daec073cc909ca86dceea6ac48c/embed/wintun_x86.dll -------------------------------------------------------------------------------- /embed_windows_386.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | // from https://www.wintun.net/builds/wintun-0.14.1.zip 8 | // 9 | //go:embed embed/wintun_x86.dll 10 | var DLL Mem 11 | -------------------------------------------------------------------------------- /embed_windows_amd64.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | // from https://www.wintun.net/builds/wintun-0.14.1.zip 8 | // 9 | //go:embed embed/wintun_amd64.dll 10 | var DLL Mem 11 | -------------------------------------------------------------------------------- /embed_windows_arm.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | // from https://www.wintun.net/builds/wintun-0.14.1.zip 8 | // 9 | //go:embed embed/wintun_arm.dll 10 | var DLL Mem 11 | -------------------------------------------------------------------------------- /embed_windows_arm64.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | import ( 4 | _ "embed" 5 | ) 6 | 7 | // from https://www.wintun.net/builds/wintun-0.14.1.zip 8 | // 9 | //go:embed embed/wintun_arm64.dll 10 | var DLL Mem 11 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | type ErrLoaded struct{} 4 | 5 | func (ErrLoaded) Error() string { return "wintun loaded" } 6 | func (ErrLoaded) Temporary() bool { return true } 7 | 8 | type ErrNotLoad struct{} 9 | 10 | func (ErrNotLoad) Error() string { return "wintun not load" } 11 | 12 | type ErrAdapterClosed struct{} 13 | 14 | func (ErrAdapterClosed) Error() string { return "adapter closed" } 15 | 16 | type ErrAdapterStoped struct{} 17 | 18 | func (ErrAdapterStoped) Error() string { return "adapter stoped" } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lysShub/wintun-go 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 // indirect 7 | github.com/google/btree v1.1.2 // indirect 8 | github.com/pmezard/go-difflib v1.0.0 // indirect 9 | golang.org/x/time v0.3.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | 13 | require ( 14 | github.com/lysShub/divert-go v0.0.0-20240525230502-6f79596abd61 15 | github.com/pkg/errors v0.9.1 16 | github.com/stretchr/testify v1.8.4 17 | golang.org/x/sys v0.16.0 18 | golang.zx2c4.com/wireguard/windows v0.5.3 19 | gvisor.dev/gvisor v0.0.0-20230916030846-1d82564559db 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 4 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 5 | github.com/lysShub/divert-go v0.0.0-20240525230502-6f79596abd61 h1:qqarPA8zZe+LnIGHaleqDikaQ3QzvlAfDigXYRrboHU= 6 | github.com/lysShub/divert-go v0.0.0-20240525230502-6f79596abd61/go.mod h1:OXuD4Q/Y84FyNiYy/sf9RVshvAC5/rvcHA6J7JvvtFM= 7 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 8 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 12 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 13 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 14 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 15 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 16 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 17 | golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= 18 | golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 22 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 23 | gvisor.dev/gvisor v0.0.0-20230916030846-1d82564559db h1:CO57Wj9fblWZhyk6rViybNDtdHr9AgiuAzVzD4aFMjE= 24 | gvisor.dev/gvisor v0.0.0-20230916030846-1d82564559db/go.mod h1:lYEMhXbxgudVhALYsMQrBaUAjM3NMinh8mKL1CJv7rc= 25 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | import ( 4 | "context" 5 | "log/slog" 6 | "runtime" 7 | "syscall" 8 | 9 | "github.com/pkg/errors" 10 | "golang.org/x/sys/windows" 11 | ) 12 | 13 | type LoggerLevel int 14 | 15 | const ( 16 | Info LoggerLevel = iota 17 | Warn 18 | Error 19 | ) 20 | 21 | type LoggerCallback func(level LoggerLevel, timestamp uint64, msg *uint16) uintptr 22 | 23 | func DefaultCallback(log *slog.Logger) LoggerCallback { 24 | return func(level LoggerLevel, timestamp uint64, msg *uint16) uintptr { 25 | var sl slog.Level 26 | switch level { 27 | case Info: 28 | sl = slog.LevelInfo 29 | case Warn: 30 | sl = slog.LevelWarn 31 | case Error: 32 | sl = slog.LevelError 33 | default: 34 | sl = slog.LevelDebug 35 | } 36 | log.LogAttrs( 37 | context.Background(), sl, 38 | windows.UTF16PtrToString(msg), 39 | ) 40 | return 0 41 | } 42 | } 43 | 44 | func SetLogger(logger LoggerCallback) error { 45 | var callback uintptr 46 | if logger != nil { 47 | switch runtime.GOARCH { 48 | case "386": 49 | callback = windows.NewCallback(func(level LoggerLevel, timestampLow, timestampHigh uint32, msg *uint16) { 50 | logger(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg) 51 | }) 52 | case "arm": 53 | callback = windows.NewCallback(func(level LoggerLevel, _, timestampLow, timestampHigh uint32, msg *uint16) { 54 | logger(level, uint64(timestampHigh)<<32|uint64(timestampLow), msg) 55 | }) 56 | case "amd64", "arm64": 57 | callback = windows.NewCallback(logger) 58 | default: 59 | return errors.Errorf("not support windows arch %s", runtime.GOARCH) 60 | } 61 | } 62 | 63 | _, _, err := syscall.SyscallN(procSetLogger.Addr(), callback) 64 | if err != windows.ERROR_SUCCESS { 65 | return errors.WithStack(err) 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package wintun 2 | 3 | import "golang.org/x/sys/windows" 4 | 5 | type options struct { 6 | tunType string 7 | guid *windows.GUID 8 | ringBuff uint32 9 | } 10 | 11 | func defaultOptions() *options { 12 | return &options{ 13 | ringBuff: MinRingCapacity, 14 | } 15 | } 16 | 17 | type Option func(*options) 18 | 19 | func TunType(typ string) Option { 20 | return func(o *options) { 21 | o.tunType = typ 22 | } 23 | } 24 | 25 | func Guid(guid *windows.GUID) Option { 26 | return func(o *options) { 27 | o.guid = guid 28 | } 29 | } 30 | 31 | func RingBuff(size uint32) Option { 32 | return func(o *options) { 33 | o.ringBuff = size 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # wintun-go 2 | 3 | golang client for [wintun](https://git.zx2c4.com/wintun/about/) 4 | 5 | 6 | ##### Example: 7 | ```golang 8 | package main 9 | 10 | import ( 11 | "context" 12 | "log" 13 | "net" 14 | "net/netip" 15 | 16 | "github.com/lysShub/wintun-go" 17 | "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" 18 | "gvisor.dev/gvisor/pkg/tcpip/header" // go get gvisor.dev/gvisor@go 19 | ) 20 | 21 | // curl google.com 22 | func main() { 23 | wintun.MustLoad(wintun.DLL) 24 | 25 | 26 | ips, err := net.DefaultResolver.LookupIP(context.Background(), "ip4", "google.com") 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | ap, err := wintun.CreateAdapter("capture-google") 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | defer ap.Close() 36 | luid, err := ap.GetAdapterLuid() 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | var addr = netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 0, 7, 3}), 24) 42 | err = luid.SetIPAddresses([]netip.Prefix{addr}) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | var routs []*winipcfg.RouteData 48 | for _, e := range ips { 49 | ip := netip.AddrFrom4([4]byte(e)) 50 | dst := netip.PrefixFrom(ip, ip.BitLen()) 51 | routs = append(routs, &winipcfg.RouteData{ 52 | Destination: dst, 53 | NextHop: addr.Addr(), 54 | Metric: 5, 55 | }) 56 | } 57 | err = luid.AddRoutes(routs) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | for { 63 | ip, err := ap.Receive(context.Background()) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | if header.IPVersion(ip) == 4 { 69 | iphdr := header.IPv4(ip) 70 | if iphdr.TransportProtocol() == header.TCPProtocolNumber { 71 | tcphdr := header.TCP(iphdr.Payload()) 72 | 73 | log.Printf("%s:%d --> %s:%d %s\n", 74 | iphdr.SourceAddress().String(), tcphdr.SourcePort(), 75 | iphdr.DestinationAddress().String(), tcphdr.DestinationPort(), 76 | tcphdr.Flags(), 77 | ) 78 | } 79 | } 80 | 81 | err = ap.ReleasePacket(ip) 82 | if err != nil { 83 | log.Fatal(err) 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /wintun.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package wintun 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | 10 | "github.com/lysShub/divert-go/dll" 11 | "github.com/pkg/errors" 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | func MustLoad[T string | Mem](p T) struct{} { 16 | err := Load(p) 17 | if err != nil && !errors.Is(err, ErrLoaded{}) { 18 | panic(err) 19 | } 20 | return struct{}{} 21 | } 22 | 23 | func Load[T string | Mem](p T) error { 24 | if wintun.Loaded() { 25 | return ErrLoaded{} 26 | } 27 | 28 | switch p := any(p).(type) { 29 | case string: 30 | dll.ResetLazyDll(wintun, p) 31 | case Mem: 32 | dll.ResetLazyDll(wintun, []byte(p)) 33 | default: 34 | panic("") 35 | } 36 | return nil 37 | } 38 | 39 | var ( 40 | wintun = dll.NewLazyDLL("wintun.dll") 41 | 42 | procCreateAdapter = wintun.NewProc("WintunCreateAdapter") 43 | procOpenAdapter = wintun.NewProc("WintunOpenAdapter") 44 | procCloseAdapter = wintun.NewProc("WintunCloseAdapter") 45 | procDeleteDriver = wintun.NewProc("WintunDeleteDriver") 46 | procGetAdapterLUID = wintun.NewProc("WintunGetAdapterLUID") 47 | procGetRunningDriverVersion = wintun.NewProc("WintunGetRunningDriverVersion") 48 | procSetLogger = wintun.NewProc("WintunSetLogger") 49 | procStartSession = wintun.NewProc("WintunStartSession") 50 | procEndSession = wintun.NewProc("WintunEndSession") 51 | procGetReadWaitEvent = wintun.NewProc("WintunGetReadWaitEvent") 52 | procReceivePacket = wintun.NewProc("WintunReceivePacket") 53 | procReleaseReceivePacket = wintun.NewProc("WintunReleaseReceivePacket") 54 | procAllocateSendPacket = wintun.NewProc("WintunAllocateSendPacket") 55 | procSendPacket = wintun.NewProc("WintunSendPacket") 56 | ) 57 | 58 | func CreateAdapter(name string, opts ...Option) (*Adapter, error) { 59 | if len(name) == 0 { 60 | return nil, errors.New("require adapter name") 61 | } 62 | 63 | var o = defaultOptions() 64 | for _, fn := range opts { 65 | fn(o) 66 | } 67 | 68 | name16, err := windows.UTF16PtrFromString(name) 69 | if err != nil { 70 | return nil, errors.WithStack(err) 71 | } 72 | tunnelType16, err := windows.UTF16PtrFromString(o.tunType) 73 | if err != nil { 74 | return nil, errors.WithStack(err) 75 | } 76 | 77 | r1, _, err := syscall.SyscallN( 78 | procCreateAdapter.Addr(), 79 | uintptr(unsafe.Pointer(name16)), 80 | uintptr(unsafe.Pointer(tunnelType16)), 81 | uintptr(unsafe.Pointer(o.guid)), 82 | ) 83 | if err != windows.ERROR_SUCCESS { 84 | return nil, errors.WithStack(err) 85 | } 86 | ap := &Adapter{handle: r1} 87 | return ap, ap.Start(o.ringBuff) 88 | } 89 | 90 | func OpenAdapter(name string) (*Adapter, error) { 91 | if len(name) == 0 { 92 | return nil, errors.New("require adapter name") 93 | } 94 | 95 | var name16 *uint16 96 | name16, err := windows.UTF16PtrFromString(name) 97 | if err != nil { 98 | return nil, errors.WithStack(err) 99 | } 100 | 101 | r1, _, err := syscall.SyscallN(procOpenAdapter.Addr(), uintptr(unsafe.Pointer(name16))) 102 | if err != windows.ERROR_SUCCESS { 103 | return nil, errors.WithStack(err) 104 | } 105 | ap := &Adapter{handle: r1} 106 | return ap, ap.Start(MinRingCapacity) 107 | } 108 | 109 | // todo: https://git.zx2c4.com/wintun-go/tree/wintun.go 110 | func DriverVersion() (version uint32, err error) { 111 | r0, _, err := syscall.SyscallN(procGetRunningDriverVersion.Addr()) 112 | if err != windows.ERROR_SUCCESS { 113 | return 0, errors.WithStack(err) 114 | } 115 | return uint32(r0), nil 116 | } 117 | 118 | func DeleteDriver() error { 119 | _, _, err := syscall.SyscallN(procDeleteDriver.Addr()) 120 | if err != windows.ERROR_SUCCESS { 121 | return errors.WithStack(err) 122 | } 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /wintun_test.go: -------------------------------------------------------------------------------- 1 | package wintun_test 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "log/slog" 8 | "math/rand" 9 | "net/netip" 10 | "os" 11 | "os/exec" 12 | "runtime" 13 | "testing" 14 | "time" 15 | 16 | "github.com/lysShub/wintun-go" 17 | "github.com/stretchr/testify/require" 18 | "gvisor.dev/gvisor/pkg/tcpip" 19 | "gvisor.dev/gvisor/pkg/tcpip/checksum" 20 | "gvisor.dev/gvisor/pkg/tcpip/header" 21 | ) 22 | 23 | func Test_Gofmt(t *testing.T) { 24 | cmd := exec.Command("cmd", "/C", "gofmt", "-l", "-w", `.`) 25 | out, err := cmd.CombinedOutput() 26 | 27 | require.NoError(t, err) 28 | require.Empty(t, string(out)) 29 | } 30 | 31 | func Test_Load(t *testing.T) { 32 | // go test -list ".*" ./... 33 | // and go test with -run flag 34 | t.Skip("require independent test") 35 | 36 | t.Run("mem:load-fail", func(t *testing.T) { 37 | require.NoError(t, wintun.Load(make(wintun.Mem, 64))) 38 | 39 | ap, err := wintun.CreateAdapter("testload0") 40 | require.Error(t, err) 41 | require.Nil(t, ap) 42 | }) 43 | t.Run("file:load-fail", func(t *testing.T) { 44 | require.NoError(t, wintun.Load("./wintun.go")) 45 | 46 | ap, err := wintun.CreateAdapter("testload1") 47 | require.Error(t, err) 48 | require.Nil(t, ap) 49 | }) 50 | 51 | t.Run("load-fail/load", func(t *testing.T) { 52 | require.NoError(t, wintun.Load(make(wintun.Mem, 64))) 53 | 54 | ap, err := wintun.CreateAdapter("testload2") 55 | require.Error(t, err) 56 | require.Nil(t, ap) 57 | 58 | require.NoError(t, wintun.Load(dllPath)) 59 | }) 60 | 61 | t.Run("load/load", func(t *testing.T) { 62 | wintun.MustLoad(wintun.DLL) 63 | 64 | err := wintun.Load(wintun.DLL) 65 | require.True(t, errors.Is(err, wintun.ErrLoaded{})) 66 | require.True(t, 67 | err.(interface{ Temporary() bool }).Temporary(), 68 | ) 69 | }) 70 | 71 | } 72 | 73 | func Test_Example(t *testing.T) { 74 | // https://github.com/WireGuard/wintun/blob/master/example/example.c 75 | wintun.MustLoad(wintun.DLL) 76 | 77 | // 10.6.7.7/24 78 | var addr = netip.PrefixFrom( 79 | netip.MustParseAddr("10.6.7.7"), 80 | 24, 81 | ) 82 | 83 | ap, err := wintun.CreateAdapter("testexample") 84 | require.NoError(t, err) 85 | defer ap.Close() 86 | 87 | luid, err := ap.GetAdapterLuid() 88 | require.NoError(t, err) 89 | err = luid.AddIPAddress(addr) 90 | require.NoError(t, err) 91 | 92 | // Send: ping -S 10.6.7.8 10.6.7.7 93 | var ch = make(chan struct{}) 94 | defer func() { <-ch }() 95 | go func() { 96 | defer close(ch) 97 | 98 | pack := buildICMP(t, 99 | addr.Addr().Next().AsSlice(), 100 | addr.Addr().AsSlice(), 101 | header.ICMPv4Echo, []byte("1234"), 102 | ) 103 | for { 104 | p, err := ap.Alloc(len(pack)) 105 | if errors.Is(err, wintun.ErrAdapterClosed{}) { 106 | return 107 | } 108 | require.NoError(t, err) 109 | 110 | copy(p, pack) 111 | 112 | err = ap.Send(p) 113 | require.NoError(t, err) 114 | if errors.Is(err, os.ErrClosed) { 115 | return 116 | } 117 | time.Sleep(time.Second) 118 | } 119 | }() 120 | 121 | // Recv outgoing ICMP Echo-Reply packet 122 | for ok := false; !ok; { 123 | p, err := ap.Recv(context.Background()) 124 | require.NoError(t, err) 125 | 126 | switch header.IPVersion(p) { 127 | case 4: 128 | iphdr := header.IPv4(p) 129 | 130 | ok = iphdr.SourceAddress().String() == "10.6.7.7" && 131 | iphdr.DestinationAddress().String() == "10.6.7.8" 132 | if iphdr.TransportProtocol() == header.ICMPv4ProtocolNumber { 133 | icmp := header.ICMPv4(iphdr.Payload()) 134 | 135 | ok = ok && 136 | icmp.Type() == header.ICMPv4EchoReply && 137 | string(icmp.Payload()) == "1234" 138 | } else { 139 | ok = false 140 | } 141 | default: 142 | } 143 | err = ap.Release(p) 144 | require.NoError(t, err) 145 | } 146 | require.NoError(t, ap.Close()) 147 | } 148 | 149 | func Test_DriverVersion(t *testing.T) { 150 | t.Skip("can't get driver version") 151 | t.Run("mem", func(t *testing.T) { 152 | 153 | wintun.MustLoad(wintun.DLL) 154 | 155 | ver, err := wintun.DriverVersion() 156 | require.NoError(t, err) 157 | t.Log(ver) 158 | }) 159 | t.Run("file", func(t *testing.T) { 160 | require.NoError(t, wintun.Load(dllPath)) 161 | 162 | ver, err := wintun.DriverVersion() 163 | require.NoError(t, err) 164 | t.Log(ver) 165 | }) 166 | } 167 | 168 | func Test_Logger(t *testing.T) { 169 | t.Run("mem", func(t *testing.T) { 170 | wintun.MustLoad(wintun.DLL) 171 | 172 | buff := bytes.NewBuffer(nil) 173 | log := slog.New(slog.NewJSONHandler(buff, nil)) 174 | callback := wintun.DefaultCallback(log) 175 | 176 | err := wintun.SetLogger(callback) 177 | require.NoError(t, err) 178 | 179 | { 180 | w, err := wintun.CreateAdapter("testlogger") 181 | require.NoError(t, err) 182 | err = w.Close() 183 | require.NoError(t, err) 184 | } 185 | 186 | require.Contains(t, buff.String(), "Creating") 187 | }) 188 | t.Run("file", func(t *testing.T) { 189 | t.Skip("require independent test") 190 | require.NoError(t, wintun.Load(dllPath)) 191 | 192 | buff := bytes.NewBuffer(nil) 193 | log := slog.New(slog.NewJSONHandler(buff, nil)) 194 | callback := wintun.DefaultCallback(log) 195 | 196 | err := wintun.SetLogger(callback) 197 | require.NoError(t, err) 198 | 199 | { 200 | w, err := wintun.CreateAdapter("testlogger") 201 | require.NoError(t, err) 202 | err = w.Close() 203 | require.NoError(t, err) 204 | } 205 | 206 | require.Contains(t, buff.String(), "Creating") 207 | }) 208 | } 209 | 210 | func Test_Open(t *testing.T) { 211 | t.Run("notload/open", func(t *testing.T) { 212 | t.Skip("require independent test") 213 | ap, err := wintun.OpenAdapter("xxx") 214 | require.True(t, errors.Is(err, wintun.ErrNotLoad{})) 215 | require.Nil(t, ap) 216 | }) 217 | } 218 | 219 | func randPort() int { 220 | for { 221 | port := uint16(rand.Uint32()) 222 | if port > 2048 && port < 0xffff-0xff { 223 | return int(port) 224 | } 225 | } 226 | } 227 | 228 | var dllPath = `.\embed\wintun_amd64.dll` 229 | 230 | func init() { 231 | switch runtime.GOARCH { 232 | case "amd64": 233 | case "386": 234 | dllPath = `.\embed\wintun_386.dll` 235 | case "arm": 236 | dllPath = `.\embed\wintun_arm.dll` 237 | case "arm64": 238 | dllPath = `.\embed\wintun_arm64.dll` 239 | default: 240 | panic("") 241 | } 242 | } 243 | 244 | func buildICMP(t require.TestingT, src, dst []byte, typ header.ICMPv4Type, msg []byte) []byte { 245 | require.Zero(t, len(msg)%4) 246 | 247 | var p = make([]byte, 28+len(msg)) 248 | iphdr := header.IPv4(p) 249 | iphdr.Encode(&header.IPv4Fields{ 250 | TOS: 0, 251 | TotalLength: uint16(len(p)), 252 | ID: uint16(rand.Uint32()), 253 | Flags: 0, 254 | FragmentOffset: 0, 255 | TTL: 128, 256 | Protocol: uint8(header.ICMPv4ProtocolNumber), 257 | Checksum: 0, 258 | SrcAddr: tcpip.AddrFromSlice(src), 259 | DstAddr: tcpip.AddrFromSlice(dst), 260 | }) 261 | iphdr.SetChecksum(^checksum.Checksum(p[:iphdr.HeaderLength()], 0)) 262 | require.True(t, iphdr.IsChecksumValid()) 263 | 264 | icmphdr := header.ICMPv4(iphdr.Payload()) 265 | icmphdr.SetType(typ) 266 | icmphdr.SetIdent(0) 267 | icmphdr.SetSequence(0) 268 | icmphdr.SetChecksum(0) 269 | copy(icmphdr.Payload(), msg) 270 | icmphdr.SetChecksum(^checksum.Checksum(icmphdr, 0)) 271 | return p 272 | } 273 | --------------------------------------------------------------------------------