Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 9 Nov 2000 19:51:57 +0100
From:      Thomas Moestl <tmoestl@gmx.net>
To:        freebsd-net@FreeBSD.ORG
Subject:   Re: setting source address for UDP packets
Message-ID:  <20001109195156.A951@forge.local>
In-Reply-To: <200011072156.QAA87433@khavrinen.lcs.mit.edu>; from wollman@khavrinen.lcs.mit.edu on Tue, Nov 07, 2000 at 04:56:23PM -0500
References:  <20001107214749.A2125@forge.local> <200011072156.QAA87433@khavrinen.lcs.mit.edu>

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

--EVF5PPMfhYS0aIcm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Tue, Nov 07, 2000 at 04:56:23PM -0500, Garrett Wollman wrote:
> > is there any way to set the source address of an UDP (over IPv4) packet
> > without using bind() or changing the socket state,
> 
> No.  This is a long-standing bug.  It shouldn't be too hard to fix if
> you're up to a bit of kernel hacking.

As followup to my recent post, here is my first version of a patch
to make UDP sendmsg() honor the IP_RECVDSTADDR ancillary data.

Please comment!

This is tested on a -stable box and works fine.
Unfortunately, I had no chance for testing this on a -current box
(I am right now building one, but my network connection is slow).
There does not seem to have changed very much in that particular
code, however.

The patch applies to only udp_output in netinet/udp_usrreq.c
For assigning the source address temporarily, in_pcbbind() is used if the
socket was not bound before, otherwise we assign the parameters and rehash.
Analog to the in_pcbconnect() handling, we lock with splnet() temporarily
while the opration is in progress. After we are done, we rebind using the old 
parameters.
Part of the ancillary data handling is taken from in6_output.c, so I assume
that it is correct (tolerable ?) to change m_len and m_data of an mbuf.
If it is not, please correct me!
This change might break programs that pass ancillary data of unknown
type. If this is a problem, we could silently ignore the options as we did
before.

Apropos: on certain error conditions, namely after a failing M_PREPEND,
in_pcbdisconnect() was not called even if a temporary in_pcbconnect() was 
issued before. Was that correct? I have changed this for now. 

As you can see, I tried to keep this simple even with the patch and
changed some more in the procedure for that, too. Please correct me in case of
errors, bad style or general failure to do The Right Thing. I am not
an experienced kernel hacker after all.
In particular, I do not know whether the fmbuf construct is good, or
if we rather should duplicate that portion of code.

Thanks for your patience,
Thomas

--EVF5PPMfhYS0aIcm
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="recvdstaddr-current.diff"

*** netinet/udp_usrreq.c.old	Thu Nov  9 19:05:13 2000
--- netinet/udp_usrreq.c	Thu Nov  9 19:23:54 2000
***************
*** 642,680 ****
  {
  	register struct udpiphdr *ui;
  	register int len = m->m_pkthdr.len;
  	struct in_addr laddr;
  	struct sockaddr_in *sin;
! 	int s = 0, error = 0;
! 
! 	if (control)
! 		m_freem(control);		/* XXX */
! 
  	if (len + sizeof(struct udpiphdr) > IP_MAXPACKET) {
  		error = EMSGSIZE;
  		goto release;
  	}
  
  	if (addr) {
  		sin = (struct sockaddr_in *)addr;
  		prison_remote_ip(p, 0, &sin->sin_addr.s_addr);
! 		laddr = inp->inp_laddr;
  		if (inp->inp_faddr.s_addr != INADDR_ANY) {
  			error = EISCONN;
! 			goto release;
  		}
  		/*
  		 * Must block input while temporarily connected.
  		 */
! 		s = splnet();
  		error = in_pcbconnect(inp, addr, p);
! 		if (error) {
! 			splx(s);
! 			goto release;
! 		}
  	} else {
  		if (inp->inp_faddr.s_addr == INADDR_ANY) {
  			error = ENOTCONN;
! 			goto release;
  		}
  	}
  	/*
--- 642,741 ----
  {
  	register struct udpiphdr *ui;
  	register int len = m->m_pkthdr.len;
+ 	register struct cmsghdr *cm = 0;
  	struct in_addr laddr;
  	struct sockaddr_in *sin;
! 	struct sockaddr_in src;
! 	int s = 0, error = 0, bound = 0, addrset = 0, fmbuf = 0;
! 	
  	if (len + sizeof(struct udpiphdr) > IP_MAXPACKET) {
  		error = EMSGSIZE;
+ 		if (control)
+ 			m_freem(control);
  		goto release;
  	}
  
+ 	if (control) {
+ 		/*
+ 		 * XXX: Currently, we assume all the optional information is stored
+ 		 * in a single mbuf.
+ 		 */
+ 	        if (control->m_next) 
+ 			error = EINVAL;
+ 		else {
+ 			for (; control->m_len; control->m_data += ALIGN(cm->cmsg_len),
+ 				     control->m_len -= ALIGN(cm->cmsg_len)) {
+ 				cm = mtod(control, struct cmsghdr *);
+ 				if (cm->cmsg_len == 0 || cm->cmsg_len > control->m_len) {
+ 					error = EINVAL;
+ 					break;
+ 				}
+ 				if (cm->cmsg_level != IPPROTO_IP)
+ 					continue;
+ 				
+ 				switch(cm->cmsg_type) {
+ 				case IP_RECVDSTADDR:
+ 					if (cm->cmsg_len != CMSG_LEN(sizeof(struct in_addr))) {
+ 						error = EINVAL;
+ 						break;
+ 					}
+ 					laddr = inp->inp_laddr;
+ 					bzero(&src, sizeof(src));
+ 					src.sin_family = AF_INET;
+ 					src.sin_port = inp->inp_lport;
+ 					src.sin_addr = *(struct in_addr *)CMSG_DATA(cm);
+ 					bound = 1;
+ 					s = splnet();
+ 					if (inp->inp_laddr.s_addr == INADDR_ANY && inp->inp_lport == 0) {
+ 						/* This will check the address */
+ 						error = in_pcbbind(inp, (struct sockaddr *)&src, p);
+ 					} else {
+ 						if (prison_ip(p, 0, &src.sin_addr.s_addr)) {
+ 							error = EINVAL;
+ 							break;
+ 						}
+ 						if (ifa_ifwithaddr((struct sockaddr *)&src) == 0) {
+ 							error = EADDRNOTAVAIL;
+ 							break;
+ 						}
+ 						inp->inp_laddr = src.sin_addr;
+ 						in_pcbrehash(inp);
+ 					}
+ 					break;
+ 				default:
+ 					error = ENOPROTOOPT;
+ 				}
+ 				if (error)
+ 					break;
+ 			}
+ 		}
+ 		m_freem(control);		/* XXX */
+ 		if (error)
+ 			goto unbind;
+ 	}
+ 
  	if (addr) {
  		sin = (struct sockaddr_in *)addr;
  		prison_remote_ip(p, 0, &sin->sin_addr.s_addr);
! 		if (!bound)
! 			laddr = inp->inp_laddr;
  		if (inp->inp_faddr.s_addr != INADDR_ANY) {
  			error = EISCONN;
! 			goto unbind;
  		}
  		/*
  		 * Must block input while temporarily connected.
  		 */
! 		addrset=1;
! 		if (!bound)
! 			s = splnet();
  		error = in_pcbconnect(inp, addr, p);
! 		if (error)
! 			goto unbind;
  	} else {
  		if (inp->inp_faddr.s_addr == INADDR_ANY) {
  			error = ENOTCONN;
! 			goto unbind;
  		}
  	}
  	/*
***************
*** 684,692 ****
  	M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT);
  	if (m == 0) {
  		error = ENOBUFS;
! 		if (addr)
! 			splx(s);
! 		goto release;
  	}
  
  	/*
--- 745,752 ----
  	M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT);
  	if (m == 0) {
  		error = ENOBUFS;
! 		/* XXX we did _not_ disconnect here before. Was that correct? Then back out! */
! 		goto disconnect;
  	}
  
  	/*
***************
*** 721,739 ****
  #ifdef IPSEC
  	ipsec_setsocket(m, inp->inp_socket);
  #endif /*IPSEC*/
  	error = ip_output(m, inp->inp_options, &inp->inp_route,
  	    (inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST)),
  	    inp->inp_moptions);
  
! 	if (addr) {
  		in_pcbdisconnect(inp);
  		inp->inp_laddr = laddr;	/* XXX rehash? */
- 		splx(s);
  	}
! 	return (error);
! 
  release:
! 	m_freem(m);
  	return (error);
  }
  
--- 781,807 ----
  #ifdef IPSEC
  	ipsec_setsocket(m, inp->inp_socket);
  #endif /*IPSEC*/
+ 	fmbuf = 1;
  	error = ip_output(m, inp->inp_options, &inp->inp_route,
  	    (inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST)),
  	    inp->inp_moptions);
  
! disconnect:
! 	if (addrset) {
  		in_pcbdisconnect(inp);
  		inp->inp_laddr = laddr;	/* XXX rehash? */
  	}
! unbind:
! 	if (bound) {
! 		/* restore old state */
! 		inp->inp_laddr = laddr;
! 		in_pcbrehash(inp);
! 	}
! 	if (addrset || bound)
! 		splx(s);
  release:
! 	if (!fmbuf)
! 		m_freem(m);
  	return (error);
  }
  

--EVF5PPMfhYS0aIcm--


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-net" in the body of the message




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