

kamailio NAT模块是公网NAT透传的核心模块,使用kamalio做公网的终端外网注册,SIP trunk对接,和SBC实现多种业务场景时,我们都需要启用这个模块。在很多历史文档中,和在售的电子书也对此模块配置使用有非常深入讲解。今天,我们重走长征路,从一个角度更深层次的代码级的角度来学习Kamailio。
nathelper和 sdpops模块提供了在用户代理(UA)位于网络地址转换(NAT)后面的环境中处理 SIP 信令和会话描述协议(SDP)载荷的核心逻辑。这些模块使 Kamailio 能够检测 NAT 后的端点、修复联系信息、通过保活机制维持 NAT 绑定,并操控媒体参数以确保音视频流的正常传输。

nathelper模块通过比较 IP 数据包的源 IP/端口与 SIP 头部及 SDP 消息体中通告的地址来识别 NAT 后的客户端。
nat_uac_test(flags)函数实现了多项测试来判断 UA 是否位于 NAT 后面:
Contact头部中的 IP 是否为 RFC1918 私有地址。 Via头部中的 IP 进行比较。 Via头部中的端口不同。 一旦检测到 NAT,可使用以下函数对信令进行"修复":
fix_nated_contact():用数据包的实际源 IP 和端口覆盖 Contact头部。 add_contact_alias():在 ContactURI 中追加包含源地址的参数。这是 fix_nated_contact的符合标准的替代方案,在保留原始 URI 的同时允许代理路由回源地址。 fix_nated_register():将源地址提取到 AVP(属性值对)中,以便在注册时由 usrloc模块存储。 下图展示了 REGISTER请求期间 NAT 后的 UA 与 nathelper模块之间的交互。
NAT 穿透逻辑流程
"usrloc 模块""Kamailio (nathelper)""NAT 后的 UA""usrloc 模块""Kamailio (nathelper)""NAT 后的 UA"nat_uac_test(0x01) 返回 True源 IP 提取至 $avp(received)存储 Contact + 接收到的 IPSIP REGISTER (Contact: 192.168.1.10)fix_nated_register()save("location")200 OK
为防止 NAT 绑定过期,nathelper会定期向已注册的 UA 发送"ping"。
Kamailio 支持两种主要的保活类型:
OPTIONS请求。这会强制 UA 响应,产生双向流量,从而可靠地维持 NAT 绑定。 Ping 逻辑由专用定时器进程管理,通过 natping_processes进行配置。 这些进程遍历 usrloc数据库,并通过 sip_pinger.h发送数据包。
NAT Pinger 组件映射
usrloc 模块
nathelper 模块
Kamailio 核心
定时器进程 (pt.c)
nh_timer()
sip_pinger.h / send_ping()
ul_entries (共享内存)
网络套接字
sdpops模块提供了检查和修改 SIP 消息 SDP 消息体的高级能力。nathelper通过 fix_nated_sdp()执行基本的 SDP IP 修复,而 sdpops则允许对媒体流进行精细控制。
sdp_remove_codecs_by_id(codecs, media):从媒体流中删除指定的载荷类型(例如删除 PCMU)。 sdp_keep_codecs_by_name(codecs, media):删除除指定名称之外的所有编解码器(例如保留 "G729")。 sdp_with_media(type):检查 SDP 是否包含特定媒体类型(如 "video" 或 "application")。 sdp_print(level):记录已解析的 SDP 结构以供调试。 该模块依赖核心 SDP 解析器,后者会填充 sdp_info结构体。sdpops使用该结构体在 SIP 消息缓冲区中定位特定行,并通过 data_lump系统应用修改,确保在消息转发时安全地执行变更。
SDP 操控架构
sdpops 模块
SIP 消息
sip_msg_t
SDP 消息体
core/parser/sdp/sdp.c
w_sdp_remove_codecs_by_id()
core/data_lump.h
参数 | 类型 | 说明 |
|---|---|---|
natping_interval | 整数 | NAT ping 之间的间隔秒数,0 表示禁用 ping。 |
ping_nated_only | 整数 | 若为 1,则仅对 usrloc 中带有 NAT 标志的联系人发送 ping。 |
sipping_method | 字符串 | 用于保活的 SIP 方法(默认:OPTIONS)。 |
force_socket | 字符串 | 指定发送 ping 时使用的本地套接字。 25 |
sdpops模块主要向路由脚本导出函数,而非配置参数,因为其行为通常是按呼叫动态决定的。
代码File:src/modules/nathelper/nathelper.c (L85-85)
#include "sip_pinger.h"代码File:src/modules/nathelper/nathelper.c (L97-107)
#define NAT_UAC_TEST_C_1918 0x01
#define NAT_UAC_TEST_RCVD 0x02
#define NAT_UAC_TEST_V_1918 0x04
#define NAT_UAC_TEST_S_1918 0x08
#define NAT_UAC_TEST_RPORT 0x10
#define NAT_UAC_TEST_O_1918 0x20
#define NAT_UAC_TEST_WS 0x40
#define NAT_UAC_TEST_C_PORT 0x80
#define NAT_UAC_TEST_SDP_CLINE 0x100
#define NAT_UAC_TEST_DEST 0x200
代码File:src/modules/nathelper/nathelper.c (L111-135)
static int nat_uac_test_f(struct sip_msg *msg, char *str1, char *str2);
static int fix_nated_contact_f(struct sip_msg *, char *, char *);
static int add_contact_alias_0_f(struct sip_msg *, char *, char *);
static int add_contact_alias_3_f(struct sip_msg *, char *, char *, char *);
static int set_contact_alias_f(struct sip_msg *msg, char *str1, char *str2);
static int w_set_contact_alias_f(struct sip_msg *msg, char *str1, char *str2);
static int handle_ruri_alias_f(struct sip_msg *, char *, char *);
static int handle_ruri_alias_mode_f(sip_msg_t *msg, char *pmode, char *p2);
static int pv_get_rr_count_f(struct sip_msg *, pv_param_t *, pv_value_t *);
static int pv_get_rr_top_count_f(struct sip_msg *, pv_param_t *, pv_value_t *);
static int fix_nated_sdp_f(struct sip_msg *, char *, char *);
static int is_rfc1918_f(struct sip_msg *, char *, char *);
static int nh_extract_mediaip(str *, str *, int *, char *, int);
static int alter_mediaip(struct sip_msg *, str *, str *, int, str *, int, int);
static int fix_nated_register_f(struct sip_msg *, char *, char *);
static int fixup_fix_nated_register(void **param, int param_no);
static int fixup_fix_sdp(void **param, int param_no);
static int fixup_free_fix_sdp(void **param, int param_no);
static int fixup_add_contact_alias(void **param, int param_no);
static int fixup_free_add_contact_alias(void **param, int param_no);
static int add_rcv_param_f(struct sip_msg *, char *, char *);
static int nh_sip_reply_received(sip_msg_t *msg);
static int test_sdp_cline(struct sip_msg *msg);
static int w_set_alias_to_pv(struct sip_msg *msg, char *uri_avp, char *hollow);代码File:src/modules/nathelper/nathelper.c (L140-210)
static void nh_timer(unsigned int, void *);
static int mod_init(void);
static int child_init(int);
static void mod_destroy(void);
static int nathelper_rpc_init(void);
static usrloc_api_t ul;
static int cblen =;
static int natping_interval =;
struct socket_info *force_socket =;
static int nh_nat_addr_mode =;
/* clang-format off */
typedef struct nh_netaddr {
const char *cnetaddr;
uint32_t netaddr;
uint32_t mask;
} nh_netaddr_t;
static nh_netaddr_t nh_nets_1918[] = {
{"10.0.0.0",, 0xffffffffu <<},
{"172.16.0.0",, 0xffffffffu <<},
{"192.168.0.0",, 0xffffffffu <<},
{"100.64.0.0",, 0xffffffffu <<}, /* rfc6598 - cg-nat */
{"192.0.0.0",, 0xffffffffu <<}, /* rfc7335 - IPv4 Service Continuity Prefix */
{NULL,,}
};
static nh_netaddr_t nh_nets_extra[] = {
{"192.0.0.0",, 0xffffffffu <<}, /* rfc7335 - IETF Protocol Assignments */
{NULL,,}
};
/* clang-format on */
/*
* If this parameter is set then the natpinger will ping only contacts
* that have the NAT flag set in user location database
*/
static int ping_nated_only =;
static const char sbuf[] = {,,,};
static str force_socket_str = STR_NULL;
static int sipping_flag = -1;
static int natping_disable_flag = -1;
static int natping_processes =;
static str nortpproxy_str = str_init("a=nortpproxy:yes");
static char *rcv_avp_param = NULL;
static avp_flags_t rcv_avp_type =;
static avp_name_t rcv_avp_name;
static char *natping_socket = NULL;
static int udpping_from_path =;
static int ignore_path =;
static int sdp_oldmediaip =;
static int raw_sock = -1;
static unsigned int raw_ip =;
static unsigned short raw_port =;
static int nh_keepalive_timeout =;
static request_method_t sipping_method_id =;
/* filter contacts by server_id */
static int nh_filter_srvid =;
/*0-> disabled, 1 ->enabled*/
unsigned int *natping_state = NULL;
static str nh_alias_name = str_init("");
本文分享自 FreeSWITCH中文社区 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!