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 ¶
- Variables
- type AddrResolver
- type Conn
- func (c *Conn) Close() error
- func (c *Conn) CloseWrite() error
- func (c *Conn) LocalAddr() net.Addr
- func (c *Conn) PeerExtensionBits() [8]byte
- func (c *Conn) Read(b []byte) (n int, err error)
- func (c *Conn) RemoteAddr() net.Addr
- func (c *Conn) SetDeadline(t time.Time) error
- func (c *Conn) SetReadBufferLen(n int)
- func (c *Conn) SetReadDeadline(t time.Time) error
- func (c *Conn) SetTargetDelay(d time.Duration)
- func (c *Conn) SetWriteBufferLen(n int)
- func (c *Conn) SetWriteDeadline(t time.Time) error
- func (c *Conn) String() string
- func (c *Conn) Write(b []byte) (n int, err error)
- type FirewallCallback
- type NewSocketOpt
- type Socket
- func (s *Socket) Accept() (net.Conn, error)
- func (s *Socket) Addr() net.Addr
- func (s *Socket) Close() error
- func (s *Socket) Dial(addr string) (net.Conn, error)
- func (s *Socket) DialAddrContext(ctx context.Context, addr net.Addr) (net.Conn, error)
- func (s *Socket) DialContext(ctx context.Context, network, addr string) (net.Conn, error)
- func (s *Socket) DialTimeout(addr string, timeout time.Duration) (net.Conn, error)
- func (s *Socket) LocalAddr() net.Addr
- func (s *Socket) ReadBufferLen() int
- func (s *Socket) ReadFrom(b []byte) (n int, addr net.Addr, err error)
- func (s *Socket) SetDeadline(t time.Time) error
- func (s *Socket) SetFirewallCallback(f FirewallCallback)
- func (s *Socket) SetLogging(normal, mtu, debug bool)
- func (s *Socket) SetReadBufferLen(n int)
- func (s *Socket) SetReadDeadline(t time.Time) error
- func (s *Socket) SetWriteBufferLen(n int)
- func (s *Socket) SetWriteDeadline(t time.Time) error
- func (s *Socket) WriteBufferLen() int
- func (s *Socket) WriteTo(b []byte, addr net.Addr) (int, error)
Constants ¶
This section is empty.
Variables ¶
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.
var ( // ErrSocketClosed is returned by Socket methods after Close. ErrSocketClosed = errors.New("socket closed") )
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 ¶
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 ¶
Close shuts the connection down. Data already written keeps being retransmitted until the peer acknowledges it or the connection times out.
func (*Conn) CloseWrite ¶
CloseWrite sends a FIN, so the peer sees end of file, while leaving this end able to read.
func (*Conn) PeerExtensionBits ¶
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) RemoteAddr ¶
func (*Conn) SetReadBufferLen ¶
SetReadBufferLen sets the receive buffer, which is what the advertised receive window is computed from.
func (*Conn) SetTargetDelay ¶
SetTargetDelay sets the one way queuing delay the congestion controller aims for. Lower means yielding sooner to competing traffic.
func (*Conn) SetWriteBufferLen ¶
SetWriteBufferLen sets the send buffer, which also caps the congestion window.
type FirewallCallback ¶
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) Close ¶
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) DialAddrContext ¶
DialAddrContext connects to an already resolved address.
func (*Socket) DialContext ¶
DialContext connects to addr. An empty network means the network the Socket is listening on.
func (*Socket) DialTimeout ¶
DialTimeout connects to addr, giving up after timeout. A zero timeout means no limit.
func (*Socket) ReadBufferLen ¶
ReadBufferLen is the receive buffer new connections get.
func (*Socket) ReadFrom ¶
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 ¶
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 ¶
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 ¶
SetReadBufferLen sets the receive buffer new connections get.
func (*Socket) SetReadDeadline ¶
SetReadDeadline sets the deadline for ReadFrom. It has no effect on connections.
func (*Socket) SetWriteBufferLen ¶
SetWriteBufferLen sets the send buffer new connections get.
func (*Socket) SetWriteDeadline ¶
SetWriteDeadline sets the deadline for WriteTo. It has no effect on connections.
func (*Socket) WriteBufferLen ¶
WriteBufferLen is the send buffer new connections get.