Browse Source

Merge remote-tracking branch 'twstrike/parse_port_config_tests'

Nick Mathewson 8 years ago
parent
commit
cbed61d128
3 changed files with 889 additions and 23 deletions
  1. 13 20
      src/or/config.c
  2. 22 1
      src/or/config.h
  3. 854 2
      src/test/test_config.c

+ 13 - 20
src/or/config.c

@@ -590,7 +590,6 @@ static const config_var_t testing_tor_network_defaults[] = {
 static char *get_windows_conf_root(void);
 #endif
 static int options_act_reversible(const or_options_t *old_options, char **msg);
-static int options_act(const or_options_t *old_options);
 static int options_transition_allowed(const or_options_t *old,
                                       const or_options_t *new,
                                       char **msg);
@@ -671,9 +670,9 @@ get_dirportfrontpage, (void))
   return global_dirfrontpagecontents;
 }
 
-/** Return the currently configured options. */
-or_options_t *
-get_options_mutable(void)
+/** Returns the currently configured options. */
+MOCK_IMPL(or_options_t *,
+get_options_mutable, (void))
 {
   tor_assert(global_options);
   return global_options;
@@ -833,7 +832,6 @@ config_free_all(void)
 
   tor_free(torrc_fname);
   tor_free(torrc_defaults_fname);
-  tor_free(the_tor_version);
   tor_free(global_dirfrontpagecontents);
 
   tor_free(the_short_tor_version);
@@ -1446,7 +1444,7 @@ options_transition_requires_fresh_tls_context(const or_options_t *old_options,
  * Note: We haven't moved all the "act on new configuration" logic
  * here yet.  Some is still in do_hup() and other places.
  */
-static int
+STATIC int
 options_act(const or_options_t *old_options)
 {
   config_line_t *cl;
@@ -1468,10 +1466,12 @@ options_act(const or_options_t *old_options)
     if (options->DisableDebuggerAttachment && !disabled_debugger_attach &&
         running_tor) {
       int ok = tor_disable_debugger_attach();
+      /* LCOV_EXCL_START the warned_debugger_attach is 0 can't reach inside. */
       if (warned_debugger_attach && ok == 1) {
         log_notice(LD_CONFIG, "Disabled attaching debuggers for unprivileged "
                    "users.");
       }
+      /* LCOV_EXCL_STOP */
       disabled_debugger_attach = (ok == 1);
     } else if (!options->DisableDebuggerAttachment &&
                !warned_debugger_attach) {
@@ -1498,12 +1498,14 @@ options_act(const or_options_t *old_options)
 #endif
 
 #ifdef ENABLE_TOR2WEB_MODE
+/* LCOV_EXCL_START */
   if (!options->Tor2webMode) {
     log_err(LD_CONFIG, "This copy of Tor was compiled to run in "
             "'tor2web mode'. It can only be run with the Tor2webMode torrc "
             "option enabled.");
     return -1;
   }
+/* LCOV_EXCL_STOP */
 #else
   if (options->Tor2webMode) {
     log_err(LD_CONFIG, "This copy of Tor was not compiled to run in "
@@ -1767,8 +1769,8 @@ options_act(const or_options_t *old_options)
     if (revise_trackexithosts)
       addressmap_clear_excluded_trackexithosts(options);
 
-    if (!options->AutomapHostsOnResolve) {
-      if (old_options->AutomapHostsOnResolve)
+    if (!options->AutomapHostsOnResolve &&
+        old_options->AutomapHostsOnResolve) {
         revise_automap_entries = 1;
     } else {
       if (!smartlist_strings_eq(old_options->AutomapHostsSuffixes,
@@ -1907,8 +1909,8 @@ options_act(const or_options_t *old_options)
       print_notice = 1;
     }
     if (print_notice)
-      log_notice(LD_CONFIG, "Configured to measure statistics. Look for "
-                 "the *-stats files that will first be written to the "
+        log_notice(LD_CONFIG, "Configured to measure statistics. Look for "
+                "the *-stats files that will first be written to the "
                  "data directory in 24 hours from now.");
   }
 
@@ -6072,15 +6074,6 @@ warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid_nonlocal)
   } SMARTLIST_FOREACH_END(port);
 }
 
-#define CL_PORT_NO_STREAM_OPTIONS (1u<<0)
-#define CL_PORT_WARN_NONLOCAL (1u<<1)
-#define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2)
-#define CL_PORT_SERVER_OPTIONS (1u<<3)
-#define CL_PORT_FORBID_NONLOCAL (1u<<4)
-#define CL_PORT_TAKES_HOSTNAMES (1u<<5)
-#define CL_PORT_IS_UNIXSOCKET (1u<<6)
-#define CL_PORT_DFLT_GROUP_WRITABLE (1u<<7)
-
 #ifdef HAVE_SYS_UN_H
 
 /** Parse the given <b>addrport</b> and set <b>path_out</b> if a Unix socket
@@ -6168,7 +6161,7 @@ config_parse_unix_port(const char *addrport, char **path_out)
  * <b>out</b> for every port that the client should listen on.  Return 0
  * on success, -1 on failure.
  */
-static int
+STATIC int
 parse_port_config(smartlist_t *out,
                   const config_line_t *ports,
                   const config_line_t *listenaddrs,

+ 22 - 1
src/or/config.h

@@ -16,7 +16,7 @@
 
 MOCK_DECL(const char*, get_dirportfrontpage, (void));
 MOCK_DECL(const or_options_t *, get_options, (void));
-or_options_t *get_options_mutable(void);
+MOCK_DECL(or_options_t *, get_options_mutable, (void));
 int set_options(or_options_t *new_val, char **msg);
 void config_free_all(void);
 const char *safe_str_client(const char *address);
@@ -136,6 +136,17 @@ smartlist_t *get_options_from_transport_options_line(const char *line,
 smartlist_t *get_options_for_server_transport(const char *transport);
 
 #ifdef CONFIG_PRIVATE
+
+#define CL_PORT_NO_STREAM_OPTIONS (1u<<0)
+#define CL_PORT_WARN_NONLOCAL (1u<<1)
+#define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2)
+#define CL_PORT_SERVER_OPTIONS (1u<<3)
+#define CL_PORT_FORBID_NONLOCAL (1u<<4)
+#define CL_PORT_TAKES_HOSTNAMES (1u<<5)
+#define CL_PORT_IS_UNIXSOCKET (1u<<6)
+#define CL_PORT_DFLT_GROUP_WRITABLE (1u<<7)
+
+STATIC int options_act(const or_options_t *old_options);
 #ifdef TOR_UNIT_TESTS
 extern struct config_format_t options_format;
 #endif
@@ -160,6 +171,16 @@ STATIC int parse_dir_authority_line(const char *line,
 STATIC int parse_dir_fallback_line(const char *line, int validate_only);
 STATIC int have_enough_mem_for_dircache(const or_options_t *options,
                                         size_t total_mem, char **msg);
+STATIC int parse_dir_fallback_line(const char *line,
+                                   int validate_only);
+STATIC int parse_port_config(smartlist_t *out,
+                  const config_line_t *ports,
+                  const config_line_t *listenaddrs,
+                  const char *portname,
+                  int listener_type,
+                  const char *defaultaddr,
+                  int defaultport,
+                  const unsigned flags);
 #endif
 
 #endif

+ 854 - 2
src/test/test_config.c

@@ -7,20 +7,43 @@
 
 #define CONFIG_PRIVATE
 #define PT_PRIVATE
+#define ROUTERSET_PRIVATE
 #include "or.h"
+#include "address.h"
 #include "addressmap.h"
+#include "circuitmux_ewma.h"
+#include "circuitbuild.h"
 #include "config.h"
 #include "confparse.h"
+#include "connection.h"
 #include "connection_edge.h"
 #include "test.h"
 #include "util.h"
 #include "address.h"
+#include "connection_or.h"
+#include "control.h"
+#include "cpuworker.h"
+#include "dirserv.h"
+#include "dirvote.h"
+#include "dns.h"
 #include "entrynodes.h"
 #include "transports.h"
-#include "routerlist.h"
+#include "ext_orport.h"
+#include "geoip.h"
+#include "hibernate.h"
+#include "main.h"
 #include "networkstatus.h"
+#include "nodelist.h"
+#include "policies.h"
+#include "rendclient.h"
+#include "rendservice.h"
 #include "router.h"
-#include "dirserv.h"
+#include "routerlist.h"
+#include "routerset.h"
+#include "statefile.h"
+#include "test.h"
+#include "transports.h"
+#include "util.h"
 
 static void
 test_config_addressmap(void *arg)
@@ -3687,6 +3710,831 @@ test_config_default_fallback_dirs(void *arg)
   clear_dir_servers();
 }
 
+static config_line_t *
+mock_config_line(const char *key, const char *val)
+{
+  config_line_t *config_line = tor_malloc(sizeof(config_line_t));
+  memset(config_line, 0, sizeof(config_line_t));
+  config_line->key = tor_strdup(key);
+  config_line->value = tor_strdup(val);
+  return config_line;
+}
+
+static void
+test_config_parse_port_config__listenaddress(void *data)
+{
+  (void)data;
+  int ret;
+  config_line_t *config_listen_address = NULL, *config_listen_address2 = NULL,
+    *config_listen_address3 = NULL;
+  config_line_t *config_port1 = NULL, *config_port2 = NULL,
+    *config_port3 = NULL, *config_port4 = NULL, *config_port5 = NULL;
+  smartlist_t *slout = NULL;
+  port_cfg_t *port_cfg = NULL;
+
+  // Test basic invocation with no arguments
+  ret = parse_port_config(NULL, NULL, NULL, NULL, 0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Setup some test data
+  config_listen_address = mock_config_line("DNSListenAddress", "127.0.0.1");
+  config_listen_address2 = mock_config_line("DNSListenAddress", "x$$$:::345");
+  config_listen_address3 = mock_config_line("DNSListenAddress",
+                                            "127.0.0.1:1442");
+  config_port1 = mock_config_line("DNSPort", "42");
+  config_port2 = mock_config_line("DNSPort", "43");
+  config_port1->next = config_port2;
+  config_port3 = mock_config_line("DNSPort", "auto");
+  config_port4 = mock_config_line("DNSPort", "55542");
+  config_port5 = mock_config_line("DNSPort", "666777");
+
+  // Test failure when we have a ListenAddress line and several
+  // Port lines for the same portname
+  ret = parse_port_config(NULL, config_port1, config_listen_address, "DNS", 0,
+                          NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test case when we have a listen address, no default port and allow
+  // spurious listen address lines
+  ret = parse_port_config(NULL, NULL, config_listen_address, "DNS", 0, NULL,
+                          0, CL_PORT_ALLOW_EXTRA_LISTENADDR);
+  tt_int_op(ret, OP_EQ, 1);
+
+  // Test case when we have a listen address, no default port but doesn't
+  // allow spurious listen address lines
+  ret = parse_port_config(NULL, NULL, config_listen_address, "DNS", 0, NULL,
+                          0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test case when we have a listen address, and a port that points to auto,
+  // should use the AUTO port
+  slout = smartlist_new();
+  ret = parse_port_config(slout, config_port3, config_listen_address, "DNS",
+                          0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, CFG_AUTO_PORT);
+
+  // Test when we have a listen address and a custom port
+  ret = parse_port_config(slout, config_port4, config_listen_address, "DNS",
+                          0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 2);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 1);
+  tt_int_op(port_cfg->port, OP_EQ, 55542);
+
+  // Test when we have a listen address and an invalid custom port
+  ret = parse_port_config(slout, config_port5, config_listen_address, "DNS",
+                          0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test we get a server port configuration when asked for it
+  ret = parse_port_config(slout, NULL, config_listen_address, "DNS", 0, NULL,
+                          123, CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 4);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 2);
+  tt_int_op(port_cfg->port, OP_EQ, 123);
+  tt_int_op(port_cfg->server_cfg.no_listen, OP_EQ, 1);
+  tt_int_op(port_cfg->server_cfg.bind_ipv4_only, OP_EQ, 1);
+
+  // Test an invalid ListenAddress configuration
+  ret = parse_port_config(NULL, NULL, config_listen_address2, "DNS", 0, NULL,
+                          222, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test default to the port in the listen address if available
+  ret = parse_port_config(slout, config_port2, config_listen_address3, "DNS",
+                          0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 5);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 4);
+  tt_int_op(port_cfg->port, OP_EQ, 1442);
+
+  // Test we work correctly without an out, but with a listen address
+  // and a port
+  ret = parse_port_config(NULL, config_port2, config_listen_address, "DNS",
+                          0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test warning nonlocal control
+  ret = parse_port_config(slout, config_port2, config_listen_address, "DNS",
+                          CONN_TYPE_CONTROL_LISTENER, NULL, 0,
+                          CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test warning nonlocal ext or listener
+  ret = parse_port_config(slout, config_port2, config_listen_address, "DNS",
+                          CONN_TYPE_EXT_OR_LISTENER, NULL, 0,
+                          CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test warning nonlocal other
+  ret = parse_port_config(slout, config_port2, config_listen_address, "DNS",
+                          0, NULL, 0, CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test warning nonlocal control without an out
+  ret = parse_port_config(NULL, config_port2, config_listen_address, "DNS",
+                          CONN_TYPE_CONTROL_LISTENER, NULL, 0,
+                          CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+ done:
+  tor_free(config_listen_address);
+  tor_free(config_listen_address2);
+  tor_free(config_listen_address3);
+  tor_free(config_port1);
+  tor_free(config_port2);
+  tor_free(config_port3);
+  tor_free(config_port4);
+  tor_free(config_port5);
+  smartlist_free(slout);
+}
+
+static void
+test_config_parse_port_config__ports__no_ports_given(void *data)
+{
+  (void)data;
+  int ret;
+  smartlist_t *slout = NULL;
+  port_cfg_t *port_cfg = NULL;
+  config_line_t *config_port_invalid = NULL, *config_port_valid = NULL;
+
+  slout = smartlist_new();
+
+  // Test no defaultport, no defaultaddress and no out
+  ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test with defaultport, no defaultaddress and no out
+  ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, NULL, 42, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test no defaultport, with defaultaddress and no out
+  ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, "127.0.0.2", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test with defaultport, with defaultaddress and no out
+  ret = parse_port_config(NULL, NULL, NULL, "DNS", 0, "127.0.0.2", 42, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test no defaultport, no defaultaddress and with out
+  ret = parse_port_config(slout, NULL, NULL, "DNS", 0, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 0);
+
+  // Test with defaultport, no defaultaddress and with out
+  ret = parse_port_config(slout, NULL, NULL, "DNS", 0, NULL, 42, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 0);
+
+  // Test no defaultport, with defaultaddress and with out
+  ret = parse_port_config(slout, NULL, NULL, "DNS", 0, "127.0.0.2", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 0);
+
+  // Test with defaultport, with defaultaddress and out, adds a new port cfg
+  smartlist_clear(slout);
+  ret = parse_port_config(slout, NULL, NULL, "DNS", 0, "127.0.0.2", 42, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, 42);
+  tt_int_op(port_cfg->is_unix_addr, OP_EQ, 0);
+
+  // Test with defaultport, with defaultaddress and out, adds a new port cfg
+  // for a unix address
+  smartlist_clear(slout);
+  ret = parse_port_config(slout, NULL, NULL, "DNS", 0, "/foo/bar/unixdomain",
+                          42, CL_PORT_IS_UNIXSOCKET);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, 0);
+  tt_int_op(port_cfg->is_unix_addr, OP_EQ, 1);
+  tt_str_op(port_cfg->unix_addr, OP_EQ, "/foo/bar/unixdomain");
+
+ done:
+  smartlist_free(slout);
+  tor_free(config_port_invalid);
+  tor_free(config_port_valid);
+}
+
+static void
+test_config_parse_port_config__ports__ports_given(void *data)
+{
+  (void)data;
+  int ret;
+  smartlist_t *slout = NULL;
+  port_cfg_t *port_cfg = NULL;
+  config_line_t *config_port_invalid = NULL, *config_port_valid = NULL;
+  tor_addr_t addr;
+
+  slout = smartlist_new();
+
+  // Test error when encounters an invalid Port specification
+  config_port_invalid = mock_config_line("DNSPort", "");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, NULL,
+                          0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test error when encounters an empty unix domain specification
+  tor_free(config_port_invalid);
+  config_port_invalid = mock_config_line("DNSPort", "unix:");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0, NULL,
+                          0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test error when encounters a unix domain specification but the listener
+  // doesnt support domain sockets
+  config_port_valid = mock_config_line("DNSPort", "unix:/tmp/foo/bar");
+  ret = parse_port_config(NULL, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_DNS_LISTENER, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test valid unix domain
+  smartlist_clear(slout);
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, NULL, 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, 0);
+  tt_int_op(port_cfg->is_unix_addr, OP_EQ, 1);
+  tt_str_op(port_cfg->unix_addr, OP_EQ, "/tmp/foo/bar");
+
+  // Test failure if we have no ipv4 and no ipv6 (for unix domain sockets,
+  // this makes no sense - it should be fixed)
+  tor_free(config_port_invalid);
+  config_port_invalid = mock_config_line("DNSPort",
+                                         "unix:/tmp/foo/bar NoIPv4Traffic");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with no ipv4 but take ipv6 (for unix domain sockets, this
+  // makes no sense - it should be fixed)
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "unix:/tmp/foo/bar "
+                                       "NoIPv4Traffic IPv6Traffic");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 1);
+
+  // Test success with both ipv4 and ipv6 (for unix domain sockets,
+  // this makes no sense - it should be fixed)
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "unix:/tmp/foo/bar "
+                                       "IPv4Traffic IPv6Traffic");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 1);
+
+  // Test failure if we specify world writable for an IP Port
+  tor_free(config_port_invalid);
+  config_port_invalid = mock_config_line("DNSPort", "42 WorldWritable");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test failure if we specify group writable for an IP Port
+  tor_free(config_port_invalid);
+  config_port_invalid = mock_config_line("DNSPort", "42 GroupWritable");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with only a port (this will fail without a default address)
+  tor_free(config_port_valid);
+  config_port_valid = mock_config_line("DNSPort", "42");
+  ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test success with only a port and isolate destination port
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IsolateDestPort");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.isolation_flags, OP_EQ,
+            ISO_DEFAULT | ISO_DESTPORT);
+
+  // Test success with a negative isolate destination port, and plural
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 NoIsolateDestPorts");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.isolation_flags, OP_EQ,
+            ISO_DEFAULT & ~ISO_DESTPORT);
+
+  // Test success with isolate destination address
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IsolateDestAddr");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.isolation_flags, OP_EQ,
+            ISO_DEFAULT | ISO_DESTADDR);
+
+  // Test success with isolate socks AUTH
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IsolateSOCKSAuth");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.isolation_flags, OP_EQ,
+            ISO_DEFAULT | ISO_SOCKSAUTH);
+
+  // Test success with isolate client protocol
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IsolateClientProtocol");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.isolation_flags, OP_EQ,
+            ISO_DEFAULT | ISO_CLIENTPROTO);
+
+  // Test success with isolate client address
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IsolateClientAddr");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.isolation_flags, OP_EQ,
+            ISO_DEFAULT | ISO_CLIENTADDR);
+
+  // Test success with ignored unknown options
+  tor_free(config_port_valid);
+  config_port_valid = mock_config_line("DNSPort", "42 ThisOptionDoesntExist");
+  ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test success with no isolate socks AUTH
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 NoIsolateSOCKSAuth");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.3", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.socks_prefer_no_auth, OP_EQ, 1);
+
+  // Test success with prefer ipv6
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IPv6Traffic PreferIPv6");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, "127.0.0.42", 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.prefer_ipv6, OP_EQ, 1);
+
+  // Test success with cache ipv4 DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 CacheIPv4DNS");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv6_answers, OP_EQ, 0);
+
+  // Test success with cache ipv6 DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 CacheIPv6DNS");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv6_answers, OP_EQ, 1);
+
+  // Test success with no cache ipv4 DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 NoCacheIPv4DNS");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv6_answers, OP_EQ, 0);
+
+  // Test success with cache DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 CacheDNS");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, CL_PORT_TAKES_HOSTNAMES);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv6_answers, OP_EQ, 1);
+
+  // Test success with use cached ipv4 DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 UseIPv4Cache");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.use_cached_ipv4_answers, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.use_cached_ipv6_answers, OP_EQ, 0);
+
+  // Test success with use cached ipv6 DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 UseIPv6Cache");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.use_cached_ipv4_answers, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.use_cached_ipv6_answers, OP_EQ, 1);
+
+  // Test success with use cached DNS
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 UseDNSCache");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.use_cached_ipv4_answers, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.use_cached_ipv6_answers, OP_EQ, 1);
+
+  // Test success with not preferring ipv6 automap
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 NoPreferIPv6Automap");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.prefer_ipv6_virtaddr, OP_EQ, 0);
+
+  // Test success with prefer SOCKS no auth
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 PreferSOCKSNoAuth");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.socks_prefer_no_auth, OP_EQ, 1);
+
+  // Test failure with both a zero port and a non-zero port
+  tor_free(config_port_invalid);
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "0");
+  config_port_valid = mock_config_line("DNSPort", "42");
+  config_port_invalid->next = config_port_valid;
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with warn non-local control
+  smartlist_clear(slout);
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_CONTROL_LISTENER, "127.0.0.42", 0,
+                          CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test success with warn non-local listener
+  smartlist_clear(slout);
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_EXT_OR_LISTENER, "127.0.0.42", 0,
+                          CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test success with warn non-local other
+  smartlist_clear(slout);
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test success with warn non-local other without out
+  ret = parse_port_config(NULL, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.42", 0, CL_PORT_WARN_NONLOCAL);
+  tt_int_op(ret, OP_EQ, 0);
+
+  // Test success with both ipv4 and ipv6 but without stream options
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 IPv4Traffic "
+                                       "IPv6Traffic");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.44", 0,
+                          CL_PORT_TAKES_HOSTNAMES |
+                          CL_PORT_NO_STREAM_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0);
+
+  // Test failure for a SessionGroup argument with invalid value
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "42 SessionGroup=invalid");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.44", 0, CL_PORT_NO_STREAM_OPTIONS);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // TODO: this seems wrong. Shouldn't it be the other way around?
+  // Potential bug.
+  // Test failure for a SessionGroup argument with valid value but with stream
+  // options allowed
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "42 SessionGroup=123");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.44", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test failure for more than one SessionGroup argument
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "42 SessionGroup=123 "
+                                         "SessionGroup=321");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.44", 0, CL_PORT_NO_STREAM_OPTIONS);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with a sessiongroup options
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "42 SessionGroup=1111122");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.44", 0, CL_PORT_NO_STREAM_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->entry_cfg.session_group, OP_EQ, 1111122);
+
+  // Test success with a zero unix domain socket, and doesnt add it to out
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "0");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.45", 0, CL_PORT_IS_UNIXSOCKET);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 0);
+
+  // Test success with a one unix domain socket, and doesnt add it to out
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "something");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.45", 0, CL_PORT_IS_UNIXSOCKET);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->is_unix_addr, OP_EQ, 1);
+  tt_str_op(port_cfg->unix_addr, OP_EQ, "something");
+
+  // Test success with a port of auto - it uses the default address
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "auto");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.46", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, CFG_AUTO_PORT);
+  tor_addr_parse(&addr, "127.0.0.46");
+  tt_assert(tor_addr_eq(&port_cfg->addr, &addr))
+
+  // Test success with parsing both an address and an auto port
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "127.0.0.122:auto");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.46", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, CFG_AUTO_PORT);
+  tor_addr_parse(&addr, "127.0.0.122");
+  tt_assert(tor_addr_eq(&port_cfg->addr, &addr))
+
+  // Test failure when asked to parse an invalid address followed by auto
+  tor_free(config_port_invalid);
+  config_port_invalid = mock_config_line("DNSPort", "invalidstuff!!:auto");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.46", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with parsing both an address and a real port
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "127.0.0.123:656");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0,
+                          "127.0.0.46", 0, 0);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->port, OP_EQ, 656);
+  tor_addr_parse(&addr, "127.0.0.123");
+  tt_assert(tor_addr_eq(&port_cfg->addr, &addr))
+
+  // Test failure if we can't parse anything at all
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "something wrong");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.46", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test failure if we find both an address, a port and an auto
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "127.0.1.0:123:auto");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0,
+                          "127.0.0.46", 0, 0);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test that default to group writeable default sets group writeable for
+  // domain socket
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "unix:/tmp/somewhere");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, "127.0.0.46", 0,
+                          CL_PORT_DFLT_GROUP_WRITABLE);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->is_group_writable, OP_EQ, 1);
+
+ done:
+  smartlist_free(slout);
+  tor_free(config_port_invalid);
+  tor_free(config_port_valid);
+}
+
+static void
+test_config_parse_port_config__ports__server_options(void *data)
+{
+  (void)data;
+  int ret;
+  smartlist_t *slout = NULL;
+  port_cfg_t *port_cfg = NULL;
+  config_line_t *config_port_invalid = NULL, *config_port_valid = NULL;
+
+  slout = smartlist_new();
+
+  // Test success with NoAdvertise option
+  tor_free(config_port_valid);
+  config_port_valid = mock_config_line("DNSPort",
+                                       "127.0.0.124:656 NoAdvertise");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0,
+                          CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->server_cfg.no_advertise, OP_EQ, 1);
+  tt_int_op(port_cfg->server_cfg.no_listen, OP_EQ, 0);
+
+  // Test success with NoListen option
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 NoListen");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0,
+                          CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->server_cfg.no_advertise, OP_EQ, 0);
+  tt_int_op(port_cfg->server_cfg.no_listen, OP_EQ, 1);
+
+  // Test failure with both NoAdvertise and NoListen option
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "127.0.0.124:656 NoListen "
+                                         "NoAdvertise");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL,
+                          0, CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with IPv4Only
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 IPv4Only");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0,
+                          CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->server_cfg.bind_ipv4_only, OP_EQ, 1);
+  tt_int_op(port_cfg->server_cfg.bind_ipv6_only, OP_EQ, 0);
+
+  // Test success with IPv6Only
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "[::1]:656 IPv6Only");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0,
+                          CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+  port_cfg = (port_cfg_t *)smartlist_get(slout, 0);
+  tt_int_op(port_cfg->server_cfg.bind_ipv4_only, OP_EQ, 0);
+  tt_int_op(port_cfg->server_cfg.bind_ipv6_only, OP_EQ, 1);
+
+  // Test failure with both IPv4Only and IPv6Only
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "127.0.0.124:656 IPv6Only "
+                                         "IPv4Only");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL,
+                          0, CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test success with invalid parameter
+  tor_free(config_port_valid);
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "127.0.0.124:656 unknown");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS", 0, NULL, 0,
+                          CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, 0);
+  tt_int_op(smartlist_len(slout), OP_EQ, 1);
+
+  // Test failure when asked to bind only to ipv6 but gets an ipv4 address
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort",
+                                         "127.0.0.124:656 IPv6Only");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL,
+                          0, CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // Test failure when asked to bind only to ipv4 but gets an ipv6 address
+  tor_free(config_port_invalid);
+  smartlist_clear(slout);
+  config_port_invalid = mock_config_line("DNSPort", "[::1]:656 IPv4Only");
+  ret = parse_port_config(slout, config_port_invalid, NULL, "DNS", 0, NULL,
+                          0, CL_PORT_SERVER_OPTIONS);
+  tt_int_op(ret, OP_EQ, -1);
+
+ done:
+  smartlist_free(slout);
+  tor_free(config_port_invalid);
+  tor_free(config_port_valid);
+}
+
 #define CONFIG_TEST(name, flags)                          \
   { #name, test_config_ ## name, flags, NULL, NULL }
 
@@ -3708,6 +4556,10 @@ struct testcase_t config_tests[] = {
   CONFIG_TEST(write_to_data_subdir, TT_FORK),
   CONFIG_TEST(fix_my_family, 0),
   CONFIG_TEST(directory_fetch, 0),
+  CONFIG_TEST(parse_port_config__listenaddress, 0),
+  CONFIG_TEST(parse_port_config__ports__no_ports_given, 0),
+  CONFIG_TEST(parse_port_config__ports__server_options, 0),
+  CONFIG_TEST(parse_port_config__ports__ports_given, 0),
   END_OF_TESTCASES
 };