Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
5.1 KB go
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019-2026 WireGuard LLC. All Rights Reserved.
 */

package tunnel

import (
	"fmt"
	"log"
	"net/netip"
	"time"

	"golang.org/x/sys/windows"
	"golang.zx2c4.com/wireguard/windows/conf"
	"golang.zx2c4.com/wireguard/windows/services"
	"golang.zx2c4.com/wireguard/windows/tunnel/firewall"
	"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)

func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []netip.Prefix) {
	if len(addresses) == 0 {
		return
	}
	addrHash := make(map[netip.Addr]bool, len(addresses))
	for i := range addresses {
		addrHash[addresses[i].Addr()] = true
	}
	interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault)
	if err != nil {
		return
	}
	for _, iface := range interfaces {
		if iface.OperStatus == winipcfg.IfOperStatusUp {
			continue
		}
		for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
			if ip, _ := netip.AddrFromSlice(address.Address.IP()); addrHash[ip] {
				prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
				log.Printf("Cleaning up stale address %s from interface ‘%s’", prefix.String(), iface.FriendlyName())
				iface.LUID.DeleteIPAddress(prefix)
			}
		}
	}
}

func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, luid winipcfg.LUID) error {
	retryOnFailure := services.StartedAtBoot()
	tryTimes := 0
	var err error
startOver:
	if tryTimes > 0 {
		log.Printf("Retrying interface configuration after failure because system just booted (T+%v): %v", windows.DurationSinceBoot(), err)
		time.Sleep(time.Second)
		retryOnFailure = retryOnFailure && tryTimes < 15
	}
	tryTimes++

	routedPrefixes := conf.RoutedPrefixes()
	routes := make(map[winipcfg.RouteData]bool, len(routedPrefixes))

	foundDefault4 := false
	foundDefault6 := false
	foundRoute4 := false
	foundRoute6 := false
	foundAddress4 := false
	foundAddress6 := false

	for _, allowedip := range routedPrefixes {
		route := winipcfg.RouteData{
			Destination: allowedip.Masked(),
			Metric:      0,
		}
		if allowedip.Addr().Is4() {
			foundRoute4 = true
			if allowedip.Bits() == 0 {
				foundDefault4 = true
			}
			route.NextHop = netip.IPv4Unspecified()
		} else if allowedip.Addr().Is6() {
			foundRoute6 = true
			if allowedip.Bits() == 0 {
				foundDefault6 = true
			}
			route.NextHop = netip.IPv6Unspecified()
		}
		routes[route] = true
	}

	deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes))
	for route := range routes {
		r := route
		deduplicatedRoutes = append(deduplicatedRoutes, &r)
	}

	if !conf.Interface.TableOff &&
		((foundRoute4 && family == windows.AF_INET) || (foundRoute6 && family == windows.AF_INET6)) {
		err = luid.SetRoutesForFamily(family, deduplicatedRoutes)
		if err == windows.ERROR_NOT_FOUND && retryOnFailure {
			goto startOver
		} else if err != nil {
			return fmt.Errorf("unable to set routes: %w", err)
		}
	}

	for _, addr := range conf.Interface.Addresses {
		if addr.Addr().Is4() {
			foundAddress4 = true
		} else if addr.Addr().Is6() {
			foundAddress6 = true
		}
	}
	if (foundAddress4 && family == windows.AF_INET) || (foundAddress6 && family == windows.AF_INET6) {
		err = luid.SetIPAddressesForFamily(family, conf.Interface.Addresses)
		if err == windows.ERROR_OBJECT_ALREADY_EXISTS {
			cleanupAddressesOnDisconnectedInterfaces(family, conf.Interface.Addresses)
			err = luid.SetIPAddressesForFamily(family, conf.Interface.Addresses)
		}
		if err == windows.ERROR_NOT_FOUND && retryOnFailure {
			goto startOver
		} else if err != nil {
			return fmt.Errorf("unable to set ips: %w", err)
		}
	}

	var ipif *winipcfg.MibIPInterfaceRow
	ipif, err = luid.IPInterface(family)
	if err != nil {
		return err
	}
	ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
	ipif.DadTransmits = 0
	ipif.ManagedAddressConfigurationSupported = false
	ipif.OtherStatefulConfigurationSupported = false
	if conf.Interface.MTU > 0 &&
		(((foundAddress4 || foundRoute4) && family == windows.AF_INET) ||
			((foundAddress6 || foundRoute6) && family == windows.AF_INET6)) {
		ipif.NLMTU = uint32(conf.Interface.MTU)
	}
	if (family == windows.AF_INET && foundDefault4) || (family == windows.AF_INET6 && foundDefault6) {
		ipif.UseAutomaticMetric = false
		ipif.Metric = 0
	}
	err = ipif.Set()
	if err == windows.ERROR_NOT_FOUND && retryOnFailure {
		goto startOver
	} else if err != nil {
		return fmt.Errorf("unable to set metric and MTU: %w", err)
	}

	err = luid.SetDNS(family, conf.Interface.DNS, conf.Interface.DNSSearch)
	if err == windows.ERROR_NOT_FOUND && retryOnFailure {
		goto startOver
	} else if err != nil {
		return fmt.Errorf("unable to set DNS: %w", err)
	}
	return nil
}

func enableFirewall(conf *conf.Config, luid winipcfg.LUID) error {
	doNotRestrict := true
	if conf.IsSocks5() {
		doNotRestrict = false
	} else if len(conf.Peers) == 1 && !conf.Interface.TableOff {
		for _, allowedip := range conf.Peers[0].AllowedIPs {
			if allowedip.Bits() == 0 && allowedip == allowedip.Masked() {
				doNotRestrict = false
				break
			}
		}
	}
	log.Println("Enabling firewall rules")
	return firewall.EnableFirewall(uint64(luid), doNotRestrict, conf.Interface.DNS)
}