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

portmap

package
v0.27.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: BSD-3-Clause Imports: 14 Imported by: 0

Documentation

Overview

Package portmap publishes a container's ports on the host: `wctr run -p`, Docker's HostConfig.PortBindings and PublishAllPorts.

A container has no host socket of its own: its listeners live on the engine's networks (netdriver). So a published port is a listener the daemon opens on the host -- through a Host, which is net.Listen natively and something else in a browser -- and every connection it accepts is dialed into the container through the container's own endpoint. Nothing is routed and no packet is rewritten; a connection is copied, both ways, like Docker's userland proxy.

⛔ One difference from Docker, on purpose: a binding that names no host address listens on 127.0.0.1, not on every address. A training lab's container should not become a service on the machine's network because somebody typed `-p 8080:80`; `-p 0.0.0.0:8080:80` says so when it is meant.

Index

Constants

View Source
const MaxBindings = 1024

MaxBindings bounds one container's bindings: each is a host listener.

View Source
const MaxDatagram = 8 << 10

MaxDatagram bounds one datagram, as the engine's networks do.

View Source
const VirtualBacklog = 64

VirtualBacklog is how many connections wait for Accept before a Dial is refused, as a listening socket's backlog refuses.

Variables

View Source
var DefaultHostIP = netip.AddrFrom4([4]byte{127, 0, 0, 1})

DefaultHostIP is the address a binding that names none listens on.

View Source
var DefaultLimits = Limits{MaxConns: 128, MaxSessions: 256, UDPIdle: 90 * time.Second, DialTimeout: 5 * time.Second}

DefaultLimits are Docker's UDP timeout (90 s) and bounds sized for a classroom, not a production load balancer.

View Source
var ErrAllocated = errors.New("port is already allocated")

ErrAllocated is a host port somebody else holds.

View Source
var ErrInvalid = errors.New("invalid port specification")

ErrInvalid is a port, a binding or a specification that cannot be.

Functions

This section is empty.

Types

type APIBinding

type APIBinding struct {
	HostIP   string `json:"HostIp"`
	HostPort string `json:"HostPort"`
}

APIBinding is Docker's PortBinding: strings, an empty HostIp for the default address, an empty HostPort for one the system picks.

type APIMap

type APIMap map[string][]APIBinding

APIMap is Docker's PortMap: "80/tcp" -> bindings, null for a port only exposed.

type Binding

type Binding struct {
	// HostIP is the address to listen on; the zero Addr is DefaultHostIP.
	HostIP netip.Addr
	// HostPort, HostPortEnd: a port, a range to take the first free one
	// from, or 0 for one the system picks.
	HostPort, HostPortEnd uint16
}

Binding is where a container port is published on the host.

func ParseBinding

func ParseBinding(hostIP, hostPort string) (Binding, error)

ParseBinding reads the API's PortBinding: a HostIp ("" for the default) and a HostPort ("", "8080", "8000-8010").

func (Binding) API

func (b Binding) API() APIBinding

API is the binding as the Engine API writes it.

func (Binding) Addr

func (b Binding) Addr() netip.Addr

Addr is the host address the binding listens on.

func (Binding) String

func (b Binding) String() string

String is the binding as Docker writes it: "127.0.0.1:8080", "127.0.0.1:8000-8010", "127.0.0.1:" for one the system picks.

type Dialer

type Dialer func(ctx context.Context, network string, addr netip.AddrPort) (net.Conn, error)

Dialer reaches the container: its own endpoint, dialing 127.0.0.1, so a port is reached on whichever of its networks it listens.

type Host

type Host interface {
	Listen(ctx context.Context, network string, addr netip.AddrPort) (net.Listener, error)
	ListenPacket(ctx context.Context, network string, addr netip.AddrPort) (net.PacketConn, error)
}

Host is where published ports listen: the machine's sockets for wctrd (OS), something the page provides in a browser.

type Key

type Key struct {
	Port  uint16
	Proto string // "tcp" or "udp"
}

Key is a container port and its protocol: Docker's "80/tcp".

func ParseKey

func ParseKey(s string) (Key, error)

ParseKey reads "80", "80/tcp" or "53/udp". sctp is refused: the engine's networks carry TCP and UDP only.

func (Key) String

func (k Key) String() string

type Limits

type Limits struct {
	// MaxConns is the connections one TCP binding proxies at once; the next
	// is closed as soon as it is accepted.
	MaxConns int
	// MaxSessions is the UDP clients one binding keeps at once; a datagram
	// from another is dropped, as a full socket buffer drops.
	MaxSessions int
	// UDPIdle ends a UDP client's session after this long without traffic.
	UDPIdle time.Duration
	// DialTimeout bounds reaching the container.
	DialTimeout time.Duration
}

Limits bound what one published port may hold.

type Map

type Map map[Key][]Binding

Map is a container's bindings, by port. A port in it with no binding is exposed and not published (Docker's `"2377/tcp": null`).

func FromAPI

func FromAPI(a APIMap) (Map, error)

FromAPI reads a PortMap from the API or from storage.

func ParseSpec

func ParseSpec(spec string) (Map, error)

ParseSpec reads one `-p` value, as Docker does (github.com/docker/go-connections/nat.ParsePortSpec):

containerPort[/proto]
hostPort:containerPort[/proto]
ip:hostPort:containerPort[/proto]     ip:  [::1] for IPv6
ip::containerPort[/proto]             a host port the system picks

Ports may be ranges of the same length ("8000-8010:80-90"); a host range with a single container port is where to take a free one from.

func ParseSpecs

func ParseSpecs(specs []string) (Map, error)

ParseSpecs reads every `-p` value into one map.

func (Map) API

func (m Map) API() APIMap

API is the map as the Engine API writes it.

func (Map) Bindings

func (m Map) Bindings() int

Bindings counts the map's bindings.

func (Map) Exposed

func (m Map) Exposed(keys map[string]struct{}) error

Exposed adds ports to m as exposed (no binding), keeping bindings it already has: Docker's Config.ExposedPorts and the image's.

func (Map) Keys

func (m Map) Keys() []Key

Keys are the map's ports, in order.

func (Map) MarshalJSON

func (m Map) MarshalJSON() ([]byte, error)

MarshalJSON writes the Engine API's shape.

func (Map) Merge

func (m Map) Merge(o Map)

Merge adds o's bindings to m.

func (Map) PublishAll

func (m Map) PublishAll() Map

PublishAll is m with every port that has no binding given one the system picks: run -P, HostConfig.PublishAllPorts.

func (*Map) UnmarshalJSON

func (m *Map) UnmarshalJSON(b []byte) error

UnmarshalJSON reads it, validating every key and binding.

type OS

type OS struct{}

OS is the machine's own sockets.

func (OS) Listen

func (OS) Listen(ctx context.Context, network string, addr netip.AddrPort) (net.Listener, error)

func (OS) ListenPacket

func (OS) ListenPacket(ctx context.Context, network string, addr netip.AddrPort) (net.PacketConn, error)

type Set

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

Set is one container's published ports while it runs.

func Publish

func Publish(ctx context.Context, host Host, dial Dialer, m Map, lim Limits) (*Set, error)

Publish opens every binding of m and proxies it to the container, or opens none: a binding that cannot be had closes the others and says which ("Bind for 127.0.0.1:8080 failed: port is already allocated", as Docker words it). A port with no binding is only exposed.

func (*Set) Close

func (s *Set) Close() error

Close stops listening and ends every proxied connection; it returns once nothing it started runs any more.

func (*Set) Ports

func (s *Set) Ports() Map

Ports are the bindings actually held: a picked port is the one picked.

type Virtual added in v0.24.0

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

Virtual is a Host with no sockets: what a page is, where an engine runs in a browser. A published port is a listener known by its host port, and a connection to it is made in memory (Dial) -- by the page's HTTP route to a container, say. The host address of a binding means nothing here: one port is one listener, whatever address it was asked on.

Only TCP: a page has no use for a UDP port, and ListenPacket refuses.

func NewVirtual added in v0.24.0

func NewVirtual() *Virtual

NewVirtual returns a Virtual with nothing listening.

func (*Virtual) Dial added in v0.24.0

func (v *Virtual) Dial(ctx context.Context, port uint16) (net.Conn, error)

Dial connects to the listener on port: ECONNREFUSED when nothing listens there or its backlog is full.

func (*Virtual) Listen added in v0.24.0

func (v *Virtual) Listen(ctx context.Context, network string, addr netip.AddrPort) (net.Listener, error)

Listen implements Host.

func (*Virtual) ListenPacket added in v0.24.0

func (v *Virtual) ListenPacket(context.Context, string, netip.AddrPort) (net.PacketConn, error)

ListenPacket implements Host: refused, a page has no UDP.

func (*Virtual) Ports added in v0.24.0

func (v *Virtual) Ports() []uint16

Ports are the host ports listening now.

func (*Virtual) Transport added in v0.24.0

func (v *Virtual) Transport() *http.Transport

Transport is an http.Transport whose connections go to the port of the request's URL ("http://localhost:8080/x" reaches host port 8080): a fresh connection per request, so a slow or broken container keeps nothing open, and bounded response headers.

Jump to

Keyboard shortcuts

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