| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803 |
- //go:build !e2e_testing
- package udp
- import (
- "context"
- "errors"
- "fmt"
- "log/slog"
- "maps"
- "net"
- "net/netip"
- "os"
- "slices"
- "strconv"
- "sync"
- "sync/atomic"
- "syscall"
- "time"
- "golang.org/x/sys/unix"
- )
- // A connected socket is a UDP socket connected to one busy peer, on the listener's port and bound to the source
- // address the listener's route to that peer picks, so the peer sees the same address and port from either. A send
- // on a connected socket skips the per-datagram address and MAC policy checks, and the EndpointSecurity send check
- // that xprotectd can hold every unconnected sendto on, and it is the only UDP send that reports the interface
- // queue's flow control, as ENOBUFS. A connected socket wins xnu's lookup for its peer's datagrams; it never sees
- // anyone else's.
- //
- // A connected socket sets SO_REUSEPORT and binds a specific address beside a wildcard listener, which sets
- // SO_REUSEPORT only for listen routines. xnu refuses a wildcard SO_REUSEPORT bind beside a holder without the
- // option, and refuses another uid's bind to an address a socket holds specifically, so no other user can join the
- // port while connected sockets open and close: uid nobody got EADDRINUSE on every bind against a root listener
- // with connected sockets in the 09-25 probes. A connected socket is closed, never disconnected, because xnu widens
- // a disconnected socket's local address to the wildcard.
- //
- // Only the current remote of an established tunnel may get one: Nebula names those with SetEstablishedPeer, so an
- // unauthenticated sender can't earn a connected socket by drawing replies such as recv_error or handshake
- // responses.
- type connSock struct {
- dst netip.AddrPort
- peer *connSockPeer
- conn *net.UDPConn
- rc syscall.RawConn
- // used is the monotonic time in nanoseconds of the connected socket's last send or receive.
- used atomic.Int64
- // stallAt is the monotonic time of the first ENOBUFS or EAGAIN since a send last went through, or 0, and
- // dropping is set once that stall has dropped a datagram, so each stall logs its drops once.
- stallAt atomic.Int64
- dropping atomic.Bool
- // parkMu serializes the writes that wait for a full send buffer, since the write deadline bounding each wait
- // is the descriptor's, not the writer's.
- parkMu sync.Mutex
- // sys writes one datagram on the descriptor: writeFd, or a stand-in for the interface in tests.
- sys func(fd int, b []byte) error
- }
- func writeFd(fd int, b []byte) error {
- _, err := syscall.Write(fd, b)
- return err
- }
- // errStallSpent is a write that found the connected socket's stall budget spent, so its datagram is dropped.
- var errStallSpent = errors.New("connected socket stall budget spent")
- // connSockWriter carries one write through RawConn.Write. They are pooled with fn bound once, since a closure
- // passed through the RawConn interface escapes and would allocate on every send.
- type connSockWriter struct {
- s *connSock
- b []byte
- park bool
- err error
- fn func(fd uintptr) bool
- }
- var connSockWriters = sync.Pool{New: func() any {
- w := &connSockWriter{}
- w.fn = w.write
- return w
- }}
- func (w *connSockWriter) write(fd uintptr) bool {
- for {
- w.err = w.s.sys(int(fd), w.b)
- switch {
- case w.err == syscall.EINTR:
- case w.err == syscall.EAGAIN && w.park:
- return false
- default:
- return true
- }
- }
- }
- // try writes b on s once and returns nil, the write's errno, os.ErrDeadlineExceeded, or net.ErrClosed. With park
- // set, EAGAIN waits in the netpoller until the socket is writable or the write deadline passes; without it, EAGAIN
- // is returned.
- func (s *connSock) try(b []byte, park bool) error {
- w := connSockWriters.Get().(*connSockWriter)
- w.s, w.b, w.park = s, b, park
- rerr := s.rc.Write(w.fn)
- err := w.err
- *w = connSockWriter{fn: w.fn}
- connSockWriters.Put(w)
- switch {
- case rerr == nil:
- return err
- case errors.Is(rerr, os.ErrDeadlineExceeded):
- return os.ErrDeadlineExceeded
- default:
- return net.ErrClosed
- }
- }
- // connSockConfig holds the knobs. connSockConfigFromEnv reads all but backoff and enobufsMax from the environment,
- // so one binary can be tuned without a rebuild.
- type connSockConfig struct {
- // max is how many connected sockets the listen routines may have open at once; 0 turns connected sockets off.
- max int
- // run is how many datagrams to one destination within one window earn it a connected socket. darwin's tun
- // hands over one packet per read, so a peer's sends arrive one WriteTo or one-datagram batch at a time and
- // busyness has to be counted across calls.
- run int
- window time.Duration
- // idle closes a connected socket after it has neither sent nor received for this long.
- idle time.Duration
- // backoff keeps a destination whose connected socket failed on the listener for this long.
- backoff time.Duration
- // openEvery is how often the open bucket earns back an open once max are spent, which bounds connected socket
- // churn however fast senders earn connected sockets and errors close them.
- openEvery time.Duration
- // enobufsMax is how long a connected socket's sends may wait out ENOBUFS without one going through before they
- // drop instead, so a suspended interface costs one write per datagram rather than stalling the tun reader.
- enobufsMax time.Duration
- }
- func connSockConfigFromEnv() connSockConfig {
- c := connSockConfig{
- max: 8,
- run: 64,
- window: time.Second,
- idle: 30 * time.Second,
- backoff: 30 * time.Second,
- openEvery: time.Second,
- enobufsMax: 10 * time.Millisecond,
- }
- if v, err := strconv.Atoi(os.Getenv("NEBULA_CONNSOCKS")); err == nil {
- c.max = v
- }
- if v, err := strconv.Atoi(os.Getenv("NEBULA_CONNSOCK_RUN")); err == nil && v > 0 {
- c.run = v
- }
- if v, err := time.ParseDuration(os.Getenv("NEBULA_CONNSOCK_WINDOW")); err == nil && v > 0 {
- c.window = v
- }
- if v, err := time.ParseDuration(os.Getenv("NEBULA_CONNSOCK_IDLE")); err == nil && v > 0 {
- c.idle = v
- }
- if v, err := time.ParseDuration(os.Getenv("NEBULA_CONNSOCK_OPEN_EVERY")); err == nil && v > 0 {
- c.openEvery = v
- }
- return c
- }
- // connSockGroups holds the connSocks that the listen routines bound to one address share, so the limits, the
- // connected sockets and Rebind cover every routine, and a peer has at most one connected socket.
- var connSockGroups = struct {
- sync.Mutex
- m map[netip.AddrPort]*connSocks
- }{m: map[netip.AddrPort]*connSocks{}}
- // joinConnSocks returns the connSocks u shares with the other listen routines bound to la, and makes it for the
- // first.
- func joinConnSocks(u *StdConn, la netip.AddrPort, s Settings) *connSocks {
- g := &connSockGroups
- g.Lock()
- defer g.Unlock()
- if cs := g.m[la]; s.Multi && cs != nil {
- cs.mu.Lock()
- cs.members = append(cs.members, u)
- cs.mu.Unlock()
- return cs
- }
- cs := &connSocks{cfg: connSockConfigFromEnv(), key: la, members: []*StdConn{u}, clock: monoNow, sleep: time.Sleep}
- if cs.cfg.max > 0 && !u.listenHost.IsUnspecified() && !s.Multi {
- // A connected socket on a specific listen.host binds the listener's own address, which xnu allows only when
- // both sockets set SO_REUSEPORT, and a listener with it lets another uid bind a dual-stack wildcard on the
- // port.
- cs.off.Store(true)
- u.l.Debug("connected sockets: off for a specific listen.host", "addr", s.Listen)
- }
- if s.Multi {
- g.m[la] = cs
- }
- return cs
- }
- type connSocks struct {
- cfg connSockConfig
- // key is the listeners' bound address, which connSockGroups files this under.
- key netip.AddrPort
- // off keeps connected sockets from opening, when the listener's address can't be shared with them.
- off atomic.Bool
- // table maps each destination that is the remote of an established tunnel, and that a connected socket could
- // reach, to its peer; it is nil when there are none, so a send with nothing to consider costs one load. Every
- // send reads it without a lock, and mu guards copying it. An open that holds mu and still finds its peer here
- // can't outlive the tunnel.
- table atomic.Pointer[map[netip.AddrPort]*connSockPeer]
- // reserved counts the connected sockets that are open, opening or closing, against cfg.max; mu guards changing it.
- reserved atomic.Int32
- mu sync.Mutex
- closed bool
- // members are the listen routines' listeners that share these connected sockets.
- members []*StdConn
- // pending holds each destination whose connected socket is opening or closing, and not on its peer. xnu
- // refuses to connect a 4-tuple that is still bound, so a destination in pending can't open another.
- pending map[netip.AddrPort]struct{}
- // rebinds counts Rebinds, so an open that copied the listener's interface before one is discarded.
- rebinds uint64
- // closers tracks the goroutines that close detached connected sockets.
- closers sync.WaitGroup
- // done stops the reaper; it is made when ListenOut starts the connected sockets.
- done chan struct{}
- // opens bounds connected socket opens to cfg.max at once and one per cfg.openEvery after.
- opens gcra
- // logs bounds connected socket log lines at info or warn; see connSockLogBurst.
- logs gcra
- // clock returns monotonic nanoseconds and sleep waits out ENOBUFS: monoNow and time.Sleep, or stand-ins in
- // tests.
- clock func() int64
- sleep func(time.Duration)
- // dialHook and closeHook, when set, run just before an open's dials and a close's Close, outside mu; tests
- // use them to hold an open or close in flight.
- dialHook, closeHook func(dst netip.AddrPort)
- }
- // connSockPeer is what connSocks keeps about an established tunnel's remote: its connected socket, how busy it is,
- // and how long a failed connected socket keeps it on the listener.
- type connSockPeer struct {
- sock atomic.Pointer[connSock]
- // windowMu guards start, the monotonic time the current window began, and n, the datagrams sent in it, so a
- // window's reset and its counts can't interleave with another sender's.
- windowMu sync.Mutex
- start int64
- n int
- // retryAt is the monotonic time before which a peer whose connected socket failed stays on the listener;
- // connSocks.mu guards it.
- retryAt int64
- }
- // busy counts n more datagrams to p and reports whether it has now sent cfg.run within one window. A busy peer's
- // count starts over, so a peer that can't get a connected socket retries at most once per cfg.run sends.
- func (p *connSockPeer) busy(now int64, n int, cfg *connSockConfig) bool {
- p.windowMu.Lock()
- defer p.windowMu.Unlock()
- if time.Duration(now-p.start) > cfg.window {
- p.start, p.n = now, 0
- }
- p.n += n
- if p.n < cfg.run {
- return false
- }
- p.n = 0
- return true
- }
- // connSockLogBurst and connSockLogEvery size the bucket that lets connected socket opens, closes and failures log
- // at info or warn; past it they log at debug, so churn can't flood the log.
- const (
- connSockLogBurst = 32
- connSockLogEvery = 10 * time.Second
- )
- var monoStart = time.Now()
- func monoNow() int64 { return int64(time.Since(monoStart)) }
- // gcra is a token bucket kept as the monotonic time it is next full (the generic cell rate algorithm), so it needs
- // no refill tick.
- type gcra struct{ tat int64 }
- // take spends a token from a bucket of burst that earns one back per every, and reports whether there was one to
- // spend.
- func (g *gcra) take(now int64, burst int, every time.Duration) bool {
- e := int64(every)
- tat := max(g.tat, now)
- if tat-now > int64(burst-1)*e {
- return false
- }
- g.tat = tat + e
- return true
- }
- // logLevelLocked returns level while the log bucket has a token, and debug once it is spent.
- func (cs *connSocks) logLevelLocked(level slog.Level) slog.Level {
- if cs.logs.take(cs.clock(), connSockLogBurst, connSockLogEvery) {
- return level
- }
- return slog.LevelDebug
- }
- // startConnSocks starts the idle reaper.
- func (u *StdConn) startConnSocks() {
- cs := u.socks
- cs.mu.Lock()
- defer cs.mu.Unlock()
- if cs.closed || cs.done != nil || cs.cfg.max <= 0 || cs.off.Load() {
- return
- }
- cs.done = make(chan struct{})
- go u.reapConnSocks(cs.done)
- }
- // stopConnSocks closes every connected socket and keeps new ones from opening, once any listen routine's listener
- // closes, as they do together at shutdown. It returns once their descriptors are closed.
- func (u *StdConn) stopConnSocks() {
- cs := u.socks
- cs.mu.Lock()
- cs.members = slices.DeleteFunc(cs.members, func(m *StdConn) bool { return m == u })
- if !cs.closed {
- cs.closed = true
- if cs.done != nil {
- close(cs.done)
- }
- u.detachAllLocked("close")
- }
- cs.mu.Unlock()
- connSockGroups.Lock()
- if connSockGroups.m[cs.key] == cs {
- delete(connSockGroups.m, cs.key)
- }
- connSockGroups.Unlock()
- cs.closers.Wait()
- }
- // siblings returns the other listen routines' listeners.
- func (cs *connSocks) siblings(u *StdConn) []*StdConn {
- cs.mu.Lock()
- defer cs.mu.Unlock()
- return slices.DeleteFunc(slices.Clone(cs.members), func(m *StdConn) bool { return m == u })
- }
- // connSockFor returns dst's connected socket, or nil.
- func (u *StdConn) connSockFor(dst netip.AddrPort) *connSock {
- if p := u.socks.peer(dst); p != nil {
- return p.sock.Load()
- }
- return nil
- }
- func (cs *connSocks) peer(dst netip.AddrPort) *connSockPeer {
- if t := cs.table.Load(); t != nil {
- return (*t)[dst]
- }
- return nil
- }
- // SetEstablishedPeer makes dst eligible for a connected socket while it is the remote of an established tunnel,
- // and closes the one it has once it no longer is. Nebula calls it for every listen routine under the hostmap's
- // write lock, so repeats are no-ops and it makes no syscalls: the descriptor is closed by another goroutine.
- func (u *StdConn) SetEstablishedPeer(dst netip.AddrPort, established bool) {
- cs := u.socks
- if cs.cfg.max <= 0 || cs.off.Load() || !u.connSockable(dst) {
- return
- }
- cs.mu.Lock()
- defer cs.mu.Unlock()
- t := cs.table.Load()
- p := cs.peer(dst)
- if cs.closed || (p != nil) == established {
- return
- }
- next := map[netip.AddrPort]*connSockPeer{}
- if t != nil {
- next = maps.Clone(*t)
- }
- if established {
- p = &connSockPeer{start: cs.clock()}
- next[dst] = p
- } else {
- delete(next, dst)
- if s := p.sock.Load(); s != nil {
- u.detachLocked(s, "tunnel gone", false)
- }
- }
- cs.storeTableLocked(next)
- }
- // storeTableLocked publishes t, or nil for an empty one.
- func (cs *connSocks) storeTableLocked(t map[netip.AddrPort]*connSockPeer) {
- if len(t) == 0 {
- cs.table.Store(nil)
- return
- }
- cs.table.Store(&t)
- }
- // connSockable reports whether a connected socket to dst could send what the listener would. A v4 listener can't
- // reach a v6 destination at all, and a connected socket bound to a specific listen.host must share its family.
- func (u *StdConn) connSockable(dst netip.AddrPort) bool {
- if !dst.IsValid() || dst.Port() == 0 {
- return false
- }
- if u.isV4 {
- return dst.Addr().Is4()
- }
- return u.listenHost.IsUnspecified() || u.listenHost.Unmap().Is4() == dst.Addr().Unmap().Is4()
- }
- // connSockRun sends bufs, all to p's destination, on p's connected socket, opening one if they make p busy. It
- // returns how many went out; when that is short of len(bufs), err is the failure of bufs[sent], or nil to leave
- // bufs[sent:] to the listener.
- func (u *StdConn) connSockRun(p *connSockPeer, dst netip.AddrPort, bufs [][]byte) (sent int, err error) {
- s := p.sock.Load()
- if s == nil {
- if s = u.maybeOpenConnSock(p, dst, len(bufs)); s == nil {
- return 0, nil
- }
- }
- sent, errno, closed := u.connSockWrite(s, bufs)
- switch {
- case sent == len(bufs):
- case connSockDead(errno):
- u.closeConnSock(s, errno.Error(), true)
- case !closed:
- return sent, &net.OpError{Op: "write", Err: errno}
- }
- return sent, nil
- }
- // maybeOpenConnSock counts n datagrams to p, and opens a connected socket to it once it is busy and nothing rules
- // one out.
- func (u *StdConn) maybeOpenConnSock(p *connSockPeer, dst netip.AddrPort, n int) *connSock {
- cs := u.socks
- if n <= 0 || u.reader.Load() == nil || int(cs.reserved.Load()) >= cs.cfg.max || !p.busy(cs.clock(), n, &cs.cfg) {
- return nil
- }
- cs.mu.Lock()
- if cs.closed || cs.peer(dst) != p {
- cs.mu.Unlock()
- return nil
- }
- if s := p.sock.Load(); s != nil {
- cs.mu.Unlock()
- return s
- }
- now := cs.clock()
- if _, ok := cs.pending[dst]; ok || int(cs.reserved.Load()) >= cs.cfg.max || now < p.retryAt ||
- !cs.opens.take(now, cs.cfg.max, cs.cfg.openEvery) {
- cs.mu.Unlock()
- return nil
- }
- if cs.pending == nil {
- cs.pending = map[netip.AddrPort]struct{}{}
- }
- cs.pending[dst] = struct{}{}
- cs.reserved.Add(1)
- rebinds := cs.rebinds
- cs.mu.Unlock()
- if cs.dialHook != nil {
- cs.dialHook(dst)
- }
- s, err := u.dialConnSock(p, dst)
- cs.mu.Lock()
- if err != nil {
- delete(cs.pending, dst)
- cs.reserved.Add(-1)
- p.retryAt = cs.clock() + int64(cs.cfg.backoff)
- level := cs.logLevelLocked(slog.LevelWarn)
- cs.mu.Unlock()
- u.l.Log(context.Background(), level, "connected socket: open failed, staying on the listener", "udpAddr", dst, "error", err)
- return nil
- }
- if cs.closed || cs.rebinds != rebinds || cs.peer(dst) != p {
- // The tunnel went away, or a Rebind or shutdown ran, while the socket was opening; it stays in pending until
- // it is closed.
- cs.mu.Unlock()
- u.finishClose(s)
- u.l.Debug("connected socket: discarded, its tunnel or route changed while it opened", "udpAddr", dst)
- return nil
- }
- delete(cs.pending, dst)
- p.sock.Store(s)
- go u.readConnSock(s)
- level := cs.logLevelLocked(slog.LevelInfo)
- cs.mu.Unlock()
- u.l.Log(context.Background(), level, "connected socket: opened", "udpAddr", dst, "local", s.conn.LocalAddr(), "open", cs.reserved.Load())
- return s
- }
- // dialConnSock opens a socket on the listener's port with SO_REUSEPORT, bound to the configured listen.host or,
- // for a wildcard listener, to the source address the route to dst picks, and connected to dst. It carries the
- // listener's IP_BOUND_IF, so both route the same way.
- func (u *StdConn) dialConnSock(p *connSockPeer, dst netip.AddrPort) (*connSock, error) {
- raddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(dst.Addr().Unmap(), dst.Port()))
- network := "udp6"
- if raddr.IP.To4() != nil {
- network = "udp4"
- }
- ifindex, err := u.boundIf()
- if err != nil {
- return nil, err
- }
- local := &net.UDPAddr{IP: u.listenHost.AsSlice(), Zone: u.listenHost.Zone()}
- if u.listenHost.IsUnspecified() {
- // A connected socket gets the source address xnu would pick for an unconnected send to dst.
- d := net.Dialer{Control: connSockControl(false, ifindex)}
- probe, err := d.Dial(network, raddr.String())
- if err != nil {
- return nil, err
- }
- local = probe.LocalAddr().(*net.UDPAddr)
- _ = probe.Close()
- }
- local.Port = int(u.port)
- d := net.Dialer{LocalAddr: local, Control: connSockControl(true, ifindex)}
- c, err := d.Dial(network, raddr.String())
- if err != nil {
- return nil, err
- }
- uc := c.(*net.UDPConn)
- rc, err := uc.SyscallConn()
- if err != nil {
- _ = uc.Close()
- return nil, err
- }
- s := &connSock{dst: dst, peer: p, conn: uc, rc: rc, sys: writeFd}
- s.used.Store(u.socks.clock())
- return s, nil
- }
- // connSockControl sets SO_REUSEPORT when reuse is set, and IP_BOUND_IF or IPV6_BOUND_IF when ifindex isn't 0,
- // before the socket binds.
- func connSockControl(reuse bool, ifindex int) func(network, address string, c syscall.RawConn) error {
- return func(network, _ string, c syscall.RawConn) error {
- var serr error
- err := c.Control(func(fd uintptr) {
- if reuse {
- serr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
- }
- if serr == nil && ifindex != 0 {
- if network == "udp4" {
- serr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifindex)
- } else {
- serr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifindex)
- }
- }
- })
- if err != nil {
- return err
- }
- return serr
- }
- }
- // boundIf returns the interface index the listener is scoped to, or 0. An error keeps the peer on the listener,
- // since a connected socket opened without the listener's scope could route out an interface the listener can't.
- func (u *StdConn) boundIf() (int, error) {
- level, opt := unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
- if u.isV4 {
- level, opt = unix.IPPROTO_IP, unix.IP_BOUND_IF
- }
- v, err := unix.GetsockoptInt(int(u.sysFd), level, opt)
- if err != nil {
- return 0, fmt.Errorf("reading the listener's bound interface: %w", err)
- }
- return v, nil
- }
- // closeConnSock closes s if it is still open. With backoff set, the peer stays on the listener until the backoff
- // runs out.
- func (u *StdConn) closeConnSock(s *connSock, why string, backoff bool) {
- u.socks.mu.Lock()
- u.detachLocked(s, why, backoff)
- u.socks.mu.Unlock()
- }
- // detachLocked takes s off its peer, if it is still there, and closes it on another goroutine, since callers such
- // as SetEstablishedPeer hold locks that no socket syscall should run under. s.dst stays in pending until the close
- // completes.
- func (u *StdConn) detachLocked(s *connSock, why string, backoff bool) {
- cs := u.socks
- if !s.peer.sock.CompareAndSwap(s, nil) {
- return
- }
- if backoff {
- s.peer.retryAt = cs.clock() + int64(cs.cfg.backoff)
- }
- if cs.pending == nil {
- cs.pending = map[netip.AddrPort]struct{}{}
- }
- cs.pending[s.dst] = struct{}{}
- level := cs.logLevelLocked(slog.LevelInfo)
- cs.closers.Add(1)
- go func() {
- defer cs.closers.Done()
- open := u.finishClose(s)
- u.l.Log(context.Background(), level, "connected socket: closed", "udpAddr", s.dst, "reason", why, "open", open)
- }()
- }
- // finishClose closes s, whose destination is in pending, then lets the destination open again, and returns how
- // many connected sockets are left. Closing waits only for a write already in a syscall: a write parked on a full
- // buffer is woken, and one waiting out ENOBUFS holds no reference on the descriptor.
- func (u *StdConn) finishClose(s *connSock) int32 {
- cs := u.socks
- if cs.closeHook != nil {
- cs.closeHook(s.dst)
- }
- _ = s.conn.Close()
- cs.mu.Lock()
- defer cs.mu.Unlock()
- delete(cs.pending, s.dst)
- return cs.reserved.Add(-1)
- }
- // detachAllLocked detaches every connected socket, for a network change or shutdown.
- func (u *StdConn) detachAllLocked(why string) {
- if t := u.socks.table.Load(); t != nil {
- for _, p := range *t {
- if s := p.sock.Load(); s != nil {
- u.detachLocked(s, why, false)
- }
- }
- }
- }
- // closeConnSocks detaches every connected socket and discards the ones still opening, for a network change.
- func (u *StdConn) closeConnSocks(why string) {
- u.socks.mu.Lock()
- u.socks.rebinds++
- u.detachAllLocked(why)
- u.socks.mu.Unlock()
- }
- func (u *StdConn) reapConnSocks(done <-chan struct{}) {
- cs := u.socks
- idle := cs.cfg.idle
- tick := time.NewTicker(max(min(idle/4, 5*time.Second), time.Millisecond))
- defer tick.Stop()
- for {
- select {
- case <-done:
- return
- case <-tick.C:
- }
- t := cs.table.Load()
- if t == nil {
- continue
- }
- now := cs.clock()
- for _, p := range *t {
- if s := p.sock.Load(); s != nil && time.Duration(now-s.used.Load()) > idle {
- u.closeConnSock(s, "idle", false)
- }
- }
- }
- }
- // connSockDead reports whether a connected socket send or receive error means the connected path to the peer is
- // gone, so its traffic belongs back on the listener. A connected UDP socket reports a peer's ICMP unreachable as
- // ECONNREFUSED on its next call, which the unconnected listener never sees.
- func connSockDead(errno syscall.Errno) bool {
- switch errno {
- case unix.ECONNREFUSED, unix.EHOSTUNREACH, unix.EHOSTDOWN, unix.ENETUNREACH, unix.ENETDOWN,
- unix.EADDRNOTAVAIL, unix.EPIPE, unix.ENOTCONN, unix.EDESTADDRREQ:
- return true
- }
- return false
- }
- // enobufsWait is how long a connected socket send sleeps when the interface queue is flow controlled. The kernel
- // posts no wakeup when the advisory lifts, so this polls; 50us kept a gigabit link full without spinning a core in
- // the 09-25 probes.
- const enobufsWait = 50 * time.Microsecond
- // connSockWrite writes bufs on s, one datagram per write. A full socket buffer parks the caller in the netpoller
- // until the socket is writable, and a flow-controlled interface makes it wait out ENOBUFS, so a busy link slows
- // the tun reader instead of dropping. Once the connected socket has waited cfg.enobufsMax without a send going
- // through, either one drops the datagram instead, and a dropped datagram counts as sent, as the listener's
- // unreported interface drops do; it never falls back to the listener, which would reorder it. closed reports that
- // the connected socket was closed before bufs[sent] went out; any other error stops the run and is returned with
- // how many went out.
- func (u *StdConn) connSockWrite(s *connSock, bufs [][]byte) (sent int, errno syscall.Errno, closed bool) {
- cs := u.socks
- defer func() {
- if sent > 0 {
- s.used.Store(cs.clock())
- }
- }()
- for sent < len(bufs) {
- err := s.try(bufs[sent], false)
- if err == syscall.EAGAIN || err == os.ErrDeadlineExceeded {
- // os.ErrDeadlineExceeded here is another writer's parked deadline, passed but not yet cleared.
- err = u.parkedWrite(s, bufs[sent])
- }
- switch err {
- case nil:
- sent++
- if s.stallAt.Load() != 0 {
- s.stallAt.Store(0)
- s.dropping.Store(false)
- }
- continue
- case errStallSpent:
- u.dropStalled(s)
- sent++
- continue
- }
- errno, ok := err.(syscall.Errno)
- if !ok {
- return sent, 0, true
- }
- if errno != syscall.ENOBUFS {
- return sent, errno, false
- }
- // The wait holds no reference on the descriptor, so a close doesn't wait for it.
- if s.stalledFor(cs.clock()) < cs.cfg.enobufsMax {
- cs.sleep(enobufsWait)
- } else {
- u.dropStalled(s)
- sent++
- }
- }
- return sent, 0, false
- }
- // parkedWrite writes b after EAGAIN, parked in the netpoller for what is left of s's stall budget, and returns
- // errStallSpent once none is. golang/go#73919: a darwin UDP write parked on EAGAIN can wait forever for a
- // writability event that never comes, so the wait always has a deadline.
- func (u *StdConn) parkedWrite(s *connSock, b []byte) error {
- cs := u.socks
- s.parkMu.Lock()
- defer s.parkMu.Unlock()
- left := cs.cfg.enobufsMax - s.stalledFor(cs.clock())
- if left <= 0 {
- return errStallSpent
- }
- if err := s.conn.SetWriteDeadline(time.Now().Add(left)); err != nil {
- return net.ErrClosed
- }
- err := s.try(b, true)
- _ = s.conn.SetWriteDeadline(time.Time{})
- if err == os.ErrDeadlineExceeded {
- return errStallSpent
- }
- return err
- }
- // dropStalled logs the first datagram a stall drops.
- func (u *StdConn) dropStalled(s *connSock) {
- if !s.dropping.Swap(true) {
- u.l.Debug("connected socket: sends stalled, dropping datagrams until one goes through", "udpAddr", s.dst)
- }
- }
- // stalledFor records an ENOBUFS or EAGAIN on s at now and returns how long its sends have stalled: since the first
- // ENOBUFS or EAGAIN after a send last went through. Every writer measures from that one time, so concurrent writers
- // spend the budget no faster than one, and a send going through ends the stall for every writer, including one
- // asleep in it.
- func (s *connSock) stalledFor(now int64) time.Duration {
- if s.stallAt.CompareAndSwap(0, now) {
- return 0
- }
- at := s.stallAt.Load()
- if at == 0 {
- return 0
- }
- return time.Duration(now - at)
- }
- // readConnSock delivers a connected socket's datagrams through the listener's reader, serialized with ListenOut,
- // until the connected socket is closed. Any receive error closes it: a connected socket reports its peer's
- // unreachables here, and retrying an unexpected error could spin.
- func (u *StdConn) readConnSock(s *connSock) {
- rd := u.reader.Load()
- buf := make([]byte, MTU)
- for {
- n, from, err := s.conn.ReadFromUDPAddrPort(buf)
- if err != nil {
- if errors.Is(err, net.ErrClosed) {
- return
- }
- why := err.Error()
- var errno syscall.Errno
- if errors.As(err, &errno) && connSockDead(errno) {
- why = errno.Error()
- } else {
- u.l.Warn("connected socket: unexpected receive error", "udpAddr", s.dst, "error", err)
- }
- u.closeConnSock(s, why, true)
- return
- }
- s.used.Store(u.socks.clock())
- u.readMu.Lock()
- rd.r(netip.AddrPortFrom(from.Addr().Unmap(), from.Port()), buf[:n:n])
- rd.flush()
- u.readMu.Unlock()
- }
- }
|