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);