Fixed
This commit is contained in:
parent
98aa68f7ed
commit
9b747f7acb
3 changed files with 135 additions and 224 deletions
185
apf_linux.go
185
apf_linux.go
|
|
@ -117,14 +117,11 @@ func NewAPF(ctx context.Context, rulePrefix string) (*APF, error) {
|
||||||
return apf, nil
|
return apf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the backend identifier for apf.
|
|
||||||
// Type returns the backend identifier for apf.
|
// Type returns the backend identifier for apf.
|
||||||
func (f *APF) Type() string {
|
func (f *APF) Type() string {
|
||||||
return APFType
|
return APFType
|
||||||
}
|
}
|
||||||
|
|
||||||
// hook returns the managed pre-hook script used to inject iptables rules for
|
|
||||||
// features apf's native config cannot express.
|
|
||||||
// Capabilities reports the firewall features apf supports.
|
// Capabilities reports the firewall features apf supports.
|
||||||
func (f *APF) Capabilities() Capabilities {
|
func (f *APF) Capabilities() Capabilities {
|
||||||
return Capabilities{
|
return Capabilities{
|
||||||
|
|
@ -152,14 +149,11 @@ func (f *APF) Capabilities() Capabilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultPolicy is unsupported: apf has no managed default-policy control.
|
|
||||||
// GetZone returns no zone; apf has no zone support.
|
// GetZone returns no zone; apf has no zone support.
|
||||||
func (f *APF) GetZone(ctx context.Context, iface string) (zoneName string, err error) {
|
func (f *APF) GetZone(ctx context.Context, iface string) (zoneName string, err error) {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePortToken parses a single apf port token: a port or an underscore
|
|
||||||
// range (e.g. "6000_7000").
|
|
||||||
// parsePortToken parses a single apf port token: a port or an underscore
|
// parsePortToken parses a single apf port token: a port or an underscore
|
||||||
// range (e.g. "6000_7000").
|
// range (e.g. "6000_7000").
|
||||||
func (f *APF) parsePortToken(tok string) (PortRange, error) {
|
func (f *APF) parsePortToken(tok string) (PortRange, error) {
|
||||||
|
|
@ -182,8 +176,6 @@ func (f *APF) parsePortToken(tok string) (PortRange, error) {
|
||||||
return pr, nil
|
return pr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// portToken renders a port spec in apf notation: a port or an underscore
|
|
||||||
// range.
|
|
||||||
// ParseConnLimit decodes a conf.apf IG_TCP_CLIMIT/IG_UDP_CLIMIT value
|
// ParseConnLimit decodes a conf.apf IG_TCP_CLIMIT/IG_UDP_CLIMIT value
|
||||||
// ("port:limit,...", port may be an underscore range) into connection-limit
|
// ("port:limit,...", port may be an underscore range) into connection-limit
|
||||||
// rules: apf caps concurrent connections per source and rejects the excess, so
|
// rules: apf caps concurrent connections per source and rejects the excess, so
|
||||||
|
|
@ -222,8 +214,6 @@ func (f *APF) ParseConnLimit(val string, proto Protocol) (rules []*Rule) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// editConnLimit renders a CLIMIT config line with a rule's "port:limit" entry
|
|
||||||
// added or removed, preserving the other entries, and records a config change.
|
|
||||||
// ParseICMPTypes decodes an apf ICMP type list (IG_ICMP_TYPES/EG_ICMP_TYPES for
|
// ParseICMPTypes decodes an apf ICMP type list (IG_ICMP_TYPES/EG_ICMP_TYPES for
|
||||||
// proto ICMP, IG_ICMPV6_TYPES/EG_ICMPV6_TYPES for proto ICMPv6) into accept rules,
|
// proto ICMP, IG_ICMPV6_TYPES/EG_ICMPV6_TYPES for proto ICMPv6) into accept rules,
|
||||||
// one per type. The "all" wildcard (which apf applies as a typeless `-p icmp -j
|
// one per type. The "all" wildcard (which apf applies as a typeless `-p icmp -j
|
||||||
|
|
@ -251,8 +241,6 @@ func (f *APF) ParseICMPTypes(val string, proto Protocol, out bool) (rules []*Rul
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitAdvFields splits an apf advanced rule on ':' while leaving colons inside
|
|
||||||
// bracketed IPv6 addresses (e.g. [2001:db8::1]) intact.
|
|
||||||
// parseAddr parses an apf address value, stripping the bracket notation used
|
// parseAddr parses an apf address value, stripping the bracket notation used
|
||||||
// to protect IPv6 addresses, and normalizing a zero-network to the empty (any)
|
// to protect IPv6 addresses, and normalizing a zero-network to the empty (any)
|
||||||
// address. It reports the family, or false when the value is not an address.
|
// address. It reports the family, or false when the value is not an address.
|
||||||
|
|
@ -279,8 +267,6 @@ func (f *APF) parseAddr(v string) (addr string, fam Family, ok bool) {
|
||||||
return v, family, true
|
return v, family, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// addrField renders an address for an advanced rule, wrapping an IPv6 address
|
|
||||||
// in brackets so it survives the colon-separated field format.
|
|
||||||
// parseStopAction maps a conf.apf ALL_STOP/TCP_STOP/UDP_STOP value to the
|
// parseStopAction maps a conf.apf ALL_STOP/TCP_STOP/UDP_STOP value to the
|
||||||
// action apf actually applies. "DROP", "REJECT" and "PROHIBIT" are valid;
|
// action apf actually applies. "DROP", "REJECT" and "PROHIBIT" are valid;
|
||||||
// anything else (including empty) falls back to the stock default of DROP.
|
// anything else (including empty) falls back to the stock default of DROP.
|
||||||
|
|
@ -297,15 +283,6 @@ func (f *APF) parseStopAction(val string) Action {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopKey names the conf.apf setting apf actually applies to a deny of the
|
|
||||||
// given protocol. A bare-address deny_hosts entry (no protocol/port) is dropped
|
|
||||||
// by trust_hosts's own bare-host branch, which applies ALL_STOP directly. An
|
|
||||||
// advanced entry (proto:flow:s/d=port:s/d=ip) is instead routed through
|
|
||||||
// trust_entry_rule, which ignores ALL_STOP entirely and applies TCP_STOP for a
|
|
||||||
// tcp entry or UDP_STOP for a udp one (files/internals/apf_trust.sh). The three
|
|
||||||
// settings default to DROP and are otherwise fully independent, so an entry read
|
|
||||||
// or matched under the wrong key can report or accept the wrong action whenever
|
|
||||||
// an operator sets them differently.
|
|
||||||
// readStopAction reads the named STOP setting (ALL_STOP/TCP_STOP/UDP_STOP)
|
// readStopAction reads the named STOP setting (ALL_STOP/TCP_STOP/UDP_STOP)
|
||||||
// from a conf.apf-format file, defaulting to the stock DROP when the key is
|
// from a conf.apf-format file, defaulting to the stock DROP when the key is
|
||||||
// absent or the file cannot be read. path is a parameter (rather than always
|
// absent or the file cannot be read. path is a parameter (rather than always
|
||||||
|
|
@ -336,11 +313,6 @@ func (f *APF) readStopAction(path, key string) Action {
|
||||||
return action
|
return action
|
||||||
}
|
}
|
||||||
|
|
||||||
// denyActionFor reads conf.apf's setting for the given protocol (see
|
|
||||||
// stopKey) and returns the action apf actually applies to a deny of that
|
|
||||||
// protocol. deny_hosts encodes no action of its own, so a rule read back must
|
|
||||||
// be stamped with what apf actually applies — otherwise a Drop rule reads back
|
|
||||||
// as Reject, never compares equal to the desired rule, and churns on every Sync.
|
|
||||||
// stopKey names the conf.apf setting apf actually applies to a deny of the
|
// stopKey names the conf.apf setting apf actually applies to a deny of the
|
||||||
// given protocol. A bare-address deny_hosts entry (no protocol/port) is dropped
|
// given protocol. A bare-address deny_hosts entry (no protocol/port) is dropped
|
||||||
// by trust_hosts's own bare-host branch, which applies ALL_STOP directly. An
|
// by trust_hosts's own bare-host branch, which applies ALL_STOP directly. An
|
||||||
|
|
@ -361,26 +333,11 @@ func (f *APF) stopKey(proto Protocol) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readStopAction reads the named STOP setting (ALL_STOP/TCP_STOP/UDP_STOP)
|
|
||||||
// from a conf.apf-format file, defaulting to the stock DROP when the key is
|
|
||||||
// absent or the file cannot be read. path is a parameter (rather than always
|
|
||||||
// APFConf) so this can be exercised against a fixture file in tests.
|
|
||||||
// denyActionFor reads conf.apf's setting for the given protocol (see
|
// denyActionFor reads conf.apf's setting for the given protocol (see
|
||||||
// stopKey) and returns the action apf actually applies to a deny of that
|
|
||||||
// protocol. deny_hosts encodes no action of its own, so a rule read back must
|
|
||||||
// be stamped with what apf actually applies — otherwise a Drop rule reads back
|
|
||||||
// as Reject, never compares equal to the desired rule, and churns on every Sync.
|
|
||||||
func (f *APF) denyActionFor(proto Protocol) Action {
|
func (f *APF) denyActionFor(proto Protocol) Action {
|
||||||
return f.readStopAction(APFConf, f.stopKey(proto))
|
return f.readStopAction(APFConf, f.stopKey(proto))
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveAction resolves the action to stamp on a rule parsed from — or
|
|
||||||
// matched against — allow_hosts/deny_hosts. base is Accept for allow_hosts
|
|
||||||
// (returned unchanged, since parseStopAction never yields Accept and
|
|
||||||
// allow_hosts has no per-protocol distinction) or the ALL_STOP-derived action
|
|
||||||
// for deny_hosts. A deny_hosts entry in apf's advanced syntax with a concrete
|
|
||||||
// tcp or udp protocol is not governed by ALL_STOP (see stopKey), so its
|
|
||||||
// action is re-derived from the matching TCP_STOP/UDP_STOP setting instead.
|
|
||||||
// resolveAction resolves the action to stamp on a rule parsed from — or
|
// resolveAction resolves the action to stamp on a rule parsed from — or
|
||||||
// matched against — allow_hosts/deny_hosts. base is Accept for allow_hosts
|
// matched against — allow_hosts/deny_hosts. base is Accept for allow_hosts
|
||||||
// (returned unchanged, since parseStopAction never yields Accept and
|
// (returned unchanged, since parseStopAction never yields Accept and
|
||||||
|
|
@ -400,8 +357,6 @@ func (f *APF) resolveAction(base Action, proto Protocol) Action {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAdvRule decodes an apf advanced allow/deny rule of the form
|
|
||||||
// proto:flow:s/d=port:s/d=ip. IPv6 addresses use bracket notation.
|
|
||||||
// splitAdvFields splits an apf advanced rule on ':' while leaving colons inside
|
// splitAdvFields splits an apf advanced rule on ':' while leaving colons inside
|
||||||
// bracketed IPv6 addresses (e.g. [2001:db8::1]) intact.
|
// bracketed IPv6 addresses (e.g. [2001:db8::1]) intact.
|
||||||
func (f *APF) splitAdvFields(s string) []string {
|
func (f *APF) splitAdvFields(s string) []string {
|
||||||
|
|
@ -425,9 +380,6 @@ func (f *APF) splitAdvFields(s string) []string {
|
||||||
return append(fields, s[start:])
|
return append(fields, s[start:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAddr parses an apf address value, stripping the bracket notation used
|
|
||||||
// to protect IPv6 addresses, and normalizing a zero-network to the empty (any)
|
|
||||||
// address. It reports the family, or false when the value is not an address.
|
|
||||||
// ParseAdvRule decodes an apf advanced allow/deny rule of the form
|
// ParseAdvRule decodes an apf advanced allow/deny rule of the form
|
||||||
// proto:flow:s/d=port:s/d=ip. IPv6 addresses use bracket notation.
|
// proto:flow:s/d=port:s/d=ip. IPv6 addresses use bracket notation.
|
||||||
func (f *APF) ParseAdvRule(val string, action Action) (r *Rule) {
|
func (f *APF) ParseAdvRule(val string, action Action) (r *Rule) {
|
||||||
|
|
@ -483,8 +435,6 @@ func (f *APF) ParseAdvRule(val string, action Action) (r *Rule) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalAdvRule encodes a rule as an apf advanced allow/deny line. An apf
|
|
||||||
// advanced rule is tcp/udp only and must carry a source or destination address.
|
|
||||||
// ParseIPList reads an apf allow_hosts/deny_hosts file and returns the rules it holds.
|
// ParseIPList reads an apf allow_hosts/deny_hosts file and returns the rules it holds.
|
||||||
func (f *APF) ParseIPList(filePath string, action Action) (rules []*Rule, err error) {
|
func (f *APF) ParseIPList(filePath string, action Action) (rules []*Rule, err error) {
|
||||||
// Read the allow_hosts/deny_hosts rule list.
|
// Read the allow_hosts/deny_hosts rule list.
|
||||||
|
|
@ -574,8 +524,6 @@ func (f *APF) ParseIPList(filePath string, action Action) (rules []*Rule, err er
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// cPortsKey returns the conf.apf CPORTS list a tcp/udp port rule of the given
|
|
||||||
// direction lives in, or "" for a protocol with no such list.
|
|
||||||
// ParsePorts decodes an apf comma-separated port list into accept rules, one per port entry.
|
// ParsePorts decodes an apf comma-separated port list into accept rules, one per port entry.
|
||||||
func (f *APF) ParsePorts(val string, proto Protocol, out bool) (rules []*Rule) {
|
func (f *APF) ParsePorts(val string, proto Protocol, out bool) (rules []*Rule) {
|
||||||
for _, port := range strings.Split(val, ",") {
|
for _, port := range strings.Split(val, ",") {
|
||||||
|
|
@ -604,10 +552,6 @@ func (f *APF) ParsePorts(val string, proto Protocol, out bool) (rules []*Rule) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseICMPTypes decodes an apf ICMP type list (IG_ICMP_TYPES/EG_ICMP_TYPES for
|
|
||||||
// proto ICMP, IG_ICMPV6_TYPES/EG_ICMPV6_TYPES for proto ICMPv6) into accept rules,
|
|
||||||
// one per type. The "all" wildcard (which apf applies as a typeless `-p icmp -j
|
|
||||||
// ACCEPT`) becomes a rule with a nil ICMPType, matching every type.
|
|
||||||
// hook returns the managed pre-hook script used to inject iptables rules for
|
// hook returns the managed pre-hook script used to inject iptables rules for
|
||||||
// features apf's native config cannot express.
|
// features apf's native config cannot express.
|
||||||
func (f *APF) hook() *hookScript {
|
func (f *APF) hook() *hookScript {
|
||||||
|
|
@ -618,7 +562,6 @@ func (f *APF) hook() *hookScript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetZone returns no zone; apf has no zone support.
|
|
||||||
// GetRules returns the current filter rules read from conf.apf, the allow/deny lists, and the pre-hook.
|
// GetRules returns the current filter rules read from conf.apf, the allow/deny lists, and the pre-hook.
|
||||||
func (f *APF) GetRules(ctx context.Context, zoneName string) (rules []*Rule, err error) {
|
func (f *APF) GetRules(ctx context.Context, zoneName string) (rules []*Rule, err error) {
|
||||||
// Read rules from conf.apf
|
// Read rules from conf.apf
|
||||||
|
|
@ -727,11 +670,6 @@ func (f *APF) GetRules(ctx context.Context, zoneName string) (rules []*Rule, err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeBareHostOneWay removes a one-way bare-address host rule. Such a rule is
|
|
||||||
// stored either as its own hook rule or as one direction of a bidirectional plain
|
|
||||||
// allow_hosts/deny_hosts line (a DirAny rule). When a matching plain line exists,
|
|
||||||
// split it: drop the line and re-add the surviving opposite direction as a hook rule
|
|
||||||
// so the untargeted direction keeps its coverage.
|
|
||||||
// portToken renders a port spec in apf notation: a port or an underscore
|
// portToken renders a port spec in apf notation: a port or an underscore
|
||||||
// range.
|
// range.
|
||||||
func (f *APF) portToken(pr PortRange) string {
|
func (f *APF) portToken(pr PortRange) string {
|
||||||
|
|
@ -742,7 +680,6 @@ func (f *APF) portToken(pr PortRange) string {
|
||||||
return fmt.Sprintf("%d_%d", pr.Start, pr.End)
|
return fmt.Sprintf("%d_%d", pr.Start, pr.End)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePorts decodes an apf comma-separated port list into accept rules, one per port entry.
|
|
||||||
// editConnLimit renders a CLIMIT config line with a rule's "port:limit" entry
|
// editConnLimit renders a CLIMIT config line with a rule's "port:limit" entry
|
||||||
// added or removed, preserving the other entries, and records a config change.
|
// added or removed, preserving the other entries, and records a config change.
|
||||||
func (f *APF) editConnLimit(key, val string, r *Rule, remove bool) string {
|
func (f *APF) editConnLimit(key, val string, r *Rule, remove bool) string {
|
||||||
|
|
@ -780,9 +717,6 @@ func (f *APF) editConnLimit(key, val string, r *Rule, remove bool) string {
|
||||||
return fmt.Sprintf(`%s="%s"`, key, strings.Join(kept, ","))
|
return fmt.Sprintf(`%s="%s"`, key, strings.Join(kept, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
// icmpTokens returns the conf.apf type token(s) an icmp/icmpv6 accept rule
|
|
||||||
// contributes to its type list: the numeric type, or "all" when the rule matches
|
|
||||||
// every type (a nil ICMPType, which apf applies as a typeless `-p icmp -j ACCEPT`).
|
|
||||||
// icmpTokens returns the conf.apf type token(s) an icmp/icmpv6 accept rule
|
// icmpTokens returns the conf.apf type token(s) an icmp/icmpv6 accept rule
|
||||||
// contributes to its type list: the numeric type, or "all" when the rule matches
|
// contributes to its type list: the numeric type, or "all" when the rule matches
|
||||||
// every type (a nil ICMPType, which apf applies as a typeless `-p icmp -j ACCEPT`).
|
// every type (a nil ICMPType, which apf applies as a typeless `-p icmp -j ACCEPT`).
|
||||||
|
|
@ -793,7 +727,6 @@ func (f *APF) icmpTokens(r *Rule) []string {
|
||||||
return []string{strconv.Itoa(int(*r.ICMPType))}
|
return []string{strconv.Itoa(int(*r.ICMPType))}
|
||||||
}
|
}
|
||||||
|
|
||||||
// portTokens renders the rule's ports as apf config tokens.
|
|
||||||
// isConnLimitRule reports whether a rule maps onto conf.apf's
|
// isConnLimitRule reports whether a rule maps onto conf.apf's
|
||||||
// IG_TCP_CLIMIT/IG_UDP_CLIMIT: a per-source cap on concurrent inbound
|
// IG_TCP_CLIMIT/IG_UDP_CLIMIT: a per-source cap on concurrent inbound
|
||||||
// connections to a single tcp/udp port (or range) with no address, rejecting the
|
// connections to a single tcp/udp port (or range) with no address, rejecting the
|
||||||
|
|
@ -805,10 +738,6 @@ func (f *APF) isConnLimitRule(r *Rule) bool {
|
||||||
len(r.PortSpecs()) == 1 && r.Action == Reject
|
len(r.PortSpecs()) == 1 && r.Action == Reject
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConnLimit decodes a conf.apf IG_TCP_CLIMIT/IG_UDP_CLIMIT value
|
|
||||||
// ("port:limit,...", port may be an underscore range) into connection-limit
|
|
||||||
// rules: apf caps concurrent connections per source and rejects the excess, so
|
|
||||||
// each entry becomes an inbound reject rule carrying a per-source ConnLimit.
|
|
||||||
// portTokens renders the rule's ports as apf config tokens.
|
// portTokens renders the rule's ports as apf config tokens.
|
||||||
func (f *APF) portTokens(r *Rule) []string {
|
func (f *APF) portTokens(r *Rule) []string {
|
||||||
specs := r.PortSpecs()
|
specs := r.PortSpecs()
|
||||||
|
|
@ -819,7 +748,6 @@ func (f *APF) portTokens(r *Rule) []string {
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditRulePort returns the conf.apf line with the rule's tokens added to or removed from the list that key manages.
|
|
||||||
// EditRulePort returns the conf.apf line with the rule's tokens added to or removed from the list that key manages.
|
// EditRulePort returns the conf.apf line with the rule's tokens added to or removed from the list that key manages.
|
||||||
func (f *APF) EditRulePort(orig, key, val string, r *Rule, remove bool) string {
|
func (f *APF) EditRulePort(orig, key, val string, r *Rule, remove bool) string {
|
||||||
// A connection-limit rule is expressed solely through the CLIMIT config; it
|
// A connection-limit rule is expressed solely through the CLIMIT config; it
|
||||||
|
|
@ -966,7 +894,6 @@ func (f *APF) EditRulePort(orig, key, val string, r *Rule, remove bool) string {
|
||||||
return fmt.Sprintf(`%s="%s"`, key, strings.Join(kept, ","))
|
return fmt.Sprintf(`%s="%s"`, key, strings.Join(kept, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditConf adds or removes a rule in conf.apf, rewriting the file in place.
|
|
||||||
// EditConf adds or removes a rule in conf.apf, rewriting the file in place.
|
// EditConf adds or removes a rule in conf.apf, rewriting the file in place.
|
||||||
func (f *APF) EditConf(ctx context.Context, r *Rule, remove bool) error {
|
func (f *APF) EditConf(ctx context.Context, r *Rule, remove bool) error {
|
||||||
// For port only rules, open the standard config file.
|
// For port only rules, open the standard config file.
|
||||||
|
|
@ -1030,13 +957,6 @@ func (f *APF) EditConf(ctx context.Context, r *Rule, remove bool) error {
|
||||||
return af.Commit()
|
return af.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// nativeICMPv6 reports whether an ICMPv6 rule can be carried by apf's native
|
|
||||||
// IG_ICMPV6_TYPES/EG_ICMPV6_TYPES lists (an address-less accept, optionally typed)
|
|
||||||
// and so belongs in conf.apf rather than the raw-iptables hook. The shared
|
|
||||||
// ruleNeedsHook diverts every ICMPv6 rule to the hook — correct for csf, which has
|
|
||||||
// no native v6 type list — so apf overrides that only for the rules its config can
|
|
||||||
// actually express, leaving an ICMPv6 rule that also needs state/interface/log/
|
|
||||||
// rate matching (which conf.apf cannot carry) on the hook path.
|
|
||||||
// addrField renders an address for an advanced rule, wrapping an IPv6 address
|
// addrField renders an address for an advanced rule, wrapping an IPv6 address
|
||||||
// in brackets so it survives the colon-separated field format.
|
// in brackets so it survives the colon-separated field format.
|
||||||
func (f *APF) addrField(addr string) string {
|
func (f *APF) addrField(addr string) string {
|
||||||
|
|
@ -1046,13 +966,6 @@ func (f *APF) addrField(addr string) string {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseStopAction maps a conf.apf ALL_STOP/TCP_STOP/UDP_STOP value to the
|
|
||||||
// action apf actually applies. "DROP", "REJECT" and "PROHIBIT" are valid;
|
|
||||||
// anything else (including empty) falls back to the stock default of DROP.
|
|
||||||
// PROHIBIT jumps to apf's own PROHIBIT chain, which rejects with an ICMP
|
|
||||||
// (un)reachable-prohibited response — the same reject-like semantics as
|
|
||||||
// REJECT, just a different ICMP code — so it maps to Reject rather than Drop;
|
|
||||||
// this model has no third action to give it.
|
|
||||||
// MarshalAdvRule encodes a rule as an apf advanced allow/deny line. An apf
|
// MarshalAdvRule encodes a rule as an apf advanced allow/deny line. An apf
|
||||||
// advanced rule is tcp/udp only and must carry a source or destination address.
|
// advanced rule is tcp/udp only and must carry a source or destination address.
|
||||||
func (f *APF) MarshalAdvRule(r *Rule) (string, error) {
|
func (f *APF) MarshalAdvRule(r *Rule) (string, error) {
|
||||||
|
|
@ -1105,7 +1018,6 @@ func (f *APF) MarshalAdvRule(r *Rule) (string, error) {
|
||||||
return strings.Join(parts, ":"), nil
|
return strings.Join(parts, ":"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseIPList reads an apf allow_hosts/deny_hosts file and returns the rules it holds.
|
|
||||||
// EditIPList adds or removes a rule in an apf allow_hosts/deny_hosts file, rewriting it in place.
|
// EditIPList adds or removes a rule in an apf allow_hosts/deny_hosts file, rewriting it in place.
|
||||||
func (f *APF) EditIPList(ctx context.Context, filePath string, action Action, r *Rule, remove bool) error {
|
func (f *APF) EditIPList(ctx context.Context, filePath string, action Action, r *Rule, remove bool) error {
|
||||||
// Read the allow_hosts/deny_hosts rule list.
|
// Read the allow_hosts/deny_hosts rule list.
|
||||||
|
|
@ -1335,8 +1247,6 @@ func (f *APF) EditIPList(ctx context.Context, filePath string, action Action, r
|
||||||
return af.Commit()
|
return af.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// removePlainHost drops the bidirectional plain allow_hosts/deny_hosts line backing
|
|
||||||
// the DirAny rule e, choosing the list by the rule's action.
|
|
||||||
// isConfRule reports whether a rule is managed in conf.apf: an address-less
|
// isConfRule reports whether a rule is managed in conf.apf: an address-less
|
||||||
// accept rule of ports (TCP/UDP lists) or ICMP/ICMPv6 types (a nil type is the
|
// accept rule of ports (TCP/UDP lists) or ICMP/ICMPv6 types (a nil type is the
|
||||||
// "all" wildcard).
|
// "all" wildcard).
|
||||||
|
|
@ -1347,10 +1257,6 @@ func (f *APF) isConfRule(r *Rule) bool {
|
||||||
return r.HasPorts() || r.Proto == ICMP || r.Proto == ICMPv6
|
return r.HasPorts() || r.Proto == ICMP || r.Proto == ICMPv6
|
||||||
}
|
}
|
||||||
|
|
||||||
// isConnLimitRule reports whether a rule maps onto conf.apf's
|
|
||||||
// IG_TCP_CLIMIT/IG_UDP_CLIMIT: a per-source cap on concurrent inbound
|
|
||||||
// connections to a single tcp/udp port (or range) with no address, rejecting the
|
|
||||||
// excess.
|
|
||||||
// nativeICMPv6 reports whether an ICMPv6 rule can be carried by apf's native
|
// nativeICMPv6 reports whether an ICMPv6 rule can be carried by apf's native
|
||||||
// IG_ICMPV6_TYPES/EG_ICMPV6_TYPES lists (an address-less accept, optionally typed)
|
// IG_ICMPV6_TYPES/EG_ICMPV6_TYPES lists (an address-less accept, optionally typed)
|
||||||
// and so belongs in conf.apf rather than the raw-iptables hook. The shared
|
// and so belongs in conf.apf rather than the raw-iptables hook. The shared
|
||||||
|
|
@ -1363,12 +1269,6 @@ func (f *APF) nativeICMPv6(r *Rule) bool {
|
||||||
!r.Log && r.RateLimit == nil && f.isConfRule(r)
|
!r.Log && r.RateLimit == nil && f.isConfRule(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipv6Unavailable reports whether adding r would silently write something
|
|
||||||
// apf itself never enforces: a bare IPv6 host in allow_hosts.rules/
|
|
||||||
// deny_hosts.rules, or a native ICMPv6 type, are both no-op'd by apf's own
|
|
||||||
// shell logic when conf.apf's USE_IPV6 is not "1". A rule diverted to the
|
|
||||||
// raw-iptables hook is unaffected — the hook runs ip6tables directly, outside
|
|
||||||
// apf's USE_IPV6-gated logic — so this only applies to the two native paths.
|
|
||||||
// ipv6Unavailable reports whether adding r would silently write something
|
// ipv6Unavailable reports whether adding r would silently write something
|
||||||
// apf itself never enforces: a bare IPv6 host in allow_hosts.rules/
|
// apf itself never enforces: a bare IPv6 host in allow_hosts.rules/
|
||||||
// deny_hosts.rules, or a native ICMPv6 type, are both no-op'd by apf's own
|
// deny_hosts.rules, or a native ICMPv6 type, are both no-op'd by apf's own
|
||||||
|
|
@ -1385,12 +1285,6 @@ func (f *APF) ipv6Unavailable(r *Rule) bool {
|
||||||
return r.impliedFamily() == IPv6 && (r.Source != "" || r.Destination != "")
|
return r.impliedFamily() == IPv6 && (r.Source != "" || r.Destination != "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// barePortAccept reports whether a rule is an address-less tcp/udp port accept —
|
|
||||||
// the shape apf stores either in a dual-stack conf.apf CPORTS list (a FamilyAny
|
|
||||||
// port) or, per family, through the raw-iptables hook (a single-family port, or a
|
|
||||||
// FamilyAny that GetRules merged back from a v4+v6 hook pair). Both the add-time hook
|
|
||||||
// decision (dualStackPortNeedsHook) and the remove-time split/clear
|
|
||||||
// (removeDualStackPort) build on it.
|
|
||||||
// barePortAccept reports whether a rule is an address-less tcp/udp port accept —
|
// barePortAccept reports whether a rule is an address-less tcp/udp port accept —
|
||||||
// the shape apf stores either in a dual-stack conf.apf CPORTS list (a FamilyAny
|
// the shape apf stores either in a dual-stack conf.apf CPORTS list (a FamilyAny
|
||||||
// port) or, per family, through the raw-iptables hook (a single-family port, or a
|
// port) or, per family, through the raw-iptables hook (a single-family port, or a
|
||||||
|
|
@ -1402,14 +1296,6 @@ func (f *APF) barePortAccept(r *Rule) bool {
|
||||||
r.Source == "" && r.Destination == "" && r.Action == Accept
|
r.Source == "" && r.Destination == "" && r.Action == Accept
|
||||||
}
|
}
|
||||||
|
|
||||||
// dualStackPortNeedsHook reports whether a bare tcp/udp port accept pinned to a
|
|
||||||
// single family must be injected through the hook. apf's IG_*_CPORTS/EG_*_CPORTS
|
|
||||||
// lists are dual-stack — one list applied to both the ip and ip6 tables — so they
|
|
||||||
// express only a FamilyAny port (unlike csf, whose TCP_IN/TCP6_IN split the
|
|
||||||
// families). A single-family one is written per-family through the hook instead,
|
|
||||||
// whose iptables (or ip6tables) rule carries just that family; removing one family
|
|
||||||
// of a FamilyAny CPORTS entry splits it (see removeDualStackPort). ICMP keeps its
|
|
||||||
// concrete family (its type lists are per-family), so this gates on a port match.
|
|
||||||
// dualStackPortNeedsHook reports whether a bare tcp/udp port accept pinned to a
|
// dualStackPortNeedsHook reports whether a bare tcp/udp port accept pinned to a
|
||||||
// single family must be injected through the hook. apf's IG_*_CPORTS/EG_*_CPORTS
|
// single family must be injected through the hook. apf's IG_*_CPORTS/EG_*_CPORTS
|
||||||
// lists are dual-stack — one list applied to both the ip and ip6 tables — so they
|
// lists are dual-stack — one list applied to both the ip and ip6 tables — so they
|
||||||
|
|
@ -1422,15 +1308,6 @@ func (f *APF) dualStackPortNeedsHook(r *Rule) bool {
|
||||||
return f.barePortAccept(r) && r.impliedFamily() != FamilyAny
|
return f.barePortAccept(r) && r.impliedFamily() != FamilyAny
|
||||||
}
|
}
|
||||||
|
|
||||||
// needsHook reports whether a rule must be injected through the apf pre-hook as a
|
|
||||||
// raw iptables rule because apf's native config cannot express it. It is the single
|
|
||||||
// gate between the hook path and apf's config files: everything it rejects (returns
|
|
||||||
// true) is written to the hook, everything it accepts (returns false) maps onto
|
|
||||||
// conf.apf or the allow_hosts/deny_hosts trust files. The shared shapes
|
|
||||||
// (ruleNeedsHook, bareHostOneWay, hostNeedsHook) and the two apf shapes RemoveRule
|
|
||||||
// reuses to route a split (dualStackPortNeedsHook, nativeICMPv6) keep their own
|
|
||||||
// predicates; the apf-only, single-use port/source-port/connlimit/icmp tests are
|
|
||||||
// inlined here as their sole caller.
|
|
||||||
// needsHook reports whether a rule must be injected through the apf pre-hook as a
|
// needsHook reports whether a rule must be injected through the apf pre-hook as a
|
||||||
// raw iptables rule because apf's native config cannot express it. It is the single
|
// raw iptables rule because apf's native config cannot express it. It is the single
|
||||||
// gate between the hook path and apf's config files: everything it rejects (returns
|
// gate between the hook path and apf's config files: everything it rejects (returns
|
||||||
|
|
@ -1496,7 +1373,6 @@ func (f *APF) needsHook(r *Rule) bool {
|
||||||
return f.dualStackPortNeedsHook(r)
|
return f.dualStackPortNeedsHook(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditIPList adds or removes a rule in an apf allow_hosts/deny_hosts file, rewriting it in place.
|
|
||||||
// addRule is AddRule's implementation, with the IPv6 gate optional. Restore
|
// addRule is AddRule's implementation, with the IPv6 gate optional. Restore
|
||||||
// passes enforceIPv6Gate false so it can reproduce a Backup snapshot's exact
|
// passes enforceIPv6Gate false so it can reproduce a Backup snapshot's exact
|
||||||
// prior state, including inert entries the gate would reject as fresh no-op
|
// prior state, including inert entries the gate would reject as fresh no-op
|
||||||
|
|
@ -1569,26 +1445,21 @@ func (f *APF) addRule(ctx context.Context, zoneName string, r *Rule, enforceIPv6
|
||||||
return f.EditIPList(ctx, APFDeny, denyAction, r, false)
|
return f.EditIPList(ctx, APFDeny, denyAction, r, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRule adds a rule to apf, routing it to conf.apf, the allow/deny lists, or the pre-hook.
|
|
||||||
// AddRule adds a rule to apf, routing it to conf.apf, the allow/deny lists, or the pre-hook.
|
// AddRule adds a rule to apf, routing it to conf.apf, the allow/deny lists, or the pre-hook.
|
||||||
func (f *APF) AddRule(ctx context.Context, zoneName string, r *Rule) error {
|
func (f *APF) AddRule(ctx context.Context, zoneName string, r *Rule) error {
|
||||||
return f.addRule(ctx, zoneName, r, true)
|
return f.addRule(ctx, zoneName, r, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRule removes a rule from apf, routing it to the config file or pre-hook that holds it.
|
|
||||||
// InsertRule is unsupported: APF organizes rules in config files, not an ordered list.
|
// InsertRule is unsupported: APF organizes rules in config files, not an ordered list.
|
||||||
func (f *APF) InsertRule(ctx context.Context, zoneName string, position int, r *Rule) error {
|
func (f *APF) InsertRule(ctx context.Context, zoneName string, position int, r *Rule) error {
|
||||||
return unsupportedOrdering(f.Type())
|
return unsupportedOrdering(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertNATRule is unsupported: APF stores NAT in a config file it applies as a
|
|
||||||
// whole, with no explicit ordering.
|
|
||||||
// MoveRule is unsupported for the same reason as InsertRule.
|
// MoveRule is unsupported for the same reason as InsertRule.
|
||||||
func (f *APF) MoveRule(ctx context.Context, zoneName string, r *Rule, position int) error {
|
func (f *APF) MoveRule(ctx context.Context, zoneName string, r *Rule, position int) error {
|
||||||
return unsupportedOrdering(f.Type())
|
return unsupportedOrdering(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capabilities reports the firewall features apf supports.
|
|
||||||
// removePlainHost drops the bidirectional plain allow_hosts/deny_hosts line backing
|
// removePlainHost drops the bidirectional plain allow_hosts/deny_hosts line backing
|
||||||
// the DirAny rule e, choosing the list by the rule's action.
|
// the DirAny rule e, choosing the list by the rule's action.
|
||||||
func (f *APF) removePlainHost(ctx context.Context, e *Rule) error {
|
func (f *APF) removePlainHost(ctx context.Context, e *Rule) error {
|
||||||
|
|
@ -1598,13 +1469,6 @@ func (f *APF) removePlainHost(ctx context.Context, e *Rule) error {
|
||||||
return f.EditIPList(ctx, APFDeny, f.denyActionFor(e.Proto), e, true)
|
return f.EditIPList(ctx, APFDeny, f.denyActionFor(e.Proto), e, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeDualStackPort removes a single-family bare tcp/udp port accept. Such a rule
|
|
||||||
// is stored either as its own per-family hook rule or as one family of a dual-stack
|
|
||||||
// conf.apf CPORTS entry (a FamilyAny port). Read the CPORTS list the port would live
|
|
||||||
// in — not the merged rule view, which cannot tell a genuine CPORTS entry from a
|
|
||||||
// pair of single-family hook rules — and split it when present: drop the dual-stack
|
|
||||||
// entry and re-add the surviving opposite family as a hook rule, so the untargeted
|
|
||||||
// family keeps its coverage. Otherwise the rule is a per-family hook rule.
|
|
||||||
// removeBareHostOneWay removes a one-way bare-address host rule. Such a rule is
|
// removeBareHostOneWay removes a one-way bare-address host rule. Such a rule is
|
||||||
// stored either as its own hook rule or as one direction of a bidirectional plain
|
// stored either as its own hook rule or as one direction of a bidirectional plain
|
||||||
// allow_hosts/deny_hosts line (a DirAny rule). When a matching plain line exists,
|
// allow_hosts/deny_hosts line (a DirAny rule). When a matching plain line exists,
|
||||||
|
|
@ -1637,10 +1501,6 @@ func (f *APF) removeBareHostOneWay(ctx context.Context, zoneName string, r *Rule
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// addRule is AddRule's implementation, with the IPv6 gate optional. Restore
|
|
||||||
// passes enforceIPv6Gate false so it can reproduce a Backup snapshot's exact
|
|
||||||
// prior state, including inert entries the gate would reject as fresh no-op
|
|
||||||
// writes.
|
|
||||||
// cPortsKey returns the conf.apf CPORTS list a tcp/udp port rule of the given
|
// cPortsKey returns the conf.apf CPORTS list a tcp/udp port rule of the given
|
||||||
// direction lives in, or "" for a protocol with no such list.
|
// direction lives in, or "" for a protocol with no such list.
|
||||||
func (f *APF) cPortsKey(proto Protocol, output bool) string {
|
func (f *APF) cPortsKey(proto Protocol, output bool) string {
|
||||||
|
|
@ -1657,9 +1517,6 @@ func (f *APF) cPortsKey(proto Protocol, output bool) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// natFile returns the routing file a NAT rule belongs in: source NAT is
|
|
||||||
// applied in POSTROUTING (postroute.rules), destination NAT in PREROUTING
|
|
||||||
// (preroute.rules).
|
|
||||||
// removeDualStackPort removes a single-family bare tcp/udp port accept. Such a rule
|
// removeDualStackPort removes a single-family bare tcp/udp port accept. Such a rule
|
||||||
// is stored either as its own per-family hook rule or as one family of a dual-stack
|
// is stored either as its own per-family hook rule or as one family of a dual-stack
|
||||||
// conf.apf CPORTS entry (a FamilyAny port). Read the CPORTS list the port would live
|
// conf.apf CPORTS entry (a FamilyAny port). Read the CPORTS list the port would live
|
||||||
|
|
@ -1704,15 +1561,6 @@ func (f *APF) removeDualStackPort(ctx context.Context, r *Rule) error {
|
||||||
// which, so remove the rule from both backings — EditConf drops it from the CPORTS
|
// which, so remove the rule from both backings — EditConf drops it from the CPORTS
|
||||||
// list and the hook edit drops both per-family rows, and each no-ops when the rule is
|
// list and the hook edit drops both per-family rows, and each no-ops when the rule is
|
||||||
// absent — clearing the whole merged rule wherever it lives. Unlike the single-family
|
// absent — clearing the whole merged rule wherever it lives. Unlike the single-family
|
||||||
// removeDualStackPort there is no untargeted family to preserve, so nothing is re-added.
|
|
||||||
// removeFamilyAnyPort removes a FamilyAny address-less bare tcp/udp port accept. Its
|
|
||||||
// two families live in a dual-stack conf.apf CPORTS entry (a genuine FamilyAny add),
|
|
||||||
// in a v4+v6 pair in the hook (two separate concrete-family adds that GetRules merged
|
|
||||||
// back into one FamilyAny rule), or split across both. The merged read cannot tell
|
|
||||||
// which, so remove the rule from both backings — EditConf drops it from the CPORTS
|
|
||||||
// list and the hook edit drops both per-family rows, and each no-ops when the rule is
|
|
||||||
// absent — clearing the whole merged rule wherever it lives. Unlike the single-family
|
|
||||||
// removeDualStackPort there is no untargeted family to preserve, so nothing is re-added.
|
|
||||||
func (f *APF) removeFamilyAnyPort(ctx context.Context, r *Rule) error {
|
func (f *APF) removeFamilyAnyPort(ctx context.Context, r *Rule) error {
|
||||||
if err := f.EditConf(ctx, r, true); err != nil {
|
if err := f.EditConf(ctx, r, true); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -1722,7 +1570,6 @@ func (f *APF) removeFamilyAnyPort(ctx context.Context, r *Rule) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRules returns the current filter rules read from conf.apf, the allow/deny lists, and the pre-hook.
|
|
||||||
// RemoveRule removes a rule from apf, routing it to the config file or pre-hook that holds it.
|
// RemoveRule removes a rule from apf, routing it to the config file or pre-hook that holds it.
|
||||||
func (f *APF) RemoveRule(ctx context.Context, zoneName string, r *Rule) error {
|
func (f *APF) RemoveRule(ctx context.Context, zoneName string, r *Rule) error {
|
||||||
// A non-plain-line DirAny target fans out into its two concrete-direction rules,
|
// A non-plain-line DirAny target fans out into its two concrete-direction rules,
|
||||||
|
|
@ -1805,7 +1652,6 @@ func (f *APF) RemoveRule(ctx context.Context, zoneName string, r *Rule) error {
|
||||||
return f.EditIPList(ctx, APFDeny, f.denyActionFor(r.Proto), r, true)
|
return f.EditIPList(ctx, APFDeny, f.denyActionFor(r.Proto), r, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backup captures the current filter and NAT rules managed by this backend.
|
|
||||||
// parseNATLine decodes a raw iptables nat command line back into a NATRule,
|
// parseNATLine decodes a raw iptables nat command line back into a NATRule,
|
||||||
// reporting whether the line is one this backend recognizes.
|
// reporting whether the line is one this backend recognizes.
|
||||||
func (f *APF) parseNATLine(line string) (*NATRule, bool) {
|
func (f *APF) parseNATLine(line string) (*NATRule, bool) {
|
||||||
|
|
@ -1828,7 +1674,6 @@ func (f *APF) parseNATLine(line string) (*NATRule, bool) {
|
||||||
return r, true
|
return r, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseNATFile reads a routing-rules file and returns the NAT rules it holds.
|
|
||||||
// parseNATFile reads a routing-rules file and returns the NAT rules it holds.
|
// parseNATFile reads a routing-rules file and returns the NAT rules it holds.
|
||||||
func (f *APF) parseNATFile(path string) ([]*NATRule, error) {
|
func (f *APF) parseNATFile(path string) ([]*NATRule, error) {
|
||||||
fd, err := os.Open(path)
|
fd, err := os.Open(path)
|
||||||
|
|
@ -1861,7 +1706,6 @@ func (f *APF) parseNATFile(path string) ([]*NATRule, error) {
|
||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNATRules returns the NAT rules held in apf's preroute and postroute routing files.
|
|
||||||
// GetNATRules returns the NAT rules held in apf's preroute and postroute routing files.
|
// GetNATRules returns the NAT rules held in apf's preroute and postroute routing files.
|
||||||
func (f *APF) GetNATRules(ctx context.Context, zoneName string) ([]*NATRule, error) {
|
func (f *APF) GetNATRules(ctx context.Context, zoneName string) ([]*NATRule, error) {
|
||||||
var rules []*NATRule
|
var rules []*NATRule
|
||||||
|
|
@ -1880,9 +1724,6 @@ func (f *APF) GetNATRules(ctx context.Context, zoneName string) ([]*NATRule, err
|
||||||
return merged, nil
|
return merged, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// editNATFile adds or removes a NAT rule's command line(s) in a routing file.
|
|
||||||
// A family-agnostic rule occupies one line per family; both are added or dropped
|
|
||||||
// together. It records whether a reload is needed.
|
|
||||||
// natFamilies lists the address families a NAT rule is written for: a rule
|
// natFamilies lists the address families a NAT rule is written for: a rule
|
||||||
// pinned to a family touches only that command; a family-agnostic rule (e.g. a
|
// pinned to a family touches only that command; a family-agnostic rule (e.g. a
|
||||||
// portless masquerade) is written for both v4 and v6.
|
// portless masquerade) is written for both v4 and v6.
|
||||||
|
|
@ -1897,8 +1738,6 @@ func (f *APF) natFamilies(r *NATRule) []Family {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// natLine encodes a NAT rule as a raw iptables/ip6tables nat-table command
|
|
||||||
// line for the given family, the form apf's shell-sourced routing files expect.
|
|
||||||
// natFile returns the routing file a NAT rule belongs in: source NAT is
|
// natFile returns the routing file a NAT rule belongs in: source NAT is
|
||||||
// applied in POSTROUTING (postroute.rules), destination NAT in PREROUTING
|
// applied in POSTROUTING (postroute.rules), destination NAT in PREROUTING
|
||||||
// (preroute.rules).
|
// (preroute.rules).
|
||||||
|
|
@ -1909,7 +1748,6 @@ func (f *APF) natFile(r *NATRule) string {
|
||||||
return APFPreroute
|
return APFPreroute
|
||||||
}
|
}
|
||||||
|
|
||||||
// natCommand returns the iptables command name for a family.
|
|
||||||
// natCommand returns the iptables command name for a family.
|
// natCommand returns the iptables command name for a family.
|
||||||
func (f *APF) natCommand(fam Family) string {
|
func (f *APF) natCommand(fam Family) string {
|
||||||
if fam == IPv6 {
|
if fam == IPv6 {
|
||||||
|
|
@ -1918,9 +1756,6 @@ func (f *APF) natCommand(fam Family) string {
|
||||||
return "iptables"
|
return "iptables"
|
||||||
}
|
}
|
||||||
|
|
||||||
// natFamilies lists the address families a NAT rule is written for: a rule
|
|
||||||
// pinned to a family touches only that command; a family-agnostic rule (e.g. a
|
|
||||||
// portless masquerade) is written for both v4 and v6.
|
|
||||||
// natLine encodes a NAT rule as a raw iptables/ip6tables nat-table command
|
// natLine encodes a NAT rule as a raw iptables/ip6tables nat-table command
|
||||||
// line for the given family, the form apf's shell-sourced routing files expect.
|
// line for the given family, the form apf's shell-sourced routing files expect.
|
||||||
func (f *APF) natLine(r *NATRule, fam Family) (string, error) {
|
func (f *APF) natLine(r *NATRule, fam Family) (string, error) {
|
||||||
|
|
@ -1934,8 +1769,6 @@ func (f *APF) natLine(r *NATRule, fam Family) (string, error) {
|
||||||
return f.natCommand(fam) + " -t nat " + spec, nil
|
return f.natCommand(fam) + " -t nat " + spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseNATLine decodes a raw iptables nat command line back into a NATRule,
|
|
||||||
// reporting whether the line is one this backend recognizes.
|
|
||||||
// editNATFile adds or removes a NAT rule's command line(s) in a routing file.
|
// editNATFile adds or removes a NAT rule's command line(s) in a routing file.
|
||||||
// A family-agnostic rule occupies one line per family; both are added or dropped
|
// A family-agnostic rule occupies one line per family; both are added or dropped
|
||||||
// together. It records whether a reload is needed.
|
// together. It records whether a reload is needed.
|
||||||
|
|
@ -2042,7 +1875,6 @@ func (f *APF) editNATFile(r *NATRule, remove bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddNATRule adds a NAT rule to apf's preroute or postroute routing file.
|
|
||||||
// AddNATRule adds a NAT rule to apf's preroute or postroute routing file.
|
// AddNATRule adds a NAT rule to apf's preroute or postroute routing file.
|
||||||
func (f *APF) AddNATRule(ctx context.Context, zoneName string, r *NATRule) error {
|
func (f *APF) AddNATRule(ctx context.Context, zoneName string, r *NATRule) error {
|
||||||
if err := r.validate(); err != nil {
|
if err := r.validate(); err != nil {
|
||||||
|
|
@ -2051,14 +1883,12 @@ func (f *APF) AddNATRule(ctx context.Context, zoneName string, r *NATRule) error
|
||||||
return f.editNATFile(r, false)
|
return f.editNATFile(r, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveNATRule removes a NAT rule from apf's preroute or postroute routing file.
|
|
||||||
// InsertNATRule is unsupported: APF stores NAT in a config file it applies as a
|
// InsertNATRule is unsupported: APF stores NAT in a config file it applies as a
|
||||||
// whole, with no explicit ordering.
|
// whole, with no explicit ordering.
|
||||||
func (f *APF) InsertNATRule(ctx context.Context, zoneName string, position int, r *NATRule) error {
|
func (f *APF) InsertNATRule(ctx context.Context, zoneName string, position int, r *NATRule) error {
|
||||||
return unsupportedOrdering(f.Type())
|
return unsupportedOrdering(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveRule is unsupported for the same reason as InsertRule.
|
|
||||||
// RemoveNATRule removes a NAT rule from apf's preroute or postroute routing file.
|
// RemoveNATRule removes a NAT rule from apf's preroute or postroute routing file.
|
||||||
func (f *APF) RemoveNATRule(ctx context.Context, zoneName string, r *NATRule) error {
|
func (f *APF) RemoveNATRule(ctx context.Context, zoneName string, r *NATRule) error {
|
||||||
if err := r.validate(); err != nil {
|
if err := r.validate(); err != nil {
|
||||||
|
|
@ -2067,9 +1897,6 @@ func (f *APF) RemoveNATRule(ctx context.Context, zoneName string, r *NATRule) er
|
||||||
return f.editNATFile(r, true)
|
return f.editNATFile(r, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isConfRule reports whether a rule is managed in conf.apf: an address-less
|
|
||||||
// accept rule of ports (TCP/UDP lists) or ICMP/ICMPv6 types (a nil type is the
|
|
||||||
// "all" wildcard).
|
|
||||||
// Backup captures the current filter and NAT rules managed by this backend.
|
// Backup captures the current filter and NAT rules managed by this backend.
|
||||||
func (f *APF) Backup(ctx context.Context, zoneName string) (*Backup, error) {
|
func (f *APF) Backup(ctx context.Context, zoneName string) (*Backup, error) {
|
||||||
rules, err := f.GetRules(ctx, zoneName)
|
rules, err := f.GetRules(ctx, zoneName)
|
||||||
|
|
@ -2085,7 +1912,6 @@ func (f *APF) Backup(ctx context.Context, zoneName string) (*Backup, error) {
|
||||||
return &Backup{Rules: rules, NATRules: natRules}, nil
|
return &Backup{Rules: rules, NATRules: natRules}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore replaces the managed rules with the contents of a Backup.
|
|
||||||
// Restore replaces the managed rules with the contents of a Backup.
|
// Restore replaces the managed rules with the contents of a Backup.
|
||||||
func (f *APF) Restore(ctx context.Context, zoneName string, backup *Backup) error {
|
func (f *APF) Restore(ctx context.Context, zoneName string, backup *Backup) error {
|
||||||
if backup == nil {
|
if backup == nil {
|
||||||
|
|
@ -2126,49 +1952,41 @@ func (f *APF) Restore(ctx context.Context, zoneName string, backup *Backup) erro
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload restarts apf to apply config changes, but only when a mutation changed its files.
|
|
||||||
// GetDefaultPolicy is unsupported: apf has no managed default-policy control.
|
// GetDefaultPolicy is unsupported: apf has no managed default-policy control.
|
||||||
func (f *APF) GetDefaultPolicy(ctx context.Context, zoneName string) (*DefaultPolicy, error) {
|
func (f *APF) GetDefaultPolicy(ctx context.Context, zoneName string) (*DefaultPolicy, error) {
|
||||||
return nil, unsupportedPolicy(f.Type())
|
return nil, unsupportedPolicy(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultPolicy is unsupported: apf has no managed default-policy control.
|
|
||||||
// SetDefaultPolicy is unsupported: apf has no managed default-policy control.
|
// SetDefaultPolicy is unsupported: apf has no managed default-policy control.
|
||||||
func (f *APF) SetDefaultPolicy(ctx context.Context, zoneName string, policy *DefaultPolicy) error {
|
func (f *APF) SetDefaultPolicy(ctx context.Context, zoneName string, policy *DefaultPolicy) error {
|
||||||
return unsupportedPolicy(f.Type())
|
return unsupportedPolicy(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddressSets is unsupported: apf has no address-set support.
|
|
||||||
// GetAddressSets is unsupported: apf has no address-set support.
|
// GetAddressSets is unsupported: apf has no address-set support.
|
||||||
func (f *APF) GetAddressSets(ctx context.Context) ([]*AddressSet, error) {
|
func (f *APF) GetAddressSets(ctx context.Context) ([]*AddressSet, error) {
|
||||||
return nil, unsupportedSet(f.Type())
|
return nil, unsupportedSet(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddressSet is unsupported: apf has no address-set support.
|
|
||||||
// GetAddressSet is unsupported: apf has no address-set support.
|
// GetAddressSet is unsupported: apf has no address-set support.
|
||||||
func (f *APF) GetAddressSet(ctx context.Context, name string) (*AddressSet, error) {
|
func (f *APF) GetAddressSet(ctx context.Context, name string) (*AddressSet, error) {
|
||||||
return nil, unsupportedSet(f.Type())
|
return nil, unsupportedSet(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAddressSet is unsupported: apf has no address-set support.
|
|
||||||
// AddAddressSet is unsupported: apf has no address-set support.
|
// AddAddressSet is unsupported: apf has no address-set support.
|
||||||
func (f *APF) AddAddressSet(ctx context.Context, set *AddressSet) error {
|
func (f *APF) AddAddressSet(ctx context.Context, set *AddressSet) error {
|
||||||
return unsupportedSet(f.Type())
|
return unsupportedSet(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAddressSet is unsupported: apf has no address-set support.
|
|
||||||
// RemoveAddressSet is unsupported: apf has no address-set support.
|
// RemoveAddressSet is unsupported: apf has no address-set support.
|
||||||
func (f *APF) RemoveAddressSet(ctx context.Context, name string) error {
|
func (f *APF) RemoveAddressSet(ctx context.Context, name string) error {
|
||||||
return unsupportedSet(f.Type())
|
return unsupportedSet(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAddressSetEntry is unsupported: apf has no address-set support.
|
|
||||||
// AddAddressSetEntry is unsupported: apf has no address-set support.
|
// AddAddressSetEntry is unsupported: apf has no address-set support.
|
||||||
func (f *APF) AddAddressSetEntry(ctx context.Context, name, entry string) error {
|
func (f *APF) AddAddressSetEntry(ctx context.Context, name, entry string) error {
|
||||||
return unsupportedSet(f.Type())
|
return unsupportedSet(f.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAddressSetEntry is unsupported: apf has no address-set support.
|
|
||||||
// RemoveAddressSetEntry is unsupported: apf has no address-set support.
|
// RemoveAddressSetEntry is unsupported: apf has no address-set support.
|
||||||
func (f *APF) RemoveAddressSetEntry(ctx context.Context, name, entry string) error {
|
func (f *APF) RemoveAddressSetEntry(ctx context.Context, name, entry string) error {
|
||||||
return unsupportedSet(f.Type())
|
return unsupportedSet(f.Type())
|
||||||
|
|
@ -2187,10 +2005,7 @@ func (f *APF) Reload(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close releases resources held by the manager; apf holds none.
|
|
||||||
// Close releases resources held by the manager; apf holds none.
|
// Close releases resources held by the manager; apf holds none.
|
||||||
func (f *APF) Close(ctx context.Context) error {
|
func (f *APF) Close(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertRule is unsupported: APF organizes rules in config files, not an ordered list.
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -21,64 +21,160 @@ import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Matches a function declaration together with its immediately preceding
|
# Regex used to parse a column-0 `func` declaration line. Groups:
|
||||||
# column-0 `//` doc comments. Groups:
|
# 1: receiver variable (None for a plain function)
|
||||||
# 3: receiver variable (None for a plain function)
|
# 2: receiver type (None for a plain function)
|
||||||
# 4: receiver type (None for a plain function)
|
# 3: function name
|
||||||
# 5: function name
|
DECL_RE = re.compile(r"^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?([A-Za-z_]\w*)\s*\(")
|
||||||
FUNC_RE = re.compile(
|
|
||||||
r"(?m)^((?://.*\n)*)^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?([A-Za-z_]\w*)\s*\("
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def split_blocks(text):
|
def split_blocks(text):
|
||||||
"""Split source into (preamble, [block_dict, ...]).
|
"""Split source into (preamble, [block_dict, ...]).
|
||||||
|
|
||||||
Each block begins with the doc comments immediately preceding a `func` line
|
Each block begins with the doc comment for the function and ends at the
|
||||||
and ends at the start of the next block, so comments stay attached to their
|
start of the next function's doc comment. This keeps comments attached to
|
||||||
function.
|
the function they document, even when the file has been reordered before.
|
||||||
|
|
||||||
|
Doc comments are detected by their first line: a column-0 `//` line whose
|
||||||
|
leading word is the function name. The first function in the file is used
|
||||||
|
to delimit the preamble.
|
||||||
"""
|
"""
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
func_lines = [i for i, line in enumerate(lines) if line.startswith("func ")]
|
func_lines = [i for i, line in enumerate(lines) if line.startswith("func ")]
|
||||||
if not func_lines:
|
if not func_lines:
|
||||||
return text, []
|
return text, []
|
||||||
|
|
||||||
decl_re = re.compile(r"^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?([A-Za-z_]\w*)\s*\(")
|
# Parse all function declarations.
|
||||||
|
funcs = []
|
||||||
def comment_start(func_line):
|
|
||||||
i = func_line - 1
|
|
||||||
# Allow a single blank line between the previous block and the doc comment.
|
|
||||||
if i >= 0 and lines[i].strip() == "":
|
|
||||||
i -= 1
|
|
||||||
while i >= 0 and lines[i].startswith("//"):
|
|
||||||
i -= 1
|
|
||||||
return i + 1
|
|
||||||
|
|
||||||
comment_starts = [comment_start(fl) for fl in func_lines]
|
|
||||||
first_start = comment_starts[0]
|
|
||||||
preamble_lines = lines[:first_start]
|
|
||||||
preamble = "\n".join(preamble_lines)
|
|
||||||
if preamble_lines:
|
|
||||||
preamble += "\n"
|
|
||||||
|
|
||||||
blocks = []
|
|
||||||
for idx, fl in enumerate(func_lines):
|
for idx, fl in enumerate(func_lines):
|
||||||
end_line = func_lines[idx + 1] if idx + 1 < len(func_lines) else len(lines)
|
m = DECL_RE.match(lines[fl])
|
||||||
block_lines = lines[comment_starts[idx] : end_line]
|
|
||||||
block_text = "\n".join(block_lines)
|
|
||||||
if block_text and not block_text.endswith("\n"):
|
|
||||||
block_text += "\n"
|
|
||||||
|
|
||||||
m = decl_re.match(lines[fl])
|
|
||||||
if not m:
|
if not m:
|
||||||
die(f"could not parse declaration: {lines[fl]}")
|
die(f"could not parse declaration: {lines[fl]}")
|
||||||
assert m
|
assert m
|
||||||
blocks.append(
|
funcs.append(
|
||||||
{
|
{
|
||||||
"recv_var": m.group(1),
|
"recv_var": m.group(1),
|
||||||
"recv_type": m.group(2),
|
"recv_type": m.group(2),
|
||||||
"name": m.group(3),
|
"name": m.group(3),
|
||||||
"text": block_text,
|
"func_line": fl,
|
||||||
|
"body_end": func_lines[idx + 1]
|
||||||
|
if idx + 1 < len(func_lines)
|
||||||
|
else len(lines),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
func_names = {f["name"] for f in funcs}
|
||||||
|
|
||||||
|
# Find all doc comment blocks in the file. A column-0 `//` sequence is split
|
||||||
|
# into separate doc comments at lines that start with another function name.
|
||||||
|
doc_re = re.compile(r"//\s+([a-zA-Z_][a-zA-Z0-9_]*)\b")
|
||||||
|
doc_comments = {} # name -> list of comment blocks (each block is a list of lines)
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
if not lines[i].startswith("//"):
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
seq = []
|
||||||
|
while i < len(lines) and lines[i].startswith("//"):
|
||||||
|
seq.append(lines[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
current = []
|
||||||
|
for line in seq:
|
||||||
|
mm = doc_re.match(line)
|
||||||
|
if mm and mm.group(1) in func_names and current:
|
||||||
|
# Line starts a new doc comment. Save the current one if valid.
|
||||||
|
first = current[0]
|
||||||
|
fm = doc_re.match(first)
|
||||||
|
if fm and fm.group(1) in func_names:
|
||||||
|
doc_comments.setdefault(fm.group(1), []).append(current)
|
||||||
|
current = [line]
|
||||||
|
else:
|
||||||
|
current.append(line)
|
||||||
|
if current:
|
||||||
|
first = current[0]
|
||||||
|
fm = doc_re.match(first)
|
||||||
|
if fm and fm.group(1) in func_names:
|
||||||
|
doc_comments.setdefault(fm.group(1), []).append(current)
|
||||||
|
|
||||||
|
# Deduplicate and choose the longest doc comment for each function.
|
||||||
|
func_comment = {}
|
||||||
|
for fname, blocks in doc_comments.items():
|
||||||
|
seen = set()
|
||||||
|
unique = []
|
||||||
|
for block in blocks:
|
||||||
|
key = "\n".join(block)
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
unique.append(block)
|
||||||
|
if unique:
|
||||||
|
func_comment[fname] = max(unique, key=len)
|
||||||
|
|
||||||
|
# Preamble is everything before the first function's doc comment or func line.
|
||||||
|
first_func = funcs[0]
|
||||||
|
if first_func["name"] in func_comment:
|
||||||
|
comment = func_comment[first_func["name"]]
|
||||||
|
# Find the earliest occurrence of this comment block before the function.
|
||||||
|
preamble_end = first_func["func_line"]
|
||||||
|
for pos in range(first_func["func_line"] - len(comment), -1, -1):
|
||||||
|
if lines[pos : pos + len(comment)] == comment:
|
||||||
|
preamble_end = pos
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
preamble_end = first_func["func_line"]
|
||||||
|
preamble_lines = lines[:preamble_end]
|
||||||
|
preamble = "\n".join(preamble_lines)
|
||||||
|
if preamble_lines:
|
||||||
|
preamble += "\n"
|
||||||
|
|
||||||
|
# Build blocks. Body is from func_line to next_func_line, with the next
|
||||||
|
# function's doc comment stripped off. The last function also has trailing
|
||||||
|
# blank lines stripped so no orphan comments are left at the end of file.
|
||||||
|
blocks = []
|
||||||
|
for idx, f in enumerate(funcs):
|
||||||
|
name = f["name"]
|
||||||
|
is_last = idx == len(funcs) - 1
|
||||||
|
body_end = f["body_end"]
|
||||||
|
|
||||||
|
if is_last:
|
||||||
|
# Skip trailing blanks so orphan comments after the last function are
|
||||||
|
# reachable.
|
||||||
|
while body_end > f["func_line"] and lines[body_end - 1] == "":
|
||||||
|
body_end -= 1
|
||||||
|
|
||||||
|
# Strip trailing // lines (the next function's doc comment, or orphan
|
||||||
|
# comments after the last function).
|
||||||
|
while body_end > f["func_line"] and lines[body_end - 1].startswith("//"):
|
||||||
|
body_end -= 1
|
||||||
|
|
||||||
|
if is_last:
|
||||||
|
# Strip any remaining blanks after the orphan comments.
|
||||||
|
while body_end > f["func_line"] and lines[body_end - 1] == "":
|
||||||
|
body_end -= 1
|
||||||
|
|
||||||
|
body_lines = lines[f["func_line"] : body_end]
|
||||||
|
body_text = "\n".join(body_lines)
|
||||||
|
# Preserve a separator blank line that sits immediately before the next
|
||||||
|
# function's doc comment. Python's split/join drops the blank when it is
|
||||||
|
# the last element of the slice, so add it back explicitly.
|
||||||
|
if (
|
||||||
|
body_lines
|
||||||
|
and body_lines[-1] == ""
|
||||||
|
and any(line != "" for line in lines[body_end:])
|
||||||
|
):
|
||||||
|
body_text += "\n"
|
||||||
|
if body_text and not body_text.endswith("\n"):
|
||||||
|
body_text += "\n"
|
||||||
|
|
||||||
|
comment_text = ""
|
||||||
|
if name in func_comment:
|
||||||
|
comment_text = "\n".join(func_comment[name]) + "\n"
|
||||||
|
|
||||||
|
blocks.append(
|
||||||
|
{
|
||||||
|
"recv_var": f["recv_var"],
|
||||||
|
"recv_type": f["recv_type"],
|
||||||
|
"name": name,
|
||||||
|
"text": comment_text + body_text,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue