]> git.pond.sub.org Git - empserver/commitdiff
Fix wildcard bind to at least bind IPv4 or else IPv6 on OpenBSD
authorMarkus Armbruster <armbru@pond.sub.org>
Sun, 7 Apr 2013 17:30:17 +0000 (19:30 +0200)
committerMarkus Armbruster <armbru@pond.sub.org>
Wed, 8 May 2013 04:57:56 +0000 (06:57 +0200)
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
src/lib/gen/tcp_listen.c

index d00c9c1cdfd35d259e612d1feadb8064c29d7897..1cfa8b148161d76fb9e8fc61baf787ddf1a24073 100644 (file)
@@ -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,
index f2ec86d090ed692f70f3ed666036eae1d09ad1d9..1b52440724b841616fba83d2f7c1c30897d98032 100644 (file)
@@ -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));