156 lines
5.6 KiB
Go
156 lines
5.6 KiB
Go
package main
|
|
|
|
// ownaddr.go — is a destination THIS machine?
|
|
//
|
|
// The outbound lanes that dial for someone else (the egress lane,
|
|
// egress.go; a coder run's proxy, coder_egress.go; the backup
|
|
// lanes' fetch and push, backupfetch.go) must never be turned back on the
|
|
// machine itself: a service listening on 0.0.0.0 here
|
|
// answers on every address the kernel delivers locally, not only on loopback
|
|
// and the private ranges internalIP refuses.
|
|
//
|
|
// Two checks, and a destination either one names is this machine:
|
|
//
|
|
// - the addresses on this machine's interfaces (net.InterfaceAddrs), which
|
|
// catches a public address on a NIC (a VPS, a server with its own IP);
|
|
// - on Linux, the kernel's own routing decision for the address
|
|
// (RTM_GETROUTE, the lookup `ip route get` makes; ownaddr_linux.go). An
|
|
// address the kernel delivers locally need not be on any interface: a
|
|
// `local` route (`ip route add local 192.0.2.0/24 dev lo`, AnyIP) makes a
|
|
// whole range this machine. Only a route of type unicast leaves the
|
|
// machine; local, broadcast, anycast and multicast routes are refused.
|
|
// A destination on a link that stays on this machine is refused too: a
|
|
// container's veth, or a bridge whose every port is a veth, a tap or a
|
|
// dummy (the bridge networks Docker, Podman, LXD and libvirt make here).
|
|
// A container or VM on one of them is a service on this machine. A route
|
|
// through a gateway on such a link leaves: the gateway sends the packet
|
|
// on, as the machine a container runs on does for the container. A link
|
|
// a main-table default route goes out of is never inside: it is this
|
|
// machine's way out, so a bastion in a container keeps the network on
|
|
// its own veth, its LAN included.
|
|
//
|
|
// The IPv4 address inside an IPv4-mapped, NAT64 (64:ff9b::/96) or 6to4
|
|
// (2002::/16) address is checked as well, since that is what such an address
|
|
// reaches. Anything that cannot be decided refuses.
|
|
//
|
|
// What neither check sees: a public address that reaches this machine only
|
|
// through a 1:1 NAT in front of it (a cloud's elastic or floating IP that is
|
|
// on no interface and in no local route). The bastion does not learn its own
|
|
// public address from anywhere, so a run that names that address is dialled
|
|
// out to the NAT, which hairpins it back if the provider allows hairpinning.
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ownInterfaceAddrs lists the addresses on this machine's interfaces. A seam
|
|
// so a test can put a public address on a NIC.
|
|
var ownInterfaceAddrs = net.InterfaceAddrs
|
|
|
|
// routeKind is what the kernel does with a packet to an address.
|
|
type routeKind int
|
|
|
|
const (
|
|
routeLeaves routeKind = iota // a unicast route: the packet leaves this machine
|
|
routeThisMachine // local, broadcast, anycast, multicast or any other non-unicast type
|
|
routeNowhere // no route at all (unreachable, prohibited): the dial cannot be made
|
|
)
|
|
|
|
// ownRouteLookup asks the kernel how it routes ip. Set by ownaddr_linux.go;
|
|
// nil where no lookup is built, and then only the interface check applies.
|
|
// A seam so a test can supply the kernel's answer.
|
|
var ownRouteLookup func(ip net.IP) (routeKind, error)
|
|
|
|
// The interface addresses, re-read at most every ownAddrTTL so an address
|
|
// added or changed (DHCP, a new interface) is seen within seconds without
|
|
// listing interfaces on every connection. A failed read is never cached.
|
|
var ownAddrTTL = 2 * time.Second
|
|
|
|
var ownAddrCache struct {
|
|
mu sync.Mutex
|
|
at time.Time
|
|
ips []net.IP
|
|
}
|
|
|
|
func ownInterfaceIPs() ([]net.IP, error) {
|
|
ownAddrCache.mu.Lock()
|
|
defer ownAddrCache.mu.Unlock()
|
|
if !ownAddrCache.at.IsZero() && time.Since(ownAddrCache.at) < ownAddrTTL {
|
|
return ownAddrCache.ips, nil
|
|
}
|
|
addrs, err := ownInterfaceAddrs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ips := make([]net.IP, 0, len(addrs))
|
|
for _, a := range addrs {
|
|
switch v := a.(type) {
|
|
case *net.IPNet:
|
|
ips = append(ips, v.IP)
|
|
case *net.IPAddr:
|
|
ips = append(ips, v.IP)
|
|
}
|
|
}
|
|
ownAddrCache.ips, ownAddrCache.at = ips, time.Now()
|
|
return ips, nil
|
|
}
|
|
|
|
// ownAddrReset drops the cached interface addresses (tests swap the seam).
|
|
func ownAddrReset() {
|
|
ownAddrCache.mu.Lock()
|
|
ownAddrCache.at, ownAddrCache.ips = time.Time{}, nil
|
|
ownAddrCache.mu.Unlock()
|
|
}
|
|
|
|
// ownAddrForms: ip, plus the IPv4 address a NAT64 or 6to4 address carries.
|
|
// An IPv4-mapped address needs no extra form: To4 already reads it as IPv4,
|
|
// for the comparison (net.IP.Equal) and for the route lookup alike.
|
|
func ownAddrForms(ip net.IP) []net.IP {
|
|
forms := []net.IP{ip}
|
|
if ip.To4() == nil && len(ip) == net.IPv6len {
|
|
if ip[0] == 0x00 && ip[1] == 0x64 && ip[2] == 0xff && ip[3] == 0x9b &&
|
|
ip[4]|ip[5]|ip[6]|ip[7]|ip[8]|ip[9]|ip[10]|ip[11] == 0 {
|
|
forms = append(forms, net.IPv4(ip[12], ip[13], ip[14], ip[15]))
|
|
}
|
|
if ip[0] == 0x20 && ip[1] == 0x02 {
|
|
forms = append(forms, net.IPv4(ip[2], ip[3], ip[4], ip[5]))
|
|
}
|
|
}
|
|
return forms
|
|
}
|
|
|
|
// ownDestination reports whether any of addrs is this machine. An error means
|
|
// it could not tell (the interfaces could not be read, or the kernel's route
|
|
// lookup failed), and every caller refuses on it.
|
|
func ownDestination(addrs []net.IP) (bool, error) {
|
|
local, err := ownInterfaceIPs()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, ip := range addrs {
|
|
forms := ownAddrForms(ip)
|
|
for _, f := range forms {
|
|
for _, l := range local {
|
|
if f.Equal(l) {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
if ownRouteLookup == nil {
|
|
continue
|
|
}
|
|
for _, f := range forms {
|
|
kind, err := ownRouteLookup(f)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if kind == routeThisMachine {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|