322 lines
10 KiB
Go
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())
|
|
}
|