diff mbox

[ovs-dev,v2,3/3] Support IPv6 link-local address scopes on Linux.

Message ID 20170714213346.21944-4-blp@ovn.org
State Accepted
Headers show

Commit Message

Ben Pfaff July 14, 2017, 9:33 p.m. UTC
I hadn't even heard of this feature before, but it seems to be at least
semi-standard to support Linux link-local address scopes via a % suffix,
e.g. fe80::1234%eth0 for a link-local address scoped to eth0.  This commit
adds support.

I'd appreciate feedback from folks who understand this feature better than
me.

Reported-by: Ali Volkan Atli <Volkan.Atli@argela.com.tr>
Signed-off-by: Ben Pfaff <blp@ovn.org>
---
 NEWS                     |  2 ++
 configure.ac             |  3 +++
 lib/socket-util.c        | 65 +++++++++++++++++++++++++++++++++++++++++-------
 lib/vconn-active.man     |  7 ++++--
 lib/vconn-passive.man    | 10 +++++---
 ovsdb/remote-active.man  | 20 +++++++--------
 ovsdb/remote-passive.man | 29 ++++++++-------------
 7 files changed, 92 insertions(+), 44 deletions(-)
diff mbox

Patch

diff --git a/NEWS b/NEWS
index f2e453a61e98..d61fc5f7bd4a 100644
--- a/NEWS
+++ b/NEWS
@@ -69,6 +69,8 @@  Post-v2.7.0
    - Add experimental support for hardware offloading
      * HW offloading is disabled by default.
      * HW offloading is done through the TC interface.
+   - IPv6 link local addresses are now supported on Linux.  Use % to designate
+     the scope device.
 
 v2.7.0 - 21 Feb 2017
 ---------------------
diff --git a/configure.ac b/configure.ac
index 23afe4c7129d..194c4b92ee34 100644
--- a/configure.ac
+++ b/configure.ac
@@ -107,6 +107,9 @@  AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec],
   [], [], [[#include <sys/stat.h>]])
 AC_CHECK_MEMBERS([struct ifreq.ifr_flagshigh], [], [], [[#include <net/if.h>]])
 AC_CHECK_MEMBERS([struct mmsghdr.msg_len], [], [], [[#include <sys/socket.h>]])
+AC_CHECK_MEMBERS([struct sockaddr_in6.sin6_scope_id], [], [],
+  [[#include <sys/socket.h>
+#include <netinet/in.h>]])
 AC_CHECK_FUNCS([mlockall strnlen getloadavg statvfs getmntent_r sendmmsg])
 AC_CHECK_HEADERS([mntent.h sys/statvfs.h linux/types.h linux/if_ether.h stdatomic.h])
 AC_CHECK_HEADERS([net/if_mib.h], [], [], [[#include <sys/types.h>
diff --git a/lib/socket-util.c b/lib/socket-util.c
index 82975aa62981..85ed54c058d6 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -17,6 +17,7 @@ 
 #include <config.h>
 #include "socket-util.h"
 #include <arpa/inet.h>
+#include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <net/if.h>
@@ -365,7 +366,7 @@  parse_bracketed_token(char **pp)
 
 static bool
 parse_sockaddr_components(struct sockaddr_storage *ss,
-                          const char *host_s,
+                          char *host_s,
                           const char *port_s, uint16_t default_port,
                           const char *s)
 {
@@ -382,20 +383,38 @@  parse_sockaddr_components(struct sockaddr_storage *ss,
     }
 
     memset(ss, 0, sizeof *ss);
-    if (strchr(host_s, ':')) {
+    if (host_s && strchr(host_s, ':')) {
         struct sockaddr_in6 *sin6
             = ALIGNED_CAST(struct sockaddr_in6 *, ss);
 
+        char *addr = strsep(&host_s, "%");
+
         sin6->sin6_family = AF_INET6;
         sin6->sin6_port = htons(port);
-        if (!ipv6_parse(host_s, &sin6->sin6_addr)) {
-            VLOG_ERR("%s: bad IPv6 address \"%s\"", s, host_s);
+        if (!addr || !*addr || !ipv6_parse(addr, &sin6->sin6_addr)) {
+            VLOG_ERR("%s: bad IPv6 address \"%s\"", s, addr ? addr : "");
             goto exit;
         }
+
+#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
+        char *scope = strsep(&host_s, "%");
+        if (scope && *scope) {
+            if (!scope[strspn(scope, "0123456789")]) {
+                sin6->sin6_scope_id = atoi(scope);
+            } else {
+                sin6->sin6_scope_id = if_nametoindex(scope);
+                if (!sin6->sin6_scope_id) {
+                    VLOG_ERR("%s: bad IPv6 scope \"%s\" (%s)",
+                             s, scope, ovs_strerror(errno));
+                    goto exit;
+                }
+            }
+        }
+#endif
     } else {
         sin->sin_family = AF_INET;
         sin->sin_port = htons(port);
-        if (!ip_parse(host_s, &sin->sin_addr.s_addr)) {
+        if (host_s && !ip_parse(host_s, &sin->sin_addr.s_addr)) {
             VLOG_ERR("%s: bad IPv4 address \"%s\"", s, host_s);
             goto exit;
         }
@@ -421,7 +440,7 @@  inet_parse_active(const char *target_, uint16_t default_port,
 {
     char *target = xstrdup(target_);
     const char *port;
-    const char *host;
+    char *host;
     char *p;
     bool ok;
 
@@ -548,7 +567,7 @@  inet_parse_passive(const char *target_, int default_port,
 {
     char *target = xstrdup(target_);
     const char *port;
-    const char *host;
+    char *host;
     char *p;
     bool ok;
 
@@ -559,8 +578,7 @@  inet_parse_passive(const char *target_, int default_port,
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
     } else {
-        ok = parse_sockaddr_components(ss, host ? host : "0.0.0.0",
-                                       port, default_port, target_);
+        ok = parse_sockaddr_components(ss, host, port, default_port, target_);
     }
     if (!ok) {
         memset(ss, 0, sizeof *ss);
@@ -958,6 +976,21 @@  ss_get_port(const struct sockaddr_storage *ss)
     }
 }
 
+/* Returns true if 'name' is safe to include inside a network address field.
+ * We want to avoid names that include confusing punctuation, etc. */
+static bool OVS_UNUSED
+is_safe_name(const char *name)
+{
+    if (!name[0] || isdigit((unsigned char) name[0])) {
+        return false;
+    }
+    for (const char *p = name; *p; p++) {
+        if (!isalnum((unsigned char) *p) && *p != '-' && *p != '_') {
+            return false;
+        }
+    }
+    return true;
+}
 
 /* Formats the IPv4 or IPv6 address in 'ss' into 's'.  If 'ss' is an IPv6
  * address, puts square brackets around the address.  'bufsize' should be at
@@ -980,6 +1013,20 @@  ss_format_address(const struct sockaddr_storage *ss, struct ds *s)
         inet_ntop(AF_INET6, sin6->sin6_addr.s6_addr, tail, INET6_ADDRSTRLEN);
         s->length += strlen(tail);
 
+#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID
+        uint32_t scope = sin6->sin6_scope_id;
+        if (scope) {
+            char namebuf[IF_NAMESIZE];
+            char *name = if_indextoname(scope, namebuf);
+            ds_put_char(s, '%');
+            if (name && is_safe_name(name)) {
+                ds_put_cstr(s, name);
+            } else {
+                ds_put_format(s, "%"PRIu32, scope);
+            }
+        }
+#endif
+
         ds_put_char(s, ']');
     } else {
         OVS_NOT_REACHED();
diff --git a/lib/vconn-active.man b/lib/vconn-active.man
index 3e789cc88e16..395879c8a58b 100644
--- a/lib/vconn-active.man
+++ b/lib/vconn-active.man
@@ -3,8 +3,11 @@ 
 The specified \fIport\fR on the host at the given \fIip\fR, which must
 be expressed as an IP address (not a DNS name) in IPv4 or IPv6 address
 format.  Wrap IPv6 addresses in square brackets,
-e.g. \fBtcp:[::1]:6653\fR.  For \fBssl\fR, the \fB\-\-private\-key\fR,
-\fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR options are mandatory.
+e.g. \fBtcp:[::1]:6653\fR.  On Linux, use \fB%\fIdevice\fR to
+designate a scope for IPv6 link-level addresses,
+e.g. \fBtcp:[fe80::1234%eth0]:6653\fR.  For \fBssl\fR, the
+\fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
+options are mandatory.
 .IP
 If \fIport\fR is not specified, it defaults to 6653.
 .TP
diff --git a/lib/vconn-passive.man b/lib/vconn-passive.man
index 9d9050b020ff..1ffa183972b8 100644
--- a/lib/vconn-passive.man
+++ b/lib/vconn-passive.man
@@ -1,10 +1,12 @@ 
 .IP "\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR]"
 .IQ "\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]"
 Listens for OpenFlow connections on \fIport\fR.  The default
-\fIport\fR is 6653.  By default, connections
-are allowed from any IPv4 address.  Specify \fIip\fR as an IPv4
-address or a bracketed IPv6 address (e.g. \fBptcp:6653:[::1]\fR).  DNS
-names may not be used.  For \fBpssl\fR, the
+\fIport\fR is 6653.  By default, connections are allowed from any IPv4
+address.  Specify \fIip\fR as an IPv4 address or a bracketed IPv6
+address (e.g. \fBptcp:6653:[::1]\fR).  On Linux, use \fB%\fIdevice\fR
+to designate a scope for IPv6 link-level addresses,
+e.g. \fBptcp:6653:[fe80::1234%eth0]\fR.  DNS names may
+not be used.  For \fBpssl\fR, the
 \fB\-\-private\-key\fR,\fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
 options are mandatory.
 .IP
diff --git a/ovsdb/remote-active.man b/ovsdb/remote-active.man
index 83d64652ddb9..bf5b323dd55e 100644
--- a/ovsdb/remote-active.man
+++ b/ovsdb/remote-active.man
@@ -1,15 +1,13 @@ 
 .IP "\fBssl:\fIip\fB:\fIport\fR"
-The specified SSL \fIport\fR on the host at the given \fIip\fR, which
-must be expressed as an IP address (not a DNS name) in IPv4 or IPv6 address
-format.  If \fIip\fR is an IPv6 address, then wrap \fIip\fR with square
-brackets, e.g.: \fBssl:[::1]:6640\fR.
-The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
-options are mandatory when this form is used.
-.
-.IP "\fBtcp:\fIip\fB:\fIport\fR"
-Connect to the given TCP \fIport\fR on \fIip\fR, where \fIip\fR can be IPv4
-or IPv6 address. If \fIip\fR is an IPv6 address, then wrap \fIip\fR with
-square brackets, e.g.: \fBtcp:[::1]:6640\fR.
+.IQ "\fBtcp:\fIip\fB:\fIport\fR"
+The given SSL or plain TCP \fIport\fR on the host at the given
+\fIip\fR, which must be expressed as an IP address (not a DNS name) in
+IPv4 or IPv6 address format.  If \fIip\fR is an IPv6 address, then
+wrap \fIip\fR with square brackets, e.g.: \fBssl:[::1]:6640\fR.  On
+Linux, use \fB%\fIdevice\fR to designate a scope for IPv6 link-level
+addresses, e.g. \fBssl:[fe80::1234%eth0]:6653\fR.  For \fBssl\fR, the
+\fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
+options are mandatory.
 .
 .IP "\fBunix:\fIfile\fR"
 On POSIX, connect to the Unix domain server socket named \fIfile\fR.
diff --git a/ovsdb/remote-passive.man b/ovsdb/remote-passive.man
index 5da2de87bf29..f2a1868442da 100644
--- a/ovsdb/remote-passive.man
+++ b/ovsdb/remote-passive.man
@@ -1,22 +1,15 @@ 
 .IP "\fBpssl:\fIport\fR[\fB:\fIip\fR]"
-Listen on the given SSL \fIport\fR for a connection.  By default,
-connections are not bound to a particular local IP address and
-it listens only on IPv4 (but not IPv6) addresses, but
-specifying \fIip\fR limits connections to those from the given
-\fIip\fR, either IPv4 or IPv6 address.  If \fIip\fR is
-an IPv6 address, then wrap \fIip\fR with square brackets, e.g.:
-\fBpssl:6640:[::1]\fR.  The \fB\-\-private\-key\fR,
-\fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR options are mandatory
-when this form is used.
-.
-.IP "\fBptcp:\fIport\fR[\fB:\fIip\fR]"
-Listen on the given TCP \fIport\fR for a connection.  By default,
-connections are not bound to a particular local IP address and
-it listens only on IPv4 (but not IPv6) addresses, but
-\fIip\fR may be specified to listen only for connections to the given
-\fIip\fR, either IPv4 or IPv6 address.  If \fIip\fR is
-an IPv6 address, then wrap \fIip\fR with square brackets, e.g.:
-\fBptcp:6640:[::1]\fR.
+.IQ "\fBptcp:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given SSL or TCP \fIport\fR for a connection.  By
+default, connections are not bound to a particular local IP address
+and it listens only on IPv4 (but not IPv6) addresses, but specifying
+\fIip\fR limits connections to those from the given \fIip\fR, either
+IPv4 or IPv6 address.  If \fIip\fR is an IPv6 address, then wrap
+\fIip\fR with square brackets, e.g.: \fBpssl:6640:[::1]\fR.  On Linux,
+use \fB%\fIdevice\fR to designate a scope for IPv6 link-level
+addresses, e.g. \fBpssl:6653:[fe80::1234%eth0]\fR.  For \fBpssl\fR,
+the \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory.
 .
 .IP "\fBpunix:\fIfile\fR"
 On POSIX, listen on the Unix domain server socket named \fIfile\fR for a