Connection tracking SIP protocol

Posted by blogfisher on Fri, 28 Jan 2022 21:58:35 +0100

ip_conntrack_sip module is used to establish the required connection tracking for SIP protocol. It supports specifying up to 8 listening ports, and multiple port numbers are separated by commas. In addition, the parameters that can be specified when loading the module are:

  • sip_timeout the timeout duration of the main SIP session, which is 3600 seconds by default
  • sip_ direct_ Signaling only accepts calls from the registration server. The default value is 1
  • sip_direct_media only establishes media streams at both ends of interactive signaling. The default value is 1
  • sip_external_media supports the establishment of media streams with non interactive signaling endpoints. The default value is 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;

For each sip port, four helper structures are initialized, which are used for UDP and TCP protocols of IPv4 and IPv6 respectively, but the processing function has only two sip_help_udp and sip_help_tcp, which can handle IPv4 and IPv6 protocols at the same time. If no port number is specified when loading this module, the default SIP port 5060 (SIP_PORT) is used. The registration function links the helper to the global linked list 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);

Test the configuration as follows.

# 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 Protocol SIP signaling

The following processing functions determine the starting position and length of SIP data according to the UDP header length. And the timeout for updating connection tracking. To process_sip_msg processing.

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 Protocol SIP signaling

For TCP protocol, TCP messages in handshake stage are not processed. Then, the starting position and length of SIP data are determined according to the TCP header length. And the timeout for updating connection tracking.

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;

First, search the content length string in the message data, which represents the length of subsequent SDP data, as shown in the following example message:

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

Convert it to the length value clen in hexadecimal. Then, search the SIP protocol end string (\ r\n\r\n), which is the end of the SIP message, followed by the SDP message.

    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;

The end of SIP data plus the length of content data clen is a complete message length. Note that a message may contain multiple SIP messages. Like UDP, it is left to process_sip_msg processes a single message. If the message is modified, the parameter msglen is the new length value.

After that, offset to the position of the next message and continue processing. For the remaining data length datalen, you need to subtract the processed data length msglen and add the length change value 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;
    }

Finally, if nf_nat_sip_hooks has a value, such as NF loaded_ nat_ The SIP module executes its sequence number adjustment function seq_adjust, adjust the serial number value of TCP header, and tdiff is the total length difference.

    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 message processing

The SIP reply message starts with a string ("SIP/2.0") to determine whether it is a request or reply message. After completion, NF_ nat_ The hooks - > MSG function of SIP module is used to process the message body.

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 request message

The kernel processes the following six types of SIP signaling messages.

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),
};

If the source port announced in the Via field is not equal to the source port recorded in the connection tracking record, it indicates that the requesting end receives the response message at different ports, records the port number in the Via, and the reply traffic will be used. This is to deal with some Cisco IP phones. The sending request uses a source port, but is fixed at port 5060 (indicated by the port number in the Via field) for reply.

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;

For SIP requests, the format at the beginning is as follows:

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

The Method field (INVITE) and sip are used here_ Compare the handlers array members to determine what kind of message it is, and call the processing function of the corresponding message for processing. Also, find SIP_HDR_CSEQ field (such as CSeq: 8 INVITE) to obtain the serial number.

    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 reply message

For SIP response message, the format is as follows:

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

First, analyze the response code; Secondly, find the sequence number string to obtain the sequence number value.

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;

For SIP response messages, the message type is determined by the method field after the sequence number. In array SIP_ Find the corresponding processing function in handlers for processing.

    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 registration request message

An example of a registration request is as follows:

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

After registering signaling, a call request (e.g. INVITE) may be received at some time in the future, and the function process_register_request creates a permanent expectation for it in advance. When the registration success response message is received, this expectation will be activated.

First, get the timeout length specified in the Expires field (e.g. 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);

As follows, when the SIP client goes offline, it sends a registration signaling with Expires equal to zero to unbind.

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

Find SIP_HDR_CONTACT field, which resolves the address and port number fields. If the address field is different from the source address of connection tracking, it indicates that the client is registering for a third party, which is not supported. That is, when the registration message uses one source address and the response message is expected to another address.

Then, the string in the Contact field (transport =) is parsed. If there is no such field, the transport protocol of the connection tracking record will be used. Finally, find out whether the Contact contains the (expires =) string and get the timeout value. When the timeout value is zero, the registration server is notified to unbind. There is no need to create an expectation session at this time.

    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;
    }

Create nf_conntrack_expect structure, if SIP is set_ direct_ Signaling (the default is 1). The source address uses the connection to track the source address in the opposite direction. Otherwise, the empty address is used; The source port is empty; The destination address and port number are the address and port number resolved in the above Contact. This expectation is required for the connection initiated by the subsequent server. It helps the server bypass the firewall rules (use related), as follows:

# iptables -I FORWARD 1 -m state --state ESTABLISHED,RELATED -j ACCEPT

This expectation is created for the server response message and has PERMANENT and INACTIVE attributes. After receiving the response message, it is modified to 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;

If NF_ nat_ The IP module is loaded, and the connection needs NAT, which will process the registration signaling message. Here, the source address of expectation has been defined as the address of the server, and the destination address and destination port are determined by the function (nf_nat_sip_expect) pointed to by expect. After that, it will create a related relationship.

Otherwise, the above does not hold and is determined by the function nf_ct_expect_related creates a related relationship and connects expectations to the global linked list (nf_ct_expect_hash) and the expectations linked list tracked by the current connection. Finally, the serial number of the registration message is saved for matching judgment when receiving the response.

    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 registration response message

In the registration response message, if the response code is 1XX, it indicates that it is a temporary message and will not be processed; For non 2XX response codes, if an error occurs, clear the expectation. The response code 2XX is the success type message code.

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

After that, get the timeout value (SIP_HDR_EXPIRES). The above registration response example does not have a separate Expires field, which is located in the Contact field.

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);

Resolve the address and port information in the Contact. There may be multiple address port pairs, as follows:

Contact: <sip:1002@192.168.1.135:53663>;expires=491, <sip:1002@192.168.1.135:36252>;expires=600

If the resolved address is different from the destination address in the same direction of connection tracking, it will not be processed, which is equivalent to that the current client is registering for other third-party devices (this situation is also detected in the registration request message, and direct release will not be processed).

Find the next address field in the Contact (the first in_contact is zero, and then 1 to distinguish), resolve to the end of the Contact, and jump out of the loop. Next, try to find the (expires =) string in the Contact and get the timeout value. Update the expectation of signaling when it is not zero.

    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;

For the unbound registration signaling, there is no Expires field in the response message. The default is C_ When Expires is zero, execute flush_expectations clears the expectation s of previously created signaling types.

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

Traverse the expectation linked list of connection tracking, find the matching expectation according to the address, port number and protocol, and delete it_ CT_ EXPECT_ The inactive attribute is removed.

        if (refresh_signalling_expectation(ct, &addr, proto, port,
                           c_expires))
            return NF_ACCEPT;
    }

flush:
    flush_expectations(ct, false);
    return NF_ACCEPT;

SIP invitation message

Receive Invite request signaling, as shown in the following example:

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

First clear the expectation of the previous media type, and then process_sdp function processing.

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;

For Invite response signaling, if the response code is 1XX or 2XX, the processing is also mainly by process_sdp completion; Otherwise, for the response code of the wrong type, clear the expectation of the media type.

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 response message

The Update request message is sent by the function process_sdp is used for processing, and the Update reply message is sent by process_update_response processing, and finally, process is called_ SDP for processing.

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 response message

The temporary ACK (proactive acknowledgement) request message is sent by the function process_sdp, and the PRACK reply message is processed by process_prack_response processing, and finally, process is called_ SDP for processing.

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 end request message

Receive Bye signaling as follows:

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

Clear the expectation of the media type.

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;

Session representation protocol SDP

Three types of media streams are supported: audio, video and 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;

Connection tracing resolves the following four SDP header types.

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),
};

The SDP data in the input request signaling is as follows, and some fields are omitted at the end:

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

First, search SDP_HDR_VERSION, i.e. string (v =), find the starting position of SDP message.

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;

The following is part of the data of SDP. Next, analyze the Connection information.

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

After finding the (c =) string, continue to search the subsequent ("IN IP4") string (with spaces at the end) and locate the address string. If the (c =) string is not found, SDP is found_ HDR_ Media, that is, the string (m =), also ends the search.

    /* 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;

Next, loop through the three media types listed above (there is only one audio type in the example message). First, find the string (m =), and convert the subsequent type string into a digital representation, such as audio corresponding to SIP_EXPECT_AUDIO category.

    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;

Next, analyze the port number used by this media. The legal port number is in the interval [102465535]. Find out whether this media stream has specified address information. First use the address here, and then use the address resolved in 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;
        }

Create the expectation of RTP and RTCP protocol traffic according to the above resolved address and port.

        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;
        }

If NF_ nat_ The SIP module is loaded, and the connection tracking needs to execute NAT, which is SDP_ The addr function handles the NAT replacement of RTP address in the (m =) field, and the parameter rtp_addr is the new converted address, which is set in the above function_ expected_ rtp_ Updated in 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++;
    }

Finally, the function updates the session information and replaces the address information in the two fields (o =) and (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;

Media type expected connection

The following establishes the desired connection of the media type. This connection is initiated by the endpoint in the opposite direction at some time in the future. Therefore, its source and destination address / port numbers are opposite to the source and destination address / port numbers of the current connection (it may also be different in NAT).

If the advertised RTP media address (daddr) is different from the sending source address of the message, it is in SIP_ direct_ If media is true (the default), it will not be processed. Otherwise, the source address of expectation is set to the source address in the opposite direction of connection tracking. The source address device will initiate RTP to connect to the address and port of this advertisement and establish expectation in advance.

Note that only in SIP_ direct_ When media is true and the RTP address of the current signaling notification is equal to the connection tracking source address, the source address of expectation will be assigned. In other cases, the source address is empty (arbitrary address), considering that in other cases, the source address of RTP may not be the source address in the opposite direction in connection tracking, but an external address.

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;

If SIP_ direct_ Media is zero and SIP_ external_ If media is true, check the route accessibility of the external RTP address (if sip_direct_media is true, it does not need to be checked because it is the same as the signaling address). If the route exists and the exit device is the same as the signaling exit device, no processing will be performed.

    } 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;
        }
    }

According to the five tuple tuple, check whether this expectation already exists in the namespace. If one of the following conditions is met, the loop will jump out:

  • expectation cannot be found according to tuple;
  • The primary connection trace of the found expectation is equal to the current connection trace;
  • The two connection traces do not use the same helper;
  • The two connection traces use different class es.

The following part is used to judge whether two SIP clients can establish direct RTP traffic and whether the expectation already exists to avoid repeated creation.

    /* 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;

The condition for running here is that the expectation exists, the helper used by its primary connection tracking is the same as the current connection, and the class is also equal (it is determined that it is created by hooks - > sdp_media). For example, helpers use SIP and classes use SIP_EXPECT_AUDIO, the only difference is that the primary connection tracking of expectation is not the same as the current connection tracking. In this case, multiple endpoints (connections) receive the same call, and the expectation has been created at the time of the first signaling message.

At this time, if the found expectation executes NAT, that is, the destination address / port number has been converted (saved_addr/saved_proto saves the original destination address / port number before NAT, see hooks - > sdp_media), it indicates that our original tuple (not NAT) is the same as the expectation of a nat. If our current connection also needs nat (IPS_NAT_MASK), give tuple the original destination address / port of the found expectation and continue to search. If a matching expectation is found again, set skip_expect flag, exit the search.

#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 uses an even port number, and RTCP uses the next odd port number. If the above will be direct_ If RTP is set to true, call NF_ nat_ SDP of SIP module_ Port to replace the media port number in the message.

    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;

Create new expectation structures for RTP and RTCP respectively. The source address uses the address or empty of the opposite end, the destination address / port uses the address / port in the message (c = or m =), or when the same expectation is found, the destination address / port uses the address / port saved before its NAT change.

If NAT is enabled for current connection tracking and direct_rtp is zero and is determined by the function sdp_media modifies the destination address and port number in the expectation of RTP and RTCP protocols. When NAT is enabled, the destination address and port seen by the opposite end are changed. Therefore, in order to pass the RTP/RTCP traffic of the opposite end, the destination address and port number of expectation need to be adjusted.

Otherwise, NAT or direct is not enabled_ When RTP is true, the two expectation s are directly linked to the global linked list and connection tracking.

    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);
        }
    }

In the following topology, there are two clients 50.1.1.2 and 60.1.1.2. The SIP server is 192.168.1.109. Set sip_direct_media is zero:

                |--------------------------|
                |                          |
 60.1.1.2 ----- | 60.1.1.1                 |
                |            192.168.1.135 |-------- 192.168.1.109
 50.1.1.2 ----- | 50.1.1.1                 |
                |                          |
                |--------------------------|

For example, when the client 60.1.1.2 sends an INVITE call 60.1.1.2, the RTP address advertised in the SDP is 60.1.1.2:31600. The kernel establishes the following expectation (address 0.0.0.0 matches all). If NAT is enabled, EXP1 is created. After NAT conversion, the new RTP address is 192.168.1.135:32778; To turn off NAT, create 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
 perhaps
EXP2:  UDP    0.0.0.0  00000     -  60.1.1.2      31600

After receiving the INVITE signaling, the SIP server 192.168.1.109 sends the INVITE to the client 50.1.1.2. The RTP address carried in the SDP is 192.168.1.135:32778 (NAT), or 60.1.1.2:31600 (non NAT). After the kernel receives this INVITE signaling, it will be processed in the following situations.

Scenario 1 - direct_rtp=0,skip_expect=0

Search the existing expectation linked list and find no expectation that meets the requirements. The flag bit is direct_rtp and skip_expect is zero, which is determined by hooks - > SDP_ Media creates an expectation. For example, the expectation created by the INVITE sent by the client in 60.1.1.2 above.

Scenario 2 - direct_rtp=0,skip_expect=1

After receiving the INVITE signaling sent by the SIP server to the client 50.1.1.2, find the expectation according to the following tuple, and find the expectation that meets the requirements, but the expectation does not turn on NAT, or the current connection does not turn on NAT. At this time, when matching to EXP2, there is no need to create an expectation or replace the media address and port in the message.

Tuple1:     UDP 0.0.0.0  00000 - 192.168.1.135  32778    /* NAT */
perhaps
Tuple2:     UDP 0.0.0.0  00000 - 60.1.1.2       31600    /* Non NAT */

Scenario 3 - direct_rtp=1,skip_expect=0

Matched to EXP1, this expectation has undergone NAT conversion, and NAT is also enabled for the current connection. The original destination address and port number of expectation are saved in saved_addr and saved_proto. The primary connection of this expectation is 60.1.1.2. SDP is sent to announce its RTP address 60.1.1.2:31600. NAT converts 60.1.1.2:31600 to 192.168.1.135:32778.

Set direct_rtp is equal to 1. Since the final destination address of 192.168.1.135:32778 is 60.1.1.2:31660, replace the destination address and port in tuple with the original address and port of EXP1. Continue to search as follows.

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

No expectation and skip meeting the requirements of the new Tuple3 were found_ Expect is zero. Use the destination address and port of the new Tuple3 to replace the media port in the message (the media address is replaced in the subsequent hooks - > sdp_addr), that is, announce the final address and port to the client 50.1.1.2. Also, create the following expectation based on the new tuple. In this way, the RTP stream of client 50.1.1.2 can be sent directly to 60.1.1.2.

EXP3:  UDP 0.0.0.0  00000 - 60.1.1.2  31600

Scenario 4 - direct_rtp=1,skip_expect=1

The expectation that meets the requirements is found again using the new Tuple3. Indicates that the required expectations have been created (EXP1 and EXP3), and there is no need to create new expectations. Replace the media port in the message with 60.1.1.2:31600 (the media address is replaced in subsequent hooks - > sdp_addr). The RTP address 60.1.1.2:31600 is directly notified to the client 50.1.1.2, and EXP3 is established. In this way, the RTP stream of the client 50.1.1.2 can be directly sent to 60.1.1.2.

On the contrary, after the INVITE reply message passes through the NAT device twice, the same expectation will be established so that the RTP stream of 60.1.1.2 can be directly sent to 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

Kernel version 5.10

Topics: sip