| #include <linux/kernel.h> | 
 | #include <linux/init.h> | 
 | #include <linux/module.h> | 
 | #include <linux/skbuff.h> | 
 | #include <linux/netfilter.h> | 
 | #include <linux/mutex.h> | 
 | #include <net/sock.h> | 
 |  | 
 | #include "nf_internals.h" | 
 |  | 
 | /* Sockopts only registered and called from user context, so | 
 |    net locking would be overkill.  Also, [gs]etsockopt calls may | 
 |    sleep. */ | 
 | static DEFINE_MUTEX(nf_sockopt_mutex); | 
 | static LIST_HEAD(nf_sockopts); | 
 |  | 
 | /* Do exclusive ranges overlap? */ | 
 | static inline int overlap(int min1, int max1, int min2, int max2) | 
 | { | 
 | 	return max1 > min2 && min1 < max2; | 
 | } | 
 |  | 
 | /* Functions to register sockopt ranges (exclusive). */ | 
 | int nf_register_sockopt(struct nf_sockopt_ops *reg) | 
 | { | 
 | 	struct nf_sockopt_ops *ops; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0) | 
 | 		return -EINTR; | 
 |  | 
 | 	list_for_each_entry(ops, &nf_sockopts, list) { | 
 | 		if (ops->pf == reg->pf | 
 | 		    && (overlap(ops->set_optmin, ops->set_optmax, | 
 | 				reg->set_optmin, reg->set_optmax) | 
 | 			|| overlap(ops->get_optmin, ops->get_optmax, | 
 | 				   reg->get_optmin, reg->get_optmax))) { | 
 | 			NFDEBUG("nf_sock overlap: %u-%u/%u-%u v %u-%u/%u-%u\n", | 
 | 				ops->set_optmin, ops->set_optmax, | 
 | 				ops->get_optmin, ops->get_optmax, | 
 | 				reg->set_optmin, reg->set_optmax, | 
 | 				reg->get_optmin, reg->get_optmax); | 
 | 			ret = -EBUSY; | 
 | 			goto out; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	list_add(®->list, &nf_sockopts); | 
 | out: | 
 | 	mutex_unlock(&nf_sockopt_mutex); | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(nf_register_sockopt); | 
 |  | 
 | void nf_unregister_sockopt(struct nf_sockopt_ops *reg) | 
 | { | 
 | 	mutex_lock(&nf_sockopt_mutex); | 
 | 	list_del(®->list); | 
 | 	mutex_unlock(&nf_sockopt_mutex); | 
 | } | 
 | EXPORT_SYMBOL(nf_unregister_sockopt); | 
 |  | 
 | static struct nf_sockopt_ops *nf_sockopt_find(struct sock *sk, int pf, | 
 | 		int val, int get) | 
 | { | 
 | 	struct nf_sockopt_ops *ops; | 
 |  | 
 | 	if (!net_eq(sock_net(sk), &init_net)) | 
 | 		return ERR_PTR(-ENOPROTOOPT); | 
 |  | 
 | 	if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0) | 
 | 		return ERR_PTR(-EINTR); | 
 |  | 
 | 	list_for_each_entry(ops, &nf_sockopts, list) { | 
 | 		if (ops->pf == pf) { | 
 | 			if (!try_module_get(ops->owner)) | 
 | 				goto out_nosup; | 
 |  | 
 | 			if (get) { | 
 | 				if (val >= ops->get_optmin && | 
 | 						val < ops->get_optmax) | 
 | 					goto out; | 
 | 			} else { | 
 | 				if (val >= ops->set_optmin && | 
 | 						val < ops->set_optmax) | 
 | 					goto out; | 
 | 			} | 
 | 			module_put(ops->owner); | 
 | 		} | 
 | 	} | 
 | out_nosup: | 
 | 	ops = ERR_PTR(-ENOPROTOOPT); | 
 | out: | 
 | 	mutex_unlock(&nf_sockopt_mutex); | 
 | 	return ops; | 
 | } | 
 |  | 
 | /* Call get/setsockopt() */ | 
 | static int nf_sockopt(struct sock *sk, int pf, int val, | 
 | 		      char __user *opt, int *len, int get) | 
 | { | 
 | 	struct nf_sockopt_ops *ops; | 
 | 	int ret; | 
 |  | 
 | 	ops = nf_sockopt_find(sk, pf, val, get); | 
 | 	if (IS_ERR(ops)) | 
 | 		return PTR_ERR(ops); | 
 |  | 
 | 	if (get) | 
 | 		ret = ops->get(sk, val, opt, len); | 
 | 	else | 
 | 		ret = ops->set(sk, val, opt, *len); | 
 |  | 
 | 	module_put(ops->owner); | 
 | 	return ret; | 
 | } | 
 |  | 
 | int nf_setsockopt(struct sock *sk, int pf, int val, char __user *opt, | 
 | 		  int len) | 
 | { | 
 | 	return nf_sockopt(sk, pf, val, opt, &len, 0); | 
 | } | 
 | EXPORT_SYMBOL(nf_setsockopt); | 
 |  | 
 | int nf_getsockopt(struct sock *sk, int pf, int val, char __user *opt, int *len) | 
 | { | 
 | 	return nf_sockopt(sk, pf, val, opt, len, 1); | 
 | } | 
 | EXPORT_SYMBOL(nf_getsockopt); | 
 |  | 
 | #ifdef CONFIG_COMPAT | 
 | static int compat_nf_sockopt(struct sock *sk, int pf, int val, | 
 | 			     char __user *opt, int *len, int get) | 
 | { | 
 | 	struct nf_sockopt_ops *ops; | 
 | 	int ret; | 
 |  | 
 | 	ops = nf_sockopt_find(sk, pf, val, get); | 
 | 	if (IS_ERR(ops)) | 
 | 		return PTR_ERR(ops); | 
 |  | 
 | 	if (get) { | 
 | 		if (ops->compat_get) | 
 | 			ret = ops->compat_get(sk, val, opt, len); | 
 | 		else | 
 | 			ret = ops->get(sk, val, opt, len); | 
 | 	} else { | 
 | 		if (ops->compat_set) | 
 | 			ret = ops->compat_set(sk, val, opt, *len); | 
 | 		else | 
 | 			ret = ops->set(sk, val, opt, *len); | 
 | 	} | 
 |  | 
 | 	module_put(ops->owner); | 
 | 	return ret; | 
 | } | 
 |  | 
 | int compat_nf_setsockopt(struct sock *sk, int pf, | 
 | 		int val, char __user *opt, int len) | 
 | { | 
 | 	return compat_nf_sockopt(sk, pf, val, opt, &len, 0); | 
 | } | 
 | EXPORT_SYMBOL(compat_nf_setsockopt); | 
 |  | 
 | int compat_nf_getsockopt(struct sock *sk, int pf, | 
 | 		int val, char __user *opt, int *len) | 
 | { | 
 | 	return compat_nf_sockopt(sk, pf, val, opt, len, 1); | 
 | } | 
 | EXPORT_SYMBOL(compat_nf_getsockopt); | 
 | #endif |