[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

(usagi-users 02118) Re: multiproto bind() ?



On Sun, 19 Jan 2003, Abdul Basit wrote:

> hey,
>
>  i am trying to bind on all available ipv4 and ipv6 addresses.
> i tried,

this is a very insidious problem (provided you want to do this with a
good level of protability) we had to solve working on the last version of
netcat6 (0.3.0 - which should be released at the end of next week).

here's the algorithm we have used:

- call to getaddrinfo
- reorder the struct addrinfo list returned by getaddrinfo in order to
  have ipv6 addresses first (because we have to support also systems
  with broken bind(2) implementations that do not support IPV6_V6ONLY -
  read the comment included in the code below)
- for each addrinfo struct returned by getaddrinfo do:
  - skip structure if it:
    1) is not SOCK_DGRAM or SOCK_STREAM, or
    2) is an ipv4-mapped address (altough this could be considered a
       somewhat buggy behaviour), or
    3) is an ipv6 address and ipv6 support was disabled at compile time
  - create a socket and set appropriate socket options
  - bind the socket to the specified address (don't print errors due to
    broken bind(2) behaviour)
  - add the socket to a list of bound sockets
- call select on the bound sockets file descriptors (the program stops
  until we have a connection attempt on a bound socket)

here you can take a look at the code (taken from netcat6, file
src/network.c):


void do_listen_continuous(const connection_attributes* attrs,
                          listen_callback callback, void* cdata, int max_accept)
{
	[...]

	/* initialize accept_fdset */
	FD_ZERO(&accept_fdset);

	/* setup hints structure to be passed to getaddrinfo */
	memset(&hints, 0, sizeof(hints));
	ca_to_addrinfo(&hints, attrs);

	hints.ai_flags = AI_PASSIVE;
	if (numeric_mode == TRUE)
		hints.ai_flags |= AI_NUMERICHOST;

	/* get the IP address of the local end of the connection */
	err = getaddrinfo(local->address, local->service, &hints, &res);
	if (err != 0) {
		fatal(_("forward host lookup failed "
		      "for local endpoint %s (%s): %s"),
		      local->address? local->address : _("[unspecified]"),
		      local->service, gai_strerror(err));
	}

	/* check the results of getaddrinfo */
	assert(res != NULL);

#ifdef ENABLE_IPV6
	/*
	 * Some systems (notably Linux) with a shared stack for ipv6 and ipv4,
	 * have a broken bind(2) implementation. In these systems, binding an
	 * ipv6 socket to ipv6_addr_any (::) will also bind the socket to
	 * the ipv4 unspecified address (0.0.0.0).
	 *
	 * So when listening on ipv6_addr_any (::) accept(2) will return also
	 * ipv4 connection attemptes, as ipv4 mapped ipv6 addresses.
	 *
	 * This is a problem, since when called with AF_UNSPEC and AI_PASSIVE,
	 * getaddrinfo will return results for both ipv6 AND ipv4, but if we
	 * try to bind on the ipv4 unspecified address (0.0.0.0) AFTER we have
	 * bound our socket to ipv6_addr_any (::), the syscall bind(2) will
	 * fail and return -1, setting errno to EADDRINUSE.
	 *
	 * The following algorithm is used to work around this error to some
	 * degree:
	 *
	 * Ensure binds to IPv6 addresses are attempted before IPv4 addresses.
	 * On broken systems where IPV6_V6ONLY is not defined or the setsockopt
	 * fails:
	 *
	 *   - Keep track of whether an IPv6 socket has been bound to
	 *     in6_addr_any.
	 *   - If a bind to IPv4 fails with EADDRINUSE and an IPv6 socket
	 *     has been bound, then just ignore the error.
	 */

	/*
	 * TODO: instead of reordering the results, just loop through the
	 * results twice - once for ipv6 then for the rest.
	 */
	res = order_ipv6_first(res);
#endif

	/* try binding to all of the addresses returned by getaddrinfo */
	nfd = 0;
	for (ptr = res; ptr != NULL; ptr = ptr->ai_next) {

		if (skip_address(ptr) == TRUE) continue;

		/* create the socket */
		fd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
		if (fd < 0) {
			/* ignore this address if it is not supported */
			if (unsupported_sock_error(errno))
				continue;
			fatal(_("cannot create the socket: %s"),
			      strerror(errno));
		}

#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY)
		if (ptr->ai_family == PF_INET6) {
			int on = 1;
			/* in case of error, we will go on anyway... */
			err = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
			                 &on, sizeof(on));
			if (err < 0)
				warn(_("error with sockopt IPV6_V6ONLY"));
			else
				set_ipv6_only = TRUE;
		}
#endif

		/* set the reuse address socket option */
		if (dont_reuse_addr == FALSE) {
			int on = 1;
			/* in case of error, we will go on anyway... */
			err = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
			                 &on, sizeof(on));
			if (err < 0) warn(_("error with sockopt SO_REUSEADDR"));
		}

		/* disable the nagle option for TCP sockets */
		if (disable_nagle == TRUE  && ptr->ai_protocol == IPPROTO_TCP) {
			int on = 1;
			/* in case of error, we will go on anyway... */
			err = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
			                 &on, sizeof(on));
			if (err < 0)
				warn(_("error with sockopt TCP_NODELAY"));
		}

		/* get the numeric name for this source address */
		getnameinfo_ex(ptr->ai_addr, ptr->ai_addrlen, name_buf,
		               sizeof(name_buf), TRUE);

		/* bind to the local address */
		err = bind(fd, ptr->ai_addr, ptr->ai_addrlen);
		if (err != 0) {
#ifdef ENABLE_IPV6
			/* suppress ADDRINUSE error for systems that double
			 * bind ipv6 and ipv4 and pretend it succeeded */
			if (errno == EADDRINUSE &&
			    ptr->ai_family == PF_INET &&
			    set_ipv6_only == FALSE &&
			    bound_ipv6_any == TRUE)
			{
				if (verbose_mode == TRUE)
					warn(_("listening on %s ..."),name_buf);
				close(fd);
				continue;
			}
#endif
			warn(_("bind to source %s failed: %s"),
			     name_buf, strerror(errno));
			close(fd);
			continue;
		}

		/* for stream based sockets, we need to listen for incoming
		 * connections. the backlog parameter is set to 5 for backward
		 * compatibility (it seems that at least some BSD-derived
		 * system limit the backlog parameter to this value). */
		if (ptr->ai_socktype == SOCK_STREAM) {
			err = listen(fd, 5);
			if (err != 0)
				fatal(_("cannot listen on %s: %s"),
				      name_buf, strerror(errno));
		}

		if (verbose_mode == TRUE)
			warn(_("listening on %s ..."), name_buf);

#ifdef ENABLE_IPV6
		/* check if this was an IPv6 socket bound to IN6_ADDR_ANY */
		if (ptr->ai_family == PF_INET6 &&
		    memcmp(&((struct sockaddr_in6*)(ptr->ai_addr))->sin6_addr,
		           &in6addr_any, sizeof(struct in6_addr)) == 0) {
			bound_ipv6_any = TRUE;
		}
#endif

		/* add fd to bound_sockets (just add to the head of the list) */
		bound_sockets =	add_bound_socket(bound_sockets, fd,
				                 ptr->ai_socktype);

		/* add fd to accept_fdset */
		FD_SET(fd, &accept_fdset);
		maxfd = MAX(maxfd, fd);
		nfd++;
	}

	freeaddrinfo(res);

	if (nfd == 0)
		fatal(_("failed to bind to any local addr/port"));

	/* enter into the accept loop */
 	for (;;) {
		fd_set tmp_ap_fdset;
		struct timeval tv, *tvp = NULL;
		struct sockaddr_storage dest;
		socklen_t destlen;
		int ns, socktype;
		char c_name_buf[AI_STR_SIZE];

		/* make a copy of accept_fdset before passing to select */
		memcpy(&tmp_ap_fdset, &accept_fdset, sizeof(fd_set));

		/* setup timeout */
		if (ca_connect_timeout(attrs) > 0) {
			tv.tv_sec = (time_t)ca_connect_timeout(attrs);
			tv.tv_usec = 0;
			tvp = &tv;
		}

		/* wait for an incoming connection */
		err = select(maxfd + 1, &tmp_ap_fdset, NULL, NULL, tvp);

		if (err == 0)
			fatal(_("connection timed out"));

		if (err < 0) {
			if (errno == EINTR) continue;
			fatal(_("select error: %s"), strerror(errno));
		}

		/* find the ready filedescriptor */
		for (fd = 0; fd <= maxfd && !FD_ISSET(fd, &tmp_ap_fdset); ++fd)
			;

		/* if none were ready, loop to select again */
		if (fd > maxfd)
			continue;

		/* find socket type in bound_sockets */
		socktype = find_bound_socket(bound_sockets, fd);

		destlen = sizeof(dest);

		/* for stream sockets we accept a new connection, whereas for
		 * dgram sockets we use MSG_PEEK to determine the sender */
		if (socktype == SOCK_STREAM) {
			ns = accept(fd, (struct sockaddr *)&dest, &destlen);
			if (ns < 0)
				fatal(_("cannot accept connection: %s"),
				      strerror(errno));
		} else {
			/* this is checked when binding listen sockets */
			assert(socktype == SOCK_DGRAM);

			err = recvfrom(fd, NULL, 0, MSG_PEEK,
			               (struct sockaddr*)&dest, &destlen);
			if (err < 0)
				fatal(_("cannot recv from socket: %s"),
				      strerror(errno));

			ns = dup(fd);
			if (ns < 0)
				fatal(_("cannot duplicate "
				      "file descriptor %d: %s"),
				      fd, strerror(errno));
		}

		[...]

		/* check if connections from this client are allowed */
		if ((remote == NULL) ||
		    (remote->address == NULL && remote->service == NULL) ||
		    (is_allowed((struct sockaddr*)&dest,remote,attrs) == TRUE))
		{

			if (socktype == SOCK_DGRAM) {
				/* connect the socket to ensure we only talk
				 * with this client */
				err = connect(ns, (struct sockaddr*)&dest,
				              destlen);
				if (err != 0)
					fatal(_("cannot connect "
					      "datagram socket: %s"),
					      strerror(errno));
			}

			if (verbose_mode == TRUE) {
				warn(_("connect to %s from %s"),
				     name_buf, c_name_buf);
			}

			callback(ns, socktype, cdata);

			if (max_accept > 0 && --max_accept == 0)
				break;
		} else {
			if (socktype == SOCK_DGRAM) {
				/* the connection wasn't accepted -
				 * remove the queued packet */
				recvfrom(ns, NULL, 0, 0, NULL, 0);
			}
			close(ns);

			if (verbose_mode == TRUE) {
				warn(_("refused connect to %s from %s"),
				     name_buf, c_name_buf);
			}
		}
	}

	/* close the listening sockets */
	for (i = 0; i <= maxfd; ++i) {
		if (FD_ISSET(i, &accept_fdset) != 0) close(i);
	}

	/* free the bound_socket list */
	destroy_bound_sockets(bound_sockets);
}

[...]

static bool skip_address(const struct addrinfo *ai)
{
	assert(ai != NULL);

	/* only use socktypes we can handle */
	if (ai->ai_socktype != SOCK_STREAM &&
	    ai->ai_socktype != SOCK_DGRAM)
		return TRUE;

#ifdef ENABLE_IPV6
	/*
	 * skip IPv4 mapped addresses returned from getaddrinfo,
	 * for security reasons. see:
	 *
	 * http://playground.iijlab.net/i-d/
	 *       /draft-itojun-ipv6-transition-abuse-01.txt
	 */
	if (is_address_ipv4_mapped(ai->ai_addr))
		return TRUE;
#else
#ifdef PF_INET6
	/* skip IPv6 if disabled */
	if (ai->ai_family == PF_INET6)
		return TRUE;
#endif
#endif

	return FALSE;
}


until netcat6 0.3.0 is released, you can retrieve the code from our CVS
repository via anonymous CVS:

cvs -d :pserver:anonymous@xxxxxxxxxxxxxx:/cvs/ds6 co nc6
(any password will work)

or (more confortably) via web (with ViewCVS):

http://cvs.deepspace6.net


> i want to distinguish between clients coming to ipv4 or ipv6
> and print client address ? if i would get sa_family correct
> i can switch(family) { and case for AF_INET and AF_INET6 and
> use inet_ntop to print address ? but i am getting same family
> always (28c/bsd and 10c/linux --> AF_INET6)
>
> any hints?

connection attempts from ipv4 addresses are sometimes returned as
ipv4-mapped ipv6 addresses (you can use the IN6_IS_ADDR_V4MAPPED
function on a struct in6_addr to find if the address is ipv4-mapped).

you can transform a sockaddr_in6 structure containing an ipv4-mapped
addresses into a simple sockaddr_in structure, if you want. here's an
example (always from netcat6, file src/filter.c):


/* compare two sockaddr structs to see if they represent the same address */
static bool sockaddr_compare(const struct sockaddr *a, const struct sockaddr *b)
{
	const struct sockaddr *aa, *bb;

	assert(a != NULL);
	assert(b != NULL);

	aa = (const struct sockaddr *)a;
	bb = (const struct sockaddr *)b;

#ifdef ENABLE_IPV6
	/* we have to handle IPv6 IPV4MAPPED addresses - convert them to IPv4 */
	if (is_address_ipv4_mapped(a)) {
		struct sockaddr *ap;
		ap = (struct sockaddr *)alloca(sizeof(struct sockaddr_in));
		memset(ap, 0, sizeof(struct sockaddr_in));
		memcpy(&(((struct sockaddr_in *)ap)->sin_addr.s_addr),
		       &(((const struct sockaddr_in6 *)a)->sin6_addr.s6_addr[12]),
		       sizeof(struct in_addr));
		((struct sockaddr_in *)ap)->sin_port =
			((const struct sockaddr_in6 *)a)->sin6_port;
		aa = ap;
	}

	[...]
}


-- 
Aequam memento rebus in arduis servare mentem...

Mauro Tortonesi			mauro@xxxxxxxxxxxxxx
				mauro@xxxxxxxxxxxxxxxx
Deep Space 6 - IPv6 on Linux	http://www.deepspace6.net
Ferrara Linux Users Group	http://www.ferrara.linux.it