Commit 8727acf2 authored by Roger Dingledine's avatar Roger Dingledine
Browse files

Add a new AddressMap directive to rewrite incoming socks addresses.

Add a new TrackHostExits directive to trigger addressmaps for
certain incoming socks addresses, for sites that break when your exit
keeps changing.
Redo the client-side dns cache so it's just an addressmap too.


svn:r3641
parent b5bddd8c
......@@ -385,7 +385,7 @@ void circuit_build_needed_circs(time_t now) {
circuit_reset_failure_count(1);
time_to_new_circuit = now + get_options()->NewCircuitPeriod;
if (proxy_mode(get_options()))
client_dns_clean();
addressmap_clean(now);
circuit_expire_old_circuits();
#if 0 /* disable for now, until predict-and-launch-new can cull leftovers */
......@@ -768,7 +768,6 @@ circuit_get_open_circ_or_launch(connection_t *conn,
uint8_t desired_circuit_purpose,
circuit_t **circp) {
circuit_t *circ;
uint32_t addr;
int is_resolve;
int need_uptime;
......@@ -801,7 +800,10 @@ circuit_get_open_circ_or_launch(connection_t *conn,
/* Do we need to check exit policy? */
if (!is_resolve && !connection_edge_is_rendezvous_stream(conn)) {
addr = client_dns_lookup_entry(conn->socks_request->address);
struct in_addr in;
uint32_t addr = 0;
if (tor_inet_aton(conn->socks_request->address, &in))
addr = ntohl(in.s_addr);
if (router_exit_policy_all_routers_reject(addr, conn->socks_request->port,
need_uptime)) {
log_fn(LOG_NOTICE,"No Tor server exists that allows exit to %s:%d. Rejecting.",
......@@ -899,6 +901,53 @@ static void link_apconn_to_circ(connection_t *apconn, circuit_t *circ) {
apconn->cpath_layer = circ->cpath->prev;
}
/** If an exit wasn't specifically chosen, save the history for future
* use */
static void
consider_recording_trackhost(connection_t *conn, circuit_t *circ) {
int found_needle = 0;
char *str;
or_options_t *options = get_options();
size_t len;
char *new_address;
/* Search the addressmap for this conn's destination. */
/* If he's not in the address map.. */
if (!options->TrackHostExits ||
addressmap_already_mapped(conn->socks_request->address))
return; /* nothing to track, or already mapped */
SMARTLIST_FOREACH(options->TrackHostExits, const char *, cp, {
if (cp[0] == '.') { /* match end */
/* XXX strstr is probably really bad here */
if ((str = strstr(conn->socks_request->address, &cp[1]))) {
if (str == conn->socks_request->address
|| strcmp(str, &cp[1]) == 0) {
found_needle = 1;
}
}
} else if (strcmp(cp, conn->socks_request->address) == 0) {
found_needle = 1;
}
});
if (!found_needle)
return;
/* Add this exit/hostname pair to the addressmap. */
len = strlen(conn->socks_request->address) + 1 /* '.' */ +
strlen(circ->build_state->chosen_exit_name) + 1 /* '.' */ +
strlen("exit") + 1 /* '\0' */;
new_address = tor_malloc(len);
tor_snprintf(new_address, len, "%s.%s.exit",
conn->socks_request->address,
circ->build_state->chosen_exit_name);
addressmap_register(conn->socks_request->address, new_address,
time(NULL) + options->TrackHostExitsExpire);
}
/** Try to find a safe live circuit for CONN_TYPE_AP connection conn. If
* we don't find one: if conn cannot be handled by any known nodes,
* warn and return -1 (conn needs to die);
......@@ -957,7 +1006,7 @@ int connection_ap_handshake_attach_circuit(connection_t *conn) {
link_apconn_to_circ(conn, circ);
tor_assert(conn->socks_request);
if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
// consider_recording_trackhost(conn, circ);
consider_recording_trackhost(conn, circ);
connection_ap_handshake_send_begin(conn, circ);
} else {
connection_ap_handshake_send_resolve(conn, circ);
......
......@@ -120,6 +120,9 @@ static config_var_t config_vars[] = {
VAR("StrictEntryNodes", BOOL, StrictEntryNodes, "0"),
VAR("ExitPolicy", LINELIST, ExitPolicy, NULL),
VAR("ExcludeNodes", STRING, ExcludeNodes, NULL),
VAR("TrackHostExits", CSV, TrackHostExits, NULL),
VAR("TrackHostExitsExpire",INTERVAL, TrackHostExitsExpire, "30 minutes"),
VAR("AddressMap", LINELIST, AddressMap, NULL),
VAR("FascistFirewall", BOOL, FascistFirewall, "0"),
VAR("FirewallPorts", CSV, FirewallPorts, "80,443"),
VAR("MyFamily", STRING, MyFamily, NULL),
......@@ -186,6 +189,7 @@ static or_options_t *options_dup(or_options_t *old);
static int options_validate(or_options_t *options);
static int options_transition_allowed(or_options_t *old, or_options_t *new);
static int check_nickname_list(const char *lst, const char *name);
static void config_register_addressmaps(or_options_t *options);
static int parse_dir_server_line(const char *line, int validate_only);
static int parse_redirect_line(smartlist_t *result,
......@@ -333,6 +337,9 @@ options_act(void) {
if (options->PidFile)
write_pidfile(options->PidFile);
/* Register addressmap directives */
config_register_addressmaps(options);
/* Update address policies. */
parse_socks_policy();
parse_dir_policy();
......@@ -1797,6 +1804,29 @@ init_from_config(int argc, char **argv)
return -1;
}
static void
config_register_addressmaps(or_options_t *options) {
smartlist_t *elts;
struct config_line_t *opt;
char *from, *to;
elts = smartlist_create();
for (opt = options->AddressMap; opt; opt = opt->next) {
smartlist_split_string(elts, opt->value, NULL,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
if (smartlist_len(elts) >= 2) {
from = smartlist_get(elts,0);
to = smartlist_get(elts,1);
addressmap_register(from, tor_strdup(to), 0);
} else {
log_fn(LOG_WARN,"AddressMap '%s' has too few arguments. Ignoring.", opt->value);
}
SMARTLIST_FOREACH(elts, char*, cp, tor_free(cp));
smartlist_clear(elts);
}
smartlist_free(elts);
}
/** If <b>range</b> is of the form MIN-MAX, for MIN and MAX both
* recognized log severity levels, set *<b>min_out</b> to MIN and
* *<b>max_out</b> to MAX and return 0. Else, if <b>range<b> is of
......
......@@ -329,6 +329,169 @@ void connection_ap_attach_pending(void)
}
}
/** A client-side struct to remember requests to rewrite addresses
* to new addresses. These structs make up a tree, with addressmap
* below as its root.
*
* There are 5 ways to set an address mapping:
* - A MapAddress command from the controller [permanent]
* - An AddressMap directive in the torrc [permanent]
* - When a TrackHostExits torrc directive is triggered [temporary]
* - When a dns resolve succeeds [temporary]
* - When a dns resolve fails [temporary]
*
* When an addressmap request is made but one is already registered,
* the new one is replaced only if the currently registered one has
* no "new_address" (that is, it's in the process of dns resolve),
* or if the new one is permanent (expires==0).
*/
typedef struct {
char *new_address;
time_t expires;
int num_resolve_failures;
} addressmap_entry_t;
/** The tree of client-side address rewrite instructions. */
static strmap_t *addressmap;
/** Initialize addressmap. */
void addressmap_init(void) {
addressmap = strmap_new();
}
/** Free the memory associated with the addressmap entry <b>_ent</b>. */
static void
addressmap_ent_free(void *_ent) {
addressmap_entry_t *ent = _ent;
tor_free(ent->new_address);
tor_free(ent);
}
/** A helper function for addressmap_clean() below. If ent is too old,
* then remove it from the tree and return NULL, else return ent.
*/
static void *
_addressmap_remove_if_expired(const char *addr,
addressmap_entry_t *ent,
time_t *nowp) {
if (ent->expires && ent->expires < *nowp) {
log(LOG_NOTICE, "Addressmap: expiring remap (%s to %s)",
addr, ent->new_address);
addressmap_ent_free(ent);
return NULL;
} else {
return ent;
}
}
/** Clean out entries from the addressmap cache that were
* added long enough ago that they are no longer valid.
*/
void addressmap_clean(time_t now) {
strmap_foreach(addressmap,
(strmap_foreach_fn)_addressmap_remove_if_expired, &now);
}
/** Free all the elements in the addressmap, and free the addressmap
* itself. */
void addressmap_free_all(void) {
strmap_free(addressmap, addressmap_ent_free);
addressmap = NULL;
}
/** Look at address, and rewrite it until it doesn't want any
* more rewrites; but don't get into an infinite loop.
* Don't write more than maxlen chars into address.
*/
void addressmap_rewrite(char *address, size_t maxlen) {
addressmap_entry_t *ent;
int rewrites;
for (rewrites = 0; rewrites < 16; rewrites++) {
ent = strmap_get(addressmap, address);
if (!ent || !ent->new_address)
return; /* done, no rewrite needed */
log_fn(LOG_NOTICE, "Addressmap: rewriting '%s' to '%s'",
address, ent->new_address);
strlcpy(address, ent->new_address, maxlen);
}
log_fn(LOG_WARN,"Loop detected: we've rewritten '%s' 16 times! Using it as-is.",
address);
/* it's fine to rewrite a rewrite, but don't loop forever */
}
/** Return 1 if <b>address</b> is already registered, else return 0 */
int addressmap_already_mapped(const char *address) {
return strmap_get(addressmap, address) ? 1 : 0;
}
/** Register a request to map <b>address</b> to <b>new_address</b>,
* which will expire on <b>expires</b> (or 0 if never expires).
*
* new_address should be a newly dup'ed string, which we'll use or
* free as appropriate. We will leave address alone.
*/
void addressmap_register(const char *address, char *new_address, time_t expires) {
addressmap_entry_t *ent;
ent = strmap_get(addressmap, address);
if (ent && ent->new_address && expires) {
log_fn(LOG_NOTICE,"Addressmap ('%s' to '%s') not performed, since it's already mapped to '%s'", address, new_address, ent->new_address);
tor_free(new_address);
return;
}
if (ent) { /* we'll replace it */
tor_free(ent->new_address);
} else { /* make a new one and register it */
ent = tor_malloc_zero(sizeof(addressmap_entry_t));
strmap_set(addressmap, address, ent);
}
ent->new_address = new_address;
ent->expires = expires;
ent->num_resolve_failures = 0;
log_fn(LOG_NOTICE, "Addressmap: (re)mapped '%s' to '%s'",
address, ent->new_address);
}
/** An attempt to resolve <b>address</b> failed at some OR.
* Increment the number of resolve failures we have on record
* for it, and then return that number.
*/
int client_dns_incr_failures(const char *address)
{
addressmap_entry_t *ent;
ent = strmap_get(addressmap,address);
if (!ent) {
ent = tor_malloc_zero(sizeof(addressmap_entry_t));
ent->expires = time(NULL)+MAX_DNS_ENTRY_AGE;
strmap_set(addressmap,address,ent);
}
++ent->num_resolve_failures;
log_fn(LOG_NOTICE,"Address %s now has %d resolve failures.",
address, ent->num_resolve_failures);
return ent->num_resolve_failures;
}
/** Record the fact that <b>address</b> resolved to <b>val</b>.
* We can now use this in subsequent streams via addressmap_rewrite()
* so we can more correctly choose an exit that will allow <b>address</b>.
*/
void client_dns_set_addressmap(const char *address, uint32_t val)
{
struct in_addr in;
tor_assert(address); tor_assert(val);
if (tor_inet_aton(address, &in))
return; /* don't set an addressmap back to ourselves! */
in.s_addr = htonl(val);
addressmap_register(address, strdup(inet_ntoa(in)),
time(NULL) + MAX_DNS_ENTRY_AGE);
}
/** Return 1 if <b>address</b> has funny characters in it like
* colons. Return 0 if it's fine.
*/
......@@ -383,6 +546,11 @@ static int connection_ap_handshake_process_socks(connection_t *conn) {
return sockshere;
} /* else socks handshake is done, continue processing */
tor_strlower(socks->address); /* normalize it */
/* For address map controls, remap the address */
addressmap_rewrite(socks->address, sizeof(socks->address));
/* Parse the address provided by SOCKS. Modify it in-place if it
* specifies a hidden-service (.onion) or particular exit node (.exit).
*/
......@@ -408,7 +576,7 @@ static int connection_ap_handshake_process_socks(connection_t *conn) {
}
if (socks->command == SOCKS_COMMAND_RESOLVE) {
uint32_t answer = 0;
uint32_t answer;
struct in_addr in;
/* Reply to resolves immediately if we can. */
if (strlen(socks->address) > RELAY_PAYLOAD_SIZE) {
......@@ -416,11 +584,8 @@ static int connection_ap_handshake_process_socks(connection_t *conn) {
connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,0,NULL);
return -1;
}
if (tor_inet_aton(socks->address, &in)) /* see if it's an IP already */
if (tor_inet_aton(socks->address, &in)) { /* see if it's an IP already */
answer = in.s_addr;
if (!answer && !conn->chosen_exit_name) /* if it's not .exit, check cache */
answer = htonl(client_dns_lookup_entry(socks->address));
if (answer) {
connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_IPV4,4,
(char*)&answer);
conn->has_sent_end = 1;
......@@ -514,8 +679,6 @@ int connection_ap_handshake_send_begin(connection_t *ap_conn, circuit_t *circ)
{
char payload[CELL_PAYLOAD_SIZE];
int payload_len;
struct in_addr in;
const char *string_addr;
tor_assert(ap_conn->type == CONN_TYPE_AP);
tor_assert(ap_conn->state == AP_CONN_STATE_CIRCUIT_WAIT);
......@@ -530,18 +693,10 @@ int connection_ap_handshake_send_begin(connection_t *ap_conn, circuit_t *circ)
return -1;
}
if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL) {
in.s_addr = htonl(client_dns_lookup_entry(ap_conn->socks_request->address));
string_addr = in.s_addr ? inet_ntoa(in) : NULL;
tor_snprintf(payload,RELAY_PAYLOAD_SIZE,
"%s:%d",
string_addr ? string_addr : ap_conn->socks_request->address,
ap_conn->socks_request->port);
} else {
tor_snprintf(payload,RELAY_PAYLOAD_SIZE,
":%d", ap_conn->socks_request->port);
}
tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:%d",
(circ->purpose == CIRCUIT_PURPOSE_C_GENERAL) ?
ap_conn->socks_request->address : "",
ap_conn->socks_request->port);
payload_len = strlen(payload)+1;
log_fn(LOG_DEBUG,"Sending relay cell to begin stream %d.",ap_conn->stream_id);
......@@ -670,7 +825,7 @@ void connection_ap_handshake_socks_resolved(connection_t *conn,
if (answer_type == RESOLVED_TYPE_IPV4) {
uint32_t a = get_uint32(answer);
if (a)
client_dns_set_entry(conn->socks_request->address, ntohl(a));
client_dns_set_addressmap(conn->socks_request->address, ntohl(a));
}
if (conn->socks_request->socks_version == 4) {
......@@ -844,6 +999,7 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ) {
tor_free(address);
return 0;
}
tor_strlower(address);
n_stream->address = address;
n_stream->state = EXIT_CONN_STATE_RESOLVEFAILED;
/* default to failed, change in dns_resolve if it turns out not to fail */
......@@ -1018,8 +1174,6 @@ int connection_edge_is_rendezvous_stream(connection_t *conn) {
*/
int connection_ap_can_use_exit(connection_t *conn, routerinfo_t *exit)
{
uint32_t addr;
tor_assert(conn);
tor_assert(conn->type == CONN_TYPE_AP);
tor_assert(conn->socks_request);
......@@ -1042,7 +1196,10 @@ int connection_ap_can_use_exit(connection_t *conn, routerinfo_t *exit)
}
if (conn->socks_request->command != SOCKS_COMMAND_RESOLVE) {
addr = client_dns_lookup_entry(conn->socks_request->address);
struct in_addr in;
uint32_t addr = 0;
if (tor_inet_aton(conn->socks_request->address, &in))
addr = ntohl(in.s_addr);
if (router_compare_addr_to_addr_policy(addr, conn->socks_request->port,
exit->exit_policy) == ADDR_POLICY_REJECTED)
return 0;
......@@ -1098,164 +1255,6 @@ int socks_policy_permits_address(uint32_t addr)
return 0;
}
/* ***** Client DNS code ***** */
/* XXX Perhaps this should get merged with the dns.c code somehow. */
/* XXX But we can't just merge them, because then nodes that act as
* both OR and OP could be attacked: people could rig the dns cache
* by answering funny things to stream begin requests, and later
* other clients would reuse those funny addr's. Hm.
*/
/** A client-side struct to remember the resolved IP (addr) for
* a given address. These structs make up a tree, with client_dns_map
* below as its root.
*/
struct client_dns_entry {
uint32_t addr; /**< The resolved IP of this entry. */
time_t expires; /**< At what second does addr expire? */
int n_failures; /**< How many times has this entry failed to resolve so far? */
};
/** How many elements are in the client dns cache currently? */
static int client_dns_size = 0;
/** The tree of client-side cached DNS resolves. */
static strmap_t *client_dns_map = NULL;
/** Initialize client_dns_map and client_dns_size. */
void client_dns_init(void) {
client_dns_map = strmap_new();
client_dns_size = 0;
}
/** Return the client_dns_entry that corresponds to <b>address</b>.
* If it's not there, allocate and return a new entry for <b>address</b>.
*/
static struct client_dns_entry *
_get_or_create_ent(const char *address)
{
struct client_dns_entry *ent;
ent = strmap_get_lc(client_dns_map,address);
if (!ent) {
ent = tor_malloc_zero(sizeof(struct client_dns_entry));
ent->expires = time(NULL)+MAX_DNS_ENTRY_AGE;
strmap_set_lc(client_dns_map,address,ent);
++client_dns_size;
}
return ent;
}
/** Return the IP associated with <b>address</b>, if we know it
* and it's still fresh enough. Otherwise return 0.
*/
uint32_t client_dns_lookup_entry(const char *address)
{
struct client_dns_entry *ent;
struct in_addr in;
time_t now;
tor_assert(address);
if (tor_inet_aton(address, &in)) {
log_fn(LOG_DEBUG, "Using static address %s (%08lX)", address,
(unsigned long)ntohl(in.s_addr));
return ntohl(in.s_addr);
}
ent = strmap_get_lc(client_dns_map,address);
if (!ent || !ent->addr) {
log_fn(LOG_DEBUG, "No entry found for address %s", address);
return 0;
} else {
now = time(NULL);
if (ent->expires < now) {
log_fn(LOG_DEBUG, "Expired entry found for address %s", address);
strmap_remove_lc(client_dns_map,address);
tor_free(ent);
--client_dns_size;
return 0;
}
in.s_addr = htonl(ent->addr);
log_fn(LOG_DEBUG, "Found cached entry for address %s: %s", address,
inet_ntoa(in));
return ent->addr;
}
}
/** An attempt to resolve <b>address</b> failed at some OR.
* Increment the number of resolve failures we have on record
* for it, and then return that number.
*/
int client_dns_incr_failures(const char *address)
{
struct client_dns_entry *ent;
ent = _get_or_create_ent(address);
++ent->n_failures;
log_fn(LOG_DEBUG,"Address %s now has %d resolve failures.",
address, ent->n_failures);
return ent->n_failures;
}
/** Record the fact that <b>address</b> resolved to <b>val</b>.
* We can now use this in subsequent streams in client_dns_lookup_entry(),
* so we can more correctly choose a router that will allow <b>address</b>
* to exit from him.
*/
void client_dns_set_entry(const char *address, uint32_t val)
{
struct client_dns_entry *ent;
struct in_addr in;
time_t now;
tor_assert(address);
tor_assert(val);
if (tor_inet_aton(address, &in))
return;
now = time(NULL);
ent = _get_or_create_ent(address);
in.s_addr = htonl(val);
log_fn(LOG_DEBUG, "Updating entry for address %s: %s", address,
inet_ntoa(in));
ent->addr = val;
ent->expires = now+MAX_DNS_ENTRY_AGE;
ent->n_failures = 0;
}
/** A helper function for client_dns_clean() below. If ent is too old,
* then remove it from the tree and return NULL, else return ent.
*/
static void* _remove_if_expired(const char *addr,
struct client_dns_entry *ent,
time_t *nowp)
{
if (ent->expires < *nowp) {
--client_dns_size;
tor_free(ent);
return NULL;
} else {
return ent;
}
}
/** Clean out entries from the client-side DNS cache that were
* resolved long enough ago that they are no longer valid.
*/
void client_dns_clean(void)
{
time_t now;
if (!client_dns_size)
return;
now = time(NULL);
strmap_foreach(client_dns_map, (strmap_foreach_fn)_remove_if_expired, &now);
}
void client_dns_free_all(void)
{
strmap_free(client_dns_map, free);
client_dns_map = NULL;
}
/** Make connection redirection follow the provided list of
* exit_redirect_t */
void
......@@ -1284,20 +1283,18 @@ parse_extended_hostname(char *address) {
s = strrchr(address,'.');
if (!s) return 0; /* no dot, thus normal */
if (!strcasecmp(s+1,"exit")) {
if (!strcmp(s+1,"exit")) {
*s = 0; /* null-terminate it */
return EXIT_HOSTNAME; /* .exit */
}
if (strcasecmp(s+1,"onion"))
if (strcmp(s+1,"onion"))
return NORMAL_HOSTNAME; /* neither .exit nor .onion, thus normal */
/* so it is .onion */
*s = 0; /* null-terminate it */
if (strlcpy(query, address, REND_SERVICE_ID_LEN+1) >= REND_SERVICE_ID_LEN+1)
goto failed;
tor_strlower(query);
if (rend_valid_service_id(query)) {
tor_strlower(address);
return ONION_HOSTNAME; /* success */
}
failed:
......
......@@ -1263,7 +1263,7 @@ static int tor_init(int argc, char *argv[]) {
rep_hist_init();
/* Initialize the service cache. */
rend_cache_init();
client_dns_init(); /* Init the client dns cache. Do it always, since it's cheap. */
addressmap_init(); /* Init the client dns cache. Do it always, since it's cheap. */
/* give it somewhere to log to initially */
add_temp_log();
......@@ -1308,7 +1308,7 @@ void tor_free_all(void)
{
routerlist_free_current();
free_trusted_dir_servers();
client_dns_free_all();
addressmap_free_all();
free_socks_policy();