patch-2.4.0-test6 linux/drivers/net/pppoe.c

Next file: linux/drivers/net/sis900.c
Previous file: linux/drivers/net/pcmcia/ray_cs.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test5/linux/drivers/net/pppoe.c linux/drivers/net/pppoe.c
@@ -5,9 +5,17 @@
  * PPPoE --- PPP over Ethernet (RFC 2516)
  *
  *
- * Version:    0.5.1
+ * Version:    0.6.1
  *
- * 030700 :     Fixed connect logic to allow for disconnect
+ * 030700 :     Fixed connect logic to allow for disconnect.
+ * 270700 :	Fixed potential SMP problems; we must protect against 
+ *		simultaneous invocation of ppp_input 
+ *		and ppp_unregister_channel.
+ * 040800 :	Respect reference count mechanisms on net-devices.
+ *
+ *		Module reference count is decremented in the right spot now,
+ *		guards against sock_put not actually freeing the sk 
+ *		in pppoe_release.
  *
  * Author:	Michal Ostrowski <mostrows@styx.uwaterloo.ca>
  *
@@ -162,36 +170,6 @@
 	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
@@ -204,6 +182,8 @@
 
 	read_lock_bh(&pppoe_hash_lock);
 	po = __get_item(sid, addr);
+	if(po)
+		sock_hold(po->sk);
 	read_unlock_bh(&pppoe_hash_lock);
 
 	return po;
@@ -239,20 +219,12 @@
 	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
+ *  Handler for device events.
+ *  Certain device events require that sockets be unconnected.
  *
  **************************************************************************/
 static int pppoe_device_event(struct notifier_block *this,
@@ -261,6 +233,7 @@
 	int error = NOTIFY_DONE;
 	struct net_device *dev = (struct net_device *) ptr;
 	struct pppox_opt *po = NULL;
+	int hash = 0;
 	
 	/* Only look at sockets that are using this specific device. */
 	switch (event) {
@@ -270,26 +243,40 @@
 	     */
 	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);
+		/* Find every socket on this device and kill it. */
+		read_lock_bh(&pppoe_hash_lock);
 
-			if (po->sk->state & PPPOX_CONNECTED) {
+		while (!po && hash < PPPOE_HASH_SIZE){
+			po = item_hash_table[hash];
+			++hash;
+		}
+	
+		while (po && hash < PPPOE_HASH_SIZE){
+			if(po->pppoe_dev == dev){
 				lock_sock(po->sk);
-				po->sk->shutdown = RCV_SHUTDOWN&SEND_SHUTDOWN;
+				if (po->sk->state & (PPPOX_CONNECTED|PPPOX_BOUND)){
+					pppox_unbind_sock(po->sk);
 				
-				po->sk->state = PPPOX_DEAD;
-				po->pppoe_dev = NULL;
+					dev_put(po->pppoe_dev);
+					po->pppoe_dev = NULL;
 
-				wake_up(po->sk->sleep);
-				release_sock(po->sk);
+					po->sk->state = PPPOX_DEAD;
+					po->sk->state_change(po->sk);
+				}
+ 				release_sock(po->sk);
 			}
-		} while (1);
-
+			if (po->next) {
+				po = po->next;
+			} else {
+				po = NULL;
+				while (!po && hash < PPPOE_HASH_SIZE){
+					po = item_hash_table[hash];
+					++hash;
+				}
+			}
+		}
+		read_unlock_bh(&pppoe_hash_lock);
 		break;
 	default:
 		break;
@@ -310,60 +297,102 @@
 
 /************************************************************************
  *
- * Receive a PPPoE Session frame.
+ * Do the real work of receiving 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;
+int pppoe_rcv_core(struct sock *sk, struct sk_buff *skb){
+	struct pppox_opt  *po=sk->protinfo.pppox;
+	struct pppox_opt *relay_po = NULL;
 
 	if (sk->state & PPPOX_BOUND) {
 		skb_pull(skb, sizeof(struct pppoe_hdr));
-
+		
 		ppp_input(&po->chan, skb);
 	} else if( sk->state & PPPOX_RELAY ){
-		struct pppox_opt *relay_po;
 
 		relay_po = get_item_by_addr( &po->pppoe_relay );
 
 		if( relay_po == NULL  ||
-		    !( relay_po->sk->state & PPPOX_CONNECTED ) )
+		    !( relay_po->sk->state & PPPOX_CONNECTED ) ){
 			goto abort;
-
+		}
+		
 		skb_pull(skb, sizeof(struct pppoe_hdr));
-		if( !__pppoe_xmit( relay_po->sk , skb) )
+		if( !__pppoe_xmit( relay_po->sk , skb) ){
 			goto abort;
-
+		}
 	} else {
 		sock_queue_rcv_skb(sk, skb);
 	}
-
 	return 1;
-
 abort:
-	kfree_skb(skb);
+	if(relay_po)
+		sock_put(relay_po->sk);
 	return 0;
+
 }
 
+
+
+
+/************************************************************************
+ *
+ * Receive wrapper called in BH context.
+ *
+ ***********************************************************************/
+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 ;
+	int ret;
+
+	po = get_item((unsigned long) ph->sid, skb->mac.ethernet->h_source);
+
+	if(!po){
+		kfree(skb);
+		return 0;
+	}
+
+	sk = po->sk;
+        bh_lock_sock(sk);
+
+	/* Socket state is unknown, must put skb into backlog. */
+	if( sk->lock.users != 0 ){
+		sk_add_backlog( sk, skb);
+		ret = 1;
+	}else{
+		ret = pppoe_rcv_core(sk, skb);
+	}
+	
+	bh_unlock_sock(sk);
+	sock_put(sk);
+	return ret;
+}
+
+
+/************************************************************************
+ *
+ * Receive wrapper called in process context.
+ *
+ ***********************************************************************/
+int pppoe_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+	lock_sock(sk);
+	pppoe_rcv_core(sk, skb);
+	release_sock(sk);
+	return 0;
+}
+
+
+
 /************************************************************************
  *
  * Receive a PPPoE Discovery frame.
- * -- This is solely for detection of PADT frames
+ * This is solely for detection of PADT frames
  *
  ***********************************************************************/
 static int pppoe_disc_rcv(struct sk_buff *skb,
@@ -373,7 +402,7 @@
 {
 	struct pppoe_hdr *ph = (struct pppoe_hdr *) skb->nh.raw;
 	struct pppox_opt *po;
-	struct sock *sk ;
+	struct sock *sk = NULL;
 
 	if (ph->code != PADT_CODE)
 		goto abort;
@@ -381,13 +410,15 @@
 	po = get_item((unsigned long) ph->sid, skb->mac.ethernet->h_source);
 
 	if (!po)
-		goto abort;
+		goto abort_put;
 
 	sk = po->sk;
 
 	pppox_unbind_sock(sk);
 
-abort:
+ abort_put:
+	sock_put(sk);
+ abort:
 	kfree_skb(skb);
 	return 0;
 }
@@ -411,34 +442,18 @@
 	NULL
 };
 
-/**********************************************************************
+/***********************************************************************
  *
- * The destruct hook --- this can be trashed if there is no need for
- * the sock to clear its receive queue?
+ * Really kill the socket. (Called from sock_put if refcnt == 0.)
  *
- *********************************************************************/
-void sock_pppoe_destruct(struct sock *sk)
+ **********************************************************************/
+void pppoe_sock_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);
-	}
+	MOD_DEC_USE_COUNT;
 }
 
-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;
-}
 
 /***********************************************************************
  *
@@ -469,6 +484,7 @@
 	sk->pprev = NULL;
 	sk->state = PPPOX_NONE;
 	sk->type = SOCK_STREAM;
+	sk->destruct = pppoe_sock_destruct;
 
 	sk->protinfo.pppox = kmalloc(sizeof(struct pppox_opt), GFP_KERNEL);
 	if (!sk->protinfo.pppox) {
@@ -487,7 +503,6 @@
 
 free_sk:
 	sk_free(sk);
-	MOD_DEC_USE_COUNT;
 	return error;
 }
 
@@ -505,27 +520,24 @@
 
 	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);
-
+		
+	if (po->pppoe_dev)
+	    dev_put(po->pppoe_dev);
 
 	/* Should also do a queue purge here */
 
-	sk->protinfo.pppox = NULL;
+	sock_orphan(sk);
 	sock->sk = NULL;
 
 	skb_queue_purge(&sk->receive_queue);
 
 	sock_put(sk);
-	MOD_DEC_USE_COUNT;
 
 	return error;
 }
@@ -546,14 +558,15 @@
 	if (sp->sa_protocol != PX_PROTO_OE)
 		goto end;
 
+	/* Check for already bound sockets */
 	error = -EBUSY;
 	if ((sk->state & PPPOX_CONNECTED) && sp->sa_addr.pppoe.sid)
 		goto end;
 
-	dev = dev_get_by_name(sp->sa_addr.pppoe.dev);
-		
-	error = -ENODEV;
-	if (!dev)
+	/* Check for already disconnected sockets, 
+	   on attempts to disconnect */
+	error = -EALREADY;
+	if((sk->state & PPPOX_DEAD) && !sp->sa_addr.pppoe.sid )
 		goto end;
 
 	error = 0;
@@ -563,6 +576,8 @@
 		/* Delete the old binding */
 		delete_item(po->pppoe_pa.sid,po->pppoe_pa.remote);
 
+		dev_put(po->pppoe_dev);
+
 		memset(po, 0, sizeof(struct pppox_opt));
 		po->sk = sk;
 
@@ -571,6 +586,14 @@
 
 	/* Don't re-bind if sid==0 */
 	if (sp->sa_addr.pppoe.sid != 0) {
+		dev = dev_get_by_name(sp->sa_addr.pppoe.dev);
+	    
+		error = -ENODEV;
+		if (!dev)
+			goto end;
+
+		if( ! (dev->flags & IFF_UP) )
+			goto end;
 		memcpy(&po->pppoe_pa,
 		       &sp->sa_addr.pppoe,
 		       sizeof(struct pppoe_addr));
@@ -743,6 +766,8 @@
 	hdr.code = 0;
 	hdr.sid = sk->num;
 
+	lock_sock(sk);
+
 	dev = sk->protinfo.pppox->pppoe_dev;
 
 	skb = sock_wmalloc(sk, total_len + dev->hard_header_len + 32,
@@ -755,14 +780,19 @@
 	/* Reserve space for headers. */
 	skb_reserve(skb, dev->hard_header_len);
 	skb->nh.raw = skb->data;
-	skb->dev = dev;
+
+	skb->rx_dev = skb->dev = dev;
+	dev_hold(skb->rx_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);
+	error = copied = memcpy_fromiovec( start, m->msg_iov, m->msg_iovlen);
+	if( error <= 0 )
+	    goto end;
 
 	dev->hard_header(skb, dev, ETH_P_PPP_SES,
 			 sk->protinfo.pppox->pppoe_pa.remote,
@@ -773,9 +803,9 @@
 	ph->length = htons(copied);
 
 	dev_queue_xmit(skb);
-	return copied;
 
 end:
+	release_sock(sk);
 	return error;
 }
 
@@ -830,7 +860,12 @@
 	skb->protocol = __constant_htons(ETH_P_PPP_SES);
 
 	skb->nh.raw = skb->data;
-	skb->dev = dev;
+
+	/* Change device of skb, update reference counts */
+	if(skb->rx_dev)
+	    dev_put(skb->rx_dev);
+	skb->rx_dev = skb->dev = dev;
+	dev_hold(skb->rx_dev);
 
 	dev->hard_header(skb, dev, ETH_P_PPP_SES,
 			 sk->protinfo.pppox->pppoe_pa.remote,

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)