| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- //go:build !ios && !e2e_testing
- package overlay
- import (
- "errors"
- "fmt"
- "net"
- "net/netip"
- "os"
- "strconv"
- "sync/atomic"
- "time"
- "unsafe"
- "github.com/slackhq/nebula/config"
- "github.com/slackhq/nebula/util"
- netroute "golang.org/x/net/route"
- "golang.org/x/sys/unix"
- )
- // Darwin has no route metrics, the routing table holds one unscoped route per prefix. When another interface already
- // routes an unsafe_route's prefix, like the lan we started on, theirs stays and ours is added once theirs is removed.
- // Our own route removed by someone else, with nothing in its place, was removed on purpose and stays gone until a reload
- // or until another interface takes the prefix, after which ours follows theirs like any other.
- const (
- // routeRetryDelay is how long the system routing table must be quiet before we judge who holds a prefix. configd
- // replaces a route by deleting and adding, looking in between would find the prefix free
- routeRetryDelay = time.Second
- // routeSweepInterval bounds how long a freed unsafe_route can go unnoticed. The routing socket drops messages that
- // don't fit its receive buffer without telling the reader, and detaching an interface deletes its routes without
- // a message for each
- routeSweepInterval = time.Minute
- // routeSocketRcvBuf is well past the 8KiB default, which a burst of neighbor routes expiring can fill
- routeSocketRcvBuf = 1 << 20
- )
- type routeStateKind int
- const (
- routeUnknown routeStateKind = iota
- // routeInstalled is our route holding the prefix
- routeInstalled
- // routeYielded is another interface holding the prefix, ours is added once it lets go
- routeYielded
- // routeRemoved is our route deleted by someone else with nothing in its place, it is left alone until a reload or
- // another interface takes the prefix
- routeRemoved
- // routeFailed is adding our route failing for some other reason, it is retried
- routeFailed
- )
- type routeState struct {
- kind routeStateKind
- // holder is the index of the interface holding the prefix when kind is routeYielded
- holder int
- }
- // routeMover is the process that last added, changed or deleted a route for an unsafe_route's prefix, for logs. Its name
- // is looked up as the message arrives, route(8) has exited by the time the table settles
- type routeMover struct {
- pid int32
- name string
- }
- // logArgs appends m to a log line's args, when we know who it was
- func (m routeMover) logArgs(args ...any) []any {
- if m.pid == 0 {
- return args
- }
- args = append(args, "pid", m.pid)
- if m.name != "" {
- args = append(args, "process", m.name)
- }
- return args
- }
- // processName is the name of process pid, truncated to 16 bytes, or empty when it has already exited
- func processName(pid int32) string {
- kp, err := unix.SysctlKinfoProc("kern.proc.pid", int(pid))
- if err != nil || kp.Proc.P_pid != pid {
- return ""
- }
- return unix.ByteSliceToString(kp.Proc.P_comm[:])
- }
- // installRoutes returns the routes we put in the system routing table, one per prefix
- func installRoutes(routes []Route) []Route {
- seen := make(map[netip.Prefix]struct{}, len(routes))
- var out []Route
- for _, r := range routes {
- if len(r.Via) == 0 || !r.Install {
- // We don't allow route MTUs so only install routes with a via
- continue
- }
- p := r.Cidr.Masked()
- if _, ok := seen[p]; ok {
- continue
- }
- seen[p] = struct{}{}
- out = append(out, r)
- }
- return out
- }
- // addRoutes puts every unsafe_route in the system routing table, including ones someone deleted, unless another
- // interface holds its prefix. It runs on start and on every reload.
- func (t *tun) addRoutes(logErrors bool, rl *config.Reload) error {
- // The route tree is still ours, the system routing table is not
- if t.hostOwned {
- return nil
- }
- t.routesMu.Lock()
- defer t.routesMu.Unlock()
- // Activate hasn't run yet, it will install the routes
- linkAddr := t.linkAddr.Load()
- if linkAddr == nil || t.closed {
- return nil
- }
- var msgs []netroute.Message
- var msgsErr error
- fetched := false
- states := make(map[netip.Prefix]routeState)
- for _, r := range installRoutes(*t.Routes.Load()) {
- p := r.Cidr.Masked()
- prev := t.routeStates[p]
- err := addRoute(p, linkAddr)
- if err == nil {
- t.l.Info("Added route", "route", r)
- states[p] = routeState{kind: routeInstalled}
- continue
- }
- if !errors.Is(err, unix.EEXIST) {
- retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
- if !logErrors {
- return retErr
- }
- retErr.Log(t.l)
- rl.Failed()
- states[p] = routeState{kind: routeFailed}
- continue
- }
- // Every route we added already exists on a reload, only warn about someone else's
- if !fetched {
- msgs, msgsErr = fetchRoutes()
- fetched = true
- }
- if msgsErr != nil {
- t.l.Warn("unable to add unsafe_route, a route already exists but its interface could not be determined", "route", r.Cidr, "error", msgsErr)
- states[p] = prev
- continue
- }
- switch holder := routeInterfaceIn(msgs, p); holder {
- case linkAddr.Index:
- states[p] = routeState{kind: routeInstalled}
- case 0:
- // Removed since the add, the watcher will see to it
- states[p] = prev
- default:
- if prev.kind != routeYielded || prev.holder != holder {
- t.l.Warn("unable to add unsafe_route, the destination is already routed through another interface, it will be added once that route is removed", "route", r.Cidr, "interface", interfaceName(holder))
- }
- states[p] = routeState{kind: routeYielded, holder: holder}
- }
- }
- t.routeStates = states
- return nil
- }
- func (t *tun) removeRoutes(routes []Route, rl *config.Reload) error {
- if t.hostOwned {
- return nil
- }
- t.routesMu.Lock()
- defer t.routesMu.Unlock()
- linkAddr := t.linkAddr.Load()
- if linkAddr == nil || t.closed {
- return nil
- }
- // The kernel deletes a route by its prefix alone, whichever interface holds it. Only delete the ones that are ours
- msgs, err := fetchRoutes()
- if err != nil {
- var left []netip.Prefix
- for _, r := range installRoutes(routes) {
- left = append(left, r.Cidr)
- }
- return fmt.Errorf("failed to read the system routing table, leaving routes %v installed: %w", left, err)
- }
- for _, r := range installRoutes(routes) {
- p := r.Cidr.Masked()
- // Whatever happens next, the prefix is no longer one we know about
- delete(t.routeStates, p)
- if routeInterfaceIn(msgs, p) != linkAddr.Index {
- continue
- }
- err = delRoute(p, linkAddr)
- if errors.Is(err, unix.ESRCH) {
- // Already gone, which is what we wanted
- t.l.Info("Route was already removed", "route", r)
- } else if err != nil {
- t.l.Error("Failed to remove route", "error", err, "route", r)
- rl.Failed()
- } else {
- t.l.Info("Removed route", "route", r)
- }
- }
- return nil
- }
- // reconcileRoutes brings the unsafe_routes in line with who holds their prefix now. It is quiet unless something
- // changed since it last looked, it runs every routeSweepEvery.
- func (t *tun) reconcileRoutes() {
- t.routesMu.Lock()
- defer t.routesMu.Unlock()
- linkAddr := t.linkAddr.Load()
- if linkAddr == nil || t.closed {
- return
- }
- t.routeMoversMu.Lock()
- movers := t.routeMovers
- t.routeMovers = nil
- t.routeMoversMu.Unlock()
- msgs, err := fetchRoutes()
- if err != nil {
- t.l.Error("Failed to read the system routing table", "error", err)
- return
- }
- states := make(map[netip.Prefix]routeState)
- for _, r := range installRoutes(*t.Routes.Load()) {
- p := r.Cidr.Masked()
- prev := t.routeStates[p]
- by := movers[p]
- holder := routeInterfaceIn(msgs, p)
- switch {
- case holder == linkAddr.Index:
- states[p] = routeState{kind: routeInstalled}
- case holder != 0:
- // Another interface taking a prefix we left alone puts ours back in line behind theirs
- if prev.kind != routeYielded || prev.holder != holder {
- t.l.Info("unsafe_route is routed through another interface, it will be added back when that route is removed", by.logArgs("route", r.Cidr, "interface", interfaceName(holder))...)
- }
- states[p] = routeState{kind: routeYielded, holder: holder}
- case prev.kind == routeInstalled:
- t.l.Warn("unsafe_route was removed outside nebula, leaving it until a reload or another interface takes its destination", by.logArgs("route", r.Cidr)...)
- states[p] = routeState{kind: routeRemoved}
- case prev.kind == routeRemoved:
- states[p] = prev
- default:
- err := addRoute(p, linkAddr)
- switch {
- case err == nil:
- if prev.kind == routeYielded {
- t.l.Info("unsafe_route's destination is free again, added it", by.logArgs("route", r)...)
- } else {
- t.l.Info("Added route", "route", r)
- }
- states[p] = routeState{kind: routeInstalled}
- case errors.Is(err, unix.EEXIST):
- // Taken since the fetch, the next look will see by whom
- states[p] = prev
- default:
- if prev.kind != routeFailed {
- util.NewContextualError("Failed to add route", map[string]any{"route": r}, err).Log(t.l)
- }
- states[p] = routeState{kind: routeFailed}
- }
- }
- }
- t.routeStates = states
- }
- // startRouteWatch starts reconciling the unsafe_routes, every routeSweepEvery and, when watch isn't nil, once the
- // routing table settles after anything that may have moved a prefix
- func (t *tun) startRouteWatch(watch *os.File) {
- t.routesMu.Lock()
- defer t.routesMu.Unlock()
- if t.closed {
- if watch != nil {
- _ = watch.Close()
- }
- return
- }
- t.routeSweep = time.AfterFunc(t.routeSweepEvery, t.sweepRoutes)
- if watch != nil {
- t.routeWatch = watch
- go t.watchRoutes(watch)
- }
- }
- func (t *tun) sweepRoutes() {
- // A table still changing gets reconciled once it settles
- if time.Since(time.Unix(0, t.lastRouteEvent.Load())) >= routeRetryDelay {
- t.reconcileRoutes()
- }
- t.routesMu.Lock()
- defer t.routesMu.Unlock()
- if !t.closed {
- t.routeSweep.Reset(t.routeSweepEvery)
- }
- }
- func openRouteSocket() (*os.File, error) {
- fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
- if err != nil {
- return nil, fmt.Errorf("unable to create AF_ROUTE socket: %w", err)
- }
- // Fewer drops, the sweep catches the rest
- _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, routeSocketRcvBuf)
- // Non-blocking puts it on the poller, so Close interrupts a Read
- if err = unix.SetNonblock(fd, true); err != nil {
- _ = unix.Close(fd)
- return nil, fmt.Errorf("failed to set the AF_ROUTE socket to non-blocking mode: %w", err)
- }
- return os.NewFile(uintptr(fd), "route"), nil
- }
- // watchRoutes reconciles the unsafe_routes once the routing table has been quiet for routeRetryDelay after a message
- // that may have moved a prefix. A table that keeps changing is reconciled every routeSweepEvery regardless.
- func (t *tun) watchRoutes(f *os.File) {
- var reconciled atomic.Int64
- settle := time.AfterFunc(time.Hour, func() {
- reconciled.Store(time.Now().UnixNano())
- t.reconcileRoutes()
- })
- settle.Stop()
- defer settle.Stop()
- var burst time.Time
- self := int32(os.Getpid())
- b := make([]byte, os.Getpagesize())
- for {
- n, err := f.Read(b)
- if err != nil {
- if !errors.Is(err, os.ErrClosed) {
- t.l.Error("Stopped watching the system routing table, unsafe_routes held by another interface are only retried every minute", "error", err)
- }
- return
- }
- moved, p, pid := routesMayHaveMoved(b[:n], *t.Routes.Load())
- if !moved {
- continue
- }
- // Our own are already in routeStates
- if p.IsValid() && pid != self {
- t.routeMoversMu.Lock()
- if t.routeMovers == nil {
- t.routeMovers = make(map[netip.Prefix]routeMover)
- }
- t.routeMovers[p] = routeMover{pid: pid, name: processName(pid)}
- t.routeMoversMu.Unlock()
- }
- now := time.Now()
- t.lastRouteEvent.Store(now.UnixNano())
- if burst.IsZero() || reconciled.Load() >= burst.UnixNano() {
- burst = now
- }
- settle.Reset(max(0, min(routeRetryDelay, time.Until(burst.Add(t.routeSweepEvery)))))
- }
- }
- // routesMayHaveMoved reports whether b, one routing socket message, may have freed a prefix one of routes installs or
- // handed it to another interface. That's a route for it added, changed or removed, or an interface that lost an address
- // or changed state, since detaching an interface deletes its routes without a message of their own. The header is read
- // directly, x/net/route skips the RTM_IFINFO the socket sends, which has no interface address, and misreads the
- // unpadded sockaddrs in messages the kernel makes itself. A request naming one of the prefixes also returns that prefix
- // and the pid of the process that made it.
- func routesMayHaveMoved(b []byte, routes []Route) (moved bool, p netip.Prefix, pid int32) {
- if len(b) < 4 {
- return false, p, 0
- }
- switch int(b[3]) {
- case unix.RTM_IFINFO, unix.RTM_DELADDR:
- return true, p, 0
- case unix.RTM_ADD, unix.RTM_CHANGE, unix.RTM_DELETE:
- default:
- return false, p, 0
- }
- if len(b) < unix.SizeofRtMsghdr {
- return false, p, 0
- }
- hdr := (*unix.RtMsghdr)(unsafe.Pointer(&b[0]))
- // A failed request is reported too, and a scoped route never held the prefix
- if hdr.Errno != 0 || hdr.Flags&unix.RTF_IFSCOPE != 0 {
- return false, p, 0
- }
- if hdr.Pid == 0 {
- // The kernel's own, which x/net/route can't parse. Neighbors coming and going are host routes, anything else
- // could be holding a prefix
- return hdr.Flags&(unix.RTF_HOST|unix.RTF_LLINFO) == 0, p, 0
- }
- // Replies to a request carry the requester's padded sockaddrs
- msgs, err := netroute.ParseRIB(unix.NET_RT_DUMP, b)
- if err != nil || len(msgs) != 1 {
- return false, p, 0
- }
- m, ok := msgs[0].(*netroute.RouteMessage)
- if !ok || len(m.Addrs) <= unix.RTAX_NETMASK {
- return false, p, 0
- }
- p, ok = routePrefix(m.Addrs[unix.RTAX_DST], m.Addrs[unix.RTAX_NETMASK])
- if !ok {
- return false, netip.Prefix{}, 0
- }
- p = p.Masked()
- for _, r := range installRoutes(routes) {
- if r.Cidr.Masked() == p {
- return true, p, hdr.Pid
- }
- }
- return false, netip.Prefix{}, 0
- }
- // interfaceName is the name of the interface with index i, for logs
- func interfaceName(i int) string {
- if ifc, err := net.InterfaceByIndex(i); err == nil {
- return ifc.Name
- }
- return strconv.Itoa(i)
- }
|