arblant
firefox-vpn-client / core / internal / tunservice / udp.go
udp.go163 lines · 4.1 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
package tunservice

import (
	"context"
	"net"
	"net/netip"
	"sync"
	"time"

	"github.com/armanlife/firefox-vpn-client/core/internal/egress"
	"github.com/armanlife/firefox-vpn-client/core/internal/logx"
	"github.com/armanlife/firefox-vpn-client/core/internal/tun"
	"github.com/armanlife/firefox-vpn-client/core/internal/tunstack"
)

// The UDP of a program that skips the VPN goes directly: each packet to its
// destination outside the TUN, a fake address by its name, which the
// physical network's DNS resolves; the replies come back to the app from
// the address it sent to. Replaced in tests.
var (
	listenUDPDirect = egress.ListenUDP
	lookupDirect    = egress.LookupNetIP
)

// udpLookupTimeout bounds resolving a fake address's name.
const udpLookupTimeout = 5 * time.Second

// packetFlow is the part of tunstack.PacketFlow the relay uses; tests fake
// it.
type packetFlow interface {
	Accept()
	Read(b []byte) (int, netip.AddrPort, error)
	Write(b []byte, from netip.AddrPort) error
}

// udpRelay carries one flow: one socket per address family, opened when
// the app first sends to one.
type udpRelay struct {
	sess *session
	flow packetFlow
	// names are the real addresses of the fake ones the app sent to.
	names map[netip.Addr]netip.Addr
	mu    sync.Mutex
	// back maps a real address the app's packets went to back to the one
	// it sent them to.
	back   map[netip.AddrPort]netip.AddrPort
	v4, v6 *net.UDPConn
}

// relayUDP carries a flow takeUDP took until it ends.
func (s *session) relayUDP(f *tunstack.PacketFlow) { s.relay(f) }

func (s *session) relay(f packetFlow) {
	r := &udpRelay{sess: s, flow: f, names: map[netip.Addr]netip.Addr{}, back: map[netip.AddrPort]netip.AddrPort{}}
	defer r.close()
	f.Accept()
	b := make([]byte, 65535)
	for {
		n, dst, err := f.Read(b)
		if err != nil {
			return
		}
		peer, ok := r.resolve(dst)
		if !ok {
			continue
		}
		conn, err := r.socket(peer.Addr())
		if err != nil {
			logx.Debug("TUN: no direct UDP socket for dst=%s err=%s", peer, logx.Err(err))
			continue
		}
		r.mu.Lock()
		r.back[peer] = dst
		r.mu.Unlock()
		_, _ = conn.WriteToUDPAddrPort(b[:n], peer)
	}
}

// resolve is where a packet to dst really goes: dst, or the address of the
// name a fake one stands for; not a filtering DNS's answer.
func (r *udpRelay) resolve(dst netip.AddrPort) (netip.AddrPort, bool) {
	addr := dst.Addr().Unmap()
	if isFilterAddress(addr) {
		return netip.AddrPort{}, false
	}
	if !tun.IsFake(addr) {
		return netip.AddrPortFrom(addr, dst.Port()), true
	}
	if peer, ok := r.names[addr]; ok {
		return netip.AddrPortFrom(peer, dst.Port()), true
	}
	name, ok := r.sess.dns.Name(addr)
	if !ok {
		return netip.AddrPort{}, false
	}
	ctx, cancel := context.WithTimeout(r.sess.ctx, udpLookupTimeout)
	addrs, err := lookupDirect(ctx, name)
	cancel()
	if err != nil || len(addrs) == 0 {
		logx.Debug("TUN: resolving name=%s for direct UDP failed err=%s", name, logx.Err(err))
		return netip.AddrPort{}, false
	}
	// IPv4 first: the app sent from IPv4, to a fake IPv4 address.
	peer := addrs[0].Unmap()
	for _, a := range addrs {
		if a.Unmap().Is4() {
			peer = a.Unmap()
			break
		}
	}
	r.names[addr] = peer
	return netip.AddrPortFrom(peer, dst.Port()), true
}

// socket is the flow's socket for addr's family.
func (r *udpRelay) socket(addr netip.Addr) (*net.UDPConn, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	slot := &r.v4
	if addr.Is6() {
		slot = &r.v6
	}
	if *slot != nil {
		return *slot, nil
	}
	conn, err := listenUDPDirect(addr)
	if err != nil {
		return nil, err
	}
	*slot = conn
	go r.readBack(conn)
	return conn, nil
}

// readBack writes each reply to the app, from the address it sent to.
func (r *udpRelay) readBack(conn *net.UDPConn) {
	b := make([]byte, 65535)
	for {
		n, from, err := conn.ReadFromUDPAddrPort(b)
		if err != nil {
			return
		}
		from = netip.AddrPortFrom(from.Addr().Unmap(), from.Port())
		r.mu.Lock()
		to, ok := r.back[from]
		r.mu.Unlock()
		if !ok {
			// A peer the app never sent to (a call's other end): as it is.
			to = from
		}
		_ = r.flow.Write(b[:n], to)
	}
}

func (r *udpRelay) close() {
	r.mu.Lock()
	defer r.mu.Unlock()
	for _, c := range []*net.UDPConn{r.v4, r.v6} {
		if c != nil {
			_ = c.Close()
		}
	}
}