From d30948cecd07d007ab2a3a0f7460b53255883208 Mon Sep 17 00:00:00 2001 From: Paras Singh Jain Date: Mon, 20 Nov 2017 20:15:46 +0530 Subject: [PATCH] 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. Also,This change handle porting of below changes c39f2db28d61ec4 ("Additional fixes for SIP Segmentation Support") 07c07c942ec37f0 ("Changes to handle MT call issue in SIP ALG") 18f40547250d8b6 ("Enable/Disable SIP Segmentation Support"). Change-Id: I8c77322f69cf4d9ad4c7b4971da924ffd585dea0 Acked-by: Vinisha Varre Signed-off-by: Paras Singh Jain --- include/linux/netfilter/nf_conntrack_sip.h | 7 + include/net/netfilter/nf_conntrack.h | 22 + include/net/netfilter/nf_conntrack_core.h | 9 + net/netfilter/Kconfig | 12 + net/netfilter/nf_conntrack_core.c | 21 +- net/netfilter/nf_conntrack_sip.c | 521 ++++++++++++++++++++- 6 files changed, 570 insertions(+), 22 deletions(-) diff --git a/include/linux/netfilter/nf_conntrack_sip.h b/include/linux/netfilter/nf_conntrack_sip.h index c620521c42bc..4e9bf9905a88 100644 --- a/include/linux/netfilter/nf_conntrack_sip.h +++ b/include/linux/netfilter/nf_conntrack_sip.h @@ -166,6 +166,13 @@ struct nf_nat_sip_hooks { }; extern const struct nf_nat_sip_hooks *nf_nat_sip_hooks; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +extern void (*nf_nat_sip_seq_adjust_hook) + (struct sk_buff *skb, + unsigned int protoff, + s16 off); +#endif + 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, diff --git a/include/net/netfilter/nf_conntrack.h b/include/net/netfilter/nf_conntrack.h index ad3183d06878..452db7dfb406 100644 --- a/include/net/netfilter/nf_conntrack.h +++ b/include/net/netfilter/nf_conntrack.h @@ -17,6 +17,9 @@ #include #include #include +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +#include +#endif #include #include @@ -26,10 +29,22 @@ #include +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +#define SIP_LIST_ELEMENTS 2 +#endif + struct nf_ct_udp { unsigned long stream_ts; }; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +struct sip_length { + int msg_length[SIP_LIST_ELEMENTS]; + int skb_len[SIP_LIST_ELEMENTS]; + int data_len[SIP_LIST_ELEMENTS]; +}; +#endif + /* per conntrack: protocol private data */ union nf_conntrack_proto { /* insert conntrack proto private data here */ @@ -117,6 +132,13 @@ struct nf_conn { #ifdef CONFIG_ENABLE_SFE void *sfe_entry; #endif +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + struct list_head sip_segment_list; + const char *dptr_prev; + struct sip_length segment; + bool sip_original_dir; + bool sip_reply_dir; +#endif /* Storage reserved for other modules, must be the last member */ union nf_conntrack_proto proto; diff --git a/include/net/netfilter/nf_conntrack_core.h b/include/net/netfilter/nf_conntrack_core.h index 7787b4774414..e799bea9dab7 100644 --- a/include/net/netfilter/nf_conntrack_core.h +++ b/include/net/netfilter/nf_conntrack_core.h @@ -22,6 +22,8 @@ standalone connection tracking module, and the compatibility layer's use of connection tracking. */ +extern unsigned int nf_conntrack_hash_rnd; + unsigned int nf_conntrack_in(struct sk_buff *skb, const struct nf_hook_state *state); @@ -90,4 +92,11 @@ void nf_conntrack_lock(spinlock_t *lock); extern spinlock_t nf_conntrack_expect_lock; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +struct sip_list { + struct nf_queue_entry *entry; + struct list_head list; +}; +#endif + #endif /* _NF_CONNTRACK_CORE_H */ diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index ea957f78a5a9..98922e00dd2c 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -342,6 +342,18 @@ config NF_CONNTRACK_SIP To compile it as a module, choose M here. If unsure, say N. +config NF_CONNTRACK_SIP_SEGMENTATION + tristate "SIP protocol segmentation support" + depends on NF_CONNTRACK_SIP + default m if NETFILTER_ADVANCED=n + help + Linux Kernel SIP ALG did not handle Segmented TCP Packets + because of which SIP communication could not be established + for some clients. This special type supports SIP segmentation + packets + + To compile it as a module, choose M here. If unsure, say N. + config NF_CONNTRACK_TFTP tristate "TFTP protocol support" depends on NETFILTER_ADVANCED diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index b8964418e66f..4f18c0e1e415 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -192,7 +192,8 @@ unsigned int nf_conntrack_pkt_threshold __read_mostly; EXPORT_SYMBOL(nf_conntrack_pkt_threshold); #endif -static unsigned int nf_conntrack_hash_rnd __read_mostly; +unsigned int nf_conntrack_hash_rnd __read_mostly; +EXPORT_SYMBOL(nf_conntrack_hash_rnd); static u32 hash_conntrack_raw(const struct nf_conntrack_tuple *tuple, const struct net *net) @@ -617,6 +618,11 @@ destroy_conntrack(struct nf_conntrack *nfct) #ifdef CONFIG_ENABLE_SFE void (*delete_entry)(struct nf_conn *ct); #endif +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + struct sip_list *sip_node = NULL; + struct list_head *sip_node_list; + struct list_head *sip_node_save_list; +#endif pr_debug("destroy_conntrack(%p)\n", ct); WARN_ON(atomic_read(&nfct->use) != 0); @@ -638,6 +644,16 @@ destroy_conntrack(struct nf_conntrack *nfct) #endif local_bh_disable(); + +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + 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); + } +#endif /* 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, @@ -1510,6 +1526,9 @@ init_conntrack(struct net *net, struct nf_conn *tmpl, GFP_ATOMIC); local_bh_disable(); +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + INIT_LIST_HEAD(&ct->sip_segment_list); +#endif if (net->ct.expect_count) { spin_lock(&nf_conntrack_expect_lock); exp = nf_ct_find_expectation(net, zone, tuple); diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index d8b842974a2f..21fa55704902 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only /* SIP extension for IP connection tracking. * + * Copyright (c) 2015,2017,2018 The Linux Foundation. All rights reserved. * (C) 2005 by Christian Hentschel * based on RR's ip_conntrack_ftp.c and other modules. * (C) 2007 United Security Providers @@ -20,12 +21,19 @@ #include #include +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +#include +#endif #include #include #include #include #include #include +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +#include +#include +#endif #define HELPER_NAME "sip" @@ -60,6 +68,14 @@ EXPORT_SYMBOL_GPL(nf_nat_sip_hooks); static struct ctl_table_header *sip_sysctl_header; static unsigned int nf_ct_disable_sip_alg; static int sip_direct_media = 1; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +static unsigned int nf_ct_enable_sip_segmentation; +static int packet_count; +static +int proc_sip_segment(struct ctl_table *ctl, int write, + void __user *buffer, size_t *lenp, loff_t *ppos); +#endif + static struct ctl_table sip_sysctl_tbl[] = { { .procname = "nf_conntrack_disable_sip_alg", @@ -75,9 +91,289 @@ static struct ctl_table sip_sysctl_tbl[] = { .mode = 0644, .proc_handler = proc_dointvec, }, +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + { + .procname = "nf_conntrack_enable_sip_segmentation", + .data = &nf_ct_enable_sip_segmentation, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = proc_sip_segment, + }, +#endif {} }; +static unsigned int (*nf_nat_sip_hook) + (struct sk_buff *skb, + unsigned int protoff, + unsigned int dataoff, + const char **dptr, + unsigned int *datalen) + __read_mostly; +EXPORT_SYMBOL(nf_nat_sip_hook); +static void sip_calculate_parameters(s16 *diff, s16 *tdiff, + unsigned int *dataoff, const char **dptr, + unsigned int *datalen, + unsigned int msglen, unsigned int origlen) +{ + *diff = msglen - origlen; + *tdiff += *diff; + *dataoff += msglen; + *dptr += msglen; + *datalen = *datalen + *diff - msglen; +} + +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +static void sip_update_params(enum ip_conntrack_dir dir, + unsigned int *msglen, unsigned int *origlen, + const char **dptr, unsigned int *datalen, + bool skb_is_combined, struct nf_conn *ct) +{ + 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; + } +} + +/* 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 + */ + 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 { + 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)) { + pr_debug(" Combining segments\n"); + *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; + + /* 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); + nf_nat_csum_recalc(skb, nf_ct_l3num((struct nf_conn *)skb->_nfct), + IPPROTO_TCP, tcph, &tcph->check, datalen, oldlen); +} + +void (*nf_nat_sip_seq_adjust_hook) + (struct sk_buff *skb, + unsigned int protoff, + s16 off); + +static 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); + +static 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); + +static 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); + +static 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); + +static 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); +#endif + static int string_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { @@ -90,6 +386,45 @@ static int string_len(const struct nf_conn *ct, const char *dptr, return len; } +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +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 proc_sip_segment(struct ctl_table *ctl, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int ret; + + ret = proc_dointvec(ctl, write, buffer, lenp, ppos); + if (nf_ct_enable_sip_segmentation) { + pr_debug("registering queue handler\n"); + nf_register_queue_handler(&init_net, &nf_sip_qh); + } else { + pr_debug("de-registering queue handler\n"); + nf_unregister_queue_handler(&init_net); + } + return ret; +} +#endif + static int digits_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { @@ -1541,23 +1876,103 @@ static int process_sip_msg(struct sk_buff *skb, struct nf_conn *ct, return ret; } +static void sip_tcp_skip_process(int ret, struct nf_conn *ct, struct sk_buff *skb, + unsigned int protoff, s16 *tdiff) +{ + if (ret == NF_ACCEPT && ct && ct->status & IPS_NAT_MASK) { + const struct nf_nat_sip_hooks *hooks; + + hooks = rcu_dereference(nf_nat_sip_hooks); + if (hooks) + hooks->seq_adjust(skb, protoff, tdiff); + } +} + +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +static void sip_tcp_skb_combined_processing(bool skb_is_combined, struct sk_buff *skb, + enum ip_conntrack_dir dir, struct sk_buff *combined_skb, + struct nf_conn *ct, unsigned int protoff, + struct sip_list *sip_entry, s16 tdiff, + unsigned int dataoff_orig) +{ + if (skb_is_combined) { + unsigned int splitlen, oldlen, oldlen1; + /* 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; + oldlen1 = 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 */ + if (sip_entry) { + 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; + } +} +#endif + 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; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + 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; + bool content_len_exists = true; - if (ctinfo != IP_CT_ESTABLISHED && - ctinfo != IP_CT_ESTABLISHED_REPLY) + packet_count++; + pr_debug("packet count %d\n", packet_count); + + if (nf_ct_disable_sip_alg) + return NF_ACCEPT; +#endif + + if (ctinfo != IP_CT_ESTABLISHED && ctinfo != IP_CT_ESTABLISHED_REPLY) return NF_ACCEPT; - /* No Data ? */ th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph); if (th == NULL) return NF_ACCEPT; @@ -1574,12 +1989,30 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, datalen = skb->len - dataoff; if (datalen < strlen("SIP/2.0 200")) return NF_ACCEPT; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + /* 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; + if (!ct) + return NF_DROP; +#endif while (1) { if (ct_sip_get_header(ct, dptr, 0, datalen, SIP_HDR_CONTENT_LENGTH, - &matchoff, &matchlen) <= 0) + &matchoff, &matchlen) <= 0){ +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + if (!nf_ct_enable_sip_segmentation) + break; + do_not_process = true; + content_len_exists = false; + goto destination; +#else break; +#endif + } clen = simple_strtoul(dptr + matchoff, (char **)&end, 10); if (dptr + matchoff == end) @@ -1595,32 +2028,78 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, } if (!term) break; - end += strlen("\r\n\r\n") + clen; + end += strlen("\r\n\r\n") + clen; +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION +destination: + + origlen = datalen; + if (content_len_exists) + origlen = end - dptr; + msglen = origlen; + pr_debug("msglen %d datalen %d\n", msglen, datalen); + dir = CTINFO2DIR(ctinfo); + combined_skb = skb; + if (msglen > datalen) { + if (!nf_ct_enable_sip_segmentation) + return NF_ACCEPT; + /* Segmented Packet */ + skip = sip_save_segment_info(ct, skb, msglen, + datalen, dptr, ctinfo); + if (!skip) + return NF_QUEUE; + } + if (nf_ct_enable_sip_segmentation) { + /* Traverse list to find prev segment */ + /* Traverse the list if list non empty */ + /* Combine segments if they are fragments of + * the same message. + */ + if (ct->sip_segment_list.next != &ct->sip_segment_list) { + sip_entry = sip_coalesce_segments(ct, &skb, + dataoff, + &combined_skb, + &skip_sip_process, + do_not_process, + ctinfo, + &skb_is_combined); + sip_update_params(dir, &msglen, &origlen, &dptr, + &datalen, + skb_is_combined, ct); + + 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); +#else msglen = origlen = end - dptr; if (msglen > datalen) return NF_ACCEPT; - ret = process_sip_msg(skb, ct, protoff, dataoff, &dptr, &msglen); +#endif /* 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; + sip_calculate_parameters(&diff, &tdiff, &dataoff, &dptr, + &datalen, msglen, origlen); +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + if (nf_ct_enable_sip_segmentation && skb_is_combined) + break; +#endif } +#ifdef CONFIG_NF_CONNTRACK_SIP_SEGMENTATION + sip_tcp_skb_combined_processing(skb_is_combined, skb, dir, combined_skb, + ct, protoff, sip_entry, tdiff, dataoff_orig); - if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) { - const struct nf_nat_sip_hooks *hooks; - - hooks = rcu_dereference(nf_nat_sip_hooks); - if (hooks) - hooks->seq_adjust(skb, protoff, tdiff); - } +here: +#endif + sip_tcp_skip_process(ret, ct, skb, protoff, tdiff); return ret; }