netfilter: Changes to handle segmentation in SIP ALG
authorRavinder Konka <rkonka@codeaurora.org>
Tue, 9 Dec 2014 10:52:47 +0000 (16:22 +0530)
committerStricted <info@stricted.net>
Tue, 6 Aug 2019 11:33:34 +0000 (11:33 +0000)
Linux Kernel SIP ALG did not handle Segmented TCP Packets
because of which SIP communication could not be established
for some clients. This change fixes that issue.

Change-Id: I8c77322f69cf4d9ad4c7b4971da924ffd585dea0
Signed-off-by: Ravinder Konka <rkonka@codeaurora.org>
Signed-off-by: Tyler Wear <twear@codeaurora.org>
Signed-off-by: Jan Altensen <info@stricted.net>
include/net/netfilter/nf_conntrack.h
include/net/netfilter/nf_conntrack_core.h
net/netfilter/nf_conntrack_core.c
net/netfilter/nf_conntrack_sip.c

index 644d9c223d249617291edce40d11c75cec6fd0fd..2970f4ee5e4acfa27b3c5c557c5397f5f336a276 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/bitops.h>
 #include <linux/compiler.h>
 #include <linux/atomic.h>
+#include <linux/list.h>
 
 #include <linux/netfilter/nf_conntrack_tcp.h>
 #include <linux/netfilter/nf_conntrack_dccp.h>
 
 #include <net/netfilter/nf_conntrack_tuple.h>
 
+#define SIP_LIST_ELEMENTS      2
+
+struct sip_length {
+       int msg_length[SIP_LIST_ELEMENTS];
+       int skb_len[SIP_LIST_ELEMENTS];
+       int data_len[SIP_LIST_ELEMENTS];
+};
+
 /* per conntrack: protocol private data */
 union nf_conntrack_proto {
        /* insert conntrack proto private data here */
@@ -105,6 +114,12 @@ struct nf_conn {
        struct net *ct_net;
 #endif
 
+       struct list_head sip_segment_list;
+       const char *dptr_prev;
+       struct sip_length segment;
+       bool sip_original_dir;
+       bool sip_reply_dir;
+
        /* Storage reserved for other modules, must be the last member */
        union nf_conntrack_proto proto;
 };
index fb2b6234e9375846bdb37727717c7dbd432550d9..c061af71d6b90be30f069489d532b6845a43d2c8 100644 (file)
@@ -86,4 +86,9 @@ print_tuple(struct seq_file *s, const struct nf_conntrack_tuple *tuple,
 
 extern spinlock_t nf_conntrack_lock ;
 
+struct sip_list {
+       struct nf_queue_entry *entry;
+       struct list_head list;
+};
+
 #endif /* _NF_CONNTRACK_CORE_H */
index 97248f8b266562d12501307b508fbc247a0c1b28..261acb30d08ea6354179783cfcd6c4743ca53234 100644 (file)
@@ -202,6 +202,9 @@ destroy_conntrack(struct nf_conntrack *nfct)
        struct nf_conn *ct = (struct nf_conn *)nfct;
        struct net *net = nf_ct_net(ct);
        struct nf_conntrack_l4proto *l4proto;
+       struct sip_list *sip_node = NULL;
+       struct list_head *sip_node_list;
+       struct list_head *sip_node_save_list;
 
        pr_debug("destroy_conntrack(%pK)\n", ct);
        NF_CT_ASSERT(atomic_read(&nfct->use) == 0);
@@ -218,6 +221,16 @@ destroy_conntrack(struct nf_conntrack *nfct)
        rcu_read_unlock();
 
        spin_lock_bh(&nf_conntrack_lock);
+
+       list_for_each_safe(sip_node_list, sip_node_save_list,
+                   &ct->sip_segment_list)
+       {
+               sip_node = list_entry(sip_node_list, struct sip_list, list);
+               pr_debug("freeing item in the SIP list\n");
+               list_del(&(sip_node->list));
+               kfree(sip_node);
+       }
+
        /* Expectations will have been removed in clean_from_lists,
         * except TFTP can create an expectation on the first packet,
         * before connection is in the list, so we need to clean here,
@@ -834,6 +847,9 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
                             GFP_ATOMIC);
 
        spin_lock_bh(&nf_conntrack_lock);
+
+       INIT_LIST_HEAD(&(ct->sip_segment_list));
+
        exp = nf_ct_find_expectation(net, zone, tuple);
        if (exp) {
                pr_debug("conntrack: expectation arrives ct=%pK exp=%pK\n",
index e0c4373b47478d4d72899d95166566db34a8bcfa..a5987c4a27f465eb2ce1d3ce83770f2ff8e2eb96 100644 (file)
@@ -1,5 +1,6 @@
 /* SIP extension for IP connection tracking.
  *
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
  * (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
 #include <linux/udp.h>
 #include <linux/tcp.h>
 #include <linux/netfilter.h>
-
+#include <net/tcp.h>
 #include <net/netfilter/nf_conntrack.h>
 #include <net/netfilter/nf_conntrack_core.h>
 #include <net/netfilter/nf_conntrack_expect.h>
 #include <net/netfilter/nf_conntrack_helper.h>
 #include <net/netfilter/nf_conntrack_zones.h>
 #include <linux/netfilter/nf_conntrack_sip.h>
+#include <net/netfilter/nf_nat.h>
+#include <net/netfilter/nf_nat_l3proto.h>
+#include <net/netfilter/nf_nat_l4proto.h>
+#include <net/netfilter/nf_queue.h>
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
@@ -52,9 +57,167 @@ module_param(sip_direct_media, int, 0600);
 MODULE_PARM_DESC(sip_direct_media, "Expect Media streams between signalling "
                                   "endpoints only (default 1)");
 
+static int packet_count;
+
 unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb, unsigned int protoff,
                                unsigned int dataoff, const char **dptr,
                                unsigned int *datalen) __read_mostly;
+
+/* This function is to save all the information of the first segment
+ * that will be needed for combining the two segments
+ */
+static bool sip_save_segment_info(struct nf_conn *ct, struct sk_buff *skb,
+                  unsigned int msglen, unsigned int datalen, const char *dptr,
+               enum ip_conntrack_info ctinfo)
+{
+       enum ip_conntrack_dir dir = IP_CT_DIR_MAX;
+       bool skip = false;
+
+       /* one set of information is saved per direction ,also only one segment
+        * per direction is queued based on the assumption that after the first
+        * complete message leaves the kernel, only then the next fragmented
+        * segment will reach the kernel
+        */
+       if (ct)
+               dir = CTINFO2DIR(ctinfo);
+
+       if (dir == IP_CT_DIR_ORIGINAL) {
+               /* here we check if there is already an element queued for this
+                * direction, in that case we do not queue the next element,we
+                * make skip 1.ideally this scenario should never be hit
+                */
+               if (ct->sip_original_dir == 1) {
+                       skip = true;
+               } else {
+                       ct->segment.msg_length[0] = msglen;
+                       ct->segment.data_len[0] = datalen;
+                       ct->segment.skb_len[0] = skb->len;
+                       ct->dptr_prev = dptr;
+                       ct->sip_original_dir = 1;
+                       skip = false;
+               }
+       } else {
+               if (ct->sip_reply_dir == 1) {
+                       skip = 1;
+               } else {
+                       ct->segment.msg_length[1] = msglen;
+                       ct->segment.data_len[1] = datalen;
+                       ct->segment.skb_len[1] = skb->len;
+                       ct->dptr_prev = dptr;
+                       ct->sip_reply_dir = 1;
+                       skip = false;
+               }
+       }
+return skip;
+
+}
+
+static struct sip_list *sip_coalesce_segments(struct nf_conn *ct,
+                       struct sk_buff **skb_ref, unsigned int dataoff,
+                       struct sk_buff **combined_skb_ref,
+                       bool *skip_sip_process, bool do_not_process,
+                       enum ip_conntrack_info ctinfo, bool *success)
+
+{
+       struct list_head *list_trav_node;
+       struct list_head *list_backup_node;
+       struct nf_conn *ct_list;
+       enum ip_conntrack_info ctinfo_list;
+       enum ip_conntrack_dir dir_list;
+       enum ip_conntrack_dir dir = IP_CT_DIR_MAX;
+       const struct tcphdr *th_old;
+       unsigned int prev_data_len;
+       unsigned int seq_no, seq_old, exp_seq_no;
+       const struct tcphdr *th_new;
+       bool fragstolen = false;
+       int delta_truesize = 0;
+       struct sip_list *sip_entry = NULL;
+
+       th_new = (struct tcphdr *)(skb_network_header(*skb_ref) +
+               ip_hdrlen(*skb_ref));
+       seq_no = ntohl(th_new->seq);
+
+       if (ct)
+               dir = CTINFO2DIR(ctinfo);
+       /* traverse the list it would have 1 or 2 elements. 1 element per
+        * direction at max
+        */
+       list_for_each_safe(list_trav_node, list_backup_node,
+                       &(ct->sip_segment_list))
+       {
+               sip_entry = list_entry(list_trav_node, struct sip_list, list);
+               ct_list = nf_ct_get(sip_entry->entry->skb, &ctinfo_list);
+               dir_list = CTINFO2DIR(ctinfo_list);
+               /* take an element and check if its direction matches with the
+                * current one
+                */
+               if (dir_list == dir) {
+                       /* once we have the two elements to be combined we do
+                        * another check. match the next expected seq no of the
+                        * packet in the list with the seq no of the current
+                        * packet.this is to be protected  against out of order
+                        * fragments
+                        */
+                       th_old = ((struct tcphdr *)(skb_network_header
+                               (sip_entry->entry->skb) +
+                               ip_hdrlen(sip_entry->entry->skb)));
+
+                       prev_data_len = (dir == IP_CT_DIR_ORIGINAL) ?
+                        ct->segment.data_len[0] : ct->segment.data_len[1];
+                       seq_old = (ntohl(th_old->seq));
+                       exp_seq_no = seq_old+prev_data_len;
+
+                       if (exp_seq_no == seq_no) {
+                               /* Found packets to be combined.Pull header from
+                                * second skb when preparing combined skb.This
+                                * shifts the second skb start pointer to its
+                                * data that was initially at the start of its
+                                * headers.This so that the  combined skb has
+                                * the tcp ip headerof the first skb followed
+                                * by the data of first skb followed by the data
+                                * of second skb.
+                                */
+                               skb_pull(*skb_ref, dataoff);
+                               if (skb_try_coalesce(sip_entry->entry->skb,
+                                                       *skb_ref, &fragstolen,
+                                                       &delta_truesize)) {
+                                       *combined_skb_ref =
+                                                         sip_entry->entry->skb;
+                                       *success = true;
+                                       list_del(list_trav_node);
+                                       } else
+                                               skb_push(*skb_ref, dataoff);
+                       }
+               } else if (do_not_process)
+                       *skip_sip_process = true;
+       }
+       return sip_entry;
+}
+
+static void recalc_header(struct sk_buff *skb, unsigned int skblen,
+                         unsigned int oldlen, unsigned int protoff)
+{
+       unsigned int datalen;
+       struct tcphdr *tcph;
+       const struct nf_nat_l3proto *l3proto;
+
+       /* here we recalculate ip and tcp headers */
+       if (nf_ct_l3num((struct nf_conn *)skb->nfct) == NFPROTO_IPV4) {
+                       /* fix IP hdr checksum information */
+                       ip_hdr(skb)->tot_len = htons(skblen);
+                       ip_send_check(ip_hdr(skb));
+               } else {
+                       ipv6_hdr(skb)->payload_len =
+                                        htons(skblen - sizeof(struct ipv6hdr));
+               }
+               datalen = skb->len - protoff;
+               tcph = (struct tcphdr *)((void *)skb->data + protoff);
+               l3proto = __nf_nat_l3proto_find(nf_ct_l3num
+                                       ((struct nf_conn *)skb->nfct));
+               l3proto->csum_recalc(skb, IPPROTO_TCP, tcph, &tcph->check,
+                                       datalen, oldlen);
+}
+
 EXPORT_SYMBOL_GPL(nf_nat_sip_hook);
 
 void (*nf_nat_sip_seq_adjust_hook)(struct sk_buff *skb, unsigned int protoff,
@@ -125,6 +288,28 @@ static int string_len(const struct nf_conn *ct, const char *dptr,
        return len;
 }
 
+static int nf_sip_enqueue_packet(struct nf_queue_entry *entry,
+                 unsigned int queuenum)
+{
+       enum ip_conntrack_info ctinfo_list;
+       struct nf_conn *ct_temp;
+       struct sip_list *node = kzalloc(sizeof(struct sip_list),
+                       GFP_ATOMIC | __GFP_NOWARN);
+       if (!node) {
+               pr_err("KERNEL MALLOC FAIL\n");
+               return XT_CONTINUE;
+       }
+
+       ct_temp = nf_ct_get(entry->skb, &ctinfo_list);
+       node->entry = entry;
+       list_add(&(node->list), &(ct_temp->sip_segment_list));
+       return 0;
+}
+
+static const struct nf_queue_handler nf_sip_qh = {
+       .outfn  = &nf_sip_enqueue_packet,
+};
+
 static int digits_len(const struct nf_conn *ct, const char *dptr,
                      const char *limit, int *shift)
 {
@@ -1539,15 +1724,28 @@ 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;
-       unsigned int dataoff, datalen;
+       unsigned int dataoff,;
        unsigned int matchoff, matchlen, clen;
-       unsigned int msglen, origlen;
        const char *dptr, *end;
        s16 diff, tdiff = 0;
        int ret = NF_ACCEPT;
        bool term;
+       unsigned int datalen = 0, msglen = 0, origlen = 0;
+       unsigned int dataoff_orig = 0;
+       unsigned int splitlen, oldlen, oldlen1;
+       struct sip_list *sip_entry = NULL;
+       bool skip_sip_process = false;
+       bool do_not_process = false;
+       bool skip = false;
+       bool skb_is_combined = false;
+       enum ip_conntrack_dir dir = IP_CT_DIR_MAX;
+       struct sk_buff *combined_skb = NULL;
+
        typeof(nf_nat_sip_seq_adjust_hook) nf_nat_sip_seq_adjust;
 
+       packet_count++;
+       pr_debug("packet count %d\n", packet_count);
+
        if (ctinfo != IP_CT_ESTABLISHED &&
            ctinfo != IP_CT_ESTABLISHED_REPLY)
                return NF_ACCEPT;
@@ -1573,12 +1771,16 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
        while (1) {
                if (ct_sip_get_header(ct, dptr, 0, datalen,
                                      SIP_HDR_CONTENT_LENGTH,
-                                     &matchoff, &matchlen) <= 0)
-                       break;
+                                     &matchoff, &matchlen) <= 0){
+                       do_not_process = true;
+                       goto destination;
+               }
 
                clen = simple_strtoul(dptr + matchoff, (char **)&end, 10);
-               if (dptr + matchoff == end)
-                       break;
+               if (dptr + matchoff == end) {
+                       do_not_process = true;
+                       goto destination;
+               }
 
                term = false;
                for (; end + strlen("\r\n\r\n") <= dptr + datalen; end++) {
@@ -1588,27 +1790,115 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
                                break;
                        }
                }
-               if (!term)
-                       break;
-               end += strlen("\r\n\r\n") + clen;
+               if (!term) {
+                               do_not_process = true;
+                               goto destination;
+               }
 
+               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);
+destination:
+
+               if (ct)
+                       dir = CTINFO2DIR(ctinfo);
+               combined_skb = skb;
+
+               /* Segmented Packet */
+               if (msglen > datalen) {
+                       skip = sip_save_segment_info(ct, skb, msglen, datalen,
+                               dptr, ctinfo);
+                       if (!skip)
+                               return NF_QUEUE;
+               }
+               /* Traverse list to find prev segment */
+               /*Traverse the list if list non empty */
+               if (((&(ct->sip_segment_list))->next) !=
+                       (&(ct->sip_segment_list))) {
+                       /* Combine segments if they are fragments of the same
+                        * message.
+                        */
+                       sip_entry = sip_coalesce_segments(ct, &skb,
+                                       dataoff, &combined_skb,
+                                       &skip_sip_process, do_not_process,
+                                       ctinfo, &skb_is_combined);
+
+               if (skb_is_combined) {
+                       /* The msglen of first skb has the total msg length of
+                        * the two fragments. hence after combining,we update
+                        * the msglen to that of the msglen of first skb
+                        */
+                       msglen = (dir == IP_CT_DIR_ORIGINAL) ?
+                         ct->segment.msg_length[0] : ct->segment.msg_length[1];
+                       origlen = msglen;
+                       dptr = ct->dptr_prev;
+                       datalen = msglen;
+               }
+               if (skip_sip_process)
+                       goto here;
+
+               } else if (do_not_process)
+                       goto here;
+               /* process the combined skb having the complete SIP message */
+               ret = process_sip_msg(combined_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;
+
+               diff     = msglen - origlen;
+               tdiff   += diff;
 
                dataoff += msglen;
                dptr    += msglen;
                datalen  = datalen + diff - msglen;
+
+               break;
        }
 
+       if (skb_is_combined) {
+               /* once combined skb is processed, split the skbs again The
+                * length to split at is the same as length of first skb. Any
+                * changes in the combined skb length because of SIP processing
+                * will reflect in the second fragment
+                */
+               splitlen = (dir == IP_CT_DIR_ORIGINAL) ?
+                               ct->segment.skb_len[0] : ct->segment.skb_len[1];
+               oldlen = combined_skb->len - protoff;
+               skb_split(combined_skb, skb, splitlen);
+               /* Headers need to be recalculated since during SIP processing
+                * headers are calculated based on the change in length of the
+                * combined message
+                */
+               recalc_header(combined_skb, splitlen, oldlen, protoff);
+               /* Reinject the first skb now that the processing is complete */
+               nf_reinject(sip_entry->entry, NF_ACCEPT);
+               kfree(sip_entry);
+               skb->len = (oldlen1 + protoff) + tdiff - dataoff_orig;
+               /* After splitting, push the headers back to the first skb which
+                * were removed before combining the skbs.This moves the skb
+                * begin pointer back to the beginning of its headers
+                */
+               skb_push(skb, dataoff_orig);
+               /* Since the length of this second segment willbe affected
+                * because of SIP processing,we need to recalculate its header
+                * as well.
+                */
+               recalc_header(skb, skb->len, oldlen1, protoff);
+               /* Now that the processing is done and the first skb reinjected.
+                * We allow addition of fragmented skbs to the list for this
+                * direction
+                */
+               if (dir == IP_CT_DIR_ORIGINAL)
+                       ct->sip_original_dir = 0;
+               else
+                       ct->sip_reply_dir = 0;
+       }
+
+here:
+
+
        if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) {
                nf_nat_sip_seq_adjust = rcu_dereference(nf_nat_sip_seq_adjust_hook);
                if (nf_nat_sip_seq_adjust)
@@ -1728,6 +2018,9 @@ static int __init nf_conntrack_sip_init(void)
                        }
                }
        }
+
+       nf_register_queue_handler(&nf_sip_qh);
+
        return 0;
 }