概述
函数nf_nat_sip_init注册SIP协议的NAT helper,用于修改协议报文中的地址和端口信息。如下nf_nat_sip_hooks结构变量sip_hooks,赋值于nf_nat_sip_hooks,在SIP连接跟踪中使用。msg指针函数主要用于修改SIP消息中的字段,其它sdp开头的指针函数修改SDP消息中的字段。
static const struct nf_nat_sip_hooks sip_hooks = {
.msg = nf_nat_sip,
.seq_adjust = nf_nat_sip_seq_adjust,
.expect = nf_nat_sip_expect,
.sdp_addr = nf_nat_sdp_addr,
.sdp_port = nf_nat_sdp_port,
.sdp_session = nf_nat_sdp_session,
.sdp_media = nf_nat_sdp_media,
};
static int __init nf_nat_sip_init(void)
{
BUG_ON(nf_nat_sip_hooks != NULL);
nf_nat_helper_register(&nat_helper_sip);
RCU_INIT_POINTER(nf_nat_sip_hooks, &sip_hooks);
nf_ct_helper_expectfn_register(&sip_nat);
另外,还注册了以下SIP协议的expectation处理函数。
static struct nf_ct_helper_expectfn sip_nat = {
.name = "sip",
.expectfn = nf_nat_sip_expected,
};
如下拓扑,测试SIP helper:
|-------------------------|
| |
| 192.168.1.127 |-----|--- 192.168.1.109
50.1.1.2 ----| 50.1.1.1 | |
| | |
|-------------------------| 192.168.1.126
配置如下,两个SIP客户端50.1.1.2和192.168.1.126,SIP服务器192.168.1.109,以下涉及到的报文都是在NAT网关上获得,测试由SIP客户端50.1.1.2发起,其账号为1001,呼叫1002,其为客户端192.168.1.126的账号:
# echo 1 > /proc/sys/net/ipv4/ip_forward
#
# iptables -t nat -A POSTROUTING -s 50.1.1.2 -o enp6s0 -j SNAT --to-source 192.168.1.127
#
# iptables -A PREROUTING -t raw -p tcp --dport 5060 -j CT --helper sip
#
# modprobe nf_nat_sip sip_direct_media=0
#
# sysctl net.netfilter.nf_conntrack_helper=1
SIP信令处理
如下SIP信令的请求和响应部分开始内容:
INVITE sip:1001@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/UDP 50.1.1.2:55719;rport;branch=z9hG4bK1972895532
--------
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.127:55719;received=192.168.1.127;rport=55719;branch=z9hG4bK1972895532
首先处理Request-Line字段,函数nf_nat_sip通过判断SIP信令的第一行Request-Line,确定是请求还是回复,对于请求信令,解析Request-Line。并且,尝试映射Request-Line中解析到的地址和端口,参见map_addr函数,如下为注册时建立的连接跟踪。
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
其中对Request-Line的地址先进行判断,对于本示例,其为服务器地址192.168.1.109,端口号没有指定,使用默认的5060。其等于以上连接跟踪的目的地址和端口号,将其替换为连接跟踪反方向的源地址和源端口(192.168.1.109::5060),二者相同,并不执行地址替换。
static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff, const char **dptr, unsigned int *datalen)
{
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
if (strncasecmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) {
if (ct_sip_parse_request(ct, *dptr, *datalen,
&matchoff, &matchlen, &addr, &port) > 0 &&
!map_addr(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, &addr, port)) {
nf_ct_helper_log(skb, ct, "cannot mangle SIP message");
return NF_DROP;
}
request = 1;
} else
request = 0;
接下来,解析Via信息(TCP或者UDP)字段,在具有多个Via字段的情况下这里仅处理第一个。对于请求信令,如果Via中地址不等于连接跟踪中记录的同方向的源地址,或者Via端口不等于源端口;或者,对于响应信令,Via中地址或端口不等于连接跟踪中保存的目的地址或者目的端口,表明当前报文不属于此连接,不进行处理。
if (nf_ct_protonum(ct) == IPPROTO_TCP)
hdr = SIP_HDR_VIA_TCP;
else
hdr = SIP_HDR_VIA_UDP;
/* Translate topmost Via header and parameters */
if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
hdr, NULL, &matchoff, &matchlen, &addr, &port) > 0) {
/* We're only interested in headers related to this connection */
if (request) {
if (!nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.src.u3) ||
port != ct->tuplehash[dir].tuple.src.u.udp.port)
goto next;
} else {
if (!nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.dst.u3) ||
port != ct->tuplehash[dir].tuple.dst.u.udp.port)
goto next;
}
否则,尝试替换报文Via字段中的地址和端口,新的端口号由连接跟踪的反方向tuple中获得,参见函数map_addr。如下为内核创建的连接跟踪。
# conntrack -L
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
对于SIP请求,将Via替换为连接跟踪反方向的目的地址和端口,如上将Via中的50.1.1.2替换为192.168.1.127(NAT后地址);对于SIP响应,将Via替换为反方向的源地址和端口,即替换为50.1.1.2,用于还原NAT之前的地址。
olen = *datalen;
if (!map_addr(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, &addr, port)) {
nf_ct_helper_log(skb, ct, "cannot mangle Via header");
return NF_DROP;
}
接下来解析Via中的maddr字段,如果其中的地址与连接跟踪当前方向的源地址相等,并且不等于反方向的目的地址,表明源地址需要执行NAT变换,使用反方向的目的地址替换maddr字段中的地址。其中的原理与以上相同,参见map_addr函数。
matchend = matchoff + matchlen + *datalen - olen;
/* The maddr= parameter (RFC 2361) specifies where to send the reply. */
if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen,
"maddr=", &poff, &plen, &addr, true) > 0 &&
nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.src.u3) &&
!nf_inet_addr_cmp(&addr, &ct->tuplehash[!dir].tuple.dst.u3)) {
buflen = sip_sprintf_addr(ct, buffer, &ct->tuplehash[!dir].tuple.dst.u3, true);
if (!mangle_packet(skb, protoff, dataoff, dptr, datalen,
poff, plen, buffer, buflen)) {
nf_ct_helper_log(skb, ct, "cannot mangle maddr");
return NF_DROP;
}
}
接下来,解析Via中的received字段,服务器使用此字段表明自身接收到的报文的源地址。如果此地址等于连接跟踪当前方向上的目的地址(服务器->客户端,如果192.168.1.127),并且此地址不等于反方向(客户端->服务器)上的源地址(50.1.1.2),表明客户端位于NAT之后,其地址执行了NAT变换。
# conntrack -L
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
将received中地址还原为NAT之前的地址。
/* The received= parameter (RFC 2361) contains the address
* from which the server received the request. */
if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen,
"received=", &poff, &plen, &addr, false) > 0 &&
nf_inet_addr_cmp(&addr, &ct->tuplehash[dir].tuple.dst.u3) &&
!nf_inet_addr_cmp(&addr, &ct->tuplehash[!dir].tuple.src.u3)) {
buflen = sip_sprintf_addr(ct, buffer,
&ct->tuplehash[!dir].tuple.src.u3,
false);
if (!mangle_packet(skb, protoff, dataoff, dptr, datalen,
poff, plen, buffer, buflen)) {
nf_ct_helper_log(skb, ct, "cannot mangle received");
return NF_DROP;
}
}
解析Via中rport字段,服务器使用此字段表明自身接收到的报文的源端口,如果此端口号等于连接跟踪当前方向上的目的端口(服务器->客户端,如下55719),并且此端口号不等于反方向(客户端->服务器)上的源端口(55719),表明客户端位于NAT之后,其端口执行了NAT变换。
# conntrack -L
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
将rport中端口号还原为NAT之前的端口号。注意,以本示例中,NAT变化了源地址,源端口没有改变,因此不需要以下的还原操作。
/* The rport= parameter (RFC 3581) contains the port number
* from which the server received the request. */
if (ct_sip_parse_numerical_param(ct, *dptr, matchend, *datalen,
"rport=", &poff, &plen, &n) > 0 &&
htons(n) == ct->tuplehash[dir].tuple.dst.u.udp.port &&
htons(n) != ct->tuplehash[!dir].tuple.src.u.udp.port) {
__be16 p = ct->tuplehash[!dir].tuple.src.u.udp.port;
buflen = sprintf(buffer, "%u", ntohs(p));
if (!mangle_packet(skb, protoff, dataoff, dptr, datalen,
poff, plen, buffer, buflen)) {
nf_ct_helper_log(skb, ct, "cannot mangle rport");
return NF_DROP;
}
}
}
至此,Via字段处理完成。接下来,解析报文Contact字段。如下为网关接收到的INVITE请求和响应报文的部分字段:
INVITE sip:1001@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/UDP 50.1.1.2:55719;rport;branch=z9hG4bK1972895532
From: <sip:1002@192.168.1.109>;tag=82488285
To: <sip:1001@192.168.1.109>
Call-ID: 1612402514@192.168.1.109
CSeq: 10 INVITE
User-Agent: YATE/6.1.0
Contact: <sip:1002@50.1.1.2:55719>
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.127:55719;received=192.168.1.127;rport=55719;branch=z9hG4bK1972895532
Record-Route: <sip:192.168.1.109;lr>
From: <sip:1002@192.168.1.109>;tag=82488285
To: <sip:1001@192.168.1.109>;tag=370640073
Call-ID: 1612402514@192.168.1.109
CSeq: 10 INVITE
Server: YATE/6.1.0
Contact: <sip:1001@192.168.1.126:65369>
其中可能包含多个地址,逐一进行解析,由函数map_addr尝试替换其中的地址和端口。INVITE请求中的Contact替换为了192.168.1.127:55719发送给了SIP服务器。INVITE响应报文中的Contact没有进行替换,因为其地址192.168.1.126(另外SIP客户端),并不等于连接跟踪中的任何地址。
next:
/* Translate Contact headers */
coff = 0;
in_header = 0;
while (ct_sip_parse_header_uri(ct, *dptr, &coff, *datalen,
SIP_HDR_CONTACT, &in_header,
&matchoff, &matchlen, &addr, &port) > 0) {
if (!map_addr(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, &addr, port)) {
nf_ct_helper_log(skb, ct, "cannot mangle contact");
return NF_DROP;
}
}
以下解析报文from和to字段,以下为INVITE请求和响应消息中的from和to字段示例:
INVITE sip:1001@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/UDP 50.1.1.2:55719;rport;branch=z9hG4bK1972895532
From: <sip:1002@192.168.1.109>;tag=82488285
To: <sip:1001@192.168.1.109>
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.127:55719;received=192.168.1.127;rport=55719;branch=z9hG4bK1972895532
Record-Route: <sip:192.168.1.109;lr>
From: <sip:1002@192.168.1.109>;tag=82488285
To: <sip:1001@192.168.1.109>;tag=370640073
由函数map_sip_addr进行解析和地址替换,内部调用map_addr执行地址转换。对于INVITE请求,与Request-Line原理相同,由于其地址与连接跟踪反方向的源地址相同,替换之后还是原值。
# conntrack -L
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
对于INVITE响应,FROM和TO中的地址与连接跟踪反方向的目的地址相同,替换之后还是原值。
if (!map_sip_addr(skb, protoff, dataoff, dptr, datalen, SIP_HDR_FROM) ||
!map_sip_addr(skb, protoff, dataoff, dptr, datalen, SIP_HDR_TO)) {
nf_ct_helper_log(skb, ct, "cannot mangle SIP from/to");
return NF_DROP;
}
对于Cisco的一些IP电话,其在端口forced_dport监听响应报文,forced_dport在连接跟踪中解析Via字段中的端口得到,此处,将报文的目的端口修改为forced_dport。
/* Mangle destination port for Cisco phones, then fix up checksums */
if (dir == IP_CT_DIR_REPLY && ct_sip_info->forced_dport) {
struct udphdr *uh;
if (skb_ensure_writable(skb, skb->len)) {
nf_ct_helper_log(skb, ct, "cannot mangle packet");
return NF_DROP;
}
uh = (void *)skb->data + protoff;
uh->dest = ct_sip_info->forced_dport;
if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, protoff, 0, 0, NULL, 0)) {
nf_ct_helper_log(skb, ct, "cannot mangle packet");
return NF_DROP;
}
}
return NF_ACCEPT;
SIP信令地址替换
函数map_sip_addr用于解析报文中的from和to头部,由函数map_addr尝试替换其中的地址和端口。
static int map_sip_addr(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff, const char **dptr,
unsigned int *datalen, enum sip_header_types type)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, type, NULL,
&matchoff, &matchlen, &addr, &port) <= 0)
return 1;
return map_addr(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, &addr, port);
如下核心的地址端口映射函数map_addr,如果地址和端口分别与连接跟踪本方向的源地址和源端口相等,替换为反方向的目的地址和目的端口,这样如果此方向执行了SNAT,报文中的字段也执行相应的地址替换。反之,如果地址和端口分别与连接跟踪本方向的目的地址和目的端口相等,还原为反方向的源地址和源端口,即还原为NAT之前的地址和端口。
如果新地址和新端口与旧地址和端口相等,直接返回。否则,替换报文中的相应内容。
另外,对于SIP注册报文,由于在函数nf_nat_sip_expect中已经修改Contact中地址和端口,所以,对于Contact字段中的地址和端口,不等于连接跟踪的源地址和端口,更不等于连接跟踪的目的地址和端口,返回1。
static int map_addr(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int matchoff, unsigned int matchlen,
union nf_inet_addr *addr, __be16 port)
{
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, addr) &&
ct->tuplehash[dir].tuple.src.u.udp.port == port) {
newaddr = ct->tuplehash[!dir].tuple.dst.u3;
newport = ct->tuplehash[!dir].tuple.dst.u.udp.port;
} else if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.dst.u3, addr) &&
ct->tuplehash[dir].tuple.dst.u.udp.port == port) {
newaddr = ct->tuplehash[!dir].tuple.src.u3;
newport = ct_sip_info->forced_dport ? : ct->tuplehash[!dir].tuple.src.u.udp.port;
} else
return 1;
if (nf_inet_addr_cmp(&newaddr, addr) && newport == port)
return 1;
buflen = sip_sprintf_addr_port(ct, buffer, &newaddr, ntohs(newport));
return mangle_packet(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, buffer, buflen);
SDP媒体消息
对于SDP消息,需要替换其中的Owner/连接/媒体的地址和端口号,如下为INVITE消息示例,必要时更新Content-Length,其表示SDP消息的长度。
INVITE sip:1001@192.168.1.109 SIP/2.0
Max-Forwards: 20
Via: SIP/2.0/UDP 50.1.1.2:55719;rport;branch=z9hG4bK1972895532
From: <sip:1002@192.168.1.109>;tag=82488285
To: <sip:1001@192.168.1.109>
Call-ID: 1612402514@192.168.1.109
CSeq: 10 INVITE
User-Agent: YATE/6.1.0
Contact: <sip:1002@50.1.1.2:55719>
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Type: application/sdp
Content-Length: 496
v=0
o=yate 1643511407 1643511407 IN IP4 50.1.1.2
s=SIP Call
c=IN IP4 50.1.1.2
t=0 0
m=audio 21254 RTP/AVP 0 8 3 11 98 97 102 103 104 105 106 101
函数nf_nat_sdp_addr,用于修改媒体(m=)中包含的连接信息(以上示例m=中不包含连接信息),此函数不处理全局的连接信息(以上示例使用的为全局c=信息)。参数type为SDP_HDR_CONNECTION,term为SDP_HDR_MEDIA,参数addr为转换之后的新地址。函数mangle_sdp_packet将其转换为字符串替换报文中的旧地址字符串。
另外,以上替换可能修改SDP报文的长度,需要更新SIP消息中的Content-Length字段。
static unsigned int nf_nat_sdp_addr(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int sdpoff,
enum sdp_header_types type,
enum sdp_header_types term,
const union nf_inet_addr *addr)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
char buffer[INET6_ADDRSTRLEN];
buflen = sip_sprintf_addr(ct, buffer, addr, false);
if (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen,
sdpoff, type, term, buffer, buflen))
return 0;
return mangle_content_len(skb, protoff, dataoff, dptr, datalen);
查找报文中的SDP_HDR_CONNECTION字段,将其替换。
static int mangle_sdp_packet(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int sdpoff,
enum sdp_header_types type,
enum sdp_header_types term,
char *buffer, int buflen)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
unsigned int matchlen, matchoff;
if (ct_sip_get_sdp_header(ct, *dptr, sdpoff, *datalen, type, term,
&matchoff, &matchlen) <= 0)
return -ENOENT;
return mangle_packet(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, buffer, buflen) ? 0 : -EINVAL;
函数nf_nat_sdp_port用于修改报文中的媒体端口(m=),参数port为新的端口号。
static unsigned int nf_nat_sdp_port(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
unsigned int matchoff, unsigned int matchlen,
u_int16_t port)
{
buflen = sprintf(buffer, "%u", port);
if (!mangle_packet(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, buffer, buflen))
return 0;
return mangle_content_len(skb, protoff, dataoff, dptr, datalen);
函数nf_nat_sdp_session用于修改报文中的SDP_HDR_OWNER和SDP_HDR_CONNECTION字段(全局的连接信息字段,非媒体m=字段中的连接信息)。参数addr为新的地址。
static unsigned int nf_nat_sdp_session(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff, const char **dptr, unsigned int *datalen,
unsigned int sdpoff, const union nf_inet_addr *addr)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
/* Mangle session description owner and contact addresses */
buflen = sip_sprintf_addr(ct, buffer, addr, false);
if (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, sdpoff,
SDP_HDR_OWNER, SDP_HDR_MEDIA, buffer, buflen))
return 0;
switch (mangle_sdp_packet(skb, protoff, dataoff, dptr, datalen, sdpoff,
SDP_HDR_CONNECTION, SDP_HDR_MEDIA, buffer, buflen)) {
case 0:
/*
* RFC 2327:
* Session description
*
* c=* (connection information - not required if included in all media)
*/
case -ENOENT:
break;
default:
return 0;
}
return mangle_content_len(skb, protoff, dataoff, dptr, datalen);
以上介绍了SDP中地址和端口的替换,其中使用的新地址和新端口由以下函数nf_nat_sdp_media来确定,过程中还是使用如下的连接跟踪信息。
# conntrack -L
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
对于原始方向,如本方向src=50.1.1.2回复方向dst=192.168.1.127,两者不相等,新媒体地址rtp_addr等于连接跟踪的反方向的目的地址(NAT之后地址192.168.1.127)。此处要建立的为与当前报文方向相反方向的expectation连接,这里仅确定此反向expectation连接的目的地址,源地址已经在连接跟踪中确定(参见set_expected_rtp_rtcp)。如果执行了NAT,目的地址应当为连接跟踪的反方向目的地址(NAT之后地址),而不是本方向的源地址。
反之,对于回复方向,本方向的src=192.168.1.109而反方向的dst=192.168.1.109,两个相同,表明地址没有变化,那么新目的地址rtp_addr等于rtp_exp的目的地址(一般情况下为报文中通告的地址,函数set_expected_rtp_rtcp可能将其改变)。如下,回复消息通告的媒体地址为192.168.1.126,为SIP客户端的真实地址,不需要改变。
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.1.127:55719;received=192.168.1.127;rport=55719;branch=z9hG4bK1972895532
Record-Route: <sip:192.168.1.109;lr>
From: <sip:1002@192.168.1.109>;tag=82488285
To: <sip:1001@192.168.1.109>;tag=370640073
Call-ID: 1612402514@192.168.1.109
CSeq: 10 INVITE
Server: YATE/6.1.0
Contact: <sip:1001@192.168.1.126:65369>
Allow: ACK, INVITE, BYE, CANCEL, OPTIONS, INFO
Content-Type: application/sdp
Content-Length: 506
v=0
o=yate 1643511411 1643511411 IN IP4 192.168.1.126
s=SIP Call
c=IN IP4 192.168.1.126
t=0 0
m=audio 25510 RTP/AVP 0 8 3 11 98 97 102 103 104 105 106 101
以下在修改目的地址之前,保存连接原本的目的地址和端口号。
static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff, const char **dptr, unsigned int *datalen,
struct nf_conntrack_expect *rtp_exp, struct nf_conntrack_expect *rtcp_exp,
unsigned int mediaoff, unsigned int medialen,
union nf_inet_addr *rtp_addr)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
/* Connection will come from reply */
if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3,
&ct->tuplehash[!dir].tuple.dst.u3))
*rtp_addr = rtp_exp->tuple.dst.u3;
else
*rtp_addr = ct->tuplehash[!dir].tuple.dst.u3;
rtp_exp->saved_addr = rtp_exp->tuple.dst.u3;
rtp_exp->tuple.dst.u3 = *rtp_addr;
rtp_exp->saved_proto.udp.port = rtp_exp->tuple.dst.u.udp.port;
rtp_exp->dir = !dir;
rtp_exp->expectfn = nf_nat_sip_expected;
rtcp_exp->saved_addr = rtcp_exp->tuple.dst.u3;
rtcp_exp->tuple.dst.u3 = *rtp_addr;
rtcp_exp->saved_proto.udp.port = rtcp_exp->tuple.dst.u.udp.port;
rtcp_exp->dir = !dir;
rtcp_exp->expectfn = nf_nat_sip_expected;
以下确定媒体的新端口号,优先使用通告的媒体端口(如以上INVITE请求通告的端口号:21254)。将RTP和RTCP两个expectation链接到系统中,如果存在冲突,更换端口,注意RTP使用偶数端口,RTCP使用紧邻的奇数端口。
/* Try to get same pair of ports: if not, try to change them. */
for (port = ntohs(rtp_exp->tuple.dst.u.udp.port); port != 0; port += 2) {
int ret;
rtp_exp->tuple.dst.u.udp.port = htons(port);
ret = nf_ct_expect_related(rtp_exp, NF_CT_EXP_F_SKIP_MASTER);
if (ret == -EBUSY)
continue;
else if (ret < 0) {
port = 0;
break;
}
rtcp_exp->tuple.dst.u.udp.port = htons(port + 1);
ret = nf_ct_expect_related(rtcp_exp, NF_CT_EXP_F_SKIP_MASTER);
if (ret == 0)
break;
else if (ret == -EBUSY) {
nf_ct_unexpect_related(rtp_exp);
continue;
} else if (ret < 0) {
nf_ct_unexpect_related(rtp_exp);
port = 0;
break;
}
}
if (port == 0) {
nf_ct_helper_log(skb, ct, "all ports in use for SDP media");
goto err1;
}
内核分别为RTP和RTCP创建了如下的两个expectation。注意这里sport等于0,即SIP客户端192.168.1.126可使用任意端口,所有当INVITE响应到达时,不再为其媒体端口(如以上25510)创建expectation。
# conntrack -E expect
[NEW] 180 proto=17 src=192.168.1.109 dst=192.168.1.127 sport=0 dport=21254 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=50.1.1.2 master-dst=192.168.1.109 sport=55719 dport=5060 class=1 helper=sip
[NEW] 180 proto=17 src=192.168.1.109 dst=192.168.1.127 sport=0 dport=21255 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=50.1.1.2 master-dst=192.168.1.109 sport=55719 dport=5060 class=1 helper=sip
使用以上确定的媒体端口号更新报文(m=)字段中的端口信息。
/* Update media port. */
if (rtp_exp->tuple.dst.u.udp.port != rtp_exp->saved_proto.udp.port &&
!nf_nat_sdp_port(skb, protoff, dataoff, dptr, datalen,
mediaoff, medialen, port)) {
nf_ct_helper_log(skb, ct, "cannot mangle SDP message");
goto err2;
}
注册信令处理
函数nf_nat_sip_expect用于处理SIP注册信令相关的expectation创建。首先确定新的目的地址,原理同以上的nf_nat_sdp_media函数。如果连接跟踪本方向的源地址和反方向的目的地址相等,表明NAT没有变换地址,新地址等于等于在Contact中解析到的地址(即保存在exp->tuple.dst.u3中的地址)。
# conntrack -L
udp 17 3582 src=50.1.1.2 dst=192.168.1.109 sport=55719 dport=5060 src=192.168.1.109 dst=192.168.1.127 sport=5060 dport=55719 [ASSURED] mark=0 helper=sip use=1
反之,例如连接跟踪本方向源地址50.1.1.2不等于反方向目的地址192.168.1.127,新目的地址等于连接跟踪反方向的目的地址(执行了NAT转换之后的地址,即192.168.1.127)。
static unsigned int nf_nat_sip_expect(struct sk_buff *skb, unsigned int protoff,
unsigned int dataoff,
const char **dptr, unsigned int *datalen,
struct nf_conntrack_expect *exp,
unsigned int matchoff, unsigned int matchlen)
{
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct);
/* Connection will come from reply */
if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3,
&ct->tuplehash[!dir].tuple.dst.u3))
newaddr = exp->tuple.dst.u3;
else
newaddr = ct->tuplehash[!dir].tuple.dst.u3;
如果expectation中初始的目的端口(即exp->tuple.dst.u.udp.port,由Contact中获得,如55719)等于连接跟踪本方向的源端口(sport=55719),本示例就是这种情况,将新的目的端口设置为连接跟踪反方向的目的端口(NAT之后的端口号,对于本示例也是dport=55719);反之,新的目的端口等于exp的目的端口。
/* If the signalling port matches the connection's source port in the
* original direction, try to use the destination port in the opposite
* direction. */
srcport = ct_sip_info->forced_dport ? : ct->tuplehash[dir].tuple.src.u.udp.port;
if (exp->tuple.dst.u.udp.port == srcport)
port = ntohs(ct->tuplehash[!dir].tuple.dst.u.udp.port);
else
port = ntohs(exp->tuple.dst.u.udp.port);
exp->saved_addr = exp->tuple.dst.u3;
exp->tuple.dst.u3 = newaddr;
exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port;
exp->dir = !dir;
exp->expectfn = nf_nat_sip_expected;
检查端口的可用性。之后将expectation链接到系统中。
for (; port != 0; port++) {
exp->tuple.dst.u.udp.port = htons(port);
ret = nf_ct_expect_related(exp, NF_CT_EXP_F_SKIP_MASTER);
if (ret == 0)
break;
else if (ret != -EBUSY) {
port = 0;
break;
}
}
if (port == 0) {
nf_ct_helper_log(skb, ct, "all ports in use for SIP");
return NF_DROP;
}
修改Contact字段中的地址和端口号,以便服务端的响应报文得到目的地址和目的端口号。
if (!nf_inet_addr_cmp(&exp->tuple.dst.u3, &exp->saved_addr) ||
exp->tuple.dst.u.udp.port != exp->saved_proto.udp.port) {
buflen = sip_sprintf_addr_port(ct, buffer, &newaddr, port);
if (!mangle_packet(skb, protoff, dataoff, dptr, datalen,
matchoff, matchlen, buffer, buflen)) {
nf_ct_helper_log(skb, ct, "cannot mangle packet");
goto err;
}
}
return NF_ACCEPT;
本示例的客户端50.1.1.2的注册信令,源信令和转换之后的信令,如下:
REGISTER sip:192.168.1.109 SIP/2.0
Contact: <sip:1002@50.1.1.2:55719>
To: <sip:1002@192.168.1.109>
Via: SIP/2.0/UDP 50.1.1.2:55719;rport;branch=z9hG4bK47338961
From: <sip:1002@192.168.1.109>;tag=954429717
Call-ID: 460361071@192.168.1.109
CSeq: 9 REGISTER
REGISTER sip:192.168.1.109 SIP/2.0
Contact: <sip:1002@192.168.1.127:55719>
To: <sip:1002@192.168.1.109>
Via: SIP/2.0/UDP 192.168.1.127:55719;rport;branch=z9hG4bK47338961
From: <sip:1002@192.168.1.109>;tag=954429717
Call-ID: 460361071@192.168.1.109
CSeq: 9 REGISTER
内核创建了如下的永久expectation。在注册请求信令时生成永久和INACTIVE属性的expectation,在收到注册响应时,去掉INACTIVE。
# conntrack -E expect
[NEW] 180 proto=17 src=192.168.1.109 dst=192.168.1.127 sport=0 dport=55719 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=50.1.1.2 master-dst=192.168.1.109 sport=55719 dport=5060 PERMANENT,INACTIVE class=0 helper=sip
#
# conntrack -L expect
529 proto=17 src=192.168.1.109 dst=192.168.1.127 sport=0 dport=55719 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=50.1.1.2 master-dst=192.168.1.109 sport=55719 dport=5060 PERMANENT class=0 helper=sip
SEQ序号调整
在sip连接跟踪函数sip_help_tcp的最后,调整序号。
static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
struct nf_conn *ct, enum ip_conntrack_info ctinfo)
{
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;
如下根据报文长度的变化,设置seqadj扩展。
static void nf_nat_sip_seq_adjust(struct sk_buff *skb, unsigned int protoff, s16 off)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
const struct tcphdr *th;
if (nf_ct_protonum(ct) != IPPROTO_TCP || off == 0)
return;
th = (struct tcphdr *)(skb->data + protoff);
nf_ct_seqadj_set(ct, ctinfo, th->seq, off);
内核版本 5.10
最后
以上就是机智大白为你收集整理的NAT SIP helper的全部内容,希望文章能够帮你解决NAT SIP helper所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复