diff --git a/changes/feature18693 b/changes/feature18693
new file mode 100644
index 0000000000000000000000000000000000000000..ce7c9939a05b4147b6afe7d7f5793e3e812bccd5
--- /dev/null
+++ b/changes/feature18693
@@ -0,0 +1,5 @@
+  o Minor feature (port flags):
+    - Add *Port flags NoDNSRequest and NoOnionTraffic, and
+      the synthetic flag OnionTrafficOnly, which is equivalent to
+      NoDNSRequest, NoIPv4Traffic, and NoIPv6Traffic.
+      Closes enhancement 18693; patch by "teor".
diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index 055073656b00151dff79926faa66e3a1c81037cb..2748f54546a5b92594f69da5e8adb6071c0aabbb 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -1083,7 +1083,18 @@ The following options are useful only for clients (that is, if
         IPv6.)
     **PreferIPv6**;;
         Tells exits that, if a host has both an IPv4 and an IPv6 address,
-        we would prefer to connect to it via IPv6. (IPv4 is the default.) +
+        we would prefer to connect to it via IPv6. (IPv4 is the default.)
+    **NoDNSRequest**;;
+        Do not ask exits to resolve DNS addresses in SOCKS5 requests. Tor will
+        connect to IPv4 addresses, IPv6 addresses (if IPv6Traffic is set) and
+        .onion addresses.
+    **NoOnionTraffic**;;
+        Do not connect to .onion addresses in SOCKS5 requests.
+    **OnionTrafficOnly**;;
+        Tell the tor client to only connect to .onion addresses in response to
+        SOCKS5 requests on this connection. This is equivalent to NoDNSRequest,
+        NoIPv4Traffic, NoIPv6Traffic. The corresponding NoOnionTrafficOnly
+        flag is not supported.
     **CacheIPv4DNS**;;
         Tells the client to remember IPv4 DNS answers we receive from exit
         nodes via this connection. (On by default.)
@@ -1125,6 +1136,10 @@ The following options are useful only for clients (that is, if
         authentication" when IsolateSOCKSAuth is disabled, or when this
         option is set.
 
+    Flags are processed left to right. If flags conflict, the last flag on the
+    line is used, and all earlier flags are ignored. No error is issued for
+    conflicting flags.
+
 [[SocksListenAddress]] **SocksListenAddress** __IP__[:__PORT__]::
     Bind to this address to listen for connections from Socks-speaking
     applications. (Default: 127.0.0.1) You can also specify a port (e.g.
diff --git a/src/or/config.c b/src/or/config.c
index 541025de16c990aabb9d8320fceb113b662d2ee6..10002ff6205c9750b52753ed4741cd250ae9fba9 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -6025,6 +6025,8 @@ port_cfg_new(size_t namelen)
   tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1);
   port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t) + namelen + 1);
   cfg->entry_cfg.ipv4_traffic = 1;
+  cfg->entry_cfg.dns_request = 1;
+  cfg->entry_cfg.onion_traffic = 1;
   cfg->entry_cfg.cache_ipv4_answers = 1;
   cfg->entry_cfg.prefer_ipv6_virtaddr = 1;
   return cfg;
@@ -6295,8 +6297,7 @@ parse_port_config(smartlist_t *out,
       tor_addr_make_unspec(&cfg->addr); /* Server ports default to 0.0.0.0 */
       cfg->server_cfg.no_listen = 1;
       cfg->server_cfg.bind_ipv4_only = 1;
-      cfg->entry_cfg.ipv4_traffic = 1;
-      cfg->entry_cfg.prefer_ipv6_virtaddr = 1;
+      /* cfg->entry_cfg defaults are already set by port_cfg_new */
       smartlist_add(out, cfg);
     }
 
@@ -6367,9 +6368,11 @@ parse_port_config(smartlist_t *out,
     char *addrport;
     uint16_t ptmp=0;
     int ok;
+    /* This must be kept in sync with port_cfg_new's defaults */
     int no_listen = 0, no_advertise = 0, all_addrs = 0,
       bind_ipv4_only = 0, bind_ipv6_only = 0,
-      ipv4_traffic = 1, ipv6_traffic = 0, prefer_ipv6 = 0,
+      ipv4_traffic = 1, ipv6_traffic = 0, prefer_ipv6 = 0, dns_request = 1,
+      onion_traffic = 1,
       cache_ipv4 = 1, use_cached_ipv4 = 0,
       cache_ipv6 = 0, use_cached_ipv6 = 0,
       prefer_ipv6_automap = 1, world_writable = 0, group_writable = 0,
@@ -6555,6 +6558,24 @@ parse_port_config(smartlist_t *out,
           } else if (!strcasecmp(elt, "PreferIPv6")) {
             prefer_ipv6 = ! no;
             continue;
+          } else if (!strcasecmp(elt, "DNSRequest")) {
+            dns_request = ! no;
+            continue;
+          } else if (!strcasecmp(elt, "OnionTraffic")) {
+            onion_traffic = ! no;
+            continue;
+          } else if (!strcasecmp(elt, "OnionTrafficOnly")) {
+            /* Only connect to .onion addresses.  Equivalent to
+             * NoDNSRequest, NoIPv4Traffic, NoIPv6Traffic. The option
+             * NoOnionTrafficOnly is not supported, it's too confusing. */
+            if (no) {
+              log_warn(LD_CONFIG, "Unsupported %sPort option 'No%s'. Use "
+                       "DNSRequest, IPv4Traffic, and/or IPv6Traffic instead.",
+                       portname, escaped(elt));
+            } else {
+              ipv4_traffic = ipv6_traffic = dns_request = 0;
+            }
+            continue;
           }
         }
         if (!strcasecmp(elt, "CacheIPv4DNS")) {
@@ -6623,9 +6644,24 @@ parse_port_config(smartlist_t *out,
     else
       got_zero_port = 1;
 
-    if (ipv4_traffic == 0 && ipv6_traffic == 0) {
-      log_warn(LD_CONFIG, "You have a %sPort entry with both IPv4 and "
-               "IPv6 disabled; that won't work.", portname);
+    if (dns_request == 0 && listener_type == CONN_TYPE_AP_DNS_LISTENER) {
+      log_warn(LD_CONFIG, "You have a %sPort entry with DNS disabled; that "
+               "won't work.", portname);
+      goto err;
+    }
+
+    if (ipv4_traffic == 0 && ipv6_traffic == 0 && onion_traffic == 0
+        && listener_type != CONN_TYPE_AP_DNS_LISTENER) {
+      log_warn(LD_CONFIG, "You have a %sPort entry with all of IPv4 and "
+               "IPv6 and .onion disabled; that won't work.", portname);
+      goto err;
+    }
+
+    if (dns_request == 1 && ipv4_traffic == 0 && ipv6_traffic == 0
+        && listener_type != CONN_TYPE_AP_DNS_LISTENER) {
+      log_warn(LD_CONFIG, "You have a %sPort entry with DNSRequest enabled, "
+               "but IPv4 and IPv6 disabled; DNS-based sites won't work.",
+               portname);
       goto err;
     }
 
@@ -6669,6 +6705,8 @@ parse_port_config(smartlist_t *out,
       cfg->entry_cfg.ipv4_traffic = ipv4_traffic;
       cfg->entry_cfg.ipv6_traffic = ipv6_traffic;
       cfg->entry_cfg.prefer_ipv6 = prefer_ipv6;
+      cfg->entry_cfg.dns_request = dns_request;
+      cfg->entry_cfg.onion_traffic = onion_traffic;
       cfg->entry_cfg.cache_ipv4_answers = cache_ipv4;
       cfg->entry_cfg.cache_ipv6_answers = cache_ipv6;
       cfg->entry_cfg.use_cached_ipv4_answers = use_cached_ipv4;
diff --git a/src/or/or.h b/src/or/or.h
index 43b31c0fdda448b45d0692310ace3fc2be69f884..d83a921ae9ba423ecc6b60ac1fad7cfc01ff2c39 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -1151,6 +1151,8 @@ typedef struct entry_port_cfg_t {
   unsigned int ipv4_traffic : 1;
   unsigned int ipv6_traffic : 1;
   unsigned int prefer_ipv6 : 1;
+  unsigned int dns_request : 1;
+  unsigned int onion_traffic : 1;
 
   /** For a socks listener: should we cache IPv4/IPv6 DNS information that
    * exit nodes tell us?
diff --git a/src/test/test_config.c b/src/test/test_config.c
index 90ea4da87d90643afaea017527d6647c9c340855..6d5b97b343ad57d2a0bc6e921b7431461dbb080b 100644
--- a/src/test/test_config.c
+++ b/src/test/test_config.c
@@ -3952,7 +3952,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
   tt_int_op(ret, OP_EQ, -1);
 
   // Test error when encounters a unix domain specification but the listener
-  // doesnt support domain sockets
+  // doesn't 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);
@@ -3972,20 +3972,108 @@ test_config_parse_port_config__ports__ports_given(void *data)
   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 entry port defaults as initialised in parse_port_config */
+  tt_int_op(port_cfg->entry_cfg.dns_request, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.prefer_ipv6_virtaddr, OP_EQ, 1);
 #endif
 
-  // Test failure if we have no ipv4 and no ipv6 (for unix domain sockets,
-  // this makes no sense - it should be fixed)
+  // Test failure if we have no ipv4 and no ipv6 and no onion (DNS only)
+  config_free_lines(config_port_invalid); config_port_invalid = NULL;
+  config_port_invalid = mock_config_line("SOCKSPort",
+                                         "unix:/tmp/foo/bar NoIPv4Traffic "
+                                         "NoOnionTraffic");
+  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 failure if we have no DNS and we're a DNSPort
   config_free_lines(config_port_invalid); config_port_invalid = NULL;
   config_port_invalid = mock_config_line("DNSPort",
-                                         "unix:/tmp/foo/bar NoIPv4Traffic");
+                                         "127.0.0.1:80 NoDNSRequest");
   ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS",
+                          CONN_TYPE_AP_DNS_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+  tt_int_op(ret, OP_EQ, -1);
+
+  // If we're a DNSPort, DNS only is ok
+  // Use a port because DNSPort doesn't support sockets
+  config_free_lines(config_port_valid); config_port_valid = NULL;
+  SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "127.0.0.1:80 "
+                                       "NoIPv4Traffic NoOnionTraffic");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_DNS_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+#ifdef _WIN32
+  tt_int_op(ret, OP_EQ, -1);
+#else
+  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.dns_request, OP_EQ, 1);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 0);
+#endif
+
+  // Test failure if we have DNS but no ipv4 and no ipv6
+  config_free_lines(config_port_invalid); config_port_invalid = NULL;
+  config_port_invalid = mock_config_line("SOCKSPort",
+                                         "unix:/tmp/foo/bar NoIPv4Traffic");
+  ret = parse_port_config(NULL, config_port_invalid, NULL, "SOCKS",
                           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)
+  // Test success with no DNS, no ipv4, no ipv6 (only onion, using separate
+  // options)
+  config_free_lines(config_port_valid); config_port_valid = NULL;
+  SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "unix:/tmp/foo/bar "
+                                       "NoDNSRequest NoIPv4Traffic");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+#ifdef _WIN32
+  tt_int_op(ret, OP_EQ, -1);
+#else
+  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.dns_request, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1);
+#endif
+
+  // Test success with OnionTrafficOnly (no DNS, no ipv4, no ipv6)
+  config_free_lines(config_port_valid); config_port_valid = NULL;
+  SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
+  smartlist_clear(slout);
+  config_port_valid = mock_config_line("DNSPort", "unix:/tmp/foo/bar "
+                                       "OnionTrafficOnly");
+  ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
+                          CONN_TYPE_AP_LISTENER, NULL, 0,
+                          CL_PORT_TAKES_HOSTNAMES);
+#ifdef _WIN32
+  tt_int_op(ret, OP_EQ, -1);
+#else
+  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.dns_request, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0);
+  tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1);
+#endif
+
+  // Test success with no ipv4 but take ipv6
   config_free_lines(config_port_valid); config_port_valid = NULL;
   SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
   smartlist_clear(slout);
@@ -4004,8 +4092,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
   tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 1);
 #endif
 
-  // Test success with both ipv4 and ipv6 (for unix domain sockets,
-  // this makes no sense - it should be fixed)
+  // Test success with both ipv4 and ipv6
   config_free_lines(config_port_valid); config_port_valid = NULL;
   SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
   smartlist_clear(slout);