commit 41cc1f612bd2112ab7cec0cc4fdeb68c26e231bf
Author: teor (Tim Wilson-Brown) <teor2345(a)gmail.com>
Date: Thu Mar 31 21:26:41 2016 +1100
Parse *Port flags NoDNSRequest, NoOnionTraffic & OnionTrafficOnly
OnionTrafficOnly is equivalent to NoDNSRequest, NoIPv4Traffic,
and NoIPv6Traffic.
Add unit tests for parsing and checking option validity.
Add documentation for each flag to the man page.
Add changes file for all of #18693.
Parsing only: the flags do not change client behaviour (yet!)
---
changes/feature18693 | 5 +++
doc/tor.1.txt | 17 +++++++-
src/or/config.c | 50 +++++++++++++++++++++---
src/or/or.h | 2 +
src/test/test_config.c | 103 +++++++++++++++++++++++++++++++++++++++++++++----
5 files changed, 162 insertions(+), 15 deletions(-)
diff --git a/changes/feature18693 b/changes/feature18693
new file mode 100644
index 0000000..ce7c993
--- /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 0550736..2748f54 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 541025d..10002ff 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 43b31c0..d83a921 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 90ea4da..6d5b97b 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);