diff options
55 files changed, 1333 insertions, 306 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 03cf2f69f6..13d0d116ae 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,8 +8,6 @@ clone_depth: 50 image: # Windows Server 2019 - Visual Studio 2019 - # Windows Server 2012 R2 - - Visual Studio 2015 environment: compiler: mingw @@ -37,12 +35,6 @@ matrix: compiler_path: mingw32 mingw_prefix: mingw-w64-i686 hardening: - - image: Visual Studio 2015 - target: x86_64-w64-mingw32 - compiler_path: mingw64 - mingw_prefix: mingw-w64-x86_64 - # hardening doesn't work with mingw-w64-x86_64-gcc, because it's gcc 8 - hardening: --disable-gcc-hardening install: - ps: >- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d2d0d55dd4..ba61c71b2b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,45 +1,228 @@ -before_script: - - apt-get update -qq - - apt-get upgrade -qy - -build: - script: - - apt-get install -qy --fix-missing automake build-essential - libevent-dev libssl-dev zlib1g-dev - libseccomp-dev liblzma-dev libscrypt-dev - - ./autogen.sh - - ./configure --disable-asciidoc --enable-fatal-warnings - --disable-silent-rules - - make check || (e=$?; cat test-suite.log; exit $e) - - make install - -update: - only: - - schedules - script: - - "apt-get install -y --fix-missing git openssh-client" - - # Run ssh-agent (inside the build environment) - - eval $(ssh-agent -s) - - # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store - - ssh-add <(echo "$DEPLOY_KEY") - - # For Docker builds disable host key checking. Be aware that by adding that - # you are susceptible to man-in-the-middle attacks. - # WARNING: Use this only with the Docker executor, if you use it with shell - # you will overwrite your user's SSH config. - - mkdir -p ~/.ssh - - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - # In order to properly check the server's host key, assuming you created the - # SSH_SERVER_HOSTKEYS variable previously, uncomment the following two lines - # instead. - - mkdir -p ~/.ssh - - '[[ -f /.dockerenv ]] && echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts' - - echo "merging from torgit" - - git config --global user.email "labadmin@oniongit.eu" - - git config --global user.name "gitadmin" - - "mkdir tor" - - "cd tor" - - git clone --bare https://git.torproject.org/tor.git - - git push --mirror git@oniongit.eu:network/tor.git +#### +# DO NOT EDIT THIS FILE IN MASTER. ONLY EDIT IT IN THE OLDEST SUPPORTED +# BRANCH, THEN MERGE FORWARD. +#### + +# This file controls how gitlab validates Tor commits and merge requests. +# +# It is primarily based on a set of scripts and configurations by +# Hans-Christoph Steiner. It only copies parts of those scripts and +# configurations for now. If you want a new piece of functionality +# (more debians, more fedoras, android support) then you shouldn't +# start from scratch: have a look at the original ticket, at +# https://gitlab.torproject.org/tpo/core/tor/-/issues/32193 ! +# +# The file to copy from is +# https://gitlab.torproject.org/tpo/core/tor/-/merge_requests/96/diffs#diff-content-587d266bb27a4dc3022bbed44dfa19849df3044c +# +# Having said that, if there is anything really stupid here, don't +# blame it on Hans-Christoph! Tor probably added it on their own. +# +# Copyright 2020, The Tor Project, Inc. +# See LICENSE for licence information. + +# These variables are set everywhere, unconditionally. +variables: + TERM: "ansi" + DEBUG_CI: "yes" + +# This template is for exporting ephemeral things from the scripts. By +# convention we expect our scripts to copy stuff into artifacts/, rather than +# having a big list of files that be treated as artifacts. +.artifacts-template: &artifacts-template + artifacts: + name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}" + expire_in: 1 week + when: always + paths: + - artifacts/ + +# This template should be usable on any system that's based on apt. +.apt-template: &apt-template | + export LC_ALL=C.UTF-8 + echo Etc/UTC > /etc/timezone + mkdir -p apt-cache + export APT_CACHE_DIR="$(pwd)/apt-cache" + echo 'quiet "1";' \ + 'APT::Install-Recommends "0";' \ + 'APT::Install-Suggests "0";' \ + 'APT::Acquire::Retries "20";' \ + 'APT::Get::Assume-Yes "true";' \ + 'Dpkg::Use-Pty "0";' \ + "Dir::Cache::Archives \"${APT_CACHE_DIR}\"; " \ + >> /etc/apt/apt.conf.d/99gitlab + apt-get update -qq + apt-get upgrade -qy + +# This template sets us up for Debian system in particular. +.debian-template: &debian-template + <<: *artifacts-template + variables: + DEBIAN_FRONTEND: "noninteractive" + # TODO: Using "cache" in this way speeds up our downloads. It would be + # even better, though, to start with a pre-upgraded debian image. + # + # TODO: Will we have to do this differently once we have more than one + # debian version that we're using? + cache: + key: apt + paths: + - apt-cache + before_script: + - *apt-template + # Install patches unconditionally. + - apt-get install + automake + build-essential + ca-certificates + git + libevent-dev + liblzma-dev + libscrypt-dev + libseccomp-dev + libssl-dev + pkg-config + python3 + zlib1g-dev + # Install patches that we only need for some use cases. + - if [ "$ASCIIDOC" = yes ]; then apt-get install asciidoc xmlto; fi + - if [ "$DOXYGEN" = yes ]; then apt-get install doxygen; fi + - if [ "$STEM" = yes ]; then apt-get install timelimit; fi + - if [ "$CC" = clang ]; then apt-get install clang; fi + - if [ "$NSS" = yes ]; then apt-get install libnss3 libnss3-dev; fi + # TODO: This next line should not be debian-only. + - if [ "$STEM" = yes ]; then git clone --depth 1 https://git.torproject.org/stem.git ; export STEM_PATH="$(pwd)/stem"; fi + # TODO: This next line should not be debian-only. + - if [ "$CHUTNEY" = yes ]; then git clone --depth 1 https://git.torproject.org/chutney.git ; export CHUTNEY_PATH="$(pwd)/chutney"; fi + - if [ "$TRACING" = yes ]; then apt install liblttng-ust-dev; fi + +# Minmal check on debian: just make, make check. +# +debian-minimal: + image: debian:stable + <<: *debian-template + script: + - ./scripts/ci/ci-driver.sh + +##### +# Run "make check" with a hardened clang on debian stable. This takes +# care of a hardening check, and a compile-with-clang check. +# +# TODO: This will be faster once we merge #40098 and #40099. +debian-hardened: + image: debian:testing + <<: *debian-template + variables: + ALL_BUGS_ARE_FATAL: "yes" + HARDENING: "yes" + CC: "clang" + script: + - ./scripts/ci/ci-driver.sh + +##### +# Distcheck on debian stable +debian-distcheck: + image: debian:stable + <<: *debian-template + variables: + DISTCHECK: "yes" + CHECK: "no" + script: + - ./scripts/ci/ci-driver.sh + +##### +# Documentation tests on debian stable: doxygen and asciidoc. +debian-docs: + image: debian:stable + <<: *debian-template + variables: + DOXYGEN: "yes" + ASCIIDOC: "yes" + CHECK: "no" + RUN_STAGE_BUILD: "no" + script: + - ./scripts/ci/ci-driver.sh + +##### +# Integration tests on debian stable: chutney and stem. +# +# TODO: It would be cool if this target didn't have to re-build tor, and +# could instead re-use Tor from debian-minimal. That can be done +# with the 'artifacts' mechanism, in theory, but it would be good to +# avoid having to have a system with hundreds of artifacts. +debian-integration: + image: debian:stable + <<: *debian-template + variables: + CHECK: "no" + CHUTNEY: "yes" + CHUTNEY_MAKE_TARGET: "test-network-all" + STEM: "yes" + ALL_BUGS_ARE_FATAL: "yes" + script: + - ./scripts/ci/ci-driver.sh + +##### +# Tracing build on Debian stable. +debian-tracing: + image: debian:stable + <<: *debian-template + variables: + TRACING: "yes" + CHECK: "no" + script: + - ./scripts/ci/ci-driver.sh + # Ensure that we only run tracing when it's implemented. + # + # Once versions before 0.4.5 are obsolete, we can remove this test. + rules: + # This first "if" check prevents us from running a duplicate version of + # this pipeline whenever we push and create an MR. I don't understand why + # it is necessary, though the following URL purports to explain: + # + # https://docs.gitlab.com/ee/ci/yaml/#prevent-duplicate-pipelines + - if: '$CI_PIPELINE_SOURCE == "push"' + exists: + - src/lib/trace/trace_sys.c + +##### +# No-authority mode +debian-disable-dirauth: + image: debian:stable + <<: *debian-template + variables: + DISABLE_DIRAUTH: "yes" + script: + - ./scripts/ci/ci-driver.sh + +##### +# No-relay mode +debian-disable-relay: + image: debian:stable + <<: *debian-template + variables: + DISABLE_RELAY: "yes" + script: + - ./scripts/ci/ci-driver.sh + # Ensure that we only run tracing when it's implemented. + # + # Once versions before 0.4.3 are obsolete, we can remove this test. + rules: + # This first "if" check prevents us from running a duplicate version of + # this pipeline whenever we push and create an MR. I don't understand why + # it is necessary, though the following URL purports to explain: + # + # https://docs.gitlab.com/ee/ci/yaml/#prevent-duplicate-pipelines + - if: '$CI_PIPELINE_SOURCE == "push"' + exists: + - src/feature/relay/relay_stub.c + +##### +# NSS check on debian +debian-nss: + image: debian:stable + <<: *debian-template + variables: + NSS: "yes" + script: + - ./scripts/ci/ci-driver.sh diff --git a/Makefile.am b/Makefile.am index 5d18666edc..a868be7362 100644 --- a/Makefile.am +++ b/Makefile.am @@ -149,6 +149,8 @@ include src/include.am include doc/include.am include contrib/include.am +manpages: $(nodist_man1_MANS) + EXTRA_DIST+= \ ChangeLog \ CONTRIBUTING \ 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/changes/bug40099 b/changes/bug40099 new file mode 100644 index 0000000000..278ede2023 --- /dev/null +++ b/changes/bug40099 @@ -0,0 +1,4 @@ + o Minor bugfixes (tests): + - Fix the behavior of the rend_cache/clean_v2_descs_as_dir when run on + its own. Previously, it would exit with an error. + Fixes bug 40099; bugfix on 0.2.8.1-alpha. diff --git a/changes/log_32868 b/changes/log_32868 new file mode 100644 index 0000000000..34476078b2 --- /dev/null +++ b/changes/log_32868 @@ -0,0 +1,4 @@ + o Minor features (debugging, directory system): + - Don't crash when we find a non-guard with a guard-fraction value set. + Instead, log a bug warning, in an attempt to figure out how this + happened. Diagnostic for ticket 32868. diff --git a/changes/parallel_unit_test b/changes/parallel_unit_test new file mode 100644 index 0000000000..79de28636d --- /dev/null +++ b/changes/parallel_unit_test @@ -0,0 +1,4 @@ + o Minor features (tests): + - Our "make check" target now runs the unit tests in 8 parallel chunks. + Doing this speeds up hardened CI builds by more than a factor of two. + Closes ticket 40098. diff --git a/changes/ticket33747 b/changes/ticket33747 new file mode 100644 index 0000000000..57c72e9d0a --- /dev/null +++ b/changes/ticket33747 @@ -0,0 +1,7 @@ + o Minor bugfixes (rate limiting, bridges, pluggable transports): + - On a bridge, treat all connections from an ExtORPort as remote + by default for the purposes of rate-limiting. Previously, + bridges would treat the connection as local unless they explicitly + received a "USERADDR" command. ExtORPort connections still + count as local if there is a USERADDR command with an explicit local + address. Fixes bug 33747; bugfix on 0.2.5.1-alpha. diff --git a/changes/ticket33880 b/changes/ticket33880 new file mode 100644 index 0000000000..c1889bb134 --- /dev/null +++ b/changes/ticket33880 @@ -0,0 +1,6 @@ + o Minor bugfixes (relay, usability): + - Adjust the rules for when to warn about having too many connections + to other relays. Previously we'd tolerate up to 1.5 connections + per relay on average. Now we tolerate more connections for directory + authorities, and raise the number of total connections we need + to see before we warn. Fixes bug 33880; bugfix on 0.3.1.1-alpha. diff --git a/changes/ticket40003 b/changes/ticket40003 new file mode 100644 index 0000000000..240f464353 --- /dev/null +++ b/changes/ticket40003 @@ -0,0 +1,3 @@ + o Deprecated features (onion service v2): + - Add deprecation warning for onion service version 2. Tor now logs a + warning once if a version 2 service is configured. Closes ticket 40003. diff --git a/changes/ticket40035 b/changes/ticket40035 new file mode 100644 index 0000000000..8cdd447199 --- /dev/null +++ b/changes/ticket40035 @@ -0,0 +1,5 @@ + o Major bugfixes (NSS): + - When running with NSS enabled, make sure that NSS knows to expect + nonblocking sockets. Previously, we set our TCP sockets as blocking, + but did not tell NSS about the fact, which in turn could lead to + unexpected blocking behavior. Fixes bug 40035; bugfix on 0.3.5.1-alpha. diff --git a/changes/ticket40073 b/changes/ticket40073 new file mode 100644 index 0000000000..30b028c042 --- /dev/null +++ b/changes/ticket40073 @@ -0,0 +1,3 @@ + o Minor bugfixes (relay configuration, crash): + - Avoid a fatal assert() when failing to create a listener connection for an + address that was in use. Fixes bug 40073; bugfix on 0.3.5.1-alpha. diff --git a/changes/ticket40081 b/changes/ticket40081 new file mode 100644 index 0000000000..683ae33518 --- /dev/null +++ b/changes/ticket40081 @@ -0,0 +1,6 @@ + o Minor features (security): + - Channels using obsolete versions of the Tor link protocol are no + longer allowed to circumvent address-canonicity checks. + (This is only a minor issue, since such channels have no way to + set ed25519 keys, and therefore should always be rejected.) + Closes ticket 40081. diff --git a/changes/ticket40125 b/changes/ticket40125 new file mode 100644 index 0000000000..c68e3ce7b3 --- /dev/null +++ b/changes/ticket40125 @@ -0,0 +1,4 @@ + o Testing (onion service v2): + - Fix a rendezvous cache unit test that was triggering an underflow on the + global rend cache allocation. Fixes bug 40125; bugfix on + 0.2.8.1-alpha. diff --git a/changes/ticket40126 b/changes/ticket40126 new file mode 100644 index 0000000000..1f5806e6cb --- /dev/null +++ b/changes/ticket40126 @@ -0,0 +1,4 @@ + o Testing (onion service v2): + - Fix another rendezvous cache unit test that was triggering an underflow on the + global rend cache allocation. Fixes bug 40126; bugfix on + 0.2.8.1-alpha. diff --git a/changes/ticket40133 b/changes/ticket40133 new file mode 100644 index 0000000000..8bbe00b6b2 --- /dev/null +++ b/changes/ticket40133 @@ -0,0 +1,5 @@ + o Minor features (protocol simplification): + - Tor no longer allows subprotocol versions larger than 63. Previously + versions up to UINT32_MAX were allowed, which significantly complicated + our code. + Implements proposal 318; closes ticket 40133. diff --git a/changes/ticket40135 b/changes/ticket40135 new file mode 100644 index 0000000000..9b60b4f655 --- /dev/null +++ b/changes/ticket40135 @@ -0,0 +1,3 @@ + o Minor bugfix (logging): + - Remove a debug logging statement that uselessly spam the logs. Fixes bug + 40135; bugfix on 0.3.5.0-alpha. diff --git a/scripts/ci/ci-driver.sh b/scripts/ci/ci-driver.sh new file mode 100755 index 0000000000..67a95a1f61 --- /dev/null +++ b/scripts/ci/ci-driver.sh @@ -0,0 +1,492 @@ +#!/bin/bash + +#### +# DO NOT EDIT THIS FILE IN MASTER. ONLY EDIT IT IN THE OLDEST SUPPORTED +# BRANCH, THEN MERGE FORWARD. +#### + +# This script is used to build Tor for continuous integration. It should +# be kept the same for all supported Tor versions. +# +# It's subject to the regular Tor license; see LICENSE for copying +# information. + +set -o errexit +set -o nounset + +# Options for this script. +DEBUG_CI="${DEBUG_CI:-no}" +COLOR_CI="${COLOR_CI:-yes}" + +# Options for which CI system this is. +ON_GITLAB="${ON_GITLAB:-yes}" + +# Options for things we usually won't want to skip. +RUN_STAGE_CONFIGURE="${RUN_STAGE_CONFIGURE:-yes}" +RUN_STAGE_BUILD="${RUN_STAGE_BUILD:-yes}" +RUN_STAGE_TEST="${RUN_STAGE_TEST:-yes}" + +# Options for how to build Tor. All should be yes/no. +FATAL_WARNINGS="${FATAL_WARNINGS:-yes}" +HARDENING="${HARDENING:-no}" +COVERAGE="${COVERAGE:-no}" +RUST="${RUST:-no}" +DOXYGEN="${DOXYGEN:-no}" +ASCIIDOC="${ASCIIDOC:-no}" +TRACING="${TRACING:-no}" +ALL_BUGS_ARE_FATAL="${ALL_BUGS_ARE_FATAL:-no}" +DISABLE_DIRAUTH="${DISABLE_DIRAUTH:-no}" +DISABLE_RELAY="${DISABLE_RELAY:-no}" +NSS="${NSS:-no}" + +# Options for which tests to run. All should be yes/no. +CHECK="${CHECK:-yes}" +STEM="${STEM:-no}" +CHUTNEY="${CHUTNEY:-no}" +DISTCHECK="${DISTCHECK:-no}" + +# Options for where the Tor source is. +CI_SRCDIR="${CI_SRCDIR:-.}" + +# Options for where to build. +CI_BUILDDIR="${CI_BUILDDIR:-./build}" + +# How parallel should we run make? +MAKE_J_OPT="${MAKE_J_OPT:--j4}" +# Should we stop after make finds an error? +MAKE_K_OPT="${MAKE_K_OPT:--k}" + +# What make target should we use for chutney? +CHUTNEY_MAKE_TARGET="${CHUTNEY_MAKE_TARGET:-test-network}" + +# Where do we find our additional testing tools? +CHUTNEY_PATH="${CHUTNEY_PATH:-}" +STEM_PATH="${STEM_PATH:-}" + +############################################################################# +# Preliminary functions. + +# Terminal coloring/emphasis stuff. +if [[ "${COLOR_CI}" == "yes" ]]; then + T_RED=$(tput setaf 1 || true) + T_GREEN=$(tput setaf 2 || true) + T_YELLOW=$(tput setaf 3 || true) + T_DIM=$(tput dim || true) + T_BOLD=$(tput bold || true) + T_RESET=$(tput sgr0 || true) +else + T_RED= + T_GREEN= + T_YELLOW= + T_DIM= + T_BOLD= + T_RESET= +fi + +function error() +{ + echo "${T_BOLD}${T_RED}ERROR:${T_RESET} $*" 1>&2 +} +function die() +{ + echo "${T_BOLD}${T_RED}FATAL ERROR:${T_RESET} $*" 1>&2 + exit 1 +} + +function skipping() +{ + echo "${T_BOLD}${T_YELLOW}Skipping $*${T_RESET}" +} + +function hooray() +{ + echo "${T_BOLD}${T_GREEN}$*${T_RESET}" +} + +if [[ "${DEBUG_CI}" == "yes" ]]; then + function debug() + { + echo "${T_DIM}(debug): $*${T_RESET}" + } +else + function debug() + { + : + } +fi + +function yes_or_no() +{ + local varname="$1" + local value="${!varname}" + debug "${varname} is ${value}" + if [[ "${value}" != 'yes' && "${value}" != 'no' ]]; then + die "${varname} must be 'yes' or 'no'. Got unexpected value ${value}". + fi +} + +function incompatible() +{ + local varname1="$1" + local varname2="$2" + local val1="${!varname1}" + local val2="${!varname2}" + if [[ "${val1}" = 'yes' && "${val2}" = 'yes' ]]; then + die "Cannot set both ${varname1} and ${varname2}: they are incompatible." + fi +} + +function runcmd() +{ + echo "${T_BOLD}\$ $*${T_RESET}" + if ! "$@" ; then + error "command '$*' has failed." + return 1 + fi +} + +function show_git_version() +{ + local tool="$1" + local dir="$2" + local version="?????" + if [[ -e "$dir/.git" ]] ; then + version=$(cd "$dir"; git rev-parse HEAD) + fi + echo "${T_BOLD}$tool:${T_RESET} $version" +} + +if [[ "${ON_GITLAB}" == "yes" ]]; then + function start_section() + { + local label="$1" + local stamp + stamp=$(date +%s) + printf "section_start:%s:%s\r\e[0K" "$stamp" "$label" + echo "${T_BOLD}${T_GREEN}========= $label${T_RESET}" + } + function end_section() + { + local label="$1" + local stamp + stamp=$(date +%s) + printf "section_end:%s:%s\r\e[0K" "$stamp" "$label" + } +else + function start_section() + { + true + } + function end_section() + { + true + } +fi + +############################################################################# +# Validate inputs. + +debug Validating inputs +yes_or_no DEBUG_CI +yes_or_no COLOR_CI +yes_or_no ON_GITLAB +yes_or_no FATAL_WARNINGS +yes_or_no HARDENING +yes_or_no COVERAGE +yes_or_no RUST +yes_or_no DOXYGEN +yes_or_no ASCIIDOC +yes_or_no TRACING +yes_or_no ALL_BUGS_ARE_FATAL +yes_or_no DISABLE_DIRAUTH +yes_or_no DISABLE_RELAY +yes_or_no NSS + +yes_or_no RUN_STAGE_CONFIGURE +yes_or_no RUN_STAGE_BUILD +yes_or_no RUN_STAGE_TEST + +yes_or_no CHECK +yes_or_no STEM +yes_or_no DISTCHECK + +incompatible DISTCHECK CHECK +incompatible DISTCHECK CHUTNEY +incompatible DISTCHECK STEM +incompatible DISTCHECK COVERAGE +incompatible DISTCHECK DOXYGEN + +if [[ "${CHUTNEY}" = yes && "${CHUTNEY_PATH}" = '' ]] ; then + die "CHUTNEY is set to 'yes', but CHUTNEY_PATH was not specified." +fi + +if [[ "${STEM}" = yes && "${STEM_PATH}" = '' ]] ; then + die "STEM is set to 'yes', but STEM_PATH was not specified." +fi + +############################################################################# +# Set up options for make and configure. + +make_options=() +if [[ "$MAKE_J_OPT" != "" ]]; then + make_options+=("$MAKE_J_OPT") +fi +if [[ "$MAKE_K_OPT" != "" ]]; then + make_options+=("$MAKE_K_OPT") +fi + +configure_options=() +if [[ "$FATAL_WARNINGS" == "yes" ]]; then + configure_options+=("--enable-fatal-warnings") +fi +if [[ "$HARDENING" == "yes" ]]; then + configure_options+=("--enable-fragile-hardening") +fi +if [[ "$COVERAGE" == "yes" ]]; then + configure_options+=("--enable-coverage") +fi +if [[ "$RUST" == "yes" ]]; then + configure_options+=("--enable-rust") +fi +if [[ "$ASCIIDOC" != "yes" ]]; then + configure_options+=("--disable-asciidoc") +fi +if [[ "$TRACING" == "yes" ]]; then + configure_options+=("--enable-tracing-instrumentation-lttng") +fi +if [[ "$ALL_BUGS_ARE_FATAL" == "yes" ]]; then + configure_options+=("--enable-all-bugs-are-fatal") +fi +if [[ "$DISABLE_DIRAUTH" == "yes" ]]; then + configure_options+=("--disable-module-dirauth") +fi +if [[ "$DISABLE_RELAY" == "yes" ]]; then + configure_options+=("--disable-module-relay") +fi +if [[ "$NSS" == "yes" ]]; then + configure_options+=("--enable-nss") +fi + +############################################################################# +# Tell the user about our versions of different tools and packages. + +uname -a +printf "python: " +python -V || echo "no 'python' binary." +printf "python3: " +python3 -V || echo "no 'python3' binary." + +show_git_version Tor "${CI_SRCDIR}" +if [[ "${STEM}" = "yes" ]]; then + show_git_version Stem "${STEM_PATH}" +fi +if [[ "${CHUTNEY}" = "yes" ]]; then + show_git_version Chutney "${CHUTNEY_PATH}" +fi + +############################################################################# +# Determine the version of Tor. + +TOR_VERSION=$(grep -m 1 AC_INIT configure.ac | sed -e 's/.*\[//; s/\].*//;') + +# Use variables like these when we need to behave differently depending on +# Tor version. Only create the variables we need. +TOR_VER_AT_LEAST_043=no +TOR_VER_AT_LEAST_044=no + +# These are the currently supported Tor versions; no need to work with anything +# ancient in this script. +case "$TOR_VERSION" in + 0.3.*) + TOR_VER_AT_LEAST_043=no + TOR_VER_AT_LEAST_044=no + ;; + 0.4.[012].*) + TOR_VER_AT_LEAST_043=no + TOR_VER_AT_LEAST_044=no + ;; + 0.4.3.*) + TOR_VER_AT_LEAST_043=yes + TOR_VER_AT_LEAST_044=no + ;; + *) + TOR_VER_AT_LEAST_043=yes + TOR_VER_AT_LEAST_044=yes + ;; +esac + +############################################################################# +# Make sure the directories are all there. + +# Make sure CI_SRCDIR exists and has a file we expect. +if [[ ! -d "$CI_SRCDIR" ]] ; then + die "CI_SRCDIR=${CI_SRCDIR} is not a directory" +fi +if [[ ! -f "$CI_SRCDIR/src/core/or/or.h" ]] ; then + die "CI_SRCDIR=${CI_SRCDIR} does not look like a Tor directory." +fi + +# Make CI_SRCDIR absolute. +CI_SRCDIR=$(cd "$CI_SRCDIR" && pwd) + +# Create an "artifacts" directory to copy artifacts into. +mkdir -p ./artifacts + +if [[ "$RUN_STAGE_CONFIGURE" = "yes" ]]; then + + start_section "Autogen" + runcmd cd "${CI_SRCDIR}" + runcmd ./autogen.sh + runcmd mkdir -p "${CI_BUILDDIR}" + runcmd cd "${CI_BUILDDIR}" + end_section "Autogen" + + # make the builddir absolute too. + CI_BUILDDIR=$(pwd) + + start_section "Configure" + if ! runcmd "${CI_SRCDIR}"/configure "${configure_options[@]}" ; then + error "Here is the end of config.log:" + runcmd tail config.log + die "Unable to continue" + fi + end_section "Configure" +else + debug "Skipping configure stage. Making sure that ${CI_BUILDDIR}/config.log exists." + if [[ ! -d "${CI_BUILDDIR}" ]]; then + die "Build directory ${CI_BUILDDIR} did not exist!"; + fi + if [[ ! -f "${CI_BUILDDIR}/config.log" ]]; then + die "Tor was not configured in ${CI_BUILDDIR}!"; + fi + + cp config.log "${CI_SRCDIR}"/artifacts + + runcmd cd "${CI_BUILDDIR}" + CI_BUILDDIR=$(pwd) +fi + +############################### +# Build Tor. + +if [[ "$RUN_STAGE_BUILD" = "yes" ]] ; then + if [[ "$DISTCHECK" = "no" ]]; then + start_section "Build" + runcmd make "${make_options[@]}" all + cp src/app/tor "${CI_SRCDIR}"/artifacts + end_section "Build" + else + export DISTCHECK_CONFIGURE_FLAGS="${configure_options[*]}" + # XXXX Set make options? + start_section Distcheck + if runcmd make "${make_options[@]}" distcheck ; then + hooray "Distcheck was successful. Nothing further will be done." + # We have to exit early here, since we can't do any other tests. + cp tor-*.tar.gz "${CI_SRCDIR}"/artifacts + exit 0 + else + error "Diagnostics:" + runcmd make show-distdir-testlog || true + runcmd make show-distdir-core || true + die "Unable to continue." + fi + end_section Distcheck + fi +fi + +############################## +# Run tests. + +if [[ "$RUN_STAGE_TEST" == "no" ]]; then + echo "Skipping tests. Exiting now." + exit 0 +fi + +FAILED_TESTS="" + +if [[ "${DOXYGEN}" = 'yes' ]]; then + start_section Doxygen + if [[ "${TOR_VER_AT_LEAST_043}" = 'yes' ]]; then + if runcmd make doxygen; then + hooray "make doxygen has succeeded." + else + FAILED_TESTS="${FAILED_TESTS} doxygen" + fi + else + skipping "make doxygen: doxygen is broken for Tor < 0.4.3" + fi + end_section Doxygen +fi + +if [[ "${ASCIIDOC}" = 'yes' ]]; then + start_section Asciidoc + if runcmd make manpages; then + hooray "make manpages has succeeded." + else + FAILED_TESTS="${FAILED_TESTS} asciidoc" + fi + end_section Asciidoc +fi + +if [[ "${CHECK}" = "yes" ]]; then + start_section "Check" + if runcmd make "${make_options[@]}" check; then + hooray "make check has succeeded." + else + error "Here are the contents of the test suite output:" + runcmd cat test-suite.log || true + FAILED_TESTS="${FAILED_TESTS} check" + fi + end_section "Check" +fi + +if [[ "${CHUTNEY}" = "yes" ]]; then + start_section "Chutney" + export CHUTNEY_TOR_SANDBOX=0 + export CHUTNEY_ALLOW_FAILURES=2 + if runcmd make "${CHUTNEY_MAKE_TARGET}"; then + hooray "Chutney tests have succeeded" + else + error "Chutney says:" + export CHUTNEY_DATA_DIR="${CHUTNEY_PATH}/net" + runcmd "${CHUTNEY_PATH}"/tools/diagnostics.sh || true + # XXXX These next two should be part of a make target. + runcmd ls test_network_log || true + runcmd cat test_network_log || true + FAILED_TESTS="${FAILED_TESTS} chutney" + fi + end_section "Chutney" +fi + +if [[ "${STEM}" = "yes" ]]; then + start_section "Stem" + if [[ "${TOR_VER_AT_LEAST_044}" = 'yes' ]]; then + # XXXX This shold probably be part some test-stem make target. + if runcmd timelimit -p -t 520 -s USR1 -T 30 -S ABRT \ + python3 "${STEM_PATH}/run_tests.py" \ + --tor src/app/tor \ + --integ --test control.controller \ + --test control.base_controller \ + --test process \ + --log TRACE \ + --log-file stem.log ; then + hooray "Stem tests have succeeded" + else + error "Stem output:" + runcmd tail -1000 "${STEM_PATH}"/test/data/tor_log + runcmd grep -v "SocketClosed" stem.log | tail -1000 + FAILED_TESTS="${FAILED_TESTS} stem" + fi + else + skipping "Stem: broken with <= 0.4.3. See bug tor#40077" + fi + end_section "Stem" +fi + +# TODO: Coverage + +if [[ "${FAILED_TESTS}" != "" ]]; then + die "Failed tests: ${FAILED_TESTS}" +fi + +hooray "Everything seems fine." diff --git a/src/app/config/config.c b/src/app/config/config.c index 1d61b76310..c1f1f153e7 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -809,9 +809,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); @@ -7375,7 +7372,7 @@ count_real_listeners(const smartlist_t *ports, int listenertype, * If <b>validate_only</b> is false, set configured_client_ports to the * new list of ports parsed from <b>options</b>. **/ -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 301faf7067..6852d352dc 100644 --- a/src/app/config/config.h +++ b/src/app/config/config.h @@ -295,6 +295,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/core/mainloop/connection.c b/src/core/mainloop/connection.c index 3595bba85c..21d4332758 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -379,8 +379,12 @@ or_connection_new(int type, int socket_family) connection_or_set_canonical(or_conn, 0); - if (type == CONN_TYPE_EXT_OR) + if (type == CONN_TYPE_EXT_OR) { + /* If we aren't told an address for this connection, we should + * presume it isn't local, and should be rate-limited. */ + TO_CONN(or_conn)->always_rate_limit_as_remote = 1; connection_or_set_ext_or_identifier(or_conn); + } return or_conn; } @@ -2923,7 +2927,14 @@ retry_all_listeners(smartlist_t *new_conns, int close_all_noncontrol) &skip, &addr_in_use); } - tor_assert(new_conn); + /* There are many reasons why we can't open a new listener port so in case + * we hit those, bail early so tor can stop. */ + if (!new_conn) { + log_warn(LD_NET, "Unable to create listener port: %s:%d", + fmt_addr(&r->new_port->addr), r->new_port->port); + retval = -1; + break; + } smartlist_add(new_conns, new_conn); @@ -3025,6 +3036,7 @@ connection_is_rate_limited(connection_t *conn) if (conn->linked) return 0; /* Internal connection */ else if (! options->CountPrivateBandwidth && + ! conn->always_rate_limit_as_remote && (tor_addr_family(&conn->addr) == AF_UNSPEC || /* no address */ tor_addr_family(&conn->addr) == AF_UNIX || /* no address */ tor_addr_is_internal(&conn->addr, 0))) diff --git a/src/core/or/channel.c b/src/core/or/channel.c index 3bef6218ef..9649bdf278 100644 --- a/src/core/or/channel.c +++ b/src/core/or/channel.c @@ -72,6 +72,7 @@ #include "core/or/relay.h" #include "core/or/scheduler.h" #include "feature/client/entrynodes.h" +#include "feature/nodelist/dirlist.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" @@ -749,6 +750,7 @@ channel_check_for_duplicates(void) { channel_idmap_entry_t **iter; channel_t *chan; + int total_dirauth_connections = 0, total_dirauths = 0; int total_relay_connections = 0, total_relays = 0, total_canonical = 0; int total_half_canonical = 0; int total_gt_one_connection = 0, total_gt_two_connections = 0; @@ -756,13 +758,18 @@ channel_check_for_duplicates(void) HT_FOREACH(iter, channel_idmap, &channel_identity_map) { int connections_to_relay = 0; + const char *id_digest = (char *) (*iter)->digest; /* Only consider relay connections */ - if (!connection_or_digest_is_known_relay((char*)(*iter)->digest)) + if (!connection_or_digest_is_known_relay(id_digest)) continue; total_relays++; + const bool is_dirauth = router_digest_is_trusted_dir(id_digest); + if (is_dirauth) + total_dirauths++; + for (chan = TOR_LIST_FIRST(&(*iter)->channel_list); chan; chan = channel_next_with_rsa_identity(chan)) { @@ -771,11 +778,12 @@ channel_check_for_duplicates(void) connections_to_relay++; total_relay_connections++; + if (is_dirauth) + total_dirauth_connections++; - if (chan->is_canonical(chan, 0)) total_canonical++; + if (chan->is_canonical(chan)) total_canonical++; - if (!chan->is_canonical_to_peer && chan->is_canonical(chan, 0) - && chan->is_canonical(chan, 1)) { + if (!chan->is_canonical_to_peer && chan->is_canonical(chan)) { total_half_canonical++; } } @@ -785,11 +793,28 @@ channel_check_for_duplicates(void) if (connections_to_relay > 4) total_gt_four_connections++; } -#define MIN_RELAY_CONNECTIONS_TO_WARN 5 + /* Don't bother warning about excessive connections unless we have + * at least this many connections, total. + */ +#define MIN_RELAY_CONNECTIONS_TO_WARN 25 + /* If the average number of connections for a regular relay is more than + * this, that's too high. + */ +#define MAX_AVG_RELAY_CONNECTIONS 1.5 + /* If the average number of connections for a dirauth is more than + * this, that's too high. + */ +#define MAX_AVG_DIRAUTH_CONNECTIONS 4 + + /* How many connections total would be okay, given the number of + * relays and dirauths that we have connections to? */ + const int max_tolerable_connections = (int)( + (total_relays-total_dirauths) * MAX_AVG_RELAY_CONNECTIONS + + total_dirauths * MAX_AVG_DIRAUTH_CONNECTIONS); /* If we average 1.5 or more connections per relay, something is wrong */ if (total_relays > MIN_RELAY_CONNECTIONS_TO_WARN && - total_relay_connections >= 1.5*total_relays) { + total_relay_connections > max_tolerable_connections) { log_notice(LD_OR, "Your relay has a very large number of connections to other relays. " "Is your outbound address the same as your relay address? " @@ -2431,21 +2456,9 @@ channel_get_for_extend(const char *rsa_id_digest, continue; } - /* Never return a non-canonical connection using a recent link protocol - * if the address is not what we wanted. - * - * The channel_is_canonical_is_reliable() function asks the lower layer - * if we should trust channel_is_canonical(). The below is from the - * comments of the old circuit_or_get_for_extend() and applies when - * the lower-layer transport is channel_tls_t. - * - * (For old link protocols, we can't rely on is_canonical getting - * set properly if we're talking to the right address, since we might - * have an out-of-date descriptor, and we will get no NETINFO cell to - * tell us about the right address.) - */ + /* Only return canonical connections or connections where the address + * is the address we wanted. */ if (!channel_is_canonical(chan) && - channel_is_canonical_is_reliable(chan) && !channel_matches_target_addr_for_extend(chan, target_addr)) { ++n_noncanonical; continue; @@ -2587,16 +2600,12 @@ channel_dump_statistics, (channel_t *chan, int severity)) /* Handle marks */ tor_log(severity, LD_GENERAL, - " * Channel %"PRIu64 " has these marks: %s %s %s " - "%s %s %s", + " * Channel %"PRIu64 " has these marks: %s %s %s %s %s", (chan->global_identifier), channel_is_bad_for_new_circs(chan) ? "bad_for_new_circs" : "!bad_for_new_circs", channel_is_canonical(chan) ? "canonical" : "!canonical", - channel_is_canonical_is_reliable(chan) ? - "is_canonical_is_reliable" : - "!is_canonical_is_reliable", channel_is_client(chan) ? "client" : "!client", channel_is_local(chan) ? @@ -2955,22 +2964,7 @@ channel_is_canonical(channel_t *chan) tor_assert(chan); tor_assert(chan->is_canonical); - return chan->is_canonical(chan, 0); -} - -/** - * Test if the canonical flag is reliable. - * - * This function asks if the lower layer thinks it's safe to trust the - * result of channel_is_canonical(). - */ -int -channel_is_canonical_is_reliable(channel_t *chan) -{ - tor_assert(chan); - tor_assert(chan->is_canonical); - - return chan->is_canonical(chan, 1); + return chan->is_canonical(chan); } /** diff --git a/src/core/or/channel.h b/src/core/or/channel.h index 4c0c9aeb4c..d41f0d70bb 100644 --- a/src/core/or/channel.h +++ b/src/core/or/channel.h @@ -351,12 +351,10 @@ struct channel_s { /** Check if the lower layer has queued writes */ int (*has_queued_writes)(channel_t *); /** - * If the second param is zero, ask the lower layer if this is - * 'canonical', for a transport-specific definition of canonical; if - * it is 1, ask if the answer to the preceding query is safe to rely - * on. + * Ask the lower layer if this is 'canonical', for a transport-specific + * definition of canonical. */ - int (*is_canonical)(channel_t *, int); + int (*is_canonical)(channel_t *); /** Check if this channel matches a specified extend_info_t */ int (*matches_extend_info)(channel_t *, extend_info_t *); /** Check if this channel matches a target address when extending */ @@ -733,7 +731,6 @@ int channel_has_queued_writes(channel_t *chan); int channel_is_bad_for_new_circs(channel_t *chan); void channel_mark_bad_for_new_circs(channel_t *chan); int channel_is_canonical(channel_t *chan); -int channel_is_canonical_is_reliable(channel_t *chan); int channel_is_client(const channel_t *chan); int channel_is_local(channel_t *chan); int channel_is_incoming(channel_t *chan); diff --git a/src/core/or/channeltls.c b/src/core/or/channeltls.c index f874e39946..299ab88576 100644 --- a/src/core/or/channeltls.c +++ b/src/core/or/channeltls.c @@ -106,7 +106,7 @@ channel_tls_get_transport_name_method(channel_t *chan, char **transport_out); static const char * channel_tls_get_remote_descr_method(channel_t *chan, int flags); static int channel_tls_has_queued_writes_method(channel_t *chan); -static int channel_tls_is_canonical_method(channel_t *chan, int req); +static int channel_tls_is_canonical_method(channel_t *chan); static int channel_tls_matches_extend_info_method(channel_t *chan, extend_info_t *extend_info); @@ -643,12 +643,11 @@ channel_tls_has_queued_writes_method(channel_t *chan) /** * Tell the upper layer if we're canonical. * - * This implements the is_canonical method for channel_tls_t; if req is zero, - * it returns whether this is a canonical channel, and if it is one it returns - * whether that can be relied upon. + * This implements the is_canonical method for channel_tls_t: + * it returns whether this is a canonical channel. */ static int -channel_tls_is_canonical_method(channel_t *chan, int req) +channel_tls_is_canonical_method(channel_t *chan) { int answer = 0; channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); @@ -656,24 +655,13 @@ channel_tls_is_canonical_method(channel_t *chan, int req) tor_assert(tlschan); if (tlschan->conn) { - switch (req) { - case 0: - answer = tlschan->conn->is_canonical; - break; - case 1: - /* - * Is the is_canonical bit reliable? In protocols version 2 and up - * we get the canonical address from a NETINFO cell, but in older - * versions it might be based on an obsolete descriptor. - */ - answer = (tlschan->conn->link_proto >= 2); - break; - default: - /* This shouldn't happen; channel.c is broken if it does */ - tor_assert_nonfatal_unreached_once(); - } + /* If this bit is set to 0, and link_proto is sufficiently old, then we + * can't actually _rely_ on this being a non-canonical channel. + * Nonetheless, we're going to believe that this is a non-canonical + * channel in this case, since nobody should be using these link protocols + * any more. */ + answer = tlschan->conn->is_canonical; } - /* else return 0 for tlschan->conn == NULL */ return answer; } diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index 67b47b38f1..70b5d8215a 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -723,6 +723,8 @@ circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, goto error; } + tor_assert_nonfatal_once(circ->n_chan->is_canonical); + memset(&cell, 0, sizeof(cell_t)); r = relayed ? create_cell_format_relayed(&cell, create_cell) : create_cell_format(&cell, create_cell); diff --git a/src/core/or/connection_st.h b/src/core/or/connection_st.h index d1430eda14..c197a81340 100644 --- a/src/core/or/connection_st.h +++ b/src/core/or/connection_st.h @@ -64,6 +64,9 @@ struct connection_t { /** True if connection_handle_write is currently running on this connection. */ unsigned int in_connection_handle_write:1; + /** If true, then we treat this connection as remote for the purpose of + * rate-limiting, no matter what its address is. */ + unsigned int always_rate_limit_as_remote:1; /* For linked connections: */ diff --git a/src/core/or/protover.c b/src/core/or/protover.c index 17979d04ea..dfb0e9e303 100644 --- a/src/core/or/protover.c +++ b/src/core/or/protover.c @@ -113,13 +113,13 @@ proto_entry_free_(proto_entry_t *entry) } /** The largest possible protocol version. */ -#define MAX_PROTOCOL_VERSION (UINT32_MAX-1) +#define MAX_PROTOCOL_VERSION (63) /** * Given a string <b>s</b> and optional end-of-string pointer * <b>end_of_range</b>, parse the protocol range and store it in * <b>low_out</b> and <b>high_out</b>. A protocol range has the format U, or - * U-U, where U is an unsigned 32-bit integer. + * U-U, where U is an unsigned integer between 0 and 63 inclusive. */ static int parse_version_range(const char *s, const char *end_of_range, diff --git a/src/feature/hs_common/shared_random_client.c b/src/feature/hs_common/shared_random_client.c index 5772034c6d..3d6be94080 100644 --- a/src/feature/hs_common/shared_random_client.c +++ b/src/feature/hs_common/shared_random_client.c @@ -255,10 +255,6 @@ sr_state_get_start_time_of_current_protocol_run(void) protocol run */ time_t time_elapsed_since_start_of_run = curr_round_slot * voting_interval; - log_debug(LD_GENERAL, "Current SRV proto run: Start of current round: %u. " - "Time elapsed: %u (%d)", (unsigned) beginning_of_curr_round, - (unsigned) time_elapsed_since_start_of_run, voting_interval); - return beginning_of_curr_round - time_elapsed_since_start_of_run; } diff --git a/src/feature/nodelist/node_select.c b/src/feature/nodelist/node_select.c index e31abb247f..7b9e241e5b 100644 --- a/src/feature/nodelist/node_select.c +++ b/src/feature/nodelist/node_select.c @@ -540,6 +540,51 @@ bridge_get_advertised_bandwidth_bounded(routerinfo_t *router) return result; } +/** + * We have found an instance of bug 32868: log our best guess about where the + * routerstatus was found. + **/ +static void +log_buggy_rs_source(const routerstatus_t *rs) +{ + static ratelim_t buggy_rs_ratelim = RATELIM_INIT(1200); + char *m; + if ((m = rate_limit_log(&buggy_rs_ratelim, approx_time()))) { + log_warn(LD_BUG, + "Found a routerstatus %p with has_guardfraction=%u " + " and guardfraction_percentage=%u, but is_possible_guard=%u.%s", + rs, + rs->has_guardfraction, + rs->guardfraction_percentage, + rs->is_possible_guard, + m); + tor_free(m); + networkstatus_t *ns; + int in_ns_count = 0; + if ((ns = networkstatus_get_latest_consensus_by_flavor(FLAV_NS))) { + int pos = smartlist_pos(ns->routerstatus_list, rs); + if (pos >= 0) { + ++in_ns_count; + log_warn(LD_BUG, "Found the routerstatus at position %d of the " + "NS consensus.", pos); + } + } + if ((ns = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC))) { + int pos = smartlist_pos(ns->routerstatus_list, rs); + if (pos >= 0) { + ++in_ns_count; + log_warn(LD_BUG, "Found the routerstatus at position %d of the " + "MD consensus.", pos); + } + } + if (in_ns_count == 0) { + log_warn(LD_BUG, "Could not find the routerstatus in any " + "latest consensus."); + } + tor_assert_nonfatal_unreached(); + } +} + /** Given a list of routers and a weighting rule as in * smartlist_choose_node_by_bandwidth_weights, compute weighted bandwidth * values for each node and store them in a freshly allocated @@ -715,10 +760,11 @@ compute_weighted_bandwidths(const smartlist_t *sl, * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B. */ if (node->rs && node->rs->has_guardfraction && rule != WEIGHT_FOR_GUARD) { - /* XXX The assert should actually check for is_guard. However, - * that crashes dirauths because of #13297. This should be - * equivalent: */ - tor_assert(node->rs->is_possible_guard); + /* We should only have guardfraction set if the node has the Guard + flag. */ + if (! node->rs->is_possible_guard) { + log_buggy_rs_source(node->rs); + } guard_get_guardfraction_bandwidth(&guardfraction_bw, this_bw, diff --git a/src/feature/relay/ext_orport.c b/src/feature/relay/ext_orport.c index 56c5bb96f5..136aee3084 100644 --- a/src/feature/relay/ext_orport.c +++ b/src/feature/relay/ext_orport.c @@ -494,6 +494,10 @@ connection_ext_or_handle_cmd_useraddr(connection_t *conn, } conn->address = tor_addr_to_str_dup(&addr); + /* Now that we know the address, we don't have to manually override rate + * limiting. */ + conn->always_rate_limit_as_remote = 0; + return 0; } @@ -659,4 +663,3 @@ ext_orport_free_all(void) if (ext_or_auth_cookie) /* Free the auth cookie */ tor_free(ext_or_auth_cookie); } - diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index e91550a78c..283f7c326b 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -1433,6 +1433,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 <b>dirport</b> if the DirPort option is 0, or @@ -1848,34 +1892,11 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) 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 bd6a8a012e..ab1f771017 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -59,6 +59,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/feature/rend/rendclient.c b/src/feature/rend/rendclient.c index cde954da95..5c9dbea8e3 100644 --- a/src/feature/rend/rendclient.c +++ b/src/feature/rend/rendclient.c @@ -261,8 +261,8 @@ rend_client_send_introduction(origin_circuit_t *introcirc, > MAX_NICKNAME_LEN)) { goto perm_err; } - strncpy(tmp, rendcirc->build_state->chosen_exit->nickname, - (MAX_NICKNAME_LEN+1)); /* nul pads */ + strlcpy(tmp, rendcirc->build_state->chosen_exit->nickname, + sizeof(tmp)); memcpy(tmp+MAX_NICKNAME_LEN+1, rendcirc->rend_data->rend_cookie, REND_COOKIE_LEN); dh_offset = MAX_NICKNAME_LEN+1+REND_COOKIE_LEN; diff --git a/src/feature/rend/rendservice.c b/src/feature/rend/rendservice.c index c96ecec308..eaf0432a7d 100644 --- a/src/feature/rend/rendservice.c +++ b/src/feature/rend/rendservice.c @@ -130,6 +130,22 @@ static smartlist_t *rend_service_list = NULL; * service on config reload. */ static smartlist_t *rend_service_staging_list = NULL; +/** Helper: log the deprecation warning for version 2 only once. */ +static void +log_once_deprecation_warning(void) +{ + static bool logged_once = false; + if (!logged_once) { + log_warn(LD_REND, "DEPRECATED: Onion service version 2 are deprecated. " + "Please use version 3 which is the default now. " + "Currently, version 2 is planned to be obsolete in " + "the Tor version 0.4.6 stable series."); + logged_once = true; + } +} +/** Macro to make it very explicit that we are warning about deprecation. */ +#define WARN_ONCE_DEPRECATION() log_once_deprecation_warning() + /* Like rend_get_service_list_mutable, but returns a read-only list. */ static const smartlist_t* rend_get_service_list(const smartlist_t* substitute_service_list) @@ -731,6 +747,9 @@ rend_config_service(const config_line_t *line_, tor_assert(options); tor_assert(config); + /* We are about to configure a version 2 service. Warn of deprecation. */ + WARN_ONCE_DEPRECATION(); + /* Use the staging service list so that we can check then do the pruning * process using the main list at the end. */ if (rend_service_staging_list == NULL) { diff --git a/src/lib/net/address.c b/src/lib/net/address.c index 076ca3eb34..69004ddb0e 100644 --- a/src/lib/net/address.c +++ b/src/lib/net/address.c @@ -337,7 +337,7 @@ tor_addr_to_str(char *dest, const tor_addr_t *addr, size_t len, int decorate) break; case AF_INET6: /* Shortest addr [ :: ] + \0 */ - if (len < (3 + (decorate ? 2 : 0))) + if (len < (3 + (decorate ? 2u : 0u))) return NULL; if (decorate) diff --git a/src/lib/tls/tortls_nss.c b/src/lib/tls/tortls_nss.c index 1436442e1c..6f6c47674e 100644 --- a/src/lib/tls/tortls_nss.c +++ b/src/lib/tls/tortls_nss.c @@ -418,6 +418,16 @@ tor_tls_new(tor_socket_t sock, int is_server) return NULL; } + /* even if though the socket is already nonblocking, we need to tell NSS + * about the fact, so that it knows what to do when it says EAGAIN. */ + PRSocketOptionData data; + data.option = PR_SockOpt_Nonblocking; + data.value.non_blocking = 1; + if (PR_SetSocketOption(ssl, &data) != PR_SUCCESS) { + PR_Close(ssl); + return NULL; + } + tor_tls_t *tls = tor_malloc_zero(sizeof(tor_tls_t)); tls->magic = TOR_TLS_MAGIC; tls->context = ctx; diff --git a/src/rust/protover/errors.rs b/src/rust/protover/errors.rs index dc0d8735f4..04397ac4fe 100644 --- a/src/rust/protover/errors.rs +++ b/src/rust/protover/errors.rs @@ -36,7 +36,7 @@ impl Display for ProtoverError { ProtoverError::Unparseable => write!(f, "The protover string was unparseable."), ProtoverError::ExceedsMax => write!( f, - "The high in a (low, high) protover range exceeds u32::MAX." + "The high in a (low, high) protover range exceeds 63." ), ProtoverError::ExceedsExpansionLimit => write!( f, diff --git a/src/rust/protover/protoset.rs b/src/rust/protover/protoset.rs index 3b283983c8..0ab94457c5 100644 --- a/src/rust/protover/protoset.rs +++ b/src/rust/protover/protoset.rs @@ -294,6 +294,10 @@ impl ProtoSet { } } +/// Largest allowed protocol version. +/// C_RUST_COUPLED: protover.c `MAX_PROTOCOL_VERSION` +const MAX_PROTOCOL_VERSION: Version = 63; + impl FromStr for ProtoSet { type Err = ProtoverError; @@ -370,7 +374,7 @@ impl FromStr for ProtoSet { let pieces: ::std::str::Split<char> = version_string.split(','); for p in pieces { - if p.contains('-') { + let (lo,hi) = if p.contains('-') { let mut pair = p.splitn(2, '-'); let low = pair.next().ok_or(ProtoverError::Unparseable)?; @@ -379,12 +383,17 @@ impl FromStr for ProtoSet { let lo: Version = low.parse().or(Err(ProtoverError::Unparseable))?; let hi: Version = high.parse().or(Err(ProtoverError::Unparseable))?; - pairs.push((lo, hi)); + (lo,hi) } else { let v: u32 = p.parse().or(Err(ProtoverError::Unparseable))?; - pairs.push((v, v)); + (v, v) + }; + + if lo > MAX_PROTOCOL_VERSION || hi > MAX_PROTOCOL_VERSION { + return Err(ProtoverError::ExceedsMax); } + pairs.push((lo, hi)); } ProtoSet::from_slice(&pairs[..]) @@ -674,12 +683,11 @@ mod test { #[test] fn test_protoset_into_vec() { - let ps: ProtoSet = "1-13,42,9001,4294967294".parse().unwrap(); + let ps: ProtoSet = "1-13,42".parse().unwrap(); let v: Vec<Version> = ps.into(); assert!(v.contains(&7)); - assert!(v.contains(&9001)); - assert!(v.contains(&4294967294)); + assert!(v.contains(&42)); } } diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs index 06fdf56c69..536667f61b 100644 --- a/src/rust/protover/protover.rs +++ b/src/rust/protover/protover.rs @@ -866,12 +866,12 @@ mod test { #[test] fn test_protoentry_from_str_allowed_number_of_versions() { - assert_protoentry_is_parseable!("Desc=1-4294967294"); + assert_protoentry_is_parseable!("Desc=1-63"); } #[test] fn test_protoentry_from_str_too_many_versions() { - assert_protoentry_is_unparseable!("Desc=1-4294967295"); + assert_protoentry_is_unparseable!("Desc=1-64"); } #[test] @@ -910,10 +910,10 @@ mod test { #[test] fn test_protoentry_all_supported_unsupported_high_version() { - let protocols: UnvalidatedProtoEntry = "HSDir=12-100".parse().unwrap(); + let protocols: UnvalidatedProtoEntry = "HSDir=12-60".parse().unwrap(); let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported(); assert_eq!(true, unsupported.is_some()); - assert_eq!("HSDir=12-100", &unsupported.unwrap().to_string()); + assert_eq!("HSDir=12-60", &unsupported.unwrap().to_string()); } #[test] @@ -962,7 +962,7 @@ mod test { ProtoSet::from_str(&versions).unwrap().to_string() ); - versions = "1-3,500"; + versions = "1-3,50"; assert_eq!( String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string() diff --git a/src/rust/protover/tests/protover.rs b/src/rust/protover/tests/protover.rs index 942fe3c6ab..d563202d87 100644 --- a/src/rust/protover/tests/protover.rs +++ b/src/rust/protover/tests/protover.rs @@ -98,10 +98,10 @@ fn protocol_all_supported_with_unsupported_protocol() { #[test] fn protocol_all_supported_with_unsupported_versions() { - let protocols: UnvalidatedProtoEntry = "Link=3-999".parse().unwrap(); + let protocols: UnvalidatedProtoEntry = "Link=3-63".parse().unwrap(); let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported(); assert_eq!(true, unsupported.is_some()); - assert_eq!("Link=6-999", &unsupported.unwrap().to_string()); + assert_eq!("Link=6-63", &unsupported.unwrap().to_string()); } #[test] @@ -114,10 +114,10 @@ fn protocol_all_supported_with_unsupported_low_version() { #[test] fn protocol_all_supported_with_unsupported_high_version() { - let protocols: UnvalidatedProtoEntry = "Cons=1-2,999".parse().unwrap(); + let protocols: UnvalidatedProtoEntry = "Cons=1-2,60".parse().unwrap(); let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported(); assert_eq!(true, unsupported.is_some()); - assert_eq!("Cons=999", &unsupported.unwrap().to_string()); + assert_eq!("Cons=60", &unsupported.unwrap().to_string()); } #[test] @@ -195,27 +195,27 @@ fn protover_compute_vote_returns_protocols_that_it_doesnt_currently_support() { #[test] fn protover_compute_vote_returns_matching_for_mix() { - let protocols: &[UnvalidatedProtoEntry] = &["Link=1-10,500 Cons=1,3-7,8".parse().unwrap()]; + let protocols: &[UnvalidatedProtoEntry] = &["Link=1-10,50 Cons=1,3-7,8".parse().unwrap()]; let listed = ProtoverVote::compute(protocols, &1); - assert_eq!("Cons=1,3-8 Link=1-10,500", listed.to_string()); + assert_eq!("Cons=1,3-8 Link=1-10,50", listed.to_string()); } #[test] fn protover_compute_vote_returns_matching_for_longer_mix() { let protocols: &[UnvalidatedProtoEntry] = &[ - "Desc=1-10,500 Cons=1,3-7,8".parse().unwrap(), - "Link=123-456,78 Cons=2-6,8 Desc=9".parse().unwrap(), + "Desc=1-10,50 Cons=1,3-7,8".parse().unwrap(), + "Link=12-45,8 Cons=2-6,8 Desc=9".parse().unwrap(), ]; let listed = ProtoverVote::compute(protocols, &1); - assert_eq!("Cons=1-8 Desc=1-10,500 Link=78,123-456", listed.to_string()); + assert_eq!("Cons=1-8 Desc=1-10,50 Link=8,12-45", listed.to_string()); } #[test] fn protover_compute_vote_returns_matching_for_longer_mix_with_threshold_two() { let protocols: &[UnvalidatedProtoEntry] = &[ - "Desc=1-10,500 Cons=1,3-7,8".parse().unwrap(), - "Link=123-456,78 Cons=2-6,8 Desc=9".parse().unwrap(), + "Desc=1-10,50 Cons=1,3-7,8".parse().unwrap(), + "Link=8,12-45 Cons=2-6,8 Desc=9".parse().unwrap(), ]; let listed = ProtoverVote::compute(protocols, &2); @@ -320,30 +320,20 @@ fn protocol_all_supported_with_single_protocol_and_protocol_range() { assert_eq!(true, unsupported.is_none()); } -// By allowing us to add to votes, the C implementation allows us to -// exceed the limit. -#[test] -fn protover_compute_vote_may_exceed_limit() { - let proto1: UnvalidatedProtoEntry = "Sleen=1-65535".parse().unwrap(); - let proto2: UnvalidatedProtoEntry = "Sleen=100000".parse().unwrap(); - - let _result: UnvalidatedProtoEntry = ProtoverVote::compute(&[proto1, proto2], &1); -} - #[test] fn protover_all_supported_should_exclude_versions_we_actually_do_support() { - let proto: UnvalidatedProtoEntry = "Link=3-999".parse().unwrap(); + let proto: UnvalidatedProtoEntry = "Link=3-63".parse().unwrap(); let result: String = proto.all_supported().unwrap().to_string(); - assert_eq!(result, "Link=6-999".to_string()); + assert_eq!(result, "Link=6-63".to_string()); } #[test] fn protover_all_supported_should_exclude_versions_we_actually_do_support_complex1() { - let proto: UnvalidatedProtoEntry = "Link=1-3,345-666".parse().unwrap(); + let proto: UnvalidatedProtoEntry = "Link=1-3,30-63".parse().unwrap(); let result: String = proto.all_supported().unwrap().to_string(); - assert_eq!(result, "Link=345-666".to_string()); + assert_eq!(result, "Link=30-63".to_string()); } #[test] @@ -356,26 +346,10 @@ fn protover_all_supported_should_exclude_versions_we_actually_do_support_complex #[test] fn protover_all_supported_should_exclude_some_versions_and_entire_protocols() { - let proto: UnvalidatedProtoEntry = "Link=1-3,5-12 Quokka=9000-9001".parse().unwrap(); - let result: String = proto.all_supported().unwrap().to_string(); - - assert_eq!(result, "Link=6-12 Quokka=9000-9001".to_string()); -} - -#[test] -fn protover_all_supported_should_not_dos_anyones_computer() { - let proto: UnvalidatedProtoEntry = "Link=1-2147483648".parse().unwrap(); - let result: String = proto.all_supported().unwrap().to_string(); - - assert_eq!(result, "Link=6-2147483648".to_string()); -} - -#[test] -fn protover_all_supported_should_not_dos_anyones_computer_max_versions() { - let proto: UnvalidatedProtoEntry = "Link=1-4294967294".parse().unwrap(); + let proto: UnvalidatedProtoEntry = "Link=1-3,5-12 Quokka=50-51".parse().unwrap(); let result: String = proto.all_supported().unwrap().to_string(); - assert_eq!(result, "Link=6-4294967294".to_string()); + assert_eq!(result, "Link=6-12 Quokka=50-51".to_string()); } #[test] diff --git a/src/test/include.am b/src/test/include.am index ecb7689579..75861fb9ef 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -23,7 +23,15 @@ TESTSCRIPTS = \ src/test/test_workqueue_pipe.sh \ src/test/test_workqueue_pipe2.sh \ src/test/test_workqueue_socketpair.sh \ - src/test/test_switch_id.sh + src/test/test_switch_id.sh \ + src/test/unittest_part1.sh \ + src/test/unittest_part2.sh \ + src/test/unittest_part3.sh \ + src/test/unittest_part4.sh \ + src/test/unittest_part5.sh \ + src/test/unittest_part6.sh \ + src/test/unittest_part7.sh \ + src/test/unittest_part8.sh if USE_RUST TESTSCRIPTS += \ @@ -35,7 +43,7 @@ TESTSCRIPTS += src/test/test_ntor.sh src/test/test_hs_ntor.sh src/test/test_bt.s TESTSCRIPTS += src/test/test_rebind.sh endif -TESTS += src/test/test src/test/test-slow src/test/test-memwipe \ +TESTS += src/test/test-slow src/test/test-memwipe \ src/test/test_workqueue \ src/test/test_keygen.sh \ src/test/test_key_expiration.sh \ @@ -369,7 +377,15 @@ EXTRA_DIST += \ src/test/test_workqueue_efd2.sh \ src/test/test_workqueue_pipe.sh \ src/test/test_workqueue_pipe2.sh \ - src/test/test_workqueue_socketpair.sh + src/test/test_workqueue_socketpair.sh \ + src/test/unittest_part1.sh \ + src/test/unittest_part2.sh \ + src/test/unittest_part3.sh \ + src/test/unittest_part4.sh \ + src/test/unittest_part5.sh \ + src/test/unittest_part6.sh \ + src/test/unittest_part7.sh \ + src/test/unittest_part8.sh test-rust: $(TESTS_ENVIRONMENT) "$(abs_top_srcdir)/src/test/test_rust.sh" diff --git a/src/test/test_channel.c b/src/test/test_channel.c index e55b9b0750..afb7db813c 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -46,7 +46,6 @@ static int dump_statistics_mock_matches = 0; static int test_close_called = 0; static int test_chan_should_be_canonical = 0; static int test_chan_should_match_target = 0; -static int test_chan_canonical_should_be_reliable = 0; static int test_chan_listener_close_fn_called = 0; static int test_chan_listener_fn_called = 0; @@ -357,14 +356,10 @@ scheduler_release_channel_mock(channel_t *ch) } static int -test_chan_is_canonical(channel_t *chan, int req) +test_chan_is_canonical(channel_t *chan) { tor_assert(chan); - if (req && test_chan_canonical_should_be_reliable) { - return 1; - } - if (test_chan_should_be_canonical) { return 1; } @@ -1381,6 +1376,9 @@ test_channel_for_extend(void *arg) /* Make it older than chan1. */ chan2->timestamp_created = chan1->timestamp_created - 1; + /* Say it's all canonical. */ + test_chan_should_be_canonical = 1; + /* Set channel identities and add it to the channel map. The last one to be * added is made the first one in the list so the lookup will always return * that one first. */ @@ -1475,8 +1473,8 @@ test_channel_for_extend(void *arg) chan2->is_bad_for_new_circs = 0; /* Non canonical channels. */ + test_chan_should_be_canonical = 0; test_chan_should_match_target = 0; - test_chan_canonical_should_be_reliable = 1; ret_chan = channel_get_for_extend(digest, &ed_id, &addr, &msg, &launch); tt_assert(!ret_chan); tt_str_op(msg, OP_EQ, "Connections all too old, or too non-canonical. " @@ -1567,4 +1565,3 @@ struct testcase_t channel_tests[] = { NULL, NULL }, END_OF_TESTCASES }; - diff --git a/src/test/test_config.c b/src/test/test_config.c index 855725411a..d648666f6e 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -4861,6 +4861,71 @@ test_config_parse_port_config__ports__server_options(void *data) } 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) { int ret; @@ -5920,6 +5985,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_protover.c b/src/test/test_protover.c index 63c508bd13..b4689045cf 100644 --- a/src/test/test_protover.c +++ b/src/test/test_protover.c @@ -25,7 +25,7 @@ test_protover_parse(void *arg) #else char *re_encoded = NULL; - const char *orig = "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,900"; + const char *orig = "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16"; smartlist_t *elts = parse_protocol_list(orig); tt_assert(elts); @@ -61,7 +61,7 @@ test_protover_parse(void *arg) e = smartlist_get(elts, 3); tt_str_op(e->name, OP_EQ, "Quux"); - tt_int_op(smartlist_len(e->ranges), OP_EQ, 4); + tt_int_op(smartlist_len(e->ranges), OP_EQ, 3); { r = smartlist_get(e->ranges, 0); tt_int_op(r->low, OP_EQ, 9); @@ -74,10 +74,6 @@ test_protover_parse(void *arg) r = smartlist_get(e->ranges, 2); tt_int_op(r->low, OP_EQ, 15); tt_int_op(r->high, OP_EQ, 16); - - r = smartlist_get(e->ranges, 3); - tt_int_op(r->low, OP_EQ, 900); - tt_int_op(r->high, OP_EQ, 900); } re_encoded = encode_protocol_list(elts); @@ -149,14 +145,14 @@ test_protover_vote(void *arg) tt_str_op(result, OP_EQ, ""); tor_free(result); - smartlist_add(lst, (void*) "Foo=1-10,500 Bar=1,3-7,8"); + smartlist_add(lst, (void*) "Foo=1-10,63 Bar=1,3-7,8"); result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, "Bar=1,3-8 Foo=1-10,500"); + tt_str_op(result, OP_EQ, "Bar=1,3-8 Foo=1-10,63"); tor_free(result); - smartlist_add(lst, (void*) "Quux=123-456,78 Bar=2-6,8 Foo=9"); + smartlist_add(lst, (void*) "Quux=12-45 Bar=2-6,8 Foo=9"); result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, "Bar=1-8 Foo=1-10,500 Quux=78,123-456"); + tt_str_op(result, OP_EQ, "Bar=1-8 Foo=1-10,63 Quux=12-45"); tor_free(result); result = protover_compute_vote(lst, 2); @@ -194,45 +190,16 @@ test_protover_vote(void *arg) /* Just below the threshold: Rust */ smartlist_clear(lst); - smartlist_add(lst, (void*) "Sleen=1-500"); + smartlist_add(lst, (void*) "Sleen=1-50"); result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, "Sleen=1-500"); + tt_str_op(result, OP_EQ, "Sleen=1-50"); tor_free(result); /* Just below the threshold: C */ smartlist_clear(lst); - smartlist_add(lst, (void*) "Sleen=1-65536"); - result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, "Sleen=1-65536"); - tor_free(result); - - /* Large protover lists that exceed the threshold */ - - /* By adding two votes, C allows us to exceed the limit */ - smartlist_add(lst, (void*) "Sleen=1-65536"); - smartlist_add(lst, (void*) "Sleen=100000"); - result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, "Sleen=1-65536,100000"); - tor_free(result); - - /* Large integers */ - smartlist_clear(lst); - smartlist_add(lst, (void*) "Sleen=4294967294"); + smartlist_add(lst, (void*) "Sleen=1-63"); result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, "Sleen=4294967294"); - tor_free(result); - - /* This parses, but fails at the vote stage */ - smartlist_clear(lst); - smartlist_add(lst, (void*) "Sleen=4294967295"); - result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, ""); - tor_free(result); - - smartlist_clear(lst); - smartlist_add(lst, (void*) "Sleen=4294967296"); - result = protover_compute_vote(lst, 1); - tt_str_op(result, OP_EQ, ""); + tt_str_op(result, OP_EQ, "Sleen=1-63"); tor_free(result); /* Protocol name too long */ @@ -272,8 +239,8 @@ test_protover_all_supported(void *arg) tt_assert(! protover_all_supported("Wombat=9", &msg)); tt_str_op(msg, OP_EQ, "Wombat=9"); tor_free(msg); - tt_assert(! protover_all_supported("Link=999", &msg)); - tt_str_op(msg, OP_EQ, "Link=999"); + tt_assert(! protover_all_supported("Link=60", &msg)); + tt_str_op(msg, OP_EQ, "Link=60"); tor_free(msg); // Mix of things we support and things we don't @@ -283,11 +250,11 @@ test_protover_all_supported(void *arg) /* Mix of things we support and don't support within a single protocol * which we do support */ - tt_assert(! protover_all_supported("Link=3-999", &msg)); - tt_str_op(msg, OP_EQ, "Link=6-999"); + tt_assert(! protover_all_supported("Link=3-60", &msg)); + tt_str_op(msg, OP_EQ, "Link=6-60"); tor_free(msg); - tt_assert(! protover_all_supported("Link=1-3,345-666", &msg)); - tt_str_op(msg, OP_EQ, "Link=345-666"); + tt_assert(! protover_all_supported("Link=1-3,50-63", &msg)); + tt_str_op(msg, OP_EQ, "Link=50-63"); tor_free(msg); tt_assert(! protover_all_supported("Link=1-3,5-12", &msg)); tt_str_op(msg, OP_EQ, "Link=6-12"); @@ -295,18 +262,8 @@ test_protover_all_supported(void *arg) /* Mix of protocols we do support and some we don't, where the protocols * we do support have some versions we don't support. */ - tt_assert(! protover_all_supported("Link=1-3,5-12 Quokka=9000-9001", &msg)); - tt_str_op(msg, OP_EQ, "Link=6-12 Quokka=9000-9001"); - tor_free(msg); - - /* We shouldn't be able to DoS ourselves parsing a large range. */ - tt_assert(! protover_all_supported("Sleen=1-2147483648", &msg)); - tt_str_op(msg, OP_EQ, "Sleen=1-2147483648"); - tor_free(msg); - - /* This case is allowed. */ - tt_assert(! protover_all_supported("Sleen=1-4294967294", &msg)); - tt_str_op(msg, OP_EQ, "Sleen=1-4294967294"); + tt_assert(! protover_all_supported("Link=1-3,5-12 Quokka=40-41", &msg)); + tt_str_op(msg, OP_EQ, "Link=6-12 Quokka=40-41"); tor_free(msg); /* If we get a (barely) valid (but unsupported list, we say "yes, that's @@ -566,9 +523,9 @@ test_protover_vote_roundtrip(void *args) /* Will fail because of 4294967295. */ { "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,900 Zn=1,4294967295", NULL }, - { "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,900 Zn=1,4294967294", - "Bar=3 Foo=1,3 Quux=9-12,14-16,900 Zn=1,4294967294" }, - { "Zu16=1,65536", "Zu16=1,65536" }, + { "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,50 Zn=1,42", + "Bar=3 Foo=1,3 Quux=9-12,14-16,50 Zn=1,42" }, + { "Zu16=1,63", "Zu16=1,63" }, { "N-1=1,2", "N-1=1-2" }, { "-1=4294967295", NULL }, { "-1=3", "-1=3" }, @@ -602,12 +559,8 @@ test_protover_vote_roundtrip(void *args) /* Large integers */ { "Link=4294967296", NULL }, /* Large range */ - { "Sleen=1-501", "Sleen=1-501" }, + { "Sleen=1-63", "Sleen=1-63" }, { "Sleen=1-65537", NULL }, - /* Both C/Rust implementations should be able to handle this mild DoS. */ - { "Sleen=1-2147483648", NULL }, - /* Rust tests are built in debug mode, so ints are bounds-checked. */ - { "Sleen=1-4294967295", NULL }, }; unsigned u; smartlist_t *votes = smartlist_new(); diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c index 4f544cf21c..8b0e2df485 100644 --- a/src/test/test_rendcache.c +++ b/src/test/test_rendcache.c @@ -954,6 +954,7 @@ test_rend_cache_free_all(void *data) one->parsed = desc_one; desc_one->timestamp = time(NULL) + TIME_IN_THE_PAST; desc_one->pk = pk_generate(0); + rend_cache_increment_allocation(rend_cache_entry_allocation(one)); strmap_set_lc(rend_cache, "foo1", one); rend_cache_free_all(); @@ -978,11 +979,13 @@ test_rend_cache_entry_free(void *data) // Handles NULL descriptor correctly e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + rend_cache_increment_allocation(rend_cache_entry_allocation(e)); rend_cache_entry_free(e); // Handles non-NULL descriptor correctly e = tor_malloc_zero(sizeof(rend_cache_entry_t)); e->desc = tor_malloc(10); + rend_cache_increment_allocation(rend_cache_entry_allocation(e)); rend_cache_entry_free(e); /* done: */ @@ -1101,6 +1104,7 @@ test_rend_cache_clean_v2_descs_as_dir(void *data) desc->timestamp = now; desc->pk = pk_generate(0); e->parsed = desc; + rend_cache_increment_allocation(rend_cache_entry_allocation(e)); digestmap_set(rend_cache_v2_dir, key, e); /* Set the cutoff to minus 10 seconds. */ @@ -1250,4 +1254,3 @@ struct testcase_t rend_cache_tests[] = { test_rend_cache_validate_intro_point_failure, 0, NULL, NULL }, END_OF_TESTCASES }; - diff --git a/src/test/test_router.c b/src/test/test_router.c index 601881a124..e0d3adfdbd 100644 --- a/src/test/test_router.c +++ b/src/test/test_router.c @@ -7,9 +7,12 @@ * \brief Unittests for code in router.c **/ +#define CONFIG_PRIVATE +#define CONNECTION_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/routerinfo_st.h" #include "feature/nodelist/routerlist.h" @@ -17,6 +20,9 @@ #include "feature/stats/rephist.h" #include "lib/crypt_ops/crypto_curve25519.h" #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" @@ -231,11 +237,124 @@ test_router_check_descriptor_bandwidth_changed(void *arg) UNMOCK(we_are_hibernating); } +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 } struct testcase_t router_tests[] = { ROUTER_TEST(check_descriptor_bandwidth_changed, TT_FORK), ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK), + ROUTER_TEST(get_advertised_or_port, TT_FORK), + ROUTER_TEST(get_advertised_or_port_localhost, TT_FORK), END_OF_TESTCASES }; diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 2c9c4538b9..daa7aa524a 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -278,6 +278,8 @@ main(int c, const char **v) control_initialize_event_queue(); configure_backtrace_handler(get_version()); + unsigned num=1, den=1; + for (i_out = i = 1; i < c; ++i) { if (!strcmp(v[i], "--warn")) { loglevel = LOG_WARN; @@ -289,6 +291,19 @@ main(int c, const char **v) loglevel = LOG_DEBUG; } else if (!strcmp(v[i], "--accel")) { accel_crypto = 1; + } else if (!strcmp(v[i], "--fraction")) { + if (i+1 == c) { + printf("--fraction needs an argument.\n"); + return 1; + } + const char *fracstr = v[++i]; + char ch; + if (sscanf(fracstr, "%u/%u%c", &num, &den, &ch) != 2) { + printf("--fraction expects a fraction as an input.\n"); + } + if (den == 0 || num == 0 || num > den) { + printf("--fraction expects a valid fraction as an input.\n"); + } } else { v[i_out++] = v[i]; } @@ -363,6 +378,33 @@ main(int c, const char **v) smartlist_free(skip); } + if (den != 1) { + // count the tests. Linear but fast. + unsigned n_tests = 0; + struct testgroup_t *tg; + struct testcase_t *tc; + for (tg = testgroups; tg->prefix != NULL; ++tg) { + for (tc = tg->cases; tc->name != NULL; ++tc) { + ++n_tests; + } + } + // Which tests should we run? This can give iffy results if den is huge + // but it doesn't actually matter in practice. + unsigned tests_per_chunk = CEIL_DIV(n_tests, den); + unsigned start_at = (num-1) * tests_per_chunk; + + // Skip the tests that are outside of the range. + unsigned idx = 0; + for (tg = testgroups; tg->prefix != NULL; ++tg) { + for (tc = tg->cases; tc->name != NULL; ++tc) { + if (idx < start_at || idx >= start_at + tests_per_chunk) { + tc->flags |= TT_SKIP; + } + ++idx; + } + } + } + int have_failed = (tinytest_main(c, v, testgroups) != 0); free_pregenerated_keys(); diff --git a/src/test/unittest_part1.sh b/src/test/unittest_part1.sh new file mode 100755 index 0000000000..5be0f499f9 --- /dev/null +++ b/src/test/unittest_part1.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 1/8 diff --git a/src/test/unittest_part2.sh b/src/test/unittest_part2.sh new file mode 100755 index 0000000000..9a614eb8c1 --- /dev/null +++ b/src/test/unittest_part2.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 2/8 diff --git a/src/test/unittest_part3.sh b/src/test/unittest_part3.sh new file mode 100755 index 0000000000..5cbc3fe495 --- /dev/null +++ b/src/test/unittest_part3.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 3/8 diff --git a/src/test/unittest_part4.sh b/src/test/unittest_part4.sh new file mode 100755 index 0000000000..bc6fe01f68 --- /dev/null +++ b/src/test/unittest_part4.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 4/8 diff --git a/src/test/unittest_part5.sh b/src/test/unittest_part5.sh new file mode 100755 index 0000000000..9bbff34fb8 --- /dev/null +++ b/src/test/unittest_part5.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 5/8 diff --git a/src/test/unittest_part6.sh b/src/test/unittest_part6.sh new file mode 100755 index 0000000000..2d5eaa8a28 --- /dev/null +++ b/src/test/unittest_part6.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 6/8 diff --git a/src/test/unittest_part7.sh b/src/test/unittest_part7.sh new file mode 100755 index 0000000000..5e6ce2aea5 --- /dev/null +++ b/src/test/unittest_part7.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 7/8 diff --git a/src/test/unittest_part8.sh b/src/test/unittest_part8.sh new file mode 100755 index 0000000000..7fea9c9c7f --- /dev/null +++ b/src/test/unittest_part8.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +"${abs_top_builddir:-.}/src/test/test" --fraction 8/8 |