837 lines
22 KiB
Go
837 lines
22 KiB
Go
|
|
//go:build windows
|
||
|
|
|
||
|
|
package win32api
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net"
|
||
|
|
"os"
|
||
|
|
"runtime"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
"unsafe"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestGetHostName(t *testing.T) {
|
||
|
|
host, err := GetHostName()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetHostName failed: %v", err)
|
||
|
|
}
|
||
|
|
host = strings.TrimSpace(host)
|
||
|
|
if host == "" {
|
||
|
|
t.Fatal("GetHostName returned empty string")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInetPtonNtopIPv4(t *testing.T) {
|
||
|
|
raw, err := InetPton(AF_INET, "127.0.0.1")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("InetPton ipv4 failed: %v", err)
|
||
|
|
}
|
||
|
|
if len(raw) != 4 {
|
||
|
|
t.Fatalf("InetPton ipv4 len mismatch: got=%d", len(raw))
|
||
|
|
}
|
||
|
|
|
||
|
|
ip, err := InetNtop(AF_INET, raw)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("InetNtop ipv4 failed: %v", err)
|
||
|
|
}
|
||
|
|
if !net.ParseIP(ip).Equal(net.ParseIP("127.0.0.1")) {
|
||
|
|
t.Fatalf("InetNtop ipv4 mismatch: got=%q", ip)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInetPtonNtopIPv6(t *testing.T) {
|
||
|
|
raw, err := InetPton(AF_INET6, "::1")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("InetPton ipv6 failed: %v", err)
|
||
|
|
}
|
||
|
|
if len(raw) != 16 {
|
||
|
|
t.Fatalf("InetPton ipv6 len mismatch: got=%d", len(raw))
|
||
|
|
}
|
||
|
|
|
||
|
|
ip, err := InetNtop(AF_INET6, raw)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("InetNtop ipv6 failed: %v", err)
|
||
|
|
}
|
||
|
|
if !net.ParseIP(ip).Equal(net.ParseIP("::1")) {
|
||
|
|
t.Fatalf("InetNtop ipv6 mismatch: got=%q", ip)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInetPtonInvalidInput(t *testing.T) {
|
||
|
|
if _, err := InetPton(AF_INET, "999.999.999.999"); err == nil {
|
||
|
|
t.Fatal("InetPton should fail on invalid ipv4 address")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetAddrInfoLocalhost(t *testing.T) {
|
||
|
|
hints := &ADDRINFOW{
|
||
|
|
Family: AF_UNSPEC,
|
||
|
|
Socktype: SOCK_STREAM,
|
||
|
|
Protocol: IPPROTO_TCP,
|
||
|
|
}
|
||
|
|
result, err := GetAddrInfo("localhost", "80", hints)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetAddrInfo failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
if freeErr := FreeAddrInfo(result); freeErr != nil {
|
||
|
|
t.Fatalf("FreeAddrInfo failed: %v", freeErr)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
|
||
|
|
total := 0
|
||
|
|
validIP := 0
|
||
|
|
for p := result; p != nil; p = p.Next {
|
||
|
|
total++
|
||
|
|
if p.Addr == nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
switch p.Family {
|
||
|
|
case AF_INET, AF_INET6:
|
||
|
|
ip, ipErr := SockaddrIPString(p.Addr)
|
||
|
|
if ipErr != nil {
|
||
|
|
t.Fatalf("SockaddrIPString failed: %v", ipErr)
|
||
|
|
}
|
||
|
|
if net.ParseIP(ip) == nil {
|
||
|
|
t.Fatalf("invalid ip parsed from addrinfo: %q", ip)
|
||
|
|
}
|
||
|
|
validIP++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if total == 0 {
|
||
|
|
t.Fatal("GetAddrInfo returned empty result list")
|
||
|
|
}
|
||
|
|
if validIP == 0 {
|
||
|
|
t.Fatal("GetAddrInfo returned no AF_INET/AF_INET6 entries")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetNameInfoNumericIPv4(t *testing.T) {
|
||
|
|
sa := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: htons(80),
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
host, service, err := GetNameInfo((*SOCKADDR)(unsafe.Pointer(&sa)), int32(unsafe.Sizeof(sa)), NI_NUMERICHOST|NI_NUMERICSERV)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetNameInfo failed: %v", err)
|
||
|
|
}
|
||
|
|
if host != "127.0.0.1" {
|
||
|
|
t.Fatalf("GetNameInfo host mismatch: got=%q", host)
|
||
|
|
}
|
||
|
|
if service != "80" {
|
||
|
|
t.Fatalf("GetNameInfo service mismatch: got=%q", service)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestHostNetworkByteOrderHelpers(t *testing.T) {
|
||
|
|
if Htons(0x1234) != 0x3412 {
|
||
|
|
t.Fatalf("Htons mismatch: got=%#x", Htons(0x1234))
|
||
|
|
}
|
||
|
|
if Ntohs(0x3412) != 0x1234 {
|
||
|
|
t.Fatalf("Ntohs mismatch: got=%#x", Ntohs(0x3412))
|
||
|
|
}
|
||
|
|
|
||
|
|
tcp4 := MIB_TCPROW_OWNER_PID{LocalPort: uint32(Htons(8080)), RemotePort: uint32(Htons(443))}
|
||
|
|
if tcp4.LocalPortHost() != 8080 || tcp4.RemotePortHost() != 443 {
|
||
|
|
t.Fatalf("tcp4 port helper mismatch: local=%d remote=%d", tcp4.LocalPortHost(), tcp4.RemotePortHost())
|
||
|
|
}
|
||
|
|
tcp6 := MIB_TCP6ROW_OWNER_PID{LocalPort: uint32(Htons(8081)), RemotePort: uint32(Htons(8443))}
|
||
|
|
if tcp6.LocalPortHost() != 8081 || tcp6.RemotePortHost() != 8443 {
|
||
|
|
t.Fatalf("tcp6 port helper mismatch: local=%d remote=%d", tcp6.LocalPortHost(), tcp6.RemotePortHost())
|
||
|
|
}
|
||
|
|
udp4 := MIB_UDPROW_OWNER_PID{LocalPort: uint32(Htons(5353))}
|
||
|
|
if udp4.LocalPortHost() != 5353 {
|
||
|
|
t.Fatalf("udp4 port helper mismatch: local=%d", udp4.LocalPortHost())
|
||
|
|
}
|
||
|
|
udp6 := MIB_UDP6ROW_OWNER_PID{LocalPort: uint32(Htons(5354))}
|
||
|
|
if udp6.LocalPortHost() != 5354 {
|
||
|
|
t.Fatalf("udp6 port helper mismatch: local=%d", udp6.LocalPortHost())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestIphlpapiTableLayouts(t *testing.T) {
|
||
|
|
wantIfTableOffset := uintptr(8)
|
||
|
|
wantIfRowSize := uintptr(1352)
|
||
|
|
if runtime.GOARCH == "386" {
|
||
|
|
wantIfTableOffset = 4
|
||
|
|
wantIfRowSize = 1348
|
||
|
|
}
|
||
|
|
if got := unsafe.Offsetof(MIB_IF_TABLE2{}.Table); got != wantIfTableOffset {
|
||
|
|
t.Fatalf("MIB_IF_TABLE2.Table offset mismatch: got=%d want=%d", got, wantIfTableOffset)
|
||
|
|
}
|
||
|
|
if got := unsafe.Sizeof(MIB_IF_ROW2{}); got != wantIfRowSize {
|
||
|
|
t.Fatalf("MIB_IF_ROW2 size mismatch: got=%d want=%d", got, wantIfRowSize)
|
||
|
|
}
|
||
|
|
if got := unsafe.Offsetof(MIB_TCPTABLE_OWNER_PID{}.Table); got != 4 {
|
||
|
|
t.Fatalf("MIB_TCPTABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Sizeof(MIB_TCPROW_OWNER_PID{}); got != 24 {
|
||
|
|
t.Fatalf("MIB_TCPROW_OWNER_PID size mismatch: got=%d want=24", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Offsetof(MIB_TCP6TABLE_OWNER_PID{}.Table); got != 4 {
|
||
|
|
t.Fatalf("MIB_TCP6TABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Sizeof(MIB_TCP6ROW_OWNER_PID{}); got != 56 {
|
||
|
|
t.Fatalf("MIB_TCP6ROW_OWNER_PID size mismatch: got=%d want=56", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Offsetof(MIB_UDPTABLE_OWNER_PID{}.Table); got != 4 {
|
||
|
|
t.Fatalf("MIB_UDPTABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Sizeof(MIB_UDPROW_OWNER_PID{}); got != 12 {
|
||
|
|
t.Fatalf("MIB_UDPROW_OWNER_PID size mismatch: got=%d want=12", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Offsetof(MIB_UDP6TABLE_OWNER_PID{}.Table); got != 4 {
|
||
|
|
t.Fatalf("MIB_UDP6TABLE_OWNER_PID.Table offset mismatch: got=%d want=4", got)
|
||
|
|
}
|
||
|
|
if got := unsafe.Sizeof(MIB_UDP6ROW_OWNER_PID{}); got != 28 {
|
||
|
|
t.Fatalf("MIB_UDP6ROW_OWNER_PID size mismatch: got=%d want=28", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func requireWSA(t *testing.T) {
|
||
|
|
t.Helper()
|
||
|
|
var data WSADATA
|
||
|
|
if err := WSAStartup(makeWord(2, 2), &data); err != nil {
|
||
|
|
t.Fatalf("WSAStartup failed: %v", err)
|
||
|
|
}
|
||
|
|
t.Cleanup(func() {
|
||
|
|
_ = WSACleanup()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSocketConnectSendRecv(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
ln, err := net.Listen("tcp4", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("listen failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = ln.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
done := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
conn, acceptErr := ln.Accept()
|
||
|
|
if acceptErr != nil {
|
||
|
|
done <- acceptErr
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = conn.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
buf := make([]byte, 4)
|
||
|
|
if _, readErr := io.ReadFull(conn, buf); readErr != nil {
|
||
|
|
done <- readErr
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if string(buf) != "ping" {
|
||
|
|
done <- io.ErrUnexpectedEOF
|
||
|
|
return
|
||
|
|
}
|
||
|
|
_, writeErr := conn.Write([]byte("pong"))
|
||
|
|
done <- writeErr
|
||
|
|
}()
|
||
|
|
|
||
|
|
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(s)
|
||
|
|
}()
|
||
|
|
|
||
|
|
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("listen addr type mismatch: %T", ln.Addr())
|
||
|
|
}
|
||
|
|
sa := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: htons(uint16(tcpAddr.Port)),
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
if err := Connect(s, (*SOCKADDR)(unsafe.Pointer(&sa)), int32(unsafe.Sizeof(sa))); err != nil {
|
||
|
|
t.Fatalf("Connect failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
payload := []byte("ping")
|
||
|
|
written, err := Send(s, payload, 0)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Send failed: %v", err)
|
||
|
|
}
|
||
|
|
if written != len(payload) {
|
||
|
|
t.Fatalf("Send wrote mismatch: got=%d want=%d", written, len(payload))
|
||
|
|
}
|
||
|
|
|
||
|
|
reply := make([]byte, 4)
|
||
|
|
read, err := Recv(s, reply, 0)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Recv failed: %v", err)
|
||
|
|
}
|
||
|
|
if string(reply[:read]) != "pong" {
|
||
|
|
t.Fatalf("Recv data mismatch: got=%q", string(reply[:read]))
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := Shutdown(s, SD_BOTH); err != nil {
|
||
|
|
t.Fatalf("Shutdown failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
select {
|
||
|
|
case serverErr := <-done:
|
||
|
|
if serverErr != nil {
|
||
|
|
t.Fatalf("server goroutine failed: %v", serverErr)
|
||
|
|
}
|
||
|
|
case <-time.After(3 * time.Second):
|
||
|
|
t.Fatal("server goroutine timeout")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBindListenEphemeral(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(s)
|
||
|
|
}()
|
||
|
|
|
||
|
|
sa := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: 0,
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
if err := Bind(s, (*SOCKADDR)(unsafe.Pointer(&sa)), int32(unsafe.Sizeof(sa))); err != nil {
|
||
|
|
t.Fatalf("Bind failed: %v", err)
|
||
|
|
}
|
||
|
|
if err := Listen(s, 1); err != nil {
|
||
|
|
t.Fatalf("Listen failed: %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSockNameAndPeerName(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
ln, err := net.Listen("tcp4", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("listen failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = ln.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
connCh := make(chan net.Conn, 1)
|
||
|
|
errCh := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
conn, acceptErr := ln.Accept()
|
||
|
|
if acceptErr != nil {
|
||
|
|
errCh <- acceptErr
|
||
|
|
return
|
||
|
|
}
|
||
|
|
connCh <- conn
|
||
|
|
}()
|
||
|
|
|
||
|
|
client, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(client)
|
||
|
|
}()
|
||
|
|
|
||
|
|
tcpAddr := ln.Addr().(*net.TCPAddr)
|
||
|
|
peer := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: htons(uint16(tcpAddr.Port)),
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
if err := Connect(client, (*SOCKADDR)(unsafe.Pointer(&peer)), int32(unsafe.Sizeof(peer))); err != nil {
|
||
|
|
t.Fatalf("Connect failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var serverConn net.Conn
|
||
|
|
select {
|
||
|
|
case serverConn = <-connCh:
|
||
|
|
defer func() {
|
||
|
|
_ = serverConn.Close()
|
||
|
|
}()
|
||
|
|
case acceptErr := <-errCh:
|
||
|
|
t.Fatalf("accept failed: %v", acceptErr)
|
||
|
|
case <-time.After(3 * time.Second):
|
||
|
|
t.Fatal("accept timeout")
|
||
|
|
}
|
||
|
|
|
||
|
|
var local SOCKADDR_IN
|
||
|
|
localLen := int32(unsafe.Sizeof(local))
|
||
|
|
if err := GetSockName(client, (*SOCKADDR)(unsafe.Pointer(&local)), &localLen); err != nil {
|
||
|
|
t.Fatalf("GetSockName failed: %v", err)
|
||
|
|
}
|
||
|
|
if local.Family != ADDRESS_FAMILY(AF_INET) {
|
||
|
|
t.Fatalf("GetSockName family mismatch: got=%d", local.Family)
|
||
|
|
}
|
||
|
|
if htons(local.Port) == 0 {
|
||
|
|
t.Fatal("GetSockName returned zero local port")
|
||
|
|
}
|
||
|
|
|
||
|
|
var remote SOCKADDR_IN
|
||
|
|
remoteLen := int32(unsafe.Sizeof(remote))
|
||
|
|
if err := GetPeerName(client, (*SOCKADDR)(unsafe.Pointer(&remote)), &remoteLen); err != nil {
|
||
|
|
t.Fatalf("GetPeerName failed: %v", err)
|
||
|
|
}
|
||
|
|
if remote.Family != ADDRESS_FAMILY(AF_INET) {
|
||
|
|
t.Fatalf("GetPeerName family mismatch: got=%d", remote.Family)
|
||
|
|
}
|
||
|
|
if remote.Addr != [4]byte{127, 0, 0, 1} {
|
||
|
|
t.Fatalf("GetPeerName ip mismatch: got=%v", remote.Addr)
|
||
|
|
}
|
||
|
|
if int(htons(remote.Port)) != tcpAddr.Port {
|
||
|
|
t.Fatalf("GetPeerName port mismatch: got=%d want=%d", htons(remote.Port), tcpAddr.Port)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSetGetSockOptInt(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(s)
|
||
|
|
}()
|
||
|
|
|
||
|
|
if err := SetSockOptInt(s, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
|
||
|
|
t.Fatalf("SetSockOptInt failed: %v", err)
|
||
|
|
}
|
||
|
|
v, err := GetSockOptInt(s, SOL_SOCKET, SO_REUSEADDR)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetSockOptInt failed: %v", err)
|
||
|
|
}
|
||
|
|
if v == 0 {
|
||
|
|
t.Fatalf("GetSockOptInt(SO_REUSEADDR) expected non-zero, got %d", v)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSendToRecvFromUDP(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
receiver, err := Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("receiver Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(receiver)
|
||
|
|
}()
|
||
|
|
|
||
|
|
bindAddr := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: 0,
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
if err := Bind(receiver, (*SOCKADDR)(unsafe.Pointer(&bindAddr)), int32(unsafe.Sizeof(bindAddr))); err != nil {
|
||
|
|
t.Fatalf("receiver Bind failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var recvBound SOCKADDR_IN
|
||
|
|
recvBoundLen := int32(unsafe.Sizeof(recvBound))
|
||
|
|
if err := GetSockName(receiver, (*SOCKADDR)(unsafe.Pointer(&recvBound)), &recvBoundLen); err != nil {
|
||
|
|
t.Fatalf("receiver GetSockName failed: %v", err)
|
||
|
|
}
|
||
|
|
recvPort := htons(recvBound.Port)
|
||
|
|
if recvPort == 0 {
|
||
|
|
t.Fatal("receiver bound port is zero")
|
||
|
|
}
|
||
|
|
|
||
|
|
sender, err := Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("sender Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(sender)
|
||
|
|
}()
|
||
|
|
|
||
|
|
target := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: recvBound.Port,
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
payload := []byte("udp-ping")
|
||
|
|
sent, err := SendTo(sender, payload, 0, (*SOCKADDR)(unsafe.Pointer(&target)), int32(unsafe.Sizeof(target)))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("SendTo failed: %v", err)
|
||
|
|
}
|
||
|
|
if sent != len(payload) {
|
||
|
|
t.Fatalf("SendTo length mismatch: got=%d want=%d", sent, len(payload))
|
||
|
|
}
|
||
|
|
|
||
|
|
buf := make([]byte, 64)
|
||
|
|
var from SOCKADDR_IN
|
||
|
|
fromLen := int32(unsafe.Sizeof(from))
|
||
|
|
n, err := RecvFrom(receiver, buf, 0, (*SOCKADDR)(unsafe.Pointer(&from)), &fromLen)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("RecvFrom failed: %v", err)
|
||
|
|
}
|
||
|
|
if string(buf[:n]) != string(payload) {
|
||
|
|
t.Fatalf("RecvFrom payload mismatch: got=%q want=%q", string(buf[:n]), string(payload))
|
||
|
|
}
|
||
|
|
if from.Family != ADDRESS_FAMILY(AF_INET) {
|
||
|
|
t.Fatalf("RecvFrom family mismatch: got=%d", from.Family)
|
||
|
|
}
|
||
|
|
if from.Addr != [4]byte{127, 0, 0, 1} {
|
||
|
|
t.Fatalf("RecvFrom source ip mismatch: got=%v", from.Addr)
|
||
|
|
}
|
||
|
|
if htons(from.Port) == 0 {
|
||
|
|
t.Fatal("RecvFrom source port is zero")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAcceptSendRecvServerFlow(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
listener, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("listener Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(listener)
|
||
|
|
}()
|
||
|
|
if err := SetSockOptInt(listener, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
|
||
|
|
t.Fatalf("SetSockOptInt(SO_REUSEADDR) failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
bindAddr := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: 0,
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
if err := Bind(listener, (*SOCKADDR)(unsafe.Pointer(&bindAddr)), int32(unsafe.Sizeof(bindAddr))); err != nil {
|
||
|
|
t.Fatalf("Bind failed: %v", err)
|
||
|
|
}
|
||
|
|
if err := Listen(listener, 1); err != nil {
|
||
|
|
t.Fatalf("Listen failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var bound SOCKADDR_IN
|
||
|
|
boundLen := int32(unsafe.Sizeof(bound))
|
||
|
|
if err := GetSockName(listener, (*SOCKADDR)(unsafe.Pointer(&bound)), &boundLen); err != nil {
|
||
|
|
t.Fatalf("GetSockName(listener) failed: %v", err)
|
||
|
|
}
|
||
|
|
port := htons(bound.Port)
|
||
|
|
if port == 0 {
|
||
|
|
t.Fatal("listener port is zero")
|
||
|
|
}
|
||
|
|
|
||
|
|
serverDone := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
var peer SOCKADDR_IN
|
||
|
|
peerLen := int32(unsafe.Sizeof(peer))
|
||
|
|
clientSock, acceptErr := Accept(listener, (*SOCKADDR)(unsafe.Pointer(&peer)), &peerLen)
|
||
|
|
if acceptErr != nil {
|
||
|
|
serverDone <- acceptErr
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(clientSock)
|
||
|
|
}()
|
||
|
|
|
||
|
|
buf := make([]byte, 16)
|
||
|
|
n, recvErr := Recv(clientSock, buf, 0)
|
||
|
|
if recvErr != nil {
|
||
|
|
serverDone <- recvErr
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if string(buf[:n]) != "accept-ping" {
|
||
|
|
serverDone <- fmt.Errorf("server recv mismatch: %q", string(buf[:n]))
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if _, sendErr := Send(clientSock, []byte("accept-pong"), 0); sendErr != nil {
|
||
|
|
serverDone <- sendErr
|
||
|
|
return
|
||
|
|
}
|
||
|
|
serverDone <- nil
|
||
|
|
}()
|
||
|
|
|
||
|
|
conn, err := net.DialTimeout("tcp4", fmt.Sprintf("127.0.0.1:%d", port), 3*time.Second)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("DialTimeout failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = conn.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
if _, err := conn.Write([]byte("accept-ping")); err != nil {
|
||
|
|
t.Fatalf("client Write failed: %v", err)
|
||
|
|
}
|
||
|
|
reply := make([]byte, 16)
|
||
|
|
n, err := io.ReadFull(conn, reply[:11])
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("client ReadFull failed: %v", err)
|
||
|
|
}
|
||
|
|
if string(reply[:n]) != "accept-pong" {
|
||
|
|
t.Fatalf("client recv mismatch: %q", string(reply[:n]))
|
||
|
|
}
|
||
|
|
|
||
|
|
select {
|
||
|
|
case err := <-serverDone:
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("server flow failed: %v", err)
|
||
|
|
}
|
||
|
|
case <-time.After(3 * time.Second):
|
||
|
|
t.Fatal("server flow timeout")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSockOptSOErrorAfterFailedConnect(t *testing.T) {
|
||
|
|
requireWSA(t)
|
||
|
|
|
||
|
|
s, err := Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Socket failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = Closesocket(s)
|
||
|
|
}()
|
||
|
|
|
||
|
|
target := SOCKADDR_IN{
|
||
|
|
Family: ADDRESS_FAMILY(AF_INET),
|
||
|
|
Port: htons(1),
|
||
|
|
Addr: [4]byte{127, 0, 0, 1},
|
||
|
|
}
|
||
|
|
connectErr := Connect(s, (*SOCKADDR)(unsafe.Pointer(&target)), int32(unsafe.Sizeof(target)))
|
||
|
|
if connectErr == nil {
|
||
|
|
t.Skip("port 1 is open in current environment, skip SO_ERROR failure-path test")
|
||
|
|
}
|
||
|
|
|
||
|
|
soErr, err := GetSockOptInt(s, SOL_SOCKET, SO_ERROR)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetSockOptInt(SO_ERROR) failed: %v", err)
|
||
|
|
}
|
||
|
|
if soErr == 0 {
|
||
|
|
// On some stacks the synchronous connect error can be reported directly and
|
||
|
|
// SO_ERROR may already be cleared; keep this as diagnostic instead of flaky fail.
|
||
|
|
t.Logf("SO_ERROR=0 after connect err=%v", connectErr)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetAdaptersAddresses(t *testing.T) {
|
||
|
|
adapters, err := GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX|GAA_FLAG_INCLUDE_GATEWAYS)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetAdaptersAddresses failed: %v", err)
|
||
|
|
}
|
||
|
|
if len(adapters) == 0 {
|
||
|
|
t.Fatal("GetAdaptersAddresses returned empty list")
|
||
|
|
}
|
||
|
|
|
||
|
|
foundNamed := false
|
||
|
|
for _, a := range adapters {
|
||
|
|
if strings.TrimSpace(a.FriendlyName) != "" || strings.TrimSpace(a.AdapterName) != "" {
|
||
|
|
foundNamed = true
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !foundNamed {
|
||
|
|
t.Fatal("GetAdaptersAddresses returned adapters without names")
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, a := range adapters {
|
||
|
|
if a.PhysicalAddressLength > 0 {
|
||
|
|
if strings.TrimSpace(a.MACAddress) == "" {
|
||
|
|
t.Fatalf("adapter has physical address length %d but empty MACAddress", a.PhysicalAddressLength)
|
||
|
|
}
|
||
|
|
if _, err := net.ParseMAC(a.MACAddress); err != nil {
|
||
|
|
t.Fatalf("invalid MACAddress format %q: %v", a.MACAddress, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, ip := range a.UnicastIPs {
|
||
|
|
if net.ParseIP(ip) == nil {
|
||
|
|
t.Fatalf("invalid unicast ip in adapter info: %q", ip)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, ip := range a.DNSServers {
|
||
|
|
if net.ParseIP(ip) == nil {
|
||
|
|
t.Fatalf("invalid dns server ip in adapter info: %q", ip)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, ip := range a.Gateways {
|
||
|
|
if net.ParseIP(ip) == nil {
|
||
|
|
t.Fatalf("invalid gateway ip in adapter info: %q", ip)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetIfTable2AndEntry2(t *testing.T) {
|
||
|
|
rows, err := GetIfTable2()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetIfTable2 failed: %v", err)
|
||
|
|
}
|
||
|
|
if len(rows) == 0 {
|
||
|
|
t.Fatal("GetIfTable2 returned empty list")
|
||
|
|
}
|
||
|
|
|
||
|
|
row := rows[0]
|
||
|
|
if row.InterfaceIndex == 0 && row.InterfaceLuid == 0 {
|
||
|
|
t.Fatal("GetIfTable2 returned row without interface identity")
|
||
|
|
}
|
||
|
|
if err := GetIfEntry2(&row); err != nil {
|
||
|
|
t.Fatalf("GetIfEntry2 failed: %v", err)
|
||
|
|
}
|
||
|
|
if row.InterfaceIndex == 0 && row.InterfaceLuid == 0 {
|
||
|
|
t.Fatal("GetIfEntry2 cleared interface identity")
|
||
|
|
}
|
||
|
|
stats := []uint64{
|
||
|
|
row.InOctets,
|
||
|
|
row.OutOctets,
|
||
|
|
row.InUcastOctets,
|
||
|
|
row.OutUcastOctets,
|
||
|
|
row.InMulticastOctets,
|
||
|
|
row.OutMulticastOctets,
|
||
|
|
row.InBroadcastOctets,
|
||
|
|
row.OutBroadcastOctets,
|
||
|
|
}
|
||
|
|
if len(stats) != 8 {
|
||
|
|
t.Fatalf("unexpected stats field count: got=%d", len(stats))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetExtendedTcp4TableIncludesCurrentListener(t *testing.T) {
|
||
|
|
listener, err := net.Listen("tcp4", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("listen tcp4 failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = listener.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
port := listener.Addr().(*net.TCPAddr).Port
|
||
|
|
row := waitForTCP4OwnerPIDRow(t, uint32(os.Getpid()), port)
|
||
|
|
if row.State != MIB_TCP_STATE_LISTEN {
|
||
|
|
t.Fatalf("unexpected tcp state: got=%d want=%d", row.State, MIB_TCP_STATE_LISTEN)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetExtendedUdp4TableIncludesCurrentSocket(t *testing.T) {
|
||
|
|
packetConn, err := net.ListenPacket("udp4", "127.0.0.1:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("listen udp4 failed: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = packetConn.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
port := packetConn.LocalAddr().(*net.UDPAddr).Port
|
||
|
|
_ = waitForUDP4OwnerPIDRow(t, uint32(os.Getpid()), port)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetExtendedTcp6TableIncludesCurrentListener(t *testing.T) {
|
||
|
|
listener, err := net.Listen("tcp6", "[::1]:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Skipf("tcp6 unavailable: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = listener.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
port := listener.Addr().(*net.TCPAddr).Port
|
||
|
|
row := waitForTCP6OwnerPIDRow(t, uint32(os.Getpid()), port)
|
||
|
|
if row.State != MIB_TCP_STATE_LISTEN {
|
||
|
|
t.Fatalf("unexpected tcp6 state: got=%d want=%d", row.State, MIB_TCP_STATE_LISTEN)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetExtendedUdp6TableIncludesCurrentSocket(t *testing.T) {
|
||
|
|
packetConn, err := net.ListenPacket("udp6", "[::1]:0")
|
||
|
|
if err != nil {
|
||
|
|
t.Skipf("udp6 unavailable: %v", err)
|
||
|
|
}
|
||
|
|
defer func() {
|
||
|
|
_ = packetConn.Close()
|
||
|
|
}()
|
||
|
|
|
||
|
|
port := packetConn.LocalAddr().(*net.UDPAddr).Port
|
||
|
|
_ = waitForUDP6OwnerPIDRow(t, uint32(os.Getpid()), port)
|
||
|
|
}
|
||
|
|
|
||
|
|
func waitForTCP4OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_TCPROW_OWNER_PID {
|
||
|
|
t.Helper()
|
||
|
|
deadline := time.Now().Add(2 * time.Second)
|
||
|
|
for time.Now().Before(deadline) {
|
||
|
|
rows, err := GetExtendedTcp4Table(false, TCP_TABLE_OWNER_PID_ALL)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetExtendedTcp4Table failed: %v", err)
|
||
|
|
}
|
||
|
|
for _, row := range rows {
|
||
|
|
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
|
||
|
|
return row
|
||
|
|
}
|
||
|
|
}
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
}
|
||
|
|
t.Fatalf("did not find tcp4 row for pid=%d port=%d", pid, port)
|
||
|
|
return MIB_TCPROW_OWNER_PID{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func waitForTCP6OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_TCP6ROW_OWNER_PID {
|
||
|
|
t.Helper()
|
||
|
|
deadline := time.Now().Add(2 * time.Second)
|
||
|
|
for time.Now().Before(deadline) {
|
||
|
|
rows, err := GetExtendedTcp6Table(false, TCP_TABLE_OWNER_PID_ALL)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetExtendedTcp6Table failed: %v", err)
|
||
|
|
}
|
||
|
|
for _, row := range rows {
|
||
|
|
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
|
||
|
|
return row
|
||
|
|
}
|
||
|
|
}
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
}
|
||
|
|
t.Fatalf("did not find tcp6 row for pid=%d port=%d", pid, port)
|
||
|
|
return MIB_TCP6ROW_OWNER_PID{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func waitForUDP4OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_UDPROW_OWNER_PID {
|
||
|
|
t.Helper()
|
||
|
|
deadline := time.Now().Add(2 * time.Second)
|
||
|
|
for time.Now().Before(deadline) {
|
||
|
|
rows, err := GetExtendedUdp4Table(false, UDP_TABLE_OWNER_PID)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetExtendedUdp4Table failed: %v", err)
|
||
|
|
}
|
||
|
|
for _, row := range rows {
|
||
|
|
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
|
||
|
|
return row
|
||
|
|
}
|
||
|
|
}
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
}
|
||
|
|
t.Fatalf("did not find udp4 row for pid=%d port=%d", pid, port)
|
||
|
|
return MIB_UDPROW_OWNER_PID{}
|
||
|
|
}
|
||
|
|
|
||
|
|
func waitForUDP6OwnerPIDRow(t *testing.T, pid uint32, port int) MIB_UDP6ROW_OWNER_PID {
|
||
|
|
t.Helper()
|
||
|
|
deadline := time.Now().Add(2 * time.Second)
|
||
|
|
for time.Now().Before(deadline) {
|
||
|
|
rows, err := GetExtendedUdp6Table(false, UDP_TABLE_OWNER_PID)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("GetExtendedUdp6Table failed: %v", err)
|
||
|
|
}
|
||
|
|
for _, row := range rows {
|
||
|
|
if row.OwningPid == pid && int(row.LocalPortHost()) == port {
|
||
|
|
return row
|
||
|
|
}
|
||
|
|
}
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
}
|
||
|
|
t.Fatalf("did not find udp6 row for pid=%d port=%d", pid, port)
|
||
|
|
return MIB_UDP6ROW_OWNER_PID{}
|
||
|
|
}
|