/* SPDX-License-Identifier: MIT * * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. */ package winipcfg import ( "fmt" "net" "golang.org/x/sys/windows" ) // LUID represents a network interface. type LUID uint64 // IPInterface method retrieves IP information for the specified interface on the local computer. func (luid LUID) IPInterface(family AddressFamily) (*MibIPInterfaceRow, error) { row := &MibIPInterfaceRow{} row.Init() row.InterfaceLUID = luid row.Family = family err := row.get() if err != nil { return nil, err } return row, nil } // Interface method retrieves information for the specified adapter on the local computer. // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getifentry2 func (luid LUID) Interface() (*MibIfRow2, error) { row := &MibIfRow2{} row.InterfaceLUID = luid err := row.get() if err != nil { return nil, err } return row, nil } // GUID method converts a locally unique identifier (LUID) for a network interface to a globally unique identifier (GUID) for the interface. // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceluidtoguid func (luid LUID) GUID() (*windows.GUID, error) { guid := &windows.GUID{} err := convertInterfaceLUIDToGUID(&luid, guid) if err != nil { return nil, err } return guid, nil } // LUIDFromGUID function converts a globally unique identifier (GUID) for a network interface to the locally unique identifier (LUID) for the interface. // https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-convertinterfaceguidtoluid func LUIDFromGUID(guid *windows.GUID) (LUID, error) { var luid LUID err := convertInterfaceGUIDToLUID(guid, &luid) if err != nil { return 0, err } return luid, nil } // IPAddress method returns MibUnicastIPAddressRow struct that matches to provided 'ip' argument. Corresponds to GetUnicastIpAddressEntry // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getunicastipaddressentry) func (luid LUID) IPAddress(ip net.IP) (*MibUnicastIPAddressRow, error) { row := &MibUnicastIPAddressRow{InterfaceLUID: luid} err := row.Address.SetIP(ip, 0) if err != nil { return nil, err } err = row.get() if err != nil { return nil, err } return row, nil } // AddIPAddress method adds new unicast IP address to the interface. Corresponds to CreateUnicastIpAddressEntry function // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). func (luid LUID) AddIPAddress(address net.IPNet) error { row := &MibUnicastIPAddressRow{} initializeUnicastIPAddressEntry(row) row.InterfaceLUID = luid err := row.Address.SetIP(address.IP, 0) if err != nil { return err } ones, _ := address.Mask.Size() row.OnLinkPrefixLength = uint8(ones) return row.Create() } // AddIPAddresses method adds multiple new unicast IP addresses to the interface. Corresponds to CreateUnicastIpAddressEntry function // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createunicastipaddressentry). func (luid LUID) AddIPAddresses(addresses []net.IPNet) error { for i := range addresses { err := luid.AddIPAddress(addresses[i]) if err != nil { return err } } return nil } // SetIPAddresses method sets new unicast IP addresses to the interface. func (luid LUID) SetIPAddresses(addresses []net.IPNet) error { err := luid.FlushIPAddresses(windows.AF_UNSPEC) if err != nil { return err } return luid.AddIPAddresses(addresses) } // DeleteIPAddress method deletes interface's unicast IP address. Corresponds to DeleteUnicastIpAddressEntry function // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteunicastipaddressentry). func (luid LUID) DeleteIPAddress(address net.IPNet) error { row := &MibUnicastIPAddressRow{} initializeUnicastIPAddressEntry(row) row.InterfaceLUID = luid err := row.Address.SetIP(address.IP, 0) if err != nil { return err } // Note: OnLinkPrefixLength member is ignored by DeleteUnicastIpAddressEntry(). ones, _ := address.Mask.Size() row.OnLinkPrefixLength = uint8(ones) return row.Delete() } // FlushIPAddresses method deletes all interface's unicast IP addresses. func (luid LUID) FlushIPAddresses(family AddressFamily) error { var tab *mibUnicastIPAddressTable err := getUnicastIPAddressTable(family, &tab) if err != nil { return err } t := tab.get() for i := range t { if t[i].InterfaceLUID == luid { t[i].Delete() } } tab.free() return nil } // Route method returns route determined with the input arguments. Corresponds to GetIpForwardEntry2 function // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-getipforwardentry2). // NOTE: If the corresponding route isn't found, the method will return error. func (luid LUID) Route(destination net.IPNet, nextHop net.IP) (*MibIPforwardRow2, error) { row := &MibIPforwardRow2{} row.Init() row.InterfaceLUID = luid err := row.DestinationPrefix.SetIPNet(destination) if err != nil { return nil, err } err = row.NextHop.SetIP(nextHop, 0) if err != nil { return nil, err } err = row.get() if err != nil { return nil, err } return row, nil } // AddRoute method adds a route to the interface. Corresponds to CreateIpForwardEntry2 function, with added splitDefault feature. // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-createipforwardentry2) func (luid LUID) AddRoute(destination net.IPNet, nextHop net.IP, metric uint32) error { row := &MibIPforwardRow2{} row.Init() row.InterfaceLUID = luid err := row.DestinationPrefix.SetIPNet(destination) if err != nil { return err } err = row.NextHop.SetIP(nextHop, 0) if err != nil { return err } row.Metric = metric return row.Create() } // AddRoutes method adds multiple routes to the interface. func (luid LUID) AddRoutes(routesData []*RouteData) error { for _, rd := range routesData { err := luid.AddRoute(rd.Destination, rd.NextHop, rd.Metric) if err != nil { return err } } return nil } // SetRoutes method sets (flush than add) multiple routes to the interface. func (luid LUID) SetRoutes(routesData []*RouteData) error { err := luid.FlushRoutes(windows.AF_UNSPEC) if err != nil { return err } return luid.AddRoutes(routesData) } // DeleteRoute method deletes a route that matches the criteria. Corresponds to DeleteIpForwardEntry2 function // (https://docs.microsoft.com/en-us/windows/desktop/api/netioapi/nf-netioapi-deleteipforwardentry2). func (luid LUID) DeleteRoute(destination net.IPNet, nextHop net.IP) error { row := &MibIPforwardRow2{} row.Init() row.InterfaceLUID = luid err := row.DestinationPrefix.SetIPNet(destination) if err != nil { return err } err = row.NextHop.SetIP(nextHop, 0) if err != nil { return err } err = row.get() if err != nil { return err } return row.Delete() } // FlushRoutes method deletes all interface's routes. // It continues on failures, and returns the last error afterwards. func (luid LUID) FlushRoutes(family AddressFamily) error { var tab *mibIPforwardTable2 err := getIPForwardTable2(family, &tab) if err != nil { return err } t := tab.get() for i := range t { if t[i].InterfaceLUID == luid { err2 := t[i].Delete() if err2 != nil { err = err2 } } } tab.free() return err } // DNS method returns all DNS server addresses associated with the adapter. func (luid LUID) DNS() ([]net.IP, error) { addresses, err := GetAdaptersAddresses(windows.AF_UNSPEC, GAAFlagDefault) if err != nil { return nil, err } r := make([]net.IP, 0, len(addresses)) for _, addr := range addresses { if addr.LUID == luid { for dns := addr.FirstDNSServerAddress; dns != nil; dns = dns.Next { if ip := dns.Address.IP(); ip != nil { r = append(r, ip) } else { return nil, windows.ERROR_INVALID_PARAMETER } } } } return r, nil } const ( netshCmdTemplateFlush4 = "interface ipv4 set dnsservers name=%d source=static address=none validate=no register=both" netshCmdTemplateFlush6 = "interface ipv6 set dnsservers name=%d source=static address=none validate=no register=both" netshCmdTemplateAdd4 = "interface ipv4 add dnsservers name=%d address=%s validate=no" netshCmdTemplateAdd6 = "interface ipv6 add dnsservers name=%d address=%s validate=no" ) // FlushDNS method clears all DNS servers associated with the adapter. func (luid LUID) FlushDNS() error { cmds := make([]string, 0, 2) ipif4, err := luid.IPInterface(windows.AF_INET) if err == nil { cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush4, ipif4.InterfaceIndex)) } ipif6, err := luid.IPInterface(windows.AF_INET6) if err == nil { cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush6, ipif6.InterfaceIndex)) } if len(cmds) == 0 { return nil } return runNetsh(cmds) } // AddDNS method associates additional DNS servers with the adapter. func (luid LUID) AddDNS(dnses []net.IP) error { var ipif4, ipif6 *MibIPInterfaceRow var err error cmds := make([]string, 0, len(dnses)) for i := 0; i < len(dnses); i++ { if v4 := dnses[i].To4(); v4 != nil { if ipif4 == nil { ipif4, err = luid.IPInterface(windows.AF_INET) if err != nil { return err } } cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif4.InterfaceIndex, v4.String())) } else if v6 := dnses[i].To16(); v6 != nil { if ipif6 == nil { ipif6, err = luid.IPInterface(windows.AF_INET6) if err != nil { return err } } cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif6.InterfaceIndex, v6.String())) } } if len(cmds) == 0 { return nil } return runNetsh(cmds) } // SetDNS method clears previous and associates new DNS servers with the adapter. func (luid LUID) SetDNS(dnses []net.IP) error { cmds := make([]string, 0, 2+len(dnses)) ipif4, err := luid.IPInterface(windows.AF_INET) if err == nil { cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush4, ipif4.InterfaceIndex)) } ipif6, err := luid.IPInterface(windows.AF_INET6) if err == nil { cmds = append(cmds, fmt.Sprintf(netshCmdTemplateFlush6, ipif6.InterfaceIndex)) } for i := 0; i < len(dnses); i++ { if v4 := dnses[i].To4(); v4 != nil { if ipif4 == nil { return windows.ERROR_NOT_SUPPORTED } cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd4, ipif4.InterfaceIndex, v4.String())) } else if v6 := dnses[i].To16(); v6 != nil { if ipif6 == nil { return windows.ERROR_NOT_SUPPORTED } cmds = append(cmds, fmt.Sprintf(netshCmdTemplateAdd6, ipif6.InterfaceIndex, v6.String())) } } if len(cmds) == 0 { return nil } return runNetsh(cmds) }