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