Subject: Re: "default" outgoing address
To: Michael Graff <explorer@flame.org>
From: Paul Goyette <paul@whooppee.com>
List: tech-kern
Date: 02/04/1999 03:36:17
Michael,

I think it is a useful addition, but wouldn't it make more sense to set 
the "default outgoing adress" on an interface-by-interface basis?  I
don't know how you could/would add the interface name to the sysctl
string, but something like

	sysctl -w net.inet.ip.<ifname-or-ifindex>.srcaddr=....

would make more sense to me.  

And of course, I'm hoping that you will verify that the specified
address is valid, ie it is assigned to an interface.  If you do_
manage to make the sysctl work on an interface-specific basis, I
do _not_ think that you should insist that the address provided be
assigned to the specific interface;  you should be able to set any
local address as the default outgoing address regardless of interface.
In particular, you should be able to specify that "the default
outgoing address for ep0 is w.x.y.z where w.x.y.z is an alias for
the loopback interface lo0."

On 3 Feb 1999, Michael Graff wrote:

> 
> 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) {
> 

-----------------------------------------------------------------------------
| Paul Goyette      | PGP DSS Key fingerprint:   | E-mail addresses:        |
| Network Engineer  |   BCD7 5301 9513 58A6 0DBC |  paul@whooppee.com       |
| and kernel hacker |   91EB ADB1 A280 3B79 9221 |  paul.goyette@ascend.com |
-----------------------------------------------------------------------------