utp

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 18 Imported by: 0

README

utp

A pure-Go implementation of the Micro Transport Protocol (uTP, BEP 29): a reliable, ordered byte stream over UDP with delay-based congestion control, as used by BitTorrent. It needs no cgo and builds with CGO_ENABLED=0.

go get github.com/arxodeus/utp

Packages

Package Purpose
utp The protocol: utp.Socket and utp.Stream, with context-aware reads and writes.
utpnet The same, as net.Conn and net.PacketConn, with non-uTP datagrams on the port passed through to ReadFrom.
compat/golibutp A drop-in replacement for github.com/anacrolix/go-libutp, as a separate module.

Usage

sock, err := utpnet.Listen(ctx, "udp", ":6881", nil)
if err != nil {
	return err
}
defer sock.Close()

// Accept incoming connections.
go func() {
	for {
		conn, err := sock.Accept()
		if err != nil {
			return
		}
		go handle(conn)
	}
}()

// Dial out from the same port.
conn, err := sock.DialContext(ctx, "udp", "203.0.113.7:6881")

sock is also a net.PacketConn: datagrams that are not uTP, such as DHT traffic sharing the port, are returned by ReadFrom, and WriteTo sends on the same port.

Features

  • Classic LEDBAT congestion control (RFC 6817) by default. LEDBAT++ and RACK loss detection (RFC 8985) are available per connection as experimental options.
  • Selective acknowledgements, fast retransmission, a tail loss probe, and undoing of window reductions caused by reordering.
  • Path MTU discovery, with don't-fragment probes where the platform supports it and optional ICMP input.
  • Half-close in both directions (CloseWrite, CloseRead).
  • Idle connections release their goroutine and buffers.
  • Connection limits, a firewall callback for refusing peers before any state is created, and per-connection metrics.
  • Batched socket reads (recvmmsg on Linux) and one acknowledgement per batch.
  • Structured logging through log/slog.

Compatibility

The library speaks the uTP wire protocol of BEP 29. Every change is tested for interoperability with BitTorrent's own uTP implementation and with the pure-Go one in anacrolix/go-utp, and against anacrolix/torrent. The experimental options change only how this side paces and detects loss; they need nothing from the peer.

Status and limitations

This is a 0.x release: the API may still change before 1.0.

  • All measurements so far are on a single machine, over loopback and an emulated network. Nothing has yet been measured over wide-area paths.
  • LEDBAT++ is experimental. A LEDBAT++ flow that joins a link where another LEDBAT++ flow is already running takes about 70% of the link while both run, rather than an even share.
  • RACK loss detection is experimental.
  • Classic LEDBAT does not yield fully to loss-based TCP traffic: it keeps about 69% of a shared bottleneck, by design of the protocol.
  • When a connection's event queue is full, further packets for it are dropped and counted in Socket.PacketsDroppedFullConnQueue.
  • The full test suite, the race detector and fuzzing run on Linux only; macOS and Windows run the short suite.
  • The Go version in go.mod is the minimum, not a recommendation: older standard libraries have known vulnerabilities this code reaches. Build with a current Go release.

Testing

go test ./...          # full suite
go test -short ./...   # quick run
go test -race ./...

None of these need cgo. The tests against other uTP implementations are in their own modules under internal/integration; the one against BitTorrent's C implementation needs cgo.

Origin

This project began as a fork of zen-eth/utp-go. See NOTICE.

License

MIT. See LICENSE and NOTICE.

Documentation

Overview

Package utp implements the Micro Transport Protocol (uTP, BEP 29), a reliable, ordered byte stream over UDP with LEDBAT delay-based congestion control, in pure Go.

A Socket runs on one UDP socket (Bind) or any Conn (NewSocket) and carries any number of connections. Connect dials a peer and Accept waits for an incoming connection; both return a Stream, whose Read and Write take a context that governs that call:

sock, err := utp.Bind(ctx, "udp", laddr, nil)
if err != nil {
	return err
}
defer sock.Close()

stream, err := sock.Connect(ctx, utp.NewUDPPeer(raddr), utp.NewConnectionConfig())
if err != nil {
	return err
}
defer stream.Close()
_, err = stream.Write(ctx, []byte("hello"))

For net.Conn and net.PacketConn, including sharing the UDP port with another protocol such as a DHT, use the utpnet package.

Congestion control

Classic LEDBAT (RFC 6817) is the default. LEDBAT++ and RACK loss detection (RFC 8985) can be selected per connection in ConnectionConfig; both are experimental. See AlgorithmLEDBATPP for LEDBAT++'s known limitation.

Path MTU

Each connection searches for the largest datagram its path carries, starting from ConnectionConfig.MaxPacketSize. Probes are sent with fragmentation disabled where the platform supports it, and ICMP reports can be fed in with Socket.ProcessICMPFragmentation and ProcessICMPError.

Logging

Bind and NewSocket take a *slog.Logger; nil discards logging. Per-packet events are logged at LevelTrace.

Example
package main

import (
	"context"
	"fmt"
	"log"
	"net"

	"github.com/arxodeus/utp"
)

func main() {
	ctx := context.Background()
	loopback := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}

	server, err := utp.Bind(ctx, "udp", loopback, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer server.Close()
	client, err := utp.Bind(ctx, "udp", loopback, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	accepted := make(chan *utp.Stream, 1)
	go func() {
		stream, err := server.Accept(ctx, nil)
		if err != nil {
			log.Fatal(err)
		}
		accepted <- stream
	}()

	peer := utp.NewUDPPeer(server.LocalAddr().(*net.UDPAddr))
	stream, err := client.Connect(ctx, peer, nil)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := stream.Write(ctx, []byte("hello")); err != nil {
		log.Fatal(err)
	}
	stream.Close()

	var got []byte
	in := <-accepted
	if _, err := in.ReadToEOF(ctx, &got); err != nil {
		log.Fatal(err)
	}
	in.Close()
	fmt.Println(string(got))
}
Output:
hello

Index

Examples

Constants

View Source
const DefaultBufferSize = 1024 * 1024

DefaultBufferSize is the default size of each connection's send and receive buffers, in bytes.

View Source
const DefaultMaxConnections = 3000

DefaultMaxConnections is the default limit on connections a socket holds before it refuses new incoming ones.

View Source
const DefaultMaxIdleTimeout = time.Duration(0)

DefaultMaxIdleTimeout is zero: by default a connection is never closed for silence. See ConnectionConfig.MaxIdleTimeout.

View Source
const DefaultMetricsInterval = 10 * time.Millisecond

DefaultMetricsInterval is how often metrics are sampled when an observer is set but no interval is given.

View Source
const DefaultSocketBufferSize = 4 * 1024 * 1024

DefaultSocketBufferSize is the size requested for the underlying UDP socket's send and receive buffers. A single goroutine drains the socket, and datagrams the kernel drops before it does are loss uTP must recover from; the operating system's default is too small for a few hundred concurrent connections. The kernel clamps the request to its configured maximum.

View Source
const DefaultWindowSize = 1024 * 1024

DefaultWindowSize is the default upper bound on the congestion window, in bytes.

View Source
const LevelTrace = slog.Level(-8)

LevelTrace is the level of the package's per-packet logging, one step below slog.LevelDebug. A handler enabled for it sees every packet sent and received.

Variables

View Source
var (
	// ErrReset means the peer reset the connection.
	ErrReset = errors.New("utp: connection reset by peer")
	// ErrTimedOut means the connection was abandoned after repeated
	// retransmission timeouts, the handshake was not answered, or the idle
	// timeout expired.
	ErrTimedOut = errors.New("utp: connection timed out")
	// ErrConnRefused means an ICMP error arrived for a connection that had
	// only sent its SYN, so nothing is listening. Otherwise an ICMP error,
	// like a received ST_RESET, produces ErrReset.
	ErrConnRefused = errors.New("utp: connection refused")
)
View Source
var (
	// ErrNotConnected is returned by Write after CloseWrite or Close, and
	// for a connection that ended without an error of its own.
	ErrNotConnected = errors.New("utp: not connected")
	// ErrReadClosed is returned by Read after CloseRead. The connection is
	// still alive and may still send; this end has stopped taking delivery.
	ErrReadClosed = errors.New("utp: read side closed")
	// ErrSocketClosed is reported by a connection whose socket has been
	// closed. It wraps net.ErrClosed.
	ErrSocketClosed = fmt.Errorf("utp: socket closed: %w", net.ErrClosed)
)
View Source
var (
	// ErrAcceptTimedOut is returned by AcceptWithCID when no matching SYN
	// arrives within 20 seconds.
	ErrAcceptTimedOut = errors.New("utp: timed out waiting for incoming connection")
)
View Source
var ErrDontFragmentUnsupported = errors.New("utp: per-packet don't-fragment is not supported on this platform")

ErrDontFragmentUnsupported is returned by WriteToDontFragment when fragmentation cannot be disabled for a single datagram on this platform. Nothing has been sent.

Functions

func IsPacket

func IsPacket(b []byte) bool

IsPacket reports whether b looks like a uTP packet, as far as its header can tell: its length, packet type, version and first extension type. A datagram it accepts is one the socket can parse, and one it rejects is one the socket would drop. utpnet uses it to tell uTP from other protocols sharing the port.

Types

type CongestionAlgorithm

type CongestionAlgorithm int

CongestionAlgorithm selects which congestion controller a connection runs.

const (
	// AlgorithmLEDBAT is classic LEDBAT (RFC 6817), the default.
	AlgorithmLEDBAT CongestionAlgorithm = iota
	// AlgorithmLEDBATPP is LEDBAT++. Experimental: a LEDBAT++ flow that
	// joins a link another LEDBAT++ flow already fills takes about 70% of
	// it while both run, rather than an even share.
	AlgorithmLEDBATPP
)

func (CongestionAlgorithm) String

func (a CongestionAlgorithm) String() string

String returns the algorithm's name.

type Conn

type Conn interface {
	// ReadFrom reads one datagram into b and returns its length and sender.
	ReadFrom(b []byte) (int, ConnectionPeer, error)
	// WriteTo sends b as one datagram to dst.
	WriteTo(b []byte, dst ConnectionPeer) (int, error)
	// Close closes the transport, ending a ReadFrom in progress.
	Close() error
}

Conn is the datagram transport a Socket runs over.

WriteTo must behave like a UDP socket's: it may wait for the local send path, but never for the peer to read. The socket writes from the goroutine that reads, so a WriteTo that waited for the other side to read could deadlock two sockets. A transport that cannot keep up drops, as UDP does. WriteTo is called concurrently.

WriteTo must not retain b after it returns: the socket reuses the buffer. net.PacketConn already works this way; a transport that queues datagrams must copy them.

A Conn may also have the methods

HoldPeer(peer ConnectionPeer)
ReleasePeer(peer ConnectionPeer)

The socket then calls HoldPeer when it starts a connection with peer, as it sends or receives a SYN, and ReleasePeer when it forgets that connection. A transport that keeps per-peer state, such as the original address to reply to, keeps it while held: from the first handshake packet to the last packet of the close. Each HoldPeer is matched by one ReleasePeer. They are called with the socket's connection table locked and must not call back into the socket.

type ConnectionConfig

type ConnectionConfig struct {
	// MaxPacketSize is the largest datagram the connection sends, in bytes:
	// the ceiling of its path-MTU search.
	MaxPacketSize uint16
	// MaxConnAttempts is how many times the SYN is sent before a dial gives
	// up with ErrTimedOut.
	MaxConnAttempts int
	// MaxIdleTimeout closes the connection with ErrTimedOut once nothing has
	// been received and nothing written for this long. Zero, the default,
	// disables it: a connection whose peer vanished while nothing was in
	// flight stays open until the application closes it, and one
	// interrupted by an outage resumes when the path returns. A live peer
	// sends a keep-alive every 29 seconds, so a value well above that never
	// closes a live connection.
	MaxIdleTimeout time.Duration
	// InitialTimeout is the retransmission timeout before the first
	// round-trip sample, and the SYN's first timeout.
	InitialTimeout time.Duration
	// MinTimeout is the floor of the retransmission timeout.
	MinTimeout time.Duration
	// MaxTimeout caps the retransmission timeout and its backoff. Zero, the
	// default, means no cap.
	MaxTimeout time.Duration
	// TargetDelay is the queuing delay classic LEDBAT aims for.
	TargetDelay time.Duration

	// DelayWindow is how far back the congestion controller looks for its
	// base delay: the lowest one-way delay seen, taken as the delay of an
	// empty queue. The window is kept in thirteen buckets; the default is
	// thirteen minutes. See baseDelayHist.
	//
	// The window bounds the error clock drift causes: a sender whose clock
	// runs slow sees its measured delay grow until inflated samples age out,
	// so the steady-state error is the window times the drift rate.
	DelayWindow time.Duration
	// WindowSize is the upper bound on the congestion window, in bytes.
	WindowSize uint32
	// BufferSize is the size of the send and receive buffers, in bytes. The
	// receive window advertised to the peer is at most this.
	BufferSize int

	// Metrics, when set, receives periodic snapshots of this connection.
	// See MetricsObserver for the constraints on the callback.
	Metrics MetricsObserver
	// MetricsInterval is the minimum gap between snapshots. Defaults to
	// DefaultMetricsInterval.
	MetricsInterval time.Duration
	// KeepAliveInterval is how long an established connection may stay
	// silent before sending a keep-alive. The default of 29 seconds is below
	// the 30-second UDP mapping timeout common in NATs.
	KeepAliveInterval time.Duration
	// ZeroWindowProbeInterval is how long a zero receive window from the
	// peer is tolerated before one packet is sent to elicit a window update.
	// The peer's update when it reopens the window is a single packet; if it
	// is lost, the sender would otherwise have nothing outstanding and no
	// reason to transmit. The default is 15 seconds.
	ZeroWindowProbeInterval time.Duration
	// CongestionAlgorithm selects the congestion controller. The zero value
	// is AlgorithmLEDBAT; AlgorithmLEDBATPP selects LEDBAT++, which is
	// experimental.
	CongestionAlgorithm CongestionAlgorithm
	// RACK replaces the default loss detection, under which a packet is lost
	// once three packets sent after it are acknowledged, with the
	// time-based rule of RFC 8985. Off by default. Experimental.
	RACK bool
	// NoDelay sends a partial packet as soon as the window allows, like
	// TCP_NODELAY. By default a packet smaller than the maximum waits while
	// anything else is unacknowledged, and later writes are appended to it
	// until it is full, everything before it is acknowledged, or the write
	// side closes. NoDelay lowers latency for small writes at the cost of
	// more, smaller packets.
	NoDelay bool
	// contains filtered or unexported fields
}

ConnectionConfig configures a connection. Start from NewConnectionConfig, which sets the defaults, and change what is needed. A config may be shared by several connections, but must not be modified once in use.

func NewConnectionConfig

func NewConnectionConfig() *ConnectionConfig

NewConnectionConfig returns a config with the default settings.

type ConnectionID

type ConnectionID struct {
	// Send is the id this end sends with: the peer's receive id.
	Send uint16
	// Recv is the id this end receives on.
	Recv uint16
	// Peer is the remote end.
	Peer ConnectionPeer
	// contains filtered or unexported fields
}

ConnectionID identifies a connection by its send and receive ids and its peer.

func NewConnectionID

func NewConnectionID(peer ConnectionPeer, recvID uint16, sendID uint16) *ConnectionID

NewConnectionID returns the connection id for peer with the given receive and send ids.

func (*ConnectionID) Hash

func (id *ConnectionID) Hash() string

Hash returns the key the connection is tracked under. NewConnectionID caches it; for a ConnectionID built as a struct literal it is computed on each call.

func (*ConnectionID) String

func (id *ConnectionID) String() string

String returns a description of the id for logging.

type ConnectionMetrics

type ConnectionMetrics struct {
	// At is when the snapshot was taken.
	At time.Time
	// CID identifies the connection.
	CID *ConnectionID

	// CwndBytes is the congestion window: what the controller believes the
	// path will carry.
	CwndBytes uint32
	// InFlightBytes is the volume the controller considers outstanding.
	InFlightBytes uint32
	// MinCwndBytes is the floor the window will not drop below. A controller
	// pinned here has collapsed.
	MinCwndBytes uint32
	// PeerRecvWindow is the peer's advertised receive window. When this is
	// smaller than CwndBytes, flow control -- not congestion control -- is
	// what limits sending.
	PeerRecvWindow uint32
	// RTT is the smoothed round-trip estimate, in whole milliseconds.
	RTT time.Duration
	// FineRTT is the same estimate kept in microseconds, for measuring a
	// queue a millisecond or two deep.
	FineRTT time.Duration
	// RTTVarianceMicros is the RTT variance estimate, in microseconds.
	RTTVarianceMicros int64
	// Timeout is the current retransmission timeout.
	Timeout time.Duration
	// BaseDelay is the lowest one-way delay seen in the delay window:
	// LEDBAT's estimate of the path with an empty queue. CurrentDelay is the
	// newest sample of the same series.
	//
	// Both measure the outbound path, as reported by the peer in the
	// timestamp_difference field; that is the direction LEDBAT controls.
	// PeerTimestampDiff measures the inbound path.
	BaseDelay    time.Duration
	CurrentDelay time.Duration
	// ClockSkewCorrection is how much has been added to BaseDelay to cancel
	// clock drift between the two ends, detected from the peer's own base
	// delay falling. Zero on a pair of clocks running at the same rate.
	ClockSkewCorrection time.Duration

	// ClockDrift is the estimated slope of the delay the peer reports for
	// our packets, in microseconds per five-second slot, smoothed 7:1
	// towards its history. A negative value means the reported delay is
	// falling, as when the peer's clock runs slow; below -200000 it incurs
	// a penalty (ClockDriftPenalty).
	//
	// ClockSkewCorrection corrects for ordinary drift between honest
	// clocks; ClockDrift detects drift far outside what hardware produces.
	ClockDrift int64

	// ClockDriftPenalty is how much is currently added to the measured
	// queuing delay because of ClockDrift. It is zero on any ordinary path.
	ClockDriftPenalty time.Duration

	// MtuCurrent is the datagram size the path-MTU search is currently
	// sending, and MtuFloor and MtuCeiling bound the sizes it has yet to
	// test. A converged search has all three equal.
	MtuCurrent uint32
	MtuFloor   uint32
	MtuCeiling uint32

	// MtuProbesLostToDuplicateAcks counts probes that duplicate
	// acknowledgements showed were too large for the path.
	MtuProbesLostToDuplicateAcks uint64

	// WindowReopenedAcks counts acknowledgements sent because delivering
	// data to the application opened a receive window larger than the one
	// last advertised. It is zero while the reader keeps up, since the
	// acknowledgement for incoming data already carries a current window.
	WindowReopenedAcks uint64

	// PeerTimestampDiff is the one-way delay measured from the peer's timestamps:
	// how long the peer's last packet took to arrive. It is echoed back in
	// timestamp_difference_microseconds. It measures the inbound path,
	// whereas BaseDelay and CurrentDelay measure the outbound one.
	PeerTimestampDiff time.Duration
	// TargetDelayMicros is the standing queue the controller aims for.
	TargetDelayMicros uint32
	// SlowStart reports whether the controller is still in slow start.
	SlowStart bool
	// AppLimitedSince is how long since the sender last had data to send
	// and no window to send it in. After a second the controller stops
	// growing the window, since an unfilled window measures nothing. Zero
	// means the window has never been filled.
	AppLimitedSince time.Duration

	// PacketsSent and BytesSent count every transmission, retransmissions
	// included.
	PacketsSent uint64
	BytesSent   uint64
	// PacketsRetransmitted and BytesRetransmitted count only retransmissions.
	// The ratio to PacketsSent is the retransmit rate.
	PacketsRetransmitted uint64
	BytesRetransmitted   uint64
	// PacketsReceived and BytesReceived count packets accepted from the peer.
	PacketsReceived uint64
	BytesReceived   uint64
	// Timeouts counts retransmission-timeout events.
	Timeouts uint64
	// FastRetransmits counts packets resent because they were declared lost
	// by duplicate acks rather than by a timeout.
	FastRetransmits uint64
	// LossProbes counts packets resent by the loss probe because nothing had
	// been acknowledged for a probe timeout, shorter than the retransmission
	// timeout.
	LossProbes uint64
	// RackLosses counts packets declared lost by RACK loss detection, and
	// RackRelosts those that were already retransmissions. Both are zero
	// with RACK off.
	RackLosses  uint64
	RackRelosts uint64

	// SendBufferPending is unsent application data still buffered.
	SendBufferPending int
	// RecvBufferPending is received data not yet read by the application,
	// contiguous or held behind a gap.
	RecvBufferPending int
	// RecvBufferReadable is the contiguous part of RecvBufferPending, which
	// can be delivered now. The difference is data held behind a gap; a
	// large difference indicates a receiver waiting on a missing packet
	// rather than a slow reader.
	RecvBufferReadable int
	// RecvBufferDrops counts data packets refused because the receive
	// buffer had no room. Each is a gap the peer must fill by
	// retransmission.
	RecvBufferDrops uint64
	// PendingWrites is the number of queued write requests.
	PendingWrites int

	// State is the connection's state: "connecting", "connected" or "closed".
	State string
}

ConnectionMetrics is a snapshot of one connection's state.

func (ConnectionMetrics) QueueingDelay

func (m ConnectionMetrics) QueueingDelay() time.Duration

QueueingDelay estimates the standing queue on the outbound path: the current one-way delay minus the lowest seen. This is the quantity LEDBAT bounds. It returns zero until BaseDelay has been established.

func (ConnectionMetrics) RetransmitRate

func (m ConnectionMetrics) RetransmitRate() float64

RetransmitRate is the fraction of transmissions that were retransmissions.

type ConnectionPeer

type ConnectionPeer interface {
	// Hash returns the peer's key, conventionally its address as text.
	Hash() string
}

ConnectionPeer identifies the remote end of a connection. Hash must return a stable string that distinguishes peers.

type DontFragmentWriter

type DontFragmentWriter interface {
	WriteToDontFragment(b []byte, dst ConnectionPeer) (int, error)
}

DontFragmentWriter is an optional interface for a Conn that can send a single datagram with fragmentation disabled. The socket uses it for path-MTU probes: a probe too large for the path is then dropped rather than fragmented, so the search learns the size the path actually carries.

A Conn that cannot disable fragmentation at the moment returns ErrDontFragmentUnsupported without sending, and the socket sends the datagram normally.

type MetricsObserver

type MetricsObserver func(ConnectionMetrics)

MetricsObserver receives connection snapshots.

It is called with the connection's lock held, from whichever goroutine is running the connection: its event loop, or the socket's reader. It must not block and must not call into the stream or socket: blocking stalls every connection on the socket, and calling back can deadlock. Copy what is needed and return.

type PathMTUProvider

type PathMTUProvider interface {
	PathMTU(peer ConnectionPeer) (int, bool)
}

PathMTUProvider is an optional interface for a Conn that can report the largest datagram the local path towards a peer carries. The connection starts its path-MTU search from that value.

The value is the largest uTP datagram, the UDP payload, not the link MTU. It only lowers the configured MaxPacketSize, never raises it.

type QueuedReader

type QueuedReader interface {
	Queued() int
}

QueuedReader is an optional interface for a Conn that can report how many datagrams ReadFrom would return without blocking. The socket reads those before acknowledging, so a run of datagrams already waiting draws one acknowledgement rather than one each. A Conn that implements neither this nor batched reads has each datagram treated as its own batch.

Queued may undercount but must not overcount: every datagram it counts must be returned by ReadFrom without blocking.

type Socket

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

Socket is a uTP endpoint: one datagram transport carrying any number of connections. Create one with Bind or NewSocket.

func Bind

func Bind(ctx context.Context, network string, addr *net.UDPAddr, logger *slog.Logger, opts ...SocketOption) (*Socket, error)

Bind opens a UDP socket on addr and runs a uTP socket on it, which owns it and closes it on Close. logger receives the socket's logging; nil discards it. LevelTrace is its per-packet level.

func NewSocket

func NewSocket(ctx context.Context, socket Conn, logger *slog.Logger, opts ...SocketOption) *Socket

NewSocket runs a uTP socket on socket, which remains the caller's. logger receives the socket's logging; nil discards it. LevelTrace is its per-packet level.

func (*Socket) Accept

func (s *Socket) Accept(ctx context.Context, config *ConnectionConfig) (*Stream, error)

Accept waits for an incoming connection from any peer and returns it once connected. config applies to the connection; nil means the socket's accept configuration (WithAcceptConfig) or the defaults.

func (*Socket) AcceptBacklog

func (s *Socket) AcceptBacklog() int

AcceptBacklog reports how many incoming connections are waiting for an Accept to take them.

func (*Socket) AcceptWithCID

func (s *Socket) AcceptWithCID(ctx context.Context, cid *ConnectionID, config *ConnectionConfig) (*Stream, error)

AcceptWithCID waits for the incoming connection with the given id and returns it once connected. It fails with ErrAcceptTimedOut if no matching SYN arrives within 20 seconds.

func (*Socket) Close

func (s *Socket) Close() error

Close shuts the socket down. It is safe to call more than once.

It first waits for streams already closed to finish delivering what was written to them, while each one's peer keeps answering (awaitLingering); everything else ends at once.

func (*Socket) Connect

func (s *Socket) Connect(ctx context.Context, peer ConnectionPeer, config *ConnectionConfig) (*Stream, error)

Connect dials peer with newly chosen connection ids and returns the stream once the handshake completes. ctx bounds the dial only; the connection lives until it is closed or the socket closes. A nil config means the defaults.

func (*Socket) ConnectWithCID

func (s *Socket) ConnectWithCID(
	ctx context.Context,
	cid *ConnectionID,
	config *ConnectionConfig,
) (*Stream, error)

ConnectWithCID is Connect with the connection ids given by cid.

func (*Socket) ConnectionsRefusedAtCapacity

func (s *Socket) ConnectionsRefusedAtCapacity() uint64

ConnectionsRefusedAtCapacity reports how many incoming connections were refused because the socket was at its connection cap. See WithMaxConnections.

func (*Socket) ConnectionsRefusedByFirewall

func (s *Socket) ConnectionsRefusedByFirewall() uint64

ConnectionsRefusedByFirewall reports how many incoming connections the firewall callback rejected.

func (*Socket) ConnectionsRefusedIDInUse

func (s *Socket) ConnectionsRefusedIDInUse() uint64

ConnectionsRefusedIDInUse reports how many incoming connections were refused because a connection this socket dialed to the same peer already receives on the id the new one would use.

func (*Socket) LocalAddr

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

LocalAddr returns the address the underlying socket is bound to, or nil if it cannot report one. After binding to port 0 it reports the chosen port.

func (*Socket) NumConnections

func (s *Socket) NumConnections() int

NumConnections returns how many connections the socket currently tracks.

func (*Socket) PacketsDroppedFullConnQueue

func (s *Socket) PacketsDroppedFullConnQueue() uint64

PacketsDroppedFullConnQueue reports how many received packets this socket discarded because the destination connection's event queue was full. It should be zero.

func (*Socket) PacketsResetSent

func (s *Socket) PacketsResetSent() uint64

PacketsResetSent reports how many RESETs this socket sent in answer to packets for connections it does not have.

func (*Socket) ProcessICMPError

func (s *Socket) ProcessICMPError(quoted []byte, peer ConnectionPeer) bool

ProcessICMPError reports an ICMP message that should end a connection: destination unreachable other than fragmentation needed, time exceeded, or parameter problem. The connection ends with ErrConnRefused if it had only sent its SYN and ErrReset otherwise.

quoted, peer and the result are as for ProcessICMPFragmentation. Which ICMP messages are fatal is for the caller to decide.

func (*Socket) ProcessICMPFragmentation

func (s *Socket) ProcessICMPFragmentation(quoted []byte, peer ConnectionPeer, nextHopMTU uint16) bool

ProcessICMPFragmentation reports an ICMP "fragmentation needed" message (IPv4 type 3 code 4, or ICMPv6 "packet too big") for a datagram this socket sent, so the path-MTU search can use the router's figure instead of losing a probe to discover it.

quoted is the uTP datagram the ICMP message quoted, the original UDP payload, without the ICMP, IP or UDP headers. peer is the address it was sent to. nextHopMTU is the router's figure, which may be zero; the search halves the gap when it is missing or implausible.

It reports whether quoted was recognized as a packet of a live connection.

The socket does not collect ICMP itself. On Linux the usual source is IP_RECVERR on the UDP socket.

type SocketOption

type SocketOption func(*Socket)

SocketOption configures a Socket at construction.

func WithAcceptConfig

func WithAcceptConfig(config func() *ConnectionConfig) SocketOption

WithAcceptConfig gives the socket its own configuration for incoming connections, read as each one is created. With it, a SYN is answered as it arrives, whether or not an Accept is waiting, and the connection is held until an Accept or AcceptWithCID takes it.

The function's configuration is used for every connection answered before an Accept asks, and for one accepted with a nil config. An Accept given a config of its own still applies it to a connection whose SYN arrives while it waits; one that takes an already answered connection gets it as it was created.

Without this option the configuration comes from each Accept, so a SYN that arrives before its Accept waits, unanswered, until an Accept claims it or 20 seconds pass.

Answered connections count against WithMaxConnections and are held until the application accepts them.

func WithFirewall

func WithFirewall(refuse func(peer ConnectionPeer) bool) SocketOption

WithFirewall refuses incoming connections the callback rejects.

The callback is consulted for a SYN for a connection this socket does not already have, before any state exists for it. Returning true drops the packet without an answer, so a refused peer costs no connection state and learns nothing; a typical use is an IP blocklist.

The callback runs on the socket's receive path. It must not block or call back into the socket.

func WithMaxConnections

func WithMaxConnections(n int) SocketOption

WithMaxConnections limits the connections this socket holds before it refuses new incoming ones. A SYN is refused while the socket holds more than n, so up to n+1 are admitted.

Every connection counts, dialed or accepted, established or closing, as does every SYN waiting for Accept. A refused SYN is dropped silently: a RESET would tell the peer something is listening.

n == 0 means DefaultMaxConnections, the default without this option. n < 0 means no limit.

type Stream

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

Stream is one uTP connection, as returned by Socket's Connect and Accept methods.

It lives as long as the socket, not the call that created it: canceling a dial's or accept's context after it returns does not end the stream. Read and Write each take a context that governs that one call. CloseWrite sends a FIN once what is written has been sent, CloseRead stops delivery, and Close does both and returns at once while the connection delivers what it holds in the background; Socket.Close waits for that while the peer still answers. Done is closed when the connection has ended.

func (*Stream) CID

func (s *Stream) CID() *ConnectionID

CID returns the stream's connection id.

func (*Stream) Close

func (s *Stream) Close() error

Close ends the stream and returns at once. What has been written continues to be delivered in the background, followed by the FIN, until the peer has acknowledged everything or stopped answering; Socket.Close waits for that.

func (*Stream) CloseRead

func (s *Stream) CloseRead() error

CloseRead shuts down the receiving side: this end stops delivering what the peer sends, while everything it has to send goes out normally.

Incoming data is still acknowledged and then discarded, so the peer is not stalled by a closing window and can finish sending and closing. Data already buffered is discarded too.

Afterwards Read returns ErrReadClosed. Write is unaffected. It is named after net.TCPConn.CloseRead.

func (*Stream) CloseWrite

func (s *Stream) CloseWrite() error

CloseWrite shuts down the sending side and leaves the receiving side open: everything written is flushed, a FIN follows it, and the peer may continue sending. It is named after net.TCPConn.CloseWrite, so callers holding a net.Conn can detect it with a type assertion.

Afterwards Write returns ErrNotConnected. Read is unaffected and returns data until the peer closes too. Close ends the connection.

func (*Stream) Done

func (s *Stream) Done() <-chan struct{}

Done returns a channel closed once the connection has ended: its close handshake finished or gave up, it was reset or timed out, or the socket closed. Data delivered before then can still be read.

func (*Stream) Read

func (s *Stream) Read(ctx context.Context, buf []byte) (int, error)

Read reads the next available bytes into buf, returning as soon as any are available. It returns io.EOF once the peer has closed its sending side and everything it sent has been read.

Do not mix Read and ReadToEOF on the same stream; each would consume chunks the other expected.

func (*Stream) ReadToEOF

func (s *Stream) ReadToEOF(ctx context.Context, buf *[]byte) (int, error)

ReadToEOF reads until the peer ends the stream, replacing the contents of *buf. It reuses the capacity *buf already has, so a caller that knows how much is coming can size it once.

func (*Stream) SetWindowSize

func (s *Stream) SetWindowSize(bytes uint32)

SetWindowSize sets the upper bound on this connection's congestion window, in bytes, overriding ConnectionConfig.WindowSize. It takes effect at once: nothing more is sent beyond it, and a larger window is cut to it. Zero restores the default.

func (*Stream) WindowSize

func (s *Stream) WindowSize() uint32

WindowSize returns the upper bound set by SetWindowSize or the connection's config.

func (*Stream) Write

func (s *Stream) Write(ctx context.Context, buf []byte) (int, error)

Write queues all of buf to be sent and returns once the connection has taken every byte into its send buffer, which may be before any has been sent; delivery continues in the background, through Close.

The count is what the connection took, all of which will be sent. If ctx ends first, Write returns the count taken with ctx's error and withdraws the rest, so retrying with the remainder sends each byte once. Concurrent writes are each sent whole, one after another.

After CloseWrite or Close it returns ErrNotConnected, and once the connection has ended, the reason it ended.

func (*Stream) WriteV

func (s *Stream) WriteV(ctx context.Context, bufs [][]byte) (int, error)

WriteV writes several buffers as one stream write, without the caller joining them first, saving a copy. It returns the number of bytes written, which on success is their sum, and otherwise behaves like Write. Empty and nil buffers are skipped; a call with nothing to write returns 0, nil.

type UDPPeer

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

UDPPeer is a ConnectionPeer for a UDP address.

func NewUDPPeer

func NewUDPPeer(addr *net.UDPAddr) *UDPPeer

NewUDPPeer returns a peer for addr.

func (*UDPPeer) Addr

func (p *UDPPeer) Addr() *net.UDPAddr

Addr returns the peer's UDP address.

func (*UDPPeer) Hash

func (p *UDPPeer) Hash() string

Hash returns the peer's address as text.

func (*UDPPeer) String

func (p *UDPPeer) String() string

String returns the peer's address.

Directories

Path Synopsis
compat
golibutp module
internal
netem
Package netem provides an in-process emulated network for testing uTP.
Package netem provides an in-process emulated network for testing uTP.
Package utpnet presents a uTP socket through the standard library's net interfaces.
Package utpnet presents a uTP socket through the standard library's net interfaces.

Jump to

Keyboard shortcuts

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