From da154ffd0699fb414a5b8387f4900e515dc6531f Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Sun, 7 Apr 2013 19:30:17 +0200 Subject: [PATCH] Fix wildcard bind to at least bind IPv4 or else IPv6 on OpenBSD OpenBSD refuses to implement IPV6_V6ONLY, in violation of RFC 3493. RFC 4038 frowningly recognizes this practice. The only way to bind both IPv4 and IPv4 there is two separate sockets. Requires more surgery than I can do now. Since we can't have both IPv6 and IPv6 on OpenBSD with our single socket, prefer IPv4, but if that doesn't work, do IPv6. To prefer IPv6 instead, put 'listen_addr "::"' into econfig. Document that in listen_addr's doc string. --- include/econfig-spec.h | 6 +++++- src/lib/gen/tcp_listen.c | 31 +++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/econfig-spec.h b/include/econfig-spec.h index d00c9c1cd..1cfa8b148 100644 --- a/include/econfig-spec.h +++ b/include/econfig-spec.h @@ -84,7 +84,11 @@ EMPCF_COMMENT("# Set this to your source tree's src/lib/global to run the server "# without installing it, else leave it alone.") EMPCFBOTH("listen_addr", listen_addr, char *, NSC_STRING, KM_INTERNAL, "Local IP address the server should listen on") -EMPCF_COMMENT("# \"\" listens on all, localhost just on the loopback interface.") +EMPCF_COMMENT("# \"\" listens on all, localhost just on the loopback interface.\n" + "# OpenBSD restriction: when the system has both IPv4 and IPv6\n" + "# addresses configured, \"\" listens on all IPv4 addresses, and \"::\"\n" + "# on all IPv6 addresses. There is no way to listen both on all IPv4\n" + "# and on all IPv6 interfaces.") EMPCFBOTH("port", loginport, char *, NSC_STRING, KM_INTERNAL, "TCP port the server will bind") EMPCFBOTH("keep_journal", keep_journal, int, NSC_INT, KM_INTERNAL, diff --git a/src/lib/gen/tcp_listen.c b/src/lib/gen/tcp_listen.c index f2ec86d09..1b5244072 100644 --- a/src/lib/gen/tcp_listen.c +++ b/src/lib/gen/tcp_listen.c @@ -66,6 +66,8 @@ tcp_listen(char *host, char *serv, size_t *addrlenp) */ int err; struct addrinfo hints, *first_ai, *ai; + /* Crap necessary for OpenBSD, see below */ + int try_v6only_off = 1, v6only_stuck = 0; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; @@ -76,17 +78,31 @@ tcp_listen(char *host, char *serv, size_t *addrlenp) cant_listen(host, serv, gai_strerror(err)); assert(first_ai); +again: for (ai = first_ai; ai; ai = ai->ai_next) { fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) continue; /* error, try next one */ #ifdef IPV6_V6ONLY - if (ai->ai_family == AF_INET6) { + if (ai->ai_family == AF_INET6 && try_v6only_off) { int off = 0; - setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, + &off, sizeof(off)) < 0) { + /* + * IPV6_V6ONLY is stuck on, violating RFC 3493 (gee, + * thanks, OpenBSD!). Address is good only for IPv6, + * not for IPv4. Means we can't have both on this + * system. Continue looking for one that's good for + * IPv4. + */ + v6only_stuck = 1; + continue; + } } +#else + (void)try_v6only_off; #endif setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); @@ -96,6 +112,17 @@ tcp_listen(char *host, char *serv, size_t *addrlenp) close(fd); /* error, close and try next one */ } + /* More crap for OpenBSD */ + if (ai == NULL && v6only_stuck) { + /* + * No go. But we skipped IPv6 addresses that don't work for + * IPv4, but could for IPv6. Try again without skipping + * these. + */ + try_v6only_off = 0; + goto again; + } + if (ai == NULL) /* errno from final socket() or bind() */ cant_listen(host, serv, strerror(errno)); -- 2.43.0