@@ -333,35 +333,62 @@ setup_enforce_drop() {
333333# Unlike enforce-drop there is no conntrack ESTABLISHED rule: nat only evaluates
334334# the first packet of a flow, so replies and established connections are not
335335# re-translated.
336+ # Two chains are needed because REDIRECT is a nat-table target but the nat table
337+ # forbids DROP ("the use of DROP is therefore inhibited"):
338+ # * nat OUTPUT / AB_REDIRECT — REDIRECT external TCP to TRANSPARENT_PORT.
339+ # * mangle OUTPUT / AB_NOTCP — DROP external non-TCP (UDP/QUIC) so HTTP/3
340+ # cannot bypass; `-p tcp -j RETURN` lets TCP
341+ # fall through to the nat REDIRECT.
342+ # mangle runs before nat in the OUTPUT hook, so non-TCP is dropped on its
343+ # original destination and TCP is passed to the nat REDIRECT. Both are inserted
344+ # at position 1 to precede Istio's appended chains.
336345setup_enforce_redirect () {
337- CHAIN=" AB_REDIRECT"
346+ REDIR_CHAIN=" AB_REDIRECT"
347+ NOTCP_CHAIN=" AB_NOTCP"
338348
339- echo " enforce-redirect: installing fail-closed egress capture (nat OUTPUT, chain ${CHAIN} )"
340- echo " enforce-redirect: external TCP -> 127.0.0.1:${TRANSPARENT_PORT} ; exempt proxy UID=${PROXY_UID} ; direct in-cluster CIDRs=${CLUSTER_CIDRS} "
349+ echo " enforce-redirect: installing fail-closed egress capture"
350+ echo " enforce-redirect: external TCP -> 127.0.0.1:${TRANSPARENT_PORT} (nat REDIRECT); external non-TCP -> DROP (mangle)"
351+ echo " enforce-redirect: exempt proxy UID=${PROXY_UID} ; direct in-cluster CIDRs=${CLUSTER_CIDRS} "
341352
342- # --- IPv4 ---
343- ${IPT} -t nat -N " ${CHAIN } " 2> /dev/null || true
344- ${IPT} -t nat -F " ${CHAIN } "
353+ # --- IPv4: nat REDIRECT for TCP ---
354+ ${IPT} -t nat -N " ${REDIR_CHAIN } " 2> /dev/null || true
355+ ${IPT} -t nat -F " ${REDIR_CHAIN } "
345356 # ztunnel's own sockets (ambient) carry fwmark 0x539 — let them through.
346- ${IPT} -t nat -A " ${CHAIN } " -m mark --mark " ${ZTUNNEL_MARK} " -j RETURN
357+ ${IPT} -t nat -A " ${REDIR_CHAIN } " -m mark --mark " ${ZTUNNEL_MARK} " -j RETURN
347358 # the AuthBridge proxy's own re-originated egress (runs as PROXY_UID) — avoids
348359 # redirecting the proxy's upstream dial back into itself.
349- ${IPT} -t nat -A " ${CHAIN } " -m owner --uid-owner " ${PROXY_UID} " -j RETURN
360+ ${IPT} -t nat -A " ${REDIR_CHAIN } " -m owner --uid-owner " ${PROXY_UID} " -j RETURN
350361 # app -> forward proxy over loopback (HTTP_PROXY target), and any loopback.
351- ${IPT} -t nat -A " ${CHAIN } " -o lo -j RETURN
352- ${IPT} -t nat -A " ${CHAIN } " -d 127.0.0.0/8 -j RETURN
362+ ${IPT} -t nat -A " ${REDIR_CHAIN } " -o lo -j RETURN
363+ ${IPT} -t nat -A " ${REDIR_CHAIN } " -d 127.0.0.0/8 -j RETURN
353364 # in-cluster traffic (pods / services / DNS) — left direct, carried by the mesh.
354365 for cidr in $( echo " ${CLUSTER_CIDRS} " | tr ' ,' ' ' ) ; do
355- [ -n " ${cidr} " ] && ${IPT} -t nat -A " ${CHAIN } " -d " ${cidr} " -j RETURN
366+ [ -n " ${cidr} " ] && ${IPT} -t nat -A " ${REDIR_CHAIN } " -d " ${cidr} " -j RETURN
356367 done
357368 # external TCP that bypassed the forward proxy — capture it transparently.
358- ${IPT} -t nat -A " ${CHAIN} " -p tcp -j REDIRECT --to-port " ${TRANSPARENT_PORT} "
359- # external non-TCP (UDP/QUIC) — cannot be redirected to a TCP listener; drop so
360- # HTTP/3 cannot bypass. Clients fall back to TCP, which the rule above captures.
361- ${IPT} -t nat -A " ${CHAIN} " -j DROP
362- # Hook at position 1 so we run before any appended Istio nat chain.
363- if ! ${IPT} -t nat -C OUTPUT -j " ${CHAIN} " 2> /dev/null; then
364- ${IPT} -t nat -I OUTPUT 1 -j " ${CHAIN} "
369+ ${IPT} -t nat -A " ${REDIR_CHAIN} " -p tcp -j REDIRECT --to-port " ${TRANSPARENT_PORT} "
370+ if ! ${IPT} -t nat -C OUTPUT -j " ${REDIR_CHAIN} " 2> /dev/null; then
371+ ${IPT} -t nat -I OUTPUT 1 -j " ${REDIR_CHAIN} "
372+ fi
373+
374+ # --- IPv4: mangle DROP for non-TCP ---
375+ ${IPT} -t mangle -N " ${NOTCP_CHAIN} " 2> /dev/null || true
376+ ${IPT} -t mangle -F " ${NOTCP_CHAIN} "
377+ # established/related replies (incl. UDP conntrack, e.g. DNS replies) first.
378+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
379+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -m mark --mark " ${ZTUNNEL_MARK} " -j RETURN
380+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -m owner --uid-owner " ${PROXY_UID} " -j RETURN
381+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -o lo -j RETURN
382+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -d 127.0.0.0/8 -j RETURN
383+ for cidr in $( echo " ${CLUSTER_CIDRS} " | tr ' ,' ' ' ) ; do
384+ [ -n " ${cidr} " ] && ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -d " ${cidr} " -j RETURN
385+ done
386+ # TCP is handled by the nat REDIRECT above — let it pass mangle untouched.
387+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -p tcp -j RETURN
388+ # everything else == external non-TCP egress (UDP/QUIC) — drop it.
389+ ${IPT} -t mangle -A " ${NOTCP_CHAIN} " -j DROP
390+ if ! ${IPT} -t mangle -C OUTPUT -j " ${NOTCP_CHAIN} " 2> /dev/null; then
391+ ${IPT} -t mangle -I OUTPUT 1 -j " ${NOTCP_CHAIN} "
365392 fi
366393 echo " enforce-redirect: IPv4 egress capture configured"
367394
@@ -370,21 +397,38 @@ setup_enforce_redirect() {
370397 # loopback + link-local (fe80::/10 unicast, ff02::/16 NDP/MLD multicast) and
371398 # the proxy UID / ztunnel mark; REDIRECT external v6 TCP; DROP other v6 egress.
372399 if command -v " ${IP6T%% * } " > /dev/null 2>&1 && ${IP6T} -t nat -L > /dev/null 2>&1 ; then
373- ${IP6T} -t nat -N " ${CHAIN} " 2> /dev/null || true
374- ${IP6T} -t nat -F " ${CHAIN} "
375- ${IP6T} -t nat -A " ${CHAIN} " -m mark --mark " ${ZTUNNEL_MARK} " -j RETURN
376- ${IP6T} -t nat -A " ${CHAIN} " -m owner --uid-owner " ${PROXY_UID} " -j RETURN
377- ${IP6T} -t nat -A " ${CHAIN} " -o lo -j RETURN
378- ${IP6T} -t nat -A " ${CHAIN} " -d ::1/128 -j RETURN
379- ${IP6T} -t nat -A " ${CHAIN} " -d fe80::/10 -j RETURN
380- ${IP6T} -t nat -A " ${CHAIN} " -d ff02::/16 -j RETURN
400+ ${IP6T} -t nat -N " ${REDIR_CHAIN} " 2> /dev/null || true
401+ ${IP6T} -t nat -F " ${REDIR_CHAIN} "
402+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -m mark --mark " ${ZTUNNEL_MARK} " -j RETURN
403+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -m owner --uid-owner " ${PROXY_UID} " -j RETURN
404+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -o lo -j RETURN
405+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -d ::1/128 -j RETURN
406+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -d fe80::/10 -j RETURN
407+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -d ff02::/16 -j RETURN
408+ for cidr in $( echo " ${CLUSTER_CIDRS6} " | tr ' ,' ' ' ) ; do
409+ [ -n " ${cidr} " ] && ${IP6T} -t nat -A " ${REDIR_CHAIN} " -d " ${cidr} " -j RETURN
410+ done
411+ ${IP6T} -t nat -A " ${REDIR_CHAIN} " -p tcp -j REDIRECT --to-port " ${TRANSPARENT_PORT} "
412+ if ! ${IP6T} -t nat -C OUTPUT -j " ${REDIR_CHAIN} " 2> /dev/null; then
413+ ${IP6T} -t nat -I OUTPUT 1 -j " ${REDIR_CHAIN} "
414+ fi
415+
416+ ${IP6T} -t mangle -N " ${NOTCP_CHAIN} " 2> /dev/null || true
417+ ${IP6T} -t mangle -F " ${NOTCP_CHAIN} "
418+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
419+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -m mark --mark " ${ZTUNNEL_MARK} " -j RETURN
420+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -m owner --uid-owner " ${PROXY_UID} " -j RETURN
421+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -o lo -j RETURN
422+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -d ::1/128 -j RETURN
423+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -d fe80::/10 -j RETURN
424+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN} " -d ff02::/16 -j RETURN
381425 for cidr in $( echo " ${CLUSTER_CIDRS6} " | tr ' ,' ' ' ) ; do
382- [ -n " ${cidr} " ] && ${IP6T} -t nat -A " ${CHAIN } " -d " ${cidr} " -j RETURN
426+ [ -n " ${cidr} " ] && ${IP6T} -t mangle -A " ${NOTCP_CHAIN } " -d " ${cidr} " -j RETURN
383427 done
384- ${IP6T} -t nat -A " ${CHAIN } " -p tcp -j REDIRECT --to-port " ${TRANSPARENT_PORT} "
385- ${IP6T} -t nat -A " ${CHAIN } " -j DROP
386- if ! ${IP6T} -t nat -C OUTPUT -j " ${CHAIN } " 2> /dev/null; then
387- ${IP6T} -t nat -I OUTPUT 1 -j " ${CHAIN } "
428+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN } " -p tcp -j RETURN
429+ ${IP6T} -t mangle -A " ${NOTCP_CHAIN } " -j DROP
430+ if ! ${IP6T} -t mangle -C OUTPUT -j " ${NOTCP_CHAIN } " 2> /dev/null; then
431+ ${IP6T} -t mangle -I OUTPUT 1 -j " ${NOTCP_CHAIN } "
388432 fi
389433 echo " enforce-redirect: IPv6 egress capture configured"
390434 else
0 commit comments