diff --git a/lib/packets.c b/lib/packets.c
index 8ebae8cc8fe8..38bfb6015b9e 100644
--- a/lib/packets.c
+++ b/lib/packets.c
@@ -652,82 +652,6 @@ ip_parse_cidr(const char *s, ovs_be32 *ip, unsigned int *plen)
     return error;
 }
 
-/* Parses the string into an IPv4 or IPv6 address.
- * The port flags act as follows:
- * * PORT_OPTIONAL: A port may be present but is not required
- * * PORT_REQUIRED: A port must be present
- * * PORT_FORBIDDEN: A port must not be present
- */
-char * OVS_WARN_UNUSED_RESULT
-ipv46_parse(const char *s, enum port_flags flags, struct sockaddr_storage *ss)
-{
-    char *error = NULL;
-
-    char *copy;
-    copy = xstrdup(s);
-
-    char *addr;
-    char *port;
-    if (*copy == '[') {
-        char *end;
-
-        addr = copy + 1;
-        end = strchr(addr, ']');
-        if (!end) {
-            error = xasprintf("No closing bracket on address %s", s);
-            goto finish;
-        }
-        *end++ = '\0';
-        if (*end == ':') {
-            port = end + 1;
-        } else {
-            port = NULL;
-        }
-    } else {
-        addr = copy;
-        port = strchr(copy, ':');
-        if (port) {
-            if (strchr(port + 1, ':')) {
-                port = NULL;
-            } else {
-                *port++ = '\0';
-            }
-        }
-    }
-
-    if (port && !*port) {
-        error = xasprintf("Port is an empty string");
-        goto finish;
-    }
-
-    if (port && flags == PORT_FORBIDDEN) {
-        error = xasprintf("Port forbidden in address %s", s);
-        goto finish;
-    } else if (!port && flags == PORT_REQUIRED) {
-        error = xasprintf("Port required in address %s", s);
-        goto finish;
-    }
-
-    struct addrinfo hints = {
-        .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
-        .ai_family = AF_UNSPEC,
-    };
-    struct addrinfo *res;
-    int status;
-    status = getaddrinfo(addr, port, &hints, &res);
-    if (status) {
-        error = xasprintf("Error parsing address %s: %s",
-                s, gai_strerror(status));
-        goto finish;
-    }
-    memcpy(ss, res->ai_addr, res->ai_addrlen);
-    freeaddrinfo(res);
-
-finish:
-    free(copy);
-    return error;
-}
-
 /* Parses string 's', which must be an IPv6 address.  Stores the IPv6 address
  * into '*ip'.  Returns true if successful, otherwise false. */
 bool
diff --git a/lib/packets.h b/lib/packets.h
index 9a71aa3abbdb..b2bf70697e90 100644
--- a/lib/packets.h
+++ b/lib/packets.h
@@ -1362,16 +1362,6 @@ struct in6_addr ipv6_create_mask(int mask);
 int ipv6_count_cidr_bits(const struct in6_addr *netmask);
 bool ipv6_is_cidr(const struct in6_addr *netmask);
 
-enum port_flags {
-    PORT_OPTIONAL,
-    PORT_REQUIRED,
-    PORT_FORBIDDEN,
-};
-
-char *ipv46_parse(const char *s, enum port_flags flags,
-                  struct sockaddr_storage *ss)
-    OVS_WARN_UNUSED_RESULT;
-
 bool ipv6_parse(const char *s, struct in6_addr *ip);
 char *ipv6_parse_masked(const char *s, struct in6_addr *ipv6,
                         struct in6_addr *mask);
diff --git a/lib/socket-util.c b/lib/socket-util.c
index 223e3780ba5f..0964a015e3f9 100644
--- a/lib/socket-util.c
+++ b/lib/socket-util.c
@@ -333,39 +333,89 @@ guess_netmask(ovs_be32 ip_)
             : htonl(0));                          /* ??? */
 }
 
-/* This is like strsep() except:
- *
- *    - The separator string is ":".
- *
- *    - Square brackets [] quote ":" separators and are removed from the
- *      tokens. */
-char *
-inet_parse_token(char **pp)
+static char *
+unbracket(char *s)
+{
+    if (*s == '[') {
+        s++;
+
+        char *end = strchr(s, '\0');
+        if (end[-1] == ']') {
+            end[-1] = '\0';
+        }
+    }
+    return s;
+}
+
+/* 'host_index' is 0 if the host precedes the port within 's', 1 otherwise. */
+static void
+inet_parse_tokens__(char *s, int host_index, char **hostp, char **portp)
 {
-    char *p = *pp;
-
-    if (p == NULL) {
-        return NULL;
-    } else if (*p == '\0') {
-        *pp = NULL;
-        return p;
-    } else if (*p == '[') {
-        char *start = p + 1;
-        char *end = start + strcspn(start, "]");
-        *pp = (*end == '\0' ? NULL
-               : end[1] == ':' ? end + 2
-               : end + 1);
-        *end = '\0';
-        return start;
+    char *colon = NULL;
+    bool in_brackets = false;
+    int n_colons = 0;
+    for (char *p = s; *p; p++) {
+        if (*p == '[') {
+            in_brackets = true;
+        } else if (*p == ']') {
+            in_brackets = false;
+        } else if (*p == ':' && !in_brackets) {
+            n_colons++;
+            colon = p;
+        }
+    }
+
+    *hostp = *portp = NULL;
+    if (n_colons > 1) {
+        *hostp = s;
     } else {
-        char *start = p;
-        char *end = start + strcspn(start, ":");
-        *pp = *end == '\0' ? NULL : end + 1;
-        *end = '\0';
-        return start;
+        char **tokens[2];
+        tokens[host_index] = hostp;
+        tokens[!host_index] = portp;
+
+        if (colon) {
+            *colon = '\0';
+            *tokens[1] = unbracket(colon + 1);
+        }
+        *tokens[0] = unbracket(s);
     }
 }
 
+/* Parses 's', a string in the form "<host>[:<port>]", into its (required) host
+ * and (optional) port components, and stores pointers to them in '*hostp' and
+ * '*portp' respectively.  Always sets '*hostp' nonnull, although possibly to
+ * an empty string empty string.  Can set '*portp' to the null string.
+ *
+ * Supports both IPv4 and IPv6.  IPv6 addresses may be quoted with square
+ * brackets.  Resolves ambiguous cases that might represent an IPv6 address or
+ * an IPv6 address and a port as representing just a host, e.g. "::1:2:3:4:80"
+ * is a host but "[::1:2:3:4]:80" is a host and a port.
+ *
+ * Modifies 's' and points '*hostp' and '*portp' (if nonnull) into it.
+ */
+void
+inet_parse_host_port_tokens(char *s, char **hostp, char **portp)
+{
+    inet_parse_tokens__(s, 0, hostp, portp);
+}
+
+/* Parses 's', a string in the form "<port>[:<host>]", into its port and host
+ * components, and stores pointers to them in '*portp' and '*hostp'
+ * respectively.  Both '*portp' and '*hostp' can end up null.
+ *
+ * Supports both IPv4 and IPv6.  IPv6 addresses may be quoted with square
+ * brackets.  Resolves ambiguous cases that might represent an IPv6 address or
+ * an IPv6 address and a port as representing just a host, e.g. "::1:2:3:4:80"
+ * is a host but "[::1:2:3:4]:80" is a host and a port.
+ *
+ * Modifies 's' and points '*hostp' and '*portp' (if nonnull) into it.
+ */
+void
+inet_parse_port_host_tokens(char *s, char **portp, char **hostp)
+{
+    inet_parse_tokens__(s, 1, hostp, portp);
+}
+
 static bool
 parse_sockaddr_components(struct sockaddr_storage *ss,
                           char *host_s,
@@ -441,23 +491,16 @@ inet_parse_active(const char *target_, int default_port,
                   struct sockaddr_storage *ss)
 {
     char *target = xstrdup(target_);
-    const char *port;
-    char *host;
-    char *p;
+    char *port, *host;
     bool ok;
 
-    p = target;
-    host = inet_parse_token(&p);
-    port = inet_parse_token(&p);
+    inet_parse_host_port_tokens(target, &host, &port);
     if (!host) {
         VLOG_ERR("%s: host must be specified", target_);
         ok = false;
     } else if (!port && default_port < 0) {
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
-    } else if (p && p[strspn(p, " \t\r\n")] != '\0') {
-        VLOG_ERR("%s: unexpected characters follow host and port", target_);
-        ok = false;
     } else {
         ok = parse_sockaddr_components(ss, host, port, default_port, target_);
     }
@@ -571,20 +614,13 @@ inet_parse_passive(const char *target_, int default_port,
                    struct sockaddr_storage *ss)
 {
     char *target = xstrdup(target_);
-    const char *port;
-    char *host;
-    char *p;
+    char *port, *host;
     bool ok;
 
-    p = target;
-    port = inet_parse_token(&p);
-    host = inet_parse_token(&p);
+    inet_parse_port_host_tokens(target, &port, &host);
     if (!port && default_port < 0) {
         VLOG_ERR("%s: port must be specified", target_);
         ok = false;
-    } else if (p && p[strspn(p, " \t\r\n")] != '\0') {
-        VLOG_ERR("%s: unexpected characters follow port and host", target_);
-        ok = false;
     } else {
         ok = parse_sockaddr_components(ss, host, port, default_port, target_);
     }
@@ -707,16 +743,8 @@ bool
 inet_parse_address(const char *target_, struct sockaddr_storage *ss)
 {
     char *target = xstrdup(target_);
-    char *p = target;
-    char *host = inet_parse_token(&p);
-    bool ok = false;
-    if (!host) {
-        VLOG_ERR("%s: host must be specified", target_);
-    } else if (p && p[strspn(p, " \t\r\n")] != '\0') {
-        VLOG_ERR("%s: unexpected characters follow host", target_);
-    } else {
-        ok = parse_sockaddr_components(ss, host, NULL, 0, target_);
-    }
+    char *host = unbracket(target);
+    bool ok = parse_sockaddr_components(ss, host, NULL, 0, target_);
     if (!ok) {
         memset(ss, 0, sizeof *ss);
     }
diff --git a/lib/socket-util.h b/lib/socket-util.h
index d927d67a0e1b..239d3f22041c 100644
--- a/lib/socket-util.h
+++ b/lib/socket-util.h
@@ -46,7 +46,8 @@ int check_connection_completion(int fd);
 void drain_fd(int fd, size_t n_packets);
 ovs_be32 guess_netmask(ovs_be32 ip);
 
-char *inet_parse_token(char **);
+void inet_parse_host_port_tokens(char *s, char **hostp, char **portp);
+void inet_parse_port_host_tokens(char *s, char **portp, char **hostp);
 bool inet_parse_active(const char *target, int default_port,
                        struct sockaddr_storage *ssp);
 int inet_open_active(int style, const char *target, int default_port,
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c
index 7efc5065c962..16698de1f049 100644
--- a/ovn/utilities/ovn-nbctl.c
+++ b/ovn/utilities/ovn-nbctl.c
@@ -1798,10 +1798,7 @@ nbctl_lb_add(struct ctl_context *ctx)
     }
 
     struct sockaddr_storage ss_vip;
-    char *error;
-    error = ipv46_parse(lb_vip, PORT_OPTIONAL, &ss_vip);
-    if (error) {
-        free(error);
+    if (!inet_parse_active(lb_vip, 0, &ss_vip)) {
         ctl_fatal("%s: should be an IP address (or an IP address "
                   "and a port number with : as a separator).", lb_vip);
     }
@@ -1848,17 +1845,13 @@ nbctl_lb_add(struct ctl_context *ctx)
             token != NULL; token = strtok_r(NULL, ",", &save_ptr)) {
         struct sockaddr_storage ss_dst;
 
-        error = ipv46_parse(token, is_vip_with_port
-                ? PORT_REQUIRED
-                : PORT_FORBIDDEN,
-                &ss_dst);
-
-        if (error) {
-            free(error);
-            if (is_vip_with_port) {
+        if (is_vip_with_port) {
+            if (!inet_parse_active(token, -1, &ss_dst)) {
                 ctl_fatal("%s: should be an IP address and a port "
-                        "number with : as a separator.", token);
-            } else {
+                          "number with : as a separator.", token);
+            }
+        } else {
+            if (!inet_parse_address(token, &ss_dst)) {
                 ctl_fatal("%s: should be an IP address.", token);
             }
         }
@@ -1954,36 +1947,18 @@ lb_info_add_smap(const struct nbrec_load_balancer *lb,
 {
     struct ds key = DS_EMPTY_INITIALIZER;
     struct ds val = DS_EMPTY_INITIALIZER;
-    char *error, *protocol;
 
     const struct smap_node **nodes = smap_sort(&lb->vips);
     if (nodes) {
         for (int i = 0; i < smap_count(&lb->vips); i++) {
             const struct smap_node *node = nodes[i];
-            protocol = lb->protocol;
 
             struct sockaddr_storage ss;
-            error = ipv46_parse(node->key, PORT_OPTIONAL, &ss);
-            if (error) {
-                VLOG_WARN("%s", error);
-                free(error);
+            if (!inet_parse_active(node->key, 0, &ss)) {
                 continue;
             }
 
-            if (ss.ss_family == AF_INET) {
-                struct sockaddr_in *sin = ALIGNED_CAST(struct sockaddr_in *,
-                                                       &ss);
-                if (!sin->sin_port) {
-                    protocol = "tcp/udp";
-                }
-            } else {
-                struct sockaddr_in6 *sin6 = ALIGNED_CAST(struct sockaddr_in6 *,
-                                                         &ss);
-                if (!sin6->sin6_port) {
-                    protocol = "tcp/udp";
-                }
-            }
-
+            char *protocol = ss_get_port(&ss) ? lb->protocol : "tcp/udp";
             i == 0 ? ds_put_format(&val,
                         UUID_FMT "    %-20.16s%-11.7s%-*.*s%s",
                         UUID_ARGS(&lb->header_.uuid),
diff --git a/ovsdb/raft.c b/ovsdb/raft.c
index edb22f2e7b60..c0c1e98977b9 100644
--- a/ovsdb/raft.c
+++ b/ovsdb/raft.c
@@ -335,9 +335,8 @@ raft_make_address_passive(const char *address_)
         return xasprintf("p%s", address_);
     } else {
         char *address = xstrdup(address_);
-        char *p = strchr(address, ':') + 1;
-        char *host = inet_parse_token(&p);
-        char *port = inet_parse_token(&p);
+        char *host, *port;
+        inet_parse_host_port_tokens(strchr(address, ':') + 1, &host, &port);
 
         struct ds paddr = DS_EMPTY_INITIALIZER;
         ds_put_format(&paddr, "p%.3s:%s:", address, port);
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index f07b47fd20ea..514e7e7d2d49 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -460,46 +460,46 @@ AT_SETUP([ovn-nbctl - LBs])
 OVN_NBCTL_TEST_START
 
 dnl Add two LBs.
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80a 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:80a 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
 [ovn-nbctl: 30.0.0.10:80a: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:a80 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:a80 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
 [ovn-nbctl: 30.0.0.10:a80: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10: 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
-[ovn-nbctl: 30.0.0.10:: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10:80 192.168.10.10:80,192.168.10.20 tcp], [1], [],
 [ovn-nbctl: 192.168.10.20: should be an IP address and a port number with : as a separator.
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.1a 192.168.10.10:80,192.168.10.20:80], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.1a 192.168.10.10:80,192.168.10.20:80], [1], [],
 [ovn-nbctl: 30.0.0.1a: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0 192.168.10.10:80,192.168.10.20:80], [1], [],
-[ovn-nbctl: 192.168.10.10:80: should be an IP address.
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0 192.168.10.10:80,192.168.10.20:80], [1], [],
+[ovn-nbctl: 30.0.0: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10,192.168.10.20:80], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10,192.168.10.20:80], [1], [],
 [ovn-nbctl: 192.168.10.20:80: should be an IP address.
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:a80], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:a80], [1], [],
 [ovn-nbctl: 192.168.10.10:a80: should be an IP address.
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10:], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.10:], [1], [],
 [ovn-nbctl: 192.168.10.10:: should be an IP address.
 ])
 
-AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.1a], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 30.0.0.10 192.168.10.1a], [1], [],
 [ovn-nbctl: 192.168.10.1a: should be an IP address.
 ])
 
+AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10: 192.168.10.10:80,192.168.10.20:80 tcp], [1], [],
+[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
+])
+
 AT_CHECK([ovn-nbctl lb-add lb0 30.0.0.10 192.168.10.10 tcp], [1], [],
 [ovn-nbctl: Protocol is unnecessary when no port of vip is given.
 ])
@@ -685,56 +685,56 @@ AT_SETUP([ovn-nbctl - LBs IPv6])
 OVN_NBCTL_TEST_START
 
 dnl A bunch of commands that should fail
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80a [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:80a [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
 [ovn-nbctl: [[ae0f::10]]:80a: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:a80 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:a80 [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
 [ovn-nbctl: [[ae0f::10]]:a80: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]: [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
-[ovn-nbctl: [[ae0f::10]]:: should be an IP address (or an IP address and a port number with : as a separator).
-])
-
-
-AT_CHECK([ovn-nbctl lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,fd0f::20 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]:80 [[fd0f::10]]:80,fd0f::20 tcp], [1], [],
 [ovn-nbctl: fd0f::20: should be an IP address and a port number with : as a separator.
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10fff [[fd0f::10]]:80,fd0f::20 tcp], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10fff [[fd0f::10]]:80,fd0f::20 tcp], [1], [],
 [ovn-nbctl: ae0f::10fff: should be an IP address (or an IP address and a port number with : as a separator).
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 [[fd0f::10]]:80,[[fd0f::20]]:80], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:80,[[fd0f::20]]:80], [1], [],
 [ovn-nbctl: [[fd0f::10]]:80: should be an IP address.
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10,[[fd0f::20]]:80], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::10,[[fd0f::20]]:80], [1], [],
 [ovn-nbctl: [[fd0f::20]]:80: should be an IP address.
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 [[fd0f::10]]:a80], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:a80], [1], [],
 [ovn-nbctl: [[fd0f::10]]:a80: should be an IP address.
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 [[fd0f::10]]:], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 [[fd0f::10]]:], [1], [],
 [ovn-nbctl: [[fd0f::10]]:: should be an IP address.
 ])
 
 
-AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::1001a], [1], [],
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 ae0f::10 fd0f::1001a], [1], [],
 [ovn-nbctl: fd0f::1001a: should be an IP address.
 ])
 
 
+AT_CHECK([ovn-nbctl -vsocket_util:off lb-add lb0 [[ae0f::10]]: [[fd0f::10]]:80,[[fd0f::20]]:80 tcp], [1], [],
+[ovn-nbctl: Protocol is unnecessary when no port of vip is given.
+])
+
+
 AT_CHECK([ovn-nbctl lb-add lb0 ae0f::10 fd0f::10 tcp], [1], [],
 [ovn-nbctl: Protocol is unnecessary when no port of vip is given.
 ])
