当前位置: 首页 > 工具软件 > SIP Router > 使用案例 >

连接跟踪SIP协议

蒯安平
2023-12-01

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

 类似资料: