1010 lines
30 KiB
Go
1010 lines
30 KiB
Go
package netconfig
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
|
|
"golang.org/x/sys/windows"
|
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
|
)
|
|
|
|
type windowsConfigurator struct {
|
|
ifaceBackends []namedIfaceBackend
|
|
panelBackends []namedPanelBackend
|
|
// The tunable settings resolved from the Option values. Its fields
|
|
// (testAddress, pingCount, skipPanels, ...) are promoted and read directly
|
|
// as c.testAddress etc. by the mutating methods.
|
|
*configOptions
|
|
}
|
|
|
|
// Returns the windows network configurator. Behaviour is tuned with Option
|
|
// values such as WithTestAddress and WithConnectivityCheck; use SetLogger to
|
|
// replace the package-wide logger.
|
|
func NewConfigurator(opts ...Option) (configurator Configurator, err error) {
|
|
options := newConfigOptions(opts...)
|
|
c := new(windowsConfigurator)
|
|
c.configOptions = options
|
|
configurator = c
|
|
|
|
var ci *cloudInit
|
|
ci, err = newCloudInit(options.backupRetention)
|
|
if err != nil {
|
|
logger.Println("error parsing cloud-init:", err)
|
|
}
|
|
if ci != nil {
|
|
c.ifaceBackends = append(c.ifaceBackends, namedIfaceBackend{"cloud-init", ci})
|
|
}
|
|
if _, serr := os.Stat(pleskBin); serr == nil {
|
|
c.panelBackends = append(c.panelBackends, namedPanelBackend{"Plesk", new(plesk)})
|
|
}
|
|
|
|
// Detection failures are logged above; they are not fatal to constructing
|
|
// the configurator, so do not propagate them to the caller.
|
|
err = nil
|
|
return
|
|
}
|
|
|
|
// Get IP addresses from ip adapter.
|
|
func ipAdapterAddresses(ipAdapter *winipcfg.IPAdapterAddresses) (addresses []*net.IPNet) {
|
|
// Parse IP addresses on the adapter. To do so,
|
|
// we need to get the unicast addresses and find
|
|
// a matching prefix to get the subnet length.
|
|
unicast := ipAdapter.FirstUnicastAddress
|
|
for unicast != nil {
|
|
addr := unicast.Address.IP()
|
|
|
|
// Check prefixes on the adapter to find the prefix for
|
|
// this address.
|
|
prefix := ipAdapter.FirstPrefix
|
|
for prefix != nil {
|
|
// Get the prefix size.
|
|
pAddr := prefix.Address.IP()
|
|
cidrSize := 32
|
|
if pAddr.To4() == nil {
|
|
cidrSize = 128
|
|
}
|
|
|
|
// If the length is the same as the size, skip.
|
|
if int(prefix.PrefixLength) == cidrSize {
|
|
prefix = prefix.Next
|
|
continue
|
|
}
|
|
|
|
// Convert prefix to ipnet.
|
|
nPrefix := &net.IPNet{
|
|
IP: pAddr,
|
|
Mask: net.CIDRMask(int(prefix.PrefixLength), cidrSize),
|
|
}
|
|
|
|
// If this is the prefix for the address, we found the
|
|
// subnet length.
|
|
if nPrefix.Contains(addr) {
|
|
// Encode address and add.
|
|
ipnet := &net.IPNet{
|
|
IP: addr,
|
|
Mask: net.CIDRMask(int(prefix.PrefixLength), cidrSize),
|
|
}
|
|
addresses = append(addresses, ipnet)
|
|
|
|
// We found the prefix, so stop searching prefixes.
|
|
prefix = nil
|
|
continue
|
|
}
|
|
|
|
// Check the next prefix.
|
|
prefix = prefix.Next
|
|
}
|
|
|
|
// Check the next IP addr.
|
|
unicast = unicast.Next
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Get list of interfaces and their configs.
|
|
func (c *windowsConfigurator) GetInterfaces(ctx context.Context) (interfaces []*Interface, err error) {
|
|
// Reference of zero IP addresses.
|
|
zeroIP4 := make(net.IP, 4)
|
|
zeroIP6 := make(net.IP, 16)
|
|
|
|
// Get routes from Windows.
|
|
routes, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get adaptures from Windows. GAAFlagSkipDNSServer is intentionally omitted
|
|
// so the per-adapter DNS server list is populated and can be reported back.
|
|
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Convert adapters to interfaces.
|
|
for _, ipAdapter := range ipAdaters {
|
|
// Skip loopback adapters using the interface type rather than a
|
|
// locale-dependent friendly name.
|
|
if ipAdapter.IfType == winipcfg.IfType(windows.IF_TYPE_SOFTWARE_LOOPBACK) {
|
|
continue
|
|
}
|
|
|
|
i := new(Interface)
|
|
i.Name = ipAdapter.FriendlyName()
|
|
i.MAC = ipAdapter.PhysicalAddress()
|
|
i.Link = ipAdapter.LUID
|
|
for _, addr := range ipAdapterAddresses(ipAdapter) {
|
|
if !addr.IP.IsLinkLocalUnicast() {
|
|
i.Addresses = append(i.Addresses, addr)
|
|
}
|
|
}
|
|
|
|
// Add DNS servers configured on this adapter. The live resolver list
|
|
// is the source of truth on Windows (analogous to addresses/routes).
|
|
for dns := ipAdapter.FirstDNSServerAddress; dns != nil; dns = dns.Next {
|
|
if ip := dns.Address.IP(); ip != nil {
|
|
i.DNS = append(i.DNS, ip)
|
|
}
|
|
}
|
|
|
|
// Add routes.
|
|
routeLoop:
|
|
for _, route := range routes {
|
|
// Only routes on this interface.
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
|
|
// Setup new route to translate.
|
|
r := new(Route)
|
|
|
|
// Get the prefix, destination IP and gateway.
|
|
prefix := route.DestinationPrefix.Prefix()
|
|
r.Destination = &net.IPNet{
|
|
IP: prefix.Addr().AsSlice(),
|
|
}
|
|
gateway := net.IP(route.NextHop.Addr().AsSlice())
|
|
r.Gateway = gateway
|
|
|
|
// If the gateway is the zero IP, ignore.
|
|
if gateway.Equal(zeroIP4) || gateway.Equal(zeroIP6) {
|
|
continue
|
|
}
|
|
|
|
// If this is the gatway route, assign gateway.
|
|
if prefix.Bits() == 0 {
|
|
if gateway.To4() != nil {
|
|
i.Gateway4 = gateway
|
|
} else {
|
|
i.Gateway6 = gateway
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Apply prefix to destination.
|
|
if r.Destination.IP.To4() != nil {
|
|
r.Destination.Mask = net.CIDRMask(prefix.Bits(), 32)
|
|
} else {
|
|
r.Destination.Mask = net.CIDRMask(prefix.Bits(), 128)
|
|
}
|
|
|
|
// Skip route to own networks.
|
|
for _, addr := range i.Addresses {
|
|
if r.Destination.Contains(addr.IP) && bytes.Equal(r.Destination.Mask, addr.Mask) {
|
|
continue routeLoop
|
|
}
|
|
}
|
|
|
|
// Add route to list.
|
|
r.Metric = int(route.Metric)
|
|
i.Routes = append(i.Routes, r)
|
|
}
|
|
|
|
// Add interface.
|
|
interfaces = append(interfaces, i)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Add an IP address.
|
|
func (c *windowsConfigurator) AddAddress(ctx context.Context, iface string, addr *net.IPNet, gateway net.IP) error {
|
|
// Get routes from Windows.
|
|
routes, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get adaptures from Windows.
|
|
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipDNSServer|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the ip adapter.
|
|
var ipAdapter *winipcfg.IPAdapterAddresses
|
|
for _, adap := range ipAdaters {
|
|
if adap.FriendlyName() == iface {
|
|
ipAdapter = adap
|
|
}
|
|
}
|
|
|
|
// If not found, return err.
|
|
if ipAdapter == nil {
|
|
return fmt.Errorf("no adapter found with name: %s", iface)
|
|
}
|
|
|
|
// Determine address family so we can confirm we're not removing
|
|
// the last address on the default gateway.
|
|
family := windows.AF_INET
|
|
zeroIP := make(net.IP, 4)
|
|
if addr.IP.To4() == nil {
|
|
family = windows.AF_INET6
|
|
zeroIP = make(net.IP, 16)
|
|
}
|
|
if gateway != nil && !gateway.Equal(zeroIP) {
|
|
if !addr.Contains(gateway) && !gateway.IsLinkLocalUnicast() {
|
|
return fmt.Errorf("provided gateway is not reachable from network")
|
|
}
|
|
}
|
|
|
|
// Get existing addresses.
|
|
exists := false
|
|
var origAddr *net.IPNet
|
|
addresses := ipAdapterAddresses(ipAdapter)
|
|
var prefixes []netip.Prefix
|
|
for a, address := range addresses {
|
|
// If the address already exists, update the netmask.
|
|
if address.IP.Equal(addr.IP) {
|
|
origAddr = address
|
|
exists = true
|
|
address = addr
|
|
addresses[a] = addr
|
|
}
|
|
|
|
// Add to prefix list.
|
|
ones, _ := address.Mask.Size()
|
|
var ip netip.Addr
|
|
if address.IP.To4() != nil {
|
|
ip = netip.AddrFrom4([4]byte(address.IP.To4()))
|
|
} else {
|
|
ip = netip.AddrFrom16([16]byte(address.IP.To16()))
|
|
}
|
|
prefixes = append(prefixes, netip.PrefixFrom(ip, ones))
|
|
}
|
|
|
|
// Add if not already existing.
|
|
if !exists {
|
|
if pingTest(ctx, addr.IP.String(), c.pingCount, c.pingTimeout) {
|
|
return fmt.Errorf("the ip we are adding is responding to ping")
|
|
}
|
|
addresses = append(addresses, addr)
|
|
|
|
// Add to prefix list.
|
|
ones, _ := addr.Mask.Size()
|
|
var ip netip.Addr
|
|
if addr.IP.To4() != nil {
|
|
ip = netip.AddrFrom4([4]byte(addr.IP.To4()))
|
|
} else {
|
|
ip = netip.AddrFrom16([16]byte(addr.IP.To16()))
|
|
}
|
|
prefixes = append(prefixes, netip.PrefixFrom(ip, ones))
|
|
}
|
|
|
|
// Set the IP addresses on the interface.
|
|
err = ipAdapter.LUID.SetIPAddresses(prefixes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the default gateway, and update or remove based on config.
|
|
var origGateway netip.Addr
|
|
var gateway4 net.IP
|
|
var gateway6 net.IP
|
|
for _, route := range routes {
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
prefix := route.DestinationPrefix.Prefix()
|
|
if prefix.Bits() == 0 {
|
|
if route.NextHop.Family == winipcfg.AddressFamily(family) {
|
|
origGateway = route.NextHop.Addr()
|
|
// If gateway is nil, we're keeping original gateway.
|
|
// If the gateway is the zero IP, we should remove the route.
|
|
// If the gateway is regular, try updating the gateway.
|
|
if gateway == nil {
|
|
gateway = net.IP(route.NextHop.Addr().AsSlice())
|
|
} else if gateway.Equal(zeroIP) {
|
|
err = route.Delete()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
} else {
|
|
var ip netip.Addr
|
|
if gateway.To4() != nil {
|
|
ip = netip.AddrFrom4([4]byte(gateway.To4()))
|
|
} else {
|
|
ip = netip.AddrFrom16([16]byte(gateway.To16()))
|
|
}
|
|
route.NextHop.SetAddr(ip)
|
|
err = route.Set()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if route.NextHop.Addr().Is4() {
|
|
gateway4 = net.IP(route.NextHop.Addr().AsSlice())
|
|
} else {
|
|
gateway6 = net.IP(route.NextHop.Addr().AsSlice())
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Set gateway as nil on zero IP.
|
|
if gateway.Equal(zeroIP) {
|
|
gateway = nil
|
|
}
|
|
|
|
// Test internet to confirm we did not break connection.
|
|
if gateway != nil {
|
|
// If the gateway doesn't exist, add it using the adapter's configured
|
|
// metric for the family instead of a hard-coded value.
|
|
addedGateway := false
|
|
if family == windows.AF_INET && gateway4 == nil {
|
|
ip := netip.AddrFrom4([4]byte(gateway.To4()))
|
|
defaultIP := netip.AddrFrom4([4]byte(zeroIP.To4()))
|
|
err = ipAdapter.LUID.AddRoute(netip.PrefixFrom(defaultIP, 0), ip, ipAdapter.Ipv4Metric)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gateway4 = gateway
|
|
addedGateway = true
|
|
} else if family == windows.AF_INET6 && gateway6 == nil {
|
|
ip := netip.AddrFrom16([16]byte(gateway.To16()))
|
|
defaultIP := netip.AddrFrom16([16]byte(zeroIP.To16()))
|
|
err = ipAdapter.LUID.AddRoute(netip.PrefixFrom(defaultIP, 0), ip, ipAdapter.Ipv6Metric)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gateway6 = gateway
|
|
addedGateway = true
|
|
}
|
|
|
|
// First ping gateway to ensure ARP works.
|
|
if !c.skipConnectivityCheck {
|
|
pingTest(ctx, gateway.String(), c.pingCount, c.pingTimeout)
|
|
}
|
|
|
|
// If the connection broke, then we should restore configurations.
|
|
// The check (and rollback) is skipped when connectivity verification
|
|
// is disabled.
|
|
if !c.skipConnectivityCheck && !testInternet(ctx, c.testAddress, c.connectivityTimeout) {
|
|
// Attempt to restore the original IP list. Rebuild the prefix
|
|
// list in place (rather than shadowing the outer prefixes) from
|
|
// the restored addresses, dropping the newly added address.
|
|
prefixes = nil
|
|
for a, address := range addresses {
|
|
if address.IP.Equal(addr.IP) {
|
|
// If the address did not exist before the change, drop it
|
|
// on rollback rather than restoring a stale value.
|
|
if !exists {
|
|
continue
|
|
}
|
|
address = origAddr
|
|
addresses[a] = origAddr
|
|
}
|
|
|
|
// Add to prefix list.
|
|
ones, _ := address.Mask.Size()
|
|
var ip netip.Addr
|
|
if address.IP.To4() != nil {
|
|
ip = netip.AddrFrom4([4]byte(address.IP.To4()))
|
|
} else {
|
|
ip = netip.AddrFrom16([16]byte(address.IP.To16()))
|
|
}
|
|
prefixes = append(prefixes, netip.PrefixFrom(ip, ones))
|
|
}
|
|
|
|
// Restore original addresses.
|
|
err = ipAdapter.LUID.SetIPAddresses(prefixes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Try and restore the original gateway.
|
|
if origGateway.IsValid() {
|
|
for _, route := range routes {
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
prefix := route.DestinationPrefix.Prefix()
|
|
if prefix.Bits() == 0 {
|
|
if route.NextHop.Family == winipcfg.AddressFamily(family) {
|
|
route.NextHop.SetAddr(origGateway)
|
|
err = route.Set()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if route.NextHop.Addr().Is4() {
|
|
gateway4 = net.IP(route.NextHop.Addr().AsSlice())
|
|
} else {
|
|
gateway6 = net.IP(route.NextHop.Addr().AsSlice())
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
} else if addedGateway {
|
|
if family == windows.AF_INET {
|
|
ip := netip.AddrFrom4([4]byte(gateway.To4()))
|
|
defaultIP := netip.AddrFrom4([4]byte(zeroIP.To4()))
|
|
err = ipAdapter.LUID.DeleteRoute(netip.PrefixFrom(defaultIP, 0), ip)
|
|
} else if family == windows.AF_INET6 {
|
|
ip := netip.AddrFrom16([16]byte(gateway.To16()))
|
|
defaultIP := netip.AddrFrom16([16]byte(zeroIP.To16()))
|
|
err = ipAdapter.LUID.DeleteRoute(netip.PrefixFrom(defaultIP, 0), ip)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("aborted operation due to loss of internet")
|
|
}
|
|
}
|
|
|
|
// Update configuration files. Skip control panels when skipPanels is set.
|
|
applyIfaceAddresses(ctx, c.ifaceBackends, iface, addresses, gateway4, gateway6)
|
|
if !c.skipPanels {
|
|
reloadPanels(ctx, c.panelBackends)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// prefixesFromAddresses converts a list of addresses to the netip.Prefix list
|
|
// expected by winipcfg's SetIPAddresses, preserving order.
|
|
func prefixesFromAddresses(addresses []*net.IPNet) []netip.Prefix {
|
|
var prefixes []netip.Prefix
|
|
for _, address := range addresses {
|
|
ones, _ := address.Mask.Size()
|
|
var ip netip.Addr
|
|
if address.IP.To4() != nil {
|
|
ip = netip.AddrFrom4([4]byte(address.IP.To4()))
|
|
} else {
|
|
ip = netip.AddrFrom16([16]byte(address.IP.To16()))
|
|
}
|
|
prefixes = append(prefixes, netip.PrefixFrom(ip, ones))
|
|
}
|
|
return prefixes
|
|
}
|
|
|
|
// Promote an existing address to be the primary address of its family. The
|
|
// address must already be present on the adapter. winipcfg's SetIPAddresses
|
|
// takes an ordered list, so the addresses are re-applied with addr first; the
|
|
// persisted backends likewise encode primary as list position. Connectivity is
|
|
// verified and rolled back on failure, mirroring AddAddress.
|
|
func (c *windowsConfigurator) SetPrimaryAddress(ctx context.Context, iface string, addr *net.IPNet) error {
|
|
// Get routes from Windows.
|
|
routes, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get adaptures from Windows.
|
|
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipDNSServer|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the ip adapter.
|
|
var ipAdapter *winipcfg.IPAdapterAddresses
|
|
for _, adap := range ipAdaters {
|
|
if adap.FriendlyName() == iface {
|
|
ipAdapter = adap
|
|
}
|
|
}
|
|
|
|
// If not found, return err.
|
|
if ipAdapter == nil {
|
|
return fmt.Errorf("no adapter found with name: %s", iface)
|
|
}
|
|
|
|
// Determine address family of the address being promoted.
|
|
family := windows.AF_INET
|
|
if addr.IP.To4() == nil {
|
|
family = windows.AF_INET6
|
|
}
|
|
|
|
// Get existing addresses and confirm the target is present.
|
|
original := ipAdapterAddresses(ipAdapter)
|
|
exists := false
|
|
for _, address := range original {
|
|
if address.IP.Equal(addr.IP) {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
if !exists {
|
|
return fmt.Errorf("address not found on adapter")
|
|
}
|
|
|
|
// Find the default gateway for each family so the config backends keep them.
|
|
var gateway4 net.IP
|
|
var gateway6 net.IP
|
|
for _, route := range routes {
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
if route.DestinationPrefix.Prefix().Bits() != 0 {
|
|
continue
|
|
}
|
|
if route.NextHop.Addr().Is4() {
|
|
gateway4 = net.IP(route.NextHop.Addr().AsSlice())
|
|
} else {
|
|
gateway6 = net.IP(route.NextHop.Addr().AsSlice())
|
|
}
|
|
}
|
|
|
|
// Reorder so the target address is first in its family.
|
|
reordered, _ := reorderPrimaryAddress(original, addr)
|
|
|
|
// If already primary within its family, just re-apply the configuration.
|
|
alreadyPrimary := false
|
|
for _, address := range original {
|
|
isV4 := address.IP.To4() != nil
|
|
if (family == windows.AF_INET) == isV4 {
|
|
alreadyPrimary = address.IP.Equal(addr.IP)
|
|
break
|
|
}
|
|
}
|
|
if !alreadyPrimary {
|
|
// Set the reordered IP addresses on the interface.
|
|
if err = ipAdapter.LUID.SetIPAddresses(prefixesFromAddresses(reordered)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Confirm we did not break connectivity, restoring the original order
|
|
// on failure.
|
|
var gw net.IP
|
|
if family == windows.AF_INET {
|
|
gw = gateway4
|
|
} else {
|
|
gw = gateway6
|
|
}
|
|
if !c.skipConnectivityCheck && gw != nil {
|
|
pingTest(ctx, gw.String(), c.pingCount, c.pingTimeout)
|
|
if !testInternet(ctx, c.testAddress, c.connectivityTimeout) {
|
|
if rerr := ipAdapter.LUID.SetIPAddresses(prefixesFromAddresses(original)); rerr != nil {
|
|
return rerr
|
|
}
|
|
return fmt.Errorf("aborted operation due to loss of internet")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update configuration files with the reordered address list. Skip control
|
|
// panels when skipPanels is set.
|
|
applyIfaceAddresses(ctx, c.ifaceBackends, iface, reordered, gateway4, gateway6)
|
|
if !c.skipPanels {
|
|
reloadPanels(ctx, c.panelBackends)
|
|
setMainIPOnPanels(ctx, c.panelBackends, addr.IP)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove an IP address.
|
|
func (c *windowsConfigurator) RemoveAddress(ctx context.Context, iface string, addr *net.IPNet) error {
|
|
// Get routes from Windows.
|
|
routes, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get adaptures from Windows.
|
|
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipDNSServer|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the ip adapter.
|
|
var ipAdapter *winipcfg.IPAdapterAddresses
|
|
for _, adap := range ipAdaters {
|
|
if adap.FriendlyName() == iface {
|
|
ipAdapter = adap
|
|
}
|
|
}
|
|
|
|
// If not found, return err.
|
|
if ipAdapter == nil {
|
|
return fmt.Errorf("no adapter found with name: %s", iface)
|
|
}
|
|
|
|
// Determine address family so we can confirm we're not removing
|
|
// the last address on the default gateway.
|
|
family := windows.AF_INET
|
|
if addr.IP.To4() == nil {
|
|
family = windows.AF_INET6
|
|
}
|
|
|
|
// Find the default gateway.
|
|
var gw net.IP
|
|
var gateway4 net.IP
|
|
var gateway6 net.IP
|
|
for _, route := range routes {
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
prefix := route.DestinationPrefix.Prefix()
|
|
if prefix.Bits() == 0 {
|
|
if route.NextHop.Family == winipcfg.AddressFamily(family) {
|
|
gw = net.IP(route.NextHop.Addr().AsSlice())
|
|
}
|
|
if route.NextHop.Addr().Is4() {
|
|
gateway4 = net.IP(route.NextHop.Addr().AsSlice())
|
|
} else {
|
|
gateway6 = net.IP(route.NextHop.Addr().AsSlice())
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Get existing addresses, building the list without the address being
|
|
// removed so both the interface and the config backends drop it.
|
|
// Determine the current primary of the same family (the first address of
|
|
// the family in adapter order); removing it is refused unless explicitly
|
|
// allowed, since it changes the system's source address and can leave a
|
|
// control panel without a main IP.
|
|
isV4 := family == windows.AF_INET
|
|
var primaryOfFamily net.IP
|
|
routeToGateway := false
|
|
var addresses []*net.IPNet
|
|
origAddresses := ipAdapterAddresses(ipAdapter)
|
|
for _, address := range origAddresses {
|
|
if primaryOfFamily == nil {
|
|
if (address.IP.To4() != nil) == isV4 {
|
|
primaryOfFamily = address.IP
|
|
}
|
|
}
|
|
// If the address is the one we're removing, skip it.
|
|
if address.IP.Equal(addr.IP) {
|
|
continue
|
|
}
|
|
|
|
// If the gateway can be reached via this IP, note that.
|
|
if gw != nil && (address.Contains(gw) || gw.IsLinkLocalUnicast()) {
|
|
routeToGateway = true
|
|
}
|
|
|
|
// Keep the address.
|
|
addresses = append(addresses, address)
|
|
}
|
|
|
|
// Refuse to remove the primary address unless explicitly allowed. The
|
|
// caller is expected to promote another address first; WithAllowPrimaryRemoval
|
|
// overrides this for deliberate teardowns.
|
|
if primaryOfFamily != nil && primaryOfFamily.Equal(addr.IP) && !c.allowPrimaryRemoval {
|
|
return fmt.Errorf("refusing to remove primary address %s; promote another address first or enable WithAllowPrimaryRemoval", addr.IP.String())
|
|
}
|
|
|
|
// If we cannot reach the gateway after removing the address,
|
|
// we should error out.
|
|
if gw != nil && !routeToGateway {
|
|
return fmt.Errorf("unable to reach gateway after removing address.")
|
|
}
|
|
|
|
// Set the IP addresses on the interface.
|
|
err = ipAdapter.LUID.SetIPAddresses(prefixesFromAddresses(addresses))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Confirm removing the address did not break connectivity, the same way
|
|
// AddAddress and SetPrimaryAddress do; if it did, restore the original
|
|
// address list and abort before persisting the change or notifying any
|
|
// panel. The check (and its rollback) is skipped when connectivity
|
|
// verification is disabled.
|
|
if !c.skipConnectivityCheck {
|
|
if gw != nil {
|
|
pingTest(ctx, gw.String(), c.pingCount, c.pingTimeout)
|
|
}
|
|
if !testInternet(ctx, c.testAddress, c.connectivityTimeout) {
|
|
if rerr := ipAdapter.LUID.SetIPAddresses(prefixesFromAddresses(origAddresses)); rerr != nil {
|
|
return rerr
|
|
}
|
|
return fmt.Errorf("removing address %s broke internet connectivity; address restored", addr.IP.String())
|
|
}
|
|
}
|
|
|
|
// Update configuration files. When skipPanels is set, control panels are
|
|
// left untouched and only the network-manager files are rewritten.
|
|
if !c.skipPanels {
|
|
removeIPFromPanels(ctx, c.panelBackends, addr.IP)
|
|
}
|
|
applyIfaceAddresses(ctx, c.ifaceBackends, iface, addresses, gateway4, gateway6)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Add a static route.
|
|
func (c *windowsConfigurator) AddRoute(ctx context.Context, iface string, dst *net.IPNet, gateway net.IP, metric int) error {
|
|
// Reference of zero IP addresses.
|
|
zeroIP4 := make(net.IP, 4)
|
|
zeroIP6 := make(net.IP, 16)
|
|
|
|
// Get adaptures from Windows.
|
|
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipDNSServer|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the ip adapter.
|
|
var ipAdapter *winipcfg.IPAdapterAddresses
|
|
for _, adap := range ipAdaters {
|
|
if adap.FriendlyName() == iface {
|
|
ipAdapter = adap
|
|
}
|
|
}
|
|
|
|
// If not found, return err.
|
|
if ipAdapter == nil {
|
|
return fmt.Errorf("no adapter found with name: %s", iface)
|
|
}
|
|
|
|
// Determine the family.
|
|
family := windows.AF_INET
|
|
if dst.IP.To4() == nil {
|
|
family = windows.AF_INET6
|
|
}
|
|
|
|
// Verify the addresses on the interface are not in the destination. This
|
|
// only guards against a redundant route to the interface's own connected
|
|
// subnet; a default route (0.0.0.0/0 or ::/0) always "contains" every
|
|
// address and is exempted, otherwise a default route could never be
|
|
// added through this API at all.
|
|
addresses := ipAdapterAddresses(ipAdapter)
|
|
prefix, _ := dst.Mask.Size()
|
|
if prefix != 0 {
|
|
for _, address := range addresses {
|
|
if dst.Contains(address.IP) {
|
|
return fmt.Errorf("cannot add routes that contains an address on the interface")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add static route. A nil gateway means an on-link route with no next
|
|
// hop; converting a nil net.IP via To4()/To16() yields a 0-length slice,
|
|
// which panics when forced into a [4]byte/[16]byte array, so it is
|
|
// represented as the unspecified address instead, matching how a
|
|
// zero-gateway route is already treated elsewhere in this file as "no
|
|
// gateway" when reading routes back.
|
|
var dstIP netip.Addr
|
|
var gatewayIP netip.Addr
|
|
if family == windows.AF_INET {
|
|
dstIP = netip.AddrFrom4([4]byte(dst.IP.To4()))
|
|
if gateway != nil {
|
|
gatewayIP = netip.AddrFrom4([4]byte(gateway.To4()))
|
|
} else {
|
|
gatewayIP = netip.IPv4Unspecified()
|
|
}
|
|
} else {
|
|
dstIP = netip.AddrFrom16([16]byte(dst.IP.To16()))
|
|
if gateway != nil {
|
|
gatewayIP = netip.AddrFrom16([16]byte(gateway.To16()))
|
|
} else {
|
|
gatewayIP = netip.IPv6Unspecified()
|
|
}
|
|
}
|
|
err = ipAdapter.LUID.AddRoute(netip.PrefixFrom(dstIP, prefix), gatewayIP, uint32(metric))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get routes from Windows.
|
|
var staticRoutes []*Route
|
|
routes, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
routeLoop:
|
|
for _, route := range routes {
|
|
// Only routes on this interface.
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
|
|
// Setup new route to translate.
|
|
r := new(Route)
|
|
|
|
// Get the prefix, destination IP and gateway.
|
|
prefix := route.DestinationPrefix.Prefix()
|
|
r.Destination = &net.IPNet{
|
|
IP: prefix.Addr().AsSlice(),
|
|
}
|
|
gateway := net.IP(route.NextHop.Addr().AsSlice())
|
|
r.Gateway = gateway
|
|
|
|
// If the gateway is the zero IP, ignore.
|
|
if gateway.Equal(zeroIP4) || gateway.Equal(zeroIP6) {
|
|
continue
|
|
}
|
|
|
|
// If this is the gatway route, assign gateway.
|
|
if prefix.Bits() == 0 {
|
|
continue
|
|
}
|
|
|
|
// Apply prefix to destination.
|
|
if r.Destination.IP.To4() != nil {
|
|
r.Destination.Mask = net.CIDRMask(prefix.Bits(), 32)
|
|
} else {
|
|
r.Destination.Mask = net.CIDRMask(prefix.Bits(), 128)
|
|
}
|
|
|
|
// Skip route to own networks.
|
|
for _, addr := range addresses {
|
|
if r.Destination.Contains(addr.IP) && bytes.Equal(r.Destination.Mask, addr.Mask) {
|
|
continue routeLoop
|
|
}
|
|
}
|
|
|
|
// Add route to list.
|
|
r.Metric = int(route.Metric)
|
|
staticRoutes = append(staticRoutes, r)
|
|
}
|
|
|
|
// Update configuration files.
|
|
applyIfaceRoutes(ctx, c.ifaceBackends, iface, staticRoutes)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Remove a static route.
|
|
func (c *windowsConfigurator) RemoveRoute(ctx context.Context, iface string, dst *net.IPNet, gateway net.IP) error {
|
|
// Reference of zero IP addresses.
|
|
zeroIP4 := make(net.IP, 4)
|
|
zeroIP6 := make(net.IP, 16)
|
|
|
|
// Get adaptures from Windows.
|
|
ipAdaters, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludePrefix|winipcfg.GAAFlagSkipAnycast|winipcfg.GAAFlagSkipMulticast|winipcfg.GAAFlagSkipDNSServer|winipcfg.GAAFlagSkipFriendlyName|winipcfg.GAAFlagSkipDNSInfo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Find the ip adapter.
|
|
var ipAdapter *winipcfg.IPAdapterAddresses
|
|
for _, adap := range ipAdaters {
|
|
if adap.FriendlyName() == iface {
|
|
ipAdapter = adap
|
|
}
|
|
}
|
|
|
|
// If not found, return err.
|
|
if ipAdapter == nil {
|
|
return fmt.Errorf("no adapter found with name: %s", iface)
|
|
}
|
|
|
|
// Determine the family.
|
|
family := windows.AF_INET
|
|
if dst.IP.To4() == nil {
|
|
family = windows.AF_INET6
|
|
}
|
|
|
|
// Verify the addresses on the interface are not in the destination. This
|
|
// only guards against a redundant route to the interface's own connected
|
|
// subnet; a default route (0.0.0.0/0 or ::/0) always "contains" every
|
|
// address and is exempted, otherwise a default route could never be
|
|
// removed through this API at all.
|
|
addresses := ipAdapterAddresses(ipAdapter)
|
|
prefix, _ := dst.Mask.Size()
|
|
if prefix != 0 {
|
|
for _, address := range addresses {
|
|
if dst.Contains(address.IP) {
|
|
return fmt.Errorf("cannot remove routes that contains an address on the interface")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove route. A nil gateway means an on-link route with no next hop;
|
|
// converting a nil net.IP via To4()/To16() yields a 0-length slice, which
|
|
// panics when forced into a [4]byte/[16]byte array, so it is represented
|
|
// as the unspecified address instead, matching how a zero-gateway route
|
|
// is already treated elsewhere in this file as "no gateway" when reading
|
|
// routes back.
|
|
var dstIP netip.Addr
|
|
var gatewayIP netip.Addr
|
|
if family == windows.AF_INET {
|
|
dstIP = netip.AddrFrom4([4]byte(dst.IP.To4()))
|
|
if gateway != nil {
|
|
gatewayIP = netip.AddrFrom4([4]byte(gateway.To4()))
|
|
} else {
|
|
gatewayIP = netip.IPv4Unspecified()
|
|
}
|
|
} else {
|
|
dstIP = netip.AddrFrom16([16]byte(dst.IP.To16()))
|
|
if gateway != nil {
|
|
gatewayIP = netip.AddrFrom16([16]byte(gateway.To16()))
|
|
} else {
|
|
gatewayIP = netip.IPv6Unspecified()
|
|
}
|
|
}
|
|
err = ipAdapter.LUID.DeleteRoute(netip.PrefixFrom(dstIP, prefix), gatewayIP)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get routes from Windows.
|
|
var staticRoutes []*Route
|
|
routes, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
routeLoop:
|
|
for _, route := range routes {
|
|
// Only routes on this interface.
|
|
if route.InterfaceLUID != ipAdapter.LUID {
|
|
continue
|
|
}
|
|
|
|
// Setup new route to translate.
|
|
r := new(Route)
|
|
|
|
// Get the prefix, destination IP and gateway.
|
|
prefix := route.DestinationPrefix.Prefix()
|
|
r.Destination = &net.IPNet{
|
|
IP: prefix.Addr().AsSlice(),
|
|
}
|
|
gateway := net.IP(route.NextHop.Addr().AsSlice())
|
|
r.Gateway = gateway
|
|
|
|
// If the gateway is the zero IP, ignore.
|
|
if gateway.Equal(zeroIP4) || gateway.Equal(zeroIP6) {
|
|
continue
|
|
}
|
|
|
|
// If this is the gatway route, assign gateway.
|
|
if prefix.Bits() == 0 {
|
|
continue
|
|
}
|
|
|
|
// Apply prefix to destination.
|
|
if r.Destination.IP.To4() != nil {
|
|
r.Destination.Mask = net.CIDRMask(prefix.Bits(), 32)
|
|
} else {
|
|
r.Destination.Mask = net.CIDRMask(prefix.Bits(), 128)
|
|
}
|
|
|
|
// Skip route to own networks.
|
|
for _, addr := range addresses {
|
|
if r.Destination.Contains(addr.IP) && bytes.Equal(r.Destination.Mask, addr.Mask) {
|
|
continue routeLoop
|
|
}
|
|
}
|
|
|
|
// Add route to list.
|
|
r.Metric = int(route.Metric)
|
|
staticRoutes = append(staticRoutes, r)
|
|
}
|
|
|
|
// Update configuration files.
|
|
applyIfaceRoutes(ctx, c.ifaceBackends, iface, staticRoutes)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Set the DNS servers and search domains for an interface. The change is
|
|
// written to the detected configuration backends (cloud-init) so it survives a
|
|
// reboot, and also applied to the live adapter via winipcfg so it takes effect
|
|
// immediately.
|
|
func (c *windowsConfigurator) SetDNS(ctx context.Context, iface string, servers []net.IP, searchDomains []string) error {
|
|
applyIfaceDNS(ctx, c.ifaceBackends, iface, servers, searchDomains)
|
|
applyLiveDNSWindows(ctx, iface, servers, searchDomains)
|
|
return nil
|
|
}
|