arblant
firefox-vpn-client / core / internal / egress / egress_darwin.go
egress_darwin.go220 lines · 6.0 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
package egress

import (
	"net"
	"net/netip"
	"os"
	"os/exec"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"golang.org/x/net/route"
	"golang.org/x/sys/unix"
)

// On macOS the TUN's routes are more specific than the default route,
// which stays: a socket bound to an interface (IP_BOUND_IF) uses that
// interface's routes only.

// defaultInterface is the interface of the primary default route (macOS
// scopes the others to their interfaces) when it is up and is not the TUN
// adapter.
func defaultInterface(v6 bool, st *tunState) (uint32, bool) {
	index, _, ok := defaultRoute(v6, st)
	return index, ok
}

func defaultRoute(v6 bool, st *tunState) (uint32, netip.Addr, bool) {
	family := unix.AF_INET
	if v6 {
		family = unix.AF_INET6
	}
	rib, err := route.FetchRIB(family, route.RIBTypeRoute, 0)
	if err != nil {
		return 0, netip.Addr{}, false
	}
	msgs, err := route.ParseRIB(route.RIBTypeRoute, rib)
	if err != nil {
		return 0, netip.Addr{}, false
	}
	for _, m := range msgs {
		rm, ok := m.(*route.RouteMessage)
		if !ok || rm.Flags&unix.RTF_UP == 0 || rm.Flags&(unix.RTF_IFSCOPE|unix.RTF_HOST|unix.RTF_REJECT|unix.RTF_BLACKHOLE) != 0 {
			continue
		}
		if !isDefault(rm.Addrs) || uint32(rm.Index) == st.index || !interfaceUp(rm.Index) {
			continue
		}
		return uint32(rm.Index), addrOf(rm.Addrs, unix.RTAX_GATEWAY), true
	}
	return 0, netip.Addr{}, false
}

// isDefault reports whether a route's destination is the whole family.
func isDefault(addrs []route.Addr) bool {
	dst := addrOf(addrs, unix.RTAX_DST)
	if !dst.IsValid() || !dst.IsUnspecified() {
		return false
	}
	mask := addrOf(addrs, unix.RTAX_NETMASK)
	return !mask.IsValid() || mask.IsUnspecified()
}

func addrOf(addrs []route.Addr, i int) netip.Addr {
	if i >= len(addrs) {
		return netip.Addr{}
	}
	switch a := addrs[i].(type) {
	case *route.Inet4Addr:
		return netip.AddrFrom4(a.IP)
	case *route.Inet6Addr:
		return netip.AddrFrom16(a.IP)
	}
	return netip.Addr{}
}

var routeSeq atomic.Int32

// bestInterface is the interface the system sends to dst through: its
// answer to a route lookup (RTM_GET).
func bestInterface(dst netip.Addr) (uint32, bool) {
	fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
	if err != nil {
		return 0, false
	}
	defer unix.Close(fd)
	_ = unix.SetsockoptTimeval(fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 1})
	var a route.Addr = &route.Inet4Addr{IP: dst.As4()}
	if dst.Is6() {
		a = &route.Inet6Addr{IP: dst.As16()}
	}
	seq := int(routeSeq.Add(1))
	pid := uintptr(os.Getpid())
	// An empty link address asks for the route's interface (RTA_IFP), as
	// route(8) does.
	req := route.RouteMessage{Version: unix.RTM_VERSION, Type: unix.RTM_GET, Flags: unix.RTF_UP | unix.RTF_HOST, ID: pid, Seq: seq,
		Addrs: []route.Addr{unix.RTAX_DST: a, unix.RTAX_IFP: &route.LinkAddr{}}}
	b, err := req.Marshal()
	if err != nil {
		return 0, false
	}
	if _, err := unix.Write(fd, b); err != nil {
		return 0, false
	}
	buf := make([]byte, 4096)
	// The socket also gets every other routing message; ours is the one
	// with our process and sequence number.
	for range 32 {
		n, err := unix.Read(fd, buf)
		if err != nil {
			return 0, false
		}
		msgs, err := route.ParseRIB(route.RIBTypeRoute, buf[:n])
		if err != nil {
			continue
		}
		for _, m := range msgs {
			if rm, ok := m.(*route.RouteMessage); ok && rm.ID == pid && rm.Seq == seq {
				if rm.Err != nil {
					return 0, false
				}
				index := rm.Index
				if len(rm.Addrs) > unix.RTAX_IFP {
					if l, ok := rm.Addrs[unix.RTAX_IFP].(*route.LinkAddr); ok && l.Index > 0 {
						index = l.Index
					}
				}
				return uint32(index), index > 0
			}
		}
	}
	return 0, false
}

// bind pins the socket to interface index (IP_BOUND_IF, IPV6_BOUND_IF).
func bind(c syscall.RawConn, v6 bool, index uint32) error {
	var opErr error
	err := c.Control(func(fd uintptr) {
		if v6 {
			opErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, int(index))
			return
		}
		opErr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, int(index))
	})
	if err != nil {
		return err
	}
	return opErr
}

// Supported: the core's sockets can always be bound outside a TUN here.
func Supported() error { return nil }

// dhcpCache keeps an interface's DHCP DNS servers a while: asking runs
// ipconfig.
var dhcpCache struct {
	mu      sync.Mutex
	iface   string
	at      time.Time
	servers []netip.Addr
}

const dhcpFresh = 30 * time.Second

// dnsServers are the DNS servers the network gave by DHCP on the IPv4
// default interface outside the TUN, else its gateway, port 53: while the
// TUN runs, the TUN service has made the TUN's the system's.
func dnsServers(st *tunState) []netip.AddrPort {
	index, gateway, ok := defaultRoute(false, st)
	if !ok {
		return nil
	}
	iface, err := net.InterfaceByIndex(int(index))
	if err != nil {
		return nil
	}
	if ports := dnsPorts(dhcpDNS(iface.Name), st); len(ports) > 0 {
		return ports
	}
	if gateway.IsValid() && !gateway.IsUnspecified() {
		return []netip.AddrPort{netip.AddrPortFrom(gateway, 53)}
	}
	return nil
}

func dhcpDNS(iface string) []netip.Addr {
	dhcpCache.mu.Lock()
	defer dhcpCache.mu.Unlock()
	if dhcpCache.iface == iface && time.Since(dhcpCache.at) < dhcpFresh {
		return dhcpCache.servers
	}
	out, err := exec.Command("/usr/sbin/ipconfig", "getpacket", iface).Output()
	var servers []netip.Addr
	if err == nil {
		servers = dhcpServers(string(out))
	}
	dhcpCache.iface, dhcpCache.at, dhcpCache.servers = iface, time.Now(), servers
	return servers
}

// dhcpServers reads the DNS servers from ipconfig's DHCP packet:
// "domain_name_server (ip_mult): {192.168.1.1, 192.168.1.2}".
func dhcpServers(packet string) []netip.Addr {
	var servers []netip.Addr
	for _, line := range strings.Split(packet, "\n") {
		name, value, ok := strings.Cut(line, ":")
		if !ok || !strings.HasPrefix(strings.TrimSpace(name), "domain_name_server") {
			continue
		}
		value = strings.Trim(strings.TrimSpace(value), "{}")
		for _, s := range strings.Split(value, ",") {
			if a, err := netip.ParseAddr(strings.TrimSpace(s)); err == nil {
				servers = append(servers, a)
			}
		}
	}
	return servers
}