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

remote

package
v0.2.55 Latest Latest
Warning

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

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

Documentation

Overview

Package remote handles Ether's network presence: LAN address enumeration, WAN IP detection, Plex connection URIs (plain IP and *.plex.direct), UPnP IGD and NAT-PMP port mapping, GDM LAN discovery and reachability probes.

It has no dependency on plex.tv; internal/plextv drives it.

Index

Constants

View Source
const (
	GDMMulticastGroup = "239.0.0.250"
	GDMServerPort     = 32414
	// GDMAnnouncePort receives HELLO/BYE announcements.
	GDMAnnouncePort = 32413
)

GDM ("G'Day Mate") is Plex's LAN discovery protocol. Clients send "M-SEARCH * HTTP/1.1" to UDP 32414 on multicast group 239.0.0.250 (and by broadcast); every PMS answers unicast with an "HTTP/1.0 200 OK" block of headers describing itself. PMS listens on 32410, 32412, 32413 and 32414.

View Source
const (
	MethodUPnP   = "upnp"
	MethodNATPMP = "nat-pmp"
)

Port mapping methods.

View Source
const (
	ServiceWANIPConnection2  = "urn:schemas-upnp-org:service:WANIPConnection:2"
	ServiceWANIPConnection1  = "urn:schemas-upnp-org:service:WANIPConnection:1"
	ServiceWANPPPConnection1 = "urn:schemas-upnp-org:service:WANPPPConnection:1"
)

UPnP IGD service types, best first.

View Source
const (
	UPnPErrConflictInMappingEntry       = 718
	UPnPErrOnlyPermanentLeasesSupported = 725
	UPnPErrExternalPortOnlySupportsWild = 727
)

Well-known UPnP error codes.

View Source
const (
	SecureRequired  = "required"
	SecurePreferred = "preferred"
	SecureDisabled  = "disabled"
)

Secure-connection modes, matching config.Plex.SecureConnections and PMS's "Secure connections" preference.

View Source
const NATPMPPort = 5351

NATPMPPort is the gateway port NAT-PMP (RFC 6886) listens on.

View Source
const SSDPMulticast = "239.255.255.250:1900"

SSDPMulticast is the standard SSDP group.

Variables

View Source
var DefaultGDMPorts = []int{32410, 32412, 32413, 32414}

DefaultGDMPorts are the UDP ports PMS answers GDM on.

View Source
var DefaultWANEndpoints = []string{
	"https://v4.plex.tv/pms/:/ip",
	"https://api.ipify.org",
	"https://icanhazip.com",
	"https://ifconfig.me/ip",
}

DefaultWANEndpoints are queried in order. The first is the endpoint PMS itself uses (GET https://v4.plex.tv/pms/:/ip in observed PMS traffic); the rest are well-known plain-text fallbacks.

View Source
var ErrNoIGD = errors.New("remote: no UPnP internet gateway found")

ErrNoIGD is returned when no usable gateway answered.

View Source
var ErrNoWANIP = errors.New("remote: could not detect public IP")

ErrNoWANIP is returned when no endpoint produced a public address.

Functions

func DefaultGateway

func DefaultGateway() (net.IP, error)

DefaultGateway returns the IPv4 default gateway. On Linux it reads /proc/net/route; elsewhere it guesses the ".1" address of the first private LAN subnet, which is right for the vast majority of home networks. NAT-PMP is only a fallback after UPnP, so a wrong guess just means the fallback fails.

func GDMHello

func GDMHello(i GDMInfo, bye bool) []byte

GDMHello renders an announcement ("HELLO * HTTP/1.0") or, with bye, a departure ("BYE * HTTP/1.0").

func GDMResponse

func GDMResponse(i GDMInfo) []byte

GDMResponse renders the reply to an M-SEARCH.

func IsPublicIP

func IsPublicIP(ip net.IP) bool

IsPublicIP reports whether ip is a globally routable unicast address.

func IsVirtualInterface

func IsVirtualInterface(name string) bool

IsVirtualInterface reports whether an interface name looks like a container bridge, VPN tunnel or hypervisor adapter.

func LANIPs

func LANIPs(opts LANOptions) ([]net.IP, error)

LANIPs is LANAddresses reduced to the IPs.

func ParsePlexDirectHost

func ParsePlexDirectHost(host string) (ip net.IP, hash string, ok bool)

ParsePlexDirectHost reverses PlexDirectHost. ok is false for hosts that are not "<ip-label>.<hash>.plex.direct".

func PlexDirectHost

func PlexDirectHost(ip net.IP, hash string) string

PlexDirectHost returns the plex.direct host for ip under a certificate hash: 192.168.1.5 -> "192-168-1-5.<hash>.plex.direct". IPv6 addresses use their expanded colon groups joined by dashes, as plex.tv does.

func PlexDirectLabel

func PlexDirectLabel(ip net.IP) string

PlexDirectLabel is the IP-encoding first label of a plex.direct host.

func URIs

func URIs(cs []Connection) []string

URIs extracts the URI of each connection.

func UsableLANIP

func UsableLANIP(ip net.IP) bool

UsableLANIP reports whether ip can be published as a LAN connection.

Types

type BuildOptions

type BuildOptions struct {
	LAN        []net.IP
	WAN        net.IP // nil when unknown or remote access is off
	LocalPort  int    // port the server listens on (32400)
	PublicPort int    // external port on the WAN; 0 = LocalPort
	// CertHash is the plex.direct hash of a valid plex.direct certificate.
	// Without it no https plex.direct URIs are produced.
	CertHash string
	// Secure is SecureRequired, SecurePreferred (default) or SecureDisabled.
	Secure string
	// CustomURLs are owner-supplied URLs (reverse proxies, DDNS names).
	CustomURLs []string
	// HTTPSOnPlainIP adds https://<ip>:port URIs (self-signed setups). Off
	// by default because clients cannot validate a certificate for a bare IP.
	HTTPSOnPlainIP bool
}

BuildOptions describes the server's addresses for BuildConnections.

type Connection

type Connection struct {
	Protocol string `json:"protocol"` // "http" or "https"
	Address  string `json:"address"`  // IP or host
	Port     int    `json:"port"`
	URI      string `json:"uri"`
	Local    bool   `json:"local"`
	Relay    bool   `json:"relay,omitempty"`
	IPv6     bool   `json:"IPv6,omitempty"`
	Custom   bool   `json:"custom,omitempty"` // owner-supplied custom URL
}

Connection is one way to reach the server, in the shape plex.tv's resources API returns it.

func BuildConnections

func BuildConnections(o BuildOptions) []Connection

BuildConnections returns the connection list for the server, best first: LAN before WAN, https plex.direct before plain http (unless secure connections are disabled), then custom URLs.

func ParseCustomURL

func ParseCustomURL(raw string) (Connection, error)

ParseCustomURL validates an owner-supplied connection URL.

type DiscoverOptions

type DiscoverOptions struct {
	// Target is where M-SEARCH is sent; SSDPMulticast when empty. Tests
	// point it at a loopback responder.
	Target string
	// Wait bounds how long responses are collected (default 3s).
	Wait   time.Duration
	Client *http.Client
}

DiscoverOptions tunes SSDP discovery.

type GDMInfo

type GDMInfo struct {
	Name              string
	MachineIdentifier string
	Port              int
	Version           string
	UpdatedAt         time.Time
	// ContentType defaults to "plex/media-server".
	ContentType string
}

GDMInfo is what the responder advertises.

type GDMOptions

type GDMOptions struct {
	// Info is called per request so name/version changes apply live.
	Info func() GDMInfo
	// Ports to listen on; DefaultGDMPorts when empty.
	Ports []int
	// ListenAddrs, when set, replaces Ports/multicast with plain unicast
	// sockets on these addresses (e.g. "127.0.0.1:0" in tests).
	ListenAddrs []string
	// Announce sends HELLO on start and every AnnounceInterval (default
	// 15m) to the multicast group, and BYE on stop. Off by default: clients
	// discover servers by searching, and current PMS builds rely on that.
	Announce         bool
	AnnounceInterval time.Duration
	Logger           *slog.Logger
	// contains filtered or unexported fields
}

GDMOptions configures a GDMResponder.

type GDMReply

type GDMReply struct {
	From              *net.UDPAddr
	ContentType       string
	Name              string
	MachineIdentifier string
	Port              int
	Version           string
	UpdatedAt         time.Time
	Headers           http.Header
}

GDMReply is one server's answer to a GDM search.

func DiscoverGDM

func DiscoverGDM(ctx context.Context, target string, wait time.Duration) ([]GDMReply, error)

DiscoverGDM sends one M-SEARCH to target ("239.0.0.250:32414" when empty) and collects replies until ctx is done or wait elapses.

func ParseGDMReply

func ParseGDMReply(b []byte) (*GDMReply, error)

ParseGDMReply parses an "HTTP/1.0 200 OK" GDM reply.

type GDMResponder

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

GDMResponder answers GDM discovery on the LAN.

func NewGDMResponder

func NewGDMResponder(o GDMOptions) *GDMResponder

NewGDMResponder returns a responder; call Start.

func (*GDMResponder) Addrs

func (g *GDMResponder) Addrs() []net.Addr

Addrs returns the bound local addresses.

func (*GDMResponder) Start

func (g *GDMResponder) Start(ctx context.Context) error

Start opens the sockets and serves until ctx is done or Stop is called. Ports that cannot be bound (e.g. a real PMS on the same host) are logged and skipped; Start fails only when no socket could be opened.

func (*GDMResponder) Stop

func (g *GDMResponder) Stop()

Stop closes the sockets and waits for the serve loops.

type IGD

type IGD struct {
	Location     string // device description URL
	FriendlyName string
	ServiceType  string
	ControlURL   string
	// LocalIP is this host's address on the interface that reaches the
	// gateway: the internal client for port mappings.
	LocalIP net.IP
	Client  *http.Client
}

IGD is a discovered UPnP Internet Gateway Device connection service.

func DiscoverIGD

func DiscoverIGD(ctx context.Context, o DiscoverOptions) (*IGD, error)

DiscoverIGD finds a UPnP gateway with a WAN connection service.

func LoadIGD

func LoadIGD(ctx context.Context, client *http.Client, location string) (*IGD, error)

LoadIGD fetches a device description and picks its best WAN connection service.

func (*IGD) AddPortMapping

func (g *IGD) AddPortMapping(ctx context.Context, protocol string, external, internal int, internalClient net.IP, description string, lease time.Duration) error

AddPortMapping maps external TCP/UDP port to internalClient:internalPort for lease (0 = permanent).

func (*IGD) DeletePortMapping

func (g *IGD) DeletePortMapping(ctx context.Context, protocol string, external int) error

DeletePortMapping removes a mapping.

func (*IGD) ExternalIP

func (g *IGD) ExternalIP(ctx context.Context) (net.IP, error)

ExternalIP asks the gateway for its WAN address.

func (*IGD) GetSpecificPortMapping

func (g *IGD) GetSpecificPortMapping(ctx context.Context, protocol string, external int) (*PortMappingEntry, error)

GetSpecificPortMapping looks up the mapping for an external port.

type InterfaceAddr

type InterfaceAddr struct {
	Interface string
	IP        net.IP
	Net       *net.IPNet
}

InterfaceAddr is one usable LAN address.

func LANAddresses

func LANAddresses(opts LANOptions) ([]InterfaceAddr, error)

LANAddresses lists the addresses Plex clients on the local network can reach this machine on: up, non-loopback, non-link-local, non-virtual interfaces. Private IPv4 addresses sort first.

type LANOptions

type LANOptions struct {
	// IncludeIPv6 adds global/ULA IPv6 addresses. PMS publishes IPv4 LAN
	// addresses by default; plex.tv only returns IPv6 connections when the
	// client asks with includeIPv6=1.
	IncludeIPv6 bool
	// IncludeVirtual keeps docker/VPN/hypervisor interfaces.
	IncludeVirtual bool
}

LANOptions tunes LAN address enumeration.

type MappingStatus

type MappingStatus struct {
	Active       bool      `json:"active"`
	Method       string    `json:"method,omitempty"`  // "upnp" or "nat-pmp"
	Gateway      string    `json:"gateway,omitempty"` // IGD friendly name or NAT-PMP address
	ExternalIP   string    `json:"externalIp,omitempty"`
	ExternalPort int       `json:"externalPort,omitempty"`
	InternalIP   string    `json:"internalIp,omitempty"`
	InternalPort int       `json:"internalPort,omitempty"`
	Lease        string    `json:"lease,omitempty"` // "permanent" or a duration
	RenewAt      time.Time `json:"renewAt,omitempty"`
	LastError    string    `json:"lastError,omitempty"`
	UpdatedAt    time.Time `json:"updatedAt,omitempty"`
}

MappingStatus is the port mapper's state for the admin dashboard.

type NATPMP

type NATPMP struct {
	// Gateway is "ip:port" of the gateway's NAT-PMP service.
	Gateway string
	// Tries bounds retransmissions (RFC: 250ms doubling); default 4.
	Tries int
}

NATPMP is a minimal RFC 6886 client.

func NewNATPMP

func NewNATPMP(gateway net.IP) *NATPMP

NewNATPMP returns a client for gateway (port NATPMPPort).

func (*NATPMP) ExternalIP

func (n *NATPMP) ExternalIP(ctx context.Context) (net.IP, error)

ExternalIP asks the gateway for its public address.

func (*NATPMP) Map

func (n *NATPMP) Map(ctx context.Context, protocol string, internal, external int, lease time.Duration) (int, time.Duration, error)

Map requests a TCP ("tcp") or UDP mapping. lease 0 deletes the mapping. It returns the external port and lease the gateway granted.

type NATPMPError

type NATPMPError struct{ Code uint16 }

NATPMPError is a non-zero NAT-PMP result code.

func (*NATPMPError) Error

func (e *NATPMPError) Error() string

type PortMapper

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

PortMapper keeps one TCP port mapping alive on the LAN gateway, via UPnP IGD first and NAT-PMP second. Ensure is idempotent and cheap to call on an interval; Close removes the mapping.

func NewPortMapper

func NewPortMapper(o PortMapperOptions) *PortMapper

NewPortMapper returns a mapper; nothing happens until Ensure.

func (*PortMapper) Close

func (m *PortMapper) Close(ctx context.Context)

Close removes the mapping from the gateway (on shutdown or when remote access is turned off).

func (*PortMapper) Ensure

func (m *PortMapper) Ensure(ctx context.Context, internal, external int) (MappingStatus, error)

Ensure makes sure external TCP port maps to this host's internal port, creating or renewing the mapping when needed. It returns the current status. After a failure it waits a minute before trying again.

func (*PortMapper) Status

func (m *PortMapper) Status() MappingStatus

Status returns a snapshot of the mapping state.

type PortMapperOptions

type PortMapperOptions struct {
	// Description appears in the router's mapping table.
	Description string
	// Lease requested from the gateway (default 2h). Renewed at half-life.
	Lease time.Duration
	// DiscoverIGD finds a UPnP gateway; nil uses SSDP multicast discovery.
	DiscoverIGD func(ctx context.Context) (*IGD, error)
	// Gateway returns the NAT-PMP gateway; nil uses DefaultGateway. Return
	// an error to disable NAT-PMP.
	Gateway func() (net.IP, error)
	// NATPMPPort overrides the NAT-PMP port (tests).
	NATPMPPort int
	Logger     *slog.Logger
	Now        func() time.Time
}

PortMapperOptions configures a PortMapper.

type PortMappingEntry

type PortMappingEntry struct {
	InternalPort   int
	InternalClient string
	Enabled        bool
	Description    string
	Lease          time.Duration
}

PortMappingEntry is an existing mapping on the gateway.

type ProbeResult

type ProbeResult struct {
	URI     string `json:"uri"`
	OK      bool   `json:"ok"`
	Latency string `json:"latency,omitempty"`
	Error   string `json:"error,omitempty"`
}

ProbeResult is one URI's outcome.

type Reachability

type Reachability struct {
	Checked   time.Time `json:"checked"`
	Reachable bool      `json:"reachable"`
	// URI is the first URI that answered with this server's identity.
	URI string `json:"uri,omitempty"`
	// Results has one entry per URI tried.
	Results []ProbeResult `json:"results"`
}

Reachability is the result of a remote-access self-test.

type ReachabilityChecker

type ReachabilityChecker struct {
	Client  *http.Client
	Timeout time.Duration
}

ReachabilityChecker probes connection URIs for this server's /identity.

Probing the WAN address from inside the LAN relies on the router's NAT loopback (hairpinning). Routers without it make a correctly forwarded port look unreachable, so a negative result is reported as such and callers should present it as "could not verify", not as a hard failure.

func NewReachabilityChecker

func NewReachabilityChecker(client *http.Client) *ReachabilityChecker

NewReachabilityChecker uses client (a 10s-timeout client when nil).

func (*ReachabilityChecker) Check

func (r *ReachabilityChecker) Check(ctx context.Context, uris []string, machineID string) Reachability

Check probes uris concurrently and reports whether any answered with machineID.

type UPnPError

type UPnPError struct {
	Code        int
	Description string
	HTTPStatus  int
}

UPnPError is a SOAP fault from the gateway.

func (*UPnPError) Error

func (e *UPnPError) Error() string

type WANDetector

type WANDetector struct {
	Endpoints []string
	Client    *http.Client
	Logger    *slog.Logger
}

WANDetector finds this network's public IPv4 address.

func NewWANDetector

func NewWANDetector(client *http.Client, logger *slog.Logger, endpoints ...string) *WANDetector

NewWANDetector returns a detector over endpoints (DefaultWANEndpoints when empty) with a short per-request timeout.

func (*WANDetector) Detect

func (d *WANDetector) Detect(ctx context.Context) (net.IP, error)

Detect returns the first public IP any endpoint reports.

Jump to

Keyboard shortcuts

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