Skip to content

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.

Edited by Clara Engler
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information