Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ec5a901 authored by Ravinder Konka's avatar Ravinder Konka
Browse files

netfilter: Changes to handle segmentation in SIP ALG



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: default avatarRavinder Konka <rkonka@codeaurora.org>
Signed-off-by: default avatarTyler Wear <twear@codeaurora.org>
parent 45f1dede
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -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>
@@ -26,6 +27,14 @@

#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 */
@@ -121,6 +130,12 @@ struct nf_conn {
	unsigned long nattype_entry;
#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;
};
+5 −0
Original line number Diff line number Diff line
@@ -86,4 +86,9 @@ extern spinlock_t nf_conntrack_locks[CONNTRACK_LOCKS];

extern spinlock_t nf_conntrack_expect_lock;

struct sip_list {
	struct nf_queue_entry *entry;
	struct list_head list;
};

#endif /* _NF_CONNTRACK_CORE_H */
+44 −32
Original line number Diff line number Diff line
@@ -58,13 +58,13 @@
int (*nfnetlink_parse_nat_setup_hook)(struct nf_conn *ct,
				      enum nf_nat_manip_type manip,
				      const struct nlattr *attr) __read_mostly;
EXPORT_SYMBOL_GPL(nfnetlink_parse_nat_setup_hook);
EXPORT_SYMBOL(nfnetlink_parse_nat_setup_hook);

__cacheline_aligned_in_smp spinlock_t nf_conntrack_locks[CONNTRACK_LOCKS];
EXPORT_SYMBOL_GPL(nf_conntrack_locks);
EXPORT_SYMBOL(nf_conntrack_locks);

__cacheline_aligned_in_smp DEFINE_SPINLOCK(nf_conntrack_expect_lock);
EXPORT_SYMBOL_GPL(nf_conntrack_expect_lock);
EXPORT_SYMBOL(nf_conntrack_expect_lock);

static void nf_conntrack_double_unlock(unsigned int h1, unsigned int h2)
{
@@ -115,16 +115,16 @@ static void nf_conntrack_all_unlock(void)
}

unsigned int nf_conntrack_htable_size __read_mostly;
EXPORT_SYMBOL_GPL(nf_conntrack_htable_size);
EXPORT_SYMBOL(nf_conntrack_htable_size);

unsigned int nf_conntrack_max __read_mostly;
EXPORT_SYMBOL_GPL(nf_conntrack_max);
EXPORT_SYMBOL(nf_conntrack_max);

DEFINE_PER_CPU(struct nf_conn, nf_conntrack_untracked);
EXPORT_PER_CPU_SYMBOL(nf_conntrack_untracked);

unsigned int nf_conntrack_hash_rnd __read_mostly;
EXPORT_SYMBOL_GPL(nf_conntrack_hash_rnd);
EXPORT_SYMBOL(nf_conntrack_hash_rnd);

static u32 hash_conntrack_raw(const struct nf_conntrack_tuple *tuple, u16 zone)
{
@@ -183,7 +183,7 @@ nf_ct_get_tuple(const struct sk_buff *skb,

	return l4proto->pkt_to_tuple(skb, dataoff, tuple);
}
EXPORT_SYMBOL_GPL(nf_ct_get_tuple);
EXPORT_SYMBOL(nf_ct_get_tuple);

bool nf_ct_get_tuplepr(const struct sk_buff *skb, unsigned int nhoff,
		       u_int16_t l3num, struct nf_conntrack_tuple *tuple)
@@ -211,7 +211,7 @@ bool nf_ct_get_tuplepr(const struct sk_buff *skb, unsigned int nhoff,
	rcu_read_unlock();
	return ret;
}
EXPORT_SYMBOL_GPL(nf_ct_get_tuplepr);
EXPORT_SYMBOL(nf_ct_get_tuplepr);

bool
nf_ct_invert_tuple(struct nf_conntrack_tuple *inverse,
@@ -230,7 +230,7 @@ nf_ct_invert_tuple(struct nf_conntrack_tuple *inverse,
	inverse->dst.protonum = orig->dst.protonum;
	return l4proto->invert_tuple(inverse, orig);
}
EXPORT_SYMBOL_GPL(nf_ct_invert_tuple);
EXPORT_SYMBOL(nf_ct_invert_tuple);

static void
clean_from_lists(struct nf_conn *ct)
@@ -293,6 +293,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(%p)\n", ct);
	NF_CT_ASSERT(atomic_read(&nfct->use) == 0);
@@ -306,6 +309,14 @@ destroy_conntrack(struct nf_conntrack *nfct)
	rcu_read_unlock();

	local_bh_disable();

	pr_debug("freeing item in the SIP list\n");
	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);
		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,
@@ -378,7 +389,7 @@ bool nf_ct_delete(struct nf_conn *ct, u32 portid, int report)
	nf_ct_put(ct);
	return true;
}
EXPORT_SYMBOL_GPL(nf_ct_delete);
EXPORT_SYMBOL(nf_ct_delete);

static void death_by_timeout(unsigned long ul_conntrack)
{
@@ -475,7 +486,7 @@ nf_conntrack_find_get(struct net *net, u16 zone,
	return __nf_conntrack_find_get(net, zone, tuple,
				       hash_conntrack_raw(tuple, zone));
}
EXPORT_SYMBOL_GPL(nf_conntrack_find_get);
EXPORT_SYMBOL(nf_conntrack_find_get);

static void __nf_conntrack_hash_insert(struct nf_conn *ct,
				       unsigned int hash,
@@ -538,7 +549,7 @@ out:
	local_bh_enable();
	return -EEXIST;
}
EXPORT_SYMBOL_GPL(nf_conntrack_hash_check_insert);
EXPORT_SYMBOL(nf_conntrack_hash_check_insert);

/* deletion from this larval template list happens via nf_ct_put() */
void nf_conntrack_tmpl_insert(struct net *net, struct nf_conn *tmpl)
@@ -560,7 +571,7 @@ void nf_conntrack_tmpl_insert(struct net *net, struct nf_conn *tmpl)
				 &pcpu->tmpl);
	spin_unlock_bh(&pcpu->lock);
}
EXPORT_SYMBOL_GPL(nf_conntrack_tmpl_insert);
EXPORT_SYMBOL(nf_conntrack_tmpl_insert);

/* Confirm a connection given skb; places it in hash table */
int
@@ -676,7 +687,7 @@ out:
	local_bh_enable();
	return NF_DROP;
}
EXPORT_SYMBOL_GPL(__nf_conntrack_confirm);
EXPORT_SYMBOL(__nf_conntrack_confirm);

/* Returns true if a connection correspondings to the tuple (required
   for NAT). */
@@ -710,7 +721,7 @@ nf_conntrack_tuple_taken(const struct nf_conntrack_tuple *tuple,

	return 0;
}
EXPORT_SYMBOL_GPL(nf_conntrack_tuple_taken);
EXPORT_SYMBOL(nf_conntrack_tuple_taken);

#define NF_CT_EVICTION_RANGE	8

@@ -872,7 +883,7 @@ struct nf_conn *nf_conntrack_alloc(struct net *net, u16 zone,
{
	return __nf_conntrack_alloc(net, zone, orig, repl, gfp, 0);
}
EXPORT_SYMBOL_GPL(nf_conntrack_alloc);
EXPORT_SYMBOL(nf_conntrack_alloc);

void nf_conntrack_free(struct nf_conn *ct)
{
@@ -889,7 +900,7 @@ void nf_conntrack_free(struct nf_conn *ct)
	smp_mb__before_atomic();
	atomic_dec(&net->ct.count);
}
EXPORT_SYMBOL_GPL(nf_conntrack_free);
EXPORT_SYMBOL(nf_conntrack_free);


/* Allocate a new conntrack: we return -ENOMEM if classification
@@ -951,6 +962,7 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
			     GFP_ATOMIC);

	local_bh_disable();
	INIT_LIST_HEAD(&ct->sip_segment_list);
	if (net->ct.expect_count) {
		spin_lock(&nf_conntrack_expect_lock);
		exp = nf_ct_find_expectation(net, zone, tuple);
@@ -1170,7 +1182,7 @@ out:

	return ret;
}
EXPORT_SYMBOL_GPL(nf_conntrack_in);
EXPORT_SYMBOL(nf_conntrack_in);

bool nf_ct_invert_tuplepr(struct nf_conntrack_tuple *inverse,
			  const struct nf_conntrack_tuple *orig)
@@ -1185,7 +1197,7 @@ bool nf_ct_invert_tuplepr(struct nf_conntrack_tuple *inverse,
	rcu_read_unlock();
	return ret;
}
EXPORT_SYMBOL_GPL(nf_ct_invert_tuplepr);
EXPORT_SYMBOL(nf_ct_invert_tuplepr);

/* Alter reply tuple (maybe alter helper).  This is for NAT, and is
   implicitly racy: see __nf_conntrack_confirm */
@@ -1208,7 +1220,7 @@ void nf_conntrack_alter_reply(struct nf_conn *ct,
	__nf_ct_try_assign_helper(ct, NULL, GFP_ATOMIC);
	rcu_read_unlock();
}
EXPORT_SYMBOL_GPL(nf_conntrack_alter_reply);
EXPORT_SYMBOL(nf_conntrack_alter_reply);

/* Refresh conntrack for this many jiffies and do accounting if do_acct is 1 */
void __nf_ct_refresh_acct(struct nf_conn *ct,
@@ -1255,7 +1267,7 @@ acct:
		}
	}
}
EXPORT_SYMBOL_GPL(__nf_ct_refresh_acct);
EXPORT_SYMBOL(__nf_ct_refresh_acct);

bool __nf_ct_kill_acct(struct nf_conn *ct,
		       enum ip_conntrack_info ctinfo,
@@ -1281,7 +1293,7 @@ bool __nf_ct_kill_acct(struct nf_conn *ct,
	}
	return false;
}
EXPORT_SYMBOL_GPL(__nf_ct_kill_acct);
EXPORT_SYMBOL(__nf_ct_kill_acct);

#ifdef CONFIG_NF_CONNTRACK_ZONES
static struct nf_ct_ext_type nf_ct_zone_extend __read_mostly = {
@@ -1311,13 +1323,13 @@ int nf_ct_port_tuple_to_nlattr(struct sk_buff *skb,
nla_put_failure:
	return -1;
}
EXPORT_SYMBOL_GPL(nf_ct_port_tuple_to_nlattr);
EXPORT_SYMBOL(nf_ct_port_tuple_to_nlattr);

const struct nla_policy nf_ct_port_nla_policy[CTA_PROTO_MAX+1] = {
	[CTA_PROTO_SRC_PORT]  = { .type = NLA_U16 },
	[CTA_PROTO_DST_PORT]  = { .type = NLA_U16 },
};
EXPORT_SYMBOL_GPL(nf_ct_port_nla_policy);
EXPORT_SYMBOL(nf_ct_port_nla_policy);

int nf_ct_port_nlattr_to_tuple(struct nlattr *tb[],
			       struct nf_conntrack_tuple *t)
@@ -1330,13 +1342,13 @@ int nf_ct_port_nlattr_to_tuple(struct nlattr *tb[],

	return 0;
}
EXPORT_SYMBOL_GPL(nf_ct_port_nlattr_to_tuple);
EXPORT_SYMBOL(nf_ct_port_nlattr_to_tuple);

int nf_ct_port_nlattr_tuple_size(void)
{
	return nla_policy_len(nf_ct_port_nla_policy, CTA_PROTO_MAX + 1);
}
EXPORT_SYMBOL_GPL(nf_ct_port_nlattr_tuple_size);
EXPORT_SYMBOL(nf_ct_port_nlattr_tuple_size);
#endif

/* Used by ipt_REJECT and ip6t_REJECT. */
@@ -1422,7 +1434,7 @@ void nf_ct_iterate_cleanup(struct net *net,
		nf_ct_put(ct);
	}
}
EXPORT_SYMBOL_GPL(nf_ct_iterate_cleanup);
EXPORT_SYMBOL(nf_ct_iterate_cleanup);

static int kill_all(struct nf_conn *i, void *data)
{
@@ -1437,13 +1449,13 @@ void nf_ct_free_hashtable(void *hash, unsigned int size)
		free_pages((unsigned long)hash,
			   get_order(sizeof(struct hlist_head) * size));
}
EXPORT_SYMBOL_GPL(nf_ct_free_hashtable);
EXPORT_SYMBOL(nf_ct_free_hashtable);

void nf_conntrack_flush_report(struct net *net, u32 portid, int report)
{
	nf_ct_iterate_cleanup(net, kill_all, NULL, portid, report);
}
EXPORT_SYMBOL_GPL(nf_conntrack_flush_report);
EXPORT_SYMBOL(nf_conntrack_flush_report);

static int untrack_refs(void)
{
@@ -1554,7 +1566,7 @@ void *nf_ct_alloc_hashtable(unsigned int *sizep, int nulls)

	return hash;
}
EXPORT_SYMBOL_GPL(nf_ct_alloc_hashtable);
EXPORT_SYMBOL(nf_ct_alloc_hashtable);

int nf_conntrack_set_hashsize(const char *val, struct kernel_param *kp)
{
@@ -1615,7 +1627,7 @@ int nf_conntrack_set_hashsize(const char *val, struct kernel_param *kp)
	nf_ct_free_hashtable(old_hash, old_size);
	return 0;
}
EXPORT_SYMBOL_GPL(nf_conntrack_set_hashsize);
EXPORT_SYMBOL(nf_conntrack_set_hashsize);

module_param_call(hashsize, nf_conntrack_set_hashsize, param_get_uint,
		  &nf_conntrack_htable_size, 0600);
@@ -1627,7 +1639,7 @@ void nf_ct_untracked_status_or(unsigned long bits)
	for_each_possible_cpu(cpu)
		per_cpu(nf_conntrack_untracked, cpu).status |= bits;
}
EXPORT_SYMBOL_GPL(nf_ct_untracked_status_or);
EXPORT_SYMBOL(nf_ct_untracked_status_or);

int nf_conntrack_init_start(void)
{
+383 −23
Original line number Diff line number Diff line
/* 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
@@ -18,13 +19,18 @@
#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>");
@@ -48,10 +54,11 @@ MODULE_PARM_DESC(sip_direct_signalling, "expect incoming calls from registrar "
					"only (default 1)");

const struct nf_nat_sip_hooks *nf_nat_sip_hooks;
EXPORT_SYMBOL_GPL(nf_nat_sip_hooks);
EXPORT_SYMBOL(nf_nat_sip_hooks);
static struct ctl_table_header *sip_sysctl_header;
static unsigned nf_ct_disable_sip_alg;
static int sip_direct_media = 1;
static int packet_count;
static struct ctl_table sip_sysctl_tbl[] = {
	{
		.procname     = "nf_conntrack_disable_sip_alg",
@@ -70,6 +77,224 @@ static struct ctl_table sip_sysctl_tbl[] = {
	{}
};

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 = true;
		} 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(nf_nat_sip_hook);
void (*nf_nat_sip_seq_adjust_hook)(struct sk_buff *skb, unsigned int protoff,
				   s16 off) __read_mostly;
EXPORT_SYMBOL(nf_nat_sip_seq_adjust_hook);
unsigned int (*nf_nat_sip_expect_hook)(struct sk_buff *skb,
				       unsigned int protoff,
				       unsigned int dataoff,
				       const char **dptr,
				       unsigned int *datalen,
				       struct nf_conntrack_expect *exp,
				       unsigned int matchoff,
				       unsigned int matchlen) __read_mostly;
EXPORT_SYMBOL(nf_nat_sip_expect_hook);

unsigned int (*nf_nat_sdp_addr_hook)(struct sk_buff *skb, unsigned int protoff,
				     unsigned int dataoff,
				     const char **dptr,
				     unsigned int *datalen,
				     unsigned int sdpoff,
				     enum sdp_header_types type,
				     enum sdp_header_types term,
				     const union nf_inet_addr *addr)
				     __read_mostly;
EXPORT_SYMBOL(nf_nat_sdp_addr_hook);

unsigned int (*nf_nat_sdp_port_hook)(struct sk_buff *skb, unsigned int protoff,
				     unsigned int dataoff,
				     const char **dptr,
				     unsigned int *datalen,
				     unsigned int matchoff,
				     unsigned int matchlen,
				     u_int16_t port) __read_mostly;
EXPORT_SYMBOL(nf_nat_sdp_port_hook);

unsigned int (*nf_nat_sdp_session_hook)(struct sk_buff *skb,
					unsigned int protoff,
					unsigned int dataoff,
					const char **dptr,
					unsigned int *datalen,
					unsigned int sdpoff,
					const union nf_inet_addr *addr)
					__read_mostly;
EXPORT_SYMBOL(nf_nat_sdp_session_hook);

unsigned int (*nf_nat_sdp_media_hook)(struct sk_buff *skb, unsigned int protoff,
				      unsigned int dataoff,
				      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)
				      __read_mostly;
EXPORT_SYMBOL(nf_nat_sdp_media_hook);

static int string_len(const struct nf_conn *ct, const char *dptr,
		      const char *limit, int *shift)
{
@@ -82,6 +307,26 @@ 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(*node),
			GFP_ATOMIC | __GFP_NOWARN);
	if (!node)
		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)
{
@@ -288,7 +533,7 @@ int ct_sip_parse_request(const struct nf_conn *ct,
	*matchlen = end - dptr;
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_request);
EXPORT_SYMBOL(ct_sip_parse_request);

/* 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
@@ -436,7 +681,7 @@ int ct_sip_get_header(const struct nf_conn *ct, const char *dptr,
	}
	return 0;
}
EXPORT_SYMBOL_GPL(ct_sip_get_header);
EXPORT_SYMBOL(ct_sip_get_header);

/* Get next header field in a list of comma separated values */
static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr,
@@ -540,7 +785,7 @@ int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr,
		*dataoff = c - dptr;
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);
EXPORT_SYMBOL(ct_sip_parse_header_uri);

static int ct_sip_parse_param(const struct nf_conn *ct, const char *dptr,
			      unsigned int dataoff, unsigned int datalen,
@@ -594,7 +839,7 @@ int ct_sip_parse_address_param(const struct nf_conn *ct, const char *dptr,
	*matchlen = end - start;
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_address_param);
EXPORT_SYMBOL(ct_sip_parse_address_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,
@@ -625,7 +870,7 @@ int ct_sip_parse_numerical_param(const struct nf_conn *ct, const char *dptr,
	}
	return 1;
}
EXPORT_SYMBOL_GPL(ct_sip_parse_numerical_param);
EXPORT_SYMBOL(ct_sip_parse_numerical_param);

static int ct_sip_parse_transport(struct nf_conn *ct, const char *dptr,
				  unsigned int dataoff, unsigned int datalen,
@@ -783,7 +1028,7 @@ int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
	}
	return 0;
}
EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);
EXPORT_SYMBOL(ct_sip_get_sdp_header);

static int ct_sip_parse_sdp_addr(const struct nf_conn *ct, const char *dptr,
				 unsigned int dataoff, unsigned int datalen,
@@ -1500,13 +1745,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;

	packet_count++;
	pr_debug("packet count %d\n", packet_count);

	if (nf_ct_disable_sip_alg)
		return NF_ACCEPT;

	if (ctinfo != IP_CT_ESTABLISHED &&
	    ctinfo != IP_CT_ESTABLISHED_REPLY)
@@ -1530,15 +1790,25 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
	if (datalen < strlen("SIP/2.0 200"))
		return NF_ACCEPT;

	/* here we save the original datalength and data offset of the skb, this
	 * is needed later to split combined skbs
	 */
	oldlen1 = skb->len - protoff;
	dataoff_orig = dataoff;

	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++) {
@@ -1548,27 +1818,114 @@ 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,
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;

		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) {
		const struct nf_nat_sip_hooks *hooks;

@@ -1700,6 +2057,9 @@ static int __init nf_conntrack_sip_init(void)
			}
		}
	}

	nf_register_queue_handler(&nf_sip_qh);

	return 0;
}