jackpal / gateway

A golang library for discovering the address of a LAN gateway.
BSD 3-Clause "New" or "Revised" License
227 stars 69 forks source link

Win32 API version #2

Open AudriusButkevicius opened 9 years ago

AudriusButkevicius commented 9 years ago

Just sharing my implementation incase you feel to incorporate it someday (IPv4 only though). Feel free to copy it and modify it in any shape or form, you can ignore the license completely.

https://github.com/calmh/syncthing/commit/6a6ec722cfcc01cd0ce44896aaa07bd77c7b4466

I have no clue how to check which one is the default one though :dancer: Stackoverflow suggests: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365924(v=vs.85).aspx

jackpal commented 9 years ago

Thanks! Very thoughtful of you to offer.

logrusorgru commented 5 years ago
package main

import (
    "fmt"
    "log"
    "net"
    "unsafe"

    "golang.org/x/sys/windows"
)

type (
    DWORD               uint32
    ULONG               uint32
    NET_IFINDEX         ULONG
    IF_INDEX            NET_IFINDEX
    NL_ROUTE_PROTOCOL   int32
    MIB_IPFORWARD_PROTO NL_ROUTE_PROTOCOL
    MIB_IPFORWARD_TYPE  int32
)

type MIB_IPFORWARDROW struct {
    DwForwardDest      DWORD
    DwForwardMask      DWORD
    DwForwardPolicy    DWORD
    DwForwardNextHop   DWORD
    DwForwardIfIndex   IF_INDEX
    ForwardType        MIB_IPFORWARD_TYPE
    ForwardProto       MIB_IPFORWARD_PROTO
    DwForwardAge       DWORD
    DwForwardNextHopAS DWORD
    DwForwardMetric1   DWORD
    DwForwardMetric2   DWORD
    DwForwardMetric3   DWORD
    DwForwardMetric4   DWORD
    DwForwardMetric5   DWORD
}

func ipDword(ip net.IP) (d DWORD) {
    ip = ip.To4()
    d |= DWORD(ip[0]) << 0
    d |= DWORD(ip[1]) << 8
    d |= DWORD(ip[2]) << 16
    d |= DWORD(ip[3]) << 24
    return
}

func dwordIP(d DWORD) (ip net.IP) {
    ip = make(net.IP, net.IPv4len)
    ip[0] = byte(d & 0xff)
    ip[1] = byte((d >> 8) & 0xff)
    ip[2] = byte((d >> 16) & 0xff)
    ip[3] = byte((d >> 24) & 0xff)
    return
}

func init() {
    log.SetFlags(log.Lshortfile)
}

func main() {

    dll, err := windows.LoadDLL("Iphlpapi.dll")
    if err != nil {
        log.Fatal(err)
    }
    defer dll.Release()

    getBestRoute, err := dll.FindProc("GetBestRoute")
    if err != nil {
        log.Fatal(err)
    }

    var row MIB_IPFORWARDROW

    // 8.8.8.8 is Google's public DNS, also there is 8.8.4.4

    _, _, err = getBestRoute.Call(
        uintptr(ipDword(net.ParseIP("8.8.8.8"))),
        uintptr(ipDword(net.ParseIP("0.0.0.0"))),
        uintptr(unsafe.Pointer(&row)),
    )
    if err != nil && err != windows.Errno(0) {
        log.Fatal(err)
    }

    for _, val := range []struct {
        Name  string
        Value DWORD
    }{
        {"DwForwardDest", row.DwForwardDest},
        {"DwForwardMask", row.DwForwardMask},
        {"DwForwardPolicy", row.DwForwardPolicy},
        {"DwForwardNextHop", row.DwForwardNextHop},
        {"DwForwardAge", row.DwForwardAge},
        {"DwForwardNextHopAS", row.DwForwardNextHopAS},
        {"DwForwardMetric1", row.DwForwardMetric1},
        {"DwForwardMetric2", row.DwForwardMetric2},
        {"DwForwardMetric3", row.DwForwardMetric3},
        {"DwForwardMetric4", row.DwForwardMetric4},
        {"DwForwardMetric5", row.DwForwardMetric5},
    } {
        fmt.Printf("%20s %s\n", val.Name+":", dwordIP(val.Value).String())
    }

}
    DwForwardDest: 0.0.0.0
          DwForwardMask: 0.0.0.0
        DwForwardPolicy: 0.0.0.0
       DwForwardNextHop: 192.168.1.1
           DwForwardAge: 79.42.1.0
     DwForwardNextHopAS: 0.0.0.0
       DwForwardMetric1: 50.0.0.0
       DwForwardMetric2: 255.255.255.255
       DwForwardMetric3: 255.255.255.255
       DwForwardMetric4: 255.255.255.255
       DwForwardMetric5: 255.255.255.255

The DwForwardNextHop is the gateway.

jackpal commented 5 years ago

I wonder if searching for a route to 8.8.8.8 will work for people running in networks that try to prevent connecting to public DNS. I tried finding the best route to 0.0.0.0 and it seemed to give the same result as 8.8.8.8

Another issue is that I don't think this handles IPv6

logrusorgru commented 5 years ago
IPv6

GetBestRoute2 handles IPv6, but it appeared in Vista.

XP + IPv6

Probably, GetBestInterfaceEx can be used with a workaround or as a combination with the

calmh/syncthing@6a6ec72

Currently I have no idea how to associate windows network interface with adapter. As I know interface is network level abstraction, and adapter is hardware level abstraction. The calmh/syncthing@6a6ec72 obtains information for adapters. The GetBestInterfaceEx obtains interface index. Hm.... May be it is the same. May be not.

AudriusButkevicius commented 5 years ago

Go is sort of not supported on XP anyway, so not sure it should matter.

logrusorgru commented 5 years ago

Аnyway, it turned out that the GetAdaptersInfo works only for IPv4.

jackpal commented 5 years ago

If the user has a complicated network they could have multiple gateways, each of which handles some subset of IP addresses. That's what makes it problematic to search for the best router for a given well-known IP address. On the other hand, I'm not sure we can do any better. (The current API for this code returns just one IP address after all.)