That is exactly what we have patched our local Tor node to do, although with a different (slightly hacky, so the patch will be an RFC type) approach by marking real exit traffic with a ToS flag to leave the decision of what to do with it to the next layer (in our setup Linux kernel based policy routing on the same host). There may be a much better approach do achieve this goal. I plan on writing up our setup (and the rationale behind it) along with the "works for me but is not ready for upstream inclusion" patch tomorrow.
I'm not sure if we want to tag Tor traffic with QoS values at Exits. Any tagging carries some degree of risk, because it makes traffic look more unique. I'm not sure how much of a risk QoS tagging represents.
Fully agreed. That is why - if this turns out to be the best approach - we would remove the QoS tag again before the packets leave the host (and only use it for local policy routing decisions).
I would prefer to add config options OutboundBindAddressOR and OutboundBindAddressExit, which would default to OutboundBindAddress when not set. (And could be specified twice, once for IPv4, and once for IPv6.)
The one concern I have about this is that Tor-over-Tor would stick out more, as it would look like Tor coming out the OutboundBindAddressExit IP. But we don't encourage Tor-over-Tor anyway.
I'd recommend a patch that modifies this section in connection_connect to use OutboundBindAddressOR and OutboundBindAddressExit, preferably with the Exit/OR/(all) and IPv4/IPv6 logic refactored into its own function.
if (!tor_addr_is_loopback(addr)) { const tor_addr_t *ext_addr = NULL; if (protocol_family == AF_INET && !tor_addr_is_null(&options->OutboundBindAddressIPv4_)) ext_addr = &options->OutboundBindAddressIPv4_; else if (protocol_family == AF_INET6 && !tor_addr_is_null(&options->OutboundBindAddressIPv6_)) ext_addr = &options->OutboundBindAddressIPv6_; if (ext_addr) { memset(&bind_addr_ss, 0, sizeof(bind_addr_ss)); bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0, (struct sockaddr *) &bind_addr_ss, sizeof(bind_addr_ss)); if (bind_addr_len == 0) { log_warn(LD_NET, "Error converting OutboundBindAddress %s into sockaddr. " "Ignoring.", fmt_and_decorate_addr(ext_addr)); } else { bind_addr = (struct sockaddr *)&bind_addr_ss; } } }
<snip>
Binding to different IP addresses can also be used for filtering and traffic redirection. Does having separate bind addresses for OR and Exit traffic work for your use case?
Yes, separate IP addresses for OutboundBindAddressOR (which we would set to our "incoming" interface address) and OutboundBindAddressExit (which we would set to our "outgoing" interface address) would work for our use case. One caveat is that we would then no longer have the mixing of relay and exit traffic (which overlaps e.g. on common ports like 80) on our outgoing interface/IP address. Without having analyzed it in detail, our gut feeling was that this mixing (if the QoS flag is removed) may actually be beneficial against traffic correlation attacks and/or filtering/scanning the exit traffic by upstream providers (because it would required DPI as the more costly version to distinguish e.g. Tor relay from HTTPS traffic). If this assumption is unwarranted and you don't see additional information leakage by separating relay and exit traffic by IP (and as mentioned, we have not thought about this systematically enough yet), then this patch would solve our issue. I assume it would need additional changes to add the new OutboundBindAddressOR and OutboundBindAddressExit options to the config parser?
best regards, Rene