The CTor localhost problem
Related: chutney#40028 (closed), onionmasq#133 (closed)
Summary
TL;DR ctor code wants to use /etc/hosts but apparantly uses the wrong libevent functions for that
The following issue describes a problem that occurs when using ctor in a local environment with chutney, with a special regard on the /etc/hosts
file and resolving localhost
.
It has little value for production systems, but is very crucial for testing the isolation cookie feature in onionmasq, that is, proper isolation of UDP streams from each other.
The Problem
ctor exit nodes do not take the /etc/hosts
file into account when resolving names, even though the code semantically clearly tries to achieve this feature, as it can be seen in the following piece of code:
int flags;
/* [...] */
flags = DNS_OPTIONS_ALL;
/* [...] */
if (flags & DNS_OPTION_HOSTSFILE) {
flags ^= DNS_OPTION_HOSTSFILE;
log_debug(LD_FS, "Loading /etc/hosts");
evdns_base_load_hosts(the_evdns_base,
sandbox_intern_string("/etc/hosts"));
}
The problem for this is, that we resolve names using libevent's evdns_base_resolve_ipv4
and evdns_base_resolve_ipv6
which only resolve names by contacting a real nameserver, without paying any attention to the /etc/hosts
file, no matter the configuration of the_evdns_base
.
This behavior is not documented in libevent's documentation, but it becomes evident when you take a closer look at its implementation.
Libevent generally recommends to resolve names with the high-level evdns_getaddrinfo
function, which in fact takes /etc/hosts
into account and then uses the low-level evdns_base_resolve_ipv4
/evdns_base_resolve_ipv6
in case that no matching entry was found within the /etc/hosts
file.
Interestingly, when our host operating system is using a nameserver in /etc/resolv.conf
that has an A Record for localhost
pointing to 127.0.0.1
, everything works fine, as evdns_base_resolve_ipv4
and friends can obviously resolve that one. Now this is the part that I would actually consider to be a bug or at least not desirable, that is, depending on way too much external influence here.
The Solution?
I believe that the cleanest solution would be to let the ctor dns module use the high-level evdns_getaddrinfo
.
However, if I understand libevent properly, this function performs caching on its own, which would then collide with the caching we do in ctor.
Further re-evaluation is needed here, although I do not see what would speak against using evdns_getaddrinfo
.
An alternative would be, to let onionmasq also use an additional local unbound server which in fact maps localhost
to 127.0.0.1
and ::1
, although that one would greatly increase the complexity of the integration test suite and would not solve the issue of the already self-contradicting code in the dns.c file.
Besides, we could also make a PR for upstream libevent to let the function take care of /etc/hosts
, although I do not feel comfortable with the internal structure of libevent to decide whether this is a good idea or not.
Finally, I also have a solution that is rather simple and does not change much of the code, but is from an aesthetic solution more of an hack. This solution would interfere in the callback called by libevent and then basically overwrites the response, in the case that the request has a match in the host file. That is not beautiful though, because it would still perform a redundant request that could have been eliminated upfront if done properly.