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

Commit 38e410d9 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "netfilter: Changes to handle segmentation in SIP ALG"

parents 0fb96303 f4530efe
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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,
+14 −0
Original line number Diff line number Diff line
@@ -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>
@@ -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 */
@@ -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;
+8 −0
Original line number Diff line number Diff line
@@ -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);

@@ -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 */
+15 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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)
@@ -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);
@@ -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,
@@ -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);
+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
@@ -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>");
@@ -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",
@@ -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)
{
@@ -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)
{
@@ -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)
@@ -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)
@@ -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);