| #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 (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 |