The kernel fast path

A plain TCP or UDP passthrough gains nothing from a proxy in the middle. Where a service is only that, SSLB3 hands it to nftables or IPVS and keeps the work only it can do.

Without it, SSLB3 accepts a connection, opens a second one, and copies bytes between them — two sockets and a pair of buffers per connection, to do what the kernel already does. With it, the packets are forwarded by the kernel and SSLB3 supervises: health checking with real semantics, deciding which backends exist, reconciling that against what the kernel holds, and reporting all of it.

A layer 4 service before and after the fast pathWithout the fast path, SSLB3 accepts a connection and opens a second one to the backend, copying bytes between them. With it, nftables or IPVS forwards the packets directly and SSLB3 keeps only the supervisory work: health checking, deciding which backends exist, and reporting.in the pathSSLB3backendtwo sockets and a buffer per connectionout of the pathbackendnftables / IPVSSSLB3health checks · reconciles · reports

Two decisions, not one

Confusing them is how an operator ends up believing a setting did nothing. Which services move is SSLB3's decision, from configuration alone. Which facility forwards is the helper's, because only the helper can see what the kernel actually has.

Which services move

Every condition must hold. Each corresponds to something SSLB3 can only do from inside the path, so failing one is not a limitation to work around — it is the feature that would otherwise be silently removed.

ConditionWhy

Plain TCP or UDP

No TLS to terminate, no QUIC, not a Unix socket. A forwarder moves packets between addresses.

Pinned to a serverfarm

The backend set is known before the connection rather than chosen from it.

Layer7Module = none

Nothing parses the bytes.

No session script

No Lua owns the connection.

Backends not over TLS

No handshake for SSLB3 to originate per connection.

A balancer the kernel can express

round-robin, weighted, least-connections or source-hash.

Connection detector not enforcing

Refusing a connection at accept requires seeing the accept.

Access log off

A forwarded connection is one SSLB3 never sees to log.

At least one healthy backend

Something to point at. A farm whose backends have all gone is withdrawn and returned to SSLB3 rather than blackholed in the kernel.

An enforceable source filter

If the listener filters by source address, the forwarder must be able to apply it.

In an HTTP ingress the answer is usually none of them, and that is correct: anything terminating TLS or parsed by the HTTP module stays where it is by definition. What moves is the layer 4 services — an L4Ingress, or a listener configured with Layer7Module = none.

Every listener's verdict is reported whether or not the fast path is on. Read that before changing anything: it answers "which of my services would this actually move" without moving any of them.

Which facility forwards

auto

IPVS where it is loaded, else nftables, else nothing forwards

ipvs

IPVS only

nftables

nftables only

auto prefers IPVS because IPVS is a load balancer: a service handed to it keeps a real scheduling algorithm and reports connections and bytes in both directions. nftables forwards correctly but distributes by an incrementing counter and can only count connections.

"Nothing forwards" is an ordinary answer, not a failure. It is what a host without the modules reports, what a container without the capability reports, and what every platform but Linux reports.

Availability is asked, not assumed

IPVS is detected by a generic netlink family lookup. nftables by a generation request and a throwaway nat chain, created and removed — because whether nf_tables answers is not the same question as whether it can make the chain this needs. A kernel can have one without the other, and a container can be missing the capability while the socket opens perfectly well. Both would otherwise be reported as usable and fail at the first rule.

The source rewrite

Neither forwarder rewrites the source on its own. Without one the backend sees the client's address and answers the client directly — and the client discards a reply from an address it never spoke to, so the connection hangs rather than failing. So SourceNat is on by default, and the reply comes back through this host.

Turn it off where the backends already route their replies back here — a classic director with the real servers behind it — because there the rewrite does nothing except hide the client's address from a backend that may need it. And for direct server return.

Source filters travel with the service

An allowSourceRange or denySourceRange becomes an nftables filter chain ahead of the forwarding. It has to: a forwarded connection is one SSLB3 never accepts, so a filter left behind would not be a slower filter — it would be no filter, and the kernel would carry exactly the addresses the configuration refuses.

text

table inet sslb3 {
	chain prerouting-filter {
		type filter hook prerouting priority -300; policy accept;
		tcp dport 19001 ip saddr 10.10.10.10 counter packets 0 bytes 0 drop
	}
}

Rules are ordered most specific first, with deny ahead of allow at equal length — SSLB3 resolves by specificity while nftables resolves by rule order, and the ordering is what turns one into the other. A forwarder that cannot enforce a filter is refused the service rather than given it, reported as source-filter.

Intrusion detection is not offloaded

A farm whose connection detector is enforcing is never handed over — it is reported threat-enforcement and SSLB3 keeps serving it.

This is a deliberate limit, not an oversight. The block list could be expressed in the kernel — an nftables set with per-element timeouts is close to purpose-built for it — but the connection detector learns by counting accepts, and a forwarded service produces none. Enforcing the list in the kernel would apply the blocks already known and make that service invisible to the detector that maintains them, so no new abuser on it would ever be found. Offloading the enforcement would quietly switch off the detection.

What the counters mean

Neither forwarder counts the way SSLB3 does, and the differences are not cosmetic.

IPVS reports connections and bytes in both directions, all real. nftables reports connections only: its rules sit in a nat chain, so the byte figure it holds is each connection's first packet, and reporting that as traffic would show a few hundred bytes for a farm carrying megabytes. Its connection count is a floor rather than an exact tally — a client fast enough to reuse a source port reuses a conntrack entry, and the rule is not entered again.

All of them belong to the current rules and return to zero when a service is reprogrammed. They are read back from the kernel each pass rather than accumulated, so a helper restart does not invent history.

Host requirements

Kernel 5.2+

SSLB3's tables are inet family, and inet nat chains arrived in 5.2. Before that nat existed only as separate ip and ip6 chain types, and the chain is refused.

IP forwarding

A rewritten destination that is not this host's own address makes the packet routed. Needed by both forwarders, per address family.

net.ipv4.vs.conntrack

IPVS only, and only with the source rewrite. IPVS works inside LOCAL_IN and its connections are invisible to conntrack by default, so they never reach the chain carrying the rewrite.

The helper reports what is missing and declines the facility rather than programming rules that would silently drop traffic.

In Kubernetes

/proc/sys is read-only in a container, so the helper cannot set those switches itself. ip_forward is inherited from the node and usually already on; net.ipv4.vs.conntrack is per network namespace with a hard default of 0 and is not inherited, so setting it on the node does not reach a pod.

config.fastpath.privilegedSysctlInit runs a privileged init container that sets them in the pod's own namespace and exits — the same shape OpenSearch uses for vm.max_map_count. It is off by default because PodSecurity baseline and restricted reject a privileged container outright, and defaulting it on would stop the chart installing at all on a cluster enforcing either. Leaving it off is a good answer: nftables forwards correctly and needs neither switch.

Where the privilege lives

SSLB3 holds no capabilities. Netlink needs CAP_NET_ADMIN at every call rather than once at startup, so a balancer that programmed the kernel itself would keep it for life — and that balancer runs Lua, terminates TLS and parses requests from strangers. It drops every capability at startup and verifies it cannot regain them.

A separate helper does the netlink work over a Unix socket: no Lua, no TLS, no request parsing, no network listener. What a compromised balancer can ask of it is the list of ports to forward, what is being forwarded, and to remove it all.

Teardown happens three times, because the one that matters is the one that did not happen: at startup before anything is created, when the balancer disconnects, and on shutdown.