bpf, datapath: move the CT_* params to runtime config
Project: cilium/cilium | Language: Go, C | Concepts: configuration-management, refactoring | Difficulty: mid
Link: https://github.com/cilium/cilium/pull/47537
The problem
The connection tracking (CT) timeout parameters (CT_*) were previously hardcoded as preprocessor macros in the datapath code. This made it difficult to change these values at runtime or configure them dynamically, requiring code changes and recompilation for any updates.
Why it's non-trivial
A naive fix might simply replace macros with variables, but this would not solve the need for a consistent, runtime-configurable mechanism across both the BPF datapath and the Go control plane. The fix must ensure that all datapath objects see the same configuration, avoid loader mismatches, and maintain testability.
The approach
1. Remove hardcoded CT_ macros and replace with a struct-based runtime config.*
All the old macro definitions are deleted:
-#define CT_CONNECTION_LIFETIME_TCP 21600
-#define CT_CONNECTION_LIFETIME_NONTCP 60
-#define CT_SERVICE_LIFETIME_TCP 21600
-#define CT_SERVICE_LIFETIME_NONTCP 60
-#define CT_SERVICE_CLOSE_REBALANCE 30
-#define CT_SYN_TIMEOUT 60
-#define CT_CLOSE_TIMEOUT 10
This removes the static, compile-time configuration for CT timeouts.
2. Introduce a new struct for CT timeouts in the BPF node config:
+struct ct_timeout_config {
+ /* Lifetime of non-service TCP conntrack entries in seconds. */
+ __u32 connection_lifetime_tcp;
+ /* Lifetime of non-service non-TCP conntrack entries in seconds. */
+ __u32 connection_lifetime_non_tcp;
+ /* Lifetime of TCP service conntrack entries in seconds. */
+ __u32 service_lifetime_tcp;
+ /* Lifetime of non-TCP service conntrack entries in seconds. */
+ __u32 service_lifetime_non_tcp;
+ /* Grace period before a closed TCP service connection may be rebalanced, in seconds. */
+ __u32 service_close_rebalance;
+ /* Lifetime of TCP conntrack entries that have only seen SYN packets, in seconds. */
+ __u32 syn_timeout;
+ /* Lifetime of closed TCP conntrack entries in seconds. */
+ __u32 close_timeout;
+};
+
+NODE_CONFIG(struct ct_timeout_config, ct_timeouts, "Conntrack timeout configuration")
+ASSIGN_CONFIG(struct ct_timeout_config, ct_timeouts, {
+ .connection_lifetime_tcp = 21600,
+ .connection_lifetime_non_tcp = 60,
+ .service_lifetime_tcp = 21600,
+ .service_lifetime_non_tcp = 60,
+ .service_close_rebalance = 30,
+ .syn_timeout = 60,
+ .close_timeout = 10,
+})
Defines a single struct holding all CT timeout values and registers it as a node config variable.
3. Update all datapath code to use the new runtime config struct:
- bpf_sec_to_mono(CT_SERVICE_LIFETIME_NONTCP) :
- bpf_sec_to_mono(CT_CONNECTION_LIFETIME_NONTCP);
+ bpf_sec_to_mono(CONFIG(ct_timeouts).service_lifetime_non_tcp) :
+ bpf_sec_to_mono(CONFIG(ct_timeouts).connection_lifetime_non_tcp);
All code that previously referenced macros now reads from the ct_timeouts config struct.
4. Update Go types and node config generation to match the new struct:
+type CTTimeoutConfig struct {
+ _ structs.HostLayout
+ ConnectionLifetimeTCP uint32
+ ConnectionLifetimeNonTCP uint32
+ ServiceLifetimeTCP uint32
+ ServiceLifetimeNonTCP uint32
+ ServiceCloseRebalance uint32
+ SYNTimeout uint32
+ CloseTimeout uint32
+}
This Go struct mirrors the BPF struct, ensuring type-safe config propagation.
5. Update the Go node config builder to fill the struct from user-configurable options:
+ node.CTTimeouts = types.CTTimeoutConfig{
+ ConnectionLifetimeTCP: uint32(option.Config.CTMapEntriesTimeoutTCP.Seconds()),
+ ConnectionLifetimeNonTCP: uint32(option.Config.CTMapEntriesTimeoutAny.Seconds()),
+ ServiceLifetimeTCP: uint32(option.Config.CTMapEntriesTimeoutSVCTCP.Seconds()),
+ ServiceLifetimeNonTCP: uint32(option.Config.CTMapEntriesTimeoutSVCAny.Seconds()),
+ ServiceCloseRebalance: uint32(option.Config.CTMapEntriesTimeoutSVCTCPGrace.Seconds()),
+ SYNTimeout: uint32(option.Config.CTMapEntriesTimeoutSYN.Seconds()),
+ CloseTimeout: uint32(option.Config.CTMapEntriesTimeoutFIN.Seconds()),
+ }
Now, the CT timeout config is populated at runtime from user options.
6. Update all tests to reference the new config struct:
- assert(timeout_in(entry, CT_SYN_TIMEOUT));
+ assert(timeout_in(entry, CONFIG(ct_timeouts).syn_timeout));
Ensures tests remain valid and reflect the new configuration approach.
7. Remove now-unused macro propagation from Go to C:
- cDefinesMap["CT_CONNECTION_LIFETIME_TCP"] = fmt.Sprintf("%d", int64(option.Config.CTMapEntriesTimeoutTCP.Seconds()))
- ...
No more macro defines for CT timeouts; everything is in the config struct.
From the review thread
ti-mo: I think we concluded NODE_CONFIG should only be declared in node.h since objects that don't import conntrack.h will end up with a different set of node configs, which breaks the loader.
Also, these values should go in a struct.
ti-mo: Nit: make these a struct?
viktor-kurchenko: My bad, sorry!
Fixed and introduced the
ct_timeout_configstruct.
Takeaways
- Prefer struct-based configuration for related runtime parameters rather than scattered macros or individual variables.
- Centralize configuration declarations to avoid loader mismatches and ensure all code paths see a consistent set of options.
- When refactoring configuration, update not only the implementation but also all associated tests and type definitions to match the new approach.