arblant
firefox-vpn-client / core / internal / egress / egress.go
egress.go266 lines · 7.9 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 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
// Package egress keeps the core's own connections out of the TUN adapter of
// the desktop TUN mode. While a TUN runs (SetTun), the adapter holds the
// default routes, so a connection the core opens itself (to Mozilla, the
// account and server-list APIs, DNS over HTTPS, a destination routed
// directly) would go into the TUN, to the TUN service, and back to the
// core's own proxy. Every outgoing socket the core opens is therefore bound
// to the interface that has the default route besides the TUN (Control),
// chosen again for each socket, so a change of network (Wi-Fi to Ethernet)
// is followed; and the names the system's resolver would answer (with the
// TUN's fake addresses) are asked of that network's own DNS servers
// instead. Without a TUN nothing changes.
package egress

import (
	"context"
	"errors"
	"net"
	"net/netip"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

// tunState is the running TUN adapter.
type tunState struct {
	index uint32
	luid  uint64
}

var current atomic.Pointer[tunState]

// SetTun binds the core's sockets outside the TUN adapter with this
// interface index and LUID, from now on.
func SetTun(index uint32, luid uint64) {
	current.Store(&tunState{index: index, luid: luid})
	forget()
}

// ClearTun ends the binding.
func ClearTun() {
	current.Store(nil)
	forget()
}

// Tun is the TUN adapter while one runs.
func Tun() (index uint32, luid uint64, ok bool) {
	st := current.Load()
	if st == nil {
		return 0, 0, false
	}
	return st.index, st.luid, true
}

// ErrNoNetwork means no interface but the TUN has a default route: a
// connection now would go into the TUN, so it is not made.
var ErrNoNetwork = errors.New("no network outside the TUN adapter")

// Control binds a socket being connected to address outside the TUN while
// one runs (net.Dialer.Control). A destination whose best route is another
// network than the TUN and the default one (a second LAN, WSL's or a
// virtual machine's) keeps that route (keepRoute).
func Control(network, address string, c syscall.RawConn) error {
	st := current.Load()
	if st == nil {
		return nil
	}
	var dst netip.Addr
	if ap, err := netip.ParseAddrPort(address); err == nil {
		dst = ap.Addr().Unmap()
	}
	if dst.IsLoopback() || local(dst) {
		return nil
	}
	v6 := dst.Is6() || (!dst.IsValid() && network[len(network)-1] == '6')
	index, ok := physical(v6, st)
	if !ok {
		return ErrNoNetwork
	}
	if dst.IsValid() && !dst.IsUnspecified() {
		if best, ok := bestInterface(dst); ok && best != st.index && best != index {
			return keepRoute(c, v6, best)
		}
	}
	return bind(c, v6, index)
}

// localAddrs keeps this computer's addresses a moment.
var localAddrs struct {
	mu    sync.Mutex
	at    time.Time
	addrs map[netip.Addr]bool
}

// local reports whether addr is one of this computer's own addresses (a
// DNS server can be one: WSL's is on loopback), which a connection reaches
// without leaving the computer, whatever it is bound to.
func local(addr netip.Addr) bool {
	localAddrs.mu.Lock()
	defer localAddrs.mu.Unlock()
	if time.Since(localAddrs.at) > time.Second {
		localAddrs.addrs = map[netip.Addr]bool{}
		if addrs, err := net.InterfaceAddrs(); err == nil {
			for _, a := range addrs {
				if p, err := netip.ParsePrefix(a.String()); err == nil {
					localAddrs.addrs[p.Addr().Unmap()] = true
				}
			}
		}
		localAddrs.at = time.Now()
	}
	return localAddrs.addrs[addr.WithZone("")]
}

// Dialer is a net.Dialer for this program's own connections: from outside
// the TUN while one runs, resolving names with the physical network's DNS,
// and noted by address (OwnDial). Make one for each connection, since a
// TUN may start or stop between them.
func Dialer(timeout time.Duration) *net.Dialer {
	d := ClientDialer(timeout)
	d.Control = func(network, address string, c syscall.RawConn) error {
		noteOwn(address)
		return Control(network, address, c)
	}
	return d
}

// ClientDialer is Dialer for a client's connection (a direct route), which
// is not noted as this program's own.
func ClientDialer(timeout time.Duration) *net.Dialer {
	d := &net.Dialer{Timeout: timeout, Control: Control}
	if current.Load() != nil {
		d.Resolver = physicalResolver
	}
	return d
}

// ownWindow is how long the address of one of this program's own
// connections is remembered.
const ownWindow = 10 * time.Second

// ownDials are the addresses this program itself connected to lately, by
// address and port, so that one coming back into its proxy as a client's
// connection shows a loop: something (PassWall2's Localhost Proxy, another
// VPN program's TUN) sends this program's own traffic into its proxy, and
// nothing connects.
var ownDials struct {
	mu sync.Mutex
	at map[string]time.Time
}

// ownNow is the clock of ownDials; tests replace it.
var ownNow = time.Now

// maxOwnDials bounds ownDials.
const maxOwnDials = 256

func noteOwn(address string) {
	key := ownKey(address)
	if key == "" {
		return
	}
	now := ownNow()
	ownDials.mu.Lock()
	defer ownDials.mu.Unlock()
	if ownDials.at == nil {
		ownDials.at = map[string]time.Time{}
	}
	if len(ownDials.at) >= maxOwnDials {
		for k, at := range ownDials.at {
			if now.Sub(at) > ownWindow {
				delete(ownDials.at, k)
			}
		}
		if len(ownDials.at) >= maxOwnDials {
			return
		}
	}
	ownDials.at[key] = now
}

// OwnDial reports whether target (host:port) is an address this program
// itself connected to in the last few seconds: a client's connection to it
// may be this program's own, sent back into its proxy.
func OwnDial(target string) bool {
	key := ownKey(target)
	if key == "" {
		return false
	}
	ownDials.mu.Lock()
	defer ownDials.mu.Unlock()
	at, ok := ownDials.at[key]
	return ok && ownNow().Sub(at) <= ownWindow
}

// ownKey writes an address one way: an IPv4 address mapped into IPv6 as
// IPv4, without a zone; a name in lower case. Loopback gives "": it never
// leaves this computer, so it never loops.
func ownKey(address string) string {
	if ap, err := netip.ParseAddrPort(address); err == nil {
		if ap.Addr().Unmap().IsLoopback() {
			return ""
		}
		return netip.AddrPortFrom(ap.Addr().Unmap().WithZone(""), ap.Port()).String()
	}
	if host, _, err := net.SplitHostPort(address); err == nil && strings.EqualFold(host, "localhost") {
		return ""
	}
	return strings.ToLower(address)
}

// DialContext connects with Dialer(0).
func DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	return Dialer(0).DialContext(ctx, network, address)
}

// LookupNetIP resolves host as the system's resolver does; while a TUN
// runs, by asking the physical network's DNS servers.
func LookupNetIP(ctx context.Context, host string) ([]netip.Addr, error) {
	r := net.DefaultResolver
	if current.Load() != nil {
		r = physicalResolver
	}
	return r.LookupNetIP(ctx, "ip", host)
}

// ListenUDP opens an unconnected UDP socket for talking to dst (QUIC), of
// its family, bound outside the TUN while one runs.
func ListenUDP(dst netip.Addr) (*net.UDPConn, error) {
	dst = dst.Unmap()
	network := "udp4"
	if dst.Is6() {
		network = "udp6"
	}
	lc := net.ListenConfig{Control: func(network, _ string, c syscall.RawConn) error {
		return Control(network, netip.AddrPortFrom(dst, 0).String(), c)
	}}
	pc, err := lc.ListenPacket(context.Background(), network, ":0")
	if err != nil {
		return nil, err
	}
	return pc.(*net.UDPConn), nil
}

// physicalResolver is Go's resolver asking the physical network's DNS
// servers (also for TCP), over sockets bound outside the TUN; it reads the
// hosts file like the system's.
var physicalResolver = &net.Resolver{PreferGo: true, Dial: dialDNS}

// dnsTurn spreads Go's resolver's attempts over the servers.
var dnsTurn atomic.Uint32

func dialDNS(ctx context.Context, network, _ string) (net.Conn, error) {
	st := current.Load()
	if st == nil {
		return nil, errors.New("no TUN runs")
	}
	servers := dnsServers(st)
	if len(servers) == 0 {
		return nil, errors.New("the network outside the TUN adapter has no DNS servers")
	}
	server := servers[int(dnsTurn.Add(1))%len(servers)]
	d := net.Dialer{Control: Control}
	return d.DialContext(ctx, network, server.String())
}