flycun's blog

From one to infinity

Kamailio topoh 模块分析

概述

topoh 模块是Kamailio中的一个拓扑隐藏(Topology Hiding)模块,其主要功能是隐藏SIP消息中暴露服务器拓扑结构的路由头信息,从而保护SIP代理服务器的内部网络架构不被外部知晓。

核心功能

  1. 拓扑信息隐藏:对SIP消息中的路由头信息(如Record-Route, Route)进行编码和解码,隐藏真实的服务器地址和路径。
  2. 无状态兼容:模块设计不受服务器是无状态(stateless)还是有状态(stateful)的影响。
  3. 脚本透明性:对SIP消息的编码/解码对Kamailio脚本解释器是透明的,脚本中接收到的SIP消息始终是解码后的完整内容,所有功能正常可用。
  4. 无缝重启:SIP服务器重启不会影响正在进行的通话。重启后,服务器能继续编码/解码拓扑信息,确保通话不中断。
  5. 集群支持:通过使用相同的 mask_key,多个SIP服务器(如负载均衡后的服务器群)可以共享解码能力。

依赖要求

必需模块

  • rr 模块:必须在 topoh 模块之前加载。服务器必须执行 record_routing 操作,以确保对话内请求(in-dialog requests)能够被正确编码和解码。

外部依赖

  • 无外部库或应用程序依赖。

核心参数详解

1. 安全与编码参数

  • mask_key (str): 用于编码/解码头部信息的密钥(关键词)。

    • 默认值: _static_value_
    • 重要性: 这是拓扑隐藏的核心。所有需要相互解码的服务器必须使用相同的 mask_key
    • 示例: modparam("topoh", "mask_key", "some secret here")
  • mask_ip (str): 用于构建编码后SIP URI的IP地址。

    • 默认值: 127.0.0.8
    • 说明: 可以是任何IP(包括私有IP、非真实IP),但不应是客户端可能使用的地址。该IP不用于实际SIP路由。
    • 示例: modparam("topoh", "mask_ip", "192.168.0.1")
  • mask_callid (integer): 是否对 Call-ID: 头部进行编码。

    • 默认值: 0 (不编码)
    • 注意: 如果启用且使用 dialog 模块终止通话,必须设置 dialog 模块的 lreq_callee_headers 参数包含 TH: dlh\r\n

2. 编码格式参数

  • uparam_name (str): 存储编码值的URI参数名称。
    • 默认值: line
  • uparam_prefix (str): 编码URI参数前添加的前缀。
    • 默认值: sr-
  • vparam_name (str): 存储编码值的Via头参数名称。
    • 默认值: branch
  • vparam_prefix (str): 编码Via头参数前添加的前缀。
    • 默认值: z9hG4bKsr-
  • callid_prefix (str): 编码Call-ID头部前添加的前缀。
    • 默认值: !!:

3. 安全与验证参数

  • sanity_checks (integer):

    • 默认值: 0 (不绑定)
    • 功能: 如果设置为1,模块将绑定 sanity 模块,对接收到的SIP请求执行格式检查,确保请求格式正确后再进行编码/解码。
  • uri_prefix_checks (integer):

    • 默认值: 0 (不检查)
    • 功能: 如果设置为1,模块在解码前会检查URI是否匹配预期的前缀(由 mask_ipuparam_prefix 组成),避免尝试解码非本模块编码的URI。
    • 警告: 如果SIP设备会修改Contact或Record-Route头中的URI(如添加端口5060或插入新参数),则不应启用此选项。

4. 事件回调参数

  • event_callback (str):

    • 默认值: 空(不执行函数)
    • 功能: 指定KEMI配置文件(如Lua, Python脚本)中要执行的函数名,代替 event_route[...] 块。
    • 参数: 该函数接收一个字符串参数,表示触发的事件名称。
    • 示例: modparam("topoh", "event_callback", "ksr_topoh_event")
  • event_mode (int):

    • 默认值: 3 (执行两个事件路由)
    • 功能: 位掩码控制执行哪些 event_route 块:
      • 1: 执行 event_route[topoh:msg-outgoing]
      • 2: 执行 event_route[topoh:msg-sending]
      • 3: 执行两者(默认)

事件路由 (Event Routes)

1. event_route[topoh:msg-outgoing]

  • 触发时机: 在对出站SIP消息进行拓扑隐藏处理之前
  • 目的: 允许在早期决定是否跳过拓扑隐藏。
  • 关键变量: $sndto(ip), $sndto(port), $sndto(proto) 指向目标地址。
  • 消息状态: 不是最终要发送的消息,而是一个内部生成的简化消息。这样设计是为了避免对不需要处理的消息进行完整解析,提高性能。
  • 控制: 如果在此路由中执行 drop,则模块将跳过拓扑隐藏处理。
  • 示例:
    1
    2
    3
    4
    5
    event_route[topoh:msg-outgoing] {
    if($sndto(ip)=="10.1.1.10") {
    drop; // 对此目标不进行拓扑隐藏
    }
    }

2. event_route[topoh:msg-sending]

  • 触发时机: 在 event_route[topoh:msg-outgoing] 之后,对即将发送的SIP消息进行拓扑隐藏处理之前
  • 目的: 允许在发送前对消息进行最终检查或决策。
  • 关键变量: $sndto(ip), $sndto(port), $sndto(proto) 指向目标地址。
  • 消息状态: 这是最终要发送的完整SIP消息
  • 控制: 如果在此路由中执行 drop,则模块将跳过拓扑隐藏处理。
  • 示例:
    1
    2
    3
    4
    5
    event_route[topoh:msg-sending] {
    if(is_request() && $fU=="alice") {
    drop; // 对用户alice的请求不进行拓扑隐藏
    }
    }

topohtopos 模块对比

特性 topoh 模块 topos 模块
存储机制 无状态,不依赖数据库或外部存储。通过密钥和算法直接编码/解码。 有状态,依赖数据库或Redis存储对话和分支信息。
重启影响 无缝,重启后仍能解码旧消息,通话不中断。 可能中断,如果存储数据丢失或未共享,可能无法正确还原拓扑。
复杂性 简单,配置少,易于部署。 复杂,需要配置存储后端和过期时间等。
适用场景 适合简单的拓扑隐藏需求,对性能要求高,希望避免外部依赖的场景。 适合需要更复杂拓扑管理、需要持久化状态的场景。
主要参数 mask_key, mask_ip storage, db_url, branch_expire, dialog_expire

总结

topoh 模块是一个轻量级、无状态、高性能的拓扑隐藏解决方案。它通过密码学方法直接在消息中编码拓扑信息,无需外部存储,非常适合需要简单、可靠且对重启不敏感的拓扑隐藏场景。其核心优势在于简单性和鲁棒性,配置只需关注 mask_keymask_ip 等少数几个关键参数即可。

Kamailio TOPOS 模块分析

概述

TOPOS模块是Kamailio的一个核心功能模块,主要用于实现SIP通信中的拓扑隐藏(Topology Hiding)。该模块通过剥离显示网络拓扑细节的SIP路由头信息,保护SIP代理服务器的内部架构不被外部知晓。

核心功能

  1. 拓扑隐藏:对基于INVITE的对话、MESSAGE请求、OPTIONS请求等进行路由头信息的剥离和还原
  2. 透明操作:对配置编写者完全透明,只需加载模块并根据需要调整参数
  3. 对话支持:同时支持INVITE-based对话和SUBSCRIBE-based的presence对话
  4. 特殊请求处理:自动跳过REGISTER和PUBLISH请求的处理,这些请求通常在本地SIP服务器终止

依赖要求

必需模块

  • rr模块:必须在TOPOS模块之前加载,用于执行record routing,确保对话内请求能够正确编码/解码
  • 数据库模块:用于存储拓扑剥离和还原所需的数据

外部依赖

  • 无外部库或应用程序依赖

存储机制

TOPOS模块支持两种存储后端:

  • db:数据库后端(默认)
  • redis:Redis后端

可通过storage参数配置,如:modparam("topos", "storage", "redis")

主要参数分析

基础配置

  • db_url:数据库连接URL,默认为mysql://kamailio:kamailiorw@localhost/kamailio
  • context:设置TOPOS实例的全局上下文,值长度不超过12个字符

过期时间设置

  • branch_expire:分支记录删除间隔,默认180秒(3分钟)
  • dialog_expire:对话记录删除间隔,默认10800秒(3小时),建议设置为最长通话时长
  • clean_interval:存储记录清理间隔,默认60秒(1分钟)

安全与隐私

  • mask_callid:是否用Kamailio生成的唯一ID替换Call-ID,默认不替换(0)
  • sanity_checks:是否绑定sanity模块进行SIP请求格式检查,默认不检查(0)

联系人控制

  • contact_host:控制Contact头部分的主机部分,默认从Record-Route URI获取
  • contact_mode:控制数据库/redis服务器查找消息数据的键存储模式
    • 0:使用Contact用户(默认)
    • 1:使用Contact URI参数
    • 2:使用AVP变量
  • cparam_name:自定义Contact URI参数名称,默认为”tps”

高级功能

  • event_callback:KEMI配置文件中执行事件的函数名
  • event_mode:控制执行哪些event_route块的位掩码
  • rr_update:是否跟踪和更新re-INVITE中的记录路由变化,默认不更新(0)

XAVU配置

  • xavu_cfg:保存运行时模块使用配置值的根XAVU名称
  • xavu_field_a_contact:A侧Contact头用户部分的字段名(仅contact_mode=2时需要)
  • xavu_field_b_contact:B侧Contact头用户部分的字段名(仅contact_mode=2时需要)
  • xavu_field_contact_host:从XAVU获取Contact头主机部分的字段名

函数与事件路由

函数

  • tps_set_context(ctx):在运行时更新上下文,可从任何路由使用

事件路由

  1. event_route[topos:msg-outgoing]:出站SIP消息拓扑隐藏前执行
  2. event_route[topos:msg-sending]:发送SIP消息前执行,在msg-outgoing之后
  3. event_route[topos:msg-incoming]:入站SIP消息拓扑隐藏前执行
  4. event_route[topos:msg-receiving]:接收SIP消息前执行,在msg-incoming之后

这些事件路由允许在拓扑隐藏处理前后进行条件判断和控制,如使用drop命令可跳过拓扑隐藏处理。

使用建议

  1. 性能优化:根据实际业务需求合理设置过期时间,避免存储过度增长
  2. 安全性:启用sanity_checks进行请求格式验证,提高系统安全性
  3. TLS支持:当使用TLS时,确保contact_host包含受信任CA签名的域名
  4. 监控:利用事件路由功能实现对拓扑隐藏过程的监控和日志记录

TOPOS模块是构建安全、可扩展SIP基础设施的关键组件,通过合理配置可以有效保护网络架构,同时保持SIP通信的完整性和可靠性。

在Kamailio的TOPOS模块中,outgoing/incomingsending/receiving 这四组事件路由代表了两个不同的处理阶段,理解它们的关键在于区分内部处理决策阶段最终发送/接收阶段

我们可以将这四个事件路由分为两组来理解:


第一组:决策与预处理阶段 (outgoing/incoming)

这些事件发生在模块决定是否进行拓扑隐藏之前,使用的是内部生成的简化消息,目的是避免不必要的消息解析开销。

  1. event_route[topos:msg-outgoing]

    • 时机:当Kamailio准备向外部发送一个SIP消息时,TOPOS模块即将开始处理这个出站消息。
    • 目的:让你有机会基于目标地址$sndto(ip), $sndto(port))决定是否完全跳过拓扑隐藏处理。
    • 消息状态:这是一个内部生成的简化消息,不是最终要发送的完整SIP消息。这样设计是为了性能,避免对不需要处理的消息进行完整解析。
    • 典型用法:根据目标IP地址黑名单,决定不对此流量进行拓扑隐藏。
    1
    2
    3
    4
    5
    event_route[topos:msg-outgoing] {
    if($sndto(ip)=="10.1.1.10") { # 特定目标
    drop; # 跳过拓扑隐藏
    }
    }
  2. event_route[topos:msg-incoming]

    • 时机:当Kamailio从外部接收到一个SIP消息时,TOPOS模块即将开始处理这个入站消息。
    • 目的:让你有机会基于源地址$si, $sp)决定是否完全跳过拓扑隐藏处理。
    • 消息状态:同样是一个内部生成的简化消息
    • 典型用法:根据源IP地址白名单,决定不对特定来源的消息进行拓扑隐藏。
    1
    2
    3
    4
    5
    event_route[topos:msg-incoming] {
    if($si=="10.1.1.10") { # 特定来源
    drop; # 跳过拓扑隐藏
    }
    }

核心思想:这一组是快速决策通道,用于在早期、低成本地决定是否应用拓扑隐藏功能。


第二组:最终处理与执行阶段 (sending/receiving)

这些事件发生在模块已经决定进行拓扑隐藏之后,使用的是即将发送或刚接收的完整SIP消息

  1. event_route[topos:msg-sending]

    • 时机:在msg-outgoing之后,TOPOS模块已经完成了拓扑隐藏处理,消息即将被发送到网络。
    • 目的:让你有机会在消息发出前,对最终的、完整的出站SIP消息进行检查或修改。
    • 消息状态:这是最终要发送的完整SIP消息,已经过拓扑隐藏处理。
    • 典型用法:检查或修改特定用户(如alice)的请求头。
    1
    2
    3
    4
    5
    event_route[topos:msg-sending] {
    if(is_request() and $fU=="alice") {
    # 可以在此处修改消息或记录日志
    }
    }
  2. event_route[topos:msg-receiving]

    • 时机:在msg-incoming之后,TOPOS模块已经完成了拓扑隐藏处理(如还原路由信息),消息刚刚从网络接收并处理完毕
    • 目的:让你有机会在消息进入主路由逻辑前,对最终的、完整的入站SIP消息进行检查或修改。
    • 消息状态:这是已处理完毕的完整SIP消息,拓扑信息已还原,准备进入Kamailio的主处理流程。
    • 典型用法:对特定用户的消息进行特殊处理或审计。

核心思想:这一组是精细操作通道,让你在拓扑隐藏处理完成后的最终时刻,对完整的SIP消息进行干预。


总结与流程图

可以将整个流程想象成一个管道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
出站消息流程:
[原始消息] --> event_route[topos:msg-outgoing] (决策: 是否隐藏?)
|
+--(如果未drop)--> [拓扑隐藏处理]
|
+--> event_route[topos:msg-sending] (最终检查)
|
+--> [发送到网络]

入站消息流程:
[从网络接收] --> event_route[topos:msg-incoming] (决策: 是否隐藏?)
|
+--(如果未drop)--> [拓扑还原处理]
|
+--> event_route[topos:msg-receiving] (最终检查)
|
+--> [进入主路由处理]
  • outgoing/incoming早期决策点,使用简化消息,决定是否应用功能。
  • sending/receiving后期执行点,使用完整消息,在功能应用后进行最终操作。

这种设计既保证了性能(早期决策避免不必要的解析),又提供了灵活性(后期可以操作完整消息)。

event_route 触发机制

event_route 是由 Kamailio 模块在特定事件发生时自动触发的,不是由用户代码直接调用。

topos:msg-outgoing 命名规则

命名格式

模块名:事件名

具体解析

  • topos: 模块名,指 Topos 模块(拓扑隐藏模块)
  • msg-outgoing: 事件名,指消息外出事件

触发时机

当 Topos 模块处理外出消息时自动触发此事件路由。

Kamailio 事件路由机制

  1. 模块定义事件
  • Topos 模块定义了 msg-outgoing 事件
  • 当有消息准备发送出去时触发
  1. 事件路由注册
  • 系统自动注册 event_route[topos:msg-outgoing]
  • 当事件发生时自动执行其中的代码
  1. 常见事件路由类型

event_route[dialog:start] # 对话开始事件
event_route[dialog:end] # 对话结束事件
event_route[tm:local-request] # 本地请求事件
event_route[acc:call-start] # 呼叫开始事件
event_route[acc:call-end] # 呼叫结束事件

  1. 工作流程

    1. Topos 模块检测到有消息需要发送
    2. 自动触发 topos:msg-outgoing 事件
    3. 系统查找并执行对应的 event_route[topos:msg-outgoing]
    4. 执行完事件路由后继续正常的消息处理流程

这种机制允许模块在特定时刻插入自定义处理逻辑,而无需修改核心代码

ICE=remove 参数的作用是移除 SDP 中的 ICE 相关信息,让我详细解释一下:

ICE 简介

ICE (Interactive Connectivity Establishment) 是一种 NAT穿透技术,用于在复杂的网络环境中建立媒体连接。

SDP中的ICE信息

在包含 ICE 的 SDP 中,通常有以下内容:

移除前的 SDP 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v=0
o=- 123456 123456 IN IP4 192.168.1.100
s=-
c=IN IP4 192.168.1.100
t=0 0
a=ice-ufrag:123456
a=ice-pwd:abcdef1234567890
a=fingerprint:sha-256 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55
:66:77:88:99:AA:BB:CC:DD:EE:FF
a=setup:actpass
a=candidate:1 1 UDP 2130706431 192.168.1.100 8000 typ host
a=candidate:2 1 UDP 1694498815 203.0.113.100 8000 typ srflx raddr 192.168.1.100 rport
8000
m=audio 8000 RTP/AVP 0 8 101

移除后的 SDP 示例:
1
2
3
4
5
6
v=0
o=- 123456 123456 IN IP4 192.168.1.100
s=-
c=IN IP4 192.168.1.100
t=0 0
m=audio 8000 RTP/AVP 0 8 101

  • 移除的内容
  1. ICE 用户名片段:a=ice-ufrag:
  2. ICE 密码:a=ice-pwd:
  3. 证书指纹:a=fingerprint:
  4. 连接建立方式:a=setup:
  5. 候选地址:a=candidate: 行
  • 为什么要移除 ICE
  1. 简化处理:RTPEngine 会重新生成适合当前网络环境的媒体地址
  2. 网络控制:让代理服务器完全控制媒体路径
  3. 安全考虑:防止内部网络拓扑信息泄露
  4. 兼容性:某些终端设备可能不支持或不需要 ICE

移除 ICE 后,RTPEngine 会根据配置重新生成适合的媒体连接信息。

分支路由(Branch Route)是 SIP 事务处理中的一个重要概念,让我详细解释一下:

分支路由的定义

分支路由是在 SIP 事务处理过程中,当一个请求被分叉(fork)到多个目标
时,为每个分叉分支执行的路由逻辑。

工作场景

  1. 串行分叉(Serial Forking)

INVITE → UserA
UserA 不在线 → UserA的语音邮箱
UserA的语音邮箱无应答 → UserB

  1. 并行分叉(Parallel Forking)

INVITE → UserA (同时发送到多个设备)
UserA的手机
UserA的桌面客户端
UserA的语音邮箱

  1. 负载均衡分叉

INVITE → Dispatcher 分发到多个 B2BUA
B2BUA1, B2BUA2, B2BUA3

分支路由的作用

  1. 每个分支独立处理:
    - 每个分叉的目标都会触发一次分支路由
    - 可以为不同的分支执行不同的处理逻辑
  2. 资源管理:
    - 在分支路由中可以进行资源分配和释放
    - 管理每个分支的媒体资源
  3. 计费和统计:
    - 为每个分支单独记录计费信息
    - 统计每个目标的呼叫成功率
  4. 路由决策:
    - 根据分支的响应情况调整后续路由
    - 实现智能路由选择

在 Kamailio 中的实现

设置分支路由

1
t_on_branch("MANAGE_BRANCH");

分支路由处理

1
2
3
4
5
branch_route[MANAGE_BRANCH] {
xdbg("new branch [$T_branch_idx] to $ru\n");
# $T_branch_idx: 分支索引
# $ru: 请求URI
}

分支路由确保了复杂的 SIP 分叉场景能够得到正确的处理和管理。

在 Kamailio 的路由系统中,return 0 或 return 1
的返回值在主路由中的处理方式如下:

  1. return 1:
    - 表示路由执行成功
    - 路由函数正常返回,继续执行后续的路由逻辑
    - 不会中断消息处理流程
  2. return 0:
    - 表示路由执行失败或不需要继续处理
    - 会停止当前路由函数的执行
    - 但不会中断整个消息处理流程,只是停止当前路由函数
  3. 在主路由中的调用:
    - 当在主路由(如 request_route)中调用子路由时:
    route(REMOVE_CODECS_INBOUND);
    - 无论子路由返回 0 还是 1,主路由都会继续执行后续的语句
    - 返回值主要用于程序逻辑判断,而不是控制主路由的执行流程
  4. exit 指令:
    - 与 return 不同,exit 会完全终止整个消息处理流程
    - 不会执行后续的任何路由逻辑

因此,return 0 或 return 1
主要用于函数内部的逻辑控制和状态返回,而不是终止整个处理流程。

kamailio.cfg 文件的开头部分可以看到这些标志位的定义:

1
2
3
4
5
6
7
8
9
10
11
#!define FLT_ACC 1
#!define FLT_ACCMISSED 2
#!define FLT_ACCFAILED 3
#!define FLT_DLG 4
#!define FLT_NATS 5

#!define FLB_NATB 6
#!define FLB_NATSIPPING 7

#!define FLT_FROM_CORE 20
#!define FLT_FROM_PUBLIC 21

标志位说明:

  1. FLT_ACC (1):用于事务级计费
  2. FLT_ACCMISSED (2):用于未接来电计费
  3. FLT_ACCFAILED (3):用于失败事务计费
  4. FLT_DLG (4):用于对话级计费(CDR生成)
  5. FLT_NATS (5):用于NAT相关处理

使用方式:

  • 设置标志:setflag(FLT_ACC)
  • 检查标志:isflagset(FLT_ACC)
  • 重置标志:resetflag(FLT_ACC)

这些标志位是 Kamailio 的事务标志(transaction flags),用于在消息处理过程中标记特定的处理需求,比如计费、NAT处理、来源标识等。每个标志位用一个唯一的数字标识,在整个 SIP 事务处理过程中保持有效。

工作机制

在 Kamailio 中,setflag() 的工作机制是这样的:

标志位存储机制:

  1. 位图存储:事务标志是用位图(bitmap)方式存储的,每个标志位占用一个二进制位
  2. 独立设置:每次调用 setflag(n) 只会将第 n 位设置为 1,不影响其他位的值
  3. 累积效果:多个标志位可以同时设置,形成一个标志位组合

示例:

1
2
3
setflag(FLT_ACC);     # 设置标志位 1
setflag(FLT_DLG); # 设置标志位 4
setflag(FLT_ACCFAILED); # 设置标志位 3

实际存储的是一个二进制数,其中对应的位都被设置为 1:

  • 标志位 1 (FLT_ACC) = 1
  • 标志位 3 (FLT_ACCFAILED) = 1
  • 标志位 4 (FLT_DLG) = 1
  • 其他位 = 0

检查标志位:

1
2
3
4
5
6
7
if(isflagset(FLT_ACC)) {      # 检查标志位 1 是否设置
# 处理计费逻辑
}

if(isflagset(FLT_DLG)) { # 检查标志位 4 是否设置
# 处理CDR逻辑
}

这种设计允许在同一个事务中同时设置多个不同的处理标志,每种处理逻辑可以独立检查和响应相应的标志位。

在 SIP 协议中,To-tag(To 标签)是一个重要的概念,让我详细介绍一下:

To-tag 的定义

To-tag 是 SIP 消息中 To 头部字段的一个参数,格式为
tag=xxx。它用于唯一标识一个 SIP 对话(dialog)。

To-tag 的作用

  1. 对话标识
  • 初始请求:在初始 INVITE 请求中,To 头部通常不包含 tag 参数
  • 响应消息:当被叫方发送响应时,会在 To 头部添加 tag 参数
  • 后续请求:在同一个对话中的所有后续请求和响应都使用相同的 To-tag
  1. 对话关联

To-tag 与以下参数一起唯一标识一个 SIP 对话:

  • Call-ID:呼叫标识符
  • From-tag:From 头部的 tag 参数
  • To-tag:To 头部的 tag 参数
  1. 对话状态管理
  • 帮助 SIP 代理和用户代理识别消息属于哪个对话
  • 确保对话内消息按照正确的路径路由
  • 维护对话的状态信息

To-tag 的使用场景

在初始请求中:

INVITE sip:bob@example.com SIP/2.0
To: sip:bob@example.com

在响应中(包含 To-tag):

180 Ringing SIP/2.0
To: sip:bob@example.com;tag=123456789

在后续对话内请求中:

BYE sip:alice@example.com SIP/2.0
To: sip:alice@example.com;tag=123456789

在 Kamailio 中的使用

在 has_totag() 函数中:

  • 检查消息的 To 头部是否包含 tag 参数
  • 用于区分初始请求和对话内请求
  • 对话内请求必须包含 To-tag 才能被正确处理

To-tag 是 SIP
对话模型的核心组成部分,确保了复杂呼叫场景下的消息正确路由和状态维护。

使用pjsua

注册 SIP 账号

1
2
3
4
5
6
7
8
9
pjsua --id="sip:2002@192.168.2.230" \
--null-audio \
--no-tcp \
--local-port=5066 \
--registrar="sip:192.168.2.230" \
--username="2002" \
--password="123456" \
--realm="*" \
--outbound sip:192.168.2.230:5091
  • 如果不指定local-port,则使用默认的 5060
  • outbound 指定 SIP 服务器地址

运行pjsua命令后,会看到如下信息:
Pasted image 20250809171558

拨打电话

要拨打电话,请按“m”,然后按回车键。然后以 sip:number@host 格式输入 SIP URI,然后再次按回车键拨打电话。

image.png

接通后,会看到如下信息:
image.png

1
2
You have 1 active call
Current call id=2 to sip:1002@192.168.2.230 [CONFIRMED]

挂断电话

要挂断电话,请按“h”,然后按回车键。

直接拨打

也可以使用以下命令直接拨打电话,无需先注册

1
2
3
4
5
pjsua --id="sip:2002@192.168.2.230" \
--null-audio \
--no-tcp \
--local-port=5066 \
sip:1001@192.168.2.230:5091

配置文件

将参数放配置文件里面,通过–config-file=pjsua.cfg启动

pjsua –config-file=pjsua.cfg

1
2
3
4
5
6
7
8
9
10
11
# pjsua.cfg
--null-audio
--no-tcp
--id=sip:1002@192.168.2.230:5092 #Set the From Header
--auto-answer=200
--local-port=5092
--outbound sip:192.168.2.230:5091;lr

# Add a buddy for the external b2bua
--add-buddy=sip:1001@192.168.2.230
--add-buddy=sip:not_registered@172.16.254.2

pjsua 参数说明:

参数 参数说明(中文翻译)
General options:
--config-file=file 从文件读取配置或参数
--help 显示此帮助信息
--version 显示版本信息
Logging options
--log-file=fname 将日志输出到指定文件(默认为 stderr)
--log-level=N 设置日志最大级别 N(0=无,6=跟踪),默认为 5
--app-log-level=N 设置控制台(stdout)显示的日志最大级别(默认为 4)
--log-append 日志追加到文件末尾,而非覆盖原有内容
--color 使用彩色日志输出(Windows 默认启用)
--no-color 禁用彩色日志输出
--light-bg 为浅色背景使用深色字体(默认为深色背景)
--no-stderr 禁用 stderr 输出
SIP Account options
--registrar=url 设置注册服务器的 URL
--id=url 设置本地 ID 的 URL(用于 From 头部)
--realm=string 设置认证域(realm)
--username=string 设置认证用户名
--password=string 设置认证密码
--contact=url 可选地覆盖 Contact 信息
--contact-params=S 在 Contact 头部中附加指定参数 S
--contact-uri-params=S 在 Contact URI 中附加指定参数 S
--proxy=url 可选的代理服务器 URL(可多次指定)
--reg-timeout=SEC 注册超时时间(默认 300 秒)
--rereg-delay=SEC 自动重注册间隔(默认 300 秒)
--reg-use-proxy=N 控制 REGISTER 请求中代理的使用:0=无代理,1=仅出站代理,2=仅账户代理,3=全部(默认)
--publish 为此账户发送存在状态(presence)PUBLISH 请求
--mwi 订阅消息摘要/等待指示(Message Waiting Indication)
--use-ims 在此账户上启用 3GPP/IMS 相关设置
--use-srtp=N 是否使用 SRTP?0:禁用,1:可选,2:强制,3:通过复制媒体提议实现可选(默认:0)
--srtp-secure=N SRTP 是否要求安全 SIP?0:否,1:TLS,2:SIPS(默认:1)
--use-100rel 要求可靠的临时响应(100rel)
--use-timer=N 是否使用 SIP 会话定时器?(默认:1)0:不活动,1:可选,2:强制,3:始终启用
--timer-se=N 会话定时器超时时间(单位:秒,默认:1800)
--timer-min-se=N 会话定时器最小超时时间(单位:秒,默认:90)
--outb-rid=string 设置 SIP 出站注册 ID(默认:1)
--auto-update-nat=N 启用/禁用对称 NAT 后的 SIP 穿透:0=禁用,1=启用但有例外,2=完全启用(默认)
--disable-stun 禁用此账户的 STUN
--next-cred 添加另一组认证凭据
SIP Account Control
--next-account 添加更多账户
Transport Options
--set-qos 为 SIP 和媒体启用 QoS 标记
--local-port=port 设置 TCP/UDP 端口。此选项隐式在指定端口上启用 TCP 和 UDP 传输,除非显式禁用 TCP 或 UDP
--ip-addr=IP 使用指定 IP 地址作为 SIP 和 RTP 地址(提示:IP 可为 NAT/路由器的公网 IP)
--bound-addr=IP 将传输绑定到指定 IP 接口
--no-tcp 禁用 TCP 传输
--no-udp 禁用 UDP 传输
--nameserver=NS 添加指定的 DNS 服务器以启用 SRV 解析(可多次指定)
--outbound=url 设置全局出站代理服务器 URL(可多次指定)
--stun-srv=FORMAT 设置 STUN 服务器主机或域名(可多次指定),格式为 hostdom[:PORT]
--upnp=if_name 使用指定接口名称启用 UPnP。若未指定接口,则使用第一个找到的接口
TLS Options
--use-tls 启用 TLS 传输(默认:否)
--tls-ca-file 指定 TLS CA 证书文件(默认:无)
--tls-cert-file 指定 TLS 证书文件(默认:无)
--tls-privkey-file 指定 TLS 私钥文件(默认:无)
--tls-password 指定 TLS 私钥文件的密码(默认:无)
--tls-verify-server 验证服务器证书(默认:否)
--tls-verify-client 验证客户端证书(默认:否)
--tls-neg-timeout 指定 TLS 协商超时时间(默认:无)
--tls-cipher 指定首选 TLS 加密套件(可选,可多次指定)
Audio Options
--add-codec=name 手动添加编解码器(默认启用所有)
--dis-codec=name 禁用编解码器(可多次指定)
--clock-rate=N 覆盖会议桥时钟频率
--snd-clock-rate=N 覆盖声音设备时钟频率
--stereo 以立体声模式打开音频设备和会议桥
--null-audio 使用空音频设备(静音)
--play-file=file 将 WAV 文件注册到会议桥(可多次指定)
--play-tone=FORMAT 将音调注册到会议桥。格式为 ‘F1,F2,ON,OFF’,F1/F2 为频率,ON/OFF 为开关持续时间(毫秒)。可多次指定
--auto-play 自动播放文件(仅对来电有效)
--auto-play-hangup 文件播放完成后自动挂断
--auto-loop 自动将入站 RTP 循环到出站 RTP
--auto-conf 自动将通话加入会议
--rec-file=file 打开文件录音器(扩展名可为 .wav 或 .mp3)
--auto-rec 自动录制通话
--quality=N 指定媒体质量(0-10,默认 8)
--ptime=MSEC 覆盖编解码器 ptime 为 MSEC(默认由编解码器决定)
--no-vad 禁用 VAD(语音活动检测)/静音检测(默认由编解码器决定)
--ec-tail=MSEC 设置回声消除器尾长(默认 200 毫秒)
--ec-opt=OPT 选择回声消除算法:0=默认,1=speex,2=抑制器,3=WebRtc,4=WebRtc AEC3
--ilbc-mode=MODE 设置 iLBC 编解码器模式(20 或 30,默认 30)
--capture-dev=id 音频采集设备 ID(默认 -1)
--playback-dev=id 音频播放设备 ID(默认 -1)
--capture-lat=N 音频采集延迟(毫秒,默认 100)
--playback-lat=N 音频播放延迟(毫秒,默认 140)
--snd-auto-close=N 当音频设备空闲 N 秒后自动关闭(默认 1)。N=-1 表示禁用,N=0 表示立即关闭
--no-tones 禁用可听音调
--jb-max-size 指定抖动缓冲区最大大小(毫秒,默认 -1)
--extra-audio 添加一条额外的音频流
Media Transport Options
--use-ice 启用 ICE(默认:否)
--ice-regular 使用 ICE 正常提名(默认为激进提名)
--ice-trickle=N 是否启用 Trickle ICE?0=禁用,1=半启用,2=全启用(默认 0)
--ice-max-hosts=N 设置 ICE 主机候选者最大数量
--ice-no-rtcp 禁用 ICE 中的 RTCP 组件(默认:否)
--rtp-port=N 尝试用于 RTP 的起始端口(默认 4000)
--rx-drop-pct=PCT 接收 RTP 包丢弃百分比(用于模拟丢包,默认 0)
--tx-drop-pct=PCT 发送 RTP 包丢弃百分比(用于模拟丢包,默认 0)
--use-turn 启用 TURN 中继配合 ICE(默认:否)
--turn-srv TURN 服务器域名或主机名(格式 “NAME:PORT”)
--turn-tcp 使用 TCP 连接 TURN 服务器(默认:否)
--turn-user TURN 用户名
--turn-passwd TURN 密码
--rtcp-mux 启用 RTP 与 RTCP 多路复用(默认:否)
--srtp-keying 出站 SDP 提议的 SRTP 密钥交换方式:0=SDES(默认),1=DTLS
TURN TLS Options
--turn-tls 使用 TLS 连接 TURN 服务器(默认:否)
--turn-tls-ca-file 指定 TURN TLS CA 证书文件(默认:无)
--turn-tls-cert-file 指定 TURN TLS 证书文件(默认:无)
--turn-tls-privkey-file 指定 TURN TLS 私钥文件(默认:无)
--turn-tls-privkey-pwd 指定 TURN TLS 私钥文件的密码(默认:无)
--turn-tls-neg-timeout 指定 TURN TLS 协商超时时间(默认:无)
--turn-tls-cipher 指定首选 TURN TLS 加密套件(可选,可多次指定)
Buddy List
--add-buddy url 将指定 URL 添加到好友列表(可多次使用)
User Agent options
--auto-answer=code 自动以指定状态码(如 200)应答来电
--max-calls=N 最大并发通话数(默认 4,最大 255)
--thread-cnt=N 工作线程数量(默认 1)
--duration=SEC 设置最大通话时长(默认无限制)
--norefersub 转接通话时抑制事件订阅
--use-compact-form 使用紧凑格式以减小 SIP 消息大小
--no-force-lr 允许使用严格路由(即不强制添加 lr 参数)
--accept-redirect=N 指定如何处理呼叫重定向(3xx)响应:0=拒绝,1=自动跟随,2=跟随并替换 To 头部(默认),3=询问用户
CLI options
--use-cli 使用命令行界面(CLI)作为用户接口
--cli-telnet-port=N CLI Telnet 服务端口
--no-cli-console 禁用 CLI 控制台

说明:当指定了 SIP URL 时,pjsua 将立即向该 URL 发起呼叫。


摘机前

image.png|800

image.png|800

1、收到第一个INVITE时,身份验证失败,发起挑战。【407后,终端回复的ACK是一个响应消息】
2、第二次收到INVITE时,终端带上验证信息,验证通过。
3、移除身份验证头信息
4、查找被叫用户地址
5、设置分支路由
6、执行有状态转发 KSR.tm.t_relay()
7、触发分支路由
8、执行退出 KSR.x.exit()
9、响应回调函数(响应路由)收到100、180 状态码

摘机后

image.png|800

image.png|800
1、响应路由收到200状态码
2、主叫发送ACK,收到ACK请求
3、进入会话处理
4、执行有状态转发 KSR.tm.t_relay()。

?【ACK这里未触发分支路由,测试发现,为ACK设置分支路由后,也不会触发】
从Kamailio发出去的ack请求,不会触发任何路由

挂机

image.png|800

image.png|800
1、被叫挂机,收到BYE请求
2、进入会话处理
3、设置分支路由(这里为什么会重新设置分支路由,是因为重启了一个事务)
4、转发
5、触发分支路由
6、退出

并未收到200响应码路由?

因为代码里面没有给BYE请求设置响应路由

1
2
3
4
5
6
7
8
9
10
11
12
13
    if KSR.is_method_in("IBSU") then
        if KSR.tm.t_is_set("branch_route") < 0 then
            KSR.tm.t_on_branch("ksr_branch_manage");
            log("设置分支路由")
        end
    end

    if KSR.is_method_in("ISU") then
        if KSR.tm.t_is_set("onreply_route") < 0 then
            KSR.tm.t_on_reply("ksr_onreply_manage");
            log("设置响应路由")
        end
    end

tips:

KSR.x.exit(); 后面的流程就不会执行。

0%