On 07/01/2023 01.38, Gordon Messmer wrote:
On 2023-01-06 06:17, Sjoerd Mullender wrote:
I have a program that is supposed to listen to the same port on both IPv4 and IPv6 sockets.  In the past, what it did, was basically: create new socket for IPv6, set option IPV6_V6ONLY to off, bind, listen; then create a new socket for IPv4, and also bind and listen.


Do you have sample code that demonstrates this process?

I'm confused by your description, because setting IPV6_V6ONLY to 0 with setsockopt should result in a socket that's bound to a port that accepts both IPv4 and IPv6 connections.  You shouldn't have ever been able to bind to that port in IPv4 in the past.

See the attached program.  Can be compiled without any extra options.

This program creates an IPv6 listening socket and tells it to also do IPv4. Then it executes "netstat -anp | grep <pid>" to show what it did. After that it tries to create an IPv4 listening socket for the port that was assigned in the first round and if successful again executes netstat. If not successful, it will tell why.

When trying to bind to the wildcard address (specify "all" on the command line), the behavior is very different from when specifying "localhost".

Run as
$ ./a.out all 0
$ ./a.out localhost 0

and see the difference in behavior. When using "all", the second round (IPv4) says that bind returns 1 unexpectedly, and the port is also unexpected. When using "localhost", both IPv6 and IPv4 succeed and listen to the same port, but using two different sockets internally.

The behavior for the "all" case is different with the older kernel 6.0.15-300.fc37.x86_64 where bind will say "Address already in use".

--
Sjoerd Mullender
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int
main(int argc, char **argv)
{
	struct addrinfo hints = {
		.ai_family = AF_INET6,
		.ai_flags = AI_PASSIVE | AI_NUMERICSERV,
		.ai_socktype = SOCK_STREAM,
		.ai_protocol = IPPROTO_TCP,
	};
	struct addrinfo *result = NULL;
	/* 0: use IPv6 and IPv4, 1: only IPv6, -1: only IPv4 */
	int ipv6_vs6only = -1;	/* may get changed depending on addr */

	if (argc != 3) {
  usage:
		fprintf(stderr, "Usage: %s address port\n", argv[0]);
		fprintf(stderr, "where address is either all or localhost\n");
		fprintf(stderr, "and port is either 0 or a value between 1025 and 65535\n");
		exit(1);
	}
	const char *listenaddr = argv[1];
	int port = atoi(argv[2]);
	if (strcmp(listenaddr, "localhost") == 0) {
		hints.ai_family = AF_INET6;
		ipv6_vs6only = 0;
	} else if (strcmp(listenaddr, "all") == 0) {
		hints.ai_family = AF_INET6;
		ipv6_vs6only = 0;
		listenaddr = NULL;
	} else {
		goto usage;
	}
	char ports[8];		/* max "65535" */
	int socks[2];
	int nsocks = 0;
	for (;;) {
		snprintf(ports, sizeof(ports), "%u", port);
		int check = getaddrinfo(listenaddr, ports, &hints, &result);
		if (check != 0) {
			fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(check));
			exit(1);
		}
		int sock = -1;
		for (struct addrinfo *rp = result; rp; rp = rp->ai_next) {
			sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
			if (sock == -1) {
				fprintf(stderr, "socket: %s\n", gai_strerror(check));
				continue;
			}
			if (hints.ai_family == AF_INET6 && /* only if IPv6 */
			    setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_vs6only, sizeof(int)) == -1) {
				perror("setsockopt");
			}
			int e;
			e = bind(sock, rp->ai_addr, rp->ai_addrlen);
			switch (e) {
			case 0:
				break;
			case -1:
				perror("bind");
				close(sock);
				continue;
			default:
				fprintf(stderr, "bind: unexpected return: %d\n", e);
				break;
			}
			union {
				struct sockaddr_storage ss;
				struct sockaddr_in i4;
				struct sockaddr_in6 i6;
			} myaddr;
			socklen_t myaddrlen = sizeof(myaddr.ss);
			if (getsockname(sock, (struct sockaddr *) &myaddr.ss, &myaddrlen) == -1) {
				perror("getsockname");
				close(sock);
				continue;
			}
			int myport = myaddr.ss.ss_family == AF_INET6 ? htons(myaddr.i6.sin6_port) : htons(myaddr.i4.sin_port);
			if (port == 0)
				port = myport;
			else if (port != myport) {
				fprintf(stderr, "bound to unexpected port %d, expected %d\n", myport, port);
				close(sock);
				continue;
			}
			if (listen(sock, 2) == -1) {
				perror("listen");
				close(sock);
				continue;
			}
			char command[64];
			snprintf(command, sizeof(command), "netstat -anp | grep %d", (int) getpid());
			system(command);
			socks[nsocks++] = sock;
			break;
		}
		freeaddrinfo(result);
		if (ipv6_vs6only == 0) {
			ipv6_vs6only = -1; /* not 0! */
			hints.ai_family = AF_INET;
		} else
			break;
	}
	for (int i = 0; i < nsocks; i++)
		close(socks[i]);
	exit(0);
}
_______________________________________________
users mailing list -- users@lists.fedoraproject.org
To unsubscribe send an email to users-le...@lists.fedoraproject.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedoraproject.org/archives/list/users@lists.fedoraproject.org
Do not reply to spam, report it: 
https://pagure.io/fedora-infrastructure/new_issue

Reply via email to