patch-2.3.99-pre7 linux/drivers/net/pppoe.c
Next file: linux/drivers/net/pppox.c
Previous file: linux/drivers/net/ppp_generic.c
Back to the patch index
Back to the overall index
- Lines: 999
- Date:
Mon May 8 22:21:58 2000
- Orig file:
v2.3.99-pre6/linux/drivers/net/pppoe.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.3.99-pre6/linux/drivers/net/pppoe.c linux/drivers/net/pppoe.c
@@ -0,0 +1,998 @@
+/** -*- linux-c -*- ***********************************************************
+ * Linux PPP over Ethernet (PPPoX/PPPoE) Sockets
+ *
+ * PPPoX --- Generic PPP encapsulation socket family
+ * PPPoE --- PPP over Ethernet (RFC 2516)
+ *
+ *
+ * Version: 0.5.0
+ *
+ * Author: Michal Ostrowski <mostrows@styx.uwaterloo.ca>
+ *
+ * License:
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/module.h>
+
+#include <asm/uaccess.h>
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <linux/errno.h>
+
+#include <linux/netdevice.h>
+#include <linux/net.h>
+#include <linux/inetdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/if_pppox.h>
+#include <net/sock.h>
+#include <linux/ppp_channel.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/if_pppvar.h>
+#include <linux/init.h>
+#include <linux/notifier.h>
+#include <linux/file.h>
+#include <linux/proc_fs.h>
+
+
+
+static int __attribute__((unused)) pppoe_debug = 7;
+#define PPPOE_HASH_SIZE 16
+
+int pppoe_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
+int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb);
+
+struct proto_ops pppoe_ops;
+
+
+#if 0
+#define CHECKPTR(x,y) { if (!(x) && pppoe_debug &7 ){ printk(KERN_CRIT "PPPoE Invalid pointer : %s , %p\n",#x,(x)); error=-EINVAL; goto y; }}
+#define DEBUG(s,args...) if( pppoe_debug & (s) ) printk(KERN_CRIT args );
+#else
+#define CHECKPTR(x,y) do {} while (0);
+#define DEBUG(s,args...) do { } while (0);
+#endif
+
+
+
+static rwlock_t pppoe_hash_lock = RW_LOCK_UNLOCKED;
+
+
+static inline int cmp_2_addr(struct pppoe_addr *a, struct pppoe_addr *b)
+{
+ return (a->sid == b->sid &&
+ (memcmp(a->remote, b->remote, ETH_ALEN) == 0));
+}
+
+static inline int cmp_addr(struct pppoe_addr *a, unsigned long sid, char *addr)
+{
+ return (a->sid == sid &&
+ (memcmp(a->remote,addr,ETH_ALEN) == 0));
+}
+
+static int hash_item(unsigned long sid, unsigned char *addr)
+{
+ int i = 0;
+ union {
+ char c[sizeof(unsigned long)];
+ unsigned long i;
+ } hash;
+
+ hash.i = sid;
+
+ for (i = 0; i < ETH_ALEN; ++i)
+ hash.c[0] = hash.c[0] ^ addr[i];
+
+ for (i = 1; i < sizeof(int); ++i)
+ hash.c[0] = hash.c[0] ^ hash.c[i];
+
+ i = (int) (hash.c[0] & (PPPOE_HASH_SIZE - 1));
+
+ return i;
+}
+
+static struct pppox_opt *item_hash_table[PPPOE_HASH_SIZE] = { 0, };
+
+/**********************************************************************
+ *
+ * Set/get/delete/rehash items (internal versions)
+ *
+ **********************************************************************/
+static struct pppox_opt *__get_item(unsigned long sid, unsigned char *addr)
+{
+ int hash = hash_item(sid, addr);
+ struct pppox_opt *ret;
+
+ ret = item_hash_table[hash];
+
+ while (ret && !cmp_addr(&ret->pppoe_pa, sid, addr))
+ ret = ret->next;
+
+ return ret;
+}
+
+static int __set_item(struct pppox_opt *po)
+{
+ int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+ struct pppox_opt *ret;
+
+ ret = item_hash_table[hash];
+ while (ret) {
+ if (cmp_2_addr(&ret->pppoe_pa, &po->pppoe_pa))
+ return -EALREADY;
+
+ ret = ret->next;
+ }
+
+ if (!ret) {
+ po->next = item_hash_table[hash];
+ item_hash_table[hash] = po;
+ }
+
+ return 0;
+}
+
+static struct pppox_opt *__delete_item(unsigned long sid, char *addr)
+{
+ int hash = hash_item(sid, addr);
+ struct pppox_opt *ret, **src;
+
+ ret = item_hash_table[hash];
+ src = &item_hash_table[hash];
+
+ while (ret) {
+ if (cmp_addr(&ret->pppoe_pa, sid, addr)) {
+ *src = ret->next;
+ break;
+ }
+
+ src = &ret->next;
+ ret = ret->next;
+ }
+
+ return ret;
+}
+
+static struct pppox_opt *__find_on_dev(struct net_device *dev,
+ struct pppox_opt *start)
+{
+ struct pppox_opt *po;
+ int hash;
+
+ if (start != NULL) {
+ hash = hash_item(start->pppoe_pa.sid, start->pppoe_pa.remote);
+ po = start;
+ } else {
+ hash = 0;
+ po = NULL;
+
+ while (!po && ++hash < PPPOE_HASH_SIZE)
+ po = item_hash_table[hash];
+ }
+
+ while (po && (po->pppoe_dev != dev)){
+ if (po->next) {
+ po = po->next;
+ } else {
+ po = NULL;
+ while (!po && ++hash < PPPOE_HASH_SIZE)
+ po = item_hash_table[hash];
+ }
+ }
+
+ return po;
+}
+
+/**********************************************************************
+ *
+ * Set/get/delete/rehash items
+ *
+ **********************************************************************/
+static inline struct pppox_opt *get_item(unsigned long sid,
+ unsigned char *addr)
+{
+ struct pppox_opt *po;
+
+ read_lock_bh(&pppoe_hash_lock);
+ po = __get_item(sid, addr);
+ read_unlock_bh(&pppoe_hash_lock);
+
+ return po;
+}
+
+static inline struct pppox_opt *get_item_by_addr(struct sockaddr_pppox *sp)
+{
+ return get_item(sp->sa_addr.pppoe.sid, sp->sa_addr.pppoe.remote);
+}
+
+static inline int set_item(struct pppox_opt *po)
+{
+ int i;
+
+ if (!po)
+ return -EINVAL;
+
+ write_lock_bh(&pppoe_hash_lock);
+ i = __set_item(po);
+ write_unlock_bh(&pppoe_hash_lock);
+
+ return i;
+}
+
+static inline struct pppox_opt *delete_item(unsigned long sid, char *addr)
+{
+ struct pppox_opt *ret;
+
+ write_lock_bh(&pppoe_hash_lock);
+ ret = __delete_item(sid, addr);
+ write_unlock_bh(&pppoe_hash_lock);
+
+ return ret;
+}
+
+static struct pppox_opt *find_on_dev(struct net_device *dev,
+ struct pppox_opt *start)
+{
+ struct pppox_opt *po;
+ read_lock_bh(&pppoe_hash_lock);
+ po = __find_on_dev(dev,start);
+ read_unlock_bh(&pppoe_hash_lock);
+ return po;
+}
+
+/***************************************************************************
+ *
+ * Handler for device events
+ * Certain device events require that sockets be unconnected
+ *
+ **************************************************************************/
+static int pppoe_device_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ int error = NOTIFY_DONE;
+ struct net_device *dev = (struct net_device *) ptr;
+ struct pppox_opt *po = NULL;
+
+ /* Only look at sockets that are using this specific device. */
+ switch (event) {
+ case NETDEV_CHANGEMTU:
+ /* A change in mtu is a bad thing, requiring
+ * LCP re-negotiation.
+ */
+ case NETDEV_GOING_DOWN:
+ case NETDEV_DOWN:
+ do {
+ po = find_on_dev(dev, po);
+ if(!po)
+ break;
+
+ if (po->sk->state & PPPOX_CONNECTED)
+ pppox_unbind_sock(po->sk);
+
+ if (po->sk->state & PPPOX_CONNECTED) {
+ lock_sock(po->sk);
+ po->sk->shutdown = RCV_SHUTDOWN&SEND_SHUTDOWN;
+
+ po->sk->state = PPPOX_DEAD;
+ po->pppoe_dev = NULL;
+
+ wake_up(po->sk->sleep);
+ release_sock(po->sk);
+ }
+ } while (1);
+
+ break;
+ default:
+ break;
+ };
+
+ return error;
+}
+
+
+static struct notifier_block pppoe_notifier = {
+ pppoe_device_event,
+ NULL,
+ 0
+};
+
+
+
+
+/************************************************************************
+ *
+ * Receive a PPPoE Session frame.
+ *
+ ***********************************************************************/
+static int pppoe_rcv(struct sk_buff *skb,
+ struct net_device *dev,
+ struct packet_type *pt)
+
+{
+ struct pppoe_hdr *ph = (struct pppoe_hdr *) skb->nh.raw;
+ struct pppox_opt *po;
+ struct sock *sk ;
+
+ po = get_item((unsigned long) ph->sid, skb->mac.ethernet->h_source);
+
+ if(!po)
+ goto abort;
+
+ sk = po->sk;
+
+ if (!sk || !(sk->state & PPPOX_CONNECTED))
+ goto abort;
+
+ if (sk->state & PPPOX_BOUND) {
+ skb_pull(skb, sizeof(struct pppoe_hdr));
+
+ ppp_input(&po->chan, skb);
+ } else {
+ sock_queue_rcv_skb(sk, skb);
+ }
+
+ return 1;
+
+abort:
+ kfree_skb(skb);
+ return 0;
+}
+
+/************************************************************************
+ *
+ * Receive a PPPoE Discovery frame.
+ * -- This is solely for detection of PADT frames
+ *
+ ***********************************************************************/
+static int pppoe_disc_rcv(struct sk_buff *skb,
+ struct net_device *dev,
+ struct packet_type *pt)
+
+{
+ struct pppoe_hdr *ph = (struct pppoe_hdr *) skb->nh.raw;
+ struct pppox_opt *po;
+ struct sock *sk ;
+
+ if (ph->code != PADT_CODE)
+ goto abort;
+
+ po = get_item((unsigned long) ph->sid, skb->mac.ethernet->h_source);
+
+ if (!po)
+ goto abort;
+
+ sk = po->sk;
+
+ pppox_unbind_sock(sk);
+
+abort:
+ kfree_skb(skb);
+ return 0;
+}
+
+
+
+
+struct packet_type pppoes_ptype = {
+ __constant_htons(ETH_P_PPP_SES),
+ NULL,
+ pppoe_rcv,
+ NULL,
+ NULL
+};
+
+struct packet_type pppoed_ptype = {
+ __constant_htons(ETH_P_PPP_DISC),
+ NULL,
+ pppoe_disc_rcv,
+ NULL,
+ NULL
+};
+
+/**********************************************************************
+ *
+ * The destruct hook --- this can be trashed if there is no need for
+ * the sock to clear its receive queue?
+ *
+ *********************************************************************/
+void sock_pppoe_destruct(struct sock *sk)
+{
+ if (sk->protinfo.destruct_hook)
+ kfree(sk->protinfo.destruct_hook);
+
+ while (skb_queue_len(&sk->receive_queue) > 0) {
+ struct sk_buff *skb = skb_dequeue(&sk->receive_queue);
+ if (skb)
+ kfree_skb(skb);
+ }
+}
+
+int pppoe_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ /* Never seen this called, don't expect it to be called,
+ though I've curious whether or not it ever will be. */
+ DEBUG(KERN_CRIT "Backlog rcv called: %p\n", sk);
+
+ kfree_skb(skb);
+
+ return 0;
+}
+
+/***********************************************************************
+ *
+ * Initialize a new struct sock.
+ *
+ **********************************************************************/
+static int pppoe_create(struct socket *sock)
+{
+ int error = 0;
+ struct sock *sk;
+
+ MOD_INC_USE_COUNT;
+
+ sk = sk_alloc(PF_PPPOX, GFP_KERNEL, 1);
+ if (!sk)
+ return -ENOMEM;
+
+ sock_init_data(sock, sk);
+
+ sock->state = SS_UNCONNECTED;
+ sock->ops = &pppoe_ops;
+
+ sk->protocol = PX_PROTO_OE;
+ sk->family = PF_PPPOX;
+
+ sk->backlog_rcv = pppoe_backlog_rcv;
+ sk->next = NULL;
+ sk->pprev = NULL;
+ sk->state = PPPOX_NONE;
+ sk->type = SOCK_STREAM;
+
+ sk->protinfo.pppox = kmalloc(sizeof(struct pppox_opt), GFP_KERNEL);
+ if (!sk->protinfo.pppox) {
+ error = -ENOMEM;
+ goto free_sk;
+ }
+
+ memset((void *) sk->protinfo.pppox, 0, sizeof(struct pppox_opt));
+ sk->protinfo.pppox->sk = sk;
+
+ /* Delete the protinfo when it is time to do so. */
+ sk->protinfo.destruct_hook = sk->protinfo.pppox;
+ sock->sk = sk;
+
+ return 0;
+
+free_sk:
+ sk_free(sk);
+ MOD_DEC_USE_COUNT;
+ return error;
+}
+
+int pppoe_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct pppox_opt *po;
+ int error = 0;
+
+ if (!sk)
+ return 0;
+
+ if (sk->dead != 0)
+ return -EBADF;
+
+ pppox_unbind_sock(sk);
+
+ sock_orphan(sk);
+
+ /* Signal the death of the socket. */
+ sk->state = PPPOX_DEAD;
+
+ po = sk->protinfo.pppox;
+ if (po->pppoe_pa.sid)
+ delete_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+
+ kfree(po);
+
+
+ /* Should also do a queue purge here */
+
+ sk->protinfo.pppox = NULL;
+ sock->sk = NULL;
+
+ skb_queue_purge(&sk->receive_queue);
+
+ sock_put(sk);
+ MOD_DEC_USE_COUNT;
+
+ return error;
+}
+
+
+int pppoe_connect(struct socket *sock, struct sockaddr *uservaddr,
+ int sockaddr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct net_device *dev = NULL;
+ struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;
+ struct pppox_opt *po=sk->protinfo.pppox;
+ int error;
+
+ lock_sock(sk);
+
+ error = -EINVAL;
+ if (sp->sa_protocol != PX_PROTO_OE)
+ goto end;
+
+ error = -EBUSY;
+ if (sk->state & PPPOX_CONNECTED)
+ goto end;
+
+ dev = dev_get_by_name(sp->sa_addr.pppoe.dev);
+
+ error = -ENODEV;
+ if (!dev)
+ goto end;
+
+ error = 0;
+ if (po->pppoe_pa.sid) {
+ pppox_unbind_sock(sk);
+
+ /* Delete the old binding */
+ delete_item(po->pppoe_pa.sid,po->pppoe_pa.remote);
+
+ memset(po, 0, sizeof(struct pppox_opt));
+ po->sk = sk;
+
+ sk->state = PPPOX_NONE;
+ }
+
+ /* Don't re-bind if sid==0 */
+ if (sp->sa_addr.pppoe.sid != 0) {
+ memcpy(&po->pppoe_pa,
+ &sp->sa_addr.pppoe,
+ sizeof(struct pppoe_addr));
+
+ error = set_item(po);
+ if (error < 0)
+ goto end;
+
+ po->pppoe_dev = dev;
+
+ po->chan.hdrlen = (sizeof(struct pppoe_hdr) +
+ dev->hard_header_len);
+
+ po->chan.private = sk;
+ po->chan.ops = &pppoe_chan_ops;
+
+ error = ppp_register_channel(&po->chan);
+
+ sk->state = PPPOX_CONNECTED;
+ }
+
+ sk->num = sp->sa_addr.pppoe.sid;
+
+ end:
+ release_sock(sk);
+ return error;
+}
+
+
+int pppoe_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *usockaddr_len, int peer)
+{
+ int len = sizeof(struct sockaddr_pppox);
+ struct sockaddr_pppox sp;
+
+ sp.sa_family = AF_PPPOX;
+ sp.sa_protocol = PX_PROTO_OE;
+ memcpy(&sp.sa_addr.pppoe, &sock->sk->protinfo.pppox->pppoe_pa,
+ sizeof(struct pppoe_addr));
+
+ memcpy(uaddr, &sp, len);
+
+ *usockaddr_len = len;
+
+ return 0;
+}
+
+
+int pppoe_ioctl(struct socket *sock, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ struct pppox_opt *po;
+ int val = 0;
+ int err = 0;
+
+ po = sk->protinfo.pppox;
+ switch (cmd) {
+ case PPPIOCGMRU:
+ err = -ENXIO;
+
+ if (!(sk->state & PPPOX_CONNECTED))
+ break;
+
+ err = -EFAULT;
+ if (put_user(po->pppoe_dev->mtu -
+ sizeof(struct pppoe_hdr) -
+ PPP_HDRLEN,
+ (int *) arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSMRU:
+ err = -ENXIO;
+ if (!(sk->state & PPPOX_CONNECTED))
+ break;
+
+ err = -EFAULT;
+ if (get_user(val,(int *) arg))
+ break;
+
+ if (val < (po->pppoe_dev->mtu
+ - sizeof(struct pppoe_hdr)
+ - PPP_HDRLEN))
+ err = 0;
+ else
+ err = -EINVAL;
+ break;
+
+ case PPPIOCSFLAGS:
+ err = -EFAULT;
+ if (get_user(val, (int *) arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPOEIOCSFWD:
+ {
+ struct pppox_opt *relay_po;
+
+ err = -EBUSY;
+ if (sk->state & PPPOX_BOUND)
+ break;
+
+ err = -ENOTCONN;
+ if (!(sk->state & PPPOX_CONNECTED))
+ break;
+
+ /* PPPoE address from the user specifies an outbound
+ PPPoE address to which frames are forwarded to */
+ err = -EFAULT;
+ if( copy_from_user(&po->pppoe_relay,
+ (void*)arg,
+ sizeof(struct sockaddr_pppox)))
+ break;
+
+ err = -EINVAL;
+ if (po->pppoe_relay.sa_family != AF_PPPOX ||
+ po->pppoe_relay.sa_protocol!= PX_PROTO_OE)
+ break;
+
+ /* Check that the socket referenced by the address
+ actually exists. */
+ relay_po = get_item_by_addr(&po->pppoe_relay);
+
+ if (!relay_po)
+ break;
+
+ sk->state |= PPPOX_RELAY;
+ err = 0;
+ break;
+ }
+
+ case PPPOEIOCDFWD:
+ err = -EALREADY;
+ if (!(sk->state & PPPOX_RELAY))
+ break;
+
+ sk->state &= ~PPPOX_RELAY;
+ err = 0;
+ break;
+
+ default:
+ };
+
+ return err;
+}
+
+
+int pppoe_sendmsg(struct socket *sock, struct msghdr *m,
+ int total_len, struct scm_cookie *scm)
+{
+ struct sk_buff *skb = NULL;
+ struct sock *sk = sock->sk;
+ int error = 0;
+ struct pppoe_hdr hdr;
+ struct pppoe_hdr *ph;
+ struct net_device *dev;
+ char *start;
+ int copied = 0;
+
+ if (sk->dead || !(sk->state & PPPOX_CONNECTED)) {
+ error = -ENOTCONN;
+ goto end;
+ }
+
+ hdr.ver = 1;
+ hdr.type = 1;
+ hdr.code = 0;
+ hdr.sid = sk->num;
+
+ dev = sk->protinfo.pppox->pppoe_dev;
+
+ skb = sock_wmalloc(sk, total_len + dev->hard_header_len + 32,
+ 0, GFP_KERNEL);
+ if (!skb) {
+ error = -ENOMEM;
+ goto end;
+ }
+
+ /* Reserve space for headers. */
+ skb_reserve(skb, dev->hard_header_len);
+ skb->nh.raw = skb->data;
+ skb->dev = dev;
+ skb->priority = sk->priority;
+ skb->protocol = __constant_htons(ETH_P_PPP_SES);
+
+ ph = (struct pppoe_hdr *) skb_put(skb, total_len + sizeof(struct pppoe_hdr));
+ start = (char *) &ph->tag[0];
+
+ copied = memcpy_fromiovec( start, m->msg_iov, m->msg_iovlen);
+
+ dev->hard_header(skb, dev, ETH_P_PPP_SES,
+ sk->protinfo.pppox->pppoe_pa.remote,
+ NULL, copied);
+
+ memcpy(ph, &hdr, sizeof(struct pppoe_hdr));
+
+ ph->length = htons(copied);
+
+ dev_queue_xmit(skb);
+ return copied;
+
+end:
+ return error;
+}
+
+/************************************************************************
+ *
+ * xmit function called by generic PPP driver
+ * sends PPP frame over PPPoE socket
+ *
+ ***********************************************************************/
+int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct sock *sk = (struct sock *) chan->private;
+ struct net_device *dev = sk->protinfo.pppox->pppoe_dev;
+ struct pppoe_hdr hdr;
+ struct pppoe_hdr *ph;
+ int headroom = skb_headroom(skb);
+ int data_len = skb->len;
+
+ if (sk->dead || !(sk->state & PPPOX_CONNECTED)) {
+ goto abort;
+ }
+
+ hdr.ver = 1;
+ hdr.type = 1;
+ hdr.code = 0;
+ hdr.sid = sk->num;
+ hdr.length = htons(skb->len);
+
+ if (!dev) {
+ goto abort;
+ }
+
+ /* Copy the skb if there is no space for the header. */
+ if (headroom < (sizeof(struct pppoe_hdr) + dev->hard_header_len)) {
+ struct sk_buff *skb2;
+
+ skb2 = dev_alloc_skb(32+skb->len +
+ sizeof(struct pppoe_hdr) +
+ dev->hard_header_len);
+
+ if (skb2 == NULL)
+ goto abort;
+
+ skb_reserve(skb2, dev->hard_header_len + sizeof(struct pppoe_hdr));
+ memcpy(skb_put(skb2, skb->len), skb->data, skb->len);
+
+ skb_unlink(skb);
+ kfree_skb(skb);
+ skb = skb2;
+ }
+
+ ph = (struct pppoe_hdr *) skb_push(skb, sizeof(struct pppoe_hdr));
+ memcpy(ph, &hdr, sizeof(struct pppoe_hdr));
+ skb->protocol = __constant_htons(ETH_P_PPP_SES);
+
+ skb->nh.raw = skb->data;
+ skb->dev = dev;
+
+ dev->hard_header(skb, dev, ETH_P_PPP_SES,
+ sk->protinfo.pppox->pppoe_pa.remote,
+ NULL, data_len);
+
+ if (dev_queue_xmit(skb) < 0)
+ goto abort;
+
+ return 1;
+ abort:
+ return 0;
+}
+
+struct ppp_channel_ops pppoe_chan_ops = { pppoe_xmit , NULL };
+
+int pppoe_rcvmsg(struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb = NULL;
+ int error = 0;
+ int len;
+ struct pppoe_hdr *ph = NULL;
+
+ if (sk->state & PPPOX_BOUND) {
+ error = -EIO;
+ goto end;
+ }
+
+ skb = skb_recv_datagram(sk, flags, 0, &error);
+
+ if (error < 0) {
+ goto end;
+ }
+
+ m->msg_namelen = 0;
+
+ if (skb) {
+ error = 0;
+ ph = (struct pppoe_hdr *) skb->nh.raw;
+ len = ntohs(ph->length);
+
+ error = memcpy_toiovec(m->msg_iov, (unsigned char *) &ph->tag[0], len);
+ if (error < 0)
+ goto do_skb_free;
+ error = len;
+ }
+
+do_skb_free:
+ if (skb)
+ kfree_skb(skb);
+end:
+ return error;
+}
+
+int pppoe_proc_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct pppox_opt *po;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ int size;
+ int i;
+
+ len += sprintf(buffer,
+ "Id Address Device\n");
+ pos = len;
+
+ write_lock_bh(&pppoe_hash_lock);
+
+ for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+ po = item_hash_table[i];
+ while (po) {
+ char *dev = po->pppoe_pa.dev;
+
+ size = sprintf(buffer + len,
+ "%08X %02X:%02X:%02X:%02X:%02X:%02X %8s\n",
+ po->pppoe_pa.sid,
+ po->pppoe_pa.remote[0],
+ po->pppoe_pa.remote[1],
+ po->pppoe_pa.remote[2],
+ po->pppoe_pa.remote[3],
+ po->pppoe_pa.remote[4],
+ po->pppoe_pa.remote[5],
+ dev);
+ len += size;
+ pos += size;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+
+ po = po->next;
+ }
+
+ if (po)
+ break;
+ }
+ write_unlock_bh(&pppoe_hash_lock);
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+
+struct proto_ops pppoe_ops = {
+ family: AF_PPPOX,
+ release: pppoe_release,
+ bind: sock_no_bind,
+ connect: pppoe_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: pppoe_getname,
+ poll: datagram_poll,
+ ioctl: pppoe_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: pppoe_sendmsg,
+ recvmsg: pppoe_rcvmsg,
+ mmap: sock_no_mmap
+};
+
+struct pppox_proto pppoe_proto = {
+ create: pppoe_create,
+ ioctl: pppoe_ioctl
+};
+
+
+int __init pppoe_init(void)
+{
+ int err = register_pppox_proto(PX_PROTO_OE, &pppoe_proto);
+
+ if (err == 0) {
+ printk(KERN_INFO "Registered PPPoE v0.5\n");
+
+ dev_add_pack(&pppoes_ptype);
+ register_netdevice_notifier(&pppoe_notifier);
+ proc_net_create("pppoe", 0, pppoe_proc_info);
+ }
+ return err;
+}
+
+
+#ifdef MODULE
+MODULE_PARM(debug, "i");
+int init_module(void)
+{
+ return pppoe_init();
+}
+
+void cleanup_module(void)
+{
+ unregister_pppox_proto(PX_PROTO_OE);
+ dev_remove_pack(&pppoes_ptype);
+ unregister_netdevice_notifier(&pppoe_notifier);
+ proc_net_remove("pppoe");
+}
+
+#else
+
+int pppoe_proto_init(struct net_proto *np)
+{
+ return pppoe_init();
+}
+
+#endif
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)