net: compat_mmsghdr must be used in sys_recvmmsg
[safe/jmp/linux-2.6] / net / netfilter / nf_conntrack_sip.c
index f929add..4b57216 100644 (file)
@@ -2,6 +2,8 @@
  *
  * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
  * based on RR's ip_conntrack_ftp.c and other modules.
+ * (C) 2007 United Security Providers
+ * (C) 2007, 2008 Patrick McHardy <kaber@trash.net>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -26,6 +28,7 @@ MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
 MODULE_DESCRIPTION("SIP connection tracking helper");
 MODULE_ALIAS("ip_conntrack_sip");
+MODULE_ALIAS_NFCT_HELPER("sip");
 
 #define MAX_PORTS      8
 static unsigned short ports[MAX_PORTS];
@@ -70,6 +73,14 @@ unsigned int (*nf_nat_sdp_addr_hook)(struct sk_buff *skb,
                                     __read_mostly;
 EXPORT_SYMBOL_GPL(nf_nat_sdp_addr_hook);
 
+unsigned int (*nf_nat_sdp_port_hook)(struct sk_buff *skb,
+                                    const char **dptr,
+                                    unsigned int *datalen,
+                                    unsigned int matchoff,
+                                    unsigned int matchlen,
+                                    u_int16_t port) __read_mostly;
+EXPORT_SYMBOL_GPL(nf_nat_sdp_port_hook);
+
 unsigned int (*nf_nat_sdp_session_hook)(struct sk_buff *skb,
                                        const char **dptr,
                                        unsigned int dataoff,
@@ -112,15 +123,30 @@ static int digits_len(const struct nf_conn *ct, const char *dptr,
        return len;
 }
 
+/* get media type + port length */
+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);
+}
+
 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 family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
        int ret = 0;
 
-       switch (family) {
+       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;
@@ -563,7 +589,7 @@ static const struct sip_header ct_sdp_hdrs[] = {
        [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=", "audio ", digits_len),
+       [SDP_HDR_MEDIA]                 = SDP_HDR("m=", NULL, media_len),
 };
 
 /* Linear string search within SDP header values */
@@ -705,18 +731,20 @@ static void flush_expectations(struct nf_conn *ct, bool media)
 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 family = ct->tuplehash[!dir].tuple.src.l3num;
-       int skip_expect = 0, ret = NF_DROP;
+       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;
 
        saddr = NULL;
@@ -730,44 +758,75 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb,
         * 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         = family;
+       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();
-       exp = __nf_ct_expect_find(&tuple);
-       if (exp && exp->master != ct &&
-           nfct_help(exp->master)->helper == nfct_help(ct)->helper &&
-           exp->class == SIP_EXPECT_AUDIO)
-               skip_expect = 1;
-       rcu_read_unlock();
+       do {
+               exp = __nf_ct_expect_find(net, &tuple);
 
-       if (skip_expect)
-               return NF_ACCEPT;
+               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) &&
+                   ct->status & IPS_NAT_MASK) {
+                       daddr->ip               = exp->saved_ip;
+                       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, SIP_EXPECT_AUDIO, family, saddr, daddr,
+       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, SIP_EXPECT_AUDIO, family, saddr, daddr,
+       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)
+       if (nf_nat_sdp_media && ct->status & IPS_NAT_MASK && !direct_rtp)
                ret = nf_nat_sdp_media(skb, dptr, datalen, rtp_exp, rtcp_exp,
                                       mediaoff, medialen, daddr);
        else {
@@ -785,26 +844,51 @@ err1:
        return ret;
 }
 
+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;
+}
+
 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);
-       int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
+       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;
-       int ret;
+       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;
 
-       c_hdr = family == AF_INET ? SDP_HDR_CONNECTION_IP4 :
-                                   SDP_HDR_CONNECTION_IP6;
+       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,
@@ -822,41 +906,55 @@ static int process_sdp(struct sk_buff *skb,
                                  &matchoff, &matchlen, &caddr) > 0)
                caddr_len = matchlen;
 
-       if (ct_sip_get_sdp_header(ct, *dptr, sdpoff, *datalen,
-                                 SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
-                                 &mediaoff, &medialen) <= 0)
-               return NF_ACCEPT;
+       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;
 
-       port = simple_strtoul(*dptr + mediaoff, NULL, 10);
-       if (port < 1024 || port > 65535)
-               return NF_DROP;
+               /* 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;
 
-       /* 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;
+               port = simple_strtoul(*dptr + mediaoff, NULL, 10);
+               if (port == 0)
+                       continue;
+               if (port < 1024 || port > 65535)
+                       return NF_DROP;
 
-       ret = set_expected_rtp_rtcp(skb, dptr, datalen, &rtp_addr, htons(port),
-                                   mediaoff, medialen);
-       if (ret != NF_ACCEPT)
-               return ret;
+               /* 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 = rcu_dereference(nf_nat_sdp_addr_hook);
-               if (nf_nat_sdp_addr && ct->status & IPS_NAT_MASK) {
+               /* 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 */
@@ -864,6 +962,9 @@ static int process_sdp(struct sk_buff *skb,
        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;
 }
 static int process_invite_response(struct sk_buff *skb,
@@ -872,14 +973,14 @@ static int process_invite_response(struct sk_buff *skb,
 {
        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 {
+       else if (help->help.ct_sip_info.invite_cseq == cseq)
                flush_expectations(ct, true);
-               return NF_ACCEPT;
-       }
+       return NF_ACCEPT;
 }
 
 static int process_update_response(struct sk_buff *skb,
@@ -888,14 +989,14 @@ static int process_update_response(struct sk_buff *skb,
 {
        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 {
+       else if (help->help.ct_sip_info.invite_cseq == cseq)
                flush_expectations(ct, true);
-               return NF_ACCEPT;
-       }
+       return NF_ACCEPT;
 }
 
 static int process_prack_response(struct sk_buff *skb,
@@ -904,14 +1005,14 @@ static int process_prack_response(struct sk_buff *skb,
 {
        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 {
+       else if (help->help.ct_sip_info.invite_cseq == cseq)
                flush_expectations(ct, true);
-               return NF_ACCEPT;
-       }
+       return NF_ACCEPT;
 }
 
 static int process_bye_request(struct sk_buff *skb,
@@ -937,7 +1038,6 @@ static int process_register_request(struct sk_buff *skb,
        struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
        struct nf_conn_help *help = nfct_help(ct);
        enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
-       int family = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num;
        unsigned int matchoff, matchlen;
        struct nf_conntrack_expect *exp;
        union nf_inet_addr *saddr, daddr;
@@ -992,8 +1092,8 @@ static int process_register_request(struct sk_buff *skb,
        if (sip_direct_signalling)
                saddr = &ct->tuplehash[!dir].tuple.src.u3;
 
-       nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, family, saddr, &daddr,
-                         IPPROTO_UDP, NULL, &port);
+       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;
@@ -1095,7 +1195,6 @@ static const struct sip_handler sip_handlers[] = {
 static int process_sip_response(struct sk_buff *skb,
                                const char **dptr, unsigned int *datalen)
 {
-       static const struct sip_handler *handler;
        enum ip_conntrack_info ctinfo;
        struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
        unsigned int matchoff, matchlen;
@@ -1116,6 +1215,8 @@ static int process_sip_response(struct sk_buff *skb,
        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;
@@ -1130,13 +1231,14 @@ static int process_sip_response(struct sk_buff *skb,
 static int process_sip_request(struct sk_buff *skb,
                               const char **dptr, unsigned int *datalen)
 {
-       static const struct sip_handler *handler;
        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;
@@ -1210,6 +1312,10 @@ static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1
                .max_expected   = 2 * IP_CT_DIR_MAX,
                .timeout        = 3 * 60,
        },
+       [SIP_EXPECT_VIDEO] = {
+               .max_expected   = 2 * IP_CT_DIR_MAX,
+               .timeout        = 3 * 60,
+       },
 };
 
 static void nf_conntrack_sip_fini(void)