package config import ( "fmt" "strings" ) type ConntrackAction string const ( ConntrackNoTrack ConntrackAction = "notrack" ConntrackHelper ConntrackAction = "helper" ConntrackDrop ConntrackAction = "drop" ConntrackLog ConntrackAction = "log" ) type ConntrackChain string const ( ConntrackPrerouting ConntrackChain = "prerouting" ConntrackOutput ConntrackChain = "output" ConntrackBoth ConntrackChain = "both" ) type ConntrackRule struct { // Action: notrack (bypass conntrack), helper (assign CT helper), drop (raw table drop), log. Action ConntrackAction `yaml:"action"` // Source zone spec. Supports zone, zone:interface, zone:interface:address. Source string `yaml:"source,omitempty"` // Dest zone spec. Same syntax as Source. Dest string `yaml:"dest,omitempty"` Proto string `yaml:"proto,omitempty"` DPort PortSpec `yaml:"dport,omitempty"` SPort PortSpec `yaml:"sport,omitempty"` // Chain to install the rule in: prerouting, output, or both. Default: prerouting. Chain ConntrackChain `yaml:"chain,omitempty"` // CT helper name (for helper action): ftp, sip, tftp, irc, pptp, amanda, snmp, etc. Helper string `yaml:"helper,omitempty"` // User/group match (only valid for output chain). User string `yaml:"user,omitempty"` Comment string `yaml:"comment,omitempty"` } var validConntrackActions = map[ConntrackAction]bool{ ConntrackNoTrack: true, ConntrackHelper: true, ConntrackDrop: true, ConntrackLog: true, } var validConntrackChains = map[ConntrackChain]bool{ ConntrackPrerouting: true, ConntrackOutput: true, ConntrackBoth: true, "": true, } func (c *Config) validateConntrack() error { for i, ct := range c.Conntrack { if !validConntrackActions[ct.Action] { return fmt.Errorf("conntrack[%d]: unknown action %q", i, ct.Action) } if !validConntrackChains[ct.Chain] { return fmt.Errorf("conntrack[%d]: chain must be prerouting, output, or both", i) } if ct.Action == ConntrackHelper && ct.Helper == "" { return fmt.Errorf("conntrack[%d]: helper name required for helper action", i) } if HasZoneExclusion(ct.Source) || HasZoneExclusion(ct.Dest) { return fmt.Errorf("conntrack[%d]: zone exclusions are not supported in conntrack entries", i) } if err := c.validateZoneRef(ct.Source); err != nil { return fmt.Errorf("conntrack[%d]: source %w", i, err) } if err := c.validateZoneRef(ct.Dest); err != nil { return fmt.Errorf("conntrack[%d]: dest %w", i, err) } if ct.User != "" { chain := ct.Chain if chain == "" { chain = ConntrackPrerouting } if chain == ConntrackPrerouting { return fmt.Errorf("conntrack[%d]: user match only valid for output chain", i) } } } return nil } // HasZoneExclusion reports an all/any zone ref with a "+" or "!" modifier (all+, all!x, any+!x, ...). func HasZoneExclusion(spec string) bool { zones, _, _ := strings.Cut(spec, ":") return (strings.HasPrefix(zones, "all") || strings.HasPrefix(zones, "any")) && strings.ContainsAny(zones[3:], "+!") }