Guides

ACLs & firewall

Stateful packet-filter rules bound to interfaces.

SiHA’s firewall is built on two resource types:

  • ACL — an ordered list of match-and-verdict rules with a default action applied to any packet that falls through the list.
  • ACLBinding — attaches one ACL to one direction (INPUT or OUTPUT) of one interface.

The ACL engine is VPP’s acl-plugin, which is stateful: an ACCEPT_STATEFUL rule permits the matched flow and automatically allows its return traffic, so you never write asymmetric rules for established sessions.

Defining an ACL

type: ACLs.acl.siha
metadata: { namespace: acl, id: ext-in }
spec:
  family: DUAL             # IPv4 | IPv6 | DUAL (default when omitted)
  defaultAction: DROP      # DROP or ACCEPT — the catch-all when no rule matches

  rules:
    - description: "Allow established return traffic"
      action: ACCEPT_STATEFUL
      protocol: tcp

    - description: "Allow HTTPS from any"
      action: ACCEPT_STATEFUL
      protocol: tcp
      destinationPortFirst: 443
      destinationPortLast: 443

    - description: "Allow ICMP echo"
      action: ACCEPT
      protocol: icmp
      icmpType: echo-request   # a name (or 'any', or a number)
      icmpCode: 0              # codes are numeric (or 'any')

    - description: "Block a bad prefix explicitly"
      action: DROP
      sourceCidr: 192.0.2.0/24

Rule fields

FieldNotes
actionACCEPT, DROP, or ACCEPT_STATEFUL
protocolProtocol nametcp, udp, icmp, icmp6, sctp (also gre/esp/ah; a raw number works too). Omit or any = all protocols. sihactl reads and prints the name.
sourceCidr / destinationCidrCIDR string; omit = any
sourcePortFirst / sourcePortLastPort range (inclusive); TCP, UDP or SCTP only
destinationPortFirst / destinationPortLastPort range (inclusive); TCP, UDP or SCTP only
icmpTypeA name (echo-request, echo-reply, destination-unreachable, time-exceeded, …), any, or a number. ICMP/ICMPv6 only.
icmpCode0255 or any. ICMP/ICMPv6 only.
tcpFlagsSet / tcpFlagsUnsetTCP flag names — any of syn, ack, fin, rst, psh, urg, ece, cwr (a list like [syn], or comma-separated; a number also works). Match packets with those flags set / cleared. TCP only.

defaultAction accepts ACCEPT or DROP only — ACCEPT_STATEFUL is not valid as a catch-all because it would gate the implicit fall-through path.

Validation rejects cross-field mistakes at apply time (e.g. port fields on a non-TCP/UDP rule), so a permissive-by-typo rule never reaches the dataplane.

Binding an ACL to an interface

type: ACLBindings.acl.siha
metadata: { namespace: acl, id: ext-in }
spec:
  acl: ext-in          # id of the ACL resource above
  interface: ext        # id of the Interface resource (any kind)
  direction: INPUT      # INPUT or OUTPUT
  priority: 10          # lower = earlier in the evaluation list

What you can bind to

An ACLBinding references an Interfaces.link.siha resource id. Any interface kind works: pmd, host-interface, tap, bond, vlan, qinq, vxlan, or loopback. To filter traffic on a bridge segment, bind to the bridge’s BVI interface, which carries the L3 addresses.

You can attach multiple ACLs to the same (interface, direction) pair — priority sets their evaluation order within that list. Lower priority numbers run first; first match wins.

Applying the config

# Write your ACL and binding into a config file, then apply:
sihactl --sihaconfig ~/.siha/config --addr <node-ip>:6443 config apply -f my-acls.yaml

Or use the S= alias for brevity:

S="sihactl --sihaconfig ~/.siha/config --addr <node-ip>:6443"

$S config apply -f my-acls.yaml

Operating

S="sihactl --sihaconfig ~/.siha/config --addr <node-ip>:6443"

$S get acl                    # list ACLs (family, default action, rule count, VPP index)
$S get acl ext-in -o yaml     # full spec + status (VPP acl_index, lastError)
$S get aclbinding             # list bindings (acl, interface, direction, priority)

$S acl counters ext-in        # per-rule packet and byte hit counters

acl counters fetches the ACL’s vpp_acl_index from status, then queries VPP’s stats segment for per-rule counters. It prints one row per rule (including the synthetic default rule appended by the controller) with packet count, byte count, and a short description of the action and match.

Gotchas

Rule order matters. Rules are evaluated top-to-bottom; the first match wins. Put your most specific rules before your broader catch-alls, and put ACCEPT_STATEFUL rules before DROP rules when you want return traffic to succeed.

Default action is mandatory. defaultAction must be DROP or ACCEPT. There is no implicit default — forgetting it is a validation error at apply time.

Stateful tracking is per-ACL, not global. ACCEPT_STATEFUL in one ACL does not automatically open return paths for traffic governed by a different ACL. If you filter both directions of the same flow, make sure the ACCEPT_STATEFUL rule appears in the direction where the initiating packet arrives.

ACCEPT_STATEFUL on the default action is rejected. Use ACCEPT if you want an open-by-default ACL. ACCEPT_STATEFUL only makes sense as a per-rule action, where there is a specific flow to track.

Multiple bindings share state within a direction. When several ACLs are bound to the same (interface, direction), VPP evaluates them in priority order as a single list. The session table is shared, so an ACCEPT_STATEFUL match in ACL #1 opens return paths that ACL #2 would also see.