kern/38554: changing interface ipaddress doesn't seem to work

Bruce M Simpson bms at spc.org
Sat Jul 3 01:09:05 PDT 2004


Please review the attached patch (which is a reworking of Archie's
patch for -CURRENT). When the underlying IP address is changed,
wildcard-bound UDP sockets which are temporarily bound locally for
a sendto() (by userland apps such as ntp, syslogd etc) will begin
using the new IP address, whilst apps using TCP (ssh, Quagga bgpd) will
error out with EADDRINUSE.

I would appreciate any feedback on our adopting this behaviour (which
strikes me as similar to that of Solaris and a few other OSes).

BMS
-------------- next part --------------
Index: in.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/in.c,v
retrieving revision 1.75
diff -u -p -r1.75 in.c
--- in.c	7 Apr 2004 20:46:13 -0000	1.75
+++ in.c	3 Jul 2004 07:40:19 -0000
@@ -420,6 +420,11 @@ in_control(so, cmd, data, ifp, td)
 		 */
 		in_ifadown(&ia->ia_ifa, 1);
 		/*
+		 * Mark the interface address as no longer valid.
+		 * Sockets that are bound to it should notice.
+		 */
+		ia->ia_ifa.ifa_flags |= RTF_REJECT;
+		/*
 		 * XXX horrible hack to detect that we are being called
 		 * from if_detach()
 		 */
Index: in_pcb.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/in_pcb.c,v
retrieving revision 1.150
diff -u -p -r1.150 in_pcb.c
--- in_pcb.c	16 Jun 2004 10:02:36 -0000	1.150
+++ in_pcb.c	3 Jul 2004 07:47:54 -0000
@@ -53,6 +53,7 @@
 #include <net/if.h>
 #include <net/if_types.h>
 #include <net/route.h>
+#include <net/net_osdep.h>
 
 #include <netinet/in.h>
 #include <netinet/in_pcb.h>
@@ -228,14 +229,17 @@ in_pcbbind(inp, nam, cred)
 	anonport = inp->inp_lport == 0 && (nam == NULL ||
 	    ((struct sockaddr_in *)nam)->sin_port == 0);
 	error = in_pcbbind_setup(inp, nam, &inp->inp_laddr.s_addr,
-	    &inp->inp_lport, cred);
+	    &inp->inp_lport, &inp->inp_locia, cred);
 	if (error)
 		return (error);
 	if (in_pcbinshash(inp) != 0) {
 		inp->inp_laddr.s_addr = INADDR_ANY;
 		inp->inp_lport = 0;
+		inp->inp_locia = NULL;
 		return (EAGAIN);
 	}
+	if (inp->inp_locia != NULL)
+		IFAREF(&inp->inp_locia->ia_ifa);
 	if (anonport)
 		inp->inp_flags |= INP_ANONPORT;
 	return (0);
@@ -251,17 +255,19 @@ in_pcbbind(inp, nam, cred)
  * On error, the values of *laddrp and *lportp are not changed.
  */
 int
-in_pcbbind_setup(inp, nam, laddrp, lportp, cred)
+in_pcbbind_setup(inp, nam, laddrp, lportp, iap, cred)
 	struct inpcb *inp;
 	struct sockaddr *nam;
 	in_addr_t *laddrp;
 	u_short *lportp;
+	struct in_ifaddr **iap;
 	struct ucred *cred;
 {
 	struct socket *so = inp->inp_socket;
 	unsigned short *lastport;
 	struct sockaddr_in *sin;
 	struct inpcbinfo *pcbinfo = inp->inp_pcbinfo;
+	struct in_ifaddr *ia = NULL;
 	struct in_addr laddr;
 	u_short lport = 0;
 	int wild = 0, reuseport = (so->so_options & SO_REUSEPORT);
@@ -312,7 +318,8 @@ in_pcbbind_setup(inp, nam, laddrp, lport
 		} else if (sin->sin_addr.s_addr != INADDR_ANY) {
 			sin->sin_port = 0;		/* yech... */
 			bzero(&sin->sin_zero, sizeof(sin->sin_zero));
-			if (ifa_ifwithaddr((struct sockaddr *)sin) == 0)
+			if ((ia = (struct in_ifaddr *)ifa_ifwithaddr(
+			    (struct sockaddr *)sin)) == 0)
 				return (EADDRNOTAVAIL);
 		}
 		laddr = sin->sin_addr;
@@ -451,6 +458,8 @@ in_pcbbind_setup(inp, nam, laddrp, lport
 		return (EINVAL);
 	*laddrp = laddr.s_addr;
 	*lportp = lport;
+	if (iap != NULL)
+		*iap = ia;
 	return (0);
 }
 
@@ -468,13 +477,14 @@ in_pcbconnect(inp, nam, cred)
 {
 	u_short lport, fport;
 	in_addr_t laddr, faddr;
+	struct in_ifaddr *locia;
 	int anonport, error;
 
 	lport = inp->inp_lport;
 	laddr = inp->inp_laddr.s_addr;
 	anonport = (lport == 0);
 	error = in_pcbconnect_setup(inp, nam, &laddr, &lport, &faddr, &fport,
-	    NULL, cred);
+	    NULL, &locia, cred);
 	if (error)
 		return (error);
 
@@ -492,6 +502,9 @@ in_pcbconnect(inp, nam, cred)
 	/* Commit the remaining changes. */
 	inp->inp_lport = lport;
 	inp->inp_laddr.s_addr = laddr;
+	inp->inp_locia = locia;
+	if (inp->inp_locia != NULL)
+		IFAREF(&inp->inp_locia->ia_ifa);
 	inp->inp_faddr.s_addr = faddr;
 	inp->inp_fport = fport;
 	in_pcbrehash(inp);
@@ -509,7 +522,9 @@ in_pcbconnect(inp, nam, cred)
  * On entry, *laddrp and *lportp should contain the current local
  * address and port for the PCB; these are updated to the values
  * that should be placed in inp_laddr and inp_lport to complete
- * the connect.
+ * the connect. If iap is not NULL, *iap is set to the interface
+ * address corresponding to *laddrp, if any, but no new reference
+ * to it has been added.
  *
  * On success, *faddrp and *fportp will be set to the remote address
  * and port. These are not updated in the error case.
@@ -520,7 +535,7 @@ in_pcbconnect(inp, nam, cred)
  * is set to NULL.
  */
 int
-in_pcbconnect_setup(inp, nam, laddrp, lportp, faddrp, fportp, oinpp, cred)
+in_pcbconnect_setup(inp, nam, laddrp, lportp, faddrp, fportp, oinpp, iap, cred)
 	register struct inpcb *inp;
 	struct sockaddr *nam;
 	in_addr_t *laddrp;
@@ -528,10 +543,11 @@ in_pcbconnect_setup(inp, nam, laddrp, lp
 	in_addr_t *faddrp;
 	u_short *fportp;
 	struct inpcb **oinpp;
+	struct in_ifaddr **iap;
 	struct ucred *cred;
 {
 	struct sockaddr_in *sin = (struct sockaddr_in *)nam;
-	struct in_ifaddr *ia;
+	struct in_ifaddr *ia = NULL;
 	struct sockaddr_in sa;
 	struct ucred *socred;
 	struct inpcb *oinp;
@@ -558,7 +574,7 @@ in_pcbconnect_setup(inp, nam, laddrp, lp
 		sa.sin_len = sizeof(sa);
 		sa.sin_family = AF_INET;
 		error = in_pcbbind_setup(inp, (struct sockaddr *)&sa,
-		    &laddr.s_addr, &lport, cred);
+		    &laddr.s_addr, &lport, &ia, cred);
 		if (error)
 			return (error);
 	}
@@ -648,7 +664,7 @@ in_pcbconnect_setup(inp, nam, laddrp, lp
 	}
 	if (lport == 0) {
 		error = in_pcbbind_setup(inp, NULL, &laddr.s_addr, &lport,
-		    cred);
+		    &ia, cred);
 		if (error)
 			return (error);
 	}
@@ -656,6 +672,8 @@ in_pcbconnect_setup(inp, nam, laddrp, lp
 	*lportp = lport;
 	*faddrp = faddr.s_addr;
 	*fportp = fport;
+	if (iap != NULL)
+		*iap = ia;
 	return (0);
 }
 
@@ -694,6 +712,8 @@ in_pcbdetach(inp)
 		so->so_pcb = 0;
 		sotryfree(so);
 	}
+	if (inp->inp_locia != NULL)
+		IFAFREE(&inp->inp_locia->ia_ifa);
 	if (inp->inp_options)
 		(void)m_free(inp->inp_options);
 	ip_freemoptions(inp->inp_moptions);
Index: in_pcb.h
===================================================================
RCS file: /home/ncvs/src/sys/netinet/in_pcb.h,v
retrieving revision 1.73
diff -u -p -r1.73 in_pcb.h
--- in_pcb.h	24 Jun 2004 02:01:48 -0000	1.73
+++ in_pcb.h	3 Jul 2004 07:49:32 -0000
@@ -71,6 +71,7 @@ struct in_addr_4in6 {
 struct in_endpoints {
 	u_int16_t	ie_fport;		/* foreign port */
 	u_int16_t	ie_lport;		/* local port */
+	struct in_ifaddr *ie_locia;		/* locally bound address */
 	/* protocol dependent part, local and foreign addr */
 	union {
 		/* foreign host table entry */
@@ -102,6 +103,7 @@ struct in_conninfo {
 #define inc_isipv6	inc_flags	/* temp compatability */
 #define	inc_fport	inc_ie.ie_fport
 #define	inc_lport	inc_ie.ie_lport
+#define	inc_locia	inc_ie.ie_locia
 #define	inc_faddr	inc_ie.ie_faddr
 #define	inc_laddr	inc_ie.ie_laddr
 #define	inc6_faddr	inc_ie.ie6_faddr
@@ -142,6 +144,7 @@ struct inpcb {
 	} inp_depend4;
 #define inp_fport	inp_inc.inc_fport
 #define inp_lport	inp_inc.inc_lport
+#define	inp_locia	inp_inc.inc_locia
 #define	inp_faddr	inp_inc.inc_faddr
 #define	inp_laddr	inp_inc.inc_laddr
 #define	inp_ip_tos	inp_depend4.inp4_ip_tos
@@ -340,6 +343,8 @@ struct inpcbinfo {		/* XXX documentation
 #define	INP_CHECK_SOCKAF(so, af) 	(INP_SOCKAF(so) == af)
 
 #ifdef _KERNEL
+struct in_ifaddr;
+
 extern int	ipport_lowfirstauto;
 extern int	ipport_lowlastauto;
 extern int	ipport_firstauto;
@@ -351,11 +356,11 @@ void	in_pcbpurgeif0(struct inpcbinfo *, 
 int	in_pcballoc(struct socket *, struct inpcbinfo *, const char *);
 int	in_pcbbind(struct inpcb *, struct sockaddr *, struct ucred *);
 int	in_pcbbind_setup(struct inpcb *, struct sockaddr *, in_addr_t *,
-	    u_short *, struct ucred *);
+	    u_short *, struct in_ifaddr **, struct ucred *);
 int	in_pcbconnect(struct inpcb *, struct sockaddr *, struct ucred *);
 int	in_pcbconnect_setup(struct inpcb *, struct sockaddr *, in_addr_t *,
 	    u_short *, in_addr_t *, u_short *, struct inpcb **,
-	    struct ucred *);
+	    struct in_ifaddr **, struct ucred *);
 void	in_pcbdetach(struct inpcb *);
 void	in_pcbdisconnect(struct inpcb *);
 int	in_pcbinshash(struct inpcb *);
Index: tcp_output.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_output.c,v
retrieving revision 1.95
diff -u -p -r1.95 tcp_output.c
--- tcp_output.c	23 Jun 2004 21:04:37 -0000	1.95
+++ tcp_output.c	3 Jul 2004 07:40:19 -0000
@@ -51,12 +51,15 @@
 #include <sys/sysctl.h>
 
 #include <net/route.h>
+#include <net/if.h>
+#include <net/if_var.h>
 
 #include <netinet/in.h>
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/in_pcb.h>
 #include <netinet/ip_var.h>
+#include <netinet/in_var.h>
 #ifdef INET6
 #include <netinet6/in6_pcb.h>
 #include <netinet/ip6.h>
@@ -804,6 +807,16 @@ send:
       }
 
 	/*
+	 * Check that our local (source) IP address is still valid.
+	 */
+	if (tp->t_inpcb->inp_locia != NULL
+	    && (tp->t_inpcb->inp_locia->ia_ifa.ifa_flags & RTF_REJECT) != 0) {
+		error = EADDRNOTAVAIL;
+		m_freem(m);
+		goto out;
+	}
+
+	/*
 	 * Fill in fields, remembering maximum advertised
 	 * window for use in delaying messages about window sizes.
 	 * If resending a FIN, be sure not to use a new sequence number.
Index: tcp_usrreq.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/tcp_usrreq.c,v
retrieving revision 1.103
diff -u -p -r1.103 tcp_usrreq.c
--- tcp_usrreq.c	26 Jun 2004 17:50:50 -0000	1.103
+++ tcp_usrreq.c	3 Jul 2004 07:51:41 -0000
@@ -51,6 +51,7 @@
 #include <sys/jail.h>
 
 #include <net/if.h>
+#include <net/net_osdep.h>
 #include <net/route.h>
 
 #include <netinet/in.h>
@@ -817,6 +818,7 @@ tcp_connect(tp, nam, td)
 	struct socket *so = inp->inp_socket;
 	struct tcptw *otw;
 	struct rmxp_tao tao;
+	struct in_ifaddr *locia;
 	struct in_addr laddr;
 	u_short lport;
 	int error;
@@ -836,8 +838,10 @@ tcp_connect(tp, nam, td)
 	 */
 	laddr = inp->inp_laddr;
 	lport = inp->inp_lport;
+	locia = inp->inp_locia;
 	error = in_pcbconnect_setup(inp, nam, &laddr.s_addr, &lport,
-	    &inp->inp_faddr.s_addr, &inp->inp_fport, &oinp, td->td_ucred);
+	    &inp->inp_faddr.s_addr, &inp->inp_fport, &oinp, &locia,
+	    td->td_ucred);
 	if (error && oinp == NULL)
 		return error;
 	if (oinp) {
@@ -852,6 +856,11 @@ tcp_connect(tp, nam, td)
 			return EADDRINUSE;
 	}
 	inp->inp_laddr = laddr;
+	if (inp->inp_locia != NULL)
+		IFAFREE(&inp->inp_locia->ia_ifa);
+	inp->inp_locia = locia;
+	if (inp->inp_locia != NULL)
+		IFAREF(&inp->inp_locia->ia_ifa);
 	in_pcbrehash(inp);
 
 	/* Compute window scaling to request.  */
Index: udp_usrreq.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/udp_usrreq.c,v
retrieving revision 1.156
diff -u -p -r1.156 udp_usrreq.c
--- udp_usrreq.c	26 Jun 2004 19:10:39 -0000	1.156
+++ udp_usrreq.c	3 Jul 2004 07:52:53 -0000
@@ -724,6 +724,7 @@ udp_output(inp, m, addr, control, td)
 {
 	register struct udpiphdr *ui;
 	register int len = m->m_pkthdr.len;
+	struct in_ifaddr *locia;
 	struct in_addr faddr, laddr;
 	struct cmsghdr *cm;
 	struct sockaddr_in *sin, src;
@@ -792,13 +793,14 @@ udp_output(inp, m, addr, control, td)
 		goto release;
 	laddr = inp->inp_laddr;
 	lport = inp->inp_lport;
+	locia = inp->inp_locia;
 	if (src.sin_addr.s_addr != INADDR_ANY) {
 		if (lport == 0) {
 			error = EINVAL;
 			goto release;
 		}
 		error = in_pcbbind_setup(inp, (struct sockaddr *)&src,
-		    &laddr.s_addr, &lport, td->td_ucred);
+		    &laddr.s_addr, &lport, &locia, td->td_ucred);
 		if (error)
 			goto release;
 	}
@@ -812,7 +814,7 @@ udp_output(inp, m, addr, control, td)
 			goto release;
 		}
 		error = in_pcbconnect_setup(inp, addr, &laddr.s_addr, &lport,
-		    &faddr.s_addr, &fport, NULL, td->td_ucred);
+		    &faddr.s_addr, &fport, NULL, &locia, td->td_ucred);
 		if (error)
 			goto release;
 
@@ -835,6 +837,15 @@ udp_output(inp, m, addr, control, td)
 			goto release;
 		}
 	}
+
+	/*
+	 * Check that the local (source) IP address is valid.
+	 */
+	if (locia != NULL && (locia->ia_ifa.ifa_flags & RTF_REJECT) != 0) {
+		error = EADDRNOTAVAIL;
+		goto release;
+	}
+
 	/*
 	 * Calculate data length and get a mbuf
 	 * for UDP and IP headers.
@@ -1054,6 +1065,10 @@ udp_disconnect(struct socket *so)
 	s = splnet();
 	in_pcbdisconnect(inp);
 	inp->inp_laddr.s_addr = INADDR_ANY;
+	if (inp->inp_locia != NULL) {
+		IFAFREE(&inp->inp_locia->ia_ifa);
+		inp->inp_locia = NULL;
+	}
 	INP_UNLOCK(inp);
 	INP_INFO_WUNLOCK(&udbinfo);
 	splx(s);


More information about the freebsd-net mailing list