potential bug in David Brownell's zcip code

Michael Schmidt <[email protected]> Wed, 22 Dec 2004 12:37:14 +0100
Newsgroups gmane.network.zeroconf.workers
Organization University of Siegen
Message-ID <[email protected]>
--Boundary-00=_qxVyBhPIbZesI/R
Content-Type: text/plain;
  charset="us-ascii"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Hi,

Now that I'm seeing a bit clearer about the legal status of David Brownell's 
zcip code, I'd like to discuss a potential but I have discovered recently. 
The bug is about time measurement and time/timeout calculation and 
recalculation around the call to 'poll()'. 

But first, let me explain how I understand certain parts of the code:

1. meaning of 'tv1':

	if (timeout > 0) {
		gettimeofday(&tv1, NULL);
		tv1.tv_usec += (timeout % 1000) * 1000;
		if (tv1.tv_usec > 1000000) {
			tv1.tv_usec -= 1000000;
			tv1.tv_sec++;
		}
		tv1.tv_sec += timeout / 1000;
	} else if (timeout == 0) {
		timeout = ms_rdelay(PROBE_WAIT);

'tv1' seems to be the timeout for the subsequent call to 'poll()' as 
"absolute" time value for finite, non-zero timeouts.


2. interpretation of 'poll()' return codes:

	switch (poll(fds, 1, timeout)) {

	// timeouts trigger protocol transitions
	case 0:
	.
	.		/* this is the 'timeout' branch */
	.
	break;

	// packets arriving
	case 1:
	.
	.		/* this is the 'packet has arrived' branch */
	.
	break;

	default:
	.
	.		/* this is the 'error' branch */
	.


3. recalculation of timeout in 'packet has arrived' branch:

		// maybe adjust timeout
		if (timeout > 0) {
			struct timeval tv2;

			gettimeofday(&tv2, NULL);
			if (timercmp(&tv1, &tv2, <)) {
				timeout = -1;
			} else {
				timersub(&tv1, &tv2, &tv1);
				timeout = 1000 * tv1.tv_sec
						+ tv1.tv_usec / 1000;
			}
		}

'tv2' is the time when the protocol has returned from 'poll()'. Apparently, 
the code calculates the time difference between the expected, absolute expiry 
time ('tv1') and the current time ('tv2'). If the current time is greater, 
the code seems to assume that the timeout has elapsed. In this case, the code 
also assumes that the protocol negotiation phase (PROBE/ANNOUNCE) is over, 
and it may go into an infinte wait state where it would only defend the 
successfully acquired IP address if necessary. 

There are two problems with this:

a)
When the code is in the 'packet has arrived' branch, the timeout cannot have 
elapsed by definition of 'poll()'. Exceeding of the timeout leads into 
the 'timeout' branch.

b)
This code may erroneously assume that the timeout has elapsed, if an 
unexpected ARP packet arrives within the regular, finite timeout (still in 
the protocol negotiation phase), and the OS preempts the zcip code between 
the return of 'poll()' and the timeout recalculation above. When the code is 
reactivated by the scheduler, the timeout may indeed have elapsed, and 
'timercmp(&tv1, &tv2, <)' will hold true. In this case, the code will 
erroneously assume that the protocol negotiation phase is finished, and will 
set the subsequent timeout to -1 (infinite). zcip will seem to hang.


I can reproduce this problem here more or less consistently by executing zcip 
on more than one node concurrently, so that "unexpected" ARP request packets 
are received within the protocol negotiation phase.


I have fixed the problem by replacing 'poll()' with 'select()' and by 
simplifying the timeout recalculation (which is an obvious consequence of 
using 'select()'. 'select()# returns with the timeout parameter containing 
the remaining timeout value when it is woken up by a packet before the 
timeout has elapsed. This value can directly be used for re-invoking 
'select()'. Please note that this behavior is only implemented in Linux, but 
not in other Unices, so that my patch is not compatible with other Unices.

My patch, which applies to David Brownell's initial code posting on 
zeroconf-workers from 26/10/04, is attached.


Any comments???


Michael


-- 
============================================
Michael Schmidt
--------------------------------------------
Institute for Digital Communications Systems
University of Siegen, Germany
--------------------------------------------
http:   www.dcs.uni-siegen.de
e-mail: [email protected]
============================================

--Boundary-00=_qxVyBhPIbZesI/R
Content-Type: text/x-diff;
  charset="us-ascii";
  name="zcip.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="zcip.patch"

--- zcip.c.orig	2004-12-20 17:09:12.000000000 +0100
+++ zcip.c	2004-12-22 10:29:02.717064544 +0100
@@ -369,28 +369,28 @@
 	//  - use it
 	//  - defend it, within limits
 	while (1) {
-		struct pollfd fds[1];
-		struct timeval tv1;
+		fd_set rfds, efds;
+		struct timeval to, *pto;
 
-		fds[0].fd = fd;
-		fds[0].events = POLLIN | POLLERR;
-		fds[0].revents = 0;
+		FD_ZERO(&rfds);
+		FD_SET(fd, &rfds);
+		FD_ZERO(&efds);
+		FD_SET(fd, &efds);
 
 		// poll, being ready to adjust current timeout 
-		if (timeout > 0) {
-			gettimeofday(&tv1, NULL);
-			tv1.tv_usec += (timeout % 1000) * 1000;
-			if (tv1.tv_usec > 1000000) {
-				tv1.tv_usec -= 1000000;
-				tv1.tv_sec++;
-			}
-			tv1.tv_sec += timeout / 1000;
-		} else if (timeout == 0) {
+		if (timeout == 0) {
 			timeout = ms_rdelay(PROBE_WAIT);
 		}
 		VDBG("...wait %ld %s nprobes=%d, nclaims=%d\n",
 				timeout, intf, nprobes, nclaims);
-		switch (poll(fds, 1, timeout)) {
+		if (timeout == -1)
+			pto = NULL;
+		else {
+			to.tv_sec = timeout / 1000;
+			to.tv_usec = (timeout % 1000) * 1000;
+			pto = &to;
+		}
+		switch (select(fd + 1, &rfds, NULL, &efds, pto)) {
 
 		// timeouts trigger protocol transitions
 		case 0:
@@ -437,19 +437,10 @@
 		case 1:
 			// maybe adjust timeout
 			if (timeout > 0) {
-				struct timeval tv2;
-
-				gettimeofday(&tv2, NULL);
-				if (timercmp(&tv1, &tv2, <)) {
-					timeout = -1;
-				} else {
-					timersub(&tv1, &tv2, &tv1);
-					timeout = 1000 * tv1.tv_sec
-							+ tv1.tv_usec / 1000;
-				}
+				timeout = to.tv_sec * 1000 + to.tv_usec / 1000;
 			}
-			if ((fds[0].revents & POLLIN) == 0) {
-				if (fds[0].revents & POLLERR) {
+			if (FD_ISSET(fd, &rfds) == 0) {
+				if (FD_ISSET(fd, &efds) != 0) {
 					// FIXME: links routinely go down;
 					// this shouldn't necessarily exit.
 					fprintf(stderr, "%s %s: poll error\n",

--Boundary-00=_qxVyBhPIbZesI/R--


-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now. 
http://productguide.itmanagersjournal.com/