Skip to content

Commit 936d7cb

Browse files
committed
Auto-update anyone_hosts from the .anyone DNS service (v2)
Reworked from PR #145 per its production-readiness review. The client fetches a fresh anyone_hosts mapping when new directory information arrives and on a periodic (jittered) schedule. Fetches are routed by .anyone name over hidden-service circuits: linked directory streams for this purpose now go through the same rewrite/attach path as SOCKS client streams (connection_ap_make_link_onion), which is where .anyone names are resolved -- the v1 approach attached them as general exit streams and could never complete. Fetched files are installed only if they are non-empty, within DNSMappingFileMaxSize, carry a valid signature from a trusted DNS signer (not configurable), are unexpired per valid-until, contain at least one mapping, and are not older (by published time) than the installed mapping. Byte-identical refetches skip the rewrite. Configuration is deliberately minimal: AnyoneHostsUpdate, AnyoneHostsUpdateInterval (floored at 1 hour, jittered +/-10%), and AnyoneHostsURL. Target selection tries configured URLs then built-in defaults, advancing only past failing servers.
1 parent cb1af7d commit 936d7cb

21 files changed

Lines changed: 1306 additions & 17 deletions

changes/anyone-hosts-autoupdate

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
o New features (anyone-hosts auto-update):
2+
- The client now automatically fetches a fresh copy of the anyone_hosts
3+
DNS mapping file from the .anyone DNS service nodes when new
4+
directory information arrives and on a periodic schedule. Fetches
5+
are routed by .anyone name over hidden-service circuits. A fetched
6+
file is installed only if it carries a valid signature from a
7+
trusted DNS signer, is unexpired, contains at least one mapping, and
8+
is not older than the installed mapping. New configuration options
9+
AnyoneHostsUpdate, AnyoneHostsUpdateInterval, and AnyoneHostsURL
10+
control the behaviour.
11+
12+
o Minor features (documentation):
13+
- DNSMappingFileMaxSize now documents bit-based units (KBits, MBits,
14+
GBits, TBits) in the man page, consistent with other MEMUNIT options.
15+
- Clarified that DNSMappingFileMaxSize only limits loading an existing
16+
file; a missing file is always recreated with the default mapping.

doc/man/anon.1.txt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,10 +1222,32 @@ The following options are useful only for clients (that is, if
12221222
addresses/ports. See <<SocksPort,SocksPort>> for an explanation of isolation
12231223
flags. (Default: 0)
12241224

1225-
[[DNSMappingFileMaxSize]] **DNSMappingFileMaxSize** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**::
1225+
[[DNSMappingFileMaxSize]] **DNSMappingFileMaxSize** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
12261226
The maximum size allowed for the `anyone_hosts` DNS mapping file in the
12271227
data directory. If the file exceeds this size, it is rejected. Set this
1228-
option to 0 to disable the size limit. (Default: 10 MB)
1228+
option to 0 to disable the size limit. This limit applies only when
1229+
reading or accepting an existing file; if the file is absent, a new
1230+
default file is created regardless of this setting. (Default: 10 MB)
1231+
1232+
[[AnyoneHostsUpdate]] **AnyoneHostsUpdate** **0**|**1**::
1233+
If 1, the client will automatically attempt to fetch a fresh copy of
1234+
the `anyone_hosts` DNS mapping file from the .anyone DNS service nodes
1235+
when new directory information arrives and on a periodic schedule.
1236+
Fetched files are installed only if they carry a valid signature from
1237+
a trusted DNS signer, have not expired, and are not older than the
1238+
currently installed mapping. (Default: 1)
1239+
1240+
[[AnyoneHostsUpdateInterval]] **AnyoneHostsUpdateInterval** __N__ **seconds**|**minutes**|**hours**|**days**|**weeks**::
1241+
How often to attempt fetching a fresh `anyone_hosts` file. The actual
1242+
schedule is randomly jittered by up to +/-10% around this value.
1243+
The minimum is 1 hour. (Default: 12 hours)
1244+
1245+
[[AnyoneHostsURL]] **AnyoneHostsURL** __address__::
1246+
A .anyone onion address of a DNS service node to try when fetching the
1247+
`anyone_hosts` file. This option may be specified multiple times; the
1248+
addresses are tried in order before the built-in defaults. A failing
1249+
address is skipped on the next attempt; a working address keeps being
1250+
used. (Default: none)
12291251

12301252
[[DownloadExtraInfo]] **DownloadExtraInfo** **0**|**1**::
12311253
If true, Tor downloads and caches "extra-info" documents. These documents

src/app/config/config.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ static const config_var_t option_vars_[] = {
327327
V(AlternateBridgeAuthority, LINELIST, NULL),
328328
V(AlternateDirAuthority, LINELIST, NULL),
329329
OBSOLETE("AlternateHSAuthority"),
330+
V(AnyoneHostsUpdate, BOOL, "1"),
331+
V(AnyoneHostsUpdateInterval, INTERVAL, "12 hours"),
332+
V(AnyoneHostsURL, LINELIST, NULL),
330333
V(AssumeReachable, BOOL, "0"),
331334
V(AssumeReachableIPv6, AUTOBOOL, "auto"),
332335
OBSOLETE("AuthDirBadDir"),
@@ -4062,6 +4065,23 @@ options_validate_cb(const void *old_options_, void *options_, char **msg)
40624065
return -1;
40634066
}
40644067

4068+
if (options->AnyoneHostsURL) {
4069+
const config_line_t *cl;
4070+
for (cl = options->AnyoneHostsURL; cl; cl = cl->next) {
4071+
const char *v = cl->value;
4072+
if (!v || !strlen(v) || strcmpend(v, ".anyone") ||
4073+
strstr(v, "://") || strchr(v, '/') || strchr(v, ':') ||
4074+
strpbrk(v, " \t\r\n")) {
4075+
REJECT("AnyoneHostsURL entries must be bare .anyone hostnames "
4076+
"(no scheme, port, path, or whitespace) ending in "
4077+
"\".anyone\".");
4078+
}
4079+
}
4080+
}
4081+
if (options->AnyoneHostsUpdateInterval < 3600) {
4082+
REJECT("AnyoneHostsUpdateInterval must be at least 1 hour.");
4083+
}
4084+
40654085
return 0;
40664086
}
40674087

src/app/config/or_options_st.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,14 @@ struct or_options_t {
705705
/** Maximum size in bytes allowed for the anyone_hosts DNS mapping file.
706706
* A value of 0 disables the size limit. */
707707
uint64_t DNSMappingFileMaxSize;
708+
/** If true, automatically update the anyone_hosts DNS mapping file when
709+
* new directory information arrives or on a periodic schedule. */
710+
int AnyoneHostsUpdate;
711+
/** How often (in seconds) to check for a new anyone_hosts file. */
712+
int AnyoneHostsUpdateInterval;
713+
/** List of .anyone service addresses to try when fetching the
714+
* anyone_hosts file. Tried in order before the built-in defaults. */
715+
struct config_line_t *AnyoneHostsURL;
708716

709717
/** If true, do not accept any requests to connect to internal addresses
710718
* over randomly chosen exits. */

src/core/mainloop/mainloop.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
#include "core/or/connection_or.h"
6868
#include "core/or/dos.h"
6969
#include "core/or/status.h"
70+
#include "feature/anyone/anyone_hosts_update.h"
7071
#include "feature/client/addressmap.h"
7172
#include "feature/client/bridges.h"
7273
#include "feature/client/dnsserv.h"
@@ -1381,6 +1382,7 @@ CALLBACK(write_stats_file);
13811382
CALLBACK(control_per_second_events);
13821383
CALLBACK(second_elapsed);
13831384
CALLBACK(manage_vglite);
1385+
CALLBACK(update_anyone_hosts);
13841386

13851387
#undef CALLBACK
13861388

@@ -1406,6 +1408,9 @@ STATIC periodic_event_item_t mainloop_periodic_events[] = {
14061408
/* Update vanguards-lite once per hour, if we have networking */
14071409
CALLBACK(manage_vglite, NET_PARTICIPANT, FL(NEED_NET)),
14081410

1411+
/* Periodically update the anyone_hosts DNS mapping file. */
1412+
CALLBACK(update_anyone_hosts, CLIENT, FL(NEED_NET)),
1413+
14091414
/* XXXX Do we have a reason to do this on a callback? Does it do any good at
14101415
* all? For now, if we're dormant, we can let our listeners decay. */
14111416
CALLBACK(retry_listeners, NET_PARTICIPANT, FL(NEED_NET)),
@@ -1553,6 +1558,8 @@ initialize_periodic_events(void)
15531558
NAMED_CALLBACK(launch_descriptor_fetches);
15541559
NAMED_CALLBACK(check_dns_honesty);
15551560
NAMED_CALLBACK(save_state);
1561+
1562+
anyone_hosts_update_init();
15561563
}
15571564

15581565
STATIC void
@@ -1691,6 +1698,17 @@ manage_vglite_callback(time_t now, const or_options_t *options)
16911698
return VANGUARDS_LITE_INTERVAL;
16921699
}
16931700

1701+
/** Periodic-event callback: attempt to update the anyone_hosts DNS mapping
1702+
* file, and return the number of seconds until the next run
1703+
* (AnyoneHostsUpdateInterval, jittered). Whether an update may actually
1704+
* run (enabled, client-only, timing) is decided inside the anyone module.
1705+
*/
1706+
static int
1707+
update_anyone_hosts_callback(time_t now, const or_options_t *options)
1708+
{
1709+
return anyone_hosts_update_callback(now, options);
1710+
}
1711+
16941712
/** Perform regular maintenance tasks. This function gets run once per
16951713
* second.
16961714
*/

src/core/or/connection_edge.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3640,6 +3640,43 @@ connection_ap_make_link(connection_t *partner,
36403640
return conn;
36413641
}
36423642

3643+
/** Like connection_ap_make_link(), but for a target that is an onion-routed
3644+
* hostname (a .anyone name) rather than a numeric address.
3645+
*
3646+
* connection_ap_make_link() queues the stream for direct circuit attachment,
3647+
* which treats the address as an exit-resolvable hostname; a .anyone name
3648+
* would reach the exit unresolved and fail. Instead, send the linked stream
3649+
* through connection_ap_handshake_rewrite_and_attach(), the same path SOCKS
3650+
* client streams take, so the name is translated via the anyone_hosts
3651+
* mapping and attached to a hidden-service circuit.
3652+
*
3653+
* Return the new entry connection, or NULL on error. On error the stream
3654+
* has already been marked for close, so the partner connection will see EOF.
3655+
*/
3656+
entry_connection_t *
3657+
connection_ap_make_link_onion(connection_t *partner,
3658+
char *address, uint16_t port,
3659+
int session_group, int isolation_flags)
3660+
{
3661+
entry_connection_t *conn =
3662+
connection_ap_make_link(partner, address, port, NULL /* digest */,
3663+
session_group, isolation_flags,
3664+
0 /* use_begindir */, 0 /* want_onehop */);
3665+
if (!conn)
3666+
return NULL;
3667+
3668+
/* Undo the direct-attach queueing and take the rewrite path instead. */
3669+
connection_ap_mark_as_non_pending_circuit(conn);
3670+
/* The rewrite path refuses onion targets unless the stream's entry
3671+
* configuration allows them; internal fetches always may. */
3672+
conn->entry_cfg.onion_traffic = 1;
3673+
if (connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL) < 0) {
3674+
/* The connection has already been marked unattached and closed. */
3675+
return NULL;
3676+
}
3677+
return conn;
3678+
}
3679+
36433680
/** Notify any interested controller connections about a new hostname resolve
36443681
* or resolve error. Takes the same arguments as does
36453682
* connection_ap_handshake_socks_resolved(). */

src/core/or/connection_edge.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ entry_connection_t *connection_ap_make_link(connection_t *partner,
114114
int session_group,
115115
int isolation_flags,
116116
int use_begindir, int want_onehop);
117+
entry_connection_t *connection_ap_make_link_onion(connection_t *partner,
118+
char *address,
119+
uint16_t port,
120+
int session_group,
121+
int isolation_flags);
117122
void connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply,
118123
size_t replylen,
119124
int endreason);

0 commit comments

Comments
 (0)