utpnet

package
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: 12 Imported by: 0

Documentation

Overview

Package utpnet presents a uTP socket through the standard library's net interfaces.

A Socket satisfies

net.PacketConn
Accept() (net.Conn, error)
Addr() net.Addr
DialContext(ctx context.Context, network, addr string) (net.Conn, error)

and its connections are net.Conn values with CloseRead and CloseWrite, so it can be used wherever a BitTorrent client or other application expects a uTP socket in net terms.

Sharing a UDP port

A BitTorrent client typically runs uTP and the DHT on one UDP port. A Socket therefore inspects each datagram: uTP packets go to the uTP connections, and everything else is returned by ReadFrom. WriteTo sends raw datagrams on the same port.

Example
package main

import (
	"context"
	"fmt"
	"io"
	"log"

	"github.com/arxodeus/utp/utpnet"
)

func main() {
	ctx := context.Background()

	server, err := utpnet.Listen(ctx, "udp", "127.0.0.1:0", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer server.Close()

	go func() {
		conn, err := server.Accept()
		if err != nil {
			return
		}
		defer conn.Close()
		io.Copy(conn, conn) // echo
	}()

	client, err := utpnet.Listen(ctx, "udp", "127.0.0.1:0", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	conn, err := client.DialContext(ctx, "udp", server.Addr().String())
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	if _, err := conn.Write([]byte("hello")); err != nil {
		log.Fatal(err)
	}
	buf := make([]byte, 5)
	if _, err := io.ReadFull(conn, buf); err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(buf))
}
Output:
hello

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNilPeer = errors.New("utpnet: nil peer")

ErrNilPeer is returned when a write names no destination.

View Source
var ErrSocketClosed = errors.New("utpnet: socket is closed")

ErrSocketClosed is returned by operations on a closed Socket.

Functions

This section is empty.

Types

type Conn

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

Conn is a uTP stream presented as a net.Conn.

func (*Conn) Close

func (c *Conn) Close() error

Close closes the connection and returns at once; what has been written is still delivered in the background.

func (*Conn) CloseRead

func (c *Conn) CloseRead() error

CloseRead shuts down the receiving side and leaves the sending side open, like net.TCPConn.CloseRead. Data the peer sends afterwards is acknowledged, so the peer is not stalled and can finish its close, and then discarded.

func (*Conn) CloseWrite

func (c *Conn) CloseWrite() error

CloseWrite shuts down the sending side and leaves the receiving side open, like net.TCPConn.CloseWrite. Callers can detect it with a type assertion to interface{ CloseWrite() error }.

func (*Conn) LocalAddr

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

LocalAddr returns the local address.

func (*Conn) Read

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

Read reads data from the connection, honoring the read deadline.

func (*Conn) RemoteAddr

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

RemoteAddr returns the peer's address.

func (*Conn) SetDeadline

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

SetDeadline sets the read and write deadlines. Moving a deadline affects calls already waiting.

func (*Conn) SetReadDeadline

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

SetReadDeadline sets the read deadline.

func (*Conn) SetWriteDeadline

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

SetWriteDeadline sets the write deadline.

func (*Conn) Stream

func (c *Conn) Stream() *utp.Stream

Stream returns the underlying uTP stream, for callers that need the context-taking API or the connection id.

func (*Conn) Write

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

Write writes all of b, or reports how much was written. If the write deadline moves while it waits, it continues with the remainder.

func (*Conn) WriteBuffers

func (c *Conn) WriteBuffers(bufs net.Buffers) (int64, error)

WriteBuffers writes several buffers as one stream write without joining them first, saving a copy. net.Buffers.WriteTo does not use it, so call it directly. If the write deadline moves while it waits, it continues with the remainder, as Write does.

type Options

type Options struct {
	// Logger receives the uTP socket's logging. A nil Logger discards it.
	Logger *slog.Logger
	// ConnectionConfig is applied to every stream this socket accepts or
	// dials. A nil value uses utp.NewConnectionConfig().
	ConnectionConfig *utp.ConnectionConfig
	// Firewall, when set, is consulted for every incoming connection before
	// any state is created for it. Returning true refuses it: the packet is
	// dropped without an answer, so a refused peer learns nothing. A typical
	// use is an IP blocklist.
	//
	// It runs on the socket's receive path: it must not block or call back
	// into the socket.
	Firewall func(net.Addr) bool
	// OnWriteError, when set, is called for every datagram the uTP
	// connections failed to send, with its destination. The connections
	// carry on regardless, since a lost datagram is retransmitted; this lets
	// a caller give up sooner on an error it knows is permanent, or count
	// them.
	//
	// It is called on the sending goroutine: it must not block or call back
	// into the socket.
	OnWriteError func(to net.Addr, err error)
	// MaxConnections limits the connections this socket holds before it
	// silently refuses new incoming ones. Zero means
	// utp.DefaultMaxConnections; a negative value means no limit. See
	// utp.WithMaxConnections.
	MaxConnections int
}

Options configures a Socket.

type Socket

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

Socket is a uTP endpoint on a UDP port, presented as net types.

func Listen

func Listen(ctx context.Context, network, addr string, opts *Options) (*Socket, error)

Listen binds a UDP port and returns a uTP socket on it.

network is "udp", "udp4" or "udp6"; addr is a host:port as accepted by net.ResolveUDPAddr, and an empty port lets the kernel choose one.

func NewSocket

func NewSocket(ctx context.Context, conn *net.UDPConn, opts *Options) (*Socket, error)

NewSocket returns a Socket on an already bound UDP connection. The Socket takes ownership, and closing it closes conn. Use it to set socket options or bind with a particular control function.

func NewSocketFromPacketConn

func NewSocketFromPacketConn(ctx context.Context, pc net.PacketConn, opts *Options) (*Socket, error)

NewSocketFromPacketConn returns a Socket on any net.PacketConn, not only a UDP socket: for example one wrapped for rate limiting or metrics, one shared with other code, or an in-memory network.

The Socket takes ownership of pc, and closing the Socket closes it. Unlike a Socket on a UDP socket, Close does not wait for the reader to return, since pc may not honor read deadlines. The reader stops when pc's ReadFrom fails. A non-timeout error on an open Socket closes it, and Accept, ReadFrom and the dial methods then return ErrSocketClosed wrapping that error.

Addresses must name a host and port. A datagram from an address that is not a *net.UDPAddr is handled under its UDP form, and replies are sent to the original address; DialAddr dials an address of pc's own type. A PacketConn that uses its own address type must accept, in WriteTo, any address it has returned from ReadFrom.

func (*Socket) Accept

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

Accept waits for the next incoming uTP connection.

func (*Socket) AcceptBacklog

func (s *Socket) AcceptBacklog() int

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

func (*Socket) Addr

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

Addr returns the local address, implementing net.Listener.

func (*Socket) Close

func (s *Socket) Close() error

Close shuts down the socket, every connection on it, and the underlying UDP port. It is safe to call more than once.

Connections already closed are given time to deliver what they hold, as utp.Socket.Close gives them, and the port keeps being read meanwhile, since their acknowledgements arrive on it.

func (*Socket) ConnectionConfig

func (s *Socket) ConnectionConfig() *utp.ConnectionConfig

ConnectionConfig returns the configuration given to streams accepted or dialed now.

func (*Socket) Dial

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

Dial calls DialContext with a background context.

func (*Socket) DialAddr

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

DialAddr opens a uTP connection to an address already resolved. It takes any address with a host and port. On a Socket created with NewSocketFromPacketConn, use it to dial an address of the PacketConn's own type, which DialContext cannot resolve.

func (*Socket) DialContext

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

DialContext opens a uTP connection to addr, a host:port. network is a UDP network name; "utp", "utp4" and "utp6" are accepted as aliases.

func (*Socket) LocalAddr

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

LocalAddr returns the local address, satisfying net.PacketConn.

func (*Socket) ProcessICMPError

func (s *Socket) ProcessICMPError(quoted []byte, addr *net.UDPAddr) bool

ProcessICMPError reports an ICMP message that should end a connection: destination unreachable other than fragmentation needed, time exceeded, or parameter problem. Which messages are fatal is for the caller to decide.

quoted, addr and the result are as for ProcessICMPFragmentation.

func (*Socket) ProcessICMPFragmentation

func (s *Socket) ProcessICMPFragmentation(quoted []byte, addr *net.UDPAddr, 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.

quoted is the uTP datagram the ICMP message quoted, the original UDP payload without the ICMP packet around it, and addr is where it was sent. nextHopMTU is the router's figure, which may be zero. It reports whether quoted matched a live connection.

This package does not collect ICMP itself. On Linux the usual source is IP_RECVERR on the UDP socket the Socket was built from.

func (*Socket) ReadFrom

func (s *Socket) ReadFrom(p []byte) (int, net.Addr, error)

ReadFrom returns the next datagram that was not a uTP packet, such as a BitTorrent client's DHT traffic on a shared port.

func (*Socket) SetConnectionConfig

func (s *Socket) SetConnectionConfig(config *utp.ConnectionConfig)

SetConnectionConfig replaces the configuration given to streams accepted or dialed from now on; open streams keep theirs. A nil config means utp.NewConnectionConfig(). The config is not copied and must not be modified afterwards.

func (*Socket) SetDeadline

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

SetDeadline sets both the read and write deadlines for the packet-oriented side of this socket. It does not affect established uTP connections, which carry their own deadlines.

func (*Socket) SetReadDeadline

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

SetReadDeadline sets how long ReadFrom will wait.

func (*Socket) SetWriteDeadline

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

SetWriteDeadline sets the deadline for WriteTo, including a WriteTo already waiting. It does not apply to the uTP connections' own writes, which share the port. See WriteTo.

func (*Socket) WriteTo

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

WriteTo sends a raw datagram to addr on the port the uTP connections use, for example a DHT reply.

The write deadline applies while the write waits, and moving it moves the wait. The datagram is handed to a single writer goroutine (see passthroughWriter), so the wait can end without the write; a deadline set on the port itself would end the uTP connections' writes too. A datagram whose deadline passes before the writer reaches it is not sent; one the writer is already sending may still be. p is copied, so the caller may reuse it on return.

On Unix a datagram is first offered to the kernel once without waiting (sendNow); only one the kernel cannot take at once goes to the writer, so a blocked write can always be ended by the deadline. While no deadline is set, handing over uses no timer or goroutine.

Jump to

Keyboard shortcuts

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