Skip to main content

Per-Interface Features

Without network.interfaces, every feature except egress IDS runs on every interface Synapse picks up from network.iface or network.ifaces; egress IDS has its own list, ids.egress_ifaces, which does nothing unless SYNAPSE_EGRESS_IDS is set in the environment as well. With the block, you say which links get which features.

The block is authoritative. Each entry names the links it matches and the features they get, and a link no entry matches is attached by nothing the block governs — no firewall, no IDS, no smart firewall, no XDP program at all. That is the point of the design rather than a side effect of it: an operator who forgets to list the management interface must not find a deny rule on it.

Two settings outside the block can still put a program on a link it never names, and one of them can drop traffic. Both are in What the block does not cover, which anyone relying on the paragraph above should read.

Leaving a link out is how you protect it

Because the block is the complete list, adding it to a working config narrows what Synapse attaches to. A link you do not list stops being filtered. There is no "everything else" entry: pattern: "*" does match every link, but then any second entry matches one of those links too and the file is refused for the double match. To record a link and leave it alone, give it features: [].

This page describes Synapse 0.8.8. The block is Linux-only — see Linux only — and changes to it take effect at startup, so an edit plus a SIGHUP does not apply it. Restart the agent.

The shape​

network:
interfaces:
wan:
match: { name: eth1 }
features: [firewall, ids, smart_firewall, fingerprint]
lan:
match: { pattern: "enp3s*" }
features: [firewall]
oob:
match: { mac: "52:54:00:12:34:56" }
features: [] # listed, nothing enabled

The keys under interfaces: are logical names you choose — wan, lan, oob above. They appear in the startup log and in error messages, and they are not interface names. Each entry takes three keys:

KeyRequiredDefaultWhat it does
matchyes—which links this entry applies to: exactly one of name, mac or pattern
featuresno[]the features enabled on the matched links
require_nativenofalserefuse to attach rather than fall back to generic XDP

A commented-out example ships in config/config.yaml, under the network: section.

match takes exactly one of three keys. A match with none, or with two, fails the load.

KeyFormMatches
nameeth1the link with exactly this kernel name
mac52:54:00:12:34:56the link with this hardware address, case-insensitive
pattern"enp3s*"every link whose name matches this glob — *, ? and […]

Interface names are not stable: udev renames them, predictable naming changes them between distributions, and swapping a NIC moves them. That is why mac and pattern exist, and why a config meant to survive hardware changes should prefer them to name.

A pattern entry covers every link it matches, and they all get that entry's features. A link reporting no hardware address, or an all-zero one — point-to-point and tunnel links do — never matches a mac.

Three matching rules decide whether the file loads:

  • a name or mac that matches no link on this host fails the load. It would otherwise silently protect nothing.
  • a pattern that matches nothing is accepted in silence. A link matching it may appear later, so it is deliberately not an error — but nothing is printed either, and a pattern that matches nothing looks exactly like one that matches everything you meant. The resolve line below is the only way to tell them apart.
  • a link matched by two entries fails the load, naming both. Each link belongs to one entry, so that its feature set is unambiguous.

The features​

Five features can be scoped to a link. Four of them have a global switch that is checked: listing one whose switch is off is refused rather than accepted, since it would otherwise parse, log as enabled and do nothing. fingerprint is the exception — it is checked against nothing, and what it governs is narrower than its name suggests.

FeatureWhat it enables on the linkGlobal switchIf the switch is off
firewallfirewall rules and access-rule IP filteringfirewall.modeload fails when the mode is none
idsIDS on ingress trafficids.enabledload fails
ids_egressIDS on egress traffic, via TC-egress captureids.enabled, and a build carrying the egress-ids featureload fails; a build without it fails at startup
smart_firewallthe smart firewall's pre-block and reactorfirewall.amygdala.enabledload fails
fingerprintwhich link the TC collectors (JA4S, JA4TS, JA4L, JA4SSH) and the single-interface capture run oncapture.enablednothing is said, and those collectors do not run — but read the caution below before concluding the link yields no fingerprints
fingerprint does not mean "the fingerprints from this link"

Two things follow from it not being checked. The first is the ordinary one: features: [fingerprint] with capture.enabled: false loads cleanly and logs the feature as enabled, while the TC collectors it selects are skipped — as they are during a PCAP replay.

The second matters more, because it runs the other way. JA4T does not come from this feature at all. Those fingerprints are read from the XDP program on every attached link and written out whenever logging.eventbridge_log.enabled is true, which is its default. So a link listed features: [firewall] with no fingerprint still produces JA4T, and turning capture.enabled off does not stop it. The switch for that is logging.eventbridge_log.enabled.

The shipped config.yaml comment names firewall.mode, ids.enabled and firewall.amygdala.enabled — neither of the two switches above.

Two more features exist in the schema and are refused under an interface, because each runs host-wide and listing it on one link would read as "only here":

FeatureWhy it cannot be scopedWhere it is controlled
ssl_uprobeit hooks a library, not a linkcapture.enabled
tls_offloadit sits behind the proxy, after TLSthe IDS post-TLS offload settings

Two more rules on the set itself: a feature listed twice fails the load, and smart_firewall requires firewall on the same interface — it enforces through the firewall datapath and registers the interface for rules on its own, so without firewall it would install rules on an interface that asked for none.

The smart firewall runs on exactly one interface

Listing smart_firewall on two or more fails at startup, naming them. If firewall.amygdala.iface is also set and names a different link, that fails too — drop the key and let the block decide. firewall.amygdala.backend must be auto or xdp; the others are host-wide.

What it replaces, and what it refuses​

The block does not merge with the older interface keys, it replaces them. Three combinations fail the load:

Also setWhy it is refused
network.iface (other than auto) or network.ifaces, including --iface, --ifaces, NETWORK_IFACE and NETWORK_IFACEStwo lists of interfaces, one of them authoritative. List every interface under network.interfaces instead
ids.egress_ifacessuperseded by the ids_egress feature, which names the links in one place
firewall.mode: nftables or iptablesthose backends install host-wide chains and cannot yet be limited to the listed links, so only auto and xdp are accepted beside the block. (none loads, as long as no entry lists firewall)

require_native​

An interface whose driver has no native XDP support can still be attached in generic (SKB) mode, which works but costs performance. require_native: true refuses to attach to such a link rather than running there degraded. It is per-entry, so one uplink can insist on native XDP while a secondary link accepts generic.

Know what the refusal costs before setting it. Synapse logs an error, skips that interface and keeps running — it is not a startup failure. The link then gets no firewall, no ingress IDS and no XDP program, which is the protection it was listed for. On an uplink that is the difference between a slower uplink and an unprotected one.

It is not quite an unlisted link, though. The TC-attached collectors go by what the configuration planned rather than by what actually attached, so they can still land on a refused link: the JA4 collectors when no interface attached XDP at all, and ids_egress always.

Declaration order and the limit​

The block is read in declaration order rather than sorted, and each entry is assigned a slot index from its position. A configuration may declare at most 64 logical interfaces, and declaring more fails the load with the count.

Reordering entries changes nothing today

The slot is designed to be what scoped kernel rules key on, and the module describes it that way, but at 0.8.8 nothing in the datapath reads it — the scoped IDS and firewall paths key on the kernel's ifindex instead. So reordering the block is currently inert. The 64-entry limit is enforced regardless.

What a bad file does​

The file saysWhat happens
match: {}load fails: match needs one of name, mac or pattern
two of name/mac/patternload fails: match takes exactly one of the three
an unknown key under match, such as ifnameload fails on the unknown field
match: { name: "" } or pattern: ""load fails; neither may be empty
a malformed MAC — wrong length, non-hex, seven groupsload fails naming the expected aa:bb:cc:dd:ee:ff form
an invalid glob, such as [load fails naming the pattern
features: [waf], or any name not in the fiveload fails on the unknown feature
features: [ssl_uprobe] or [tls_offload]load fails: host-wide, and the message names where it is controlled
features: [firewall, firewall]load fails: listed twice
features: [smart_firewall] without firewallload fails
an empty logical nameload fails
more than 64 entriesload fails with the count
name or mac matching no link on this hostload fails
pattern matching no linkaccepted, with no startup line at all
one link matched by two entriesload fails, naming both entries
the block together with iface/ifaces, ids.egress_ifaces, or a netfilter firewall.modeload fails, as above
smart_firewall on two interfacesstartup fails, naming them
ids_egress on a build without the egress-ids featurestartup fails
the block on a host with no /sys/class/netload fails: cannot enumerate /sys/class/net

Every link that does resolve is logged at startup, as interface 'eth1' -> network.interfaces.wan (features: firewall, ids). That line is the quickest check that a pattern matched what you meant.

What stays quiet​

These do not fail the load or the start, and each has surprised someone:

  • fingerprint without capture.enabled, and JA4T without fingerprint, as above.
  • Packet capture may not start at all. Under the block, a single-interface capture runs on a fingerprint interface or not at all — never on a default such as auto or eth0, either of which could be a link the config deliberately left out. If capture.interface names a link with no fingerprint feature, or no entry has fingerprint, capture is skipped and the reason is logged at info level as packet capture not started: … — and only when capture.enabled is true. Left unset or auto, capture takes the first fingerprint interface once physical links have been preferred over virtual ones.
  • Only one ids link is covered if the IDS falls back to AF_PACKET. When the XDP IDS is unavailable, the fallback binds to the first ids interface and logs an error naming the ones it is not covering. Scoping ids to three NICs does not mean three are inspected on that path.
  • An unavailable XDP is a degraded start, not a failure. With the block set, firewall.mode: auto and no XDP program loaded, Synapse logs an error and does not fall back to nftables or iptables — those are the host-wide backends the block cannot scope. It continues with no kernel firewall at all.

What the block does not cover​

The guarantee at the top of this page is about the features the block governs. Three things reach an interface by another route, and the first two can reach a link the block never names:

  • capture.observe_interfaces and capture.observe_egress_interfaces. These are read straight from the configuration; the block is not consulted. The named link gets a BPF program attached, or a capture socket opened, depending on capture.observe_mode. And despite the name it is not purely passive: on a smart-firewall drop verdict for an overlay flow it has resolved — pod-to-pod identity, or a destination matched from a snooped domain — the observer drops the packet. A link that appears only here can therefore carry a program and lose traffic without being in network.interfaces at all.
  • firewall.urpf.interfaces. Its keys are kernel interface names and are never cross-checked against the block. uRPF is applied only to links that have an XDP dispatcher, which is every link the block attaches — so it reaches a link whose only feature is fingerprint, with no firewall requested, at the global firewall.urpf.mode, because a name absent from the map falls back to it. A key naming a link the block does not list is simply never applied, and no line at any level says so — not even at debug.
  • A require_native refusal, which leaves the named link without the protection it was listed for while the TC collectors may still use it — see require_native.

If the management interface must stay untouched, leaving it out of network.interfaces is necessary and not sufficient: check that it is named in neither capture.observe_* nor firewall.urpf.interfaces.

veth, bridges and auto-detection​

network.iface: auto enumerates the host's links and drops the ones that are virtual or not up — loopback, veth, bridges and CNI-style prefixes among them. Under network.interfaces that detection never runs, so none of its exclusions apply: the block's matchers are the only thing that selects a link.

One exclusion survives, on the attach path rather than in detection. A veth link is skipped unless firewall.attach_veth is set — or the block matched it, which is read as the more specific request and attaches it. Matching by pattern counts as matching it.

An interface with only ids_egress gets no XDP program

The XDP entry program is attached for firewall, ids, smart_firewall and fingerprint. TC-egress capture does not need it, so an entry whose only feature is ids_egress is in the plan and carries no XDP.

Linux only​

network.interfaces resolves its matchers by reading /sys/class/net, and that read is not conditional on the platform. On Windows the configuration therefore fails to load with network.interfaces: cannot enumerate /sys/class/net. Use network.iface or network.ifaces there; the shipped config_windows.yaml does.

(The exception is synapse --terminal run standalone, which ignores a configuration it cannot load and starts on defaults. Every other start path propagates the error.)

The features themselves need a Linux build with the bpf feature, which is on by default.

ids_egress cannot be used on a released package

It needs the egress-ids build feature, and that feature exists only on the inner synapse-app crate. There is no root-level one, so a plain --features egress-ids is rejected and only the dependency form --features synapse-app/egress-ids enables it — and no packaging job passes either. On every published build, features: [ids_egress] therefore makes the agent refuse to start. Treat it as unavailable unless you build Synapse yourself.

A worked example​

A host with a public uplink, two internal NICs and a management port, where the management port must never be touched:

network:
interfaces:
uplink:
match: { mac: "52:54:00:12:34:56" } # survives a NIC swap
features: [firewall, ids, smart_firewall, fingerprint]
require_native: true # refuse generic XDP — see the caveat above
internal:
match: { pattern: "enp3s*" }
features: [firewall]

firewall:
mode: auto
amygdala:
enabled: true
ids:
enabled: true
capture:
enabled: true

The management port appears nowhere, which is most of what keeps it clear — the rest is checking it is named in neither capture.observe_* nor firewall.urpf.interfaces.

Note what the file has to get right for the features above to run: firewall.mode is auto rather than a netfilter backend, and ids.enabled and firewall.amygdala.enabled are true because ids and smart_firewall are listed — those three are checked, and the file would be refused without them. capture.enabled is true because fingerprint is listed, and that one is not checked: with it false the file still loads and the TC collectors simply do not run.