Documentation
¶
Overview ¶
Package model evaluates whether a packet from a given source zone reaches a listener, using only a facts snapshot. It is pure: no netlink, no /proc, no Docker, no clock. That is what makes it testable.
Index ¶
- func ForwardNotes(f facts.Facts) []facts.Warning
- func MatchRule(pkt *Packet, r facts.Rule, sets []facts.Set) (Outcome, Action)
- func Traverse(rs facts.Ruleset, family, hook string, pkt *Packet) (Result, []Hit)
- type Action
- type DNAT
- type Endpoint
- type Hit
- type Outcome
- type Packet
- type Result
- type Verdict
- type Zone
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ForwardNotes ¶ added in v1.7.0
ForwardNotes are the destination rewrites in the ruleset whose forwarded ports whyopen will not turn into rows: a rule that constrains no port at all forwards every one of them, and a range forwards more than a table should list.
They are warnings rather than verdicts because whyopen reports one port per row and these name no single port. Saying nothing was the bug this whole scan exists to fix, so what cannot become a row has to become a sentence.
func MatchRule ¶
MatchRule evaluates one rule against the packet. It returns OutcomeUnknown the moment it meets anything it cannot resolve, so a verdict is never built on a guess. sets is the rule's own table's sets (docs/decisions/0005), needed to resolve a facts.ExprLookup; a rule with none can pass nil.
Types ¶
type Action ¶
type Action struct {
Kind string // accept | drop | return | jump | goto | continue | dnat | none
Chain string
DNAT *DNAT
}
Action is what a matching rule does.
type DNAT ¶ added in v0.4.0
DNAT is the single resolved rewrite target for a matched DNAT rule. Unlike facts.DNATInfo, which genuinely describes a port range (MinPort/MaxPort), a DNAT value names one concrete port whyopen will follow, so the field is just Port. It is exported so that report can render it: the verdict schema report writes is deliberately its own shape, not this type marshalled, but it still has to read this one.
type Endpoint ¶
type Endpoint struct {
Kind string `json:"kind"` // socket | publish | forward
Family string `json:"family"`
Proto string `json:"proto"`
BindIP string `json:"bind_ip"`
Port uint16 `json:"port"`
Owner string `json:"owner,omitempty"`
}
Endpoint is something that can receive a connection. It comes from a listening socket on the host, from a Docker publish, because a container's socket lives in another network namespace and never appears in the host's /proc/net/tcp, or from a destination rewrite in the ruleset, because a port this host forwards to another machine has neither a socket nor a publish here (see forwards).
type Hit ¶
type Hit struct {
Family string `json:"family"`
Table string `json:"table"`
Chain string `json:"chain"`
Hook string `json:"hook,omitempty"`
Priority int32 `json:"priority"`
Handle uint64 `json:"handle"`
Action string `json:"action"`
Rule facts.Rule `json:"-"`
}
Hit is one rule the packet actually reached, in traversal order.
type Outcome ¶
type Outcome int
const ( OutcomeMatch Outcome = iota OutcomeNoMatch OutcomeUnknown // OutcomeSkipped is a rule carrying an expression whyopen cannot // resolve, in a rule with no verdict, so neither outcome of the match // changes where the packet goes. It steers traversal exactly like // OutcomeNoMatch and exists only so the rule can still be recorded in // the path: a reader chasing an unresolved expression should see the // rule that carried it, not a gap where it was. OutcomeSkipped )
type Packet ¶
type Packet struct {
Family string // ip | ip6
Proto string // tcp | udp
Src netip.Addr
Dst netip.Addr
SrcPort uint16
DstPort uint16
InIface string
OutIface string
CtState string
DstIsLocal bool
// DNATApplied says a rule earlier on this packet's path rewrote its
// destination, which is what `ct status dnat` asks about. whyopen is
// the thing that applied the rewrite, so this is not an assumption
// about conntrack: it is what happened in the traversal.
//
// It is set for the hook walked after the rewrite, not within the
// prerouting walk that performs it, so a `ct status dnat` rule placed
// after the DNAT rule in the same chain reads as not-yet-rewritten.
// That errs toward reporting the packet as continuing rather than as
// stopped, which is the safe direction for an exposure audit.
DNATApplied bool
// SrcRouteDev is the device the host would route a reply to Src out
// of, or empty when whyopen could not resolve one. It answers a fib
// presence lookup and nothing else. Empty is not "no route exists":
// it is "whyopen cannot say", which is why the evaluator refuses
// rather than concluding the route is missing
// (docs/decisions/0012-fib-and-routes.md).
SrcRouteDev string
// InIfaceIndex and OutIfaceIndex are the kernel interface indices of
// InIface and OutIface, which `meta iif` and `meta oif` compare
// against. Zero means whyopen does not know the index, which is not
// the same as index zero: the evaluator refuses rather than compare
// against a value it made up.
InIfaceIndex uint32
OutIfaceIndex uint32
}
Packet is the synthetic probe pushed through the ruleset. CtState is always "new": whyopen asks whether a fresh inbound connection can be established, which is what makes "ct state related,established accept" correctly fail to match.
type Result ¶
Result is the outcome of one hook: accept, drop or unknown. walkChain uses two further kinds internally, neither of which can escape a base chain: "none" for a regular chain that fell through, and "unwind" for a goto that fell through, which resumes at the base chain's policy rather than in any calling chain.
type Verdict ¶
type Zone ¶
Zone is where the synthetic packet comes from. v1 ships one.
func InternetZone ¶
func InternetZone() Zone
InternetZone sources from documentation ranges, which are never a local address on a sane host, so the packet cannot be mistaken for local traffic.