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

portforward

package
v0.0.187 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: GPL-3.0 Imports: 14 Imported by: 0

Documentation

Overview

Package portforward opens TCP and UDP ports on the local network gateway via UPnP IGD so a node's inbounds are reachable from the public internet without manual router configuration. IGDv2 is tried first and IGDv1 is the fallback.

Index

Constants

View Source
const (
	TCP = "TCP"
	UDP = "UDP"
)

Protocols a mapping can carry, as UPnP IGD names them.

Variables

View Source
var ErrNoPortForwarding = errors.New("no port forwarding available")

ErrNoPortForwarding is returned when no UPnP gateway is reachable, the gateway refuses to map a port, or the discovery scan times out. Callers should treat this as "this network can't host a peer proxy" and surface it to the user rather than retry indefinitely.

Functions

func LocalIP

func LocalIP() (string, error)

func ParseManualPort

func ParseManualPort(s string) (uint16, error)

ParseManualPort parses a string into a port number. Values outside 1..65535 return an error so callers can log and fall through to UPnP discovery rather than register a non-listening port with the server.

func ProbeUPnP

func ProbeUPnP(ctx context.Context) bool

ProbeUPnP reports whether IGD discovery on the local network turns up a gateway that could host a port mapping. No port is actually mapped — this is the "would Share My Connection's residential-proxy mode work on this network?" pre-flight check that UI flows show before committing to the long-lived SmC path.

True means discovery succeeded; false means either no gateway was found (ErrNoPortForwarding) or ctx expired before discovery completed. Callers generally treat both as "not available" — distinguishing them requires returning the underlying error, which most UI surfaces don't have a productive use for.

Pick a 5-10s timeout on ctx: M-SEARCH multicast waits for replies and a fast bail leaves slower gateways unmatched. The discovered forwarder is discarded after the probe; it holds no goroutines or sockets that need explicit cleanup, so a subsequent NewForwarder call can re-discover without coordination.

Types

type Forwarder

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

Forwarder manages port mappings on the local gateway, at most one per protocol. Construct one per session.

func NewForwarder

func NewForwarder(ctx context.Context) (*Forwarder, error)

NewForwarder discovers the local gateway and returns a Forwarder bound to it. Callers should pick a 5-10s timeout on ctx — UPnP discovery is M-SEARCH multicast and waits for replies.

Returns ErrNoPortForwarding only when discovery completes without finding a usable gateway. If ctx was canceled or its deadline expired during discovery, the ctx error is returned verbatim so callers can distinguish "this network can't host a peer" from "we ran out of time, retry later".

func (*Forwarder) ExternalIP

func (f *Forwarder) ExternalIP(ctx context.Context) (string, error)

ExternalIP queries the gateway for its WAN-side IP address. Cheaper than dialing a public-IP service when we already have a UPnP client open.

func (*Forwarder) MapPort

func (f *Forwarder) MapPort(ctx context.Context, protocol string, internalPort uint16, description string) (*Mapping, error)

MapPort asks the gateway to forward externalPort → (LocalIP():internalPort) for protocol, TCP or UDP. A Forwarder holds one mapping per protocol. Lease duration is requested as 1 hour but some routers ignore the request and assign their own (or none — "permanent"). description is shown in the router's UI so users can identify and remove the mapping manually if needed.

func (*Forwarder) StartRenewal

func (f *Forwarder) StartRenewal(ctx context.Context)

func (*Forwarder) UnmapPort

func (f *Forwarder) UnmapPort(ctx context.Context) error

UnmapPort removes every active mapping. No-op if none is active. Always called as part of teardown — even if the gateway has already let the lease expire, DeletePortMapping is the polite signal to the router.

A mapping is forgotten only on a successful delete. A failed delete leaves it in place so the caller can retry; otherwise we'd "forget" about a router rule that's actually still live and the user would have to wait for the UPnP lease to expire.

type ManualForwarder

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

ManualForwarder exposes the same Map/Unmap/StartRenewal/ExternalIP surface as Forwarder but does no UPnP work. The user is expected to have configured a port forward on their router by hand and supplied the port number out-of-band. This implementation reports the same value for both the external and internal port — callers needing distinct external/internal ports should use the UPnP-based Forwarder (which can negotiate them) or build their own portForwarder.

Use case: networks where UPnP is disabled or unavailable (router has UPnP off for security, ISP-provided gateways without IGD, networks behind double-NAT). The UPnP-based Forwarder fails in those environments; this type lets callers bypass discovery entirely.

func NewManualForwarder

func NewManualForwarder(port uint16) *ManualForwarder

NewManualForwarder builds a ManualForwarder for a pre-configured router port forward. port must be in 1..65535; the caller is responsible for validating its input before calling.

func (*ManualForwarder) ExternalIP

func (m *ManualForwarder) ExternalIP(_ context.Context) (string, error)

ExternalIP returns the empty string deliberately. With a manual port forward we have no UPnP gateway to ask for the WAN address, and probing a public IP service from the client adds a network roundtrip for information lantern-cloud already has — the server observes the peer's source address on the register call and uses that as the canonical external IP when this field is empty.

func (*ManualForwarder) MapPort

func (m *ManualForwarder) MapPort(_ context.Context, protocol string, _ uint16, _ string) (*Mapping, error)

MapPort reports the configured port as both external and internal, for protocol. The router-side rule is already in place; nothing to do at the protocol layer. Whether the user forwarded that protocol is unknown here, so callers verify reachability for each protocol they use. Returns an error if the forwarder was constructed with port==0 (not a valid listen/registration port) so callers can fall through to UPnP rather than register a port no peer will listen on.

func (*ManualForwarder) Port

func (m *ManualForwarder) Port() uint16

Port returns the router port this forwarder was configured with.

func (*ManualForwarder) StartRenewal

func (m *ManualForwarder) StartRenewal(_ context.Context)

StartRenewal is a no-op: manually-configured rules don't carry a UPnP lease and don't need refreshing.

func (*ManualForwarder) UnmapPort

func (m *ManualForwarder) UnmapPort(_ context.Context) error

UnmapPort is a no-op: the user owns the router rule and is responsible for removing it.

type Mapping

type Mapping struct {
	ExternalPort  uint16
	InternalPort  uint16
	InternalIP    string
	Protocol      string
	LeaseDuration time.Duration
	Method        string
	// contains filtered or unexported fields
}

Jump to

Keyboard shortcuts

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