Merge branch 'merge' of git://git.kernel.org/pub/scm/linux/kernel/git/paulus/powerpc
[linux-2.6] / net / netfilter / nf_sockopt.c
1 #include <linux/kernel.h>
2 #include <linux/init.h>
3 #include <linux/module.h>
4 #include <linux/skbuff.h>
5 #include <linux/netfilter.h>
6 #include <linux/mutex.h>
7 #include <net/sock.h>
8
9 #include "nf_internals.h"
10
11 /* Sockopts only registered and called from user context, so
12    net locking would be overkill.  Also, [gs]etsockopt calls may
13    sleep. */
14 static DEFINE_MUTEX(nf_sockopt_mutex);
15 static LIST_HEAD(nf_sockopts);
16
17 /* Do exclusive ranges overlap? */
18 static inline int overlap(int min1, int max1, int min2, int max2)
19 {
20         return max1 > min2 && min1 < max2;
21 }
22
23 /* Functions to register sockopt ranges (exclusive). */
24 int nf_register_sockopt(struct nf_sockopt_ops *reg)
25 {
26         struct nf_sockopt_ops *ops;
27         int ret = 0;
28
29         if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
30                 return -EINTR;
31
32         list_for_each_entry(ops, &nf_sockopts, list) {
33                 if (ops->pf == reg->pf
34                     && (overlap(ops->set_optmin, ops->set_optmax,
35                                 reg->set_optmin, reg->set_optmax)
36                         || overlap(ops->get_optmin, ops->get_optmax,
37                                    reg->get_optmin, reg->get_optmax))) {
38                         NFDEBUG("nf_sock overlap: %u-%u/%u-%u v %u-%u/%u-%u\n",
39                                 ops->set_optmin, ops->set_optmax,
40                                 ops->get_optmin, ops->get_optmax,
41                                 reg->set_optmin, reg->set_optmax,
42                                 reg->get_optmin, reg->get_optmax);
43                         ret = -EBUSY;
44                         goto out;
45                 }
46         }
47
48         list_add(&reg->list, &nf_sockopts);
49 out:
50         mutex_unlock(&nf_sockopt_mutex);
51         return ret;
52 }
53 EXPORT_SYMBOL(nf_register_sockopt);
54
55 void nf_unregister_sockopt(struct nf_sockopt_ops *reg)
56 {
57         mutex_lock(&nf_sockopt_mutex);
58         list_del(&reg->list);
59         mutex_unlock(&nf_sockopt_mutex);
60 }
61 EXPORT_SYMBOL(nf_unregister_sockopt);
62
63 /* Call get/setsockopt() */
64 static int nf_sockopt(struct sock *sk, int pf, int val,
65                       char __user *opt, int *len, int get)
66 {
67         struct nf_sockopt_ops *ops;
68         int ret;
69
70         if (sk->sk_net != &init_net)
71                 return -ENOPROTOOPT;
72
73         if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
74                 return -EINTR;
75
76         list_for_each_entry(ops, &nf_sockopts, list) {
77                 if (ops->pf == pf) {
78                         if (!try_module_get(ops->owner))
79                                 goto out_nosup;
80                         if (get) {
81                                 if (val >= ops->get_optmin
82                                     && val < ops->get_optmax) {
83                                         mutex_unlock(&nf_sockopt_mutex);
84                                         ret = ops->get(sk, val, opt, len);
85                                         goto out;
86                                 }
87                         } else {
88                                 if (val >= ops->set_optmin
89                                     && val < ops->set_optmax) {
90                                         mutex_unlock(&nf_sockopt_mutex);
91                                         ret = ops->set(sk, val, opt, *len);
92                                         goto out;
93                                 }
94                         }
95                         module_put(ops->owner);
96                 }
97         }
98  out_nosup:
99         mutex_unlock(&nf_sockopt_mutex);
100         return -ENOPROTOOPT;
101
102  out:
103         module_put(ops->owner);
104         return ret;
105 }
106
107 int nf_setsockopt(struct sock *sk, int pf, int val, char __user *opt,
108                   int len)
109 {
110         return nf_sockopt(sk, pf, val, opt, &len, 0);
111 }
112 EXPORT_SYMBOL(nf_setsockopt);
113
114 int nf_getsockopt(struct sock *sk, int pf, int val, char __user *opt, int *len)
115 {
116         return nf_sockopt(sk, pf, val, opt, len, 1);
117 }
118 EXPORT_SYMBOL(nf_getsockopt);
119
120 #ifdef CONFIG_COMPAT
121 static int compat_nf_sockopt(struct sock *sk, int pf, int val,
122                              char __user *opt, int *len, int get)
123 {
124         struct nf_sockopt_ops *ops;
125         int ret;
126
127         if (sk->sk_net != &init_net)
128                 return -ENOPROTOOPT;
129
130
131         if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
132                 return -EINTR;
133
134         list_for_each_entry(ops, &nf_sockopts, list) {
135                 if (ops->pf == pf) {
136                         if (!try_module_get(ops->owner))
137                                 goto out_nosup;
138
139                         if (get) {
140                                 if (val >= ops->get_optmin
141                                     && val < ops->get_optmax) {
142                                         mutex_unlock(&nf_sockopt_mutex);
143                                         if (ops->compat_get)
144                                                 ret = ops->compat_get(sk,
145                                                         val, opt, len);
146                                         else
147                                                 ret = ops->get(sk,
148                                                         val, opt, len);
149                                         goto out;
150                                 }
151                         } else {
152                                 if (val >= ops->set_optmin
153                                     && val < ops->set_optmax) {
154                                         mutex_unlock(&nf_sockopt_mutex);
155                                         if (ops->compat_set)
156                                                 ret = ops->compat_set(sk,
157                                                         val, opt, *len);
158                                         else
159                                                 ret = ops->set(sk,
160                                                         val, opt, *len);
161                                         goto out;
162                                 }
163                         }
164                         module_put(ops->owner);
165                 }
166         }
167  out_nosup:
168         mutex_unlock(&nf_sockopt_mutex);
169         return -ENOPROTOOPT;
170
171  out:
172         module_put(ops->owner);
173         return ret;
174 }
175
176 int compat_nf_setsockopt(struct sock *sk, int pf,
177                 int val, char __user *opt, int len)
178 {
179         return compat_nf_sockopt(sk, pf, val, opt, &len, 0);
180 }
181 EXPORT_SYMBOL(compat_nf_setsockopt);
182
183 int compat_nf_getsockopt(struct sock *sk, int pf,
184                 int val, char __user *opt, int *len)
185 {
186         return compat_nf_sockopt(sk, pf, val, opt, len, 1);
187 }
188 EXPORT_SYMBOL(compat_nf_getsockopt);
189 #endif