purego

package
v0.0.0-...-9f1664a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package purego implements the Micro Transport Protocol (µTP, BEP 29) in pure Go.

µTP is a reliable, ordered, stream oriented transport that runs over UDP. What distinguishes it from TCP is its congestion control: LEDBAT measures the one way queuing delay towards the peer and grows the window towards a fixed delay target rather than until something is dropped. A bulk transfer over µTP therefore backs off as soon as it starts filling the buffer in front of the bottleneck, leaving the link responsive for interactive traffic sharing it.

The protocol logic here is a port of BitTorrent's libutp, which is the reference implementation and what the rest of the network is running. The state machine, the LEDBAT controller, the selective acknowledgement and fast resend rules, the retransmission timers and the MTU search all follow it closely, including the constants they're tuned with, so that a connection between this package and any other µTP implementation behaves the way both ends expect. The interop tests in the repository's interop package check exactly that against libutp itself.

Using it

A Socket owns a UDP port and multiplexes connections over it. It implements both net.Listener and net.PacketConn, and the connections it hands out implement net.Conn:

s, err := purego.NewSocket("udp", ":4242")
if err != nil {
	return err
}
defer s.Close()
c, err := s.DialContext(ctx, "", "example.com:4242")

Datagrams arriving on the port that aren't µTP are not dropped: they're delivered through Socket.ReadFrom, so one port can carry µTP connections and another protocol, a DHT say, at the same time. NewSocketFromPacketConn takes that further and runs µTP over a net.PacketConn obtained elsewhere; if that PacketConn doesn't use UDP addresses, pass WithAddrResolver so Dial knows how to turn an address string into one it accepts.

Nothing outside the standard library is imported, logging included: a Socket logs to a log/slog.Logger, its own if WithLogger gave it one and otherwise slog's default. The package builds for every platform Go does.

Differences from libutp

Two of libutp's inputs aren't available portably from Go, and their absence only affects how quickly the MTU search converges, not correctness:

  • ICMP fragmentation-needed and unreachable reports are not fed back in, so a path MTU below what we're sending is discovered from the resulting timeouts and duplicate acks instead of being reported outright.
  • The don't-fragment bit is not set on MTU probes, so an oversized probe is fragmented along the way rather than dropped. The search still runs; it just tends to settle high.

Everything else follows libutp, with four deliberate departures where following it would be worse:

  • A repeated SYN for a connection still in the handshake gets the syn-ack sent again. Nothing else retransmits it, so a single lost syn-ack otherwise strands the accepted connection until the dialer gives up.
  • The sender flushes its queue when acks free up window space, rather than waiting for the application's next write or for the 500ms timer. A sender that queued more than the window and then stopped writing otherwise trickles out at one window per 500ms.
  • A reset arriving on a connection still being dialled is reported as a refused connection. libutp overwrites the state before testing it, so it always reports a reset one.
  • A selective acknowledgement is read one bit short of where libutp reads it, which is one byte past the end of the header.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrConnRefused = errors.New("connection refused")
	ErrConnReset   = errors.New("connection reset by peer")
	ErrTimedOut    = errors.New("uTP connection timed out")
	// Returned by Conn methods after Close. It wraps net.ErrClosed, so callers written against
	// the standard library's conventions recognize it.
	ErrConnClosed = fmt.Errorf("uTP connection closed: %w", net.ErrClosed)
)

Errors reported through Conn's methods. They correspond to libutp's UTP_ECONNREFUSED, UTP_ECONNRESET and UTP_ETIMEDOUT.

View Source
var (

	// ErrSocketClosed is returned by Socket methods after Close.
	ErrSocketClosed = errors.New("socket closed")
)
View Source
var Logger *slog.Logger

Logger is the logger a Socket is given when NewSocket isn't passed WithLogger. When it's nil, which is the default, a Socket uses slog.Default() as it stands when the Socket is created.

Functions

This section is empty.

Types

type AddrResolver

type AddrResolver func(network, addr string) (net.Addr, error)

AddrResolver turns the network and address strings given to Dial into an address the underlying PacketConn will accept.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a uTP connection. It implements net.Conn.

func (*Conn) Close

func (c *Conn) Close() error

Close shuts the connection down. Data already written keeps being retransmitted until the peer acknowledges it or the connection times out.

func (*Conn) CloseWrite

func (c *Conn) CloseWrite() error

CloseWrite sends a FIN, so the peer sees end of file, while leaving this end able to read.

func (*Conn) LocalAddr

func (c *Conn) LocalAddr() net.Addr

func (*Conn) PeerExtensionBits

func (c *Conn) PeerExtensionBits() [8]byte

PeerExtensionBits returns the eight extension bytes the peer sent in its SYN, or zero if it sent none. Nothing in µTP itself is negotiated with them; they're here for protocols layered on top that want to.

func (*Conn) Read

func (c *Conn) Read(b []byte) (n int, err error)

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

func (*Conn) SetReadBufferLen

func (c *Conn) SetReadBufferLen(n int)

SetReadBufferLen sets the receive buffer, which is what the advertised receive window is computed from.

func (*Conn) SetReadDeadline

func (c *Conn) SetReadDeadline(t time.Time) error

func (*Conn) SetTargetDelay

func (c *Conn) SetTargetDelay(d time.Duration)

SetTargetDelay sets the one way queuing delay the congestion controller aims for. Lower means yielding sooner to competing traffic.

func (*Conn) SetWriteBufferLen

func (c *Conn) SetWriteBufferLen(n int)

SetWriteBufferLen sets the send buffer, which also caps the congestion window.

func (*Conn) SetWriteDeadline

func (c *Conn) SetWriteDeadline(t time.Time) error

func (*Conn) String

func (c *Conn) String() string

func (*Conn) Write

func (c *Conn) Write(b []byte) (n int, err error)

type FirewallCallback

type FirewallCallback = func(net.Addr) bool

A firewall callback returns true if an incoming connection should be ignored. This is better than accepting and immediately closing, because the peer sees no response at all rather than an acknowledgement followed by a reset.

It's an alias rather than a defined type so that this Socket satisfies interfaces declaring the callback in their own terms, such as github.com/anacrolix/go-utp.Socket, without a wrapper to convert between two identical function types.

type NewSocketOpt

type NewSocketOpt func(s *Socket)

NewSocketOpt configures a Socket at construction.

func WithAddrResolver

func WithAddrResolver(r AddrResolver) NewSocketOpt

WithAddrResolver sets how the address strings passed to Dial are resolved. The default is net.ResolveUDPAddr, which is what a Socket over a UDP PacketConn wants. Pass this when the PacketConn given to NewSocketFromPacketConn uses addresses of some other type.

func WithBufferSizes

func WithBufferSizes(send, receive int) NewSocketOpt

WithBufferSizes sets the initial send and receive buffer sizes for connections on this Socket. The send buffer caps the congestion window; the receive buffer is what the advertised receive window is computed from.

func WithLogger

func WithLogger(l *slog.Logger) NewSocketOpt

WithLogger gives a Socket its own logger instead of the package level Logger.

func WithTargetDelay

func WithTargetDelay(d time.Duration) NewSocketOpt

WithTargetDelay sets the one way queuing delay the congestion controller aims for on connections from this Socket. The default is 100ms, as in libutp.

type Socket

type Socket struct {
	// contains filtered or unexported fields
}

Socket multiplexes uTP connections over a net.PacketConn. It implements net.Listener and net.PacketConn: uTP traffic is dispatched to connections, and everything else is handed to ReadFrom, so a single port can carry uTP and another protocol at once.

func NewSocket

func NewSocket(network, addr string, opts ...NewSocketOpt) (*Socket, error)

NewSocket listens on the given network and address and returns a Socket carrying uTP over it.

func NewSocketFromPacketConn

func NewSocketFromPacketConn(pc net.PacketConn, opts ...NewSocketOpt) (*Socket, error)

NewSocketFromPacketConn runs uTP over a net.PacketConn you already have. The Socket takes ownership of it: closing the Socket closes the PacketConn.

func (*Socket) Accept

func (s *Socket) Accept() (net.Conn, error)

Accept returns the next incoming connection. It implements net.Listener.

func (*Socket) Addr

func (s *Socket) Addr() net.Addr

Addr returns the local address. It implements net.Listener.

func (*Socket) Close

func (s *Socket) Close() error

Close closes the Socket and the PacketConn under it. Connections on it are torn down without waiting for outstanding data to be delivered.

func (*Socket) Dial

func (s *Socket) Dial(addr string) (net.Conn, error)

Dial connects to addr on the Socket's own network.

func (*Socket) DialAddrContext

func (s *Socket) DialAddrContext(ctx context.Context, addr net.Addr) (net.Conn, error)

DialAddrContext connects to an already resolved address.

func (*Socket) DialContext

func (s *Socket) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

DialContext connects to addr. An empty network means the network the Socket is listening on.

func (*Socket) DialTimeout

func (s *Socket) DialTimeout(addr string, timeout time.Duration) (net.Conn, error)

DialTimeout connects to addr, giving up after timeout. A zero timeout means no limit.

func (*Socket) LocalAddr

func (s *Socket) LocalAddr() net.Addr

LocalAddr returns the local address. It implements net.PacketConn.

func (*Socket) ReadBufferLen

func (s *Socket) ReadBufferLen() int

ReadBufferLen is the receive buffer new connections get.

func (*Socket) ReadFrom

func (s *Socket) ReadFrom(b []byte) (n int, addr net.Addr, err error)

ReadFrom returns the next datagram received on this port that wasn't uTP. It implements net.PacketConn. Datagrams are dropped if they aren't read promptly.

func (*Socket) SetDeadline

func (s *Socket) SetDeadline(t time.Time) error

SetDeadline sets both ReadFrom and WriteTo deadlines.

func (*Socket) SetFirewallCallback

func (s *Socket) SetFirewallCallback(f FirewallCallback)

SetFirewallCallback sets a function consulted before each incoming connection is accepted. It is called with the Socket's lock held, so it must not call back into this package or block.

func (*Socket) SetLogging

func (s *Socket) SetLogging(normal, mtu, debug bool)

SetLogging enables or disables the log categories libutp distinguishes: normal covers the connection lifecycle and packet loss, mtu the path MTU search, and debug every packet. All are off by default. They're logged at debug level, so the Socket's logger has to be passing debug records for them to appear.

func (*Socket) SetReadBufferLen

func (s *Socket) SetReadBufferLen(n int)

SetReadBufferLen sets the receive buffer new connections get.

func (*Socket) SetReadDeadline

func (s *Socket) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for ReadFrom. It has no effect on connections.

func (*Socket) SetWriteBufferLen

func (s *Socket) SetWriteBufferLen(n int)

SetWriteBufferLen sets the send buffer new connections get.

func (*Socket) SetWriteDeadline

func (s *Socket) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for WriteTo. It has no effect on connections.

func (*Socket) WriteBufferLen

func (s *Socket) WriteBufferLen() int

WriteBufferLen is the send buffer new connections get.

func (*Socket) WriteTo

func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error)

WriteTo sends a datagram on the underlying PacketConn, bypassing uTP entirely. It implements net.PacketConn.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL