netpolicy

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package netpolicy holds the one network policy the executor applies to guest traffic: which transports a guest may use, which destinations it may reach, and on which ports. It has two halves that are always checked together. The operator half comes from the executor configuration and denies destinations and transports regardless of what a job asks for; the job half is the destination list the submitter declared for one run.

The package deliberately takes plain strings and addresses rather than configuration or scheduler types, so the same rules can be applied before a connect, before a handshake, on an accepted connection, and on a received datagram, without those call sites sharing anything but this package.

Index

Constants

View Source
const (
	// ICMPDisabled: the operator's network policy switches ICMP off.
	ICMPDisabled = "disabled"
	// ICMPNotPermitted: the process may not open raw sockets (CAP_NET_RAW).
	ICMPNotPermitted = "not_permitted"
	// ICMPPingSocketOnly: raw sockets are refused, but an unprivileged ping
	// socket opens. Guests need raw sockets, so ICMP remains unavailable.
	ICMPPingSocketOnly = "ping_socket_only"
	// ICMPUnsupported: the raw socket failed for another reason.
	ICMPUnsupported = "unsupported"
)

ICMP probe reasons, reported with an unavailable ICMP capability.

Variables

View Source
var (
	// ErrTransportUnavailable is a transport the operator disabled, one the
	// job did not request, or one whose host privileges are missing.
	ErrTransportUnavailable = errors.New("transport unavailable")
	// ErrDenied is an operator denial: a reserved or internal range, an
	// opted-out destination, or a port outside the permitted ranges.
	ErrDenied = errors.New("denied by the operator network policy")
	// ErrNotInPolicy is an address the job did not declare.
	ErrNotInPolicy = errors.New("outside the job's destination policy")
	// ErrUntagged is traffic the run's tagger cannot attribute: an IPv6
	// destination, peer or listener address of a run whose kernel tagger tags
	// IPv4 only. It is refused rather than sent without the attribution the
	// run expects.
	ErrUntagged = errors.New("not tagged on this executor")
)

Refusals. Every enforcement point wraps one of these, so a caller can tell a policy refusal from a network failure without matching on message text.

View Source
var ErrDestinationDenied = fmt.Errorf("%w: the destination opted out", ErrDenied)

ErrDestinationDenied is a destination the dispatcher reported as opted out while the executor runs. It is an operator denial as well, so a guest and a run outcome see it the way they see one from the configuration.

Functions

func DestinationKey added in v0.3.0

func DestinationKey(destination string) string

DestinationKey is the one spelling of a destination the set compares: the normalized address, or the lower-case name without a trailing dot. A SCION address or a port is reduced to its host, as for a declared destination.

func ICMPPermitted

func ICMPPermitted() error

ICMPPermitted reports whether this process may open the raw ICMPv4 sockets the connect_icmp4 import needs. It is a real attempt to open one, not a property of the packet counter or of any configuration key: a host that cannot open the socket must not advertise the capability, and a job that asks for it must be refused before it dials.

The first call probes; later calls return the latest answer. Repeating the probe on every connect would open a raw socket for every guest call, so the executor refreshes it on the capability cadence with RefreshICMP instead.

func MatchesPrefix added in v0.3.0

func MatchesPrefix(prefix netip.Prefix, address netip.Addr) bool

MatchesPrefix applies the same embedded-address aliases as destination policy.

func Normalize

func Normalize(addr netip.Addr) netip.Addr

Normalize is the one spelling of an address the policy works with: no IPv4 mapping, no zone. Two guests writing the same destination differently reach the same decision, and the same accounting key.

func RefreshICMP added in v0.3.0

func RefreshICMP() (reason string, err error)

RefreshICMP probes again and records the answer for ICMPPermitted. A refusal comes with its reason: ICMPNotPermitted, ICMPPingSocketOnly or ICMPUnsupported. Both are empty when raw sockets are permitted.

func ValidateEgressGrant added in v0.3.0

func ValidateEgressGrant(g *pb.EgressGrant) error

Types

type Destination

type Destination struct {
	// Addrs are the admitted peers in the order the resolver returned them.
	// A name with several addresses keeps all of them, so a host that tries
	// them in turn reaches the destination the way dialling the name would
	// have, without any of them escaping the policy. The port is zero for
	// portless transports.
	Addrs []netip.AddrPort
	// Key is the address the submitter declared, which is what per-destination
	// bandwidth is accounted against.
	Key string
	// ServerName is the name the guest asked for, for TLS verification. It is
	// empty when the guest gave an address literal.
	ServerName string
	// contains filtered or unexported fields
}

Destination is one admitted outbound target. It carries the addresses that were checked, so the connection is made to one of exactly those rather than to a name that may resolve differently a moment later.

func (Destination) CheckSocket

func (d Destination) CheckSocket(network, address string) error

CheckSocket is the last gate before a socket is connected. The dialer calls it with the address the operating system is about to use, after any name resolution of its own, so a destination that changed underneath admission never reaches connect. Only an address this destination was admitted for passes, whichever of them the host is trying.

func (Destination) DialAddresses

func (d Destination) DialAddresses() []string

DialAddresses are the literals the host dials, in the order it should try them: the checked addresses, never the name the guest wrote.

type Egress added in v0.3.0

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

Egress is one immutable dispatcher grant. All of a run's sockets share it. It charges admitted payloads and socket connection attempts, not wire packets.

func NewEgress added in v0.3.0

func NewEgress(g *pb.EgressGrant) *Egress

func (*Egress) Charge added in v0.3.0

func (e *Egress) Charge(n int) error

Charge reserves the whole attempted write. Short/failed writes are deliberately not refunded, so cancellation cannot create additional authority.

func (*Egress) CheckAddress added in v0.3.0

func (e *Egress) CheckAddress(address netip.Addr) error

func (*Egress) Connect added in v0.3.0

func (e *Egress) Connect(address netip.Addr) error

Connect charges each actual outgoing socket attempt, including failed connects.

func (*Egress) Wrap added in v0.3.0

func (e *Egress) Wrap(conn net.Conn) net.Conn

Wrap applies the grant after the packet counter has attached its socket.

type Match

type Match struct {
	Key string
}

Match is an admitted address that the host did not resolve itself: an accepted peer, a datagram sender, or a SCION destination. Key is the declared address its traffic is accounted against.

type Operator

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

Operator is a compiled operator policy. It is immutable once parsed and safe for concurrent use.

func Parse

func Parse(spec Spec) (Operator, error)

Parse compiles a Spec. It reports the first unusable value, naming the configuration key, so an operator can correct the file.

func (Operator) CheckAddr

func (o Operator) CheckAddr(addr netip.Addr) error

CheckAddr applies the operator's destination rules to one address. It is the rule that holds whatever a job declared: a destination the operator denies is never reached, on any transport.

func (Operator) CheckHost

func (o Operator) CheckHost(name string) error

CheckHost denies a destination by name, which is how an opted-out target is kept unreachable even while its addresses change.

func (Operator) CheckPort

func (o Operator) CheckPort(port int) error

CheckPort applies the permitted port ranges. Transports without a port, such as ICMP, pass a port of zero and are not checked.

func (Operator) DeniedHosts

func (o Operator) DeniedHosts() []string

DeniedHosts are the names the operator denied, which admission resolves so that their current addresses are denied too.

func (Operator) Enabled

func (o Operator) Enabled(t Transport) bool

Enabled reports whether the operator left transport t switched on.

func (Operator) WithRevocations added in v0.3.0

func (o Operator) WithRevocations(r *Revocations) Operator

WithRevocations returns the operator policy with the destinations r denies added to it. Every run of one executor session shares the same r.

type Option

type Option func(*Policy)

Option adjusts a Policy at construction. Options exist for the seams tests need: name resolution and the host privilege probe.

func WithICMPProbe

func WithICMPProbe(probe func() error) Option

WithICMPProbe replaces the raw-socket privilege probe.

func WithResolver

func WithResolver(resolver Resolver) Option

WithResolver replaces the name resolver.

type Policy

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

Policy is the operator policy and one job's policy together. It is the only object the host functions consult, so a destination cannot be admitted by one half alone.

func New

func New(op Operator, run Run, options ...Option) *Policy

New combines an operator policy with one run's declared policy.

func (*Policy) AdmitAddr

func (p *Policy) AdmitAddr(ctx context.Context, t Transport, addr netip.AddrPort) (result Match, err error)

AdmitAddr admits an address the host already holds: a peer that connected, the sender of a datagram, or a SCION destination this executor parsed. The source port of an inbound peer is whatever it happened to pick, so the permitted destination ports are not applied to it.

func (*Policy) AdmitDestination

func (p *Policy) AdmitDestination(ctx context.Context, t Transport, target string) (result Destination, err error)

AdmitDestination admits one outbound target written as the guest wrote it. It resolves the target and the job's declared destinations with the same resolver, applies the operator's rules to the result, and returns the address the caller must connect to. Nothing is sent to the target here: a refusal happens before any socket is connected.

func (*Policy) Available

func (p *Policy) Available(t Transport) error

Available reports whether transport t may be used at all: the operator's switch, the capability the submission requested, and, for ICMP, whether this process actually holds the privilege the transport needs.

func (*Policy) AvailableListener

func (p *Policy) AvailableListener(t Transport) error

AvailableListener reports whether the job may run the listener for t. Inbound traffic needs both the operator's inbound switch and the listener the submission asked for.

func (*Policy) Egress added in v0.3.0

func (p *Policy) Egress() *Egress

func (*Policy) RefusesIPv6 added in v0.3.0

func (p *Policy) RefusesIPv6() bool

RefusesIPv6 reports whether this run refuses IPv6 destinations and peers. Its listeners are then bound to IPv4 only.

func (*Policy) Revoked added in v0.3.0

func (p *Policy) Revoked(remote string) bool

Revoked reports whether a socket connected to remote, written "host:port", reaches a destination the dispatcher denied: the address itself, or an address this run resolved a denied name to. It uses only the answers the run already obtained, so it never waits for the resolver.

func (*Policy) WatchRevocations added in v0.3.0

func (p *Policy) WatchRevocations(run string, closeRevoked func() int) (stop func())

WatchRevocations registers closeRevoked, which closes this run's sockets that Revoked reports, with the executor's denied destinations, so the update that denies a destination closes them. The run calls stop when it ends.

type Resolver

type Resolver interface {
	LookupNetIP(ctx context.Context, network, host string) ([]netip.Addr, error)
}

Resolver is the name resolution admission uses. The default is the host resolver; tests install their own so that alias and DNS-change behaviour can be observed without a name server.

type Revocations added in v0.3.0

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

Revocations are the destinations the dispatcher denied while the executor runs, as it names them: names and addresses. One set belongs to one executor session and every run's operator policy refers to it, so a change applies to the admission of all of them at once. A run also registers the close of its own sockets here, so the update that denies a destination closes the active connections to it before it returns.

func NewRevocations added in v0.3.0

func NewRevocations() *Revocations

NewRevocations returns an empty set.

func (*Revocations) DenialObservations added in v0.3.0

func (r *Revocations) DenialObservations() (refusedAdmissions, revokedSockets uint64)

DenialObservations counts final network policy admission refusals and sockets actually closed by revocation during this executor session. These are not denied packet/byte counters or evidence about packets already in transit.

func (*Revocations) Update added in v0.3.0

func (r *Revocations) Update(destinations []string, replace bool) []Revoked

Update denies destinations. With replace the list is the complete set and every destination missing from it is allowed again; otherwise it is added. The set changes before any socket is closed, so a connection the guest opens meanwhile is refused, and every registered run then closes the sockets the set now denies. Update returns the runs that closed at least one socket.

type Revoked added in v0.3.0

type Revoked struct {
	Run     string
	Sockets int
}

Revoked is one run whose sockets an update closed.

type Run

type Run struct {
	Egress      *Egress
	Addresses   []string
	RequireICMP bool
	ListenTCP   bool
	ListenUDP   bool
	ListenSCION bool
	// RefuseIPv6 refuses every IPv6 destination and peer with ErrUntagged. It
	// is set for a run whose packets the kernel tagger attributes, which tags
	// IPv4 only. SCION is not affected: its sockets are never tagged, and its
	// traffic is reported untagged instead.
	RefuseIPv6 bool
}

Run is the job's half of the policy: the destinations the submitter declared and the capabilities the submission requested.

type Spec

type Spec struct {
	// Per-transport capability switches.
	TCP, TLS, UDP, ICMP, SCION, Inbound bool
	// LocalTargets re-allows loopback destinations, which the reserved-range
	// rule denies. It is the local profile's switch and nothing else: link
	// local, private and unique-local ranges stay denied.
	LocalTargets bool
	// DeniedDestinations are additional operator denials as a comma-separated
	// list of CIDR blocks, IP addresses and DNS names.
	DeniedDestinations string
	// PermittedPorts are the destination ports a guest may reach, written as
	// the same comma-separated ranges as the listener pool. Empty permits
	// every port.
	PermittedPorts string
}

Spec is the operator policy as the configuration file writes it. It is plain data: the configuration package resolves omitted keys to their documented defaults and hands the result here.

The zero Spec disables every transport, which is what an unconfigured executor should do rather than fall open.

func Defaults

func Defaults() Spec

Defaults is the policy an executor applies where its configuration says nothing: the transports this build supports are on, SCION is off because it cannot be held to the same contract, and every reserved or internal range is denied on every port.

Loopback is denied with them. An executor that measures against services on its own host — the local, wallet-free profile — has to say so with local_targets, so that a deployment which never writes the section cannot be asked by a submitter to reach the services behind its own loopback interface.

type Transport

type Transport uint8

Transport identifies one guest transport. Every enforcement point names the transport it is about, so a capability switch and an error message can be specific about which path was refused.

const (
	TCP Transport = iota
	TLS
	UDP
	ICMP
	SCION
	// Inbound covers accepted TCP connections and datagrams received on the
	// job's UDP listener: traffic a peer starts, not the guest.
	Inbound
)

func (Transport) String

func (t Transport) String() string

Jump to

Keyboard shortcuts

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