From fd94eab2c40737ac2cf598db07654df32261b850 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Wed, 22 Apr 2026 22:05:43 -0500 Subject: [PATCH 1/2] add support for net.ListenUDP Co-authored-by: Copilot --- dial.go | 14 ++++++++++++++ udpsock.go | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/dial.go b/dial.go index 876a1d8..ace604d 100644 --- a/dial.go +++ b/dial.go @@ -281,3 +281,17 @@ func Listen(network, address string) (Listener, error) { return listenTCP(laddr) } + +func ListenUDP(network string, laddr *UDPAddr) (*UDPConn, error) { + switch network { + case "udp", "udp4": + default: + return nil, fmt.Errorf("Network %s not supported", network) + } + + if laddr == nil { + laddr = &UDPAddr{} + } + + return listenUDP(laddr) +} diff --git a/udpsock.go b/udpsock.go index ef683b7..858c529 100644 --- a/udpsock.go +++ b/udpsock.go @@ -303,3 +303,22 @@ func (c *UDPConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t return nil } + +func listenUDP(laddr *UDPAddr) (*UDPConn, error) { + fd, err := netdev.Socket(_AF_INET, _SOCK_DGRAM, _IPPROTO_UDP) + + if err != nil { + return nil, err + } + + laddrport := laddr.AddrPort() + err = netdev.Bind(fd, laddrport) + if err != nil { + return nil, err + } + + return &UDPConn{ + fd: fd, + laddr: laddr, + }, nil +} From 937775a6a17b5bba3b0f4dd10ccd9201e150147e Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 9 May 2026 09:50:40 -0500 Subject: [PATCH 2/2] fill out more UDP methods from net package --- netdev.go | 8 +++++ udpsock.go | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/netdev.go b/netdev.go index c228f20..2fe341b 100644 --- a/netdev.go +++ b/netdev.go @@ -115,7 +115,9 @@ type netdever interface { // - MSG_WAITALL: This flag requests that the operation block until the full request is satisfied. Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) + SendTo(sockfd int, buf []byte, flags int, deadline time.Time, addr netip.AddrPort) (int, error) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) + RecvFrom(sockfd int, buf []byte, flags int, deadline time.Time) (int, netip.AddrPort, error) Close(sockfd int) error // SetSockOpt manipulates options for the socket @@ -166,9 +168,15 @@ func (n *nopNetdev) Accept(sockfd int) (int, netip.AddrPort, error) { func (n *nopNetdev) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { return -1, ErrNetdevNotSet } +func (n *nopNetdev) SendTo(sockfd int, buf []byte, flags int, deadline time.Time, addr netip.AddrPort) (int, error) { + return -1, ErrNetdevNotSet +} func (n *nopNetdev) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { return -1, ErrNetdevNotSet } +func (n *nopNetdev) RecvFrom(sockfd int, buf []byte, flags int, deadline time.Time) (int, netip.AddrPort, error) { + return -1, netip.AddrPort{}, ErrNetdevNotSet +} func (n *nopNetdev) Close(sockfd int) error { return ErrNetdevNotSet } func (n *nopNetdev) SetSockOpt(sockfd int, level int, opt int, value interface{}) error { return ErrNetdevNotSet diff --git a/udpsock.go b/udpsock.go index 858c529..25fe6c7 100644 --- a/udpsock.go +++ b/udpsock.go @@ -242,9 +242,43 @@ func (c *UDPConn) Write(b []byte) (int, error) { return n, err } +// ReadFromUDP acts like [UDPConn.ReadFrom] but returns a UDPAddr. +func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) { + // This function is designed to allow the caller to control the lifetime + // of the returned *UDPAddr and thereby prevent an allocation. + // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/. + // The real work is done by readFromUDP, below. + return c.readFromUDP(b, &UDPAddr{}) +} + +// readFromUDP implements ReadFromUDP. +func (c *UDPConn) readFromUDP(b []byte, addr *UDPAddr) (int, *UDPAddr, error) { + n, addr, err := c.readFrom(b, addr) + if err != nil { + err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } + return n, addr, err +} + // ReadFrom implements the PacketConn ReadFrom method. func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) { - return 0, nil, errors.New("ReadFrom not implemented") + n, addr, err := c.readFromUDP(b, &UDPAddr{}) + if addr == nil { + // Return Addr(nil), not Addr(*UDPConn(nil)). + return n, nil, err + } + return n, addr, err +} + +func (c *UDPConn) readFrom(b []byte, addr *UDPAddr) (int, *UDPAddr, error) { + n, raddr, err := netdev.RecvFrom(c.fd, b, 0, c.readDeadline) + if n < 0 { + n = 0 + } + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.net, Source: c.laddr, Addr: c.raddr, Err: err} + } + return n, UDPAddrFromAddrPort(raddr), err } // ReadMsgUDP reads a message from c, copying the payload into b and @@ -259,9 +293,50 @@ func (c *UDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, return } -// WriteTo implements the PacketConn WriteTo method. +// WriteToUDP acts like [UDPConn.WriteTo] but takes a [UDPAddr]. +func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error) { + n, err := c.writeTo(b, addr) + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: addr.opAddr(), Err: err} + } + return n, err +} + +// WriteToUDPAddrPort acts like [UDPConn.WriteTo] but takes a [netip.AddrPort]. +func (c *UDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) { + n, err := c.writeToAddrPort(b, addr) + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: UDPAddrFromAddrPort(addr), Err: err} + } + return n, err +} + +// WriteTo implements the [PacketConn] WriteTo method. func (c *UDPConn) WriteTo(b []byte, addr Addr) (int, error) { - return 0, errors.New("WriteTo not implemented") + a, ok := addr.(*UDPAddr) + if !ok { + return 0, &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: addr, Err: syscall.EINVAL} + } + n, err := c.writeTo(b, a) + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: a.opAddr(), Err: err} + } + return n, err +} + +func (c *UDPConn) writeTo(b []byte, addr *UDPAddr) (int, error) { + return c.writeToAddrPort(b, addr.AddrPort()) +} + +func (c *UDPConn) writeToAddrPort(b []byte, addr netip.AddrPort) (int, error) { + n, err := netdev.SendTo(c.fd, b, 0, c.writeDeadline, addr) + if n < 0 { + n = 0 + } + if err != nil { + err = &OpError{Op: "write", Net: c.net, Source: c.laddr, Addr: UDPAddrFromAddrPort(addr), Err: err} + } + return n, err } // WriteMsgUDP writes a message to addr via c if c isn't connected, or @@ -276,6 +351,11 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er return 0, 0, errors.New("WriteMsgUDP not implemented") } +// WriteMsgUDPAddrPort is like [UDPConn.WriteMsgUDP] but takes a [netip.AddrPort] instead of a [UDPAddr]. +func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error) { + return 0, 0, errors.New("WriteMsgUDPAddrPort not implemented") +} + func (c *UDPConn) Close() error { return netdev.Close(c.fd) }