ip_conntrack_sip模块用于为SIP协议建立所需的连接跟踪。支持指定最大8个监听端口,多个端口号使用逗号分隔。另外在加载模块时可指定的参数有:
- sip_timeout 主SIP会话的超时时长,默认3600秒
- sip_direct_signalling 仅接受注册服务器发出的呼叫,默认为1
- sip_direct_media 仅在交互信令的两端建立媒体流,默认为1
- sip_external_media 支持与非交互信令的端点建立媒体流,默认为0
# modprobe ip_conntrack_sip ports=5060
#define SIP_PORT 5060
#define SIP_TIMEOUT 3600
#define MAX_PORTS 8
static unsigned short ports[MAX_PORTS];
static unsigned int ports_c;
module_param_array(ports, ushort, &ports_c, 0400);
static unsigned int sip_timeout __read_mostly = SIP_TIMEOUT;
module_param(sip_timeout, uint, 0600);
static int sip_direct_signalling __read_mostly = 1;
module_param(sip_direct_signalling, int, 0600);
static int sip_direct_media __read_mostly = 1;
module_param(sip_direct_media, int, 0600);
static int sip_external_media __read_mostly = 0;
对于每个SIP端口,初始化四个helper结构,分别用于IPv4和IPv6的UDP和TCP协议,但是处理函数只有两个sip_help_udp和sip_help_tcp,可同时处理IPv4和IPv6协议。如果在加载此模块时,没有指定端口号,使用默认的SIP端口5060(SIP_PORT)。注册函数将helper链接到全局链表nf_ct_helper_hash。
static struct nf_conntrack_helper sip[MAX_PORTS * 4] __read_mostly;
static int __init nf_conntrack_sip_init(void)
{
NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_sip_master));
if (ports_c == 0)
ports[ports_c++] = SIP_PORT;
for (i = 0; i < ports_c; i++) {
nf_ct_helper_init(&sip[4 * i], AF_INET, IPPROTO_UDP,
HELPER_NAME, SIP_PORT, ports[i], i,
sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp,
NULL, THIS_MODULE);
nf_ct_helper_init(&sip[4 * i + 1], AF_INET, IPPROTO_TCP,
HELPER_NAME, SIP_PORT, ports[i], i,
sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp,
NULL, THIS_MODULE);
nf_ct_helper_init(&sip[4 * i + 2], AF_INET6, IPPROTO_UDP,
HELPER_NAME, SIP_PORT, ports[i], i,
sip_exp_policy, SIP_EXPECT_MAX, sip_help_udp,
NULL, THIS_MODULE);
nf_ct_helper_init(&sip[4 * i + 3], AF_INET6, IPPROTO_TCP,
HELPER_NAME, SIP_PORT, ports[i], i,
sip_exp_policy, SIP_EXPECT_MAX, sip_help_tcp,
NULL, THIS_MODULE);
}
ret = nf_conntrack_helpers_register(sip, ports_c * 4);
如下测试配置。
# echo 1 > /proc/sys/net/ipv4/ip_forward
#
# modprobe nf_nat_sip
#
# iptables -t nat -A POSTROUTING -s 50.1.1.0/24 -j SNAT --to-source 192.168.1.127
#
# iptables -t raw -A PREROUTING -p udp -m udp --dport 5060 -j CT --helper sip
UDP协议SIP信令
以下处理函数,根据UDP头部长度,确定SIP数据的起始位置和长度。以及更新连接跟踪的超时时间。交由process_sip_msg处理。
static int sip_help_udp(struct sk_buff *skb, unsigned int protoff,
struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
unsigned int dataoff, datalen;
const char *dptr;
/* No Data ? */
dataoff = protoff + sizeof(struct udphdr);
if (dataoff >= skb->len)
return NF_ACCEPT;
nf_ct_refresh(ct, skb, sip_timeout * HZ);
if (unlikely(skb_linearize(skb)))
return NF_DROP;
dptr = skb->data + dataoff;
datalen = skb->len - dataoff;
if (datalen < strlen("SIP/2.0 200"))
return NF_ACCEPT;
return process_sip_msg(skb, ct, protoff, dataoff, &dptr, &datalen);
TCP协议SIP信令
对于TCP协议,不处理握手阶段的TCP报文。之后,根据TCP头部长度,确定SIP数据的起始位置和长度。以及更新连接跟踪的超时时间。
static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
struct tcphdr *th, _tcph;
const char *dptr, *end;
s16 diff, tdiff = 0;
int ret = NF_ACCEPT;
if (ctinfo != IP_CT_ESTABLISHED &&
ctinfo != IP_CT_ESTABLISHED_REPLY)
return NF_ACCEPT;
/* No Data ? */
th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
if (th == NULL)
return NF_ACCEPT;
dataoff = protoff + th->doff * 4;
if (dataoff >= skb->len)
return NF_ACCEPT;
nf_ct_refresh(ct, skb, sip_timeout * HZ);
if (unlikely(skb_linearize(skb)))
return NF_DROP;
dptr = skb->data + dataoff;
datalen = skb->len - dataoff;
if (datalen < strlen("SIP/2.0 200"))
return NF_ACCEPT;
首先在报文数据中,搜索Content-Length字符串,其表示随后SDP数据的长度,如下示例报文:
INVITE sip:1001@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/TCP 50.1.1.2:56658;alias;rport;branch=z9hG4bK1365174431
From: <sip:1002@192.168.1.109>;tag=1136953115
Content-Type: application/sdp
Content-Length: 496
v=0
o=yate 1642668860 1642668860 IN IP4 50.1.1.2
s=SIP Call
c=IN IP4 50.1.1.2
t=0 0
m=audio 29898 RTP/AVP 0 8 3 11 98 97 102 103 104 105 106 101
将其转换为10进制的长度值clen。随后,搜索SIP协议结束字符串(\r\n\r\n),其为SIP消息结尾,随后就是SDP消息了。
while (1) {
if (ct_sip_get_header(ct, dptr, 0, datalen,
SIP_HDR_CONTENT_LENGTH,
&matchoff, &matchlen) <= 0)
break;
clen = simple_strtoul(dptr + matchoff, (char **)&end, 10);
if (dptr + matchoff == end)
break;
term = false;
for (; end + strlen("\r\n\r\n") <= dptr + datalen; end++) {
if (end[0] == '\r' && end[1] == '\n' &&
end[2] == '\r' && end[3] == '\n') {
term = true;
break;
}
}
if (!term)
break;
SIP数据的结尾加上内容数据长度clen为一个完整的消息长度,注意一个报文中可能包含多个SIP消息。这里同UDP一样,交由process_sip_msg处理单个消息,其中如果修改了报文,参数msglen为新的长度值。
之后,偏移到下一条消息的位置,继续处理。剩余的数据长度datalen,需要减去已经处理的数据长度msglen,加上长度的变化值diff。
end += strlen("\r\n\r\n") + clen;
msglen = origlen = end - dptr;
if (msglen > datalen)
return NF_ACCEPT;
ret = process_sip_msg(skb, ct, protoff, dataoff,
&dptr, &msglen);
/* process_sip_* functions report why this packet is dropped */
if (ret != NF_ACCEPT)
break;
diff = msglen - origlen;
tdiff += diff;
dataoff += msglen;
dptr += msglen;
datalen = datalen + diff - msglen;
}
最后,如果nf_nat_sip_hooks有值,例如加载了nf_nat_sip模块,执行其序号调整函数seq_adjust,调整TCP头部的序号值,tdiff为总的长度差值。
if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
const struct nf_nat_sip_hooks *hooks;
hooks = rcu_dereference(nf_nat_sip_hooks);
if (hooks)
hooks->seq_adjust(skb, protoff, tdiff);
}
return ret;
SIP消息处理
SIP回复消息统一由字符串("SIP/2.0 ")开头,据此来判定是请求还是回复类型的消息。完成之后由nf_nat_sip模块的hooks->msg函数来处理消息体。
static int process_sip_msg(struct sk_buff *skb, struct nf_conn *ct,
unsigned int protoff, unsigned int dataoff,
const char **dptr, unsigned int *datalen)
{
const struct nf_nat_sip_hooks *hooks;
int ret;
if (strncasecmp(*dptr, "SIP/2.0 ", strlen("SIP/2.0 ")) != 0)
ret = process_sip_request(skb, protoff, dataoff, dptr, datalen);
else
ret = process_sip_response(skb, protoff, dataoff, dptr, datalen);
if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
hooks = rcu_dereference(nf_nat_sip_hooks);
if (hooks && !hooks->msg(skb, protoff, dataoff, dptr, datalen)) {
nf_ct_helper_log(skb, ct, "cannot NAT SIP message");
ret = NF_DROP;
}
}
return ret;
SIP请求消息
内核处理以下的6类SIP信令消息。
static const struct sip_handler sip_handlers[] = {
SIP_HANDLER("INVITE", process_invite_request, process_invite_response),
SIP_HANDLER("UPDATE", process_sdp, process_update_response),
SIP_HANDLER("ACK", process_sdp, NULL),
SIP_HANDLER("PRACK", process_sdp, process_prack_response),
SIP_HANDLER("BYE", process_bye_request, NULL),
SIP_HANDLER("REGISTER", process_register_request, process_register_response),
};
如果Via字段中通告的源端口和连接跟踪记录的源端口不相等,表明请求端在不同的端口接收响应报文,记录下Via中的端口号,回复流量将会用到。此处是为了处理Cisco的一些IP电话,其发送请求使用一个源端口,但是固定在5060端口(通过Via字段中端口号表示)等待回复。
static int process_sip_request(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
union nf_inet_addr addr;
/* Many Cisco IP phones use a high source port for SIP requests, but
* listen for the response on port 5060. If we are the local
* router for one of these phones, save the port number from the
* Via: header so that nf_nat_sip can redirect the responses to
* the correct port.
*/
if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
SIP_HDR_VIA_UDP, NULL, &matchoff,
&matchlen, &addr, &port) > 0 &&
port != ct->tuplehash[dir].tuple.src.u.udp.port &&
nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.src.u3))
ct_sip_info->forced_dport = port;
对于SIP请求,开头的格式如:
INVITE sip:1001@192.168.1.109
Max-Forwards: 20
Via: SIP/2.0/TCP 50.1.1.2:56658;alias;rport;branch=z9hG4bK1365174431
From: <sip:1002@192.168.1.109>;tag=1136953115
To: <sip:1001@192.168.1.109>
Call-ID: 1096881037@192.168.1.109
CSeq: 48 INVITE
这里使用Method字段(INVITE)与sip_handlers数组成员进行对比,确定是哪一类消息,调用相应消息的处理函数进行处理。另外,查找SIP_HDR_CSEQ字段(如 CSeq: 8 INVITE),获得序号。
for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
const struct sip_handler *handler;
handler = &sip_handlers[i];
if (handler->request == NULL)
continue;
if (*datalen < handler->len + 2 ||
strncasecmp(*dptr, handler->method, handler->len))
continue;
if ((*dptr)[handler->len] != ' ' ||
!isalpha((*dptr)[handler->len+1]))
continue;
if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
&matchoff, &matchlen) <= 0) {
nf_ct_helper_log(skb, ct, "cannot parse cseq");
return NF_DROP;
}
cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
if (!cseq && *(*dptr + matchoff) != '0') {
nf_ct_helper_log(skb, ct, "cannot get cseq");
return NF_DROP;
}
return handler->request(skb, protoff, dataoff, dptr, datalen, cseq);
}
return NF_ACCEPT;
SIP回复消息
对于SIP响应消息,格式如:
SIP/2.0 200 OK
Via: SIP/2.0/TCP 50.1.1.2:56658;received=50.1.1.2;alias;rport=56658;branch=z9hG4bK1365174431
Record-Route: <sip:192.168.1.109;transport=tcp;lr>
From: <sip:1002@192.168.1.109>;tag=1136953115
To: <sip:1001@192.168.1.109>;tag=774899820
Call-ID: 1096881037@192.168.1.109
CSeq: 48 INVITE
Server: YATE/6.1.0
Contact: <sip:1001@192.168.1.114:63181>
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Type: application/sdp
Content-Length: 506
首先解析响应代码;其次,查找序号字符串,获得序号值。
static int process_sip_response(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
if (*datalen < strlen("SIP/2.0 200"))
return NF_ACCEPT;
code = simple_strtoul(*dptr + strlen("SIP/2.0 "), NULL, 10);
if (!code) {
nf_ct_helper_log(skb, ct, "cannot get code");
return NF_DROP;
}
if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
&matchoff, &matchlen) <= 0) {
nf_ct_helper_log(skb, ct, "cannot parse cseq");
return NF_DROP;
}
cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
if (!cseq && *(*dptr + matchoff) != '0') {
nf_ct_helper_log(skb, ct, "cannot get cseq");
return NF_DROP;
}
matchend = matchoff + matchlen + 1;
对于SIP响应消息,通过序号之后的method字段来确定消息类型。在数组sip_handlers中找到相应的处理函数进行处理。
for (i = 0; i < ARRAY_SIZE(sip_handlers); i++) {
const struct sip_handler *handler;
handler = &sip_handlers[i];
if (handler->response == NULL)
continue;
if (*datalen < matchend + handler->len ||
strncasecmp(*dptr + matchend, handler->method, handler->len))
continue;
return handler->response(skb, protoff, dataoff, dptr, datalen, cseq, code);
}
return NF_ACCEPT;
SIP注册请求消息
注册请求如下示例:
REGISTER sip:192.168.1.109 SIP/2.0
Contact: <sip:1001@192.168.1.114:5060>
Expires: 600
To: <sip:1001@192.168.1.109>
Via: SIP/2.0/UDP 192.168.1.114:5060;rport;branch=z9hG4bK912085676
From: <sip:1001@192.168.1.109>;tag=601050461
Call-ID: 2045540049@192.168.1.109
CSeq: 3 REGISTER
User-Agent: YATE/6.1.0
Max-Forwards: 70
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Length: 0
注册信令之后,在将来某个时刻可能接收到呼叫请求(如:INVITE),函数process_register_request提前为其创建永久expectation,当接收到注册成功响应消息时,将此expectation激活。
首先,取得Expires字段中指定的超时时长(如:600)。
static int process_register_request(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen, unsigned int cseq)
{
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
/* Expected connections can not register again. */
if (ct->status & IPS_EXPECTED)
return NF_ACCEPT;
/* We must check the expiration time: a value of zero signals the
* registrar to release the binding. We'll remove our expectation
* when receiving the new bindings in the response, but we don't
* want to create new ones.
*
* The expiration time may be contained in Expires: header, the
* Contact: header parameters or the URI parameters.
*/
if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES,
&matchoff, &matchlen) > 0)
expires = simple_strtoul(*dptr + matchoff, NULL, 10);
如下,SIP客户端下线时,发送Expires等于零的注册信令,解除绑定。
REGISTER sip:192.168.1.109 SIP/2.0
Contact: <sip:1001@192.168.1.114:5060>
Expires: 0
To: <sip:1001@192.168.1.109>
Call-ID: 282775376@192.168.1.109
Via: SIP/2.0/UDP 192.168.1.114:5060;rport;branch=z9hG4bK699650197
From: <sip:1001@192.168.1.109>;tag=1019243247
CSeq: 3 REGISTER
查找SIP_HDR_CONTACT字段,解析其中的地址和端口号字段,如果地址字段和连接跟踪的源地址不相同,表明客户端在为第三方注册,不支持这种情况。即注册消息使用一个源地址,而期待响应消息到另外一个地址的情况。
随后,解析Contact字段中的字符串(transport=),如果没有此字段,将使用连接跟踪记录的传输协议。最后,查找Contact中是否包含(expires=)字符串,获取超时值。超时值为零时,表明通知注册服务器取消绑定。此时不需要创建expectation会话。
ret = ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
SIP_HDR_CONTACT, NULL,
&matchoff, &matchlen, &daddr, &port);
if (ret < 0) {
nf_ct_helper_log(skb, ct, "cannot parse contact");
return NF_DROP;
} else if (ret == 0)
return NF_ACCEPT;
/* We don't support third-party registrations */
if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, &daddr))
return NF_ACCEPT;
if (ct_sip_parse_transport(ct, *dptr, matchoff + matchlen, *datalen, &proto) == 0)
return NF_ACCEPT;
if (ct_sip_parse_numerical_param(ct, *dptr,
matchoff + matchlen, *datalen,
"expires=", NULL, NULL, &expires) < 0) {
nf_ct_helper_log(skb, ct, "cannot parse expires");
return NF_DROP;
}
if (expires == 0) {
ret = NF_ACCEPT;
goto store_cseq;
}
创建nf_conntrack_expect结构,如果设置了sip_direct_signalling(默认为1),源地址使用连接跟踪反方向的源地址,否则使用空地址;源端口为空;目的地址和目的端口号为在以上Contact中解析的地址和端口号。此expectation为之后的服务端主动发起的连接所需要,其帮助服务端绕过防火墙规则(使用related),如下:
# iptables -I FORWARD 1 -m state --state ESTABLISHED,RELATED -j ACCEPT
此expectation为了服务端响应报文创建,具有永久(PERMANENT)和非活动(INACTIVE)属性,当接收到响应报文之后,修改为活动(ACTIVE)。
exp = nf_ct_expect_alloc(ct);
if (!exp) {
nf_ct_helper_log(skb, ct, "cannot alloc expectation");
return NF_DROP;
}
saddr = NULL;
if (sip_direct_signalling)
saddr = &ct->tuplehash[!dir].tuple.src.u3;
nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct),
saddr, &daddr, proto, NULL, &port);
exp->timeout.expires = sip_timeout * HZ;
exp->helper = nfct_help(ct)->helper;
exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;
如果nf_nat_ip模块加载,并且此连接需要进行NAT,交由其处理注册信令消息,此处expectation的源地址已经明确为服务器的地址,目的地址和目的端口由expect指向的函数(nf_nat_sip_expect)决定,之后,其将创建related关系。
否则,以上不成立,由函数nf_ct_expect_related创建related关系,将expectation连接到全局链表(nf_ct_expect_hash),以及当前连接跟踪的expectations链表。最后,保存注册消息的序号,以便在接收到响应时,进行匹配判断。
hooks = rcu_dereference(nf_nat_sip_hooks);
if (hooks && ct->status & IPS_NAT_MASK)
ret = hooks->expect(skb, protoff, dataoff, dptr, datalen,
exp, matchoff, matchlen);
else {
if (nf_ct_expect_related(exp, 0) != 0) {
nf_ct_helper_log(skb, ct, "cannot add expectation");
ret = NF_DROP;
} else
ret = NF_ACCEPT;
}
nf_ct_expect_put(exp);
store_cseq:
if (ret == NF_ACCEPT)
ct_sip_info->register_cseq = cseq;
SIP注册响应消息
在注册响应消息中,如果响应码为1XX,表明为临时消息,不进行处理;对于非2XX的响应码,发生错误,清空expectation。响应码2XX为成功类型消息码。
SIP/2.0 200 OK
To: <sip:1001@192.168.1.109>;tag=117fc8cface42e6d25bf31f1b817dec2.09d1
Via: SIP/2.0/UDP 192.168.1.114:5060;received=192.168.1.114;rport=5060;branch=z9hG4bK912085676
From: <sip:1001@192.168.1.109>;tag=601050461
Call-ID: 2045540049@192.168.1.109
CSeq: 3 REGISTER
Contact: <sip:1001@192.168.1.114:5060>;expires=600
Server: OpenSIPS (2.4.4 (x86_64/linux))
Content-Length: 0
之后,获取超时值(SIP_HDR_EXPIRES)。以上注册响应示例没有单独的Expires字段,其位于Contact字段。
static int process_register_response(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff, const char **dptr, unsigned int *datalen,
unsigned int cseq, unsigned int code)
{
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
unsigned int matchoff, matchlen, coff = 0;
unsigned int expires = 0;
int in_contact = 0, ret;
/* According to RFC 3261, "UAs MUST NOT send a new registration until
* they have received a final response from the registrar for the
* previous one or the previous REGISTER request has timed out".
*
* However, some servers fail to detect retransmissions and send late
* responses, so we store the sequence number of the last valid
* request and compare it here.
*/
if (ct_sip_info->register_cseq != cseq)
return NF_ACCEPT;
if (code >= 100 && code <= 199)
return NF_ACCEPT;
if (code < 200 || code > 299)
goto flush;
if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES,
&matchoff, &matchlen) > 0)
expires = simple_strtoul(*dptr + matchoff, NULL, 10);
解析Contact中的地址和端口信息,可能有多个地址端口对,如下:
Contact: <sip:1002@192.168.1.135:53663>;expires=491, <sip:1002@192.168.1.135:36252>;expires=600
如果解析到地址与连接跟踪同方向的目的地址不相同,不处理,这相当于当前客户端在为其它第三方设备做注册(在注册请求消息中同样检测了此种情况,直接放行不处理)。
查找Contact中的下一个地址字段(首次in_contact为零,之后为1,来做区分),解析到Contact末尾结束,跳出循环。接下来尝试查找Contact中的(expires=)字符串,获取超时值。不为零的情况下更新信令的expectation。
while (1) {
unsigned int c_expires = expires;
ret = ct_sip_parse_header_uri(ct, *dptr, &coff, *datalen,
SIP_HDR_CONTACT, &in_contact,
&matchoff, &matchlen,
&addr, &port);
if (ret < 0) {
nf_ct_helper_log(skb, ct, "cannot parse contact");
return NF_DROP;
} else if (ret == 0)
break;
/* We don't support third-party registrations */
if (!nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3, &addr))
continue;
if (ct_sip_parse_transport(ct, *dptr, matchoff + matchlen,
*datalen, &proto) == 0)
continue;
ret = ct_sip_parse_numerical_param(ct, *dptr,
matchoff + matchlen,
*datalen, "expires=",
NULL, NULL, &c_expires);
if (ret < 0) {
nf_ct_helper_log(skb, ct, "cannot parse expires");
return NF_DROP;
}
if (c_expires == 0)
break;
对于解除绑定的注册信令,在响应报文中没有Expires字段,默认c_expires为零,执行flush_expectations清除之前创建的信令类型的expectation。
SIP/2.0 200 OK
To: <sip:1001@192.168.1.109>;tag=117fc8cface42e6d25bf31f1b817dec2.3cdd
Call-ID: 282775376@192.168.1.109
Via: SIP/2.0/UDP 192.168.1.114:5060;received=192.168.1.114;rport=5060;branch=z9hG4bK699650197
From: <sip:1001@192.168.1.109>;tag=1019243247
CSeq: 3 REGISTER
Server: OpenSIPS (2.4.4 (x86_64/linux))
Content-Length: 0
遍历连接跟踪的expectation链表,根据地址,端口号和协议找到匹配的expectation,将其NF_CT_EXPECT_INACTIVE属性去掉。
if (refresh_signalling_expectation(ct, &addr, proto, port,
c_expires))
return NF_ACCEPT;
}
flush:
flush_expectations(ct, false);
return NF_ACCEPT;
SIP邀请消息
收到Invite请求信令,如下示例:
INVITE sip:1002@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/UDP 192.168.1.114:5060;rport;branch=z9hG4bK190551602
From: <sip:1001@192.168.1.109>;tag=1003764480
To: <sip:1002@192.168.1.109>
Call-ID: 1905338113@192.168.1.109
CSeq: 5 INVITE
User-Agent: YATE/6.1.0
Contact: <sip:1001@192.168.1.114:5060>
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Type: application/sdp
Content-Length: 506
v=0
o=yate 1643183047 1643183047 IN IP4 192.168.1.114
s=SIP Call
c=IN IP4 192.168.1.114
t=0 0
m=audio 31518 RTP/AVP 0 8 3 11 98 97 102 103 104 105 106 101
a=rtpmap:0 PCMU/8000
首先清空之前的媒体类型的expectation,之后由process_sdp函数处理。
static int process_invite_request(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen, unsigned int cseq)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
flush_expectations(ct, true);
ret = process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
if (ret == NF_ACCEPT)
ct_sip_info->invite_cseq = cseq;
return ret;
对于Invite响应信令,如果响应码为1XX或者2XX,处理也是主要由process_sdp完成;否则,为错误类型的响应码,清除媒体类型的expectation。
static int process_invite_response(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int cseq, unsigned int code)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
if ((code >= 100 && code <= 199) ||
(code >= 200 && code <= 299))
return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
else if (ct_sip_info->invite_cseq == cseq)
flush_expectations(ct, true);
return NF_ACCEPT;
SIP更新响应消息
Update请求消息由函数process_sdp进行处理,Update回复消息由process_update_response处理,最终,也是调用process_sdp进行处理。
static int process_update_response(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int cseq, unsigned int code)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
if ((code >= 100 && code <= 199) ||
(code >= 200 && code <= 299))
return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
else if (ct_sip_info->invite_cseq == cseq)
flush_expectations(ct, true);
return NF_ACCEPT;
PRACK响应消息
临时ACK(PRovisional Acknowledgement)请求消息由函数process_sdp进行处理,PRACK回复消息由process_prack_response处理,最终,也是调用process_sdp进行处理。
static int process_prack_response(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int cseq, unsigned int code)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
if ((code >= 100 && code <= 199) ||
(code >= 200 && code <= 299))
return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq);
else if (ct_sip_info->invite_cseq == cseq)
flush_expectations(ct, true);
return NF_ACCEPT;
SIP结束请求消息
收到Bye信令,如下:
BYE sip:1001@192.168.1.114:5060 SIP/2.0
Call-ID: 1905338113@192.168.1.109
From: <sip:1002@192.168.1.109>;tag=1394983491
To: <sip:1001@192.168.1.109>;tag=1003764480
P-RTP-Stat: PS=393,OS=62880,PR=402,OR=64320,PL=0
Via: SIP/2.0/UDP 192.168.1.109:5060;branch=z9hG4bK8444.76f83b16.0
Via: SIP/2.0/UDP 192.168.1.135:60088;received=192.168.1.135;rport=60088;branch=z9hG4bK840227390
CSeq: 62 BYE
User-Agent: YATE/6.1.0
Max-Forwards: 69
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Length: 0
清空媒体类型的expectation。
static int process_bye_request(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int cseq)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
flush_expectations(ct, true);
return NF_ACCEPT;
会话表述协议SDP
支持三种类型的媒体流,如下:audio、video和image。
static const struct sdp_media_type sdp_media_types[] = {
SDP_MEDIA_TYPE("audio ", SIP_EXPECT_AUDIO),
SDP_MEDIA_TYPE("video ", SIP_EXPECT_VIDEO),
SDP_MEDIA_TYPE("image ", SIP_EXPECT_IMAGE),
};
static const struct sdp_media_type *sdp_media_type(const char *dptr,
unsigned int matchoff, unsigned int matchlen)
{
const struct sdp_media_type *t;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(sdp_media_types); i++) {
t = &sdp_media_types[i];
if (matchlen < t->len ||
strncmp(dptr + matchoff, t->name, t->len))
continue;
return t;
}
return NULL;
连接跟踪解析以下四个SDP头部类型。
static const struct sip_header ct_sdp_hdrs_v4[] = {
[SDP_HDR_VERSION] = SDP_HDR("v=", NULL, digits_len),
[SDP_HDR_OWNER] = SDP_HDR("o=", "IN IP4 ", sdp_addr_len),
[SDP_HDR_CONNECTION] = SDP_HDR("c=", "IN IP4 ", sdp_addr_len),
[SDP_HDR_MEDIA] = SDP_HDR("m=", NULL, media_len),
};
static const struct sip_header ct_sdp_hdrs_v6[] = {
[SDP_HDR_VERSION] = SDP_HDR("v=", NULL, digits_len),
[SDP_HDR_OWNER] = SDP_HDR("o=", "IN IP6 ", sdp_addr_len),
[SDP_HDR_CONNECTION] = SDP_HDR("c=", "IN IP6 ", sdp_addr_len),
[SDP_HDR_MEDIA] = SDP_HDR("m=", NULL, media_len),
};
如下INVATE请求信令中的SDP数据,在结尾省略了部分字段:
INVITE sip:1002@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/UDP 192.168.1.114:5060;rport;branch=z9hG4bK190551602
From: <sip:1001@192.168.1.109>;tag=1003764480
To: <sip:1002@192.168.1.109>
Call-ID: 1905338113@192.168.1.109
CSeq: 5 INVITE
User-Agent: YATE/6.1.0
Contact: <sip:1001@192.168.1.114:5060>
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Type: application/sdp
Content-Length: 506
v=0
o=yate 1643183047 1643183047 IN IP4 192.168.1.114
s=SIP Call
c=IN IP4 192.168.1.114
t=0 0
m=audio 31518 RTP/AVP 0 8 3 11 98 97 102 103 104 105 106 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
首先,搜索SDP_HDR_VERSION,即字符串(v=),找到SDP消息的开始位置。
static int process_sdp(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen, unsigned int cseq)
{
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
union nf_inet_addr caddr, maddr, rtp_addr;
const struct nf_nat_sip_hooks *hooks;
const struct sdp_media_type *t;
int ret = NF_ACCEPT;
hooks = rcu_dereference(nf_nat_sip_hooks);
/* Find beginning of session description */
if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
SDP_HDR_VERSION, SDP_HDR_UNSPEC,
&matchoff, &matchlen) <= 0)
return NF_ACCEPT;
sdpoff = matchoff;
如下SDP的部分数据,接下来解析Connection信息。
v=0
o=yate 1643183047 1643183047 IN IP4 192.168.1.114
s=SIP Call
c=IN IP4 192.168.1.114
t=0 0
m=audio 31518 RTP/AVP 0 8 3 11 98 97 102 103 104 105 106 101
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
在找到(c=)字符串之后,还要继续搜索其后的("IN IP4 ")字符串(最后有空格),定位到地址字符串。如果没有找到(c=)字符串,找到SDP_HDR_MEDIA,即字符串(m=),也要结束搜索。
/* The connection information is contained in the session description
* and/or once per media description. The first media description marks
* the end of the session description. */
caddr_len = 0;
if (ct_sip_parse_sdp_addr(ct, *dptr, sdpoff, *datalen,
SDP_HDR_CONNECTION, SDP_HDR_MEDIA,
&matchoff, &matchlen, &caddr) > 0)
caddr_len = matchlen;
接下来,循环查找以上列出的三种媒体类型(示例报文中只有一种audio类型)。首先找到字符串(m=),将其后的类型字符串转换为数字表示,如audio对应SIP_EXPECT_AUDIO类别。
mediaoff = sdpoff;
for (i = 0; i < ARRAY_SIZE(sdp_media_types); ) {
if (ct_sip_get_sdp_header(ct, *dptr, mediaoff, *datalen,
SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
&mediaoff, &medialen) <= 0)
break;
/* Get media type and port number. A media port value of zero
* indicates an inactive stream. */
t = sdp_media_type(*dptr, mediaoff, medialen);
if (!t) {
mediaoff += medialen;
continue;
}
mediaoff += t->len;
medialen -= t->len;
接下来,解析此媒体使用的端口号,合法的端口号位于区间[1024,65535]。查找此媒体流有没有指定地址信息,优先使用此处的地址,其次使用Connection中解析到的地址。
port = simple_strtoul(*dptr + mediaoff, NULL, 10);
if (port == 0)
continue;
if (port < 1024 || port > 65535) {
nf_ct_helper_log(skb, ct, "wrong port %u", port);
return NF_DROP;
}
/* The media description overrides the session description. */
maddr_len = 0;
if (ct_sip_parse_sdp_addr(ct, *dptr, mediaoff, *datalen,
SDP_HDR_CONNECTION, SDP_HDR_MEDIA,
&matchoff, &matchlen, &maddr) > 0) {
maddr_len = matchlen;
memcpy(&rtp_addr, &maddr, sizeof(rtp_addr));
} else if (caddr_len)
memcpy(&rtp_addr, &caddr, sizeof(rtp_addr));
else {
nf_ct_helper_log(skb, ct, "cannot parse SDP message");
return NF_DROP;
}
根据以上解析到的地址和端口,创建RTP和RTCP协议流量的expectation。
ret = set_expected_rtp_rtcp(skb, protoff, dataoff,
dptr, datalen,
&rtp_addr, htons(port), t->class,
mediaoff, medialen);
if (ret != NF_ACCEPT) {
nf_ct_helper_log(skb, ct, "cannot add expectation for voice");
return ret;
}
如果nf_nat_sip模块加载,并且连接跟踪需要执行NAT,由其sdp_addr函数处理(m=)字段中RTP地址的NAT替换,参数rtp_addr为新的转换后地址,其在以上函数set_expected_rtp_rtcp中得到更新。
/* Update media connection address if present */
if (maddr_len && hooks && ct->status & IPS_NAT_MASK) {
ret = hooks->sdp_addr(skb, protoff, dataoff,
dptr, datalen, mediaoff,
SDP_HDR_CONNECTION,
SDP_HDR_MEDIA,
&rtp_addr);
if (ret != NF_ACCEPT) {
nf_ct_helper_log(skb, ct, "cannot mangle SDP");
return ret;
}
}
i++;
}
函数最后,更新会话信息,替换包括(o=)和(c=)两个字段中的地址信息。
/* Update session connection and owner addresses */
hooks = rcu_dereference(nf_nat_sip_hooks);
if (hooks && ct->status & IPS_NAT_MASK)
ret = hooks->sdp_session(skb, protoff, dataoff,
dptr, datalen, sdpoff, &rtp_addr);
return ret;
媒体类型期望连接
以下建立媒体类型的期望连接,此连接为未来某个时刻由相反方向的端点发起,所以,其源和目的地址/端口号是与当前连接的源和目的地址/端口号相反的(NAT情况下还可能会不相等)。
如果通告的自身RTP媒体地址(daddr)与报文的发送源地址不相同,在sip_direct_media为真(默认)的情况下,不进行处理。否则,expectation的源地址设置为连接跟踪反方向的源地址。源地址设备将发起RTP连接到此通告的地址和端口,预先为期建立expectation。
注意,只有在sip_direct_media为真,并且当前信令通告的RTP地址与连接跟踪源地址相等时,才会为expectation的源地址赋值,其它情况下源地址为空(任意地址),这是考虑到其它情况下,RTP的源地址可能并不是连接跟踪中反方向的源地址,而是一个外部地址。
static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
union nf_inet_addr *daddr, __be16 port,
enum sip_expectation_classes class,
unsigned int mediaoff, unsigned int medialen)
{
struct nf_conntrack_expect *exp, *rtp_exp, *rtcp_exp;
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
struct nf_conntrack_tuple tuple;
int direct_rtp = 0, skip_expect = 0, ret = NF_DROP;
saddr = NULL;
if (sip_direct_media) {
if (!nf_inet_addr_cmp(daddr, &ct->tuplehash[dir].tuple.src.u3))
return NF_ACCEPT;
saddr = &ct->tuplehash[!dir].tuple.src.u3;
如果sip_direct_media为零,并且sip_external_media为真,检查外部RTP地址的路由可达性(sip_direct_media为真时,由于和信令地址相同不需要检查),如果路由存在,并且出口设备和信令的出口设备相同,不做处理。
} else if (sip_external_media) {
struct net_device *dev = skb_dst(skb)->dev;
struct net *net = dev_net(dev);
struct flowi fl;
struct dst_entry *dst = NULL;
memset(&fl, 0, sizeof(fl));
switch (nf_ct_l3num(ct)) {
case NFPROTO_IPV4:
fl.u.ip4.daddr = daddr->ip;
nf_ip_route(net, &dst, &fl, false);
break;
case NFPROTO_IPV6:
fl.u.ip6.daddr = daddr->in6;
nf_ip6_route(net, &dst, &fl, false);
break;
}
/* Don't predict any conntracks when media endpoint is reachable
* through the same interface as the signalling peer.
*/
if (dst) {
bool external_media = (dst->dev == dev);
dst_release(dst);
if (external_media) return NF_ACCEPT;
}
}
根据五元组tuple,查看命名空间中是否已经存在此expectation,满足如下条件中的一个即跳出循环:
- 依据tuple找不到expectation;
- 找到的expectation的主连接跟踪就等于当前的连接跟踪;
- 两个连接跟踪使用的不是同一个helper;
- 两个连接跟踪使用的class不相同。
以下部分用于判断两个SIP客户端直接是否可建立直接的RTP流量,已经expectation是否已经存在,避免重复创建。
/* We need to check whether the registration exists before attempting
* to register it since we can see the same media description multiple
* times on different connections in case multiple endpoints receive
* the same call.
* RTP optimization: if we find a matching media channel expectation
* and both the expectation and this connection are SNATed, we assume
* both sides can reach each other directly and use the final
* destination address from the expectation. We still need to keep
* the NATed expectations for media that might arrive from the
* outside, and additionally need to expect the direct RTP stream
* in case it passes through us even without NAT.
*/
memset(&tuple, 0, sizeof(tuple));
if (saddr) tuple.src.u3 = *saddr;
tuple.src.l3num = nf_ct_l3num(ct);
tuple.dst.protonum = IPPROTO_UDP;
tuple.dst.u3 = *daddr;
tuple.dst.u.udp.port = port;
do {
exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple);
if (!exp || exp->master == ct ||
nfct_help(exp->master)->helper != nfct_help(ct)->helper ||
exp->class != class)
break;
运行到这里的条件是,expectation存在,并且其主连接跟踪使用的helper与当前连接是同一个,class也相等(断定由hooks->sdp_media创建)。比如helper使用的都是sip,class都是SIP_EXPECT_AUDIO,唯一的不同是expectation的主连接跟踪和当前连接跟踪不是同一个。这种情况下多个端点(连接)接收到相同的呼叫,expectation已经在第一个信令报文的时候创建。
此时,如果找到的expectation执行了NAT,即目的地址/端口号进行了转换(saved_addr/saved_proto保存NAT之前的原始目的地址/端口号,参见hooks->sdp_media),表明我们的原始tuple(未NAT)和一个NAT的expectation相同。如果我们当前的连接也是需要进行NAT的(IPS_NAT_MASK),将找到的expectation的原始目的地址/端口赋予tuple,继续查找,如果再次找到匹配的expectation,设置skip_expect标志,退出查找。
#if IS_ENABLED(CONFIG_NF_NAT)
if (!direct_rtp &&
(!nf_inet_addr_cmp(&exp->saved_addr, &exp->tuple.dst.u3) ||
exp->saved_proto.udp.port != exp->tuple.dst.u.udp.port) &&
ct->status & IPS_NAT_MASK) {
*daddr = exp->saved_addr;
tuple.dst.u3 = exp->saved_addr;
tuple.dst.u.udp.port = exp->saved_proto.udp.port;
direct_rtp = 1;
} else
#endif
skip_expect = 1;
} while (!skip_expect);
RTP使用偶数端口号,RTCP使用紧邻的下一个奇数端口号。如果以上将direct_rtp设置为真,调用nf_nat_sip模块的sdp_port进行处理,替换报文中的媒体端口号。
base_port = ntohs(tuple.dst.u.udp.port) & ~1;
rtp_port = htons(base_port);
rtcp_port = htons(base_port + 1);
if (direct_rtp) {
hooks = rcu_dereference(nf_nat_sip_hooks);
if (hooks &&
!hooks->sdp_port(skb, protoff, dataoff, dptr, datalen,
mediaoff, medialen, ntohs(rtp_port)))
goto err1;
}
if (skip_expect)
return NF_ACCEPT;
分别为RTP和RTCP创建新的expectation结构,源地址使用的是对端的地址或者空,目的地址/端口使用的是报文中(c=或者m=)中的地址/端口,或者在找到相同expectation时,目的地址/端口使用的是其NAT变换之前保存的地址/端口。
如果当前连接跟踪启用了NAT,并且direct_rtp为零,由函数sdp_media修正RTP和RTCP协议expectation中的目的地址和目的端口号,在启用NAT的情况下,对端看到的目的地址和端口是变化之后的,所以为使对端的RTP/RTCP流量通过,需要调整expectation的目的地址和端口号。
否则,未使能NAT,或者direct_rtp为真的情况下,直接将两个expectation链接到全局链表和连接跟踪上。
rtp_exp = nf_ct_expect_alloc(ct);
if (rtp_exp == NULL)
goto err1;
nf_ct_expect_init(rtp_exp, class, nf_ct_l3num(ct), saddr, daddr,
IPPROTO_UDP, NULL, &rtp_port);
rtcp_exp = nf_ct_expect_alloc(ct);
if (rtcp_exp == NULL)
goto err2;
nf_ct_expect_init(rtcp_exp, class, nf_ct_l3num(ct), saddr, daddr,
IPPROTO_UDP, NULL, &rtcp_port);
hooks = rcu_dereference(nf_nat_sip_hooks);
if (hooks && ct->status & IPS_NAT_MASK && !direct_rtp)
ret = hooks->sdp_media(skb, protoff, dataoff, dptr,
datalen, rtp_exp, rtcp_exp,
mediaoff, medialen, daddr);
else {
/* -EALREADY handling works around end-points that send
* SDP messages with identical port but different media type,
* we pretend expectation was set up.
* It also works in the case that SDP messages are sent with
* identical expect tuples but for different master conntracks.
*/
int errp = nf_ct_expect_related(rtp_exp, NF_CT_EXP_F_SKIP_MASTER);
if (errp == 0 || errp == -EALREADY) {
int errcp = nf_ct_expect_related(rtcp_exp, NF_CT_EXP_F_SKIP_MASTER);
if (errcp == 0 || errcp == -EALREADY)
ret = NF_ACCEPT;
else if (errp == 0)
nf_ct_unexpect_related(rtp_exp);
}
}
如下拓扑,两个客户端50.1.1.2和60.1.1.2,SIP服务器为192.168.1.109,设置sip_direct_media为零:
|--------------------------|
| |
60.1.1.2 ----- | 60.1.1.1 |
| 192.168.1.135 |-------- 192.168.1.109
50.1.1.2 ----- | 50.1.1.1 |
| |
|--------------------------|
例如客户端60.1.1.2发送INVITE呼叫60.1.1.2,其SDP中通告的RTP地址为:60.1.1.2:31600,内核建立以下的expectation(地址0.0.0.0匹配所有),开启NAT的话,创建EXP1,经过NAT转换,新的RTP地址为192.168.1.135:32778;关闭NAT的话,创建EXP2:
Proto srcaddr src_port - dstaddr dst_port
EXP1: UDP 0.0.0.0 00000 - 192.168.1.135 32778 /* sip_direct_media = 0 */
saved_addr = 60.1.1.2
saved_proto.udp.port = 31600
或者
EXP2: UDP 0.0.0.0 00000 - 60.1.1.2 31600
SIP服务器192.168.1.109接收到INVITE信令之后,发送INVIE到客户端50.1.1.2,其SDP中携带的RTP地址为192.168.1.135:32778(NAT),或者60.1.1.2:31600(非NAT)。当内核接收到此INVITE信令之后,分以下情形处理。
情形1 - direct_rtp=0,skip_expect=0
搜索已有expectation链表,没有找到符合要求的expectation,标志位direct_rtp和skip_expect都为零,由hooks->sdp_media创建expectation。例如,以上60.1.1.2客户端发送的INVITE所创建的expectation。
情形2 - direct_rtp=0,skip_expect=1
接收到SIP服务器发送到客户端50.1.1.2的INVITE信令,根据以下tuple查找expectation,找到了符合要求的expectation,但是expectation没有开启NAT,或者当前连接没有开启NAT。此时,匹配到EXP2,无需再新建expectation,也不需要替换报文中媒体地址和端口。
Tuple1: UDP 0.0.0.0 00000 - 192.168.1.135 32778 /* NAT */
或者
Tuple2: UDP 0.0.0.0 00000 - 60.1.1.2 31600 /* 非NAT */
情形3 - direct_rtp=1,skip_expect=0
匹配到EXP1,此expectation经过了NAT转换,并且当前连接也开启了NAT。expectation的原始目的地址和端口号保存在saved_addr和saved_proto中。此expectation的主连接为60.1.1.2发送SDP通告了自身的RTP地址60.1.1.2:31600,NAT将60.1.1.2:31600转换为了192.168.1.135:32778。
设置direct_rtp等于1,既然192.168.1.135:32778的最终目的地址为60.1.1.2:31660,那么现在就用EXP1的原始地址和端口,替换tuple中的目的地址和端口,如下,继续查找。
Tuple3: UDP 0.0.0.0 00000 - 60.1.1.2 31600 /* NAT */
Tuple: UDP 192.168.1.114 18600 - 50.1.1.2 30352
saved_addr = x.x.x.x
saved_proto.udp.port = nnnnn
EXP: UDP 192.168.1.114 18600 - 50.1.1.2 30352
saved_addr = 60.1.1.2
saved_proto.udp.port = 30118
New Tuple: UDP 192.168.1.114 18600 - 60.1.1.2 30118
没有找到符合新Tuple3要求的expectation,skip_expect为零。使用新Tuple3的目的地址和端口,替换报文中的媒体端口(媒体地址在随后的hooks->sdp_addr中替换),即通告最终的地址和端口给客户端50.1.1.2。并且,依据新tuple创建以下expectation。这样,客户端50.1.1.2的RTP流可以直接发送给60.1.1.2。
EXP3: UDP 0.0.0.0 00000 - 60.1.1.2 31600
情形4 - direct_rtp=1,skip_expect=1
使用新Tuple3再次找到了符合要求的expectation。表明需要的expectation都已经创建完成(EXP1和EXP3),不需要创建新的expectation。使用60.1.1.2:31600替换报文中的媒体端口(媒体地址在随后的hooks->sdp_addr中替换)。将RTP地址60.1.1.2:31600直接通告给客户端50.1.1.2,并建立了EXP3,这样,客户端50.1.1.2的RTP流可以直接发送给60.1.1.2。
反之,在INVITE回复消息两次经过NAT设备之后,将建立同样的expectation(使能NAT),使得60.1.1.2的RTP流可直接发送到50.1.1.2。
EXP4: UDP 0.0.0.0 00000 - 192.168.1.135 32798 /* sip_direct_media = 0 */
saved_addr = 50.1.1.2
saved_proto.udp.port = 19188
EXP5: UDP 0.0.0.0 00000 - 50.1.1.2 19188
内核版本 5.10
|