tech-net archive

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

Re: shutdown(2)'ing a bound UDP socket



On Thu, Jul 16, 2020 at 10:24:16AM +0100, Roy Marples wrote:
> Hi Peter

Hi Roy and tech-net,

Thank you for the patient wait.  I have finally written something and was
able to reproduce the condition tonight.  I'm going to paste the program
inline here and then talk a little below it.  Just search for "---" to
skip the code to see the commentary...

------->
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#include <err.h>
#include <errno.h>

int bind_socketv6(int so, u_short port);
int bind_socketv4(int so, u_short port);

int
main(int argc, char *argv[])
{
	struct sockaddr_in sin;
	struct sockaddr_in6 sin6;
	struct timeval tv;

	int so, so6, dup, dup6, on = 1;
	int max, len, sel;

	u_short port = 65053;
	fd_set rdset;
	pid_t pid;
	socklen_t slen;

	char buf[512];


	if (argc > 1)
		port = atoi(argv[1]);


	/* make the dup's */

	dup = socket(AF_INET, SOCK_DGRAM, 0);
	dup6 = socket(AF_INET6, SOCK_DGRAM, 0);

	if (dup < 0 || dup6 < 0) {
		err(1, "socket");
	}
	
	on = 1;
	if (setsockopt(dup, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
		err(1, "setsockopt");
	}
		
	on = 1;
	if (setsockopt(dup6, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
		err(1, "setsockopt6");
	}
	
	/* shutdown on the dup's */

	if (shutdown(dup, SHUT_RD) < 0) {
		err(1, "shutdown dup");
	}

	if (shutdown(dup6, SHUT_RD) < 0) {
		err(1, "shutdown dup6");
	}

	/* bind the dup's too */
	
	if (bind_socketv4(dup, port) < 0) {
		err(1, "dup bind");
	}

	if (bind_socketv6(dup6, port) < 0) {
		err(1, "dup bind6");
	}

	/* the main sockets */

	so = socket(AF_INET, SOCK_DGRAM, 0);
	so6 = socket(AF_INET6, SOCK_DGRAM, 0);

	if (so < 0 || so6 < 0) {
		err(1, "socket");
	}
	
	on = 1;
	if (setsockopt(so, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
		err(1, "setsockopt");
	}
		
	on = 1;
	if (setsockopt(so6, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) {
		err(1, "setsockopt6");
	}
	
	if (bind_socketv4(so, port) < 0) {
		err(1, "bind");
	}

	if (bind_socketv6(so6, port) < 0) {
		err(1, "bind6");
	}

	/* fork a child */

	switch (pid = fork()) {
	case -1:
		err(1, "fork");
		break;
	case 0:
		close(so); close(so6);	
		for (;;) {
			/* here we can write to the dup's if we wish */
			sleep(10);
			memset(&buf, 'X', 16);
			memset(&sin, 0, sizeof(sin));
			sin.sin_family = AF_INET;
			sin.sin_port = htons(8888);
			sin.sin_addr.s_addr = inet_addr("192.168.177.2");
			sendto(dup, buf, 16, 0, (struct sockaddr*)&sin, 
				sizeof(struct sockaddr_in));
		}
		/* NOTREACHED */
		break;
	default:
		close(dup); close(dup6);
		max = so6;
		break;
	}
	
	for (;;) {
		FD_ZERO(&rdset);
		FD_SET(so, &rdset);
		FD_SET(so6, &rdset);
	
		tv.tv_sec = 5;
		tv.tv_usec = 0;

		if ((sel = select(max + 1, &rdset, NULL, NULL, &tv)) < 0) {
			fprintf(stderr, "select error: %s\n", strerror(errno));
			continue;
		}

		if (sel == 0) {
			continue;
		}
		
		if (FD_ISSET(so, &rdset)) {
			slen = sizeof(struct sockaddr_in);
			if ((len = recvfrom(so, buf, sizeof(buf), 0, 
					(struct sockaddr*)&sin, &slen)) < 0) {
				warn("recvfrom");
			}
			printf("%lu so read %d bytes\n", time(NULL), len);

			/* send something back */
			if (sendto(so, buf, len, 0, (struct sockaddr*)&sin, slen) < 0)
				warn("sendto");
			continue;
		} else if (FD_ISSET(so6, &rdset)) {
			slen = sizeof(struct sockaddr_in6);
			if ((len = recvfrom(so6, buf, sizeof(buf), 0, 
					(struct sockaddr*)&sin6, &slen)) < 0) {
				warn("recvfrom");
			}
			printf("%lu so6 read %d bytes\n", time(NULL), len);
			continue;
		}

	} /* for(); */
/* NOTREACHED */
}
	
int
bind_socketv4(int so, u_short port)
{
	struct sockaddr_in sin;

	memset(&sin, 0, sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	
	return (bind(so, (struct sockaddr *)&sin, sizeof(sin)));	
}

int
bind_socketv6(int so, u_short port)
{
	struct sockaddr_in6 sin6;

	memset(&sin6, 0, sizeof(sin6));
	sin6.sin6_family = AF_INET6;
	sin6.sin6_port = htons(port);
	sin6.sin6_len = sizeof(struct sockaddr_in6);
	
	return (bind(so, (struct sockaddr *)&sin6, sizeof(sin6)));	
}
<-------

So as you can see the program binds to 65053, but I used ./test-program 8888
to have it bind on port 8888, but it doesn't really matter which port.

The dup descriptors go into the child and it sends every 10 seconds a packet
to 192.168.177.2 (my OpenBSD workstation, but it can be any address).

What I have found is that with netcat from any source I can get the select
loop in the parent to display UNIX timestamp and the "so" descriptor 
read X bytes, UNTIL the child in the for(;;)/sleep(10) loop transmits.  
Then it won't receive any more on the parent.  And this is exactly what 
I'm running into on my program delphinusdnsd.

I hope you can work with this, let me know if I must do anything to accomodate
some sort of fix.  The behaviour I'm looking for is that one can write on
the shutdown SHUT_RD socket (dup) and read without it blocking on (so) parent
socket.

Best Regards,
-peter



> On 15/07/2020 13:00, Peter J. Philipp wrote:
> > Hi,
> > 
> > I'm the author of delphinusdnsd, a lightweight dns server.  I develop on
> > OpenBSD but produce ports to Linux, FreeBSD and NetBSD.  My latest code I
> > have ported to Linux and FreeBSD successfully, but NetBSD is not working.
> > 
> > What I do in my code is I bind (with SO_REUSEPORT option) two UDP descriptors
> > on the same port and shutdown(2) one of those (called dup) in the receive
> > setting (SHUT_RD).  This allows me to read off the non-shut descriptor but
> > send packets on either, it works out well on OpenBSD.  However while NetBSD
> > does allow shutting down the descriptor (unlike FreeBSD which has other
> > code to fix that problem it looks like), it does want to deliver incoming
> > queries to the shut descriptor.  I get one answer from my server on NetBSD
> > and then it blocks.  I tried patching this in kernel but it seems to be over
> > my head, I'm doing something wrong.  Basically the socket should get a
> > SS_CANTRCVMORE state, but checking for this seems to be hard, plus I don't
> > know what I'M doing in the NetBSD kernel.
> > 
> > So I'm basically left of begging someone to fix this functionality to skip
> > shutdown(2)'ed bound reading sockets and let the ones that do read receive
> > the packet.
> > 
> > Otherwise this may be my last year of supporting NetBSD unfortunately.  I
> > would like to give a donation but I'm dirt poor, as gesture I can maybe
> > afford five euros or something, but can't find more.  I have donated five USD
> > before in 2018, if it's worth any.  I'm releasing version 1.5.0 between
> > september 2020 and november 2020, and I hope to continue NetBSD support, if
> > only in -current.
> > 
> > If you need to see my code to see what I'm doing you can get it at
> > https://delphinusdns.org/download/snapshot/delphinusdnsd-snapshot.tgz and the
> > relevant lines of code are in delphinusdnsd.c (main()) and go further into
> > forward.c.  If you need a config file for the forwarding mode I can produce
> > you one on request.
> > 
> > Please CC me directly as I'm not on the tech-net%netbsd.org@localhost list.
> 
> Do you have a small test case for this that can reproduce the issue?
> 
> Roy


Home | Main Index | Thread Index | Old Index