diff options
Diffstat (limited to 'net/atm/pppoatm.c')
-rw-r--r-- | net/atm/pppoatm.c | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c new file mode 100644 index 000000000000..58f4a2b5aebe --- /dev/null +++ b/net/atm/pppoatm.c | |||
@@ -0,0 +1,369 @@ | |||
1 | /* net/atm/pppoatm.c - RFC2364 PPP over ATM/AAL5 */ | ||
2 | |||
3 | /* Copyright 1999-2000 by Mitchell Blank Jr */ | ||
4 | /* Based on clip.c; 1995-1999 by Werner Almesberger, EPFL LRC/ICA */ | ||
5 | /* And on ppp_async.c; Copyright 1999 Paul Mackerras */ | ||
6 | /* And help from Jens Axboe */ | ||
7 | |||
8 | /* | ||
9 | * This program is free software; you can redistribute it and/or | ||
10 | * modify it under the terms of the GNU General Public License | ||
11 | * as published by the Free Software Foundation; either version | ||
12 | * 2 of the License, or (at your option) any later version. | ||
13 | * | ||
14 | * This driver provides the encapsulation and framing for sending | ||
15 | * and receiving PPP frames in ATM AAL5 PDUs. | ||
16 | */ | ||
17 | |||
18 | /* | ||
19 | * One shortcoming of this driver is that it does not comply with | ||
20 | * section 8 of RFC2364 - we are supposed to detect a change | ||
21 | * in encapsulation and immediately abort the connection (in order | ||
22 | * to avoid a black-hole being created if our peer loses state | ||
23 | * and changes encapsulation unilaterally. However, since the | ||
24 | * ppp_generic layer actually does the decapsulation, we need | ||
25 | * a way of notifying it when we _think_ there might be a problem) | ||
26 | * There's two cases: | ||
27 | * 1. LLC-encapsulation was missing when it was enabled. In | ||
28 | * this case, we should tell the upper layer "tear down | ||
29 | * this session if this skb looks ok to you" | ||
30 | * 2. LLC-encapsulation was present when it was disabled. Then | ||
31 | * we need to tell the upper layer "this packet may be | ||
32 | * ok, but if its in error tear down the session" | ||
33 | * These hooks are not yet available in ppp_generic | ||
34 | */ | ||
35 | |||
36 | #include <linux/module.h> | ||
37 | #include <linux/config.h> | ||
38 | #include <linux/init.h> | ||
39 | #include <linux/skbuff.h> | ||
40 | #include <linux/atm.h> | ||
41 | #include <linux/atmdev.h> | ||
42 | #include <linux/ppp_defs.h> | ||
43 | #include <linux/if_ppp.h> | ||
44 | #include <linux/ppp_channel.h> | ||
45 | #include <linux/atmppp.h> | ||
46 | |||
47 | #include "common.h" | ||
48 | |||
49 | #if 0 | ||
50 | #define DPRINTK(format, args...) \ | ||
51 | printk(KERN_DEBUG "pppoatm: " format, ##args) | ||
52 | #else | ||
53 | #define DPRINTK(format, args...) | ||
54 | #endif | ||
55 | |||
56 | enum pppoatm_encaps { | ||
57 | e_autodetect = PPPOATM_ENCAPS_AUTODETECT, | ||
58 | e_vc = PPPOATM_ENCAPS_VC, | ||
59 | e_llc = PPPOATM_ENCAPS_LLC, | ||
60 | }; | ||
61 | |||
62 | struct pppoatm_vcc { | ||
63 | struct atm_vcc *atmvcc; /* VCC descriptor */ | ||
64 | void (*old_push)(struct atm_vcc *, struct sk_buff *); | ||
65 | void (*old_pop)(struct atm_vcc *, struct sk_buff *); | ||
66 | /* keep old push/pop for detaching */ | ||
67 | enum pppoatm_encaps encaps; | ||
68 | int flags; /* SC_COMP_PROT - compress protocol */ | ||
69 | struct ppp_channel chan; /* interface to generic ppp layer */ | ||
70 | struct tasklet_struct wakeup_tasklet; | ||
71 | }; | ||
72 | |||
73 | /* | ||
74 | * Header used for LLC Encapsulated PPP (4 bytes) followed by the LCP protocol | ||
75 | * ID (0xC021) used in autodetection | ||
76 | */ | ||
77 | static const unsigned char pppllc[6] = { 0xFE, 0xFE, 0x03, 0xCF, 0xC0, 0x21 }; | ||
78 | #define LLC_LEN (4) | ||
79 | |||
80 | static inline struct pppoatm_vcc *atmvcc_to_pvcc(const struct atm_vcc *atmvcc) | ||
81 | { | ||
82 | return (struct pppoatm_vcc *) (atmvcc->user_back); | ||
83 | } | ||
84 | |||
85 | static inline struct pppoatm_vcc *chan_to_pvcc(const struct ppp_channel *chan) | ||
86 | { | ||
87 | return (struct pppoatm_vcc *) (chan->private); | ||
88 | } | ||
89 | |||
90 | /* | ||
91 | * We can't do this directly from our _pop handler, since the ppp code | ||
92 | * doesn't want to be called in interrupt context, so we do it from | ||
93 | * a tasklet | ||
94 | */ | ||
95 | static void pppoatm_wakeup_sender(unsigned long arg) | ||
96 | { | ||
97 | ppp_output_wakeup((struct ppp_channel *) arg); | ||
98 | } | ||
99 | |||
100 | /* | ||
101 | * This gets called every time the ATM card has finished sending our | ||
102 | * skb. The ->old_pop will take care up normal atm flow control, | ||
103 | * but we also need to wake up the device if we blocked it | ||
104 | */ | ||
105 | static void pppoatm_pop(struct atm_vcc *atmvcc, struct sk_buff *skb) | ||
106 | { | ||
107 | struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc); | ||
108 | pvcc->old_pop(atmvcc, skb); | ||
109 | /* | ||
110 | * We don't really always want to do this since it's | ||
111 | * really inefficient - it would be much better if we could | ||
112 | * test if we had actually throttled the generic layer. | ||
113 | * Unfortunately then there would be a nasty SMP race where | ||
114 | * we could clear that flag just as we refuse another packet. | ||
115 | * For now we do the safe thing. | ||
116 | */ | ||
117 | tasklet_schedule(&pvcc->wakeup_tasklet); | ||
118 | } | ||
119 | |||
120 | /* | ||
121 | * Unbind from PPP - currently we only do this when closing the socket, | ||
122 | * but we could put this into an ioctl if need be | ||
123 | */ | ||
124 | static void pppoatm_unassign_vcc(struct atm_vcc *atmvcc) | ||
125 | { | ||
126 | struct pppoatm_vcc *pvcc; | ||
127 | pvcc = atmvcc_to_pvcc(atmvcc); | ||
128 | atmvcc->push = pvcc->old_push; | ||
129 | atmvcc->pop = pvcc->old_pop; | ||
130 | tasklet_kill(&pvcc->wakeup_tasklet); | ||
131 | ppp_unregister_channel(&pvcc->chan); | ||
132 | atmvcc->user_back = NULL; | ||
133 | kfree(pvcc); | ||
134 | /* Gee, I hope we have the big kernel lock here... */ | ||
135 | module_put(THIS_MODULE); | ||
136 | } | ||
137 | |||
138 | /* Called when an AAL5 PDU comes in */ | ||
139 | static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb) | ||
140 | { | ||
141 | struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc); | ||
142 | DPRINTK("pppoatm push\n"); | ||
143 | if (skb == NULL) { /* VCC was closed */ | ||
144 | DPRINTK("removing ATMPPP VCC %p\n", pvcc); | ||
145 | pppoatm_unassign_vcc(atmvcc); | ||
146 | atmvcc->push(atmvcc, NULL); /* Pass along bad news */ | ||
147 | return; | ||
148 | } | ||
149 | atm_return(atmvcc, skb->truesize); | ||
150 | switch (pvcc->encaps) { | ||
151 | case e_llc: | ||
152 | if (skb->len < LLC_LEN || | ||
153 | memcmp(skb->data, pppllc, LLC_LEN)) | ||
154 | goto error; | ||
155 | skb_pull(skb, LLC_LEN); | ||
156 | break; | ||
157 | case e_autodetect: | ||
158 | if (pvcc->chan.ppp == NULL) { /* Not bound yet! */ | ||
159 | kfree_skb(skb); | ||
160 | return; | ||
161 | } | ||
162 | if (skb->len >= sizeof(pppllc) && | ||
163 | !memcmp(skb->data, pppllc, sizeof(pppllc))) { | ||
164 | pvcc->encaps = e_llc; | ||
165 | skb_pull(skb, LLC_LEN); | ||
166 | break; | ||
167 | } | ||
168 | if (skb->len >= (sizeof(pppllc) - LLC_LEN) && | ||
169 | !memcmp(skb->data, &pppllc[LLC_LEN], | ||
170 | sizeof(pppllc) - LLC_LEN)) { | ||
171 | pvcc->encaps = e_vc; | ||
172 | pvcc->chan.mtu += LLC_LEN; | ||
173 | break; | ||
174 | } | ||
175 | DPRINTK("(unit %d): Couldn't autodetect yet " | ||
176 | "(skb: %02X %02X %02X %02X %02X %02X)\n", | ||
177 | pvcc->chan.unit, | ||
178 | skb->data[0], skb->data[1], skb->data[2], | ||
179 | skb->data[3], skb->data[4], skb->data[5]); | ||
180 | goto error; | ||
181 | case e_vc: | ||
182 | break; | ||
183 | } | ||
184 | ppp_input(&pvcc->chan, skb); | ||
185 | return; | ||
186 | error: | ||
187 | kfree_skb(skb); | ||
188 | ppp_input_error(&pvcc->chan, 0); | ||
189 | } | ||
190 | |||
191 | /* | ||
192 | * Called by the ppp_generic.c to send a packet - returns true if packet | ||
193 | * was accepted. If we return false, then it's our job to call | ||
194 | * ppp_output_wakeup(chan) when we're feeling more up to it. | ||
195 | * Note that in the ENOMEM case (as opposed to the !atm_may_send case) | ||
196 | * we should really drop the packet, but the generic layer doesn't | ||
197 | * support this yet. We just return 'DROP_PACKET' which we actually define | ||
198 | * as success, just to be clear what we're really doing. | ||
199 | */ | ||
200 | #define DROP_PACKET 1 | ||
201 | static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) | ||
202 | { | ||
203 | struct pppoatm_vcc *pvcc = chan_to_pvcc(chan); | ||
204 | ATM_SKB(skb)->vcc = pvcc->atmvcc; | ||
205 | DPRINTK("(unit %d): pppoatm_send (skb=0x%p, vcc=0x%p)\n", | ||
206 | pvcc->chan.unit, skb, pvcc->atmvcc); | ||
207 | if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT)) | ||
208 | (void) skb_pull(skb, 1); | ||
209 | switch (pvcc->encaps) { /* LLC encapsulation needed */ | ||
210 | case e_llc: | ||
211 | if (skb_headroom(skb) < LLC_LEN) { | ||
212 | struct sk_buff *n; | ||
213 | n = skb_realloc_headroom(skb, LLC_LEN); | ||
214 | if (n != NULL && | ||
215 | !atm_may_send(pvcc->atmvcc, n->truesize)) { | ||
216 | kfree_skb(n); | ||
217 | goto nospace; | ||
218 | } | ||
219 | kfree_skb(skb); | ||
220 | if ((skb = n) == NULL) | ||
221 | return DROP_PACKET; | ||
222 | } else if (!atm_may_send(pvcc->atmvcc, skb->truesize)) | ||
223 | goto nospace; | ||
224 | memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN); | ||
225 | break; | ||
226 | case e_vc: | ||
227 | if (!atm_may_send(pvcc->atmvcc, skb->truesize)) | ||
228 | goto nospace; | ||
229 | break; | ||
230 | case e_autodetect: | ||
231 | DPRINTK("(unit %d): Trying to send without setting encaps!\n", | ||
232 | pvcc->chan.unit); | ||
233 | kfree_skb(skb); | ||
234 | return 1; | ||
235 | } | ||
236 | |||
237 | atomic_add(skb->truesize, &sk_atm(ATM_SKB(skb)->vcc)->sk_wmem_alloc); | ||
238 | ATM_SKB(skb)->atm_options = ATM_SKB(skb)->vcc->atm_options; | ||
239 | DPRINTK("(unit %d): atm_skb(%p)->vcc(%p)->dev(%p)\n", | ||
240 | pvcc->chan.unit, skb, ATM_SKB(skb)->vcc, | ||
241 | ATM_SKB(skb)->vcc->dev); | ||
242 | return ATM_SKB(skb)->vcc->send(ATM_SKB(skb)->vcc, skb) | ||
243 | ? DROP_PACKET : 1; | ||
244 | nospace: | ||
245 | /* | ||
246 | * We don't have space to send this SKB now, but we might have | ||
247 | * already applied SC_COMP_PROT compression, so may need to undo | ||
248 | */ | ||
249 | if ((pvcc->flags & SC_COMP_PROT) && skb_headroom(skb) > 0 && | ||
250 | skb->data[-1] == '\0') | ||
251 | (void) skb_push(skb, 1); | ||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | /* This handles ioctls sent to the /dev/ppp interface */ | ||
256 | static int pppoatm_devppp_ioctl(struct ppp_channel *chan, unsigned int cmd, | ||
257 | unsigned long arg) | ||
258 | { | ||
259 | switch (cmd) { | ||
260 | case PPPIOCGFLAGS: | ||
261 | return put_user(chan_to_pvcc(chan)->flags, (int __user *) arg) | ||
262 | ? -EFAULT : 0; | ||
263 | case PPPIOCSFLAGS: | ||
264 | return get_user(chan_to_pvcc(chan)->flags, (int __user *) arg) | ||
265 | ? -EFAULT : 0; | ||
266 | } | ||
267 | return -ENOTTY; | ||
268 | } | ||
269 | |||
270 | static /*const*/ struct ppp_channel_ops pppoatm_ops = { | ||
271 | .start_xmit = pppoatm_send, | ||
272 | .ioctl = pppoatm_devppp_ioctl, | ||
273 | }; | ||
274 | |||
275 | static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg) | ||
276 | { | ||
277 | struct atm_backend_ppp be; | ||
278 | struct pppoatm_vcc *pvcc; | ||
279 | int err; | ||
280 | /* | ||
281 | * Each PPPoATM instance has its own tasklet - this is just a | ||
282 | * prototypical one used to initialize them | ||
283 | */ | ||
284 | static const DECLARE_TASKLET(tasklet_proto, pppoatm_wakeup_sender, 0); | ||
285 | if (copy_from_user(&be, arg, sizeof be)) | ||
286 | return -EFAULT; | ||
287 | if (be.encaps != PPPOATM_ENCAPS_AUTODETECT && | ||
288 | be.encaps != PPPOATM_ENCAPS_VC && be.encaps != PPPOATM_ENCAPS_LLC) | ||
289 | return -EINVAL; | ||
290 | pvcc = kmalloc(sizeof(*pvcc), GFP_KERNEL); | ||
291 | if (pvcc == NULL) | ||
292 | return -ENOMEM; | ||
293 | memset(pvcc, 0, sizeof(*pvcc)); | ||
294 | pvcc->atmvcc = atmvcc; | ||
295 | pvcc->old_push = atmvcc->push; | ||
296 | pvcc->old_pop = atmvcc->pop; | ||
297 | pvcc->encaps = (enum pppoatm_encaps) be.encaps; | ||
298 | pvcc->chan.private = pvcc; | ||
299 | pvcc->chan.ops = &pppoatm_ops; | ||
300 | pvcc->chan.mtu = atmvcc->qos.txtp.max_sdu - PPP_HDRLEN - | ||
301 | (be.encaps == e_vc ? 0 : LLC_LEN); | ||
302 | pvcc->wakeup_tasklet = tasklet_proto; | ||
303 | pvcc->wakeup_tasklet.data = (unsigned long) &pvcc->chan; | ||
304 | if ((err = ppp_register_channel(&pvcc->chan)) != 0) { | ||
305 | kfree(pvcc); | ||
306 | return err; | ||
307 | } | ||
308 | atmvcc->user_back = pvcc; | ||
309 | atmvcc->push = pppoatm_push; | ||
310 | atmvcc->pop = pppoatm_pop; | ||
311 | __module_get(THIS_MODULE); | ||
312 | return 0; | ||
313 | } | ||
314 | |||
315 | /* | ||
316 | * This handles ioctls actually performed on our vcc - we must return | ||
317 | * -ENOIOCTLCMD for any unrecognized ioctl | ||
318 | */ | ||
319 | static int pppoatm_ioctl(struct socket *sock, unsigned int cmd, | ||
320 | unsigned long arg) | ||
321 | { | ||
322 | struct atm_vcc *atmvcc = ATM_SD(sock); | ||
323 | void __user *argp = (void __user *)arg; | ||
324 | |||
325 | if (cmd != ATM_SETBACKEND && atmvcc->push != pppoatm_push) | ||
326 | return -ENOIOCTLCMD; | ||
327 | switch (cmd) { | ||
328 | case ATM_SETBACKEND: { | ||
329 | atm_backend_t b; | ||
330 | if (get_user(b, (atm_backend_t __user *) argp)) | ||
331 | return -EFAULT; | ||
332 | if (b != ATM_BACKEND_PPP) | ||
333 | return -ENOIOCTLCMD; | ||
334 | if (!capable(CAP_NET_ADMIN)) | ||
335 | return -EPERM; | ||
336 | return pppoatm_assign_vcc(atmvcc, argp); | ||
337 | } | ||
338 | case PPPIOCGCHAN: | ||
339 | return put_user(ppp_channel_index(&atmvcc_to_pvcc(atmvcc)-> | ||
340 | chan), (int __user *) argp) ? -EFAULT : 0; | ||
341 | case PPPIOCGUNIT: | ||
342 | return put_user(ppp_unit_number(&atmvcc_to_pvcc(atmvcc)-> | ||
343 | chan), (int __user *) argp) ? -EFAULT : 0; | ||
344 | } | ||
345 | return -ENOIOCTLCMD; | ||
346 | } | ||
347 | |||
348 | static struct atm_ioctl pppoatm_ioctl_ops = { | ||
349 | .owner = THIS_MODULE, | ||
350 | .ioctl = pppoatm_ioctl, | ||
351 | }; | ||
352 | |||
353 | static int __init pppoatm_init(void) | ||
354 | { | ||
355 | register_atm_ioctl(&pppoatm_ioctl_ops); | ||
356 | return 0; | ||
357 | } | ||
358 | |||
359 | static void __exit pppoatm_exit(void) | ||
360 | { | ||
361 | deregister_atm_ioctl(&pppoatm_ioctl_ops); | ||
362 | } | ||
363 | |||
364 | module_init(pppoatm_init); | ||
365 | module_exit(pppoatm_exit); | ||
366 | |||
367 | MODULE_AUTHOR("Mitchell Blank Jr <mitch@sfgoth.com>"); | ||
368 | MODULE_DESCRIPTION("RFC2364 PPP over ATM/AAL5"); | ||
369 | MODULE_LICENSE("GPL"); | ||