Subject: "default" outgoing address
To: None <tech-kern@netbsd.org>
From: Michael Graff <explorer@flame.org>
List: tech-kern
Date: 02/03/1999 22:00:57
I'd like to commit this soon, if noone has serious objections.
Please comment.  :)

In my environment at home and where my primary flame.org machines
live, each machine has a physical IP address (which by and large is
not reachable from the outside world) and a number of aliases.  These
aliases are reachable to the outside world.

The physical address of one machine for instance is
kechara.rc.vix.com, with an ifalias of kechara.flame.org.  I really
want all outgoing connections (unless bound to a specific port) to
originate from the alias, not the real interface address.

Here is a patch that allows me to:

	sysctl -w net.inet.ip.srcaddr=204.152.184.79

to set the default address.  Basically, if this value is set (it
defaults to 0.0.0.0, which is treated as "do the old thing") the
sections of code which find the "best" IP address to use based on
which interface the connection is going out on is disabled, and the
value set is used instead.

I'd like to commit this, as I know a few others who would like to use
it as well.

Other than the default address, there are other reasons this is
useful.  For one, I have several machines at home, one of which has a
4 port ethernet card.  Rather than blowing 4 addresses per
host-to-host port, I can use the private network numbers and make an
exported alias the default.  This means I can have 4 addresses to run
3 host-to-host ethernets rather than 12.

Here's the diff, from -current as of a day or two ago:

Index: sys/netinet/in.h
===================================================================
RCS file: /cvsroot/src/sys/netinet/in.h,v
retrieving revision 1.39
diff -u -r1.39 in.h
--- in.h	1998/09/14 21:15:56	1.39
+++ in.h	1999/02/04 03:44:32
@@ -297,7 +297,8 @@
 #define	IPCTL_ANONPORTMAX      11	/* maximum ephemeral port */
 #define	IPCTL_MTUDISCTIMEOUT   12	/* allow path MTU discovery */
 #define	IPCTL_MAXFLOWS         13	/* maximum ip flows allowed */
-#define	IPCTL_MAXID	       14
+#define IPCTL_SRCADDR	       14	/* default source address */
+#define	IPCTL_MAXID	       15
 
 #define	IPCTL_NAMES { \
 	{ 0, 0 }, \
@@ -314,12 +315,14 @@
 	{ "anonportmax", CTLTYPE_INT }, \
 	{ "mtudisctimeout", CTLTYPE_INT }, \
 	{ "maxflows", CTLTYPE_INT }, \
+	{ "srcaddr", CTLTYPE_STRUCT }, \
 }
 #endif /* !_XOPEN_SOURCE */
 
 
 #ifdef _KERNEL
 extern	struct in_addr zeroin_addr;
+extern	struct in_addr ip_srcaddr;
 
 int	in_broadcast __P((struct in_addr, struct ifnet *));
 int	in_canforward __P((struct in_addr));
Index: sys/netinet/in_pcb.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/in_pcb.c,v
retrieving revision 1.57
diff -u -r1.57 in_pcb.c
--- in_pcb.c	1998/12/19 02:46:12	1.57
+++ in_pcb.c	1999/02/04 03:44:32
@@ -96,6 +96,7 @@
 #include <netinet/ip_var.h>
 
 struct	in_addr zeroin_addr;
+struct	in_addr ip_srcaddr;
 
 #define	INPCBHASH_BIND(table, laddr, lport) \
 	&(table)->inpt_bindhashtbl[ \
@@ -288,6 +289,7 @@
 	struct in_ifaddr *ia;
 	struct sockaddr_in *ifaddr = NULL;
 	register struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *);
+	struct sockaddr_in lsin;
 	int error;
 
 	if (nam->m_len != sizeof (*sin))
@@ -330,22 +332,21 @@
 	if (in_nullhost(inp->inp_laddr)) {
 		register struct route *ro;
 
-		ia = (struct in_ifaddr *)0;
+		ia = NULL;
+
 		/* 
 		 * If route is known or can be allocated now,
 		 * our src addr is taken from the i/f, else punt.
 		 */
 		ro = &inp->inp_route;
 		if (ro->ro_rt &&
-		    (!in_hosteq(satosin(&ro->ro_dst)->sin_addr,
-			sin->sin_addr) || 
-		    inp->inp_socket->so_options & SO_DONTROUTE)) {
+		    (!in_hosteq(satosin(&ro->ro_dst)->sin_addr, sin->sin_addr)
+		     || inp->inp_socket->so_options & SO_DONTROUTE)) {
 			RTFREE(ro->ro_rt);
-			ro->ro_rt = (struct rtentry *)0;
+			ro->ro_rt = NULL;
 		}
 		if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 && /*XXX*/
-		    (ro->ro_rt == (struct rtentry *)0 ||
-		    ro->ro_rt->rt_ifp == (struct ifnet *)0)) {
+		    (ro->ro_rt == NULL || ro->ro_rt->rt_ifp == NULL)) {
 			/* No route yet, so try to acquire one */
 			ro->ro_dst.sa_family = AF_INET;
 			ro->ro_dst.sa_len = sizeof(struct sockaddr_in);
@@ -363,39 +364,54 @@
 		if (ro->ro_rt && !(ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK))
 			ia = ifatoia(ro->ro_rt->rt_ifa);
 		if (ia == 0) {
-		    u_int16_t fport = sin->sin_port;
-
-		    sin->sin_port = 0;
-		    ia = ifatoia(ifa_ifwithladdr(sintosa(sin)));
-		    sin->sin_port = fport;
-		    if (ia == 0)
-			/* Find 1st non-loopback AF_INET address */
-			for (ia = in_ifaddr.tqh_first ; ia != NULL ;
-				ia = ia->ia_list.tqe_next)
-			    if (!(ia->ia_ifp->if_flags & IFF_LOOPBACK))
-				break;
-		    if (ia == 0)
-			return (EADDRNOTAVAIL);
+			u_int16_t fport = sin->sin_port;
+			sin->sin_port = 0;
+			ia = ifatoia(ifa_ifwithladdr(sintosa(sin)));
+			sin->sin_port = fport;
+			if (ia == 0)
+				/* Find 1st non-loopback AF_INET address */
+				for (ia = in_ifaddr.tqh_first ; ia != NULL ;
+				     ia = ia->ia_list.tqe_next)
+					if (!(ia->ia_ifp->if_flags & IFF_LOOPBACK))
+						break;
+			if (ia == 0)
+				return (EADDRNOTAVAIL);
 		}
 		/*
-		 * If the destination address is multicast and an outgoing
-		 * interface has been set as a multicast option, use the
-		 * address of that interface as our source address.
+		 * If the destination address is multicast and an
+		 * outgoing interface has been set as a multicast
+		 * option, use the address of that interface as
+		 * our source address.
 		 */
 		if (IN_MULTICAST(sin->sin_addr.s_addr) &&
 		    inp->inp_moptions != NULL) {
 			struct ip_moptions *imo;
 			struct ifnet *ifp;
-
 			imo = inp->inp_moptions;
 			if (imo->imo_multicast_ifp != NULL) {
 				ifp = imo->imo_multicast_ifp;
-				IFP_TO_IA(ifp, ia);		/* XXX */
+				IFP_TO_IA(ifp, ia);	      /* XXX */
 				if (ia == 0)
 					return (EADDRNOTAVAIL);
 			}
 		}
+
 		ifaddr = satosin(&ia->ia_addr);
+
+#if 0
+		printf("src 1 %08x:%d dst %08x:%d, route %08x\n",
+		       (u_int32_t)inp->inp_laddr.s_addr, ntohs(inp->inp_lport),
+		       sin->sin_addr.s_addr, ntohs(sin->sin_port),
+		       ifaddr->sin_addr.s_addr);
+#endif
+
+		if ((in_nullhost(ip_srcaddr) == 0)
+		    && (sin->sin_addr.s_addr != INADDR_LOOPBACK)
+		    && (sin->sin_addr.s_addr != INADDR_ANY)) {
+			lsin.sin_addr = ip_srcaddr;
+			lsin.sin_port = 0;
+			ifaddr = &lsin;
+		}
 	}
 	if (in_pcblookup_connect(inp->inp_table, sin->sin_addr, sin->sin_port,
 	    !in_nullhost(inp->inp_laddr) ? inp->inp_laddr : ifaddr->sin_addr,
@@ -403,8 +419,7 @@
 		return (EADDRINUSE);
 	if (in_nullhost(inp->inp_laddr)) {
 		if (inp->inp_lport == 0) {
-			error = in_pcbbind(inp, (struct mbuf *)0,
-			    (struct proc *)0);
+			error = in_pcbbind(inp, NULL, NULL);
 			/*
 			 * This used to ignore the return value
 			 * completely, but we need to check for
Index: sys/netinet/ip_input.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/ip_input.c,v
retrieving revision 1.80
diff -u -r1.80 ip_input.c
--- ip_input.c	1999/01/19 23:39:57	1.80
+++ ip_input.c	1999/02/04 03:44:33
@@ -1498,6 +1498,46 @@
 		return (error);
 	    }
 #endif
+	case IPCTL_SRCADDR: {
+		struct sockaddr_in sin;
+		int s;
+
+		sin.sin_addr = ip_srcaddr;
+
+		error = sysctl_struct(oldp, oldlenp, newp, newlen,
+				      &sin.sin_addr, sizeof(sin.sin_addr));
+
+		if (error)
+			return (error);
+
+		s = splsoftnet();
+
+		/*
+		 * setting this to 0.0.0.0 will make the normal look-for-iface
+		 * logic work again, so allow that.
+		 */
+		if (in_nullhost(sin.sin_addr)) {
+			ip_srcaddr = sin.sin_addr;
+			splx(s);
+			return (0);
+		}
+
+#if 0
+		/*
+		 * search for an interface with this address
+		 */
+		if (ifa_ifwithladdr(sintosa(&sin)) == NULL) {
+			splx(s);
+			return (EADDRNOTAVAIL);
+		}
+#endif
+
+		ip_srcaddr = sin.sin_addr;
+		splx(s);
+
+		return (0);
+	}
+		
 	default:
 		return (EOPNOTSUPP);
 	}
Index: usr.sbin/sysctl/sysctl.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/sysctl/sysctl.c,v
retrieving revision 1.18
diff -u -r1.18 sysctl.c
--- sysctl.c	1998/11/13 20:56:21	1.18
+++ sysctl.c	1999/02/04 03:45:12
@@ -78,6 +78,8 @@
 #include <netinet/tcp_timer.h>
 #include <netinet/tcp_var.h>
 
+#include <arpa/inet.h>
+
 #include <err.h>
 #include <ctype.h>
 #include <errno.h>
@@ -130,6 +132,7 @@
 #define	CLOCK		0x00000001
 #define	BOOTTIME	0x00000002
 #define	CONSDEV		0x00000004
+#define IP_ADDRESS	0x00000008
 
 int main __P((int, char *[]));
 
@@ -229,6 +232,7 @@
 	void *newval = 0;
 	int intval, newsize = 0;
 	quad_t quadval;
+	struct in_addr ina;
 	size_t size;
 	struct list *lp;
 	int mib[CTL_MAXNAME];
@@ -333,10 +337,13 @@
 	case CTL_NET:
 		if (mib[1] == PF_INET) {
 			len = sysctl_inet(string, &bufp, mib, flags, &type);
+			if (len == 4 && mib[2] == IPPROTO_IP && mib[3] == IPCTL_SRCADDR)
+				special |= IP_ADDRESS;
 			if (len >= 0)
 				break;
 			return;
 		}
+
 		if (flags == 0)
 			return;
 		warnx("Use netstat to view %s information", string);
@@ -397,6 +404,17 @@
 			newval = &quadval;
 			newsize = sizeof quadval;
 			break;
+
+		case CTLTYPE_STRUCT:
+			if (special & IP_ADDRESS) {
+				if (inet_aton(newval, &ina) == 0)
+					errx(1, "Invalid IP address %s",
+					     (char *)newval);
+
+				newval = &ina;
+				newsize = sizeof(ina);
+			}
+			break;
 		}
 	}
 	size = BUFSIZ;
@@ -449,6 +467,28 @@
 			    devname(dev, S_IFCHR));
 		else
 			fprintf(stdout, "0x%x\n", dev);
+		return;
+	}
+	if (special & IP_ADDRESS) {
+		struct in_addr ina2;
+		char *ina2_s;
+
+		if (newsize == 0) {
+			if (!nflag)
+				fprintf(stdout, "%s = ", string);
+			ina2 = *(struct in_addr *)buf;
+			ina2_s = inet_ntoa(ina2);
+			fprintf(stdout, "%s\n", ina2_s);
+		} else {
+			ina2 = *(struct in_addr *)buf;
+			ina2_s = inet_ntoa(ina2);
+			fprintf(stdout, "%s: %s -> ", string, ina2_s);
+
+			ina2 = *(struct in_addr *)newval;
+			ina2_s = inet_ntoa(ina2);
+			fprintf(stdout, "%s\n", ina2_s);
+		}
+
 		return;
 	}
 	switch (type) {