// Copyright 2026 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package nettest import ( "context" "errors" "internal/gate" "net" "os" "slices" "sync" "time" ) // A PacketNet is a group of communicating [PacketConn]s. type PacketNet struct { mu sync.Mutex conns map[netAddr]*PacketConn } type netAddr struct { network string addr string } // NewPacketNet returns a new PacketNet. func NewPacketNet() *PacketNet { return &PacketNet{ conns: make(map[netAddr]*PacketConn), } } // NewConn returns a new [PacketConn] listening on the given address. // It returns an error if there is an existing listener on this address. func (n *PacketNet) NewConn(a net.Addr) (*PacketConn, error) { n.mu.Lock() defer n.mu.Unlock() addrKey := netAddr{a.Network(), a.String()} if _, ok := n.conns[addrKey]; ok { return nil, &net.OpError{ Op: "listen", Net: "udp", Addr: a, Err: errors.New("address is in use"), } } p := &PacketConn{ gate: gate.New(false), addr: a, net: n, } n.conns[addrKey] = p return p, nil } type PacketConn struct { gate gate.Gate queue queue[*packet] closed bool readErr error writeErr error closeErr error readDeadline connDeadline net *PacketNet addr net.Addr } type packet struct { b []byte src net.Addr } // ReadFrom reads a packet from the connection, copying the payload into b. func (p *PacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) { p.gate.WaitAndLock(context.Background()) defer p.unlock() switch { case p.closed: err = net.ErrClosed case p.readDeadline.expired: err = os.ErrDeadlineExceeded case p.queue.len() == 0 && p.readErr != nil: err = p.readErr } if err != nil { return 0, nil, &net.OpError{ Op: "read", Net: "udp", Addr: p.addr, Err: err, } } pkt := p.queue.pop() n = copy(b, pkt.b) return n, pkt.src, nil } // WriteTo writes a packet with payload b to addr. // addr must be a [*net.UDPAddr]. // // WriteTo appends the packet to the recipient's receive buffer. // If no recipient is listening on addr or if the recipient's // receive buffer is full, the packet is silently discarded. func (p *PacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { p.gate.Lock() switch { case p.closed: err = net.ErrClosed case p.writeErr != nil: err = p.writeErr } p.unlock() if err != nil { return 0, &net.OpError{ Op: "write", Net: "udp", Source: p.addr, Addr: addr, Err: err, } } p.net.mu.Lock() dst := p.net.conns[netAddr{addr.Network(), addr.String()}] p.net.mu.Unlock() if dst == nil { // There is no PacketConn listening on the destination address, // and the packet falls silently into the void. return len(b), nil } dst.lock() if !dst.closed { dst.queue.push(&packet{b: slices.Clone(b), src: p.addr}) } dst.unlock() return len(b), nil } // Close closes the connection. func (p *PacketConn) Close() error { p.net.mu.Lock() delete(p.net.conns, netAddr{p.addr.Network(), p.addr.String()}) p.net.mu.Unlock() p.lock() defer p.unlock() err := p.closeErr p.closed = true p.readErr = net.ErrClosed p.writeErr = net.ErrClosed p.closeErr = net.ErrClosed if err != nil { return &net.OpError{ Op: "close", Net: "udp", Addr: p.addr, Err: err, } } return err } // LocalAddr returns the (fake) local network address. func (p *PacketConn) LocalAddr() net.Addr { p.lock() defer p.unlock() return p.addr } // SetReadDeadline sets the read deadline for the connection. // PacketConns have no write deadline. func (p *PacketConn) SetDeadline(t time.Time) error { return p.SetReadDeadline(t) } // SetReadDeadline sets the read deadline for the connection. func (p *PacketConn) SetReadDeadline(t time.Time) error { p.readDeadline.setDeadline(p, t) return nil } // SetWriteDeadline has no effect. // Writes to PacketConns never block. func (p *PacketConn) SetWriteDeadline(t time.Time) error { return nil } // SetReadError causes any currently blocked and future ReadFrom calls to return // a net.OpError wrapping err. It does not affect the other half of the connection. // Reads will return any buffered data before returning the error, // including data written after the error is set. // A nil error restores the usual behavior. func (c *PacketConn) SetReadError(err error) { c.lock() defer c.unlock() c.readErr = err } // SetWriteError causes any currently blocked and future WriteTo calls to return // a net.OpError wrapping err. It does not affect the other half of the connection. // Writes will not write data while an error is set. // A nil error restores the usual behavior. func (c *PacketConn) SetWriteError(err error) { c.lock() defer c.unlock() c.writeErr = err } // SetCloseError sets the error returned by Close. // Close still closes the connection. // A nil error restores the usual behavior. func (c *PacketConn) SetCloseError(err error) { c.lock() defer c.unlock() c.closeErr = err } // CanRead reports whether [ReadFrom] can return at least one byte or an error. // If [ReadFrom] would block, CanRead returns false. func (p *PacketConn) CanRead() bool { p.lock() defer p.unlock() return p.canReadLocked() } func (p *PacketConn) canReadLocked() bool { return p.queue.len() > 0 || p.readDeadline.expired || p.closed || p.readErr != nil } // IsClosed reports whether the connection has been closed. func (p *PacketConn) IsClosed() bool { p.lock() defer p.unlock() return p.closed } func (p *PacketConn) lock() { p.gate.Lock() } func (p *PacketConn) unlock() { p.gate.Unlock(p.canReadLocked()) }