Loading include/linux/netfilter/nf_conntrack_sip.h +5 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,11 @@ struct nf_nat_sip_hooks { }; extern const struct nf_nat_sip_hooks *nf_nat_sip_hooks; extern void (*nf_nat_sip_seq_adjust_hook) (struct sk_buff *skb, unsigned int protoff, s16 off); 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, Loading include/net/netfilter/nf_conntrack.h +14 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <linux/compiler.h> #include <linux/atomic.h> #include <linux/rhashtable.h> #include <linux/list.h> #include <linux/netfilter/nf_conntrack_tcp.h> #include <linux/netfilter/nf_conntrack_dccp.h> Loading @@ -27,6 +28,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 */ Loading Loading @@ -130,6 +139,11 @@ struct nf_conn { #ifdef CONFIG_IP_NF_TARGET_NATTYPE_MODULE 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; Loading include/net/netfilter/nf_conntrack_core.h +8 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ /* This header is used to share core functionality between the 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 net *net, u_int8_t pf, unsigned int hooknum, struct sk_buff *skb); Loading Loading @@ -88,4 +91,9 @@ void nf_conntrack_lock(spinlock_t *lock); extern spinlock_t nf_conntrack_expect_lock; struct sip_list { struct nf_queue_entry *entry; struct list_head list; }; #endif /* _NF_CONNTRACK_CORE_H */ net/netfilter/nf_conntrack_core.c +15 −1 Original line number Diff line number Diff line Loading @@ -188,6 +188,7 @@ unsigned int nf_conntrack_htable_size __read_mostly; EXPORT_SYMBOL_GPL(nf_conntrack_htable_size); unsigned int nf_conntrack_max __read_mostly; seqcount_t nf_conntrack_generation __read_mostly; unsigned int nf_conntrack_pkt_threshold __read_mostly; Loading @@ -196,7 +197,8 @@ EXPORT_SYMBOL(nf_conntrack_pkt_threshold); DEFINE_PER_CPU(struct nf_conn, nf_conntrack_untracked); EXPORT_PER_CPU_SYMBOL(nf_conntrack_untracked); 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) Loading Loading @@ -399,6 +401,9 @@ destroy_conntrack(struct nf_conntrack *nfct) struct nf_conn *ct = (struct nf_conn *)nfct; struct nf_conntrack_l4proto *l4proto; void (*delete_entry)(struct nf_conn *ct); 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); Loading Loading @@ -426,6 +431,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, Loading Loading @@ -1200,6 +1213,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); Loading net/netfilter/nf_conntrack_sip.c +461 −16 Original line number Diff line number Diff line /* SIP extension for IP connection tracking. * * Copyright (c) 2015,2017, 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 Loading @@ -20,13 +21,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>"); Loading Loading @@ -54,6 +60,12 @@ 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; 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); static struct ctl_table sip_sysctl_tbl[] = { { .procname = "nf_conntrack_disable_sip_alg", Loading @@ -69,9 +81,289 @@ static struct ctl_table sip_sysctl_tbl[] = { .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "nf_conntrack_enable_sip_segmentation", .data = &nf_ct_enable_sip_segmentation, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_sip_segment, }, {} }; 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; } 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; 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); } 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); static int string_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { Loading @@ -84,6 +376,43 @@ 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 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; } static int digits_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { Loading Loading @@ -1505,13 +1834,29 @@ 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; bool content_len_exists = 1; 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) Loading @@ -1535,11 +1880,26 @@ 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; if (!ct) return NF_DROP; while (1) { if (ct_sip_get_header(ct, dptr, 0, datalen, SIP_HDR_CONTENT_LENGTH, &matchoff, &matchlen) <= 0) &matchoff, &matchlen) <= 0){ if (nf_ct_enable_sip_segmentation) { do_not_process = true; content_len_exists = 0; goto destination; } else { break; } } clen = simple_strtoul(dptr + matchoff, (char **)&end, 10); if (dptr + matchoff == end) Loading @@ -1555,26 +1915,111 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, } if (!term) break; end += strlen("\r\n\r\n") + clen; destination: msglen = origlen = end - dptr; if (msglen > datalen) if (content_len_exists == 0) { origlen = datalen; msglen = origlen; } else { origlen = end - dptr; msglen = origlen; } pr_debug("mslgen %d datalen %d\n", msglen, datalen); dir = CTINFO2DIR(ctinfo); combined_skb = skb; if (nf_ct_enable_sip_segmentation) { /* 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); 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; } } else if (msglen > datalen) { return NF_ACCEPT; ret = process_sip_msg(skb, ct, protoff, dataoff, } /* 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; sip_calculate_parameters(&diff, &tdiff, &dataoff, &dptr, &datalen, msglen, origlen); if (nf_ct_enable_sip_segmentation && skb_is_combined) 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 */ 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; } if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) { here: if (ret == NF_ACCEPT && ct && ct->status & IPS_NAT_MASK) { const struct nf_nat_sip_hooks *hooks; hooks = rcu_dereference(nf_nat_sip_hooks); Loading Loading
include/linux/netfilter/nf_conntrack_sip.h +5 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,11 @@ struct nf_nat_sip_hooks { }; extern const struct nf_nat_sip_hooks *nf_nat_sip_hooks; extern void (*nf_nat_sip_seq_adjust_hook) (struct sk_buff *skb, unsigned int protoff, s16 off); 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, Loading
include/net/netfilter/nf_conntrack.h +14 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #include <linux/compiler.h> #include <linux/atomic.h> #include <linux/rhashtable.h> #include <linux/list.h> #include <linux/netfilter/nf_conntrack_tcp.h> #include <linux/netfilter/nf_conntrack_dccp.h> Loading @@ -27,6 +28,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 */ Loading Loading @@ -130,6 +139,11 @@ struct nf_conn { #ifdef CONFIG_IP_NF_TARGET_NATTYPE_MODULE 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; Loading
include/net/netfilter/nf_conntrack_core.h +8 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,9 @@ /* This header is used to share core functionality between the 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 net *net, u_int8_t pf, unsigned int hooknum, struct sk_buff *skb); Loading Loading @@ -88,4 +91,9 @@ void nf_conntrack_lock(spinlock_t *lock); extern spinlock_t nf_conntrack_expect_lock; struct sip_list { struct nf_queue_entry *entry; struct list_head list; }; #endif /* _NF_CONNTRACK_CORE_H */
net/netfilter/nf_conntrack_core.c +15 −1 Original line number Diff line number Diff line Loading @@ -188,6 +188,7 @@ unsigned int nf_conntrack_htable_size __read_mostly; EXPORT_SYMBOL_GPL(nf_conntrack_htable_size); unsigned int nf_conntrack_max __read_mostly; seqcount_t nf_conntrack_generation __read_mostly; unsigned int nf_conntrack_pkt_threshold __read_mostly; Loading @@ -196,7 +197,8 @@ EXPORT_SYMBOL(nf_conntrack_pkt_threshold); DEFINE_PER_CPU(struct nf_conn, nf_conntrack_untracked); EXPORT_PER_CPU_SYMBOL(nf_conntrack_untracked); 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) Loading Loading @@ -399,6 +401,9 @@ destroy_conntrack(struct nf_conntrack *nfct) struct nf_conn *ct = (struct nf_conn *)nfct; struct nf_conntrack_l4proto *l4proto; void (*delete_entry)(struct nf_conn *ct); 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); Loading Loading @@ -426,6 +431,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, Loading Loading @@ -1200,6 +1213,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); Loading
net/netfilter/nf_conntrack_sip.c +461 −16 Original line number Diff line number Diff line /* SIP extension for IP connection tracking. * * Copyright (c) 2015,2017, 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 Loading @@ -20,13 +21,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>"); Loading Loading @@ -54,6 +60,12 @@ 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; 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); static struct ctl_table sip_sysctl_tbl[] = { { .procname = "nf_conntrack_disable_sip_alg", Loading @@ -69,9 +81,289 @@ static struct ctl_table sip_sysctl_tbl[] = { .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "nf_conntrack_enable_sip_segmentation", .data = &nf_ct_enable_sip_segmentation, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_sip_segment, }, {} }; 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; } 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; 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); } 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); static int string_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { Loading @@ -84,6 +376,43 @@ 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 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; } static int digits_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { Loading Loading @@ -1505,13 +1834,29 @@ 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; bool content_len_exists = 1; 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) Loading @@ -1535,11 +1880,26 @@ 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; if (!ct) return NF_DROP; while (1) { if (ct_sip_get_header(ct, dptr, 0, datalen, SIP_HDR_CONTENT_LENGTH, &matchoff, &matchlen) <= 0) &matchoff, &matchlen) <= 0){ if (nf_ct_enable_sip_segmentation) { do_not_process = true; content_len_exists = 0; goto destination; } else { break; } } clen = simple_strtoul(dptr + matchoff, (char **)&end, 10); if (dptr + matchoff == end) Loading @@ -1555,26 +1915,111 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff, } if (!term) break; end += strlen("\r\n\r\n") + clen; destination: msglen = origlen = end - dptr; if (msglen > datalen) if (content_len_exists == 0) { origlen = datalen; msglen = origlen; } else { origlen = end - dptr; msglen = origlen; } pr_debug("mslgen %d datalen %d\n", msglen, datalen); dir = CTINFO2DIR(ctinfo); combined_skb = skb; if (nf_ct_enable_sip_segmentation) { /* 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); 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; } } else if (msglen > datalen) { return NF_ACCEPT; ret = process_sip_msg(skb, ct, protoff, dataoff, } /* 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; sip_calculate_parameters(&diff, &tdiff, &dataoff, &dptr, &datalen, msglen, origlen); if (nf_ct_enable_sip_segmentation && skb_is_combined) 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 */ 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; } if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK) { here: if (ret == NF_ACCEPT && ct && ct->status & IPS_NAT_MASK) { const struct nf_nat_sip_hooks *hooks; hooks = rcu_dereference(nf_nat_sip_hooks); Loading