From ea22a08fd4f8ebb296baf4ff59861d8885ac3435 Mon Sep 17 00:00:00 2001 From: mattn Date: Mon, 19 Jan 2015 16:56:04 +0900 Subject: [PATCH] net: re-implement Interfaces and InterfaceAddrs for IPNet, IPv6 on Windows Fixes #5395 Change-Id: I4322bc8a974d04d9bae6b48c71c5d32d9252973c Reviewed-on: https://go-review.googlesource.com/3024 Reviewed-by: Alex Brainman --- src/go/build/deps_test.go | 2 +- .../syscall/windows/syscall_windows.go | 99 +++++++ .../syscall/windows/zsyscall_windows.go | 20 ++ src/net/interface_test.go | 11 +- src/net/interface_windows.go | 246 ++++++++++++------ 5 files changed, 281 insertions(+), 97 deletions(-) create mode 100644 src/internal/syscall/windows/syscall_windows.go create mode 100644 src/internal/syscall/windows/zsyscall_windows.go diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index b3c1105156..98201a5d96 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -240,7 +240,7 @@ var pkgDeps = map[string][]string{ // Basic networking. // Because net must be used by any package that wants to // do networking portably, it must have a small dependency set: just L1+basic os. - "net": {"L1", "CGO", "os", "syscall", "time"}, + "net": {"L1", "CGO", "os", "syscall", "time", "internal/syscall/windows"}, // NET enables use of basic network-related packages. "NET": { diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go new file mode 100644 index 0000000000..2541a83440 --- /dev/null +++ b/src/internal/syscall/windows/syscall_windows.go @@ -0,0 +1,99 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package windows + +import ( + "syscall" +) + +//go:generate go run ../../../syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go + +const GAA_FLAG_INCLUDE_PREFIX = 0x00000010 + +const IF_TYPE_SOFTWARE_LOOPBACK = 24 + +type SocketAddress struct { + Sockaddr *syscall.RawSockaddrAny + SockaddrLength int32 +} + +type IpAdapterUnicastAddress struct { + Length uint32 + Flags uint32 + Next *IpAdapterUnicastAddress + Address SocketAddress + PrefixOrigin int32 + SuffixOrigin int32 + DadState int32 + ValidLifetime uint32 + PreferredLifetime uint32 + LeaseLifetime uint32 + OnLinkPrefixLength uint8 +} + +type IpAdapterAnycastAddress struct { + Length uint32 + Flags uint32 + Next *IpAdapterAnycastAddress + Address SocketAddress +} + +type IpAdapterMulticastAddress struct { + Length uint32 + Flags uint32 + Next *IpAdapterMulticastAddress + Address SocketAddress +} + +type IpAdapterDnsServerAdapter struct { + Length uint32 + Reserved uint32 + Next *IpAdapterDnsServerAdapter + Address SocketAddress +} + +type IpAdapterPrefix struct { + Length uint32 + Flags uint32 + Next *IpAdapterPrefix + Address SocketAddress + PrefixLength uint32 +} + +type IpAdapterAddresses struct { + Length uint32 + IfIndex uint32 + Next *IpAdapterAddresses + AdapterName *byte + FirstUnicastAddress *IpAdapterUnicastAddress + FirstAnycastAddress *IpAdapterAnycastAddress + FirstMulticastAddress *IpAdapterMulticastAddress + FirstDnsServerAddress *IpAdapterDnsServerAdapter + DnsSuffix *uint16 + Description *uint16 + FriendlyName *uint16 + PhysicalAddress [syscall.MAX_ADAPTER_ADDRESS_LENGTH]byte + PhysicalAddressLength uint32 + Flags uint32 + Mtu uint32 + IfType uint32 + OperStatus uint32 + Ipv6IfIndex uint32 + ZoneIndices [16]uint32 + FirstPrefix *IpAdapterPrefix + /* more fields might be present here. */ +} + +const ( + IfOperStatusUp = 1 + IfOperStatusDown = 2 + IfOperStatusTesting = 3 + IfOperStatusUnknown = 4 + IfOperStatusDormant = 5 + IfOperStatusNotPresent = 6 + IfOperStatusLowerLayerDown = 7 +) + +//sys GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizeOfPointer *uint32) (errcode error) = iphlpapi.GetAdaptersAddresses diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go new file mode 100644 index 0000000000..90e2034641 --- /dev/null +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -0,0 +1,20 @@ +// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT + +package windows + +import "unsafe" +import "syscall" + +var ( + modiphlpapi = syscall.NewLazyDLL("iphlpapi.dll") + + procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") +) + +func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizeOfPointer *uint32) (errcode error) { + r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizeOfPointer)), 0) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} diff --git a/src/net/interface_test.go b/src/net/interface_test.go index fbf15de4d3..15c0cd7be4 100644 --- a/src/net/interface_test.go +++ b/src/net/interface_test.go @@ -38,8 +38,7 @@ func ipv6LinkLocalUnicastAddr(ifi *Interface) string { return "" } for _, ifa := range ifat { - switch ifa := ifa.(type) { - case *IPNet: + if ifa, ok := ifa.(*IPNet); ok { if ifa.IP.To4() == nil && ifa.IP.IsLinkLocalUnicast() { return ifa.IP.String() } @@ -49,10 +48,6 @@ func ipv6LinkLocalUnicastAddr(ifi *Interface) string { } func TestInterfaces(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("temporarily disabled until golang.org/issue/5395 is fixed") - } - ift, err := Interfaces() if err != nil { t.Fatal(err) @@ -110,10 +105,6 @@ func TestInterfaces(t *testing.T) { } func TestInterfaceAddrs(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("temporarily disabled until golang.org/issue/5395 is fixed") - } - ift, err := Interfaces() if err != nil { t.Fatal(err) diff --git a/src/net/interface_windows.go b/src/net/interface_windows.go index 0759dc255d..438dc874d6 100644 --- a/src/net/interface_windows.go +++ b/src/net/interface_windows.go @@ -5,123 +5,139 @@ package net import ( + "internal/syscall/windows" "os" "syscall" "unsafe" ) -func bytePtrToString(p *uint8) string { - a := (*[10000]uint8)(unsafe.Pointer(p)) - i := 0 - for a[i] != 0 { - i++ +func getAdapters() (*windows.IpAdapterAddresses, error) { + block := uint32(unsafe.Sizeof(windows.IpAdapterAddresses{})) + + // pre-allocate a 15KB working buffer pointed to by the AdapterAddresses + // parameter. + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx + size := uint32(15000) + + var addrs []windows.IpAdapterAddresses + for { + addrs = make([]windows.IpAdapterAddresses, size/block+1) + err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, &addrs[0], &size) + if err == nil { + break + } + if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW { + return nil, os.NewSyscallError("GetAdaptersAddresses", err) + } } - return string(a[:i]) + return &addrs[0], nil } -func getAdapterList() (*syscall.IpAdapterInfo, error) { - b := make([]byte, 1000) - l := uint32(len(b)) - a := (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) - // TODO(mikio): GetAdaptersInfo returns IP_ADAPTER_INFO that - // contains IPv4 address list only. We should use another API - // for fetching IPv6 stuff from the kernel. - err := syscall.GetAdaptersInfo(a, &l) - if err == syscall.ERROR_BUFFER_OVERFLOW { - b = make([]byte, l) - a = (*syscall.IpAdapterInfo)(unsafe.Pointer(&b[0])) - err = syscall.GetAdaptersInfo(a, &l) - } - if err != nil { - return nil, os.NewSyscallError("GetAdaptersInfo", err) - } - return a, nil -} - -func getInterfaceList() ([]syscall.InterfaceInfo, error) { +func getInterfaceInfos() ([]syscall.InterfaceInfo, error) { s, err := sysSocket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) if err != nil { return nil, os.NewSyscallError("Socket", err) } defer syscall.Closesocket(s) - ii := [20]syscall.InterfaceInfo{} + iia := [20]syscall.InterfaceInfo{} ret := uint32(0) - size := uint32(unsafe.Sizeof(ii)) - err = syscall.WSAIoctl(s, syscall.SIO_GET_INTERFACE_LIST, nil, 0, (*byte)(unsafe.Pointer(&ii[0])), size, &ret, nil, 0) + size := uint32(unsafe.Sizeof(iia)) + err = syscall.WSAIoctl(s, syscall.SIO_GET_INTERFACE_LIST, nil, 0, (*byte)(unsafe.Pointer(&iia[0])), size, &ret, nil, 0) if err != nil { return nil, os.NewSyscallError("WSAIoctl", err) } - c := ret / uint32(unsafe.Sizeof(ii[0])) - return ii[:c-1], nil + iilen := ret / uint32(unsafe.Sizeof(iia[0])) + return iia[:iilen-1], nil +} + +func bytesEqualIP(a []byte, b []int8) bool { + for i := 0; i < len(a); i++ { + if a[i] != byte(b[i]) { + return false + } + } + return true +} + +func findInterfaceInfo(iis []syscall.InterfaceInfo, paddr *windows.IpAdapterAddresses) *syscall.InterfaceInfo { + for _, ii := range iis { + iaddr := (*syscall.RawSockaddr)(unsafe.Pointer(&ii.Address)) + puni := paddr.FirstUnicastAddress + for ; puni != nil; puni = puni.Next { + if iaddr.Family == puni.Address.Sockaddr.Addr.Family { + switch iaddr.Family { + case syscall.AF_INET: + a := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&ii.Address)).Addr + if bytesEqualIP(a[:], puni.Address.Sockaddr.Addr.Data[2:]) { + return &ii + } + case syscall.AF_INET6: + a := (*syscall.RawSockaddrInet6)(unsafe.Pointer(&ii.Address)).Addr + if bytesEqualIP(a[:], puni.Address.Sockaddr.Addr.Data[2:]) { + return &ii + } + default: + continue + } + } + } + } + return nil } // If the ifindex is zero, interfaceTable returns mappings of all // network interfaces. Otherwise it returns a mapping of a specific // interface. func interfaceTable(ifindex int) ([]Interface, error) { - ai, err := getAdapterList() + paddr, err := getAdapters() if err != nil { return nil, err } - ii, err := getInterfaceList() + iis, err := getInterfaceInfos() if err != nil { return nil, err } var ift []Interface - for ; ai != nil; ai = ai.Next { - index := ai.Index + for ; paddr != nil; paddr = paddr.Next { + index := paddr.IfIndex + if paddr.Ipv6IfIndex != 0 { + index = paddr.Ipv6IfIndex + } if ifindex == 0 || ifindex == int(index) { + ii := findInterfaceInfo(iis, paddr) + if ii == nil { + continue + } var flags Flags - - row := syscall.MibIfRow{Index: index} - e := syscall.GetIfEntry(&row) - if e != nil { - return nil, os.NewSyscallError("GetIfEntry", e) + if paddr.Flags&windows.IfOperStatusUp != 0 { + flags |= FlagUp } - - for _, ii := range ii { - ip := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&ii.Address)).Addr - ipv4 := IPv4(ip[0], ip[1], ip[2], ip[3]) - ipl := &ai.IpAddressList - for ipl != nil { - ips := bytePtrToString(&ipl.IpAddress.String[0]) - if ipv4.Equal(parseIPv4(ips)) { - break - } - ipl = ipl.Next - } - if ipl == nil { - continue - } - if ii.Flags&syscall.IFF_UP != 0 { - flags |= FlagUp - } - if ii.Flags&syscall.IFF_LOOPBACK != 0 { - flags |= FlagLoopback - } - if ii.Flags&syscall.IFF_BROADCAST != 0 { - flags |= FlagBroadcast - } - if ii.Flags&syscall.IFF_POINTTOPOINT != 0 { - flags |= FlagPointToPoint - } - if ii.Flags&syscall.IFF_MULTICAST != 0 { - flags |= FlagMulticast - } + if paddr.IfType&windows.IF_TYPE_SOFTWARE_LOOPBACK != 0 { + flags |= FlagLoopback + } + if ii.Flags&syscall.IFF_BROADCAST != 0 { + flags |= FlagBroadcast + } + if ii.Flags&syscall.IFF_POINTTOPOINT != 0 { + flags |= FlagPointToPoint + } + if ii.Flags&syscall.IFF_MULTICAST != 0 { + flags |= FlagMulticast } - - name := bytePtrToString(&ai.AdapterName[0]) - ifi := Interface{ Index: int(index), - MTU: int(row.Mtu), - Name: name, - HardwareAddr: HardwareAddr(row.PhysAddr[:row.PhysAddrLen]), - Flags: flags} + MTU: int(paddr.Mtu), + Name: syscall.UTF16ToString((*(*[10000]uint16)(unsafe.Pointer(paddr.FriendlyName)))[:]), + HardwareAddr: HardwareAddr(paddr.PhysicalAddress[:]), + Flags: flags, + } ift = append(ift, ifi) + if ifindex == int(ifi.Index) { + break + } } } return ift, nil @@ -131,28 +147,86 @@ func interfaceTable(ifindex int) ([]Interface, error) { // network interfaces. Otherwise it returns addresses for a specific // interface. func interfaceAddrTable(ifi *Interface) ([]Addr, error) { - ai, err := getAdapterList() + paddr, err := getAdapters() if err != nil { return nil, err } var ifat []Addr - for ; ai != nil; ai = ai.Next { - index := ai.Index + for ; paddr != nil; paddr = paddr.Next { + index := paddr.IfIndex + if paddr.Ipv6IfIndex != 0 { + index = paddr.Ipv6IfIndex + } if ifi == nil || ifi.Index == int(index) { - ipl := &ai.IpAddressList - for ; ipl != nil; ipl = ipl.Next { - ifa := IPAddr{IP: parseIPv4(bytePtrToString(&ipl.IpAddress.String[0]))} - ifat = append(ifat, ifa.toAddr()) + puni := paddr.FirstUnicastAddress + for ; puni != nil; puni = puni.Next { + if sa, err := puni.Address.Sockaddr.Sockaddr(); err == nil { + switch sav := sa.(type) { + case *syscall.SockaddrInet4: + ifa := &IPNet{IP: make(IP, IPv4len), Mask: CIDRMask(int(puni.Address.SockaddrLength), 8*IPv4len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + case *syscall.SockaddrInet6: + ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(puni.Address.SockaddrLength), 8*IPv6len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + } + } + } + pany := paddr.FirstAnycastAddress + for ; pany != nil; pany = pany.Next { + if sa, err := pany.Address.Sockaddr.Sockaddr(); err == nil { + switch sav := sa.(type) { + case *syscall.SockaddrInet4: + ifa := &IPNet{IP: make(IP, IPv4len), Mask: CIDRMask(int(pany.Address.SockaddrLength), 8*IPv4len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + case *syscall.SockaddrInet6: + ifa := &IPNet{IP: make(IP, IPv6len), Mask: CIDRMask(int(pany.Address.SockaddrLength), 8*IPv6len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa) + } + } } } } + return ifat, nil } // interfaceMulticastAddrTable returns addresses for a specific // interface. func interfaceMulticastAddrTable(ifi *Interface) ([]Addr, error) { - // TODO(mikio): Implement this like other platforms. - return nil, nil + paddr, err := getAdapters() + if err != nil { + return nil, err + } + + var ifat []Addr + for ; paddr != nil; paddr = paddr.Next { + index := paddr.IfIndex + if paddr.Ipv6IfIndex != 0 { + index = paddr.Ipv6IfIndex + } + if ifi == nil || ifi.Index == int(index) { + pmul := paddr.FirstMulticastAddress + for ; pmul != nil; pmul = pmul.Next { + if sa, err := pmul.Address.Sockaddr.Sockaddr(); err == nil { + switch sav := sa.(type) { + case *syscall.SockaddrInet4: + ifa := &IPAddr{IP: make(IP, IPv4len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa.toAddr()) + case *syscall.SockaddrInet6: + ifa := &IPAddr{IP: make(IP, IPv6len)} + copy(ifa.IP, sav.Addr[:]) + ifat = append(ifat, ifa.toAddr()) + } + } + } + } + } + + return ifat, nil }