338 lines
13 KiB
Go
338 lines
13 KiB
Go
package firewalld
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
// timeoutSeconds converts a Go duration to firewalld's int32 timeout in seconds.
|
|
// A zero or negative duration means "no timeout" (the runtime edit persists until
|
|
// reload), which firewalld encodes as 0.
|
|
func timeoutSeconds(d time.Duration) int32 {
|
|
if d <= 0 {
|
|
return 0
|
|
}
|
|
return int32(d / time.Second)
|
|
}
|
|
|
|
// Runtime is the entry point for transient operations on the running firewall.
|
|
// Edits made here last until the next reload unless given a timeout, mirroring
|
|
// firewall-cmd without --permanent.
|
|
type Runtime struct{ c *Conn }
|
|
|
|
// Runtime returns the runtime operation namespace.
|
|
func (c *Conn) Runtime() *Runtime { return &Runtime{c: c} }
|
|
|
|
// Zone returns a handle for runtime operations on the named zone.
|
|
func (r *Runtime) Zone(name string) *RuntimeZone {
|
|
return &RuntimeZone{c: r.c, name: name}
|
|
}
|
|
|
|
// Zones lists the names of all defined zones (runtime view).
|
|
func (r *Runtime) Zones(ctx context.Context) ([]string, error) {
|
|
var zones []string
|
|
err := r.c.call(ctx, basePath, ifaceZone, "getZones", []any{&zones})
|
|
return zones, err
|
|
}
|
|
|
|
// ActiveZone pairs a zone name with the interfaces and sources currently bound to
|
|
// it, as reported by getActiveZones.
|
|
type ActiveZone struct {
|
|
Name string
|
|
Interfaces []string
|
|
Sources []string
|
|
}
|
|
|
|
// ActiveZones returns the zones that currently have interfaces or sources bound.
|
|
// getActiveZones returns a{sa{sas}}: zone -> {"interfaces": [...], "sources": [...]}.
|
|
func (r *Runtime) ActiveZones(ctx context.Context) ([]ActiveZone, error) {
|
|
var raw map[string]map[string][]string
|
|
if err := r.c.call(ctx, basePath, ifaceZone, "getActiveZones", []any{&raw}); err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]ActiveZone, 0, len(raw))
|
|
for name, binding := range raw {
|
|
out = append(out, ActiveZone{
|
|
Name: name,
|
|
Interfaces: binding["interfaces"],
|
|
Sources: binding["sources"],
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ZoneOfInterface returns the zone an interface is bound to at runtime, or "" if
|
|
// none.
|
|
func (r *Runtime) ZoneOfInterface(ctx context.Context, iface string) (string, error) {
|
|
var zone string
|
|
err := r.c.call(ctx, basePath, ifaceZone, "getZoneOfInterface", []any{&zone}, iface)
|
|
return zone, err
|
|
}
|
|
|
|
// ZoneOfSource returns the zone a source is bound to at runtime, or "" if none.
|
|
func (r *Runtime) ZoneOfSource(ctx context.Context, source string) (string, error) {
|
|
var zone string
|
|
err := r.c.call(ctx, basePath, ifaceZone, "getZoneOfSource", []any{&zone}, source)
|
|
return zone, err
|
|
}
|
|
|
|
// Settings reads the runtime settings of a zone. On dict-capable servers it uses
|
|
// getZoneSettings2; otherwise it falls back to the main getZoneSettings tuple.
|
|
func (r *Runtime) Settings(ctx context.Context, zone string) (ZoneSettings, error) {
|
|
if r.c.caps[DictZoneSettings] {
|
|
var d map[string]dbus.Variant
|
|
if err := r.c.call(ctx, basePath, ifaceZone, "getZoneSettings2", []any{&d}, zone); err != nil {
|
|
return ZoneSettings{}, err
|
|
}
|
|
return zoneSettingsFromDict(d), nil
|
|
}
|
|
var raw []any
|
|
if err := r.c.call(ctx, basePath, ifaceMain, "getZoneSettings", []any{&raw}, zone); err != nil {
|
|
return ZoneSettings{}, err
|
|
}
|
|
return zoneSettingsFromTuple(raw), nil
|
|
}
|
|
|
|
// SetSettings replaces a zone's runtime settings wholesale via setZoneSettings2.
|
|
// This is a dict-only path (firewalld >= 0.9); older servers expose no runtime
|
|
// settings writer, so it returns ErrUnsupported there.
|
|
func (r *Runtime) SetSettings(ctx context.Context, zone string, settings ZoneSettings) error {
|
|
if !r.c.caps[DictZoneSettings] {
|
|
return ErrUnsupported
|
|
}
|
|
return r.c.call(ctx, basePath, ifaceZone, "setZoneSettings2", nil, zone, settings.toDict())
|
|
}
|
|
|
|
// RuntimeZone is a handle for transient edits to a single zone. Add methods take a
|
|
// timeout (zero for none) and return the affected zone name from firewalld.
|
|
type RuntimeZone struct {
|
|
c *Conn
|
|
name string
|
|
}
|
|
|
|
// Name returns the zone name this handle targets.
|
|
func (z *RuntimeZone) Name() string { return z.name }
|
|
|
|
// addElement invokes a runtime add* method: (zone, args..., timeout) -> zone.
|
|
func (z *RuntimeZone) addElement(ctx context.Context, method string, timeout time.Duration, args ...any) error {
|
|
full := append([]any{z.name}, args...)
|
|
full = append(full, timeoutSeconds(timeout))
|
|
return z.c.call(ctx, basePath, ifaceZone, method, nil, full...)
|
|
}
|
|
|
|
// removeElement invokes a runtime remove* method: (zone, args...) -> zone.
|
|
func (z *RuntimeZone) removeElement(ctx context.Context, method string, args ...any) error {
|
|
full := append([]any{z.name}, args...)
|
|
return z.c.call(ctx, basePath, ifaceZone, method, nil, full...)
|
|
}
|
|
|
|
// queryElement invokes a runtime query* method: (zone, args...) -> bool.
|
|
func (z *RuntimeZone) queryElement(ctx context.Context, method string, args ...any) (bool, error) {
|
|
var ok bool
|
|
full := append([]any{z.name}, args...)
|
|
err := z.c.call(ctx, basePath, ifaceZone, method, []any{&ok}, full...)
|
|
return ok, err
|
|
}
|
|
|
|
// getStrings invokes a runtime get* method returning "as": (zone) -> []string.
|
|
func (z *RuntimeZone) getStrings(ctx context.Context, method string) ([]string, error) {
|
|
var xs []string
|
|
err := z.c.call(ctx, basePath, ifaceZone, method, []any{&xs}, z.name)
|
|
return xs, err
|
|
}
|
|
|
|
// Port operations.
|
|
func (z *RuntimeZone) AddPort(ctx context.Context, p Port, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addPort", timeout, p.Port, p.Protocol)
|
|
}
|
|
func (z *RuntimeZone) RemovePort(ctx context.Context, p Port) error {
|
|
return z.removeElement(ctx, "removePort", p.Port, p.Protocol)
|
|
}
|
|
func (z *RuntimeZone) QueryPort(ctx context.Context, p Port) (bool, error) {
|
|
return z.queryElement(ctx, "queryPort", p.Port, p.Protocol)
|
|
}
|
|
|
|
// Protocol operations.
|
|
func (z *RuntimeZone) AddProtocol(ctx context.Context, proto string, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addProtocol", timeout, proto)
|
|
}
|
|
func (z *RuntimeZone) RemoveProtocol(ctx context.Context, proto string) error {
|
|
return z.removeElement(ctx, "removeProtocol", proto)
|
|
}
|
|
func (z *RuntimeZone) QueryProtocol(ctx context.Context, proto string) (bool, error) {
|
|
return z.queryElement(ctx, "queryProtocol", proto)
|
|
}
|
|
|
|
// SourcePort operations.
|
|
func (z *RuntimeZone) AddSourcePort(ctx context.Context, p Port, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addSourcePort", timeout, p.Port, p.Protocol)
|
|
}
|
|
func (z *RuntimeZone) RemoveSourcePort(ctx context.Context, p Port) error {
|
|
return z.removeElement(ctx, "removeSourcePort", p.Port, p.Protocol)
|
|
}
|
|
func (z *RuntimeZone) QuerySourcePort(ctx context.Context, p Port) (bool, error) {
|
|
return z.queryElement(ctx, "querySourcePort", p.Port, p.Protocol)
|
|
}
|
|
|
|
// Service operations.
|
|
func (z *RuntimeZone) AddService(ctx context.Context, service string, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addService", timeout, service)
|
|
}
|
|
func (z *RuntimeZone) RemoveService(ctx context.Context, service string) error {
|
|
return z.removeElement(ctx, "removeService", service)
|
|
}
|
|
func (z *RuntimeZone) QueryService(ctx context.Context, service string) (bool, error) {
|
|
return z.queryElement(ctx, "queryService", service)
|
|
}
|
|
|
|
// Source operations. Unlike ports/services, a source binding is not timed, so
|
|
// AddSource takes no timeout (firewalld's addSource has no timeout argument).
|
|
func (z *RuntimeZone) AddSource(ctx context.Context, source string) error {
|
|
return z.removeElement(ctx, "addSource", source)
|
|
}
|
|
func (z *RuntimeZone) RemoveSource(ctx context.Context, source string) error {
|
|
return z.removeElement(ctx, "removeSource", source)
|
|
}
|
|
func (z *RuntimeZone) QuerySource(ctx context.Context, source string) (bool, error) {
|
|
return z.queryElement(ctx, "querySource", source)
|
|
}
|
|
|
|
// RichRule operations.
|
|
func (z *RuntimeZone) AddRichRule(ctx context.Context, rule string, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addRichRule", timeout, rule)
|
|
}
|
|
func (z *RuntimeZone) RemoveRichRule(ctx context.Context, rule string) error {
|
|
return z.removeElement(ctx, "removeRichRule", rule)
|
|
}
|
|
func (z *RuntimeZone) QueryRichRule(ctx context.Context, rule string) (bool, error) {
|
|
return z.queryElement(ctx, "queryRichRule", rule)
|
|
}
|
|
|
|
// ForwardPort operations.
|
|
func (z *RuntimeZone) AddForwardPort(ctx context.Context, fp ForwardPort, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addForwardPort", timeout, fp.Port, fp.Protocol, fp.ToPort, fp.ToAddr)
|
|
}
|
|
func (z *RuntimeZone) RemoveForwardPort(ctx context.Context, fp ForwardPort) error {
|
|
return z.removeElement(ctx, "removeForwardPort", fp.Port, fp.Protocol, fp.ToPort, fp.ToAddr)
|
|
}
|
|
func (z *RuntimeZone) QueryForwardPort(ctx context.Context, fp ForwardPort) (bool, error) {
|
|
return z.queryElement(ctx, "queryForwardPort", fp.Port, fp.Protocol, fp.ToPort, fp.ToAddr)
|
|
}
|
|
|
|
// Masquerade operations. Add takes a timeout; the others do not.
|
|
func (z *RuntimeZone) AddMasquerade(ctx context.Context, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addMasquerade", timeout)
|
|
}
|
|
func (z *RuntimeZone) RemoveMasquerade(ctx context.Context) error {
|
|
return z.removeElement(ctx, "removeMasquerade")
|
|
}
|
|
func (z *RuntimeZone) QueryMasquerade(ctx context.Context) (bool, error) {
|
|
return z.queryElement(ctx, "queryMasquerade")
|
|
}
|
|
|
|
// IcmpBlock operations.
|
|
func (z *RuntimeZone) AddIcmpBlock(ctx context.Context, icmptype string, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addIcmpBlock", timeout, icmptype)
|
|
}
|
|
func (z *RuntimeZone) RemoveIcmpBlock(ctx context.Context, icmptype string) error {
|
|
return z.removeElement(ctx, "removeIcmpBlock", icmptype)
|
|
}
|
|
func (z *RuntimeZone) QueryIcmpBlock(ctx context.Context, icmptype string) (bool, error) {
|
|
return z.queryElement(ctx, "queryIcmpBlock", icmptype)
|
|
}
|
|
|
|
// IcmpBlockInversion operations. Add takes a timeout; the others do not.
|
|
func (z *RuntimeZone) AddIcmpBlockInversion(ctx context.Context, timeout time.Duration) error {
|
|
return z.addElement(ctx, "addIcmpBlockInversion", timeout)
|
|
}
|
|
func (z *RuntimeZone) RemoveIcmpBlockInversion(ctx context.Context) error {
|
|
return z.removeElement(ctx, "removeIcmpBlockInversion")
|
|
}
|
|
func (z *RuntimeZone) QueryIcmpBlockInversion(ctx context.Context) (bool, error) {
|
|
return z.queryElement(ctx, "queryIcmpBlockInversion")
|
|
}
|
|
|
|
// Interface operations bind and unbind network interfaces to the zone at runtime.
|
|
func (z *RuntimeZone) AddInterface(ctx context.Context, iface string) error {
|
|
return z.removeElement(ctx, "addInterface", iface) // no timeout arg on interface add
|
|
}
|
|
func (z *RuntimeZone) RemoveInterface(ctx context.Context, iface string) error {
|
|
return z.removeElement(ctx, "removeInterface", iface)
|
|
}
|
|
func (z *RuntimeZone) QueryInterface(ctx context.Context, iface string) (bool, error) {
|
|
return z.queryElement(ctx, "queryInterface", iface)
|
|
}
|
|
|
|
// ChangeInterface moves an interface into this zone, detaching it from whatever
|
|
// zone currently owns it. Unlike AddInterface, it does not fail if the interface
|
|
// is already bound elsewhere (firewall-cmd --change-interface).
|
|
func (z *RuntimeZone) ChangeInterface(ctx context.Context, iface string) error {
|
|
return z.removeElement(ctx, "changeZoneOfInterface", iface)
|
|
}
|
|
|
|
// ChangeSource moves a source into this zone, detaching it from its current zone.
|
|
func (z *RuntimeZone) ChangeSource(ctx context.Context, source string) error {
|
|
return z.removeElement(ctx, "changeZoneOfSource", source)
|
|
}
|
|
|
|
// Getters for the current runtime element lists.
|
|
func (z *RuntimeZone) Services(ctx context.Context) ([]string, error) {
|
|
return z.getStrings(ctx, "getServices")
|
|
}
|
|
func (z *RuntimeZone) Interfaces(ctx context.Context) ([]string, error) {
|
|
return z.getStrings(ctx, "getInterfaces")
|
|
}
|
|
func (z *RuntimeZone) Sources(ctx context.Context) ([]string, error) {
|
|
return z.getStrings(ctx, "getSources")
|
|
}
|
|
func (z *RuntimeZone) Protocols(ctx context.Context) ([]string, error) {
|
|
return z.getStrings(ctx, "getProtocols")
|
|
}
|
|
func (z *RuntimeZone) RichRules(ctx context.Context) ([]string, error) {
|
|
return z.getStrings(ctx, "getRichRules")
|
|
}
|
|
func (z *RuntimeZone) ICMPBlocks(ctx context.Context) ([]string, error) {
|
|
return z.getStrings(ctx, "getIcmpBlocks")
|
|
}
|
|
|
|
// Ports returns the runtime ports as typed pairs.
|
|
func (z *RuntimeZone) Ports(ctx context.Context) ([]Port, error) {
|
|
var raw [][]any
|
|
err := z.c.call(ctx, basePath, ifaceZone, "getPorts", []any{&raw}, z.name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return asPorts(toAnySlice(raw)), nil
|
|
}
|
|
|
|
// SourcePorts returns the runtime source ports as typed pairs.
|
|
func (z *RuntimeZone) SourcePorts(ctx context.Context) ([]Port, error) {
|
|
var raw [][]any
|
|
err := z.c.call(ctx, basePath, ifaceZone, "getSourcePorts", []any{&raw}, z.name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return asPorts(toAnySlice(raw)), nil
|
|
}
|
|
|
|
// ForwardPorts returns the runtime forward ports as typed values.
|
|
func (z *RuntimeZone) ForwardPorts(ctx context.Context) ([]ForwardPort, error) {
|
|
var raw [][]any
|
|
err := z.c.call(ctx, basePath, ifaceZone, "getForwardPorts", []any{&raw}, z.name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return asForwardPorts(toAnySlice(raw)), nil
|
|
}
|
|
|
|
// toAnySlice re-wraps a [][]any as []any so the shared tuple
|
|
// helpers (asPorts/asForwardPorts) can consume it.
|
|
func toAnySlice(raw [][]any) []any {
|
|
out := make([]any, len(raw))
|
|
for i := range raw {
|
|
out[i] = raw[i]
|
|
}
|
|
return out
|
|
}
|