Help us shape the future of Go! Take the 2026 Go Developer Survey

skyudpbridge

package
v1.3.104 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package skyudpbridge pkg/skyudpbridge/skyudpbridge.go c2-net-transport bridge ("sub"): length-prefixed UDP datagrams ferried over a reliable skywire byte-stream (dmsg or skywire-routing). Plan B of the UDP-over-skynet split (Plan A — true packet-level UDP at the route-group layer — lives in pkg/router under RFC #2607).

Use shape: a co-located application speaks UDP to a local socket (e.g. DNS resolver hitting :53). The client-side bridge captures each datagram, frames it with a uint16 length prefix, and writes it to a skywire stream. The peer's server-side bridge reads frames off the stream and replays them as UDP to a configured local target (e.g. an upstream resolver on 127.0.0.1:53). The stream stays open for the lifetime of the UDP "flow" (per-source (ip, port) tuple) with an idle timeout.

Two ends, mirror-symmetric:

client side:  UDP listener  →  per-source stream  →  framer  →  Stream
server side:  Stream  →  defamer  →  per-stream UDP socket  →  target

Replies flow back via the SAME stream/UDP socket pair, so a stateful UDP protocol (DNS reply on same socket) works.

Suitable for non-realtime UDP: DNS, NTP, SNMP, MQTT-SN, WireGuard control plane. For media-class UDP (VoIP, RTP, WebRTC media, real-time game protocols) use Plan A (true packet-level UDP at the route-group layer; see RFC #2607).

Reliability tradeoff: the skywire stream is TCP-like (reliable, in-order). Wrapping UDP in it gains reliability + ordering but adds head-of-line blocking. For DNS/NTP/SNMP this is fine — the app would retry on packet loss anyway, and replies arriving in order is harmless. For RTP/voice it's wrong — Plan A.

The Dialer abstraction lets the standalone (cmd/sub) and any future visor-app variant share this protocol core: standalone backs Dialer with its own dmsg.Client; visor-app would back it with app.Client.Dial. Same pattern as pkg/skymailbridge.

Index

Constants

View Source
const DefaultDialTimeout = 15 * time.Second

DefaultDialTimeout bounds a single peer-dial attempt. Long enough to ride out a skywire route setup, short enough that a UDP app gets a "we tried" failure rather than indefinite blocking.

View Source
const DefaultIdleTimeout = 60 * time.Second

DefaultIdleTimeout is how long a per-source flow's stream stays open with no traffic before the bridge closes it. Trades stream reuse (cheaper for chatty flows) against resource bloat (a stuck flow consuming a stream forever). 60s is the same default WireGuard uses for keepalive-driven NAT entries — a sane neighborhood.

View Source
const MaxFrameSize = 65535

MaxFrameSize is the largest UDP payload the bridge will ferry in a single frame. Set to 65535 (the absolute UDP payload limit) so no realistic datagram is truncated. Frames larger than this are dropped on the client side and never reach the wire.

Variables

This section is empty.

Functions

func ReadFrame

func ReadFrame(r io.Reader, buf []byte) (int, error)

ReadFrame reads one length-prefixed datagram from r into buf. Returns the number of bytes read into buf. If the incoming frame is larger than len(buf), returns an error (caller should size buf to MaxFrameSize for guaranteed acceptance).

func RunClient

func RunClient(ctx context.Context, cfg ClientConfig, dialer Dialer, log logrus.FieldLogger) error

RunClient starts the client-side bridge: UDP listener → per-source stream → framer. Blocks until ctx is canceled or the underlying listener fails. The Dialer is invoked lazily on the first datagram from each new source.

func RunServer

func RunServer(ctx context.Context, cfg ServerConfig, accept func() (net.Conn, error), log logrus.FieldLogger) error

RunServer starts the server-side bridge: accept skywire streams, read framed datagrams, forward via UDP. Blocks until ctx is canceled or the listener fails. accept is the inbound-stream accept function — provided by the caller because dmsg and skywire-routing have different listen primitives.

func WriteFrame

func WriteFrame(w io.Writer, payload []byte) error

WriteFrame writes one length-prefixed datagram to w. Returns an error if the payload is over MaxFrameSize or the write partially completes. Exported so tests + the future visor-app variant share the same wire format guarantees.

Types

type ClientConfig

type ClientConfig struct {
	// ListenUDP is the local UDP address the bridge listens on for
	// app traffic. Conventionally "127.0.0.1:<port>". Required.
	ListenUDP string
	// Peer is the destination visor's public key. Required.
	Peer cipher.PubKey
	// PeerPort is the skywire routing/dmsg port the peer's
	// server-side bridge listens on. Required (no default —
	// operators must agree on a port).
	PeerPort uint16
	// IdleTimeout is the per-flow idle timer. Zero uses
	// DefaultIdleTimeout.
	IdleTimeout time.Duration
	// DialTimeout caps each peer-dial attempt. Zero uses
	// DefaultDialTimeout.
	DialTimeout time.Duration
}

ClientConfig configures a client-side bridge instance.

type Dialer

type Dialer interface {
	Dial(ctx context.Context, peer cipher.PubKey, port uint16) (net.Conn, error)
}

Dialer abstracts the peer-dial step so the same protocol core runs on top of either a direct dmsg.Client (standalone) or app.Client.Dial (visor app). Implementations must return a usable net.Conn or an error; returning (nil, nil) is a contract violation. Mirrors pkg/skymailbridge.Dialer by intent.

type ServerConfig

type ServerConfig struct {
	// TargetUDP is the local UDP address the server forwards
	// unframed datagrams to. Conventionally "127.0.0.1:<port>".
	// Required.
	TargetUDP string
	// IdleTimeout is the per-stream UDP-socket idle timer.
	IdleTimeout time.Duration
}

ServerConfig configures a server-side bridge instance.

Jump to

Keyboard shortcuts

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