Chinaunix首页 | 论坛 | 博客
  • 博客访问: 159116
  • 博文数量: 24
  • 博客积分: 2019
  • 博客等级: 大尉
  • 技术积分: 352
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-22 17:36
文章分类
文章存档

2011年(2)

2010年(22)

我的朋友

分类: 网络与安全

2010-04-28 15:20:03

SIP ALG in Netfilter

  本博客Netfilter/IPtables系列文章均基于Linux2.6.30内核。
  本文档版权归hereitis所有,可以自由拷贝/转载,转载时请保持文档的完整性并且注明来源,禁止用于任何商业用途。
  hereitis.cu@gmail.com


  1. nf_conntrack_sip_init(net/netfilter/nf_conntrack_sip.c)
static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1] = {
    [SIP_EXPECT_SIGNALLING] = {
        .max_expected    = 1,
        .timeout    = 3 * 60,
    },
    [SIP_EXPECT_AUDIO] = {
        .max_expected    = 2 * IP_CT_DIR_MAX,
        .timeout    = 3 * 60,
    },
    [SIP_EXPECT_VIDEO] = {
        .max_expected    = 2 * IP_CT_DIR_MAX,
        .timeout    = 3 * 60,
    },
};

static int __init nf_conntrack_sip_init(void)
{
    int i, j, ret;
    char *tmpname;

    if (ports_c == 0)
        ports[ports_c++] = SIP_PORT; //UDP: 5060

    for (i = 0; i < ports_c; i++) {
        memset(&sip[i], 0, sizeof(sip[i]));

        sip[i][0].tuple.src.l3num = AF_INET;
        sip[i][1].tuple.src.l3num = AF_INET6;
        for (j = 0; j < 2; j++) {
            sip[i][j].tuple.dst.protonum = IPPROTO_UDP;
            sip[i][j].tuple.src.u.udp.port = htons(ports[i]);
            sip[i][j].expect_policy = sip_exp_policy;
            sip[i][j].expect_class_max = SIP_EXPECT_MAX;
            sip[i][j].me = THIS_MODULE;
            sip[i][j].help = sip_help;

            tmpname = &sip_names[i][j][0];
            if (ports[i] == SIP_PORT)
                sprintf(tmpname, "sip");
            else
                sprintf(tmpname, "sip-%u", i);
            sip[i][j].name = tmpname;

            pr_debug("port #%u: %u\n", i, ports[i]);

            ret = nf_conntrack_helper_register(&sip[i][j]);
            if (ret) {
                printk("nf_ct_sip: failed to register helper "
                       "for pf: %u port: %u\n",
                       sip[i][j].tuple.src.l3num, ports[i]);
                nf_conntrack_sip_fini();
                return ret;
            }
        }
    }
    return 0;
}
  1. sip_help
static int sip_help(struct sk_buff *skb,
            unsigned int protoff,
            struct nf_conn *ct,
            enum ip_conntrack_info ctinfo)
{
    unsigned int dataoff, datalen;
    const char *dptr;
    int ret;
    typeof(nf_nat_sip_hook) nf_nat_sip;

    /* No Data ? */
    dataoff = protoff + sizeof(struct udphdr);
    if (dataoff >= skb->len)
        return NF_ACCEPT;

    nf_ct_refresh(ct, skb, sip_timeout * HZ); //Refresh connection timeout

    if (!skb_is_nonlinear(skb))
        dptr = skb->data + dataoff;
    else {
        pr_debug("Copy of skbuff not supported yet.\n");
        return NF_ACCEPT;
    }

    datalen = skb->len - dataoff;
    if (datalen < strlen("SIP/2.0 200"))
        return NF_ACCEPT;

    if (strnicmp(dptr, "SIP/2.0 ", strlen("SIP/2.0 ")) != 0) //Status-Line = SIP-Version SP Status-Code SP Reason-Phrase CRLF
        ret = process_sip_request(skb, &dptr, &datalen);
    else
        ret = process_sip_response(skb, &dptr, &datalen);

    if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
        nf_nat_sip = rcu_dereference(nf_nat_sip_hook);
        if (nf_nat_sip && !nf_nat_sip(skb, &dptr, &datalen))
            ret = NF_DROP;
    }

    return ret;
}
  1. Utility function
    1. ct_sip_get_header
      1. enum sip_header_types {
    SIP_HDR_CSEQ,
    SIP_HDR_FROM,
    SIP_HDR_TO,
    SIP_HDR_CONTACT,
    SIP_HDR_VIA,
    SIP_HDR_EXPIRES,
    SIP_HDR_CONTENT_LENGTH,
};
  1. sip_header
    1. /* SIP header parsing: SIP headers are located at the beginning of a line, but
       * may span several lines, in which case the continuation lines begin with a
       * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or
       * CRLF, RFC 3261 allows only CRLF, we support both.
       *
       * Headers are followed by (optionally) whitespace, a colon, again (optionally)
       * whitespace and the values. Whitespace in this context means any amount of
       * tabs, spaces and continuation lines, which are treated as a single whitespace
       * character.
       *
       * Some headers may appear multiple times. A comma seperated list of values is
       * equivalent to multiple headers.
       */
      static const struct sip_header ct_sip_hdrs[] = { //RFC3261: header = "header-name" HCOLON header-value *(COMMA header-value)
          [SIP_HDR_CSEQ]            = SIP_HDR("CSeq", NULL, NULL, digits_len),
          [SIP_HDR_FROM]            = SIP_HDR("From", "f", "sip:", skp_epaddr_len), //<sip:200.57.7.195:55061;user=phone>;tag=GR52RWG346-34
          [SIP_HDR_TO]            = SIP_HDR("To", "t", "sip:", skp_epaddr_len), //\"francisco@bestel.com\" <sip:francisco@bestel.com:55060>;tag=298852044
          [SIP_HDR_CONTACT]        = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len), //<sip:francisco@200.57.7.204:5061>
          [SIP_HDR_VIA]            = SIP_HDR("Via", "v", "UDP ", epaddr_len),                                                                             //SIP/2.0/UDP 200.57.7.195;branch=z9hG4bKff9b46fb055c0521cc24024da96cd290
          [SIP_HDR_EXPIRES]        = SIP_HDR("Expires", NULL, NULL, digits_len),
          [SIP_HDR_CONTENT_LENGTH]    = SIP_HDR("Content-Length", "l", NULL, digits_len),
      };
       
    1. struct sip_header {
    const char    *name;
    const char    *cname;
    const char    *search;
    unsigned int    len;
    unsigned int    clen;
    unsigned int    slen;
    int        (*match_len)(const struct nf_conn *ct,
                     const char *dptr, const char *limit,
                     int *shift);
};

#define __SIP_HDR(__name, __cname, __search, __match)            \
{                                    \
    .name        = (__name),                    \
    .len        = sizeof(__name) - 1,                \
    .cname        = (__cname),                    \
    .clen        = (__cname) ? sizeof(__cname) - 1 : 0,        \
    .search        = (__search),                    \
    .slen        = (__search) ? sizeof(__search) - 1 : 0,    \
    .match_len    = (__match),                    \
}

#define SIP_HDR(__name, __cname, __search, __match) \
    __SIP_HDR(__name, __cname, __search, __match)

#define SDP_HDR(__name, __search, __match) \
    __SIP_HDR(__name, NULL, __search, __match)
    1. digits_len
        static int digits_len(const struct nf_conn *ct, const char *dptr,
              const char *limit, int *shift)
{
    int len = 0;
    while (dptr < limit && isdigit(*dptr)) {
        dptr++;
        len++;
    }
    return len;
}
    1. skp_epaddr_len
/* get address length, skiping user info. */
//<sip:200.57.7.195:55061;user=phone>;tag=GR52RWG346-34
//<sip:francisco@200.57.7.204:5061>
static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr,
              const char *limit, int *shift)
{
    const char *start = dptr;
    int s = *shift;

    /* Search for @, but stop at the end of the line.
     * We are inside a sip: URI, so we don't need to worry about
     * continuation lines. */
    while (dptr < limit &&
           *dptr != '@' && *dptr != '\r' && *dptr != '\n') {
        (*shift)++;
        dptr++;
    }

    if (dptr < limit && *dptr == '@') { //has @
        dptr++;
        (*shift)++;
    } else { //No @ and restore the pointer to original one
        dptr = start;
        *shift = s;
    }

    return epaddr_len(ct, dptr, limit, shift);
}
    1. epaddr_len
/* skip ip address. returns its length. */
static int epaddr_len(const struct nf_conn *ct, const char *dptr,
              const char *limit, int *shift)
{
    union nf_inet_addr addr;
    const char *aux = dptr;

    if (!parse_addr(ct, dptr, &dptr, &addr, limit)) {
        pr_debug("ip: %s parse failed.!\n", dptr);
        return 0;
    }

    /* Port number */
    if (*dptr == ':') {
        dptr++;
        dptr += digits_len(ct, dptr, limit, shift);
    }
    return dptr - aux;
}
  1. parse_addr
static int parse_addr(const struct nf_conn *ct, const char *cp,
                      const char **endp, union nf_inet_addr *addr,
                      const char *limit)
{
    const char *end;
    int ret = 0;

    memset(addr, 0, sizeof(*addr));
    switch (nf_ct_l3num(ct)) {
    case AF_INET:
        ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end);
        break;
    case AF_INET6:
        ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end);
        break;
    default:
        BUG();
    }

    if (ret == 0 || end == cp)
        return 0;
    if (endp)
        *endp = end;
    return 1;
}
    1. ct_sip_get_header
            int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
              unsigned int dataoff, unsigned int datalen,
              enum sip_header_types type,
              unsigned int *matchoff, unsigned int *matchlen)
{
    const struct sip_header *hdr = &ct_sip_hdrs[type];
    const char *start = dptr, *limit = dptr + datalen;
    int shift = 0;

    for (dptr += dataoff; dptr < limit; dptr++) {
        /* Find beginning of line */
        if (*dptr != '\r' && *dptr != '\n')
            continue;
        if (++dptr >= limit)
            break;
        if (*(dptr - 1) == '\r' && *dptr == '\n') {
            if (++dptr >= limit)
                break;
        }

        /* Skip continuation lines */
        if (*dptr == ' ' || *dptr == '\t') // SIP header may span serveral lines, in which case continuation lines begin with a whiltespace character
            continue;

        /* Find header. Compact headers must be followed by a
         * non-alphabetic character to avoid mismatches. */
        if (limit - dptr >= hdr->len &&
            strnicmp(dptr, hdr->name, hdr->len) == 0)  // From: ...
            dptr += hdr->len;
        else if (hdr->cname && limit - dptr >= hdr->clen + 1 &&
             strnicmp(dptr, hdr->cname, hdr->clen) == 0 &&
             !isalpha(*(dptr + hdr->clen + 1))) //f:
            dptr += hdr->clen;
        else
            continue;

        /* Find and skip colon */
        dptr = sip_skip_whitespace(dptr, limit);
        if (dptr == NULL)
            break;
        if (*dptr != ':' || ++dptr >= limit)
            break;

        /* Skip whitespace after colon */
        dptr = sip_skip_whitespace(dptr, limit);
        if (dptr == NULL)
            break;

        *matchoff = dptr - start;
        if (hdr->search) { //Find prefix if present, such as sip:
            dptr = ct_sip_header_search(dptr, limit, hdr->search,
                            hdr->slen);
            if (!dptr)
                return -1;
            dptr += hdr->slen;
        }

        *matchlen = hdr->match_len(ct, dptr, limit, &shift); //Get the length of the value of the header
        if (!*matchlen)
            return -1;
        *matchoff = dptr - start + shift;
        return 1;
    }
    return 0;
}
  1. ct_sdp_header_search
    1. /* SDP header parsing: a SDP session description contains an ordered set of
       * headers, starting with a section containing general session parameters,
       * optionally followed by multiple media descriptions.
       *
       * SDP headers always start at the beginning of a line. According to RFC 2327:
       * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
       * be tolerant and also accept records terminated with a single newline
       * character". We handle both cases.
       */
      static const struct sip_header ct_sdp_hdrs[] = {
          [SDP_HDR_VERSION]        = SDP_HDR("v=", NULL, digits_len),
          [SDP_HDR_OWNER_IP4]        = SDP_HDR("o=", "IN IP4 ", epaddr_len),
          [SDP_HDR_CONNECTION_IP4]    = SDP_HDR("c=", "IN IP4 ", epaddr_len),
          [SDP_HDR_OWNER_IP6]        = SDP_HDR("o=", "IN IP6 ", epaddr_len),
          [SDP_HDR_CONNECTION_IP6]    = SDP_HDR("c=", "IN IP6 ", epaddr_len),
          [SDP_HDR_MEDIA]            = SDP_HDR("m=", NULL, media_len),
      };
    2. enum sdp_header_types {
          SDP_HDR_UNSPEC,
          SDP_HDR_VERSION,
          SDP_HDR_OWNER_IP4,
          SDP_HDR_CONNECTION_IP4,
          SDP_HDR_OWNER_IP6,
          SDP_HDR_CONNECTION_IP6,
          SDP_HDR_MEDIA,
      };
    3. media_len
  1. /* get media type + port length */
        //m=audio 40376 RTP/AVP 8 18 4 0
static int media_len(const struct nf_conn *ct, const char *dptr,
             const char *limit, int *shift)
{
    int len = string_len(ct, dptr, limit, shift);

    dptr += len;
    if (dptr >= limit || *dptr != ' ')
        return 0;
    len++;
    dptr++;

    return len + digits_len(ct, dptr, limit, shift);
}
  1. string_len
static int string_len(const struct nf_conn *ct, const char *dptr,
              const char *limit, int *shift)
{
    int len = 0;

    while (dptr < limit && isalpha(*dptr)) {
        dptr++;
        len++;
    }
    return len;
}

  1. ct_sip_get_sdp_header
    /* Locate a SDP header (optionally a substring within the header value),
     * optionally stopping at the first occurence of the term header, parse
     * it and return the offset and length of the data we're interested in.
     */
    int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
                  unsigned int dataoff, unsigned int datalen,
                  enum sdp_header_types type,
                  enum sdp_header_types term,
                  unsigned int *matchoff, unsigned int *matchlen)
    {
        const struct sip_header *hdr = &ct_sdp_hdrs[type];
        const struct sip_header *thdr = &ct_sdp_hdrs[term];
        const char *start = dptr, *limit = dptr + datalen;
        int shift = 0;

        for (dptr += dataoff; dptr < limit; dptr++) {
            /* Find beginning of line */
            if (*dptr != '\r' && *dptr != '\n')
                continue;
            if (++dptr >= limit)
                break;
            if (*(dptr - 1) == '\r' && *dptr == '\n') {
                if (++dptr >= limit)
                    break;
            }

            if (term != SDP_HDR_UNSPEC &&
                limit - dptr >= thdr->len &&
                strnicmp(dptr, thdr->name, thdr->len) == 0)
                break;
            else if (limit - dptr >= hdr->len &&
                 strnicmp(dptr, hdr->name, hdr->len) == 0)
                dptr += hdr->len;
            else
                continue;

            *matchoff = dptr - start;
            if (hdr->search) {
                dptr = ct_sdp_header_search(dptr, limit, hdr->search,
                                hdr->slen);
                if (!dptr)
                    return -1;
                dptr += hdr->slen;
            }

            *matchlen = hdr->match_len(ct, dptr, limit, &shift);
            if (!*matchlen)
                return -1;
            *matchoff = dptr - start + shift;
            return 1;
        }
        return 0;
    }
  2. ct_sip_parse_sdp_addr
static int ct_sip_parse_sdp_addr(const struct nf_conn *ct, const char *dptr,
                 unsigned int dataoff, unsigned int datalen,
                 enum sdp_header_types type,
                 enum sdp_header_types term,
                 unsigned int *matchoff, unsigned int *matchlen,
                 union nf_inet_addr *addr)
{
    int ret;

    ret = ct_sip_get_sdp_header(ct, dptr, dataoff, datalen, type, term,
                    matchoff, matchlen);
    if (ret <= 0)
        return ret;

    if (!parse_addr(ct, dptr + *matchoff, NULL, addr,
            dptr + *matchoff + *matchlen))
        return -1;
    return 1;
}
  1. process_sip_resuqest
  1. static int process_sip_request(struct sk_buff *skb,
                   const char **dptr, unsigned int *datalen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    unsigned int matchoff, matchlen;
    unsigned int cseq, i;

    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 ||
            strnicmp(*dptr, handler->method, handler->len))
            continue;

        if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
                      &matchoff, &matchlen) <= 0)
            return NF_DROP;
        cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
        if (!cseq)
            return NF_DROP;

        return handler->request(skb, dptr, datalen, cseq);
    }
    return NF_ACCEPT;
}
  1. sip_handlers
      1. static const struct sip_handler sip_handlers[] = {
    SIP_HANDLER("INVITE", process_sdp, 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),
};
  1. process_sdp
static int process_sdp(struct sk_buff *skb,
               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_conn_help *help = nfct_help(ct);
    unsigned int matchoff, matchlen;
    unsigned int mediaoff, medialen;
    unsigned int sdpoff;
    unsigned int caddr_len, maddr_len;
    unsigned int i;
    union nf_inet_addr caddr, maddr, rtp_addr;
    unsigned int port;
    enum sdp_header_types c_hdr;
    const struct sdp_media_type *t;
    int ret = NF_ACCEPT;
    typeof(nf_nat_sdp_addr_hook) nf_nat_sdp_addr;
    typeof(nf_nat_sdp_session_hook) nf_nat_sdp_session;

    nf_nat_sdp_addr = rcu_dereference(nf_nat_sdp_addr_hook);
    c_hdr = nf_ct_l3num(ct) == AF_INET ? SDP_HDR_CONNECTION_IP4 :
                         SDP_HDR_CONNECTION_IP6;

    /* 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 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,
                  c_hdr, SDP_HDR_MEDIA,
                  &matchoff, &matchlen, &caddr) > 0) // Get connection IP in seesion level
        caddr_len = matchlen;

    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. */  ==On hold call?
        t = sdp_media_type(*dptr, mediaoff, medialen);
        if (!t) {
            mediaoff += medialen;
            continue;
        }
        mediaoff += t->len;
        medialen -= t->len;

        port = simple_strtoul(*dptr + mediaoff, NULL, 10);
        if (port == 0)
            continue;
        if (port < 1024 || port > 65535)
            return NF_DROP;

        /* The media description overrides the session description. */
        maddr_len = 0;
        if (ct_sip_parse_sdp_addr(ct, *dptr, mediaoff, *datalen,
                      c_hdr, 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
            return NF_DROP;

        ret = set_expected_rtp_rtcp(skb, dptr, datalen,
                        &rtp_addr, htons(port), t->class,
                        mediaoff, medialen);
        if (ret != NF_ACCEPT)
            return ret;

        /* Update media connection address if present */
        if (maddr_len && nf_nat_sdp_addr && ct->status & IPS_NAT_MASK) {
            ret = nf_nat_sdp_addr(skb, dptr, mediaoff, datalen,
                          c_hdr, SDP_HDR_MEDIA, &rtp_addr);
            if (ret != NF_ACCEPT)
                return ret;
        }
        i++;
    }

    /* Update session connection and owner addresses */
    nf_nat_sdp_session = rcu_dereference(nf_nat_sdp_session_hook);
    if (nf_nat_sdp_session && ct->status & IPS_NAT_MASK)
        ret = nf_nat_sdp_session(skb, dptr, sdpoff, datalen, &rtp_addr);

    if (ret == NF_ACCEPT && i > 0)
        help->help.ct_sip_info.invite_cseq = cseq;

    return ret;
}
  1. set_expected_rtp_rtcp
static int set_expected_rtp_rtcp(struct sk_buff *skb,
                 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);
    struct net *net = nf_ct_net(ct);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    union nf_inet_addr *saddr;
    struct nf_conntrack_tuple tuple;
    int direct_rtp = 0, skip_expect = 0, ret = NF_DROP;
    u_int16_t base_port;
    __be16 rtp_port, rtcp_port;
    typeof(nf_nat_sdp_port_hook) nf_nat_sdp_port;
    typeof(nf_nat_sdp_media_hook) nf_nat_sdp_media;

    /*
       In case someone wants to do some testing, the conntrack module has two
new parameters controlling whether expectations for signalling and
RTP connections are set up with wildcards or only between the two
sides of the connection:

- sip_direct_signalling (default 1): expect signalling connections only
from registrar

- sip_direct_media (default 1): expect media streams only from remote
side of the connection

Depending on the setup, the defaults might be too strict and need to
be changed to zero.
*/
    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;
    }

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

    rcu_read_lock();
    do {
        exp = __nf_ct_expect_find(net, &tuple);

        if (!exp || exp->master == ct ||
            nfct_help(exp->master)->helper != nfct_help(ct)->helper ||
            exp->class != class)
            break;
#ifdef CONFIG_NF_NAT_NEEDED
        if (exp->tuple.src.l3num == AF_INET && !direct_rtp &&
            (exp->saved_ip != exp->tuple.dst.u3.ip ||
             exp->saved_proto.udp.port != exp->tuple.dst.u.udp.port) &&     ==>This means already NATed
            ct->status & IPS_NAT_MASK) {
            daddr->ip        = exp->saved_ip; ==>Original IP before NAT
            tuple.dst.u3.ip        = exp->saved_ip;
            tuple.dst.u.udp.port    = exp->saved_proto.udp.port;
            direct_rtp = 1;
        } else
#endif
            skip_expect = 1;
    } while (!skip_expect);
    rcu_read_unlock();

    base_port = ntohs(tuple.dst.u.udp.port) & ~1;
    rtp_port = htons(base_port);
    rtcp_port = htons(base_port + 1);

    if (direct_rtp) {
        nf_nat_sdp_port = rcu_dereference(nf_nat_sdp_port_hook);
        if (nf_nat_sdp_port &&
            !nf_nat_sdp_port(skb, dptr, datalen,
                     mediaoff, medialen, ntohs(rtp_port)))
            goto err1;
    }

    if (skip_expect)
        return NF_ACCEPT;

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

    nf_nat_sdp_media = rcu_dereference(nf_nat_sdp_media_hook);
    if (nf_nat_sdp_media && ct->status & IPS_NAT_MASK && !direct_rtp) //Aready NATed
        ret = nf_nat_sdp_media(skb, dptr, datalen, rtp_exp, rtcp_exp,
                       mediaoff, medialen, daddr);
    else {
        if (nf_ct_expect_related(rtp_exp) == 0) { //Insert new expectation to hash list
            if (nf_ct_expect_related(rtcp_exp) != 0)
                nf_ct_unexpect_related(rtp_exp);
            else
                ret = NF_ACCEPT;
        }
    }
    nf_ct_expect_put(rtcp_exp);
err2:
    nf_ct_expect_put(rtp_exp);
err1:
    return ret;
}
  1. ip_nat_sdp_port
static unsigned int ip_nat_sdp_port(struct sk_buff *skb,
                    const char **dptr,
                    unsigned int *datalen,
                    unsigned int matchoff,
                    unsigned int matchlen,
                    u_int16_t port)
{
    char buffer[sizeof("nnnnn")];
    unsigned int buflen;

    buflen = sprintf(buffer, "%u", port);
    if (!mangle_packet(skb, dptr, datalen, matchoff, matchlen,
               buffer, buflen))
        return 0;

    return mangle_content_len(skb, dptr, datalen);
}
  1. mangle_packet
static unsigned int mangle_packet(struct sk_buff *skb,
                  const char **dptr, unsigned int *datalen,
                  unsigned int matchoff, unsigned int matchlen,
                  const char *buffer, unsigned int buflen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);

    if (!nf_nat_mangle_udp_packet(skb, ct, ctinfo, matchoff, matchlen,
                      buffer, buflen))
        return 0;

    /* Reload data pointer and adjust datalen value */
    *dptr = skb->data + ip_hdrlen(skb) + sizeof(struct udphdr);
    *datalen += buflen - matchlen;
    return 1;
}
  1. mangle_content_len
static int mangle_content_len(struct sk_buff *skb,
                  const char **dptr, unsigned int *datalen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    unsigned int matchoff, matchlen;
    char buffer[sizeof("65536")];
    int buflen, c_len;

    /* Get actual SDP length */
    if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
                  SDP_HDR_VERSION, SDP_HDR_UNSPEC,
                  &matchoff, &matchlen) <= 0)
        return 0;
    c_len = *datalen - matchoff + strlen("v="); //matchoff is computed after "v="

    /* Now, update SDP length */
    if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CONTENT_LENGTH,
                  &matchoff, &matchlen) <= 0)
        return 0;

    buflen = sprintf(buffer, "%u", c_len);
    return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
                 buffer, buflen);
}
  1. ip_nat_sdp_media
/* So, this packet has hit the connection tracking matching code.
   Mangle it, and change the expectation to match the new version. */
static unsigned int ip_nat_sdp_media(struct sk_buff *skb,
                     const char **dptr,
                     unsigned int *datalen,
                     struct nf_conntrack_expect *rtp_exp,
                     struct nf_conntrack_expect *rtcp_exp,
                     unsigned int mediaoff,
                     unsigned int medialen,
                     union nf_inet_addr *rtp_addr)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    u_int16_t port;

    /* Connection will come from reply */
    if (ct->tuplehash[dir].tuple.src.u3.ip ==
        ct->tuplehash[!dir].tuple.dst.u3.ip)  //Not NATed
        rtp_addr->ip = rtp_exp->tuple.dst.u3.ip;
    else
        rtp_addr->ip = ct->tuplehash[!dir].tuple.dst.u3.ip;  //Aready NATed, used NATed IP info

    rtp_exp->saved_ip = rtp_exp->tuple.dst.u3.ip; //Save original IP
    rtp_exp->tuple.dst.u3.ip = rtp_addr->ip; //New destination IP
    rtp_exp->saved_proto.udp.port = rtp_exp->tuple.dst.u.udp.port;
    rtp_exp->dir = !dir;
    rtp_exp->expectfn = ip_nat_sip_expected;

    rtcp_exp->saved_ip = rtcp_exp->tuple.dst.u3.ip;
    rtcp_exp->tuple.dst.u3.ip = rtp_addr->ip;
    rtcp_exp->saved_proto.udp.port = rtcp_exp->tuple.dst.u.udp.port;
    rtcp_exp->dir = !dir;
    rtcp_exp->expectfn = ip_nat_sip_expected;

    /* Try to get same pair of ports: if not, try to change them. */
    for (port = ntohs(rtp_exp->tuple.dst.u.udp.port);
         port != 0; port += 2) {
        rtp_exp->tuple.dst.u.udp.port = htons(port);
        if (nf_ct_expect_related(rtp_exp) != 0)
            continue;
        rtcp_exp->tuple.dst.u.udp.port = htons(port + 1);
        if (nf_ct_expect_related(rtcp_exp) == 0)
            break;
        nf_ct_unexpect_related(rtp_exp);
    }

    if (port == 0)
        goto err1;

    /* Update media port. */
    if (rtp_exp->tuple.dst.u.udp.port != rtp_exp->saved_proto.udp.port &&  ==>Port has been changed
        !ip_nat_sdp_port(skb, dptr, datalen, mediaoff, medialen, port))
        goto err2;

    return NF_ACCEPT;

err2:
    nf_ct_unexpect_related(rtp_exp);
    nf_ct_unexpect_related(rtcp_exp);
err1:
    return NF_DROP;
}
  1. ip_nat_sip_expected
/* Handles expected signalling connections and media streams */
static void ip_nat_sip_expected(struct nf_conn *ct,
                struct nf_conntrack_expect *exp)
{
    struct nf_nat_range range;

    /* This must be a fresh one. */
    BUG_ON(ct->status & IPS_NAT_DONE_MASK);

    /* For DST manip, map port here to where it's expected. */
    range.flags = (IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED);
    range.min = range.max = exp->saved_proto;
    range.min_ip = range.max_ip = exp->saved_ip; //DNAT:public -> private
    nf_nat_setup_info(ct, &range, IP_NAT_MANIP_DST);

    /* Change src to where master sends to, but only if the connection
     * actually came from the same source. */
    if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip ==
        ct->master->tuplehash[exp->dir].tuple.src.u3.ip) {
        range.flags = IP_NAT_RANGE_MAP_IPS;
        range.min_ip = range.max_ip
            = ct->master->tuplehash[!exp->dir].tuple.dst.u3.ip;
        nf_nat_setup_info(ct, &range, IP_NAT_MANIP_SRC);
    }
}
  1. ip_nat_sdp_addr
static unsigned int ip_nat_sdp_addr(struct sk_buff *skb, const char **dptr,
                    unsigned int dataoff,
                    unsigned int *datalen,
                    enum sdp_header_types type,
                    enum sdp_header_types term,
                    const union nf_inet_addr *addr)
{
    char buffer[sizeof("nnn.nnn.nnn.nnn")];
    unsigned int buflen;

    buflen = sprintf(buffer, "%pI4", &addr->ip);
    if (mangle_sdp_packet(skb, dptr, dataoff, datalen, type, term,
                  buffer, buflen))
        return 0;

    return mangle_content_len(skb, dptr, datalen);
}
  1. mangle_sdp_packet
static int mangle_sdp_packet(struct sk_buff *skb, const char **dptr,
                 unsigned int dataoff, unsigned int *datalen,
                 enum sdp_header_types type,
                 enum sdp_header_types term,
                 char *buffer, int buflen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    unsigned int matchlen, matchoff;

    if (ct_sip_get_sdp_header(ct, *dptr, dataoff, *datalen, type, term,
                  &matchoff, &matchlen) <= 0)
        return -ENOENT;
    return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
                 buffer, buflen) ? 0 : -EINVAL;
}
  1. ip_nat_sdp_session
static unsigned int ip_nat_sdp_session(struct sk_buff *skb, const char **dptr,
                       unsigned int dataoff,
                       unsigned int *datalen,
                       const union nf_inet_addr *addr)
{
    char buffer[sizeof("nnn.nnn.nnn.nnn")];
    unsigned int buflen;

    /* Mangle session description owner and contact addresses */
    buflen = sprintf(buffer, "%pI4", &addr->ip);
    if (mangle_sdp_packet(skb, dptr, dataoff, datalen,
                   SDP_HDR_OWNER_IP4, SDP_HDR_MEDIA,
                   buffer, buflen))
        return 0;

    switch (mangle_sdp_packet(skb, dptr, dataoff, datalen,
                  SDP_HDR_CONNECTION_IP4, SDP_HDR_MEDIA,
                  buffer, buflen)) {
    case 0:
    /*
     * RFC 2327:
     *
     * Session description
     *
     * c=* (connection information - not required if included in all media)
     */
    case -ENOENT:
        break;
    default:
        return 0;
    }

    return mangle_content_len(skb, dptr, datalen);
}
  1. sdp_media_type
static const struct sdp_media_type sdp_media_types[] = {
    SDP_MEDIA_TYPE("audio ", SIP_EXPECT_AUDIO),
    SDP_MEDIA_TYPE("video ", SIP_EXPECT_VIDEO),
};

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;
}
  1. process_bye_request
    1. process_bye_request
static int process_bye_request(struct sk_buff *skb,
                   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;
}
  1. flush_expectations
static void flush_expectations(struct nf_conn *ct, bool media)
{
    struct nf_conn_help *help = nfct_help(ct);
    struct nf_conntrack_expect *exp;
    struct hlist_node *n, *next;

    spin_lock_bh(&nf_conntrack_lock);
    hlist_for_each_entry_safe(exp, n, next, &help->expectations, lnode) {
        if ((exp->class != SIP_EXPECT_SIGNALLING) ^ media)
            continue;
        if (!del_timer(&exp->timeout))
            continue;
        nf_ct_unlink_expect(exp);
        nf_ct_expect_put(exp);
        if (!media)
            break;
    }
    spin_unlock_bh(&nf_conntrack_lock);
}
  1. process_register_request
    1. process_register_request
/* Parse a REGISTER request and create a permanent expectation for incoming
 * signalling connections. The expectation is marked inactive and is activated
 * when receiving a response indicating success from the registrar.
 */
static int process_register_request(struct sk_buff *skb,
                    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_conn_help *help = nfct_help(ct);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    unsigned int matchoff, matchlen;
    struct nf_conntrack_expect *exp;
    union nf_inet_addr *saddr, daddr;
    __be16 port;
    unsigned int expires = 0;
    int ret;
    typeof(nf_nat_sip_expect_hook) nf_nat_sip_expect;

    /* 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. ==>(Example?)
     */
    //Expires: 30
    if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_EXPIRES,
                  &matchoff, &matchlen) > 0)
        expires = simple_strtoul(*dptr + matchoff, NULL, 10);
    //Contact: sip:galvani@bolognauni.edu; expires=45
    ret = ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
                      SIP_HDR_CONTACT, NULL,
                      &matchoff, &matchlen, &daddr, &port);
    if (ret < 0)
        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_numerical_param(ct, *dptr,
                     matchoff + matchlen, *datalen,
                     "expires=", NULL, NULL, &expires) < 0)
        return NF_DROP;

    if (expires == 0) { //Cancel all registration
        ret = NF_ACCEPT;
        goto store_cseq;
    }

    exp = nf_ct_expect_alloc(ct);
    if (!exp)
        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, IPPROTO_UDP, 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_sip_expect = rcu_dereference(nf_nat_sip_expect_hook);
    if (nf_nat_sip_expect && ct->status & IPS_NAT_MASK)
        ret = nf_nat_sip_expect(skb, dptr, datalen, exp,
                    matchoff, matchlen);
    else {
        if (nf_ct_expect_related(exp) != 0)
            ret = NF_DROP;
        else
            ret = NF_ACCEPT;
    }
    nf_ct_expect_put(exp);

store_cseq:
    if (ret == NF_ACCEPT)
        help->help.ct_sip_info.register_cseq = cseq;
    return ret;
}
  1. ct_sip_parse_header_uri
    1. ct_sip_parse_header_uri
/* Locate a SIP header, parse the URI and return the offset and length of
 * the address as well as the address and port themselves. A stream of
 * headers can be parsed by handing in a non-NULL datalen and in_header
 * pointer.
 */
int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
                unsigned int *dataoff, unsigned int datalen,
                enum sip_header_types type, int *in_header,
                unsigned int *matchoff, unsigned int *matchlen,
                union nf_inet_addr *addr, __be16 *port)
{
    const char *c, *limit = dptr + datalen;
    unsigned int p;
    int ret;

    ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen,
                  type, in_header, matchoff, matchlen);
    WARN_ON(ret < 0);
    if (ret == 0)
        return ret;

    if (!parse_addr(ct, dptr + *matchoff, &c, addr, limit))
        return -1;
    if (*c == ':') {
        c++;
        p = simple_strtoul(c, (char **)&c, 10);
        if (p < 1024 || p > 65535)
            return -1;
        *port = htons(p);
    } else
        *port = htons(SIP_PORT);

    if (dataoff)
        *dataoff = c - dptr;
    return 1;
}
  1. ct_sip_walk_headers
/* Walk through headers until a parsable one is found or no header of the
 * given type is left. */
static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr,
                   unsigned int dataoff, unsigned int datalen,
                   enum sip_header_types type, int *in_header,
                   unsigned int *matchoff, unsigned int *matchlen)
{
    int ret;

    if (in_header && *in_header) {
        while (1) {
            ret = ct_sip_next_header(ct, dptr, dataoff, datalen,
                         type, matchoff, matchlen);
            if (ret > 0)
                return ret;
            if (ret == 0)
                break;
            dataoff += *matchoff;
        }
        *in_header = 0;
    }

    while (1) {
        ret = ct_sip_get_header(ct, dptr, dataoff, datalen,
                    type, matchoff, matchlen);
        if (ret > 0)
            break;
        if (ret == 0)
            return ret;
        dataoff += *matchoff;
    }

    if (in_header)
        *in_header = 1;
    return 1;
}
  1. ct_sip_next_header
/* Get next header field in a list of comma seperated values */
static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
                  unsigned int dataoff, unsigned int datalen,
                  enum sip_header_types type,
                  unsigned int *matchoff, unsigned int *matchlen)
{
    const struct sip_header *hdr = &ct_sip_hdrs[type];
    const char *start = dptr, *limit = dptr + datalen;
    int shift = 0;

    dptr += dataoff;

    dptr = ct_sip_header_search(dptr, limit, ",", strlen(","));
    if (!dptr)
        return 0;

    dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen);
    if (!dptr)
        return 0;
    dptr += hdr->slen;

    *matchoff = dptr - start;
    *matchlen = hdr->match_len(ct, dptr, limit, &shift);
    if (!*matchlen)
        return -1;
    *matchoff += shift;
    return 1;
}
  1. ct_sip_parse_numerical_param
/* Parse numerical header parameter and return value, offset and length */
int ct_sip_parse_numerical_param(const struct nf_conn *ct, const char *dptr,
                 unsigned int dataoff, unsigned int datalen,
                 const char *name,
                 unsigned int *matchoff, unsigned int *matchlen,
                 unsigned int *val)
{
    const char *limit = dptr + datalen;
    const char *start;
    char *end;

    limit = ct_sip_header_search(dptr + dataoff, limit, ",", strlen(","));
    if (!limit)
        limit = dptr + datalen;

    start = ct_sip_header_search(dptr + dataoff, limit, name, strlen(name));
    if (!start)
        return 0;

    start += strlen(name);
    *val = simple_strtoul(start, &end, 0);
    if (start == end)
        return 0;
    if (matchoff && matchlen) {
        *matchoff = start - dptr;
        *matchlen = end - start;
    }
    return 1;
}
  1. ip_nat_sip_expect
static unsigned int ip_nat_sip_expect(struct sk_buff *skb,
                      const char **dptr, unsigned int *datalen,
                      struct nf_conntrack_expect *exp,
                      unsigned int matchoff,
                      unsigned int matchlen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    __be32 newip;
    u_int16_t port;
    char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
    unsigned buflen;

    /* Connection will come from reply */
    if (ct->tuplehash[dir].tuple.src.u3.ip == ct->tuplehash[!dir].tuple.dst.u3.ip)
        newip = exp->tuple.dst.u3.ip; //No NAT needed
    else
        newip = ct->tuplehash[!dir].tuple.dst.u3.ip;

    /* If the signalling port matches the connection's source port in the
     * original direction, try to use the destination port in the opposite
     * direction. */
    if (exp->tuple.dst.u.udp.port ==
        ct->tuplehash[dir].tuple.src.u.udp.port)
        port = ntohs(ct->tuplehash[!dir].tuple.dst.u.udp.port);
    else
        port = ntohs(exp->tuple.dst.u.udp.port);

    exp->saved_ip = exp->tuple.dst.u3.ip;
    exp->tuple.dst.u3.ip = newip;
    exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port;
    exp->dir = !dir;
    exp->expectfn = ip_nat_sip_expected;

    for (; port != 0; port++) {  //Find the first available port begin from port
        exp->tuple.dst.u.udp.port = htons(port);
        if (nf_ct_expect_related(exp) == 0)
            break;
    }

    if (port == 0) //65535 + 1, means no available port
        return NF_DROP;

    if (exp->tuple.dst.u3.ip != exp->saved_ip ||
        exp->tuple.dst.u.udp.port != exp->saved_proto.udp.port) { //Already NATed since IP or Port has been changed
        buflen = sprintf(buffer, "%pI4:%u", &newip, port);
        if (!mangle_packet(skb, dptr, datalen, matchoff, matchlen,
                   buffer, buflen))
            goto err;
    }
    return NF_ACCEPT;

err:
    nf_ct_unexpect_related(exp);
    return NF_DROP;
}
  1. process_sip_response
    1. process_sip_response
static int process_sip_response(struct sk_buff *skb,
                const char **dptr, unsigned int *datalen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    unsigned int matchoff, matchlen;
    unsigned int code, cseq, dataoff, i;

    if (*datalen < strlen("SIP/2.0 200"))
        return NF_ACCEPT;
    code = simple_strtoul(*dptr + strlen("SIP/2.0 "), NULL, 10);
    if (!code)
        return NF_DROP;

    if (ct_sip_get_header(ct, *dptr, 0, *datalen, SIP_HDR_CSEQ,
                  &matchoff, &matchlen) <= 0)
        return NF_DROP;
    cseq = simple_strtoul(*dptr + matchoff, NULL, 10);
    if (!cseq)
        return NF_DROP;
    dataoff = matchoff + matchlen + 1;

    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 < dataoff + handler->len ||
            strnicmp(*dptr + dataoff, handler->method, handler->len))
            continue;
        return handler->response(skb, dptr, datalen, cseq, code);
    }
    return NF_ACCEPT;
}
  1. process_invite_response
static int process_invite_response(struct sk_buff *skb,
                   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_conn_help *help = nfct_help(ct);

    if ((code >= 100 && code <= 199) ||
        (code >= 200 && code <= 299))
        return process_sdp(skb, dptr, datalen, cseq);
    else if (help->help.ct_sip_info.invite_cseq == cseq)
        flush_expectations(ct, true);
    return NF_ACCEPT;
}
  1. process_update_response
static int process_update_response(struct sk_buff *skb,
                   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_conn_help *help = nfct_help(ct);

    if ((code >= 100 && code <= 199) ||
        (code >= 200 && code <= 299))
        return process_sdp(skb, dptr, datalen, cseq);
    else if (help->help.ct_sip_info.invite_cseq == cseq)
        flush_expectations(ct, true);
    return NF_ACCEPT;
}
  1. process_prack_response
static int process_prack_response(struct sk_buff *skb,
                  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_conn_help *help = nfct_help(ct);

    if ((code >= 100 && code <= 199) ||
        (code >= 200 && code <= 299))
        return process_sdp(skb, dptr, datalen, cseq);
    else if (help->help.ct_sip_info.invite_cseq == cseq)
        flush_expectations(ct, true);
    return NF_ACCEPT;
}
  1. process_register_response
    1. process_register_response
static int process_register_response(struct sk_buff *skb,
                     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_conn_help *help = nfct_help(ct);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    union nf_inet_addr addr;
    __be16 port;
    unsigned int matchoff, matchlen, dataoff = 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 (help->help.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);

    while (1) {
        unsigned int c_expires = expires;

        ret = ct_sip_parse_header_uri(ct, *dptr, &dataoff, *datalen,
                          SIP_HDR_CONTACT, &in_contact, //expires: might be contained in Contack: header field
                          &matchoff, &matchlen,
                          &addr, &port);
        if (ret < 0)
            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;

        ret = ct_sip_parse_numerical_param(ct, *dptr,
                           matchoff + matchlen,
                           *datalen, "expires=",
                           NULL, NULL, &c_expires);
        if (ret < 0)
            return NF_DROP;
        if (c_expires == 0) //Fulsh all realated connections
            break;
        if (refresh_signalling_expectation(ct, &addr, port, c_expires))
            return NF_ACCEPT;
    }

flush:
    flush_expectations(ct, false);
    return NF_ACCEPT;
}
  1. refresh_signalling_expectation
static int refresh_signalling_expectation(struct nf_conn *ct,
                      union nf_inet_addr *addr,
                      __be16 port,
                      unsigned int expires)
{
    struct nf_conn_help *help = nfct_help(ct);
    struct nf_conntrack_expect *exp;
    struct hlist_node *n, *next;
    int found = 0;

    spin_lock_bh(&nf_conntrack_lock);
    hlist_for_each_entry_safe(exp, n, next, &help->expectations, lnode) {
        if (exp->class != SIP_EXPECT_SIGNALLING ||
            !nf_inet_addr_cmp(&exp->tuple.dst.u3, addr) ||
            exp->tuple.dst.u.udp.port != port)
            continue;
        if (!del_timer(&exp->timeout))
            continue;
        exp->flags &= ~NF_CT_EXPECT_INACTIVE; //Clear inactive bit -> Active
        exp->timeout.expires = jiffies + expires * HZ;
        add_timer(&exp->timeout);
        found = 1;
        break;
    }
    spin_unlock_bh(&nf_conntrack_lock);
    return found;
}
  1. ip_nat_sip
    1. ip_nat_sip
static unsigned int ip_nat_sip(struct sk_buff *skb,
                   const char **dptr, unsigned int *datalen)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    unsigned int dataoff, matchoff, matchlen;
    union nf_inet_addr addr;
    __be16 port;
    int request, in_header;

    /* Basic rules: requests and responses. */
    if (strnicmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) {
        if (ct_sip_parse_request(ct, *dptr, *datalen,
                     &matchoff, &matchlen,
                     &addr, &port) > 0 &&
            !map_addr(skb, dptr, datalen, matchoff, matchlen,
                  &addr, port))
            return NF_DROP;
        request = 1;
    } else
        request = 0;

    /* Translate topmost Via header and parameters */
    if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen,
                    SIP_HDR_VIA, NULL, &matchoff, &matchlen,
                    &addr, &port) > 0) {
        unsigned int matchend, poff, plen, buflen, n;
        char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")];

        /* We're only interested in headers related to this
         * connection
*/
        if (request) {
            if (addr.ip != ct->tuplehash[dir].tuple.src.u3.ip ||
                port != ct->tuplehash[dir].tuple.src.u.udp.port)
                goto next;
        } else {
            if (addr.ip != ct->tuplehash[dir].tuple.dst.u3.ip ||
                port != ct->tuplehash[dir].tuple.dst.u.udp.port)
                goto next;
        }

        if (!map_addr(skb, dptr, datalen, matchoff, matchlen,
                  &addr, port))
            return NF_DROP;

        matchend = matchoff + matchlen;

        /* The maddr= parameter (RFC 2361) specifies where to send
         * the reply. */

        /*RFC3261: The maddr parameter indicates the server address to be
contacted for this user, overriding any address derived from
the host field. When an maddr parameter is present, the port
and transport components of the URI apply to the address
indicated in the maddr parameter value.
*/


        if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen,
                           "maddr=", &poff, &plen,
                           &addr) > 0 &&
            addr.ip == ct->tuplehash[dir].tuple.src.u3.ip &&
            addr.ip != ct->tuplehash[!dir].tuple.dst.u3.ip) {
            buflen = sprintf(buffer, "%pI4",
                    &ct->tuplehash[!dir].tuple.dst.u3.ip);
            if (!mangle_packet(skb, dptr, datalen, poff, plen,
                       buffer, buflen))
                return NF_DROP;
        }

        /* The received= parameter (RFC 2361) contains the address
         * from which the server received the request. */
        /*  RFC3261:  When the server transport receives a request over any transport, it
MUST examine the value of the "sent-by" parameter in the top Via
header field value. If the host portion of the "sent-by" parameter
contains a domain name, or if it contains an IP address that differs
from the packet source address, the server MUST add a "received"
parameter to that Via header field value. This parameter MUST
contain the source address from which the packet was received. This
is to assist the server transport layer in sending the response,
since it must be sent to the source IP address from which the request
came. */
        if (ct_sip_parse_address_param(ct, *dptr, matchend, *datalen,
                           "received=", &poff, &plen,
                           &addr) > 0 &&
            addr.ip == ct->tuplehash[dir].tuple.dst.u3.ip &&
            addr.ip != ct->tuplehash[!dir].tuple.src.u3.ip) {
            buflen = sprintf(buffer, "%pI4",
                    &ct->tuplehash[!dir].tuple.src.u3.ip);
            if (!mangle_packet(skb, dptr, datalen, poff, plen,
                       buffer, buflen))
                return NF_DROP;
        }

        /* The rport= parameter (RFC 3581) contains the port number
         * from which the server received the request. */
        if (ct_sip_parse_numerical_param(ct, *dptr, matchend, *datalen,
                         "rport=", &poff, &plen,
                         &n) > 0 &&
            htons(n) == ct->tuplehash[dir].tuple.dst.u.udp.port &&
            htons(n) != ct->tuplehash[!dir].tuple.src.u.udp.port) {
            __be16 p = ct->tuplehash[!dir].tuple.src.u.udp.port;
            buflen = sprintf(buffer, "%u", ntohs(p));
            if (!mangle_packet(skb, dptr, datalen, poff, plen,
                       buffer, buflen))
                return NF_DROP;
        }
    }

next:
    /* Translate Contact headers */
    dataoff = 0;
    in_header = 0;
    while (ct_sip_parse_header_uri(ct, *dptr, &dataoff, *datalen,
                       SIP_HDR_CONTACT, &in_header,
                       &matchoff, &matchlen,
                       &addr, &port) > 0) {
        if (!map_addr(skb, dptr, datalen, matchoff, matchlen,
                  &addr, port))
            return NF_DROP;
    }

    if (!map_sip_addr(skb, dptr, datalen, SIP_HDR_FROM) ||
        !map_sip_addr(skb, dptr, datalen, SIP_HDR_TO))
        return NF_DROP;
    return NF_ACCEPT;
}
  1. ct_sip_parse_request
/* Parse a SIP request line of the form:
 *
 * Request-Line = Method SP Request-URI SP SIP-Version CRLF
 *
 * and return the offset and length of the address contained in the Request-URI.
 */
int ct_sip_parse_request(const struct nf_conn *ct,
             const char *dptr, unsigned int datalen,
             unsigned int *matchoff, unsigned int *matchlen,
             union nf_inet_addr *addr, __be16 *port)
{
    const char *start = dptr, *limit = dptr + datalen, *end;
    unsigned int mlen;
    unsigned int p;
    int shift = 0;

    /* Skip method and following whitespace */
    mlen = string_len(ct, dptr, limit, NULL);
    if (!mlen)
        return 0;
    dptr += mlen;
    if (++dptr >= limit)
        return 0;

    /* Find SIP URI */
    limit -= strlen("sip:");
    for (; dptr < limit; dptr++) {
        if (*dptr == '\r' || *dptr == '\n')
            return -1;
        if (strnicmp(dptr, "sip:", strlen("sip:")) == 0)
            break;
    }
    if (!skp_epaddr_len(ct, dptr, limit, &shift))
        return 0;
    dptr += shift;

    if (!parse_addr(ct, dptr, &end, addr, limit))
        return -1;
    if (end < limit && *end == ':') {
        end++;
        p = simple_strtoul(end, (char **)&end, 10);
        if (p < 1024 || p > 65535)
            return -1;
        *port = htons(p);
    } else
        *port = htons(SIP_PORT);

    if (end == dptr)
        return 0;
    *matchoff = dptr - start;
    *matchlen = end - dptr;
    return 1;
}
  1. map_addr
static int map_addr(struct sk_buff *skb,
            const char **dptr, unsigned int *datalen,
            unsigned int matchoff, unsigned int matchlen,
            union nf_inet_addr *addr, __be16 port)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
    char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
    unsigned int buflen;
    __be32 newaddr;
    __be16 newport;

    if (ct->tuplehash[dir].tuple.src.u3.ip == addr->ip &&
        ct->tuplehash[dir].tuple.src.u.udp.port == port) {
        newaddr = ct->tuplehash[!dir].tuple.dst.u3.ip;
        newport = ct->tuplehash[!dir].tuple.dst.u.udp.port;
    } else if (ct->tuplehash[dir].tuple.dst.u3.ip == addr->ip &&
           ct->tuplehash[dir].tuple.dst.u.udp.port == port) {
        newaddr = ct->tuplehash[!dir].tuple.src.u3.ip;
        newport = ct->tuplehash[!dir].tuple.src.u.udp.port;
    } else
        return 1;

    if (newaddr == addr->ip && newport == port) //No need to NAT
        return 1;

    buflen = sprintf(buffer, "%pI4:%u", &newaddr, ntohs(newport));

    return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
                 buffer, buflen);
}
  1. map_ip_addr
static int map_sip_addr(struct sk_buff *skb,
            const char **dptr, unsigned int *datalen,
            enum sip_header_types type)
{
    enum ip_conntrack_info ctinfo;
    struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
    unsigned int matchlen, matchoff;
    union nf_inet_addr addr;
    __be16 port;

    if (ct_sip_parse_header_uri(ct, *dptr, NULL, *datalen, type, NULL,
                    &matchoff, &matchlen, &addr, &port) <= 0)
        return 1;
    return map_addr(skb, dptr, datalen, matchoff, matchlen, &addr, port);
}

            





阅读(5081) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~