77// non-IPv4/ARP → DROP
88// 2. IP frag 2+ → PASS (no L4 header to inspect)
99// 3. conntrack hit → PASS (TCP/UDP stateful fast path)
10- // 4. return traffic → PASS (TCP ACK, UDP ephemeral, ICMP-if-enabled)
11- // 4. static blocklist → DROP
12- // 5. dynamic blocklist → DROP (if not expired)
13- // 6. public port → PASS (any source)
14- // 7. private port + allow → PASS
15- // 8. private port, no allow→ DROP
16- // 9. no rule → DROP (default deny)
10+ // 4. return traffic → PASS (TCP established, DHCP client, ICMP-if-enabled)
11+ // NOTE: UDP ephemeral-port fallback was removed
12+ // in build.21 — attackers were trivially bypassing
13+ // it by targeting dport >= 32768. UDP return now
14+ // depends entirely on the egress seed (TC hook)
15+ // populating flowtrack_v4, which the stateful
16+ // fast path in step 3 consumes.
17+ // 5. static blocklist → DROP
18+ // 6. dynamic blocklist → DROP (if not expired)
19+ // 7. public port → PASS (any source)
20+ // 8. private port + allow → PASS
21+ // 9. private port, no allow→ DROP
22+ // 10. no rule → DROP (default deny)
1723//
1824// All counters are per-CPU to avoid contention on the hot path. Userspace
1925// sums across CPUs when rendering stats.
@@ -37,7 +43,6 @@ char __license[] SEC("license") = "GPL";
3743#define EVENTS_RINGBUF_BYTES (1 << 18)
3844#define FLOW_TCP_TTL_NS (5ULL * 60ULL * 1000000000ULL)
3945#define FLOW_UDP_TTL_NS (120ULL * 1000000000ULL)
40- #define DEFAULT_UDP_EPHEMERAL_MIN 32768
4146#define LEGACY_DHCP_CLIENT_PORT 68
4247
4348// --- global stats slots -----------------------------------------------------
@@ -270,11 +275,9 @@ static __always_inline int port_in(void *map, __u16 port_be) {
270275 return bpf_map_lookup_elem (map , & port_be ) != NULL ;
271276}
272277
273- static __always_inline void runtime_cfg_get (__u8 * allow_arp , __u8 * allow_icmp ,
274- __u16 * udp_ephemeral_min ) {
278+ static __always_inline void runtime_cfg_get (__u8 * allow_arp , __u8 * allow_icmp ) {
275279 * allow_arp = 1 ;
276280 * allow_icmp = 1 ;
277- * udp_ephemeral_min = DEFAULT_UDP_EPHEMERAL_MIN ;
278281
279282 __u32 k = 0 ;
280283 struct runtime_cfg_v4 * cfg = bpf_map_lookup_elem (& runtime_cfg_v4 , & k );
@@ -283,8 +286,10 @@ static __always_inline void runtime_cfg_get(__u8 *allow_arp, __u8 *allow_icmp,
283286
284287 * allow_arp = (cfg -> flags & RUNTIME_FLAG_ALLOW_ARP ) ? 1 : 0 ;
285288 * allow_icmp = (cfg -> flags & RUNTIME_FLAG_ALLOW_ICMP ) ? 1 : 0 ;
286- if (cfg -> udp_ephemeral_min >= 1024 )
287- * udp_ephemeral_min = cfg -> udp_ephemeral_min ;
289+ // cfg->udp_ephemeral_min is deliberately ignored: UDP return traffic
290+ // is now governed entirely by the egress seed + stateful fast path.
291+ // The config field is still accepted by the loader for backward
292+ // compatibility but has no effect on packet decisions.
288293}
289294
290295static __always_inline __u64 flow_ttl_ns (__u8 proto ) {
@@ -366,8 +371,7 @@ int kekkai_xdp(struct xdp_md *ctx) {
366371 stat_add (STAT_BYTES_TOTAL , pkt_len );
367372
368373 __u8 allow_arp = 1 , allow_icmp = 1 ;
369- __u16 udp_ephemeral_min = DEFAULT_UDP_EPHEMERAL_MIN ;
370- runtime_cfg_get (& allow_arp , & allow_icmp , & udp_ephemeral_min );
374+ runtime_cfg_get (& allow_arp , & allow_icmp );
371375
372376 // 1. ethernet + IPv4 only (ARP may be toggled at runtime)
373377 struct ethhdr * eth = data ;
@@ -462,6 +466,9 @@ int kekkai_xdp(struct xdp_md *ctx) {
462466 }
463467
464468 // 3. stateful conntrack fast path (TCP/UDP only).
469+ // Counters are split: stateful hits bump only the stateful counter,
470+ // not `return`. Earlier builds double-counted here, which made the
471+ // return-traffic numbers unreadable when the box is under load.
465472 if (proto == IPPROTO_TCP || proto == IPPROTO_UDP ) {
466473 struct flow4_key fkey = {
467474 .saddr = saddr ,
@@ -473,10 +480,8 @@ int kekkai_xdp(struct xdp_md *ctx) {
473480 if (flow_lookup_alive (& fkey , now_ns )) {
474481 if (proto == IPPROTO_TCP ) {
475482 stat_add (STAT_PASS_STATEFUL_TCP , 1 );
476- stat_add (STAT_PASS_RETURN_TCP , 1 );
477483 } else {
478484 stat_add (STAT_PASS_STATEFUL_UDP , 1 );
479- stat_add (STAT_PASS_RETURN_UDP , 1 );
480485 }
481486 stat_add (STAT_PKTS_PASSED , 1 );
482487 perip_touch (saddr , pkt_len , proto , 0 );
@@ -494,10 +499,12 @@ int kekkai_xdp(struct xdp_md *ctx) {
494499 // Matching RST/FIN is essential — otherwise the server-side
495500 // session dies the moment the peer sends a reset or starts a
496501 // graceful close, because those packets carry no payload ACK.
497- // - UDP: dst port in ephemeral range (matches responses to
498- // agent-initiated DNS/NTP/etc without conntrack). DHCP client
499- // replies (dst 68) are also always allowed so lease renewal does
500- // not flap the interface IP.
502+ // - UDP: only DHCP client replies (dst 68) are allowed here, so
503+ // lease renewal does not flap the interface IP. All other UDP
504+ // return traffic must come through the stateful fast path above,
505+ // which is fed by the TC egress seed. This is intentional: the
506+ // old "dport >= ephemeral" heuristic was a trivial UDP amplifier
507+ // bypass (attackers just aimed at dport 32768+).
501508 if (proto == IPPROTO_ICMP && allow_icmp ) {
502509 stat_add (STAT_PASS_RETURN_ICMP , 1 );
503510 stat_add (STAT_PKTS_PASSED , 1 );
@@ -528,17 +535,10 @@ int kekkai_xdp(struct xdp_md *ctx) {
528535 perip_touch (saddr , pkt_len , proto , 0 );
529536 return XDP_PASS ;
530537 }
531- if (bpf_ntohs (dport_be ) >= udp_ephemeral_min ) {
532- struct flow4_key fkey = {
533- .saddr = saddr , .daddr = ip -> daddr ,
534- .sport = sport_be , .dport = dport_be , .proto = proto ,
535- };
536- flow_upsert (& fkey , now_ns );
537- stat_add (STAT_PASS_RETURN_UDP , 1 );
538- stat_add (STAT_PKTS_PASSED , 1 );
539- perip_touch (saddr , pkt_len , proto , 0 );
540- return XDP_PASS ;
541- }
538+ // No UDP ephemeral fallback — the stateful fast path already covers
539+ // legitimate return traffic via the TC egress seed. Fall through to
540+ // the port-policy checks below; anything not matching public/private
541+ // rules will hit default deny.
542542 } else {
543543 // Unknown L4 proto and not a return classification — default deny.
544544 stat_add (STAT_DROP_NO_POLICY , 1 );
0 commit comments