status-go/vendor/github.com/libp2p/go-netroute/netroute_windows.go

238 lines
6.2 KiB
Go

//go:build windows
// +build windows
package netroute
// Implementation Warning: mapping of the correct interface ID and index is not
// hooked up.
// Reference:
// https://docs.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getbestroute2
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"unsafe"
"github.com/google/gopacket/routing"
"golang.org/x/sys/windows"
)
var (
modiphlpapi = windows.NewLazyDLL("iphlpapi.dll")
procGetBestRoute2 = modiphlpapi.NewProc("GetBestRoute2")
)
type NetLUID uint64
type AddressPrefix struct {
*windows.RawSockaddrAny
PrefixLength byte
}
type RouteProtocol uint32 // MIB_IPFORWARD_PROTO
// https://docs.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2
type mib_row2 struct {
luid NetLUID
index uint32
destinationPrefix *AddressPrefix
nextHop *windows.RawSockaddrAny
prefixLength byte
lifetime uint32
preferredLifetime uint32
metric uint32
protocol RouteProtocol
loopback byte
autoconfigured byte
publish byte
immortal byte
age uint32
origin byte
}
func callBestRoute(source, dest net.IP) (*mib_row2, net.IP, error) {
sourceAddr, _, _ := sockaddrToAny(ipAndZoneToSockaddr(source, ""))
destAddr, _, _ := sockaddrToAny(ipAndZoneToSockaddr(dest, ""))
bestRoute := make([]byte, 512)
bestSource := make([]byte, 116)
err := getBestRoute2(nil, 0, sourceAddr, destAddr, 0, bestRoute, bestSource)
if err != nil {
return nil, nil, err
}
// interpret best route and best source.
route, err := parseRoute(bestRoute)
if err != nil {
return nil, nil, err
}
// per https://docs.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2
// If the route is to a local loopback address or an IP address on the local link, the next hop is unspecified (all zeros)
if isZero(route.nextHop) {
route.nextHop = nil
}
var bestSourceRaw windows.RawSockaddrAny
bestSourceRaw.Addr.Family = binary.LittleEndian.Uint16(bestSource[0:2])
copyInto(bestSourceRaw.Addr.Data[:], bestSource[2:16])
copyInto(bestSourceRaw.Pad[:], bestSource[16:])
addr, _ := bestSourceRaw.Sockaddr()
bestSrc, _ := sockaddrToIPAndZone(addr)
return route, bestSrc, nil
}
func copyInto(dst []int8, src []byte) {
for i, b := range src {
dst[i] = int8(b)
}
}
func isZero(addr *windows.RawSockaddrAny) bool {
for _, b := range addr.Addr.Data {
if b != 0 {
return false
}
}
for _, b := range addr.Pad {
if b != 0 {
return false
}
}
return true
}
func parseRoute(mib []byte) (*mib_row2, error) {
var route mib_row2
var err error
route.luid = NetLUID(binary.LittleEndian.Uint64(mib[0:]))
route.index = binary.LittleEndian.Uint32(mib[8:])
idx := 0
route.destinationPrefix, idx, err = readDestPrefix(mib, 12)
if err != nil {
return nil, err
}
route.nextHop, idx, err = readSockAddr(mib, idx)
if err != nil {
return nil, err
}
route.prefixLength = mib[idx]
idx += 1
route.lifetime = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.preferredLifetime = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.metric = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.protocol = RouteProtocol(binary.LittleEndian.Uint32(mib[idx : idx+4]))
idx += 4
route.loopback = mib[idx]
idx += 1
route.autoconfigured = mib[idx]
idx += 1
route.publish = mib[idx]
idx += 1
route.immortal = mib[idx]
idx += 1
route.age = binary.LittleEndian.Uint32(mib[idx : idx+4])
idx += 4
route.origin = mib[idx]
return &route, err
}
func readDestPrefix(buffer []byte, idx int) (*AddressPrefix, int, error) {
sock, idx2, err := readSockAddr(buffer, idx)
if err != nil {
return nil, 0, err
}
pfixLen := buffer[idx2]
if idx2-idx > 32 {
return nil, idx, fmt.Errorf("Unexpectedly large internal sockaddr struct")
}
return &AddressPrefix{sock, pfixLen}, idx + 32, nil
}
func readSockAddr(buffer []byte, idx int) (*windows.RawSockaddrAny, int, error) {
var rsa windows.RawSockaddrAny
rsa.Addr.Family = binary.LittleEndian.Uint16(buffer[idx : idx+2])
if rsa.Addr.Family == windows.AF_INET || rsa.Addr.Family == windows.AF_UNSPEC {
copyInto(rsa.Addr.Data[:], buffer[idx+2:idx+16])
return &rsa, idx + 16, nil
} else if rsa.Addr.Family == windows.AF_INET6 {
copyInto(rsa.Addr.Data[:], buffer[idx+2:idx+16])
copyInto(rsa.Pad[:], buffer[idx+16:idx+28])
return &rsa, idx + 28, nil
} else {
return nil, 0, fmt.Errorf("Unknown windows addr family %d", rsa.Addr.Family)
}
}
func getBestRoute2(interfaceLuid *NetLUID, interfaceIndex uint32, sourceAddress, destinationAddress *windows.RawSockaddrAny, addressSortOptions uint32, bestRoute []byte, bestSourceAddress []byte) (errcode error) {
r0, _, _ := procGetBestRoute2.Call(
uintptr(unsafe.Pointer(interfaceLuid)),
uintptr(interfaceIndex),
uintptr(unsafe.Pointer(sourceAddress)),
uintptr(unsafe.Pointer(destinationAddress)),
uintptr(addressSortOptions),
uintptr(unsafe.Pointer(&bestRoute[0])),
uintptr(unsafe.Pointer(&bestSourceAddress[0])))
if r0 != 0 {
errcode = windows.Errno(r0)
}
return
}
func getIface(index uint32) *net.Interface {
var ifRow windows.MibIfRow
ifRow.Index = index
err := windows.GetIfEntry(&ifRow)
if err != nil {
return nil
}
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
for _, iface := range ifaces {
if bytes.Equal(iface.HardwareAddr, ifRow.PhysAddr[:]) {
return &iface
}
}
return nil
}
type winRouter struct{}
func (r *winRouter) Route(dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) {
return r.RouteWithSrc(nil, nil, dst)
}
func (r *winRouter) RouteWithSrc(input net.HardwareAddr, src, dst net.IP) (iface *net.Interface, gateway, preferredSrc net.IP, err error) {
route, pref, err := callBestRoute(src, dst)
if err != nil {
return nil, nil, nil, err
}
iface = getIface(route.index)
if route.nextHop == nil || route.nextHop.Addr.Family == 0 /* AF_UNDEF */ {
return iface, nil, pref, nil
}
addr, err := route.nextHop.Sockaddr()
if err != nil {
return nil, nil, nil, err
}
nextHop, _ := sockaddrToIPAndZone(addr)
return iface, nextHop, pref, nil
}
func New() (routing.Router, error) {
rtr := &winRouter{}
return rtr, nil
}