Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 14 Mar 2008 03:01:55 -0700
From:      Alfred Perlstein <alfred@freebsd.org>
To:        fs@freebsd.org
Cc:        ups@freebsd.org, jhb@freebsd.org
Subject:   nfs no longer reconnects for udp sockets
Message-ID:  <20080314100155.GW67856@elvis.mu.org>

next in thread | raw e-mail | index | archive | help

[-- Attachment #1 --]
Hey guys, someone was having issues with NFS mounts and
I happened to notice that it appears that the
"reconnect if socket went south" semantics I added a few
years ago were basically disabled by the nfs optimizations
added for "recv side processing".

The problem is as such:

You have an NFS mount on UDP.
Somehow the route goes bad.
The UDP socket is now "broken" as the route will remain
hosed forever.  This is particularly bad when an interface
flaps and loses its IP address as the UDP socket's route is
then set to nul or loopback or something and never gets fixed.
Your nfs mount goes dead even if the routing issues is
resolved (interface brought back up).

Please see attached patch.

Easy way to reproduce problem:

mount an nfs filesystem using UDP.
ifconfig interface down
try to access mount
ifconfig interface up
mount should still be dead.

Please review.

-- 
- Alfred Perlstein

[-- Attachment #2 --]
? cscope.out
? nfs.diff
Index: nfs_socket.c
===================================================================
RCS file: /home/ncvs/src/sys/nfsclient/nfs_socket.c,v
retrieving revision 1.125.2.18
diff -u -r1.125.2.18 nfs_socket.c
--- nfs_socket.c	17 Jan 2008 21:04:51 -0000	1.125.2.18
+++ nfs_socket.c	14 Mar 2008 09:56:50 -0000
@@ -498,8 +498,9 @@
 	while ((error = nfs_connect(nmp, rep)) != 0) {
 		if (error == ERESTART)
 			error = EINTR;
-		if (error == EIO || error == EINTR)
+		if (error == EIO || error == EINTR) {
 			return (error);
+		}
 		(void) tsleep(&lbolt, PSOCK, "nfscon", 0);
 	}
 
@@ -512,9 +513,11 @@
  	 * until the connection is established successfully, and 
  	 * then re-transmit the request.
  	 */
-	mtx_lock(&nmp->nm_nfstcpstate.mtx);
-	nmp->nm_nfstcpstate.flags &= ~NFS_TCP_FORCE_RECONNECT;
-	mtx_unlock(&nmp->nm_nfstcpstate.mtx);	
+	if (nmp->nm_sotype == SOCK_STREAM) {
+		mtx_lock(&nmp->nm_nfstcpstate.mtx);
+		nmp->nm_nfstcpstate.flags &= ~NFS_TCP_FORCE_RECONNECT;
+		mtx_unlock(&nmp->nm_nfstcpstate.mtx);
+	}
 
 	/*
 	 * Loop through outstanding request list and fix up all requests
@@ -624,17 +627,20 @@
 		 * Deal with errors for the client side.
 		 */
 		error2 = NFS_SIGREP(rep);
-		if (error2)
+		if (error2) {
 			error = error2;
-		else
+		} else {
 			rep->r_flags |= R_MUSTRESEND;
+		}
 
 		/*
 		 * Handle any recoverable (soft) socket errors here. (?)
 		 * Make EWOULDBLOCK a recoverable error, we'll rexmit from nfs_timer().
 		 */
-		if (error != EINTR && error != ERESTART && error != EIO && error != EPIPE)
+		if (error != EINTR && error != ERESTART && error != EIO &&
+		    error != EPIPE) {
 			error = 0;
+		}
 	}
 	return (error);
 }
@@ -700,9 +706,29 @@
 	mtx_lock(&nfs_reply_mtx);
 	while ((rep->r_mrep == NULL) && (error == 0) && 
 	       ((rep->r_flags & R_SOFTTERM) == 0) &&
-	       ((sotype == SOCK_DGRAM) || ((rep->r_flags & R_MUSTRESEND) == 0)))
+	       ((sotype == SOCK_DGRAM) || ((rep->r_flags & R_MUSTRESEND) == 0))) {
 		error = msleep((caddr_t)rep, &nfs_reply_mtx, 
 			       slpflag | (PZERO - 1), "nfsreq", 0);
+		/*
+		 * If the nfs_timer woke us (error == 0)
+		 * AND we're a DGRAM socket
+		 * AND there's been a send error
+		 * AND we're not having a soft timeout
+		 * THEN reconnect the socket to clear possible
+		 * issues with the socket, most likely a bad cached
+		 * route due to an interface flap.
+		 */
+		if (error == 0 && sotype == SOCK_DGRAM &&
+		    (rep->r_flags & R_RESENDERR) != 0 &&
+		    (rep->r_flags & R_SOFTTERM) == 0) {
+			error = nfs_sndlock(rep);
+			if (error != 0) {
+				break;
+			}
+			error = nfs_reconnect(rep);
+			nfs_sndunlock(rep);
+		}
+	}
 	mtx_unlock(&nfs_reply_mtx);
 	if (error == EINTR || error == ERESTART)
 		/* NFS operations aren't restartable. Map ERESTART to EINTR */
@@ -719,8 +745,20 @@
 			if (error)
 				return (error);
 			goto tryagain;
-		} else
+		} else {
 			mtx_unlock(&rep->r_nmp->nm_nfstcpstate.mtx);
+		}
+	} else {
+		if (rep->r_flags & R_RESENDERR) {
+			error = nfs_sndlock(rep);
+			if (error)
+				return (error);
+			error = nfs_reconnect(rep);
+			if (error) {
+				nfs_sndunlock(rep);
+				return (error);
+			}
+		}
 	}
 	return (error);
 }
@@ -1392,10 +1430,24 @@
 			    error = (*so->so_proto->pr_usrreqs->pru_send)
 				    (so, 0, m, nmp->nm_nam, NULL, curthread);
 			mtx_lock(&nfs_reqq_mtx);
+			/*
+			 * If we get a send error, wakeup the waiter
+			 * so they they may reconnect the socket,
+			 * this is NEEDED for SOCK_DGRAM because
+			 * when an interface flaps the socket's cached
+			 * route may be set to something bogus.
+			 *
+			 * The easiest way to recover from this is to
+			 * have the waiter (in nfs_reply) do a reconnect.
+			 *
+			 * Please don't break this, it's annoying.
+			 * -Alfred
+			 */
 			if (error) {
 				if (NFSIGNORE_SOERROR(nmp->nm_soflags, error))
 					so->so_error = 0;
 				rep->r_flags |= R_RESENDERR;
+				wakeup_nfsreq(rep);
 			} else {
 				/*
 				 * Iff first send, start timing

Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20080314100155.GW67856>