go-firewalld/direct.go
2026-07-02 14:08:37 -05:00

322 lines
10 KiB
Go

package firewalld
import "context"
// Direct is the entry point for firewalld's direct interface, which passes rules
// straight to the underlying iptables/nftables backend. It is a low-level escape
// hatch: firewalld does not manage or reconcile direct rules the way it does
// zones. All operations here are runtime (the direct interface has no per-element
// permanent methods; the permanent form is a single config.direct blob).
type Direct struct{ c *Conn }
// Direct returns the direct-interface namespace.
func (c *Conn) Direct() *Direct { return &Direct{c: c} }
// DirectChain is a custom chain added through the direct interface.
type DirectChain struct {
IPV string // "ipv4", "ipv6", or "eb" (ebtables)
Table string // e.g. "filter", "nat"
Chain string
}
// DirectRule is a rule added through the direct interface, including the chain it
// belongs to, its priority, and the raw backend arguments.
type DirectRule struct {
IPV string
Table string
Chain string
Priority int32
Args []string
}
// AddChain adds a custom chain (ipv, table, chain).
func (d *Direct) AddChain(ctx context.Context, ch DirectChain) error {
return d.c.call(ctx, basePath, ifaceDirect, "addChain", nil, ch.IPV, ch.Table, ch.Chain)
}
// RemoveChain removes a custom chain.
func (d *Direct) RemoveChain(ctx context.Context, ch DirectChain) error {
return d.c.call(ctx, basePath, ifaceDirect, "removeChain", nil, ch.IPV, ch.Table, ch.Chain)
}
// QueryChain reports whether a custom chain exists.
func (d *Direct) QueryChain(ctx context.Context, ch DirectChain) (bool, error) {
var ok bool
err := d.c.call(ctx, basePath, ifaceDirect, "queryChain", []any{&ok}, ch.IPV, ch.Table, ch.Chain)
return ok, err
}
// Chains lists the custom chains in a table for an IP family.
func (d *Direct) Chains(ctx context.Context, ipv, table string) ([]string, error) {
var chains []string
err := d.c.call(ctx, basePath, ifaceDirect, "getChains", []any{&chains}, ipv, table)
return chains, err
}
// AllChains lists every custom chain across all families and tables.
func (d *Direct) AllChains(ctx context.Context) ([]DirectChain, error) {
var raw [][]any
if err := d.c.call(ctx, basePath, ifaceDirect, "getAllChains", []any{&raw}); err != nil {
return nil, err
}
out := make([]DirectChain, 0, len(raw))
for _, e := range raw {
ipv, table, chain, _ := quad(e)
out = append(out, DirectChain{IPV: ipv, Table: table, Chain: chain})
}
return out, nil
}
// AddRule adds a direct rule at the given priority.
func (d *Direct) AddRule(ctx context.Context, r DirectRule) error {
return d.c.call(ctx, basePath, ifaceDirect, "addRule", nil, r.IPV, r.Table, r.Chain, r.Priority, r.Args)
}
// RemoveRule removes a direct rule matching the given fields exactly.
func (d *Direct) RemoveRule(ctx context.Context, r DirectRule) error {
return d.c.call(ctx, basePath, ifaceDirect, "removeRule", nil, r.IPV, r.Table, r.Chain, r.Priority, r.Args)
}
// RemoveRules removes all direct rules in a chain.
func (d *Direct) RemoveRules(ctx context.Context, ipv, table, chain string) error {
return d.c.call(ctx, basePath, ifaceDirect, "removeRules", nil, ipv, table, chain)
}
// QueryRule reports whether a direct rule is present.
func (d *Direct) QueryRule(ctx context.Context, r DirectRule) (bool, error) {
var ok bool
err := d.c.call(ctx, basePath, ifaceDirect, "queryRule", []any{&ok}, r.IPV, r.Table, r.Chain, r.Priority, r.Args)
return ok, err
}
// Rules lists the rules in a chain as (priority, args) pairs. The IPV/Table/Chain
// on each returned rule are filled in from the query arguments.
func (d *Direct) Rules(ctx context.Context, ipv, table, chain string) ([]DirectRule, error) {
var raw [][]any
if err := d.c.call(ctx, basePath, ifaceDirect, "getRules", []any{&raw}, ipv, table, chain); err != nil {
return nil, err
}
out := make([]DirectRule, 0, len(raw))
for _, e := range raw {
r := DirectRule{IPV: ipv, Table: table, Chain: chain}
if len(e) >= 1 {
r.Priority = asInt32(e[0])
}
if len(e) >= 2 {
r.Args = asStrings(e[1])
}
out = append(out, r)
}
return out, nil
}
// AllRules lists every direct rule across all families, tables, and chains.
func (d *Direct) AllRules(ctx context.Context) ([]DirectRule, error) {
var raw [][]any
if err := d.c.call(ctx, basePath, ifaceDirect, "getAllRules", []any{&raw}); err != nil {
return nil, err
}
out := make([]DirectRule, 0, len(raw))
for _, e := range raw {
var r DirectRule
if len(e) >= 1 {
r.IPV = asString(e[0])
}
if len(e) >= 2 {
r.Table = asString(e[1])
}
if len(e) >= 3 {
r.Chain = asString(e[2])
}
if len(e) >= 4 {
r.Priority = asInt32(e[3])
}
if len(e) >= 5 {
r.Args = asStrings(e[4])
}
out = append(out, r)
}
return out, nil
}
// Passthrough sends a raw command straight to the backend for an IP family and
// returns the backend's output.
func (d *Direct) Passthrough(ctx context.Context, ipv string, args []string) (string, error) {
var out string
err := d.c.call(ctx, basePath, ifaceDirect, "passthrough", []any{&out}, ipv, args)
return out, err
}
// AddPassthrough persists a passthrough rule for an IP family.
func (d *Direct) AddPassthrough(ctx context.Context, ipv string, args []string) error {
return d.c.call(ctx, basePath, ifaceDirect, "addPassthrough", nil, ipv, args)
}
// RemovePassthrough removes a passthrough rule.
func (d *Direct) RemovePassthrough(ctx context.Context, ipv string, args []string) error {
return d.c.call(ctx, basePath, ifaceDirect, "removePassthrough", nil, ipv, args)
}
// QueryPassthrough reports whether a passthrough rule is present.
func (d *Direct) QueryPassthrough(ctx context.Context, ipv string, args []string) (bool, error) {
var ok bool
err := d.c.call(ctx, basePath, ifaceDirect, "queryPassthrough", []any{&ok}, ipv, args)
return ok, err
}
// Passthroughs lists the passthrough rules for an IP family, each as an argv.
func (d *Direct) Passthroughs(ctx context.Context, ipv string) ([][]string, error) {
var raw [][]string
err := d.c.call(ctx, basePath, ifaceDirect, "getPassthroughs", []any{&raw}, ipv)
return raw, err
}
// DirectPassthrough is a passthrough rule tagged with its IP family, as returned
// by AllPassthroughs.
type DirectPassthrough struct {
IPV string
Args []string
}
// AllPassthroughs lists every passthrough rule across all IP families.
func (d *Direct) AllPassthroughs(ctx context.Context) ([]DirectPassthrough, error) {
var raw [][]any
if err := d.c.call(ctx, basePath, ifaceDirect, "getAllPassthroughs", []any{&raw}); err != nil {
return nil, err
}
out := make([]DirectPassthrough, 0, len(raw))
for _, e := range raw {
var pt DirectPassthrough
if len(e) >= 1 {
pt.IPV = asString(e[0])
}
if len(e) >= 2 {
pt.Args = asStrings(e[1])
}
out = append(out, pt)
}
return out, nil
}
// RemoveAllPassthroughs clears all passthrough rules.
func (d *Direct) RemoveAllPassthroughs(ctx context.Context) error {
return d.c.call(ctx, basePath, ifaceDirect, "removeAllPassthroughs", nil)
}
// DirectConfig is the complete permanent direct configuration: all custom chains,
// rules, and passthroughs. It maps to the config.direct tuple
// "(a(sss)a(sssias)a(sas))".
type DirectConfig struct {
Chains []DirectChain
Rules []DirectRule
Passthroughs []DirectPassthrough
}
// The concrete encoder structs for the permanent direct tuple. Field order is
// load-bearing and must match firewalld's signatures exactly.
type directChainTuple struct {
IPV, Table, Chain string // (sss)
}
type directRuleTuple struct {
IPV, Table, Chain string
Priority int32
Args []string // (sssias)
}
type directPassthroughTuple struct {
IPV string
Args []string // (sas)
}
type directConfigTuple struct {
Chains []directChainTuple
Rules []directRuleTuple
Passthroughs []directPassthroughTuple
}
func (c DirectConfig) toTuple() directConfigTuple {
t := directConfigTuple{
Chains: make([]directChainTuple, 0, len(c.Chains)),
Rules: make([]directRuleTuple, 0, len(c.Rules)),
Passthroughs: make([]directPassthroughTuple, 0, len(c.Passthroughs)),
}
for _, ch := range c.Chains {
t.Chains = append(t.Chains, directChainTuple{ch.IPV, ch.Table, ch.Chain})
}
for _, r := range c.Rules {
args := r.Args
if args == nil {
args = []string{}
}
t.Rules = append(t.Rules, directRuleTuple{r.IPV, r.Table, r.Chain, r.Priority, args})
}
for _, pt := range c.Passthroughs {
args := pt.Args
if args == nil {
args = []string{}
}
t.Passthroughs = append(t.Passthroughs, directPassthroughTuple{pt.IPV, args})
}
return t
}
// directConfigFromTuple decodes the raw config.direct tuple, length-tolerant.
func directConfigFromTuple(raw []any) DirectConfig {
var c DirectConfig
for _, e := range sliceElems(tupleField(raw, 0)) {
ipv, table, chain, _ := quad(e)
c.Chains = append(c.Chains, DirectChain{IPV: ipv, Table: table, Chain: chain})
}
for _, e := range sliceElems(tupleField(raw, 1)) {
els := sliceElems(e)
var r DirectRule
if len(els) >= 1 {
r.IPV = asString(els[0])
}
if len(els) >= 2 {
r.Table = asString(els[1])
}
if len(els) >= 3 {
r.Chain = asString(els[2])
}
if len(els) >= 4 {
r.Priority = asInt32(els[3])
}
if len(els) >= 5 {
r.Args = asStrings(els[4])
}
c.Rules = append(c.Rules, r)
}
for _, e := range sliceElems(tupleField(raw, 2)) {
els := sliceElems(e)
var pt DirectPassthrough
if len(els) >= 1 {
pt.IPV = asString(els[0])
}
if len(els) >= 2 {
pt.Args = asStrings(els[1])
}
c.Passthroughs = append(c.Passthroughs, pt)
}
return c
}
// Direct returns a handle for the permanent direct configuration.
func (p *Permanent) Direct() *PermDirect { return &PermDirect{c: p.c} }
// PermDirect reads and writes firewalld's permanent direct configuration as a
// single blob (the config.direct object), the permanent counterpart to the
// runtime Direct interface.
type PermDirect struct{ c *Conn }
// Settings reads the whole permanent direct configuration.
func (p *PermDirect) Settings(ctx context.Context) (DirectConfig, error) {
var raw []any
if err := p.c.call(ctx, configPath, ifaceConfigDirect, "getSettings", []any{&raw}); err != nil {
return DirectConfig{}, err
}
return directConfigFromTuple(raw), nil
}
// Update replaces the whole permanent direct configuration.
func (p *PermDirect) Update(ctx context.Context, cfg DirectConfig) error {
return p.c.call(ctx, configPath, ifaceConfigDirect, "update", nil, cfg.toTuple())
}