diff --git a/changes/bug32588 b/changes/bug32588 new file mode 100644 index 0000000000..f31f2ce1ad --- /dev/null +++ b/changes/bug32588 @@ -0,0 +1,4 @@ + o Minor bugfixes (relays): + - Stop advertising incorrect IPv6 ORPorts in relay and bridge descriptors, + when the IPv6 port was configured as "auto". + Fixes bug 32588; bugfix on 0.2.3.9-alpha diff --git a/src/app/config/config.c b/src/app/config/config.c index 8c635ab848..126256843c 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -828,9 +828,6 @@ static int normalize_nickname_list(config_line_t **normalized_out, char **msg); static char *get_bindaddr_from_transport_listen_line(const char *line, const char *transport); -static int parse_ports(or_options_t *options, int validate_only, - char **msg_out, int *n_ports_out, - int *world_writable_control_socket); static int check_server_ports(const smartlist_t *ports, const or_options_t *options, int *num_low_ports_out); @@ -7333,7 +7330,7 @@ count_real_listeners(const smartlist_t *ports, int listenertype, * If validate_only is false, set configured_client_ports to the * new list of ports parsed from options. **/ -static int +STATIC int parse_ports(or_options_t *options, int validate_only, char **msg, int *n_ports_out, int *world_writable_control_socket) diff --git a/src/app/config/config.h b/src/app/config/config.h index 44f09e5ee9..2ed64fab34 100644 --- a/src/app/config/config.h +++ b/src/app/config/config.h @@ -292,6 +292,10 @@ STATIC int open_and_add_file_log(const log_severity_list_t *severity, const char *fname, int truncate_log); +STATIC int parse_ports(or_options_t *options, int validate_only, + char **msg, int *n_ports_out, + int *world_writable_control_socket); + #endif /* defined(CONFIG_PRIVATE) */ #endif /* !defined(TOR_CONFIG_H) */ diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index a46b522bd6..3f3be98336 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -1449,6 +1449,50 @@ router_get_advertised_or_port_by_af(const or_options_t *options, return port; } +/** As router_get_advertised_or_port(), but returns the IPv6 address and + * port in ipv6_ap_out, which must not be NULL. Returns a null address and + * zero port, if no ORPort is found. */ +void +router_get_advertised_ipv6_or_ap(const or_options_t *options, + tor_addr_port_t *ipv6_ap_out) +{ + /* Bug in calling function, we can't return a sensible result, and it + * shouldn't use the NULL pointer once we return. */ + tor_assert(ipv6_ap_out); + + /* If there is no valid IPv6 ORPort, return a null address and port. */ + tor_addr_make_null(&ipv6_ap_out->addr, AF_INET6); + ipv6_ap_out->port = 0; + + const tor_addr_t *addr = get_first_advertised_addr_by_type_af( + CONN_TYPE_OR_LISTENER, + AF_INET6); + const uint16_t port = router_get_advertised_or_port_by_af( + options, + AF_INET6); + + if (!addr || port == 0) { + log_info(LD_CONFIG, "There is no advertised IPv6 ORPort."); + return; + } + + /* If the relay is configured using the default authorities, disallow + * internal IPs. Otherwise, allow them. For IPv4 ORPorts and DirPorts, + * this check is done in resolve_my_address(). See #33681. */ + const int default_auth = using_default_dir_authorities(options); + if (tor_addr_is_internal(addr, 0) && default_auth) { + log_warn(LD_CONFIG, + "Unable to use configured IPv6 ORPort \"%s\" in a " + "descriptor. Skipping it. " + "Try specifying a globally reachable address explicitly.", + fmt_addrport(addr, port)); + return; + } + + tor_addr_copy(&ipv6_ap_out->addr, addr); + ipv6_ap_out->port = port; +} + /** Return the port that we should advertise as our DirPort; * this is one of three possibilities: * The one that is passed as dirport if the DirPort option is 0, or @@ -1993,34 +2037,11 @@ router_build_fresh_unsigned_routerinfo,(routerinfo_t **ri_out)) sizeof(curve25519_public_key_t)); /* For now, at most one IPv6 or-address is being advertised. */ - { - const port_cfg_t *ipv6_orport = NULL; - SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) { - if (p->type == CONN_TYPE_OR_LISTENER && - ! p->server_cfg.no_advertise && - ! p->server_cfg.bind_ipv4_only && - tor_addr_family(&p->addr) == AF_INET6) { - /* Like IPv4, if the relay is configured using the default - * authorities, disallow internal IPs. Otherwise, allow them. */ - const int default_auth = using_default_dir_authorities(options); - if (! tor_addr_is_internal(&p->addr, 0) || ! default_auth) { - ipv6_orport = p; - break; - } else { - char addrbuf[TOR_ADDR_BUF_LEN]; - log_warn(LD_CONFIG, - "Unable to use configured IPv6 address \"%s\" in a " - "descriptor. Skipping it. " - "Try specifying a globally reachable address explicitly.", - tor_addr_to_str(addrbuf, &p->addr, sizeof(addrbuf), 1)); - } - } - } SMARTLIST_FOREACH_END(p); - if (ipv6_orport) { - tor_addr_copy(&ri->ipv6_addr, &ipv6_orport->addr); - ri->ipv6_orport = ipv6_orport->port; - } - } + tor_addr_port_t ipv6_orport; + router_get_advertised_ipv6_or_ap(options, &ipv6_orport); + /* If there is no valud IPv6 ORPort, the address and port are null. */ + tor_addr_copy(&ri->ipv6_addr, &ipv6_orport.addr); + ri->ipv6_orport = ipv6_orport.port; ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key()); if (BUG(crypto_pk_get_digest(ri->identity_pkey, diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h index 55b9ef9e68..3d756c6088 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -60,6 +60,8 @@ int init_keys_client(void); uint16_t router_get_active_listener_port_by_type_af(int listener_type, sa_family_t family); uint16_t router_get_advertised_or_port(const or_options_t *options); +void router_get_advertised_ipv6_or_ap(const or_options_t *options, + tor_addr_port_t *ipv6_ap_out); uint16_t router_get_advertised_or_port_by_af(const or_options_t *options, sa_family_t family); uint16_t router_get_advertised_dir_port(const or_options_t *options, diff --git a/src/test/test_config.c b/src/test/test_config.c index ebc0624fb2..41f9f3d25f 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -4903,6 +4903,71 @@ test_config_parse_port_config__ports__server_options(void *data) config_free_lines(config_port_valid); config_port_valid = NULL; } +static void +test_config_get_first_advertised(void *data) +{ + (void)data; + int r, w=0, n=0; + char *msg=NULL; + or_options_t *opts = options_new(); + int port; + const tor_addr_t *addr; + + // no ports are configured? We get NULL. + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_int_op(port, OP_EQ, 0); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_ptr_op(addr, OP_EQ, NULL); + + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_int_op(port, OP_EQ, 0); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_ptr_op(addr, OP_EQ, NULL); + + config_line_append(&opts->ORPort_lines, "ORPort", "[1234::5678]:8080"); + config_line_append(&opts->ORPort_lines, "ORPort", + "1.2.3.4:9999 noadvertise"); + config_line_append(&opts->ORPort_lines, "ORPort", + "5.6.7.8:9911 nolisten"); + + r = parse_ports(opts, 0, &msg, &n, &w); + tt_assert(r == 0); + + // UNSPEC gets us nothing. + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_UNSPEC); + tt_int_op(port, OP_EQ, 0); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_UNSPEC); + tt_ptr_op(addr, OP_EQ, NULL); + + // Try AF_INET. + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_int_op(port, OP_EQ, 9911); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET); + tt_ptr_op(addr, OP_NE, NULL); + tt_str_op(fmt_addrport(addr,port), OP_EQ, "5.6.7.8:9911"); + + // Try AF_INET6 + port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_int_op(port, OP_EQ, 8080); + addr = get_first_advertised_addr_by_type_af(CONN_TYPE_OR_LISTENER, + AF_INET6); + tt_ptr_op(addr, OP_NE, NULL); + tt_str_op(fmt_addrport(addr,port), OP_EQ, "[1234::5678]:8080"); + + done: + or_options_free(opts); + config_free_all(); +} + static void test_config_parse_log_severity(void *data) { @@ -6121,6 +6186,7 @@ struct testcase_t config_tests[] = { CONFIG_TEST(parse_port_config__ports__no_ports_given, 0), CONFIG_TEST(parse_port_config__ports__server_options, 0), CONFIG_TEST(parse_port_config__ports__ports_given, 0), + CONFIG_TEST(get_first_advertised, TT_FORK), CONFIG_TEST(parse_log_severity, 0), CONFIG_TEST(include_limit, 0), CONFIG_TEST(include_does_not_exist, 0), diff --git a/src/test/test_router.c b/src/test/test_router.c index 5477ab51e9..e0dd607b8a 100644 --- a/src/test/test_router.c +++ b/src/test/test_router.c @@ -8,11 +8,13 @@ **/ #define CONFIG_PRIVATE +#define CONNECTION_PRIVATE #define ROUTER_PRIVATE #include "core/or/or.h" #include "app/config/config.h" #include "core/mainloop/mainloop.h" +#include "core/mainloop/connection.h" #include "feature/hibernate/hibernate.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/networkstatus_st.h" @@ -27,6 +29,8 @@ #include "lib/crypt_ops/crypto_ed25519.h" #include "lib/encoding/confline.h" +#include "core/or/listener_connection_st.h" + /* Test suite stuff */ #include "test/test.h" #include "test/log_test_helpers.h" @@ -484,6 +488,117 @@ test_router_get_my_family(void *arg) #undef CLEAR } +static smartlist_t *fake_connection_array = NULL; +static smartlist_t * +mock_get_connection_array(void) +{ + return fake_connection_array; +} + +static void +test_router_get_advertised_or_port(void *arg) +{ + (void)arg; + int r, w=0, n=0; + char *msg=NULL; + or_options_t *opts = options_new(); + listener_connection_t *listener = NULL; + tor_addr_port_t ipv6; + + // Test one failing case of router_get_advertised_ipv6_or_ap(). + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::]:0"); + + // And one failing case of router_get_advertised_or_port(). + tt_int_op(0, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(0, OP_EQ, router_get_advertised_or_port(opts)); + + // Set up a couple of configured ports. + config_line_append(&opts->ORPort_lines, "ORPort", "[1234::5678]:auto"); + config_line_append(&opts->ORPort_lines, "ORPort", "5.6.7.8:9999"); + r = parse_ports(opts, 0, &msg, &n, &w); + tt_assert(r == 0); + + // There are no listeners, so the "auto" case will turn up no results. + tt_int_op(0, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::]:0"); + + // This will return the matching value from the configured port. + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(9999, OP_EQ, router_get_advertised_or_port(opts)); + + // Now set up a dummy listener. + MOCK(get_connection_array, mock_get_connection_array); + fake_connection_array = smartlist_new(); + listener = listener_connection_new(CONN_TYPE_OR_LISTENER, AF_INET6); + TO_CONN(listener)->port = 54321; + smartlist_add(fake_connection_array, TO_CONN(listener)); + + // We should get a port this time. + tt_int_op(54321, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + + // Test one succeeding case of router_get_advertised_ipv6_or_ap(). + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, + "[1234::5678]:54321"); + + // This will return the matching value from the configured port. + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(9999, OP_EQ, router_get_advertised_or_port(opts)); + + done: + or_options_free(opts); + config_free_all(); + smartlist_free(fake_connection_array); + connection_free_minimal(TO_CONN(listener)); + UNMOCK(get_connection_array); +} + +static void +test_router_get_advertised_or_port_localhost(void *arg) +{ + (void)arg; + int r, w=0, n=0; + char *msg=NULL; + or_options_t *opts = options_new(); + tor_addr_port_t ipv6; + + // Set up a couple of configured ports on localhost. + config_line_append(&opts->ORPort_lines, "ORPort", "[::1]:9999"); + config_line_append(&opts->ORPort_lines, "ORPort", "127.0.0.1:8888"); + r = parse_ports(opts, 0, &msg, &n, &w); + tt_assert(r == 0); + + // We should refuse to advertise them, since we have default dirauths. + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::]:0"); + // But the lower-level function should still report the correct value + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + + // The IPv4 checks are done in resolve_my_address(), which doesn't use + // ORPorts so we can't test them here. (See #33681.) Both these lower-level + // functions should still report the correct value. + tt_int_op(8888, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(8888, OP_EQ, router_get_advertised_or_port(opts)); + + // Now try with a fake authority set up. + config_line_append(&opts->DirAuthorities, "DirAuthority", + "127.0.0.1:1066 " + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + + tt_int_op(9999, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET6)); + router_get_advertised_ipv6_or_ap(opts, &ipv6); + tt_str_op(fmt_addrport(&ipv6.addr, ipv6.port), OP_EQ, "[::1]:9999"); + + tt_int_op(8888, OP_EQ, router_get_advertised_or_port_by_af(opts, AF_INET)); + tt_int_op(8888, OP_EQ, router_get_advertised_or_port(opts)); + + done: + or_options_free(opts); + config_free_all(); +} + #define ROUTER_TEST(name, flags) \ { #name, test_router_ ## name, flags, NULL, NULL } @@ -492,5 +607,7 @@ struct testcase_t router_tests[] = { ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK), ROUTER_TEST(mark_if_too_old, TT_FORK), ROUTER_TEST(get_my_family, TT_FORK), + ROUTER_TEST(get_advertised_or_port, TT_FORK), + ROUTER_TEST(get_advertised_or_port_localhost, TT_FORK), END_OF_TESTCASES };