From 49bc8146b41c8ceb7e887546dfb5b85585540273 Mon Sep 17 00:00:00 2001 From: Carsten Date: Mon, 1 Dec 2025 14:07:32 +0100 Subject: [PATCH 1/4] proto_ipsec: Add IPSec NAT Traversal (NAT-T) support Implement IPSec NAT Traversal according to: - 3GPP TS 33.203 Annex M: IPsec NAT traversal - 3GPP TS 24.229: IP multimedia call control protocol - RFC 3948: UDP Encapsulation of IPsec ESP Packets New features: - New module parameter 'nat_traversal' (default: 0/disabled) - Support for mod=UDP-enc-tun in Security-Client headers - UDP encapsulation for ESP packets when NAT-T mode is negotiated - Tunnel mode instead of transport mode for NAT-T SAs - New pseudo-variable field $ipsec(mode) to query the IPSec mode - Persistent storage/restoration of NAT-T mode in usrloc When nat_traversal=1, the module accepts Security-Client headers with mod=UDP-enc-tun and creates Security Associations with UDP encapsulation (ESPINUDP) in tunnel mode, allowing UEs behind NAT devices to establish IPSec tunnels with the P-CSCF. --- modules/proto_ipsec/doc/proto_ipsec_admin.xml | 78 ++++++++- modules/proto_ipsec/ipsec.c | 52 +++++- modules/proto_ipsec/ipsec.h | 22 ++- modules/proto_ipsec/ipsec_algo.c | 47 +++++- modules/proto_ipsec/ipsec_algo.h | 1 + modules/proto_ipsec/proto_ipsec.c | 150 ++++++++++++++++-- 6 files changed, 329 insertions(+), 21 deletions(-) diff --git a/modules/proto_ipsec/doc/proto_ipsec_admin.xml b/modules/proto_ipsec/doc/proto_ipsec_admin.xml index c9e9189ffb9..1928fa58584 100644 --- a/modules/proto_ipsec/doc/proto_ipsec_admin.xml +++ b/modules/proto_ipsec/doc/proto_ipsec_admin.xml @@ -18,6 +18,14 @@ specification (GSMA PRD IR.92) and implements the extensions defined in TS 33.203 (3G Security: Access Security for IP-based Services). + + The module also supports IPSec NAT Traversal (NAT-T) + as specified in 3GPP TS 33.203 Annex M and TS 24.229. When NAT-T is enabled, + the module can accept and use the mod=UDP-enc-tun mode + in Security-Client headers, which encapsulates ESP packets in UDP datagrams + (as per RFC 3948) to traverse NAT devices. This feature is optional and can + be enabled via the parameter. + It allows creation of both UDP and TCP secure connections on the same IP:port pair, defined as sockets. Essentially, when defining a socket @@ -352,6 +360,58 @@ modparam("proto_ipsec", "disable_deprecated_algorithms", yes) +
+ <varname>nat_traversal</varname> (integer) + + Enables or disables IPSec NAT Traversal (NAT-T) support according to + 3GPP TS 33.203 Annex M and TS 24.229. + + + When enabled (set to 1), the module will: + + + + Accept mod=UDP-enc-tun in Security-Client headers + + + + + Use UDP encapsulation for ESP packets (RFC 3948) when NAT-T mode is negotiated + + + + + Use tunnel mode instead of transport mode for IPSec SAs + + + + + Include mod=UDP-enc-tun in Security-Server headers + when the UE requests NAT-T mode + + + + + + When disabled (set to 0, default), only mod=trans + (standard transport mode) is accepted in Security-Client headers. + + + + Default value is 0 (disabled). + + + + Set <varname>nat_traversal</varname> parameter + +... +# Enable NAT Traversal support for UEs behind NAT +modparam("proto_ipsec", "nat_traversal", 1) +... + + +
+
@@ -454,16 +514,21 @@ onreply_route[ipsec] { port-c - local port chosen for communicating through the client channel. - port-c - local port + port-s - local port chosen for communicating through the server channel. + mode - IPSec mode being used: + trans for transport mode or + UDP-enc-tun for NAT-T tunnel mode + (as per 3GPP TS 33.203 Annex M). + <function>$ipsec(field)</function> usage ... -xlog("Using $ipsec(ip):$ipsec(port-c) and $ipsec(ip):$ipsec(port-s) socket\n"); +xlog("Using $ipsec(ip):$ipsec(port-c) and $ipsec(ip):$ipsec(port-s) socket, mode=$ipsec(mode)\n"); ... @@ -502,16 +567,21 @@ xlog("Using $ipsec(ip):$ipsec(port-c) and $ipsec(ip):$ipsec(port-s) socket\n"); port-c - remote port chosen for communicating through the client channel. - port-c - remote port + port-s - remote port chosen for communicating through the server channel. + mode - IPSec mode being used: + trans for transport mode or + UDP-enc-tun for NAT-T tunnel mode + (as per 3GPP TS 33.203 Annex M). + <function>$ipsec_ue(field)</function> usage ... -xlog("Using $ipsec_ue(ip):$ipsec_ue(port-c) and $ipsec_ue(ip):$ipsec_ue(port-s) socket\n"); +xlog("UE $ipsec_ue(ip):$ipsec_ue(port-c) and $ipsec_ue(ip):$ipsec_ue(port-s), mode=$ipsec_ue(mode)\n"); ... diff --git a/modules/proto_ipsec/ipsec.c b/modules/proto_ipsec/ipsec.c index eae353fa115..69e30f93a0f 100644 --- a/modules/proto_ipsec/ipsec.c +++ b/modules/proto_ipsec/ipsec.c @@ -392,6 +392,21 @@ struct xfrm_algo_osips { static_assert(sizeof(struct xfrm_algo_osips) == sizeof(struct xfrm_algo) + IPSEC_ALGO_MAX_KEY_SIZE, "ERROR! Unexpected 'xfrm_algo' size!"); +/* + * NAT-T (NAT Traversal) support according to: + * - 3GPP TS 33.203 Annex M: IPsec NAT traversal + * - 3GPP TS 24.229: IP multimedia call control protocol + * - RFC 3948: UDP Encapsulation of IPsec ESP Packets + * + * When NAT-T mode (mod=UDP-enc-tun) is used: + * - ESP packets are encapsulated in UDP (port 4500 typically) + * - Tunnel mode is used instead of transport mode + * - XFRMA_ENCAP attribute is added to the SA + */ +#ifndef UDP_ENCAP_ESPINUDP +#define UDP_ENCAP_ESPINUDP 2 /* RFC 3948 */ +#endif + int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, enum ipsec_dir dir, int client) { @@ -401,10 +416,12 @@ int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, struct xfrm_userpolicy_info *policy_info; struct xfrm_algo_osips ia, ie; struct xfrm_user_tmpl tmpl; + struct xfrm_encap_tmpl encap; unsigned short dst_port; unsigned short src_port; unsigned int spi; struct ipsec_endpoint *src, *dst; + int xfrm_mode; if (dir == IPSEC_POLICY_IN) { src = &ctx->ue; @@ -510,13 +527,41 @@ int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, sa_info->reqid = htonl(spi); sa_info->family = dst->ip.af; sa_info->replay_window = 32; - sa_info->mode = XFRM_MODE_TRANSPORT; + + /* + * Set mode according to NAT-T configuration: + * - Transport mode (mod=trans): Standard IPsec transport mode + * - Tunnel mode (mod=UDP-enc-tun): NAT-T with UDP encapsulation + * as per 3GPP TS 33.203 Annex M + */ + if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { + xfrm_mode = XFRM_MODE_TUNNEL; + sa_info->flags |= XFRM_STATE_NOPMTUDISC; + } else { + xfrm_mode = XFRM_MODE_TRANSPORT; + } + sa_info->mode = xfrm_mode; mnl_attr_put(nlh, XFRMA_ALG_AUTH, sizeof(struct xfrm_algo) + ia.alg_key_len, &ia); mnl_attr_put(nlh, XFRMA_ALG_CRYPT, sizeof(struct xfrm_algo) + ie.alg_key_len, &ie); + /* + * NAT-T UDP Encapsulation (3GPP TS 33.203 Annex M, RFC 3948) + * When mod=UDP-enc-tun is negotiated, ESP packets are encapsulated + * in UDP datagrams to traverse NAT devices. + */ + if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { + memset(&encap, 0, sizeof(encap)); + encap.encap_type = UDP_ENCAP_ESPINUDP; + encap.encap_sport = htons(src_port); + encap.encap_dport = htons(dst_port); + /* OA (Original Address) - set to zero, kernel fills if needed */ + mnl_attr_put(nlh, XFRMA_ENCAP, sizeof(encap), &encap); + LM_DBG("NAT-T encapsulation: sport=%hu dport=%hu\n", src_port, dst_port); + } + if (mnl_socket_sendto(sock, nlh, nlh->nlmsg_len) < 0) { LM_ERR("communicating with kernel for new SA: %s\n", strerror(errno)); goto error; @@ -560,7 +605,7 @@ int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, tmpl.family = dst->ip.af; memcpy(&tmpl.saddr, &src->ip.u, src->ip.len); tmpl.reqid = htonl(spi); - tmpl.mode = XFRM_MODE_TRANSPORT; + tmpl.mode = xfrm_mode; /* Transport or Tunnel mode based on NAT-T */ tmpl.share = XFRM_SHARE_ANY; tmpl.optional = 0; tmpl.aalgos = 0xffffffff; @@ -648,7 +693,7 @@ static void ipsec_ctx_free(struct ipsec_ctx *ctx) struct ipsec_ctx *ipsec_ctx_new(sec_agree_body_t *sa, struct ip_addr *ip, struct socket_info *ss, struct socket_info *sc, str *ck, str *ik, - unsigned int spi_pc, unsigned int spi_ps) + unsigned int spi_pc, unsigned int spi_ps, enum ipsec_mode mode) { struct ipsec_spi *spi_s, *spi_c; struct ipsec_ctx *ctx; @@ -710,6 +755,7 @@ struct ipsec_ctx *ipsec_ctx_new(sec_agree_body_t *sa, struct ip_addr *ip, ctx->client = sc; ctx->alg = alg; ctx->ealg = ealg; + ctx->mode = mode; /* own information - shortcut */ memcpy(&ctx->me.ip, &sc->address, sizeof(struct ip_addr)); ctx->me.spi_s = spi_s->spi; diff --git a/modules/proto_ipsec/ipsec.h b/modules/proto_ipsec/ipsec.h index 95561c06e4e..ed2a4358bc9 100644 --- a/modules/proto_ipsec/ipsec.h +++ b/modules/proto_ipsec/ipsec.h @@ -42,6 +42,23 @@ enum ipsec_state { IPSEC_STATE_INVALID, }; +/* + * IPSec mode according to 3GPP TS 33.203 Annex H/M: + * - IPSEC_MODE_TRANSPORT: mod=trans (standard transport mode) + * - IPSEC_MODE_UDP_ENCAP_TUNNEL: mod=UDP-enc-tun (NAT-T mode with UDP encapsulation) + */ +enum ipsec_mode { + IPSEC_MODE_TRANSPORT = 0, + IPSEC_MODE_UDP_ENCAP_TUNNEL, +}; + +/* NAT-T encapsulation port (RFC 3948) */ +#define IPSEC_NAT_T_PORT 4500 + +#define VALID_IPSEC_STATE(_s) \ + ((_s) == IPSEC_STATE_TMP || \ + (_s) == IPSEC_STATE_OK) + #define ipsec_socket mnl_socket #include "../../str.h" @@ -63,6 +80,7 @@ struct ipsec_ctx { struct ipsec_algorithm_desc *alg, *ealg; struct ipsec_endpoint me; struct ipsec_endpoint ue; + enum ipsec_mode mode; /* transport or UDP-enc-tun (NAT-T) */ /* dynamic values - should be locked */ gen_lock_t lock; @@ -116,7 +134,7 @@ void ipsec_sa_rm_all(struct ipsec_socket *sock, struct ipsec_ctx *ctx); /* ctx */ struct ipsec_ctx *ipsec_ctx_new(sec_agree_body_t *sa, struct ip_addr *ip, struct socket_info *ss, struct socket_info *sc, str *ck, str *ik, - unsigned int spi_pc, unsigned int spi_ps); + unsigned int spi_pc, unsigned int spi_ps, enum ipsec_mode mode); struct ipsec_ctx *ipsec_ctx_find(struct ipsec_user *user, unsigned short port); void ipsec_ctx_push(struct ipsec_ctx *ctx); struct ipsec_ctx *ipsec_ctx_get(void); @@ -126,7 +144,9 @@ void ipsec_ctx_release_tmp_user(struct ipsec_user *user); void ipsec_ctx_release_user(struct ipsec_ctx *ctx); void ipsec_ctx_release(struct ipsec_ctx *ctx); int ipsec_ctx_release_unsafe(struct ipsec_ctx *ctx); +void ipsec_ctx_add_tmp(struct ipsec_ctx *ctx); void ipsec_ctx_remove_tmp(struct ipsec_ctx *ctx); +void ipsec_ctx_remove_free_tmp(struct ipsec_ctx *ctx, int _free); void ipsec_ctx_extend_tmp(struct ipsec_ctx *ctx); #endif /* _IPSEC_H_ */ diff --git a/modules/proto_ipsec/ipsec_algo.c b/modules/proto_ipsec/ipsec_algo.c index 3ced5a9d0ec..82be95f5a6e 100644 --- a/modules/proto_ipsec/ipsec_algo.c +++ b/modules/proto_ipsec/ipsec_algo.c @@ -73,6 +73,39 @@ #define IPSEC_ALGO_MAX_KEY_SIZE IPSEC_ALGO_DES3_KEY_SIZE int ipsec_disable_deprecated_algorithms = 0; +/* NAT-T support flag - defined in proto_ipsec.c, declared in ipsec_algo.h */ + +/* + * Validate IPSec mode from Security-Client header + * According to 3GPP TS 33.203 Annex H/M: + * - "trans": Transport mode (always accepted) + * - "UDP-enc-tun": UDP-encapsulated tunnel mode (only if NAT-T enabled) + */ +static int ipsec_validate_mode(str *mod_str) +{ + static str trans_str = str_init("trans"); + static str udp_enc_tun_str = str_init("UDP-enc-tun"); + + if (!mod_str || !mod_str->len) { + /* No mode specified - default to transport, acceptable */ + return 0; + } + + if (str_casematch(mod_str, &trans_str)) { + return 0; /* Transport mode always accepted */ + } + + if (str_casematch(mod_str, &udp_enc_tun_str)) { + if (ipsec_nat_traversal_enabled) { + return 0; /* NAT-T mode accepted when enabled */ + } + LM_DBG("NAT-T mode (UDP-enc-tun) not accepted - nat_traversal disabled\n"); + return -1; + } + + LM_DBG("unknown IPSec mode: %.*s\n", mod_str->len, mod_str->s); + return -1; +} static struct ipsec_algorithm_desc ipsec_auth_algorithms[] = { { @@ -286,7 +319,12 @@ sec_agree_body_t *ipsec_get_security_client(struct sip_msg *msg, struct ipsec_al for (sa = sas; sa; sa = sa->next) { if (sa->invalid || sa->mechanism != SEC_AGREE_MECHANISM_IPSEC_3GPP) continue; - /* TODO: should we check mode for now? */ + /* Validate mode (trans or UDP-enc-tun per 3GPP TS 33.203) */ + if (ipsec_validate_mode(&sa->ts3gpp.mod_str) < 0) { + LM_DBG("unsupported mode %.*s in Security-Client\n", + sa->ts3gpp.mod_str.len, sa->ts3gpp.mod_str.s); + continue; + } if (!sa->ts3gpp.alg_str.len) continue; alg_desc = ipsec_parse_algorithm(&sa->ts3gpp.alg_str, IPSEC_ALGO_TYPE_AUTH); @@ -334,7 +372,12 @@ sec_agree_body_t *ipsec_get_security_client(struct sip_msg *msg, struct ipsec_al for (sa = sas; sa; sa = sa->next) { if (sa->invalid || sa->mechanism != SEC_AGREE_MECHANISM_IPSEC_3GPP) continue; - /* TODO: should we check mode for now? */ + /* Validate mode (trans or UDP-enc-tun per 3GPP TS 33.203) */ + if (ipsec_validate_mode(&sa->ts3gpp.mod_str) < 0) { + LM_DBG("unsupported mode %.*s in Security-Client\n", + sa->ts3gpp.mod_str.len, sa->ts3gpp.mod_str.s); + continue; + } if (!sa->ts3gpp.alg_str.len) continue; auth = ipsec_parse_algorithm(&sa->ts3gpp.alg_str, IPSEC_ALGO_TYPE_AUTH); diff --git a/modules/proto_ipsec/ipsec_algo.h b/modules/proto_ipsec/ipsec_algo.h index 3e02602df8c..d00082afb46 100644 --- a/modules/proto_ipsec/ipsec_algo.h +++ b/modules/proto_ipsec/ipsec_algo.h @@ -94,5 +94,6 @@ sec_agree_body_t *ipsec_get_security_client(struct sip_msg *msg, struct ipsec_al void ipsec_free_allowed_algorithms(struct ipsec_allowed_algo *algos); extern int ipsec_disable_deprecated_algorithms; +extern int ipsec_nat_traversal_enabled; /* NAT-T support enabled */ #endif /* _IPSEC_ALGO_H_ */ diff --git a/modules/proto_ipsec/proto_ipsec.c b/modules/proto_ipsec/proto_ipsec.c index 0364a3e5fda..0f9e048c13b 100644 --- a/modules/proto_ipsec/proto_ipsec.c +++ b/modules/proto_ipsec/proto_ipsec.c @@ -58,6 +58,23 @@ static usrloc_api_t ul_ipsec; static int ipsec_ctx_tm_idx = -1; static int ipsec_port = IPSEC_DEFAULT_PORT; + +/* + * NAT Traversal (NAT-T) support according to: + * - 3GPP TS 33.203 Annex M: IPsec NAT traversal + * - 3GPP TS 24.229: IP multimedia call control protocol + * + * When enabled (nat_traversal=1), the module will: + * - Accept and use mod=UDP-enc-tun in Security-Client headers + * - Use UDP encapsulation for ESP packets (RFC 3948) + * - Use tunnel mode instead of transport mode + * + * Default: 0 (disabled) - only mod=trans is accepted + */ +static int ipsec_nat_traversal = 0; + +/* Exported for ipsec_algo.c to check NAT-T support in Security-Client parsing */ +int ipsec_nat_traversal_enabled = 0; static int mod_init(void); static void mod_destroy(void); static int proto_ipsec_init(struct proto_info *pi); @@ -76,6 +93,44 @@ static int ipsec_aka_auth_match_f(const struct authenticate_body *auth, static struct match_auth_hf_desc ipsec_aka_auth_match = MATCH_AUTH_HF(ipsec_aka_auth_match_f, NULL); +/* + * Parse IPSec mode from Security-Client/Server "mod" parameter + * According to 3GPP TS 33.203 Annex H/M: + * - "trans": Transport mode (standard) + * - "UDP-enc-tun": UDP-encapsulated tunnel mode (NAT-T) + * + * Returns: + * IPSEC_MODE_TRANSPORT for mod=trans + * IPSEC_MODE_UDP_ENCAP_TUNNEL for mod=UDP-enc-tun (if nat_traversal enabled) + * -1 on error (unsupported mode or NAT-T not enabled) + */ +static int ipsec_parse_mode(str *mod_str) +{ + static str trans_str = str_init("trans"); + static str udp_enc_tun_str = str_init("UDP-enc-tun"); + + if (!mod_str || !mod_str->len) { + /* Default to transport mode if not specified */ + return IPSEC_MODE_TRANSPORT; + } + + if (str_casematch(mod_str, &trans_str)) { + return IPSEC_MODE_TRANSPORT; + } + + if (str_casematch(mod_str, &udp_enc_tun_str)) { + if (!ipsec_nat_traversal) { + LM_WARN("NAT-T mode (UDP-enc-tun) requested but nat_traversal is disabled\n"); + return -1; + } + LM_DBG("Using NAT-T mode (UDP-enc-tun) as per 3GPP TS 33.203 Annex M\n"); + return IPSEC_MODE_UDP_ENCAP_TUNNEL; + } + + LM_ERR("unsupported IPSec mode: %.*s\n", mod_str->len, mod_str->s); + return -1; +} + static int w_ipsec_create(struct sip_msg *msg, int *port_ps, int *port_pc, struct ipsec_allowed_algo *algos); static int fixup_ipsec_create_algos(void **param); @@ -104,6 +159,12 @@ static const param_export_t params[] = { { "default_server_port", INT_PARAM, &ipsec_default_server_port }, { "allowed_algorithms", STR_PARAM, &ipsec_allowed_algorithms.s }, { "disable_deprecated_algorithms", INT_PARAM, &ipsec_disable_deprecated_algorithms }, + /* + * NAT Traversal (NAT-T) support - 3GPP TS 33.203 Annex M, TS 24.229 + * 0 = disabled (default): only mod=trans accepted + * 1 = enabled: mod=UDP-enc-tun accepted, UDP encapsulation used + */ + { "nat_traversal", INT_PARAM, &ipsec_nat_traversal }, {0, 0, 0} }; @@ -232,6 +293,12 @@ static void ipsec_handle_register_req(struct cell* t, int type, struct tmcb_para static int mod_init(void) { LM_INFO("initializing IPSec protocols\n"); + + /* Export NAT-T setting for ipsec_algo.c */ + ipsec_nat_traversal_enabled = ipsec_nat_traversal; + if (ipsec_nat_traversal) + LM_INFO("NAT Traversal (NAT-T) support enabled per 3GPP TS 33.203 Annex M\n"); + if (ipsec_tmp_timeout <= 0) { LM_ERR("invalid temporary timeout value %d - positive value required\n", ipsec_tmp_timeout); @@ -537,7 +604,15 @@ static int ipsec_add_security_server(struct sip_msg *msg, struct ipsec_ctx *ctx) struct lump* anchor; char *h, *p; str tmp; - str hdr1 = str_init("Supported: sec-agree\r\nSecurity-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-s="); + /* + * Security-Server header according to RFC 3329 and 3GPP TS 33.203 + * mod parameter values: + * - "trans": Transport mode (standard IPSec) + * - "UDP-enc-tun": UDP-encapsulated tunnel mode (NAT-T per Annex M) + */ + str hdr1_trans = str_init("Supported: sec-agree\r\nSecurity-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-s="); + str hdr1_natt = str_init("Supported: sec-agree\r\nSecurity-Server: ipsec-3gpp;q=0.1;prot=esp;mod=UDP-enc-tun;spi-s="); + str *hdr1; str hdr2 = str_init(";spi-c="); str hdr3 = str_init(";port-s="); str hdr4 = str_init(";port-c="); @@ -546,10 +621,18 @@ static int ipsec_add_security_server(struct sip_msg *msg, struct ipsec_ctx *ctx) str hdr7 = str_init("\r\n"); int alg_len, ealg_len; + /* Select header based on NAT-T mode */ + if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { + hdr1 = &hdr1_natt; + LM_DBG("Adding Security-Server with NAT-T mode (UDP-enc-tun)\n"); + } else { + hdr1 = &hdr1_trans; + } + alg_len = strlen(ctx->alg->name); ealg_len = strlen(ctx->ealg->name); - h = pkg_malloc(hdr1.len + 5 /* max port */ + hdr2.len + 5 + + h = pkg_malloc(hdr1->len + 5 /* max port */ + hdr2.len + 5 + hdr3.len + INT2STR_MAX_LEN /* spi */ + hdr4.len + INT2STR_MAX_LEN + hdr5.len + alg_len + hdr6.len + strlen(ctx->ealg->name) + ealg_len); if (!h) { @@ -557,8 +640,8 @@ static int ipsec_add_security_server(struct sip_msg *msg, struct ipsec_ctx *ctx) return -1; } p = h; - memcpy(p, hdr1.s, hdr1.len); - p += hdr1.len; + memcpy(p, hdr1->s, hdr1->len); + p += hdr1->len; tmp.s = int2str(ctx->me.spi_s, &tmp.len); memcpy(p, tmp.s, tmp.len); p += tmp.len; @@ -784,11 +867,22 @@ static int w_ipsec_create(struct sip_msg *msg, int *_port_ps, int *_port_pc, goto release_user; } - ret = -5; - ctx = ipsec_ctx_new(sa, &req->rcv.src_ip, ss, sc, &auth->ck, &auth->ik, 0, 0); - if (!ctx) { - LM_ERR("could not allocate new IPSec ctx\n"); - goto release_user; + /* Parse IPSec mode from Security-Client (3GPP TS 33.203 Annex M) */ + { + int mode = ipsec_parse_mode(&sa->ts3gpp.mod_str); + if (mode < 0) { + LM_ERR("unsupported or disabled IPSec mode in Security-Client\n"); + ret = -2; + goto release_user; + } + + ret = -5; + ctx = ipsec_ctx_new(sa, &req->rcv.src_ip, ss, sc, &auth->ck, &auth->ik, 0, 0, + (enum ipsec_mode)mode); + if (!ctx) { + LM_ERR("could not allocate new IPSec ctx\n"); + goto release_user; + } } ipsec_ctx_push(ctx); @@ -845,6 +939,7 @@ str pv_ipsec_ctx_type[] = { str_init("port-s"), /* 6 */ str_init("ik"), /* 7 */ str_init("ck"), /* 8 */ + str_init("mode"), /* 9 - NAT-T mode: "trans" or "UDP-enc-tun" */ }; static int pv_parse_ipsec_ctx_flag(str *name) @@ -946,6 +1041,15 @@ static int pv_get_ipsec_ctx(struct sip_msg *msg, pv_param_t *param, case 8: /* ck */ res->rs = ctx->ck; break; + case 9: /* mode */ + if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { + res->rs.s = "UDP-enc-tun"; + res->rs.len = 11; + } else { + res->rs.s = "trans"; + res->rs.len = 5; + } + break; default: LM_BUG("invalid name %d\n", name); return -1; @@ -1208,6 +1312,7 @@ static str ipsec_usrloc_spi_us = str_init("ipsec.spi_us"); static str ipsec_usrloc_port_pc = str_init("ipsec.port_pc"); static str ipsec_usrloc_port_uc = str_init("ipsec.port_uc"); static str ipsec_usrloc_port_us = str_init("ipsec.port_us"); +static str ipsec_usrloc_mode = str_init("ipsec.mode"); /* NAT-T mode */ #define UL_GET_S_RET(_c, _s, _d, _e) \ ({ \ @@ -1279,11 +1384,15 @@ static void ipsec_usrloc_restore(ucontact_t *contact) { str ck, ik; int spi_pc, spi_ps, port_pc; + int stored_mode; + enum ipsec_mode mode; sec_agree_body_t sa; struct socket_info *sc, *ss; struct ipsec_user *user; struct ipsec_socket *sock; struct ipsec_ctx *ctx; + int_str_t *mode_val; + LM_DBG("restoring IPSec context for %.*s (%.*s)\n", contact->aor->len, contact->aor->s, contact->c.len, contact->c.s); @@ -1298,7 +1407,24 @@ static void ipsec_usrloc_restore(ucontact_t *contact) ik = UL_GET_S(contact, ipsec_usrloc_ik, "IK"); sa.ts3gpp.alg_str = UL_GET_S(contact, ipsec_usrloc_alg, "ALG"); sa.ts3gpp.prot_str = str_init("esp"); - sa.ts3gpp.mod_str = str_init("trans"); + + /* Restore NAT-T mode if stored, default to transport mode */ + mode_val = ul_ipsec.get_ucontact_key(contact, &ipsec_usrloc_mode); + if (mode_val && !mode_val->is_str) { + stored_mode = mode_val->i; + if (stored_mode == IPSEC_MODE_UDP_ENCAP_TUNNEL && ipsec_nat_traversal) { + mode = IPSEC_MODE_UDP_ENCAP_TUNNEL; + sa.ts3gpp.mod_str = str_init("UDP-enc-tun"); + LM_DBG("restoring NAT-T mode (UDP-enc-tun)\n"); + } else { + mode = IPSEC_MODE_TRANSPORT; + sa.ts3gpp.mod_str = str_init("trans"); + } + } else { + mode = IPSEC_MODE_TRANSPORT; + sa.ts3gpp.mod_str = str_init("trans"); + } + sa.ts3gpp.ealg_str = UL_GET_S(contact, ipsec_usrloc_ealg, "EALG"); spi_pc = UL_GET_I(contact, ipsec_usrloc_spi_pc, "SPI-PC"); spi_ps = UL_GET_I(contact, ipsec_usrloc_spi_ps, "SPI-PS"); @@ -1322,7 +1448,7 @@ static void ipsec_usrloc_restore(ucontact_t *contact) return; } - ctx = ipsec_ctx_new(&sa, &user->ip, ss, sc, &ck, &ik, spi_pc, spi_ps); + ctx = ipsec_ctx_new(&sa, &user->ip, ss, sc, &ck, &ik, spi_pc, spi_ps, mode); if (!ctx) { LM_ERR("could not allocate new IPSec ctx\n"); goto release_user; @@ -1390,6 +1516,8 @@ static void ipsec_usrloc_insert(ucontact_t *contact) ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_port_pc, INT_STR_I(ctx->me.port_c)); ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_port_uc, INT_STR_I(ctx->ue.port_c)); ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_port_us, INT_STR_I(ctx->ue.port_s)); + /* Store NAT-T mode for restoration */ + ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_mode, INT_STR_I((int)ctx->mode)); /* all good mark the SA as OK */ lock_get(&ctx->lock); From f0dfc24c543cf296a9c11c438cc5244a8c3ae51f Mon Sep 17 00:00:00 2001 From: Carsten Date: Mon, 1 Dec 2025 14:10:12 +0100 Subject: [PATCH 2/4] Revert "proto_ipsec: Add IPSec NAT Traversal (NAT-T) support" This reverts commit 49bc8146b41c8ceb7e887546dfb5b85585540273. --- modules/proto_ipsec/doc/proto_ipsec_admin.xml | 78 +-------- modules/proto_ipsec/ipsec.c | 52 +----- modules/proto_ipsec/ipsec.h | 22 +-- modules/proto_ipsec/ipsec_algo.c | 47 +----- modules/proto_ipsec/ipsec_algo.h | 1 - modules/proto_ipsec/proto_ipsec.c | 150 ++---------------- 6 files changed, 21 insertions(+), 329 deletions(-) diff --git a/modules/proto_ipsec/doc/proto_ipsec_admin.xml b/modules/proto_ipsec/doc/proto_ipsec_admin.xml index 1928fa58584..c9e9189ffb9 100644 --- a/modules/proto_ipsec/doc/proto_ipsec_admin.xml +++ b/modules/proto_ipsec/doc/proto_ipsec_admin.xml @@ -18,14 +18,6 @@ specification (GSMA PRD IR.92) and implements the extensions defined in TS 33.203 (3G Security: Access Security for IP-based Services). - - The module also supports IPSec NAT Traversal (NAT-T) - as specified in 3GPP TS 33.203 Annex M and TS 24.229. When NAT-T is enabled, - the module can accept and use the mod=UDP-enc-tun mode - in Security-Client headers, which encapsulates ESP packets in UDP datagrams - (as per RFC 3948) to traverse NAT devices. This feature is optional and can - be enabled via the parameter. - It allows creation of both UDP and TCP secure connections on the same IP:port pair, defined as sockets. Essentially, when defining a socket @@ -360,58 +352,6 @@ modparam("proto_ipsec", "disable_deprecated_algorithms", yes)
-
- <varname>nat_traversal</varname> (integer) - - Enables or disables IPSec NAT Traversal (NAT-T) support according to - 3GPP TS 33.203 Annex M and TS 24.229. - - - When enabled (set to 1), the module will: - - - - Accept mod=UDP-enc-tun in Security-Client headers - - - - - Use UDP encapsulation for ESP packets (RFC 3948) when NAT-T mode is negotiated - - - - - Use tunnel mode instead of transport mode for IPSec SAs - - - - - Include mod=UDP-enc-tun in Security-Server headers - when the UE requests NAT-T mode - - - - - - When disabled (set to 0, default), only mod=trans - (standard transport mode) is accepted in Security-Client headers. - - - - Default value is 0 (disabled). - - - - Set <varname>nat_traversal</varname> parameter - -... -# Enable NAT Traversal support for UEs behind NAT -modparam("proto_ipsec", "nat_traversal", 1) -... - - -
-
@@ -514,21 +454,16 @@ onreply_route[ipsec] { port-c - local port chosen for communicating through the client channel. - port-s - local port + port-c - local port chosen for communicating through the server channel. - mode - IPSec mode being used: - trans for transport mode or - UDP-enc-tun for NAT-T tunnel mode - (as per 3GPP TS 33.203 Annex M). - <function>$ipsec(field)</function> usage ... -xlog("Using $ipsec(ip):$ipsec(port-c) and $ipsec(ip):$ipsec(port-s) socket, mode=$ipsec(mode)\n"); +xlog("Using $ipsec(ip):$ipsec(port-c) and $ipsec(ip):$ipsec(port-s) socket\n"); ... @@ -567,21 +502,16 @@ xlog("Using $ipsec(ip):$ipsec(port-c) and $ipsec(ip):$ipsec(port-s) socket, mode port-c - remote port chosen for communicating through the client channel. - port-s - remote port + port-c - remote port chosen for communicating through the server channel. - mode - IPSec mode being used: - trans for transport mode or - UDP-enc-tun for NAT-T tunnel mode - (as per 3GPP TS 33.203 Annex M). - <function>$ipsec_ue(field)</function> usage ... -xlog("UE $ipsec_ue(ip):$ipsec_ue(port-c) and $ipsec_ue(ip):$ipsec_ue(port-s), mode=$ipsec_ue(mode)\n"); +xlog("Using $ipsec_ue(ip):$ipsec_ue(port-c) and $ipsec_ue(ip):$ipsec_ue(port-s) socket\n"); ... diff --git a/modules/proto_ipsec/ipsec.c b/modules/proto_ipsec/ipsec.c index 69e30f93a0f..eae353fa115 100644 --- a/modules/proto_ipsec/ipsec.c +++ b/modules/proto_ipsec/ipsec.c @@ -392,21 +392,6 @@ struct xfrm_algo_osips { static_assert(sizeof(struct xfrm_algo_osips) == sizeof(struct xfrm_algo) + IPSEC_ALGO_MAX_KEY_SIZE, "ERROR! Unexpected 'xfrm_algo' size!"); -/* - * NAT-T (NAT Traversal) support according to: - * - 3GPP TS 33.203 Annex M: IPsec NAT traversal - * - 3GPP TS 24.229: IP multimedia call control protocol - * - RFC 3948: UDP Encapsulation of IPsec ESP Packets - * - * When NAT-T mode (mod=UDP-enc-tun) is used: - * - ESP packets are encapsulated in UDP (port 4500 typically) - * - Tunnel mode is used instead of transport mode - * - XFRMA_ENCAP attribute is added to the SA - */ -#ifndef UDP_ENCAP_ESPINUDP -#define UDP_ENCAP_ESPINUDP 2 /* RFC 3948 */ -#endif - int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, enum ipsec_dir dir, int client) { @@ -416,12 +401,10 @@ int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, struct xfrm_userpolicy_info *policy_info; struct xfrm_algo_osips ia, ie; struct xfrm_user_tmpl tmpl; - struct xfrm_encap_tmpl encap; unsigned short dst_port; unsigned short src_port; unsigned int spi; struct ipsec_endpoint *src, *dst; - int xfrm_mode; if (dir == IPSEC_POLICY_IN) { src = &ctx->ue; @@ -527,41 +510,13 @@ int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, sa_info->reqid = htonl(spi); sa_info->family = dst->ip.af; sa_info->replay_window = 32; - - /* - * Set mode according to NAT-T configuration: - * - Transport mode (mod=trans): Standard IPsec transport mode - * - Tunnel mode (mod=UDP-enc-tun): NAT-T with UDP encapsulation - * as per 3GPP TS 33.203 Annex M - */ - if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { - xfrm_mode = XFRM_MODE_TUNNEL; - sa_info->flags |= XFRM_STATE_NOPMTUDISC; - } else { - xfrm_mode = XFRM_MODE_TRANSPORT; - } - sa_info->mode = xfrm_mode; + sa_info->mode = XFRM_MODE_TRANSPORT; mnl_attr_put(nlh, XFRMA_ALG_AUTH, sizeof(struct xfrm_algo) + ia.alg_key_len, &ia); mnl_attr_put(nlh, XFRMA_ALG_CRYPT, sizeof(struct xfrm_algo) + ie.alg_key_len, &ie); - /* - * NAT-T UDP Encapsulation (3GPP TS 33.203 Annex M, RFC 3948) - * When mod=UDP-enc-tun is negotiated, ESP packets are encapsulated - * in UDP datagrams to traverse NAT devices. - */ - if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { - memset(&encap, 0, sizeof(encap)); - encap.encap_type = UDP_ENCAP_ESPINUDP; - encap.encap_sport = htons(src_port); - encap.encap_dport = htons(dst_port); - /* OA (Original Address) - set to zero, kernel fills if needed */ - mnl_attr_put(nlh, XFRMA_ENCAP, sizeof(encap), &encap); - LM_DBG("NAT-T encapsulation: sport=%hu dport=%hu\n", src_port, dst_port); - } - if (mnl_socket_sendto(sock, nlh, nlh->nlmsg_len) < 0) { LM_ERR("communicating with kernel for new SA: %s\n", strerror(errno)); goto error; @@ -605,7 +560,7 @@ int ipsec_sa_add(struct mnl_socket *sock, struct ipsec_ctx *ctx, tmpl.family = dst->ip.af; memcpy(&tmpl.saddr, &src->ip.u, src->ip.len); tmpl.reqid = htonl(spi); - tmpl.mode = xfrm_mode; /* Transport or Tunnel mode based on NAT-T */ + tmpl.mode = XFRM_MODE_TRANSPORT; tmpl.share = XFRM_SHARE_ANY; tmpl.optional = 0; tmpl.aalgos = 0xffffffff; @@ -693,7 +648,7 @@ static void ipsec_ctx_free(struct ipsec_ctx *ctx) struct ipsec_ctx *ipsec_ctx_new(sec_agree_body_t *sa, struct ip_addr *ip, struct socket_info *ss, struct socket_info *sc, str *ck, str *ik, - unsigned int spi_pc, unsigned int spi_ps, enum ipsec_mode mode) + unsigned int spi_pc, unsigned int spi_ps) { struct ipsec_spi *spi_s, *spi_c; struct ipsec_ctx *ctx; @@ -755,7 +710,6 @@ struct ipsec_ctx *ipsec_ctx_new(sec_agree_body_t *sa, struct ip_addr *ip, ctx->client = sc; ctx->alg = alg; ctx->ealg = ealg; - ctx->mode = mode; /* own information - shortcut */ memcpy(&ctx->me.ip, &sc->address, sizeof(struct ip_addr)); ctx->me.spi_s = spi_s->spi; diff --git a/modules/proto_ipsec/ipsec.h b/modules/proto_ipsec/ipsec.h index ed2a4358bc9..95561c06e4e 100644 --- a/modules/proto_ipsec/ipsec.h +++ b/modules/proto_ipsec/ipsec.h @@ -42,23 +42,6 @@ enum ipsec_state { IPSEC_STATE_INVALID, }; -/* - * IPSec mode according to 3GPP TS 33.203 Annex H/M: - * - IPSEC_MODE_TRANSPORT: mod=trans (standard transport mode) - * - IPSEC_MODE_UDP_ENCAP_TUNNEL: mod=UDP-enc-tun (NAT-T mode with UDP encapsulation) - */ -enum ipsec_mode { - IPSEC_MODE_TRANSPORT = 0, - IPSEC_MODE_UDP_ENCAP_TUNNEL, -}; - -/* NAT-T encapsulation port (RFC 3948) */ -#define IPSEC_NAT_T_PORT 4500 - -#define VALID_IPSEC_STATE(_s) \ - ((_s) == IPSEC_STATE_TMP || \ - (_s) == IPSEC_STATE_OK) - #define ipsec_socket mnl_socket #include "../../str.h" @@ -80,7 +63,6 @@ struct ipsec_ctx { struct ipsec_algorithm_desc *alg, *ealg; struct ipsec_endpoint me; struct ipsec_endpoint ue; - enum ipsec_mode mode; /* transport or UDP-enc-tun (NAT-T) */ /* dynamic values - should be locked */ gen_lock_t lock; @@ -134,7 +116,7 @@ void ipsec_sa_rm_all(struct ipsec_socket *sock, struct ipsec_ctx *ctx); /* ctx */ struct ipsec_ctx *ipsec_ctx_new(sec_agree_body_t *sa, struct ip_addr *ip, struct socket_info *ss, struct socket_info *sc, str *ck, str *ik, - unsigned int spi_pc, unsigned int spi_ps, enum ipsec_mode mode); + unsigned int spi_pc, unsigned int spi_ps); struct ipsec_ctx *ipsec_ctx_find(struct ipsec_user *user, unsigned short port); void ipsec_ctx_push(struct ipsec_ctx *ctx); struct ipsec_ctx *ipsec_ctx_get(void); @@ -144,9 +126,7 @@ void ipsec_ctx_release_tmp_user(struct ipsec_user *user); void ipsec_ctx_release_user(struct ipsec_ctx *ctx); void ipsec_ctx_release(struct ipsec_ctx *ctx); int ipsec_ctx_release_unsafe(struct ipsec_ctx *ctx); -void ipsec_ctx_add_tmp(struct ipsec_ctx *ctx); void ipsec_ctx_remove_tmp(struct ipsec_ctx *ctx); -void ipsec_ctx_remove_free_tmp(struct ipsec_ctx *ctx, int _free); void ipsec_ctx_extend_tmp(struct ipsec_ctx *ctx); #endif /* _IPSEC_H_ */ diff --git a/modules/proto_ipsec/ipsec_algo.c b/modules/proto_ipsec/ipsec_algo.c index 82be95f5a6e..3ced5a9d0ec 100644 --- a/modules/proto_ipsec/ipsec_algo.c +++ b/modules/proto_ipsec/ipsec_algo.c @@ -73,39 +73,6 @@ #define IPSEC_ALGO_MAX_KEY_SIZE IPSEC_ALGO_DES3_KEY_SIZE int ipsec_disable_deprecated_algorithms = 0; -/* NAT-T support flag - defined in proto_ipsec.c, declared in ipsec_algo.h */ - -/* - * Validate IPSec mode from Security-Client header - * According to 3GPP TS 33.203 Annex H/M: - * - "trans": Transport mode (always accepted) - * - "UDP-enc-tun": UDP-encapsulated tunnel mode (only if NAT-T enabled) - */ -static int ipsec_validate_mode(str *mod_str) -{ - static str trans_str = str_init("trans"); - static str udp_enc_tun_str = str_init("UDP-enc-tun"); - - if (!mod_str || !mod_str->len) { - /* No mode specified - default to transport, acceptable */ - return 0; - } - - if (str_casematch(mod_str, &trans_str)) { - return 0; /* Transport mode always accepted */ - } - - if (str_casematch(mod_str, &udp_enc_tun_str)) { - if (ipsec_nat_traversal_enabled) { - return 0; /* NAT-T mode accepted when enabled */ - } - LM_DBG("NAT-T mode (UDP-enc-tun) not accepted - nat_traversal disabled\n"); - return -1; - } - - LM_DBG("unknown IPSec mode: %.*s\n", mod_str->len, mod_str->s); - return -1; -} static struct ipsec_algorithm_desc ipsec_auth_algorithms[] = { { @@ -319,12 +286,7 @@ sec_agree_body_t *ipsec_get_security_client(struct sip_msg *msg, struct ipsec_al for (sa = sas; sa; sa = sa->next) { if (sa->invalid || sa->mechanism != SEC_AGREE_MECHANISM_IPSEC_3GPP) continue; - /* Validate mode (trans or UDP-enc-tun per 3GPP TS 33.203) */ - if (ipsec_validate_mode(&sa->ts3gpp.mod_str) < 0) { - LM_DBG("unsupported mode %.*s in Security-Client\n", - sa->ts3gpp.mod_str.len, sa->ts3gpp.mod_str.s); - continue; - } + /* TODO: should we check mode for now? */ if (!sa->ts3gpp.alg_str.len) continue; alg_desc = ipsec_parse_algorithm(&sa->ts3gpp.alg_str, IPSEC_ALGO_TYPE_AUTH); @@ -372,12 +334,7 @@ sec_agree_body_t *ipsec_get_security_client(struct sip_msg *msg, struct ipsec_al for (sa = sas; sa; sa = sa->next) { if (sa->invalid || sa->mechanism != SEC_AGREE_MECHANISM_IPSEC_3GPP) continue; - /* Validate mode (trans or UDP-enc-tun per 3GPP TS 33.203) */ - if (ipsec_validate_mode(&sa->ts3gpp.mod_str) < 0) { - LM_DBG("unsupported mode %.*s in Security-Client\n", - sa->ts3gpp.mod_str.len, sa->ts3gpp.mod_str.s); - continue; - } + /* TODO: should we check mode for now? */ if (!sa->ts3gpp.alg_str.len) continue; auth = ipsec_parse_algorithm(&sa->ts3gpp.alg_str, IPSEC_ALGO_TYPE_AUTH); diff --git a/modules/proto_ipsec/ipsec_algo.h b/modules/proto_ipsec/ipsec_algo.h index d00082afb46..3e02602df8c 100644 --- a/modules/proto_ipsec/ipsec_algo.h +++ b/modules/proto_ipsec/ipsec_algo.h @@ -94,6 +94,5 @@ sec_agree_body_t *ipsec_get_security_client(struct sip_msg *msg, struct ipsec_al void ipsec_free_allowed_algorithms(struct ipsec_allowed_algo *algos); extern int ipsec_disable_deprecated_algorithms; -extern int ipsec_nat_traversal_enabled; /* NAT-T support enabled */ #endif /* _IPSEC_ALGO_H_ */ diff --git a/modules/proto_ipsec/proto_ipsec.c b/modules/proto_ipsec/proto_ipsec.c index 0f9e048c13b..0364a3e5fda 100644 --- a/modules/proto_ipsec/proto_ipsec.c +++ b/modules/proto_ipsec/proto_ipsec.c @@ -58,23 +58,6 @@ static usrloc_api_t ul_ipsec; static int ipsec_ctx_tm_idx = -1; static int ipsec_port = IPSEC_DEFAULT_PORT; - -/* - * NAT Traversal (NAT-T) support according to: - * - 3GPP TS 33.203 Annex M: IPsec NAT traversal - * - 3GPP TS 24.229: IP multimedia call control protocol - * - * When enabled (nat_traversal=1), the module will: - * - Accept and use mod=UDP-enc-tun in Security-Client headers - * - Use UDP encapsulation for ESP packets (RFC 3948) - * - Use tunnel mode instead of transport mode - * - * Default: 0 (disabled) - only mod=trans is accepted - */ -static int ipsec_nat_traversal = 0; - -/* Exported for ipsec_algo.c to check NAT-T support in Security-Client parsing */ -int ipsec_nat_traversal_enabled = 0; static int mod_init(void); static void mod_destroy(void); static int proto_ipsec_init(struct proto_info *pi); @@ -93,44 +76,6 @@ static int ipsec_aka_auth_match_f(const struct authenticate_body *auth, static struct match_auth_hf_desc ipsec_aka_auth_match = MATCH_AUTH_HF(ipsec_aka_auth_match_f, NULL); -/* - * Parse IPSec mode from Security-Client/Server "mod" parameter - * According to 3GPP TS 33.203 Annex H/M: - * - "trans": Transport mode (standard) - * - "UDP-enc-tun": UDP-encapsulated tunnel mode (NAT-T) - * - * Returns: - * IPSEC_MODE_TRANSPORT for mod=trans - * IPSEC_MODE_UDP_ENCAP_TUNNEL for mod=UDP-enc-tun (if nat_traversal enabled) - * -1 on error (unsupported mode or NAT-T not enabled) - */ -static int ipsec_parse_mode(str *mod_str) -{ - static str trans_str = str_init("trans"); - static str udp_enc_tun_str = str_init("UDP-enc-tun"); - - if (!mod_str || !mod_str->len) { - /* Default to transport mode if not specified */ - return IPSEC_MODE_TRANSPORT; - } - - if (str_casematch(mod_str, &trans_str)) { - return IPSEC_MODE_TRANSPORT; - } - - if (str_casematch(mod_str, &udp_enc_tun_str)) { - if (!ipsec_nat_traversal) { - LM_WARN("NAT-T mode (UDP-enc-tun) requested but nat_traversal is disabled\n"); - return -1; - } - LM_DBG("Using NAT-T mode (UDP-enc-tun) as per 3GPP TS 33.203 Annex M\n"); - return IPSEC_MODE_UDP_ENCAP_TUNNEL; - } - - LM_ERR("unsupported IPSec mode: %.*s\n", mod_str->len, mod_str->s); - return -1; -} - static int w_ipsec_create(struct sip_msg *msg, int *port_ps, int *port_pc, struct ipsec_allowed_algo *algos); static int fixup_ipsec_create_algos(void **param); @@ -159,12 +104,6 @@ static const param_export_t params[] = { { "default_server_port", INT_PARAM, &ipsec_default_server_port }, { "allowed_algorithms", STR_PARAM, &ipsec_allowed_algorithms.s }, { "disable_deprecated_algorithms", INT_PARAM, &ipsec_disable_deprecated_algorithms }, - /* - * NAT Traversal (NAT-T) support - 3GPP TS 33.203 Annex M, TS 24.229 - * 0 = disabled (default): only mod=trans accepted - * 1 = enabled: mod=UDP-enc-tun accepted, UDP encapsulation used - */ - { "nat_traversal", INT_PARAM, &ipsec_nat_traversal }, {0, 0, 0} }; @@ -293,12 +232,6 @@ static void ipsec_handle_register_req(struct cell* t, int type, struct tmcb_para static int mod_init(void) { LM_INFO("initializing IPSec protocols\n"); - - /* Export NAT-T setting for ipsec_algo.c */ - ipsec_nat_traversal_enabled = ipsec_nat_traversal; - if (ipsec_nat_traversal) - LM_INFO("NAT Traversal (NAT-T) support enabled per 3GPP TS 33.203 Annex M\n"); - if (ipsec_tmp_timeout <= 0) { LM_ERR("invalid temporary timeout value %d - positive value required\n", ipsec_tmp_timeout); @@ -604,15 +537,7 @@ static int ipsec_add_security_server(struct sip_msg *msg, struct ipsec_ctx *ctx) struct lump* anchor; char *h, *p; str tmp; - /* - * Security-Server header according to RFC 3329 and 3GPP TS 33.203 - * mod parameter values: - * - "trans": Transport mode (standard IPSec) - * - "UDP-enc-tun": UDP-encapsulated tunnel mode (NAT-T per Annex M) - */ - str hdr1_trans = str_init("Supported: sec-agree\r\nSecurity-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-s="); - str hdr1_natt = str_init("Supported: sec-agree\r\nSecurity-Server: ipsec-3gpp;q=0.1;prot=esp;mod=UDP-enc-tun;spi-s="); - str *hdr1; + str hdr1 = str_init("Supported: sec-agree\r\nSecurity-Server: ipsec-3gpp;q=0.1;prot=esp;mod=trans;spi-s="); str hdr2 = str_init(";spi-c="); str hdr3 = str_init(";port-s="); str hdr4 = str_init(";port-c="); @@ -621,18 +546,10 @@ static int ipsec_add_security_server(struct sip_msg *msg, struct ipsec_ctx *ctx) str hdr7 = str_init("\r\n"); int alg_len, ealg_len; - /* Select header based on NAT-T mode */ - if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { - hdr1 = &hdr1_natt; - LM_DBG("Adding Security-Server with NAT-T mode (UDP-enc-tun)\n"); - } else { - hdr1 = &hdr1_trans; - } - alg_len = strlen(ctx->alg->name); ealg_len = strlen(ctx->ealg->name); - h = pkg_malloc(hdr1->len + 5 /* max port */ + hdr2.len + 5 + + h = pkg_malloc(hdr1.len + 5 /* max port */ + hdr2.len + 5 + hdr3.len + INT2STR_MAX_LEN /* spi */ + hdr4.len + INT2STR_MAX_LEN + hdr5.len + alg_len + hdr6.len + strlen(ctx->ealg->name) + ealg_len); if (!h) { @@ -640,8 +557,8 @@ static int ipsec_add_security_server(struct sip_msg *msg, struct ipsec_ctx *ctx) return -1; } p = h; - memcpy(p, hdr1->s, hdr1->len); - p += hdr1->len; + memcpy(p, hdr1.s, hdr1.len); + p += hdr1.len; tmp.s = int2str(ctx->me.spi_s, &tmp.len); memcpy(p, tmp.s, tmp.len); p += tmp.len; @@ -867,22 +784,11 @@ static int w_ipsec_create(struct sip_msg *msg, int *_port_ps, int *_port_pc, goto release_user; } - /* Parse IPSec mode from Security-Client (3GPP TS 33.203 Annex M) */ - { - int mode = ipsec_parse_mode(&sa->ts3gpp.mod_str); - if (mode < 0) { - LM_ERR("unsupported or disabled IPSec mode in Security-Client\n"); - ret = -2; - goto release_user; - } - - ret = -5; - ctx = ipsec_ctx_new(sa, &req->rcv.src_ip, ss, sc, &auth->ck, &auth->ik, 0, 0, - (enum ipsec_mode)mode); - if (!ctx) { - LM_ERR("could not allocate new IPSec ctx\n"); - goto release_user; - } + ret = -5; + ctx = ipsec_ctx_new(sa, &req->rcv.src_ip, ss, sc, &auth->ck, &auth->ik, 0, 0); + if (!ctx) { + LM_ERR("could not allocate new IPSec ctx\n"); + goto release_user; } ipsec_ctx_push(ctx); @@ -939,7 +845,6 @@ str pv_ipsec_ctx_type[] = { str_init("port-s"), /* 6 */ str_init("ik"), /* 7 */ str_init("ck"), /* 8 */ - str_init("mode"), /* 9 - NAT-T mode: "trans" or "UDP-enc-tun" */ }; static int pv_parse_ipsec_ctx_flag(str *name) @@ -1041,15 +946,6 @@ static int pv_get_ipsec_ctx(struct sip_msg *msg, pv_param_t *param, case 8: /* ck */ res->rs = ctx->ck; break; - case 9: /* mode */ - if (ctx->mode == IPSEC_MODE_UDP_ENCAP_TUNNEL) { - res->rs.s = "UDP-enc-tun"; - res->rs.len = 11; - } else { - res->rs.s = "trans"; - res->rs.len = 5; - } - break; default: LM_BUG("invalid name %d\n", name); return -1; @@ -1312,7 +1208,6 @@ static str ipsec_usrloc_spi_us = str_init("ipsec.spi_us"); static str ipsec_usrloc_port_pc = str_init("ipsec.port_pc"); static str ipsec_usrloc_port_uc = str_init("ipsec.port_uc"); static str ipsec_usrloc_port_us = str_init("ipsec.port_us"); -static str ipsec_usrloc_mode = str_init("ipsec.mode"); /* NAT-T mode */ #define UL_GET_S_RET(_c, _s, _d, _e) \ ({ \ @@ -1384,15 +1279,11 @@ static void ipsec_usrloc_restore(ucontact_t *contact) { str ck, ik; int spi_pc, spi_ps, port_pc; - int stored_mode; - enum ipsec_mode mode; sec_agree_body_t sa; struct socket_info *sc, *ss; struct ipsec_user *user; struct ipsec_socket *sock; struct ipsec_ctx *ctx; - int_str_t *mode_val; - LM_DBG("restoring IPSec context for %.*s (%.*s)\n", contact->aor->len, contact->aor->s, contact->c.len, contact->c.s); @@ -1407,24 +1298,7 @@ static void ipsec_usrloc_restore(ucontact_t *contact) ik = UL_GET_S(contact, ipsec_usrloc_ik, "IK"); sa.ts3gpp.alg_str = UL_GET_S(contact, ipsec_usrloc_alg, "ALG"); sa.ts3gpp.prot_str = str_init("esp"); - - /* Restore NAT-T mode if stored, default to transport mode */ - mode_val = ul_ipsec.get_ucontact_key(contact, &ipsec_usrloc_mode); - if (mode_val && !mode_val->is_str) { - stored_mode = mode_val->i; - if (stored_mode == IPSEC_MODE_UDP_ENCAP_TUNNEL && ipsec_nat_traversal) { - mode = IPSEC_MODE_UDP_ENCAP_TUNNEL; - sa.ts3gpp.mod_str = str_init("UDP-enc-tun"); - LM_DBG("restoring NAT-T mode (UDP-enc-tun)\n"); - } else { - mode = IPSEC_MODE_TRANSPORT; - sa.ts3gpp.mod_str = str_init("trans"); - } - } else { - mode = IPSEC_MODE_TRANSPORT; - sa.ts3gpp.mod_str = str_init("trans"); - } - + sa.ts3gpp.mod_str = str_init("trans"); sa.ts3gpp.ealg_str = UL_GET_S(contact, ipsec_usrloc_ealg, "EALG"); spi_pc = UL_GET_I(contact, ipsec_usrloc_spi_pc, "SPI-PC"); spi_ps = UL_GET_I(contact, ipsec_usrloc_spi_ps, "SPI-PS"); @@ -1448,7 +1322,7 @@ static void ipsec_usrloc_restore(ucontact_t *contact) return; } - ctx = ipsec_ctx_new(&sa, &user->ip, ss, sc, &ck, &ik, spi_pc, spi_ps, mode); + ctx = ipsec_ctx_new(&sa, &user->ip, ss, sc, &ck, &ik, spi_pc, spi_ps); if (!ctx) { LM_ERR("could not allocate new IPSec ctx\n"); goto release_user; @@ -1516,8 +1390,6 @@ static void ipsec_usrloc_insert(ucontact_t *contact) ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_port_pc, INT_STR_I(ctx->me.port_c)); ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_port_uc, INT_STR_I(ctx->ue.port_c)); ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_port_us, INT_STR_I(ctx->ue.port_s)); - /* Store NAT-T mode for restoration */ - ul_ipsec.put_ucontact_key(contact, &ipsec_usrloc_mode, INT_STR_I((int)ctx->mode)); /* all good mark the SA as OK */ lock_get(&ctx->lock); From 89e5089c7f0b64462a6832a747edb15f76218106 Mon Sep 17 00:00:00 2001 From: Carsten Date: Thu, 18 Dec 2025 16:10:12 +0100 Subject: [PATCH 3/4] Add CacheDB support for authentication vector synchronization - Introduced clustering and multi-node support in the Auth_aka module. - Added `cachedb_url` parameter to enable synchronization of authentication vectors across OpenSIPS nodes. - Implemented CacheDB functions for storing, fetching, and removing authentication vectors. - Updated README and admin documentation to reflect new features and usage examples. - Modified existing functions to integrate CacheDB operations for cross-node authentication. --- modules/auth_aka/README | 197 +++++++++----- modules/auth_aka/aka_av_mgm.c | 326 +++++++++++++++++++++++- modules/auth_aka/auth_aka.c | 63 ++++- modules/auth_aka/auth_aka.h | 16 +- modules/auth_aka/doc/auth_aka_admin.xml | 122 ++++++++- 5 files changed, 649 insertions(+), 75 deletions(-) diff --git a/modules/auth_aka/README b/modules/auth_aka/README index 335c535dadf..e528c6b4909 100644 --- a/modules/auth_aka/README +++ b/modules/auth_aka/README @@ -8,50 +8,52 @@ Auth_aka Module 1.1. Overview 1.2. Authentication Vectors 1.3. Supported algorithms - 1.4. Dependencies + 1.4. Clustering / Multi-Node Support + 1.5. Dependencies - 1.4.1. OpenSIPS Modules - 1.4.2. External Libraries or Applications + 1.5.1. OpenSIPS Modules + 1.5.2. External Libraries or Applications - 1.5. Exported Parameters + 1.6. Exported Parameters - 1.5.1. default_av_mgm (string) - 1.5.2. default_qop (string) - 1.5.3. default_algorithm (string) - 1.5.4. hash_size (integer) - 1.5.5. sync_timeout (integer) - 1.5.6. async_timeout (integer) - 1.5.7. unused_timeout (integer) - 1.5.8. unused_timeout (integer) + 1.6.1. default_av_mgm (string) + 1.6.2. default_qop (string) + 1.6.3. default_algorithm (string) + 1.6.4. hash_size (integer) + 1.6.5. sync_timeout (integer) + 1.6.6. async_timeout (integer) + 1.6.7. unused_timeout (integer) + 1.6.8. pending_timeout (integer) + 1.6.9. cachedb_url (string) - 1.6. Exported Functions + 1.7. Exported Functions - 1.6.1. aka_www_authorize([realm]]) - 1.6.2. aka_proxy_authorize([realm]]) - 1.6.3. aka_www_challenge([av_mgm[, realm[ ,qop[, + 1.7.1. aka_www_authorize([realm]]) + 1.7.2. aka_proxy_authorize([realm]]) + 1.7.3. aka_www_challenge([av_mgm[, realm[ ,qop[, alg]]]]) - 1.6.4. aka_proxy_challenge([realm]]) - 1.6.5. aka_av_add(public_identity, private_identity, + 1.7.4. aka_proxy_challenge([realm]]) + 1.7.5. aka_av_add(public_identity, private_identity, authenticate, authorize, confidentiality_key, integrity_key[, algorithms]) - 1.6.6. aka_av_drop(public_identity, + 1.7.6. aka_av_drop(public_identity, private_identity, authenticate) - 1.6.7. aka_av_drop_all(public_identity, + 1.7.7. aka_av_drop_all(public_identity, private_identity[, count]) - 1.6.8. aka_av_fail(public_identity, + 1.7.8. aka_av_fail(public_identity, private_identity[, count]) - 1.7. Exported MI Functions + 1.8. Exported MI Functions - 1.7.1. aka_av_add - 1.7.2. aka_av_drop - 1.7.3. aka_av_drop_all - 1.7.4. aka_av_fail + 1.8.1. aka_av_add + 1.8.2. aka_av_drop + 1.8.3. aka_av_drop_all + 1.8.4. aka_av_fail 2. Contributors @@ -79,18 +81,19 @@ Auth_aka Module 1.6. async_timeout parameter usage 1.7. unused_timeout parameter usage 1.8. pending_timeout parameter usage - 1.9. aka_www_authorize usage - 1.10. aka_proxy_authorize usage - 1.11. aka_www_challenge usage - 1.12. aka_proxy_challenge usage - 1.13. aka_av_add usage - 1.14. aka_av_drop usage - 1.15. aka_av_drop_all usage - 1.16. aka_av_fail usage - 1.17. aka_av_add usage - 1.18. aka_av_drop usage - 1.19. aka_av_drop_all usage - 1.20. aka_av_drop usage + 1.9. cachedb_url parameter usage + 1.10. aka_www_authorize usage + 1.11. aka_proxy_authorize usage + 1.12. aka_www_challenge usage + 1.13. aka_proxy_challenge usage + 1.14. aka_av_add usage + 1.15. aka_av_drop usage + 1.16. aka_av_drop_all usage + 1.17. aka_av_fail usage + 1.18. aka_av_add usage + 1.19. aka_av_drop usage + 1.20. aka_av_drop_all usage + 1.21. aka_av_drop usage Chapter 1. Admin Guide @@ -148,23 +151,55 @@ Chapter 1. Admin Guide algorithms as well, but the response cannot be handled by this module, and an appropriate error will be returned. -1.4. Dependencies +1.4. Clustering / Multi-Node Support -1.4.1. OpenSIPS Modules + In distributed deployments where multiple OpenSIPS nodes handle + SIP REGISTER requests, the AKA authentication flow requires that + the authentication vector (AV) issued in the 401 challenge is + available on the node that receives the subsequent authenticated + REGISTER. + + To support this scenario, the module can optionally use the + OpenSIPS CacheDB infrastructure to synchronize authentication + vectors across nodes. When the cachedb_url parameter is set: + + * AVs are stored in the external cache when created + * AVs are fetched from the cache when not found locally + * AV state changes are synchronized to the cache + * AVs are removed from the cache when they expire + + Supported CacheDB backends include: + * Redis (cachedb_redis module) + * MongoDB (cachedb_mongodb module) + * Cassandra (cachedb_cassandra module) + * Any other CacheDB-compatible backend + + Example configuration for a clustered setup: + +loadmodule "cachedb_redis.so" +loadmodule "auth_aka.so" +modparam("cachedb_redis", "cachedb_url", "redis://redis-cluster:6379/") +modparam("auth_aka", "cachedb_url", "redis://redis-cluster:6379/") + +1.5. Dependencies + +1.5.1. OpenSIPS Modules The module depends on the following modules (in the other words the listed modules must be loaded before this module): * auth -- Authentication framework * AV manage module -- at least one module that fetches AVs and pushes them in the AV storage + * cachedb_* module (optional) -- required only if cachedb_url + is set for multi-node AV synchronization -1.4.2. External Libraries or Applications +1.5.2. External Libraries or Applications This module does not depend on any external library. -1.5. Exported Parameters +1.6. Exported Parameters -1.5.1. default_av_mgm (string) +1.6.1. default_av_mgm (string) The default AV Manager used in case the functions do not provide them explicitly. @@ -174,7 +209,7 @@ Chapter 1. Admin Guide modparam("auth_aka", "default_av_mgm", "diameter") # fetch AVs through t he Cx interface -1.5.2. default_qop (string) +1.6.2. default_qop (string) The default qop parameter used during challenge, if the functions do not provide them explicitly. @@ -185,7 +220,7 @@ he Cx interface modparam("auth_aka", "default_qop", "auth,auth-int") -1.5.3. default_algorithm (string) +1.6.3. default_algorithm (string) The default algorithm to be advertise during challenge, if the functions do not provide them explicitly. Note that at least @@ -200,7 +235,7 @@ modparam("auth_aka", "default_qop", "auth,auth-int") modparam("auth_aka", "default_algorithm", "AKAv2-MD5") -1.5.4. hash_size (integer) +1.6.4. hash_size (integer) The size of the hash that stores the AVs for each user. Must be a power of 2 number. @@ -211,7 +246,7 @@ modparam("auth_aka", "default_algorithm", "AKAv2-MD5") modparam("auth_aka", "hash_size", 1024) -1.5.5. sync_timeout (integer) +1.6.5. sync_timeout (integer) The amount of milliseconds a synchronous call should wait for getting an authentication vector. @@ -225,7 +260,7 @@ modparam("auth_aka", "hash_size", 1024) modparam("auth_aka", "sync_timeout", 200) -1.5.6. async_timeout (integer) +1.6.6. async_timeout (integer) The amount of milliseconds an asynchronous call should wait for getting an authentication vector. @@ -242,7 +277,7 @@ modparam("auth_aka", "sync_timeout", 200) modparam("auth_aka", "async_timeout", 2000) -1.5.7. unused_timeout (integer) +1.6.7. unused_timeout (integer) The amount of seconds an authentication vector that has not been used can stay in memory. Once this timeout is reached, the @@ -256,7 +291,7 @@ modparam("auth_aka", "async_timeout", 2000) modparam("auth_aka", "unused_timeout", 120) -1.5.8. unused_timeout (integer) +1.6.8. pending_timeout (integer) The amount of seconds an authentication vector that is being used in the authentication process shall stay in memory. Once @@ -271,9 +306,43 @@ modparam("auth_aka", "unused_timeout", 120) modparam("auth_aka", "pending_timeout", 10) -1.6. Exported Functions +1.6.9. cachedb_url (string) + + If set, this parameter enables the synchronization of + authentication vectors across multiple OpenSIPS nodes through + the CacheDB interface. This is essential for distributed/clustered + deployments where one node may issue the 401 challenge and another + node may receive the authenticated REGISTER request. + + When enabled, authentication vectors are stored in the configured + CacheDB backend (e.g., Redis, MongoDB, Cassandra) with a TTL based + on the pending_timeout parameter plus a small margin. + + The flow for multi-node authentication is: + 1. Node A receives initial REGISTER without credentials + 2. Node A fetches AV, stores it in CacheDB, sends 401 + 3. Node B receives REGISTER with credentials + 4. Node B looks up AV locally, on miss fetches from CacheDB + 5. Node B validates credentials using the cached AV + + If not set (default), authentication vectors are only stored + locally and multi-node authentication will not work. + + Example 1.9. cachedb_url parameter usage + +# Using Redis for AV synchronization +loadmodule "cachedb_redis.so" +modparam("cachedb_redis", "cachedb_url", "redis://localhost:6379/") +modparam("auth_aka", "cachedb_url", "redis://localhost:6379/") + +# Using MongoDB for AV synchronization +loadmodule "cachedb_mongodb.so" +modparam("cachedb_mongodb", "cachedb_url", "mongodb://localhost:27017/opensips") +modparam("auth_aka", "cachedb_url", "mongodb://localhost:27017/opensips") + +1.7. Exported Functions -1.6.1. aka_www_authorize([realm]]) +1.7.1. aka_www_authorize([realm]]) The function verifies credentials according to RFC3310, by using an authentication vector priorly allocated by an @@ -321,7 +390,7 @@ if (!aka_www_authorize("diameter", "siphub.com")) ... -1.6.2. aka_proxy_authorize([realm]]) +1.7.2. aka_proxy_authorize([realm]]) The function behaves the same as aka_www_authorize(), but it authenticates the user from a proxy perspective. It receives @@ -338,7 +407,7 @@ if (!aka_proxy_authorize("siphub.com")) ... -1.6.3. aka_www_challenge([av_mgm[, realm[ ,qop[, alg]]]]) +1.7.3. aka_www_challenge([av_mgm[, realm[ ,qop[, alg]]]]) The function challenges a user agent. It fetches an authentication vector for each algorigthm used through the @@ -408,7 +477,7 @@ if (!aka_www_authorize("siphub.com")) { } ... -1.6.4. aka_proxy_challenge([realm]]) +1.7.4. aka_proxy_challenge([realm]]) The function behaves the same as aka_www_challenge(), but it challenges the user from a proxy perspective. It receives the @@ -429,7 +498,7 @@ if (!aka_proxy_authorize("siphub.com")) ... -1.6.5. aka_av_add(public_identity, private_identity, authenticate, +1.7.5. aka_av_add(public_identity, private_identity, authenticate, authorize, confidentiality_key, integrity_key[, algorithms]) Adds an authentication vector for the user identitied by @@ -467,7 +536,7 @@ uthorize */ "6151667b9ef815c1dcb87473685f062a" /* ik */); ... -1.6.6. aka_av_drop(public_identity, private_identity, authenticate) +1.7.6. aka_av_drop(public_identity, private_identity, authenticate) Drops the authentication vector corresponding to the authenticate/nonce value for an user identitied by @@ -490,7 +559,7 @@ aka_av_drop("sip:test@siphub.com", "test@siphub.com", "KFQ/MpR3cE3V9PxucEQS5KED8uUNYIAALFyk59sIJI4="); ... -1.6.7. aka_av_drop_all(public_identity, private_identity[, count]) +1.7.7. aka_av_drop_all(public_identity, private_identity[, count]) Drops all authentication vectors for an user identitied by public_identity and private_identity. This function is useful @@ -512,7 +581,7 @@ aka_av_drop("sip:test@siphub.com", "test@siphub.com", aka_av_drop_all("sip:test@siphub.com", "test@siphub.com", $var(count)); ... -1.6.8. aka_av_fail(public_identity, private_identity[, count]) +1.7.8. aka_av_fail(public_identity, private_identity[, count]) Marks the engine that an authentication vector query for a user has failed, unlocking the processing of the message. @@ -537,9 +606,9 @@ aka_av_drop_all("sip:test@siphub.com", "test@siphub.com", $var(count)); aka_av_fail("sip:test@siphub.com", "test@siphub.com", 3); ... -1.7. Exported MI Functions +1.8. Exported MI Functions -1.7.1. aka_av_add +1.8.1. aka_av_add Adds an Authentication Vector through the MI interface. @@ -574,7 +643,7 @@ JI4= 6151667b9ef815c1dcb87473685f062a ... -1.7.2. aka_av_drop +1.8.2. aka_av_drop Invalidates an Authentication Vector of an user identified by its authenticate value. @@ -597,7 +666,7 @@ $ opensips-cli -x mi aka_av_drop \ JI4= ... -1.7.3. aka_av_drop_all +1.8.3. aka_av_drop_all Invalidates all Authentication Vectors of an user through the MI interface. @@ -616,7 +685,7 @@ $ opensips-cli -x mi aka_av_drop_all \ test@siphub.com ... -1.7.4. aka_av_fail +1.8.4. aka_av_fail Indicates the fact that the fetching of an authentication vector has failed, unlocking the processing of the message. diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index 4e6233e6422..ea52467a94d 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -30,17 +30,288 @@ static gen_hash_t *aka_users; OSIPS_LIST_HEAD(aka_av_managers); +/* CacheDB AV TTL (set from pending_timeout) */ +static int aka_cdb_av_ttl = 30; -int aka_init_mgm(int hash_size) +/* CacheDB key prefix */ +#define AKA_CDB_KEY_PREFIX "aka_av:" +#define AKA_CDB_KEY_PREFIX_LEN (sizeof(AKA_CDB_KEY_PREFIX) - 1) + +/* Serialization delimiter */ +#define AKA_CDB_DELIM '|' + + +int aka_init_mgm(int hash_size, int pending_timeout) { aka_users = hash_init(hash_size); if (!aka_users) { LM_ERR("cannot create AKA users hash\n"); return -1; } + /* Add some margin to TTL for race conditions */ + aka_cdb_av_ttl = pending_timeout + 5; + return 0; +} + +/* + * Build CacheDB key: aka_av::: + * Returns allocated pkg memory that must be freed by caller + */ +static int aka_cdb_build_key(str *impu, str *impi, str *nonce, str *key) +{ + key->len = AKA_CDB_KEY_PREFIX_LEN + impu->len + 1 + impi->len + 1 + nonce->len; + key->s = pkg_malloc(key->len + 1); + if (!key->s) { + LM_ERR("oom for cachedb key\n"); + return -1; + } + memcpy(key->s, AKA_CDB_KEY_PREFIX, AKA_CDB_KEY_PREFIX_LEN); + memcpy(key->s + AKA_CDB_KEY_PREFIX_LEN, impu->s, impu->len); + key->s[AKA_CDB_KEY_PREFIX_LEN + impu->len] = ':'; + memcpy(key->s + AKA_CDB_KEY_PREFIX_LEN + impu->len + 1, impi->s, impi->len); + key->s[AKA_CDB_KEY_PREFIX_LEN + impu->len + 1 + impi->len] = ':'; + memcpy(key->s + AKA_CDB_KEY_PREFIX_LEN + impu->len + 1 + impi->len + 1, + nonce->s, nonce->len); + key->s[key->len] = '\0'; + return 0; +} + +/* + * Serialize AV to string: state|algmask|alg|authenticate|authorize|ck|ik + * Returns allocated pkg memory that must be freed by caller + */ +static int aka_cdb_serialize_av(struct aka_av *av, str *value) +{ + char state_buf[16], algmask_buf[16], alg_buf[16]; + int state_len, algmask_len, alg_len; + + state_len = snprintf(state_buf, sizeof(state_buf), "%d", av->state); + algmask_len = snprintf(algmask_buf, sizeof(algmask_buf), "%d", av->algmask); + alg_len = snprintf(alg_buf, sizeof(alg_buf), "%d", av->alg); + + value->len = state_len + 1 + algmask_len + 1 + alg_len + 1 + + av->authenticate.len + 1 + av->authorize.len + 1 + + av->ck.len + 1 + av->ik.len; + value->s = pkg_malloc(value->len + 1); + if (!value->s) { + LM_ERR("oom for cachedb value\n"); + return -1; + } + + snprintf(value->s, value->len + 1, "%s%c%s%c%s%c%.*s%c%.*s%c%.*s%c%.*s", + state_buf, AKA_CDB_DELIM, + algmask_buf, AKA_CDB_DELIM, + alg_buf, AKA_CDB_DELIM, + av->authenticate.len, av->authenticate.s, AKA_CDB_DELIM, + av->authorize.len, av->authorize.s, AKA_CDB_DELIM, + av->ck.len, av->ck.s, AKA_CDB_DELIM, + av->ik.len, av->ik.s); return 0; } +/* + * Parse a field from serialized string + * Updates pos to point after the delimiter + */ +static int aka_cdb_parse_field(char *start, char *end, str *field, char **next) +{ + char *delim = memchr(start, AKA_CDB_DELIM, end - start); + if (delim) { + field->s = start; + field->len = delim - start; + *next = delim + 1; + } else { + /* Last field */ + field->s = start; + field->len = end - start; + *next = end; + } + return 0; +} + +/* + * Deserialize AV from string: state|algmask|alg|authenticate|authorize|ck|ik + * Creates a new aka_av in shared memory + */ +static struct aka_av *aka_cdb_deserialize_av(str *value) +{ + struct aka_av *av; + str field; + char *pos, *end; + int state, algmask, alg; + str authenticate, authorize, ck, ik; + char *p; + + pos = value->s; + end = value->s + value->len; + + /* Parse state */ + aka_cdb_parse_field(pos, end, &field, &pos); + if (str2sint(&field, &state) < 0) { + LM_ERR("invalid state in cached AV\n"); + return NULL; + } + + /* Parse algmask */ + aka_cdb_parse_field(pos, end, &field, &pos); + if (str2sint(&field, &algmask) < 0) { + LM_ERR("invalid algmask in cached AV\n"); + return NULL; + } + + /* Parse alg */ + aka_cdb_parse_field(pos, end, &field, &pos); + if (str2sint(&field, &alg) < 0) { + LM_ERR("invalid alg in cached AV\n"); + return NULL; + } + + /* Parse authenticate */ + aka_cdb_parse_field(pos, end, &authenticate, &pos); + + /* Parse authorize */ + aka_cdb_parse_field(pos, end, &authorize, &pos); + + /* Parse ck */ + aka_cdb_parse_field(pos, end, &ck, &pos); + + /* Parse ik */ + aka_cdb_parse_field(pos, end, &ik, &pos); + + /* Allocate AV structure */ + av = shm_malloc(sizeof(*av) + authenticate.len + authorize.len + ck.len + ik.len); + if (!av) { + LM_ERR("oom for cached AV\n"); + return NULL; + } + memset(av, 0, sizeof(*av)); + av->state = state; + av->algmask = algmask; + av->alg = alg; + + p = av->buf; + av->authenticate.s = p; + av->authenticate.len = authenticate.len; + memcpy(p, authenticate.s, authenticate.len); + p += authenticate.len; + + av->authorize.s = p; + av->authorize.len = authorize.len; + memcpy(p, authorize.s, authorize.len); + p += authorize.len; + + av->ck.s = p; + av->ck.len = ck.len; + memcpy(p, ck.s, ck.len); + p += ck.len; + + av->ik.s = p; + av->ik.len = ik.len; + memcpy(p, ik.s, ik.len); + + INIT_LIST_HEAD(&av->list); + av->ts = av->new_ts = get_ticks(); + + LM_DBG("deserialized AV state=%d algmask=%d alg=%d nonce=%.*s\n", + av->state, av->algmask, av->alg, av->authenticate.len, av->authenticate.s); + return av; +} + +/* + * Store AV in CacheDB + */ +int aka_cdb_store_av(str *impu, str *impi, struct aka_av *av) +{ + str key, value; + int ret = -1; + + if (!aka_cdb) { + return 0; /* CacheDB not configured, silently succeed */ + } + + if (aka_cdb_build_key(impu, impi, &av->authenticate, &key) < 0) + return -1; + + if (aka_cdb_serialize_av(av, &value) < 0) { + pkg_free(key.s); + return -1; + } + + LM_DBG("storing AV key=%.*s ttl=%d\n", key.len, key.s, aka_cdb_av_ttl); + if (aka_cdbf.set(aka_cdb, &key, &value, aka_cdb_av_ttl) < 0) { + LM_ERR("failed to store AV in cachedb\n"); + } else { + ret = 0; + } + + pkg_free(key.s); + pkg_free(value.s); + return ret; +} + +/* + * Fetch AV from CacheDB + * Returns new AV allocated in shm memory, or NULL if not found + */ +struct aka_av *aka_cdb_fetch_av(str *impu, str *impi, str *nonce) +{ + str key, value; + struct aka_av *av = NULL; + + if (!aka_cdb) { + return NULL; /* CacheDB not configured */ + } + + if (aka_cdb_build_key(impu, impi, nonce, &key) < 0) + return NULL; + + value.s = NULL; + value.len = 0; + + LM_DBG("fetching AV key=%.*s\n", key.len, key.s); + if (aka_cdbf.get(aka_cdb, &key, &value) <= 0 || value.s == NULL) { + LM_DBG("AV not found in cachedb for key=%.*s\n", key.len, key.s); + pkg_free(key.s); + return NULL; + } + + av = aka_cdb_deserialize_av(&value); + if (av) { + LM_DBG("fetched AV from cachedb key=%.*s state=%d\n", + key.len, key.s, av->state); + } + + pkg_free(key.s); + pkg_free(value.s); + return av; +} + +/* + * Remove AV from CacheDB + */ +int aka_cdb_remove_av(str *impu, str *impi, str *nonce) +{ + str key; + int ret = -1; + + if (!aka_cdb) { + return 0; /* CacheDB not configured, silently succeed */ + } + + if (aka_cdb_build_key(impu, impi, nonce, &key) < 0) + return -1; + + LM_DBG("removing AV key=%.*s\n", key.len, key.s); + if (aka_cdbf.remove(aka_cdb, &key) < 0) { + LM_DBG("failed to remove AV from cachedb (may not exist)\n"); + } else { + ret = 0; + } + + pkg_free(key.s); + return ret; +} + struct aka_av_mgm *aka_get_mgm(str *name) { @@ -288,6 +559,34 @@ struct aka_av *aka_av_get_nonce(struct aka_user *user, int algmask, str *nonce) av->state = AKA_AV_USED; } cond_unlock(&user->cond); + + /* If not found locally, try CacheDB */ + if (!av && aka_cdb) { + LM_DBG("AV not found locally, checking CacheDB for nonce=%.*s\n", + nonce->len, nonce->s); + av = aka_cdb_fetch_av(&user->impu, &user->impi->impi, nonce); + if (av) { + /* Check algorithm compatibility */ + if (algmask >= 0 && av->algmask >= 0 && !(algmask & av->algmask)) { + LM_DBG("AV found in CacheDB but algorithm mismatch\n"); + shm_free(av); + return NULL; + } + /* Check state - only USING or USED states are valid for authorization */ + if (av->state != AKA_AV_USING && av->state != AKA_AV_USED) { + LM_DBG("AV found in CacheDB but invalid state %d\n", av->state); + shm_free(av); + return NULL; + } + /* Insert into local user's AV list */ + cond_lock(&user->cond); + av->state = AKA_AV_USED; + aka_av_insert(user, av); + cond_unlock(&user->cond); + LM_DBG("AV fetched from CacheDB and inserted locally\n"); + } + } + return av; } @@ -369,6 +668,10 @@ int aka_av_get_new_wait(struct aka_user *user, int algmask, } end: cond_unlock(&user->cond); + /* Update CacheDB with USING state after releasing lock */ + if (ret == 1 && *av) { + aka_cdb_store_av(&user->impu, &user->impi->impi, *av); + } return ret; } @@ -389,6 +692,10 @@ int aka_av_get_new(struct aka_user *user, int algmask, struct aka_av **av) user->error_count--; } cond_unlock(&user->cond); + /* Update CacheDB with USING state after releasing lock */ + if (ret == 1 && *av) { + aka_cdb_store_av(&user->impu, &user->impi->impi, *av); + } return ret; } @@ -448,8 +755,12 @@ static struct aka_av *aka_av_new(int algmask, str *authenticate, str *authorize, return av; } -void aka_av_free(struct aka_av *av) +void aka_av_free(struct aka_av *av, str *impu, str *impi) { + /* Remove from CacheDB if configured */ + if (impu && impi) { + aka_cdb_remove_av(impu, impi, &av->authenticate); + } list_del(&av->list); shm_free(av); } @@ -486,6 +797,11 @@ int aka_av_add(str *pub_id, str *priv_id, int algmask, av->ts = av->new_ts = get_ticks(); ret = 1; LM_DBG("adding av %p\n", av); + + /* Store AV in CacheDB for cross-node synchronization */ + if (aka_cdb_store_av(pub_id, priv_id, av) < 0) { + LM_WARN("failed to store AV in cachedb, cross-node auth may fail\n"); + } end: aka_user_release(user); return ret; @@ -570,6 +886,9 @@ void aka_av_set_new(struct aka_user *user, struct aka_av *av) av->state = AKA_AV_NEW; av->ts = av->new_ts; /* restore the new timestamp */ cond_unlock(&user->cond); + + /* Update state in CacheDB */ + aka_cdb_store_av(&user->impu, &user->impi->impi, av); } void aka_push_async(struct aka_user *user, struct list_head *subs) @@ -605,7 +924,8 @@ static int aka_async_hash_iterator(void *param, str key, void *value) aka_check_expire_async(ticks, it); } list_for_each_safe(it, safe, &user->avs) { - aka_check_expire_av(ticks, list_entry(it, struct aka_av, list)); + aka_check_expire_av(ticks, list_entry(it, struct aka_av, list), + &user->impu, &user->impi->impi); } cond_unlock(&user->cond); aka_user_try_free(user); diff --git a/modules/auth_aka/auth_aka.c b/modules/auth_aka/auth_aka.c index 2dfc93527bc..0063f4215a5 100644 --- a/modules/auth_aka/auth_aka.c +++ b/modules/auth_aka/auth_aka.c @@ -46,9 +46,15 @@ #include "../../parser/parse_to.h" #include "../../parser/parse_uri.h" +#include "../../cachedb/cachedb.h" auth_api_t auth_api; +/* CacheDB support for AV synchronization across nodes */ +static str aka_cachedb_url = {NULL, 0}; +cachedb_funcs aka_cdbf; +cachedb_con *aka_cdb = NULL; + static int aka_www_authorize(struct sip_msg *msg, str *realm); static int aka_proxy_authorize(struct sip_msg *msg, str *realm); static int aka_www_challenge(struct sip_msg *msg, struct aka_av_mgm *mgm, @@ -184,7 +190,8 @@ static const param_export_t params[] = { {"default_algorithm", STR_PARAM, &aka_default_alg_s.s }, {"hash_size", INT_PARAM, &aka_hash_size }, {"sync_timeout", INT_PARAM, &aka_sync_timeout }, - {"async_timeout", INT_PARAM, &aka_async_timeout }, + {"async_timeout", INT_PARAM, &aka_async_timeout }, + {"cachedb_url", STR_PARAM, &aka_cachedb_url.s }, {0, 0, 0} }; @@ -214,9 +221,11 @@ static const mi_export_t mi_cmds[] = { static const dep_export_t deps = { { /* OpenSIPS module dependencies */ { MOD_TYPE_DEFAULT, "auth", DEP_ABORT }, + { MOD_TYPE_CACHEDB, NULL, DEP_SILENT }, { MOD_TYPE_NULL, NULL, 0 }, }, { /* modparam dependencies */ + { "cachedb_url", get_deps_cachedb_url }, { NULL, NULL }, }, }; @@ -276,7 +285,25 @@ static int mod_init(void) } aka_async_timeout /= 1000; /* XXX: add support for milliseconds */ - if (aka_init_mgm(aka_hash_size) < 0) { + /* Initialize CacheDB if URL is provided */ + if (aka_cachedb_url.s) { + aka_cachedb_url.len = strlen(aka_cachedb_url.s); + if (cachedb_bind_mod(&aka_cachedb_url, &aka_cdbf) < 0) { + LM_ERR("cannot bind functions for cachedb_url %.*s\n", + aka_cachedb_url.len, aka_cachedb_url.s); + return -1; + } + aka_cdb = aka_cdbf.init(&aka_cachedb_url); + if (!aka_cdb) { + LM_ERR("cannot connect to cachedb_url %.*s\n", + aka_cachedb_url.len, aka_cachedb_url.s); + return -1; + } + LM_INFO("CacheDB AV sync enabled with %.*s\n", + aka_cachedb_url.len, aka_cachedb_url.s); + } + + if (aka_init_mgm(aka_hash_size, aka_pending_timeout) < 0) { LM_ERR("cannot initialize aka management hash\n"); return -1; } @@ -876,6 +903,33 @@ static int aka_authorize(struct sip_msg *_msg, str *_realm, private_id->len, private_id->s); user = aka_user_find(public_id, private_id); if (user == NULL) { + /* User not found locally - check CacheDB if configured */ + if (aka_cdb && digest->nonce.len) { + LM_DBG("user not found locally, checking CacheDB for nonce %.*s\n", + digest->nonce.len, digest->nonce.s); + av = aka_cdb_fetch_av(public_id, private_id, &digest->nonce); + if (av) { + /* Check state - only USING or USED states are valid */ + if (av->state != AKA_AV_USING && av->state != AKA_AV_USED) { + LM_DBG("AV found in CacheDB but invalid state %d\n", av->state); + shm_free(av); + return STALE_NONCE; + } + /* Create user locally and attach the AV */ + user = aka_user_get(public_id, private_id); + if (user) { + cond_lock(&user->cond); + av->state = AKA_AV_USED; + list_add_tail(&av->list, &user->avs); + cond_unlock(&user->cond); + LM_DBG("created local user from CacheDB AV\n"); + goto av_found; + } else { + shm_free(av); + av = NULL; + } + } + } if (digest->nonce.len) LM_ERR("could not get AKA user %.*s/%.*s with nonce %.*s\n", public_id->len, public_id->s, private_id->len, private_id->s, @@ -893,6 +947,7 @@ static int aka_authorize(struct sip_msg *_msg, str *_realm, ret = STALE_NONCE; goto release; } +av_found: /* now that we are trusting the user, check whether it has an auts * parameter - if it does, we need to re-challenge him */ @@ -1164,7 +1219,7 @@ void aka_check_expire_async(unsigned int ticks, struct list_head *subs) aka_signal_async_resume(param, aka_challenge_resume_tout); } -void aka_check_expire_av(unsigned int ticks, struct aka_av *av) +void aka_check_expire_av(unsigned int ticks, struct aka_av *av, str *impu, str *impi) { int timeout; switch (av->state) { @@ -1186,7 +1241,7 @@ void aka_check_expire_av(unsigned int ticks, struct aka_av *av) return; LM_DBG("removing av %p in state %d after %ds now %ds\n", av, av->state, timeout, ticks); - aka_av_free(av); + aka_av_free(av, impu, impi); } diff --git a/modules/auth_aka/auth_aka.h b/modules/auth_aka/auth_aka.h index 1f5d080cc98..a92f4179287 100644 --- a/modules/auth_aka/auth_aka.h +++ b/modules/auth_aka/auth_aka.h @@ -28,8 +28,13 @@ #include "../../lib/list.h" #include "../../parser/digest/digest_parser.h" #include "../../lib/digest_auth/digest_auth.h" +#include "../../cachedb/cachedb.h" #include "aka_av_mgm.h" +/* CacheDB support for AV synchronization across nodes */ +extern cachedb_funcs aka_cdbf; +extern cachedb_con *aka_cdb; + enum aka_user_state { AKA_USER_STATE_INIT = 0, }; @@ -82,7 +87,7 @@ struct aka_av_mgm { -int aka_init_mgm(int hash_size); +int aka_init_mgm(int hash_size, int pending_timeout); struct aka_av_mgm *aka_get_mgm(str *name); struct aka_av_mgm *aka_load_mgm(str *name); @@ -111,9 +116,14 @@ void aka_pop_async(struct aka_user *user, struct list_head *subs); void aka_pop_unsafe_async(struct aka_user *user, struct list_head *subs); void aka_signal_async(struct aka_user *user, struct list_head *subs); void aka_check_expire_async(unsigned int ticks, struct list_head *subs); -void aka_check_expire_av(unsigned int ticks, struct aka_av *av); -void aka_av_free(struct aka_av *av); +void aka_check_expire_av(unsigned int ticks, struct aka_av *av, str *impu, str *impi); +void aka_av_free(struct aka_av *av, str *impu, str *impi); void aka_async_expire(unsigned int ticks, void* param); +/* CacheDB helper functions */ +int aka_cdb_store_av(str *impu, str *impi, struct aka_av *av); +struct aka_av *aka_cdb_fetch_av(str *impu, str *impi, str *nonce); +int aka_cdb_remove_av(str *impu, str *impi, str *nonce); + #endif /* AUTH_AKA_H */ diff --git a/modules/auth_aka/doc/auth_aka_admin.xml b/modules/auth_aka/doc/auth_aka_admin.xml index d17081c4355..d1a3fbec771 100644 --- a/modules/auth_aka/doc/auth_aka_admin.xml +++ b/modules/auth_aka/doc/auth_aka_admin.xml @@ -77,6 +77,64 @@
+
+ Clustering / Multi-Node Support + + In distributed deployments where multiple OpenSIPS nodes handle + SIP REGISTER requests, the AKA authentication flow requires that + the authentication vector (AV) issued in the 401 challenge is + available on the node that receives the subsequent authenticated + REGISTER. + + + To support this scenario, the module can optionally use the + OpenSIPS CacheDB infrastructure to synchronize authentication + vectors across nodes. When the + parameter is set: + + + AVs are stored in the external cache when created + + + AVs are fetched from the cache when not found locally + + + AV state changes are synchronized to the cache + + + AVs are removed from the cache when they expire + + + + + Supported CacheDB backends include: + + + Redis (cachedb_redis module) + + + MongoDB (cachedb_mongodb module) + + + Cassandra (cachedb_cassandra module) + + + Any other CacheDB-compatible backend + + + + + Clustered setup configuration + + +loadmodule "cachedb_redis.so" +loadmodule "auth_aka.so" +modparam("cachedb_redis", "cachedb_url", "redis://redis-cluster:6379/") +modparam("auth_aka", "cachedb_url", "redis://redis-cluster:6379/") + + +
+
Dependencies
@@ -95,6 +153,13 @@ them in the AV storage + + + cachedb_* module (optional) + -- required only if + is set for multi-node AV synchronization + +
@@ -246,7 +311,7 @@ modparam("auth_aka", "unused_timeout", 120)
- <varname>unused_timeout</varname> (integer) + <varname>pending_timeout</varname> (integer) The amount of seconds an authentication vector that is being used in the authentication process shall stay in memory. @@ -267,6 +332,61 @@ modparam("auth_aka", "pending_timeout", 10)
+
+ <varname>cachedb_url</varname> (string) + + If set, this parameter enables the synchronization of + authentication vectors across multiple OpenSIPS nodes through + the CacheDB interface. This is essential for distributed/clustered + deployments where one node may issue the 401 challenge and another + node may receive the subsequent authenticated REGISTER request. + + + When enabled, authentication vectors are stored in the configured + CacheDB backend (e.g., Redis, MongoDB, Cassandra) with a TTL based + on the parameter plus a + small margin. + + + The flow for multi-node authentication is: + + + Node A receives initial REGISTER without credentials + + + Node A fetches AV, stores it in CacheDB, sends 401 + + + Node B receives REGISTER with credentials + + + Node B looks up AV locally, on miss fetches from CacheDB + + + Node B validates credentials using the cached AV + + + + + If not set (default), authentication vectors are only stored + locally and multi-node authentication will not work. + + + <varname>cachedb_url</varname> parameter usage + + +# Using Redis for AV synchronization +loadmodule "cachedb_redis.so" +modparam("cachedb_redis", "cachedb_url", "redis://localhost:6379/") +modparam("auth_aka", "cachedb_url", "redis://localhost:6379/") + +# Using MongoDB for AV synchronization +loadmodule "cachedb_mongodb.so" +modparam("cachedb_mongodb", "cachedb_url", "mongodb://localhost:27017/opensips") +modparam("auth_aka", "cachedb_url", "mongodb://localhost:27017/opensips") + + +
From c20db0ad4fc2f1255da2500a28c820112c553440 Mon Sep 17 00:00:00 2001 From: Carsten Date: Thu, 18 Dec 2025 17:02:03 +0100 Subject: [PATCH 4/4] auth: Add forward declaration for aka_av_insert function --- modules/auth_aka/aka_av_mgm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/auth_aka/aka_av_mgm.c b/modules/auth_aka/aka_av_mgm.c index ea52467a94d..c92a57767d6 100644 --- a/modules/auth_aka/aka_av_mgm.c +++ b/modules/auth_aka/aka_av_mgm.c @@ -30,6 +30,9 @@ static gen_hash_t *aka_users; OSIPS_LIST_HEAD(aka_av_managers); +/* Forward declaration for static functions */ +static void aka_av_insert(struct aka_user *user, struct aka_av *av); + /* CacheDB AV TTL (set from pending_timeout) */ static int aka_cdb_av_ttl = 30;