diff options
538 files changed, 37392 insertions, 11231 deletions
diff --git a/.gitignore b/.gitignore index f2a6dfb493..4dc8867a89 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .#* *~ *.swp +*.swo # C stuff *.o *.obj @@ -70,6 +71,7 @@ uptime-*.json /Tor*Bundle.dmg /tor-*-win32.exe /coverage_html/ +/callgraph/ # /contrib/ /contrib/dist/tor.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..ba981d5f14 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,45 @@ +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 suspectible 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 @@ -1,3 +1,1282 @@ +Changes in version 0.3.2.2-alpha - 2017-09-29 + Tor 0.3.2.2-alpha is the second release in the 0.3.2 series. This + release fixes several minor bugs in the new scheduler and next- + generation onion services; both features were newly added in the 0.3.2 + series. Other fixes in this alpha include several fixes for non-fatal + tracebacks which would appear in logs. + + With the aim to stabilise the 0.3.2 series by 15 December 2017, this + alpha does not contain any substantial new features. Minor features + include better testing and logging. + + The following comprises the complete list of changes included + in tor-0.3.2.2-alpha: + + o Major bugfixes (relay, crash, assertion failure): + - Fix a timing-based assertion failure that could occur when the + circuit out-of-memory handler freed a connection's output buffer. + Fixes bug 23690; bugfix on 0.2.6.1-alpha. + + o Major bugfixes (scheduler): + - If a channel is put into the scheduler's pending list, then it + starts closing, and then if the scheduler runs before it finishes + closing, the scheduler will get stuck trying to flush its cells + while the lower layers refuse to cooperate. Fix that race + condition by giving the scheduler an escape method. Fixes bug + 23676; bugfix on 0.3.2.1-alpha. + + o Minor features (build, compilation): + - The "check-changes" feature is now part of the "make check" tests; + we'll use it to try to prevent misformed changes files from + accumulating. Closes ticket 23564. + - Tor builds should now fail if there are any mismatches between the + C type representing a configuration variable and the C type the + data-driven parser uses to store a value there. Previously, we + needed to check these by hand, which sometimes led to mistakes. + Closes ticket 23643. + + o Minor features (directory authorities): + - Remove longclaw's IPv6 address, as it will soon change. Authority + IPv6 addresses were originally added in 0.2.8.1-alpha. This leaves + 3/8 directory authorities with IPv6 addresses, but there are also + 52 fallback directory mirrors with IPv6 addresses. Resolves 19760. + + o Minor features (hidden service, circuit, logging): + - Improve logging of many callsite in the circuit subsystem to print + the circuit identifier(s). + - Log when we cleanup an intro point from a service so we know when + and for what reason it happened. Closes ticket 23604. + + o Minor features (logging): + - Log more circuit information whenever we are about to try to + package a relay cell on a circuit with a nonexistent n_chan. + Attempt to diagnose ticket 8185. + - Improve info-level log identification of particular circuits, to + help with debugging. Closes ticket 23645. + + o Minor features (relay): + - When choosing which circuits can be expired as unused, consider + circuits from clients even if those clients used regular CREATE + cells to make them; and do not consider circuits from relays even + if they were made with CREATE_FAST. Part of ticket 22805. + + o Minor features (robustness): + - Change several fatal assertions when flushing buffers into non- + fatal assertions, to prevent any recurrence of 23690. + + o Minor features (spec conformance, bridge, diagnostic): + - When handling the USERADDR command on an ExtOrPort, warn when the + transports provides a USERADDR with no port. In a future version, + USERADDR commands of this format may be rejected. Detects problems + related to ticket 23080. + + o Minor features (testing): + - Add a unit test to make sure that our own generated platform + string will be accepted by directory authorities. Closes + ticket 22109. + + o Minor bugfixes (bootstrapping): + - When warning about state file clock skew, report the correct + direction for the detected skew. Fixes bug 23606; bugfix + on 0.2.8.1-alpha. + - Avoid an assertion failure when logging a state file clock skew + very early in bootstrapping. Fixes bug 23607; bugfix + on 0.3.2.1-alpha. + + o Minor bugfixes (build, compilation): + - Fix a compilation warning when building with zstd support on + 32-bit platforms. Fixes bug 23568; bugfix on 0.3.1.1-alpha. Found + and fixed by Andreas Stieger. + - When searching for OpenSSL, don't accept any OpenSSL library that + lacks TLSv1_1_method(): Tor doesn't build with those versions. + Additionally, look in /usr/local/opt/openssl, if it's present. + These changes together repair the default build on OSX systems + with Homebrew installed. Fixes bug 23602; bugfix on 0.2.7.2-alpha. + + o Minor bugfixes (compression): + - Handle a pathological case when decompressing Zstandard data when + the output buffer size is zero. Fixes bug 23551; bugfix + on 0.3.1.1-alpha. + + o Minor bugfixes (documentation): + - Fix manpage to not refer to the obsolete (and misspelled) + UseEntryGuardsAsDirectoryGuards parameter in the description of + NumDirectoryGuards. Fixes bug 23611; bugfix on 0.2.4.8-alpha. + + o Minor bugfixes (hidden service v3): + - Don't log an assertion failure when we can't find the right + information to extend to an introduction point. In rare cases, + this could happen, causing a warning, even though tor would + recover gracefully. Fixes bug 23159; bugfix on 0.3.2.1-alpha. + - Pad RENDEZVOUS cell up to the size of the legacy cell which is + much bigger so the rendezvous point can't distinguish which hidden + service protocol is being used. Fixes bug 23420; bugfix + on 0.3.2.1-alpha. + + o Minor bugfixes (hidden service, relay): + - Avoid a possible double close of a circuit by the intro point on + error of sending the INTRO_ESTABLISHED cell. Fixes bug 23610; + bugfix on 0.3.0.1-alpha. + + o Minor bugfixes (logging, relay shutdown, annoyance): + - When a circuit is marked for close, do not attempt to package any + cells for channels on that circuit. Previously, we would detect + this condition lower in the call stack, when we noticed that the + circuit had no attached channel, and log an annoying message. + Fixes bug 8185; bugfix on 0.2.5.4-alpha. + + o Minor bugfixes (scheduler): + - When switching schedulers due to a consensus change, we didn't + give the new scheduler a chance to react to the consensus. Fix + that. Fixes bug 23537; bugfix on 0.3.2.1-alpha. + - Make the KISTSchedRunInterval option a non negative value. With + this, the way to disable KIST through the consensus is to set it + to 0. Fixes bug 23539; bugfix on 0.3.2.1-alpha. + - Only notice log the selected scheduler when we switch scheduler + types. Fixes bug 23552; bugfix on 0.3.2.1-alpha. + - Avoid a compilation warning on macOS in scheduler_ev_add() caused + by a different tv_usec data type. Fixes bug 23575; bugfix + on 0.3.2.1-alpha. + - Make a hard exit if tor is unable to pick a scheduler which can + happen if the user specifies a scheduler type that is not + supported and not other types in Schedulers. Fixes bug 23581; + bugfix on 0.3.2.1-alpha. + - Properly initialize the scheduler last run time counter so it is + not 0 at the first tick. Fixes bug 23696; bugfix on 0.3.2.1-alpha. + + o Minor bugfixes (testing): + - Capture and detect several "Result does not fit" warnings in unit + tests on platforms with 32-bit time_t. Fixes bug 21800; bugfix + on 0.2.9.3-alpha. + - Fix additional channelpadding unit test failures by using mocked + time instead of actual time for all tests. Fixes bug 23608; bugfix + on 0.3.1.1-alpha. + - The removal of some old scheduler options caused some tests to + fail on BSD systems. Assume current behavior is correct and make + the tests pass again. Fixes bug 23566; bugfix on 0.3.2.1-alpha. + + o Code simplification and refactoring: + - Remove various ways of testing circuits and connections for + "clientness"; instead, favor channel_is_client(). Part of + ticket 22805. + + o Deprecated features: + - The ReachableDirAddresses and ClientPreferIPv6DirPort options are + now deprecated; they do not apply to relays, and they have had no + effect on clients since 0.2.8.x. Closes ticket 19704. + + o Documentation: + - HiddenServiceVersion man page entry wasn't mentioning the now + supported version 3. Fixes ticket 23580; bugfix on 0.3.2.1-alpha. + - Clarify that the Address option is entirely about setting an + advertised IPv4 address. Closes ticket 18891. + - Clarify the manpage's use of the term "address" to clarify what + kind of address is intended. Closes ticket 21405. + - Document that onion service subdomains are allowed, and ignored. + Closes ticket 18736. + + +Changes in version 0.3.2.1-alpha - 2017-09-18 + Tor 0.3.2.1-alpha is the first release in the 0.3.2.x series. It + includes support for our next-generation ("v3") onion service + protocol, and adds a new circuit scheduler for more responsive + forwarding decisions from relays. There are also numerous other small + features and bugfixes here. + + Below are the changes since Tor 0.3.1.7. + + o Major feature (scheduler, channel): + - Tor now uses new schedulers to decide which circuits should + deliver cells first, in order to improve congestion at relays. The + first type is called "KIST" ("Kernel Informed Socket Transport"), + and is only available on Linux-like systems: it uses feedback from + the kernel to prevent the kernel's TCP buffers from growing too + full. The second new scheduler type is called "KISTLite": it + behaves the same as KIST, but runs on systems without kernel + support for inspecting TCP implementation details. The old + scheduler is still available, under the name "Vanilla". To change + the default scheduler preference order, use the new "Schedulers" + option. (The default preference order is "KIST,KISTLite,Vanilla".) + + Matt Traudt implemented KIST, based on research by Rob Jansen, + John Geddes, Christ Wacek, Micah Sherr, and Paul Syverson. For + more information, see the design paper at + http://www.robgjansen.com/publications/kist-sec2014.pdf and the + followup implementation paper at https://arxiv.org/abs/1709.01044. + Closes ticket 12541. + + o Major features (next-generation onion services): + - Tor now supports the next-generation onion services protocol for + clients and services! As part of this release, the core of + proposal 224 has been implemented and is available for + experimentation and testing by our users. This newer version of + onion services ("v3") features many improvements over the legacy + system, including: + + a) Better crypto (replaced SHA1/DH/RSA1024 + with SHA3/ed25519/curve25519) + + b) Improved directory protocol, leaking much less information to + directory servers. + + c) Improved directory protocol, with smaller surface for + targeted attacks. + + d) Better onion address security against impersonation. + + e) More extensible introduction/rendezvous protocol. + + f) A cleaner and more modular codebase. + + You can identify a next-generation onion address by its length: + they are 56 characters long, as in + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion". + + In the future, we will release more options and features for v3 + onion services, but we first need a testing period, so that the + current codebase matures and becomes more robust. Planned features + include: offline keys, advanced client authorization, improved + guard algorithms, and statistics. For full details, see + proposal 224. + + Legacy ("v2") onion services will still work for the foreseeable + future, and will remain the default until this new codebase gets + tested and hardened. Service operators who want to experiment with + the new system can use the 'HiddenServiceVersion 3' torrc + directive along with the regular onion service configuration + options. We will publish a blog post about this new feature + soon! Enjoy! + + o Major bugfixes (usability, control port): + - Report trusted clock skew indications as bootstrap errors, so + controllers can more easily alert users when their clocks are + wrong. Fixes bug 23506; bugfix on 0.1.2.6-alpha. + + o Minor features (bug detection): + - Log a warning message with a stack trace for any attempt to call + get_options() during option validation. This pattern has caused + subtle bugs in the past. Closes ticket 22281. + + o Minor features (client): + - You can now use Tor as a tunneled HTTP proxy: use the new + HTTPTunnelPort option to open a port that accepts HTTP CONNECT + requests. Closes ticket 22407. + - Add an extra check to make sure that we always use the newer guard + selection code for picking our guards. Closes ticket 22779. + - When downloading (micro)descriptors, don't split the list into + multiple requests unless we want at least 32 descriptors. + Previously, we split at 4, not 32, which led to significant + overhead in HTTP request size and degradation in compression + performance. Closes ticket 23220. + + o Minor features (command line): + - Add a new commandline option, --key-expiration, which prints when + the current signing key is going to expire. Implements ticket + 17639; patch by Isis Lovecruft. + + o Minor features (control port): + - If an application tries to use the control port as an HTTP proxy, + respond with a meaningful "This is the Tor control port" message, + and log the event. Closes ticket 1667. Patch from Ravi + Chandra Padmala. + - Provide better error message for GETINFO desc/(id|name) when not + fetching router descriptors. Closes ticket 5847. Patch by + Kevin Butler. + - Add GETINFO "{desc,md}/download-enabled", to inform the controller + whether Tor will try to download router descriptors and + microdescriptors respectively. Closes ticket 22684. + - Added new GETINFO targets "ip-to-country/{ipv4,ipv6}-available", + so controllers can tell whether the geoip databases are loaded. + Closes ticket 23237. + - Adds a timestamp field to the CIRC_BW and STREAM_BW bandwidth + events. Closes ticket 19254. Patch by "DonnchaC". + + o Minor features (development support): + - Developers can now generate a call-graph for Tor using the + "calltool" python program, which post-processes object dumps. It + should work okay on many Linux and OSX platforms, and might work + elsewhere too. To run it, install calltool from + https://gitweb.torproject.org/user/nickm/calltool.git and run + "make callgraph". Closes ticket 19307. + + o Minor features (ed25519): + - Add validation function to checks for torsion components in + ed25519 public keys, used by prop224 client-side code. Closes + ticket 22006. Math help by Ian Goldberg. + + o Minor features (exit relay, DNS): + - Improve the clarity and safety of the log message from evdns when + receiving an apparently spoofed DNS reply. Closes ticket 3056. + + o Minor features (integration, hardening): + - Add a new NoExec option to prevent Tor from running other + programs. When this option is set to 1, Tor will never try to run + another program, regardless of the settings of + PortForwardingHelper, ClientTransportPlugin, or + ServerTransportPlugin. Once NoExec is set, it cannot be disabled + without restarting Tor. Closes ticket 22976. + + o Minor features (logging): + - Improve the warning message for specifying a relay by nickname. + The previous message implied that nickname registration was still + part of the Tor network design, which it isn't. Closes + ticket 20488. + - If the sandbox filter fails to load, suggest to the user that + their kernel might not support seccomp2. Closes ticket 23090. + + o Minor features (portability): + - Check at configure time whether uint8_t is the same type as + unsigned char. Lots of existing code already makes this + assumption, and there could be strict aliasing issues if the + assumption is violated. Closes ticket 22410. + + o Minor features (relay, configuration): + - Reject attempts to use relative file paths when RunAsDaemon is + set. Previously, Tor would accept these, but the directory- + changing step of RunAsDaemon would give strange and/or confusing + results. Closes ticket 22731. + + o Minor features (startup, safety): + - When configured to write a PID file, Tor now exits if it is unable + to do so. Previously, it would warn and continue. Closes + ticket 20119. + + o Minor features (static analysis): + - The BUG() macro has been changed slightly so that Coverity no + longer complains about dead code if the bug is impossible. Closes + ticket 23054. + + o Minor features (testing): + - The default chutney network tests now include tests for the v3 + hidden service design. Make sure you have the latest version of + chutney if you want to run these. Closes ticket 22437. + - Add a unit test to verify that we can parse a hardcoded v2 hidden + service descriptor. Closes ticket 15554. + + o Minor bugfixes (certificate handling): + - Fix a time handling bug in Tor certificates set to expire after + the year 2106. Fixes bug 23055; bugfix on 0.3.0.1-alpha. Found by + Coverity as CID 1415728. + + o Minor bugfixes (client, usability): + - Refrain from needlessly rejecting SOCKS5-with-hostnames and + SOCKS4a requests that contain IP address strings, even when + SafeSocks in enabled, as this prevents user from connecting to + known IP addresses without relying on DNS for resolving. SafeSocks + still rejects SOCKS connections that connect to IP addresses when + those addresses are _not_ encoded as hostnames. Fixes bug 22461; + bugfix on Tor 0.2.6.2-alpha. + + o Minor bugfixes (code correctness): + - Call htons() in extend_cell_format() for encoding a 16-bit value. + Previously we used ntohs(), which happens to behave the same on + all the platforms we support, but which isn't really correct. + Fixes bug 23106; bugfix on 0.2.4.8-alpha. + - For defense-in-depth, make the controller's write_escaped_data() + function robust to extremely long inputs. Fixes bug 19281; bugfix + on 0.1.1.1-alpha. Reported by Guido Vranken. + + o Minor bugfixes (compilation): + - Fix unused-variable warnings in donna's Curve25519 SSE2 code. + Fixes bug 22895; bugfix on 0.2.7.2-alpha. + + o Minor bugfixes (consensus expiry): + - Check for adequate directory information correctly. Previously, Tor + would reconsider whether it had sufficient directory information + every 2 minutes. Fixes bug 23091; bugfix on 0.2.0.19-alpha. + + o Minor bugfixes (directory protocol): + - Directory servers now include a "Date:" http header for response + codes other than 200. Clients starting with a skewed clock and a + recent consensus were getting "304 Not modified" responses from + directory authorities, so without the Date header, the client + would never hear about a wrong clock. Fixes bug 23499; bugfix + on 0.0.8rc1. + - Make clients wait for 6 seconds before trying to download a + consensus from an authority. Fixes bug 17750; bugfix + on 0.2.8.1-alpha. + + o Minor bugfixes (DoS-resistance): + - If future code asks if there are any running bridges, without + checking if bridges are enabled, log a BUG warning rather than + crashing. Fixes bug 23524; bugfix on 0.3.0.1-alpha. + + o Minor bugfixes (format strictness): + - Restrict several data formats to decimal. Previously, the + BuildTimeHistogram entries in the state file, the "bw=" entries in + the bandwidth authority file, and the process IDs passed to the + __OwningControllerProcess option could all be specified in hex or + octal as well as in decimal. This was not an intentional feature. + Fixes bug 22802; bugfixes on 0.2.2.1-alpha, 0.2.2.2-alpha, + and 0.2.2.28-beta. + + o Minor bugfixes (heartbeat): + - If we fail to write a heartbeat message, schedule a retry for the + minimum heartbeat interval number of seconds in the future. Fixes + bug 19476; bugfix on 0.2.3.1-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, logging): + - Fix some messages on unexpected errors from the seccomp2 library. + Fixes bug 22750; bugfix on 0.2.5.1-alpha. Patch from "cypherpunks". + + o Minor bugfixes (logging): + - Remove duplicate log messages regarding opening non-local + SocksPorts upon parsing config and opening listeners at startup. + Fixes bug 4019; bugfix on 0.2.3.3-alpha. + - Use a more comprehensible log message when telling the user + they've excluded every running exit node. Fixes bug 7890; bugfix + on 0.2.2.25-alpha. + - When logging the number of descriptors we intend to download per + directory request, do not log a number higher than then the number + of descriptors we're fetching in total. Fixes bug 19648; bugfix + on 0.1.1.8-alpha. + - When warning about a directory owned by the wrong user, log the + actual name of the user owning the directory. Previously, we'd log + the name of the process owner twice. Fixes bug 23487; bugfix + on 0.2.9.1-alpha. + - The tor specification says hop counts are 1-based, so fix two log + messages that mistakenly logged 0-based hop counts. Fixes bug + 18982; bugfix on 0.2.6.2-alpha and 0.2.4.5-alpha. Patch by teor. + Credit to Xiaofan Li for reporting this issue. + + o Minor bugfixes (portability): + - Stop using the PATH_MAX variable, which is not defined on GNU + Hurd. Fixes bug 23098; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (relay): + - When uploading our descriptor for the first time after startup, + report the reason for uploading as "Tor just started" rather than + leaving it blank. Fixes bug 22885; bugfix on 0.2.3.4-alpha. + - Avoid unnecessary calls to directory_fetches_from_authorities() on + relays, to prevent spurious address resolutions and descriptor + rebuilds. This is a mitigation for bug 21789. Fixes bug 23470; + bugfix on in 0.2.8.1-alpha. + + o Minor bugfixes (tests): + - Fix a broken unit test for the OutboundAddress option: the parsing + function was never returning an error on failure. Fixes bug 23366; + bugfix on 0.3.0.3-alpha. + - Fix a signed-integer overflow in the unit tests for + dir/download_status_random_backoff, which was untriggered until we + fixed bug 17750. Fixes bug 22924; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (usability, control port): + - Stop making an unnecessary routerlist check in NETINFO clock skew + detection; this was preventing clients from reporting NETINFO clock + skew to controllers. Fixes bug 23532; bugfix on 0.2.4.4-alpha. + + o Code simplification and refactoring: + - Extract the code for handling newly-open channels into a separate + function from the general code to handle channel state + transitions. This change simplifies our callgraph, reducing the + size of the largest strongly connected component by roughly a + factor of two. Closes ticket 22608. + - Remove dead code for largely unused statistics on the number of + times we've attempted various public key operations. Fixes bug + 19871; bugfix on 0.1.2.4-alpha. Fix by Isis Lovecruft. + - Remove several now-obsolete functions for asking about old + variants directory authority status. Closes ticket 22311; patch + from "huyvq". + - Remove some of the code that once supported "Named" and "Unnamed" + routers. Authorities no longer vote for these flags. Closes + ticket 22215. + - Rename the obsolete malleable hybrid_encrypt functions used in TAP + and old hidden services, to indicate that they aren't suitable for + new protocols or formats. Closes ticket 23026. + - Replace our STRUCT_OFFSET() macro with offsetof(). Closes ticket + 22521. Patch from Neel Chauhan. + - Split the enormous circuit_send_next_onion_skin() function into + multiple subfunctions. Closes ticket 22804. + - Split the portions of the buffer.c module that handle particular + protocols into separate modules. Part of ticket 23149. + - Use our test macros more consistently, to produce more useful + error messages when our unit tests fail. Add coccinelle patches to + allow us to re-check for test macro uses. Closes ticket 22497. + + o Deprecated features: + - Deprecate HTTPProxy/HTTPProxyAuthenticator config options. They + only applies to direct unencrypted HTTP connections to your + directory server, which your Tor probably isn't using. Closes + ticket 20575. + + o Documentation: + - Clarify in the manual that "Sandbox 1" is only supported on Linux + kernels. Closes ticket 22677. + - Document all values of PublishServerDescriptor in the manpage. + Closes ticket 15645. + - Improve the documentation for the directory port part of the + DirAuthority line. Closes ticket 20152. + - Restore documentation for the authorities' "approved-routers" + file. Closes ticket 21148. + + o Removed features: + - The AllowDotExit option has been removed as unsafe. It has been + deprecated since 0.2.9.2-alpha. Closes ticket 23426. + - The ClientDNSRejectInternalAddresses flag can no longer be set on + non-testing networks. It has been deprecated since 0.2.9.2-alpha. + Closes ticket 21031. + - The controller API no longer includes an AUTHDIR_NEWDESCS event: + nobody was using it any longer. Closes ticket 22377. + + +Changes in version 0.2.8.15 - 2017-09-18 + Tor 0.2.8.15 backports a collection of bugfixes from later + Tor series. + + Most significantly, it includes a fix for TROVE-2017-008, a + security bug that affects hidden services running with the + SafeLogging option disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + Note that Tor 0.2.8.x will no longer be supported after 1 Jan + 2018. We suggest that you upgrade to the latest stable release if + possible. If you can't, we recommend that you upgrade at least to + 0.2.9, which will be supported until 2020. + + o Major bugfixes (openbsd, denial-of-service, backport from 0.3.1.5-alpha): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xx" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Minor features: + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (compilation, mingw, backport from 0.3.1.1-alpha): + - Backport a fix for an "unused variable" warning that appeared + in some versions of mingw. Fixes bug 22838; bugfix on + 0.2.8.1-alpha. + + o Minor bugfixes (defensive programming, undefined behavior, backport from 0.3.1.4-alpha): + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + o Build features (backport from 0.3.1.5-alpha): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + +Changes in version 0.2.9.12 - 2017-09-18 + Tor 0.2.9.12 backports a collection of bugfixes from later + Tor series. + + Most significantly, it includes a fix for TROVE-2017-008, a + security bug that affects hidden services running with the + SafeLogging option disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + o Major features (security, backport from 0.3.0.2-alpha): + - Change the algorithm used to decide DNS TTLs on client and server + side, to better resist DNS-based correlation attacks like the + DefecTor attack of Greschbach, Pulls, Roberts, Winter, and + Feamster. Now relays only return one of two possible DNS TTL + values, and clients are willing to believe DNS TTL values up to 3 + hours long. Closes ticket 19769. + + o Major bugfixes (crash, directory connections, backport from 0.3.0.5-rc): + - Fix a rare crash when sending a begin cell on a circuit whose + linked directory connection had already been closed. Fixes bug + 21576; bugfix on 0.2.9.3-alpha. Reported by Alec Muffett. + + o Major bugfixes (DNS, backport from 0.3.0.2-alpha): + - Fix a bug that prevented exit nodes from caching DNS records for + more than 60 seconds. Fixes bug 19025; bugfix on 0.2.4.7-alpha. + + o Major bugfixes (linux TPROXY support, backport from 0.3.1.1-alpha): + - Fix a typo that had prevented TPROXY-based transparent proxying + from working under Linux. Fixes bug 18100; bugfix on 0.2.6.3-alpha. + Patch from "d4fq0fQAgoJ". + + o Major bugfixes (openbsd, denial-of-service, backport from 0.3.1.5-alpha): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xx" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Minor features (code style, backport from 0.3.1.3-alpha): + - Add "Falls through" comments to our codebase, in order to silence + GCC 7's -Wimplicit-fallthrough warnings. Patch from Andreas + Stieger. Closes ticket 22446. + + o Minor features (geoip): + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (bandwidth accounting, backport from 0.3.1.1-alpha): + - Roll over monthly accounting at the configured hour and minute, + rather than always at 00:00. Fixes bug 22245; bugfix on 0.0.9rc1. + Found by Andrey Karpov with PVS-Studio. + + o Minor bugfixes (compilation, backport from 0.3.1.5-alpha): + - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug 22915; + bugfix on 0.2.8.1-alpha. + - Fix warnings when building with libscrypt and openssl scrypt support + on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. + - When building with certain versions the mingw C header files, avoid + float-conversion warnings when calling the C functions isfinite(), + isnan(), and signbit(). Fixes bug 22801; bugfix on 0.2.8.1-alpha. + + o Minor bugfixes (compilation, backport from 0.3.1.7): + - Avoid compiler warnings in the unit tests for running tor_sscanf() + with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. + + o Minor bugfixes (compilation, mingw, backport from 0.3.1.1-alpha): + - Backport a fix for an "unused variable" warning that appeared + in some versions of mingw. Fixes bug 22838; bugfix on + 0.2.8.1-alpha. + + o Minor bugfixes (controller, backport from 0.3.1.7): + - Do not crash when receiving a HSPOST command with an empty body. + Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. + - Do not crash when receiving a POSTDESCRIPTOR command with an + empty body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. + + o Minor bugfixes (coverity build support, backport from 0.3.1.5-alpha): + - Avoid Coverity build warnings related to our BUG() macro. By + default, Coverity treats BUG() as the Linux kernel does: an + instant abort(). We need to override that so our BUG() macro + doesn't prevent Coverity from analyzing functions that use it. + Fixes bug 23030; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (defensive programming, undefined behavior, backport from 0.3.1.4-alpha): + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + o Minor bugfixes (file limits, osx, backport from 0.3.1.5-alpha): + - When setting the maximum number of connections allowed by the OS, + always allow some extra file descriptors for other files. Fixes + bug 22797; bugfix on 0.2.0.10-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.5-alpha): + - Avoid a sandbox failure when trying to re-bind to a socket and + mark it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.4-alpha): + - Permit the fchmod system call, to avoid crashing on startup when + starting with the seccomp2 sandbox and an unexpected set of + permissions on the data directory or its contents. Fixes bug + 22516; bugfix on 0.2.5.4-alpha. + + o Minor bugfixes (relay, backport from 0.3.0.5-rc): + - Avoid a double-marked-circuit warning that could happen when we + receive DESTROY cells under heavy load. Fixes bug 20059; bugfix + on 0.1.0.1-rc. + + o Minor bugfixes (voting consistency, backport from 0.3.1.1-alpha): + - Reject version numbers with non-numeric prefixes (such as +, -, or + whitespace). Disallowing whitespace prevents differential version + parsing between POSIX-based and Windows platforms. Fixes bug 21507 + and part of 21508; bugfix on 0.0.8pre1. + + o Build features (backport from 0.3.1.5-alpha): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + +Changes in version 0.3.0.11 - 2017-09-18 + Tor 0.3.0.11 backports a collection of bugfixes from Tor the 0.3.1 + series. + + Most significantly, it includes a fix for TROVE-2017-008, a + security bug that affects hidden services running with the + SafeLogging option disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + o Minor features (code style, backport from 0.3.1.7): + - Add "Falls through" comments to our codebase, in order to silence + GCC 7's -Wimplicit-fallthrough warnings. Patch from Andreas + Stieger. Closes ticket 22446. + + o Minor features: + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (compilation, backport from 0.3.1.7): + - Avoid compiler warnings in the unit tests for calling tor_sscanf() + with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. + + o Minor bugfixes (controller, backport from 0.3.1.7): + - Do not crash when receiving a HSPOST command with an empty body. + Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. + - Do not crash when receiving a POSTDESCRIPTOR command with an empty + body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. + + o Minor bugfixes (file limits, osx, backport from 0.3.1.5-alpha): + - When setting the maximum number of connections allowed by the OS, + always allow some extra file descriptors for other files. Fixes + bug 22797; bugfix on 0.2.0.10-alpha. + + o Minor bugfixes (logging, relay, backport from 0.3.1.6-rc): + - Remove a forgotten debugging message when an introduction point + successfully establishes a hidden service prop224 circuit with + a client. + - Change three other log_warn() for an introduction point to + protocol warnings, because they can be failure from the network + and are not relevant to the operator. Fixes bug 23078; bugfix on + 0.3.0.1-alpha and 0.3.0.2-alpha. + + +Changes in version 0.3.1.7 - 2017-09-18 + Tor 0.3.1.7 is the first stable release in the 0.3.1 series. + + With the 0.3.1 series, Tor now serves and downloads directory + information in more compact formats, to save on bandwidth overhead. It + also contains a new padding system to resist netflow-based traffic + analysis, and experimental support for building parts of Tor in Rust + (though no parts of Tor are in Rust yet). There are also numerous + small features, bugfixes on earlier release series, and groundwork for + the hidden services revamp of 0.3.2. + + This release also includes a fix for TROVE-2017-008, a security bug + that affects hidden services running with the SafeLogging option + disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + Per our stable release policy, we plan to support each stable release + series for at least the next nine months, or for three months after + the first stable release of the next series: whichever is longer. If + you need a release with long-term support, we recommend that you stay + with the 0.2.9 series. + + Below is a list of the changes since 0.3.1.6-rc. For a list of all + changes since 0.3.0, see the ReleaseNotes file. + + o Major bugfixes (security, hidden services, loggging): + - Fix a bug where we could log uninitialized stack when a certain + hidden service error occurred while SafeLogging was disabled. + Fixes bug #23490; bugfix on 0.2.7.2-alpha. This is also tracked as + TROVE-2017-008 and CVE-2017-0380. + + o Minor features (defensive programming): + - Create a pair of consensus parameters, nf_pad_tor2web and + nf_pad_single_onion, to disable netflow padding in the consensus + for non-anonymous connections in case the overhead is high. Closes + ticket 17857. + + o Minor features (diagnostic): + - Add a stack trace to the bug warnings that can be logged when + trying to send an outgoing relay cell with n_chan == 0. Diagnostic + attempt for bug 23105. + + o Minor features (geoip): + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (compilation): + - Avoid compiler warnings in the unit tests for calling tor_sscanf() + with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. + + o Minor bugfixes (controller): + - Do not crash when receiving a HSPOST command with an empty body. + Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. + - Do not crash when receiving a POSTDESCRIPTOR command with an empty + body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. + + o Minor bugfixes (relay): + - Inform the geoip and rephist modules about all requests, even on + relays that are only fetching microdescriptors. Fixes a bug + related to 21585; bugfix on 0.3.0.1-alpha. + + o Minor bugfixes (unit tests): + - Fix a channelpadding unit test failure on slow systems by using + mocked time instead of actual time. Fixes bug 23077; bugfix + on 0.3.1.1-alpha. + + +Changes in version 0.3.1.6-rc - 2017-09-05 + Tor 0.3.1.6-rc fixes a few small bugs and annoyances in the 0.3.1 + release series, including a bug that produced weird behavior on + Windows directory caches. + + This is the first release candidate in the Tor 0.3.1 series. If we + find no new bugs or regressions here, the first stable 0.3.1 release + will be nearly identical to it. + + o Major bugfixes (windows, directory cache): + - On Windows, do not try to delete cached consensus documents and + diffs before they are unmapped from memory--Windows won't allow + that. Instead, allow the consensus cache directory to grow larger, + to hold files that might need to stay around longer. Fixes bug + 22752; bugfix on 0.3.1.1-alpha. + + o Minor features (directory authority): + - Improve the message that authorities report to relays that present + RSA/Ed25519 keypairs that conflict with previously pinned keys. + Closes ticket 22348. + + o Minor features (geoip): + - Update geoip and geoip6 to the August 3 2017 Maxmind GeoLite2 + Country database. + + o Minor features (testing): + - Add more tests for compression backend initialization. Closes + ticket 22286. + + o Minor bugfixes (directory cache): + - Fix a memory leak when recovering space in the consensus cache. + Fixes bug 23139; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (hidden service): + - Increase the number of circuits that a service is allowed to + open over a specific period of time. The value was lower than it + should be (8 vs 12) in the normal case of 3 introduction points. + Fixes bug 22159; bugfix on 0.3.0.5-rc. + - Fix a BUG warning during HSv3 descriptor decoding that could be + cause by a specially crafted descriptor. Fixes bug 23233; bugfix + on 0.3.0.1-alpha. Bug found by "haxxpop". + - Rate-limit the log messages if we exceed the maximum number of + allowed intro circuits. Fixes bug 22159; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (logging, relay): + - Remove a forgotten debugging message when an introduction point + successfully establishes a hidden service prop224 circuit with + a client. + - Change three other log_warn() for an introduction point to + protocol warnings, because they can be failure from the network + and are not relevant to the operator. Fixes bug 23078; bugfix on + 0.3.0.1-alpha and 0.3.0.2-alpha. + + o Minor bugfixes (relay): + - When a relay is not running as a directory cache, it will no + longer generate compressed consensuses and consensus diff + information. Previously, this was a waste of disk and CPU. Fixes + bug 23275; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (robustness, error handling): + - Improve our handling of the cases where OpenSSL encounters a + memory error while encoding keys and certificates. We haven't + observed these errors in the wild, but if they do happen, we now + detect and respond better. Fixes bug 19418; bugfix on all versions + of Tor. Reported by Guido Vranken. + + o Minor bugfixes (stability): + - Avoid crashing on a double-free when unable to load or process an + included file. Fixes bug 23155; bugfix on 0.3.1.1-alpha. Found + with the clang static analyzer. + + o Minor bugfixes (testing): + - Fix an undersized buffer in test-memwipe.c. Fixes bug 23291; + bugfix on 0.2.7.2-alpha. Found and patched by Ties Stuij. + - Port the hs_ntor handshake test to work correctly with recent + versions of the pysha3 module. Fixes bug 23071; bugfix + on 0.3.1.1-alpha. + + o Minor bugfixes (Windows service): + - When running as a Windows service, set the ID of the main thread + correctly. Failure to do so made us fail to send log messages to + the controller in 0.2.1.16-rc, slowed down controller event + delivery in 0.2.7.3-rc and later, and crash with an assertion + failure in 0.3.1.1-alpha. Fixes bug 23081; bugfix on 0.2.1.6-alpha. + Patch and diagnosis from "Vort". + + +Changes in version 0.3.0.10 - 2017-08-02 + Tor 0.3.0.10 backports a collection of small-to-medium bugfixes + from the current Tor alpha series. OpenBSD users and TPROXY users + should upgrade; others are probably okay sticking with 0.3.0.9. + + o Major features (build system, continuous integration, backport from 0.3.1.5-alpha): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + o Major bugfixes (linux TPROXY support, backport from 0.3.1.1-alpha): + - Fix a typo that had prevented TPROXY-based transparent proxying + from working under Linux. Fixes bug 18100; bugfix on 0.2.6.3-alpha. + Patch from "d4fq0fQAgoJ". + + o Major bugfixes (openbsd, denial-of-service, backport from 0.3.1.5-alpha): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xbar" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Minor features (backport from 0.3.1.5-alpha): + - Update geoip and geoip6 to the July 4 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (bandwidth accounting, backport from 0.3.1.2-alpha): + - Roll over monthly accounting at the configured hour and minute, + rather than always at 00:00. Fixes bug 22245; bugfix on 0.0.9rc1. + Found by Andrey Karpov with PVS-Studio. + + o Minor bugfixes (compilation warnings, backport from 0.3.1.5-alpha): + - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug 22915; + bugfix on 0.2.8.1-alpha. + - Fix warnings when building with libscrypt and openssl scrypt + support on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. + - When building with certain versions of the mingw C header files, + avoid float-conversion warnings when calling the C functions + isfinite(), isnan(), and signbit(). Fixes bug 22801; bugfix + on 0.2.8.1-alpha. + + o Minor bugfixes (compilation, mingw, backport from 0.3.1.1-alpha): + - Backport a fix for an "unused variable" warning that appeared + in some versions of mingw. Fixes bug 22838; bugfix on + 0.2.8.1-alpha. + + o Minor bugfixes (coverity build support, backport from 0.3.1.5-alpha): + - Avoid Coverity build warnings related to our BUG() macro. By + default, Coverity treats BUG() as the Linux kernel does: an + instant abort(). We need to override that so our BUG() macro + doesn't prevent Coverity from analyzing functions that use it. + Fixes bug 23030; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (directory authority, backport from 0.3.1.1-alpha): + - When rejecting a router descriptor for running an obsolete version + of Tor without ntor support, warn about the obsolete tor version, + not the missing ntor key. Fixes bug 20270; bugfix on 0.2.9.3-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.5-alpha): + - Avoid a sandbox failure when trying to re-bind to a socket and + mark it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. + + o Minor bugfixes (unit tests, backport from 0.3.1.5-alpha) + - Fix a memory leak in the link-handshake/certs_ok_ed25519 test. + Fixes bug 22803; bugfix on 0.3.0.1-alpha. + + +Changes in version 0.3.1.5-alpha - 2017-08-01 + Tor 0.3.1.5-alpha improves the performance of consensus diff + calculation, fixes a crash bug on older versions of OpenBSD, and fixes + several other bugs. If no serious bugs are found in this version, the + next version will be a release candidate. + + This release also marks the end of support for the Tor 0.2.4.x, + 0.2.6.x, and 0.2.7.x release series. Those releases will receive no + further bug or security fixes. Anyone still running or distributing + one of those versions should upgrade. + + o Major features (build system, continuous integration): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + o Major bugfixes (openbsd, denial-of-service): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xbar" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Major bugfixes (relay, performance): + - Perform circuit handshake operations at a higher priority than we + use for consensus diff creation and compression. This should + prevent circuits from starving when a relay or bridge receives a + new consensus, especially on lower-powered machines. Fixes bug + 22883; bugfix on 0.3.1.1-alpha. + + o Minor features (bridge authority): + - Add "fingerprint" lines to the networkstatus-bridges file produced + by bridge authorities. Closes ticket 22207. + + o Minor features (directory cache, consensus diff): + - Add a new MaxConsensusAgeForDiffs option to allow directory cache + operators with low-resource environments to adjust the number of + consensuses they'll store and generate diffs from. Most cache + operators should leave it unchanged. Helps to work around + bug 22883. + + o Minor features (geoip): + - Update geoip and geoip6 to the July 4 2017 Maxmind GeoLite2 + Country database. + + o Minor features (relay, performance): + - Always start relays with at least two worker threads, to prevent + priority inversion on slow tasks. Part of the fix for bug 22883. + - Allow background work to be queued with different priorities, so + that a big pile of slow low-priority jobs will not starve out + higher priority jobs. This lays the groundwork for a fix for + bug 22883. + + o Minor bugfixes (build system, rust): + - Fix a problem where Rust toolchains were not being found when + building without --enable-cargo-online-mode, due to setting the + $HOME environment variable instead of $CARGO_HOME. Fixes bug + 22830; bugfix on 0.3.1.1-alpha. Fix by Chelsea Komlo. + + o Minor bugfixes (compatibility, zstd): + - Write zstd epilogues correctly when the epilogue requires + reallocation of the output buffer, even with zstd 1.3.0. + (Previously, we worked on 1.2.0 and failed with 1.3.0). Fixes bug + 22927; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (compilation warnings): + - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug + 22915; bugfix on 0.2.8.1-alpha. + - Fix warnings when building with libscrypt and openssl scrypt + support on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. + - Compile correctly when both openssl 1.1.0 and libscrypt are + detected. Previously this would cause an error. Fixes bug 22892; + bugfix on 0.3.1.1-alpha. + - When building with certain versions of the mingw C header files, + avoid float-conversion warnings when calling the C functions + isfinite(), isnan(), and signbit(). Fixes bug 22801; bugfix + on 0.2.8.1-alpha. + + o Minor bugfixes (coverity build support): + - Avoid Coverity build warnings related to our BUG() macro. By + default, Coverity treats BUG() as the Linux kernel does: an + instant abort(). We need to override that so our BUG() macro + doesn't prevent Coverity from analyzing functions that use it. + Fixes bug 23030; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (directory authority): + - When a directory authority rejects a descriptor or extrainfo with + a given digest, mark that digest as undownloadable, so that we do + not attempt to download it again over and over. We previously + tried to avoid downloading such descriptors by other means, but we + didn't notice if we accidentally downloaded one anyway. This + behavior became problematic in 0.2.7.2-alpha, when authorities + began pinning Ed25519 keys. Fixes bug 22349; bugfix + on 0.2.1.19-alpha. + + o Minor bugfixes (error reporting, windows): + - When formatting Windows error messages, use the English format to + avoid codepage issues. Fixes bug 22520; bugfix on 0.1.2.8-alpha. + Patch from "Vort". + + o Minor bugfixes (file limits, osx): + - When setting the maximum number of connections allowed by the OS, + always allow some extra file descriptors for other files. Fixes + bug 22797; bugfix on 0.2.0.10-alpha. + + o Minor bugfixes (linux seccomp2 sandbox): + - Avoid a sandbox failure when trying to re-bind to a socket and + mark it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. + + o Minor bugfixes (memory leaks): + - Fix a small memory leak when validating a configuration that uses + two or more AF_UNIX sockets for the same port type. Fixes bug + 23053; bugfix on 0.2.6.3-alpha. This is CID 1415725. + + o Minor bugfixes (unit tests): + - test_consdiff_base64cmp would fail on OS X because while OS X + follows the standard of (less than zero/zero/greater than zero), + it doesn't follow the convention of (-1/0/+1). Make the test + comply with the standard. Fixes bug 22870; bugfix on 0.3.1.1-alpha. + - Fix a memory leak in the link-handshake/certs_ok_ed25519 test. + Fixes bug 22803; bugfix on 0.3.0.1-alpha. + + +Changes in version 0.3.1.4-alpha - 2017-06-29 + Tor 0.3.1.4-alpha fixes a path selection bug that would allow a client + to use a guard that was in the same network family as a chosen exit + relay. This is a security regression; all clients running earlier + versions of 0.3.0.x or 0.3.1.x should upgrade to 0.3.0.9 + or 0.3.1.4-alpha. + + This release also fixes several other bugs introduced in 0.3.0.x + and 0.3.1.x, including others that can affect bandwidth usage + and correctness. + + o New dependencies: + - To build with zstd and lzma support, Tor now requires the + pkg-config tool at build time. (This requirement was new in + 0.3.1.1-alpha, but was not noted at the time. Noting it here to + close ticket 22623.) + + o Major bugfixes (path selection, security): + - When choosing which guard to use for a circuit, avoid the exit's + family along with the exit itself. Previously, the new guard + selection logic avoided the exit, but did not consider its family. + Fixes bug 22753; bugfix on 0.3.0.1-alpha. Tracked as TROVE-2017- + 006 and CVE-2017-0377. + + o Major bugfixes (compression, zstd): + - Correctly detect a full buffer when decompressing a large zstd- + compressed input. Previously, we would sometimes treat a full + buffer as an error. Fixes bug 22628; bugfix on 0.3.1.1-alpha. + + o Major bugfixes (directory protocol): + - Ensure that we send "304 Not modified" as HTTP status code when a + client is attempting to fetch a consensus or consensus diff, and + the best one we can send them is one they already have. Fixes bug + 22702; bugfix on 0.3.1.1-alpha. + + o Major bugfixes (entry guards): + - When starting with an old consensus, do not add new entry guards + unless the consensus is "reasonably live" (under 1 day old). Fixes + one root cause of bug 22400; bugfix on 0.3.0.1-alpha. + + o Minor features (bug mitigation, diagnostics, logging): + - Avoid an assertion failure, and log a better error message, when + unable to remove a file from the consensus cache on Windows. + Attempts to mitigate and diagnose bug 22752. + + o Minor features (geoip): + - Update geoip and geoip6 to the June 8 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (compression): + - When compressing or decompressing a buffer, check for a failure to + create a compression object. Fixes bug 22626; bugfix + on 0.3.1.1-alpha. + - When decompressing a buffer, check for extra data after the end of + the compressed data. Fixes bug 22629; bugfix on 0.3.1.1-alpha. + - When decompressing an object received over an anonymous directory + connection, if we have already decompressed it using an acceptable + compression method, do not reject it for looking like an + unacceptable compression method. Fixes part of bug 22670; bugfix + on 0.3.1.1-alpha. + - When serving directory votes compressed with zlib, do not claim to + have compressed them with zstd. Fixes bug 22669; bugfix + on 0.3.1.1-alpha. + - When spooling compressed data to an output buffer, don't try to + spool more data when there is no more data to spool and we are not + trying to flush the input. Previously, we would sometimes launch + compression requests with nothing to do, which interferes with our + 22672 checks. Fixes bug 22719; bugfix on 0.2.0.16-alpha. + + o Minor bugfixes (defensive programming): + - Detect and break out of infinite loops in our compression code. We + don't think that any such loops exist now, but it's best to be + safe. Closes ticket 22672. + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + o Minor bugfixes (linux seccomp2 sandbox): + - Permit the fchmod system call, to avoid crashing on startup when + starting with the seccomp2 sandbox and an unexpected set of + permissions on the data directory or its contents. Fixes bug + 22516; bugfix on 0.2.5.4-alpha. + - Fix a crash in the LZMA module, when the sandbox was enabled, and + liblzma would allocate more than 16 MB of memory. We solve this by + bumping the mprotect() limit in the sandbox module from 16 MB to + 20 MB. Fixes bug 22751; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (logging): + - When decompressing, do not warn if we fail to decompress using a + compression method that we merely guessed. Fixes part of bug + 22670; bugfix on 0.1.1.14-alpha. + - When decompressing, treat mismatch between content-encoding and + actual compression type as a protocol warning. Fixes part of bug + 22670; bugfix on 0.1.1.9-alpha. + - Downgrade "assigned_to_cpuworker failed" message to info-level + severity. In every case that can reach it, either a better warning + has already been logged, or no warning is warranted. Fixes bug + 22356; bugfix on 0.2.6.3-alpha. + - Demote a warn that was caused by libevent delays to info if + netflow padding is less than 4.5 seconds late, or to notice + if it is more (4.5 seconds is the amount of time that a netflow + record might be emitted after, if we chose the maximum timeout). + Fixes bug 22212; bugfix on 0.3.1.1-alpha. + + o Minor bugfixes (process behavior): + - When exiting because of an error, always exit with a nonzero exit + status. Previously, we would fail to report an error in our exit + status in cases related to __OwningControllerProcess failure, + lockfile contention, and Ed25519 key initialization. Fixes bug + 22720; bugfix on versions 0.2.1.6-alpha, 0.2.2.28-beta, and + 0.2.7.2-alpha respectively. Reported by "f55jwk4f"; patch + from "huyvq". + + o Documentation: + - Add a manpage description for the key-pinning-journal file. Closes + ticket 22347. + - Correctly note that bandwidth accounting values are stored in the + state file, and the bw_accounting file is now obsolete. Closes + ticket 16082. + - Document more of the files in the Tor data directory, including + cached-extrainfo, secret_onion_key{,_ntor}.old, hidserv-stats, + approved-routers, sr-random, and diff-cache. Found while fixing + ticket 22347. + + +Changes in version 0.3.0.9 - 2017-06-29 + Tor 0.3.0.9 fixes a path selection bug that would allow a client + to use a guard that was in the same network family as a chosen exit + relay. This is a security regression; all clients running earlier + versions of 0.3.0.x or 0.3.1.x should upgrade to 0.3.0.9 or + 0.3.1.4-alpha. + + This release also backports several other bugfixes from the 0.3.1.x + series. + + o Major bugfixes (path selection, security, backport from 0.3.1.4-alpha): + - When choosing which guard to use for a circuit, avoid the exit's + family along with the exit itself. Previously, the new guard + selection logic avoided the exit, but did not consider its family. + Fixes bug 22753; bugfix on 0.3.0.1-alpha. Tracked as TROVE-2017- + 006 and CVE-2017-0377. + + o Major bugfixes (entry guards, backport from 0.3.1.1-alpha): + - Don't block bootstrapping when a primary bridge is offline and we + can't get its descriptor. Fixes bug 22325; fixes one case of bug + 21969; bugfix on 0.3.0.3-alpha. + + o Major bugfixes (entry guards, backport from 0.3.1.4-alpha): + - When starting with an old consensus, do not add new entry guards + unless the consensus is "reasonably live" (under 1 day old). Fixes + one root cause of bug 22400; bugfix on 0.3.0.1-alpha. + + o Minor features (geoip): + - Update geoip and geoip6 to the June 8 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (voting consistency, backport from 0.3.1.1-alpha): + - Reject version numbers with non-numeric prefixes (such as +, -, or + whitespace). Disallowing whitespace prevents differential version + parsing between POSIX-based and Windows platforms. Fixes bug 21507 + and part of 21508; bugfix on 0.0.8pre1. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.4-alpha): + - Permit the fchmod system call, to avoid crashing on startup when + starting with the seccomp2 sandbox and an unexpected set of + permissions on the data directory or its contents. Fixes bug + 22516; bugfix on 0.2.5.4-alpha. + + o Minor bugfixes (defensive programming, backport from 0.3.1.4-alpha): + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + Changes in version 0.3.1.3-alpha - 2017-06-08 Tor 0.3.1.3-alpha fixes a pair of bugs that would allow an attacker to remotely crash a hidden service with an assertion failure. Anyone @@ -1996,7 +3275,7 @@ Changes in version 0.3.0.1-alpha - 2016-12-19 subprotocol-versions mechanism, so that clients can tell which relays can identity themselves by Ed25519 ID. Closes ticket 20552. - o Minor features (fingerprinting resistence, authentication): + o Minor features (fingerprinting resistance, authentication): - Extend the length of RSA keys used for TLS link authentication to 2048 bits. (These weren't used for forward secrecy; for forward secrecy, we used P256.) Closes ticket 13752. @@ -5062,7 +6341,7 @@ Changes in version 0.2.6.8 - 2015-05-21 o Major bugfixes (hidden services, backport from 0.2.7.1-alpha): - Revert commit that made directory authorities assign the HSDir - flag to relay without a DirPort; this was bad because such relays + flag to relays without a DirPort; this was bad because such relays can't handle BEGIN_DIR cells. Fixes bug 15850; bugfix on 0.2.6.3-alpha. @@ -5103,7 +6382,7 @@ Changes in version 0.2.7.1-alpha - 2015-05-12 o Major bugfixes (hidden services): - Revert commit that made directory authorities assign the HSDir - flag to relay without a DirPort; this was bad because such relays + flag to relays without a DirPort; this was bad because such relays can't handle BEGIN_DIR cells. Fixes bug 15850; bugfix on 0.2.6.3-alpha. diff --git a/Makefile.am b/Makefile.am index 79d2e78521..ad2ceb66a9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,7 +26,7 @@ TESTING_TOR_BINARY=$(top_builddir)/src/or/tor$(EXEEXT) endif if USE_RUST -rust_ldadd=$(top_builddir)/src/rust/target/release/libtor_util.a +rust_ldadd=$(top_builddir)/src/rust/target/release/@TOR_RUST_UTIL_STATIC_NAME@ else rust_ldadd= endif @@ -96,7 +96,7 @@ doxygen: test: all $(top_builddir)/src/test/test -check-local: check-spaces +check-local: check-spaces check-changes need-chutney-path: @if test ! -d "$$CHUTNEY_PATH"; then \ @@ -216,14 +216,20 @@ check-logs: .PHONY: check-changes check-changes: +if USEPYTHON @if test -d "$(top_srcdir)/changes"; then \ - $(PYTHON) $(top_srcdir)/scripts/maint/lintChanges.py $(top_srcdir)/changes/*; \ + $(PYTHON) $(top_srcdir)/scripts/maint/lintChanges.py $(top_srcdir)/changes; \ fi +endif .PHONY: update-versions update-versions: $(PERL) $(top_builddir)/scripts/maint/updateVersions.pl +.PHONY: callgraph +callgraph: + $(top_builddir)/scripts/maint/run_calltool.sh + version: @echo "Tor @VERSION@" @if test -d "$(top_srcdir)/.git" && test -x "`which git 2>&1;true`"; then \ @@ -27,3 +27,6 @@ Frequently Asked Questions: To get started working on Tor development: See the doc/HACKING directory. + +Release timeline: + https://trac.torproject.org/projects/tor/wiki/org/teams/NetworkTeam/CoreTorReleases diff --git a/ReleaseNotes b/ReleaseNotes index 1e56ffaf89..07a3881acb 100644 --- a/ReleaseNotes +++ b/ReleaseNotes @@ -2,6 +2,1010 @@ This document summarizes new features and bugfixes in each stable release of Tor. If you want to see more detailed descriptions of the changes in each development snapshot, see the ChangeLog file. +Changes in version 0.2.8.15 - 2017-09-18 + Tor 0.2.8.15 backports a collection of bugfixes from later + Tor series. + + Most significantly, it includes a fix for TROVE-2017-008, a + security bug that affects hidden services running with the + SafeLogging option disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + Note that Tor 0.2.8.x will no longer be supported after 1 Jan + 2018. We suggest that you upgrade to the latest stable release if + possible. If you can't, we recommend that you upgrade at least to + 0.2.9, which will be supported until 2020. + + o Major bugfixes (openbsd, denial-of-service, backport from 0.3.1.5-alpha): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xx" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Minor features: + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (compilation, mingw, backport from 0.3.1.1-alpha): + - Backport a fix for an "unused variable" warning that appeared + in some versions of mingw. Fixes bug 22838; bugfix on + 0.2.8.1-alpha. + + o Minor bugfixes (defensive programming, undefined behavior, backport from 0.3.1.4-alpha): + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + o Build features (backport from 0.3.1.5-alpha): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + +Changes in version 0.2.9.12 - 2017-09-18 + Tor 0.2.9.12 backports a collection of bugfixes from later + Tor series. + + Most significantly, it includes a fix for TROVE-2017-008, a + security bug that affects hidden services running with the + SafeLogging option disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + o Major features (security, backport from 0.3.0.2-alpha): + - Change the algorithm used to decide DNS TTLs on client and server + side, to better resist DNS-based correlation attacks like the + DefecTor attack of Greschbach, Pulls, Roberts, Winter, and + Feamster. Now relays only return one of two possible DNS TTL + values, and clients are willing to believe DNS TTL values up to 3 + hours long. Closes ticket 19769. + + o Major bugfixes (crash, directory connections, backport from 0.3.0.5-rc): + - Fix a rare crash when sending a begin cell on a circuit whose + linked directory connection had already been closed. Fixes bug + 21576; bugfix on 0.2.9.3-alpha. Reported by Alec Muffett. + + o Major bugfixes (DNS, backport from 0.3.0.2-alpha): + - Fix a bug that prevented exit nodes from caching DNS records for + more than 60 seconds. Fixes bug 19025; bugfix on 0.2.4.7-alpha. + + o Major bugfixes (linux TPROXY support, backport from 0.3.1.1-alpha): + - Fix a typo that had prevented TPROXY-based transparent proxying + from working under Linux. Fixes bug 18100; bugfix on 0.2.6.3-alpha. + Patch from "d4fq0fQAgoJ". + + o Major bugfixes (openbsd, denial-of-service, backport from 0.3.1.5-alpha): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xx" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Minor features (code style, backport from 0.3.1.3-alpha): + - Add "Falls through" comments to our codebase, in order to silence + GCC 7's -Wimplicit-fallthrough warnings. Patch from Andreas + Stieger. Closes ticket 22446. + + o Minor features (geoip): + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (bandwidth accounting, backport from 0.3.1.1-alpha): + - Roll over monthly accounting at the configured hour and minute, + rather than always at 00:00. Fixes bug 22245; bugfix on 0.0.9rc1. + Found by Andrey Karpov with PVS-Studio. + + o Minor bugfixes (compilation, backport from 0.3.1.5-alpha): + - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug 22915; + bugfix on 0.2.8.1-alpha. + - Fix warnings when building with libscrypt and openssl scrypt support + on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. + - When building with certain versions the mingw C header files, avoid + float-conversion warnings when calling the C functions isfinite(), + isnan(), and signbit(). Fixes bug 22801; bugfix on 0.2.8.1-alpha. + + o Minor bugfixes (compilation, backport from 0.3.1.7): + - Avoid compiler warnings in the unit tests for running tor_sscanf() + with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. + + o Minor bugfixes (compilation, mingw, backport from 0.3.1.1-alpha): + - Backport a fix for an "unused variable" warning that appeared + in some versions of mingw. Fixes bug 22838; bugfix on + 0.2.8.1-alpha. + + o Minor bugfixes (controller, backport from 0.3.1.7): + - Do not crash when receiving a HSPOST command with an empty body. + Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. + - Do not crash when receiving a POSTDESCRIPTOR command with an + empty body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. + + o Minor bugfixes (coverity build support, backport from 0.3.1.5-alpha): + - Avoid Coverity build warnings related to our BUG() macro. By + default, Coverity treats BUG() as the Linux kernel does: an + instant abort(). We need to override that so our BUG() macro + doesn't prevent Coverity from analyzing functions that use it. + Fixes bug 23030; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (defensive programming, undefined behavior, backport from 0.3.1.4-alpha): + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + o Minor bugfixes (file limits, osx, backport from 0.3.1.5-alpha): + - When setting the maximum number of connections allowed by the OS, + always allow some extra file descriptors for other files. Fixes + bug 22797; bugfix on 0.2.0.10-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.5-alpha): + - Avoid a sandbox failure when trying to re-bind to a socket and + mark it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.4-alpha): + - Permit the fchmod system call, to avoid crashing on startup when + starting with the seccomp2 sandbox and an unexpected set of + permissions on the data directory or its contents. Fixes bug + 22516; bugfix on 0.2.5.4-alpha. + + o Minor bugfixes (relay, backport from 0.3.0.5-rc): + - Avoid a double-marked-circuit warning that could happen when we + receive DESTROY cells under heavy load. Fixes bug 20059; bugfix + on 0.1.0.1-rc. + + o Minor bugfixes (voting consistency, backport from 0.3.1.1-alpha): + - Reject version numbers with non-numeric prefixes (such as +, -, or + whitespace). Disallowing whitespace prevents differential version + parsing between POSIX-based and Windows platforms. Fixes bug 21507 + and part of 21508; bugfix on 0.0.8pre1. + + o Build features (backport from 0.3.1.5-alpha): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + +Changes in version 0.3.0.11 - 2017-09-18 + Tor 0.3.0.11 backports a collection of bugfixes from Tor the 0.3.1 + series. + + Most significantly, it includes a fix for TROVE-2017-008, a + security bug that affects hidden services running with the + SafeLogging option disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + o Minor features (code style, backport from 0.3.1.7): + - Add "Falls through" comments to our codebase, in order to silence + GCC 7's -Wimplicit-fallthrough warnings. Patch from Andreas + Stieger. Closes ticket 22446. + + o Minor features: + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (compilation, backport from 0.3.1.7): + - Avoid compiler warnings in the unit tests for calling tor_sscanf() + with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. + + o Minor bugfixes (controller, backport from 0.3.1.7): + - Do not crash when receiving a HSPOST command with an empty body. + Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. + - Do not crash when receiving a POSTDESCRIPTOR command with an empty + body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. + + o Minor bugfixes (file limits, osx, backport from 0.3.1.5-alpha): + - When setting the maximum number of connections allowed by the OS, + always allow some extra file descriptors for other files. Fixes + bug 22797; bugfix on 0.2.0.10-alpha. + + o Minor bugfixes (logging, relay, backport from 0.3.1.6-rc): + - Remove a forgotten debugging message when an introduction point + successfully establishes a hidden service prop224 circuit with + a client. + - Change three other log_warn() for an introduction point to + protocol warnings, because they can be failure from the network + and are not relevant to the operator. Fixes bug 23078; bugfix on + 0.3.0.1-alpha and 0.3.0.2-alpha. + + +Changes in version 0.3.1.7 - 2017-09-18 + Tor 0.3.1.7 is the first stable release in the 0.3.1 series. + + With the 0.3.1 series, Tor now serves and downloads directory + information in more compact formats, to save on bandwidth overhead. It + also contains a new padding system to resist netflow-based traffic + analysis, and experimental support for building parts of Tor in Rust + (though no parts of Tor are in Rust yet). There are also numerous + small features, bugfixes on earlier release series, and groundwork for + the hidden services revamp of 0.3.2. + + This release also includes a fix for TROVE-2017-008, a security bug + that affects hidden services running with the SafeLogging option + disabled. For more information, see + https://trac.torproject.org/projects/tor/ticket/23490 + + Per our stable release policy, we plan to support each stable release + series for at least the next nine months, or for three months after + the first stable release of the next series: whichever is longer. If + you need a release with long-term support, we recommend that you stay + with the 0.2.9 series. + + Below is a list of the changes since 0.3.0. For a list of all + changes since 0.3.1.6-rc, see the ChangeLog file. + + o New dependencies: + - To build with zstd and lzma support, Tor now requires the + pkg-config tool at build time. + + o Major bugfixes (security, hidden services, loggging): + - Fix a bug where we could log uninitialized stack when a certain + hidden service error occurred while SafeLogging was disabled. + Fixes bug #23490; bugfix on 0.2.7.2-alpha. + This is also tracked as TROVE-2017-008 and CVE-2017-0380. + + o Major features (build system, continuous integration): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + o Major features (directory protocol): + - Tor relays and authorities can now serve clients an abbreviated + version of the consensus document, containing only the changes + since an older consensus document that the client holds. Clients + now request these documents when available. When both client and + server use this new protocol, they will use far less bandwidth (up + to 94% less) to keep the client's consensus up-to-date. Implements + proposal 140; closes ticket 13339. Based on work by Daniel MartÃ. + - Tor can now compress directory traffic with lzma or with zstd + compression algorithms, which can deliver better bandwidth + performance. Because lzma is computationally expensive, it's only + used for documents that can be compressed once and served many + times. Support for these algorithms requires that tor is built + with the libzstd and/or liblzma libraries available. Implements + proposal 278; closes ticket 21662. + - Relays now perform the more expensive compression operations, and + consensus diff generation, in worker threads. This separation + avoids delaying the main thread when a new consensus arrives. + + o Major features (experimental): + - Tor can now build modules written in Rust. To turn this on, pass + the "--enable-rust" flag to the configure script. It's not time to + get excited yet: currently, there is no actual Rust functionality + beyond some simple glue code, and a notice at startup to tell you + that Rust is running. Still, we hope that programmers and + packagers will try building Tor with Rust support, so that we can + find issues and solve portability problems. Closes ticket 22106. + + o Major features (traffic analysis resistance): + - Connections between clients and relays now send a padding cell in + each direction every 1.5 to 9.5 seconds (tunable via consensus + parameters). This padding will not resist specialized + eavesdroppers, but it should be enough to make many ISPs' routine + network flow logging less useful in traffic analysis against + Tor users. + + Padding is negotiated using Tor's link protocol, so both relays + and clients must upgrade for this to take effect. Clients may + still send padding despite the relay's version by setting + ConnectionPadding 1 in torrc, and may disable padding by setting + ConnectionPadding 0 in torrc. Padding may be minimized for mobile + users with the torrc option ReducedConnectionPadding. Implements + Proposal 251 and Section 2 of Proposal 254; closes ticket 16861. + - Relays will publish 24 hour totals of padding and non-padding cell + counts to their extra-info descriptors, unless PaddingStatistics 0 + is set in torrc. These 24 hour totals are also rounded to + multiples of 10000. + + o Major bugfixes (hidden service, relay, security): + - Fix a remotely triggerable assertion failure when a hidden service + handles a malformed BEGIN cell. Fixes bug 22493, tracked as + TROVE-2017-004 and as CVE-2017-0375; bugfix on 0.3.0.1-alpha. + - Fix a remotely triggerable assertion failure caused by receiving a + BEGIN_DIR cell on a hidden service rendezvous circuit. Fixes bug + 22494, tracked as TROVE-2017-005 and CVE-2017-0376; bugfix + on 0.2.2.1-alpha. + + o Major bugfixes (path selection, security): + - When choosing which guard to use for a circuit, avoid the exit's + family along with the exit itself. Previously, the new guard + selection logic avoided the exit, but did not consider its family. + Fixes bug 22753; bugfix on 0.3.0.1-alpha. Tracked as TROVE-2017- + 006 and CVE-2017-0377. + + o Major bugfixes (connection usage): + - We use NETINFO cells to try to determine if both relays involved + in a connection will agree on the canonical status of that + connection. We prefer the connections where this is the case for + extend cells, and try to close connections where relays disagree + on their canonical status early. Also, we now prefer the oldest + valid connection for extend cells. These two changes should reduce + the number of long-term connections that are kept open between + relays. Fixes bug 17604; bugfix on 0.2.5.5-alpha. + - Relays now log hourly statistics (look for + "channel_check_for_duplicates" lines) on the total number of + connections to other relays. If the number of connections per + relay is unexpectedly large, this log message is at notice level. + Otherwise it is at info. + + o Major bugfixes (entry guards): + - When starting with an old consensus, do not add new entry guards + unless the consensus is "reasonably live" (under 1 day old). Fixes + one root cause of bug 22400; bugfix on 0.3.0.1-alpha. + - Don't block bootstrapping when a primary bridge is offline and we + can't get its descriptor. Fixes bug 22325; fixes one case of bug + 21969; bugfix on 0.3.0.3-alpha. + + o Major bugfixes (linux TPROXY support): + - Fix a typo that had prevented TPROXY-based transparent proxying + from working under Linux. Fixes bug 18100; bugfix on 0.2.6.3-alpha. + Patch from "d4fq0fQAgoJ". + + o Major bugfixes (openbsd, denial-of-service): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xx" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Major bugfixes (relay, link handshake): + - When performing the v3 link handshake on a TLS connection, report + that we have the x509 certificate that we actually used on that + connection, even if we have changed certificates since that + connection was first opened. Previously, we would claim to have + used our most recent x509 link certificate, which would sometimes + make the link handshake fail. Fixes one case of bug 22460; bugfix + on 0.2.3.6-alpha. + + o Major bugfixes (relays, key management): + - Regenerate link and authentication certificates whenever the key + that signs them changes; also, regenerate link certificates + whenever the signed key changes. Previously, these processes were + only weakly coupled, and we relays could (for minutes to hours) + wind up with an inconsistent set of keys and certificates, which + other relays would not accept. Fixes two cases of bug 22460; + bugfix on 0.3.0.1-alpha. + - When sending an Ed25519 signing->link certificate in a CERTS cell, + send the certificate that matches the x509 certificate that we + used on the TLS connection. Previously, there was a race condition + if the TLS context rotated after we began the TLS handshake but + before we sent the CERTS cell. Fixes a case of bug 22460; bugfix + on 0.3.0.1-alpha. + + o Minor features (security, windows): + - Enable a couple of pieces of Windows hardening: one + (HeapEnableTerminationOnCorruption) that has been on-by-default + since Windows 8, and unavailable before Windows 7; and one + (PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION) which we believe doesn't + affect us, but shouldn't do any harm. Closes ticket 21953. + + o Minor features (bridge authority): + - Add "fingerprint" lines to the networkstatus-bridges file produced + by bridge authorities. Closes ticket 22207. + + o Minor features (code style): + - Add "Falls through" comments to our codebase, in order to silence + GCC 7's -Wimplicit-fallthrough warnings. Patch from Andreas + Stieger. Closes ticket 22446. + + o Minor features (config options): + - Allow "%include" directives in torrc configuration files. These + directives import the settings from other files, or from all the + files in a directory. Closes ticket 1922. Code by Daniel Pinto. + - Make SAVECONF return an error when overwriting a torrc that has + includes. Using SAVECONF with the FORCE option will allow it to + overwrite torrc even if includes are used. Related to ticket 1922. + - Add "GETINFO config-can-saveconf" to tell controllers if SAVECONF + will work without the FORCE option. Related to ticket 1922. + + o Minor features (controller): + - Warn the first time that a controller requests data in the long- + deprecated 'GETINFO network-status' format. Closes ticket 21703. + + o Minor features (defaults): + - The default value for UseCreateFast is now 0: clients which + haven't yet received a consensus document will now use a proper + ntor handshake to talk to their directory servers whenever they + can. Closes ticket 21407. + - Onion key rotation and expiry intervals are now defined as a + network consensus parameter, per proposal 274. The default + lifetime of an onion key is increased from 7 to 28 days. Old onion + keys will expire after 7 days by default. This change will make + consensus diffs much smaller, and save significant bandwidth. + Closes ticket 21641. + + o Minor features (defensive programming): + - Create a pair of consensus parameters, nf_pad_tor2web and + nf_pad_single_onion, to disable netflow padding in the consensus + for non-anonymous connections in case the overhead is high. Closes + ticket 17857. + + o Minor features (diagnostic): + - Add a stack trace to the bug warnings that can be logged when + trying to send an outgoing relay cell with n_chan == 0. Diagnostic + attempt for bug 23105. + - Add logging messages to try to diagnose a rare bug that seems to + generate RSA->Ed25519 cross-certificates dated in the 1970s. We + think this is happening because of incorrect system clocks, but + we'd like to know for certain. Diagnostic for bug 22466. + - Avoid an assertion failure, and log a better error message, when + unable to remove a file from the consensus cache on Windows. + Attempts to mitigate and diagnose bug 22752. + + o Minor features (directory authority): + - Improve the message that authorities report to relays that present + RSA/Ed25519 keypairs that conflict with previously pinned keys. + Closes ticket 22348. + + o Minor features (directory cache, consensus diff): + - Add a new MaxConsensusAgeForDiffs option to allow directory cache + operators with low-resource environments to adjust the number of + consensuses they'll store and generate diffs from. Most cache + operators should leave it unchanged. Helps to work around + bug 22883. + + o Minor features (fallback directory list): + - Update the fallback directory mirror whitelist and blacklist based + on operator emails. Closes task 21121. + - Replace the 177 fallbacks originally introduced in Tor 0.2.9.8 in + December 2016 (of which ~126 were still functional) with a list of + 151 fallbacks (32 new, 119 unchanged, 58 removed) generated in May + 2017. Resolves ticket 21564. + + o Minor features (geoip): + - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 + Country database. + + o Minor features (hidden services, logging): + - Log a message when a hidden service descriptor has fewer + introduction points than specified in + HiddenServiceNumIntroductionPoints. Closes tickets 21598. + - Log a message when a hidden service reaches its introduction point + circuit limit, and when that limit is reset. Follow up to ticket + 21594; closes ticket 21622. + - Warn user if multiple entries in EntryNodes and at least one + HiddenService are used together. Pinning EntryNodes along with a + hidden service can be possibly harmful; for instance see ticket + 14917 or 21155. Closes ticket 21155. + + o Minor features (linux seccomp2 sandbox): + - We now have a document storage backend compatible with the Linux + seccomp2 sandbox. This backend is used for consensus documents and + diffs between them; in the long term, we'd like to use it for + unparseable directory material too. Closes ticket 21645 + - Increase the maximum allowed size passed to mprotect(PROT_WRITE) + from 1MB to 16MB. This was necessary with the glibc allocator in + order to allow worker threads to allocate more memory -- which in + turn is necessary because of our new use of worker threads for + compression. Closes ticket 22096. + + o Minor features (logging): + - Log files are no longer created world-readable by default. + (Previously, most distributors would store the logs in a non- + world-readable location to prevent inappropriate access. This + change is an extra precaution.) Closes ticket 21729; patch + from toralf. + + o Minor features (performance): + - Our Keccak (SHA-3) implementation now accesses memory more + efficiently, especially on little-endian systems. Closes + ticket 21737. + - Add an O(1) implementation of channel_find_by_global_id(), to + speed some controller functions. + + o Minor features (relay, configuration): + - The MyFamily option may now be repeated as many times as desired, + for relays that want to configure large families. Closes ticket + 4998; patch by Daniel Pinto. + + o Minor features (relay, performance): + - Always start relays with at least two worker threads, to prevent + priority inversion on slow tasks. Part of the fix for bug 22883. + - Allow background work to be queued with different priorities, so + that a big pile of slow low-priority jobs will not starve out + higher priority jobs. This lays the groundwork for a fix for + bug 22883. + + o Minor features (safety): + - Add an explicit check to extrainfo_parse_entry_from_string() for + NULL inputs. We don't believe this can actually happen, but it may + help silence a warning from the Clang analyzer. Closes + ticket 21496. + + o Minor features (testing): + - Add more tests for compression backend initialization. Closes + ticket 22286. + - Add a "--disable-memory-sentinels" feature to help with fuzzing. + When Tor is compiled with this option, we disable a number of + redundant memory-safety failsafes that are intended to stop bugs + from becoming security issues. This makes it easier to hunt for + bugs that would be security issues without the failsafes turned + on. Closes ticket 21439. + - Add a general event-tracing instrumentation support to Tor. This + subsystem will enable developers and researchers to add fine- + grained instrumentation to their Tor instances, for use when + examining Tor network performance issues. There are no trace + events yet, and event-tracing is off by default unless enabled at + compile time. Implements ticket 13802. + - Improve our version parsing tests: add tests for typical version + components, add tests for invalid versions, including numeric + range and non-numeric prefixes. Unit tests 21278, 21450, and + 21507. Partially implements 21470. + + o Minor bugfixes (bandwidth accounting): + - Roll over monthly accounting at the configured hour and minute, + rather than always at 00:00. Fixes bug 22245; bugfix on 0.0.9rc1. + Found by Andrey Karpov with PVS-Studio. + + o Minor bugfixes (code correctness): + - Accurately identify client connections by their lack of peer + authentication. This means that we bail out earlier if asked to + extend to a client. Follow-up to 21407. Fixes bug 21406; bugfix + on 0.2.4.23. + + o Minor bugfixes (compilation warnings): + - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug + 22915; bugfix on 0.2.8.1-alpha. + - Fix warnings when building with libscrypt and openssl scrypt + support on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. + - When building with certain versions of the mingw C header files, + avoid float-conversion warnings when calling the C functions + isfinite(), isnan(), and signbit(). Fixes bug 22801; bugfix + on 0.2.8.1-alpha. + + o Minor bugfixes (compilation): + - Avoid compiler warnings in the unit tests for calling tor_sscanf() + with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. + + o Minor bugfixes (compression): + - When spooling compressed data to an output buffer, don't try to + spool more data when there is no more data to spool and we are not + trying to flush the input. Previously, we would sometimes launch + compression requests with nothing to do, which interferes with our + 22672 checks. Fixes bug 22719; bugfix on 0.2.0.16-alpha. + + o Minor bugfixes (configuration): + - Do not crash when starting with LearnCircuitBuildTimeout 0. Fixes + bug 22252; bugfix on 0.2.9.3-alpha. + + o Minor bugfixes (connection lifespan): + - Allow more control over how long TLS connections are kept open: + unify CircuitIdleTimeout and PredictedPortsRelevanceTime into a + single option called CircuitsAvailableTimeout. Also, allow the + consensus to control the default values for both this preference + and the lifespan of relay-to-relay connections. Fixes bug 17592; + bugfix on 0.2.5.5-alpha. + - Increase the initial circuit build timeout testing frequency, to + help ensure that ReducedConnectionPadding clients finish learning + a timeout before their orconn would expire. The initial testing + rate was set back in the days of TAP and before the Tor Browser + updater, when we had to be much more careful about new clients + making lots of circuits. With this change, a circuit build timeout + is learned in about 15-20 minutes, instead of 100-120 minutes. + + o Minor bugfixes (controller): + - Do not crash when receiving a HSPOST command with an empty body. + Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. + - Do not crash when receiving a POSTDESCRIPTOR command with an empty + body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. + - GETINFO onions/current and onions/detached no longer respond with + 551 on empty lists. Fixes bug 21329; bugfix on 0.2.7.1-alpha. + - Trigger HS descriptor events on the control port when the client + fails to pick a hidden service directory for a hidden service. + This can happen if all the hidden service directories are in + ExcludeNodes, or they have all been queried within the last 15 + minutes. Fixes bug 22042; bugfix on 0.2.5.2-alpha. + + o Minor bugfixes (correctness): + - Avoid undefined behavior when parsing IPv6 entries from the geoip6 + file. Fixes bug 22490; bugfix on 0.2.4.6-alpha. + + o Minor bugfixes (coverity build support): + - Avoid Coverity build warnings related to our BUG() macro. By + default, Coverity treats BUG() as the Linux kernel does: an + instant abort(). We need to override that so our BUG() macro + doesn't prevent Coverity from analyzing functions that use it. + Fixes bug 23030; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (defensive programming): + - Detect and break out of infinite loops in our compression code. We + don't think that any such loops exist now, but it's best to be + safe. Closes ticket 22672. + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + o Minor bugfixes (directory authority): + - When a directory authority rejects a descriptor or extrainfo with + a given digest, mark that digest as undownloadable, so that we do + not attempt to download it again over and over. We previously + tried to avoid downloading such descriptors by other means, but we + didn't notice if we accidentally downloaded one anyway. This + behavior became problematic in 0.2.7.2-alpha, when authorities + began pinning Ed25519 keys. Fixes bug 22349; bugfix + on 0.2.1.19-alpha. + - When rejecting a router descriptor for running an obsolete version + of Tor without ntor support, warn about the obsolete tor version, + not the missing ntor key. Fixes bug 20270; bugfix on 0.2.9.3-alpha. + - Prevent the shared randomness subsystem from asserting when + initialized by a bridge authority with an incomplete configuration + file. Fixes bug 21586; bugfix on 0.2.9.8. + + o Minor bugfixes (error reporting, windows): + - When formatting Windows error messages, use the English format to + avoid codepage issues. Fixes bug 22520; bugfix on 0.1.2.8-alpha. + Patch from "Vort". + + o Minor bugfixes (exit-side DNS): + - Fix an untriggerable assertion that checked the output of a + libevent DNS error, so that the assertion actually behaves as + expected. Fixes bug 22244; bugfix on 0.2.0.20-rc. Found by Andrey + Karpov using PVS-Studio. + + o Minor bugfixes (fallback directories): + - Make the usage example in updateFallbackDirs.py actually work, and + explain what it does. Fixes bug 22270; bugfix on 0.3.0.3-alpha. + - Decrease the guard flag average required to be a fallback. This + allows us to keep relays that have their guard flag removed when + they restart. Fixes bug 20913; bugfix on 0.2.8.1-alpha. + - Decrease the minimum number of fallbacks to 100. Fixes bug 20913; + bugfix on 0.2.8.1-alpha. + - Make sure fallback directory mirrors have the same address, port, + and relay identity key for at least 30 days before they are + selected. Fixes bug 20913; bugfix on 0.2.8.1-alpha. + + o Minor bugfixes (file limits, osx): + - When setting the maximum number of connections allowed by the OS, + always allow some extra file descriptors for other files. Fixes + bug 22797; bugfix on 0.2.0.10-alpha. + + o Minor bugfixes (hidden services): + - Increase the number of circuits that a service is allowed to + open over a specific period of time. The value was lower than it + should be (8 vs 12) in the normal case of 3 introduction points. + Fixes bug 22159; bugfix on 0.3.0.5-rc. + - Fix a BUG warning during HSv3 descriptor decoding that could be + cause by a specially crafted descriptor. Fixes bug 23233; bugfix + on 0.3.0.1-alpha. Bug found by "haxxpop". + - Stop printing a cryptic warning when a hidden service gets a + request to connect to a virtual port that it hasn't configured. + Fixes bug 16706; bugfix on 0.2.6.3-alpha. + - Simplify hidden service descriptor creation by using an existing + flag to check if an introduction point is established. Fixes bug + 21599; bugfix on 0.2.7.2-alpha. + + o Minor bugfixes (link handshake): + - Lower the lifetime of the RSA->Ed25519 cross-certificate to six + months, and regenerate it when it is within one month of expiring. + Previously, we had generated this certificate at startup with a + ten-year lifetime, but that could lead to weird behavior when Tor + was started with a grossly inaccurate clock. Mitigates bug 22466; + mitigation on 0.3.0.1-alpha. + + o Minor bugfixes (linux seccomp2 sandbox): + - Avoid a sandbox failure when trying to re-bind to a socket and + mark it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. + - Permit the fchmod system call, to avoid crashing on startup when + starting with the seccomp2 sandbox and an unexpected set of + permissions on the data directory or its contents. Fixes bug + 22516; bugfix on 0.2.5.4-alpha. + + o Minor bugfixes (logging): + - When decompressing, do not warn if we fail to decompress using a + compression method that we merely guessed. Fixes part of bug + 22670; bugfix on 0.1.1.14-alpha. + - When decompressing, treat mismatch between content-encoding and + actual compression type as a protocol warning. Fixes part of bug + 22670; bugfix on 0.1.1.9-alpha. + - Downgrade "assigned_to_cpuworker failed" message to info-level + severity. In every case that can reach it, either a better warning + has already been logged, or no warning is warranted. Fixes bug + 22356; bugfix on 0.2.6.3-alpha. + - Log a better message when a directory authority replies to an + upload with an unexpected status code. Fixes bug 11121; bugfix + on 0.1.0.1-rc. + - Downgrade a log statement about unexpected relay cells from "bug" + to "protocol warning", because there is at least one use case + where it can be triggered by a buggy tor implementation. Fixes bug + 21293; bugfix on 0.1.1.14-alpha. + + o Minor bugfixes (logging, relay): + - Remove a forgotten debugging message when an introduction point + successfully establishes a hidden service prop224 circuit with + a client. + - Change three other log_warn() for an introduction point to + protocol warnings, because they can be failure from the network + and are not relevant to the operator. Fixes bug 23078; bugfix on + 0.3.0.1-alpha and 0.3.0.2-alpha. + + o Minor bugfixes (relay): + - Inform the geoip and rephist modules about all requests, even on + relays that are only fetching microdescriptors. Fixes a bug + related to 21585; bugfix on 0.3.0.1-alpha. + + o Minor bugfixes (memory leaks): + - Fix a small memory leak at exit from the backtrace handler code. + Fixes bug 21788; bugfix on 0.2.5.2-alpha. Patch from Daniel Pinto. + - When directory authorities reject a router descriptor due to + keypinning, free the router descriptor rather than leaking the + memory. Fixes bug 22370; bugfix on 0.2.7.2-alpha. + - Fix a small memory leak when validating a configuration that uses + two or more AF_UNIX sockets for the same port type. Fixes bug + 23053; bugfix on 0.2.6.3-alpha. This is CID 1415725. + + o Minor bugfixes (process behavior): + - When exiting because of an error, always exit with a nonzero exit + status. Previously, we would fail to report an error in our exit + status in cases related to __OwningControllerProcess failure, + lockfile contention, and Ed25519 key initialization. Fixes bug + 22720; bugfix on versions 0.2.1.6-alpha, 0.2.2.28-beta, and + 0.2.7.2-alpha respectively. Reported by "f55jwk4f"; patch + from "huyvq". + + o Minor bugfixes (robustness, error handling): + - Improve our handling of the cases where OpenSSL encounters a + memory error while encoding keys and certificates. We haven't + observed these errors in the wild, but if they do happen, we now + detect and respond better. Fixes bug 19418; bugfix on all versions + of Tor. Reported by Guido Vranken. + + o Minor bugfixes (testing): + - Fix an undersized buffer in test-memwipe.c. Fixes bug 23291; + bugfix on 0.2.7.2-alpha. Found and patched by Ties Stuij. + - Use unbuffered I/O for utility functions around the + process_handle_t type. This fixes unit test failures reported on + OpenBSD and FreeBSD. Fixes bug 21654; bugfix on 0.2.3.1-alpha. + - Make display of captured unit test log messages consistent. Fixes + bug 21510; bugfix on 0.2.9.3-alpha. + - Make test-network.sh always call chutney's test-network.sh. + Previously, this only worked on systems which had bash installed, + due to some bash-specific code in the script. Fixes bug 19699; + bugfix on 0.3.0.4-rc. Follow-up to ticket 21581. + - Fix a memory leak in the link-handshake/certs_ok_ed25519 test. + Fixes bug 22803; bugfix on 0.3.0.1-alpha. + - The unit tests now pass on systems where localhost is misconfigured + to some IPv4 address other than 127.0.0.1. Fixes bug 6298; bugfix + on 0.0.9pre2. + + o Minor bugfixes (voting consistency): + - Reject version numbers with non-numeric prefixes (such as +, -, or + whitespace). Disallowing whitespace prevents differential version + parsing between POSIX-based and Windows platforms. Fixes bug 21507 + and part of 21508; bugfix on 0.0.8pre1. + + o Minor bugfixes (Windows service): + - When running as a Windows service, set the ID of the main thread + correctly. Failure to do so made us fail to send log messages to + the controller in 0.2.1.16-rc, slowed down controller event + delivery in 0.2.7.3-rc and later, and crash with an assertion + failure in 0.3.1.1-alpha. Fixes bug 23081; bugfix on 0.2.1.6-alpha. + Patch and diagnosis from "Vort". + + o Minor bugfixes (windows, relay): + - Resolve "Failure from drain_fd: No error" warnings on Windows + relays. Fixes bug 21540; bugfix on 0.2.6.3-alpha. + + o Code simplification and refactoring: + - Break up the 630-line function connection_dir_client_reached_eof() + into a dozen smaller functions. This change should help + maintainability and readability of the client directory code. + - Isolate our use of the openssl headers so that they are only + included from our crypto wrapper modules, and from tests that + examine those modules' internals. Closes ticket 21841. + - Simplify our API to launch directory requests, making it more + extensible and less error-prone. Now it's easier to add extra + headers to directory requests. Closes ticket 21646. + - Our base64 decoding functions no longer overestimate the output + space that they need when parsing unpadded inputs. Closes + ticket 17868. + - Remove unused "ROUTER_ADDED_NOTIFY_GENERATOR" internal value. + Resolves ticket 22213. + - The logic that directory caches use to spool request to clients, + serving them one part at a time so as not to allocate too much + memory, has been refactored for consistency. Previously there was + a separate spooling implementation per type of spoolable data. Now + there is one common spooling implementation, with extensible data + types. Closes ticket 21651. + - Tor's compression module now supports multiple backends. Part of + the implementation for proposal 278; closes ticket 21663. + + o Documentation: + - Add a manpage description for the key-pinning-journal file. Closes + ticket 22347. + - Correctly note that bandwidth accounting values are stored in the + state file, and the bw_accounting file is now obsolete. Closes + ticket 16082. + - Document more of the files in the Tor data directory, including + cached-extrainfo, secret_onion_key{,_ntor}.old, hidserv-stats, + approved-routers, sr-random, and diff-cache. Found while fixing + ticket 22347. + - Clarify the manpage for the (deprecated) torify script. Closes + ticket 6892. + - Clarify the behavior of the KeepAliveIsolateSOCKSAuth sub-option. + Closes ticket 21873. + - Correct documentation about the default DataDirectory value. + Closes ticket 21151. + - Document the default behavior of NumEntryGuards and + NumDirectoryGuards correctly. Fixes bug 21715; bugfix + on 0.3.0.1-alpha. + - Document key=value pluggable transport arguments for Bridge lines + in torrc. Fixes bug 20341; bugfix on 0.2.5.1-alpha. + - Note that bandwidth-limiting options don't affect TCP headers or + DNS. Closes ticket 17170. + + o Removed features (configuration options, all in ticket 22060): + - These configuration options are now marked Obsolete, and no longer + have any effect: AllowInvalidNodes, AllowSingleHopCircuits, + AllowSingleHopExits, ExcludeSingleHopRelays, FastFirstHopPK, + TLSECGroup, WarnUnsafeSocks. They were first marked as deprecated + in 0.2.9.2-alpha and have now been removed. The previous default + behavior is now always chosen; the previous (less secure) non- + default behavior is now unavailable. + - CloseHSClientCircuitsImmediatelyOnTimeout and + CloseHSServiceRendCircuitsImmediatelyOnTimeout were deprecated in + 0.2.9.2-alpha and now have been removed. HS circuits never close + on circuit build timeout; they have a longer timeout period. + - {Control,DNS,Dir,Socks,Trans,NATD,OR}ListenAddress were deprecated + in 0.2.9.2-alpha and now have been removed. Use the ORPort option + (and others) to configure listen-only and advertise-only addresses. + + o Removed features (tools): + - We've removed the tor-checkkey tool from src/tools. Long ago, we + used it to help people detect RSA keys that were generated by + versions of Debian affected by CVE-2008-0166. But those keys have + been out of circulation for ages, and this tool is no longer + required. Closes ticket 21842. + + +Changes in version 0.3.0.10 - 2017-08-02 + Tor 0.3.0.10 backports a collection of small-to-medium bugfixes + from the current Tor alpha series. OpenBSD users and TPROXY users + should upgrade; others are probably okay sticking with 0.3.0.9. + + o Major features (build system, continuous integration, backport from 0.3.1.5-alpha): + - Tor's repository now includes a Travis Continuous Integration (CI) + configuration file (.travis.yml). This is meant to help new + developers and contributors who fork Tor to a Github repository be + better able to test their changes, and understand what we expect + to pass. To use this new build feature, you must fork Tor to your + Github account, then go into the "Integrations" menu in the + repository settings for your fork and enable Travis, then push + your changes. Closes ticket 22636. + + o Major bugfixes (linux TPROXY support, backport from 0.3.1.1-alpha): + - Fix a typo that had prevented TPROXY-based transparent proxying + from working under Linux. Fixes bug 18100; bugfix on 0.2.6.3-alpha. + Patch from "d4fq0fQAgoJ". + + o Major bugfixes (openbsd, denial-of-service, backport from 0.3.1.5-alpha): + - Avoid an assertion failure bug affecting our implementation of + inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() + handling of "0xbar" differs from what we had expected. Fixes bug + 22789; bugfix on 0.2.3.8-alpha. Also tracked as TROVE-2017-007. + + o Minor features (backport from 0.3.1.5-alpha): + - Update geoip and geoip6 to the July 4 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (bandwidth accounting, backport from 0.3.1.2-alpha): + - Roll over monthly accounting at the configured hour and minute, + rather than always at 00:00. Fixes bug 22245; bugfix on 0.0.9rc1. + Found by Andrey Karpov with PVS-Studio. + + o Minor bugfixes (compilation warnings, backport from 0.3.1.5-alpha): + - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug 22915; + bugfix on 0.2.8.1-alpha. + - Fix warnings when building with libscrypt and openssl scrypt + support on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. + - When building with certain versions of the mingw C header files, + avoid float-conversion warnings when calling the C functions + isfinite(), isnan(), and signbit(). Fixes bug 22801; bugfix + on 0.2.8.1-alpha. + + o Minor bugfixes (compilation, mingw, backport from 0.3.1.1-alpha): + - Backport a fix for an "unused variable" warning that appeared + in some versions of mingw. Fixes bug 22838; bugfix on + 0.2.8.1-alpha. + + o Minor bugfixes (coverity build support, backport from 0.3.1.5-alpha): + - Avoid Coverity build warnings related to our BUG() macro. By + default, Coverity treats BUG() as the Linux kernel does: an + instant abort(). We need to override that so our BUG() macro + doesn't prevent Coverity from analyzing functions that use it. + Fixes bug 23030; bugfix on 0.2.9.1-alpha. + + o Minor bugfixes (directory authority, backport from 0.3.1.1-alpha): + - When rejecting a router descriptor for running an obsolete version + of Tor without ntor support, warn about the obsolete tor version, + not the missing ntor key. Fixes bug 20270; bugfix on 0.2.9.3-alpha. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.5-alpha): + - Avoid a sandbox failure when trying to re-bind to a socket and + mark it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. + + o Minor bugfixes (unit tests, backport from 0.3.1.5-alpha) + - Fix a memory leak in the link-handshake/certs_ok_ed25519 test. + Fixes bug 22803; bugfix on 0.3.0.1-alpha. + + +Changes in version 0.3.0.9 - 2017-06-29 + Tor 0.3.0.9 fixes a path selection bug that would allow a client + to use a guard that was in the same network family as a chosen exit + relay. This is a security regression; all clients running earlier + versions of 0.3.0.x or 0.3.1.x should upgrade to 0.3.0.9 or + 0.3.1.4-alpha. + + This release also backports several other bugfixes from the 0.3.1.x + series. + + o Major bugfixes (path selection, security, backport from 0.3.1.4-alpha): + - When choosing which guard to use for a circuit, avoid the exit's + family along with the exit itself. Previously, the new guard + selection logic avoided the exit, but did not consider its family. + Fixes bug 22753; bugfix on 0.3.0.1-alpha. Tracked as TROVE-2017- + 006 and CVE-2017-0377. + + o Major bugfixes (entry guards, backport from 0.3.1.1-alpha): + - Don't block bootstrapping when a primary bridge is offline and we + can't get its descriptor. Fixes bug 22325; fixes one case of bug + 21969; bugfix on 0.3.0.3-alpha. + + o Major bugfixes (entry guards, backport from 0.3.1.4-alpha): + - When starting with an old consensus, do not add new entry guards + unless the consensus is "reasonably live" (under 1 day old). Fixes + one root cause of bug 22400; bugfix on 0.3.0.1-alpha. + + o Minor features (geoip): + - Update geoip and geoip6 to the June 8 2017 Maxmind GeoLite2 + Country database. + + o Minor bugfixes (voting consistency, backport from 0.3.1.1-alpha): + - Reject version numbers with non-numeric prefixes (such as +, -, or + whitespace). Disallowing whitespace prevents differential version + parsing between POSIX-based and Windows platforms. Fixes bug 21507 + and part of 21508; bugfix on 0.0.8pre1. + + o Minor bugfixes (linux seccomp2 sandbox, backport from 0.3.1.4-alpha): + - Permit the fchmod system call, to avoid crashing on startup when + starting with the seccomp2 sandbox and an unexpected set of + permissions on the data directory or its contents. Fixes bug + 22516; bugfix on 0.2.5.4-alpha. + + o Minor bugfixes (defensive programming, backport from 0.3.1.4-alpha): + - Fix a memset() off the end of an array when packing cells. This + bug should be harmless in practice, since the corrupted bytes are + still in the same structure, and are always padding bytes, + ignored, or immediately overwritten, depending on compiler + behavior. Nevertheless, because the memset()'s purpose is to make + sure that any other cell-handling bugs can't expose bytes to the + network, we need to fix it. Fixes bug 22737; bugfix on + 0.2.4.11-alpha. Fixes CID 1401591. + + Changes in version 0.3.0.8 - 2017-06-08 Tor 0.3.0.8 fixes a pair of bugs that would allow an attacker to remotely crash a hidden service with an assertion failure. Anyone @@ -538,7 +1542,7 @@ Changes in version 0.3.0.6 - 2017-04-26 - Select 200 fallback directories for each release. Closes ticket 20881. - o Minor features (fingerprinting resistence, authentication): + o Minor features (fingerprinting resistance, authentication): - Extend the length of RSA keys used for TLS link authentication to 2048 bits. (These weren't used for forward secrecy; for forward secrecy, we used P256.) Closes ticket 13752. @@ -3185,7 +4189,7 @@ Changes in version 0.2.7.5 - 2015-11-20 o Major bugfixes (hidden services): - Revert commit that made directory authorities assign the HSDir - flag to relay without a DirPort; this was bad because such relays + flag to relays without a DirPort; this was bad because such relays can't handle BEGIN_DIR cells. Fixes bug 15850; bugfix on 0.2.6.3-alpha. - When cannibalizing a circuit for an introduction point, always @@ -3800,7 +4804,7 @@ Changes in version 0.2.6.8 - 2015-05-21 o Major bugfixes (hidden services, backport from 0.2.7.1-alpha): - Revert commit that made directory authorities assign the HSDir - flag to relay without a DirPort; this was bad because such relays + flag to relays without a DirPort; this was bad because such relays can't handle BEGIN_DIR cells. Fixes bug 15850; bugfix on 0.2.6.3-alpha. diff --git a/changes/bug15582 b/changes/bug15582 deleted file mode 100644 index 5ea6431cf8..0000000000 --- a/changes/bug15582 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (compilation): - - Avoid compiler warnings in the unit tests for running tor_sscanf() - with wide string outputs. Fixes bug 15582; bugfix on 0.2.6.2-alpha. - diff --git a/changes/bug16082 b/changes/bug16082 deleted file mode 100644 index 0f2f04fb35..0000000000 --- a/changes/bug16082 +++ /dev/null @@ -1,4 +0,0 @@ - o Documentation: - - Correctly note that bandwidth accounting values are stored in the - state file, and the bw_accounting file is now obsolete. Closes - ticket 16082. diff --git a/changes/bug17857 b/changes/bug17857 deleted file mode 100644 index 6c88638231..0000000000 --- a/changes/bug17857 +++ /dev/null @@ -1,6 +0,0 @@ - o Minor features (defensive programming): - - Create a pair of consensus parameters nf_pad_tor2web and - nf_pad_single_onion that allow us to disable netflow padding in the - consensus for non-anonymous connections, in case the overhead is high. - Closes #17857. - diff --git a/changes/bug18329-minimal b/changes/bug18329-minimal deleted file mode 100644 index 804c4e8dd1..0000000000 --- a/changes/bug18329-minimal +++ /dev/null @@ -1,6 +0,0 @@ - o Minor features (bridge): - - Bridges now include notice in their descriptors that they are bridges, - and notice of their distribution status, based on their publication - settings. Implements ticket 18329. For more fine-grained control of - how a bridge is distributed, upgrade to 0.3.2.x or later. - diff --git a/changes/bug18859 b/changes/bug18859 new file mode 100644 index 0000000000..1fe5bc2107 --- /dev/null +++ b/changes/bug18859 @@ -0,0 +1,7 @@ + o Major bugfixes (circuit prediction): + - Fix circuit prediction logic so that a client doesn't treat a stream as + being "handled" by a circuit if that circuit already has isolation + settings on it that might make it incompatible with the stream. This + change should make Tor clients more responsive by improving their + chances of having a pre-created circuit ready for use when a new client + request arrives. Fixes bug 18859; bugfix on 0.2.3.3-alpha. diff --git a/changes/bug19418 b/changes/bug19418 deleted file mode 100644 index fb5f6ad5df..0000000000 --- a/changes/bug19418 +++ /dev/null @@ -1,7 +0,0 @@ - o Minor bugfixes (robustness, error handling): - - Improve our handling of the cases where OpenSSL encounters a - memory error while encoding keys and certificates. We haven't - observed these happening in the wild, but if they do happen, - we now detect and respond better. Fixes bug 19418; bugfix - on all versions of Tor. Reported by Guido Vranken. - diff --git a/changes/bug20247 b/changes/bug20247 deleted file mode 100644 index 731cf0046f..0000000000 --- a/changes/bug20247 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (linux seccomp2 sandbox): - - Avoid a sandbox failure when trying to re-bind to a socket and mark - it as IPv6-only. Fixes bug 20247; bugfix on 0.2.5.1-alpha. - diff --git a/changes/bug20532 b/changes/bug20532 new file mode 100644 index 0000000000..7c190ea032 --- /dev/null +++ b/changes/bug20532 @@ -0,0 +1,4 @@ + o Minor bugfixes (bridges): + - Overwrite the bridge address earlier in the process of directly + retrieving its descriptor, to make sure we reach it on the configured + address. Fixes bug 20532; bugfix on 0.2.0.10-alpha. diff --git a/changes/bug20963 b/changes/bug20963 new file mode 100644 index 0000000000..a65c58399c --- /dev/null +++ b/changes/bug20963 @@ -0,0 +1,4 @@ + o Minor features (logging): + - Improve the message we log when re-enabling circuit build timeouts + after having received a consensus. Closes ticket 20963. + diff --git a/changes/bug21509 b/changes/bug21509 new file mode 100644 index 0000000000..593a01ef20 --- /dev/null +++ b/changes/bug21509 @@ -0,0 +1,3 @@ + o Minor bugfixes (hidden service v3, fuzzing): + - Fix the hidden service v3 descriptor decoding fuzzing to use the latest + decoding API correctly. Fixes bug 21509; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug22159 b/changes/bug22159 deleted file mode 100644 index c319c7e322..0000000000 --- a/changes/bug22159 +++ /dev/null @@ -1,7 +0,0 @@ - o Minor bugfixes (hidden service): - - A service is allowed to open a maximum number of circuits for a specific - period of time. That value was lower than it should be (8 vs 12) in the - normal case of 3 introduction points. Fixes bug 22159.; bugfix on - tor-0.3.0.5-rc. - - Rate limit the log if we ever go above the maximum number of allowed - intro circuits. Fixes bug 22159.; bugfix on tor-0.3.1.1-alpha. diff --git a/changes/bug22212 b/changes/bug22212 deleted file mode 100644 index f92d6701d3..0000000000 --- a/changes/bug22212 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (netflow padding logging): - - Demote a warn that was caused by libevent delays to info if - the padding is less than 4.5 seconds late, or notice if it is more - (4.5 seconds is the amount of time that a netflow record might - be emitted after, if we chose the maximum timeout). Fixes bug #22212. diff --git a/changes/bug22286 b/changes/bug22286 deleted file mode 100644 index f72e8fe2c7..0000000000 --- a/changes/bug22286 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor features (tests): - - Add a couple more tests for compression backend initialization. - Closes ticket 22286. diff --git a/changes/bug22347 b/changes/bug22347 deleted file mode 100644 index f294ba0a2d..0000000000 --- a/changes/bug22347 +++ /dev/null @@ -1,2 +0,0 @@ - o Documentation: - - Add a manpage description for the key-pinning-journal file. diff --git a/changes/bug22349 b/changes/bug22349 deleted file mode 100644 index bb43404bfe..0000000000 --- a/changes/bug22349 +++ /dev/null @@ -1,9 +0,0 @@ - o Minor bugfixes (directory authority): - - When a directory authority rejects a descriptor or extrainfo with - a given digest, mark that digest as undownloadable, so that we - do not attempt to download it again over and over. We previously - tried to avoid downloading such descriptors by other means, but - we didn't notice if we accidentally downloaded one anyway. This - behavior became problematic in 0.2.7.2-alpha, when authorities - began pinning Ed25519 keys. Fixes ticket - 22349; bugfix on 0.2.1.19-alpha. diff --git a/changes/bug22356 b/changes/bug22356 deleted file mode 100644 index 0082b542be..0000000000 --- a/changes/bug22356 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (logging, relay): - - Downgrade "assigned_to_cpuworker failed" message to INFO-level - severity. In every case that can reach it, either a better warning - has already been logged, or no warning is warranted. Fixes bug 22356; - bugfix on 0.2.6.3-alpha. diff --git a/changes/bug22400_01 b/changes/bug22400_01 deleted file mode 100644 index 454c5f746f..0000000000 --- a/changes/bug22400_01 +++ /dev/null @@ -1,4 +0,0 @@ - o Major bugfixes (entry guards): - - When starting with an old consensus, do not add new entry guards - unless the consensus is "reasonably live" (under 1 day old). Fixes - one root cause of bug 22400; bugfix on 0.3.0.1-alpha. diff --git a/changes/bug22446 b/changes/bug22446 deleted file mode 100644 index eab65aac00..0000000000 --- a/changes/bug22446 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features (code style, backport from 0.3.1.3-alpha): - - Add "Falls through" comments to our codebase, in order to silence - GCC 7's -Wimplicit-fallthrough warnings. Patch from Andreas - Stieger. Closes ticket 22446. diff --git a/changes/bug22502_part1 b/changes/bug22502_part1 deleted file mode 100644 index bd95b7c7c4..0000000000 --- a/changes/bug22502_part1 +++ /dev/null @@ -1,12 +0,0 @@ - o Major bugfixes (compression, zstd): - - Correctly detect a full buffer when decompessing a large - zstd-compressed input. Fixes bug 22628; bugfix on 0.3.1.1-alpha. - - o Minor bugfixes (compression): - - When compressing or decompressing a buffer, check for a failure to - create a compression object. Fixes bug 22626; bugfix on - 0.3.1.1-alpha. - - - When decompressing a buffer, check for extra data after the end of - the compressed data. Fixes bug 22629; bugfix on 0.3.1.1-alpha. - diff --git a/changes/bug22516 b/changes/bug22516 deleted file mode 100644 index f024a3c470..0000000000 --- a/changes/bug22516 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (linux seccomp2 sandbox): - - Permit the fchmod system call, to avoid crashing on startup when - starting with the seccomp2 sandbox and an unexpected set of permissions - on the data directory or its contents. Fixes bug 22516; bugfix on - 0.2.5.4-alpha. diff --git a/changes/bug22520 b/changes/bug22520 deleted file mode 100644 index cc14f7214c..0000000000 --- a/changes/bug22520 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (error reporting, windows): - - When formatting Windows error messages, use the English format - to avoid codepage issues. Fixes bug 22520; bugfix on - 0.1.2.8-alpha. Patch from "Vort". - diff --git a/changes/bug22636 b/changes/bug22636 deleted file mode 100644 index 770cac72e9..0000000000 --- a/changes/bug22636 +++ /dev/null @@ -1,8 +0,0 @@ - o Build features: - - Tor's repository now includes a Travis Continuous Integration (CI) - configuration file (.travis.yml). This is meant to help new developers and - contributors who fork Tor to a Github repository be better able to test - their changes, and understand what we expect to pass. To use this new build - feature, you must fork Tor to your Github account, then go into the - "Integrations" menu in the repository settings for your fork and enable - Travis, then push your changes. diff --git a/changes/bug22644 b/changes/bug22644 deleted file mode 100644 index 9b8742edaf..0000000000 --- a/changes/bug22644 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (controller): - - Do not crash when receiving a POSTDESCRIPTOR command with an - empty body. Fixes part of bug 22644; bugfix on 0.2.0.1-alpha. - - Do not crash when receiving a HSPOST command with an empty body. - Fixes part of bug 22644; bugfix on 0.2.7.1-alpha. diff --git a/changes/bug22669 b/changes/bug22669 deleted file mode 100644 index 804a39e781..0000000000 --- a/changes/bug22669 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (compression): - - When serving directory votes compressed with zlib, - do not claim to have compressed them with zstd. Fixes bug 22669; - bugfix on 0.3.1.1-alpha. diff --git a/changes/bug22670 b/changes/bug22670 deleted file mode 100644 index 47403277d2..0000000000 --- a/changes/bug22670 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (logging, compression): - - When decompressing, do not warn if we fail to decompress using a - compression method that we merely guessed. Fixes part of - bug 22670; bugfix on 0.1.1.14-alpha. diff --git a/changes/bug22670_02 b/changes/bug22670_02 deleted file mode 100644 index 3e7a428faf..0000000000 --- a/changes/bug22670_02 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (logging, compression): - - When decompressing, treat mismatch between content-encoding and - actual compression type as a protocol warning. Fixes part of bug - 22670; bugfix on 0.1.1.9-alpha. diff --git a/changes/bug22670_03 b/changes/bug22670_03 deleted file mode 100644 index 8a7aa49bcd..0000000000 --- a/changes/bug22670_03 +++ /dev/null @@ -1,6 +0,0 @@ - o Minor bugfixes (compression): - - When decompressing an object received over an anonymous directory - connection, if we have already successfully decompressed it using an - acceptable compression method, do not reject it for looking like an - unacceptable compression method. Fixes part of bug 22670; bugfix on - 0.3.1.1-alpha. diff --git a/changes/bug22672 b/changes/bug22672 deleted file mode 100644 index ec6681149d..0000000000 --- a/changes/bug22672 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor features (compression, defensive programming): - - Detect and break out of infinite loops in our compression code. - We don't think that any such loops exist now, but it's best to be - safe. Closes ticket 22672. - diff --git a/changes/bug22702 b/changes/bug22702 deleted file mode 100644 index a2044c70bf..0000000000 --- a/changes/bug22702 +++ /dev/null @@ -1,5 +0,0 @@ - o Major bugfixes (directory protocol): - - Ensure that we sent "304 Not modified" as HTTP status code when a - client is attempting to fetch a consensus or consensus diff that - matches the latest consensus we have available. Fixes bug 22702; - bugfix on 0.3.1.1-alpha. diff --git a/changes/bug22719 b/changes/bug22719 deleted file mode 100644 index bfcda0a4e1..0000000000 --- a/changes/bug22719 +++ /dev/null @@ -1,7 +0,0 @@ - o Minor bugfixes (compression): - - When spooling compressed data to an output buffer, don't try to - spool more data when there is no more data to spool and we are - not trying to flush the input. Previously, we would sometimes - launch compression requests with nothing to do, which interferes - with our 22672 checks. Fixes bug 22719; bugfix on 0.2.0.16-alpha. - diff --git a/changes/bug22720 b/changes/bug22720 deleted file mode 100644 index 4893b577f0..0000000000 --- a/changes/bug22720 +++ /dev/null @@ -1,9 +0,0 @@ - o Minor bugfixes (process behavior): - - When exiting because of an error, always exit with a nonzero - exit status. Previously, we would fail to report an error in - our exit status in cases related to lockfile contention, - __OwningControllerProcess failure, and Ed25519 key - initialization. Fixes bug 22720; bugfix on versions - 0.2.1.6-alpha, 0.2.2.28-beta, and 0.2.7.2-alpha - respectively. Reported by "f55jwk4f"; patch from "huyvq". - diff --git a/changes/bug22737 b/changes/bug22737 deleted file mode 100644 index f0de8e6c41..0000000000 --- a/changes/bug22737 +++ /dev/null @@ -1,12 +0,0 @@ - o Minor bugfixes (defensive programming, undefined behavior): - - - Fix a memset() off the end of an array when packing cells. This - bug should be harmless in practice, since the corrupted bytes - are still in the same structure, and are always padding bytes, - ignored, or immediately overwritten, depending on compiler - behavior. Nevertheless, because the memset()'s purpose is to - make sure that any other cell-handling bugs can't expose bytes - to the network, we need to fix it. Fixes bug 22737; bugfix on - 0.2.4.11-alpha. Fixes CID 1401591. - - diff --git a/changes/bug22751 b/changes/bug22751 deleted file mode 100644 index 714525c8af..0000000000 --- a/changes/bug22751 +++ /dev/null @@ -1,5 +0,0 @@ - o Major bugfixes (compression): - - Fix crash in LZMA module, when the Sandbox is enabled, where - liblzma would allocate more than 16 MB of memory. We solve this - by bumping the mprotect() limit in the Sandbox module from 16 MB - to 20 MB. Fixes bug 22751; bugfix on 0.3.1.1-alpha. diff --git a/changes/bug22752_simple b/changes/bug22752_simple deleted file mode 100644 index 7e60357052..0000000000 --- a/changes/bug22752_simple +++ /dev/null @@ -1,6 +0,0 @@ - o Major bugfixes (windows, directory cache): - - On windows, do not try to delete cached consensus documents and - diffs, until they unmapped from memory. Allow the diff storage - directory to grow larger in order to handle files that might - need to stay around longer. Fixes bug 22752; bugfix on - 0.3.1.1-alpha. diff --git a/changes/bug22753 b/changes/bug22753 deleted file mode 100644 index 32a6dfa56c..0000000000 --- a/changes/bug22753 +++ /dev/null @@ -1,7 +0,0 @@ - o Major bugfixes (path selection, security): - - When choosing which guard to use for a circuit, avoid the - exit's family along with the exit itself. Previously, the new - guard selection logic avoided the exit, but did not consider - its family. Fixes bug 22753; bugfix on 0.3.0.1-alpha. Tracked - as TROVE-2016-006 and CVE-2017-0377. - diff --git a/changes/bug22789 b/changes/bug22789 deleted file mode 100644 index a653592848..0000000000 --- a/changes/bug22789 +++ /dev/null @@ -1,7 +0,0 @@ - o Major bugfixes (openbsd, denial-of-service): - - Avoid an assertion failure bug affecting our implementation of - inet_pton(AF_INET6) on certain OpenBSD systems whose strtol() - handling of "0xfoo" differs from what we had expected. - Fixes bug 22789; bugfix on 0.2.3.8-alpha. Also tracked as - TROVE-2017-007. - diff --git a/changes/bug22797 b/changes/bug22797 deleted file mode 100644 index 619baaa409..0000000000 --- a/changes/bug22797 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (file limits): - - When setting the maximum number of connections allowed by the OS, - always allow some extra file descriptors for other files. - Fixes bug 22797; bugfix on 0.2.0.10-alpha. diff --git a/changes/bug22801 b/changes/bug22801 deleted file mode 100644 index 7edc79bc84..0000000000 --- a/changes/bug22801 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (compilation): - - When building with certain versions the mingw C header files, avoid - float-conversion warnings when calling the C functions isfinite(), - isnan(), and signbit(). Fixes bug 22801; bugfix on 0.2.8.1-alpha. - diff --git a/changes/bug22803 b/changes/bug22803 deleted file mode 100644 index 80b4b9f589..0000000000 --- a/changes/bug22803 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (unit tests): - - Fix a memory leak in the link-handshake/certs_ok_ed25519 test. - Fixes bug 22803; bugfix on 0.3.0.1-alpha. diff --git a/changes/bug22830 b/changes/bug22830 deleted file mode 100644 index 123b725aff..0000000000 --- a/changes/bug22830 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes: - - Fix a problem with Rust toolchains not being found when building - without --enable-cargo-online-mode, due to setting the $HOME - environment variable instead of $CARGO_HOME. Fixes bug 22830; - fix by Chelsea Komlo. Bugfix on 0.3.1.1-alpha. diff --git a/changes/bug22883-config b/changes/bug22883-config deleted file mode 100644 index d60594d9ae..0000000000 --- a/changes/bug22883-config +++ /dev/null @@ -1,7 +0,0 @@ - o Minor features (directory cache, consensus diff): - - Add a new MaxConsensusAgeForDiffs option to allow directory cache - operators with low-resource environments to adjust the number of - consensuses they'll store and generate diffs from. Most cache operators - should leave it unchanged. Helps to work around bug 22883. - - diff --git a/changes/bug22883-priority b/changes/bug22883-priority deleted file mode 100644 index 4b3531c30b..0000000000 --- a/changes/bug22883-priority +++ /dev/null @@ -1,8 +0,0 @@ - o Major bugfixes (relay, performance): - - - Perform circuit handshake operations at a higher priority than we use - for consensus diff creation and compression. This should prevent - circuits from starving when a relay or bridge receive a new consensus, - especially on lower-powered machines. Fixes bug 22883; bugfix on - 0.3.1.1-alpha. - diff --git a/changes/bug22892 b/changes/bug22892 deleted file mode 100644 index 9a70cb0576..0000000000 --- a/changes/bug22892 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (compilation): - - Compile correctly when both openssl 1.1.0 and libscrypt are detected. - Previously this would cause an error. Fixes bug 22892; bugfix on - 0.3.1.1-alpha. diff --git a/changes/bug22915 b/changes/bug22915 deleted file mode 100644 index 17a9c6018f..0000000000 --- a/changes/bug22915 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (compilation warnings): - - Suppress -Wdouble-promotion warnings with clang 4.0. Fixes bug 22915; - bugfix on 0.2.8.1-alpha. diff --git a/changes/bug22916_027 b/changes/bug22916_027 deleted file mode 100644 index 5cf99c7d15..0000000000 --- a/changes/bug22916_027 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (Compilation): - - Fix warnings when building with libscrypt and openssl scrypt support - on Clang. Fixes bug 22916; bugfix on 0.2.7.2-alpha. diff --git a/changes/bug22927 b/changes/bug22927 deleted file mode 100644 index 6e68e6ff08..0000000000 --- a/changes/bug22927 +++ /dev/null @@ -1,6 +0,0 @@ - o Minor bugfixes (compatibility, zstd): - - Write zstd epilogues correctly when the epilogue requires reallocation - of the output buffer, even with zstd 1.3.0. (Previously, - we worked on 1.2.0 and failed with 1.3.0). Fixes bug 22927; bugfix on - 0.3.1.1-alpha. - diff --git a/changes/bug23030_029 b/changes/bug23030_029 deleted file mode 100644 index 89a1b507d7..0000000000 --- a/changes/bug23030_029 +++ /dev/null @@ -1,7 +0,0 @@ - o Minor bugfixes (coverity builds): - - Avoid Coverity build warnings related to our BUG() macro. By - default, Coverity treats BUG() as the Linux kernel does: an - instant abort(). We need to override that so our BUG() macro - doesn't prevent Coverity from analyzing functions that use it. - Fixes bug 23030; bugfix on 0.2.9.1-alpha. - diff --git a/changes/bug23053 b/changes/bug23053 deleted file mode 100644 index 082e239409..0000000000 --- a/changes/bug23053 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (memory leak): - - Fix a small memory leak when validating a configuration that - uses two or more AF_UNIX sockets for the same port type. - Fixes bug 23053; bugfix on 0.2.6.3-alpha. This is CID - 1415725. diff --git a/changes/bug23071 b/changes/bug23071 deleted file mode 100644 index 4756dd6252..0000000000 --- a/changes/bug23071 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (tests): - - Port the hs_ntor handshake test to work correctly with recent - versions of the pysha3 module. Fixes bug 23071; bugfix on - 0.3.1.1-alpha. - diff --git a/changes/bug23077 b/changes/bug23077 deleted file mode 100644 index 5ed1c56742..0000000000 --- a/changes/bug23077 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (unit tests): - - Fix a channelpadding unit test failure on extremely slow systems - by using mocked time instead of actual time. Fixes bug 23077; bugfix on - 0.3.1.1-alpha. diff --git a/changes/bug23078 b/changes/bug23078 deleted file mode 100644 index 67624007cf..0000000000 --- a/changes/bug23078 +++ /dev/null @@ -1,7 +0,0 @@ - o Minor bugfixes (logging, relay): - - Remove a log_warn() that has been forgotten when an introduction point - successfully established a hidden service prop224 circuit with a client. - - Three other log_warn() for an introduction point have been changed to - protocol warning because they can be failure from the network and are - not relevant to the operator. Fixes bug 23078; bugfix on - tor-0.3.0.1-alpha and tor-0.3.0.2-alpha. diff --git a/changes/bug23081 b/changes/bug23081 deleted file mode 100644 index 76c4e30971..0000000000 --- a/changes/bug23081 +++ /dev/null @@ -1,8 +0,0 @@ - o Minor bugfixes (Windows service): - - When running as a Windows service, set the ID of the main thread - correctly. Failure to do so made us fail to send log messages - to the controller in 0.2.1.16-rc, slowed down controller - event delivery in 0.2.7.3-rc and later, and crash with an assertion - failure in 0.3.1.1-alpha. Fixes bug 23081; bugfix on 0.2.1.6-alpha. - Patch and diagnosis from "Vort". - diff --git a/changes/bug23105-diagnostic b/changes/bug23105-diagnostic deleted file mode 100644 index 8ba4931e36..0000000000 --- a/changes/bug23105-diagnostic +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features (diagnostic): - - Add a stack trace to the bug warnings that can be logged when - trying to send an outgoing relay cell with n_chan == 0. - Diagnostic attempt for bug 23105. diff --git a/changes/bug23139 b/changes/bug23139 deleted file mode 100644 index ed63ce85e2..0000000000 --- a/changes/bug23139 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (directory cache): - - Fix a memory leak in the code that recovers space in the consensus - directory cache. Fixes bug 23139; bugfix on 0.3.1.1-alpha. diff --git a/changes/bug23155 b/changes/bug23155 deleted file mode 100644 index 4c24ab136c..0000000000 --- a/changes/bug23155 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (stability): - - Avoid crashing on double-free when unable to load or process - an included file. Fixes bug 23155; bugfix on 0.3.1.1-alpha. - Found with the clang static analyzer. diff --git a/changes/bug23233 b/changes/bug23233 deleted file mode 100644 index 689a99a2a8..0000000000 --- a/changes/bug23233 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (hidden service): - - Fix a BUG alert during HSv3 descriptor decoding that could trigger with a - specially crafted descriptor. Fixes bug #23233; bugfix on 0.3.0.1-alpha. - Bug found by "haxxpop". diff --git a/changes/bug23275 b/changes/bug23275 deleted file mode 100644 index d6c3c47743..0000000000 --- a/changes/bug23275 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (relay): - - When a relay is not running as a directory cache, it will no longer - generate compressed consensuses and consensus diff information. - Previously, this was a waste of disk and CPU. Fixes bug 23275; - bugfix on 0.3.1.1-alpha. diff --git a/changes/bug23291 b/changes/bug23291 deleted file mode 100644 index a5b0efda0a..0000000000 --- a/changes/bug23291 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (testing): - - Fix an undersized buffer in test-memwipe.c. Fixes bug 23291; bugfix on - 0.2.7.2-alpha. Found and patched by Ties Stuij. diff --git a/changes/bug23318 b/changes/bug23318 new file mode 100644 index 0000000000..7fcb8d4487 --- /dev/null +++ b/changes/bug23318 @@ -0,0 +1,11 @@ + o Minor bugfixes (path selection): + - When selecting relays by bandwidth, avoid a rounding error that + could sometimes cause load to be imbalanced incorrectly. Previously, + we would always round upwards; now, we round towards the nearest + integer. This had the biggest effect when a relay's weight adjustments + should have given it weight 0, but it got weight 1 instead. + Fixes bug 23318; bugfix on 0.2.4.3-alpha. + - When calculating the fraction of nodes that have descriptors, and all + all nodes in the network have zero bandwidths, count the number of nodes + instead. + Fixes bug 23318; bugfix on 0.2.4.10-alpha. diff --git a/changes/bug23470 b/changes/bug23470 deleted file mode 100644 index 33367b3a30..0000000000 --- a/changes/bug23470 +++ /dev/null @@ -1,6 +0,0 @@ - o Minor bugfix (relay address resolution): - - Avoid unnecessary calls to directory_fetches_from_authorities() - on relays. This avoids spurious address resolutions and - descriptor rebuilds. This is a mitigation for 21789. The original - bug was introduced in commit 35bbf2e as part of prop210. - Fixes 23470 in 0.2.8.1-alpha. diff --git a/changes/bug23533 b/changes/bug23533 deleted file mode 100644 index b5bfdc0ce2..0000000000 --- a/changes/bug23533 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (relay): - - Inform the geoip and rephist modules about all requests, even - on relays that are only fetching microdescriptors. Fixes a bug related - to 21585; bugfix on 0.3.0.1-alpha. diff --git a/changes/bug23551 b/changes/bug23551 deleted file mode 100644 index 2f918bfa3a..0000000000 --- a/changes/bug23551 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (compression): - - Handle a pathological case when decompressing Zstandard data when the - output buffer size is zero. Fixes bug 23551; bugfix on 0.3.1.1-alpha. diff --git a/changes/bug23568 b/changes/bug23568 deleted file mode 100644 index cac4655687..0000000000 --- a/changes/bug23568 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (compilation): - - Fix a compilation warning when building with zstd support - on 32-bit platforms. Fixes bug 23568; bugfix on 0.3.1.1-alpha. - Found and fixed by Andreas Stieger. diff --git a/changes/bug23603 b/changes/bug23603 new file mode 100644 index 0000000000..dfb2052c9a --- /dev/null +++ b/changes/bug23603 @@ -0,0 +1,7 @@ + o Minor bugfixes (hidden service v3): + - Fix a race between the circuit close and free where the service would + launch a new intro circuit after the close, and then fail to register it + before the free of the previously closed circuit. This was making the + service unable to find the established intro circuit and thus not upload + its descriptor. It can make a service unavailable for up to 24 hours. + Fixes bug 23603; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug23608 b/changes/bug23608 deleted file mode 100644 index 16cf88aa3d..0000000000 --- a/changes/bug23608 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (unit tests): - - Fix additional channelpadding unit test failures by using mocked time - instead of actual time for all tests. Fixes bug 23608; - bugfix on 0.3.1.1-alpha. diff --git a/changes/bug23610 b/changes/bug23610 deleted file mode 100644 index f2dc8bd1a6..0000000000 --- a/changes/bug23610 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor bugfixes (hidden service, relay): - - Avoid a possible double close of a circuit by the intro point on error - of sending the INTRO_ESTABLISHED cell. Fixes ticket 23610; bugfix on - 0.3.0.1-alpha. diff --git a/changes/bug23623 b/changes/bug23623 new file mode 100644 index 0000000000..1e2e5c2ac0 --- /dev/null +++ b/changes/bug23623 @@ -0,0 +1,4 @@ + o Minor bugfixes (onion services): + - Cache some needed onion service client information instead of + continuously computing it over and over again. Fixes bug 23623; bugfix + on 0.3.2.1-alpha. diff --git a/changes/bug23653 b/changes/bug23653 new file mode 100644 index 0000000000..81760cbb82 --- /dev/null +++ b/changes/bug23653 @@ -0,0 +1,7 @@ + o Minor bugfixes (hidden service client): + - When getting multiple SOCKS request for the same .onion address, don't + trigger multiple descriptor fetches. + - When the descriptor fetch fails with an internal error, no more HSDir to + query or we aren't allowed to fetch (FetchHidServDescriptors 0), close + all pending SOCKS request for that .onion. Fixes bug 23653; bugfix on + 0.3.2.1-alpha. diff --git a/changes/bug23662 b/changes/bug23662 new file mode 100644 index 0000000000..1000bde3d6 --- /dev/null +++ b/changes/bug23662 @@ -0,0 +1,4 @@ + o Minor bugfixes (onion services): + - Silence a warning about failed v3 onion descriptor uploads since it can + happen naturally under certain edge-cases. Fixes part of bug 23662; + bugfix on 0.3.2.1-alpha. diff --git a/changes/bug23670 b/changes/bug23670 new file mode 100644 index 0000000000..039bc39478 --- /dev/null +++ b/changes/bug23670 @@ -0,0 +1,3 @@ + o Minor features (entry guards): + - Improve logs issued when we are missing descriptors of primary guards. + Resolves ticket 23670. diff --git a/changes/bug23678 b/changes/bug23678 new file mode 100644 index 0000000000..8138ea71ea --- /dev/null +++ b/changes/bug23678 @@ -0,0 +1,7 @@ + o Minor bugfixes (warnings): + - When we get an HTTP request on a SOCKS port, tell the user about + the new HTTPTunnelPort option. Previously, we would give a + "Tor is not an HTTP Proxy" message, which stopped being true when + HTTPTunnelPort was introduced. Fixes bug 23678; bugfix on + 0.3.2.1-alpha. + diff --git a/changes/bug23681 b/changes/bug23681 new file mode 100644 index 0000000000..e317f36d50 --- /dev/null +++ b/changes/bug23681 @@ -0,0 +1,5 @@ + o Minor bugfixes (hidden service client): + - The introduction circuit was being timed out too quickly while waiting + for the rendezvous circuit to complete. Keep the intro circuit around + longer instead of timing out and reopening new ones constantly. Fixes + bug 23681; bugfix on 0.2.4.8-alpha. diff --git a/changes/bug23690 b/changes/bug23690 deleted file mode 100644 index 36ff32e499..0000000000 --- a/changes/bug23690 +++ /dev/null @@ -1,5 +0,0 @@ - o Major bugfixes (relay, crash, assertion failure): - - Fix a timing-based assertion failure that could occur when the - circuit out-of-memory handler freed a connection's output buffer. - Fixes bug 23690; bugfix on 0.2.6.1-alpha. - diff --git a/changes/bug23696 b/changes/bug23696 new file mode 100644 index 0000000000..c5d18583d4 --- /dev/null +++ b/changes/bug23696 @@ -0,0 +1,5 @@ + o Minor bugfix (KIST scheduler): + - Downgrade a warning to log info when the monotonic time diff is + negative. This can happen on platform not supporting monotonic time. The + scheduler recovers from this without any problem. Fixes bug 23696; + bugfix on 0.3.2.1-alpha. diff --git a/changes/bug23739 b/changes/bug23739 new file mode 100644 index 0000000000..3207b5eaf3 --- /dev/null +++ b/changes/bug23739 @@ -0,0 +1,3 @@ + o Minor bugfixes (documentation): + - Document better how to read gcov and what our postprocessing scripts do. + Fixes bug 23739; bugfix on 0.2.9.1-alpha. diff --git a/changes/bug23741 b/changes/bug23741 new file mode 100644 index 0000000000..92f06f5270 --- /dev/null +++ b/changes/bug23741 @@ -0,0 +1,4 @@ + o Minor bugfixes (testing): + - Prevent scripts/test/coverage from attempting to move gcov + output to the root directory. Fixes bug 23741; bugfix on + 0.2.5.1-alpha. diff --git a/changes/bug23748 b/changes/bug23748 new file mode 100644 index 0000000000..0bd3f3f8ff --- /dev/null +++ b/changes/bug23748 @@ -0,0 +1,5 @@ + o Minor bugfixes (hidden service): + - Always make sure the hidden service generate the public key file if it + is missing. Prior to this, if the public key was deleted from disk, it + wouldn't get recreated. Fixes bug 23748; bugfix on 0.3.2.2-alpha. + Patch from "cathugger". diff --git a/changes/bug23751 b/changes/bug23751 new file mode 100644 index 0000000000..2fd7021664 --- /dev/null +++ b/changes/bug23751 @@ -0,0 +1,6 @@ + o Minor bugfixes (scheduler, channel): + - Ignore channels that have been closed while flushing cells. This can + happen if the write on the connection fails leading to the channel being + closed while in the scheduler loop. This is not a complete fix, it is a + bandaid until we are able to refactor those interactions. Fixes bug + 23751; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug23753 b/changes/bug23753 new file mode 100644 index 0000000000..8782a8e2d0 --- /dev/null +++ b/changes/bug23753 @@ -0,0 +1,4 @@ + o Minor features (logging, scheduler): + - Introduce a SCHED_BUG() function to log extra information about the + scheduler state if we ever catch a bug in the scheduler. Closes ticket + 23753. diff --git a/changes/bug23755 b/changes/bug23755 new file mode 100644 index 0000000000..98f0970344 --- /dev/null +++ b/changes/bug23755 @@ -0,0 +1,4 @@ + o Minor bugfixes (testing): + - Stop unconditionally mirroring the tor repository in GitLab CI. + This prevented developers from enabling GitLab CI on master. + Fixes bug 23755; bugfix on 0.3.2.2-alpha. diff --git a/changes/bug23757 b/changes/bug23757 new file mode 100644 index 0000000000..02507a0b4d --- /dev/null +++ b/changes/bug23757 @@ -0,0 +1,4 @@ + o Minor bugfixes (testing): + - Adjust the GitLab CI configuration to more closely match that of Travis + CI. Fixes bug 23757; bugfix on 0.3.2.2-alpha. + diff --git a/changes/bug23758 b/changes/bug23758 new file mode 100644 index 0000000000..565791e8f4 --- /dev/null +++ b/changes/bug23758 @@ -0,0 +1,4 @@ + o Minor bugfixes (testing): + - Skip a test that would fail if run as root (because it expects a + permissions error). This affects some continuous integration setups. + Fixes bug 23758; bugfix on 0.3.2.2-alpha. diff --git a/changes/bug23762 b/changes/bug23762 new file mode 100644 index 0000000000..741a88e21f --- /dev/null +++ b/changes/bug23762 @@ -0,0 +1,4 @@ + o Minor bugfixes (hidden service v3): + - Properly retry HSv3 descriptor fetches in the case where we were initially + missing required directory information. Fixes bug 23762; bugfix on + 0.3.2.1-alpha. diff --git a/changes/bug23774 b/changes/bug23774 new file mode 100644 index 0000000000..2ea5c0122a --- /dev/null +++ b/changes/bug23774 @@ -0,0 +1,4 @@ + o Minor bugfixes (memory leak): + - Fix a minor memory-leak-at-exit in the KIST scheduler. This + bug should have no user-visible impact. Fixes bug 23774; + bugfix on 0.3.2.1-alpha. diff --git a/changes/bug23783 b/changes/bug23783 new file mode 100644 index 0000000000..98c583a12b --- /dev/null +++ b/changes/bug23783 @@ -0,0 +1,5 @@ + o Minor bugfixes (compilation, windows): + - When detecting OpenSSL on Windows from our configure script, make sure + to try linking with the ws2_32 library. Fixes bug 23783; bugfix on + 0.3.2.2-alpha. + diff --git a/changes/bug23790 b/changes/bug23790 new file mode 100644 index 0000000000..5ebe77f806 --- /dev/null +++ b/changes/bug23790 @@ -0,0 +1,6 @@ + o Minor bugfixes (hidden service v2): + - When reloading tor (HUP) configured with hidden service(s), some + information weren't copy to the new service object. One problem with + this was that tor would wait at least the RendPostPeriod time before + uploading the descriptor if the reload happened before the descriptor + needed to be published. Fixes bug 23790; bugfix on 0.2.1.9-alpha. diff --git a/changes/bug23816 b/changes/bug23816 new file mode 100644 index 0000000000..6139dec9e8 --- /dev/null +++ b/changes/bug23816 @@ -0,0 +1,6 @@ + o Minor bugfixes (directory client): + - On failure to download directory information, delay retry attempts + by a random amount based on the "decorrelated jitter" algorithm. + Our previous delay algorithm tended to produce extra-long delays too + easily. Fixes bug 23816; bugfix on 0.2.9.1-alpha. + diff --git a/changes/bug23820 b/changes/bug23820 new file mode 100644 index 0000000000..4e920d0498 --- /dev/null +++ b/changes/bug23820 @@ -0,0 +1,5 @@ + o Minor bugfixes (IPv6, v3 single onion services): + - Remove buggy code for IPv6-only v3 single onion services, and reject + attempts to configure them. This release supports IPv4, dual-stack, and + IPv6-only v3 hidden services; and IPv4 and dual-stack v3 single onion + services. Fixes bug 23820; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug23861 b/changes/bug23861 new file mode 100644 index 0000000000..c6f017640d --- /dev/null +++ b/changes/bug23861 @@ -0,0 +1,5 @@ + o Minor bugfixes (logging, relay): + - Suppress a log notice when relay descriptors arrive. We already have a + bootstrap progress for this so no need to log notice everytime tor + receives relay descriptors. Microdescriptors behave the same. Fixes bug + 23861; bugfix on 0.2.8.2-alpha. diff --git a/changes/bug23908 b/changes/bug23908 deleted file mode 100644 index f641b66bb9..0000000000 --- a/changes/bug23908 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (directory authority, backport from 0.3.2.1-alpha): - - Remove the length limit on HTTP status lines that authorities can send - in their replies. Fixes bug 23499; bugfix on 0.3.1.6-rc. diff --git a/changes/bug23952 b/changes/bug23952 new file mode 100644 index 0000000000..ab1462e522 --- /dev/null +++ b/changes/bug23952 @@ -0,0 +1,4 @@ + o Minor bugfixes (relay): + - Avoid a BUG warning when receiving a dubious CREATE cell while + an option transition is in progress. Fixes bug 23952; bugfix on + 0.3.2.1-alpha. diff --git a/changes/bug24002 b/changes/bug24002 new file mode 100644 index 0000000000..cdb6081110 --- /dev/null +++ b/changes/bug24002 @@ -0,0 +1,5 @@ + o Minor bugfixes (hidden service): + - Make sure that we have a usable ed25519 key when the intro point relay + does support ed25519 link authentication. We do check for an empty key + when the relay does not support it so this makes it nice and symmetric. + Fixes bug 24002; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug24025 b/changes/bug24025 new file mode 100644 index 0000000000..1d7841af53 --- /dev/null +++ b/changes/bug24025 @@ -0,0 +1,5 @@ + o Minor bugfixes (logging, relay): + - Downgrade a warning to a protocol warning in the case the ed25519 key is + not consistent between the descriptor and micro descriptor of a relay. + This can happen for instance if the relay has been flagged + NoEdConsensus. Fixes bug 24025; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug24050 b/changes/bug24050 new file mode 100644 index 0000000000..d184a77ac0 --- /dev/null +++ b/changes/bug24050 @@ -0,0 +1,5 @@ + o Minor bugfixes (client): + - By default, do not enable storage of client-side DNS values. + These values were unused by default previously, but they should + not have been cached at all. Fixes bug 24050; bugfix on + 0.2.6.3-alpha. diff --git a/changes/bug24082 b/changes/bug24082 new file mode 100644 index 0000000000..1523239351 --- /dev/null +++ b/changes/bug24082 @@ -0,0 +1,3 @@ + o Minor bugfixes (testing): + - Fix a spurious fuzzing-only use of an uninitialized value. + Found by Brian Carpenter. Fixes bug 24082; bugfix on 0.3.0.3-alpha. diff --git a/changes/bug24115 b/changes/bug24115 new file mode 100644 index 0000000000..767f13840b --- /dev/null +++ b/changes/bug24115 @@ -0,0 +1,4 @@ + o Minor bugfixes (manpage, hidden service): + - Mention that the HiddenServiceNumIntroductionPoints option is 0-10 for + v2 service and 0-20 for v3 service. Fixes bug 24115; bugfix on + 0.3.2.1-alpha. diff --git a/changes/bug24150 b/changes/bug24150 new file mode 100644 index 0000000000..cfda7c40da --- /dev/null +++ b/changes/bug24150 @@ -0,0 +1,4 @@ + o Minor bugfixes (v3 onion services): + - Fix a memory leak when decrypting a badly formatted v3 onion + service descriptor. Fixes bug 24150; bugfix on 0.3.2.1-alpha. + Found by OSS-Fuzz; this is OSS-Fuzz issue 3994. diff --git a/changes/bug24170 b/changes/bug24170 new file mode 100644 index 0000000000..d3d7347693 --- /dev/null +++ b/changes/bug24170 @@ -0,0 +1,3 @@ + o Minor bugfixes (path selection): + - Actually log the total bandwidth in compute_weighted_bandwidths(). + Fixes bug 24170; bugfix on 0.2.4.3-alpha. diff --git a/changes/bug24198 b/changes/bug24198 new file mode 100644 index 0000000000..6790706872 --- /dev/null +++ b/changes/bug24198 @@ -0,0 +1,4 @@ + o Minor bugfixes (controller, linux seccomp2 sandbox): + - Avoid a crash when attempting to use the seccomp2 sandbox + together with the OwningControllerProcess feature. + Fixes bug 24198; bugfix on 0.2.5.1-alpha. diff --git a/changes/bug24230 b/changes/bug24230 new file mode 100644 index 0000000000..b08c4cde24 --- /dev/null +++ b/changes/bug24230 @@ -0,0 +1,4 @@ + o Minor bugfixes (control port, hidden service): + - Control port was reporting the action "UPLOAD_FAILED" instead of + "FAILED" for the HS_DESC event when a service was not able to upload a + descriptor. Fixes bug 24230; bugfix on 0.2.7.1-alpha. diff --git a/changes/bug24247 b/changes/bug24247 new file mode 100644 index 0000000000..1f4ddcdde2 --- /dev/null +++ b/changes/bug24247 @@ -0,0 +1,6 @@ + o Minor bugfixes (fuzzing): + - Fix a bug in our fuzzing mock replacement for crypto_pk_checksig(), to + correctly handle cases where a caller gives it an RSA key of under 160 + bits. (This is not actually a bug in Tor itself, but wrather in our + fuzzing code.) Fixes bug 24247; bugfix on 0.3.0.3-alpha. + Found by OSS-Fuzz as issue 4177. diff --git a/changes/bug24279 b/changes/bug24279 new file mode 100644 index 0000000000..ab2932b341 --- /dev/null +++ b/changes/bug24279 @@ -0,0 +1,5 @@ + o Minor bugfixes (compilation, hardening): + - Fix a memory leak warning in one of the libevent-related + configuration tests that could occur when manually specifying + -fsanitize=address. Fixes bug 24279; bugfix on 0.3.0.2-alpha. + Found and patched by Alex Xu. diff --git a/changes/bug24345 b/changes/bug24345 new file mode 100644 index 0000000000..22eb412514 --- /dev/null +++ b/changes/bug24345 @@ -0,0 +1,3 @@ + o Minor bugfixes (tests): + - Fix a unit test in one of the bridge-distribution test cases. + Fixes bug 24345; bugfix on 0.3.2.3-alpha. diff --git a/changes/bug24367 b/changes/bug24367 new file mode 100644 index 0000000000..09ef3bb877 --- /dev/null +++ b/changes/bug24367 @@ -0,0 +1,13 @@ + o Minor bugfixes (bridge clients, bootstrap): + - Retry directory downloads when we get our first bridge descriptor + during bootstrap or while reconnecting to the network. Keep retrying + every time we get a bridge descriptor, until we have a reachable bridge. + Fixes bug 24367; bugfix on 0.2.0.3-alpha. + - Stop delaying bridge descriptor fetches when we have cached bridge + descriptors. Instead, only delay bridge descriptor fetches when we + have at least one reachable bridge. + Fixes bug 24367; bugfix on 0.2.0.3-alpha. + - Stop delaying directory fetches when we have cached bridge descriptors. + Instead, only delay bridge descriptor fetches when all our bridges are + definitely unreachable. + Fixes bug 24367; bugfix on 0.2.0.3-alpha. diff --git a/changes/bug24424 b/changes/bug24424 new file mode 100644 index 0000000000..63c2d39ba1 --- /dev/null +++ b/changes/bug24424 @@ -0,0 +1,3 @@ + o Minor features (portability): + - Tor now compiles correctly on arm64 with libseccomp-dev installed. + (It doesn't yet work with the sandbox enabled.) Closes ticket 24424. diff --git a/changes/bug24502 b/changes/bug24502 new file mode 100644 index 0000000000..3fa6fb58dd --- /dev/null +++ b/changes/bug24502 @@ -0,0 +1,4 @@ + o Minor bugfixes (scheduler): + - Properly set the scheduler state of an unopened channel in the KIST + scheduler main loop. This prevents a harmless but annoying log warning. + Fixes bug 24502; bugfix on 0.3.2.4-alpha. diff --git a/changes/bug24590 b/changes/bug24590 new file mode 100644 index 0000000000..77e039f8d2 --- /dev/null +++ b/changes/bug24590 @@ -0,0 +1,5 @@ + o Minor bugfixes (scheduler, KIST): + - Avoid a possible integer overflow when computing the available space on + the TCP buffer of a channel. This has no security implications but can + make KIST not behave properly by allowing more cells on a already + saturated connection. Fixes bug 24590; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug24665 b/changes/bug24665 new file mode 100644 index 0000000000..f950d9dd01 --- /dev/null +++ b/changes/bug24665 @@ -0,0 +1,6 @@ + o Major bugfixes (KIST, scheduler): + - The KIST scheduler did not correctly account for data already enqueued + in each connection's send socket buffer, particularly in cases when the + TCP/IP congestion window was reduced between scheduler calls. This + situation lead to excessive per-connection buffering in the kernel, and + a potential memory DoS. Fixes bug 24665; bugfix on 0.3.2.1-alpha. diff --git a/changes/bug8185_025 b/changes/bug8185_025 deleted file mode 100644 index 1bfc12b1e4..0000000000 --- a/changes/bug8185_025 +++ /dev/null @@ -1,6 +0,0 @@ - o Minor bugfixes (logging, relay shutdown, annoyance): - - When a circuit is marked for close, do not attempt to package any cells - for channels on that circuit. Previously, we would detect this - condition lower in the call stack, when we noticed that the circuit had - no attached channel, and log an annoying message. Fixes bug 8185; - bugfix on 0.2.5.4-alpha. diff --git a/changes/diagnose_22752 b/changes/diagnose_22752 deleted file mode 100644 index b5bda05ec0..0000000000 --- a/changes/diagnose_22752 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features (bug mitigation, diagnostics, logging): - - Avoid an assertion failure, and log a better error message, - when unable to remove a file from the consensus cache on - Windows. Attempts to mitigate and diagnose bug 22752. diff --git a/changes/feature18329 b/changes/feature18329 new file mode 100644 index 0000000000..1dabf50244 --- /dev/null +++ b/changes/feature18329 @@ -0,0 +1,9 @@ + o Minor features (bridge): + - Bridge relays can now set the BridgeDistribution config option to + add a "bridge-distribution-request" line to their bridge descriptor, + which tells BridgeDB how they'd like their bridge address to be + given out. (Note that as of Oct 2017, BridgeDB does not yet implement + this feature.) As a side benefit, this feature provides a way + to distinguish bridge descriptors from non-bridge descriptors. + Implements tickets 18329. + diff --git a/changes/geoip-august2017 b/changes/geoip-august2017 deleted file mode 100644 index 2dab18a63f..0000000000 --- a/changes/geoip-august2017 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features: - - Update geoip and geoip6 to the August 3 2017 Maxmind GeoLite2 - Country database. - diff --git a/changes/geoip-july2017 b/changes/geoip-july2017 deleted file mode 100644 index ed10369f1b..0000000000 --- a/changes/geoip-july2017 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features: - - Update geoip and geoip6 to the July 4 2017 Maxmind GeoLite2 - Country database. - diff --git a/changes/geoip-june2017 b/changes/geoip-june2017 deleted file mode 100644 index 2ea7bf105e..0000000000 --- a/changes/geoip-june2017 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features: - - Update geoip and geoip6 to the June 8 2017 Maxmind GeoLite2 - Country database. - diff --git a/changes/geoip-september2017 b/changes/geoip-september2017 deleted file mode 100644 index be01ff9521..0000000000 --- a/changes/geoip-september2017 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features: - - Update geoip and geoip6 to the September 6 2017 Maxmind GeoLite2 - Country database. - diff --git a/changes/hsdescv3_fuzz_more b/changes/hsdescv3_fuzz_more new file mode 100644 index 0000000000..25626bb9a4 --- /dev/null +++ b/changes/hsdescv3_fuzz_more @@ -0,0 +1,3 @@ + o Minor features (testing): + - Our fuzzing tests now test the encrypted portions of the + v3 hidden service descriptors. Implements more of 21509. diff --git a/changes/longclaw-ipv6 b/changes/longclaw-ipv6 deleted file mode 100644 index 75899c9d07..0000000000 --- a/changes/longclaw-ipv6 +++ /dev/null @@ -1,6 +0,0 @@ - o Minor features (directory authorities): - - Remove longclaw's IPv6 address, as it will soon change. - Authority IPv6 addresses were originally added in 0.2.8.1-alpha. - This leaves 3/8 directory authorities with IPv6 addresses, but there - are also 52 fallback directory mirrors with IPv6 addresses. - Resolves 19760. diff --git a/changes/more-files b/changes/more-files deleted file mode 100644 index 861d6a3143..0000000000 --- a/changes/more-files +++ /dev/null @@ -1,4 +0,0 @@ - o Documentation: - - Document more of the files in the Tor data directory, including - cached-extrainfo, secret_onion_key{,_ntor}.old, hidserv-stats, - approved-routers, sr-random, and diff-cache. diff --git a/changes/more-threads b/changes/more-threads deleted file mode 100644 index eae88b70fd..0000000000 --- a/changes/more-threads +++ /dev/null @@ -1,3 +0,0 @@ - o Minor features (relay, performance): - - Always start relays with at least two worker threads, to prevent - priority inversion on slow tasks. Part of the fix for bug 22883. diff --git a/changes/multi-priority b/changes/multi-priority deleted file mode 100644 index 6f19314b53..0000000000 --- a/changes/multi-priority +++ /dev/null @@ -1,5 +0,0 @@ - o Minor features (relay, thread pool): - - Allow background work to be queued with different priorities, so - that a big pile of slow low-priority jobs will not starve out - higher priority jobs. This lays the groundwork for a fix for bug - 22883. diff --git a/changes/new_requirement_pkgconfig b/changes/new_requirement_pkgconfig deleted file mode 100644 index 503ff58c9e..0000000000 --- a/changes/new_requirement_pkgconfig +++ /dev/null @@ -1,5 +0,0 @@ - o New dependencies: - - To build with zstd and lzma support, Tor now requires the - pkg-config tool at build time. (This requirement was new in - 0.3.1.1-alpha, but was not noted at the time. Noting it here to - close ticket 22623.) diff --git a/changes/stack b/changes/stack new file mode 100644 index 0000000000..ffdf536cb9 --- /dev/null +++ b/changes/stack @@ -0,0 +1,7 @@ + o Minor bugfixes (correctness): + - Fix several places in our codebase where a C compiler would be likely + to eliminate a check, based on assuming that undefined behavior had not + happened elsewhere in the code. These cases are usually a sign of + redundant checking, or dubious arithmetic. Found by Georg Koppen using + the "STACK" tool from Wang, Zeldovich, Kaashoek, and + Solar-Lezama. Fixes bug 24423; bugfix on various Tor versions. diff --git a/changes/task-22207 b/changes/task-22207 deleted file mode 100644 index 63544834bf..0000000000 --- a/changes/task-22207 +++ /dev/null @@ -1,4 +0,0 @@ - o Minor features: - - Add "fingerprint" line to networkstatus-bridges produced by - bridge authorities. Implements #22207. - diff --git a/changes/ticket21031 b/changes/ticket21031 new file mode 100644 index 0000000000..b081fb018f --- /dev/null +++ b/changes/ticket21031 @@ -0,0 +1,7 @@ + o Minor features (removed deprecations): + - The ClientDNSRejectInternalAddresses flag can once again be set in + non-testing Tor networks, so long as they do not use the default + directory authorities. + This change also removes the deprecation of this + flag in 0.2.9.2-alpha. Closes ticket 21031. + diff --git a/changes/ticket22348 b/changes/ticket22348 deleted file mode 100644 index 49ae94cdf3..0000000000 --- a/changes/ticket22348 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor features (directory authority): - - Improve the message that authorities report to relays when - the RSA/Ed25519 key pair they present conflicts with a previously - pinned key. Closes ticket 22348. - diff --git a/changes/ticket22870 b/changes/ticket22870 deleted file mode 100644 index 07cc8a1d04..0000000000 --- a/changes/ticket22870 +++ /dev/null @@ -1,5 +0,0 @@ - o Minor bugfixes (consensus diff): - - test_consdiff_base64cmp would fail on OS X because while OS X - follows the standard of (less than zero/zero/greater than zero), - it doesn't follow the convention of (-1/0/+1). Make the test - comply with the standard. Fixes bug 22870; bugfix on 0.3.1.1-alpha. diff --git a/changes/ticket22895 b/changes/ticket22895 deleted file mode 100644 index a3f7b86019..0000000000 --- a/changes/ticket22895 +++ /dev/null @@ -1,3 +0,0 @@ - o Minor bugfixes (compilation): - - Fix unused variable warnings in donna's Curve25519 SSE2 code. - Fixes bug 22895; bugfix on 0.2.7.2-alpha. diff --git a/changes/ticket23637 b/changes/ticket23637 new file mode 100644 index 0000000000..0c524f34c3 --- /dev/null +++ b/changes/ticket23637 @@ -0,0 +1,5 @@ + o Minor features (directory authority): + - Make the "Exit" flag assignment only depend on whether the exit + policy allows connections to ports 80 and 443. Previously relays + would get the Exit flag if they allowed connections to one of + these ports and also port 6667. Resolves ticket 23637. diff --git a/changes/ticket24097 b/changes/ticket24097 new file mode 100644 index 0000000000..36547a8ddb --- /dev/null +++ b/changes/ticket24097 @@ -0,0 +1,4 @@ + o Minor features (logging): + - Downgrade a pair of log messages that could occur when an exit's + resolver gave us an unusual (but not forbidden) response. + Closes ticket 24097. diff --git a/changes/ticket24109 b/changes/ticket24109 new file mode 100644 index 0000000000..f66271817d --- /dev/null +++ b/changes/ticket24109 @@ -0,0 +1,4 @@ + o Minor features (integration tests): + - Test that IPv6-only clients can use microdescriptors when running + "make test-network-all". Requires chutney master 61c28b9 or later. + Closes ticket 24109. diff --git a/changes/ticket24158 b/changes/ticket24158 new file mode 100644 index 0000000000..3cdc06afae --- /dev/null +++ b/changes/ticket24158 @@ -0,0 +1,3 @@ + o Minor bugfixes (logging): + Only log about no longer having KIST support once. Fixes bug 24158; bugfix + on 0.3.2 diff --git a/changes/ticket24254 b/changes/ticket24254 new file mode 100644 index 0000000000..98d5d6bacd --- /dev/null +++ b/changes/ticket24254 @@ -0,0 +1,3 @@ + o Documentation: + Add notes in man page regarding OS support for the various scheduler types. + Attempt to use less jargon in the scheduler section. Closes ticket 24254. diff --git a/changes/ticket24315 b/changes/ticket24315 new file mode 100644 index 0000000000..df34dbf412 --- /dev/null +++ b/changes/ticket24315 @@ -0,0 +1,3 @@ + o Major features (linux seccomp2 sandbox): + - Update the sandbox rules so that they should now work correctly with + Glibc 2.26. Closes ticket 24315. diff --git a/changes/ticket24425 b/changes/ticket24425 new file mode 100644 index 0000000000..aa6f082bcc --- /dev/null +++ b/changes/ticket24425 @@ -0,0 +1,4 @@ + o Minor bugfixes (hidden service v3): + - Bump hsdir_spread_store parameter from 3 to 4 in order to increase the + probability of reaching a service for a client missing microdescriptors. + Fixes bug 24425; bugfix on 0.3.2.1-alpha. diff --git a/changes/ticket24500 b/changes/ticket24500 new file mode 100644 index 0000000000..b49b7a5551 --- /dev/null +++ b/changes/ticket24500 @@ -0,0 +1,3 @@ + o Minor features (logging): + - Provide better warnings when the getrandom() syscall fails. + Closes ticket 24500. diff --git a/changes/trove-2017-008 b/changes/trove-2017-008 deleted file mode 100644 index 4b9c5b0a12..0000000000 --- a/changes/trove-2017-008 +++ /dev/null @@ -1,5 +0,0 @@ - o Major bugfixes (security, hidden services, loggging): - - Fix a bug where we could log uninitialized stack when a certain - hidden service error occurred while SafeLogging was disabled. - Fixes bug #23490; bugfix on 0.2.7.2-alpha. - This is also tracked as TROVE-2017-008 and CVE-2017-0380. diff --git a/configure.ac b/configure.ac index 14e676aa13..608d6864bc 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ dnl Copyright (c) 2007-2017, The Tor Project, Inc. dnl See LICENSE for licensing information AC_PREREQ([2.63]) -AC_INIT([tor],[0.3.1.9-dev]) +AC_INIT([tor],[0.3.2.7-rc-dev]) AC_CONFIG_SRCDIR([src/or/main.c]) AC_CONFIG_MACRO_DIR([m4]) @@ -258,64 +258,6 @@ dnl List all external rust crates we depend on here. Include the version rust_crates="libc-0.2.22" AC_SUBST(rust_crates) -if test "x$enable_rust" = "xyes"; then - AC_ARG_VAR([RUSTC], [path to the rustc binary]) - AC_CHECK_PROG([RUSTC], [rustc], [rustc],[no]) - if test "x$RUSTC" = "xno"; then - AC_MSG_ERROR([rustc unavailable but rust integration requested.]) - fi - - AC_ARG_VAR([CARGO], [path to the cargo binary]) - AC_CHECK_PROG([CARGO], [cargo], [cargo],[no]) - if test "x$CARGO" = "xno"; then - AC_MSG_ERROR([cargo unavailable but rust integration requested.]) - fi - - AC_DEFINE([HAVE_RUST], 1, [have Rust]) - if test "x$enable_cargo_online_mode" = "xyes"; then - CARGO_ONLINE= - RUST_DL=# - else - CARGO_ONLINE=--frozen - RUST_DL= - - dnl When we're not allowed to touch the network, we need crate dependencies - dnl locally available. - AC_MSG_CHECKING([rust crate dependencies]) - AC_ARG_VAR([RUST_DEPENDENCIES], [path to directory with local crate mirror]) - if test "x$RUST_DEPENDENCIES" = "x"; then - RUST_DEPENDENCIES="$srcdir/src/ext/rust/" - NEED_MOD=1 - fi - if test ! -d "$RUST_DEPENDENCIES"; then - AC_MSG_ERROR([Rust dependency directory $RUST_DEPENDENCIES does not exist. Specify a dependency directory using the RUST_DEPENDENCIES variable or allow cargo to fetch crates using --enable-cargo-online-mode.]) - fi - for dep in $rust_crates; do - if test ! -d "$RUST_DEPENDENCIES"/"$dep"; then - AC_MSG_ERROR([Failure to find rust dependency $RUST_DEPENDENCIES/$dep. Specify a dependency directory using the RUST_DEPENDENCIES variable or allow cargo to fetch crates using --enable-cargo-online-mode.]) - fi - done - if test "x$NEED_MOD" = "x1"; then - dnl When looking for dependencies from cargo, pick right directory - RUST_DEPENDENCIES="../../src/ext/rust" - fi - fi - - AC_SUBST(CARGO_ONLINE) - AC_SUBST(RUST_DL) - -dnl Let's check the rustc version, too - AC_MSG_CHECKING([rust version]) - RUSTC_VERSION_MAJOR=`$RUSTC --version | cut -d ' ' -f 2 | cut -d '.' -f 1` - RUSTC_VERSION_MINOR=`$RUSTC --version | cut -d ' ' -f 2 | cut -d '.' -f 2` - if test "x$RUSTC_VERSION_MAJOR" = "x" -o "x$RUSTC_VERSION_MINOR" = "x"; then - AC_MSG_ERROR([rustc version couldn't be identified]) - fi - if test "$RUSTC_VERSION_MAJOR" -lt 2 -a "$RUSTC_VERSION_MINOR" -lt 14; then - AC_MSG_ERROR([rustc must be at least version 1.14]) - fi -fi - ifdef([AC_C_FLEXIBLE_ARRAY_MEMBER], [ AC_C_FLEXIBLE_ARRAY_MEMBER ], [ @@ -451,6 +393,73 @@ fi AC_C_BIGENDIAN +if test "x$enable_rust" = "xyes"; then + AC_ARG_VAR([RUSTC], [path to the rustc binary]) + AC_CHECK_PROG([RUSTC], [rustc], [rustc],[no]) + if test "x$RUSTC" = "xno"; then + AC_MSG_ERROR([rustc unavailable but rust integration requested.]) + fi + + AC_ARG_VAR([CARGO], [path to the cargo binary]) + AC_CHECK_PROG([CARGO], [cargo], [cargo],[no]) + if test "x$CARGO" = "xno"; then + AC_MSG_ERROR([cargo unavailable but rust integration requested.]) + fi + + AC_DEFINE([HAVE_RUST], 1, [have Rust]) + if test "x$enable_cargo_online_mode" = "xyes"; then + CARGO_ONLINE= + RUST_DL=# + else + CARGO_ONLINE=--frozen + RUST_DL= + + dnl When we're not allowed to touch the network, we need crate dependencies + dnl locally available. + AC_MSG_CHECKING([rust crate dependencies]) + AC_ARG_VAR([RUST_DEPENDENCIES], [path to directory with local crate mirror]) + if test "x$RUST_DEPENDENCIES" = "x"; then + RUST_DEPENDENCIES="$srcdir/src/ext/rust/" + NEED_MOD=1 + fi + if test ! -d "$RUST_DEPENDENCIES"; then + AC_MSG_ERROR([Rust dependency directory $RUST_DEPENDENCIES does not exist. Specify a dependency directory using the RUST_DEPENDENCIES variable or allow cargo to fetch crates using --enable-cargo-online-mode.]) + fi + for dep in $rust_crates; do + if test ! -d "$RUST_DEPENDENCIES"/"$dep"; then + AC_MSG_ERROR([Failure to find rust dependency $RUST_DEPENDENCIES/$dep. Specify a dependency directory using the RUST_DEPENDENCIES variable or allow cargo to fetch crates using --enable-cargo-online-mode.]) + fi + done + if test "x$NEED_MOD" = "x1"; then + dnl When looking for dependencies from cargo, pick right directory + RUST_DEPENDENCIES="../../src/ext/rust" + fi + fi + + dnl For now both MSVC and MinGW rust libraries will output static libs with + dnl the MSVC naming convention. + if test "$bwin32" = "true"; then + TOR_RUST_UTIL_STATIC_NAME=tor_util.lib + else + TOR_RUST_UTIL_STATIC_NAME=libtor_util.a + fi + + AC_SUBST(TOR_RUST_UTIL_STATIC_NAME) + AC_SUBST(CARGO_ONLINE) + AC_SUBST(RUST_DL) + + dnl Let's check the rustc version, too + AC_MSG_CHECKING([rust version]) + RUSTC_VERSION_MAJOR=`$RUSTC --version | cut -d ' ' -f 2 | cut -d '.' -f 1` + RUSTC_VERSION_MINOR=`$RUSTC --version | cut -d ' ' -f 2 | cut -d '.' -f 2` + if test "x$RUSTC_VERSION_MAJOR" = "x" -o "x$RUSTC_VERSION_MINOR" = "x"; then + AC_MSG_ERROR([rustc version couldn't be identified]) + fi + if test "$RUSTC_VERSION_MAJOR" -lt 2 -a "$RUSTC_VERSION_MINOR" -lt 14; then + AC_MSG_ERROR([rustc must be at least version 1.14]) + fi +fi + AC_SEARCH_LIBS(socket, [socket network]) AC_SEARCH_LIBS(gethostbyname, [nsl]) AC_SEARCH_LIBS(dlopen, [dl]) @@ -481,12 +490,14 @@ AC_CHECK_FUNCS( timingsafe_memcmp \ flock \ ftime \ + get_current_dir_name \ getaddrinfo \ getifaddrs \ getpass \ getrlimit \ gettimeofday \ gmtime_r \ + gnu_get_libc_version \ htonll \ inet_aton \ ioctl \ @@ -573,13 +584,16 @@ if test "$bwin32" = "true"; then # Some of the cargo-cults recommend -lwsock32 as well, but I don't # think it's actually necessary. TOR_LIB_GDI=-lgdi32 + TOR_LIB_USERENV=-luserenv else TOR_LIB_WS32= TOR_LIB_GDI= + TOR_LIB_USERENV= fi AC_SUBST(TOR_LIB_WS32) AC_SUBST(TOR_LIB_GDI) AC_SUBST(TOR_LIB_IPHLPAPI) +AC_SUBST(TOR_LIB_USERENV) tor_libevent_pkg_redhat="libevent" tor_libevent_pkg_debian="libevent-dev" @@ -611,7 +625,7 @@ struct event_base *event_base_new(void);], #ifdef _WIN32 {WSADATA d; WSAStartup(0x101,&d); } #endif -event_base_new(); +event_base_free(event_base_new()); ], [--with-libevent-dir], [/opt/libevent]) dnl Determine the incantation needed to link libevent. @@ -709,11 +723,12 @@ AC_ARG_WITH(ssl-dir, fi ]) -TOR_SEARCH_LIBRARY(openssl, $tryssldir, [-lssl -lcrypto $TOR_LIB_GDI], - [#include <openssl/rand.h>], - [void RAND_add(const void *buf, int num, double entropy);], - [RAND_add((void*)0,0,0);], [], - [/usr/local/openssl /usr/lib/openssl /usr/local/ssl /usr/lib/ssl /usr/local /usr/athena /opt/openssl]) +AC_MSG_NOTICE([Now, we'll look for OpenSSL >= 1.0.1]) +TOR_SEARCH_LIBRARY(openssl, $tryssldir, [-lssl -lcrypto $TOR_LIB_GDI $TOR_LIB_WS32], + [#include <openssl/ssl.h>], + [struct ssl_method_st; const struct ssl_method_st *TLSv1_1_method(void);], + [TLSv1_1_method();], [], + [/usr/local/opt/openssl /usr/local/openssl /usr/lib/openssl /usr/local/ssl /usr/lib/ssl /usr/local /usr/athena /opt/openssl]) dnl XXXX check for OPENSSL_VERSION_NUMBER == SSLeay() @@ -779,6 +794,34 @@ AC_CHECK_MEMBERS([SSL.state], , , [#include <openssl/ssl.h> ]) +dnl Define the set of checks for KIST scheduler support. +AC_DEFUN([CHECK_KIST_SUPPORT],[ + dnl KIST needs struct tcp_info and for certain members to exist. + AC_CHECK_MEMBERS( + [struct tcp_info.tcpi_unacked, struct tcp_info.tcpi_snd_mss], + , ,[[#include <netinet/tcp.h>]]) + dnl KIST needs SIOCOUTQNSD to exist for an ioctl call. + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [ + #include <linux/sockios.h> + #ifndef SIOCOUTQNSD + #error + #endif + ])], have_siocoutqnsd=yes, have_siocoutqnsd=no) + if test "x$have_siocoutqnsd" = "xyes"; then + if test "x$ac_cv_member_struct_tcp_info_tcpi_unacked" = "xyes"; then + if test "x$ac_cv_member_struct_tcp_info_tcpi_snd_mss" = "xyes"; then + have_kist_support=yes + fi + fi + fi +]) +dnl Now, trigger the check. +CHECK_KIST_SUPPORT +AS_IF([test "x$have_kist_support" = "xyes"], + [AC_DEFINE(HAVE_KIST_SUPPORT, 1, [Defined if KIST scheduler is supported + on this system])], + [AC_MSG_NOTICE([KIST scheduler can't be used. Missing support.])]) + LIBS="$save_LIBS" LDFLAGS="$save_LDFLAGS" CPPFLAGS="$save_CPPFLAGS" @@ -893,7 +936,7 @@ dnl since sometimes the linker will like an option but not be willing to dnl use it with a build of a library. all_ldflags_for_check="$TOR_LDFLAGS_zlib $TOR_LDFLAGS_openssl $TOR_LDFLAGS_libevent" -all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI $TOR_CAP_LIBS" +all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI $TOR_LIB_USERENV $TOR_CAP_LIBS" CFLAGS_FTRAPV= CFLAGS_FWRAPV= @@ -1173,6 +1216,7 @@ AC_CHECK_HEADERS([assert.h \ arpa/inet.h \ crt_externs.h \ execinfo.h \ + gnu/libc-version.h \ grp.h \ ifaddrs.h \ inttypes.h \ @@ -1552,6 +1596,24 @@ if test "$tor_cv_sign_extend" != "no"; then [Define to 1 iff right-shifting a negative value performs sign-extension]) fi +# Is uint8_t the same type as unsigned char? +AC_CACHE_CHECK([whether uint8_t is the same type as unsigned char], tor_cv_uint8_uchar, +[AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#include <stdint.h> +extern uint8_t c; +unsigned char c;]])], + [tor_cv_uint8_uchar=yes], + [tor_cv_uint8_uchar=no], + [tor_cv_uint8_uchar=cross])]) + +if test "$tor_cv_uint8_uchar" = "cross"; then + AC_MSG_NOTICE([Cross-compiling: we'll assume that uint8_t is the same type as unsigned char]) +fi + +if test "$tor_cv_uint8_uchar" = "no"; then + AC_MSG_ERROR([We assume that uint8_t is the same type as unsigned char, but your compiler disagrees.]) +fi + # Whether we should use the dmalloc memory allocation debugging library. AC_MSG_CHECKING(whether to use dmalloc (debug memory allocation library)) AC_ARG_WITH(dmalloc, diff --git a/contrib/win32build/tor-mingw.nsi.in b/contrib/win32build/tor-mingw.nsi.in index 472ec0d26e..e9099b1085 100644 --- a/contrib/win32build/tor-mingw.nsi.in +++ b/contrib/win32build/tor-mingw.nsi.in @@ -8,7 +8,7 @@ !include "LogicLib.nsh" !include "FileFunc.nsh" !insertmacro GetParameters -!define VERSION "0.3.1.9-dev" +!define VERSION "0.3.2.7-rc-dev" !define INSTALLER "tor-${VERSION}-win32.exe" !define WEBSITE "https://www.torproject.org/" !define LICENSE "LICENSE" diff --git a/doc/HACKING/CodingStandards.md b/doc/HACKING/CodingStandards.md index c7787a72cc..dd21d6fd2c 100644 --- a/doc/HACKING/CodingStandards.md +++ b/doc/HACKING/CodingStandards.md @@ -6,8 +6,8 @@ tl;dr: - Run configure with `--enable-fatal-warnings` - Document your functions - Write unit tests - - Run `make test-full` to test against all unit and integration tests. - - Run `make distcheck` to ensure the distribution works + - Run `make check` before submitting a patch + - Run `make distcheck` if you have made changes to build system components - Add a file in `changes` for your branch. Patch checklist @@ -32,6 +32,16 @@ Did you remember... - To base your code on the appropriate branch? - To include a file in the `changes` directory as appropriate? +If you are submitting a major patch or new feature, or want to in the future... + + - Set up Chutney and Stem, see HACKING/WritingTests.md + - Run `make test-full` to test against all unit and integration tests. + +If you have changed build system components: + - Please run `make distcheck` + - For example, if you have changed Makefiles, autoconf files, or anything + else that affects the build system. + How we use Git branches ======================= @@ -52,8 +62,17 @@ before it gets merged into maint, but that's rare. If you're working on a bugfix for a bug that occurs in a particular version, base your bugfix branch on the "maint" branch for the first supported series -that has that bug. (As of June 2013, we're supporting 0.2.3 and later.) If -you're working on a new feature, base it on the master branch. +that has that bug. (As of June 2013, we're supporting 0.2.3 and later.) + +If you're working on a new feature, base it on the master branch. If you're +working on a new feature and it will take a while to implement and/or you'd +like to avoid the possibility of unrelated bugs in Tor while you're +implementing your feature, consider branching off of the latest maint- branch. +_Never_ branch off a relase- branch. Don't branch off a tag either: they come +from release branches. Doing so will likely produce a nightmare of merge +conflicts in the ChangeLog when it comes time to merge your branch into Tor. +Best advice: don't try to keep an independent branch forked for more than 6 +months and expect it to merge cleanly. Try to merge pieces early and often. How we log changes @@ -77,17 +96,34 @@ you can use `git describe --contains <sha1 of commit>`. If at all possible, try to create this file in the same commit where you are making the change. Please give it a distinctive name that no other branch will use for the lifetime of your change. To verify the format of the changes file, -you can use `make check-changes`. +you can use `make check-changes`. This is run automatically as part of +`make check` -- if it fails, we must fix it before we release. These +checks are implemented in `scripts/maint/lintChanges.py`. + +Changes file style guide: + * Changes files begin with " o Header (subheading):". The header + should usually be "Minor/Major bugfixes/features". The subheading is a + particular area within Tor. See the ChangeLog for examples. + + * Make everything terse. + + * Write from the user's point of view: describe the user-visible changes + right away. + + * Mention configuration options by name. If they're rare or unusual, + remind people what they're for. + + * Describe changes in the present tense and in the imperative: not past. + + * Every bugfix should have a sentence of the form "Fixes bug 1234; bugfix + on 0.1.2.3-alpha", describing what bug was fixed and where it came from. + + * "Relays", not "servers", "nodes", or "Tor relays". When we go to make a release, we will concatenate all the entries in changes to make a draft changelog, and clear the directory. We'll then edit the draft changelog into a nice readable format. -To make sure that stuff is in the right format, we use -scripts/maint/lintChanges.py to check the changes files for -(superficial) validity. You can run this script on your own changes -files! - What needs a changes file? * A not-exhaustive list: Anything that might change user-visible @@ -156,6 +192,79 @@ old C functions. Use `strlcat`, `strlcpy`, or `tor_snprintf/tor_asprintf` inste We don't call `memcmp()` directly. Use `fast_memeq()`, `fast_memneq()`, `tor_memeq()`, or `tor_memneq()` for most purposes. +Also see a longer list of functions to avoid in: +https://people.torproject.org/~nickm/tor-auto/internal/this-not-that.html + +Floating point math is hard +--------------------------- + +Floating point arithmetic as typically implemented by computers is +very counterintuitive. Failure to adequately analyze floating point +usage can result in surprising behavior and even security +vulnerabilities! + +General advice: + + - Don't use floating point. + - If you must use floating point, document how the limits of + floating point precision and calculation accuracy affect function + outputs. + - Try to do as much as possible of your calculations using integers + (possibly acting as fixed-point numbers) and convert to floating + point for display. + - If you must send floating point numbers on the wire, serialize + them in a platform-independent way. Tor avoids exchanging + floating-point values, but when it does, it uses ASCII numerals, + with a decimal point ("."). + - Binary fractions behave very differently from decimal fractions. + Make sure you understand how these differences affect your + calculations. + - Every floating point arithmetic operation is an opportunity to + lose precision, overflow, underflow, or otherwise produce + undesired results. Addition and subtraction tend to be worse + than multiplication and division (due to things like catastrophic + cancellation). Try to arrange your calculations to minimize such + effects. + - Changing the order of operations changes the results of many + floating-point calculations. Be careful when you simplify + calculations! If the order is significant, document it using a + code comment. + - Comparing most floating point values for equality is unreliable. + Avoid using `==`, instead, use `>=` or `<=`. If you use an + epsilon value, make sure it's appropriate for the ranges in + question. + - Different environments (including compiler flags and per-thread + state on a single platform!) can get different results from the + same floating point calculations. This means you can't use + floats in anything that needs to be deterministic, like consensus + generation. This also makes reliable unit tests of + floating-point outputs hard to write. + +For additional useful advice (and a little bit of background), see +[What Every Programmer Should Know About Floating-Point +Arithmetic](http://floating-point-gui.de/). + +A list of notable (and surprising) facts about floating point +arithmetic is at [Floating-point +complexities](https://randomascii.wordpress.com/2012/04/05/floating-point-complexities/). +Most of that [series of posts on floating +point](https://randomascii.wordpress.com/category/floating-point/) is +helpful. + +For more detailed (and math-intensive) background, see [What Every +Computer Scientist Should Know About Floating-Point +Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). + +Other C conventions +------------------- + +The `a ? b : c` trinary operator only goes inside other expressions; +don't use it as a replacement for if. (You can ignore this inside macro +definitions when necessary.) + +Assignment operators shouldn't nest inside other expressions. (You can +ignore this inside macro definitions when necessary.) + Functions not to write ---------------------- @@ -217,7 +326,25 @@ end-users that they aren't expected to understand the message (perhaps with a string like "internal error"). Option (A) is to be preferred to option (B). +Assertions In Tor +----------------- + +Assertions should be used for bug-detection only. Don't use assertions to +detect bad user inputs, network errors, resource exhaustion, or similar +issues. + +Tor is always built with assertions enabled, so try to only use +`tor_assert()` for cases where you are absolutely sure that crashing is the +least bad option. Many bugs have been caused by use of `tor_assert()` when +another kind of check would have been safer. + +If you're writing an assertion to test for a bug that you _can_ recover from, +use `tor_assert_nonfatal()` in place of `tor_assert()`. If you'd like to +write a conditional that incorporates a nonfatal assertion, use the `BUG()` +macro, as in: + if (BUG(ptr == NULL)) + return -1; Doxygen comment conventions --------------------------- diff --git a/doc/HACKING/CodingStandardsRust.md b/doc/HACKING/CodingStandardsRust.md new file mode 100644 index 0000000000..d0b17c1604 --- /dev/null +++ b/doc/HACKING/CodingStandardsRust.md @@ -0,0 +1,469 @@ + + Rust Coding Standards +======================= + +You MUST follow the standards laid out in `.../doc/HACKING/CodingStandards.md`, +where applicable. + + Module/Crate Declarations +--------------------------- + +Each Tor C module which is being rewritten MUST be in its own crate. +See the structure of `.../src/rust` for examples. + +In your crate, you MUST use `lib.rs` ONLY for pulling in external +crates (e.g. `extern crate libc;`) and exporting public objects from +other Rust modules (e.g. `pub use mymodule::foo;`). For example, if +you create a crate in `.../src/rust/yourcrate`, your Rust code should +live in `.../src/rust/yourcrate/yourcode.rs` and the public interface +to it should be exported in `.../src/rust/yourcrate/lib.rs`. + +If your code is to be called from Tor C code, you MUST define a safe +`ffi.rs`. See the "Safety" section further down for more details. + +For example, in a hypothetical `tor_addition` Rust module: + +In `.../src/rust/tor_addition/addition.rs`: + + pub fn get_sum(a: i32, b: i32) -> i32 { + a + b + } + +In `.../src/rust/tor_addition/lib.rs`: + + pub use addition::*; + +In `.../src/rust/tor_addition/ffi.rs`: + + #[no_mangle] + pub extern "C" fn tor_get_sum(a: c_int, b: c_int) -> c_int { + get_sum(a, b) + } + +If your Rust code must call out to parts of Tor's C code, you must +declare the functions you are calling in the `external` crate, located +at `.../src/rust/external`. + +<!-- XXX get better examples of how to declare these externs, when/how they --> +<!-- XXX are unsafe, what they are expected to do —isis --> + +Modules should strive to be below 500 lines (tests excluded). Single +responsibility and limited dependencies should be a guiding standard. + +If you have any external modules as dependencies (e.g. `extern crate +libc;`), you MUST declare them in your crate's `lib.rs` and NOT in any +other module. + + Dependencies +-------------- + +In general, we use modules from only the Rust standard library +whenever possible. We will review including external crates on a +case-by-case basis. + + Documentation +--------------- + +You MUST include `#[deny(missing_docs)]` in your crate. + +For function/method comments, you SHOULD include a one-sentence, "first person" +description of function behaviour (see requirements for documentation as +described in `.../src/HACKING/CodingStandards.md`), then an `# Inputs` section +for inputs or initialisation values, a `# Returns` section for return +values/types, a `# Warning` section containing warnings for unsafe behaviours or +panics that could happen. For publicly accessible +types/constants/objects/functions/methods, you SHOULD also include an +`# Examples` section with runnable doctests. + +You MUST document your module with _module docstring_ comments, +i.e. `//!` at the beginning of each line. + + Style +------- + +You SHOULD consider breaking up large literal numbers with `_` when it makes it +more human readable to do so, e.g. `let x: u64 = 100_000_000_000`. + + Testing +--------- + +All code MUST be unittested and integration tested. + +Public functions/objects exported from a crate SHOULD include doctests +describing how the function/object is expected to be used. + +Integration tests SHOULD go into a `tests/` directory inside your +crate. Unittests SHOULD go into their own module inside the module +they are testing, e.g. in `.../src/rust/tor_addition/addition.rs` you +should put: + + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn addition_with_zero() { + let sum: i32 = get_sum(5i32, 0i32); + assert_eq!(sum, 5); + } + } + + Benchmarking +-------------- + +The external `test` crate can be used for most benchmarking. However, using +this crate requires nightly Rust. Since we may want to switch to a more +stable Rust compiler eventually, we shouldn't do things which will automatically +break builds for stable compilers. Therefore, you MUST feature-gate your +benchmarks in the following manner. + +If you wish to benchmark some of your Rust code, you MUST put the +following in the `[features]` section of your crate's `Cargo.toml`: + + [features] + bench = [] + +Next, in your crate's `lib.rs` you MUST put: + + #[cfg(all(test, feature = "bench"))] + extern crate test; + +This ensures that the external crate `test`, which contains utilities +for basic benchmarks, is only used when running benchmarks via `cargo +bench --features bench`. + +Finally, to write your benchmark code, in +`.../src/rust/tor_addition/addition.rs` you SHOULD put: + + #[cfg(all(test, features = "bench"))] + mod bench { + use test::Bencher; + use super::*; + + #[bench] + fn addition_small_integers(b: &mut Bencher) { + b.iter(| | get_sum(5i32, 0i32)); + } + } + + Fuzzing +--------- + +If you wish to fuzz parts of your code, please see the +[`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz) crate, which uses +[libfuzzer-sys](https://github.com/rust-fuzz/libfuzzer-sys). + + Whitespace & Formatting +------------------------- + +You MUST run `rustfmt` (https://github.com/rust-lang-nursery/rustfmt) +on your code before your code will be merged. You can install rustfmt +by doing `cargo install rustfmt-nightly` and then run it with `cargo +fmt`. + + Safety +-------- + +You SHOULD read [the nomicon](https://doc.rust-lang.org/nomicon/) before writing +Rust FFI code. It is *highly advised* that you read and write normal Rust code +before attempting to write FFI or any other unsafe code. + +Here are some additional bits of advice and rules: + +0. Any behaviours which Rust considers to be undefined are forbidden + + From https://doc.rust-lang.org/reference/behavior-considered-undefined.html: + + > Behavior considered undefined + > + > The following is a list of behavior which is forbidden in all Rust code, + > including within unsafe blocks and unsafe functions. Type checking provides the + > guarantee that these issues are never caused by safe code. + > + > * Data races + > * Dereferencing a null/dangling raw pointer + > * Reads of [undef](http://llvm.org/docs/LangRef.html#undefined-values) + > (uninitialized) memory + > * Breaking the + > [pointer aliasing rules](http://llvm.org/docs/LangRef.html#pointer-aliasing-rules) + > with raw pointers (a subset of the rules used by C) + > * `&mut T` and `&T` follow LLVM’s scoped noalias model, except if the `&T` + > contains an `UnsafeCell<U>`. Unsafe code must not violate these aliasing + > guarantees. + > * Mutating non-mutable data (that is, data reached through a shared + > reference or data owned by a `let` binding), unless that data is + > contained within an `UnsafeCell<U>`. + > * Invoking undefined behavior via compiler intrinsics: + > - Indexing outside of the bounds of an object with + > `std::ptr::offset` (`offset` intrinsic), with the exception of + > one byte past the end which is permitted. + > - Using `std::ptr::copy_nonoverlapping_memory` (`memcpy32`/`memcpy64` + > intrinsics) on overlapping buffers + > * Invalid values in primitive types, even in private fields/locals: + > - Dangling/null references or boxes + > - A value other than `false` (0) or `true` (1) in a `bool` + > - A discriminant in an `enum` not included in the type definition + > - A value in a `char` which is a surrogate or above `char::MAX` + > - Non-UTF-8 byte sequences in a `str` + > * Unwinding into Rust from foreign code or unwinding from Rust into foreign + > code. Rust's failure system is not compatible with exception handling in other + > languages. Unwinding must be caught and handled at FFI boundaries. + +1. `unwrap()` + + If you call `unwrap()`, anywhere, even in a test, you MUST include + an inline comment stating how the unwrap will either 1) never fail, + or 2) should fail (i.e. in a unittest). + + You SHOULD NOT use `unwrap()` anywhere in which it is possible to handle the + potential error with either `expect()` or the eel operator, `?`. + For example, consider a function which parses a string into an integer: + + fn parse_port_number(config_string: &str) -> u16 { + u16::from_str_radix(config_string, 10).unwrap() + } + + There are numerous ways this can fail, and the `unwrap()` will cause the + whole program to byte the dust! Instead, either you SHOULD use `expect()` + (or another equivalent function which will return an `Option` or a `Result`) + and change the return type to be compatible: + + fn parse_port_number(config_string: &str) -> Option<u16> { + u16::from_str_radix(config_string, 10).expect("Couldn't parse port into a u16") + } + + or you SHOULD use `or()` (or another similar method): + + fn parse_port_number(config_string: &str) -> Option<u16> { + u16::from_str_radix(config_string, 10).or(Err("Couldn't parse port into a u16") + } + + Using methods like `or()` can be particularly handy when you must do + something afterwards with the data, for example, if we wanted to guarantee + that the port is high. Combining these methods with the eel operator (`?`) + makes this even easier: + + fn parse_port_number(config_string: &str) -> Result<u16, Err> { + let port = u16::from_str_radix(config_string, 10).or(Err("Couldn't parse port into a u16"))?; + + if port > 1024 { + return Ok(port); + } else { + return Err("Low ports not allowed"); + } + } + +2. `unsafe` + + If you use `unsafe`, you MUST describe a contract in your + documentation which describes how and when the unsafe code may + fail, and what expectations are made w.r.t. the interfaces to + unsafe code. This is also REQUIRED for major pieces of FFI between + C and Rust. + + When creating an FFI in Rust for C code to call, it is NOT REQUIRED + to declare the entire function `unsafe`. For example, rather than doing: + + #[no_mangle] + pub unsafe extern "C" fn increment_and_combine_numbers(mut numbers: [u8; 4]) -> u32 { + for number in &mut numbers { + *number += 1; + } + std::mem::transmute::<[u8; 4], u32>(numbers) + } + + You SHOULD instead do: + + #[no_mangle] + pub extern "C" fn increment_and_combine_numbers(mut numbers: [u8; 4]) -> u32 { + for index in 0..numbers.len() { + numbers[index] += 1; + } + unsafe { + std::mem::transmute::<[u8; 4], u32>(numbers) + } + } + +3. Pass only integer types and bytes over the boundary + + The only non-integer type which may cross the FFI boundary is + bytes, e.g. `&[u8]`. This SHOULD be done on the Rust side by + passing a pointer (`*mut libc::c_char`) and a length + (`libc::size_t`). + + One might be tempted to do this via doing + `CString::new("blah").unwrap().into_raw()`. This has several problems: + + a) If you do `CString::new("bl\x00ah")` then the unwrap() will fail + due to the additional NULL terminator, causing a dangling + pointer to be returned (as well as a potential use-after-free). + + b) Returning the raw pointer will cause the CString to run its deallocator, + which causes any C code which tries to access the contents to dereference a + NULL pointer. + + c) If we were to do `as_raw()` this would result in a potential double-free + since the Rust deallocator would run and possibly Tor's deallocator. + + d) Calling `into_raw()` without later using the same pointer in Rust to call + `from_raw()` and then deallocate in Rust can result in a + [memory leak](https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw). + + [It was determined](https://github.com/rust-lang/rust/pull/41074) that this + is safe to do if you use the same allocator in C and Rust and also specify + the memory alignment for CString (except that there is no way to specify + the alignment for CString). It is believed that the alignment is always 1, + which would mean it's safe to dealloc the resulting `*mut c_char` in Tor's + C code. However, the Rust developers are not willing to guarantee the + stability of, or a contract for, this behaviour, citing concerns that this + is potentially extremely and subtly unsafe. + +4. Perform an allocation on the other side of the boundary + + After crossing the boundary, the other side MUST perform an + allocation to copy the data and is therefore responsible for + freeing that memory later. + +5. No touching other language's enums + + Rust enums should never be touched from C (nor can they be safely + `#[repr(C)]`) nor vice versa: + + > "The chosen size is the default enum size for the target platform's C + > ABI. Note that enum representation in C is implementation defined, so this is + > really a "best guess". In particular, this may be incorrect when the C code + > of interest is compiled with certain flags." + + (from https://gankro.github.io/nomicon/other-reprs.html) + +6. Type safety + + Wherever possible and sensical, you SHOULD create new types in a + manner which prevents type confusion or misuse. For example, + rather than using an untyped mapping between strings and integers + like so: + + use std::collections::HashMap; + + pub fn get_elements_with_over_9000_points(map: &HashMap<String, usize>) -> Vec<String> { + ... + } + + It would be safer to define a new type, such that some other usage + of `HashMap<String, usize>` cannot be confused for this type: + + pub struct DragonBallZPowers(pub HashMap<String, usize>); + + impl DragonBallZPowers { + pub fn over_nine_thousand<'a>(&'a self) -> Vec<&'a String> { + let mut powerful_enough: Vec<&'a String> = Vec::with_capacity(5); + + for (character, power) in &self.0 { + if *power > 9000 { + powerful_enough.push(character); + } + } + powerful_enough + } + } + + Note the following code, which uses Rust's type aliasing, is valid + but it does NOT meet the desired type safety goals: + + pub type Power = usize; + + pub fn over_nine_thousand(power: &Power) -> bool { + if *power > 9000 { + return true; + } + false + } + + // We can still do the following: + let his_power: usize = 9001; + over_nine_thousand(&his_power); + +7. Unsafe mucking around with lifetimes + + Because lifetimes are technically, in type theory terms, a kind, i.e. a + family of types, individual lifetimes can be treated as types. For example, + one can arbitrarily extend and shorten lifetime using `std::mem::transmute`: + + struct R<'a>(&'a i32); + + unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> { + std::mem::transmute::<R<'b>, R<'static>>(r) + } + + unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> { + std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r) + } + + Calling `extend_lifetime()` would cause an `R` passed into it to live forever + for the life of the program (the `'static` lifetime). Similarly, + `shorten_invariant_lifetime()` could be used to take something meant to live + forever, and cause it to disappear! This is incredibly unsafe. If you're + going to be mucking around with lifetimes like this, first, you better have + an extremely good reason, and second, you may as be honest and explicit about + it, and for ferris' sake just use a raw pointer. + + In short, just because lifetimes can be treated like types doesn't mean you + should do it. + +8. Doing excessively unsafe things when there's a safer alternative + + Similarly to #7, often there are excessively unsafe ways to do a task and a + simpler, safer way. You MUST choose the safer option where possible. + + For example, `std::mem::transmute` can be abused in ways where casting with + `as` would be both simpler and safer: + + // Don't do this + let ptr = &0; + let ptr_num_transmute = unsafe { std::mem::transmute::<&i32, usize>(ptr)}; + + // Use an `as` cast instead + let ptr_num_cast = ptr as *const i32 as usize; + + In fact, using `std::mem::transmute` for *any* reason is a code smell and as + such SHOULD be avoided. + +9. Casting integers with `as` + + This is generally fine to do, but it has some behaviours which you should be + aware of. Casting down chops off the high bits, e.g.: + + let x: u32 = 4294967295; + println!("{}", x as u16); // prints 65535 + + Some cases which you MUST NOT do include: + + * Casting an `u128` down to an `f32` or vice versa (e.g. + `u128::MAX as f32` but this isn't only a problem with overflowing + as it is also undefined behaviour for `42.0f32 as u128`), + + * Casting between integers and floats when the thing being cast + cannot fit into the type it is being casted into, e.g.: + + println!("{}", 42949.0f32 as u8); // prints 197 in debug mode and 0 in release + println!("{}", 1.04E+17 as u8); // prints 0 in both modes + println!("{}", (0.0/0.0) as i64); // prints whatever the heck LLVM wants + + Because this behaviour is undefined, it can even produce segfaults in + safe Rust code. For example, the following program built in release + mode segfaults: + + #[inline(never)] + pub fn trigger_ub(sl: &[u8; 666]) -> &[u8] { + // Note that the float is out of the range of `usize`, invoking UB when casting. + let idx = 1e99999f64 as usize; + &sl[idx..] // The bound check is elided due to `idx` being of an undefined value. + } + + fn main() { + println!("{}", trigger_ub(&[1; 666])[999999]); // ~ out of bound + } + + And in debug mode panics with: + + thread 'main' panicked at 'slice index starts at 140721821254240 but ends at 666', /checkout/src/libcore/slice/mod.rs:754:4 diff --git a/doc/HACKING/GettingStarted.md b/doc/HACKING/GettingStarted.md index 0295adc1ff..0c42404634 100644 --- a/doc/HACKING/GettingStarted.md +++ b/doc/HACKING/GettingStarted.md @@ -11,8 +11,9 @@ whole Tor ecosystem.) If you are looking for a more bare-bones, less user-friendly information -dump of important information, you might like reading doc/HACKING -instead. You should probably read it before you write your first patch. +dump of important information, you might like reading the "torguts" +documents linked to below. You should probably read it before you write +your first patch. Required background diff --git a/doc/HACKING/GettingStartedRust.md b/doc/HACKING/GettingStartedRust.md new file mode 100644 index 0000000000..a5253b46a6 --- /dev/null +++ b/doc/HACKING/GettingStartedRust.md @@ -0,0 +1,161 @@ + + Hacking on Rust in Tor +======================== + + Getting Started +----------------- + +Please read or review our documentation on Rust coding standards +(`.../doc/HACKING/CodingStandardsRust.md`) before doing anything. + +Please also read +[the Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). We aim +to follow the good example set by the Rust community and be excellent to one +another. Let's be careful with each other, so we can be memory-safe together! + +Next, please contact us before rewriting anything! Rust in Tor is still an +experiment. It is an experiment that we very much want to see succeed, so we're +going slowly and carefully. For the moment, it's also a completely +volunteer-driven effort: while many, if not most, of us are paid to work on Tor, +we are not yet funded to write Rust code for Tor. Please be patient with the +other people who are working on getting more Rust code into Tor, because they +are graciously donating their free time to contribute to this effort. + + Resources for learning Rust +----------------------------- + +**Beginning resources** + +The primary resource for learning Rust is +[The Book](https://doc.rust-lang.org/book/). If you'd like to start writing +Rust immediately, without waiting for anything to install, there is +[an interactive browser-based playground](https://play.rust-lang.org/). + +**Advanced resources** + +If you're interested in playing with various Rust compilers and viewing a very +nicely displayed output of the generated assembly, there is +[the Godbolt compiler explorer](https://rust.godbolt.org/) + +For learning how to write unsafe Rust, read +[The Rustonomicon](https://doc.rust-lang.org/nomicon/). + +For learning everything you ever wanted to know about Rust macros, there is +[The Little Book of Rust Macros](https://danielkeep.github.io/tlborm/book/index.html). + +For learning more about FFI and Rust, see Jake Goulding's +[Rust FFI Omnibus](http://jakegoulding.com/rust-ffi-omnibus/). + + Compiling Tor with Rust enabled +--------------------------------- + +You will need to run the `configure` script with the `--enable-rust` flag to +explicitly build with Rust. Additionally, you will need to specify where to +fetch Rust dependencies, as we allow for either fetching dependencies from Cargo +or specifying a local directory. + +**Fetch dependencies from Cargo** + + ./configure --enable-rust --enable-cargo-online-mode + +**Using a local dependency cache** + +**NOTE**: local dependency caches which were not *originally* created via + `--enable-cargo-online-mode` are broken. See https://bugs.torproject.org/22907 + +To specify a local directory: + + RUST_DEPENDENCIES='path_to_dependencies_directory' ./configure --enable-rust + +(Note that RUST_DEPENDENCIES must be the full path to the directory; it cannot +be relative.) + +You'll need the following Rust dependencies (as of this writing): + + libc==0.2.22 + +To get them, do: + + mkdir path_to_dependencies_directory + cd path_to_dependencies_directory + git clone https://github.com/rust-lang/libc + cd libc + git checkout 0.2.22 + cargo package + cd .. + ln -s libc/target/package/libc-0.2.22 libc-0.2.22 + + + Identifying which modules to rewrite +====================================== + +The places in the Tor codebase that are good candidates for porting to Rust are: + +1. loosely coupled to other Tor submodules, +2. have high test coverage, and +3. would benefit from being implemented in a memory safe language. + +Help in either identifying places such as this, or working to improve existing +areas of the C codebase by adding regression tests and simplifying dependencies, +would be really helpful. + +Furthermore, as submodules in C are implemented in Rust, this is a good +opportunity to refactor, add more tests, and split modules into smaller areas of +responsibility. + +A good first step is to build a module-level callgraph to understand how +interconnected your target module is. + + git clone https://git.torproject.org/user/nickm/calltool.git + cd tor + CFLAGS=0 ./configure + ../calltool/src/main.py module_callgraph + +The output will tell you each module name, along with a set of every module that +the module calls. Modules which call fewer other modules are better targets. + + Writing your Rust module +========================== + +Strive to change the C API as little as possible. + +We are currently targetting Rust nightly, *for now*. We expect this to change +moving forward, as we understand more about which nightly features we need. It +is on our TODO list to try to cultivate good standing with various distro +maintainers of `rustc` and `cargo`, in order to ensure that whatever version we +solidify on is readily available. + + Adding your Rust module to Tor's build system +----------------------------------------------- + +0. Your translation of the C module should live in its own crate(s) + in the `.../tor/src/rust/` directory. +1. Add your crate to `.../tor/src/rust/Cargo.toml`, in the + `[workspace.members]` section. +2. Append your crate's static library to the `rust_ldadd` definition + (underneath `if USE_RUST`) in `.../tor/Makefile.am`. + + How to test your Rust code +---------------------------- + +Everything should be tested full stop. Even non-public functionality. + +Be sure to edit `.../tor/src/test/test_rust.sh` to add the name of your crate to +the `crates` variable! This will ensure that `cargo test` is run on your crate. + +Configure Tor's build system to build with Rust enabled: + + ./configure --enable-fatal-warnings --enable-rust --enable-cargo-online-mode + +Tor's test should be run by doing: + + make check + +Tor's integration tests should also pass: + + make test-stem + + Submitting a patch +===================== + +Please follow the instructions in `.../doc/HACKING/GettingStarted.md`. diff --git a/doc/HACKING/HelpfulTools.md b/doc/HACKING/HelpfulTools.md index b8ba2aa408..f919d08ec1 100644 --- a/doc/HACKING/HelpfulTools.md +++ b/doc/HACKING/HelpfulTools.md @@ -111,15 +111,19 @@ Running gcov for unit test coverage (On OSX, you'll need to start with `--enable-coverage CC=clang`.) -Then, look at the .gcov files in `coverage-output`. '-' before a line means -that the compiler generated no code for that line. '######' means that the -line was never reached. Lines with numbers were called that number of times. - If that doesn't work: * Try configuring Tor with `--disable-gcc-hardening` * You might need to run `make clean` after you run `./configure`. +Then, look at the .gcov files in `coverage-output`. '-' before a line means +that the compiler generated no code for that line. '######' means that the +line was never reached. Lines with numbers were called that number of times. + +For more details about how to read gcov output, see the [Invoking +gcov](https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html) chapter +of the GCC manual. + If you make changes to Tor and want to get another set of coverage results, you can run `make reset-gcov` to clear the intermediary gcov output. @@ -128,9 +132,13 @@ a meaningful diff between them, you can run: ./scripts/test/cov-diff coverage-output1 coverage-output2 | less -In this diff, any lines that were visited at least once will have coverage -"1". This lets you inspect what you (probably) really want to know: which -untested lines were changed? Are there any new untested lines? +In this diff, any lines that were visited at least once will have coverage "1", +and line numbers are deleted. This lets you inspect what you (probably) really +want to know: which untested lines were changed? Are there any new untested +lines? + +If you run ./scripts/test/cov-exclude, it marks excluded unreached +lines with 'x', and excluded reached lines with '!!!'. Running integration tests ------------------------- diff --git a/doc/HACKING/HowToReview.md b/doc/HACKING/HowToReview.md index d53318942f..2d1f3d1c9e 100644 --- a/doc/HACKING/HowToReview.md +++ b/doc/HACKING/HowToReview.md @@ -19,6 +19,8 @@ Top-level smell-checks - Does `make check-spaces` pass? +- Does `make check-changes` pass? + - Does it have a reasonable amount of tests? Do they pass? Do they leak memory? @@ -32,6 +34,7 @@ Top-level smell-checks - If this changes Tor's behavior on the wire, is there a design proposal? +- If this changes anything in the code, is there a "changes" file? Let's look at the code! diff --git a/doc/HACKING/ReleasingTor.md b/doc/HACKING/ReleasingTor.md index 4ece4d7a1d..62029b44f0 100644 --- a/doc/HACKING/ReleasingTor.md +++ b/doc/HACKING/ReleasingTor.md @@ -8,7 +8,11 @@ new Tor release: === 0. Preliminaries 1. Get at least three of weasel/arma/Sebastian/Sina to put the new - version number in their approved versions list. + version number in their approved versions list. Give them a few + days to do this if you can. + +2. If this is going to be an important security release, give the packagers + some advance warning: See this list of packagers in IV.3 below. === I. Make sure it works @@ -26,19 +30,24 @@ new Tor release: What about Coverity Scan? + What about clan scan-build? + Does 'make distcheck' complain? - How about 'make test-stem' and 'make test-network'? + How about 'make test-stem' and 'make test-network' and + `make test-network-full`? - Are all those tests still happy with --enable-expensive-hardening ? Any memory leaks? -=== II. Write a changelog. +=== II. Write a changelog -1. Gather the `changes/*` files into a changelog entry, rewriting many +1a. (Alpha release variant) + + Gather the `changes/*` files into a changelog entry, rewriting many of them and reordering to focus on what users and funders would find interesting and understandable. @@ -83,6 +92,15 @@ new Tor release: 4. Run `./scripts/maint/format_changelog.py --inplace` to make it prettier +1b. (old-stable release variant) + + For stable releases that backport things from later, we try to compose + their releases, we try to make sure that we keep the changelog entries + identical to their original versions, with a 'backport from 0.x.y.z' + note added to each section. So in this case, once you have the items + from the changes files copied together, don't use them to build a new + changelog: instead, look up the corrected versions that were merged + into ChangeLog in the master branch, and use those. 2. Compose a short release blurb to highlight the user-facing changes. Insert said release blurb into the ChangeLog stanza. If it's @@ -109,7 +127,7 @@ new Tor release: === III. Making the source release. 1. In `maint-0.?.x`, bump the version number in `configure.ac` and run - `scripts/maint/updateVersions.pl` to update version numbers in other + `perl scripts/maint/updateVersions.pl` to update version numbers in other places, and commit. Then merge `maint-0.?.x` into `release-0.?.x`. (NOTE: To bump the version number, edit `configure.ac`, and then run @@ -147,10 +165,13 @@ new Tor release: - {weasel,gk,mikeperry} at torproject dot org - {blueness} at gentoo dot org - {paul} at invizbox dot io + - {vincent} at invizbox dot com - {lfleischer} at archlinux dot org - {Nathan} at freitas dot net - {mike} at tig dot as - {tails-rm} at boum dot org + - {simon} at sdeziel.info + - {yuri} at rawbw.com 4. Add the version number to Trac. To do this, go to Trac, log in, select "Admin" near the top of the screen, then select "Versions" from @@ -162,7 +183,7 @@ new Tor release: 5. Mail the release blurb and ChangeLog to tor-talk (development release) or tor-announce (stable). - Post the changelog on the the blog as well. You can generate a + Post the changelog on the blog as well. You can generate a blog-formatted version of the changelog with the -B option to format-changelog. diff --git a/doc/HACKING/WritingTests.md b/doc/HACKING/WritingTests.md index 4dae41e922..cc393494ec 100644 --- a/doc/HACKING/WritingTests.md +++ b/doc/HACKING/WritingTests.md @@ -91,6 +91,9 @@ coverage percentage. For a summary of the test coverage for each _function_, run `./scripts/test/cov-display -f ${TMPDIR}/*`. +For more details on using gcov, including the helper scripts in +scripts/test, see HelpfulTools.md. + ### Comparing test coverage Sometimes it's useful to compare test coverage for a branch you're writing to @@ -117,7 +120,8 @@ with LCOV_EXCL_START... LCOV_EXCL_STOP. Note that older versions of lcov don't understand these lines. You can post-process .gcov files to make these lines 'unreached' by -running ./scripts/test/cov-exclude on them. +running ./scripts/test/cov-exclude on them. It marks excluded +unreached lines with 'x', and excluded reached lines with '!!!'. Note: you should never do this unless the line is meant to 100% unreachable by actual code. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index e17c111919..fc285ebe1b 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -128,6 +128,16 @@ COMMAND-LINE OPTIONS the passphrase, including any trailing newlines. Default: read from the terminal. +[[opt-key-expiration]] **--key-expiration** [**purpose**]:: + The **purpose** specifies which type of key certificate to determine + the expiration of. The only currently recognised **purpose** is + "sign". + + + + Running "tor --key-expiration sign" will attempt to find your signing + key certificate and will output, both in the logs as well as to stdout, + the signing key certificate's expiration time in ISO-8601 format. + For example, the output sent to stdout will be of the form: + "signing-cert-expiry: 2017-07-25 08:30:15 UTC" Other options can be specified on the command-line in the format "--option value", in the format "option value", or in a configuration file. For @@ -241,7 +251,9 @@ GENERAL OPTIONS [[ClientTransportPlugin]] **ClientTransportPlugin** __transport__ socks4|socks5 __IP__:__PORT__:: **ClientTransportPlugin** __transport__ exec __path-to-binary__ [options]:: In its first form, when set along with a corresponding Bridge line, the Tor - client forwards its traffic to a SOCKS-speaking proxy on "IP:PORT". It's the + client forwards its traffic to a SOCKS-speaking proxy on "IP:PORT". + (IPv4 addresses should written as-is; IPv6 addresses should be wrapped in + square brackets.) It's the duty of that proxy to properly forward the traffic to the bridge. + + In its second form, when set along with a corresponding Bridge line, the Tor @@ -258,7 +270,8 @@ GENERAL OPTIONS [[ServerTransportListenAddr]] **ServerTransportListenAddr** __transport__ __IP__:__PORT__:: When this option is set, Tor will suggest __IP__:__PORT__ as the listening address of any pluggable transport proxy that tries to - launch __transport__. + launch __transport__. (IPv4 addresses should written as-is; IPv6 + addresses should be wrapped in square brackets.) [[ServerTransportOptions]] **ServerTransportOptions** __transport__ __k=v__ __k=v__ ...:: When this option is set, Tor will pass the __k=v__ parameters to @@ -402,7 +415,7 @@ GENERAL OPTIONS DataDirectory. If the option is set to 1, make the DataDirectory readable by the default GID. (Default: 0) -[[FallbackDir]] **FallbackDir** __address__:__port__ orport=__port__ id=__fingerprint__ [weight=__num__] [ipv6=__address__:__orport__]:: +[[FallbackDir]] **FallbackDir** __ipv4address__:__port__ orport=__port__ id=__fingerprint__ [weight=__num__] [ipv6=**[**__ipv6address__**]**:__orport__]:: When we're unable to connect to any directory cache for directory info (usually because we don't know about any yet) we try a directory authority. Clients also simultaneously try a FallbackDir, to avoid hangs on client @@ -418,7 +431,7 @@ GENERAL OPTIONS FallbackDir line is present, it replaces the hard-coded FallbackDirs, regardless of the value of UseDefaultFallbackDirs.) (Default: 1) -[[DirAuthority]] **DirAuthority** [__nickname__] [**flags**] __address__:__port__ __fingerprint__:: +[[DirAuthority]] **DirAuthority** [__nickname__] [**flags**] __ipv4address__:__port__ __fingerprint__:: Use a nonstandard authoritative directory server at the provided address and port, with the specified key fingerprint. This option can be repeated many times, for multiple authoritative directory servers. Flags are @@ -432,13 +445,16 @@ GENERAL OPTIONS with probability proportional to that weight (default 1.0). If a flag "v3ident=**fp**" is given, the dirserver is a v3 directory authority whose v3 long-term signing key has the fingerprint **fp**. Lastly, - if an "ipv6=__address__:__orport__" flag is present, then the directory + if an "ipv6=**[**__ipv6address__**]**:__orport__" flag is present, then + the directory authority is listening for IPv6 connections on the indicated IPv6 address and OR Port. + + - Tor will contact the authority at __address__:__port__ (the DirPort) to - download directory documents. If an IPv6 address is supplied, Tor will - also download directory documents at the IPv6 address on the DirPort. + + Tor will contact the authority at __ipv4address__ to + download directory documents. The provided __port__ value is a dirport; + clients ignore this in favor of the specified "orport=" value. If an + IPv6 ORPort is supplied, Tor will + also download directory documents at the IPv6 ORPort. + + If no **DirAuthority** line is given, Tor will use the default directory authorities. NOTE: this option is intended for setting up a private Tor @@ -452,9 +468,9 @@ GENERAL OPTIONS chosen with their regular weights, multiplied by this number, which should be 1.0 or less. (Default: 1.0) -[[AlternateDirAuthority]] **AlternateDirAuthority** [__nickname__] [**flags**] __address__:__port__ __fingerprint__ + +[[AlternateDirAuthority]] **AlternateDirAuthority** [__nickname__] [**flags**] __ipv4address__:__port__ __fingerprint__ + -[[AlternateBridgeAuthority]] **AlternateBridgeAuthority** [__nickname__] [**flags**] __address__:__port__ __ fingerprint__:: +[[AlternateBridgeAuthority]] **AlternateBridgeAuthority** [__nickname__] [**flags**] __ipv4address__:__port__ __ fingerprint__:: These options behave as DirAuthority, but they replace fewer of the default directory authorities. Using AlternateDirAuthority replaces the default Tor directory authorities, but @@ -521,13 +537,14 @@ GENERAL OPTIONS [[HTTPProxy]] **HTTPProxy** __host__[:__port__]:: Tor will make all its directory requests through this host:port (or host:80 if port is not specified), rather than connecting directly to any directory - servers. + servers. (DEPRECATED: As of 0.3.1.0-alpha you should use HTTPSProxy.) [[HTTPProxyAuthenticator]] **HTTPProxyAuthenticator** __username:password__:: If defined, Tor will use this username:password for Basic HTTP proxy authentication, as in RFC 2617. This is currently the only form of HTTP proxy authentication that Tor supports; feel free to submit a patch if you - want it to support others. + want it to support others. (DEPRECATED: As of 0.3.1.0-alpha you should use + HTTPSProxyAuthenticator.) [[HTTPSProxy]] **HTTPSProxy** __host__[:__port__]:: Tor will make all its OR (SSL) connections through this host:port (or @@ -545,8 +562,10 @@ GENERAL OPTIONS [[Sandbox]] **Sandbox** **0**|**1**:: If set to 1, Tor will run securely through the use of a syscall sandbox. Otherwise the sandbox will be disabled. The option is currently an - experimental feature. Can not be changed while tor is running. - + experimental feature. It only works on Linux-based operating systems, + and only when Tor has been built with the libseccomp library. This option + can not be changed while tor is running. + + When the Sandbox is 1, the following options can not be changed when tor is running: Address @@ -641,6 +660,7 @@ GENERAL OPTIONS is only useful when you have multiple network interfaces, and you want all of Tor's outgoing connections to use a single one. This option may be used twice, once with an IPv4 address and once with an IPv6 address. + IPv6 addresses should be wrapped in square brackets. This setting will be ignored for connections to the loopback addresses (127.0.0.0/8 and ::1). @@ -649,14 +669,17 @@ GENERAL OPTIONS originate from the IP address specified. This option overrides **OutboundBindAddress** for the same IP version. This option may be used twice, once with an IPv4 address and once with an IPv6 - address. This setting will be ignored for connections to the loopback + address. IPv6 addresses should be wrapped in square brackets. + This setting will be ignored for connections to the loopback addresses (127.0.0.0/8 and ::1). [[OutboundBindAddressExit]] **OutboundBindAddressExit** __IP__:: Make all outbound exit connections originate from the IP address specified. This option overrides **OutboundBindAddress** for the same IP version. This option may be used twice, once with an IPv4 - address and once with an IPv6 address. This setting will be ignored + address and once with an IPv6 address. + IPv6 addresses should be wrapped in square brackets. + This setting will be ignored for connections to the loopback addresses (127.0.0.0/8 and ::1). [[PidFile]] **PidFile** __FILE__:: @@ -760,11 +783,57 @@ GENERAL OPTIONS circuits. If the option is set to "default", we obey a parameter in the consensus document. (Default: auto) +[[NoExec]] **NoExec** **0**|**1**:: + If this option is set to 1, then Tor will never launch another + executable, regardless of the settings of PortForwardingHelper, + ClientTransportPlugin, or ServerTransportPlugin. Once this + option has been set to 1, it cannot be set back to 0 without + restarting Tor. (Default: 0) + +[[Schedulers]] **Schedulers** **KIST**|**KISTLite**|**Vanilla**:: + Specify the scheduler type that tor should use. The scheduler is + responsible for moving data around within a Tor process. This is an ordered + list by priority which means that the first value will be tried first and if + unavailable, the second one is tried and so on. It is possible to change + these values at runtime. This option mostly effects relays, and most + operators should leave it set to its default value. + (Default: KIST,KISTLite,Vanilla) + + + The possible scheduler types are: + + + **KIST**: Kernel-Informed Socket Transport. Tor will use TCP information + from the kernel to make informed decisions regarding how much data to send + and when to send it. KIST also handles traffic in batches (see + KISTSchedRunInterval) in order to improve traffic prioritization decisions. + As implemented, KIST will only work on Linux kernel version 2.6.39 or + higher. + + + **KISTLite**: Same as KIST but without kernel support. Tor will use all + the same mechanics as with KIST, including the batching, but its decisions + regarding how much data to send will not be as good. KISTLite will work on + all kernels and operating systems, and the majority of the benefits of KIST + are still realized with KISTLite. + + + **Vanilla**: The scheduler that Tor used before KIST was implemented. It + sends as much data as possible, as soon as possible. Vanilla will work on + all kernels and operating systems. + +[[KISTSchedRunInterval]] **KISTSchedRunInterval** __NUM__ **msec**:: + If KIST or KISTLite is used in the Schedulers option, this controls at which + interval the scheduler tick is. If the value is 0 msec, the value is taken + from the consensus if possible else it will fallback to the default 10 + msec. Maximum possible value is 100 msec. (Default: 0 msec) + +[[KISTSockBufSizeFactor]] **KISTSockBufSizeFactor** __NUM__:: + If KIST is used in Schedulers, this is a multiplier of the per-socket + limit calculation of the KIST algorithm. (Default: 1.0) + CLIENT OPTIONS -------------- The following options are useful only for clients (that is, if -**SocksPort**, **TransPort**, **DNSPort**, or **NATDPort** is non-zero): +**SocksPort**, **HTTPTunnelPort**, **TransPort**, **DNSPort**, or +**NATDPort** is non-zero): [[Bridge]] **Bridge** [__transport__] __IP__:__ORPort__ [__fingerprint__]:: When set along with UseBridges, instructs Tor to use the relay at @@ -901,7 +970,7 @@ The following options are useful only for clients (that is, if The ExcludeNodes option overrides this option: any node listed in both ExitNodes and ExcludeNodes is treated as excluded. + + - The .exit address notation, if enabled via AllowDotExit, overrides + The .exit address notation, if enabled via MapAddress, overrides this option. [[EntryNodes]] **EntryNodes** __node__,__node__,__...__:: @@ -941,7 +1010,7 @@ The following options are useful only for clients (that is, if **FascistFirewall** is set. This option is deprecated; use ReachableAddresses instead. (Default: 80, 443) -[[ReachableAddresses]] **ReachableAddresses** __ADDR__[/__MASK__][:__PORT__]...:: +[[ReachableAddresses]] **ReachableAddresses** __IP__[/__MASK__][:__PORT__]...:: A comma-separated list of IP addresses and ports that your firewall allows you to connect to. The format is as for the addresses in ExitPolicy, except that "accept" is understood unless "reject" is explicitly provided. For @@ -950,14 +1019,15 @@ The following options are useful only for clients (that is, if 99, rejects port 80 connections to net 18, and accepts connections to port 80 otherwise. (Default: \'accept \*:*'.) -[[ReachableDirAddresses]] **ReachableDirAddresses** __ADDR__[/__MASK__][:__PORT__]...:: +[[ReachableDirAddresses]] **ReachableDirAddresses** __IP__[/__MASK__][:__PORT__]...:: Like **ReachableAddresses**, a list of addresses and ports. Tor will obey these restrictions when fetching directory information, using standard HTTP GET requests. If not set explicitly then the value of **ReachableAddresses** is used. If **HTTPProxy** is set then these - connections will go through that proxy. + connections will go through that proxy. (DEPRECATED: This option has + had no effect for some time.) -[[ReachableORAddresses]] **ReachableORAddresses** __ADDR__[/__MASK__][:__PORT__]...:: +[[ReachableORAddresses]] **ReachableORAddresses** __IP__[/__MASK__][:__PORT__]...:: Like **ReachableAddresses**, a list of addresses and ports. Tor will obey these restrictions when connecting to Onion Routers, using TLS/SSL. If not set explicitly then the value of **ReachableAddresses** is used. If @@ -1088,7 +1158,9 @@ The following options are useful only for clients (that is, if Unsupported and force-disabled when using Unix domain sockets.) **IsolateSOCKSAuth**;; Don't share circuits with streams for which different - SOCKS authentication was provided. (On by default; + SOCKS authentication was provided. (For HTTPTunnelPort + connections, this option looks at the Proxy-Authorization and + X-Tor-Stream-Isolation headers. On by default; you can disable it with **NoIsolateSOCKSAuth**.) **IsolateClientProtocol**;; Don't share circuits with streams using a different protocol. @@ -1252,11 +1324,10 @@ The following options are useful only for clients (that is, if default to 1 if the consensus parameter isn't set. (Default: 0) [[NumDirectoryGuards]] **NumDirectoryGuards** __NUM__:: - If UseEntryGuardsAsDirectoryGuards is enabled, we try to make sure we have - at least NUM routers to use as directory guards. If this option is set to - 0, use the value from the guard-n-primary-dir-guards-to-use consensus - parameter, and default to 3 if the consensus parameter isn't set. - (Default: 0) + If UseEntryGuards is set to 1, we try to make sure we have at least NUM + routers to use as directory guards. If this option is set to 0, use the + value from the guard-n-primary-dir-guards-to-use consensus parameter, and + default to 3 if the consensus parameter isn't set. (Default: 0) [[GuardLifetime]] **GuardLifetime** __N__ **days**|**weeks**|**months**:: If nonzero, and UseEntryGuards is set, minimum time to keep a guard before @@ -1278,9 +1349,9 @@ The following options are useful only for clients (that is, if helps to determine whether an application using Tor is possibly leaking DNS requests. (Default: 0) -[[VirtualAddrNetworkIPv4]] **VirtualAddrNetworkIPv4** __Address__/__bits__ + +[[VirtualAddrNetworkIPv4]] **VirtualAddrNetworkIPv4** __IPv4Address__/__bits__ + -[[VirtualAddrNetworkIPv6]] **VirtualAddrNetworkIPv6** [__Address__]/__bits__:: +[[VirtualAddrNetworkIPv6]] **VirtualAddrNetworkIPv6** [__IPv6Address__]/__bits__:: When Tor needs to assign a virtual (unused) address because of a MAPADDRESS command from the controller or the AutomapHostsOnResolve feature, Tor picks an unassigned address from this range. (Defaults: @@ -1303,11 +1374,13 @@ The following options are useful only for clients (that is, if resolved. This helps trap accidental attempts to resolve URLs and so on. (Default: 0) -[[AllowDotExit]] **AllowDotExit** **0**|**1**:: - If enabled, we convert "www.google.com.foo.exit" addresses on the - SocksPort/TransPort/NATDPort into "www.google.com" addresses that exit from - the node "foo". Disabled by default since attacking websites and exit - relays can use it to manipulate your path selection. (Default: 0) +[[HTTPTunnelPort]] **HTTPTunnelPort** \['address':]__port__|**auto** [_isolation flags_]:: + Open this port to listen for proxy connections using the "HTTP CONNECT" + protocol instead of SOCKS. Set this to 0 + 0 if you don't want to allow "HTTP CONNECT" connections. Set the port + to "auto" to have Tor pick a port for you. This directive can be + specified multiple times to bind to multiple addresses/ports. See + SOCKSPort for an explanation of isolation flags. (Default: 0) [[TransPort]] **TransPort** \['address':]__port__|**auto** [_isolation flags_]:: Open this port to listen for transparent proxy connections. Set this to @@ -1378,12 +1451,12 @@ The following options are useful only for clients (that is, if [[ClientDNSRejectInternalAddresses]] **ClientDNSRejectInternalAddresses** **0**|**1**:: If true, Tor does not believe any anonymously retrieved DNS answer that tells it that an address resolves to an internal address (like 127.0.0.1 or - 192.168.0.1). This option prevents certain browser-based attacks; don't - turn it off unless you know what you're doing. (Default: 1) + 192.168.0.1). This option prevents certain browser-based attacks; it + is not allowed to be set on the default network. (Default: 1) [[ClientRejectInternalAddresses]] **ClientRejectInternalAddresses** **0**|**1**:: If true, Tor does not try to fulfill requests to connect to an internal - address (like 127.0.0.1 or 192.168.0.1) __unless a exit node is + address (like 127.0.0.1 or 192.168.0.1) __unless an exit node is specifically requested__ (for example, via a .exit hostname, or a controller request). If true, multicast DNS hostnames for machines on the local network (of the form *.local) are also rejected. (Default: 1) @@ -1520,7 +1593,8 @@ The following options are useful only for clients (that is, if server has both. (Tor also prefers an IPv6 DirPort if IPv4Client is set to 0.) If this option is set to auto, clients prefer IPv4. Other things may influence the choice. This option breaks a tie to the favor of IPv6. - (Default: auto) + (Default: auto) (DEPRECATED: This option has had no effect for some + time.) [[ClientPreferIPv6ORPort]] **ClientPreferIPv6ORPort** **0**|**1**|**auto**:: If this option is set to 1, Tor prefers an OR port with an IPv6 @@ -1549,8 +1623,8 @@ The following options are useful only for clients (that is, if live consensus). Only used by clients fetching from a list of fallback directory mirrors. This schedule is advanced by (potentially concurrent) connection attempts, unlike other schedules, which are advanced by - connection failures. (Default: 10, 11, 3600, 10800, 25200, 54000, - 111600, 262800) + connection failures. (Default: 6, 11, 3600, 10800, 25200, 54000, 111600, + 262800) [[ClientBootstrapConsensusFallbackDownloadSchedule]] **ClientBootstrapConsensusFallbackDownloadSchedule** __N__,__N__,__...__:: Schedule for when clients should download consensuses from fallback @@ -1580,7 +1654,7 @@ The following options are useful only for clients (that is, if [[ClientBootstrapConsensusMaxInProgressTries]] **ClientBootstrapConsensusMaxInProgressTries** __NUM__:: Try this many simultaneous connections to download a consensus before - waiting for one to complete, timeout, or error out. (Default: 4) + waiting for one to complete, timeout, or error out. (Default: 3) SERVER OPTIONS -------------- @@ -1589,12 +1663,13 @@ The following options are useful only for servers (that is, if ORPort is non-zero): [[Address]] **Address** __address__:: - The IP address or fully qualified domain name of this server (e.g. - moria.mit.edu). You can leave this unset, and Tor will guess your IP - address. This IP address is the one used to tell clients and other - servers where to find your Tor server; it doesn't affect the IP that your - Tor client binds to. To bind to a different address, use the - *ListenAddress and OutboundBindAddress options. + The IPv4 address of this server, or a fully qualified domain name of + this server that resolves to an IPv4 address. You can leave this + unset, and Tor will try to guess your IPv4 address. This IPv4 + address is the one used to tell clients and other servers where to + find your Tor server; it doesn't affect the address that your server + binds to. To bind to a different address, use the ORPort and + OutboundBindAddress options. [[AssumeReachable]] **AssumeReachable** **0**|**1**:: This option is used when bootstrapping a new Tor network. If set to 1, @@ -1609,6 +1684,17 @@ is non-zero): server descriptor to the bridge database, rather than to the public directory authorities. +[[BridgeDistribution]] **BridgeDistribution** __string__:: + If set along with BridgeRelay, Tor will include a new line in its + bridge descriptor which indicates to the BridgeDB service how it + would like its bridge address to be given out. Set it to "none" if + you want BridgeDB to avoid distributing your bridge address, or "any" to + let BridgeDB decide. (Default: any) + + + Note: as of Oct 2017, the BridgeDB part of this option is not yet + implemented. Until BridgeDB is updated to obey this option, your + bridge will make this request, but it will not (yet) be obeyed. + [[ContactInfo]] **ContactInfo** __email_address__:: Administrative contact information for this relay or bridge. This line can be used to contact you if your relay or bridge is misconfigured or @@ -1802,11 +1888,13 @@ is non-zero): + If this option is set to 0, Tor will not publish its descriptors to any directories. (This is useful if you're testing - out your server, or if you're using a Tor controller that handles directory - publishing for you.) Otherwise, Tor will publish its descriptors of all - type(s) specified. The default is "1", - which means "if running as a server, publish the - appropriate descriptors to the authorities". + out your server, or if you're using a Tor controller that handles + directory publishing for you.) Otherwise, Tor will publish its + descriptors of all type(s) specified. The default is "1", which + means "if running as a relay or bridge, publish descriptors to the + appropriate authorities". Other possibilities are "v3", meaning + "publish as if you're a relay", and "bridge", meaning "publish as + if you're a bridge". [[ShutdownWaitLength]] **ShutdownWaitLength** __NUM__:: When we get a SIGINT and we're a server, we begin shutting down: @@ -1900,7 +1988,7 @@ is non-zero): correct this. This option only affects name lookups that your server does on behalf of clients. (Default: 1) -[[ServerDNSTestAddresses]] **ServerDNSTestAddresses** __address__,__address__,__...__:: +[[ServerDNSTestAddresses]] **ServerDNSTestAddresses** __hostname__,__hostname__,__...__:: When we're detecting DNS hijacking, make sure that these __valid__ addresses aren't getting redirected. If they are, then our DNS is completely useless, and we'll reset our exit policy to "reject \*:*". This option only affects @@ -2327,7 +2415,7 @@ The following options are used to configure a hidden service. [[HiddenServiceVersion]] **HiddenServiceVersion** __version__,__version__,__...__:: A list of rendezvous service descriptor versions to publish for the hidden - service. Currently, only version 2 is supported. (Default: 2) + service. Currently, versions 2 and 3 are supported. (Default: 2) [[HiddenServiceAuthorizeClient]] **HiddenServiceAuthorizeClient** __auth-type__ __client-name__,__client-name__,__...__:: If configured, the hidden service is accessible for authorized clients @@ -2371,7 +2459,7 @@ The following options are used to configure a hidden service. [[HiddenServiceNumIntroductionPoints]] **HiddenServiceNumIntroductionPoints** __NUM__:: Number of introduction points the hidden service will have. You can't - have more than 10. (Default: 3) + have more than 10 for v2 service and 20 for v3. (Default: 3) [[HiddenServiceSingleHopMode]] **HiddenServiceSingleHopMode** **0**|**1**:: **Experimental - Non Anonymous** Hidden Services on a tor instance in @@ -2451,7 +2539,8 @@ The following options are used for running a testing Tor network. TestingClientDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 TestingServerConsensusDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 TestingClientConsensusDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 - TestingBridgeDownloadSchedule 60, 30, 30, 60 + TestingBridgeDownloadSchedule 10, 30, 60 + TestingBridgeBootstrapDownloadSchedule 0, 0, 5, 10, 15, 20, 30, 60 TestingClientMaxIntervalWithoutRequest 5 seconds TestingDirConnectionMaxStall 30 seconds TestingConsensusMaxDownloadTries 80 @@ -2516,8 +2605,16 @@ The following options are used for running a testing Tor network. 1800, 3600, 3600, 3600, 10800, 21600, 43200) [[TestingBridgeDownloadSchedule]] **TestingBridgeDownloadSchedule** __N__,__N__,__...__:: - Schedule for when clients should download bridge descriptors. Changing this - requires that **TestingTorNetwork** is set. (Default: 3600, 900, 900, 3600) + Schedule for when clients should download each bridge descriptor when they + know that one or more of their configured bridges are running. Changing + this requires that **TestingTorNetwork** is set. (Default: 10800, 25200, + 54000, 111600, 262800) + +[[TestingBridgeBootstrapDownloadSchedule]] **TestingBridgeBootstrapDownloadSchedule** __N__,__N__,__...__:: + Schedule for when clients should download each bridge descriptor when they + have just started, or when they can not contact any of their bridges. + Changing this requires that **TestingTorNetwork** is set. (Default: 0, 30, + 90, 600, 3600, 10800, 25200, 54000, 111600, 262800) [[TestingClientMaxIntervalWithoutRequest]] **TestingClientMaxIntervalWithoutRequest** __N__ **seconds**|**minutes**:: When directory clients have only a few descriptors to request, they batch @@ -2844,6 +2941,16 @@ __DataDirectory__**/hashed-fingerprint**:: Only used by bridges. Holds the hashed fingerprint of the bridge's identity key. (That is, the hash of the hash of the identity key.) +__DataDirectory__**/approved-routers**:: + Only used by authoritative directory servers. This file lists + the status of routers by their identity fingerprint. + Each line lists a status and a fingerprint separated by + whitespace. See your **fingerprint** file in the __DataDirectory__ for an + example line. If the status is **!reject** then descriptors from the + given identity (fingerprint) are rejected by this server. If it is + **!invalid** then descriptors are accepted but marked in the directory as + not valid, that is, not recommended. + __DataDirectory__**/v3-status-votes**:: Only for v3 authoritative directory servers. This file contains status votes from all the authoritative directory servers. @@ -2908,6 +3015,11 @@ __HiddenServiceDirectory__**/hostname**:: The <base32-encoded-fingerprint>.onion domain name for this hidden service. If the hidden service is restricted to authorized clients only, this file also contains authorization data for all clients. + + + Note that clients will ignore any extra subdomains prepended to a hidden + service hostname. So if you have "xyz.onion" as your hostname, you + can tell clients to connect to "www.xyz.onion" or "irc.xyz.onion" + for virtual-hosting purposes. __HiddenServiceDirectory__**/private_key**:: The private key for this hidden service. diff --git a/doc/torrc_format.txt b/doc/torrc_format.txt index b175b9fb15..7a4a92a663 100644 --- a/doc/torrc_format.txt +++ b/doc/torrc_format.txt @@ -18,9 +18,10 @@ does, not what it should do. ; specified in RFC5234. ; A file is interpreted as every Entry in the file, in order. - TorrcFile = *Line + TorrcFile = *Line [ UnterminatedLine ] - Line = BlankLine / Entry + Line = BlankLine LF / Entry LF + UnterminatedLine = BlankLine / Entry BlankLine = *WSP OptComment LF BlankLine =/ *WSP LF @@ -69,6 +70,12 @@ does, not what it should do. ; Anything besides NUL and LF NonLF = %x01-%x09 / %x0b - %xff + ; Note that on windows, we open our configuration files in "text" mode, + ; which causes CRLF pairs to be interpreted as LF. So, on windows: + ; LF = [ %x0d ] %x0a + ; but everywhere else, + LF = %0x0a + OCTDIG = '0' - '7' KC = Any character except an isspace() character or '#' or NUL diff --git a/scripts/coccinelle/ceil_div.cocci b/scripts/coccinelle/ceil_div.cocci new file mode 100644 index 0000000000..00843e82c3 --- /dev/null +++ b/scripts/coccinelle/ceil_div.cocci @@ -0,0 +1,6 @@ +@@ +expression n, d; +@@ + +- (((n) + (d) - 1) / (d)) ++ CEIL_DIV(n, d) diff --git a/scripts/coccinelle/test-operator-cleanup b/scripts/coccinelle/test-operator-cleanup new file mode 100755 index 0000000000..e7822542a4 --- /dev/null +++ b/scripts/coccinelle/test-operator-cleanup @@ -0,0 +1,11 @@ +#!/usr/bin/perl -w -p -i + +next if m#^ */\*# or m#^ *\* #; + +s/<([,)])/OP_LT$1/; +s/(?<=[\s,])>([,)])/OP_GT$1/; +#s/>([,)])/OP_GT$1/; +s/==([,)])/OP_EQ$1/; +s/>=([,)])/OP_GE$1/; +s/<=([,)])/OP_LE$1/; +s/!=([,)])/OP_NE$1/; diff --git a/scripts/coccinelle/test_assert_int.cocci b/scripts/coccinelle/test_assert_int.cocci new file mode 100644 index 0000000000..80e86b4f3c --- /dev/null +++ b/scripts/coccinelle/test_assert_int.cocci @@ -0,0 +1,49 @@ +@@ +int e; +constant c; +@@ + +( +- tt_assert(e == c) ++ tt_int_op(e, OP_EQ, c) +| +- tt_assert(e != c) ++ tt_int_op(e, OP_NE, c) +| +- tt_assert(e < c) ++ tt_int_op(e, OP_LT, c) +| +- tt_assert(e <= c) ++ tt_int_op(e, OP_LE, c) +| +- tt_assert(e > c) ++ tt_int_op(e, OP_GT, c) +| +- tt_assert(e >= c) ++ tt_int_op(e, OP_GE, c) +) + +@@ +unsigned int e; +constant c; +@@ + +( +- tt_assert(e == c) ++ tt_uint_op(e, OP_EQ, c) +| +- tt_assert(e != c) ++ tt_uint_op(e, OP_NE, c) +| +- tt_assert(e < c) ++ tt_uint_op(e, OP_LT, c) +| +- tt_assert(e <= c) ++ tt_uint_op(e, OP_LE, c) +| +- tt_assert(e > c) ++ tt_uint_op(e, OP_GT, c) +| +- tt_assert(e >= c) ++ tt_uint_op(e, OP_GE, c) +) diff --git a/scripts/coccinelle/test_assert_null.cocci b/scripts/coccinelle/test_assert_null.cocci new file mode 100644 index 0000000000..3d66e1ee0b --- /dev/null +++ b/scripts/coccinelle/test_assert_null.cocci @@ -0,0 +1,11 @@ +@@ +expression * e; +@@ + +( +- tt_assert(e != NULL) ++ tt_ptr_op(e, OP_NE, NULL) +| +- tt_assert(e == NULL) ++ tt_ptr_op(e, OP_EQ, NULL) +) diff --git a/scripts/coccinelle/test_assert_zero.cocci b/scripts/coccinelle/test_assert_zero.cocci new file mode 100644 index 0000000000..09feaa5fb4 --- /dev/null +++ b/scripts/coccinelle/test_assert_zero.cocci @@ -0,0 +1,5 @@ +@@ +@@ + +- tt_assert(0) ++ tt_abort() diff --git a/scripts/codegen/fuzzing_include_am.py b/scripts/codegen/fuzzing_include_am.py index 6e45c21926..fee6d4494a 100755 --- a/scripts/codegen/fuzzing_include_am.py +++ b/scripts/codegen/fuzzing_include_am.py @@ -7,7 +7,9 @@ FUZZERS = """ diff-apply extrainfo hsdescv2 + hsdescv3 http + http-connect iptsv2 microdesc vrs @@ -30,13 +32,14 @@ FUZZING_LIBS = \ src/common/libor-ctime-testing.a \ src/common/libor-event-testing.a \ src/trunnel/libor-trunnel-testing.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ \ @TOR_SYSTEMD_LIBS@ \ @TOR_LZMA_LIBS@ \ - @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_ZSTD_LIBS@ oss-fuzz-prereqs: \ src/or/libtor-testing.a \ diff --git a/scripts/maint/annotate_ifdef_directives b/scripts/maint/annotate_ifdef_directives new file mode 100755 index 0000000000..368d842e2e --- /dev/null +++ b/scripts/maint/annotate_ifdef_directives @@ -0,0 +1,74 @@ +#!/usr/bin/python +# Copyright (c) 2017, The Tor Project, Inc. +# See LICENSE for licensing information + +import re + +LINE_OBVIOUSNESS_LIMIT = 4 + +class Problem(Exception): + pass + +def uncomment(s): + s = re.sub(r'//.*','',s) + s = re.sub(r'/\*.*','',s) + return s.strip() + +def translate(f_in, f_out): + whole_file = [] + stack = [] + cur_level = whole_file + lineno = 0 + for line in f_in: + lineno += 1 + m = re.match(r'\s*#\s*(if|ifdef|ifndef|else|endif|elif)\b\s*(.*)', + line) + if not m: + f_out.write(line) + continue + command,rest = m.groups() + if command in ("if", "ifdef", "ifndef"): + # The #if directive pushes us one level lower on the stack. + if command == 'ifdef': + rest = "defined(%s)"%uncomment(rest) + elif command == 'ifndef': + rest = "!defined(%s)"%uncomment(rest) + elif rest.endswith("\\"): + rest = rest[:-1]+"..." + + rest = uncomment(rest) + + new_level = [ (command, rest, lineno) ] + stack.append(cur_level) + cur_level = new_level + f_out.write(line) + elif command in ("else", "elif"): + if len(cur_level) == 0 or cur_level[-1][0] == 'else': + raise Problem("Unexpected #%s on %d"% (command,lineno)) + if (len(cur_level) == 1 and command == 'else' and + lineno > cur_level[0][2] + LINE_OBVIOUSNESS_LIMIT): + f_out.write("#else /* !(%s) */\n"%cur_level[0][1]) + else: + f_out.write(line) + cur_level.append((command, rest, lineno)) + else: + assert command == 'endif' + if len(stack) == 0: + raise Problem("Unmatched #%s on %s"% (command,lineno)) + if lineno <= cur_level[0][2] + LINE_OBVIOUSNESS_LIMIT: + f_out.write(line) + elif len(cur_level) == 1 or ( + len(cur_level) == 2 and cur_level[1][0] == 'else'): + f_out.write("#endif /* %s */\n"%cur_level[0][1]) + else: + f_out.write("#endif /* %s || ... */\n"%cur_level[0][1]) + cur_level = stack.pop() + if len(stack) or cur_level != whole_file: + raise Problem("Missing #endif") + +import sys,os +for fn in sys.argv[1:]: + with open(fn+"_OUT", 'w') as output_file: + translate(open(fn, 'r'), output_file) + os.rename(fn+"_OUT", fn) + diff --git a/scripts/maint/checkSpace.pl b/scripts/maint/checkSpace.pl index 6d19d6ccde..9929932cc5 100755 --- a/scripts/maint/checkSpace.pl +++ b/scripts/maint/checkSpace.pl @@ -123,6 +123,10 @@ for my $fn (@ARGV) { if (/([^\s'])\{/) { msg " $1\{:$fn:$.\n"; } + ## Warn about double semi-colons at the end of a line. + if (/;;$/) { + msg " double semi-colons at the end of $. in $fn\n" + } ## Warn about multiple internal spaces. #if (/[^\s,:]\s{2,}[^\s\\=]/) { # msg " X X:$fn:$.\n"; @@ -140,7 +144,7 @@ for my $fn (@ARGV) { $1 ne "switch" and $1 ne "return" and $1 ne "int" and $1 ne "elsif" and $1 ne "WINAPI" and $2 ne "WINAPI" and $1 ne "void" and $1 ne "__attribute__" and $1 ne "op" and - $1 ne "size_t" and $1 ne "double" and + $1 ne "size_t" and $1 ne "double" and $1 ne "uint64_t" and $1 ne "workqueue_reply_t") { msg " fn ():$fn:$.\n"; } diff --git a/scripts/maint/lintChanges.py b/scripts/maint/lintChanges.py index bf06064fa8..d5b8fcae5c 100755 --- a/scripts/maint/lintChanges.py +++ b/scripts/maint/lintChanges.py @@ -20,8 +20,20 @@ KNOWN_GROUPS = set([ "Testing", "Documentation", "Code simplification and refactoring", - "Removed features"]) + "Removed features", + "Deprecated features", + "Directory authority changes"]) +NEEDS_SUBCATEGORIES = set([ + "Minor bugfix", + "Minor bugfixes", + "Major bugfix", + "Major bugfixes", + "Minor feature", + "Minor features", + "Major feature", + "Major features", + ]) def lintfile(fname): have_warned = [] @@ -46,13 +58,11 @@ def lintfile(fname): m = re.match(r'^[ ]{2}o ([^\(:]*)([^:]*):', contents) if not m: - warn("header not in format expected") + warn("Header not in format expected. (' o Foo:' or ' o Foo (Bar):')") elif m.group(1).strip() not in KNOWN_GROUPS: - warn("Weird header: %r" % m.group(1)) - elif (("bugfix" in m.group(1) or "feature" in m.group(1)) and - ("Removed" not in m.group(1)) and - '(' not in m.group(2)): - warn("Missing subcategory on %s" % m.group(1)) + warn("Unrecognized header: %r" % m.group(1)) + elif (m.group(1) in NEEDS_SUBCATEGORIES and '(' not in m.group(2)): + warn("Missing subcategory on %r" % m.group(1)) if m: isBug = ("bug" in m.group(1).lower() or "fix" in m.group(1).lower()) @@ -62,25 +72,46 @@ def lintfile(fname): contents = " ".join(contents.split()) if re.search(r'\#\d{2,}', contents): - warn("don't use a # before ticket numbers") + warn("Don't use a # before ticket numbers. ('bug 1234' not '#1234')") if isBug and not re.search(r'(\d+)', contents): - warn("bugfix does not mention a number") - elif isBug and not re.search(r'Fixes ([a-z ]*)bug (\d+)', contents): - warn("bugfix does not say 'Fixes bug XXX'") + warn("Ticket marked as bugfix, but does not mention a number.") + elif isBug and not re.search(r'Fixes ([a-z ]*)bugs? (\d+)', contents): + warn("Ticket marked as bugfix, but does not say 'Fixes bug XXX'") if re.search(r'[bB]ug (\d+)', contents): if not re.search(r'[Bb]ugfix on ', contents): - warn("bugfix does not say 'bugfix on X.Y.Z'") - elif not re.search('[fF]ixes ([a-z ]*)bug (\d+); bugfix on ', + warn("Bugfix does not say 'bugfix on X.Y.Z'") + elif not re.search('[fF]ixes ([a-z ]*)bugs? (\d+)((, \d+)* and \d+)?; bugfix on ', contents): - warn("bugfix incant is not semicoloned") + warn("Bugfix does not say 'Fixes bug X; bugfix on Y'") elif re.search('tor-([0-9]+)', contents): - warn("do not prefix versions with 'tor-'") - + warn("Do not prefix versions with 'tor-'. ('0.1.2', not 'tor-0.1.2'.)") + + return have_warned != [] + +def files(args): + """Walk through the arguments: for directories, yield their contents; + for files, just yield the files. Only search one level deep, because + that's how the changes directory is laid out.""" + for f in args: + if os.path.isdir(f): + for item in os.listdir(f): + if item.startswith("."): #ignore dotfiles + continue + yield os.path.join(f, item) + else: + yield f if __name__ == '__main__': - for fname in sys.argv[1:]: + problems = 0 + for fname in files(sys.argv[1:]): if fname.endswith("~"): continue - lintfile(fname) + if lintfile(fname): + problems += 1 + + if problems: + sys.exit(1) + else: + sys.exit(0) diff --git a/scripts/maint/run_calltool.sh b/scripts/maint/run_calltool.sh new file mode 100755 index 0000000000..efb8706fea --- /dev/null +++ b/scripts/maint/run_calltool.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# You can find calltool at https://gitweb.torproject.org/user/nickm/calltool.git + +set -e + +if test "x$CALLTOOL_PATH" != "x"; then + PYTHONPATH="${CALLTOOL_PATH}:${PYTHONPATH}" + export PYTHONPATH +fi + +mkdir -p callgraph + +SUBITEMS="fn_graph fn_invgraph fn_scc fn_scc_weaklinks module_graph module_invgraph module_scc module_scc_weaklinks" + +for calculation in $SUBITEMS; do + echo "======== $calculation" + python -m calltool $calculation > callgraph/$calculation +done + +echo <<EOF > callgraph/README +This directory holds output from calltool, as run on Tor. For more +information about each of these files, see the NOTES and README files in +the calltool distribution. + +You can find calltool at + https://gitweb.torproject.org/user/nickm/calltool.git +EOF + diff --git a/scripts/test/cov-diff b/scripts/test/cov-diff index 7da7f0be9d..ed8874d2d3 100755 --- a/scripts/test/cov-diff +++ b/scripts/test/cov-diff @@ -7,9 +7,13 @@ DIRA="$1" DIRB="$2" -for A in $DIRA/*; do - B=$DIRB/`basename $A` - perl -pe 's/^\s*\!*\d+:/ 1:/; s/^([^:]+:)[\d\s]+:/$1/; s/^ *-:(Runs|Programs):.*//;' "$A" > "$A.tmp" +for B in $DIRB/*; do + A=$DIRA/`basename $B` + if [ -f $A ]; then + perl -pe 's/^\s*\!*\d+:/ 1:/; s/^([^:]+:)[\d\s]+:/$1/; s/^ *-:(Runs|Programs):.*//;' "$A" > "$A.tmp" + else + cat /dev/null > "$A.tmp" + fi perl -pe 's/^\s*\!*\d+:/ 1:/; s/^([^:]+:)[\d\s]+:/$1/; s/^ *-:(Runs|Programs):.*//;' "$B" > "$B.tmp" diff -u "$A.tmp" "$B.tmp" rm "$A.tmp" "$B.tmp" diff --git a/scripts/test/coverage b/scripts/test/coverage index f4ae475828..f10c5afaae 100755 --- a/scripts/test/coverage +++ b/scripts/test/coverage @@ -29,7 +29,7 @@ for fn in src/or/*.c src/common/*.c; do gcov -o $on $fn if [ -e $GC ] then - if [ -n $dst ] + if [ -d "$dst" ] then mv $GC $dst/$GC fi diff --git a/scripts/test/scan-build.sh b/scripts/test/scan-build.sh index 36e69e6d00..793adf87e2 100644..100755 --- a/scripts/test/scan-build.sh +++ b/scripts/test/scan-build.sh @@ -5,37 +5,76 @@ # This script is used for running a bunch of clang scan-build checkers # on Tor. +# These don't seem to cause false positives in our code, so let's turn +# them on. CHECKERS="\ - -disable-checker deadcode.DeadStores \ - -enable-checker alpha.core.CastSize \ + -enable-checker alpha.core.CallAndMessageUnInitRefArg \ -enable-checker alpha.core.CastToStruct \ + -enable-checker alpha.core.Conversion \ + -enable-checker alpha.core.FixedAddr \ -enable-checker alpha.core.IdenticalExpr \ + -enable-checker alpha.core.PointerArithm \ -enable-checker alpha.core.SizeofPtr \ - -enable-checker alpha.security.ArrayBoundV2 \ + -enable-checker alpha.core.TestAfterDivZero \ -enable-checker alpha.security.MallocOverflow \ -enable-checker alpha.security.ReturnPtrRange \ - -enable-checker alpha.unix.SimpleStream + -enable-checker alpha.unix.BlockInCriticalSection \ + -enable-checker alpha.unix.Chroot \ + -enable-checker alpha.unix.PthreadLock \ + -enable-checker alpha.unix.PthreadLock \ + -enable-checker alpha.unix.SimpleStream \ + -enable-checker alpha.unix.Stream \ -enable-checker alpha.unix.cstring.BufferOverlap \ -enable-checker alpha.unix.cstring.NotNullTerminated \ - -enable-checker alpha.unix.cstring.OutOfBounds \ - -enable-checker alpha.core.FixedAddr \ + -enable-checker alpha.valist.CopyToSelf \ + -enable-checker alpha.valist.Uninitialized \ + -enable-checker alpha.valist.Unterminated \ + -enable-checker security.FloatLoopCounter \ -enable-checker security.insecureAPI.strcpy \ - -enable-checker alpha.unix.PthreadLock \ - -enable-checker alpha.core.PointerArithm \ - -enable-checker alpha.core.TestAfterDivZero \ " +# These have high false-positive rates. +EXTRA_CHECKERS="\ + -enable-checker alpha.security.ArrayBoundV2 \ + -enable-checker alpha.unix.cstring.OutOfBounds \ + -enable-checker alpha.core.CastSize \ +" + +# These don't seem to generate anything useful +NOISY_CHECKERS="\ + -enable-checker alpha.clone.CloneChecker \ + -enable-checker alpha.deadcode.UnreachableCode \ +" + +if test "x$SCAN_BUILD_OUTPUT" != "x"; then + OUTPUTARG="-o $SCAN_BUILD_OUTPUT" +else + OUTPUTARG="" +fi + scan-build \ $CHECKERS \ ./configure scan-build \ + make clean + +# Make this not get scanned for dead assignments, since it has lots of +# dead assignments we don't care about. +scan-build \ $CHECKERS \ - make -j2 -k + -disable-checker deadcode.DeadStores \ + make -j5 -k ./src/ext/ed25519/ref10/libed25519_ref10.a +scan-build \ + $CHECKERS $OUTPUTARG \ + make -j5 -k + +CHECKERS="\ +" # This one gives a false positive on every strcmp. # -enable-checker alpha.core.PointerSub # Needs work -# alpha.unix.MallocWithAnnotations ?? +# -enable-checker alpha.unix.MallocWithAnnotations diff --git a/src/common/address.c b/src/common/address.c index 894e15114c..dbe129be59 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -33,7 +33,7 @@ #include <process.h> #include <windows.h> #include <iphlpapi.h> -#endif +#endif /* defined(_WIN32) */ #include "compat.h" #include "util.h" @@ -198,7 +198,7 @@ tor_sockaddr_to_str(const struct sockaddr *sa) tor_asprintf(&result, "unix:%s", s_un->sun_path); return result; } -#endif +#endif /* defined(HAVE_SYS_UN_H) */ if (sa->sa_family == AF_UNSPEC) return tor_strdup("unspec"); @@ -305,7 +305,7 @@ tor_addr_lookup,(const char *name, uint16_t family, tor_addr_t *addr)) return result; } return (err == EAI_AGAIN) ? 1 : -1; -#else +#else /* !(defined(HAVE_GETADDRINFO)) */ struct hostent *ent; int err; #ifdef HAVE_GETHOSTBYNAME_R_6_ARG @@ -330,7 +330,7 @@ tor_addr_lookup,(const char *name, uint16_t family, tor_addr_t *addr)) #else err = h_errno; #endif -#endif /* endif HAVE_GETHOSTBYNAME_R_6_ARG. */ +#endif /* defined(HAVE_GETHOSTBYNAME_R_6_ARG) || ... */ if (ent) { if (ent->h_addrtype == AF_INET) { tor_addr_from_in(addr, (struct in_addr*) ent->h_addr); @@ -346,7 +346,7 @@ tor_addr_lookup,(const char *name, uint16_t family, tor_addr_t *addr)) #else return (err == TRY_AGAIN) ? 1 : -1; #endif -#endif +#endif /* defined(HAVE_GETADDRINFO) */ } } @@ -907,8 +907,8 @@ tor_addr_is_loopback(const tor_addr_t *addr) return (tor_addr_to_ipv4h(addr) & 0xff000000) == 0x7f000000; case AF_UNSPEC: return 0; - default: /* LCOV_EXCL_START */ + default: tor_fragile_assert(); return 0; /* LCOV_EXCL_STOP */ @@ -1031,8 +1031,10 @@ tor_addr_copy_tight(tor_addr_t *dest, const tor_addr_t *src) memcpy(dest->addr.in6_addr.s6_addr, src->addr.in6_addr.s6_addr, 16); case AF_UNSPEC: break; + // LCOV_EXCL_START default: - tor_fragile_assert(); // LCOV_EXCL_LINE + tor_fragile_assert(); + // LCOV_EXCL_STOP } } @@ -1138,8 +1140,8 @@ tor_addr_compare_masked(const tor_addr_t *addr1, const tor_addr_t *addr2, return 0; else return 1; - default: /* LCOV_EXCL_START */ + default: tor_fragile_assert(); return 0; /* LCOV_EXCL_STOP */ @@ -1194,8 +1196,8 @@ tor_addr_hash(const tor_addr_t *addr) return 0x4e4d5342; case AF_INET6: return siphash24g(&addr->addr.in6_addr.s6_addr, 16); - default: /* LCOV_EXCL_START */ + default: tor_fragile_assert(); return 0; /* LCOV_EXCL_STOP */ @@ -1409,7 +1411,7 @@ get_interface_addresses_ifaddrs(int severity, sa_family_t family) return result; } -#endif +#endif /* defined(HAVE_IFADDRS_TO_SMARTLIST) */ #ifdef HAVE_IP_ADAPTER_TO_SMARTLIST @@ -1500,7 +1502,7 @@ get_interface_addresses_win32(int severity, sa_family_t family) return result; } -#endif +#endif /* defined(HAVE_IP_ADAPTER_TO_SMARTLIST) */ #ifdef HAVE_IFCONF_TO_SMARTLIST @@ -1602,7 +1604,7 @@ get_interface_addresses_ioctl(int severity, sa_family_t family) tor_free(ifc.ifc_buf); return result; } -#endif +#endif /* defined(HAVE_IFCONF_TO_SMARTLIST) */ /** Try to ask our network interfaces what addresses they are bound to. * Return a new smartlist of tor_addr_t on success, and NULL on failure. diff --git a/src/common/address.h b/src/common/address.h index ce85b3d81d..c7f6935754 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -44,7 +44,7 @@ #endif // TODO win32 specific includes -#endif // ADDRESS_PRIVATE +#endif /* defined(ADDRESS_PRIVATE) */ /** The number of bits from an address to consider while doing a masked * comparison. */ @@ -358,23 +358,23 @@ STATIC smartlist_t *ifaddrs_to_smartlist(const struct ifaddrs *ifa, sa_family_t family); STATIC smartlist_t *get_interface_addresses_ifaddrs(int severity, sa_family_t family); -#endif +#endif /* defined(HAVE_IFADDRS_TO_SMARTLIST) */ #ifdef HAVE_IP_ADAPTER_TO_SMARTLIST STATIC smartlist_t *ip_adapter_addresses_to_smartlist( const IP_ADAPTER_ADDRESSES *addresses); STATIC smartlist_t *get_interface_addresses_win32(int severity, sa_family_t family); -#endif +#endif /* defined(HAVE_IP_ADAPTER_TO_SMARTLIST) */ #ifdef HAVE_IFCONF_TO_SMARTLIST STATIC smartlist_t *ifreq_to_smartlist(char *ifr, size_t buflen); STATIC smartlist_t *get_interface_addresses_ioctl(int severity, sa_family_t family); -#endif +#endif /* defined(HAVE_IFCONF_TO_SMARTLIST) */ -#endif // ADDRESS_PRIVATE +#endif /* defined(ADDRESS_PRIVATE) */ -#endif +#endif /* !defined(TOR_ADDRESS_H) */ diff --git a/src/common/aes.c b/src/common/aes.c index 73abef143e..20b51a6758 100644 --- a/src/common/aes.c +++ b/src/common/aes.c @@ -66,11 +66,11 @@ ENABLE_GCC_WARNING(redundant-decls) #elif OPENSSL_VERSION_NUMBER >= OPENSSL_V_NOPATCH(1,0,1) && \ (defined(__i386) || defined(__i386__) || defined(_M_IX86) || \ defined(__x86_64) || defined(__x86_64__) || \ - defined(_M_AMD64) || defined(_M_X64) || defined(__INTEL__)) \ + defined(_M_AMD64) || defined(_M_X64) || defined(__INTEL__)) #define USE_EVP_AES_CTR -#endif +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_NOPATCH(1,1,0) || ... */ /* We have 2 strategies for getting the AES block cipher: Via OpenSSL's * AES_encrypt function, or via OpenSSL's EVP_EncryptUpdate function. @@ -142,7 +142,7 @@ evaluate_ctr_for_aes(void) { return 0; } -#else +#else /* !(defined(USE_EVP_AES_CTR)) */ /*======================================================================*/ /* Interface to AES code, and counter implementation */ @@ -163,7 +163,7 @@ struct aes_cnt_cipher { uint32_t counter2; uint32_t counter1; uint32_t counter0; -#endif +#endif /* !defined(WORDS_BIGENDIAN) */ union { /** The counter, in big-endian order, as bytes. */ @@ -212,7 +212,7 @@ evaluate_evp_for_aes(int force_val) log_info(LD_CRYPTO, "No AES engine found; using AES_* functions."); should_use_EVP = 0; } -#endif +#endif /* defined(DISABLE_ENGINES) */ return 0; } @@ -312,7 +312,7 @@ aes_set_key(aes_cnt_cipher_t *cipher, const uint8_t *key, int key_bits) cipher->counter1 = 0; cipher->counter2 = 0; cipher->counter3 = 0; -#endif +#endif /* defined(USING_COUNTER_VARS) */ memset(cipher->ctr_buf.buf, 0, sizeof(cipher->ctr_buf.buf)); @@ -341,7 +341,7 @@ aes_cipher_free(aes_cnt_cipher_t *cipher) STMT_END #else #define UPDATE_CTR_BUF(c, n) -#endif +#endif /* defined(USING_COUNTER_VARS) */ /* Helper function to use EVP with openssl's counter-mode wrapper. */ static void @@ -396,10 +396,10 @@ aes_set_iv(aes_cnt_cipher_t *cipher, const uint8_t *iv) cipher->counter2 = ntohl(get_uint32(iv+4)); cipher->counter1 = ntohl(get_uint32(iv+8)); cipher->counter0 = ntohl(get_uint32(iv+12)); -#endif +#endif /* defined(USING_COUNTER_VARS) */ cipher->pos = 0; memcpy(cipher->ctr_buf.buf, iv, 16); } -#endif +#endif /* defined(USE_EVP_AES_CTR) */ diff --git a/src/common/aes.h b/src/common/aes.h index 6fd9c3ea16..1e400a56e0 100644 --- a/src/common/aes.h +++ b/src/common/aes.h @@ -23,5 +23,5 @@ void aes_crypt_inplace(aes_cnt_cipher_t *cipher, char *data, size_t len); int evaluate_evp_for_aes(int force_value); int evaluate_ctr_for_aes(void); -#endif +#endif /* !defined(TOR_AES_H) */ diff --git a/src/common/backtrace.c b/src/common/backtrace.c index 0f0fa857bf..f2498b2aa6 100644 --- a/src/common/backtrace.c +++ b/src/common/backtrace.c @@ -37,7 +37,7 @@ #include <sys/ucontext.h> #elif defined(HAVE_UCONTEXT_H) #include <ucontext.h> -#endif +#endif /* defined(HAVE_CYGWIN_SIGNAL_H) || ... */ #define EXPOSE_CLEAN_BACKTRACE #include "backtrace.h" @@ -81,16 +81,16 @@ clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx) const size_t n = 2; #else const size_t n = 1; -#endif +#endif /* defined(__linux__) || ... */ if (depth <= n) return; stack[n] = (void*) ctx->PC_FROM_UCONTEXT; -#else +#else /* !(defined(PC_FROM_UCONTEXT)) */ (void) depth; (void) ctx; (void) stack; -#endif +#endif /* defined(PC_FROM_UCONTEXT) */ } /** Log a message <b>msg</b> at <b>severity</b> in <b>domain</b>, and follow @@ -202,7 +202,7 @@ remove_bt_handler(void) { tor_mutex_uninit(&cb_buf_mutex); } -#endif +#endif /* defined(USE_BACKTRACE) */ #ifdef NO_BACKTRACE_IMPL void @@ -221,7 +221,7 @@ static void remove_bt_handler(void) { } -#endif +#endif /* defined(NO_BACKTRACE_IMPL) */ /** Set up code to handle generating error messages on crashes. */ int diff --git a/src/common/backtrace.h b/src/common/backtrace.h index 8cd1dcf06c..3d0ab8a90a 100644 --- a/src/common/backtrace.h +++ b/src/common/backtrace.h @@ -15,7 +15,7 @@ void clean_up_backtrace_handler(void); defined(HAVE_BACKTRACE_SYMBOLS_FD) && defined(HAVE_SIGACTION) void clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx); #endif -#endif +#endif /* defined(EXPOSE_CLEAN_BACKTRACE) */ -#endif +#endif /* !defined(TOR_BACKTRACE_H) */ diff --git a/src/common/buffers.c b/src/common/buffers.c new file mode 100644 index 0000000000..c45e13d551 --- /dev/null +++ b/src/common/buffers.c @@ -0,0 +1,1077 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file buffers.c + * \brief Implements a generic buffer interface. + * + * A buf_t is a (fairly) opaque byte-oriented FIFO that can read to or flush + * from memory, sockets, file descriptors, TLS connections, or another buf_t. + * Buffers are implemented as linked lists of memory chunks. + * + * All socket-backed and TLS-based connection_t objects have a pair of + * buffers: one for incoming data, and one for outcoming data. These are fed + * and drained from functions in connection.c, trigged by events that are + * monitored in main.c. + **/ + +#define BUFFERS_PRIVATE +#include "orconfig.h" +#include <stddef.h> +#include "buffers.h" +#include "compat.h" +#include "compress.h" +#include "util.h" +#include "torint.h" +#include "torlog.h" +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +//#define PARANOIA + +#ifdef PARANOIA +/** Helper: If PARANOIA is defined, assert that the buffer in local variable + * <b>buf</b> is well-formed. */ +#define check() STMT_BEGIN buf_assert_ok(buf); STMT_END +#else +#define check() STMT_NIL +#endif /* defined(PARANOIA) */ + +/* Implementation notes: + * + * After flirting with memmove, and dallying with ring-buffers, we're finally + * getting up to speed with the 1970s and implementing buffers as a linked + * list of small chunks. Each buffer has such a list; data is removed from + * the head of the list, and added at the tail. The list is singly linked, + * and the buffer keeps a pointer to the head and the tail. + * + * Every chunk, except the tail, contains at least one byte of data. Data in + * each chunk is contiguous. + * + * When you need to treat the first N characters on a buffer as a contiguous + * string, use the buf_pullup function to make them so. Don't do this more + * than necessary. + * + * The major free Unix kernels have handled buffers like this since, like, + * forever. + */ + +/* Chunk manipulation functions */ + +#define CHUNK_HEADER_LEN offsetof(chunk_t, mem[0]) + +/* We leave this many NUL bytes at the end of the buffer. */ +#ifdef DISABLE_MEMORY_SENTINELS +#define SENTINEL_LEN 0 +#else +#define SENTINEL_LEN 4 +#endif + +/* Header size plus NUL bytes at the end */ +#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) + +/** Return the number of bytes needed to allocate a chunk to hold + * <b>memlen</b> bytes. */ +#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen)) +/** Return the number of usable bytes in a chunk allocated with + * malloc(<b>memlen</b>). */ +#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD) + +#define DEBUG_SENTINEL + +#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS) +#define DBG_S(s) s +#else +#define DBG_S(s) (void)0 +#endif + +#ifdef DISABLE_MEMORY_SENTINELS +#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL +#else +#define CHUNK_SET_SENTINEL(chunk, alloclen) do { \ + uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \ + DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \ + DBG_S(tor_assert(a == b)); \ + memset(a,0,SENTINEL_LEN); \ + } while (0) +#endif /* defined(DISABLE_MEMORY_SENTINELS) */ + +/** Move all bytes stored in <b>chunk</b> to the front of <b>chunk</b>->mem, + * to free up space at the end. */ +static inline void +chunk_repack(chunk_t *chunk) +{ + if (chunk->datalen && chunk->data != &chunk->mem[0]) { + memmove(chunk->mem, chunk->data, chunk->datalen); + } + chunk->data = &chunk->mem[0]; +} + +/** Keep track of total size of allocated chunks for consistency asserts */ +static size_t total_bytes_allocated_in_chunks = 0; +static void +buf_chunk_free_unchecked(chunk_t *chunk) +{ + if (!chunk) + return; +#ifdef DEBUG_CHUNK_ALLOC + tor_assert(CHUNK_ALLOC_SIZE(chunk->memlen) == chunk->DBG_alloc); +#endif + tor_assert(total_bytes_allocated_in_chunks >= + CHUNK_ALLOC_SIZE(chunk->memlen)); + total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); + tor_free(chunk); +} +static inline chunk_t * +chunk_new_with_alloc_size(size_t alloc) +{ + chunk_t *ch; + ch = tor_malloc(alloc); + ch->next = NULL; + ch->datalen = 0; +#ifdef DEBUG_CHUNK_ALLOC + ch->DBG_alloc = alloc; +#endif + ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); + total_bytes_allocated_in_chunks += alloc; + ch->data = &ch->mem[0]; + CHUNK_SET_SENTINEL(ch, alloc); + return ch; +} + +/** Expand <b>chunk</b> until it can hold <b>sz</b> bytes, and return a + * new pointer to <b>chunk</b>. Old pointers are no longer valid. */ +static inline chunk_t * +chunk_grow(chunk_t *chunk, size_t sz) +{ + off_t offset; + const size_t memlen_orig = chunk->memlen; + const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig); + const size_t new_alloc = CHUNK_ALLOC_SIZE(sz); + tor_assert(sz > chunk->memlen); + offset = chunk->data - chunk->mem; + chunk = tor_realloc(chunk, new_alloc); + chunk->memlen = sz; + chunk->data = chunk->mem + offset; +#ifdef DEBUG_CHUNK_ALLOC + tor_assert(chunk->DBG_alloc == orig_alloc); + chunk->DBG_alloc = new_alloc; +#endif + total_bytes_allocated_in_chunks += new_alloc - orig_alloc; + CHUNK_SET_SENTINEL(chunk, new_alloc); + return chunk; +} + +/** Every chunk should take up at least this many bytes. */ +#define MIN_CHUNK_ALLOC 256 +/** No chunk should take up more than this many bytes. */ +#define MAX_CHUNK_ALLOC 65536 + +/** Return the allocation size we'd like to use to hold <b>target</b> + * bytes. */ +size_t +buf_preferred_chunk_size(size_t target) +{ + tor_assert(target <= SIZE_T_CEILING - CHUNK_OVERHEAD); + if (CHUNK_ALLOC_SIZE(target) >= MAX_CHUNK_ALLOC) + return CHUNK_ALLOC_SIZE(target); + size_t sz = MIN_CHUNK_ALLOC; + while (CHUNK_SIZE_WITH_ALLOC(sz) < target) { + sz <<= 1; + } + return sz; +} + +/** Collapse data from the first N chunks from <b>buf</b> into buf->head, + * growing it as necessary, until buf->head has the first <b>bytes</b> bytes + * of data from the buffer, or until buf->head has all the data in <b>buf</b>. + * + * Set *<b>head_out</b> to point to the first byte of available data, and + * *<b>len_out</b> to the number of bytes of data available at + * *<b>head_out</b>. Note that *<b>len_out</b> may be more or less than + * <b>bytes</b>, depending on the number of bytes available. + */ +void +buf_pullup(buf_t *buf, size_t bytes, const char **head_out, size_t *len_out) +{ + chunk_t *dest, *src; + size_t capacity; + if (!buf->head) { + *head_out = NULL; + *len_out = 0; + return; + } + + check(); + if (buf->datalen < bytes) + bytes = buf->datalen; + + capacity = bytes; + if (buf->head->datalen >= bytes) { + *head_out = buf->head->data; + *len_out = buf->head->datalen; + return; + } + + if (buf->head->memlen >= capacity) { + /* We don't need to grow the first chunk, but we might need to repack it.*/ + size_t needed = capacity - buf->head->datalen; + if (CHUNK_REMAINING_CAPACITY(buf->head) < needed) + chunk_repack(buf->head); + tor_assert(CHUNK_REMAINING_CAPACITY(buf->head) >= needed); + } else { + chunk_t *newhead; + size_t newsize; + /* We need to grow the chunk. */ + chunk_repack(buf->head); + newsize = CHUNK_SIZE_WITH_ALLOC(buf_preferred_chunk_size(capacity)); + newhead = chunk_grow(buf->head, newsize); + tor_assert(newhead->memlen >= capacity); + if (newhead != buf->head) { + if (buf->tail == buf->head) + buf->tail = newhead; + buf->head = newhead; + } + } + + dest = buf->head; + while (dest->datalen < bytes) { + size_t n = bytes - dest->datalen; + src = dest->next; + tor_assert(src); + if (n >= src->datalen) { + memcpy(CHUNK_WRITE_PTR(dest), src->data, src->datalen); + dest->datalen += src->datalen; + dest->next = src->next; + if (buf->tail == src) + buf->tail = dest; + buf_chunk_free_unchecked(src); + } else { + memcpy(CHUNK_WRITE_PTR(dest), src->data, n); + dest->datalen += n; + src->data += n; + src->datalen -= n; + tor_assert(dest->datalen == bytes); + } + } + + check(); + *head_out = buf->head->data; + *len_out = buf->head->datalen; +} + +#ifdef TOR_UNIT_TESTS +/* Write sz bytes from cp into a newly allocated buffer buf. + * Returns NULL when passed a NULL cp or zero sz. + * Asserts on failure: only for use in unit tests. + * buf must be freed using buf_free(). */ +buf_t * +buf_new_with_data(const char *cp, size_t sz) +{ + /* Validate arguments */ + if (!cp || sz <= 0) { + return NULL; + } + + tor_assert(sz < SSIZE_T_CEILING); + + /* Allocate a buffer */ + buf_t *buf = buf_new_with_capacity(sz); + tor_assert(buf); + buf_assert_ok(buf); + tor_assert(!buf->head); + + /* Allocate a chunk that is sz bytes long */ + buf->head = chunk_new_with_alloc_size(CHUNK_ALLOC_SIZE(sz)); + buf->tail = buf->head; + tor_assert(buf->head); + buf_assert_ok(buf); + tor_assert(buf_allocation(buf) >= sz); + + /* Copy the data and size the buffers */ + tor_assert(sz <= buf_slack(buf)); + tor_assert(sz <= CHUNK_REMAINING_CAPACITY(buf->head)); + memcpy(&buf->head->mem[0], cp, sz); + buf->datalen = sz; + buf->head->datalen = sz; + buf->head->data = &buf->head->mem[0]; + buf_assert_ok(buf); + + /* Make sure everything is large enough */ + tor_assert(buf_allocation(buf) >= sz); + tor_assert(buf_allocation(buf) >= buf_datalen(buf) + buf_slack(buf)); + /* Does the buffer implementation allocate more than the requested size? + * (for example, by rounding up). If so, these checks will fail. */ + tor_assert(buf_datalen(buf) == sz); + tor_assert(buf_slack(buf) == 0); + + return buf; +} +#endif /* defined(TOR_UNIT_TESTS) */ + +/** Remove the first <b>n</b> bytes from buf. */ +void +buf_drain(buf_t *buf, size_t n) +{ + tor_assert(buf->datalen >= n); + while (n) { + tor_assert(buf->head); + if (buf->head->datalen > n) { + buf->head->datalen -= n; + buf->head->data += n; + buf->datalen -= n; + return; + } else { + chunk_t *victim = buf->head; + n -= victim->datalen; + buf->datalen -= victim->datalen; + buf->head = victim->next; + if (buf->tail == victim) + buf->tail = NULL; + buf_chunk_free_unchecked(victim); + } + } + check(); +} + +/** Create and return a new buf with default chunk capacity <b>size</b>. + */ +buf_t * +buf_new_with_capacity(size_t size) +{ + buf_t *b = buf_new(); + b->default_chunk_size = buf_preferred_chunk_size(size); + return b; +} + +/** Allocate and return a new buffer with default capacity. */ +buf_t * +buf_new(void) +{ + buf_t *buf = tor_malloc_zero(sizeof(buf_t)); + buf->magic = BUFFER_MAGIC; + buf->default_chunk_size = 4096; + return buf; +} + +size_t +buf_get_default_chunk_size(const buf_t *buf) +{ + return buf->default_chunk_size; +} + +/** Remove all data from <b>buf</b>. */ +void +buf_clear(buf_t *buf) +{ + chunk_t *chunk, *next; + buf->datalen = 0; + for (chunk = buf->head; chunk; chunk = next) { + next = chunk->next; + buf_chunk_free_unchecked(chunk); + } + buf->head = buf->tail = NULL; +} + +/** Return the number of bytes stored in <b>buf</b> */ +MOCK_IMPL(size_t, +buf_datalen, (const buf_t *buf)) +{ + return buf->datalen; +} + +/** Return the total length of all chunks used in <b>buf</b>. */ +size_t +buf_allocation(const buf_t *buf) +{ + size_t total = 0; + const chunk_t *chunk; + for (chunk = buf->head; chunk; chunk = chunk->next) { + total += CHUNK_ALLOC_SIZE(chunk->memlen); + } + return total; +} + +/** Return the number of bytes that can be added to <b>buf</b> without + * performing any additional allocation. */ +size_t +buf_slack(const buf_t *buf) +{ + if (!buf->tail) + return 0; + else + return CHUNK_REMAINING_CAPACITY(buf->tail); +} + +/** Release storage held by <b>buf</b>. */ +void +buf_free(buf_t *buf) +{ + if (!buf) + return; + + buf_clear(buf); + buf->magic = 0xdeadbeef; + tor_free(buf); +} + +/** Return a new copy of <b>in_chunk</b> */ +static chunk_t * +chunk_copy(const chunk_t *in_chunk) +{ + chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); + total_bytes_allocated_in_chunks += CHUNK_ALLOC_SIZE(in_chunk->memlen); +#ifdef DEBUG_CHUNK_ALLOC + newch->DBG_alloc = CHUNK_ALLOC_SIZE(in_chunk->memlen); +#endif + newch->next = NULL; + if (in_chunk->data) { + off_t offset = in_chunk->data - in_chunk->mem; + newch->data = newch->mem + offset; + } + return newch; +} + +/** Return a new copy of <b>buf</b> */ +buf_t * +buf_copy(const buf_t *buf) +{ + chunk_t *ch; + buf_t *out = buf_new(); + out->default_chunk_size = buf->default_chunk_size; + for (ch = buf->head; ch; ch = ch->next) { + chunk_t *newch = chunk_copy(ch); + if (out->tail) { + out->tail->next = newch; + out->tail = newch; + } else { + out->head = out->tail = newch; + } + } + out->datalen = buf->datalen; + return out; +} + +/** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to + * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger + * than MAX_CHUNK_ALLOC. */ +chunk_t * +buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) +{ + chunk_t *chunk; + + if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) { + chunk = chunk_new_with_alloc_size(buf->default_chunk_size); + } else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) { + chunk = chunk_new_with_alloc_size(MAX_CHUNK_ALLOC); + } else { + chunk = chunk_new_with_alloc_size(buf_preferred_chunk_size(capacity)); + } + + chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec(); + + if (buf->tail) { + tor_assert(buf->head); + buf->tail->next = chunk; + buf->tail = chunk; + } else { + tor_assert(!buf->head); + buf->head = buf->tail = chunk; + } + check(); + return chunk; +} + +/** Return the age of the oldest chunk in the buffer <b>buf</b>, in + * milliseconds. Requires the current monotonic time, in truncated msec, + * as its input <b>now</b>. + */ +uint32_t +buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now) +{ + if (buf->head) { + return now - buf->head->inserted_time; + } else { + return 0; + } +} + +size_t +buf_get_total_allocation(void) +{ + return total_bytes_allocated_in_chunks; +} + +/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into + * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set + * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, + * and the number of bytes read otherwise. */ +static inline int +read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, + int *reached_eof, int *socket_error) +{ + ssize_t read_result; + if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) + at_most = CHUNK_REMAINING_CAPACITY(chunk); + read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); + + if (read_result < 0) { + int e = tor_socket_errno(fd); + if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ +#ifdef _WIN32 + if (e == WSAENOBUFS) + log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?"); +#endif + *socket_error = e; + return -1; + } + return 0; /* would block. */ + } else if (read_result == 0) { + log_debug(LD_NET,"Encountered eof on fd %d", (int)fd); + *reached_eof = 1; + return 0; + } else { /* actually got bytes. */ + buf->datalen += read_result; + chunk->datalen += read_result; + log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, + (int)buf->datalen); + tor_assert(read_result < INT_MAX); + return (int)read_result; + } +} + +/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most + * <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0 + * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on + * error; else return the number of bytes read. + */ +/* XXXX indicate "read blocked" somehow? */ +int +buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most, + int *reached_eof, + int *socket_error) +{ + /* XXXX It's stupid to overload the return values for these functions: + * "error status" and "number of bytes read" are not mutually exclusive. + */ + int r = 0; + size_t total_read = 0; + + check(); + tor_assert(reached_eof); + tor_assert(SOCKET_OK(s)); + + if (BUG(buf->datalen >= INT_MAX)) + return -1; + if (BUG(buf->datalen >= INT_MAX - at_most)) + return -1; + + while (at_most > total_read) { + size_t readlen = at_most - total_read; + chunk_t *chunk; + if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { + chunk = buf_add_chunk_with_capacity(buf, at_most, 1); + if (readlen > chunk->memlen) + readlen = chunk->memlen; + } else { + size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); + chunk = buf->tail; + if (cap < readlen) + readlen = cap; + } + + r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error); + check(); + if (r < 0) + return r; /* Error */ + tor_assert(total_read+r < INT_MAX); + total_read += r; + if ((size_t)r < readlen) { /* eof, block, or no more to read. */ + break; + } + } + return (int)total_read; +} + +/** Helper for buf_flush_to_socket(): try to write <b>sz</b> bytes from chunk + * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct + * the bytes written from *<b>buf_flushlen</b>. Return the number of bytes + * written on success, 0 on blocking, -1 on failure. + */ +static inline int +flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, + size_t *buf_flushlen) +{ + ssize_t write_result; + + if (sz > chunk->datalen) + sz = chunk->datalen; + write_result = tor_socket_send(s, chunk->data, sz, 0); + + if (write_result < 0) { + int e = tor_socket_errno(s); + if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ +#ifdef _WIN32 + if (e == WSAENOBUFS) + log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?"); +#endif + return -1; + } + log_debug(LD_NET,"write() would block, returning."); + return 0; + } else { + *buf_flushlen -= write_result; + buf_drain(buf, write_result); + tor_assert(write_result < INT_MAX); + return (int)write_result; + } +} + +/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most + * <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by + * the number of bytes actually written, and remove the written bytes + * from the buffer. Return the number of bytes written on success, + * -1 on failure. Return 0 if write() would block. + */ +int +buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz, + size_t *buf_flushlen) +{ + /* XXXX It's stupid to overload the return values for these functions: + * "error status" and "number of bytes flushed" are not mutually exclusive. + */ + int r; + size_t flushed = 0; + tor_assert(buf_flushlen); + tor_assert(SOCKET_OK(s)); + if (BUG(*buf_flushlen > buf->datalen)) { + *buf_flushlen = buf->datalen; + } + if (BUG(sz > *buf_flushlen)) { + sz = *buf_flushlen; + } + + check(); + while (sz) { + size_t flushlen0; + tor_assert(buf->head); + if (buf->head->datalen >= sz) + flushlen0 = sz; + else + flushlen0 = buf->head->datalen; + + r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen); + check(); + if (r < 0) + return r; + flushed += r; + sz -= r; + if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */ + break; + } + tor_assert(flushed < INT_MAX); + return (int)flushed; +} + +/** Append <b>string_len</b> bytes from <b>string</b> to the end of + * <b>buf</b>. + * + * Return the new length of the buffer on success, -1 on failure. + */ +int +buf_add(buf_t *buf, const char *string, size_t string_len) +{ + if (!string_len) + return (int)buf->datalen; + check(); + + if (BUG(buf->datalen >= INT_MAX)) + return -1; + if (BUG(buf->datalen >= INT_MAX - string_len)) + return -1; + + while (string_len) { + size_t copy; + if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail)) + buf_add_chunk_with_capacity(buf, string_len, 1); + + copy = CHUNK_REMAINING_CAPACITY(buf->tail); + if (copy > string_len) + copy = string_len; + memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy); + string_len -= copy; + string += copy; + buf->datalen += copy; + buf->tail->datalen += copy; + } + + check(); + tor_assert(buf->datalen < INT_MAX); + return (int)buf->datalen; +} + +/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> + * onto <b>string</b>. + */ +void +buf_peek(const buf_t *buf, char *string, size_t string_len) +{ + chunk_t *chunk; + + tor_assert(string); + /* make sure we don't ask for too much */ + tor_assert(string_len <= buf->datalen); + /* buf_assert_ok(buf); */ + + chunk = buf->head; + while (string_len) { + size_t copy = string_len; + tor_assert(chunk); + if (chunk->datalen < copy) + copy = chunk->datalen; + memcpy(string, chunk->data, copy); + string_len -= copy; + string += copy; + chunk = chunk->next; + } +} + +/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store + * them into <b>string</b>. Return the new buffer size. <b>string_len</b> + * must be \<= the number of bytes on the buffer. + */ +int +buf_get_bytes(buf_t *buf, char *string, size_t string_len) +{ + /* There must be string_len bytes in buf; write them onto string, + * then memmove buf back (that is, remove them from buf). + * + * Return the number of bytes still on the buffer. */ + + check(); + buf_peek(buf, string, string_len); + buf_drain(buf, string_len); + check(); + tor_assert(buf->datalen < INT_MAX); + return (int)buf->datalen; +} + +/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to + * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. + * Return the number of bytes actually copied. + */ +int +buf_move_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) +{ + /* We can do way better here, but this doesn't turn up in any profiles. */ + char b[4096]; + size_t cp, len; + + if (BUG(buf_out->datalen >= INT_MAX)) + return -1; + if (BUG(buf_out->datalen >= INT_MAX - *buf_flushlen)) + return -1; + + len = *buf_flushlen; + if (len > buf_in->datalen) + len = buf_in->datalen; + + cp = len; /* Remember the number of bytes we intend to copy. */ + tor_assert(cp < INT_MAX); + while (len) { + /* This isn't the most efficient implementation one could imagine, since + * it does two copies instead of 1, but I kinda doubt that this will be + * critical path. */ + size_t n = len > sizeof(b) ? sizeof(b) : len; + buf_get_bytes(buf_in, b, n); + buf_add(buf_out, b, n); + len -= n; + } + *buf_flushlen -= cp; + return (int)cp; +} + +/** Internal structure: represents a position in a buffer. */ +typedef struct buf_pos_t { + const chunk_t *chunk; /**< Which chunk are we pointing to? */ + int pos;/**< Which character inside the chunk's data are we pointing to? */ + size_t chunk_pos; /**< Total length of all previous chunks. */ +} buf_pos_t; + +/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/ +static void +buf_pos_init(const buf_t *buf, buf_pos_t *out) +{ + out->chunk = buf->head; + out->pos = 0; + out->chunk_pos = 0; +} + +/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current + * position of <b>out</b>, or later. Return -1 if no instances are found; + * otherwise returns the absolute position of the character. */ +static off_t +buf_find_pos_of_char(char ch, buf_pos_t *out) +{ + const chunk_t *chunk; + int pos; + tor_assert(out); + if (out->chunk) { + if (out->chunk->datalen) { + tor_assert(out->pos < (off_t)out->chunk->datalen); + } else { + tor_assert(out->pos == 0); + } + } + pos = out->pos; + for (chunk = out->chunk; chunk; chunk = chunk->next) { + char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos); + if (cp) { + out->chunk = chunk; + tor_assert(cp - chunk->data < INT_MAX); + out->pos = (int)(cp - chunk->data); + return out->chunk_pos + out->pos; + } else { + out->chunk_pos += chunk->datalen; + pos = 0; + } + } + return -1; +} + +/** Advance <b>pos</b> by a single character, if there are any more characters + * in the buffer. Returns 0 on success, -1 on failure. */ +static inline int +buf_pos_inc(buf_pos_t *pos) +{ + ++pos->pos; + if (pos->pos == (off_t)pos->chunk->datalen) { + if (!pos->chunk->next) + return -1; + pos->chunk_pos += pos->chunk->datalen; + pos->chunk = pos->chunk->next; + pos->pos = 0; + } + return 0; +} + +/** Return true iff the <b>n</b>-character string in <b>s</b> appears + * (verbatim) at <b>pos</b>. */ +static int +buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) +{ + buf_pos_t p; + if (!n) + return 1; + + memcpy(&p, pos, sizeof(p)); + + while (1) { + char ch = p.chunk->data[p.pos]; + if (ch != *s) + return 0; + ++s; + /* If we're out of characters that don't match, we match. Check this + * _before_ we test incrementing pos, in case we're at the end of the + * string. */ + if (--n == 0) + return 1; + if (buf_pos_inc(&p)<0) + return 0; + } +} + +/** Return the first position in <b>buf</b> at which the <b>n</b>-character + * string <b>s</b> occurs, or -1 if it does not occur. */ +int +buf_find_string_offset(const buf_t *buf, const char *s, size_t n) +{ + buf_pos_t pos; + buf_pos_init(buf, &pos); + while (buf_find_pos_of_char(*s, &pos) >= 0) { + if (buf_matches_at_pos(&pos, s, n)) { + tor_assert(pos.chunk_pos + pos.pos < INT_MAX); + return (int)(pos.chunk_pos + pos.pos); + } else { + if (buf_pos_inc(&pos)<0) + return -1; + } + } + return -1; +} + +/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null + * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */ +int +buf_peek_startswith(const buf_t *buf, const char *cmd) +{ + char tmp[PEEK_BUF_STARTSWITH_MAX]; + size_t clen = strlen(cmd); + if (clen == 0) + return 1; + if (BUG(clen > sizeof(tmp))) + return 0; + if (buf->datalen < clen) + return 0; + buf_peek(buf, tmp, clen); + return fast_memeq(tmp, cmd, clen); +} + +/** Return the index within <b>buf</b> at which <b>ch</b> first appears, + * or -1 if <b>ch</b> does not appear on buf. */ +static off_t +buf_find_offset_of_char(buf_t *buf, char ch) +{ + chunk_t *chunk; + off_t offset = 0; + for (chunk = buf->head; chunk; chunk = chunk->next) { + char *cp = memchr(chunk->data, ch, chunk->datalen); + if (cp) + return offset + (cp - chunk->data); + else + offset += chunk->datalen; + } + return -1; +} + +/** Try to read a single LF-terminated line from <b>buf</b>, and write it + * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer + * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the + * line, not counting the terminating NUL. Return 1 if we read a whole line, + * return 0 if we don't have a whole line yet, and return -1 if the line + * length exceeds *<b>data_len</b>. + */ +int +buf_get_line(buf_t *buf, char *data_out, size_t *data_len) +{ + size_t sz; + off_t offset; + + if (!buf->head) + return 0; + + offset = buf_find_offset_of_char(buf, '\n'); + if (offset < 0) + return 0; + sz = (size_t) offset; + if (sz+2 > *data_len) { + *data_len = sz + 2; + return -1; + } + buf_get_bytes(buf, data_out, sz+1); + data_out[sz+1] = '\0'; + *data_len = sz+1; + return 1; +} + +/** Compress or uncompress the <b>data_len</b> bytes in <b>data</b> using the + * compression state <b>state</b>, appending the result to <b>buf</b>. If + * <b>done</b> is true, flush the data in the state and finish the + * compression/uncompression. Return -1 on failure, 0 on success. */ +int +buf_add_compress(buf_t *buf, tor_compress_state_t *state, + const char *data, size_t data_len, + const int done) +{ + char *next; + size_t old_avail, avail; + int over = 0; + + do { + int need_new_chunk = 0; + if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { + size_t cap = data_len / 4; + buf_add_chunk_with_capacity(buf, cap, 1); + } + next = CHUNK_WRITE_PTR(buf->tail); + avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); + switch (tor_compress_process(state, &next, &avail, + &data, &data_len, done)) { + case TOR_COMPRESS_DONE: + over = 1; + break; + case TOR_COMPRESS_ERROR: + return -1; + case TOR_COMPRESS_OK: + if (data_len == 0) { + tor_assert_nonfatal(!done); + over = 1; + } + break; + case TOR_COMPRESS_BUFFER_FULL: + if (avail) { + /* The compression module says we need more room + * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically, + * whether were going to or not. */ + need_new_chunk = 1; + } + if (data_len == 0 && !done) { + /* We've consumed all the input data, though, so there's no + * point in forging ahead right now. */ + over = 1; + } + break; + } + buf->datalen += old_avail - avail; + buf->tail->datalen += old_avail - avail; + if (need_new_chunk) { + buf_add_chunk_with_capacity(buf, data_len/4, 1); + } + + } while (!over); + check(); + return 0; +} + +/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ +int +buf_set_to_copy(buf_t **output, + const buf_t *input) +{ + if (*output) + buf_free(*output); + *output = buf_copy(input); + return 0; +} + +/** Log an error and exit if <b>buf</b> is corrupted. + */ +void +buf_assert_ok(buf_t *buf) +{ + tor_assert(buf); + tor_assert(buf->magic == BUFFER_MAGIC); + + if (! buf->head) { + tor_assert(!buf->tail); + tor_assert(buf->datalen == 0); + } else { + chunk_t *ch; + size_t total = 0; + tor_assert(buf->tail); + for (ch = buf->head; ch; ch = ch->next) { + total += ch->datalen; + tor_assert(ch->datalen <= ch->memlen); + tor_assert(ch->data >= &ch->mem[0]); + tor_assert(ch->data <= &ch->mem[0]+ch->memlen); + if (ch->data == &ch->mem[0]+ch->memlen) { + /* LCOV_EXCL_START */ + static int warned = 0; + if (! warned) { + log_warn(LD_BUG, "Invariant violation in buf.c related to #15083"); + warned = 1; + } + /* LCOV_EXCL_STOP */ + } + tor_assert(ch->data+ch->datalen <= &ch->mem[0] + ch->memlen); + if (!ch->next) + tor_assert(ch == buf->tail); + } + tor_assert(buf->datalen == total); + } +} + diff --git a/src/or/buffers.h b/src/common/buffers.h index 23b58a571a..1eaa5f2d04 100644 --- a/src/or/buffers.h +++ b/src/common/buffers.h @@ -12,8 +12,15 @@ #ifndef TOR_BUFFERS_H #define TOR_BUFFERS_H +#include "compat.h" +#include "compat.h" +#include "torint.h" #include "testsupport.h" +typedef struct buf_t buf_t; + +struct tor_compress_state_t; + buf_t *buf_new(void); buf_t *buf_new_with_capacity(size_t size); size_t buf_get_default_chunk_size(const buf_t *buf); @@ -28,47 +35,39 @@ size_t buf_slack(const buf_t *buf); uint32_t buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now); size_t buf_get_total_allocation(void); -int read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error); -int read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf); +int buf_read_from_socket(buf_t *buf, tor_socket_t s, size_t at_most, + int *reached_eof, + int *socket_error); -int flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen); -int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen); +int buf_flush_to_socket(buf_t *buf, tor_socket_t s, size_t sz, + size_t *buf_flushlen); -int write_to_buf(const char *string, size_t string_len, buf_t *buf); -int write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, +int buf_add(buf_t *buf, const char *string, size_t string_len); +int buf_add_compress(buf_t *buf, struct tor_compress_state_t *state, const char *data, size_t data_len, int done); -int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); -int fetch_from_buf(char *string, size_t string_len, buf_t *buf); -int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto); -int fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete); -socks_request_t *socks_request_new(void); -void socks_request_free(socks_request_t *req); -int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks); -int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); -int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); - -int peek_buf_has_control0_command(buf_t *buf); - -int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out); +int buf_move_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); +void buf_peek(const buf_t *buf, char *string, size_t string_len); +void buf_drain(buf_t *buf, size_t n); +int buf_get_bytes(buf_t *buf, char *string, size_t string_len); +int buf_get_line(buf_t *buf, char *data_out, size_t *data_len); + +#define PEEK_BUF_STARTSWITH_MAX 16 +int buf_peek_startswith(const buf_t *buf, const char *cmd); int buf_set_to_copy(buf_t **output, const buf_t *input); -void assert_buf_ok(buf_t *buf); +void buf_assert_ok(buf_t *buf); + +int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); +void buf_pullup(buf_t *buf, size_t bytes, + const char **head_out, size_t *len_out); #ifdef BUFFERS_PRIVATE -STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); -STATIC void buf_pullup(buf_t *buf, size_t bytes); #ifdef TOR_UNIT_TESTS -void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz); buf_t *buf_new_with_data(const char *cp, size_t sz); #endif -STATIC size_t preferred_chunk_size(size_t target); +size_t buf_preferred_chunk_size(size_t target); #define DEBUG_CHUNK_ALLOC /** A single chunk on a buffer. */ @@ -98,12 +97,29 @@ struct buf_t { chunk_t *head; /**< First chunk in the list, or NULL for none. */ chunk_t *tail; /**< Last chunk in the list, or NULL for none. */ }; -#endif -#ifdef BUFFERS_PRIVATE -STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, - size_t *result_out); -#endif - -#endif +chunk_t *buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped); +/** If a read onto the end of a chunk would be smaller than this number, then + * just start a new chunk. */ +#define MIN_READ_LEN 8 + +/** Return the number of bytes that can be written onto <b>chunk</b> without + * running out of space. */ +static inline size_t +CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) +{ + return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); +} + +/** Return the next character in <b>chunk</b> onto which data can be appended. + * If the chunk is full, this might be off the end of chunk->mem. */ +static inline char * +CHUNK_WRITE_PTR(chunk_t *chunk) +{ + return chunk->data + chunk->datalen; +} + +#endif /* defined(BUFFERS_PRIVATE) */ + +#endif /* !defined(TOR_BUFFERS_H) */ diff --git a/src/common/buffers_tls.c b/src/common/buffers_tls.c new file mode 100644 index 0000000000..041f78b818 --- /dev/null +++ b/src/common/buffers_tls.c @@ -0,0 +1,179 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define BUFFERS_PRIVATE +#include "orconfig.h" +#include <stddef.h> +#include "buffers.h" +#include "buffers_tls.h" +#include "compat.h" +#include "compress.h" +#include "util.h" +#include "torint.h" +#include "torlog.h" +#include "tortls.h" +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +/** As read_to_chunk(), but return (negative) error code on error, blocking, + * or TLS, and the number of bytes read otherwise. */ +static inline int +read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, + size_t at_most) +{ + int read_result; + + tor_assert(CHUNK_REMAINING_CAPACITY(chunk) >= at_most); + read_result = tor_tls_read(tls, CHUNK_WRITE_PTR(chunk), at_most); + if (read_result < 0) + return read_result; + buf->datalen += read_result; + chunk->datalen += read_result; + return read_result; +} + +/** As read_to_buf, but reads from a TLS connection, and returns a TLS + * status value rather than the number of bytes read. + * + * Using TLS on OR connections complicates matters in two ways. + * + * First, a TLS stream has its own read buffer independent of the + * connection's read buffer. (TLS needs to read an entire frame from + * the network before it can decrypt any data. Thus, trying to read 1 + * byte from TLS can require that several KB be read from the network + * and decrypted. The extra data is stored in TLS's decrypt buffer.) + * Because the data hasn't been read by Tor (it's still inside the TLS), + * this means that sometimes a connection "has stuff to read" even when + * poll() didn't return POLLIN. The tor_tls_get_pending_bytes function is + * used in connection.c to detect TLS objects with non-empty internal + * buffers and read from them again. + * + * Second, the TLS stream's events do not correspond directly to network + * events: sometimes, before a TLS stream can read, the network must be + * ready to write -- or vice versa. + */ +int +buf_read_from_tls(buf_t *buf, tor_tls_t *tls, size_t at_most) +{ + int r = 0; + size_t total_read = 0; + + check_no_tls_errors(); + + if (BUG(buf->datalen >= INT_MAX)) + return -1; + if (BUG(buf->datalen >= INT_MAX - at_most)) + return -1; + + while (at_most > total_read) { + size_t readlen = at_most - total_read; + chunk_t *chunk; + if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { + chunk = buf_add_chunk_with_capacity(buf, at_most, 1); + if (readlen > chunk->memlen) + readlen = chunk->memlen; + } else { + size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); + chunk = buf->tail; + if (cap < readlen) + readlen = cap; + } + + r = read_to_chunk_tls(buf, chunk, tls, readlen); + if (r < 0) + return r; /* Error */ + tor_assert(total_read+r < INT_MAX); + total_read += r; + if ((size_t)r < readlen) /* eof, block, or no more to read. */ + break; + } + return (int)total_read; +} + +/** Helper for buf_flush_to_tls(): try to write <b>sz</b> bytes from chunk + * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. (Tries to write + * more if there is a forced pending write size.) On success, deduct the + * bytes written from *<b>buf_flushlen</b>. Return the number of bytes + * written on success, and a TOR_TLS error code on failure or blocking. + */ +static inline int +flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, + size_t sz, size_t *buf_flushlen) +{ + int r; + size_t forced; + char *data; + + forced = tor_tls_get_forced_write_size(tls); + if (forced > sz) + sz = forced; + if (chunk) { + data = chunk->data; + tor_assert(sz <= chunk->datalen); + } else { + data = NULL; + tor_assert(sz == 0); + } + r = tor_tls_write(tls, data, sz); + if (r < 0) + return r; + if (*buf_flushlen > (size_t)r) + *buf_flushlen -= r; + else + *buf_flushlen = 0; + buf_drain(buf, r); + log_debug(LD_NET,"flushed %d bytes, %d ready to flush, %d remain.", + r,(int)*buf_flushlen,(int)buf->datalen); + return r; +} + +/** As buf_flush_to_socket(), but writes data to a TLS connection. Can write + * more than <b>flushlen</b> bytes. + */ +int +buf_flush_to_tls(buf_t *buf, tor_tls_t *tls, size_t flushlen, + size_t *buf_flushlen) +{ + int r; + size_t flushed = 0; + ssize_t sz; + tor_assert(buf_flushlen); + if (BUG(*buf_flushlen > buf->datalen)) { + *buf_flushlen = buf->datalen; + } + if (BUG(flushlen > *buf_flushlen)) { + flushlen = *buf_flushlen; + } + sz = (ssize_t) flushlen; + + /* we want to let tls write even if flushlen is zero, because it might + * have a partial record pending */ + check_no_tls_errors(); + + do { + size_t flushlen0; + if (buf->head) { + if ((ssize_t)buf->head->datalen >= sz) + flushlen0 = sz; + else + flushlen0 = buf->head->datalen; + } else { + flushlen0 = 0; + } + + r = flush_chunk_tls(tls, buf, buf->head, flushlen0, buf_flushlen); + if (r < 0) + return r; + flushed += r; + sz -= r; + if (r == 0) /* Can't flush any more now. */ + break; + } while (sz > 0); + tor_assert(flushed < INT_MAX); + return (int)flushed; +} + diff --git a/src/common/buffers_tls.h b/src/common/buffers_tls.h new file mode 100644 index 0000000000..2f9fda45a0 --- /dev/null +++ b/src/common/buffers_tls.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_BUFFERS_TLS_H +#define TOR_BUFFERS_TLS_H + +struct buf_t; +struct tor_tls_t; + +int buf_read_from_tls(struct buf_t *buf, + struct tor_tls_t *tls, size_t at_most); +int buf_flush_to_tls(struct buf_t *buf, struct tor_tls_t *tls, + size_t sz, size_t *buf_flushlen); + +#endif /* !defined(TOR_BUFFERS_TLS_H) */ + diff --git a/src/common/compat.c b/src/common/compat.c index 4d110aba35..7fe97488e3 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -88,12 +88,12 @@ SecureZeroMemory(PVOID ptr, SIZE_T cnt) while (cnt--) *vcptr++ = 0; } -#endif +#endif /* defined(HAVE_DECL_SECUREZEROMEMORY) && !HAVE_DECL_SECUREZEROMEMORY */ #elif defined(HAVE_READPASSPHRASE_H) #include <readpassphrase.h> #else #include "tor_readpassphrase.h" -#endif +#endif /* defined(_WIN32) || ... */ /* Includes for the process attaching prevention */ #if defined(HAVE_SYS_PRCTL_H) && defined(__linux__) @@ -102,7 +102,7 @@ SecureZeroMemory(PVOID ptr, SIZE_T cnt) #elif defined(__APPLE__) #include <sys/types.h> #include <sys/ptrace.h> -#endif +#endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) || ... */ #ifdef HAVE_NETDB_H #include <netdb.h> @@ -161,7 +161,7 @@ tor_open_cloexec(const char *path, int flags, unsigned mode) * are running on one without. */ if (errno != EINVAL) return -1; -#endif +#endif /* defined(O_CLOEXEC) */ log_debug(LD_FS, "Opening %s with flags %x", p, flags); fd = open(p, flags, mode); @@ -173,7 +173,7 @@ tor_open_cloexec(const char *path, int flags, unsigned mode) return -1; } } -#endif +#endif /* defined(FD_CLOEXEC) */ return fd; } @@ -191,7 +191,7 @@ tor_fopen_cloexec(const char *path, const char *mode) return NULL; } } -#endif +#endif /* defined(FD_CLOEXEC) */ return result; } @@ -212,7 +212,8 @@ tor_rename(const char *path_old, const char *path_new) #define COMPAT_HAS_MMAN_AND_PAGESIZE #endif -#if defined(COMPAT_HAS_MMAN_AND_PAGESIZE) || defined(RUNNING_DOXYGEN) +#if defined(COMPAT_HAS_MMAN_AND_PAGESIZE) || \ + defined(RUNNING_DOXYGEN) /** Try to create a memory mapping for <b>filename</b> and return it. On * failure, return NULL. Sets errno properly, using ERANGE to mean * "empty file". */ @@ -450,7 +451,7 @@ tor_munmap_file(tor_mmap_t *handle) /* Can't fail in this mmap()/munmap()-free case */ return 0; } -#endif +#endif /* defined(COMPAT_HAS_MMAN_AND_PAGESIZE) || ... || ... */ /** Replacement for snprintf. Differs from platform snprintf in two * ways: First, always NUL-terminates its output. Second, always @@ -590,7 +591,7 @@ tor_vasprintf(char **strp, const char *fmt, va_list args) } *strp = strp_tmp; return len; -#endif +#endif /* defined(HAVE_VASPRINTF) || ... */ } /** Given <b>hlen</b> bytes at <b>haystack</b> and <b>nlen</b> bytes at @@ -636,7 +637,7 @@ tor_memmem(const void *_haystack, size_t hlen, } } return NULL; -#endif +#endif /* defined(HAVE_MEMMEM) && (!defined(__GNUC__) || __GNUC__ >= 2) */ } /** @@ -774,7 +775,7 @@ tor_fix_source_file(const char *fname) } return r; } -#endif +#endif /* defined(_WIN32) */ /** * Read a 16-bit value beginning at <b>cp</b>. Equivalent to @@ -868,7 +869,7 @@ replace_file(const char *from, const char *to) return -1; } return tor_rename(from,to); -#endif +#endif /* !defined(_WIN32) */ } /** Change <b>fname</b>'s modification time to now. */ @@ -954,7 +955,7 @@ tor_lockfile_lock(const char *filename, int blocking, int *locked_out) return NULL; } } -#endif +#endif /* defined(_WIN32) || ... */ result = tor_malloc(sizeof(tor_lockfile_t)); result->filename = tor_strdup(filename); @@ -982,7 +983,7 @@ tor_lockfile_unlock(tor_lockfile_t *lockfile) } #else /* Closing the lockfile is sufficient. */ -#endif +#endif /* defined(_WIN32) || ... */ close(lockfile->fd); lockfile->fd = -1; @@ -1030,9 +1031,9 @@ tor_fd_seekend(int fd) * no need to worry. */ if (rc < 0 && errno == ESPIPE) rc = 0; -#endif +#endif /* defined(ESPIPE) */ return (rc < 0) ? -1 : 0; -#endif +#endif /* defined(_WIN32) */ } /** Move <b>fd</b> to position <b>pos</b> in the file. Return -1 on error, 0 @@ -1071,7 +1072,7 @@ tor_ftruncate(int fd) static bitarray_t *open_sockets = NULL; /** The size of <b>open_sockets</b>, in bits. */ static int max_socket = -1; -#endif +#endif /* defined(DEBUG_SOCKET_COUNTING) */ /** Count of number of sockets currently open. (Undercounts sockets opened by * eventdns and libevent.) */ @@ -1141,7 +1142,7 @@ tor_close_socket,(tor_socket_t s)) tor_assert(open_sockets && s <= max_socket); bitarray_clear(open_sockets, s); } -#endif +#endif /* defined(DEBUG_SOCKET_COUNTING) */ if (r == 0) { --n_sockets_open; } else { @@ -1151,7 +1152,7 @@ tor_close_socket,(tor_socket_t s)) #else if (r != EBADF) --n_sockets_open; // LCOV_EXCL_LINE -- EIO and EINTR too hard to force. -#endif +#endif /* defined(_WIN32) */ r = -1; } @@ -1184,9 +1185,9 @@ mark_socket_open(tor_socket_t s) } bitarray_set(open_sockets, s); } -#else +#else /* !(defined(DEBUG_SOCKET_COUNTING)) */ #define mark_socket_open(s) STMT_NIL -#endif +#endif /* defined(DEBUG_SOCKET_COUNTING) */ /** @} */ /** As socket(), but counts the number of open sockets. */ @@ -1244,7 +1245,7 @@ tor_open_socket_with_extensions(int domain, int type, int protocol, * support, we are running on one without. */ if (errno != EINVAL) return s; -#endif /* SOCK_CLOEXEC && SOCK_NONBLOCK */ +#endif /* defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) */ s = socket(domain, type, protocol); if (! SOCKET_OK(s)) @@ -1258,9 +1259,9 @@ tor_open_socket_with_extensions(int domain, int type, int protocol, return TOR_INVALID_SOCKET; } } -#else +#else /* !(defined(FD_CLOEXEC)) */ (void)cloexec; -#endif +#endif /* defined(FD_CLOEXEC) */ if (nonblock) { if (set_socket_nonblocking(s) == -1) { @@ -1316,7 +1317,8 @@ tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr, return TOR_INVALID_SOCKET; } -#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) +#if defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) \ + && defined(SOCK_NONBLOCK) int ext_flags = (cloexec ? SOCK_CLOEXEC : 0) | (nonblock ? SOCK_NONBLOCK : 0); s = accept4(sockfd, addr, len, ext_flags); @@ -1328,7 +1330,7 @@ tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr, * we are missing SOCK_CLOEXEC/SOCK_NONBLOCK support. */ if (errno != EINVAL && errno != ENOSYS) return s; -#endif +#endif /* defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) ... */ s = accept(sockfd, addr, len); if (!SOCKET_OK(s)) @@ -1342,9 +1344,9 @@ tor_accept_socket_with_extensions(tor_socket_t sockfd, struct sockaddr *addr, return TOR_INVALID_SOCKET; } } -#else +#else /* !(defined(FD_CLOEXEC)) */ (void)cloexec; -#endif +#endif /* defined(FD_CLOEXEC) */ if (nonblock) { if (set_socket_nonblocking(s) == -1) { @@ -1404,7 +1406,7 @@ set_socket_nonblocking(tor_socket_t sock) log_warn(LD_NET, "Couldn't set file status flags: %s", strerror(errno)); return -1; } -#endif +#endif /* defined(_WIN32) */ return 0; } @@ -1442,7 +1444,7 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) * are running on one without. */ if (errno != EINVAL) return -errno; -#endif +#endif /* defined(SOCK_CLOEXEC) */ r = socketpair(family, type, protocol, fd); if (r < 0) @@ -1465,7 +1467,7 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) return -errno; } } -#endif +#endif /* defined(FD_CLOEXEC) */ goto sockets_ok; /* So that sockets_ok will not be unused. */ sockets_ok: @@ -1481,9 +1483,9 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) socket_accounting_unlock(); return 0; -#else +#else /* !(defined(HAVE_SOCKETPAIR) && !defined(_WIN32)) */ return tor_ersatz_socketpair(family, type, protocol, fd); -#endif +#endif /* defined(HAVE_SOCKETPAIR) && !defined(_WIN32) */ } #ifdef NEED_ERSATZ_SOCKETPAIR @@ -1640,7 +1642,7 @@ tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) #undef SIZEOF_SOCKADDR -#endif +#endif /* defined(NEED_ERSATZ_SOCKETPAIR) */ /* Return the maximum number of allowed sockets. */ int @@ -1694,7 +1696,7 @@ set_max_file_descriptors(rlim_t limit, int *max_out) #else const char *platform = "unknown platforms with no getrlimit()"; const unsigned long MAX_CONNECTIONS = 15000; -#endif +#endif /* defined(CYGWIN) || defined(__CYGWIN__) || ... */ log_fn(LOG_INFO, LD_NET, "This platform is missing getrlimit(). Proceeding."); if (limit > MAX_CONNECTIONS) { @@ -1705,7 +1707,7 @@ set_max_file_descriptors(rlim_t limit, int *max_out) return -1; } limit = MAX_CONNECTIONS; -#else /* HAVE_GETRLIMIT */ +#else /* !(!defined(HAVE_GETRLIMIT)) */ struct rlimit rlim; if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { @@ -1754,7 +1756,7 @@ set_max_file_descriptors(rlim_t limit, int *max_out) bad = 0; } } -#endif /* OPEN_MAX */ +#endif /* defined(OPEN_MAX) */ if (bad) { log_warn(LD_CONFIG,"Couldn't set maximum number of file descriptors: %s", strerror(errno)); @@ -1763,7 +1765,7 @@ set_max_file_descriptors(rlim_t limit, int *max_out) } /* leave some overhead for logs, etc, */ limit = rlim.rlim_cur; -#endif /* HAVE_GETRLIMIT */ +#endif /* !defined(HAVE_GETRLIMIT) */ if (limit > INT_MAX) limit = INT_MAX; @@ -1801,7 +1803,7 @@ log_credential_status(void) "UID is %u (real), %u (effective), %u (saved)", (unsigned)ruid, (unsigned)euid, (unsigned)suid); } -#else +#else /* !(defined(HAVE_GETRESUID)) */ /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */ ruid = getuid(); euid = geteuid(); @@ -1810,7 +1812,7 @@ log_credential_status(void) log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "UID is %u (real), %u (effective), unknown (saved)", (unsigned)ruid, (unsigned)euid); -#endif +#endif /* defined(HAVE_GETRESUID) */ /* log GIDs */ #ifdef HAVE_GETRESGID @@ -1822,7 +1824,7 @@ log_credential_status(void) "GID is %u (real), %u (effective), %u (saved)", (unsigned)rgid, (unsigned)egid, (unsigned)sgid); } -#else +#else /* !(defined(HAVE_GETRESGID)) */ /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */ rgid = getgid(); egid = getegid(); @@ -1830,7 +1832,7 @@ log_credential_status(void) log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "GID is %u (real), %u (effective), unknown (saved)", (unsigned)rgid, (unsigned)egid); -#endif +#endif /* defined(HAVE_GETRESGID) */ /* log supplementary groups */ sup_gids_size = 64; @@ -1870,7 +1872,7 @@ log_credential_status(void) return 0; } -#endif +#endif /* !defined(_WIN32) */ #ifndef _WIN32 /** Cached struct from the last getpwname() call we did successfully. */ @@ -1970,7 +1972,7 @@ tor_getpwuid(uid_t uid) return NULL; } -#endif +#endif /* !defined(_WIN32) */ /** Return true iff we were compiled with capability support, and capabilities * seem to work. **/ @@ -1983,9 +1985,9 @@ have_capability_support(void) return 0; cap_free(caps); return 1; -#else +#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */ return 0; -#endif +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ } #ifdef HAVE_LINUX_CAPABILITIES @@ -2044,7 +2046,7 @@ drop_capabilities(int pre_setuid) return 0; } -#endif +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ /** Call setuid and setgid to run as <b>user</b> and switch to their * primary group. Return 0 on success. On failure, log and return -1. @@ -2094,13 +2096,13 @@ switch_id(const char *user, const unsigned flags) if (drop_capabilities(1)) return -1; } -#else +#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */ (void) keep_bindlow; if (warn_if_no_caps) { log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support " "on this system."); } -#endif +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ /* Properly switch egid,gid,euid,uid here or bail out */ if (setgroups(1, &pw->pw_gid)) { @@ -2160,7 +2162,7 @@ switch_id(const char *user, const unsigned flags) if (drop_capabilities(0)) return -1; } -#endif +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ #if !defined(CYGWIN) && !defined(__CYGWIN__) /* If we tried to drop privilege to a group/user other than root, attempt to @@ -2184,7 +2186,7 @@ switch_id(const char *user, const unsigned flags) return -1; } } -#endif +#endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */ /* Check what really happened */ if (log_credential_status()) { @@ -2193,8 +2195,8 @@ switch_id(const char *user, const unsigned flags) have_already_switched_id = 1; /* mark success so we never try again */ -#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && defined(HAVE_PRCTL) -#ifdef PR_SET_DUMPABLE +#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \ + defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) if (pw->pw_uid) { /* Re-enable core dumps if we're not running as root. */ log_info(LD_CONFIG, "Re-enabling coredumps"); @@ -2202,17 +2204,16 @@ switch_id(const char *user, const unsigned flags) log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno)); } } -#endif -#endif +#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */ return 0; -#else +#else /* !(!defined(_WIN32)) */ (void)user; (void)flags; log_warn(LD_CONFIG, "Switching users is unsupported on your OS."); return -1; -#endif +#endif /* !defined(_WIN32) */ } /* We only use the linux prctl for now. There is no Win32 support; this may @@ -2235,35 +2236,32 @@ switch_id(const char *user, const unsigned flags) int tor_disable_debugger_attach(void) { - int r, attempted; - r = -1; - attempted = 0; + int r = -1; log_debug(LD_CONFIG, "Attemping to disable debugger attachment to Tor for " "unprivileged users."); -#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && defined(HAVE_PRCTL) -#ifdef PR_SET_DUMPABLE - attempted = 1; +#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) \ + && defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE) +#define TRIED_TO_DISABLE r = prctl(PR_SET_DUMPABLE, 0); -#endif -#endif -#if defined(__APPLE__) && defined(PT_DENY_ATTACH) - if (r < 0) { - attempted = 1; - r = ptrace(PT_DENY_ATTACH, 0, 0, 0); - } -#endif +#elif defined(__APPLE__) && defined(PT_DENY_ATTACH) +#define TRIED_TO_ATTACH + r = ptrace(PT_DENY_ATTACH, 0, 0, 0); +#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) ... || ... */ // XXX: TODO - Mac OS X has dtrace and this may be disabled. // XXX: TODO - Windows probably has something similar - if (r == 0 && attempted) { +#ifdef TRIED_TO_DISABLE + if (r == 0) { log_debug(LD_CONFIG,"Debugger attachment disabled for " "unprivileged users."); return 1; - } else if (attempted) { + } else { log_warn(LD_CONFIG, "Unable to disable debugger attaching: %s", strerror(errno)); } +#endif /* defined(TRIED_TO_DISABLE) */ +#undef TRIED_TO_DISABLE return r; } @@ -2282,7 +2280,7 @@ get_user_homedir(const char *username) } return tor_strdup(pw->pw_dir); } -#endif +#endif /* defined(HAVE_PWD_H) */ /** Modify <b>fname</b> to contain the name of its parent directory. Doesn't * actually examine the filesystem; does a purely syntactic modification. @@ -2310,7 +2308,7 @@ get_parent_directory(char *fname) if (fname[0] && fname[1] == ':') { fname += 2; } -#endif +#endif /* defined(_WIN32) */ /* Now we want to remove all path-separators at the end of the string, * and to remove the end of the string starting with the path separator * before the last non-path-separator. In perl, this would be @@ -2349,17 +2347,36 @@ get_parent_directory(char *fname) static char * alloc_getcwd(void) { -#ifdef PATH_MAX -#define MAX_CWD PATH_MAX -#else -#define MAX_CWD 4096 -#endif +#ifdef HAVE_GET_CURRENT_DIR_NAME + /* Glibc makes this nice and simple for us. */ + char *cwd = get_current_dir_name(); + char *result = NULL; + if (cwd) { + /* We make a copy here, in case tor_malloc() is not malloc(). */ + result = tor_strdup(cwd); + raw_free(cwd); // alias for free to avoid tripping check-spaces. + } + return result; +#else /* !(defined(HAVE_GET_CURRENT_DIR_NAME)) */ + size_t size = 1024; + char *buf = NULL; + char *ptr = NULL; + + while (ptr == NULL) { + buf = tor_realloc(buf, size); + ptr = getcwd(buf, size); - char path_buf[MAX_CWD]; - char *path = getcwd(path_buf, sizeof(path_buf)); - return path ? tor_strdup(path) : NULL; + if (ptr == NULL && errno != ERANGE) { + tor_free(buf); + return NULL; + } + + size *= 2; + } + return buf; +#endif /* defined(HAVE_GET_CURRENT_DIR_NAME) */ } -#endif +#endif /* !defined(_WIN32) */ /** Expand possibly relative path <b>fname</b> to an absolute path. * Return a newly allocated string, possibly equal to <b>fname</b>. */ @@ -2375,7 +2392,7 @@ make_path_absolute(char *fname) if (absfname_malloced) raw_free(absfname_malloced); return absfname; -#else +#else /* !(defined(_WIN32)) */ char *absfname = NULL, *path = NULL; tor_assert(fname); @@ -2398,7 +2415,7 @@ make_path_absolute(char *fname) } } return absfname; -#endif +#endif /* defined(_WIN32) */ } #ifndef HAVE__NSGETENVIRON @@ -2407,8 +2424,8 @@ make_path_absolute(char *fname) #ifndef RUNNING_DOXYGEN extern char **environ; #endif -#endif -#endif +#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */ +#endif /* !defined(HAVE__NSGETENVIRON) */ /** Return the current environment. This is a portable replacement for * 'environ'. */ @@ -2420,9 +2437,9 @@ get_environment(void) * when we do a mostly-static build on OSX 10.7, the resulting binary won't * work on OSX 10.6. */ return *_NSGetEnviron(); -#else +#else /* !(defined(HAVE__NSGETENVIRON)) */ return environ; -#endif +#endif /* defined(HAVE__NSGETENVIRON) */ } /** Get name of current host and write it to <b>name</b> array, whose @@ -2563,6 +2580,7 @@ tor_inet_pton(int af, const char *src, void *dst) int gapPos = -1, i, setWords=0; const char *dot = strchr(src, '.'); const char *eow; /* end of words. */ + memset(words, 0xf8, sizeof(words)); if (dot == src) return 0; else if (!dot) @@ -2600,7 +2618,7 @@ tor_inet_pton(int af, const char *src, void *dst) long r = strtol(src, &next, 16); if (next == NULL || next == src) { /* The 'next == src' error case can happen on versions of openbsd - * where treats "0xfoo" as an error, rather than as "0" followed by + * which treat "0xfoo" as an error, rather than as "0" followed by * "xfoo". */ return 0; } @@ -2699,7 +2717,7 @@ get_uname,(void)) /* (Linux says 0 is success, Solaris says 1 is success) */ strlcpy(uname_result, u.sysname, sizeof(uname_result)); } else -#endif +#endif /* defined(HAVE_UNAME) */ { #ifdef _WIN32 OSVERSIONINFOEX info; @@ -2761,12 +2779,12 @@ get_uname,(void)) info.wProductType == VER_NT_DOMAIN_CONTROLLER) { strlcat(uname_result, " [server]", sizeof(uname_result)); } -#endif -#else +#endif /* defined(VER_NT_SERVER) */ +#else /* !(defined(_WIN32)) */ /* LCOV_EXCL_START -- can't provoke uname failure */ strlcpy(uname_result, "Unknown platform", sizeof(uname_result)); /* LCOV_EXCL_STOP */ -#endif +#endif /* defined(_WIN32) */ } uname_result_is_set = 1; } @@ -2822,7 +2840,7 @@ compute_num_cpus_impl(void) return -1; #else return -1; -#endif +#endif /* defined(_WIN32) || ... */ } #define MAX_DETECTABLE_CPUS 16 @@ -2985,7 +3003,7 @@ tor_localtime_r(const time_t *timep, struct tm *result) memcpy(result, r, sizeof(struct tm)); return correct_tm(1, timep, result, r); } -#endif +#endif /* defined(HAVE_LOCALTIME_R) || ... */ /** @} */ /** @{ */ @@ -3028,9 +3046,13 @@ tor_gmtime_r(const time_t *timep, struct tm *result) memcpy(result, r, sizeof(struct tm)); return correct_tm(0, timep, result, r); } -#endif +#endif /* defined(HAVE_GMTIME_R) || ... */ #if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK) +#define HAVE_UNIX_MLOCKALL +#endif + +#ifdef HAVE_UNIX_MLOCKALL /** Attempt to raise the current and max rlimit to infinity for our process. * This only needs to be done once and can probably only be done when we have * not already dropped privileges. @@ -3061,7 +3083,7 @@ tor_set_max_memlock(void) return 0; } -#endif +#endif /* defined(HAVE_UNIX_MLOCKALL) */ /** Attempt to lock all current and all future memory pages. * This should only be called once and while we're privileged. @@ -3086,7 +3108,7 @@ tor_mlockall(void) * http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx */ -#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK) +#ifdef HAVE_UNIX_MLOCKALL if (tor_set_max_memlock() == 0) { log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY."); } @@ -3107,10 +3129,10 @@ tor_mlockall(void) "pages: %s", strerror(errno)); return -1; } -#else +#else /* !(defined(HAVE_UNIX_MLOCKALL)) */ log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?"); return -1; -#endif +#endif /* defined(HAVE_UNIX_MLOCKALL) */ } /** @@ -3138,7 +3160,7 @@ tor_socket_errno(tor_socket_t sock) } return err; } -#endif +#endif /* defined(_WIN32) */ #if defined(_WIN32) #define E(code, s) { code, (s " [" #code " ]") } @@ -3214,7 +3236,7 @@ tor_socket_strerror(int e) } return strerror(e); } -#endif +#endif /* defined(_WIN32) */ /** Called before we make any calls to network-related functions. * (Some operating systems require their network libraries to be @@ -3240,7 +3262,7 @@ network_init(void) /* WSAData.iMaxSockets might show the max sockets we're allowed to use. * We might use it to complain if we're trying to be a server but have * too few sockets available. */ -#endif +#endif /* defined(_WIN32) */ return 0; } @@ -3276,9 +3298,9 @@ format_win32_error(DWORD err) result = tor_malloc(len); wcstombs(result,str,len); result[len-1] = '\0'; -#else +#else /* !(defined(UNICODE)) */ result = tor_strdup(str); -#endif +#endif /* defined(UNICODE) */ } else { result = tor_strdup("<unformattable error>"); } @@ -3287,7 +3309,7 @@ format_win32_error(DWORD err) } return result; } -#endif +#endif /* defined(_WIN32) */ #if defined(HW_PHYSMEM64) /* This appears to be an OpenBSD thing */ @@ -3295,7 +3317,7 @@ format_win32_error(DWORD err) #elif defined(HW_MEMSIZE) /* OSX defines this one */ #define INT64_HW_MEM HW_MEMSIZE -#endif +#endif /* defined(HW_PHYSMEM64) || ... */ /** * Helper: try to detect the total system memory, and return it. On failure, @@ -3328,8 +3350,8 @@ get_total_system_memory_impl(void) tor_free(s); return result * 1024; - err: /* LCOV_EXCL_START Can't reach this unless proc is broken. */ + err: tor_free(s); close(fd); return 0; @@ -3369,7 +3391,7 @@ get_total_system_memory_impl(void) #else /* I have no clue. */ return 0; -#endif +#endif /* defined(__linux__) || ... */ } /** @@ -3402,7 +3424,7 @@ get_total_system_memory(size_t *mem_out) * size_t. */ m = SIZE_MAX; } -#endif +#endif /* SIZE_MAX != UINT64_MAX */ *mem_out = mem_cached = (size_t) m; @@ -3483,7 +3505,7 @@ tor_getpass(const char *prompt, char *output, size_t buflen) return r; #else #error "No implementation for tor_getpass found!" -#endif +#endif /* defined(HAVE_READPASSPHRASE) || ... */ } /** Return the amount of free disk space we have permission to use, in @@ -3523,6 +3545,6 @@ tor_get_avail_disk_space(const char *path) (void)path; errno = ENOSYS; return -1; -#endif +#endif /* defined(HAVE_STATVFS) || ... */ } diff --git a/src/common/compat.h b/src/common/compat.h index 473ad2b957..fee9e6587d 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -50,8 +50,8 @@ * clang rejects because it is off the end of a less-than-3. Clang hates this, * even though those references never actually happen. */ # undef strcmp -# endif -#endif +#endif /* __has_feature(address_sanitizer) */ +#endif /* defined(__has_feature) */ #include <stdio.h> #include <errno.h> @@ -76,13 +76,13 @@ __attribute__ ((format(printf, formatIdx, firstArg))) #else #define CHECK_PRINTF(formatIdx, firstArg) -#endif +#endif /* defined(__GNUC__) */ #ifdef __GNUC__ #define CHECK_SCANF(formatIdx, firstArg) \ __attribute__ ((format(scanf, formatIdx, firstArg))) #else #define CHECK_SCANF(formatIdx, firstArg) -#endif +#endif /* defined(__GNUC__) */ /* What GCC do we have? */ #ifdef __GNUC__ @@ -109,18 +109,18 @@ PRAGMA_DIAGNOSTIC_(ignored PRAGMA_JOIN_STRINGIFY_(-W,warningopt)) # define ENABLE_GCC_WARNING(warningopt) \ PRAGMA_DIAGNOSTIC_(pop) -# else +#else /* !(defined(__clang__) || GCC_VERSION >= 406) */ /* older version of gcc: no push/pop support. */ # define DISABLE_GCC_WARNING(warningopt) \ PRAGMA_DIAGNOSTIC_(ignored PRAGMA_JOIN_STRINGIFY_(-W,warningopt)) # define ENABLE_GCC_WARNING(warningopt) \ PRAGMA_DIAGNOSTIC_(warning PRAGMA_JOIN_STRINGIFY_(-W,warningopt)) -# endif -#else /* ifdef __GNUC__ */ +#endif /* defined(__clang__) || GCC_VERSION >= 406 */ +#else /* !(defined(__GNUC__)) */ /* not gcc at all */ # define DISABLE_GCC_WARNING(warning) # define ENABLE_GCC_WARNING(warning) -#endif +#endif /* defined(__GNUC__) */ /* inline is __inline on windows. */ #ifdef _WIN32 @@ -142,9 +142,9 @@ #define __func__ __FUNC__ #else #define __func__ "???" -#endif -#endif /* ifndef MAVE_MACRO__func__ */ -#endif /* if not windows */ +#endif /* defined(HAVE_MACRO__FUNCTION__) || ... */ +#endif /* !defined(HAVE_MACRO__func__) */ +#endif /* defined(_MSC_VER) */ #define U64_TO_DBL(x) ((double) (x)) #define DBL_TO_U64(x) ((uint64_t) (x)) @@ -157,7 +157,7 @@ * problems), but if enumerated types are unsigned, we must use unsigned, * so that the loss of precision doesn't make large values negative. */ #define ENUM_BF(t) t -#endif +#endif /* defined(ENUM_VALS_ARE_SIGNED) */ /* GCC has several useful attributes. */ #if defined(__GNUC__) && __GNUC__ >= 3 @@ -194,7 +194,7 @@ * taken. This can generate slightly better code with some CPUs. */ #define PREDICT_UNLIKELY(exp) __builtin_expect(!!(exp), 0) -#else +#else /* !(defined(__GNUC__) && __GNUC__ >= 3) */ #define ATTR_NORETURN #define ATTR_CONST #define ATTR_MALLOC @@ -204,7 +204,7 @@ #define ATTR_WUR #define PREDICT_LIKELY(exp) (exp) #define PREDICT_UNLIKELY(exp) (exp) -#endif +#endif /* defined(__GNUC__) && __GNUC__ >= 3 */ /** Expands to a syntactically valid empty statement. */ #define STMT_NIL (void)0 @@ -224,7 +224,7 @@ #else #define STMT_BEGIN do { #define STMT_END } while (0) -#endif +#endif /* defined(__GNUC__) || ... */ /* Some tools (like coccinelle) don't like to see operators as macro * arguments. */ @@ -251,7 +251,7 @@ */ #undef strlcat #undef strlcpy -#endif +#endif /* defined __APPLE__ */ #ifndef HAVE_STRLCAT size_t strlcat(char *dst, const char *src, size_t siz) ATTR_NONNULL((1,2)); @@ -272,24 +272,28 @@ size_t strlcpy(char *dst, const char *src, size_t siz) ATTR_NONNULL((1,2)); #define I64_PRINTF_ARG(a) (a) #define I64_SCANF_ARG(a) (a) #define I64_LITERAL(n) (n ## i64) -#else +#else /* !(defined(_MSC_VER)) */ #define U64_PRINTF_ARG(a) ((long long unsigned int)(a)) #define U64_SCANF_ARG(a) ((long long unsigned int*)(a)) #define U64_LITERAL(n) (n ## llu) #define I64_PRINTF_ARG(a) ((long long signed int)(a)) #define I64_SCANF_ARG(a) ((long long signed int*)(a)) #define I64_LITERAL(n) (n ## ll) +#endif /* defined(_MSC_VER) */ + +#if defined(__MINGW32__) || defined(__MINGW64__) +#define MINGW_ANY #endif -#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__) +#if defined(_MSC_VER) || defined(MINGW_ANY) /** The formatting string used to put a uint64_t value in a printf() or * scanf() function. See also U64_PRINTF_ARG and U64_SCANF_ARG. */ #define U64_FORMAT "%I64u" #define I64_FORMAT "%I64d" -#else +#else /* !(defined(_MSC_VER) || defined(MINGW_ANY)) */ #define U64_FORMAT "%llu" #define I64_FORMAT "%lld" -#endif +#endif /* defined(_MSC_VER) || defined(MINGW_ANY) */ #if (SIZEOF_INTPTR_T == SIZEOF_INT) #define INTPTR_T_FORMAT "%d" @@ -302,7 +306,7 @@ size_t strlcpy(char *dst, const char *src, size_t siz) ATTR_NONNULL((1,2)); #define INTPTR_PRINTF_ARG(x) I64_PRINTF_ARG(x) #else #error Unknown: SIZEOF_INTPTR_T -#endif +#endif /* (SIZEOF_INTPTR_T == SIZEOF_INT) || ... */ /** Represents an mmaped file. Allocated via tor_mmap_file; freed with * tor_munmap_file. */ @@ -316,7 +320,7 @@ typedef struct tor_mmap_t { * size, rounded up to the nearest page.) */ #elif defined _WIN32 HANDLE mmap_handle; -#endif +#endif /* defined(HAVE_SYS_MMAN_H) || ... */ } tor_mmap_t; @@ -378,7 +382,7 @@ const char *tor_fix_source_file(const char *fname); #else #define SHORT_FILE__ (__FILE__) #define tor_fix_source_file(s) (s) -#endif +#endif /* defined(_WIN32) */ /* ===== Time compatibility */ @@ -397,7 +401,7 @@ struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); (tvout)->tv_sec++; \ } \ } while (0) -#endif +#endif /* !defined(timeradd) */ #ifndef timersub /** Replacement for timersub on platforms that do not have it: sets tvout to @@ -411,7 +415,7 @@ struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); (tvout)->tv_sec--; \ } \ } while (0) -#endif +#endif /* !defined(timersub) */ #ifndef timercmp /** Replacement for timercmp on platforms that do not have it: returns true @@ -425,7 +429,7 @@ struct tm *tor_gmtime_r(const time_t *timep, struct tm *result); (((tv1)->tv_sec == (tv2)->tv_sec) ? \ ((tv1)->tv_usec op (tv2)->tv_usec) : \ ((tv1)->tv_sec op (tv2)->tv_sec)) -#endif +#endif /* !defined(timercmp) */ /* ===== File compatibility */ int tor_open_cloexec(const char *path, int flags, unsigned mode); @@ -467,7 +471,7 @@ typedef int socklen_t; #define TOR_SOCKET_T_FORMAT INTPTR_T_FORMAT #define SOCKET_OK(s) ((SOCKET)(s) != INVALID_SOCKET) #define TOR_INVALID_SOCKET INVALID_SOCKET -#else +#else /* !(defined(_WIN32)) */ /** Type used for a network socket. */ #define tor_socket_t int #define TOR_SOCKET_T_FORMAT "%d" @@ -475,7 +479,7 @@ typedef int socklen_t; #define SOCKET_OK(s) ((s) >= 0) /** Error/uninitialized value for a tor_socket_t. */ #define TOR_INVALID_SOCKET (-1) -#endif +#endif /* defined(_WIN32) */ int tor_close_socket_simple(tor_socket_t s); MOCK_DECL(int, tor_close_socket, (tor_socket_t s)); @@ -522,19 +526,19 @@ struct in6_addr #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 }; -#endif +#endif /* !defined(HAVE_STRUCT_IN6_ADDR) */ /** @{ */ /** Many BSD variants seem not to define these. */ -#if defined(__APPLE__) || defined(__darwin__) || defined(__FreeBSD__) \ - || defined(__NetBSD__) || defined(__OpenBSD__) +#if defined(__APPLE__) || defined(__darwin__) || \ + defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #ifndef s6_addr16 #define s6_addr16 __u6_addr.__u6_addr16 #endif #ifndef s6_addr32 #define s6_addr32 __u6_addr.__u6_addr32 #endif -#endif +#endif /* defined(__APPLE__) || defined(__darwin__) || ... */ /** @} */ #ifndef HAVE_SA_FAMILY_T @@ -566,7 +570,7 @@ struct sockaddr_in6 { struct in6_addr sin6_addr; // uint32_t sin6_scope_id; }; -#endif +#endif /* !defined(HAVE_STRUCT_SOCKADDR_IN6) */ MOCK_DECL(int,tor_gethostname,(char *name, size_t namelen)); int tor_inet_aton(const char *cp, struct in_addr *addr) ATTR_NONNULL((1,2)); @@ -607,14 +611,14 @@ int network_init(void); #define ERRNO_IS_EINTR(e) ((e) == WSAEINTR || 0) int tor_socket_errno(tor_socket_t sock); const char *tor_socket_strerror(int e); -#else +#else /* !(defined(_WIN32)) */ #define SOCK_ERRNO(e) e #if EAGAIN == EWOULDBLOCK /* || 0 is for -Wparentheses-equality (-Wall?) appeasement under clang */ #define ERRNO_IS_EAGAIN(e) ((e) == EAGAIN || 0) #else #define ERRNO_IS_EAGAIN(e) ((e) == EAGAIN || (e) == EWOULDBLOCK) -#endif +#endif /* EAGAIN == EWOULDBLOCK */ #define ERRNO_IS_EINTR(e) ((e) == EINTR || 0) #define ERRNO_IS_EINPROGRESS(e) ((e) == EINPROGRESS || 0) #define ERRNO_IS_CONN_EINPROGRESS(e) ((e) == EINPROGRESS || 0) @@ -625,7 +629,7 @@ const char *tor_socket_strerror(int e); #define ERRNO_IS_EADDRINUSE(e) (((e) == EADDRINUSE) || 0) #define tor_socket_errno(sock) (errno) #define tor_socket_strerror(e) strerror(e) -#endif +#endif /* defined(_WIN32) */ /** Specified SOCKS5 status codes. */ typedef enum { @@ -728,7 +732,7 @@ char *format_win32_error(DWORD err); #define VER_SUITE_SINGLEUSERTS 0x00000100 #endif -#endif +#endif /* defined(_WIN32) */ #ifdef COMPAT_PRIVATE #if !defined(HAVE_SOCKETPAIR) || defined(_WIN32) || defined(TOR_UNIT_TESTS) @@ -736,12 +740,12 @@ char *format_win32_error(DWORD err); STATIC int tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]); #endif -#endif +#endif /* defined(COMPAT_PRIVATE) */ ssize_t tor_getpass(const char *prompt, char *output, size_t buflen); /* This needs some of the declarations above so we include it here. */ #include "compat_threads.h" -#endif +#endif /* !defined(TOR_COMPAT_H) */ diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index 31eb4ac496..740cc2a11d 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -88,8 +88,8 @@ static struct event_base *the_event_base = NULL; (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1040) #else #define MACOSX_KQUEUE_IS_BROKEN 0 -#endif -#endif +#endif /* defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) */ +#endif /* defined(__APPLE__) */ /** Initialize the Libevent library and set up the event base. */ void @@ -237,8 +237,9 @@ tor_init_libevent_rng(void) return rv; } -#if defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER >= V(2,1,1) \ - && !defined(TOR_UNIT_TESTS) +#if defined(LIBEVENT_VERSION_NUMBER) && \ + LIBEVENT_VERSION_NUMBER >= V(2,1,1) && \ + !defined(TOR_UNIT_TESTS) void tor_gettimeofday_cached(struct timeval *tv) { @@ -249,7 +250,7 @@ tor_gettimeofday_cache_clear(void) { event_base_update_cache_time(the_event_base); } -#else +#else /* !(defined(LIBEVENT_VERSION_NUMBER) && ...) */ /** Cache the current hi-res time; the cache gets reset when libevent * calls us. */ static struct timeval cached_time_hires = {0, 0}; @@ -289,6 +290,6 @@ tor_libevent_postfork(void) int r = event_reinit(tor_libevent_get_base()); tor_assert(r == 0); } -#endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(LIBEVENT_VERSION_NUMBER) && ... */ diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 904938415c..834354c405 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -70,7 +70,7 @@ void tor_libevent_postfork(void); STATIC void libevent_logging_callback(int severity, const char *msg); -#endif +#endif /* defined(COMPAT_LIBEVENT_PRIVATE) */ -#endif +#endif /* !defined(TOR_COMPAT_LIBEVENT_H) */ diff --git a/src/common/compat_openssl.h b/src/common/compat_openssl.h index 2b94fe5b4e..c695f1e9df 100644 --- a/src/common/compat_openssl.h +++ b/src/common/compat_openssl.h @@ -25,7 +25,7 @@ /* We define this macro if we're trying to build with the majorly refactored * API in OpenSSL 1.1 */ #define OPENSSL_1_1_API -#endif +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) && ... */ #ifndef OPENSSL_1_1_API #define OPENSSL_VERSION SSLEAY_VERSION @@ -37,11 +37,11 @@ ((st) == SSL3_ST_SW_SRVR_HELLO_B)) #define OSSL_HANDSHAKE_STATE int #define CONST_IF_OPENSSL_1_1_API -#else +#else /* !(!defined(OPENSSL_1_1_API)) */ #define STATE_IS_SW_SERVER_HELLO(st) \ ((st) == TLS_ST_SW_SRVR_HELLO) #define CONST_IF_OPENSSL_1_1_API const -#endif +#endif /* !defined(OPENSSL_1_1_API) */ -#endif +#endif /* !defined(TOR_COMPAT_OPENSSL_H) */ diff --git a/src/common/compat_pthreads.c b/src/common/compat_pthreads.c index 8e4b833573..002274c469 100644 --- a/src/common/compat_pthreads.c +++ b/src/common/compat_pthreads.c @@ -201,20 +201,21 @@ tor_cond_init(tor_cond_t *cond) } #if defined(HAVE_CLOCK_GETTIME) -#if defined(CLOCK_MONOTONIC) && defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) +#if defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && \ + defined(CLOCK_MONOTONIC) /* Use monotonic time so when we timedwait() on it, any clock adjustment * won't affect the timeout value. */ if (pthread_condattr_setclock(&condattr, CLOCK_MONOTONIC)) { return -1; } #define USE_COND_CLOCK CLOCK_MONOTONIC -#else /* !defined HAVE_PTHREAD_CONDATTR_SETCLOCK */ +#else /* !(defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && ...) */ /* On OSX Sierra, there is no pthread_condattr_setclock, so we are stuck * with the realtime clock. */ #define USE_COND_CLOCK CLOCK_REALTIME -#endif /* which clock to use */ -#endif /* HAVE_CLOCK_GETTIME */ +#endif /* defined(HAVE_PTHREAD_CONDATTR_SETCLOCK) && ... */ +#endif /* defined(HAVE_CLOCK_GETTIME) */ if (pthread_cond_init(&cond->cond, &condattr)) { return -1; } @@ -266,11 +267,11 @@ tor_cond_wait(tor_cond_t *cond, tor_mutex_t *mutex, const struct timeval *tv) tvnow.tv_sec = ts.tv_sec; tvnow.tv_usec = (int)(ts.tv_nsec / 1000); timeradd(tv, &tvnow, &tvsum); -#else +#else /* !(defined(HAVE_CLOCK_GETTIME) && defined(USE_COND_CLOCK)) */ if (gettimeofday(&tvnow, NULL) < 0) return -1; timeradd(tv, &tvnow, &tvsum); -#endif /* HAVE_CLOCK_GETTIME, CLOCK_MONOTONIC */ +#endif /* defined(HAVE_CLOCK_GETTIME) && defined(USE_COND_CLOCK) */ ts.tv_sec = tvsum.tv_sec; ts.tv_nsec = tvsum.tv_usec * 1000; diff --git a/src/common/compat_rust.h b/src/common/compat_rust.h index 752a29b56c..72fde39296 100644 --- a/src/common/compat_rust.h +++ b/src/common/compat_rust.h @@ -24,5 +24,5 @@ const char *rust_str_get(const rust_str_t); rust_str_t rust_welcome_string(void); -#endif +#endif /* !defined(TOR_RUST_COMPAT_H) */ diff --git a/src/common/compat_threads.c b/src/common/compat_threads.c index c593e9af8d..208d3138d9 100644 --- a/src/common/compat_threads.c +++ b/src/common/compat_threads.c @@ -126,7 +126,7 @@ read_ni(int fd, void *buf, size_t n) } return r; } -#endif +#endif /* defined(HAVE_EVENTFD) || defined(HAVE_PIPE) */ /** As send(), but retry on EINTR, and return the negative error code on * error. */ @@ -186,7 +186,7 @@ eventfd_drain(int fd) return r; return 0; } -#endif +#endif /* defined(HAVE_EVENTFD) */ #ifdef HAVE_PIPE /** Send a byte over a pipe. Return 0 on success or EAGAIN; -1 on error */ @@ -214,7 +214,7 @@ pipe_drain(int fd) /* A value of r = 0 means EOF on the fd so successfully drained. */ return 0; } -#endif +#endif /* defined(HAVE_PIPE) */ /** Send a byte on socket <b>fd</b>t. Return 0 on success or EAGAIN, * -1 on error. */ @@ -276,7 +276,7 @@ alert_sockets_create(alert_sockets_t *socks_out, uint32_t flags) socks_out->drain_fn = eventfd_drain; return 0; } -#endif +#endif /* defined(HAVE_EVENTFD) */ #ifdef HAVE_PIPE2 /* Now we're going to try pipes. First type the pipe2() syscall, if we @@ -289,7 +289,7 @@ alert_sockets_create(alert_sockets_t *socks_out, uint32_t flags) socks_out->drain_fn = pipe_drain; return 0; } -#endif +#endif /* defined(HAVE_PIPE2) */ #ifdef HAVE_PIPE /* Now try the regular pipe() syscall. Pipes have a bit lower overhead than @@ -313,7 +313,7 @@ alert_sockets_create(alert_sockets_t *socks_out, uint32_t flags) socks_out->drain_fn = pipe_drain; return 0; } -#endif +#endif /* defined(HAVE_PIPE) */ /* If nothing else worked, fall back on socketpair(). */ if (!(flags & ASOCKS_NOSOCKETPAIR) && diff --git a/src/common/compat_threads.h b/src/common/compat_threads.h index 9fa3d0d0b7..42f14eab2a 100644 --- a/src/common/compat_threads.h +++ b/src/common/compat_threads.h @@ -20,7 +20,7 @@ #define USE_PTHREADS #else #error "No threading system was found" -#endif +#endif /* defined(_WIN32) || ... */ int spawn_func(void (*func)(void *), void *data); void spawn_exit(void) ATTR_NORETURN; @@ -41,7 +41,7 @@ typedef struct tor_mutex_t { #else /** No-threads only: Dummy variable so that tor_mutex_t takes up space. */ int _unused; -#endif +#endif /* defined(USE_WIN32_THREADS) || ... */ } tor_mutex_t; tor_mutex_t *tor_mutex_new(void); @@ -73,7 +73,7 @@ typedef struct tor_cond_t { int generation; #else #error no known condition implementation. -#endif +#endif /* defined(USE_PTHREADS) || ... */ } tor_cond_t; tor_cond_t *tor_cond_new(void); @@ -161,5 +161,5 @@ void atomic_counter_add(atomic_counter_t *counter, size_t add); void atomic_counter_sub(atomic_counter_t *counter, size_t sub); size_t atomic_counter_get(atomic_counter_t *counter); -#endif +#endif /* !defined(TOR_COMPAT_THREADS_H) */ diff --git a/src/common/compat_time.c b/src/common/compat_time.c index 2ccaa36e49..1ce6f5ce4e 100644 --- a/src/common/compat_time.c +++ b/src/common/compat_time.c @@ -28,7 +28,7 @@ /* as fallback implementation for tor_sleep_msec */ #include <sys/select.h> #endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef __APPLE__ #include <mach/mach_time.h> @@ -64,9 +64,9 @@ tor_sleep_msec(int msec) select(0, NULL, NULL, NULL, &tv); #else sleep(CEIL_DIV(msec, 1000)); -#endif +#endif /* defined(_WIN32) || ... */ } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Set *timeval to the current time of day. On error, log and terminate. * (Same as gettimeofday(timeval,NULL), but never returns -1.) @@ -112,7 +112,7 @@ tor_gettimeofday(struct timeval *timeval) timeval->tv_usec = tb.millitm * 1000; #else #error "No way to get time." -#endif +#endif /* defined(_WIN32) || ... */ return; } @@ -187,8 +187,8 @@ monotime_coarse_set_mock_time_nsec(int64_t nsec) tor_assert_nonfatal(monotime_mocking_enabled == 1); mock_time_nsec_coarse = nsec; } -#endif -#endif +#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */ +#endif /* defined(TOR_UNIT_TESTS) */ /* "ratchet" functions for monotonic time. */ @@ -235,7 +235,7 @@ ratchet_coarse_performance_counter(const int64_t count_raw) last_tick_count = count; return count; } -#endif +#endif /* defined(_WIN32) || defined(TOR_UNIT_TESTS) */ #if defined(MONOTIME_USING_GETTIMEOFDAY) || defined(TOR_UNIT_TESTS) static struct timeval last_timeofday = { 0, 0 }; @@ -251,7 +251,7 @@ ratchet_timeval(const struct timeval *timeval_raw, struct timeval *out) { /* must hold lock */ timeradd(timeval_raw, &timeofday_offset, out); - if (PREDICT_UNLIKELY(timercmp(out, &last_timeofday, <))) { + if (PREDICT_UNLIKELY(timercmp(out, &last_timeofday, OP_LT))) { /* time ran backwards. Instead, declare that no time occurred. */ timersub(&last_timeofday, timeval_raw, &timeofday_offset); memcpy(out, &last_timeofday, sizeof(struct timeval)); @@ -259,7 +259,7 @@ ratchet_timeval(const struct timeval *timeval_raw, struct timeval *out) memcpy(&last_timeofday, out, sizeof(struct timeval)); } } -#endif +#endif /* defined(MONOTIME_USING_GETTIMEOFDAY) || defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** For testing: reset all the ratchets */ @@ -271,7 +271,7 @@ monotime_reset_ratchets_for_testing(void) memset(&last_timeofday, 0, sizeof(struct timeval)); memset(&timeofday_offset, 0, sizeof(struct timeval)); } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef __APPLE__ @@ -301,7 +301,7 @@ monotime_get(monotime_t *out) / mach_time_info.numer; return; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ out->abstime_ = mach_absolute_time(); } @@ -332,7 +332,7 @@ monotime_diff_nsec(const monotime_t *start, * an old Linux kernel. In that case, we will fall back to CLOCK_MONOTONIC. */ static int clock_monotonic_coarse = CLOCK_MONOTONIC_COARSE; -#endif +#endif /* defined(CLOCK_MONOTONIC_COARSE) */ static void monotime_init_internal(void) @@ -344,7 +344,7 @@ monotime_init_internal(void) "falling back to CLOCK_MONOTONIC.", strerror(errno)); clock_monotonic_coarse = CLOCK_MONOTONIC; } -#endif +#endif /* defined(CLOCK_MONOTONIC_COARSE) */ } void @@ -356,7 +356,7 @@ monotime_get(monotime_t *out) out->ts_.tv_nsec = (int) (mock_time_nsec % ONE_BILLION); return; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ int r = clock_gettime(CLOCK_MONOTONIC, &out->ts_); tor_assert(r == 0); } @@ -371,7 +371,7 @@ monotime_coarse_get(monotime_coarse_t *out) out->ts_.tv_nsec = (int) (mock_time_nsec_coarse % ONE_BILLION); return; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ int r = clock_gettime(clock_monotonic_coarse, &out->ts_); if (PREDICT_UNLIKELY(r < 0) && errno == EINVAL && @@ -386,7 +386,7 @@ monotime_coarse_get(monotime_coarse_t *out) tor_assert(r == 0); } -#endif +#endif /* defined(CLOCK_MONOTONIC_COARSE) */ int64_t monotime_diff_nsec(const monotime_t *start, @@ -462,7 +462,7 @@ monotime_get(monotime_t *out) / nsec_per_tick_numer; return; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /* Alas, QueryPerformanceCounter is not always monotonic: see bug list at @@ -486,7 +486,7 @@ monotime_coarse_get(monotime_coarse_t *out) out->tick_count_ = mock_time_nsec_coarse / ONE_MILLION; return; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ if (GetTickCount64_fn) { out->tick_count_ = (int64_t)GetTickCount64_fn(); @@ -570,7 +570,7 @@ monotime_diff_nsec(const monotime_t *start, /* end of "MONOTIME_USING_GETTIMEOFDAY" */ #else #error "No way to implement monotonic timers." -#endif +#endif /* defined(__APPLE__) || ... */ /** * Initialize the monotonic timer subsystem. Must be called before any @@ -653,5 +653,5 @@ monotime_coarse_absolute_msec(void) { return monotime_coarse_absolute_nsec() / ONE_MILLION; } -#endif +#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */ diff --git a/src/common/compat_time.h b/src/common/compat_time.h index 90194c5ebc..5ea4aae42b 100644 --- a/src/common/compat_time.h +++ b/src/common/compat_time.h @@ -28,13 +28,13 @@ #include <time.h> #endif -#if !defined(HAVE_GETTIMEOFDAY) && !defined(HAVE_STRUCT_TIMEVAL_TV_SEC) +#if !defined(HAVE_STRUCT_TIMEVAL_TV_SEC) /** Implementation of timeval for platforms that don't have it. */ struct timeval { time_t tv_sec; unsigned int tv_usec; }; -#endif +#endif /* !defined(HAVE_STRUCT_TIMEVAL_TV_SEC) */ /** Represents a monotonic timer in a platform-dependent way. */ typedef struct monotime_t { @@ -51,10 +51,11 @@ typedef struct monotime_t { #define MONOTIME_USING_GETTIMEOFDAY /* Otherwise, we will be stuck using gettimeofday. */ struct timeval tv_; -#endif +#endif /* defined(__APPLE__) || ... */ } monotime_t; -#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC_COARSE) +#if defined(CLOCK_MONOTONIC_COARSE) && \ + defined(HAVE_CLOCK_GETTIME) #define MONOTIME_COARSE_FN_IS_DIFFERENT #define monotime_coarse_t monotime_t #elif defined(_WIN32) @@ -66,7 +67,7 @@ typedef struct monotime_coarse_t { } monotime_coarse_t; #else #define monotime_coarse_t monotime_t -#endif +#endif /* defined(CLOCK_MONOTONIC_COARSE) && ... || ... */ /** * Initialize the timing subsystem. This function is idempotent. @@ -109,12 +110,12 @@ void monotime_coarse_get(monotime_coarse_t *out); uint64_t monotime_coarse_absolute_nsec(void); uint64_t monotime_coarse_absolute_usec(void); uint64_t monotime_coarse_absolute_msec(void); -#else +#else /* !(defined(MONOTIME_COARSE_FN_IS_DIFFERENT)) */ #define monotime_coarse_get monotime_get #define monotime_coarse_absolute_nsec monotime_absolute_nsec #define monotime_coarse_absolute_usec monotime_absolute_usec #define monotime_coarse_absolute_msec monotime_absolute_msec -#endif +#endif /* defined(MONOTIME_COARSE_FN_IS_DIFFERENT) */ #if defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT) int64_t monotime_coarse_diff_nsec(const monotime_coarse_t *start, @@ -123,11 +124,11 @@ int64_t monotime_coarse_diff_usec(const monotime_coarse_t *start, const monotime_coarse_t *end); int64_t monotime_coarse_diff_msec(const monotime_coarse_t *start, const monotime_coarse_t *end); -#else +#else /* !(defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT)) */ #define monotime_coarse_diff_nsec monotime_diff_nsec #define monotime_coarse_diff_usec monotime_diff_usec #define monotime_coarse_diff_msec monotime_diff_msec -#endif +#endif /* defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT) */ void tor_gettimeofday(struct timeval *timeval); @@ -142,7 +143,7 @@ void monotime_coarse_set_mock_time_nsec(int64_t); #else #define monotime_coarse_set_mock_time_nsec monotime_set_mock_time_nsec #endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef COMPAT_TIME_PRIVATE #if defined(_WIN32) || defined(TOR_UNIT_TESTS) @@ -156,7 +157,7 @@ STATIC void ratchet_timeval(const struct timeval *timeval_raw, #ifdef TOR_UNIT_TESTS void monotime_reset_ratchets_for_testing(void); #endif -#endif +#endif /* defined(COMPAT_TIME_PRIVATE) */ -#endif +#endif /* !defined(TOR_COMPAT_TIME_H) */ diff --git a/src/common/compat_winthreads.c b/src/common/compat_winthreads.c index 915368b94f..50a3c498ca 100644 --- a/src/common/compat_winthreads.c +++ b/src/common/compat_winthreads.c @@ -246,5 +246,5 @@ tor_threads_init(void) set_main_thread(); } -#endif +#endif /* defined(_WIN32) */ diff --git a/src/common/compress.c b/src/common/compress.c index beeff5fcb8..bc12a58ad6 100644 --- a/src/common/compress.c +++ b/src/common/compress.c @@ -51,8 +51,8 @@ static atomic_counter_t total_compress_allocation; /** Return true if uncompressing an input of size <b>in_size</b> to an input of * size at least <b>size_out</b> looks like a compression bomb. */ -int -tor_compress_is_compression_bomb(size_t size_in, size_t size_out) +MOCK_IMPL(int, +tor_compress_is_compression_bomb,(size_t size_in, size_t size_out)) { if (size_in == 0 || size_out < CHECK_FOR_COMPRESSION_BOMB_AFTER) return 0; @@ -164,7 +164,7 @@ tor_compress_impl(int compress, goto err; } if (out_alloc >= SIZE_T_CEILING / 2) { - log_warn(LD_GENERAL, "While %scompresing data: ran out of space.", + log_warn(LD_GENERAL, "While %scompressing data: ran out of space.", compress?"":"un"); goto err; } @@ -185,11 +185,12 @@ tor_compress_impl(int compress, } case TOR_COMPRESS_ERROR: log_fn(protocol_warn_level, LD_GENERAL, - "Error while %scompresing data: bad input?", + "Error while %scompressing data: bad input?", compress?"":"un"); goto err; // bad data. - default: + // LCOV_EXCL_START + default: tor_assert_nonfatal_unreached(); goto err; // LCOV_EXCL_STOP @@ -561,9 +562,9 @@ tor_compress_process(tor_compress_state_t *state, finish); break; case LZMA_METHOD: - rv =tor_lzma_compress_process(state->u.lzma_state, - out, out_len, in, in_len, - finish); + rv = tor_lzma_compress_process(state->u.lzma_state, + out, out_len, in, in_len, + finish); break; case ZSTD_METHOD: rv = tor_zstd_compress_process(state->u.zstd_state, @@ -581,6 +582,12 @@ tor_compress_process(tor_compress_state_t *state, if (BUG((rv == TOR_COMPRESS_OK) && *in_len == in_len_orig && *out_len == out_len_orig)) { + log_warn(LD_GENERAL, + "More info on the bug: method == %s, finish == %d, " + " *in_len == in_len_orig == %lu, " + "*out_len == out_len_orig == %lu", + compression_method_get_human_name(state->method), finish, + (unsigned long)in_len_orig, (unsigned long)out_len_orig); return TOR_COMPRESS_ERROR; } diff --git a/src/common/compress.h b/src/common/compress.h index 59c8b7b9b5..23a9817479 100644 --- a/src/common/compress.h +++ b/src/common/compress.h @@ -45,7 +45,8 @@ int tor_uncompress(char **out, size_t *out_len, compress_method_t detect_compression_method(const char *in, size_t in_len); -int tor_compress_is_compression_bomb(size_t size_in, size_t size_out); +MOCK_DECL(int,tor_compress_is_compression_bomb,(size_t size_in, + size_t size_out)); int tor_compress_supports_method(compress_method_t method); unsigned tor_compress_get_supported_method_bitmask(void); @@ -85,5 +86,5 @@ size_t tor_compress_state_size(const tor_compress_state_t *state); void tor_compress_init(void); -#endif // TOR_COMPRESS_H. +#endif /* !defined(TOR_COMPRESS_H) */ diff --git a/src/common/compress_lzma.c b/src/common/compress_lzma.c index d453d9f718..6426ede4fd 100644 --- a/src/common/compress_lzma.c +++ b/src/common/compress_lzma.c @@ -75,7 +75,7 @@ lzma_error_str(lzma_ret error) return "Unknown LZMA error"; } } -#endif // HAVE_LZMA. +#endif /* defined(HAVE_LZMA) */ /** Return 1 if LZMA compression is supported; otherwise 0. */ int @@ -158,10 +158,12 @@ tor_lzma_state_size_precalc(int compress, compression_level_t level) return (size_t)memory_usage; + // LCOV_EXCL_START err: - return 0; // LCOV_EXCL_LINE + return 0; + // LCOV_EXCL_STOP } -#endif // HAVE_LZMA. +#endif /* defined(HAVE_LZMA) */ /** Construct and return a tor_lzma_compress_state_t object using * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for @@ -212,16 +214,18 @@ tor_lzma_compress_new(int compress, atomic_counter_add(&total_lzma_allocation, result->allocation); return result; + /* LCOV_EXCL_START */ err: - tor_free(result); // LCOV_EXCL_LINE + tor_free(result); return NULL; -#else // HAVE_LZMA. + /* LCOV_EXCL_STOP */ +#else /* !(defined(HAVE_LZMA)) */ (void)compress; (void)method; (void)level; return NULL; -#endif // HAVE_LZMA. +#endif /* defined(HAVE_LZMA) */ } /** Compress/decompress some bytes using <b>state</b>. Read up to @@ -306,7 +310,7 @@ tor_lzma_compress_process(tor_lzma_compress_state_t *state, lzma_error_str(retval)); return TOR_COMPRESS_ERROR; } -#else // HAVE_LZMA. +#else /* !(defined(HAVE_LZMA)) */ (void)state; (void)out; (void)out_len; @@ -314,7 +318,7 @@ tor_lzma_compress_process(tor_lzma_compress_state_t *state, (void)in_len; (void)finish; return TOR_COMPRESS_ERROR; -#endif // HAVE_LZMA. +#endif /* defined(HAVE_LZMA) */ } /** Deallocate <b>state</b>. */ diff --git a/src/common/compress_lzma.h b/src/common/compress_lzma.h index 1433c89f88..7639d98a70 100644 --- a/src/common/compress_lzma.h +++ b/src/common/compress_lzma.h @@ -39,5 +39,5 @@ size_t tor_lzma_get_total_allocation(void); void tor_lzma_init(void); -#endif // TOR_COMPRESS_LZMA_H. +#endif /* !defined(TOR_COMPRESS_LZMA_H) */ diff --git a/src/common/compress_none.h b/src/common/compress_none.h index d1ebb4b625..77c3cef47b 100644 --- a/src/common/compress_none.h +++ b/src/common/compress_none.h @@ -16,5 +16,5 @@ tor_cnone_compress_process(char **out, size_t *out_len, const char **in, size_t *in_len, int finish); -#endif // TOR_COMPRESS_NONE_H. +#endif /* !defined(TOR_COMPRESS_NONE_H) */ diff --git a/src/common/compress_zlib.h b/src/common/compress_zlib.h index df5c196ac7..8ace467bf0 100644 --- a/src/common/compress_zlib.h +++ b/src/common/compress_zlib.h @@ -39,5 +39,5 @@ size_t tor_zlib_get_total_allocation(void); void tor_zlib_init(void); -#endif // TOR_COMPRESS_ZLIB_H. +#endif /* !defined(TOR_COMPRESS_ZLIB_H) */ diff --git a/src/common/compress_zstd.c b/src/common/compress_zstd.c index 63e92ed22d..c1cdaf17ad 100644 --- a/src/common/compress_zstd.c +++ b/src/common/compress_zstd.c @@ -38,7 +38,7 @@ memory_level(compression_level_t level) case LOW_COMPRESSION: return 7; } } -#endif // HAVE_ZSTD. +#endif /* defined(HAVE_ZSTD) */ /** Return 1 if Zstandard compression is supported; otherwise 0. */ int @@ -68,9 +68,9 @@ tor_zstd_get_version_str(void) (int) version_number % 100); return version_str; -#else +#else /* !(defined(HAVE_ZSTD)) */ return NULL; -#endif +#endif /* defined(HAVE_ZSTD) */ } /** Return a string representation of the version of the version of libzstd @@ -95,7 +95,7 @@ struct tor_zstd_compress_state_t { /** Decompression stream. Used when <b>compress</b> is false. */ ZSTD_DStream *decompress_stream; } u; /**< Zstandard stream objects. */ -#endif // HAVE_ZSTD. +#endif /* defined(HAVE_ZSTD) */ int compress; /**< True if we are compressing; false if we are inflating */ int have_called_end; /**< True if we are compressing and we've called @@ -171,7 +171,7 @@ tor_zstd_state_size_precalc(int compress, int preset) return memory_usage; } -#endif // HAVE_ZSTD. +#endif /* defined(HAVE_ZSTD) */ /** Construct and return a tor_zstd_compress_state_t object using * <b>method</b>. If <b>compress</b>, it's for compression; otherwise it's for @@ -248,13 +248,13 @@ tor_zstd_compress_new(int compress, tor_free(result); return NULL; // LCOV_EXCL_STOP -#else // HAVE_ZSTD. +#else /* !(defined(HAVE_ZSTD)) */ (void)compress; (void)method; (void)level; return NULL; -#endif // HAVE_ZSTD. +#endif /* defined(HAVE_ZSTD) */ } /** Compress/decompress some bytes using <b>state</b>. Read up to @@ -385,7 +385,7 @@ tor_zstd_compress_process(tor_zstd_compress_state_t *state, return TOR_COMPRESS_OK; } -#else // HAVE_ZSTD. +#else /* !(defined(HAVE_ZSTD)) */ (void)state; (void)out; (void)out_len; @@ -394,7 +394,7 @@ tor_zstd_compress_process(tor_zstd_compress_state_t *state, (void)finish; return TOR_COMPRESS_ERROR; -#endif // HAVE_ZSTD. +#endif /* defined(HAVE_ZSTD) */ } /** Deallocate <b>state</b>. */ @@ -412,7 +412,7 @@ tor_zstd_compress_free(tor_zstd_compress_state_t *state) } else { ZSTD_freeDStream(state->u.decompress_stream); } -#endif // HAVE_ZSTD. +#endif /* defined(HAVE_ZSTD) */ tor_free(state); } diff --git a/src/common/compress_zstd.h b/src/common/compress_zstd.h index d3e65c2f16..02466010ff 100644 --- a/src/common/compress_zstd.h +++ b/src/common/compress_zstd.h @@ -39,5 +39,5 @@ size_t tor_zstd_get_total_allocation(void); void tor_zstd_init(void); -#endif // TOR_COMPRESS_ZSTD_H. +#endif /* !defined(TOR_COMPRESS_ZSTD_H) */ diff --git a/src/common/confline.c b/src/common/confline.c index 15fd96bf38..04545bc2c3 100644 --- a/src/common/confline.c +++ b/src/common/confline.c @@ -288,7 +288,7 @@ config_process_include(const char *path, int recursion_level, int extended, return -1; } tor_free(unquoted_path); -#endif +#endif /* 0 */ smartlist_t *config_files = config_get_file_list(path); if (!config_files) { return -1; @@ -342,7 +342,8 @@ config_lines_dup(const config_line_t *inp) } /** Return a newly allocated deep copy of the lines in <b>inp</b>, - * but only the ones that match <b>key</b>. */ + * but only the ones whose keys begin with <b>key</b> (case-insensitive). + * If <b>key</b> is NULL, do not filter. */ config_line_t * config_lines_dup_and_filter(const config_line_t *inp, const char *key) diff --git a/src/common/confline.h b/src/common/confline.h index eb863e8fd8..8256326f2d 100644 --- a/src/common/confline.h +++ b/src/common/confline.h @@ -49,5 +49,5 @@ void config_free_lines(config_line_t *front); const char *parse_config_line_from_str_verbose(const char *line, char **key_out, char **value_out, const char **err_out); -#endif +#endif /* !defined(TOR_CONFLINE_H) */ diff --git a/src/common/container.c b/src/common/container.c index 689e7e55e9..8645cb4826 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -843,13 +843,13 @@ smartlist_sort_pointers(smartlist_t *sl) * } * * void timer_heap_insert(smartlist_t *heap, timer_t *timer) { - * smartlist_pqueue_add(heap, compare, STRUCT_OFFSET(timer_t, heap_index), + * smartlist_pqueue_add(heap, compare, offsetof(timer_t, heap_index), * timer); * } * * void timer_heap_pop(smartlist_t *heap) { * return smartlist_pqueue_pop(heap, compare, - * STRUCT_OFFSET(timer_t, heap_index)); + * offsetof(timer_t, heap_index)); * } */ diff --git a/src/common/container.h b/src/common/container.h index db68d892f5..f6affd3bc6 100644 --- a/src/common/container.h +++ b/src/common/container.h @@ -74,11 +74,11 @@ static inline void smartlist_set(smartlist_t *sl, int idx, void *val) { tor_assert(sl->num_used > idx); sl->list[idx] = val; } -#else +#else /* !(defined(DEBUG_SMARTLIST)) */ #define smartlist_len(sl) ((sl)->num_used) #define smartlist_get(sl, idx) ((sl)->list[idx]) #define smartlist_set(sl, idx, val) ((sl)->list[idx] = (val)) -#endif +#endif /* defined(DEBUG_SMARTLIST) */ /** Exchange the elements at indices <b>idx1</b> and <b>idx2</b> of the * smartlist <b>sl</b>. */ @@ -580,7 +580,7 @@ void* strmap_remove_lc(strmap_t *map, const char *key); #define BITARRAY_SHIFT 6 #else #error "int is neither 4 nor 8 bytes. I can't deal with that." -#endif +#endif /* SIZEOF_INT == 4 || ... */ #define BITARRAY_MASK ((1u<<BITARRAY_SHIFT)-1) /** A random-access array of one-bit-wide elements. */ @@ -723,5 +723,5 @@ third_quartile_uint32(uint32_t *array, int n_elements) return find_nth_uint32(array, n_elements, (n_elements*3)/4); } -#endif +#endif /* !defined(TOR_CONTAINER_H) */ diff --git a/src/common/crypto.c b/src/common/crypto.c index b82f181a39..380f038f42 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -20,7 +20,7 @@ /* Windows defines this; so does OpenSSL 0.9.8h and later. We don't actually * use either definition. */ #undef OCSP_RESPONSE -#endif +#endif /* defined(_WIN32) */ #define CRYPTO_PRIVATE #include "crypto.h" @@ -50,7 +50,7 @@ ENABLE_GCC_WARNING(redundant-decls) #else #pragma GCC diagnostic warning "-Wredundant-decls" #endif -#endif +#endif /* __GNUC__ && GCC_VERSION >= 402 */ #ifdef HAVE_CTYPE_H #include <ctype.h> @@ -101,7 +101,7 @@ ENABLE_GCC_WARNING(redundant-decls) * pointless, so let's not. */ #define NEW_THREAD_API -#endif +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) && ... */ /** Longest recognized */ #define MAX_DNS_LABEL_SIZE 63 @@ -114,7 +114,7 @@ ENABLE_GCC_WARNING(redundant-decls) static tor_mutex_t **openssl_mutexes_ = NULL; /** How many mutexes have we allocated for use by OpenSSL? */ static int n_openssl_mutexes_ = 0; -#endif +#endif /* !defined(NEW_THREAD_API) */ /** A public key, or a public/private key-pair. */ struct crypto_pk_t @@ -198,7 +198,7 @@ log_engine(const char *fn, ENGINE *e) log_info(LD_CRYPTO, "Using default implementation for %s", fn); } } -#endif +#endif /* !defined(DISABLE_ENGINES) */ #ifndef DISABLE_ENGINES /** Try to load an engine in a shared library via fully qualified path. @@ -218,7 +218,7 @@ try_load_engine(const char *path, const char *engine) } return e; } -#endif +#endif /* !defined(DISABLE_ENGINES) */ /* Returns a trimmed and human-readable version of an openssl version string * <b>raw_version</b>. They are usually in the form of 'OpenSSL 1.0.0b 10 @@ -394,7 +394,7 @@ crypto_global_init(int useAccel, const char *accelName, const char *accelDir) #else log_engine("ECDH", ENGINE_get_default_ECDH()); log_engine("ECDSA", ENGINE_get_default_ECDSA()); -#endif +#endif /* defined(OPENSSL_1_1_API) */ log_engine("RAND", ENGINE_get_default_RAND()); log_engine("RAND (which we will not use)", ENGINE_get_default_RAND()); log_engine("SHA1", ENGINE_get_digest_engine(NID_sha1)); @@ -412,7 +412,7 @@ crypto_global_init(int useAccel, const char *accelName, const char *accelDir) log_engine("AES-256-GCM", ENGINE_get_cipher_engine(NID_aes_256_gcm)); #endif -#endif +#endif /* defined(DISABLE_ENGINES) */ } else { log_info(LD_CRYPTO, "NOT using OpenSSL engine support."); } @@ -450,9 +450,9 @@ crypto_pk_private_ok(const crypto_pk_t *k) const BIGNUM *p, *q; RSA_get0_factors(k->key, &p, &q); return p != NULL; /* XXX/yawning: Should we check q? */ -#else +#else /* !(defined(OPENSSL_1_1_API)) */ return k && k->key && k->key->p; -#endif +#endif /* defined(OPENSSL_1_1_API) */ } /** used by tortls.c: wrap an RSA* in a crypto_pk_t. */ @@ -894,7 +894,7 @@ crypto_pk_public_exponent_ok(crypto_pk_t *env) RSA_get0_key(env->key, &n, &e, &d); #else e = env->key->e; -#endif +#endif /* defined(OPENSSL_1_1_API) */ return BN_is_word(e, 65537); } @@ -928,7 +928,7 @@ crypto_pk_cmp_keys(const crypto_pk_t *a, const crypto_pk_t *b) a_e = a->key->e; b_n = b->key->n; b_e = b->key->e; -#endif +#endif /* defined(OPENSSL_1_1_API) */ tor_assert(a_n != NULL && a_e != NULL); tor_assert(b_n != NULL && b_e != NULL); @@ -977,10 +977,10 @@ crypto_pk_num_bits(crypto_pk_t *env) tor_assert(n != NULL); return RSA_bits(env->key); -#else +#else /* !(defined(OPENSSL_1_1_API)) */ tor_assert(env->key->n); return BN_num_bits(env->key->n); -#endif +#endif /* defined(OPENSSL_1_1_API) */ } /** Increase the reference count of <b>env</b>, and return it. @@ -1007,7 +1007,7 @@ crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src) RSA_free(dest->key); dest->key = RSAPrivateKey_dup(src->key); } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Make a real honest-to-goodness copy of <b>env</b>, and return it. * Returns NULL on failure. */ @@ -1248,9 +1248,12 @@ crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, * - The beginning of the source data prefixed with a 16-byte symmetric key, * padded and encrypted with the public key; followed by the rest of * the source data encrypted in AES-CTR mode with the symmetric key. + * + * NOTE that this format does not authenticate the symmetrically encrypted + * part of the data, and SHOULD NOT BE USED for new protocols. */ int -crypto_pk_public_hybrid_encrypt(crypto_pk_t *env, +crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen, @@ -1312,10 +1315,14 @@ crypto_pk_public_hybrid_encrypt(crypto_pk_t *env, return -1; } -/** Invert crypto_pk_public_hybrid_encrypt. Returns the number of bytes - * written on success, -1 on failure. */ +/** Invert crypto_pk_obsolete_public_hybrid_encrypt. Returns the number of + * bytes written on success, -1 on failure. + * + * NOTE that this format does not authenticate the symmetrically encrypted + * part of the data, and SHOULD NOT BE USED for new protocols. + */ int -crypto_pk_private_hybrid_decrypt(crypto_pk_t *env, +crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, char *to, size_t tolen, const char *from, @@ -1801,8 +1808,8 @@ crypto_digest_algorithm_get_name(digest_algorithm_t alg) return "sha3-256"; case DIGEST_SHA3_512: return "sha3-512"; - default: // LCOV_EXCL_START + default: tor_fragile_assert(); return "??unknown_digest??"; // LCOV_EXCL_STOP @@ -1864,6 +1871,18 @@ struct crypto_digest_t { } d; }; +#ifdef TOR_UNIT_TESTS + +digest_algorithm_t +crypto_digest_get_algorithm(crypto_digest_t *digest) +{ + tor_assert(digest); + + return digest->algorithm; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + /** * Return the number of bytes we need to malloc in order to get a * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe @@ -1875,7 +1894,7 @@ crypto_digest_alloc_bytes(digest_algorithm_t alg) /* Helper: returns the number of bytes in the 'f' field of 'st' */ #define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f )) /* Gives the length of crypto_digest_t through the end of the field 'd' */ -#define END_OF_FIELD(f) (STRUCT_OFFSET(crypto_digest_t, f) + \ +#define END_OF_FIELD(f) (offsetof(crypto_digest_t, f) + \ STRUCT_FIELD_SIZE(crypto_digest_t, f)) switch (alg) { case DIGEST_SHA1: @@ -2246,12 +2265,12 @@ crypto_validate_dh_params(const BIGNUM *p, const BIGNUM *g) goto out; if (!DH_set0_pqg(dh, dh_p, NULL, dh_g)) goto out; -#else +#else /* !(defined(OPENSSL_1_1_API)) */ if (!(dh->p = BN_dup(p))) goto out; if (!(dh->g = BN_dup(g))) goto out; -#endif +#endif /* defined(OPENSSL_1_1_API) */ /* Perform the validation. */ int codes = 0; @@ -2422,7 +2441,7 @@ crypto_dh_new(int dh_type) if (!DH_set_length(res->dh, DH_PRIVATE_KEY_BITS)) goto err; -#else +#else /* !(defined(OPENSSL_1_1_API)) */ if (dh_type == DH_TYPE_TLS) { if (!(res->dh->p = BN_dup(dh_param_p_tls))) goto err; @@ -2435,12 +2454,13 @@ crypto_dh_new(int dh_type) goto err; res->dh->length = DH_PRIVATE_KEY_BITS; -#endif +#endif /* defined(OPENSSL_1_1_API) */ return res; - err: + /* LCOV_EXCL_START * This error condition is only reached when an allocation fails */ + err: crypto_log_errors(LOG_WARN, "creating DH object"); if (res->dh) DH_free(res->dh); /* frees p and g too */ tor_free(res); @@ -2497,7 +2517,7 @@ crypto_dh_generate_public(crypto_dh_t *dh) "the-universe chances really do happen. Treating as a failure."); return -1; } -#else +#else /* !(defined(OPENSSL_1_1_API)) */ if (tor_check_dh_key(LOG_WARN, dh->dh->pub_key)<0) { /* LCOV_EXCL_START * If this happens, then openssl's DH implementation is busted. */ @@ -2510,7 +2530,7 @@ crypto_dh_generate_public(crypto_dh_t *dh) goto again; /* LCOV_EXCL_STOP */ } -#endif +#endif /* defined(OPENSSL_1_1_API) */ return 0; } @@ -2531,7 +2551,7 @@ crypto_dh_get_public(crypto_dh_t *dh, char *pubkey, size_t pubkey_len) DH_get0_key(dh->dh, &dh_pub, &dh_priv); #else dh_pub = dh->dh->pub_key; -#endif +#endif /* defined(OPENSSL_1_1_API) */ if (!dh_pub) { if (crypto_dh_generate_public(dh)<0) @@ -2855,8 +2875,17 @@ crypto_strongest_rand_syscall(uint8_t *out, size_t out_len) tor_assert(errno != EAGAIN); tor_assert(errno != EINTR); - /* Probably ENOSYS. */ - log_warn(LD_CRYPTO, "Can't get entropy from getrandom()."); + /* Useful log message for errno. */ + if (errno == ENOSYS) { + log_warn(LD_CRYPTO, "Can't get entropy from getrandom(). " + " You are running a version of Tor built to support" + " getrandom(), but the kernel doesn't implement this" + " implement this function--probably because it is too old?"); + } else { + log_warn(LD_CRYPTO, "Can't get entropy from getrandom(): %s.", + strerror(errno)); + } + getrandom_works = 0; /* Don't bother trying again. */ return -1; /* LCOV_EXCL_STOP */ @@ -2874,7 +2903,7 @@ crypto_strongest_rand_syscall(uint8_t *out, size_t out_len) return getentropy(out, out_len); #else (void) out; -#endif +#endif /* defined(_WIN32) || ... */ /* This platform doesn't have a supported syscall based random. */ return -1; @@ -2898,7 +2927,7 @@ crypto_strongest_rand_fallback(uint8_t *out, size_t out_len) (void)out; (void)out_len; return -1; -#else +#else /* !(defined(_WIN32)) */ static const char *filenames[] = { "/dev/srandom", "/dev/urandom", "/dev/random", NULL }; @@ -2926,7 +2955,7 @@ crypto_strongest_rand_fallback(uint8_t *out, size_t out_len) } return -1; -#endif +#endif /* defined(_WIN32) */ } /** Try to get <b>out_len</b> bytes of the strongest entropy we can generate, @@ -3175,7 +3204,7 @@ crypto_rand_double(void) #define UINT_MAX_AS_DOUBLE 1.8446744073709552e+19 #else #error SIZEOF_INT is neither 4 nor 8 -#endif +#endif /* SIZEOF_INT == 4 || ... */ return ((double)u) / UINT_MAX_AS_DOUBLE; } @@ -3304,7 +3333,7 @@ memwipe(void *mem, uint8_t byte, size_t sz) **/ OPENSSL_cleanse(mem, sz); -#endif +#endif /* defined(SecureZeroMemory) || defined(HAVE_SECUREZEROMEMORY) || ... */ /* Just in case some caller of memwipe() is relying on getting a buffer * filled with a particular value, fill the buffer. @@ -3346,7 +3375,7 @@ tor_set_openssl_thread_id(CRYPTO_THREADID *threadid) { CRYPTO_THREADID_set_numeric(threadid, tor_get_thread_id()); } -#endif +#endif /* !defined(NEW_THREAD_API) */ #if 0 /* This code is disabled, because OpenSSL never actually uses these callbacks. @@ -3396,7 +3425,7 @@ openssl_dynlock_destroy_cb_(struct CRYPTO_dynlock_value *v, tor_mutex_free(v->lock); tor_free(v); } -#endif +#endif /* 0 */ /** @{ */ /** Helper: Construct mutexes, and set callbacks to help OpenSSL handle being @@ -3413,7 +3442,7 @@ setup_openssl_threading(void) openssl_mutexes_[i] = tor_mutex_new(); CRYPTO_set_locking_callback(openssl_locking_cb_); CRYPTO_THREADID_set_callback(tor_set_openssl_thread_id); -#endif +#endif /* !defined(NEW_THREAD_API) */ #if 0 CRYPTO_set_dynlock_create_callback(openssl_dynlock_create_cb_); CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock_cb_); @@ -3460,7 +3489,7 @@ crypto_global_cleanup(void) } tor_free(ms); } -#endif +#endif /* !defined(NEW_THREAD_API) */ tor_free(crypto_openssl_version_str); tor_free(crypto_openssl_header_version_str); @@ -3479,5 +3508,5 @@ crypto_use_tor_alloc_functions(void) int r = CRYPTO_set_mem_ex_functions(tor_malloc_, tor_realloc_, tor_free_); return r ? 0 : -1; } -#endif +#endif /* defined(USE_DMALLOC) */ diff --git a/src/common/crypto.h b/src/common/crypto.h index c70d91c262..f9aeeee2c0 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -20,6 +20,9 @@ #include "testsupport.h" #include "compat.h" +#include <openssl/engine.h> +#include "keccak-tiny/keccak-tiny.h" + /* Macro to create an arbitrary OpenSSL version number as used by OPENSSL_VERSION_NUMBER or SSLeay(), since the actual numbers are a bit hard @@ -70,6 +73,9 @@ /** Length of our DH keys. */ #define DH_BYTES (1024/8) +/** Length of a sha1 message digest when encoded in base32 with trailing = + * signs removed. */ +#define BASE32_DIGEST_LEN 32 /** Length of a sha1 message digest when encoded in base64 with trailing = * signs removed. */ #define BASE64_DIGEST_LEN 27 @@ -194,11 +200,11 @@ int crypto_pk_private_sign(const crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen); int crypto_pk_private_sign_digest(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen); -int crypto_pk_public_hybrid_encrypt(crypto_pk_t *env, char *to, +int crypto_pk_obsolete_public_hybrid_encrypt(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen, int padding, int force); -int crypto_pk_private_hybrid_decrypt(crypto_pk_t *env, char *to, +int crypto_pk_obsolete_private_hybrid_decrypt(crypto_pk_t *env, char *to, size_t tolen, const char *from, size_t fromlen, int padding, int warnOnFailure); @@ -335,6 +341,7 @@ struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh); void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); #ifdef CRYPTO_PRIVATE + STATIC int crypto_force_rand_ssleay(void); STATIC int crypto_strongest_rand_raw(uint8_t *out, size_t out_len); @@ -342,11 +349,12 @@ STATIC int crypto_strongest_rand_raw(uint8_t *out, size_t out_len); extern int break_strongest_rng_syscall; extern int break_strongest_rng_fallback; #endif -#endif +#endif /* defined(CRYPTO_PRIVATE) */ #ifdef TOR_UNIT_TESTS void crypto_pk_assign_(crypto_pk_t *dest, const crypto_pk_t *src); +digest_algorithm_t crypto_digest_get_algorithm(crypto_digest_t *digest); #endif -#endif +#endif /* !defined(TOR_CRYPTO_H) */ diff --git a/src/common/crypto_curve25519.c b/src/common/crypto_curve25519.c index b99f13a93b..8793fa6274 100644 --- a/src/common/crypto_curve25519.c +++ b/src/common/crypto_curve25519.c @@ -43,7 +43,7 @@ int curve25519_donna(uint8_t *mypublic, #elif defined(HAVE_NACL_CRYPTO_SCALARMULT_CURVE25519_H) #include <nacl/crypto_scalarmult_curve25519.h> #endif -#endif +#endif /* defined(USE_CURVE25519_NACL) */ static void pick_curve25519_basepoint_impl(void); @@ -72,7 +72,7 @@ curve25519_impl(uint8_t *output, const uint8_t *secret, r = crypto_scalarmult_curve25519(output, secret, bp); #else #error "No implementation of curve25519 is available." -#endif +#endif /* defined(USE_CURVE25519_DONNA) || ... */ memwipe(bp, 0, sizeof(bp)); return r; } @@ -318,8 +318,11 @@ curve25519_basepoint_spot_check(void) } goto end; + // LCOV_EXCL_START -- we can only hit this code if there is a bug in our + // curve25519-basepoint implementation. fail: r = -1; + // LCOV_EXCL_STOP end: curve25519_use_ed = save_use_ed; return r; diff --git a/src/common/crypto_curve25519.h b/src/common/crypto_curve25519.h index e7790edac0..d024ab79f5 100644 --- a/src/common/crypto_curve25519.h +++ b/src/common/crypto_curve25519.h @@ -71,7 +71,7 @@ STATIC int curve25519_impl(uint8_t *output, const uint8_t *secret, const uint8_t *basepoint); STATIC int curve25519_basepoint_impl(uint8_t *output, const uint8_t *secret); -#endif +#endif /* defined(CRYPTO_CURVE25519_PRIVATE) */ #define CURVE25519_BASE64_PADDED_LEN 44 @@ -83,5 +83,5 @@ int curve25519_public_to_base64(char *output, void curve25519_set_impl_params(int use_ed); void curve25519_init(void); -#endif +#endif /* !defined(TOR_CRYPTO_CURVE25519_H) */ diff --git a/src/common/crypto_ed25519.c b/src/common/crypto_ed25519.c index 188e18c710..94b23e31b9 100644 --- a/src/common/crypto_ed25519.c +++ b/src/common/crypto_ed25519.c @@ -28,6 +28,7 @@ #include "crypto_format.h" #include "torlog.h" #include "util.h" +#include "util_format.h" #include "ed25519/ref10/ed25519_ref10.h" #include "ed25519/donna/ed25519_donna_tor.h" @@ -57,6 +58,9 @@ typedef struct { int (*pubkey_from_curve25519_pubkey)(unsigned char *, const unsigned char *, int); + + int (*ed25519_scalarmult_with_group_order)(unsigned char *, + const unsigned char *); } ed25519_impl_t; /** The Ref10 Ed25519 implementation. This one is pure C and lightly @@ -77,6 +81,7 @@ static const ed25519_impl_t impl_ref10 = { ed25519_ref10_blind_public_key, ed25519_ref10_pubkey_from_curve25519_pubkey, + ed25519_ref10_scalarmult_with_group_order, }; /** The Ref10 Ed25519 implementation. This one is heavily optimized, but still @@ -97,6 +102,7 @@ static const ed25519_impl_t impl_donna = { ed25519_donna_blind_public_key, ed25519_donna_pubkey_from_curve25519_pubkey, + ed25519_donna_scalarmult_with_group_order, }; /** Which Ed25519 implementation are we using? NULL if we haven't decided @@ -145,7 +151,7 @@ crypto_ed25519_testing_restore_impl(void) ed25519_impl = saved_ed25519_impl; saved_ed25519_impl = NULL; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** * Initialize a new ed25519 secret key in <b>seckey_out</b>. If @@ -287,9 +293,12 @@ ed25519_sign_prefixed,(ed25519_signature_t *signature_out, prefixed_msg = get_prefixed_msg(msg, msg_len, prefix_str, &prefixed_msg_len); - if (!prefixed_msg) { + if (BUG(!prefixed_msg)) { + /* LCOV_EXCL_START -- only possible when the message and prefix are + * ridiculously huge */ log_warn(LD_GENERAL, "Failed to get prefixed msg."); return -1; + /* LCOV_EXCL_STOP */ } retval = ed25519_sign(signature_out, @@ -332,9 +341,12 @@ ed25519_checksig_prefixed(const ed25519_signature_t *signature, prefixed_msg = get_prefixed_msg(msg, msg_len, prefix_str, &prefixed_msg_len); - if (!prefixed_msg) { + if (BUG(!prefixed_msg)) { + /* LCOV_EXCL_START -- only possible when the message and prefix are + * ridiculously huge */ log_warn(LD_GENERAL, "Failed to get prefixed msg."); return -1; + /* LCOV_EXCL_STOP */ } retval = ed25519_checksig(signature, @@ -462,7 +474,6 @@ ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out, tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32)); memwipe(&pubkey_check, 0, sizeof(pubkey_check)); - memwipe(&ctx, 0, sizeof(ctx)); memwipe(sha512_output, 0, sizeof(sha512_output)); return 0; @@ -491,7 +502,8 @@ ed25519_public_key_from_curve25519_public_key(ed25519_public_key_t *pubkey, * service descriptors are encrypted with a key derived from the service's * long-term public key, and then signed with (and stored at a position * indexed by) a short-term key derived by blinding the long-term keys. - */ + * + * Return 0 if blinding was successful, else return -1. */ int ed25519_keypair_blind(ed25519_keypair_t *out, const ed25519_keypair_t *inp, @@ -502,7 +514,9 @@ ed25519_keypair_blind(ed25519_keypair_t *out, get_ed_impl()->blind_secret_key(out->seckey.seckey, inp->seckey.seckey, param); - ed25519_public_blind(&pubkey_check, &inp->pubkey, param); + if (ed25519_public_blind(&pubkey_check, &inp->pubkey, param) < 0) { + return -1; + } ed25519_public_key_generate(&out->pubkey, &out->seckey); tor_assert(fast_memeq(pubkey_check.pubkey, out->pubkey.pubkey, 32)); @@ -522,8 +536,7 @@ ed25519_public_blind(ed25519_public_key_t *out, const ed25519_public_key_t *inp, const uint8_t *param) { - get_ed_impl()->blind_public_key(out->pubkey, inp->pubkey, param); - return 0; + return get_ed_impl()->blind_public_key(out->pubkey, inp->pubkey, param); } /** @@ -711,8 +724,11 @@ ed25519_impl_spot_check,(void)) */ goto end; + // LCOV_EXCL_START -- We can only reach this if our ed25519 implementation is + // broken. fail: r = -1; + // LCOV_EXCL_STOP end: return r; } @@ -754,3 +770,47 @@ ed25519_init(void) pick_ed25519_impl(); } +/* Return true if <b>point</b> is the identity element of the ed25519 group. */ +static int +ed25519_point_is_identity_element(const uint8_t *point) +{ + /* The identity element in ed25159 is the point with coordinates (0,1). */ + static const uint8_t ed25519_identity[32] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + tor_assert(sizeof(ed25519_identity) == ED25519_PUBKEY_LEN); + return tor_memeq(point, ed25519_identity, sizeof(ed25519_identity)); +} + +/** Validate <b>pubkey</b> to ensure that it has no torsion component. + * Return 0 if <b>pubkey</b> is valid, else return -1. */ +int +ed25519_validate_pubkey(const ed25519_public_key_t *pubkey) +{ + uint8_t result[32] = {9}; + + /* First check that we were not given the identity element */ + if (ed25519_point_is_identity_element(pubkey->pubkey)) { + log_warn(LD_CRYPTO, "ed25519 pubkey is the identity"); + return -1; + } + + /* For any point on the curve, doing l*point should give the identity element + * (where l is the group order). Do the computation and check that the + * identity element is returned. */ + if (get_ed_impl()->ed25519_scalarmult_with_group_order(result, + pubkey->pubkey) < 0) { + log_warn(LD_CRYPTO, "ed25519 group order scalarmult failed"); + return -1; + } + + if (!ed25519_point_is_identity_element(result)) { + log_warn(LD_CRYPTO, "ed25519 validation failed"); + return -1; + } + + return 0; +} + diff --git a/src/common/crypto_ed25519.h b/src/common/crypto_ed25519.h index 77a3313adc..8d13a487d6 100644 --- a/src/common/crypto_ed25519.h +++ b/src/common/crypto_ed25519.h @@ -127,6 +127,8 @@ void ed25519_pubkey_copy(ed25519_public_key_t *dest, void ed25519_set_impl_params(int use_donna); void ed25519_init(void); +int ed25519_validate_pubkey(const ed25519_public_key_t *pubkey); + #ifdef TOR_UNIT_TESTS void crypto_ed25519_testing_force_impl(const char *name); void crypto_ed25519_testing_restore_impl(void); @@ -136,5 +138,5 @@ void crypto_ed25519_testing_restore_impl(void); MOCK_DECL(STATIC int, ed25519_impl_spot_check, (void)); #endif -#endif +#endif /* !defined(TOR_CRYPTO_ED25519_H) */ diff --git a/src/common/crypto_format.h b/src/common/crypto_format.h index 390916cf04..bbd85dc720 100644 --- a/src/common/crypto_format.h +++ b/src/common/crypto_format.h @@ -43,5 +43,5 @@ int digest_from_base64(char *digest, const char *d64); int digest256_to_base64(char *d64, const char *digest); int digest256_from_base64(char *digest, const char *d64); -#endif +#endif /* !defined(TOR_CRYPTO_FORMAT_H) */ diff --git a/src/common/crypto_pwbox.c b/src/common/crypto_pwbox.c index db8892e376..12acc9331c 100644 --- a/src/common/crypto_pwbox.c +++ b/src/common/crypto_pwbox.c @@ -107,7 +107,6 @@ crypto_pwbox(uint8_t **out, size_t *outlen_out, rv = 0; goto out; - err: /* LCOV_EXCL_START This error case is often unreachable if we're correctly coded, unless @@ -123,6 +122,7 @@ crypto_pwbox(uint8_t **out, size_t *outlen_out, - pwbox_encoded_encode can't fail unless we're using trunnel wrong, or it's buggy. */ + err: tor_free(result); rv = -1; /* LCOV_EXCL_STOP */ diff --git a/src/common/crypto_pwbox.h b/src/common/crypto_pwbox.h index aadd477078..cee8653587 100644 --- a/src/common/crypto_pwbox.h +++ b/src/common/crypto_pwbox.h @@ -16,5 +16,5 @@ int crypto_unpwbox(uint8_t **out, size_t *outlen_out, const uint8_t *inp, size_t input_len, const char *secret, size_t secret_len); -#endif +#endif /* !defined(CRYPTO_PWBOX_H_INCLUDED_) */ diff --git a/src/common/crypto_s2k.c b/src/common/crypto_s2k.c index 076df815a9..b2fcca54c4 100644 --- a/src/common/crypto_s2k.c +++ b/src/common/crypto_s2k.c @@ -86,9 +86,11 @@ secret_to_key_key_len(uint8_t type) return DIGEST_LEN; case S2K_TYPE_SCRYPT: return DIGEST256_LEN; + // LCOV_EXCL_START default: - tor_fragile_assert(); // LCOV_EXCL_LINE - return -1; // LCOV_EXCL_LINE + tor_fragile_assert(); + return -1; + // LCOV_EXCL_STOP } } @@ -169,9 +171,11 @@ make_specifier(uint8_t *spec_out, uint8_t type, unsigned flags) /* r = 8; p = 2. */ spec_out[SCRYPT_SPEC_LEN-1] = (3u << 4) | (1u << 0); break; + // LCOV_EXCL_START - we should have returned above. default: - tor_fragile_assert(); // LCOV_EXCL_LINE - we should have returned above. + tor_fragile_assert(); return S2K_BAD_ALGORITHM; + // LCOV_EXCL_STOP } return speclen; @@ -290,9 +294,9 @@ secret_to_key_compute_key(uint8_t *key_out, size_t key_out_len, if (rv != 0) return S2K_FAILED; return (int)key_out_len; -#else +#else /* !(defined(HAVE_SCRYPT)) */ return S2K_NO_SCRYPT_SUPPORT; -#endif +#endif /* defined(HAVE_SCRYPT) */ } default: return S2K_BAD_ALGORITHM; diff --git a/src/common/crypto_s2k.h b/src/common/crypto_s2k.h index 04212b868a..849ff59ce8 100644 --- a/src/common/crypto_s2k.h +++ b/src/common/crypto_s2k.h @@ -67,7 +67,7 @@ STATIC int secret_to_key_compute_key(uint8_t *key_out, size_t key_out_len, const uint8_t *spec, size_t spec_len, const char *secret, size_t secret_len, int type); -#endif +#endif /* defined(CRYPTO_S2K_PRIVATE) */ -#endif +#endif /* !defined(TOR_CRYPTO_S2K_H_INCLUDED) */ diff --git a/src/common/di_ops.c b/src/common/di_ops.c index e47998107d..7c0b4e7630 100644 --- a/src/common/di_ops.c +++ b/src/common/di_ops.c @@ -86,7 +86,7 @@ tor_memcmp(const void *a, const void *b, size_t len) } return retval; -#endif /* timingsafe_memcmp */ +#endif /* defined(HAVE_TIMINGSAFE_MEMCMP) */ } /** @@ -238,7 +238,7 @@ gt_i64_timei(uint64_t a, uint64_t b) int res = diff >> 63; return res & 1; } -#endif +#endif /* SIZEOF_VOID_P == 8 */ /** * Given an array of list of <b>n_entries</b> uint64_t values, whose sum is diff --git a/src/common/di_ops.h b/src/common/di_ops.h index e174fcc4e4..e79973ba52 100644 --- a/src/common/di_ops.h +++ b/src/common/di_ops.h @@ -46,5 +46,5 @@ int select_array_member_cumulative_timei(const uint64_t *entries, int n_entries, uint64_t total, uint64_t rand_val); -#endif +#endif /* !defined(TOR_DI_OPS_H) */ diff --git a/src/common/handles.h b/src/common/handles.h index 6d7262ab80..a610753a1c 100644 --- a/src/common/handles.h +++ b/src/common/handles.h @@ -149,5 +149,5 @@ } \ } -#endif /* TOR_HANDLE_H */ +#endif /* !defined(TOR_HANDLE_H) */ diff --git a/src/common/include.am b/src/common/include.am index 1253888815..cd5eea3404 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -81,6 +81,7 @@ src_common_libor_ctime_testing_a_CFLAGS = @CFLAGS_CONSTTIME@ $(TEST_CFLAGS) LIBOR_A_SRC = \ src/common/address.c \ src/common/backtrace.c \ + src/common/buffers.c \ src/common/compat.c \ src/common/compat_threads.c \ src/common/compat_time.c \ @@ -110,6 +111,7 @@ src/common/src_common_libor_testing_a-log.$(OBJEXT) \ LIBOR_CRYPTO_A_SRC = \ src/common/aes.c \ + src/common/buffers_tls.c \ src/common/compress.c \ src/common/compress_lzma.c \ src/common/compress_none.c \ @@ -147,6 +149,8 @@ src_common_libor_event_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) COMMONHEADERS = \ src/common/address.h \ src/common/backtrace.h \ + src/common/buffers.h \ + src/common/buffers_tls.h \ src/common/aes.h \ src/common/ciphers.inc \ src/common/compat.h \ diff --git a/src/common/log.c b/src/common/log.c index 6a5819064a..0becb5ce03 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -444,11 +444,11 @@ logfile_deliver(logfile_t *lf, const char *buf, size_t msg_len, if (m != msg_after_prefix) { tor_free(m); } -#else +#else /* !(defined(MAXLINE)) */ /* We have syslog but not MAXLINE. That's promising! */ syslog(severity, "%s", msg_after_prefix); -#endif -#endif +#endif /* defined(MAXLINE) */ +#endif /* defined(HAVE_SYSLOG_H) */ } else if (lf->callback) { if (domain & LD_NOCB) { if (!*callbacks_deferred && pending_cb_messages) { @@ -807,7 +807,7 @@ close_log(logfile_t *victim) /* There are no other syslogs; close the logging facility. */ closelog(); } -#endif +#endif /* defined(HAVE_SYSLOG_H) */ } } @@ -1144,7 +1144,7 @@ add_syslog_log(const log_severity_list_t *severity, UNLOCK_LOGS(); return 0; } -#endif +#endif /* defined(HAVE_SYSLOG_H) */ /** If <b>level</b> is a valid log severity, return the corresponding * numeric value. Otherwise, return -1. */ diff --git a/src/common/memarea.c b/src/common/memarea.c index 659d1edf54..b059987e0e 100644 --- a/src/common/memarea.c +++ b/src/common/memarea.c @@ -7,6 +7,7 @@ */ #include "orconfig.h" +#include <stddef.h> #include <stdlib.h> #include "memarea.h" #include "util.h" @@ -32,7 +33,7 @@ #define MEMAREA_ALIGN_MASK ((uintptr_t)7) #else #error "void* is neither 4 nor 8 bytes long. I don't know how to align stuff." -#endif +#endif /* MEMAREA_ALIGN == 4 || ... */ #if defined(__GNUC__) && defined(FLEXIBLE_ARRAY_MEMBER) #define USE_ALIGNED_ATTRIBUTE @@ -40,7 +41,7 @@ #define U_MEM mem #else #define U_MEM u.mem -#endif +#endif /* defined(__GNUC__) && defined(FLEXIBLE_ARRAY_MEMBER) */ #ifdef USE_SENTINELS /** Magic value that we stick at the end of a memarea so we can make sure @@ -60,11 +61,11 @@ uint32_t sent_val = get_uint32(&(chunk)->U_MEM[chunk->mem_size]); \ tor_assert(sent_val == SENTINEL_VAL); \ STMT_END -#else +#else /* !(defined(USE_SENTINELS)) */ #define SENTINEL_LEN 0 #define SET_SENTINEL(chunk) STMT_NIL #define CHECK_SENTINEL(chunk) STMT_NIL -#endif +#endif /* defined(USE_SENTINELS) */ /** Increment <b>ptr</b> until it is aligned to MEMAREA_ALIGN. */ static inline void * @@ -96,12 +97,12 @@ typedef struct memarea_chunk_t { void *void_for_alignment_; /**< Dummy; used to make sure mem is aligned. */ } u; /**< Union used to enforce alignment when we don't have support for * doing it right. */ -#endif +#endif /* defined(USE_ALIGNED_ATTRIBUTE) */ } memarea_chunk_t; /** How many bytes are needed for overhead before we get to the memory part * of a chunk? */ -#define CHUNK_HEADER_SIZE STRUCT_OFFSET(memarea_chunk_t, U_MEM) +#define CHUNK_HEADER_SIZE offsetof(memarea_chunk_t, U_MEM) /** What's the smallest that we'll allocate a chunk? */ #define CHUNK_SIZE 4096 @@ -307,7 +308,7 @@ memarea_assert_ok(memarea_t *area) } } -#else +#else /* !(!defined(DISABLE_MEMORY_SENTINELS)) */ struct memarea_t { smartlist_t *pieces; @@ -393,5 +394,5 @@ memarea_assert_ok(memarea_t *area) (void)area; } -#endif +#endif /* !defined(DISABLE_MEMORY_SENTINELS) */ diff --git a/src/common/memarea.h b/src/common/memarea.h index 85012c1c34..c3d954e1ce 100644 --- a/src/common/memarea.h +++ b/src/common/memarea.h @@ -20,5 +20,5 @@ void memarea_get_stats(memarea_t *area, size_t *allocated_out, size_t *used_out); void memarea_assert_ok(memarea_t *area); -#endif +#endif /* !defined(TOR_MEMAREA_H) */ diff --git a/src/common/procmon.c b/src/common/procmon.c index d49e7f18f5..26c11823e8 100644 --- a/src/common/procmon.c +++ b/src/common/procmon.c @@ -36,7 +36,7 @@ typedef int pid_t; #define PID_T_FORMAT I64_FORMAT #else #error Unknown: SIZEOF_PID_T -#endif +#endif /* (0 == SIZEOF_PID_T) && defined(_WIN32) || ... */ /* Define to 1 if process-termination monitors on this OS and Libevent version must poll for process termination themselves. */ @@ -71,7 +71,7 @@ parse_process_specifier(const char *process_spec, /* If we're lucky, long will turn out to be large enough to hold a * PID everywhere that Tor runs. */ - pid_l = tor_parse_long(process_spec, 0, 1, LONG_MAX, &pid_ok, &pspec_next); + pid_l = tor_parse_long(process_spec, 10, 1, LONG_MAX, &pid_ok, &pspec_next); /* Reserve room in the ‘process specifier’ for additional * (platform-specific) identifying information beyond the PID, to @@ -114,7 +114,7 @@ struct tor_process_monitor_t { HANDLE hproc; /* XXXX We should have Libevent watch hproc for us, * if/when some version of Libevent can be told to do so. */ -#endif +#endif /* defined(_WIN32) */ /* XXXX On Linux, we can and should receive the 22nd * (space-delimited) field (‘starttime’) of /proc/$PID/stat from the @@ -219,7 +219,7 @@ tor_process_monitor_new(struct event_base *base, "try again later.", procmon->pid); } -#endif +#endif /* defined(_WIN32) */ procmon->cb = cb; procmon->cb_arg = cb_arg; @@ -232,9 +232,9 @@ tor_process_monitor_new(struct event_base *base, * tor_evtimer_new never returns NULL. */ evtimer_add(procmon->e, &poll_interval_tv); -#else +#else /* !(defined(PROCMON_POLLS)) */ #error OOPS? -#endif +#endif /* defined(PROCMON_POLLS) */ return procmon; err: @@ -306,11 +306,11 @@ tor_process_monitor_poll_cb(evutil_socket_t unused1, short unused2, tor_free(errmsg); } } -#else +#else /* !(defined(_WIN32)) */ /* Unix makes this part easy, if a bit racy. */ its_dead_jim = kill(procmon->pid, 0); its_dead_jim = its_dead_jim && (errno == ESRCH); -#endif +#endif /* defined(_WIN32) */ tor_log(its_dead_jim ? LOG_NOTICE : LOG_INFO, procmon->log_domain, "Monitored process "PID_T_FORMAT" is %s.", @@ -321,7 +321,7 @@ tor_process_monitor_poll_cb(evutil_socket_t unused1, short unused2, procmon->cb(procmon->cb_arg); } } -#endif +#endif /* defined(PROCMON_POLLS) */ /** Free the process-termination monitor <b>procmon</b>. */ void diff --git a/src/common/procmon.h b/src/common/procmon.h index b07cff2c4a..10ead11ba8 100644 --- a/src/common/procmon.h +++ b/src/common/procmon.h @@ -29,5 +29,5 @@ tor_process_monitor_t *tor_process_monitor_new(struct event_base *base, const char **msg); void tor_process_monitor_free(tor_process_monitor_t *procmon); -#endif +#endif /* !defined(TOR_PROCMON_H) */ diff --git a/src/common/pubsub.h b/src/common/pubsub.h index 6f4ce08754..2bee3af085 100644 --- a/src/common/pubsub.h +++ b/src/common/pubsub.h @@ -175,5 +175,5 @@ int pubsub_notify_(pubsub_topic_t *topic, pubsub_notify_fn_t notify_fn, pubsub_clear_(&name##_topic_); \ } -#endif /* TOR_PUBSUB_H */ +#endif /* !defined(TOR_PUBSUB_H) */ diff --git a/src/common/sandbox.c b/src/common/sandbox.c index 5063717355..8827370c24 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -17,7 +17,7 @@ * with the libevent fix. */ #define _LARGEFILE64_SOURCE -#endif +#endif /* !defined(_LARGEFILE64_SOURCE) */ /** Malloc mprotect limit in bytes. * @@ -62,6 +62,9 @@ #include <time.h> #include <poll.h> +#ifdef HAVE_GNU_LIBC_VERSION_H +#include <gnu/libc-version.h> +#endif #ifdef HAVE_LINUX_NETFILTER_IPV4_H #include <linux/netfilter_ipv4.h> #endif @@ -77,7 +80,7 @@ #define USE_BACKTRACE #define EXPOSE_CLEAN_BACKTRACE #include "backtrace.h" -#endif +#endif /* defined(HAVE_EXECINFO_H) && defined(HAVE_BACKTRACE) && ... */ #ifdef USE_BACKTRACE #include <execinfo.h> @@ -103,7 +106,12 @@ #define M_SYSCALL arm_r7 -#endif +#elif defined(__aarch64__) && defined(__LP64__) + +#define REG_SYSCALL 8 +#define M_SYSCALL regs[REG_SYSCALL] + +#endif /* defined(__i386__) || ... */ /**Determines if at least one sandbox is active.*/ static int sandbox_active = 0; @@ -133,6 +141,9 @@ static int filter_nopar_gen[] = { SCMP_SYS(clone), SCMP_SYS(epoll_create), SCMP_SYS(epoll_wait), +#ifdef __NR_epoll_pwait + SCMP_SYS(epoll_pwait), +#endif #ifdef HAVE_EVENTFD SCMP_SYS(eventfd2), #endif @@ -289,37 +300,6 @@ sb_rt_sigaction(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return rc; } -#if 0 -/** - * Function responsible for setting up the execve syscall for - * the seccomp filter sandbox. - */ -static int -sb_execve(scmp_filter_ctx ctx, sandbox_cfg_t *filter) -{ - int rc; - sandbox_cfg_t *elem = NULL; - - // for each dynamic parameter filters - for (elem = filter; elem != NULL; elem = elem->next) { - smp_param_t *param = elem->param; - - if (param != NULL && param->prot == 1 && param->syscall - == SCMP_SYS(execve)) { - rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(execve), - SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); - if (rc != 0) { - log_err(LD_BUG,"(Sandbox) failed to add execve syscall, received " - "libseccomp error %d", rc); - return rc; - } - } - } - - return 0; -} -#endif - /** * Function responsible for setting up the time syscall for * the seccomp filter sandbox. @@ -333,7 +313,7 @@ sb_time(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(0, SCMP_CMP_EQ, 0)); #else return 0; -#endif +#endif /* defined(__NR_time) */ } /** @@ -352,7 +332,7 @@ sb_accept4(scmp_filter_ctx ctx, sandbox_cfg_t *filter) if (rc) { return rc; } -#endif +#endif /* defined(__i386__) */ rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4), SCMP_CMP_MASKED(3, SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); @@ -425,7 +405,53 @@ sb_mmap2(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return 0; } +#endif /* defined(__NR_mmap2) */ + +#ifdef HAVE_GNU_LIBC_VERSION_H +#ifdef HAVE_GNU_GET_LIBC_VERSION +#define CHECK_LIBC_VERSION #endif +#endif + +/* Return true if we think we're running with a libc that always uses + * openat on linux. */ +static int +libc_uses_openat_for_everything(void) +{ +#ifdef CHECK_LIBC_VERSION + const char *version = gnu_get_libc_version(); + if (version == NULL) + return 0; + + int major = -1; + int minor = -1; + + tor_sscanf(version, "%d.%d", &major, &minor); + if (major >= 3) + return 1; + else if (major == 2 && minor >= 26) + return 1; + else + return 0; +#else + return 0; +#endif +} + +/** Allow a single file to be opened. If <b>use_openat</b> is true, + * we're using a libc that remaps all the opens into openats. */ +static int +allow_file_open(scmp_filter_ctx ctx, int use_openat, const char *file) +{ + if (use_openat) { + return seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), + SCMP_CMP_STR(0, SCMP_CMP_EQ, AT_FDCWD), + SCMP_CMP_STR(1, SCMP_CMP_EQ, file)); + } else { + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), + SCMP_CMP_STR(0, SCMP_CMP_EQ, file)); + } +} /** * Function responsible for setting up the open syscall for @@ -437,14 +463,15 @@ sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter) int rc; sandbox_cfg_t *elem = NULL; + int use_openat = libc_uses_openat_for_everything(); + // for each dynamic parameter filters for (elem = filter; elem != NULL; elem = elem->next) { smp_param_t *param = elem->param; if (param != NULL && param->prot == 1 && param->syscall == SCMP_SYS(open)) { - rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), - SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); + rc = allow_file_open(ctx, use_openat, param->value); if (rc != 0) { log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " "libseccomp error %d", rc); @@ -462,6 +489,15 @@ sb_open(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return rc; } + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(openat), + SCMP_CMP_MASKED(2, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|O_NOFOLLOW, + O_RDONLY)); + if (rc != 0) { + log_err(LD_BUG,"(Sandbox) failed to add openat syscall, received " + "libseccomp error %d", rc); + return rc; + } + return 0; } @@ -480,7 +516,7 @@ sb_chmod(scmp_filter_ctx ctx, sandbox_cfg_t *filter) rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chmod), SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); if (rc != 0) { - log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " + log_err(LD_BUG,"(Sandbox) failed to add chmod syscall, received " "libseccomp error %d", rc); return rc; } @@ -505,7 +541,7 @@ sb_chown(scmp_filter_ctx ctx, sandbox_cfg_t *filter) rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(chown), SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); if (rc != 0) { - log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " + log_err(LD_BUG,"(Sandbox) failed to add chown syscall, received " "libseccomp error %d", rc); return rc; } @@ -651,7 +687,7 @@ sb_socket(scmp_filter_ctx ctx, sandbox_cfg_t *filter) rc = seccomp_rule_add_3(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), SCMP_CMP(0, SCMP_CMP_EQ, PF_NETLINK), - SCMP_CMP(1, SCMP_CMP_EQ, SOCK_RAW), + SCMP_CMP_MASKED(1, SOCK_CLOEXEC, SOCK_RAW), SCMP_CMP(2, SCMP_CMP_EQ, 0)); if (rc) return rc; @@ -684,6 +720,25 @@ sb_socketpair(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return 0; } +#ifdef HAVE_KIST_SUPPORT + +#include <linux/sockios.h> + +static int +sb_ioctl(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + int rc; + (void) filter; + + rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), + SCMP_CMP(1, SCMP_CMP_EQ, SIOCOUTQNSD)); + if (rc) + return rc; + return 0; +} + +#endif /* defined(HAVE_KIST_SUPPORT) */ + /** * Function responsible for setting up the setsockopt syscall for * the seccomp filter sandbox. @@ -724,7 +779,7 @@ sb_setsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUFFORCE)); if (rc) return rc; -#endif +#endif /* defined(HAVE_SYSTEMD) */ #ifdef IP_TRANSPARENT rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), @@ -732,7 +787,7 @@ sb_setsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(2, SCMP_CMP_EQ, IP_TRANSPARENT)); if (rc) return rc; -#endif +#endif /* defined(IP_TRANSPARENT) */ #ifdef IPV6_V6ONLY rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), @@ -740,7 +795,7 @@ sb_setsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(2, SCMP_CMP_EQ, IPV6_V6ONLY)); if (rc) return rc; -#endif +#endif /* defined(IPV6_V6ONLY) */ return 0; } @@ -773,7 +828,7 @@ sb_getsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(2, SCMP_CMP_EQ, SO_SNDBUF)); if (rc) return rc; -#endif +#endif /* defined(HAVE_SYSTEMD) */ #ifdef HAVE_LINUX_NETFILTER_IPV4_H rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), @@ -781,7 +836,7 @@ sb_getsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(2, SCMP_CMP_EQ, SO_ORIGINAL_DST)); if (rc) return rc; -#endif +#endif /* defined(HAVE_LINUX_NETFILTER_IPV4_H) */ #ifdef HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), @@ -789,7 +844,16 @@ sb_getsockopt(scmp_filter_ctx ctx, sandbox_cfg_t *filter) SCMP_CMP(2, SCMP_CMP_EQ, IP6T_SO_ORIGINAL_DST)); if (rc) return rc; -#endif +#endif /* defined(HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H) */ + +#ifdef HAVE_KIST_SUPPORT +#include <netinet/tcp.h> + rc = seccomp_rule_add_2(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockopt), + SCMP_CMP(1, SCMP_CMP_EQ, SOL_TCP), + SCMP_CMP(2, SCMP_CMP_EQ, TCP_INFO)); + if (rc) + return rc; +#endif /* defined(HAVE_KIST_SUPPORT) */ return 0; } @@ -829,7 +893,7 @@ sb_fcntl64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return 0; } -#endif +#endif /* defined(__NR_fcntl64) */ /** * Function responsible for setting up the epoll_ctl syscall for @@ -1045,8 +1109,8 @@ sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) rc = seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat64), SCMP_CMP_STR(0, SCMP_CMP_EQ, param->value)); if (rc != 0) { - log_err(LD_BUG,"(Sandbox) failed to add open syscall, received " - "libseccomp error %d", rc); + log_err(LD_BUG,"(Sandbox) failed to add stat64 syscall, received " + "libseccomp error %d", rc); return rc; } } @@ -1054,7 +1118,20 @@ sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) return 0; } +#endif /* defined(__NR_stat64) */ + +static int +sb_kill(scmp_filter_ctx ctx, sandbox_cfg_t *filter) +{ + (void) filter; +#ifdef __NR_kill + /* Allow killing anything with signal 0 -- it isn't really a kill. */ + return seccomp_rule_add_1(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill), + SCMP_CMP(1, SCMP_CMP_EQ, 0)); +#else + return 0; #endif +} /** * Array of function pointers responsible for filtering different syscalls at @@ -1063,9 +1140,6 @@ sb_stat64(scmp_filter_ctx ctx, sandbox_cfg_t *filter) static sandbox_filter_func_t filter_func[] = { sb_rt_sigaction, sb_rt_sigprocmask, -#if 0 - sb_execve, -#endif sb_time, sb_accept4, #ifdef __NR_mmap2 @@ -1094,7 +1168,11 @@ static sandbox_filter_func_t filter_func[] = { sb_socket, sb_setsockopt, sb_getsockopt, - sb_socketpair + sb_socketpair, +#ifdef HAVE_KIST_SUPPORT + sb_ioctl, +#endif + sb_kill }; const char * @@ -1320,10 +1398,6 @@ sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file) sandbox_cfg_t *elem = NULL; elem = new_element(SCMP_stat, file); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } elem->next = *cfg; *cfg = elem; @@ -1337,10 +1411,6 @@ sandbox_cfg_allow_open_filename(sandbox_cfg_t **cfg, char *file) sandbox_cfg_t *elem = NULL; elem = new_element(SCMP_SYS(open), file); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } elem->next = *cfg; *cfg = elem; @@ -1354,10 +1424,6 @@ sandbox_cfg_allow_chmod_filename(sandbox_cfg_t **cfg, char *file) sandbox_cfg_t *elem = NULL; elem = new_element(SCMP_SYS(chmod), file); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } elem->next = *cfg; *cfg = elem; @@ -1371,10 +1437,6 @@ sandbox_cfg_allow_chown_filename(sandbox_cfg_t **cfg, char *file) sandbox_cfg_t *elem = NULL; elem = new_element(SCMP_SYS(chown), file); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } elem->next = *cfg; *cfg = elem; @@ -1389,11 +1451,6 @@ sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2) elem = new_element2(SCMP_SYS(rename), file1, file2); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } - elem->next = *cfg; *cfg = elem; @@ -1406,10 +1463,6 @@ sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file) sandbox_cfg_t *elem = NULL; elem = new_element(SCMP_SYS(openat), file); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } elem->next = *cfg; *cfg = elem; @@ -1417,26 +1470,6 @@ sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file) return 0; } -#if 0 -int -sandbox_cfg_allow_execve(sandbox_cfg_t **cfg, const char *com) -{ - sandbox_cfg_t *elem = NULL; - - elem = new_element(SCMP_SYS(execve), com); - if (!elem) { - log_err(LD_BUG,"(Sandbox) failed to register parameter!"); - return -1; - } - - elem->next = *cfg; - *cfg = elem; - - return 0; -} - -#endif - /** Cache entry for getaddrinfo results; used when sandboxing is implemented * so that we can consult the cache when the sandbox prevents us from doing * getaddrinfo. @@ -1622,7 +1655,8 @@ add_param_filter(scmp_filter_ctx ctx, sandbox_cfg_t* cfg) // function pointer for (i = 0; i < ARRAY_LENGTH(filter_func); i++) { - if ((filter_func[i])(ctx, cfg)) { + rc = filter_func[i](ctx, cfg); + if (rc) { log_err(LD_BUG,"(Sandbox) failed to add syscall %d, received libseccomp " "error %d", i, rc); return rc; @@ -1692,7 +1726,9 @@ install_syscall_filter(sandbox_cfg_t* cfg) // loading the seccomp2 filter if ((rc = seccomp_load(ctx))) { - log_err(LD_BUG, "(Sandbox) failed to load: %d (%s)!", rc, + log_err(LD_BUG, "(Sandbox) failed to load: %d (%s)! " + "Are you sure that your kernel has seccomp2 support? The " + "sandbox won't work without it.", rc, strerror(-rc)); goto end; } @@ -1760,7 +1796,7 @@ sigsys_debugging(int nr, siginfo_t *info, void *void_context) /* Clean up the top stack frame so we get the real function * name for the most recently failing function. */ clean_backtrace(syscall_cb_buf, depth, ctx); -#endif +#endif /* defined(USE_BACKTRACE) */ syscall_name = get_syscall_name(syscall); @@ -1834,7 +1870,7 @@ register_cfg(sandbox_cfg_t* cfg) return 0; } -#endif // USE_LIBSECCOMP +#endif /* defined(USE_LIBSECCOMP) */ #ifdef USE_LIBSECCOMP /** @@ -1864,7 +1900,7 @@ sandbox_is_active(void) { return sandbox_active != 0; } -#endif // USE_LIBSECCOMP +#endif /* defined(USE_LIBSECCOMP) */ sandbox_cfg_t* sandbox_cfg_new(void) @@ -1892,7 +1928,7 @@ sandbox_init(sandbox_cfg_t *cfg) "Currently, sandboxing is only implemented on Linux. The feature " "is disabled on your platform."); return 0; -#endif +#endif /* defined(USE_LIBSECCOMP) || ... */ } #ifndef USE_LIBSECCOMP @@ -1910,15 +1946,6 @@ sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file) return 0; } -#if 0 -int -sandbox_cfg_allow_execve(sandbox_cfg_t **cfg, const char *com) -{ - (void)cfg; (void)com; - return 0; -} -#endif - int sandbox_cfg_allow_stat_filename(sandbox_cfg_t **cfg, char *file) { @@ -1957,5 +1984,5 @@ void sandbox_disable_getaddrinfo_cache(void) { } -#endif +#endif /* !defined(USE_LIBSECCOMP) */ diff --git a/src/common/sandbox.h b/src/common/sandbox.h index a6b83153af..d0f85570f4 100644 --- a/src/common/sandbox.h +++ b/src/common/sandbox.h @@ -23,7 +23,7 @@ */ #define SYS_SECCOMP 1 -#endif +#endif /* !defined(SYS_SECCOMP) */ #if defined(HAVE_SECCOMP_H) && defined(__linux__) #define USE_LIBSECCOMP @@ -101,7 +101,7 @@ typedef struct { sandbox_cfg_t *filter_dynamic; } sandbox_t; -#endif // USE_LIBSECCOMP +#endif /* defined(USE_LIBSECCOMP) */ #ifdef USE_LIBSECCOMP /** Pre-calls getaddrinfo in order to pre-record result. */ @@ -114,7 +114,7 @@ int sandbox_getaddrinfo(const char *name, const char *servname, struct addrinfo **res); void sandbox_freeaddrinfo(struct addrinfo *addrinfo); void sandbox_free_getaddrinfo_cache(void); -#else +#else /* !(defined(USE_LIBSECCOMP)) */ #define sandbox_getaddrinfo(name, servname, hints, res) \ getaddrinfo((name),(servname), (hints),(res)) #define sandbox_add_addrinfo(name) \ @@ -122,16 +122,16 @@ void sandbox_free_getaddrinfo_cache(void); #define sandbox_freeaddrinfo(addrinfo) \ freeaddrinfo((addrinfo)) #define sandbox_free_getaddrinfo_cache() -#endif +#endif /* defined(USE_LIBSECCOMP) */ #ifdef USE_LIBSECCOMP /** Returns a registered protected string used with the sandbox, given that * it matches the parameter. */ const char* sandbox_intern_string(const char *param); -#else +#else /* !(defined(USE_LIBSECCOMP)) */ #define sandbox_intern_string(s) (s) -#endif +#endif /* defined(USE_LIBSECCOMP) */ /** Creates an empty sandbox configuration file.*/ sandbox_cfg_t * sandbox_cfg_new(void); @@ -156,14 +156,6 @@ int sandbox_cfg_allow_rename(sandbox_cfg_t **cfg, char *file1, char *file2); */ int sandbox_cfg_allow_openat_filename(sandbox_cfg_t **cfg, char *file); -#if 0 -/** - * Function used to add a execve allowed filename to a supplied configuration. - * The (char*) specifies the path to the allowed file; that pointer is stolen. - */ -int sandbox_cfg_allow_execve(sandbox_cfg_t **cfg, const char *com); -#endif - /** * Function used to add a stat/stat64 allowed filename to a configuration. * The (char*) specifies the path to the allowed file; that pointer is stolen. @@ -178,5 +170,5 @@ int sandbox_is_active(void); void sandbox_disable_getaddrinfo_cache(void); -#endif /* SANDBOX_H_ */ +#endif /* !defined(SANDBOX_H_) */ diff --git a/src/common/storagedir.h b/src/common/storagedir.h index db25057e65..3de0afc361 100644 --- a/src/common/storagedir.h +++ b/src/common/storagedir.h @@ -47,5 +47,5 @@ int storage_dir_shrink(storage_dir_t *d, int storage_dir_remove_all(storage_dir_t *d); int storage_dir_get_max_files(storage_dir_t *d); -#endif +#endif /* !defined(TOR_STORAGEDIR_H) */ diff --git a/src/common/testsupport.h b/src/common/testsupport.h index 9fc96199b4..a3f2ff91ed 100644 --- a/src/common/testsupport.h +++ b/src/common/testsupport.h @@ -10,7 +10,7 @@ #else #define STATIC static #define EXTERN(type, name) -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Quick and dirty macros to implement test mocking. * @@ -76,15 +76,15 @@ do { \ func = func ##__real; \ } while (0) -#else +#else /* !(defined(TOR_UNIT_TESTS)) */ #define MOCK_DECL(rv, funcname, arglist) \ rv funcname arglist #define MOCK_DECL_ATTR(rv, funcname, arglist, attr) \ rv funcname arglist attr #define MOCK_IMPL(rv, funcname, arglist) \ rv funcname arglist -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** @} */ -#endif +#endif /* !defined(TOR_TESTSUPPORT_H) */ diff --git a/src/common/timers.c b/src/common/timers.c index c43c49c083..c8e09414f4 100644 --- a/src/common/timers.c +++ b/src/common/timers.c @@ -53,7 +53,7 @@ struct timeout_cb { #else /* We're not exposing any of the functions outside this file. */ #define TIMEOUT_PUBLIC static -#endif +#endif /* defined(__GNUC__) */ /* We're not using periodic events. */ #define TIMEOUT_DISABLE_INTERVALS /* We always know the global_timeouts object, so we don't need each timeout @@ -191,7 +191,7 @@ timers_initialize(void) if (BUG(global_timeouts)) return; // LCOV_EXCL_LINE - timeout_error_t err; + timeout_error_t err = 0; global_timeouts = timeouts_open(0, &err); if (!global_timeouts) { // LCOV_EXCL_START -- this can only fail on malloc failure. diff --git a/src/common/timers.h b/src/common/timers.h index d9602cd2ae..d4d4fb00a9 100644 --- a/src/common/timers.h +++ b/src/common/timers.h @@ -26,5 +26,5 @@ void timers_shutdown(void); STATIC void timers_run_pending(void); #endif -#endif +#endif /* !defined(TOR_TIMERS_H) */ diff --git a/src/common/torint.h b/src/common/torint.h index ee31459e94..bc81c114f8 100644 --- a/src/common/torint.h +++ b/src/common/torint.h @@ -34,8 +34,8 @@ does the same thing (but doesn't defined __FreeBSD__). */ #include <machine/limits.h> -#endif -#endif +#endif /* !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) */ +#endif /* defined(HAVE_MACHINE_LIMITS_H) */ #ifdef HAVE_INTTYPES_H #include <inttypes.h> #endif @@ -80,7 +80,7 @@ typedef signed char int8_t; typedef unsigned char uint8_t; #define HAVE_UINT8_T #endif -#endif +#endif /* (SIZEOF_CHAR == 1) */ #if (SIZEOF_SHORT == 2) #ifndef HAVE_INT16_T @@ -91,7 +91,7 @@ typedef signed short int16_t; typedef unsigned short uint16_t; #define HAVE_UINT16_T #endif -#endif +#endif /* (SIZEOF_SHORT == 2) */ #if (SIZEOF_INT == 2) #ifndef HAVE_INT16_T @@ -129,7 +129,7 @@ typedef unsigned int uint32_t; #ifndef INT32_MIN #define INT32_MIN (-2147483647-1) #endif -#endif +#endif /* (SIZEOF_INT == 2) || ... */ #if (SIZEOF_LONG == 4) #ifndef HAVE_INT32_T @@ -142,7 +142,7 @@ typedef unsigned long uint32_t; #ifndef UINT32_MAX #define UINT32_MAX 0xfffffffful #endif -#endif +#endif /* !defined(HAVE_UINT32_T) */ #elif (SIZEOF_LONG == 8) #ifndef HAVE_INT64_T typedef signed long int64_t; @@ -155,7 +155,7 @@ typedef unsigned long uint64_t; #ifndef UINT64_MAX #define UINT64_MAX 0xfffffffffffffffful #endif -#endif +#endif /* (SIZEOF_LONG == 4) || ... */ #if (SIZEOF_LONG_LONG == 8) #ifndef HAVE_INT64_T @@ -172,7 +172,7 @@ typedef unsigned long long uint64_t; #ifndef INT64_MAX #define INT64_MAX 0x7fffffffffffffffll #endif -#endif +#endif /* (SIZEOF_LONG_LONG == 8) */ #if (SIZEOF___INT64 == 8) #ifndef HAVE_INT64_T @@ -189,7 +189,7 @@ typedef unsigned __int64 uint64_t; #ifndef INT64_MAX #define INT64_MAX 0x7fffffffffffffffi64 #endif -#endif +#endif /* (SIZEOF___INT64 == 8) */ #ifndef INT64_MIN #define INT64_MIN ((- INT64_MAX) - 1) @@ -202,8 +202,8 @@ typedef unsigned __int64 uint64_t; #define SIZE_MAX UINT32_MAX #else #error "Can't define SIZE_MAX" -#endif -#endif +#endif /* SIZEOF_SIZE_T == 8 || ... */ +#endif /* !defined(SIZE_MAX) */ #ifndef HAVE_SSIZE_T #if SIZEOF_SIZE_T == 8 @@ -212,8 +212,8 @@ typedef int64_t ssize_t; typedef int32_t ssize_t; #else #error "Can't define ssize_t." -#endif -#endif +#endif /* SIZEOF_SIZE_T == 8 || ... */ +#endif /* !defined(HAVE_SSIZE_T) */ #if (SIZEOF_VOID_P > 4 && SIZEOF_VOID_P <= 8) #ifndef HAVE_INTPTR_T @@ -235,7 +235,7 @@ typedef uint32_t uintptr_t; #endif #else #error "void * is either >8 bytes or <= 2. In either case, I am confused." -#endif +#endif /* (SIZEOF_VOID_P > 4 && SIZEOF_VOID_P <= 8) || ... */ #ifndef HAVE_INT8_T #error "Missing type int8_t" @@ -275,8 +275,8 @@ typedef uint32_t uintptr_t; #define LONG_MAX 0x7fffffffffffffffL #else #error "Can't define LONG_MAX" -#endif -#endif +#endif /* (SIZEOF_LONG == 4) || ... */ +#endif /* !defined(LONG_MAX) */ #ifndef INT_MAX #if (SIZEOF_INT == 4) @@ -285,8 +285,8 @@ typedef uint32_t uintptr_t; #define INT_MAX 0x7fffffffffffffffL #else #error "Can't define INT_MAX" -#endif -#endif +#endif /* (SIZEOF_INT == 4) || ... */ +#endif /* !defined(INT_MAX) */ #ifndef UINT_MAX #if (SIZEOF_INT == 2) @@ -297,8 +297,8 @@ typedef uint32_t uintptr_t; #define UINT_MAX 0xffffffffffffffffu #else #error "Can't define UINT_MAX" -#endif -#endif +#endif /* (SIZEOF_INT == 2) || ... */ +#endif /* !defined(UINT_MAX) */ #ifndef SHORT_MAX #if (SIZEOF_SHORT == 2) @@ -307,8 +307,8 @@ typedef uint32_t uintptr_t; #define SHORT_MAX 0x7fffffff #else #error "Can't define SHORT_MAX" -#endif -#endif +#endif /* (SIZEOF_SHORT == 2) || ... */ +#endif /* !defined(SHORT_MAX) */ #ifndef TIME_MAX @@ -320,9 +320,9 @@ typedef uint32_t uintptr_t; #define TIME_MAX ((time_t)INT64_MAX) #else #error "Can't define TIME_MAX" -#endif +#endif /* (SIZEOF_TIME_T == SIZEOF_INT) || ... */ -#endif /* ifndef(TIME_MAX) */ +#endif /* !defined(TIME_MAX) */ #ifndef TIME_MIN @@ -334,9 +334,9 @@ typedef uint32_t uintptr_t; #define TIME_MIN ((time_t)INT64_MIN) #else #error "Can't define TIME_MIN" -#endif +#endif /* (SIZEOF_TIME_T == SIZEOF_INT) || ... */ -#endif /* ifndef(TIME_MIN) */ +#endif /* !defined(TIME_MIN) */ #ifndef SIZE_MAX #if (SIZEOF_SIZE_T == 4) @@ -345,8 +345,8 @@ typedef uint32_t uintptr_t; #define SIZE_MAX UINT64_MAX #else #error "Can't define SIZE_MAX" -#endif -#endif +#endif /* (SIZEOF_SIZE_T == 4) || ... */ +#endif /* !defined(SIZE_MAX) */ #ifndef SSIZE_MAX #if (SIZEOF_SIZE_T == 4) @@ -355,13 +355,13 @@ typedef uint32_t uintptr_t; #define SSIZE_MAX INT64_MAX #else #error "Can't define SSIZE_MAX" -#endif -#endif +#endif /* (SIZEOF_SIZE_T == 4) || ... */ +#endif /* !defined(SSIZE_MAX) */ /** Any ssize_t larger than this amount is likely to be an underflow. */ #define SSIZE_T_CEILING ((ssize_t)(SSIZE_MAX-16)) /** Any size_t larger than this amount is likely to be an underflow. */ #define SIZE_T_CEILING ((size_t)(SSIZE_MAX-16)) -#endif /* __TORINT_H */ +#endif /* !defined(TOR_TORINT_H) */ diff --git a/src/common/torlog.h b/src/common/torlog.h index 6e374b1c11..65bf41702a 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -22,7 +22,7 @@ #error "Your syslog.h thinks high numbers are more important. " \ "We aren't prepared to deal with that." #endif -#else +#else /* !(defined(HAVE_SYSLOG_H)) */ /* Note: Syslog's logging code refers to priorities, with 0 being the most * important. Thus, all our comparisons needed to be reversed when we added * syslog support. @@ -48,7 +48,7 @@ /** Error-level severity: for messages that only appear when something has gone * very wrong, and the Tor process can no longer proceed. */ #define LOG_ERR 3 -#endif +#endif /* defined(HAVE_SYSLOG_H) */ /* Logging domains */ @@ -213,7 +213,7 @@ void log_fn_ratelim_(struct ratelim_t *ratelim, int severity, #define log_err(domain, args...) \ log_fn_(LOG_ERR, domain, __FUNCTION__, args) -#else /* ! defined(__GNUC__) */ +#else /* !(defined(__GNUC__) && __GNUC__ <= 3) */ /* Here are the c99 variadic macros, to work with non-GCC compilers */ @@ -240,7 +240,7 @@ void log_fn_ratelim_(struct ratelim_t *ratelim, int severity, #define log_fn_ratelim(ratelim, severity, domain, args,...) \ log_fn_ratelim_(ratelim, severity, domain, __FUNCTION__, \ args, ##__VA_ARGS__) -#endif +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ #ifdef LOG_PRIVATE MOCK_DECL(STATIC void, logv, (int severity, log_domain_mask_t domain, @@ -249,5 +249,5 @@ MOCK_DECL(STATIC void, logv, (int severity, log_domain_mask_t domain, #endif # define TOR_TORLOG_H -#endif +#endif /* !defined(TOR_TORLOG_H) */ diff --git a/src/common/tortls.c b/src/common/tortls.c index 1c47cf9882..9d2d0240a2 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -76,7 +76,7 @@ ENABLE_GCC_WARNING(redundant-decls) * SSL3 safely at the same time. */ #define DISABLE_SSL3_HANDSHAKE -#endif +#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,0,'f') */ /* We redefine these so that we can run correctly even if the vendor gives us * a version of OpenSSL that does not match its header files. (Apple: I am @@ -390,7 +390,7 @@ tor_tls_init(void) "when configuring it) would make ECDH much faster."); } /* LCOV_EXCL_STOP */ -#endif +#endif /* (SIZEOF_VOID_P >= 8 && ... */ tor_tls_allocate_tor_tls_object_ex_data_index(); @@ -444,8 +444,9 @@ tor_x509_name_new(const char *cname) goto error; /* LCOV_EXCL_BR_STOP */ return name; - error: + /* LCOV_EXCL_START : these lines will only execute on out of memory errors*/ + error: X509_NAME_free(name); return NULL; /* LCOV_EXCL_STOP */ @@ -490,11 +491,14 @@ tor_tls_create_certificate,(crypto_pk_t *rsa, * the past. */ const time_t min_real_lifetime = 24*3600; const time_t start_granularity = 24*3600; - time_t earliest_start_time = now - cert_lifetime + min_real_lifetime - + start_granularity; + time_t earliest_start_time; /* Don't actually start in the future! */ - if (earliest_start_time >= now) + if (cert_lifetime <= min_real_lifetime + start_granularity) { earliest_start_time = now - 1; + } else { + earliest_start_time = now + min_real_lifetime + start_granularity + - cert_lifetime; + } start_time = crypto_rand_time_range(earliest_start_time, now); /* Round the start time back to the start of a day. */ start_time -= start_time % start_granularity; @@ -1134,7 +1138,7 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, * with existing Tors. */ if (!(result->ctx = SSL_CTX_new(TLSv1_method()))) goto error; -#endif +#endif /* 0 */ /* Tell OpenSSL to use TLS 1.0 or later but not SSL2 or SSL3. */ #ifdef HAVE_TLS_METHOD @@ -1143,7 +1147,7 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, #else if (!(result->ctx = SSL_CTX_new(SSLv23_method()))) goto error; -#endif +#endif /* defined(HAVE_TLS_METHOD) */ SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv2); SSL_CTX_set_options(result->ctx, SSL_OP_NO_SSLv3); @@ -1181,17 +1185,20 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, SSL_CTX_set_options(result->ctx, SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION); } + + /* Don't actually allow compression; it uses RAM and time, it makes TLS + * vulnerable to CRIME-style attacks, and most of the data we transmit over + * TLS is encrypted (and therefore uncompressible) anyway. */ #ifdef SSL_OP_NO_COMPRESSION SSL_CTX_set_options(result->ctx, SSL_OP_NO_COMPRESSION); #endif #if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) #ifndef OPENSSL_NO_COMP - /* Don't actually allow compression; it uses ram and time, but the data - * we transmit is all encrypted anyway. */ if (result->ctx->comp_methods) result->ctx->comp_methods = NULL; #endif -#endif +#endif /* OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) */ + #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(result->ctx, SSL_MODE_RELEASE_BUFFERS); #endif @@ -1351,7 +1358,7 @@ find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) tor_assert((SSL_CIPHER_get_id(c) & 0xffff) == cipher); return c != NULL; } -#else +#else /* !(defined(HAVE_SSL_CIPHER_FIND)) */ # if defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) if (m && m->get_cipher_by_char) { @@ -1365,7 +1372,7 @@ find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) tor_assert((c->id & 0xffff) == cipher); return c != NULL; } -# endif +#endif /* defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) */ # ifndef OPENSSL_1_1_API if (m && m->get_cipher && m->num_ciphers) { /* It would seem that some of the "let's-clean-up-openssl" forks have @@ -1381,12 +1388,12 @@ find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) } return 0; } -# endif +#endif /* !defined(OPENSSL_1_1_API) */ (void) ssl; (void) m; (void) cipher; return 1; /* No way to search */ -#endif +#endif /* defined(HAVE_SSL_CIPHER_FIND) */ } /** Remove from v2_cipher_list every cipher that we don't support, so that @@ -1514,7 +1521,7 @@ tor_tls_client_is_using_v2_ciphers(const SSL *ssl) return CIPHERS_ERR; } ciphers = session->ciphers; -#endif +#endif /* defined(HAVE_SSL_GET_CLIENT_CIPHERS) */ return tor_tls_classify_client_ciphers(ssl, ciphers) >= CIPHERS_V2; } @@ -1645,7 +1652,7 @@ tor_tls_new(int sock, int isServer) SSL_set_tlsext_host_name(result->ssl, fake_hostname); tor_free(fake_hostname); } -#endif +#endif /* defined(SSL_set_tlsext_host_name) */ if (!SSL_set_cipher_list(result->ssl, isServer ? SERVER_CIPHER_LIST : CLIENT_CIPHER_LIST)) { @@ -1772,7 +1779,7 @@ tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls) tor_assert(0 != (options & SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)); #else (void) tls; -#endif +#endif /* defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION) && ... */ } /** Return whether this tls initiated the connect (client) or @@ -2308,7 +2315,7 @@ tor_x509_cert_replace_expiration(const tor_x509_cert_t *inp, EVP_PKEY_free(pk); return tor_x509_cert_new(newc); } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Return the number of bytes available for reading from <b>tls</b>. */ @@ -2352,10 +2359,10 @@ tor_tls_get_n_raw_bytes(tor_tls_t *tls, size_t *n_read, size_t *n_written) if (BIO_method_type(wbio) == BIO_TYPE_BUFFER && (tmpbio = BIO_next(wbio)) != NULL) wbio = tmpbio; -#else +#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5)) */ if (wbio->method == BIO_f_buffer() && (tmpbio = BIO_next(wbio)) != NULL) wbio = tmpbio; -#endif +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_VER(1,1,0,0,5) */ w = (unsigned long) BIO_number_written(wbio); /* We are ok with letting these unsigned ints go "negative" here: @@ -2434,7 +2441,7 @@ SSL_get_client_random(SSL *s, uint8_t *out, size_t len) memcpy(out, s->s3->client_random, len); return len; } -#endif +#endif /* !defined(HAVE_SSL_GET_CLIENT_RANDOM) */ #ifndef HAVE_SSL_GET_SERVER_RANDOM static size_t @@ -2447,7 +2454,7 @@ SSL_get_server_random(SSL *s, uint8_t *out, size_t len) memcpy(out, s->s3->server_random, len); return len; } -#endif +#endif /* !defined(HAVE_SSL_GET_SERVER_RANDOM) */ #ifndef HAVE_SSL_SESSION_GET_MASTER_KEY STATIC size_t @@ -2461,7 +2468,7 @@ SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, size_t len) memcpy(out, s->master_key, len); return len; } -#endif +#endif /* !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) */ /** Set the DIGEST256_LEN buffer at <b>secrets_out</b> to the value used in * the v3 handshake to prove that the client knows the TLS secrets for the @@ -2570,7 +2577,7 @@ tor_tls_get_buffer_sizes(tor_tls_t *tls, (void)wbuf_bytes; return -1; -#else +#else /* !(OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0)) */ if (tls->ssl->s3->rbuf.buf) *rbuf_capacity = tls->ssl->s3->rbuf.len; else @@ -2582,7 +2589,7 @@ tor_tls_get_buffer_sizes(tor_tls_t *tls, *rbuf_bytes = tls->ssl->s3->rbuf.left; *wbuf_bytes = tls->ssl->s3->wbuf.left; return 0; -#endif +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ } /** Check whether the ECC group requested is supported by the current OpenSSL diff --git a/src/common/tortls.h b/src/common/tortls.h index f430aff70b..6145f7dbc9 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -161,7 +161,7 @@ STATIC int tor_tls_session_secret_cb(struct ssl_st *ssl, void *secret, void *arg); STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher); -#endif +#endif /* defined(TORTLS_OPENSSL_PRIVATE) */ MOCK_DECL(STATIC struct x509_st *, tor_tls_create_certificate, (crypto_pk_t *rsa, crypto_pk_t *rsa_sign, @@ -192,9 +192,9 @@ STATIC tor_x509_cert_t *tor_x509_cert_replace_expiration( const tor_x509_cert_t *inp, time_t new_expiration_time, crypto_pk_t *signing_key); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* endif TORTLS_PRIVATE */ +#endif /* defined(TORTLS_PRIVATE) */ tor_x509_cert_t *tor_x509_cert_dup(const tor_x509_cert_t *cert); const char *tor_tls_err_to_string(int err); @@ -288,5 +288,5 @@ const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls); int evaluate_ecgroup_for_tls(const char *ecgroup); -#endif +#endif /* !defined(TOR_TORTLS_H) */ diff --git a/src/common/util.c b/src/common/util.c index 5b47028097..5ff7e104d6 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -31,11 +31,11 @@ #include <process.h> #include <tchar.h> #include <winbase.h> -#else +#else /* !(defined(_WIN32)) */ #include <dirent.h> #include <pwd.h> #include <grp.h> -#endif +#endif /* defined(_WIN32) */ /* math.h needs this on Linux */ #ifndef _USE_ISOC99_ @@ -84,8 +84,8 @@ * scold us for being so stupid as to autodetect its presence. To be fair, * they've done this since 1996, when autoconf was only 5 years old. */ #include <malloc.h> -#endif -#endif +#endif /* !defined(OpenBSD) && !defined(__FreeBSD__) */ +#endif /* defined(HAVE_MALLOC_H) */ #ifdef HAVE_MALLOC_NP_H #include <malloc_np.h> #endif @@ -116,12 +116,12 @@ dmalloc_strndup(file, line, (string), -1, xalloc_b) #else #error "No dmalloc_strdup or equivalent" - #endif +#endif /* defined(HAVE_DMALLOC_STRDUP) || ... */ -#else /* not using dmalloc */ +#else /* !(defined(USE_DMALLOC)) */ #define DMALLOC_FN_ARGS -#endif +#endif /* defined(USE_DMALLOC) */ /** Allocate a chunk of <b>size</b> bytes of memory, and return a pointer to * result. On error, log and terminate the process. (Same as malloc(size), @@ -142,7 +142,7 @@ tor_malloc_(size_t size DMALLOC_PARAMS) if (size==0) { size=1; } -#endif +#endif /* !defined(MALLOC_ZERO_WORKS) */ #ifdef USE_DMALLOC result = dmalloc_malloc(file, line, size, DMALLOC_FUNC_MALLOC, 0, 0); @@ -233,7 +233,7 @@ tor_realloc_(void *ptr, size_t size DMALLOC_PARAMS) if (size==0) { size=1; } -#endif +#endif /* !defined(MALLOC_ZERO_WORKS) */ #ifdef USE_DMALLOC result = dmalloc_realloc(file, line, ptr, size, DMALLOC_FUNC_REALLOC, 0); @@ -362,16 +362,16 @@ tor_log_mallinfo(int severity) mi.arena, mi.ordblks, mi.smblks, mi.hblks, mi.hblkhd, mi.usmblks, mi.fsmblks, mi.uordblks, mi.fordblks, mi.keepcost); -#else +#else /* !(defined(HAVE_MALLINFO)) */ (void)severity; -#endif +#endif /* defined(HAVE_MALLINFO) */ #ifdef USE_DMALLOC dmalloc_log_changed(0, /* Since the program started. */ 1, /* Log info about non-freed pointers. */ 0, /* Do not log info about freed pointers. */ 0 /* Do not log individual pointers. */ ); -#endif +#endif /* defined(USE_DMALLOC) */ } ENABLE_GCC_WARNING(aggregate-return) @@ -401,7 +401,7 @@ tor_lround(double d) return (long)rint(d); #else return (long)(d > 0 ? d + 0.5 : ceil(d - 0.5)); -#endif +#endif /* defined(HAVE_LROUND) || ... */ } /** Return the 64-bit integer closest to d. We define this wrapper here so @@ -416,7 +416,7 @@ tor_llround(double d) return (int64_t)rint(d); #else return (int64_t)(d > 0 ? d + 0.5 : ceil(d - 0.5)); -#endif +#endif /* defined(HAVE_LLROUND) || ... */ } /** Returns floor(log2(u64)). If u64 is 0, (incorrectly) returns 0. */ @@ -445,7 +445,7 @@ tor_log2(uint64_t u64) r += 2; } if (u64 >= (U64_LITERAL(1)<<1)) { - u64 >>= 1; + // u64 >>= 1; // not using this any more. r += 1; } return r; @@ -477,7 +477,7 @@ round_to_power_of_2(uint64_t u64) /** Return the lowest x such that x is at least <b>number</b>, and x modulo * <b>divisor</b> == 0. If no such x can be expressed as an unsigned, return - * UINT_MAX */ + * UINT_MAX. Asserts if divisor is zero. */ unsigned round_to_next_multiple_of(unsigned number, unsigned divisor) { @@ -491,7 +491,7 @@ round_to_next_multiple_of(unsigned number, unsigned divisor) /** Return the lowest x such that x is at least <b>number</b>, and x modulo * <b>divisor</b> == 0. If no such x can be expressed as a uint32_t, return - * UINT32_MAX */ + * UINT32_MAX. Asserts if divisor is zero. */ uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor) { @@ -506,7 +506,7 @@ round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor) /** Return the lowest x such that x is at least <b>number</b>, and x modulo * <b>divisor</b> == 0. If no such x can be expressed as a uint64_t, return - * UINT64_MAX */ + * UINT64_MAX. Asserts if divisor is zero. */ uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor) { @@ -1172,7 +1172,7 @@ tor_parse_long(const char *s, int base, long min, long max, char *endptr; long r; - if (base < 0) { + if (BUG(base < 0)) { if (ok) *ok = 0; return 0; @@ -1191,7 +1191,7 @@ tor_parse_ulong(const char *s, int base, unsigned long min, char *endptr; unsigned long r; - if (base < 0) { + if (BUG(base < 0)) { if (ok) *ok = 0; return 0; @@ -1223,7 +1223,7 @@ tor_parse_uint64(const char *s, int base, uint64_t min, char *endptr; uint64_t r; - if (base < 0) { + if (BUG(base < 0)) { if (ok) *ok = 0; return 0; @@ -1233,20 +1233,12 @@ tor_parse_uint64(const char *s, int base, uint64_t min, #ifdef HAVE_STRTOULL r = (uint64_t)strtoull(s, &endptr, base); #elif defined(_WIN32) -#if defined(_MSC_VER) && _MSC_VER < 1300 - tor_assert(base <= 10); - r = (uint64_t)_atoi64(s); - endptr = (char*)s; - while (TOR_ISSPACE(*endptr)) endptr++; - while (TOR_ISDIGIT(*endptr)) endptr++; -#else r = (uint64_t)_strtoui64(s, &endptr, base); -#endif #elif SIZEOF_LONG == 8 r = (uint64_t)strtoul(s, &endptr, base); #else #error "I don't know how to parse 64-bit numbers." -#endif +#endif /* defined(HAVE_STRTOULL) || ... */ CHECK_STRTOX_RESULT(); } @@ -1644,7 +1636,7 @@ tor_timegm(const struct tm *tm, time_t *time_out) log_warn(LD_BUG, "Result does not fit in tor_timegm"); return -1; } -#endif +#endif /* SIZEOF_TIME_T < 8 */ *time_out = (time_t)seconds; return 0; } @@ -2022,7 +2014,7 @@ update_approx_time(time_t now) { cached_approx_time = now; } -#endif +#endif /* !defined(TIME_IS_FAST) */ /* ===== * Rate limiting @@ -2151,9 +2143,9 @@ clean_name_for_stat(char *name) return; name[len-1]='\0'; } -#else +#else /* !(defined(_WIN32)) */ (void)name; -#endif +#endif /* defined(_WIN32) */ } /** Wrapper for unlink() to make it mockable for the test suite; returns 0 @@ -2350,21 +2342,27 @@ check_private_dir,(const char *dirname, cpd_check_t check, running_gid = getgid(); } if (st.st_uid != running_uid) { - const struct passwd *pw_uid = NULL; - char *process_ownername = NULL; + char *process_ownername = NULL, *file_ownername = NULL; - pw_uid = tor_getpwuid(running_uid); - process_ownername = pw_uid ? tor_strdup(pw_uid->pw_name) : - tor_strdup("<unknown>"); + { + const struct passwd *pw_running = tor_getpwuid(running_uid); + process_ownername = pw_running ? tor_strdup(pw_running->pw_name) : + tor_strdup("<unknown>"); + } - pw_uid = tor_getpwuid(st.st_uid); + { + const struct passwd *pw_stat = tor_getpwuid(st.st_uid); + file_ownername = pw_stat ? tor_strdup(pw_stat->pw_name) : + tor_strdup("<unknown>"); + } log_warn(LD_FS, "%s is not owned by this user (%s, %d) but by " "%s (%d). Perhaps you are running Tor as the wrong user?", - dirname, process_ownername, (int)running_uid, - pw ? pw->pw_name : "<unknown>", (int)st.st_uid); + dirname, process_ownername, (int)running_uid, + file_ownername, (int)st.st_uid); tor_free(process_ownername); + tor_free(file_ownername); close(fd); return -1; } @@ -2421,7 +2419,7 @@ check_private_dir,(const char *dirname, cpd_check_t check, } } close(fd); -#else +#else /* !(!defined(_WIN32)) */ /* Win32 case: we can't open() a directory. */ (void)effective_user; @@ -2455,7 +2453,7 @@ check_private_dir,(const char *dirname, cpd_check_t check, return -1; } -#endif +#endif /* !defined(_WIN32) */ return 0; } @@ -2475,7 +2473,7 @@ write_str_to_file,(const char *fname, const char *str, int bin)) "We're writing a text string that already contains a CR to %s", escaped(fname)); } -#endif +#endif /* defined(_WIN32) */ return write_bytes_to_file(fname, str, strlen(str), bin); } @@ -2875,7 +2873,7 @@ read_file_to_str, (const char *filename, int flags, struct stat *stat_out)) errno = save_errno; return string; } -#endif +#endif /* !defined(_WIN32) */ if ((uint64_t)(statbuf.st_size)+1 >= SIZE_T_CEILING) { close(fd); @@ -2908,7 +2906,7 @@ read_file_to_str, (const char *filename, int flags, struct stat *stat_out)) if (!bin) { statbuf.st_size = (size_t) r; } else -#endif +#endif /* defined(_WIN32) || defined(__CYGWIN__) */ if (r != statbuf.st_size) { /* Unless we're using text mode on win32, we'd better have an exact * match for size. */ @@ -2982,8 +2980,9 @@ unescape_string(const char *s, char **result, size_t *size_out) *out = '\0'; if (size_out) *size_out = out - *result; return cp+1; - case '\0': + /* LCOV_EXCL_START -- we caught this in parse_config_from_line. */ + case '\0': tor_fragile_assert(); tor_free(*result); return NULL; @@ -3031,8 +3030,9 @@ unescape_string(const char *s, char **result, size_t *size_out) *out++ = cp[1]; cp += 2; break; - default: + /* LCOV_EXCL_START */ + default: /* we caught this above in the initial loop. */ tor_assert_nonfatal_unreached(); tor_free(*result); return NULL; @@ -3092,7 +3092,7 @@ expand_filename(const char *filename) * Chapter+3.+Input+Validation/3.7+Validating+Filenames+and+Paths/ */ return tor_strdup(filename); -#else +#else /* !(defined(_WIN32)) */ if (*filename == '~') { char *home, *result=NULL; const char *rest; @@ -3122,10 +3122,10 @@ expand_filename(const char *filename) } tor_free(username); rest = slash ? (slash+1) : ""; -#else +#else /* !(defined(HAVE_PWD_H)) */ log_warn(LD_CONFIG, "Couldn't expand homedir on system without pwd.h"); return tor_strdup(filename); -#endif +#endif /* defined(HAVE_PWD_H) */ } tor_assert(home); /* Remove trailing slash. */ @@ -3138,7 +3138,7 @@ expand_filename(const char *filename) } else { return tor_strdup(filename); } -#endif +#endif /* defined(_WIN32) */ } #define MAX_SCANF_WIDTH 9999 @@ -3507,7 +3507,7 @@ tor_listdir, (const char *dirname)) name[sizeof(name)-1] = '\0'; #else strlcpy(name,findData.cFileName,sizeof(name)); -#endif +#endif /* defined(UNICODE) */ if (strcmp(name, ".") && strcmp(name, "..")) { smartlist_add_strdup(result, name); @@ -3524,7 +3524,7 @@ tor_listdir, (const char *dirname)) } FindClose(handle); tor_free(pattern); -#else +#else /* !(defined(_WIN32)) */ const char *prot_dname = sandbox_intern_string(dirname); DIR *d; struct dirent *de; @@ -3539,7 +3539,7 @@ tor_listdir, (const char *dirname)) smartlist_add_strdup(result, de->d_name); } closedir(d); -#endif +#endif /* defined(_WIN32) */ return result; } @@ -3555,7 +3555,7 @@ path_is_relative(const char *filename) else if (filename && strlen(filename)>3 && TOR_ISALPHA(filename[0]) && filename[1] == ':' && filename[2] == '\\') return 0; -#endif +#endif /* defined(_WIN32) */ else return 1; } @@ -3618,7 +3618,7 @@ start_daemon(void) } else { /* Child */ close(daemon_filedes[0]); /* we only write */ - pid = setsid(); /* Detach from controlling terminal */ + (void) setsid(); /* Detach from controlling terminal */ /* * Fork one more time, so the parent (the session group leader) can exit. * This means that we, as a non-session group leader, can never regain a @@ -3685,7 +3685,7 @@ finish_daemon(const char *desired_cwd) } close(daemon_filedes[1]); } -#else +#else /* !(!defined(_WIN32)) */ /* defined(_WIN32) */ void start_daemon(void) @@ -3696,11 +3696,12 @@ finish_daemon(const char *cp) { (void)cp; } -#endif +#endif /* !defined(_WIN32) */ /** Write the current process ID, followed by NL, into <b>filename</b>. + * Return 0 on success, -1 on failure. */ -void +int write_pidfile(const char *filename) { FILE *pidfile; @@ -3708,13 +3709,19 @@ write_pidfile(const char *filename) if ((pidfile = fopen(filename, "w")) == NULL) { log_warn(LD_FS, "Unable to open \"%s\" for writing: %s", filename, strerror(errno)); + return -1; } else { #ifdef _WIN32 - fprintf(pidfile, "%d\n", (int)_getpid()); + int pid = (int)_getpid(); #else - fprintf(pidfile, "%d\n", (int)getpid()); + int pid = (int)getpid(); #endif - fclose(pidfile); + int rv = 0; + if (fprintf(pidfile, "%d\n", pid) < 0) + rv = -1; + if (fclose(pidfile) < 0) + rv = -1; + return rv; } } @@ -3731,7 +3738,7 @@ load_windows_system_library(const TCHAR *library_name) _tcscat(path, library_name); return LoadLibrary(path); } -#endif +#endif /* defined(_WIN32) */ /** Format a single argument for being put on a Windows command line. * Returns a newly allocated string */ @@ -4026,7 +4033,7 @@ format_helper_exit_status(unsigned char child_state, int saved_errno, done: return res; } -#endif +#endif /* !defined(_WIN32) */ /* Maximum number of file descriptors, if we cannot get it via sysconf() */ #define DEFAULT_MAX_FD 256 @@ -4050,12 +4057,12 @@ tor_terminate_process(process_handle_t *process_handle) else return 0; } -#else /* Unix */ +#else /* !(defined(_WIN32)) */ if (process_handle->waitpid_cb) { /* We haven't got a waitpid yet, so we can just kill off the process. */ return kill(process_handle->pid, SIGTERM); } -#endif +#endif /* defined(_WIN32) */ return 0; /* We didn't need to kill the process, so report success */ } @@ -4077,14 +4084,14 @@ tor_process_get_stdout_pipe(process_handle_t *process_handle) { return process_handle->stdout_pipe; } -#else +#else /* !(defined(_WIN32)) */ /* DOCDOC tor_process_get_stdout_pipe */ int tor_process_get_stdout_pipe(process_handle_t *process_handle) { return process_handle->stdout_pipe; } -#endif +#endif /* defined(_WIN32) */ /* DOCDOC process_handle_new */ static process_handle_t * @@ -4100,7 +4107,7 @@ process_handle_new(void) out->stdin_pipe = -1; out->stdout_pipe = -1; out->stderr_pipe = -1; -#endif +#endif /* defined(_WIN32) */ return out; } @@ -4120,7 +4127,7 @@ process_handle_waitpid_cb(int status, void *arg) process_handle->status = PROCESS_STATUS_NOTRUNNING; process_handle->waitpid_cb = 0; } -#endif +#endif /* !defined(_WIN32) */ /** * @name child-process states @@ -4142,6 +4149,20 @@ process_handle_waitpid_cb(int status, void *arg) #define CHILD_STATE_EXEC 8 #define CHILD_STATE_FAILEXEC 9 /** @} */ +/** + * Boolean. If true, then Tor may call execve or CreateProcess via + * tor_spawn_background. + **/ +static int may_spawn_background_process = 1; +/** + * Turn off may_spawn_background_process, so that all future calls to + * tor_spawn_background are guaranteed to fail. + **/ +void +tor_disable_spawning_background_processes(void) +{ + may_spawn_background_process = 0; +} /** Start a program in the background. If <b>filename</b> contains a '/', then * it will be treated as an absolute or relative path. Otherwise, on * non-Windows systems, the system path will be searched for <b>filename</b>. @@ -4166,6 +4187,12 @@ tor_spawn_background(const char *const filename, const char **argv, process_environment_t *env, process_handle_t **process_handle_out) { + if (BUG(may_spawn_background_process == 0)) { + /* We should never reach this point if we're forbidden to spawn + * processes. Instead we should have caught the attempt earlier. */ + return PROCESS_STATUS_ERROR; + } + #ifdef _WIN32 HANDLE stdout_pipe_read = NULL; HANDLE stdout_pipe_write = NULL; @@ -4283,13 +4310,12 @@ tor_spawn_background(const char *const filename, const char **argv, /* TODO: Close pipes on exit */ *process_handle_out = process_handle; return status; -#else // _WIN32 +#else /* !(defined(_WIN32)) */ pid_t pid; int stdout_pipe[2]; int stderr_pipe[2]; int stdin_pipe[2]; int fd, retval; - ssize_t nbytes; process_handle_t *process_handle; int status; @@ -4310,7 +4336,7 @@ tor_spawn_background(const char *const filename, const char **argv, and we are not allowed to use unsafe functions between fork and exec */ error_message_length = strlen(error_message); - child_state = CHILD_STATE_PIPE; + // child_state = CHILD_STATE_PIPE; /* Set up pipe for redirecting stdout, stderr, and stdin of child */ retval = pipe(stdout_pipe); @@ -4347,7 +4373,7 @@ tor_spawn_background(const char *const filename, const char **argv, return status; } - child_state = CHILD_STATE_MAXFD; + // child_state = CHILD_STATE_MAXFD; #ifdef _SC_OPEN_MAX if (-1 == max_fd) { @@ -4358,11 +4384,11 @@ tor_spawn_background(const char *const filename, const char **argv, "Cannot find maximum file descriptor, assuming %d", max_fd); } } -#else +#else /* !(defined(_SC_OPEN_MAX)) */ max_fd = DEFAULT_MAX_FD; -#endif +#endif /* defined(_SC_OPEN_MAX) */ - child_state = CHILD_STATE_FORK; + // child_state = CHILD_STATE_FORK; pid = fork(); if (0 == pid) { @@ -4375,7 +4401,7 @@ tor_spawn_background(const char *const filename, const char **argv, * than nothing. */ prctl(PR_SET_PDEATHSIG, SIGTERM); -#endif +#endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) */ child_state = CHILD_STATE_DUPOUT; @@ -4398,7 +4424,7 @@ tor_spawn_background(const char *const filename, const char **argv, if (-1 == retval) goto error; - child_state = CHILD_STATE_CLOSEFD; + // child_state = CHILD_STATE_CLOSEFD; close(stderr_pipe[0]); close(stderr_pipe[1]); @@ -4414,7 +4440,7 @@ tor_spawn_background(const char *const filename, const char **argv, close(fd); } - child_state = CHILD_STATE_EXEC; + // child_state = CHILD_STATE_EXEC; /* Call the requested program. We need the cast because execvp doesn't define argv as const, even though it @@ -4433,7 +4459,8 @@ tor_spawn_background(const char *const filename, const char **argv, error: { /* XXX: are we leaking fds from the pipe? */ - int n; + int n, err=0; + ssize_t nbytes; n = format_helper_exit_status(child_state, errno, hex_errno); @@ -4442,13 +4469,14 @@ tor_spawn_background(const char *const filename, const char **argv, value, but there is nothing we can do if it fails */ /* TODO: Don't use STDOUT, use a pipe set up just for this purpose */ nbytes = write(STDOUT_FILENO, error_message, error_message_length); + err = (nbytes < 0); nbytes = write(STDOUT_FILENO, hex_errno, n); + err += (nbytes < 0); } - } - (void) nbytes; + _exit(err?254:255); + } - _exit(255); /* Never reached, but avoids compiler warning */ return status; // LCOV_EXCL_LINE } @@ -4515,8 +4543,8 @@ tor_spawn_background(const char *const filename, const char **argv, } *process_handle_out = process_handle; - return process_handle->status; -#endif // _WIN32 + return status; +#endif /* defined(_WIN32) */ } /** Destroy all resources allocated by the process handle in @@ -4558,13 +4586,13 @@ tor_process_handle_destroy,(process_handle_t *process_handle, if (process_handle->stdin_pipe) CloseHandle(process_handle->stdin_pipe); -#else +#else /* !(defined(_WIN32)) */ close(process_handle->stdout_pipe); close(process_handle->stderr_pipe); close(process_handle->stdin_pipe); clear_waitpid_callback(process_handle->waitpid_cb); -#endif +#endif /* defined(_WIN32) */ memset(process_handle, 0x0f, sizeof(process_handle_t)); tor_free(process_handle); @@ -4617,7 +4645,7 @@ tor_get_exit_code(process_handle_t *process_handle, return PROCESS_EXIT_ERROR; } } -#else +#else /* !(defined(_WIN32)) */ int stat_loc; int retval; @@ -4652,7 +4680,7 @@ tor_get_exit_code(process_handle_t *process_handle, if (exit_code != NULL) *exit_code = WEXITSTATUS(stat_loc); -#endif // _WIN32 +#endif /* defined(_WIN32) */ return PROCESS_EXIT_EXITED; } @@ -4892,7 +4920,7 @@ tor_read_all_handle(HANDLE h, char *buf, size_t count, } return (ssize_t)numread; } -#else +#else /* !(defined(_WIN32)) */ /** Read from a handle <b>fd</b> into <b>buf</b>, up to <b>count</b> bytes. If * <b>process</b> is NULL, the function will return immediately if there is * nothing more to read. Otherwise data will be read until end of file, or @@ -4937,7 +4965,7 @@ tor_read_all_handle(int fd, char *buf, size_t count, log_debug(LD_GENERAL, "read() read %d bytes from handle", (int)numread); return (ssize_t)numread; } -#endif +#endif /* defined(_WIN32) */ /** Read from stdout of a process until the process exits. */ ssize_t @@ -4950,7 +4978,7 @@ tor_read_all_from_process_stdout(const process_handle_t *process_handle, #else return tor_read_all_handle(process_handle->stdout_pipe, buf, count, process_handle, NULL); -#endif +#endif /* defined(_WIN32) */ } /** Read from stdout of a process until the process exits. */ @@ -4964,7 +4992,7 @@ tor_read_all_from_process_stderr(const process_handle_t *process_handle, #else return tor_read_all_handle(process_handle->stderr_pipe, buf, count, process_handle, NULL); -#endif +#endif /* defined(_WIN32) */ } /** Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be @@ -5153,7 +5181,7 @@ log_from_handle(HANDLE *pipe, int severity) return 0; } -#else +#else /* !(defined(_WIN32)) */ /** Return a smartlist containing lines outputted from * <b>fd</b>. Return NULL on error, and set @@ -5218,7 +5246,7 @@ log_from_pipe(int fd, int severity, const char *executable, /* We should never get here */ return -1; } -#endif +#endif /* defined(_WIN32) */ /** Reads from <b>fd</b> and stores input in <b>buf_out</b> making * sure it's below <b>count</b> bytes. @@ -5470,7 +5498,7 @@ tor_check_port_forwarding(const char *filename, status = tor_spawn_background(NULL, argv, NULL, &child_handle); #else status = tor_spawn_background(filename, argv, NULL, &child_handle); -#endif +#endif /* defined(_WIN32) */ tor_free_((void*)argv); argv=NULL; @@ -5496,7 +5524,7 @@ tor_check_port_forwarding(const char *filename, #else stderr_status = log_from_pipe(child_handle->stderr_pipe, LOG_INFO, filename, &retval); -#endif +#endif /* defined(_WIN32) */ if (handle_fw_helper_output(filename, child_handle) < 0) { log_warn(LD_GENERAL, "Failed to handle fw helper output."); stdout_status = -1; @@ -5521,13 +5549,13 @@ tor_check_port_forwarding(const char *filename, * between log_from_handle and tor_get_exit_code? */ retval = 1; } -#else +#else /* !(defined(_WIN32)) */ else if (1 == stdout_status || 1 == stderr_status) /* stdout or stderr was closed, the process probably * exited. It will be reaped by waitpid() in main.c */ /* TODO: Do something with the process return value */ retval = 1; -#endif +#endif /* defined(_WIN32) */ else /* Both are fine */ retval = 0; @@ -5598,7 +5626,7 @@ clamp_double_to_int64(double number) { int exponent; -#if (defined(__MINGW32__) || defined(__MINGW64__)) && GCC_VERSION >= 409 +#if defined(MINGW_ANY) && GCC_VERSION >= 409 /* Mingw's math.h uses gcc's __builtin_choose_expr() facility to declare isnan, isfinite, and signbit. But as implemented in at least some @@ -5607,7 +5635,7 @@ clamp_double_to_int64(double number) */ #define PROBLEMATIC_FLOAT_CONVERSION_WARNING DISABLE_GCC_WARNING(float-conversion) -#endif +#endif /* defined(MINGW_ANY) && GCC_VERSION >= 409 */ /* With clang 4.0 we apparently run into "double promotion" warnings here, @@ -5618,7 +5646,7 @@ DISABLE_GCC_WARNING(float-conversion) #define PROBLEMATIC_DOUBLE_PROMOTION_WARNING DISABLE_GCC_WARNING(double-promotion) #endif -#endif +#endif /* defined(__clang__) */ /* NaN is a special case that can't be used with the logic below. */ if (isnan(number)) { @@ -5665,7 +5693,7 @@ tor_htonll(uint64_t a) /* Little endian. The worst... */ return htonl((uint32_t)(a>>32)) | (((uint64_t)htonl((uint32_t)a))<<32); -#endif /* WORDS_BIGENDIAN */ +#endif /* defined(WORDS_BIGENDIAN) */ } /** Return a uint64_t value from <b>a</b> in host byte order. */ diff --git a/src/common/util.h b/src/common/util.h index d56abcee2e..6bc853da26 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -45,7 +45,7 @@ #else #define DMALLOC_PARAMS #define DMALLOC_ARGS -#endif +#endif /* defined(USE_DMALLOC) */ /* Memory management */ void *tor_malloc_(size_t size DMALLOC_PARAMS) ATTR_MALLOC; @@ -72,7 +72,7 @@ extern int dmalloc_free(const char *file, const int line, void *pnt, (p)=NULL; \ } \ STMT_END -#else +#else /* !(defined(USE_DMALLOC)) */ /** Release memory allocated by tor_malloc, tor_realloc, tor_strdup, etc. * Unlike the free() function, tor_free() will still work on NULL pointers, * and it sets the pointer value to NULL after freeing it. @@ -86,7 +86,7 @@ extern int dmalloc_free(const char *file, const int line, void *pnt, (p)=NULL; \ } \ STMT_END -#endif +#endif /* defined(USE_DMALLOC) */ #define tor_malloc(size) tor_malloc_(size DMALLOC_ARGS) #define tor_malloc_zero(size) tor_malloc_zero_(size DMALLOC_ARGS) @@ -109,19 +109,11 @@ extern int dmalloc_free(const char *file, const int line, void *pnt, void tor_log_mallinfo(int severity); -/** Return the offset of <b>member</b> within the type <b>tp</b>, in bytes */ -#if defined(__GNUC__) && __GNUC__ > 3 -#define STRUCT_OFFSET(tp, member) __builtin_offsetof(tp, member) -#else - #define STRUCT_OFFSET(tp, member) \ - ((off_t) (((char*)&((tp*)0)->member)-(char*)0)) -#endif - /** Macro: yield a pointer to the field at position <b>off</b> within the * structure <b>st</b>. Example: * <pre> * struct a { int foo; int bar; } x; - * off_t bar_offset = STRUCT_OFFSET(struct a, bar); + * off_t bar_offset = offsetof(struct a, bar); * int *bar_p = STRUCT_VAR_P(&x, bar_offset); * *bar_p = 3; * </pre> @@ -138,7 +130,7 @@ void tor_log_mallinfo(int severity); * </pre> */ #define SUBTYPE_P(p, subtype, basemember) \ - ((void*) ( ((char*)(p)) - STRUCT_OFFSET(subtype, basemember) )) + ((void*) ( ((char*)(p)) - offsetof(subtype, basemember) )) /* Logic */ /** Macro: true if two values have the same boolean value. */ @@ -269,7 +261,7 @@ int format_time_interval(char *out, size_t out_len, long interval); #else time_t approx_time(void); void update_approx_time(time_t now); -#endif +#endif /* defined(TIME_IS_FAST) */ /* Rate-limiter */ @@ -397,13 +389,15 @@ int path_is_relative(const char *filename); /* Process helpers */ void start_daemon(void); void finish_daemon(const char *desired_cwd); -void write_pidfile(const char *filename); +int write_pidfile(const char *filename); /* Port forwarding */ void tor_check_port_forwarding(const char *filename, struct smartlist_t *ports_to_forward, time_t now); +void tor_disable_spawning_background_processes(void); + typedef struct process_handle_t process_handle_t; typedef struct process_environment_t process_environment_t; int tor_spawn_background(const char *const filename, const char **argv, @@ -457,7 +451,7 @@ struct process_handle_t { HANDLE stdout_pipe; HANDLE stderr_pipe; PROCESS_INFORMATION pid; -#else +#else /* !(defined(_WIN32)) */ int stdin_pipe; int stdout_pipe; int stderr_pipe; @@ -468,9 +462,9 @@ struct process_handle_t { struct waitpid_callback_t *waitpid_cb; /** The exit status reported by waitpid. */ int waitpid_exit_status; -#endif // _WIN32 +#endif /* defined(_WIN32) */ }; -#endif +#endif /* defined(UTIL_PRIVATE) */ /* Return values of tor_get_exit_code() */ #define PROCESS_EXIT_RUNNING 1 @@ -486,7 +480,7 @@ ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count, ssize_t tor_read_all_handle(int fd, char *buf, size_t count, const process_handle_t *process, int *eof); -#endif +#endif /* defined(_WIN32) */ ssize_t tor_read_all_from_process_stdout( const process_handle_t *process_handle, char *buf, size_t count); ssize_t tor_read_all_from_process_stderr( @@ -508,7 +502,7 @@ tor_get_lines_from_handle,(HANDLE *handle, MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(int fd, enum stream_status *stream_status)); -#endif +#endif /* defined(_WIN32) */ int tor_terminate_process(process_handle_t *process_handle); @@ -545,13 +539,13 @@ STATIC int format_helper_exit_status(unsigned char child_state, leading minus) and newline (no null) */ #define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \ 1 + sizeof(int) * 2 + 1) -#endif +#endif /* !defined(_WIN32) */ -#endif +#endif /* defined(UTIL_PRIVATE) */ int size_mul_check(const size_t x, const size_t y); #define ARRAY_LENGTH(x) ((sizeof(x)) / sizeof(x[0])) -#endif +#endif /* !defined(TOR_UTIL_H) */ diff --git a/src/common/util_bug.c b/src/common/util_bug.c index 3d990e3700..126e843866 100644 --- a/src/common/util_bug.c +++ b/src/common/util_bug.c @@ -13,6 +13,10 @@ #include "backtrace.h" #include "container.h" +#ifdef __COVERITY__ +int bug_macro_deadcode_dummy__ = 0; +#endif + #ifdef TOR_UNIT_TESTS static void (*failed_assertion_cb)(void) = NULL; static int n_bugs_to_capture = 0; @@ -55,10 +59,10 @@ tor_set_failed_assertion_callback(void (*fn)(void)) { failed_assertion_cb = fn; } -#else +#else /* !(defined(TOR_UNIT_TESTS)) */ #define capturing_bugs() (0) #define add_captured_bug(s) do { } while (0) -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Helper for tor_assert: report the assertion failure. */ void diff --git a/src/common/util_bug.h b/src/common/util_bug.h index ae7e7a37fd..be549fde07 100644 --- a/src/common/util_bug.h +++ b/src/common/util_bug.h @@ -5,6 +5,32 @@ /** * \file util_bug.h + * + * \brief Macros to manage assertions, fatal and non-fatal. + * + * Guidelines: All the different kinds of assertion in this file are for + * bug-checking only. Don't write code that can assert based on bad inputs. + * + * We provide two kinds of assertion here: "fatal" and "nonfatal". Use + * nonfatal assertions for any bug you can reasonably recover from -- and + * please, try to recover! Many severe bugs in Tor have been caused by using + * a regular assertion when a nonfatal assertion would have been better. + * + * If you need to check a condition with a nonfatal assertion, AND recover + * from that same condition, consider using the BUG() macro inside a + * conditional. For example: + * + * <code> + * // wrong -- use tor_assert_nonfatal() if you just want an assertion. + * BUG(ptr == NULL); + * + * // okay, but needlessly verbose + * tor_assert_nonfatal(ptr != NULL); + * if (ptr == NULL) { ... } + * + * // this is how we do it: + * if (BUG(ptr == NULL)) { ... } + * </code> **/ #ifndef TOR_UTIL_BUG_H @@ -27,7 +53,7 @@ * security-critical properties. */ #error "Sorry; we don't support building with NDEBUG." -#endif +#endif /* defined(NDEBUG) */ /* Sometimes we don't want to use assertions during branch coverage tests; it * leads to tons of unreached branches which in reality are only assertions we @@ -44,7 +70,7 @@ tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr); \ abort(); \ } STMT_END -#endif +#endif /* defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) */ #define tor_assert_unreached() tor_assert(0) @@ -59,11 +85,14 @@ */ #ifdef __COVERITY__ +extern int bug_macro_deadcode_dummy__; #undef BUG // Coverity defines this in global headers; let's override it. This is a // magic coverity-only preprocessor thing. -#nodef BUG(x) ((x)?(__coverity_panic__(),1):0) -#endif +// We use this "deadcode_dummy__" trick to prevent coverity from +// complaining about unreachable bug cases. +#nodef BUG(x) ((x)?(__coverity_panic__(),1):(0+bug_macro_deadcode_dummy__)) +#endif /* defined(__COVERITY__) */ #if defined(__COVERITY__) || defined(__clang_analyzer__) // We're running with a static analysis tool: let's treat even nonfatal @@ -114,7 +143,7 @@ (PREDICT_UNLIKELY(cond) ? \ (tor_bug_occurred_(SHORT_FILE__,__LINE__,__func__,"!("#cond")",0), 1) \ : 0) -#endif +#endif /* defined(ALL_BUGS_ARE_FATAL) || ... */ #ifdef __GNUC__ #define IF_BUG_ONCE__(cond,var) \ @@ -127,7 +156,7 @@ "!("#cond")", 1); \ } \ PREDICT_UNLIKELY(bool_result); } )) -#else +#else /* !(defined(__GNUC__)) */ #define IF_BUG_ONCE__(cond,var) \ static int var = 0; \ if (PREDICT_UNLIKELY(cond) ? \ @@ -137,7 +166,7 @@ "!("#cond")", 1), \ 1)) \ : 0) -#endif +#endif /* defined(__GNUC__) */ #define IF_BUG_ONCE_VARNAME_(a) \ warning_logged_on_ ## a ## __ #define IF_BUG_ONCE_VARNAME__(a) \ @@ -167,7 +196,7 @@ void tor_capture_bugs_(int n); void tor_end_capture_bugs_(void); const struct smartlist_t *tor_get_captured_bug_log_(void); void tor_set_failed_assertion_callback(void (*fn)(void)); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ -#endif +#endif /* !defined(TOR_UTIL_BUG_H) */ diff --git a/src/common/util_format.c b/src/common/util_format.c index 1f7b8b03aa..e51757a4e8 100644 --- a/src/common/util_format.c +++ b/src/common/util_format.c @@ -266,10 +266,13 @@ base64_encode(char *dest, size_t destlen, const char *src, size_t srclen, ENCODE_N(3); ENCODE_PAD(); break; + // LCOV_EXCL_START -- we can't reach this point, because we enforce + // 0 <= ncov_idx < 3 in the loop above. default: /* Something went catastrophically wrong. */ - tor_fragile_assert(); // LCOV_EXCL_LINE + tor_fragile_assert(); return -1; + // LCOV_EXCL_STOP } #undef ENCODE_N diff --git a/src/common/util_format.h b/src/common/util_format.h index 4af8832bbe..0aefe3a44e 100644 --- a/src/common/util_format.h +++ b/src/common/util_format.h @@ -48,5 +48,5 @@ int hex_decode_digit(char c); void base16_encode(char *dest, size_t destlen, const char *src, size_t srclen); int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen); -#endif +#endif /* !defined(TOR_UTIL_FORMAT_H) */ diff --git a/src/common/util_process.c b/src/common/util_process.c index 9e9679b099..c2826152e9 100644 --- a/src/common/util_process.c +++ b/src/common/util_process.c @@ -154,5 +154,5 @@ notify_pending_waitpid_callbacks(void) } } -#endif +#endif /* !defined(_WIN32) */ diff --git a/src/common/util_process.h b/src/common/util_process.h index c3a63498b5..c9aa771b77 100644 --- a/src/common/util_process.h +++ b/src/common/util_process.h @@ -20,7 +20,7 @@ waitpid_callback_t *set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg); void clear_waitpid_callback(waitpid_callback_t *ent); void notify_pending_waitpid_callbacks(void); -#endif +#endif /* !defined(_WIN32) */ -#endif +#endif /* !defined(TOR_UTIL_PROCESS_H) */ diff --git a/src/common/workqueue.h b/src/common/workqueue.h index d2508f5329..eb885e680d 100644 --- a/src/common/workqueue.h +++ b/src/common/workqueue.h @@ -59,5 +59,5 @@ replyqueue_t *replyqueue_new(uint32_t alertsocks_flags); tor_socket_t replyqueue_get_socket(replyqueue_t *rq); void replyqueue_process(replyqueue_t *queue); -#endif +#endif /* !defined(TOR_WORKQUEUE_H) */ diff --git a/src/ext/ed25519/donna/ed25519_donna_tor.h b/src/ext/ed25519/donna/ed25519_donna_tor.h index d225407b1c..7d7b8c0625 100644 --- a/src/ext/ed25519/donna/ed25519_donna_tor.h +++ b/src/ext/ed25519/donna/ed25519_donna_tor.h @@ -30,4 +30,9 @@ int ed25519_donna_blind_public_key(unsigned char *out, const unsigned char *inp, int ed25519_donna_pubkey_from_curve25519_pubkey(unsigned char *out, const unsigned char *inp, int signbit); + +int +ed25519_donna_scalarmult_with_group_order(unsigned char *out, + const unsigned char *pubkey); + #endif diff --git a/src/ext/ed25519/donna/ed25519_tor.c b/src/ext/ed25519/donna/ed25519_tor.c index 9537ae66a1..44ec562f02 100644 --- a/src/ext/ed25519/donna/ed25519_tor.c +++ b/src/ext/ed25519/donna/ed25519_tor.c @@ -245,13 +245,7 @@ ed25519_donna_sign(unsigned char *sig, const unsigned char *m, size_t mlen, static void ed25519_donna_gettweak(unsigned char *out, const unsigned char *param) { - static const char str[] = "Derive temporary signing key"; - ed25519_hash_context ctx; - - ed25519_hash_init(&ctx); - ed25519_hash_update(&ctx, (const unsigned char*)str, strlen(str)); - ed25519_hash_update(&ctx, param, 32); - ed25519_hash_final(&ctx, out); + memcpy(out, param, 32); out[0] &= 248; /* Is this necessary ? */ out[31] &= 63; @@ -304,7 +298,9 @@ ed25519_donna_blind_public_key(unsigned char *out, const unsigned char *inp, /* No "ge25519_unpack", negate the public key. */ memcpy(pkcopy, inp, 32); pkcopy[31] ^= (1<<7); - ge25519_unpack_negative_vartime(&A, pkcopy); + if (!ge25519_unpack_negative_vartime(&A, pkcopy)) { + return -1; + } /* A' = [tweak] * A + [0] * basepoint. */ ge25519_double_scalarmult_vartime(&Aprime, &A, t, zero); @@ -340,5 +336,32 @@ ed25519_donna_pubkey_from_curve25519_pubkey(unsigned char *out, return 0; } +/* Do the scalar multiplication of <b>pubkey</b> with the group order + * <b>modm_m</b>. Place the result in <b>out</b> which must be at least 32 + * bytes long. */ +int +ed25519_donna_scalarmult_with_group_order(unsigned char *out, + const unsigned char *pubkey) +{ + static const bignum256modm ALIGN(16) zero = { 0 }; + unsigned char pkcopy[32]; + ge25519 ALIGN(16) Point, Result; + + /* No "ge25519_unpack", negate the public key and unpack it back. + * See ed25519_donna_blind_public_key() */ + memcpy(pkcopy, pubkey, 32); + pkcopy[31] ^= (1<<7); + if (!ge25519_unpack_negative_vartime(&Point, pkcopy)) { + return -1; /* error: bail out */ + } + + /* There is no regular scalarmult function so we have to do: + * Result = l*P + 0*B */ + ge25519_double_scalarmult_vartime(&Result, &Point, modm_m, zero); + ge25519_pack(out, &Result); + + return 0; +} + #include "test-internals.c" diff --git a/src/ext/ed25519/ref10/blinding.c b/src/ext/ed25519/ref10/blinding.c index ee3e8666fa..a3b32fa80c 100644 --- a/src/ext/ed25519/ref10/blinding.c +++ b/src/ext/ed25519/ref10/blinding.c @@ -12,8 +12,8 @@ static void ed25519_ref10_gettweak(unsigned char *out, const unsigned char *param) { - const char str[] = "Derive temporary signing key"; - crypto_hash_sha512_2(out, (const unsigned char*)str, strlen(str), param, 32); + memcpy(out, param, 32); + out[0] &= 248; /* Is this necessary necessary ? */ out[31] &= 63; out[31] |= 64; @@ -49,6 +49,7 @@ int ed25519_ref10_blind_public_key(unsigned char *out, unsigned char pkcopy[32]; ge_p3 A; ge_p2 Aprime; + int retval = -1; ed25519_ref10_gettweak(tweak, param); @@ -62,15 +63,57 @@ int ed25519_ref10_blind_public_key(unsigned char *out, * "ge_frombytes", we'd use that, but there isn't. */ memcpy(pkcopy, inp, 32); pkcopy[31] ^= (1<<7); - ge_frombytes_negate_vartime(&A, pkcopy); + if (ge_frombytes_negate_vartime(&A, pkcopy) != 0) { + goto done; + } /* There isn't a regular ge_scalarmult -- we have to do tweak*A + zero*B. */ ge_double_scalarmult_vartime(&Aprime, tweak, &A, zero); ge_tobytes(out, &Aprime); + retval = 0; + + done: memwipe(tweak, 0, sizeof(tweak)); memwipe(&A, 0, sizeof(A)); memwipe(&Aprime, 0, sizeof(Aprime)); memwipe(pkcopy, 0, sizeof(pkcopy)); + return retval; +} + +/* This is the group order encoded in a format that + * ge_double_scalarmult_vartime() understands. The group order m is: + * m = 2^252 + 27742317777372353535851937790883648493 = + * 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed + */ +static const uint8_t modm_m[32] = {0xed,0xd3,0xf5,0x5c,0x1a,0x63,0x12,0x58, + 0xd6,0x9c,0xf7,0xa2,0xde,0xf9,0xde,0x14, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10}; + +/* Do the scalar multiplication of <b>pubkey</b> with the group order + * <b>modm_m</b>. Place the result in <b>out</b> which must be at least 32 + * bytes long. */ +int +ed25519_ref10_scalarmult_with_group_order(unsigned char *out, + const unsigned char *pubkey) +{ + unsigned char pkcopy[32]; + unsigned char zero[32] = {0}; + ge_p3 Point; + ge_p2 Result; + + /* All this is done to fit 'pubkey' in 'Point' so that it can be used by + * ed25519 ref code. Same thing as in blinding function */ + memcpy(pkcopy, pubkey, 32); + pkcopy[31] ^= (1<<7); + if (ge_frombytes_negate_vartime(&Point, pkcopy) != 0) { + return -1; /* error: bail out */ + } + + /* There isn't a regular scalarmult -- we have to do r = l*P + 0*B */ + ge_double_scalarmult_vartime(&Result, modm_m, &Point, zero); + ge_tobytes(out, &Result); + return 0; } diff --git a/src/ext/ed25519/ref10/ed25519_ref10.h b/src/ext/ed25519/ref10/ed25519_ref10.h index af7e21a2ad..5965694977 100644 --- a/src/ext/ed25519/ref10/ed25519_ref10.h +++ b/src/ext/ed25519/ref10/ed25519_ref10.h @@ -27,4 +27,8 @@ int ed25519_ref10_blind_public_key(unsigned char *out, const unsigned char *inp, const unsigned char *param); +int +ed25519_ref10_scalarmult_with_group_order(unsigned char *out, + const unsigned char *pubkey); + #endif diff --git a/src/ext/ht.h b/src/ext/ht.h index caa420e9b5..99da773faf 100644 --- a/src/ext/ht.h +++ b/src/ext/ht.h @@ -150,6 +150,8 @@ #define HT_CLEAR(name, head) name##_HT_CLEAR(head) #define HT_INIT(name, head) name##_HT_INIT(head) #define HT_REP_IS_BAD_(name, head) name##_HT_REP_IS_BAD_(head) +#define HT_FOREACH_FN(name, head, fn, data) \ + name##_HT_FOREACH_FN((head), (fn), (data)) /* Helper: */ static inline unsigned ht_improve_hash(unsigned h) diff --git a/src/ext/timeouts/timeout-bitops.c b/src/ext/timeouts/timeout-bitops.c index a018f33b95..e99af8f9c4 100644 --- a/src/ext/timeouts/timeout-bitops.c +++ b/src/ext/timeouts/timeout-bitops.c @@ -231,7 +231,8 @@ main(int c, char **v) int result = 0; for (i = 0; i <= 63; ++i) { - uint64_t x = 1 << i; + uint64_t x = 1; + x <<= i; if (!check(x)) result = 1; --x; diff --git a/src/ext/trunnel/trunnel-impl.h b/src/ext/trunnel/trunnel-impl.h index 85c847b3fd..b233cf7631 100644 --- a/src/ext/trunnel/trunnel-impl.h +++ b/src/ext/trunnel/trunnel-impl.h @@ -1,4 +1,4 @@ -/* trunnel-impl.h -- copied from Trunnel v1.5.1 +/* trunnel-impl.h -- copied from Trunnel v1.5.2 * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/ext/trunnel/trunnel.c b/src/ext/trunnel/trunnel.c index 6a42417241..b749d8136f 100644 --- a/src/ext/trunnel/trunnel.c +++ b/src/ext/trunnel/trunnel.c @@ -1,4 +1,4 @@ -/* trunnel.c -- copied from Trunnel v1.5.1 +/* trunnel.c -- copied from Trunnel v1.5.2 * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/ext/trunnel/trunnel.h b/src/ext/trunnel/trunnel.h index dd78553c77..32c80bac23 100644 --- a/src/ext/trunnel/trunnel.h +++ b/src/ext/trunnel/trunnel.h @@ -1,4 +1,4 @@ -/* trunnel.h -- copied from Trunnel v1.5.1 +/* trunnel.h -- copied from Trunnel v1.5.2 * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/or/addressmap.c b/src/or/addressmap.c index c92af38254..7e92633601 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -213,8 +213,8 @@ addressmap_clear_excluded_trackexithosts(const or_options_t *options) while (dot > target && *dot != '.') dot--; if (*dot == '.') dot++; - nodename = tor_strndup(dot, len-5-(dot-target));; - node = node_get_by_nickname(nodename, 0); + nodename = tor_strndup(dot, len-5-(dot-target)); + node = node_get_by_nickname(nodename, NNF_NO_WARN_UNNAMED); tor_free(nodename); if (!node || (allow_nodes && !routerset_contains_node(allow_nodes, node)) || @@ -814,7 +814,7 @@ parse_virtual_addr_network(const char *val, sa_family_t family, ipv6?"IPv6":""); return -1; } -#endif +#endif /* 0 */ if (bits > max_prefix_bits) { if (msg) @@ -1044,7 +1044,7 @@ addressmap_register_virtual_address(int type, char *new_address) safe_str_client(*addrp), safe_str_client(new_address)); } -#endif +#endif /* 0 */ return *addrp; } diff --git a/src/or/addressmap.h b/src/or/addressmap.h index 80f453b4c1..1544b76e10 100644 --- a/src/or/addressmap.h +++ b/src/or/addressmap.h @@ -59,7 +59,7 @@ typedef struct virtual_addr_conf_t { STATIC void get_random_virtual_addr(const virtual_addr_conf_t *conf, tor_addr_t *addr_out); -#endif +#endif /* defined(ADDRESSMAP_PRIVATE) */ -#endif +#endif /* !defined(TOR_ADDRESSMAP_H) */ diff --git a/src/or/bridges.c b/src/or/bridges.c index 0818fb0812..0b1bbbd158 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -54,6 +54,8 @@ struct bridge_info_t { }; static void bridge_free(bridge_info_t *bridge); +static void rewrite_node_address_for_bridge(const bridge_info_t *bridge, + node_t *node); /** A list of configured bridges. Whenever we actually get a descriptor * for one, we add it as an entry guard. Note that the order of bridges @@ -310,7 +312,7 @@ learned_router_identity(const tor_addr_t *addr, uint16_t port, memcpy(&bridge->ed25519_identity, ed_id, sizeof(*ed_id)); learned = 1; } -#endif +#endif /* 0 */ if (learned) { char *transport_info = NULL; const char *transport_name = @@ -454,6 +456,9 @@ bridge_add_from_config(bridge_line_t *bridge_line) b->transport_name = bridge_line->transport_name; b->fetch_status.schedule = DL_SCHED_BRIDGE; b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL; + b->fetch_status.increment_on = DL_SCHED_INCREMENT_ATTEMPT; + /* We can't reset the bridge's download status here, because UseBridges + * might be 0 now, and it might be changed to 1 much later. */ b->socks_args = bridge_line->socks_args; if (!bridge_list) bridge_list = smartlist_new(); @@ -571,6 +576,12 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; } + /* If we already have a node_t for this bridge, rewrite its address now. */ + node_t *node = node_get_mutable_by_id(bridge->identity); + if (node) { + rewrite_node_address_for_bridge(bridge, node); + } + tor_addr_port_t bridge_addrport; memcpy(&bridge_addrport.addr, &bridge->addr, sizeof(tor_addr_t)); bridge_addrport.port = bridge->port; @@ -622,6 +633,7 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { + /* This resets the download status on first use */ if (!download_status_is_ready(&bridge->fetch_status, now, IMPOSSIBLE_TO_DOWNLOAD)) continue; /* don't bother, no need to retry yet */ @@ -632,8 +644,13 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) continue; } - /* schedule another fetch as if this one will fail, in case it does */ - download_status_failed(&bridge->fetch_status, 0); + /* schedule the next attempt + * we can't increment after a failure, because sometimes we use the + * bridge authority, and sometimes we use the bridge direct */ + download_status_increment_attempt( + &bridge->fetch_status, + safe_str_client(fmt_and_decorate_addr(&bridge->addr)), + now); can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && num_bridge_auths; @@ -779,7 +796,11 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) tor_assert(ri); tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); if (get_options()->UseBridges) { - int first = num_bridges_usable() <= 1; + /* Retry directory downloads whenever we get a bridge descriptor: + * - when bootstrapping, and + * - when we aren't sure if any of our bridges are reachable. + * Keep on retrying until we have at least one reachable bridge. */ + int first = num_bridges_usable(0) < 1; bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); time_t now = time(NULL); router_set_status(ri->cache_info.identity_digest, 1); @@ -787,8 +808,12 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) if (bridge) { /* if we actually want to use this one */ node_t *node; /* it's here; schedule its re-fetch for a long time from now. */ - if (!from_cache) + if (!from_cache) { + /* This schedules the re-fetch at a constant interval, which produces + * a pattern of bridge traffic. But it's better than trying all + * configured briges several times in the first few minutes. */ download_status_reset(&bridge->fetch_status); + } node = node_get_mutable_by_id(ri->cache_info.identity_digest); tor_assert(node); @@ -805,8 +830,8 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); - /* set entry->made_contact so if it goes down we don't drop it from - * our entry node list */ + /* If we didn't have a reachable bridge before this one, try directory + * documents again. */ if (first) { routerlist_retry_directory_downloads(now); } @@ -814,32 +839,6 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) } } -/** Return the number of bridges that have descriptors that - * are marked with purpose 'bridge' and are running. - * - * We use this function to decide if we're ready to start building - * circuits through our bridges, or if we need to wait until the - * directory "server/authority" requests finish. */ -int -any_bridge_descriptors_known(void) -{ - tor_assert(get_options()->UseBridges); - - if (!bridge_list) - return 0; - - SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { - const node_t *node; - if (!tor_digest_is_zero(bridge->identity) && - (node = node_get_by_id(bridge->identity)) != NULL && - node->ri) { - return 1; - } - } SMARTLIST_FOREACH_END(bridge); - - return 0; -} - /** Return a smartlist containing all bridge identity digests */ MOCK_IMPL(smartlist_t *, list_bridge_identities, (void)) diff --git a/src/or/bridges.h b/src/or/bridges.h index 3bfc782f9a..54a6250259 100644 --- a/src/or/bridges.h +++ b/src/or/bridges.h @@ -45,7 +45,6 @@ void bridge_add_from_config(struct bridge_line_t *bridge_line); void retry_bridge_descriptor_fetch_directly(const char *digest); void fetch_bridge_descriptors(const or_options_t *options, time_t now); void learned_bridge_descriptor(routerinfo_t *ri, int from_cache); -int any_bridge_descriptors_known(void); const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port); @@ -66,5 +65,5 @@ MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id, void bridges_free_all(void); -#endif +#endif /* !defined(TOR_BRIDGES_H) */ diff --git a/src/or/buffers.c b/src/or/buffers.c deleted file mode 100644 index 12a6c0239b..0000000000 --- a/src/or/buffers.c +++ /dev/null @@ -1,2192 +0,0 @@ -/* Copyright (c) 2001 Matej Pfajfar. - * Copyright (c) 2001-2004, Roger Dingledine. - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.c - * \brief Implements a generic buffer interface. - * - * A buf_t is a (fairly) opaque byte-oriented FIFO that can read to or flush - * from memory, sockets, file descriptors, TLS connections, or another buf_t. - * Buffers are implemented as linked lists of memory chunks. - * - * All socket-backed and TLS-based connection_t objects have a pair of - * buffers: one for incoming data, and one for outcoming data. These are fed - * and drained from functions in connection.c, trigged by events that are - * monitored in main.c. - * - * This module has basic support for reading and writing on buf_t objects. It - * also contains specialized functions for handling particular protocols - * on a buf_t backend, including SOCKS (used in connection_edge.c), Tor cells - * (used in connection_or.c and channeltls.c), HTTP (used in directory.c), and - * line-oriented communication (used in control.c). - **/ -#define BUFFERS_PRIVATE -#include "or.h" -#include "addressmap.h" -#include "buffers.h" -#include "config.h" -#include "connection_edge.h" -#include "connection_or.h" -#include "control.h" -#include "reasons.h" -#include "ext_orport.h" -#include "util.h" -#include "torlog.h" -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -//#define PARANOIA - -#ifdef PARANOIA -/** Helper: If PARANOIA is defined, assert that the buffer in local variable - * <b>buf</b> is well-formed. */ -#define check() STMT_BEGIN assert_buf_ok(buf); STMT_END -#else -#define check() STMT_NIL -#endif - -/* Implementation notes: - * - * After flirting with memmove, and dallying with ring-buffers, we're finally - * getting up to speed with the 1970s and implementing buffers as a linked - * list of small chunks. Each buffer has such a list; data is removed from - * the head of the list, and added at the tail. The list is singly linked, - * and the buffer keeps a pointer to the head and the tail. - * - * Every chunk, except the tail, contains at least one byte of data. Data in - * each chunk is contiguous. - * - * When you need to treat the first N characters on a buffer as a contiguous - * string, use the buf_pullup function to make them so. Don't do this more - * than necessary. - * - * The major free Unix kernels have handled buffers like this since, like, - * forever. - */ - -static void socks_request_set_socks5_error(socks_request_t *req, - socks5_reply_status_t reason); - -static int parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out); -static int parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out); - -/* Chunk manipulation functions */ - -#define CHUNK_HEADER_LEN STRUCT_OFFSET(chunk_t, mem[0]) - -/* We leave this many NUL bytes at the end of the buffer. */ -#ifdef DISABLE_MEMORY_SENTINELS -#define SENTINEL_LEN 0 -#else -#define SENTINEL_LEN 4 -#endif - -/* Header size plus NUL bytes at the end */ -#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) - -/** Return the number of bytes needed to allocate a chunk to hold - * <b>memlen</b> bytes. */ -#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen)) -/** Return the number of usable bytes in a chunk allocated with - * malloc(<b>memlen</b>). */ -#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD) - -#define DEBUG_SENTINEL - -#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS) -#define DBG_S(s) s -#else -#define DBG_S(s) (void)0 -#endif - -#ifdef DISABLE_MEMORY_SENTINELS -#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL -#else -#define CHUNK_SET_SENTINEL(chunk, alloclen) do { \ - uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \ - DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \ - DBG_S(tor_assert(a == b)); \ - memset(a,0,SENTINEL_LEN); \ - } while (0) -#endif - -/** Return the next character in <b>chunk</b> onto which data can be appended. - * If the chunk is full, this might be off the end of chunk->mem. */ -static inline char * -CHUNK_WRITE_PTR(chunk_t *chunk) -{ - return chunk->data + chunk->datalen; -} - -/** Return the number of bytes that can be written onto <b>chunk</b> without - * running out of space. */ -static inline size_t -CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) -{ - return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); -} - -/** Move all bytes stored in <b>chunk</b> to the front of <b>chunk</b>->mem, - * to free up space at the end. */ -static inline void -chunk_repack(chunk_t *chunk) -{ - if (chunk->datalen && chunk->data != &chunk->mem[0]) { - memmove(chunk->mem, chunk->data, chunk->datalen); - } - chunk->data = &chunk->mem[0]; -} - -/** Keep track of total size of allocated chunks for consistency asserts */ -static size_t total_bytes_allocated_in_chunks = 0; -static void -buf_chunk_free_unchecked(chunk_t *chunk) -{ - if (!chunk) - return; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(CHUNK_ALLOC_SIZE(chunk->memlen) == chunk->DBG_alloc); -#endif - tor_assert(total_bytes_allocated_in_chunks >= - CHUNK_ALLOC_SIZE(chunk->memlen)); - total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); - tor_free(chunk); -} -static inline chunk_t * -chunk_new_with_alloc_size(size_t alloc) -{ - chunk_t *ch; - ch = tor_malloc(alloc); - ch->next = NULL; - ch->datalen = 0; -#ifdef DEBUG_CHUNK_ALLOC - ch->DBG_alloc = alloc; -#endif - ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); - total_bytes_allocated_in_chunks += alloc; - ch->data = &ch->mem[0]; - CHUNK_SET_SENTINEL(ch, alloc); - return ch; -} - -/** Expand <b>chunk</b> until it can hold <b>sz</b> bytes, and return a - * new pointer to <b>chunk</b>. Old pointers are no longer valid. */ -static inline chunk_t * -chunk_grow(chunk_t *chunk, size_t sz) -{ - off_t offset; - const size_t memlen_orig = chunk->memlen; - const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig); - const size_t new_alloc = CHUNK_ALLOC_SIZE(sz); - tor_assert(sz > chunk->memlen); - offset = chunk->data - chunk->mem; - chunk = tor_realloc(chunk, new_alloc); - chunk->memlen = sz; - chunk->data = chunk->mem + offset; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(chunk->DBG_alloc == orig_alloc); - chunk->DBG_alloc = new_alloc; -#endif - total_bytes_allocated_in_chunks += new_alloc - orig_alloc; - CHUNK_SET_SENTINEL(chunk, new_alloc); - return chunk; -} - -/** If a read onto the end of a chunk would be smaller than this number, then - * just start a new chunk. */ -#define MIN_READ_LEN 8 -/** Every chunk should take up at least this many bytes. */ -#define MIN_CHUNK_ALLOC 256 -/** No chunk should take up more than this many bytes. */ -#define MAX_CHUNK_ALLOC 65536 - -/** Return the allocation size we'd like to use to hold <b>target</b> - * bytes. */ -STATIC size_t -preferred_chunk_size(size_t target) -{ - tor_assert(target <= SIZE_T_CEILING - CHUNK_OVERHEAD); - if (CHUNK_ALLOC_SIZE(target) >= MAX_CHUNK_ALLOC) - return CHUNK_ALLOC_SIZE(target); - size_t sz = MIN_CHUNK_ALLOC; - while (CHUNK_SIZE_WITH_ALLOC(sz) < target) { - sz <<= 1; - } - return sz; -} - -/** Collapse data from the first N chunks from <b>buf</b> into buf->head, - * growing it as necessary, until buf->head has the first <b>bytes</b> bytes - * of data from the buffer, or until buf->head has all the data in <b>buf</b>. - */ -STATIC void -buf_pullup(buf_t *buf, size_t bytes) -{ - chunk_t *dest, *src; - size_t capacity; - if (!buf->head) - return; - - check(); - if (buf->datalen < bytes) - bytes = buf->datalen; - - capacity = bytes; - if (buf->head->datalen >= bytes) - return; - - if (buf->head->memlen >= capacity) { - /* We don't need to grow the first chunk, but we might need to repack it.*/ - size_t needed = capacity - buf->head->datalen; - if (CHUNK_REMAINING_CAPACITY(buf->head) < needed) - chunk_repack(buf->head); - tor_assert(CHUNK_REMAINING_CAPACITY(buf->head) >= needed); - } else { - chunk_t *newhead; - size_t newsize; - /* We need to grow the chunk. */ - chunk_repack(buf->head); - newsize = CHUNK_SIZE_WITH_ALLOC(preferred_chunk_size(capacity)); - newhead = chunk_grow(buf->head, newsize); - tor_assert(newhead->memlen >= capacity); - if (newhead != buf->head) { - if (buf->tail == buf->head) - buf->tail = newhead; - buf->head = newhead; - } - } - - dest = buf->head; - while (dest->datalen < bytes) { - size_t n = bytes - dest->datalen; - src = dest->next; - tor_assert(src); - if (n >= src->datalen) { - memcpy(CHUNK_WRITE_PTR(dest), src->data, src->datalen); - dest->datalen += src->datalen; - dest->next = src->next; - if (buf->tail == src) - buf->tail = dest; - buf_chunk_free_unchecked(src); - } else { - memcpy(CHUNK_WRITE_PTR(dest), src->data, n); - dest->datalen += n; - src->data += n; - src->datalen -= n; - tor_assert(dest->datalen == bytes); - } - } - - check(); -} - -#ifdef TOR_UNIT_TESTS -/* Return the data from the first chunk of buf in cp, and its length in sz. */ -void -buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz) -{ - if (!buf || !buf->head) { - *cp = NULL; - *sz = 0; - } else { - *cp = buf->head->data; - *sz = buf->head->datalen; - } -} - -/* Write sz bytes from cp into a newly allocated buffer buf. - * Returns NULL when passed a NULL cp or zero sz. - * Asserts on failure: only for use in unit tests. - * buf must be freed using buf_free(). */ -buf_t * -buf_new_with_data(const char *cp, size_t sz) -{ - /* Validate arguments */ - if (!cp || sz <= 0) { - return NULL; - } - - tor_assert(sz < SSIZE_T_CEILING); - - /* Allocate a buffer */ - buf_t *buf = buf_new_with_capacity(sz); - tor_assert(buf); - assert_buf_ok(buf); - tor_assert(!buf->head); - - /* Allocate a chunk that is sz bytes long */ - buf->head = chunk_new_with_alloc_size(CHUNK_ALLOC_SIZE(sz)); - buf->tail = buf->head; - tor_assert(buf->head); - assert_buf_ok(buf); - tor_assert(buf_allocation(buf) >= sz); - - /* Copy the data and size the buffers */ - tor_assert(sz <= buf_slack(buf)); - tor_assert(sz <= CHUNK_REMAINING_CAPACITY(buf->head)); - memcpy(&buf->head->mem[0], cp, sz); - buf->datalen = sz; - buf->head->datalen = sz; - buf->head->data = &buf->head->mem[0]; - assert_buf_ok(buf); - - /* Make sure everything is large enough */ - tor_assert(buf_allocation(buf) >= sz); - tor_assert(buf_allocation(buf) >= buf_datalen(buf) + buf_slack(buf)); - /* Does the buffer implementation allocate more than the requested size? - * (for example, by rounding up). If so, these checks will fail. */ - tor_assert(buf_datalen(buf) == sz); - tor_assert(buf_slack(buf) == 0); - - return buf; -} -#endif - -/** Remove the first <b>n</b> bytes from buf. */ -static inline void -buf_remove_from_front(buf_t *buf, size_t n) -{ - tor_assert(buf->datalen >= n); - while (n) { - tor_assert(buf->head); - if (buf->head->datalen > n) { - buf->head->datalen -= n; - buf->head->data += n; - buf->datalen -= n; - return; - } else { - chunk_t *victim = buf->head; - n -= victim->datalen; - buf->datalen -= victim->datalen; - buf->head = victim->next; - if (buf->tail == victim) - buf->tail = NULL; - buf_chunk_free_unchecked(victim); - } - } - check(); -} - -/** Create and return a new buf with default chunk capacity <b>size</b>. - */ -buf_t * -buf_new_with_capacity(size_t size) -{ - buf_t *b = buf_new(); - b->default_chunk_size = preferred_chunk_size(size); - return b; -} - -/** Allocate and return a new buffer with default capacity. */ -buf_t * -buf_new(void) -{ - buf_t *buf = tor_malloc_zero(sizeof(buf_t)); - buf->magic = BUFFER_MAGIC; - buf->default_chunk_size = 4096; - return buf; -} - -size_t -buf_get_default_chunk_size(const buf_t *buf) -{ - return buf->default_chunk_size; -} - -/** Remove all data from <b>buf</b>. */ -void -buf_clear(buf_t *buf) -{ - chunk_t *chunk, *next; - buf->datalen = 0; - for (chunk = buf->head; chunk; chunk = next) { - next = chunk->next; - buf_chunk_free_unchecked(chunk); - } - buf->head = buf->tail = NULL; -} - -/** Return the number of bytes stored in <b>buf</b> */ -MOCK_IMPL(size_t, -buf_datalen, (const buf_t *buf)) -{ - return buf->datalen; -} - -/** Return the total length of all chunks used in <b>buf</b>. */ -size_t -buf_allocation(const buf_t *buf) -{ - size_t total = 0; - const chunk_t *chunk; - for (chunk = buf->head; chunk; chunk = chunk->next) { - total += CHUNK_ALLOC_SIZE(chunk->memlen); - } - return total; -} - -/** Return the number of bytes that can be added to <b>buf</b> without - * performing any additional allocation. */ -size_t -buf_slack(const buf_t *buf) -{ - if (!buf->tail) - return 0; - else - return CHUNK_REMAINING_CAPACITY(buf->tail); -} - -/** Release storage held by <b>buf</b>. */ -void -buf_free(buf_t *buf) -{ - if (!buf) - return; - - buf_clear(buf); - buf->magic = 0xdeadbeef; - tor_free(buf); -} - -/** Return a new copy of <b>in_chunk</b> */ -static chunk_t * -chunk_copy(const chunk_t *in_chunk) -{ - chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); - total_bytes_allocated_in_chunks += CHUNK_ALLOC_SIZE(in_chunk->memlen); -#ifdef DEBUG_CHUNK_ALLOC - newch->DBG_alloc = CHUNK_ALLOC_SIZE(in_chunk->memlen); -#endif - newch->next = NULL; - if (in_chunk->data) { - off_t offset = in_chunk->data - in_chunk->mem; - newch->data = newch->mem + offset; - } - return newch; -} - -/** Return a new copy of <b>buf</b> */ -buf_t * -buf_copy(const buf_t *buf) -{ - chunk_t *ch; - buf_t *out = buf_new(); - out->default_chunk_size = buf->default_chunk_size; - for (ch = buf->head; ch; ch = ch->next) { - chunk_t *newch = chunk_copy(ch); - if (out->tail) { - out->tail->next = newch; - out->tail = newch; - } else { - out->head = out->tail = newch; - } - } - out->datalen = buf->datalen; - return out; -} - -/** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to - * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger - * than MAX_CHUNK_ALLOC. */ -static chunk_t * -buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) -{ - chunk_t *chunk; - - if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) { - chunk = chunk_new_with_alloc_size(buf->default_chunk_size); - } else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) { - chunk = chunk_new_with_alloc_size(MAX_CHUNK_ALLOC); - } else { - chunk = chunk_new_with_alloc_size(preferred_chunk_size(capacity)); - } - - chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec(); - - if (buf->tail) { - tor_assert(buf->head); - buf->tail->next = chunk; - buf->tail = chunk; - } else { - tor_assert(!buf->head); - buf->head = buf->tail = chunk; - } - check(); - return chunk; -} - -/** Return the age of the oldest chunk in the buffer <b>buf</b>, in - * milliseconds. Requires the current monotonic time, in truncated msec, - * as its input <b>now</b>. - */ -uint32_t -buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now) -{ - if (buf->head) { - return now - buf->head->inserted_time; - } else { - return 0; - } -} - -size_t -buf_get_total_allocation(void) -{ - return total_bytes_allocated_in_chunks; -} - -/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into - * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set - * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, - * and the number of bytes read otherwise. */ -static inline int -read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, - int *reached_eof, int *socket_error) -{ - ssize_t read_result; - if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) - at_most = CHUNK_REMAINING_CAPACITY(chunk); - read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); - - if (read_result < 0) { - int e = tor_socket_errno(fd); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?"); -#endif - *socket_error = e; - return -1; - } - return 0; /* would block. */ - } else if (read_result == 0) { - log_debug(LD_NET,"Encountered eof on fd %d", (int)fd); - *reached_eof = 1; - return 0; - } else { /* actually got bytes. */ - buf->datalen += read_result; - chunk->datalen += read_result; - log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, - (int)buf->datalen); - tor_assert(read_result < INT_MAX); - return (int)read_result; - } -} - -/** As read_to_chunk(), but return (negative) error code on error, blocking, - * or TLS, and the number of bytes read otherwise. */ -static inline int -read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, - size_t at_most) -{ - int read_result; - - tor_assert(CHUNK_REMAINING_CAPACITY(chunk) >= at_most); - read_result = tor_tls_read(tls, CHUNK_WRITE_PTR(chunk), at_most); - if (read_result < 0) - return read_result; - buf->datalen += read_result; - chunk->datalen += read_result; - return read_result; -} - -/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most - * <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0 - * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on - * error; else return the number of bytes read. - */ -/* XXXX indicate "read blocked" somehow? */ -int -read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes read" are not mutually exclusive. - */ - int r = 0; - size_t total_read = 0; - - check(); - tor_assert(reached_eof); - tor_assert(SOCKET_OK(s)); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - at_most)) - return -1; - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) { /* eof, block, or no more to read. */ - break; - } - } - return (int)total_read; -} - -/** As read_to_buf, but reads from a TLS connection, and returns a TLS - * status value rather than the number of bytes read. - * - * Using TLS on OR connections complicates matters in two ways. - * - * First, a TLS stream has its own read buffer independent of the - * connection's read buffer. (TLS needs to read an entire frame from - * the network before it can decrypt any data. Thus, trying to read 1 - * byte from TLS can require that several KB be read from the network - * and decrypted. The extra data is stored in TLS's decrypt buffer.) - * Because the data hasn't been read by Tor (it's still inside the TLS), - * this means that sometimes a connection "has stuff to read" even when - * poll() didn't return POLLIN. The tor_tls_get_pending_bytes function is - * used in connection.c to detect TLS objects with non-empty internal - * buffers and read from them again. - * - * Second, the TLS stream's events do not correspond directly to network - * events: sometimes, before a TLS stream can read, the network must be - * ready to write -- or vice versa. - */ -int -read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf) -{ - int r = 0; - size_t total_read = 0; - - check_no_tls_errors(); - - check(); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - at_most)) - return -1; - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk_tls(buf, chunk, tls, readlen); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) /* eof, block, or no more to read. */ - break; - } - return (int)total_read; -} - -/** Helper for flush_buf(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct - * the bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, 0 on blocking, -1 on failure. - */ -static inline int -flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, - size_t *buf_flushlen) -{ - ssize_t write_result; - - if (sz > chunk->datalen) - sz = chunk->datalen; - write_result = tor_socket_send(s, chunk->data, sz, 0); - - if (write_result < 0) { - int e = tor_socket_errno(s); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?"); -#endif - return -1; - } - log_debug(LD_NET,"write() would block, returning."); - return 0; - } else { - *buf_flushlen -= write_result; - buf_remove_from_front(buf, write_result); - tor_assert(write_result < INT_MAX); - return (int)write_result; - } -} - -/** Helper for flush_buf_tls(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. (Tries to write - * more if there is a forced pending write size.) On success, deduct the - * bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, and a TOR_TLS error code on failure or blocking. - */ -static inline int -flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, - size_t sz, size_t *buf_flushlen) -{ - int r; - size_t forced; - char *data; - - forced = tor_tls_get_forced_write_size(tls); - if (forced > sz) - sz = forced; - if (chunk) { - data = chunk->data; - tor_assert(sz <= chunk->datalen); - } else { - data = NULL; - tor_assert(sz == 0); - } - r = tor_tls_write(tls, data, sz); - if (r < 0) - return r; - if (*buf_flushlen > (size_t)r) - *buf_flushlen -= r; - else - *buf_flushlen = 0; - buf_remove_from_front(buf, r); - log_debug(LD_NET,"flushed %d bytes, %d ready to flush, %d remain.", - r,(int)*buf_flushlen,(int)buf->datalen); - return r; -} - -/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most - * <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by - * the number of bytes actually written, and remove the written bytes - * from the buffer. Return the number of bytes written on success, - * -1 on failure. Return 0 if write() would block. - */ -int -flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes flushed" are not mutually exclusive. - */ - int r; - size_t flushed = 0; - tor_assert(buf_flushlen); - tor_assert(SOCKET_OK(s)); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(sz <= *buf_flushlen); - - check(); - while (sz) { - size_t flushlen0; - tor_assert(buf->head); - if (buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - - r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */ - break; - } - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** As flush_buf(), but writes data to a TLS connection. Can write more than - * <b>flushlen</b> bytes. - */ -int -flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t flushlen, - size_t *buf_flushlen) -{ - int r; - size_t flushed = 0; - ssize_t sz; - tor_assert(buf_flushlen); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(flushlen <= *buf_flushlen); - sz = (ssize_t) flushlen; - - /* we want to let tls write even if flushlen is zero, because it might - * have a partial record pending */ - check_no_tls_errors(); - - check(); - do { - size_t flushlen0; - if (buf->head) { - if ((ssize_t)buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - } else { - flushlen0 = 0; - } - - r = flush_chunk_tls(tls, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0) /* Can't flush any more now. */ - break; - } while (sz > 0); - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** Append <b>string_len</b> bytes from <b>string</b> to the end of - * <b>buf</b>. - * - * Return the new length of the buffer on success, -1 on failure. - */ -int -write_to_buf(const char *string, size_t string_len, buf_t *buf) -{ - if (!string_len) - return (int)buf->datalen; - check(); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - string_len)) - return -1; - - while (string_len) { - size_t copy; - if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail)) - buf_add_chunk_with_capacity(buf, string_len, 1); - - copy = CHUNK_REMAINING_CAPACITY(buf->tail); - if (copy > string_len) - copy = string_len; - memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy); - string_len -= copy; - string += copy; - buf->datalen += copy; - buf->tail->datalen += copy; - } - - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> - * onto <b>string</b>. - */ -static inline void -peek_from_buf(char *string, size_t string_len, const buf_t *buf) -{ - chunk_t *chunk; - - tor_assert(string); - /* make sure we don't ask for too much */ - tor_assert(string_len <= buf->datalen); - /* assert_buf_ok(buf); */ - - chunk = buf->head; - while (string_len) { - size_t copy = string_len; - tor_assert(chunk); - if (chunk->datalen < copy) - copy = chunk->datalen; - memcpy(string, chunk->data, copy); - string_len -= copy; - string += copy; - chunk = chunk->next; - } -} - -/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store - * them into <b>string</b>. Return the new buffer size. <b>string_len</b> - * must be \<= the number of bytes on the buffer. - */ -int -fetch_from_buf(char *string, size_t string_len, buf_t *buf) -{ - /* There must be string_len bytes in buf; write them onto string, - * then memmove buf back (that is, remove them from buf). - * - * Return the number of bytes still on the buffer. */ - - check(); - peek_from_buf(string, string_len, buf); - buf_remove_from_front(buf, string_len); - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** True iff the cell command <b>command</b> is one that implies a - * variable-length cell in Tor link protocol <b>linkproto</b>. */ -static inline int -cell_command_is_var_length(uint8_t command, int linkproto) -{ - /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells - * work as implemented here. If it's 1, there are no variable-length cells. - * Tor does not support other versions right now, and so can't negotiate - * them. - */ - switch (linkproto) { - case 1: - /* Link protocol version 1 has no variable-length cells. */ - return 0; - case 2: - /* In link protocol version 2, VERSIONS is the only variable-length cell */ - return command == CELL_VERSIONS; - case 0: - case 3: - default: - /* In link protocol version 3 and later, and in version "unknown", - * commands 128 and higher indicate variable-length. VERSIONS is - * grandfathered in. */ - return command == CELL_VERSIONS || command >= 128; - } -} - -/** Check <b>buf</b> for a variable-length cell according to the rules of link - * protocol version <b>linkproto</b>. If one is found, pull it off the buffer - * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. - * Return 0 if whatever is on the start of buf_t is not a variable-length - * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start - * of a variable-length cell on <b>buf</b>, but the whole thing isn't there - * yet. */ -int -fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) -{ - char hdr[VAR_CELL_MAX_HEADER_SIZE]; - var_cell_t *result; - uint8_t command; - uint16_t length; - const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; - const int circ_id_len = get_circ_id_size(wide_circ_ids); - const unsigned header_len = get_var_cell_header_size(wide_circ_ids); - check(); - *out = NULL; - if (buf->datalen < header_len) - return 0; - peek_from_buf(hdr, header_len, buf); - - command = get_uint8(hdr + circ_id_len); - if (!(cell_command_is_var_length(command, linkproto))) - return 0; - - length = ntohs(get_uint16(hdr + circ_id_len + 1)); - if (buf->datalen < (size_t)(header_len+length)) - return 1; - result = var_cell_new(length); - result->command = command; - if (wide_circ_ids) - result->circ_id = ntohl(get_uint32(hdr)); - else - result->circ_id = ntohs(get_uint16(hdr)); - - buf_remove_from_front(buf, header_len); - peek_from_buf((char*) result->payload, length, buf); - buf_remove_from_front(buf, length); - check(); - - *out = result; - return 1; -} - -/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to - * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. - * Return the number of bytes actually copied. - */ -int -move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) -{ - /* We can do way better here, but this doesn't turn up in any profiles. */ - char b[4096]; - size_t cp, len; - - if (BUG(buf_out->datalen >= INT_MAX)) - return -1; - if (BUG(buf_out->datalen >= INT_MAX - *buf_flushlen)) - return -1; - - len = *buf_flushlen; - if (len > buf_in->datalen) - len = buf_in->datalen; - - cp = len; /* Remember the number of bytes we intend to copy. */ - tor_assert(cp < INT_MAX); - while (len) { - /* This isn't the most efficient implementation one could imagine, since - * it does two copies instead of 1, but I kinda doubt that this will be - * critical path. */ - size_t n = len > sizeof(b) ? sizeof(b) : len; - fetch_from_buf(b, n, buf_in); - write_to_buf(b, n, buf_out); - len -= n; - } - *buf_flushlen -= cp; - return (int)cp; -} - -/** Internal structure: represents a position in a buffer. */ -typedef struct buf_pos_t { - const chunk_t *chunk; /**< Which chunk are we pointing to? */ - int pos;/**< Which character inside the chunk's data are we pointing to? */ - size_t chunk_pos; /**< Total length of all previous chunks. */ -} buf_pos_t; - -/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/ -static void -buf_pos_init(const buf_t *buf, buf_pos_t *out) -{ - out->chunk = buf->head; - out->pos = 0; - out->chunk_pos = 0; -} - -/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current - * position of <b>out</b>, or later. Return -1 if no instances are found; - * otherwise returns the absolute position of the character. */ -static off_t -buf_find_pos_of_char(char ch, buf_pos_t *out) -{ - const chunk_t *chunk; - int pos; - tor_assert(out); - if (out->chunk) { - if (out->chunk->datalen) { - tor_assert(out->pos < (off_t)out->chunk->datalen); - } else { - tor_assert(out->pos == 0); - } - } - pos = out->pos; - for (chunk = out->chunk; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos); - if (cp) { - out->chunk = chunk; - tor_assert(cp - chunk->data < INT_MAX); - out->pos = (int)(cp - chunk->data); - return out->chunk_pos + out->pos; - } else { - out->chunk_pos += chunk->datalen; - pos = 0; - } - } - return -1; -} - -/** Advance <b>pos</b> by a single character, if there are any more characters - * in the buffer. Returns 0 on success, -1 on failure. */ -static inline int -buf_pos_inc(buf_pos_t *pos) -{ - ++pos->pos; - if (pos->pos == (off_t)pos->chunk->datalen) { - if (!pos->chunk->next) - return -1; - pos->chunk_pos += pos->chunk->datalen; - pos->chunk = pos->chunk->next; - pos->pos = 0; - } - return 0; -} - -/** Return true iff the <b>n</b>-character string in <b>s</b> appears - * (verbatim) at <b>pos</b>. */ -static int -buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) -{ - buf_pos_t p; - if (!n) - return 1; - - memcpy(&p, pos, sizeof(p)); - - while (1) { - char ch = p.chunk->data[p.pos]; - if (ch != *s) - return 0; - ++s; - /* If we're out of characters that don't match, we match. Check this - * _before_ we test incrementing pos, in case we're at the end of the - * string. */ - if (--n == 0) - return 1; - if (buf_pos_inc(&p)<0) - return 0; - } -} - -/** Return the first position in <b>buf</b> at which the <b>n</b>-character - * string <b>s</b> occurs, or -1 if it does not occur. */ -STATIC int -buf_find_string_offset(const buf_t *buf, const char *s, size_t n) -{ - buf_pos_t pos; - buf_pos_init(buf, &pos); - while (buf_find_pos_of_char(*s, &pos) >= 0) { - if (buf_matches_at_pos(&pos, s, n)) { - tor_assert(pos.chunk_pos + pos.pos < INT_MAX); - return (int)(pos.chunk_pos + pos.pos); - } else { - if (buf_pos_inc(&pos)<0) - return -1; - } - } - return -1; -} - -/** - * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at - * <b>headers</b>, looking for a "Content-Length" header. Try to set - * *<b>result_out</b> to the numeric value of that header if possible. - * Return -1 if the header was malformed, 0 if it was missing, and 1 if - * it was present and well-formed. - */ -STATIC int -buf_http_find_content_length(const char *headers, size_t headerlen, - size_t *result_out) -{ - const char *p, *newline; - char *len_str, *eos=NULL; - size_t remaining, result; - int ok; - *result_out = 0; /* The caller shouldn't look at this unless the - * return value is 1, but let's prevent confusion */ - -#define CONTENT_LENGTH "\r\nContent-Length: " - p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); - if (p == NULL) - return 0; - - tor_assert(p >= headers && p < headers+headerlen); - remaining = (headers+headerlen)-p; - p += strlen(CONTENT_LENGTH); - remaining -= strlen(CONTENT_LENGTH); - - newline = memchr(p, '\n', remaining); - if (newline == NULL) - return -1; - - len_str = tor_memdup_nulterm(p, newline-p); - /* We limit the size to INT_MAX because other parts of the buffer.c - * code don't like buffers to be any bigger than that. */ - result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); - if (eos && !tor_strisspace(eos)) { - ok = 0; - } else { - *result_out = result; - } - tor_free(len_str); - - return ok ? 1 : -1; -} - -/** There is a (possibly incomplete) http statement on <b>buf</b>, of the - * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) - * If a) the headers include a Content-Length field and all bytes in - * the body are present, or b) there's no Content-Length field and - * all headers are present, then: - * - * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. - * - memdup body into <b>*body_out</b>, and NUL-terminate it. - * - Then remove them from <b>buf</b>, and return 1. - * - * - If headers or body is NULL, discard that part of the buf. - * - If a headers or body doesn't fit in the arg, return -1. - * (We ensure that the headers or body don't exceed max len, - * _even if_ we're planning to discard them.) - * - If force_complete is true, then succeed even if not all of the - * content has arrived. - * - * Else, change nothing and return 0. - */ -int -fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete) -{ - char *headers; - size_t headerlen, bodylen, contentlen=0; - int crlf_offset; - int r; - - check(); - if (!buf->head) - return 0; - - crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); - if (crlf_offset > (int)max_headerlen || - (crlf_offset < 0 && buf->datalen > max_headerlen)) { - log_debug(LD_HTTP,"headers too long."); - return -1; - } else if (crlf_offset < 0) { - log_debug(LD_HTTP,"headers not all here yet."); - return 0; - } - /* Okay, we have a full header. Make sure it all appears in the first - * chunk. */ - if ((int)buf->head->datalen < crlf_offset + 4) - buf_pullup(buf, crlf_offset+4); - headerlen = crlf_offset + 4; - - headers = buf->head->data; - bodylen = buf->datalen - headerlen; - log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); - - if (max_headerlen <= headerlen) { - log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", - (int)headerlen, (int)max_headerlen-1); - return -1; - } - if (max_bodylen <= bodylen) { - log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", - (int)bodylen, (int)max_bodylen-1); - return -1; - } - - r = buf_http_find_content_length(headers, headerlen, &contentlen); - if (r == -1) { - log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " - "someone is trying to crash us."); - return -1; - } else if (r == 1) { - /* if content-length is malformed, then our body length is 0. fine. */ - log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); - if (bodylen < contentlen) { - if (!force_complete) { - log_debug(LD_HTTP,"body not all here yet."); - return 0; /* not all there yet */ - } - } - if (bodylen > contentlen) { - bodylen = contentlen; - log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); - } - } else { - tor_assert(r == 0); - /* Leave bodylen alone */ - } - - /* all happy. copy into the appropriate places, and return 1 */ - if (headers_out) { - *headers_out = tor_malloc(headerlen+1); - fetch_from_buf(*headers_out, headerlen, buf); - (*headers_out)[headerlen] = 0; /* NUL terminate it */ - } - if (body_out) { - tor_assert(body_used); - *body_used = bodylen; - *body_out = tor_malloc(bodylen+1); - fetch_from_buf(*body_out, bodylen, buf); - (*body_out)[bodylen] = 0; /* NUL terminate it */ - } - check(); - return 1; -} - -/** - * Wait this many seconds before warning the user about using SOCKS unsafely - * again. */ -#define SOCKS_WARN_INTERVAL 5 - -/** Warn that the user application has made an unsafe socks request using - * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than - * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ -static void -log_unsafe_socks_warning(int socks_protocol, const char *address, - uint16_t port, int safe_socks) -{ - static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); - - if (safe_socks) { - log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, - "Your application (using socks%d to port %d) is giving " - "Tor only an IP address. Applications that do DNS resolves " - "themselves may leak information. Consider using Socks4A " - "(e.g. via privoxy or socat) instead. For more information, " - "please see https://wiki.torproject.org/TheOnionRouter/" - "TorFAQ#SOCKSAndDNS.%s", - socks_protocol, - (int)port, - safe_socks ? " Rejecting." : ""); - } - control_event_client_status(LOG_WARN, - "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", - socks_protocol, address, (int)port); -} - -/** Do not attempt to parse socks messages longer than this. This value is - * actually significantly higher than the longest possible socks message. */ -#define MAX_SOCKS_MESSAGE_LEN 512 - -/** Return a new socks_request_t. */ -socks_request_t * -socks_request_new(void) -{ - return tor_malloc_zero(sizeof(socks_request_t)); -} - -/** Free all storage held in the socks_request_t <b>req</b>. */ -void -socks_request_free(socks_request_t *req) -{ - if (!req) - return; - if (req->username) { - memwipe(req->username, 0x10, req->usernamelen); - tor_free(req->username); - } - if (req->password) { - memwipe(req->password, 0x04, req->passwordlen); - tor_free(req->password); - } - memwipe(req, 0xCC, sizeof(socks_request_t)); - tor_free(req); -} - -/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one - * of the forms - * - socks4: "socksheader username\\0" - * - socks4a: "socksheader username\\0 destaddr\\0" - * - socks5 phase one: "version #methods methods" - * - socks5 phase two: "version command 0 addresstype..." - * If it's a complete and valid handshake, and destaddr fits in - * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, - * assign to <b>req</b>, and return 1. - * - * If it's invalid or too big, return -1. - * - * Else it's not all there yet, leave buf alone and return 0. - * - * If you want to specify the socks reply, write it into <b>req->reply</b> - * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. - * - * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether - * the connection is possibly leaking DNS requests locally or not. - * - * If <b>safe_socks</b> is true, then reject unsafe socks protocols. - * - * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are - * undefined. - */ -int -fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks) -{ - int res; - ssize_t n_drain; - size_t want_length = 128; - - if (buf->datalen < 2) /* version and another byte */ - return 0; - - do { - n_drain = 0; - buf_pullup(buf, want_length); - tor_assert(buf->head && buf->head->datalen >= 2); - want_length = 0; - - res = parse_socks(buf->head->data, buf->head->datalen, req, log_sockstype, - safe_socks, &n_drain, &want_length); - - if (n_drain < 0) - buf_clear(buf); - else if (n_drain > 0) - buf_remove_from_front(buf, n_drain); - - } while (res == 0 && buf->head && want_length < buf->datalen && - buf->datalen >= 2); - - return res; -} - -/** The size of the header of an Extended ORPort message: 2 bytes for - * COMMAND, 2 bytes for BODYLEN */ -#define EXT_OR_CMD_HEADER_SIZE 4 - -/** Read <b>buf</b>, which should contain an Extended ORPort message - * from a transport proxy. If well-formed, create and populate - * <b>out</b> with the Extended ORport message. Return 0 if the - * buffer was incomplete, 1 if it was well-formed and -1 if we - * encountered an error while parsing it. */ -int -fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) -{ - char hdr[EXT_OR_CMD_HEADER_SIZE]; - uint16_t len; - - check(); - if (buf->datalen < EXT_OR_CMD_HEADER_SIZE) - return 0; - peek_from_buf(hdr, sizeof(hdr), buf); - len = ntohs(get_uint16(hdr+2)); - if (buf->datalen < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) - return 0; - *out = ext_or_cmd_new(len); - (*out)->cmd = ntohs(get_uint16(hdr)); - (*out)->len = len; - buf_remove_from_front(buf, EXT_OR_CMD_HEADER_SIZE); - fetch_from_buf((*out)->body, len, buf); - return 1; -} - -/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and - * have Tor send it as error response to <b>req</b>. - */ -static void -socks_request_set_socks5_error(socks_request_t *req, - socks5_reply_status_t reason) -{ - req->replylen = 10; - memset(req->reply,0,10); - - req->reply[0] = 0x05; // VER field. - req->reply[1] = reason; // REP field. - req->reply[3] = 0x01; // ATYP field. -} - -/** Implementation helper to implement fetch_from_*_socks. Instead of looking - * at a buffer's contents, we look at the <b>datalen</b> bytes of data in - * <b>data</b>. Instead of removing data from the buffer, we set - * <b>drain_out</b> to the amount of data that should be removed (or -1 if the - * buffer should be cleared). Instead of pulling more data into the first - * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes - * we'd like to see in the input buffer, if they're available. */ -static int -parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out) -{ - unsigned int len; - char tmpbuf[TOR_ADDR_BUF_LEN+1]; - tor_addr_t destaddr; - uint32_t destip; - uint8_t socksver; - char *next, *startaddr; - unsigned char usernamelen, passlen; - struct in_addr in; - - if (datalen < 2) { - /* We always need at least 2 bytes. */ - *want_length_out = 2; - return 0; - } - - if (req->socks_version == 5 && !req->got_auth) { - /* See if we have received authentication. Strictly speaking, we should - also check whether we actually negotiated username/password - authentication. But some broken clients will send us authentication - even if we negotiated SOCKS_NO_AUTH. */ - if (*data == 1) { /* username/pass version 1 */ - /* Format is: authversion [1 byte] == 1 - usernamelen [1 byte] - username [usernamelen bytes] - passlen [1 byte] - password [passlen bytes] */ - usernamelen = (unsigned char)*(data + 1); - if (datalen < 2u + usernamelen + 1u) { - *want_length_out = 2u + usernamelen + 1u; - return 0; - } - passlen = (unsigned char)*(data + 2u + usernamelen); - if (datalen < 2u + usernamelen + 1u + passlen) { - *want_length_out = 2u + usernamelen + 1u + passlen; - return 0; - } - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 1; /* authversion == 1 */ - req->reply[1] = 0; /* authentication successful */ - log_debug(LD_APP, - "socks5: Accepted username/password without checking."); - if (usernamelen) { - req->username = tor_memdup(data+2u, usernamelen); - req->usernamelen = usernamelen; - } - if (passlen) { - req->password = tor_memdup(data+3u+usernamelen, passlen); - req->passwordlen = passlen; - } - *drain_out = 2u + usernamelen + 1u + passlen; - req->got_auth = 1; - *want_length_out = 7; /* Minimal socks5 command. */ - return 0; - } else if (req->auth_type == SOCKS_USER_PASS) { - /* unknown version byte */ - log_warn(LD_APP, "Socks5 username/password version %d not recognized; " - "rejecting.", (int)*data); - return -1; - } - } - - socksver = *data; - - switch (socksver) { /* which version of socks? */ - case 5: /* socks5 */ - - if (req->socks_version != 5) { /* we need to negotiate a method */ - unsigned char nummethods = (unsigned char)*(data+1); - int have_user_pass, have_no_auth; - int r=0; - tor_assert(!req->socks_version); - if (datalen < 2u+nummethods) { - *want_length_out = 2u+nummethods; - return 0; - } - if (!nummethods) - return -1; - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; /* socks5 reply */ - have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); - have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); - if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { - req->auth_type = SOCKS_USER_PASS; - req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" - auth method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); - r=0; - } else if (have_no_auth) { - req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth - method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); - r=0; - } else { - log_warn(LD_APP, - "socks5: offered methods don't include 'no auth' or " - "username/password. Rejecting."); - req->reply[1] = '\xFF'; /* reject all methods */ - r=-1; - } - /* Remove packet from buf. Some SOCKS clients will have sent extra - * junk at this point; let's hope it's an authentication message. */ - *drain_out = 2u + nummethods; - - return r; - } - if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { - log_warn(LD_APP, - "socks5: negotiated authentication, but none provided"); - return -1; - } - /* we know the method; read in the request */ - log_debug(LD_APP,"socks5: checking request"); - if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ - *want_length_out = 7; - return 0; /* not yet */ - } - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE && - req->command != SOCKS_COMMAND_RESOLVE_PTR) { - /* not a connect or resolve or a resolve_ptr? we don't support it. */ - socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); - - log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", - req->command); - return -1; - } - switch (*(data+3)) { /* address type */ - case 1: /* IPv4 address */ - case 4: /* IPv6 address */ { - const int is_v6 = *(data+3) == 4; - const unsigned addrlen = is_v6 ? 16 : 4; - log_debug(LD_APP,"socks5: ipv4 address type"); - if (datalen < 6+addrlen) {/* ip/port there? */ - *want_length_out = 6+addrlen; - return 0; /* not yet */ - } - - if (is_v6) - tor_addr_from_ipv6_bytes(&destaddr, data+4); - else - tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); - - tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); - - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - log_warn(LD_APP, - "socks5 IP takes %d bytes, which doesn't fit in %d. " - "Rejecting.", - (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); - return -1; - } - strlcpy(req->address,tmpbuf,sizeof(req->address)); - req->port = ntohs(get_uint16(data+4+addrlen)); - *drain_out = 6+addrlen; - if (req->command != SOCKS_COMMAND_RESOLVE_PTR && - !addressmap_have_mapping(req->address,0)) { - log_unsafe_socks_warning(5, req->address, req->port, safe_socks); - if (safe_socks) { - socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); - return -1; - } - } - return 1; - } - case 3: /* fqdn */ - log_debug(LD_APP,"socks5: fqdn address type"); - if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { - socks_request_set_socks5_error(req, - SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); - log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " - "hostname type. Rejecting."); - return -1; - } - len = (unsigned char)*(data+4); - if (datalen < 7+len) { /* addr/port there? */ - *want_length_out = 7+len; - return 0; /* not yet */ - } - if (len+1 > MAX_SOCKS_ADDR_LEN) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - log_warn(LD_APP, - "socks5 hostname is %d bytes, which doesn't fit in " - "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); - return -1; - } - memcpy(req->address,data+5,len); - req->address[len] = 0; - req->port = ntohs(get_uint16(data+5+len)); - *drain_out = 5+len+2; - - if (string_is_valid_ipv4_address(req->address) || - string_is_valid_ipv6_address(req->address)) { - log_unsafe_socks_warning(5,req->address,req->port,safe_socks); - - if (safe_socks) { - socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); - return -1; - } - } else if (!string_is_valid_hostname(req->address)) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - - log_warn(LD_PROTOCOL, - "Your application (using socks5 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks5 to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - return 1; - default: /* unsupported */ - socks_request_set_socks5_error(req, - SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); - log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", - (int) *(data+3)); - return -1; - } - tor_assert(0); - break; - case 4: { /* socks4 */ - enum {socks4, socks4a} socks4_prot = socks4a; - const char *authstart, *authend; - /* http://ss5.sourceforge.net/socks4.protocol.txt */ - /* http://ss5.sourceforge.net/socks4A.protocol.txt */ - - req->socks_version = 4; - if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ - *want_length_out = SOCKS4_NETWORK_LEN; - return 0; /* not yet */ - } - // buf_pullup(buf, 1280); - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE) { - /* not a connect or resolve? we don't support it. (No resolve_ptr with - * socks4.) */ - log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", - req->command); - return -1; - } - - req->port = ntohs(get_uint16(data+2)); - destip = ntohl(get_uint32(data+4)); - if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { - log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); - return -1; - } - if (destip >> 8) { - log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); - in.s_addr = htonl(destip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", - (int)strlen(tmpbuf)); - return -1; - } - log_debug(LD_APP, - "socks4: successfully read destip (%s)", - safe_str_client(tmpbuf)); - socks4_prot = socks4; - } - - authstart = data + SOCKS4_NETWORK_LEN; - next = memchr(authstart, 0, - datalen-SOCKS4_NETWORK_LEN); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP, "Socks4 user name too long; rejecting."); - return -1; - } - log_debug(LD_APP,"socks4: Username not here yet."); - *want_length_out = datalen+1024; /* More than we need, but safe */ - return 0; - } - authend = next; - tor_assert(next < data+datalen); - - startaddr = NULL; - if (socks4_prot != socks4a && - !addressmap_have_mapping(tmpbuf,0)) { - log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); - - if (safe_socks) - return -1; - } - if (socks4_prot == socks4a) { - if (next+1 == data+datalen) { - log_debug(LD_APP,"socks4: No part of destaddr here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - startaddr = next+1; - next = memchr(startaddr, 0, data + datalen - startaddr); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP,"socks4: Destaddr too long."); - return -1; - } - log_debug(LD_APP,"socks4: Destaddr not all here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { - log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); - return -1; - } - // tor_assert(next < buf->cur+buf->datalen); - - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks4a to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - } - log_debug(LD_APP,"socks4: Everything is here. Success."); - strlcpy(req->address, startaddr ? startaddr : tmpbuf, - sizeof(req->address)); - if (!tor_strisprint(req->address) || strchr(req->address,'\"')) { - log_warn(LD_PROTOCOL, - "Your application (using socks4 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (authend != authstart) { - req->got_auth = 1; - req->usernamelen = authend - authstart; - req->username = tor_memdup(authstart, authend - authstart); - } - /* next points to the final \0 on inbuf */ - *drain_out = next - data + 1; - return 1; - } - case 'G': /* get */ - case 'H': /* head */ - case 'P': /* put/post */ - case 'C': /* connect */ - strlcpy((char*)req->reply, -"HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" -"Content-Type: text/html; charset=iso-8859-1\r\n\r\n" -"<html>\n" -"<head>\n" -"<title>Tor is not an HTTP Proxy</title>\n" -"</head>\n" -"<body>\n" -"<h1>Tor is not an HTTP Proxy</h1>\n" -"<p>\n" -"It appears you have configured your web browser to use Tor as an HTTP proxy." -"\n" -"This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" -"Please configure your client accordingly.\n" -"</p>\n" -"<p>\n" -"See <a href=\"https://www.torproject.org/documentation.html\">" - "https://www.torproject.org/documentation.html</a> for more " - "information.\n" -"<!-- Plus this comment, to make the body response more than 512 bytes, so " -" IE will be willing to display it. Comment comment comment comment " -" comment comment comment comment comment comment comment comment.-->\n" -"</p>\n" -"</body>\n" -"</html>\n" - , MAX_SOCKS_REPLY_LEN); - req->replylen = strlen((char*)req->reply)+1; - /* fall through */ - default: /* version is not socks4 or socks5 */ - log_warn(LD_APP, - "Socks version %d not recognized. (Tor is not an http proxy.)", - *(data)); - { - /* Tell the controller the first 8 bytes. */ - char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); - control_event_client_status(LOG_WARN, - "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", - escaped(tmp)); - tor_free(tmp); - } - return -1; - } -} - -/** Inspect a reply from SOCKS server stored in <b>buf</b> according - * to <b>state</b>, removing the protocol data upon success. Return 0 on - * incomplete response, 1 on success and -1 on error, in which case - * <b>reason</b> is set to a descriptive message (free() when finished - * with it). - * - * As a special case, 2 is returned when user/pass is required - * during SOCKS5 handshake and user/pass is configured. - */ -int -fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) -{ - ssize_t drain = 0; - int r; - if (buf->datalen < 2) - return 0; - - buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN); - tor_assert(buf->head && buf->head->datalen >= 2); - - r = parse_socks_client((uint8_t*)buf->head->data, buf->head->datalen, - state, reason, &drain); - if (drain > 0) - buf_remove_from_front(buf, drain); - else if (drain < 0) - buf_clear(buf); - - return r; -} - -/** Implementation logic for fetch_from_*_socks_client. */ -static int -parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out) -{ - unsigned int addrlen; - *drain_out = 0; - if (datalen < 2) - return 0; - - switch (state) { - case PROXY_SOCKS4_WANT_CONNECT_OK: - /* Wait for the complete response */ - if (datalen < 8) - return 0; - - if (data[1] != 0x5a) { - *reason = tor_strdup(socks4_response_code_to_string(data[1])); - return -1; - } - - /* Success */ - *drain_out = 8; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: - /* we don't have any credentials */ - if (data[1] != 0x00) { - *reason = tor_strdup("server doesn't support any of our " - "available authentication methods"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: - /* we have a username and password. return 1 if we can proceed without - * providing authentication, or 2 otherwise. */ - switch (data[1]) { - case 0x00: - log_info(LD_NET, "SOCKS 5 client: we have auth details but server " - "doesn't require authentication."); - *drain_out = -1; - return 1; - case 0x02: - log_info(LD_NET, "SOCKS 5 client: need authentication."); - *drain_out = -1; - return 2; - /* fall through */ - } - - *reason = tor_strdup("server doesn't support any of our available " - "authentication methods"); - return -1; - - case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: - /* handle server reply to rfc1929 authentication */ - if (data[1] != 0x00) { - *reason = tor_strdup("authentication failed"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: authentication successful."); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_CONNECT_OK: - /* response is variable length. BND.ADDR, etc, isn't needed - * (don't bother with buf_pullup()), but make sure to eat all - * the data used */ - - /* wait for address type field to arrive */ - if (datalen < 4) - return 0; - - switch (data[3]) { - case 0x01: /* ip4 */ - addrlen = 4; - break; - case 0x04: /* ip6 */ - addrlen = 16; - break; - case 0x03: /* fqdn (can this happen here?) */ - if (datalen < 5) - return 0; - addrlen = 1 + data[4]; - break; - default: - *reason = tor_strdup("invalid response to connect request"); - return -1; - } - - /* wait for address and port */ - if (datalen < 6 + addrlen) - return 0; - - if (data[1] != 0x00) { - *reason = tor_strdup(socks5_response_code_to_string(data[1])); - return -1; - } - - *drain_out = 6 + addrlen; - return 1; - } - - /* shouldn't get here... */ - tor_assert(0); - - return -1; -} - -/** Return 1 iff buf looks more like it has an (obsolete) v0 controller - * command on it than any valid v1 controller command. */ -int -peek_buf_has_control0_command(buf_t *buf) -{ - if (buf->datalen >= 4) { - char header[4]; - uint16_t cmd; - peek_from_buf(header, sizeof(header), buf); - cmd = ntohs(get_uint16(header+2)); - if (cmd <= 0x14) - return 1; /* This is definitely not a v1 control command. */ - } - return 0; -} - -/** Return the index within <b>buf</b> at which <b>ch</b> first appears, - * or -1 if <b>ch</b> does not appear on buf. */ -static off_t -buf_find_offset_of_char(buf_t *buf, char ch) -{ - chunk_t *chunk; - off_t offset = 0; - for (chunk = buf->head; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data, ch, chunk->datalen); - if (cp) - return offset + (cp - chunk->data); - else - offset += chunk->datalen; - } - return -1; -} - -/** Try to read a single LF-terminated line from <b>buf</b>, and write it - * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer - * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the - * line, not counting the terminating NUL. Return 1 if we read a whole line, - * return 0 if we don't have a whole line yet, and return -1 if the line - * length exceeds *<b>data_len</b>. - */ -int -fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) -{ - size_t sz; - off_t offset; - - if (!buf->head) - return 0; - - offset = buf_find_offset_of_char(buf, '\n'); - if (offset < 0) - return 0; - sz = (size_t) offset; - if (sz+2 > *data_len) { - *data_len = sz + 2; - return -1; - } - fetch_from_buf(data_out, sz+1, buf); - data_out[sz+1] = '\0'; - *data_len = sz+1; - return 1; -} - -/** Compress on uncompress the <b>data_len</b> bytes in <b>data</b> using the - * compression state <b>state</b>, appending the result to <b>buf</b>. If - * <b>done</b> is true, flush the data in the state and finish the - * compression/uncompression. Return -1 on failure, 0 on success. */ -int -write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, - const char *data, size_t data_len, - const int done) -{ - char *next; - size_t old_avail, avail; - int over = 0; - - do { - int need_new_chunk = 0; - if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { - size_t cap = data_len / 4; - buf_add_chunk_with_capacity(buf, cap, 1); - } - next = CHUNK_WRITE_PTR(buf->tail); - avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); - switch (tor_compress_process(state, &next, &avail, - &data, &data_len, done)) { - case TOR_COMPRESS_DONE: - over = 1; - break; - case TOR_COMPRESS_ERROR: - return -1; - case TOR_COMPRESS_OK: - if (data_len == 0) { - tor_assert_nonfatal(!done); - over = 1; - } - break; - case TOR_COMPRESS_BUFFER_FULL: - if (avail) { - /* The compression module says we need more room - * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically, - * whether were going to or not. */ - need_new_chunk = 1; - } - if (data_len == 0 && !done) { - /* We've consumed all the input data, though, so there's no - * point in forging ahead right now. */ - over = 1; - } - break; - } - buf->datalen += old_avail - avail; - buf->tail->datalen += old_avail - avail; - if (need_new_chunk) { - buf_add_chunk_with_capacity(buf, data_len/4, 1); - } - - } while (!over); - check(); - return 0; -} - -/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ -int -buf_set_to_copy(buf_t **output, - const buf_t *input) -{ - if (*output) - buf_free(*output); - *output = buf_copy(input); - return 0; -} - -/** Log an error and exit if <b>buf</b> is corrupted. - */ -void -assert_buf_ok(buf_t *buf) -{ - tor_assert(buf); - tor_assert(buf->magic == BUFFER_MAGIC); - - if (! buf->head) { - tor_assert(!buf->tail); - tor_assert(buf->datalen == 0); - } else { - chunk_t *ch; - size_t total = 0; - tor_assert(buf->tail); - for (ch = buf->head; ch; ch = ch->next) { - total += ch->datalen; - tor_assert(ch->datalen <= ch->memlen); - tor_assert(ch->data >= &ch->mem[0]); - tor_assert(ch->data <= &ch->mem[0]+ch->memlen); - if (ch->data == &ch->mem[0]+ch->memlen) { - static int warned = 0; - if (! warned) { - log_warn(LD_BUG, "Invariant violation in buf.c related to #15083"); - warned = 1; - } - } - tor_assert(ch->data+ch->datalen <= &ch->mem[0] + ch->memlen); - if (!ch->next) - tor_assert(ch == buf->tail); - } - tor_assert(buf->datalen == total); - } -} - diff --git a/src/or/channel.c b/src/or/channel.c index 2970b96791..0b5a7fde90 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -701,7 +701,7 @@ channel_remove_from_digest_map(channel_t *chan) return; } -#endif +#endif /* 0 */ /* Pull it out of its list, wherever that list is */ TOR_LIST_REMOVE(chan, next_with_same_id); @@ -1832,7 +1832,7 @@ cell_queue_entry_is_padding(cell_queue_entry_t *q) return 0; } -#endif +#endif /* 0 */ /** * Allocate a new cell queue entry for a fixed-size cell @@ -2088,8 +2088,8 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) * are appropriate to the state transition in question. */ -void -channel_change_state(channel_t *chan, channel_state_t to_state) +static void +channel_change_state_(channel_t *chan, channel_state_t to_state) { channel_state_t from_state; unsigned char was_active, is_active; @@ -2208,18 +2208,8 @@ channel_change_state(channel_t *chan, channel_state_t to_state) estimated_total_queue_size += chan->bytes_in_queue; } - /* Tell circuits if we opened and stuff */ - if (to_state == CHANNEL_STATE_OPEN) { - channel_do_open_actions(chan); - chan->has_been_open = 1; - - /* Check for queued cells to process */ - if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) - channel_process_cells(chan); - if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)) - channel_flush_cells(chan); - } else if (to_state == CHANNEL_STATE_CLOSED || - to_state == CHANNEL_STATE_ERROR) { + if (to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { /* Assert that all queues are empty */ tor_assert(TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)); tor_assert(TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)); @@ -2227,6 +2217,35 @@ channel_change_state(channel_t *chan, channel_state_t to_state) } /** + * As channel_change_state_, but change the state to any state but open. + */ +void +channel_change_state(channel_t *chan, channel_state_t to_state) +{ + tor_assert(to_state != CHANNEL_STATE_OPEN); + channel_change_state_(chan, to_state); +} + +/** + * As channel_change_state, but change the state to open. + */ +void +channel_change_state_open(channel_t *chan) +{ + channel_change_state_(chan, CHANNEL_STATE_OPEN); + + /* Tell circuits if we opened and stuff */ + channel_do_open_actions(chan); + chan->has_been_open = 1; + + /* Check for queued cells to process */ + if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) + channel_process_cells(chan); + if (! TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue)) + channel_flush_cells(chan); +} + +/** * Change channel listener state * * This internal and subclass use only function is used to change channel @@ -2584,8 +2603,8 @@ channel_flush_cells(channel_t *chan) * available. */ -int -channel_more_to_flush(channel_t *chan) +MOCK_IMPL(int, +channel_more_to_flush, (channel_t *chan)) { tor_assert(chan); @@ -4071,7 +4090,7 @@ channel_mark_bad_for_new_circs(channel_t *chan) */ int -channel_is_client(channel_t *chan) +channel_is_client(const channel_t *chan) { tor_assert(chan); @@ -4093,6 +4112,20 @@ channel_mark_client(channel_t *chan) } /** + * Clear the client flag + * + * Mark a channel as being _not_ from a client + */ + +void +channel_clear_client(channel_t *chan) +{ + tor_assert(chan); + + chan->is_client = 0; +} + +/** * Get the canonical flag for a channel * * This returns the is_canonical for a channel; this flag is determined by @@ -4822,8 +4855,6 @@ channel_update_xmit_queue_size(channel_t *chan) U64_FORMAT ", new size is " U64_FORMAT, U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(estimated_total_queue_size)); - /* Tell the scheduler we're increasing the queue size */ - scheduler_adjust_queue_size(chan, 1, adj); } } else if (queued < chan->bytes_queued_for_xmit) { adj = chan->bytes_queued_for_xmit - queued; @@ -4846,8 +4877,6 @@ channel_update_xmit_queue_size(channel_t *chan) U64_FORMAT ", new size is " U64_FORMAT, U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(estimated_total_queue_size)); - /* Tell the scheduler we're decreasing the queue size */ - scheduler_adjust_queue_size(chan, -1, adj); } } } diff --git a/src/or/channel.h b/src/or/channel.h index ea280f2fd2..32336fe1d2 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -487,7 +487,7 @@ STATIC void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off); void channel_write_cell_generic_(channel_t *chan, const char *cell_type, void *cell, cell_queue_entry_t *q); -#endif +#endif /* defined(CHANNEL_PRIVATE_) */ /* Channel operations for subclasses and internal use only */ @@ -522,6 +522,7 @@ void channel_listener_free(channel_listener_t *chan_l); /* State/metadata setters */ void channel_change_state(channel_t *chan, channel_state_t to_state); +void channel_change_state_open(channel_t *chan); void channel_clear_identity_digest(channel_t *chan); void channel_clear_remote_end(channel_t *chan); void channel_mark_local(channel_t *chan); @@ -567,7 +568,7 @@ MOCK_DECL(ssize_t, channel_flush_some_cells, (channel_t *chan, ssize_t num_cells)); /* Query if data available on this channel */ -int channel_more_to_flush(channel_t *chan); +MOCK_DECL(int, channel_more_to_flush, (channel_t *chan)); /* Notify flushed outgoing for dirreq handling */ void channel_notify_flushed(channel_t *chan); @@ -579,7 +580,7 @@ void channel_do_open_actions(channel_t *chan); extern uint64_t estimated_total_queue_size; #endif -#endif +#endif /* defined(TOR_CHANNEL_INTERNAL_) */ /* Helper functions to perform operations on channels */ @@ -665,11 +666,12 @@ 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(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); int channel_is_outgoing(channel_t *chan); void channel_mark_client(channel_t *chan); +void channel_clear_client(channel_t *chan); int channel_matches_extend_info(channel_t *chan, extend_info_t *extend_info); int channel_matches_target_addr_for_extend(channel_t *chan, const tor_addr_t *target); @@ -718,5 +720,5 @@ int packed_cell_is_destroy(channel_t *chan, /* Declare the handle helpers */ HANDLE_DECL(channel, channel_s,) -#endif +#endif /* !defined(TOR_CHANNEL_H) */ diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c index ccaf5b4ec8..435436c45c 100644 --- a/src/or/channelpadding.c +++ b/src/or/channelpadding.c @@ -71,7 +71,7 @@ static int consensus_nf_pad_single_onion; * its a client, use that. Then finally verify in the consensus). */ #define CHANNEL_IS_CLIENT(chan, options) \ - (!public_server_mode((options)) || (chan)->is_client || \ + (!public_server_mode((options)) || channel_is_client(chan) || \ !connection_or_digest_is_known_relay((chan)->identity_digest)) /** diff --git a/src/or/channelpadding.h b/src/or/channelpadding.h index a227e27d5b..58bf741d5c 100644 --- a/src/or/channelpadding.h +++ b/src/or/channelpadding.h @@ -41,5 +41,5 @@ int channelpadding_get_circuits_available_timeout(void); unsigned int channelpadding_get_channel_idle_timeout(const channel_t *, int); void channelpadding_new_consensus_params(networkstatus_t *ns); -#endif +#endif /* !defined(TOR_CHANNELPADDING_H) */ diff --git a/src/or/channeltls.c b/src/or/channeltls.c index c6a0bb21e9..8277813186 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -847,7 +847,7 @@ channel_tls_write_packed_cell_method(channel_t *chan, tor_assert(packed_cell); if (tlschan->conn) { - connection_write_to_buf(packed_cell->body, cell_network_size, + connection_buf_add(packed_cell->body, cell_network_size, TO_CONN(tlschan->conn)); /* This is where the cell is finished; used to be done from relay.c */ @@ -993,7 +993,7 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, * We can go to CHANNEL_STATE_OPEN from CHANNEL_STATE_OPENING or * CHANNEL_STATE_MAINT on this. */ - channel_change_state(base_chan, CHANNEL_STATE_OPEN); + channel_change_state_open(base_chan); /* We might have just become writeable; check and tell the scheduler */ if (connection_or_num_cells_writeable(conn) > 0) { scheduler_channel_wants_writes(base_chan); @@ -1044,7 +1044,7 @@ channel_tls_time_process_cell(cell_t *cell, channel_tls_t *chan, int *time, *time += time_passed; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ /** * Handle an incoming cell on a channel_tls_t @@ -1072,9 +1072,9 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn) channel_tls_time_process_cell(cl, cn, & tp ## time , \ channel_tls_process_ ## tp ## _cell); \ } STMT_END -#else +#else /* !(defined(KEEP_TIMING_STATS)) */ #define PROCESS_CELL(tp, cl, cn) channel_tls_process_ ## tp ## _cell(cl, cn) -#endif +#endif /* defined(KEEP_TIMING_STATS) */ tor_assert(cell); tor_assert(conn); @@ -1204,7 +1204,7 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn) /* remember which second it is, for next time */ current_second = now; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ tor_assert(var_cell); tor_assert(conn); @@ -1579,7 +1579,7 @@ channel_tls_process_versions_cell(var_cell_t *cell, channel_tls_t *chan) connection_or_close_normally(chan->conn, 1); return; } -#endif +#endif /* defined(DISABLE_V3_LINKPROTO_SERVERSIDE) */ if (send_versions) { if (connection_or_send_versions(chan->conn, 1) < 0) { @@ -1680,6 +1680,8 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) long apparent_skew = 0; tor_addr_t my_apparent_addr = TOR_ADDR_NULL; + int started_here = 0; + const char *identity_digest = NULL; tor_assert(cell); tor_assert(chan); @@ -1699,10 +1701,12 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) } tor_assert(chan->conn->handshake_state && chan->conn->handshake_state->received_versions); + started_here = connection_or_nonopen_was_started_here(chan->conn); + identity_digest = chan->conn->identity_digest; if (chan->conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) { tor_assert(chan->conn->link_proto >= 3); - if (chan->conn->handshake_state->started_here) { + if (started_here) { if (!(chan->conn->handshake_state->authenticated)) { log_fn(LOG_PROTOCOL_WARN, LD_OR, "Got a NETINFO cell from server, " @@ -1789,12 +1793,11 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) return; } /* A relay can connect from anywhere and be canonical, so - * long as it tells you from where it came. This may be a bit - * concerning.. Luckily we have another check in - * channel_tls_matches_target_method() to ensure that extends - * only go to the IP they ask for. - * - * XXX: Bleh. That check is not used if the connection is canonical. + * long as it tells you from where it came. This may sound a bit + * concerning... but that's what "canonical" means: that the + * address is one that the relay itself has claimed. The relay + * might be doing something funny, but nobody else is doing a MITM + * on the relay's TCP. */ if (tor_addr_eq(&addr, &(chan->conn->real_addr))) { connection_or_set_canonical(chan->conn, 1); @@ -1813,7 +1816,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) "they will not consider this connection canonical. They " "think we are at %s, but we think its %s.", safe_str(descr), - safe_str(hex_str(chan->conn->identity_digest, DIGEST_LEN)), + safe_str(hex_str(identity_digest, DIGEST_LEN)), safe_str(tor_addr_is_null(&my_apparent_addr) ? "<none>" : fmt_and_decorate_addr(&my_apparent_addr)), safe_str(fmt_addr32(me->addr))); @@ -1823,7 +1826,8 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) /** Warn when we get a netinfo skew with at least this value. */ #define NETINFO_NOTICE_SKEW 3600 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && - connection_or_digest_is_known_relay(chan->conn->identity_digest)) { + (started_here || + connection_or_digest_is_known_relay(chan->conn->identity_digest))) { int trusted = router_digest_is_trusted_dir(chan->conn->identity_digest); clock_skew_warning(TO_CONN(chan->conn), apparent_skew, trusted, LD_GENERAL, "NETINFO cell", "OR"); @@ -1857,8 +1861,7 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) safe_str_client(chan->conn->base_.address), chan->conn->base_.port, (int)(chan->conn->link_proto), - hex_str(TLS_CHAN_TO_BASE(chan)->identity_digest, - DIGEST_LEN), + hex_str(identity_digest, DIGEST_LEN), tor_addr_is_null(&my_apparent_addr) ? "<none>" : fmt_and_decorate_addr(&my_apparent_addr)); } @@ -1915,7 +1918,6 @@ certs_cell_typenum_to_cert_type(int typenum) * of the connection, we then authenticate the server or mark the connection. * If it's the server side, wait for an AUTHENTICATE cell. */ - STATIC void channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) { @@ -1930,7 +1932,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) int n_certs, i; certs_cell_t *cc = NULL; - int send_netinfo = 0; + int send_netinfo = 0, started_here = 0; memset(x509_certs, 0, sizeof(x509_certs)); memset(ed_certs, 0, sizeof(ed_certs)); @@ -1948,6 +1950,11 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) goto err; \ } while (0) + /* Can't use connection_or_nonopen_was_started_here(); its conn->tls + * check looks like it breaks + * test_link_handshake_recv_certs_ok_server(). */ + started_here = chan->conn->handshake_state->started_here; + if (chan->conn->base_.state != OR_CONN_STATE_OR_HANDSHAKING_V3) ERR("We're not doing a v3 handshake!"); if (chan->conn->link_proto < 3) @@ -2061,7 +2068,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) /* Note that this warns more loudly about time and validity if we were * _trying_ to connect to an authority, not necessarily if we _did_ connect * to one. */ - if (chan->conn->handshake_state->started_here && + if (started_here && router_digest_is_trusted_dir(TLS_CHAN_TO_BASE(chan)->identity_digest)) severity = LOG_WARN; else @@ -2079,7 +2086,7 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) if (!checked_rsa_id) ERR("Invalid certificate chain!"); - if (chan->conn->handshake_state->started_here) { + if (started_here) { /* No more information is needed. */ chan->conn->handshake_state->authenticated = 1; diff --git a/src/or/channeltls.h b/src/or/channeltls.h index 1f9a39d8a4..d9c4239c3a 100644 --- a/src/or/channeltls.h +++ b/src/or/channeltls.h @@ -26,7 +26,7 @@ struct channel_tls_s { or_connection_t *conn; }; -#endif /* TOR_CHANNEL_INTERNAL_ */ +#endif /* defined(TOR_CHANNEL_INTERNAL_) */ channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port, const char *id_digest, @@ -69,7 +69,7 @@ STATIC void channel_tls_process_auth_challenge_cell(var_cell_t *cell, STATIC void channel_tls_common_init(channel_tls_t *tlschan); STATIC void channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *tlschan); -#endif +#endif /* defined(CHANNELTLS_PRIVATE) */ -#endif +#endif /* !defined(TOR_CHANNELTLS_H) */ diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 4c0bd9e455..f4bd5ea2fa 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -292,7 +292,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) return circ->cpath && circ->cpath->next != circ->cpath && circ->cpath->next->state == CPATH_STATE_AWAITING_KEYS; -#else +#else /* !(defined(N2N_TAGGING_IS_POSSIBLE)) */ /* If tagging attacks are no longer possible, we probably want to * count bias from the first hop. However, one could argue that * timing-based tagging is still more useful than per-hop failure. @@ -300,7 +300,7 @@ pathbias_is_new_circ_attempt(origin_circuit_t *circ) */ return circ->cpath && circ->cpath->state == CPATH_STATE_AWAITING_KEYS; -#endif +#endif /* defined(N2N_TAGGING_IS_POSSIBLE) */ } /** diff --git a/src/or/circpathbias.h b/src/or/circpathbias.h index 2a4c316807..c9e572d2ae 100644 --- a/src/or/circpathbias.h +++ b/src/or/circpathbias.h @@ -25,5 +25,5 @@ void pathbias_mark_use_success(origin_circuit_t *circ); void pathbias_mark_use_rollback(origin_circuit_t *circ); const char *pathbias_state_to_string(path_state_t state); -#endif +#endif /* !defined(TOR_CIRCPATHBIAS_H) */ diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 16cef0e56b..b36fed63b3 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -46,6 +46,7 @@ #include "crypto.h" #include "directory.h" #include "entrynodes.h" +#include "hs_ntor.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -70,10 +71,15 @@ static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, static int circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, int relayed); -static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); +static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit, + int is_hs_v3_rp_circuit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); +static int circuit_send_first_onion_skin(origin_circuit_t *circ); +static int circuit_build_no_more_hops(origin_circuit_t *circ); +static int circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop); /** This function tries to get a channel to the specified endpoint, * and then calls command_setup_channel() to give it the right @@ -284,14 +290,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } } else { /* ! verbose_names */ - node = node_get_by_id(id); - if (node && node_is_named(node)) { - elt = tor_strdup(node_get_nickname(node)); - } else { - elt = tor_malloc(HEX_DIGEST_LEN+2); - elt[0] = '$'; - base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); - } + elt = tor_malloc(HEX_DIGEST_LEN+2); + elt[0] = '$'; + base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } tor_assert(elt); if (verbose) { @@ -500,10 +501,15 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags) { origin_circuit_t *circ; int err_reason = 0; + int is_hs_v3_rp_circuit = 0; + + if (flags & CIRCLAUNCH_IS_V3_RP) { + is_hs_v3_rp_circuit = 1; + } circ = origin_circuit_init(purpose, flags); - if (onion_pick_cpath_exit(circ, exit_ei) < 0 || + if (onion_pick_cpath_exit(circ, exit_ei, is_hs_v3_rp_circuit) < 0 || onion_populate_cpath(circ) < 0) { circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOPATH); return NULL; @@ -912,234 +918,275 @@ circuit_purpose_may_omit_guard(int purpose) * If circ's first hop is closed, then we need to build a create * cell and send it forward. * - * Otherwise, we need to build a relay extend cell and send it - * forward. + * Otherwise, if circ's cpath still has any non-open hops, we need to + * build a relay extend cell and send it forward to the next non-open hop. + * + * If all hops on the cpath are open, we're done building the circuit + * and we should do housekeeping for the newly opened circuit. * * Return -reason if we want to tear down circ, else return 0. */ int circuit_send_next_onion_skin(origin_circuit_t *circ) { - crypt_path_t *hop; - const node_t *node; - tor_assert(circ); if (circ->cpath->state == CPATH_STATE_CLOSED) { - /* This is the first hop. */ - create_cell_t cc; - int fast; - int len; - log_debug(LD_CIRC,"First skin; sending create cell."); - memset(&cc, 0, sizeof(cc)); - if (circ->build_state->onehop_tunnel) - control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); - else { - control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - - /* If this is not a one-hop tunnel, the channel is being used - * for traffic that wants anonymity and protection from traffic - * analysis (such as netflow record retention). That means we want - * to pad it. - */ - if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) - circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; - } + /* Case one: we're on the first hop. */ + return circuit_send_first_onion_skin(circ); + } - node = node_get_by_id(circ->base_.n_chan->identity_digest); - fast = should_use_create_fast_for_circuit(circ); - if (!fast) { - /* We are an OR and we know the right onion key: we should - * send a create cell. - */ - circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, - circ->cpath->extend_info); - } else { - /* We are not an OR, and we're building the first hop of a circuit to a - * new OR: we can be speedy and use CREATE_FAST to save an RSA operation - * and a DH operation. */ - cc.cell_type = CELL_CREATE_FAST; - cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; - } + tor_assert(circ->cpath->state == CPATH_STATE_OPEN); + tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); + crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath); - len = onion_skin_create(cc.handshake_type, - circ->cpath->extend_info, - &circ->cpath->handshake_state, - cc.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); - return - END_CIRC_REASON_INTERNAL; - } - cc.handshake_len = len; + if (hop) { + /* Case two: we're on a hop after the first. */ + return circuit_send_intermediate_onion_skin(circ, hop); + } - if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) - return - END_CIRC_REASON_RESOURCELIMIT; + /* Case three: the circuit is finished. Do housekeeping tasks on it. */ + return circuit_build_no_more_hops(circ); +} - circ->cpath->state = CPATH_STATE_AWAITING_KEYS; - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); - log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", - fast ? "CREATE_FAST" : "CREATE", - node ? node_describe(node) : "<unnamed>"); +/** + * Called from circuit_send_next_onion_skin() when we find ourselves connected + * to the first hop in <b>circ</b>: Send a CREATE or CREATE2 or CREATE_FAST + * cell to that hop. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_send_first_onion_skin(origin_circuit_t *circ) +{ + int fast; + int len; + const node_t *node; + create_cell_t cc; + memset(&cc, 0, sizeof(cc)); + + log_debug(LD_CIRC,"First skin; sending create cell."); + + if (circ->build_state->onehop_tunnel) { + control_event_bootstrap(BOOTSTRAP_STATUS_ONEHOP_CREATE, 0); } else { - extend_cell_t ec; - int len; - tor_assert(circ->cpath->state == CPATH_STATE_OPEN); - tor_assert(circ->base_.state == CIRCUIT_STATE_BUILDING); - log_debug(LD_CIRC,"starting to send subsequent skin."); - hop = onion_next_hop_in_cpath(circ->cpath); - memset(&ec, 0, sizeof(ec)); - if (!hop) { - /* done building the circuit. whew. */ - guard_usable_t r; - if (! circ->guard_state) { - if (circuit_get_cpath_len(circ) != 1 && - ! circuit_purpose_may_omit_guard(circ->base_.purpose) && - get_options()->UseEntryGuards) { - log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " - "guard state", - circuit_get_cpath_len(circ), circ, circ->base_.purpose); - } - r = GUARD_USABLE_NOW; - } else { - r = entry_guard_succeeded(&circ->guard_state); - } - const int is_usable_for_streams = (r == GUARD_USABLE_NOW); - if (r == GUARD_USABLE_NOW) { - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); - } else if (r == GUARD_MAYBE_USABLE_LATER) { - // Wait till either a better guard succeeds, or till - // all better guards fail. - circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); - } else { - tor_assert_nonfatal(r == GUARD_USABLE_NEVER); - return - END_CIRC_REASON_INTERNAL; - } + control_event_bootstrap(BOOTSTRAP_STATUS_CIRCUIT_CREATE, 0); - /* XXXX #21422 -- the rest of this branch needs careful thought! - * Some of the things here need to happen when a circuit becomes - * mechanically open; some need to happen when it is actually usable. - * I think I got them right, but more checking would be wise. -NM - */ + /* If this is not a one-hop tunnel, the channel is being used + * for traffic that wants anonymity and protection from traffic + * analysis (such as netflow record retention). That means we want + * to pad it. + */ + if (circ->base_.n_chan->channel_usage < CHANNEL_USED_FOR_FULL_CIRCS) + circ->base_.n_chan->channel_usage = CHANNEL_USED_FOR_FULL_CIRCS; + } - if (circuit_timeout_want_to_count_circ(circ)) { - struct timeval end; - long timediff; - tor_gettimeofday(&end); - timediff = tv_mdiff(&circ->base_.timestamp_began, &end); + node = node_get_by_id(circ->base_.n_chan->identity_digest); + fast = should_use_create_fast_for_circuit(circ); + if (!fast) { + /* We know the right onion key: we should send a create cell. */ + circuit_pick_create_handshake(&cc.cell_type, &cc.handshake_type, + circ->cpath->extend_info); + } else { + /* We don't know an onion key, so we need to fall back to CREATE_FAST. */ + cc.cell_type = CELL_CREATE_FAST; + cc.handshake_type = ONION_HANDSHAKE_TYPE_FAST; + } - /* - * If the circuit build time is much greater than we would have cut - * it off at, we probably had a suspend event along this codepath, - * and we should discard the value. - */ - if (timediff < 0 || - timediff > 2*get_circuit_build_close_time_ms()+1000) { - log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " - "Assuming clock jump. Purpose %d (%s)", timediff, - circ->base_.purpose, - circuit_purpose_to_string(circ->base_.purpose)); - } else if (!circuit_build_times_disabled(get_options())) { - /* Only count circuit times if the network is live */ - if (circuit_build_times_network_check_live( - get_circuit_build_times())) { - circuit_build_times_add_time(get_circuit_build_times_mutable(), - (build_time_t)timediff); - circuit_build_times_set_timeout(get_circuit_build_times_mutable()); - } - - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_build_times_network_circ_success( - get_circuit_build_times_mutable()); - } - } - } - log_info(LD_CIRC,"circuit built!"); - circuit_reset_failure_count(0); + len = onion_skin_create(cc.handshake_type, + circ->cpath->extend_info, + &circ->cpath->handshake_state, + cc.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create (first hop) failed."); + return - END_CIRC_REASON_INTERNAL; + } + cc.handshake_len = len; - if (circ->build_state->onehop_tunnel || circ->has_opened) { - control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); - } + if (circuit_deliver_create_cell(TO_CIRCUIT(circ), &cc, 0) < 0) + return - END_CIRC_REASON_RESOURCELIMIT; - pathbias_count_build_success(circ); - circuit_rep_hist_note_result(circ); - if (is_usable_for_streams) - circuit_has_opened(circ); /* do other actions as necessary */ - - if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { - const or_options_t *options = get_options(); - note_that_we_completed_a_circuit(); - /* FFFF Log a count of known routers here */ - log_notice(LD_GENERAL, - "Tor has successfully opened a circuit. " - "Looks like client functionality is working."); - if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) { - log_notice(LD_GENERAL, - "Tor has successfully opened a circuit. " - "Looks like client functionality is working."); - } - control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); - clear_broken_connection_map(1); - if (server_mode(options) && !check_whether_orport_reachable(options)) { - inform_testing_reachability(); - consider_testing_reachability(1, 1); - } + circ->cpath->state = CPATH_STATE_AWAITING_KEYS; + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING); + log_info(LD_CIRC,"First hop: finished sending %s cell to '%s'", + fast ? "CREATE_FAST" : "CREATE", + node ? node_describe(node) : "<unnamed>"); + return 0; +} + +/** + * Called from circuit_send_next_onion_skin() when we find that we have no + * more hops: mark the circuit as finished, and perform the necessary + * bookkeeping. Return 0 on success; -reason on failure (if the circuit + * should be torn down). + */ +static int +circuit_build_no_more_hops(origin_circuit_t *circ) +{ + guard_usable_t r; + if (! circ->guard_state) { + if (circuit_get_cpath_len(circ) != 1 && + ! circuit_purpose_may_omit_guard(circ->base_.purpose) && + get_options()->UseEntryGuards) { + log_warn(LD_BUG, "%d-hop circuit %p with purpose %d has no " + "guard state", + circuit_get_cpath_len(circ), circ, circ->base_.purpose); + } + r = GUARD_USABLE_NOW; + } else { + r = entry_guard_succeeded(&circ->guard_state); + } + const int is_usable_for_streams = (r == GUARD_USABLE_NOW); + if (r == GUARD_USABLE_NOW) { + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN); + } else if (r == GUARD_MAYBE_USABLE_LATER) { + // Wait till either a better guard succeeds, or till + // all better guards fail. + circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_GUARD_WAIT); + } else { + tor_assert_nonfatal(r == GUARD_USABLE_NEVER); + return - END_CIRC_REASON_INTERNAL; + } + + /* XXXX #21422 -- the rest of this branch needs careful thought! + * Some of the things here need to happen when a circuit becomes + * mechanically open; some need to happen when it is actually usable. + * I think I got them right, but more checking would be wise. -NM + */ + + if (circuit_timeout_want_to_count_circ(circ)) { + struct timeval end; + long timediff; + tor_gettimeofday(&end); + timediff = tv_mdiff(&circ->base_.timestamp_began, &end); + + /* + * If the circuit build time is much greater than we would have cut + * it off at, we probably had a suspend event along this codepath, + * and we should discard the value. + */ + if (timediff < 0 || + timediff > 2*get_circuit_build_close_time_ms()+1000) { + log_notice(LD_CIRC, "Strange value for circuit build time: %ldmsec. " + "Assuming clock jump. Purpose %d (%s)", timediff, + circ->base_.purpose, + circuit_purpose_to_string(circ->base_.purpose)); + } else if (!circuit_build_times_disabled(get_options())) { + /* Only count circuit times if the network is live */ + if (circuit_build_times_network_check_live( + get_circuit_build_times())) { + circuit_build_times_add_time(get_circuit_build_times_mutable(), + (build_time_t)timediff); + circuit_build_times_set_timeout(get_circuit_build_times_mutable()); } - /* We're done with measurement circuits here. Just close them */ - if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_build_times_network_circ_success( + get_circuit_build_times_mutable()); } - return 0; } - - if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { - log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); - return - END_CIRC_REASON_INTERNAL; + } + log_info(LD_CIRC,"circuit built!"); + circuit_reset_failure_count(0); + + if (circ->build_state->onehop_tunnel || circ->has_opened) { + control_event_bootstrap(BOOTSTRAP_STATUS_REQUESTING_STATUS, 0); + } + + pathbias_count_build_success(circ); + circuit_rep_hist_note_result(circ); + if (is_usable_for_streams) + circuit_has_opened(circ); /* do other actions as necessary */ + + if (!have_completed_a_circuit() && !circ->build_state->onehop_tunnel) { + const or_options_t *options = get_options(); + note_that_we_completed_a_circuit(); + /* FFFF Log a count of known routers here */ + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); + if (control_event_bootstrap(BOOTSTRAP_STATUS_DONE, 0) == 0) { + log_notice(LD_GENERAL, + "Tor has successfully opened a circuit. " + "Looks like client functionality is working."); } - - circuit_pick_extend_handshake(&ec.cell_type, - &ec.create_cell.cell_type, - &ec.create_cell.handshake_type, - hop->extend_info); - - tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); - ec.orport_ipv4.port = hop->extend_info->port; - tor_addr_make_unspec(&ec.orport_ipv6.addr); - memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); - /* Set the ED25519 identity too -- it will only get included - * in the extend2 cell if we're configured to use it, though. */ - ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity); - - len = onion_skin_create(ec.create_cell.handshake_type, - hop->extend_info, - &hop->handshake_state, - ec.create_cell.onionskin); - if (len < 0) { - log_warn(LD_CIRC,"onion_skin_create failed."); - return - END_CIRC_REASON_INTERNAL; + control_event_client_status(LOG_NOTICE, "CIRCUIT_ESTABLISHED"); + clear_broken_connection_map(1); + if (server_mode(options) && !check_whether_orport_reachable(options)) { + inform_testing_reachability(); + consider_testing_reachability(1, 1); } - ec.create_cell.handshake_len = len; + } - log_info(LD_CIRC,"Sending extend relay cell."); - { - uint8_t command = 0; - uint16_t payload_len=0; - uint8_t payload[RELAY_PAYLOAD_SIZE]; - if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { - log_warn(LD_CIRC,"Couldn't format extend cell"); - return -END_CIRC_REASON_INTERNAL; - } + /* We're done with measurement circuits here. Just close them */ + if (circ->base_.purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) { + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + } + return 0; +} - /* send it to hop->prev, because it will transfer - * it to a create cell and then send to hop */ - if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), - command, - (char*)payload, payload_len, - hop->prev) < 0) - return 0; /* circuit is closed */ +/** + * Called from circuit_send_next_onion_skin() when we find that we have a hop + * other than the first that we need to extend to: use <b>hop</b>'s + * information to extend the circuit another step. Return 0 on success; + * -reason on failure (if the circuit should be torn down). + */ +static int +circuit_send_intermediate_onion_skin(origin_circuit_t *circ, + crypt_path_t *hop) +{ + int len; + extend_cell_t ec; + memset(&ec, 0, sizeof(ec)); + + log_debug(LD_CIRC,"starting to send subsequent skin."); + + if (tor_addr_family(&hop->extend_info->addr) != AF_INET) { + log_warn(LD_BUG, "Trying to extend to a non-IPv4 address."); + return - END_CIRC_REASON_INTERNAL; + } + + circuit_pick_extend_handshake(&ec.cell_type, + &ec.create_cell.cell_type, + &ec.create_cell.handshake_type, + hop->extend_info); + + tor_addr_copy(&ec.orport_ipv4.addr, &hop->extend_info->addr); + ec.orport_ipv4.port = hop->extend_info->port; + tor_addr_make_unspec(&ec.orport_ipv6.addr); + memcpy(ec.node_id, hop->extend_info->identity_digest, DIGEST_LEN); + /* Set the ED25519 identity too -- it will only get included + * in the extend2 cell if we're configured to use it, though. */ + ed25519_pubkey_copy(&ec.ed_pubkey, &hop->extend_info->ed_identity); + + len = onion_skin_create(ec.create_cell.handshake_type, + hop->extend_info, + &hop->handshake_state, + ec.create_cell.onionskin); + if (len < 0) { + log_warn(LD_CIRC,"onion_skin_create failed."); + return - END_CIRC_REASON_INTERNAL; + } + ec.create_cell.handshake_len = len; + + log_info(LD_CIRC,"Sending extend relay cell."); + { + uint8_t command = 0; + uint16_t payload_len=0; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + if (extend_cell_format(&command, &payload_len, payload, &ec)<0) { + log_warn(LD_CIRC,"Couldn't format extend cell"); + return -END_CIRC_REASON_INTERNAL; } - hop->state = CPATH_STATE_AWAITING_KEYS; + + /* send it to hop->prev, because that relay will transfer + * it to a create cell and then send to hop */ + if (relay_send_command_from_edge(0, TO_CIRCUIT(circ), + command, + (char*)payload, payload_len, + hop->prev) < 0) + return 0; /* circuit is closed */ } + hop->state = CPATH_STATE_AWAITING_KEYS; return 0; } @@ -1325,40 +1372,77 @@ circuit_extend(cell_t *cell, circuit_t *circ) return 0; } -/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in - * key_data. key_data must contain CPATH_KEY_MATERIAL bytes, which are - * used as follows: +/** Initialize cpath-\>{f|b}_{crypto|digest} from the key material in key_data. + * + * If <b>is_hs_v3</b> is set, this cpath will be used for next gen hidden + * service circuits and <b>key_data</b> must be at least + * HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN bytes in length. + * + * If <b>is_hs_v3</b> is not set, key_data must contain CPATH_KEY_MATERIAL_LEN + * bytes, which are used as follows: * - 20 to initialize f_digest * - 20 to initialize b_digest * - 16 to key f_crypto * - 16 to key b_crypto * * (If 'reverse' is true, then f_XX and b_XX are swapped.) + * + * Return 0 if init was successful, else -1 if it failed. */ int -circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, - int reverse) +circuit_init_cpath_crypto(crypt_path_t *cpath, + const char *key_data, size_t key_data_len, + int reverse, int is_hs_v3) { crypto_digest_t *tmp_digest; crypto_cipher_t *tmp_crypto; + size_t digest_len = 0; + size_t cipher_key_len = 0; tor_assert(cpath); tor_assert(key_data); tor_assert(!(cpath->f_crypto || cpath->b_crypto || cpath->f_digest || cpath->b_digest)); - cpath->f_digest = crypto_digest_new(); - crypto_digest_add_bytes(cpath->f_digest, key_data, DIGEST_LEN); - cpath->b_digest = crypto_digest_new(); - crypto_digest_add_bytes(cpath->b_digest, key_data+DIGEST_LEN, DIGEST_LEN); + /* Basic key size validation */ + if (is_hs_v3 && BUG(key_data_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) { + return -1; + } else if (!is_hs_v3 && BUG(key_data_len != CPATH_KEY_MATERIAL_LEN)) { + return -1; + } - if (!(cpath->f_crypto = - crypto_cipher_new(key_data+(2*DIGEST_LEN)))) { + /* If we are using this cpath for next gen onion services use SHA3-256, + otherwise use good ol' SHA1 */ + if (is_hs_v3) { + digest_len = DIGEST256_LEN; + cipher_key_len = CIPHER256_KEY_LEN; + cpath->f_digest = crypto_digest256_new(DIGEST_SHA3_256); + cpath->b_digest = crypto_digest256_new(DIGEST_SHA3_256); + } else { + digest_len = DIGEST_LEN; + cipher_key_len = CIPHER_KEY_LEN; + cpath->f_digest = crypto_digest_new(); + cpath->b_digest = crypto_digest_new(); + } + + tor_assert(digest_len != 0); + tor_assert(cipher_key_len != 0); + const int cipher_key_bits = (int) cipher_key_len * 8; + + crypto_digest_add_bytes(cpath->f_digest, key_data, digest_len); + crypto_digest_add_bytes(cpath->b_digest, key_data+digest_len, digest_len); + + cpath->f_crypto = crypto_cipher_new_with_bits(key_data+(2*digest_len), + cipher_key_bits); + if (!cpath->f_crypto) { log_warn(LD_BUG,"Forward cipher initialization failed."); return -1; } - if (!(cpath->b_crypto = - crypto_cipher_new(key_data+(2*DIGEST_LEN)+CIPHER_KEY_LEN))) { + + cpath->b_crypto = crypto_cipher_new_with_bits( + key_data+(2*digest_len)+cipher_key_len, + cipher_key_bits); + if (!cpath->b_crypto) { log_warn(LD_BUG,"Backward cipher initialization failed."); return -1; } @@ -1424,7 +1508,7 @@ circuit_finish_handshake(origin_circuit_t *circ, onion_handshake_state_release(&hop->handshake_state); - if (circuit_init_cpath_crypto(hop, keys, 0)<0) { + if (circuit_init_cpath_crypto(hop, keys, sizeof(keys), 0, 0)<0) { return -END_CIRC_REASON_TORPROTOCOL; } @@ -1482,7 +1566,7 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) log_info(LD_CIRC, "finished"); return 0; -#endif +#endif /* 0 */ } /** Given a response payload and keys, initialize, then send a created @@ -1491,12 +1575,14 @@ circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason) int onionskin_answer(or_circuit_t *circ, const created_cell_t *created_cell, - const char *keys, + const char *keys, size_t keys_len, const uint8_t *rend_circ_nonce) { cell_t cell; crypt_path_t *tmp_cpath; + tor_assert(keys_len == CPATH_KEY_MATERIAL_LEN); + if (created_cell_format(&cell, created_cell) < 0) { log_warn(LD_BUG,"couldn't format created cell (type=%d, len=%d)", (int)created_cell->cell_type, (int)created_cell->handshake_len); @@ -1512,7 +1598,7 @@ onionskin_answer(or_circuit_t *circ, log_debug(LD_CIRC,"init digest forward 0x%.8x, backward 0x%.8x.", (unsigned int)get_uint32(keys), (unsigned int)get_uint32(keys+20)); - if (circuit_init_cpath_crypto(tmp_cpath, keys, 0)<0) { + if (circuit_init_cpath_crypto(tmp_cpath, keys, keys_len, 0, 0)<0) { log_warn(LD_BUG,"Circuit initialization failed"); tor_free(tmp_cpath); return -1; @@ -1526,12 +1612,12 @@ onionskin_answer(or_circuit_t *circ, memcpy(circ->rend_circ_nonce, rend_circ_nonce, DIGEST_LEN); - circ->is_first_hop = (created_cell->cell_type == CELL_CREATED_FAST); + int used_create_fast = (created_cell->cell_type == CELL_CREATED_FAST); append_cell_to_circuit_queue(TO_CIRCUIT(circ), circ->p_chan, &cell, CELL_DIRECTION_IN, 0); log_debug(LD_CIRC,"Finished sending '%s' cell.", - circ->is_first_hop ? "created_fast" : "created"); + used_create_fast ? "created_fast" : "created"); /* Ignore the local bit when ExtendAllowPrivateAddresses is set: * it violates the assumption that private addresses are local. @@ -1956,9 +2042,10 @@ choose_good_exit_server_general(int need_uptime, int need_capacity) } if (options->ExitNodes) { log_warn(LD_CIRC, - "No specified %sexit routers seem to be running: " + "No exits in ExitNodes%s seem to be running: " "can't choose an exit.", - options->ExcludeExitNodesUnion_ ? "non-excluded " : ""); + options->ExcludeExitNodesUnion_ ? + ", except possibly those excluded by your configuration, " : ""); } return NULL; } @@ -2017,7 +2104,7 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags, return rp_node; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) */ /* Pick a Rendezvous Point for our HS circuits according to <b>flags</b>. */ static const node_t * @@ -2053,7 +2140,7 @@ pick_rendezvous_node(router_crn_flags_t flags) "Unable to find a random rendezvous point that is reachable via " "a direct connection, falling back to a 3-hop path."); } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ return router_choose_random_node(NULL, options->ExcludeNodes, flags); } @@ -2070,7 +2157,8 @@ pick_rendezvous_node(router_crn_flags_t flags) */ static const node_t * choose_good_exit_server(uint8_t purpose, - int need_uptime, int need_capacity, int is_internal) + int need_uptime, int need_capacity, int is_internal, + int need_hs_v3) { const or_options_t *options = get_options(); router_crn_flags_t flags = CRN_NEED_DESC; @@ -2078,6 +2166,8 @@ choose_good_exit_server(uint8_t purpose, flags |= CRN_NEED_UPTIME; if (need_capacity) flags |= CRN_NEED_CAPACITY; + if (need_hs_v3) + flags |= CRN_RENDEZVOUS_V3; switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: @@ -2177,9 +2267,15 @@ warn_if_last_router_excluded(origin_circuit_t *circ, /** Decide a suitable length for circ's cpath, and pick an exit * router (or use <b>exit</b> if provided). Store these in the - * cpath. Return 0 if ok, -1 if circuit should be closed. */ + * cpath. + * + * If <b>is_hs_v3_rp_circuit</b> is set, then this exit should be suitable to + * be used as an HS v3 rendezvous point. + * + * Return 0 if ok, -1 if circuit should be closed. */ static int -onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) +onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei, + int is_hs_v3_rp_circuit) { cpath_build_state_t *state = circ->build_state; @@ -2203,7 +2299,8 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) } else { /* we have to decide one */ const node_t *node = choose_good_exit_server(circ->base_.purpose, state->need_uptime, - state->need_capacity, state->is_internal); + state->need_capacity, state->is_internal, + is_hs_v3_rp_circuit); if (!node) { log_warn(LD_CIRC,"Failed to choose an exit server"); return -1; @@ -2311,6 +2408,30 @@ onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop) } } +#ifdef TOR_UNIT_TESTS + +/** Unittest helper function: Count number of hops in cpath linked list. */ +unsigned int +cpath_get_n_hops(crypt_path_t **head_ptr) +{ + unsigned int n_hops = 0; + crypt_path_t *tmp; + + if (!*head_ptr) { + return 0; + } + + tmp = *head_ptr; + do { + n_hops++; + tmp = tmp->next; + } while (tmp != *head_ptr); + + return n_hops; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + /** A helper function used by onion_extend_cpath(). Use <b>purpose</b> * and <b>state</b> and the cpath <b>head</b> (currently populated only * to length <b>cur_len</b> to decide a suitable middle hop for a @@ -2332,8 +2453,8 @@ choose_good_middle_server(uint8_t purpose, tor_assert(CIRCUIT_PURPOSE_MIN_ <= purpose && purpose <= CIRCUIT_PURPOSE_MAX_); - log_debug(LD_CIRC, "Contemplating intermediate hop %d: random choice.", - cur_len); + log_debug(LD_CIRC, "Contemplating intermediate hop #%d: random choice.", + cur_len+1); excluded = smartlist_new(); if ((r = build_state_get_exit_node(state))) { nodelist_add_node_and_family(excluded, r); @@ -2358,9 +2479,6 @@ choose_good_middle_server(uint8_t purpose, * router (if we're an OR), and respect firewall settings; if we're * configured to use entry guards, return one. * - * If <b>state</b> is NULL, we're choosing a router to serve as an entry - * guard, not for any particular circuit. - * * Set *<b>guard_state_out</b> to information about the guard that * we're selecting, which we'll use later to remember whether the * guard worked or not. @@ -2378,6 +2496,11 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state, CRN_DIRECT_CONN); const node_t *node; + /* Once we used this function to select a node to be a guard. We had + * 'state == NULL' be the signal for that. But we don't do that any more. + */ + tor_assert_nonfatal(state); + if (state && options->UseEntryGuards && (purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) { /* This request is for an entry server to use for a regular circuit, @@ -2467,12 +2590,12 @@ onion_extend_cpath(origin_circuit_t *circ) } if (!info) { - log_warn(LD_CIRC,"Failed to find node for hop %d of our path. Discarding " - "this circuit.", cur_len); + log_warn(LD_CIRC,"Failed to find node for hop #%d of our path. Discarding " + "this circuit.", cur_len+1); return -1; } - log_debug(LD_CIRC,"Chose router %s for hop %d (exit is %s)", + log_debug(LD_CIRC,"Chose router %s for hop #%d (exit is %s)", extend_info_describe(info), cur_len+1, build_state_get_exit_nickname(state)); @@ -2580,7 +2703,7 @@ extend_info_from_node(const node_t *node, int for_direct_connect) ed_pubkey = node_get_ed25519_id(node); } else if (node_get_ed25519_id(node)) { log_info(LD_CIRC, "Not including the ed25519 ID for %s, since it won't " - " be able to authenticate it.", + "be able to authenticate it.", node_describe(node)); } diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h index 45d9b2fb75..b8a651e055 100644 --- a/src/or/circuitbuild.h +++ b/src/or/circuitbuild.h @@ -31,8 +31,9 @@ int circuit_timeout_want_to_count_circ(origin_circuit_t *circ); int circuit_send_next_onion_skin(origin_circuit_t *circ); void circuit_note_clock_jumped(int seconds_elapsed); int circuit_extend(cell_t *cell, circuit_t *circ); -int circuit_init_cpath_crypto(crypt_path_t *cpath, const char *key_data, - int reverse); +int circuit_init_cpath_crypto(crypt_path_t *cpath, + const char *key_data, size_t key_data_len, + int reverse, int is_hs_v3); struct created_cell_t; int circuit_finish_handshake(origin_circuit_t *circ, const struct created_cell_t *created_cell); @@ -40,7 +41,7 @@ int circuit_truncated(origin_circuit_t *circ, crypt_path_t *layer, int reason); int onionskin_answer(or_circuit_t *circ, const struct created_cell_t *created_cell, - const char *keys, + const char *keys, size_t keys_len, const uint8_t *rend_circ_nonce); MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now, int *need_uptime, @@ -83,9 +84,11 @@ MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes)); #if defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) STATIC const node_t *pick_tor2web_rendezvous_node(router_crn_flags_t flags, const or_options_t *options); -#endif +unsigned int cpath_get_n_hops(crypt_path_t **head_ptr); -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) || defined(TOR_UNIT_TESTS) */ -#endif +#endif /* defined(CIRCUITBUILD_PRIVATE) */ + +#endif /* !defined(TOR_CIRCUITBUILD_H) */ diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 6ffaabc16f..d442887c9e 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -65,8 +65,9 @@ #include "control.h" #include "entrynodes.h" #include "main.h" +#include "hs_circuit.h" #include "hs_circuitmap.h" -#include "hs_common.h" +#include "hs_ident.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -924,14 +925,24 @@ circuit_clear_testing_cell_stats(circuit_t *circ) STATIC void circuit_free(circuit_t *circ) { + circid_t n_circ_id = 0; void *mem; size_t memlen; int should_free = 1; if (!circ) return; + /* We keep a copy of this so we can log its value before it gets unset. */ + n_circ_id = circ->n_circ_id; + circuit_clear_testing_cell_stats(circ); + /* Cleanup circuit from anything HS v3 related. We also do this when the + * circuit is closed. This is to avoid any code path that free registered + * circuits without closing them before. This needs to be done before the + * hs identifier is freed. */ + hs_circ_cleanup(circ); + if (CIRCUIT_IS_ORIGIN(circ)) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); mem = ocirc; @@ -958,6 +969,11 @@ circuit_free(circuit_t *circ) crypto_pk_free(ocirc->intro_key); rend_data_free(ocirc->rend_data); + /* Finally, free the identifier of the circuit and nullify it so multiple + * cleanup will work. */ + hs_ident_circuit_free(ocirc->hs_ident); + ocirc->hs_ident = NULL; + tor_free(ocirc->dest_address); if (ocirc->socks_username) { memwipe(ocirc->socks_username, 0x12, ocirc->socks_username_len); @@ -1015,15 +1031,15 @@ circuit_free(circuit_t *circ) /* Remove from map. */ circuit_set_n_circid_chan(circ, 0, NULL); - /* Clear HS circuitmap token from this circ (if any) */ - if (circ->hs_token) { - hs_circuitmap_remove_circuit(circ); - } - /* Clear cell queue _after_ removing it from the map. Otherwise our * "active" checks will be violated. */ cell_queue_clear(&circ->n_chan_cells); + log_info(LD_CIRC, "Circuit %u (id: %" PRIu32 ") has been freed.", + n_circ_id, + CIRCUIT_IS_ORIGIN(circ) ? + TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0); + if (should_free) { memwipe(mem, 0xAA, memlen); /* poison memory */ tor_free(mem); @@ -1431,7 +1447,7 @@ circuit_unlink_all_from_channel(channel_t *chan, int reason) smartlist_free(detached_2); } } -#endif +#endif /* defined(DEBUG_CIRCUIT_UNLINK_ALL) */ SMARTLIST_FOREACH_BEGIN(detached, circuit_t *, circ) { int mark = 0; @@ -1530,6 +1546,41 @@ circuit_get_next_service_intro_circ(origin_circuit_t *start) return NULL; } +/** Return the first service rendezvous circuit originating from the global + * circuit list after <b>start</b> or at the start of the list if <b>start</b> + * is NULL. Return NULL if no circuit is found. + * + * A service rendezvous point circuit has a purpose of either + * CIRCUIT_PURPOSE_S_CONNECT_REND or CIRCUIT_PURPOSE_S_REND_JOINED. This does + * not return a circuit marked for close and its state must be open. */ +origin_circuit_t * +circuit_get_next_service_rp_circ(origin_circuit_t *start) +{ + int idx = 0; + smartlist_t *lst = circuit_get_global_list(); + + if (start) { + idx = TO_CIRCUIT(start)->global_circuitlist_idx + 1; + } + + for ( ; idx < smartlist_len(lst); ++idx) { + circuit_t *circ = smartlist_get(lst, idx); + + /* Ignore a marked for close circuit or purpose not matching a service + * intro point or if the state is not open. */ + if (circ->marked_for_close || circ->state != CIRCUIT_STATE_OPEN || + (circ->purpose != CIRCUIT_PURPOSE_S_CONNECT_REND && + circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED)) { + continue; + } + /* The purposes we are looking for are only for origin circuits so the + * following is valid. */ + return TO_ORIGIN_CIRCUIT(circ); + } + /* Not found. */ + return NULL; +} + /** Return the first circuit originating here in global_circuitlist after * <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if * set) matches the private key digest of the rend data associated with the @@ -1571,6 +1622,30 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, return NULL; } +/** We might cannibalize this circuit: Return true if its last hop can be used + * as a v3 rendezvous point. */ +static int +circuit_can_be_cannibalized_for_v3_rp(const origin_circuit_t *circ) +{ + if (!circ->build_state) { + return 0; + } + + extend_info_t *chosen_exit = circ->build_state->chosen_exit; + if (BUG(!chosen_exit)) { + return 0; + } + + const node_t *rp_node = node_get_by_id(chosen_exit->identity_digest); + if (rp_node) { + if (node_supports_v3_rendezvous_point(rp_node)) { + return 1; + } + } + + return 0; +} + /** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL, * has a timestamp_dirty value of 0, has flags matching the CIRCLAUNCH_* * flags in <b>flags</b>, and if info is defined, does not already use info @@ -1653,6 +1728,14 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, hop = hop->next; } while (hop != circ->cpath); } + + if ((flags & CIRCLAUNCH_IS_V3_RP) && + !circuit_can_be_cannibalized_for_v3_rp(circ)) { + log_debug(LD_GENERAL, "Skipping uncannibalizable circuit for v3 " + "rendezvous point."); + goto next; + } + if (!best || (best->build_state->need_uptime && !need_uptime)) best = circ; next: ; @@ -1838,10 +1921,20 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, } } + /* Notify the HS subsystem that this circuit is closing. */ + hs_circ_cleanup(circ); + if (circuits_pending_close == NULL) circuits_pending_close = smartlist_new(); smartlist_add(circuits_pending_close, circ); + + log_info(LD_GENERAL, "Circuit %u (id: %" PRIu32 ") marked for close at " + "%s:%d (orig reason: %d, new reason: %d)", + circ->n_circ_id, + CIRCUIT_IS_ORIGIN(circ) ? + TO_ORIGIN_CIRCUIT(circ)->global_identifier : 0, + file, line, orig_reason, reason); } /** Called immediately before freeing a marked circuit <b>circ</b> from @@ -1916,8 +2009,8 @@ circuit_about_to_free(circuit_t *circ) int timed_out = (reason == END_CIRC_REASON_TIMEOUT); tor_assert(circ->state == CIRCUIT_STATE_OPEN); tor_assert(ocirc->build_state->chosen_exit); - tor_assert(ocirc->rend_data); - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { /* treat this like getting a nack from it */ log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", safe_str_client(rend_data_get_address(ocirc->rend_data)), @@ -1933,7 +2026,8 @@ circuit_about_to_free(circuit_t *circ) reason != END_CIRC_REASON_TIMEOUT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); if (ocirc->build_state->chosen_exit && ocirc->rend_data) { - if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { + if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT && + ocirc->rend_data) { log_info(LD_REND, "Failed intro circ %s to %s " "(building circuit to intro point). " "Marking intro point as possibly unreachable.", @@ -2387,8 +2481,8 @@ assert_cpath_ok(const crypt_path_t *cp) /** Verify that circuit <b>c</b> has all of its invariants * correct. Trigger an assert if anything is invalid. */ -void -assert_circuit_ok(const circuit_t *c) +MOCK_IMPL(void, +assert_circuit_ok,(const circuit_t *c)) { edge_connection_t *conn; const or_circuit_t *or_circ = NULL; diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index d647062f46..4190c1f82e 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -13,6 +13,7 @@ #define TOR_CIRCUITLIST_H #include "testsupport.h" +#include "hs_ident.h" MOCK_DECL(smartlist_t *, circuit_get_global_list, (void)); smartlist_t *circuit_get_global_origin_circuit_list(void); @@ -48,6 +49,8 @@ origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, const uint8_t *digest, uint8_t purpose); origin_circuit_t *circuit_get_next_service_intro_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_rp_circ(origin_circuit_t *start); +origin_circuit_t *circuit_get_next_service_hsdir_circ(origin_circuit_t *start); origin_circuit_t *circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, int flags); void circuit_mark_all_unused_circs(void); @@ -65,7 +68,7 @@ int circuit_count_pending_on_channel(channel_t *chan); circuit_mark_for_close_((c), (reason), __LINE__, SHORT_FILE__) void assert_cpath_layer_ok(const crypt_path_t *cp); -void assert_circuit_ok(const circuit_t *c); +MOCK_DECL(void, assert_circuit_ok,(const circuit_t *c)); void circuit_free_all(void); void circuits_handle_oom(size_t current_allocation); @@ -83,7 +86,7 @@ STATIC size_t n_cells_in_circ_queues(const circuit_t *c); STATIC uint32_t circuit_max_queued_data_age(const circuit_t *c, uint32_t now); STATIC uint32_t circuit_max_queued_cell_age(const circuit_t *c, uint32_t now); STATIC uint32_t circuit_max_queued_item_age(const circuit_t *c, uint32_t now); -#endif +#endif /* defined(CIRCUITLIST_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITLIST_H) */ diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 6c825011b6..2cc0e8fc44 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -185,7 +185,7 @@ struct chanid_circid_muxinfo_t { circuitmux_assert_okay(cmux) #else #define circuitmux_assert_okay_paranoid(cmux) -#endif +#endif /* defined(CMUX_PARANOIA) */ /* * Static function declarations @@ -261,13 +261,11 @@ circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, if (circ->n_mux == cmux) { next_p = &(circ->next_active_on_n_chan); prev_p = &(circ->prev_active_on_n_chan); - direction = CELL_DIRECTION_OUT; } else { or_circ = TO_OR_CIRCUIT(circ); tor_assert(or_circ->p_mux == cmux); next_p = &(or_circ->next_active_on_p_chan); prev_p = &(or_circ->prev_active_on_p_chan); - direction = CELL_DIRECTION_IN; } } tor_assert(next_p); @@ -1646,7 +1644,6 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) circid_t circ_id; circuit_t *circ; or_circuit_t *or_circ; - unsigned int circ_is_active; circuit_t **next_p, **prev_p; unsigned int n_circuits, n_active_circuits, n_cells; @@ -1670,8 +1667,6 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) tor_assert(chan); circ = circuit_get_by_circid_channel_even_if_marked(circ_id, chan); tor_assert(circ); - /* Clear the circ_is_active bit to start */ - circ_is_active = 0; /* Assert that we know which direction this is going */ tor_assert((*i)->muxinfo.direction == CELL_DIRECTION_OUT || @@ -1698,7 +1693,7 @@ circuitmux_assert_okay_pass_one(circuitmux_t *cmux) * Should this circuit be active? I.e., does the mux know about > 0 * cells on it? */ - circ_is_active = ((*i)->muxinfo.cell_count > 0); + const int circ_is_active = ((*i)->muxinfo.cell_count > 0); /* It should be in the linked list iff it's active */ if (circ_is_active) { @@ -1750,7 +1745,6 @@ circuitmux_assert_okay_pass_two(circuitmux_t *cmux) circuit_t **next_p, **prev_p; channel_t *chan; unsigned int n_active_circuits = 0; - cell_direction_t direction; chanid_circid_muxinfo_t search, *hashent = NULL; tor_assert(cmux); @@ -1769,7 +1763,7 @@ circuitmux_assert_okay_pass_two(circuitmux_t *cmux) curr_or_circ = NULL; next_circ = NULL; next_p = prev_p = NULL; - direction = 0; + cell_direction_t direction; /* Figure out if this is n_mux or p_mux */ if (cmux == curr_circ->n_mux) { diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index bf93bb8cbf..a95edb99d8 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -156,5 +156,5 @@ void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, MOCK_DECL(int, circuitmux_compare_muxes, (circuitmux_t *cmux_1, circuitmux_t *cmux_2)); -#endif /* TOR_CIRCUITMUX_H */ +#endif /* !defined(TOR_CIRCUITMUX_H) */ diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index c2440b13f0..fde2d22a89 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -731,7 +731,7 @@ add_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) smartlist_pqueue_add(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), + offsetof(cell_ewma_t, heap_index), ewma); } @@ -746,7 +746,7 @@ remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma) smartlist_pqueue_remove(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index), + offsetof(cell_ewma_t, heap_index), ewma); } @@ -760,6 +760,6 @@ pop_first_cell_ewma(ewma_policy_data_t *pol) return smartlist_pqueue_pop(pol->active_circuit_pqueue, compare_cell_ewma_counts, - STRUCT_OFFSET(cell_ewma_t, heap_index)); + offsetof(cell_ewma_t, heap_index)); } diff --git a/src/or/circuitmux_ewma.h b/src/or/circuitmux_ewma.h index 1f04408789..8f4e57865e 100644 --- a/src/or/circuitmux_ewma.h +++ b/src/or/circuitmux_ewma.h @@ -20,5 +20,5 @@ unsigned int cell_ewma_get_tick(void); void cell_ewma_set_scale_factor(const or_options_t *options, const networkstatus_t *consensus); -#endif /* TOR_CIRCUITMUX_EWMA_H */ +#endif /* !defined(TOR_CIRCUITMUX_EWMA_H) */ diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 51d580a1a4..b8421a3c7e 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -60,7 +60,7 @@ static circuit_build_times_t circ_times; static int unit_tests = 0; #else #define unit_tests 0 -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Return a pointer to the data structure describing our current circuit * build time history and computations. */ @@ -148,7 +148,7 @@ circuit_build_times_disabled_(const or_options_t *options, "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); -#endif +#endif /* 0 */ return 1; } else { #if 0 @@ -157,7 +157,7 @@ circuit_build_times_disabled_(const or_options_t *options, "Consensus=%d, Config=%d, AuthDir=%d, StateFile=%d", consensus_disabled, config_disabled, dirauth_disabled, state_disabled); -#endif +#endif /* 0 */ return 0; } } @@ -431,9 +431,14 @@ circuit_build_times_new_consensus_params(circuit_build_times_t *cbt, if (num > 0) { if (num != cbt->liveness.num_recent_circs) { int8_t *recent_circs; - log_notice(LD_CIRC, "The Tor Directory Consensus has changed how many " - "circuits we must track to detect network failures from %d " - "to %d.", cbt->liveness.num_recent_circs, num); + if (cbt->liveness.num_recent_circs > 0) { + log_notice(LD_CIRC, "The Tor Directory Consensus has changed how " + "many circuits we must track to detect network failures " + "from %d to %d.", cbt->liveness.num_recent_circs, num); + } else { + log_notice(LD_CIRC, "Upon receiving a consensus directory, " + "re-enabling circuit-based network failure detection."); + } tor_assert(cbt->liveness.timeouts_after_firsthop || cbt->liveness.num_recent_circs == 0); @@ -608,7 +613,7 @@ circuit_build_times_rewind_history(circuit_build_times_t *cbt, int n) "Rewound history by %d places. Current index: %d. " "Total: %d", n, cbt->build_times_idx, cbt->total_build_times); } -#endif +#endif /* 0 */ /** * Add a new build time value <b>time</b> to the set of build times. Time @@ -676,7 +681,7 @@ circuit_build_times_min(circuit_build_times_t *cbt) } return min_build_time; } -#endif +#endif /* 0 */ /** * Calculate and return a histogram for the set of build times. @@ -910,7 +915,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, int tot_values = 0; uint32_t loaded_cnt = 0, N = 0; config_line_t *line; - unsigned int i; + int i; build_time_t *loaded_times; int err = 0; circuit_build_times_init(cbt); @@ -939,7 +944,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, uint32_t count, k; build_time_t ms; int ok; - ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0, + ms = (build_time_t)tor_parse_ulong(ms_str, 10, 0, CBT_BUILD_TIME_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -949,7 +954,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, smartlist_free(args); break; } - count = (uint32_t)tor_parse_ulong(count_str, 0, 0, + count = (uint32_t)tor_parse_ulong(count_str, 10, 0, UINT32_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -960,8 +965,8 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, break; } - if (loaded_cnt+count+state->CircuitBuildAbandonedCount - > state->TotalBuildTimes) { + if (loaded_cnt+count+ (unsigned)state->CircuitBuildAbandonedCount + > (unsigned) state->TotalBuildTimes) { log_warn(LD_CIRC, "Too many build times in state file. " "Stopping short before %d", @@ -986,7 +991,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, loaded_times[loaded_cnt++] = CBT_BUILD_ABANDONED; } - if (loaded_cnt != state->TotalBuildTimes) { + if (loaded_cnt != (unsigned)state->TotalBuildTimes) { log_warn(LD_CIRC, "Corrupt state file? Build times count mismatch. " "Read %d times, but file says %d", loaded_cnt, @@ -1165,7 +1170,7 @@ circuit_build_times_cdf(circuit_build_times_t *cbt, double x) tor_assert(0 <= ret && ret <= 1.0); return ret; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** @@ -1200,7 +1205,7 @@ circuit_build_times_generate_sample(circuit_build_times_t *cbt, tor_assert(ret > 0); return ret; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ #ifdef TOR_UNIT_TESTS /** @@ -1223,7 +1228,7 @@ circuit_build_times_initial_alpha(circuit_build_times_t *cbt, (tor_mathlog(cbt->Xm)-tor_mathlog(timeout_ms)); tor_assert(cbt->alpha > 0); } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** * Returns true if we need circuits to be built @@ -1682,7 +1687,7 @@ circuitbuild_running_unit_tests(void) { unit_tests = 1; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ void circuit_build_times_update_last_circ(circuit_build_times_t *cbt) diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h index 8a1dec4bfd..92dc6405ba 100644 --- a/src/or/circuitstats.h +++ b/src/or/circuitstats.h @@ -54,7 +54,7 @@ STATIC void circuit_build_times_reset(circuit_build_times_t *cbt); /* Network liveness functions */ STATIC int circuit_build_times_network_check_changed( circuit_build_times_t *cbt); -#endif +#endif /* defined(CIRCUITSTATS_PRIVATE) */ #ifdef TOR_UNIT_TESTS build_time_t circuit_build_times_generate_sample(circuit_build_times_t *cbt, @@ -63,7 +63,7 @@ double circuit_build_times_cdf(circuit_build_times_t *cbt, double x); void circuit_build_times_initial_alpha(circuit_build_times_t *cbt, double quantile, double time_ms); void circuitbuild_running_unit_tests(void); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /* Network liveness functions */ void circuit_build_times_network_is_live(circuit_build_times_t *cbt); @@ -95,7 +95,7 @@ struct circuit_build_times_s { /** How long we wait before actually closing the circuit. */ double close_ms; }; -#endif +#endif /* defined(CIRCUITSTATS_PRIVATE) */ -#endif +#endif /* !defined(TOR_CIRCUITSTATS_H) */ diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 9f9d3abf7c..7baa035696 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -42,6 +42,9 @@ #include "control.h" #include "entrynodes.h" #include "hs_common.h" +#include "hs_client.h" +#include "hs_circuit.h" +#include "hs_ident.h" #include "nodelist.h" #include "networkstatus.h" #include "policies.h" @@ -55,6 +58,36 @@ static void circuit_expire_old_circuits_clientside(void); static void circuit_increment_failure_count(void); +/** Check whether the hidden service destination of the stream at + * <b>edge_conn</b> is the same as the destination of the circuit at + * <b>origin_circ</b>. */ +static int +circuit_matches_with_rend_stream(const edge_connection_t *edge_conn, + const origin_circuit_t *origin_circ) +{ + /* Check if this is a v2 rendezvous circ/stream */ + if ((edge_conn->rend_data && !origin_circ->rend_data) || + (!edge_conn->rend_data && origin_circ->rend_data) || + (edge_conn->rend_data && origin_circ->rend_data && + rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), + rend_data_get_address(origin_circ->rend_data)))) { + /* this circ is not for this conn */ + return 0; + } + + /* Check if this is a v3 rendezvous circ/stream */ + if ((edge_conn->hs_ident && !origin_circ->hs_ident) || + (!edge_conn->hs_ident && origin_circ->hs_ident) || + (edge_conn->hs_ident && origin_circ->hs_ident && + !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + &origin_circ->hs_ident->identity_pk))) { + /* this circ is not for this conn */ + return 0; + } + + return 1; +} + /** Return 1 if <b>circ</b> could be returned by circuit_get_best(). * Else return 0. */ @@ -169,14 +202,9 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, /* can't exit from this router */ return 0; } - } else { /* not general */ + } else { /* not general: this might be a rend circuit */ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); - if ((edge_conn->rend_data && !origin_circ->rend_data) || - (!edge_conn->rend_data && origin_circ->rend_data) || - (edge_conn->rend_data && origin_circ->rend_data && - rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), - rend_data_get_address(origin_circ->rend_data)))) { - /* this circ is not for this conn */ + if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) { return 0; } } @@ -309,7 +337,8 @@ circuit_get_best(const entry_connection_t *conn, /* Log an info message if we're going to launch a new intro circ in * parallel */ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && - !must_be_open && origin_circ->hs_circ_has_timed_out) { + !must_be_open && origin_circ->hs_circ_has_timed_out && + !circ->marked_for_close) { intro_going_on_but_too_old = 1; continue; } @@ -381,7 +410,7 @@ circuit_conforms_to_options(const origin_circuit_t *circ, return 1; } -#endif +#endif /* 0 */ /** Close all circuits that start at us, aren't open, and were born * at least CircuitBuildTimeout seconds ago. @@ -514,8 +543,7 @@ circuit_expire_building(void) cutoff = begindir_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) cutoff = close_cutoff; - else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING || - victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) + else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) cutoff = c_intro_cutoff; else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) cutoff = s_intro_cutoff; @@ -605,7 +633,7 @@ circuit_expire_building(void) victim->n_circ_id, (int)(now - victim->timestamp_dirty)); } -#endif +#endif /* 0 */ /* if circ is !open, or if it's open but purpose is a non-finished * intro or rend, then mark it for close */ @@ -622,6 +650,7 @@ circuit_expire_building(void) * because that's set when they switch purposes */ if (TO_ORIGIN_CIRCUIT(victim)->rend_data || + TO_ORIGIN_CIRCUIT(victim)->hs_ident || victim->timestamp_dirty > cutoff.tv_sec) continue; break; @@ -631,12 +660,13 @@ circuit_expire_building(void) TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED; break; case CIRCUIT_PURPOSE_C_INTRODUCING: - /* We keep old introducing circuits around for - * a while in parallel, and they can end up "opened". - * We decide below if we're going to mark them timed - * out and eventually close them. - */ - break; + /* That purpose means that the intro point circuit has been opened + * succesfully but the INTRODUCE1 cell hasn't been sent yet because + * the client is waiting for the rendezvous point circuit to open. + * Keep this circuit open while waiting for the rendezvous circuit. + * We let the circuit idle timeout take care of cleaning this + * circuit if it never used. */ + continue; case CIRCUIT_PURPOSE_C_ESTABLISH_REND: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: @@ -727,8 +757,6 @@ circuit_expire_building(void) NULL) break; /* fallthrough! */ - case CIRCUIT_PURPOSE_C_INTRODUCING: - /* connection_ap_handshake_attach_circuit() will relaunch for us */ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT: case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: /* If we have reached this line, we want to spare the circ for now. */ @@ -755,7 +783,7 @@ circuit_expire_building(void) victim->state, circuit_state_to_string(victim->state), victim->purpose); TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1; - rend_service_relaunch_rendezvous(TO_ORIGIN_CIRCUIT(victim)); + hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim)); continue; } @@ -973,7 +1001,7 @@ circuit_remove_handled_ports(smartlist_t *needed_ports) tor_assert(*port); if (circuit_stream_is_being_handled(NULL, *port, MIN_CIRCUITS_HANDLING_STREAM)) { -// log_debug(LD_CIRC,"Port %d is already being handled; removing.", port); + log_debug(LD_CIRC,"Port %d is already being handled; removing.", *port); smartlist_del(needed_ports, i--); tor_free(port); } else { @@ -1010,6 +1038,10 @@ circuit_stream_is_being_handled(entry_connection_t *conn, continue; if (origin_circ->unusable_for_new_conns) continue; + if (origin_circ->isolation_values_set && + (conn == NULL || + !connection_edge_compatible_with_circuit(conn, origin_circ))) + continue; exitnode = build_state_get_exit_node(build_state); if (exitnode && (!need_uptime || build_state->need_uptime)) { @@ -1086,11 +1118,32 @@ needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity) /* Return true if we need any more hidden service server circuits. * HS servers only need an internal circuit. */ STATIC int -needs_hs_server_circuits(int num_uptime_internal) +needs_hs_server_circuits(time_t now, int num_uptime_internal) { - return (num_rend_services() && - num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS && - router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN); + if (!rend_num_services() && !hs_service_get_num_services()) { + /* No services, we don't need anything. */ + goto no_need; + } + + if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) { + /* We have sufficient amount of internal circuit. */ + goto no_need; + } + + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) { + /* Consensus hasn't been checked or might be invalid so requesting + * internal circuits is not wise. */ + goto no_need; + } + + /* At this point, we need a certain amount of circuits and we will most + * likely use them for rendezvous so we note down the use of internal + * circuit for our prediction for circuit needing uptime and capacity. */ + rep_hist_note_used_internal(now, 1, 1); + + return 1; + no_need: + return 0; } /* We need at least this many internal circuits for hidden service clients */ @@ -1189,7 +1242,7 @@ circuit_predict_and_launch_new(void) return; } - if (needs_hs_server_circuits(num_uptime_internal)) { + if (needs_hs_server_circuits(now, num_uptime_internal)) { flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL); @@ -1253,11 +1306,6 @@ circuit_build_needed_circs(time_t now) if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) connection_ap_rescan_and_attach_pending(); - /* make sure any hidden services have enough intro points - * HS intro point streams only require an internal circuit */ - if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) - rend_consider_services_intro_points(); - circuit_expire_old_circs_as_needed(now); if (!options->DisablePredictedCircuits) @@ -1293,7 +1341,7 @@ circuit_expire_old_circs_as_needed(time_t now) log_fn(LOG_INFO,"Creating a new testing circuit."); circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0); } -#endif +#endif /* 0 */ } } @@ -1339,8 +1387,7 @@ circuit_detach_stream(circuit_t *circ, edge_connection_t *conn) * number of streams on the circuit associated with the rend service. */ if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ->rend_data); - origin_circ->rend_data->nr_streams--; + hs_dec_rdv_stream_counter(origin_circ); } return; } @@ -1469,7 +1516,7 @@ circuit_expire_old_circuits_clientside(void) #define IDLE_ONE_HOP_CIRC_TIMEOUT 60 /** Find each non-origin circuit that has been unused for too long, - * has no streams on it, used a create_fast, and ends here: mark it + * has no streams on it, came from a client, and ends here: mark it * for close. */ void @@ -1485,9 +1532,9 @@ circuit_expire_old_circuits_serverside(time_t now) /* If the circuit has been idle for too long, and there are no streams * on it, and it ends here, and it used a create_fast, mark it for close. */ - if (or_circ->is_first_hop && !circ->n_chan && + if (or_circ->p_chan && channel_is_client(or_circ->p_chan) && + !circ->n_chan && !or_circ->n_streams && !or_circ->resolving_streams && - or_circ->p_chan && channel_when_last_xmit(or_circ->p_chan) <= cutoff) { log_info(LD_CIRC, "Closing circ_id %u (empty %d secs ago)", (unsigned)or_circ->p_circ_id, @@ -1593,7 +1640,7 @@ circuit_has_opened(origin_circuit_t *circ) switch (TO_CIRCUIT(circ)->purpose) { case CIRCUIT_PURPOSE_C_ESTABLISH_REND: - rend_client_rendcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); /* Start building an intro circ if we don't have one yet. */ connection_ap_attach_pending(1); /* This isn't a call to circuit_try_attaching_streams because a @@ -1605,7 +1652,7 @@ circuit_has_opened(origin_circuit_t *circ) * state. */ break; case CIRCUIT_PURPOSE_C_INTRODUCING: - rend_client_introcirc_has_opened(circ); + hs_client_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_C_GENERAL: /* Tell any AP connections that have been waiting for a new @@ -1614,11 +1661,11 @@ circuit_has_opened(origin_circuit_t *circ) break; case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: /* at the service, waiting for introductions */ - rend_service_intro_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_S_CONNECT_REND: /* at the service, connecting to rend point */ - rend_service_rendezvous_has_opened(circ); + hs_service_circuit_has_opened(circ); break; case CIRCUIT_PURPOSE_TESTING: circuit_testing_opened(circ); @@ -1707,13 +1754,17 @@ circuit_build_failed(origin_circuit_t *circ) already_marked = 1; } log_info(LD_OR, - "Our circuit failed to get a response from the first hop " - "(%s). I'm going to try to rotate to a better connection.", + "Our circuit %u (id: %" PRIu32 ") failed to get a response " + "from the first hop (%s). I'm going to try to rotate to a " + "better connection.", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, channel_get_canonical_remote_descr(n_chan)); n_chan->is_bad_for_new_circs = 1; } else { log_info(LD_OR, - "Our circuit died before the first hop with no connection"); + "Our circuit %u (id: %" PRIu32 ") died before the first hop " + "with no connection", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier); } if (n_chan_id && !already_marked) { /* New guard API: we failed. */ @@ -1768,7 +1819,7 @@ circuit_build_failed(origin_circuit_t *circ) "(%s hop failed).", escaped(build_state_get_exit_nickname(circ->build_state)), failed_at_last_hop?"last":"non-last"); - rend_service_relaunch_rendezvous(circ); + hs_circ_retry_service_rendezvous_point(circ); break; /* default: * This won't happen in normal operation, but might happen if the @@ -1855,8 +1906,9 @@ circuit_launch_by_extend_info(uint8_t purpose, uint8_t old_purpose = circ->base_.purpose; struct timeval old_timestamp_began = circ->base_.timestamp_began; - log_info(LD_CIRC,"Cannibalizing circ '%s' for purpose %d (%s)", - build_state_get_exit_nickname(circ->build_state), purpose, + log_info(LD_CIRC, "Cannibalizing circ %u (id: %" PRIu32 ") for " + "purpose %d (%s)", + TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, purpose, circuit_purpose_to_string(purpose)); if ((purpose == CIRCUIT_PURPOSE_S_CONNECT_REND || @@ -2027,8 +2079,12 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (!connection_get_by_type(CONN_TYPE_DIR)) { int severity = LOG_NOTICE; /* Retry some stuff that might help the connection work. */ - if (entry_list_is_constrained(options) && - guards_retry_optimistic(options)) { + /* If we are configured with EntryNodes or UseBridges */ + if (entry_list_is_constrained(options)) { + /* Retry all our guards / bridges. + * guards_retry_optimistic() always returns true here. */ + int rv = guards_retry_optimistic(options); + tor_assert_nonfatal_once(rv); log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying known %s again.", @@ -2036,7 +2092,12 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, "used client functionality lately" : "received a consensus with exits", options->UseBridges ? "bridges" : "entrynodes"); - } else if (!options->UseBridges || any_bridge_descriptors_known()) { + } else { + /* Getting directory documents doesn't help much if we have a limited + * number of guards */ + tor_assert_nonfatal(!options->UseBridges); + tor_assert_nonfatal(!options->EntryNodes); + /* Retry our directory fetches, so we have a fresh set of guard info */ log_fn(severity, LD_APP|LD_DIR, "Application request when we haven't %s. " "Optimistically trying directory fetches again.", @@ -2076,7 +2137,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } else { /* XXXX Duplicates checks in connection_ap_handshake_attach_circuit: * refactor into a single function. */ - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (node && !connection_ap_can_use_exit(conn, node)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, @@ -2131,22 +2192,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* If this is a hidden service trying to start an introduction point, * handle that case. */ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); /* need to pick an intro point */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; - tor_assert(rend_data); - extend_info = rend_client_get_random_intro(rend_data); + extend_info = hs_client_get_random_intro_from_edge(edge_conn); if (!extend_info) { - log_info(LD_REND, - "No intro points for '%s': re-fetching service descriptor.", - safe_str_client(rend_data_get_address(rend_data))); - rend_client_refetch_v2_renddesc(rend_data); + log_info(LD_REND, "No intro points: re-fetching service descriptor."); + if (edge_conn->rend_data) { + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + } else { + hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + } connection_ap_mark_as_non_pending_circuit(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } log_info(LD_REND,"Chose %s as intro point for '%s'.", extend_info_describe(extend_info), - safe_str_client(rend_data_get_address(rend_data))); + (edge_conn->rend_data) ? + safe_str_client(rend_data_get_address(edge_conn->rend_data)) : + "service"); } /* If we have specified a particular exit node for our @@ -2156,7 +2220,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (conn->chosen_exit_name) { const node_t *r; int opt = conn->chosen_exit_optional; - r = node_get_by_nickname(conn->chosen_exit_name, 1); + r = node_get_by_nickname(conn->chosen_exit_name, 0); if (r && node_has_descriptor(r)) { /* We might want to connect to an IPv6 bridge for loading descriptors so we use the preferred address rather than @@ -2234,7 +2298,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, new_circ_purpose == CIRCUIT_PURPOSE_C_INTRODUCING)) { want_onehop = 1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* Determine what kind of a circuit to launch, and actually launch it. */ { @@ -2242,6 +2306,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL; if (need_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; if (need_internal) flags |= CIRCLAUNCH_IS_INTERNAL; + + /* If we are about to pick a v3 RP right now, make sure we pick a + * rendezvous point that supports the v3 protocol! */ + if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED && + new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && + ENTRY_TO_EDGE_CONN(conn)->hs_ident) { + flags |= CIRCLAUNCH_IS_V3_RP; + log_info(LD_GENERAL, "Getting rendezvous circuit to v3 service!"); + } + circ = circuit_launch_by_extend_info(new_circ_purpose, extend_info, flags); } @@ -2265,8 +2339,15 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, /* help predict this next time */ rep_hist_note_used_internal(time(NULL), need_uptime, 1); if (circ) { - /* write the service_id into circ */ - circ->rend_data = rend_data_dup(ENTRY_TO_EDGE_CONN(conn)->rend_data); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + if (edge_conn->rend_data) { + /* write the service_id into circ */ + circ->rend_data = rend_data_dup(edge_conn->rend_data); + } else if (edge_conn->hs_ident) { + circ->hs_ident = + hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk, + HS_IDENT_CIRCUIT_INTRO); + } if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && circ->base_.state == CIRCUIT_STATE_OPEN) circuit_has_opened(circ); @@ -2348,8 +2429,7 @@ link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ, /* We are attaching a stream to a rendezvous circuit. That means * that an attempt to connect to a hidden service just * succeeded. Tell rendclient.c. */ - rend_client_note_connection_attempt_ended( - ENTRY_TO_EDGE_CONN(apconn)->rend_data); + hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn)); } if (cpath) { /* we were given one; use it */ @@ -2556,7 +2636,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) * open to that exit. See what exit we meant, and whether we can use it. */ if (conn->chosen_exit_name) { - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (!node && !want_onehop) { /* We ran into this warning when trying to extend a circuit to a @@ -2626,9 +2706,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) tor_assert(rendcirc); /* one is already established, attach */ log_info(LD_REND, - "rend joined circ %u already here. attaching. " - "(stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, conn_age); + "rend joined circ %u (id: %" PRIu32 ") already here. " + "Attaching. (stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); /* Mark rendezvous circuits as 'newly dirty' every time you use * them, since the process of rebuilding a rendezvous circ is so * expensive. There is a tradeoff between linkability and @@ -2661,9 +2742,10 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (rendcirc && (rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)) { log_info(LD_REND, - "pending-join circ %u already here, with intro ack. " - "Stalling. (stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, conn_age); + "pending-join circ %u (id: %" PRIu32 ") already here, with " + "intro ack. Stalling. (stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); return 0; } @@ -2675,10 +2757,13 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (retval > 0) { /* one has already sent the intro. keep waiting. */ tor_assert(introcirc); - log_info(LD_REND, "Intro circ %u present and awaiting ack (rend %u). " - "Stalling. (stream %d sec old)", - (unsigned)introcirc->base_.n_circ_id, - rendcirc ? (unsigned)rendcirc->base_.n_circ_id : 0, + log_info(LD_REND, "Intro circ %u (id: %" PRIu32 ") present and " + "awaiting ACK. Rend circuit %u (id: %" PRIu32 "). " + "Stalling. (stream %d sec old)", + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, + rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0, + rendcirc ? rendcirc->global_identifier : 0, conn_age); return 0; } @@ -2688,19 +2773,26 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (rendcirc && introcirc && rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY) { log_info(LD_REND, - "ready rend circ %u already here (no intro-ack yet on " - "intro %u). (stream %d sec old)", - (unsigned)rendcirc->base_.n_circ_id, - (unsigned)introcirc->base_.n_circ_id, conn_age); + "ready rend circ %u (id: %" PRIu32 ") already here. No" + "intro-ack yet on intro %u (id: %" PRIu32 "). " + "(stream %d sec old)", + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, conn_age); tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); if (introcirc->base_.state == CIRCUIT_STATE_OPEN) { - log_info(LD_REND,"found open intro circ %u (rend %u); sending " - "introduction. (stream %d sec old)", - (unsigned)introcirc->base_.n_circ_id, - (unsigned)rendcirc->base_.n_circ_id, - conn_age); - switch (rend_client_send_introduction(introcirc, rendcirc)) { + int ret; + log_info(LD_REND, "Found open intro circ %u (id: %" PRIu32 "). " + "Rend circuit %u (id: %" PRIu32 "); Sending " + "introduction. (stream %d sec old)", + (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, + introcirc->global_identifier, + (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, + rendcirc->global_identifier, conn_age); + ret = hs_client_send_introduce1(introcirc, rendcirc); + switch (ret) { case 0: /* success */ rendcirc->base_.timestamp_dirty = time(NULL); introcirc->base_.timestamp_dirty = time(NULL); @@ -2722,10 +2814,13 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) } } - log_info(LD_REND, "Intro (%u) and rend (%u) circs are not both ready. " - "Stalling conn. (%d sec old)", - introcirc ? (unsigned)introcirc->base_.n_circ_id : 0, - rendcirc ? (unsigned)rendcirc->base_.n_circ_id : 0, conn_age); + log_info(LD_REND, "Intro %u (id: %" PRIu32 ") and rend circuit %u " + "(id: %" PRIu32 ") circuits are not both ready. " + "Stalling conn. (%d sec old)", + introcirc ? (unsigned) TO_CIRCUIT(introcirc)->n_circ_id : 0, + introcirc ? introcirc->global_identifier : 0, + rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0, + rendcirc ? rendcirc->global_identifier : 0, conn_age); return 0; } } diff --git a/src/or/circuituse.h b/src/or/circuituse.h index ad4c214a3b..2b0f983f1a 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -44,6 +44,9 @@ void circuit_build_failed(origin_circuit_t *circ); /** Flag to set when the last hop of a circuit doesn't need to be an * exit node. */ #define CIRCLAUNCH_IS_INTERNAL (1<<3) +/** Flag to set when we are trying to launch a v3 rendezvous circuit. We need + * to apply some additional filters on the node picked. */ +#define CIRCLAUNCH_IS_V3_RP (1<<4) origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, int flags); @@ -68,7 +71,8 @@ STATIC int circuit_is_available_for_use(const circuit_t *circ); STATIC int needs_exit_circuits(time_t now, int *port_needs_uptime, int *port_needs_capacity); -STATIC int needs_hs_server_circuits(int num_uptime_internal); +STATIC int needs_hs_server_circuits(time_t now, + int num_uptime_internal); STATIC int needs_hs_client_circuits(time_t now, int *needs_uptime, @@ -78,7 +82,7 @@ STATIC int needs_hs_client_circuits(time_t now, STATIC int needs_circuits_for_build(int num); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ -#endif +#endif /* !defined(TOR_CIRCUITUSE_H) */ diff --git a/src/or/command.c b/src/or/command.c index c667cbbe52..ae419ad068 100644 --- a/src/or/command.c +++ b/src/or/command.c @@ -128,7 +128,7 @@ command_time_process_cell(cell_t *cell, channel_t *chan, int *time, } *time += time_passed; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ /** Process a <b>cell</b> that was just received on <b>chan</b>. Keep internal * statistics about how many of each cell we've processed so far @@ -165,7 +165,7 @@ command_process_cell(channel_t *chan, cell_t *cell) /* remember which second it is, for next time */ current_second = now; } -#endif +#endif /* defined(KEEP_TIMING_STATS) */ #ifdef KEEP_TIMING_STATS #define PROCESS_CELL(tp, cl, cn) STMT_BEGIN { \ @@ -173,9 +173,9 @@ command_process_cell(channel_t *chan, cell_t *cell) command_time_process_cell(cl, cn, & tp ## time , \ command_process_ ## tp ## _cell); \ } STMT_END -#else +#else /* !(defined(KEEP_TIMING_STATS)) */ #define PROCESS_CELL(tp, cl, cn) command_process_ ## tp ## _cell(cl, cn) -#endif +#endif /* defined(KEEP_TIMING_STATS) */ switch (cell->command) { case CELL_CREATE: @@ -331,7 +331,7 @@ command_process_create_cell(cell_t *cell, channel_t *chan) // Needed for chutney: Sometimes relays aren't in the consensus yet, and // get marked as clients. This resets their channels once they appear. // Probably useful for normal operation wrt relay flapping, too. - chan->is_client = 0; + channel_clear_client(chan); } else { channel_mark_client(chan); } @@ -353,16 +353,6 @@ command_process_create_cell(cell_t *cell, channel_t *chan) int len; created_cell_t created_cell; - /* If the client used CREATE_FAST, it's probably a tor client or bridge - * relay, and we must not use it for EXTEND requests (in most cases, we - * won't have an authenticated peer ID for the extend). - * Public relays on 0.2.9 and later will use CREATE_FAST if they have no - * ntor onion key for this relay, but that should be a rare occurrence. - * Clients on 0.3.1 and later avoid using CREATE_FAST as much as they can, - * even during bootstrap, so the CREATE_FAST check is most accurate for - * earlier tor client versions. */ - channel_mark_client(chan); - memset(&created_cell, 0, sizeof(created_cell)); len = onion_skin_server_handshake(ONION_HANDSHAKE_TYPE_FAST, create_cell->onionskin, @@ -381,7 +371,8 @@ command_process_create_cell(cell_t *cell, channel_t *chan) created_cell.handshake_len = len; if (onionskin_answer(circ, &created_cell, - (const char *)keys, rend_circ_nonce)<0) { + (const char *)keys, sizeof(keys), + rend_circ_nonce)<0) { log_warn(LD_OR,"Failed to reply to CREATE_FAST cell. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return; diff --git a/src/or/command.h b/src/or/command.h index 5079d42e75..c0d1996cbb 100644 --- a/src/or/command.h +++ b/src/or/command.h @@ -27,5 +27,5 @@ extern uint64_t stats_n_created_cells_processed; extern uint64_t stats_n_relay_cells_processed; extern uint64_t stats_n_destroy_cells_processed; -#endif +#endif /* !defined(TOR_COMMAND_H) */ diff --git a/src/or/config.c b/src/or/config.c index 2f92591b19..d8f71a0193 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -91,6 +91,7 @@ #include "relay.h" #include "rendclient.h" #include "rendservice.h" +#include "hs_config.h" #include "rephist.h" #include "router.h" #include "sandbox.h" @@ -113,9 +114,9 @@ * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> -#endif +#endif /* defined(HAVE_SYSTEMD) */ /* Prefix used to indicate a Unix socket in a FooPort configuration. */ static const char unix_socket_prefix[] = "unix:"; @@ -171,18 +172,26 @@ static config_abbrev_t option_abbrevs_[] = { { NULL, NULL, 0, 0}, }; +/** dummy instance of or_options_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(or_options_t); + /** An entry for config_vars: "The option <b>name</b> has type * CONFIG_TYPE_<b>conftype</b>, and corresponds to * or_options_t.<b>member</b>" */ #define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_options_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \ + initvalue CONF_TEST_MEMBERS(or_options_t, conftype, member) } /** As VAR, but the option name and member name are the same. */ #define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) /** An entry for config_vars: "The option <b>name</b> is obsolete." */ +#ifdef TOR_UNIT_TESTS +#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL, {.INT=NULL} } +#else #define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL } +#endif /** * Macro to declare *Port options. Each one comes in three entries. @@ -205,7 +214,7 @@ static config_var_t option_vars_[] = { VAR("AccountingRule", STRING, AccountingRule_option, "max"), V(AccountingStart, STRING, NULL), V(Address, STRING, NULL), - V(AllowDotExit, BOOL, "0"), + OBSOLETE("AllowDotExit"), OBSOLETE("AllowInvalidNodes"), V(AllowNonRFC953Hostnames, BOOL, "0"), OBSOLETE("AllowSingleHopCircuits"), @@ -242,6 +251,7 @@ static config_var_t option_vars_[] = { V(BridgePassword, STRING, NULL), V(BridgeRecordUsageByCountry, BOOL, "1"), V(BridgeRelay, BOOL, "0"), + V(BridgeDistribution, STRING, NULL), V(CellStatistics, BOOL, "0"), V(PaddingStatistics, BOOL, "1"), V(LearnCircuitBuildTimeout, BOOL, "1"), @@ -342,7 +352,7 @@ static config_var_t option_vars_[] = { SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"), V(GeoIPv6File, FILENAME, SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip6"), -#endif +#endif /* defined(_WIN32) */ OBSOLETE("Group"), V(GuardLifetime, INTERVAL, "0 minutes"), V(HardwareAccel, BOOL, "0"), @@ -371,6 +381,7 @@ static config_var_t option_vars_[] = { V(HTTPProxyAuthenticator, STRING, NULL), V(HTTPSProxy, STRING, NULL), V(HTTPSProxyAuthenticator, STRING, NULL), + VPORT(HTTPTunnelPort), V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(ServerTransportListenAddr, LINELIST, NULL), @@ -408,6 +419,7 @@ static config_var_t option_vars_[] = { OBSOLETE("PredictedPortsRelevanceTime"), OBSOLETE("WarnUnsafeSocks"), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), + V(NoExec, BOOL, "0"), V(NumCPUs, UINT, "0"), V(NumDirectoryGuards, UINT, "0"), V(NumEntryGuards, UINT, "0"), @@ -483,9 +495,12 @@ static config_var_t option_vars_[] = { V(ServerDNSSearchDomains, BOOL, "0"), V(ServerDNSTestAddresses, CSV, "www.google.com,www.mit.edu,www.yahoo.com,www.slashdot.org"), - V(SchedulerLowWaterMark__, MEMUNIT, "100 MB"), - V(SchedulerHighWaterMark__, MEMUNIT, "101 MB"), - V(SchedulerMaxFlushCells__, UINT, "1000"), + OBSOLETE("SchedulerLowWaterMark__"), + OBSOLETE("SchedulerHighWaterMark__"), + OBSOLETE("SchedulerMaxFlushCells__"), + V(KISTSchedRunInterval, MSEC_INTERVAL, "0 msec"), + V(KISTSockBufSizeFactor, DOUBLE, "1.0"), + V(Schedulers, CSV, "KIST,KISTLite,Vanilla"), V(ShutdownWaitLength, INTERVAL, "30 seconds"), OBSOLETE("SocksListenAddress"), V(SocksPolicy, LINELIST, NULL), @@ -584,7 +599,16 @@ static config_var_t option_vars_[] = { * blackholed. Clients will try 3 directories simultaneously. * (Relays never use simultaneous connections.) */ V(ClientBootstrapConsensusMaxInProgressTries, UINT, "3"), - V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "1200, 900, 900, 3600"), + /* When a client has any running bridges, check each bridge occasionally, + * whether or not that bridge is actually up. */ + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, + "10800, 25200, 54000, 111600, 262800"), + /* When a client is just starting, or has no running bridges, check each + * bridge a few times quickly, and then try again later. These schedules + * are much longer than the other schedules, because we try each and every + * configured bridge with this schedule. */ + V(TestingBridgeBootstrapDownloadSchedule, CSV_INTERVAL, + "0, 30, 90, 600, 3600, 10800, 25200, 54000, 111600, 262800"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), V(TestingConsensusMaxDownloadTries, UINT, "8"), @@ -604,13 +628,12 @@ static config_var_t option_vars_[] = { V(TestingDirAuthVoteHSDirIsStrict, BOOL, "0"), VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; /** Override default values with these if the user sets the TestingTorNetwork * option. */ static const config_var_t testing_tor_network_defaults[] = { - V(ServerDNSAllowBrokenConfig, BOOL, "1"), V(DirAllowPrivateAddresses, BOOL, "1"), V(EnforceDistinctSubnets, BOOL, "0"), V(AssumeReachable, BOOL, "1"), @@ -623,7 +646,7 @@ static const config_var_t testing_tor_network_defaults[] = { "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), V(ClientBootstrapConsensusMaxDownloadTries, UINT, "80"), V(ClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "80"), - V(ClientDNSRejectInternalAddresses, BOOL,"0"), // deprecated in 0.2.9.2-alpha + V(ClientDNSRejectInternalAddresses, BOOL,"0"), V(ClientRejectInternalAddresses, BOOL, "0"), V(CountPrivateBandwidth, BOOL, "1"), V(ExitPolicyRejectPrivate, BOOL, "0"), @@ -634,7 +657,6 @@ static const config_var_t testing_tor_network_defaults[] = { V(TestingV3AuthInitialVotingInterval, INTERVAL, "150 seconds"), V(TestingV3AuthInitialVoteDelay, INTERVAL, "20 seconds"), V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"), - V(TestingV3AuthVotingStartOffset, INTERVAL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"), V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"), V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"), @@ -646,7 +668,9 @@ static const config_var_t testing_tor_network_defaults[] = { "15, 20, 30, 60"), V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " "15, 20, 30, 60"), - V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "60, 30, 30, 60"), + V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "10, 30, 60"), + V(TestingBridgeBootstrapDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " + "15, 20, 30, 60"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), V(TestingConsensusMaxDownloadTries, UINT, "80"), @@ -659,7 +683,7 @@ static const config_var_t testing_tor_network_defaults[] = { VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"), V(RendPostPeriod, INTERVAL, "2 minutes"), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; #undef VAR @@ -667,12 +691,19 @@ static const config_var_t testing_tor_network_defaults[] = { #undef OBSOLETE static const config_deprecation_t option_deprecation_notes_[] = { - /* Deprecated since 0.2.9.2-alpha... */ - { "AllowDotExit", "Unrestricted use of the .exit notation can be used for " - "a wide variety of application-level attacks." }, - { "ClientDNSRejectInternalAddresses", "Turning this on makes your client " - "easier to fingerprint, and may open you to esoteric attacks." }, - /* End of options deprecated since 0.2.9.2-alpha. */ + /* Deprecated since 0.3.2.0-alpha. */ + { "HTTPProxy", "It only applies to direct unencrypted HTTP connections " + "to your directory server, which your Tor probably wasn't using." }, + { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy " + "which should be used with HTTPSProxyAuthenticator." }, + /* End of options deprecated since 0.3.2.1-alpha */ + + /* Options deprecated since 0.3.2.2-alpha */ + { "ReachableDirAddresses", "It has no effect on relays, and has had no " + "effect on clients since 0.2.8." }, + { "ClientPreferIPv6DirPort", "It has no effect on relays, and has had no " + "effect on clients since 0.2.8." }, + /* End of options deprecated since 0.3.2.2-alpha. */ { NULL, NULL } }; @@ -699,7 +730,6 @@ static int parse_ports(or_options_t *options, int validate_only, static int check_server_ports(const smartlist_t *ports, const or_options_t *options, int *num_low_ports_out); - static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, const or_options_t *options); @@ -725,7 +755,7 @@ static uint64_t compute_real_max_mem_in_queues(const uint64_t val, STATIC config_format_t options_format = { sizeof(or_options_t), OR_OPTIONS_MAGIC, - STRUCT_OFFSET(or_options_t, magic_), + offsetof(or_options_t, magic_), option_abbrevs_, option_deprecation_notes_, option_vars_, @@ -756,6 +786,9 @@ static int have_parsed_cmdline = 0; static char *global_dirfrontpagecontents = NULL; /** List of port_cfg_t for all configured ports. */ static smartlist_t *configured_ports = NULL; +/** True iff we're currently validating options, and any calls to + * get_options() are likely to be bugs. */ +static int in_option_validation = 0; /** Return the contents of our frontpage string, or NULL if not configured. */ MOCK_IMPL(const char*, @@ -769,6 +802,7 @@ MOCK_IMPL(or_options_t *, get_options_mutable, (void)) { tor_assert(global_options); + tor_assert_nonfatal(! in_option_validation); return global_options; } @@ -895,6 +929,10 @@ or_options_free(or_options_t *options) rs, routerset_free(rs)); smartlist_free(options->NodeFamilySets); } + if (options->SchedulerTypes_) { + SMARTLIST_FOREACH(options->SchedulerTypes_, int *, i, tor_free(i)); + smartlist_free(options->SchedulerTypes_); + } tor_free(options->BridgePassword_AuthDigest_); tor_free(options->command_arg); tor_free(options->master_key_fname); @@ -990,6 +1028,23 @@ escaped_safe_str(const char *address) return escaped(address); } +/** + * The severity level that should be used for warnings of severity + * LOG_PROTOCOL_WARN. + * + * We keep this outside the options, in case somebody needs to use + * LOG_PROTOCOL_WARN while an option transition is happening. + */ +static int protocol_warning_severity_level = LOG_WARN; + +/** Return the severity level that should be used for warnings of severity + * LOG_PROTOCOL_WARN. */ +int +get_protocol_warning_severity_level(void) +{ + return protocol_warning_severity_level; +} + /** List of default directory authorities */ static const char *default_authorities[] = { @@ -1228,13 +1283,13 @@ options_act_reversible(const or_options_t *old_options, char **msg) "on this OS/with this build."); goto rollback; } -#else +#else /* !(!defined(HAVE_SYS_UN_H)) */ if (options->ControlSocketsGroupWritable && !options->ControlSocket) { *msg = tor_strdup("Setting ControlSocketGroupWritable without setting" "a ControlSocket makes no sense."); goto rollback; } -#endif +#endif /* !defined(HAVE_SYS_UN_H) */ if (running_tor) { int n_ports=0; @@ -1311,7 +1366,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) goto rollback; } } -#endif +#endif /* defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) */ /* Attempt to lock all current and future memory with mlockall() only once */ if (options->DisableAllSwap) { @@ -1363,7 +1418,7 @@ options_act_reversible(const or_options_t *old_options, char **msg) options->DataDirectory, strerror(errno)); } } -#endif +#endif /* !defined(_WIN32) */ /* Bail out at this point if we're not going to be a client or server: * we don't run Tor itself. */ @@ -1590,6 +1645,10 @@ options_act(const or_options_t *old_options) const int transition_affects_guards = old_options && options_transition_affects_guards(old_options, options); + if (options->NoExec || options->Sandbox) { + tor_disable_spawning_background_processes(); + } + /* disable ptrace and later, other basic debugging techniques */ { /* Remember if we already disabled debugger attachment */ @@ -1624,6 +1683,11 @@ options_act(const or_options_t *old_options) return -1; } + if (options->ProtocolWarnings) + protocol_warning_severity_level = LOG_WARN; + else + protocol_warning_severity_level = LOG_INFO; + if (consider_adding_dir_servers(options, old_options) < 0) return -1; @@ -1641,7 +1705,7 @@ options_act(const or_options_t *old_options) return -1; } /* LCOV_EXCL_STOP */ -#else +#else /* !(defined(ENABLE_TOR2WEB_MODE)) */ if (options->Tor2webMode) { log_err(LD_CONFIG, "This copy of Tor was not compiled to run in " "'tor2web mode'. It cannot be run with the Tor2webMode torrc " @@ -1649,7 +1713,7 @@ options_act(const or_options_t *old_options) "--enable-tor2web-mode option."); return -1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* If we are a bridge with a pluggable transport proxy but no Extended ORPort, inform the user that they are missing out. */ @@ -1678,7 +1742,7 @@ options_act(const or_options_t *old_options) sweep_bridge_list(); } - if (running_tor && rend_config_services(options, 0)<0) { + if (running_tor && hs_config_service_all(options, 0)<0) { log_warn(LD_BUG, "Previously validated hidden services line could not be added!"); return -1; @@ -1761,9 +1825,13 @@ options_act(const or_options_t *old_options) } /* Write our PID to the PID file. If we do not have write permissions we - * will log a warning */ + * will log a warning and exit. */ if (options->PidFile && !sandbox_is_active()) { - write_pidfile(options->PidFile); + if (write_pidfile(options->PidFile) < 0) { + log_err(LD_CONFIG, "Unable to write PIDFile %s", + escaped(options->PidFile)); + return -1; + } } /* Register addressmap directives */ @@ -1795,16 +1863,14 @@ options_act(const or_options_t *old_options) monitor_owning_controller_process(options->OwningControllerProcess); /* reload keys as needed for rendezvous services. */ - if (rend_service_load_all_keys(NULL)<0) { + if (hs_service_load_all_keys() < 0) { log_warn(LD_GENERAL,"Error loading rendezvous service keys"); return -1; } - /* Set up scheduler thresholds */ - scheduler_set_watermarks((uint32_t)options->SchedulerLowWaterMark__, - (uint32_t)options->SchedulerHighWaterMark__, - (options->SchedulerMaxFlushCells__ > 0) ? - options->SchedulerMaxFlushCells__ : 1000); + /* Inform the scheduler subsystem that a configuration changed happened. It + * might be a change of scheduler or parameter. */ + scheduler_conf_changed(); /* Set up accounting */ if (accounting_parse_options(options, 0)<0) { @@ -2132,6 +2198,7 @@ static const struct { { "--dump-config", ARGUMENT_OPTIONAL }, { "--list-fingerprint", TAKES_NO_ARGUMENT }, { "--keygen", TAKES_NO_ARGUMENT }, + { "--key-expiration", ARGUMENT_OPTIONAL }, { "--newpass", TAKES_NO_ARGUMENT }, { "--no-passphrase", TAKES_NO_ARGUMENT }, { "--passphrase-fd", ARGUMENT_NECESSARY }, @@ -2292,24 +2359,36 @@ options_trial_assign(config_line_t *list, unsigned flags, char **msg) return r; } - if (options_validate(get_options_mutable(), trial_options, + setopt_err_t rv; + or_options_t *cur_options = get_options_mutable(); + + in_option_validation = 1; + + if (options_validate(cur_options, trial_options, global_default_options, 1, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + rv = SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + goto done; } - if (options_transition_allowed(get_options(), trial_options, msg) < 0) { + if (options_transition_allowed(cur_options, trial_options, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_TRANSITION; + rv = SETOPT_ERR_TRANSITION; + goto done; } + in_option_validation = 0; if (set_options(trial_options, msg)<0) { or_options_free(trial_options); - return SETOPT_ERR_SETTING; + rv = SETOPT_ERR_SETTING; + goto done; } /* we liked it. put it in place. */ - return SETOPT_OK; + rv = SETOPT_OK; + done: + in_option_validation = 0; + return rv; } /** Print a usage message for tor. */ @@ -2801,10 +2880,6 @@ compute_publishserverdescriptor(or_options_t *options) * will generate too many circuits and potentially overload the network. */ #define MIN_CIRCUIT_STREAM_TIMEOUT 10 -/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might - * expose more information than we're comfortable with. */ -#define MIN_HEARTBEAT_PERIOD (30*60) - /** Lowest recommended value for CircuitBuildTimeout; if it is set too low * and LearnCircuitBuildTimeout is off, the failure rate for circuit * construction may be very high. In that case, if it is set below this @@ -2816,8 +2891,11 @@ static int options_validate_cb(void *old_options, void *options, void *default_options, int from_setconf, char **msg) { - return options_validate(old_options, options, default_options, + in_option_validation = 1; + int rv = options_validate(old_options, options, default_options, from_setconf, msg); + in_option_validation = 0; + return rv; } #define REJECT(arg) \ @@ -2828,15 +2906,17 @@ options_validate_cb(void *old_options, void *options, void *default_options, #else #define COMPLAIN(args, ...) \ STMT_BEGIN log_warn(LD_CONFIG, args, ##__VA_ARGS__); STMT_END -#endif +#endif /* defined(__GNUC__) && __GNUC__ <= 3 */ /** Log a warning message iff <b>filepath</b> is not absolute. * Warning message must contain option name <b>option</b> and * an absolute path that <b>filepath</b> will resolve to. * * In case <b>filepath</b> is absolute, do nothing. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_if_option_path_is_relative(const char *option, char *filepath) { @@ -2845,39 +2925,100 @@ warn_if_option_path_is_relative(const char *option, COMPLAIN("Path for %s (%s) is relative and will resolve to %s." " Is this what you wanted?", option, filepath, abs_path); tor_free(abs_path); + return 1; } + return 0; } /** Scan <b>options</b> for occurances of relative file/directory * path and log a warning whenever it is found. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_about_relative_paths(or_options_t *options) { tor_assert(options); + int n = 0; - warn_if_option_path_is_relative("CookieAuthFile", - options->CookieAuthFile); - warn_if_option_path_is_relative("ExtORPortCookieAuthFile", - options->ExtORPortCookieAuthFile); - warn_if_option_path_is_relative("DirPortFrontPage", - options->DirPortFrontPage); - warn_if_option_path_is_relative("V3BandwidthsFile", - options->V3BandwidthsFile); - warn_if_option_path_is_relative("ControlPortWriteToFile", - options->ControlPortWriteToFile); - warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); - warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); - warn_if_option_path_is_relative("Log",options->DebugLogFile); - warn_if_option_path_is_relative("AccelDir",options->AccelDir); - warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); - warn_if_option_path_is_relative("PidFile",options->PidFile); + n += warn_if_option_path_is_relative("CookieAuthFile", + options->CookieAuthFile); + n += warn_if_option_path_is_relative("ExtORPortCookieAuthFile", + options->ExtORPortCookieAuthFile); + n += warn_if_option_path_is_relative("DirPortFrontPage", + options->DirPortFrontPage); + n += warn_if_option_path_is_relative("V3BandwidthsFile", + options->V3BandwidthsFile); + n += warn_if_option_path_is_relative("ControlPortWriteToFile", + options->ControlPortWriteToFile); + n += warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); + n += warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); + n += warn_if_option_path_is_relative("Log",options->DebugLogFile); + n += warn_if_option_path_is_relative("AccelDir",options->AccelDir); + n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); + n += warn_if_option_path_is_relative("PidFile",options->PidFile); for (config_line_t *hs_line = options->RendConfigLines; hs_line; hs_line = hs_line->next) { if (!strcasecmp(hs_line->key, "HiddenServiceDir")) - warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + n += warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + } + return n != 0; +} + +/* Validate options related to the scheduler. From the Schedulers list, the + * SchedulerTypes_ list is created with int values so once we select the + * scheduler, which can happen anytime at runtime, we don't have to parse + * strings and thus be quick. + * + * Return 0 on success else -1 and msg is set with an error message. */ +static int +options_validate_scheduler(or_options_t *options, char **msg) +{ + tor_assert(options); + tor_assert(msg); + + if (!options->Schedulers || smartlist_len(options->Schedulers) == 0) { + REJECT("Empty Schedulers list. Either remove the option so the defaults " + "can be used or set at least one value."); + } + /* Ok, we do have scheduler types, validate them. */ + options->SchedulerTypes_ = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(options->Schedulers, const char *, type) { + int *sched_type; + if (!strcasecmp("KISTLite", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_KIST_LITE; + smartlist_add(options->SchedulerTypes_, sched_type); + } else if (!strcasecmp("KIST", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_KIST; + smartlist_add(options->SchedulerTypes_, sched_type); + } else if (!strcasecmp("Vanilla", type)) { + sched_type = tor_malloc_zero(sizeof(int)); + *sched_type = SCHEDULER_VANILLA; + smartlist_add(options->SchedulerTypes_, sched_type); + } else { + tor_asprintf(msg, "Unknown type %s in option Schedulers. " + "Possible values are KIST, KISTLite and Vanilla.", + escaped(type)); + return -1; + } + } SMARTLIST_FOREACH_END(type); + + if (options->KISTSockBufSizeFactor < 0) { + REJECT("KISTSockBufSizeFactor must be at least 0"); + } + + /* Don't need to validate that the Interval is less than anything because + * zero is valid and all negative values are valid. */ + if (options->KISTSchedRunInterval > KIST_SCHED_RUN_INTERVAL_MAX) { + tor_asprintf(msg, "KISTSchedRunInterval must not be more than %d (ms)", + KIST_SCHED_RUN_INTERVAL_MAX); + return -1; } + + return 0; } /* Validate options related to single onion services. @@ -2908,7 +3049,8 @@ options_validate_single_onion(or_options_t *options, char **msg) const int client_port_set = (options->SocksPort_set || options->TransPort_set || options->NATDPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); if (rend_service_non_anonymous_mode_enabled(options) && client_port_set && !options->Tor2webMode) { REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as " @@ -2980,7 +3122,11 @@ options_validate(or_options_t *old_options, or_options_t *options, * Always use the value of UseEntryGuards, not UseEntryGuards_option. */ options->UseEntryGuards = options->UseEntryGuards_option; - warn_about_relative_paths(options); + if (warn_about_relative_paths(options) && options->RunAsDaemon) { + REJECT("You have specified at least one relative path (see above) " + "with the RunAsDaemon option. RunAsDaemon is not compatible " + "with relative paths."); + } if (server_mode(options) && (!strcmpstart(uname, "Windows 95") || @@ -3063,7 +3209,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "and OS X/Darwin-specific feature."); #else options->TransProxyType_parsed = TPT_PF_DIVERT; -#endif +#endif /* !defined(OpenBSD) && !defined( DARWIN ) */ } else if (!strcasecmp(options->TransProxyType, "tproxy")) { #if !defined(__linux__) REJECT("TPROXY is a Linux-specific feature."); @@ -3077,7 +3223,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "and OS X/Darwin-specific feature."); #else options->TransProxyType_parsed = TPT_IPFW; -#endif +#endif /* !defined(KERNEL_MAY_SUPPORT_IPFW) */ } else { REJECT("Unrecognized value for TransProxyType"); } @@ -3087,10 +3233,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Cannot use TransProxyType without any valid TransPort."); } } -#else +#else /* !(defined(USE_TRANSPARENT)) */ if (options->TransPort_set) REJECT("TransPort is disabled in this build."); -#endif +#endif /* defined(USE_TRANSPARENT) */ if (options->TokenBucketRefillInterval <= 0 || options->TokenBucketRefillInterval > 1000) { @@ -3103,17 +3249,6 @@ options_validate(or_options_t *old_options, or_options_t *options, routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeNodes); } - if (options->SchedulerLowWaterMark__ == 0 || - options->SchedulerLowWaterMark__ > UINT32_MAX) { - log_warn(LD_GENERAL, "Bad SchedulerLowWaterMark__ option"); - return -1; - } else if (options->SchedulerHighWaterMark__ <= - options->SchedulerLowWaterMark__ || - options->SchedulerHighWaterMark__ > UINT32_MAX) { - log_warn(LD_GENERAL, "Bad SchedulerHighWaterMark option"); - return -1; - } - if (options->NodeFamilies) { options->NodeFamilySets = smartlist_new(); for (cl = options->NodeFamilies; cl; cl = cl->next) { @@ -3158,7 +3293,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "UseEntryGuards. Disabling."); options->UseEntryGuards = 0; } - if (!options->DownloadExtraInfo && authdir_mode_any_main(options)) { + if (!options->DownloadExtraInfo && authdir_mode_v3(options)) { log_info(LD_CONFIG, "Authoritative directories always try to download " "extra-info documents. Setting DownloadExtraInfo."); options->DownloadExtraInfo = 1; @@ -3376,6 +3511,15 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Relays cannot set ReducedConnectionPadding. "); } + if (options->BridgeDistribution) { + if (!options->BridgeRelay) { + REJECT("You set BridgeDistribution, but you didn't set BridgeRelay!"); + } + if (check_bridge_distribution_setting(options->BridgeDistribution) < 0) { + REJECT("Invalid BridgeDistribution value."); + } + } + if (options->MinUptimeHidServDirectoryV2 < 0) { log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at " "least 0 seconds. Changing to 0."); @@ -3388,7 +3532,7 @@ options_validate(or_options_t *old_options, or_options_t *options, if (options->RendPostPeriod < min_rendpostperiod) { log_warn(LD_CONFIG, "RendPostPeriod option is too short; " "raising to %d seconds.", min_rendpostperiod); - options->RendPostPeriod = min_rendpostperiod;; + options->RendPostPeriod = min_rendpostperiod; } if (options->RendPostPeriod > MAX_DIR_PERIOD) { @@ -3422,7 +3566,7 @@ options_validate(or_options_t *old_options, or_options_t *options, "Tor2WebMode is enabled; disabling UseEntryGuards."); options->UseEntryGuards = 0; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ if (options->Tor2webRendezvousPoints && !options->Tor2webMode) { REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode."); @@ -3568,6 +3712,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("PortForwarding is not compatible with Sandbox; at most one can " "be set"); } + if (options->PortForwarding && options->NoExec) { + COMPLAIN("Both PortForwarding and NoExec are set; PortForwarding will " + "be ignored."); + } if (ensure_bandwidth_cap(&options->BandwidthRate, "BandwidthRate", msg) < 0) @@ -4006,7 +4154,7 @@ options_validate(or_options_t *old_options, or_options_t *options, COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours."); } - if (rend_config_services(options, 1) < 0) + if (hs_config_service_all(options, 1) < 0) REJECT("Failed to configure rendezvous options. See logs for details."); /* Parse client-side authorization for hidden services. */ @@ -4050,6 +4198,7 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); CHECK_DEFAULT(TestingBridgeDownloadSchedule); + CHECK_DEFAULT(TestingBridgeBootstrapDownloadSchedule); CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); CHECK_DEFAULT(TestingDirConnectionMaxStall); CHECK_DEFAULT(TestingConsensusMaxDownloadTries); @@ -4063,6 +4212,10 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingLinkKeySlop); #undef CHECK_DEFAULT + if (!options->ClientDNSRejectInternalAddresses && + !(options->DirAuthorities || + (options->AlternateDirAuthority && options->AlternateBridgeAuthority))) + REJECT("ClientDNSRejectInternalAddresses used for default network."); if (options->SigningKeyLifetime < options->TestingSigningKeySlop*2) REJECT("SigningKeyLifetime is too short."); if (options->TestingLinkCertLifetime < options->TestingAuthKeySlop*2) @@ -4226,6 +4379,10 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid " "combination."); + if (options_validate_scheduler(options, msg) < 0) { + return -1; + } + return 0; } @@ -4260,7 +4417,7 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess) #else /* (presumably) 32-bit system. Let's hope for 1 GB. */ result = ONE_GIGABYTE; -#endif +#endif /* SIZEOF_VOID_P >= 8 */ } else { /* We detected it, so let's pick 3/4 of the total RAM as our limit. */ const uint64_t avail = (ram / 4) * 3; @@ -4445,6 +4602,12 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->NoExec && !new_val->NoExec) { + *msg = tor_strdup("While Tor is running, disabling " + "NoExec is not allowed."); + return -1; + } + if (sandbox_is_active()) { #define SB_NOCHANGE_STR(opt) \ do { \ @@ -4535,6 +4698,8 @@ options_transition_affects_descriptor(const or_options_t *old_options, get_effective_bwburst(old_options) != get_effective_bwburst(new_options) || !opt_streq(old_options->ContactInfo, new_options->ContactInfo) || + !opt_streq(old_options->BridgeDistribution, + new_options->BridgeDistribution) || !config_lines_eq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || old_options->AccountingMax != new_options->AccountingMax || @@ -4588,7 +4753,7 @@ get_windows_conf_root(void) path[sizeof(path)-1] = '\0'; #else strlcpy(path,tpath,sizeof(path)); -#endif +#endif /* defined(UNICODE) */ /* Now we need to free the memory that the path-idl was stored in. In * typical Windows fashion, we can't just call 'free()' on it. */ @@ -4604,7 +4769,7 @@ get_windows_conf_root(void) is_set = 1; return path; } -#endif +#endif /* defined(_WIN32) */ /** Return the default location for our torrc file (if <b>defaults_file</b> is * false), or for the torrc-defaults file (if <b>defaults_file</b> is true). */ @@ -4628,7 +4793,7 @@ get_default_conf_file(int defaults_file) } #else return defaults_file ? CONFDIR "/torrc-defaults" : CONFDIR "/torrc"; -#endif +#endif /* defined(DISABLE_SYSTEM_TORRC) || ... */ } /** Verify whether lst is a list of strings containing valid-looking @@ -4779,9 +4944,9 @@ find_torrc_filename(config_line_t *cmd_arg, } else { fname = dflt ? tor_strdup(dflt) : NULL; } -#else +#else /* !(!defined(_WIN32)) */ fname = dflt ? tor_strdup(dflt) : NULL; -#endif +#endif /* !defined(_WIN32) */ } } return fname; @@ -4932,6 +5097,9 @@ options_init_from_torrc(int argc, char **argv) for (p_index = cmdline_only_options; p_index; p_index = p_index->next) { if (!strcmp(p_index->key,"--keygen")) { command = CMD_KEYGEN; + } else if (!strcmp(p_index->key, "--key-expiration")) { + command = CMD_KEY_EXPIRATION; + command_arg = p_index->value; } else if (!strcmp(p_index->key,"--list-fingerprint")) { command = CMD_LIST_FINGERPRINT; } else if (!strcmp(p_index->key, "--hash-password")) { @@ -5179,6 +5347,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, } newoptions->IncludeUsed = cf_has_include; + in_option_validation = 1; /* Validate newoptions */ if (options_validate(oldoptions, newoptions, newdefaultoptions, @@ -5191,17 +5360,20 @@ options_init_from_string(const char *cf_defaults, const char *cf, err = SETOPT_ERR_TRANSITION; goto err; } + in_option_validation = 0; if (set_options(newoptions, msg)) { err = SETOPT_ERR_SETTING; goto err; /* frees and replaces old options */ } + or_options_free(global_default_options); global_default_options = newdefaultoptions; return SETOPT_OK; err: + in_option_validation = 0; or_options_free(newoptions); or_options_free(newdefaultoptions); if (*msg) { @@ -5405,7 +5577,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options, } #else log_warn(LD_CONFIG, "Syslog is not supported on this system. Sorry."); -#endif +#endif /* defined(HAVE_SYSLOG_H) */ goto cleanup; } @@ -5722,6 +5894,15 @@ parse_transport_line(const or_options_t *options, goto err; } + if (is_managed && options->NoExec) { + log_warn(LD_CONFIG, + "Managed proxies are not compatible with NoExec mode; ignoring." + "(%sTransportPlugin line was %s)", + server ? "Server" : "Client", escaped(line)); + r = 0; + goto done; + } + if (is_managed) { /* managed */ @@ -5992,6 +6173,8 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, dirinfo_type_t type = 0; double weight = 1.0; + memset(v3_digest, 0, sizeof(v3_digest)); + items = smartlist_new(); smartlist_split_string(items, line, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); @@ -6241,7 +6424,6 @@ port_cfg_new(size_t namelen) cfg->entry_cfg.ipv6_traffic = 1; cfg->entry_cfg.dns_request = 1; cfg->entry_cfg.onion_traffic = 1; - cfg->entry_cfg.cache_ipv4_answers = 1; cfg->entry_cfg.prefer_ipv6_virtaddr = 1; return cfg; } @@ -6256,8 +6438,9 @@ port_cfg_free(port_cfg_t *port) /** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is * on a publicly routable address. */ static void -warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname, - int listener_type) +warn_nonlocal_client_ports(const smartlist_t *ports, + const char *portname, + const int listener_type) { SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) { if (port->type != listener_type) @@ -6414,6 +6597,55 @@ warn_client_dns_cache(const char *option, int disabling) } /** + * Validate the configured bridge distribution method from a BridgeDistribution + * config line. + * + * The input <b>bd</b>, is a string taken from the BridgeDistribution config + * line (if present). If the option wasn't set, return 0 immediately. The + * BridgeDistribution option is then validated. Currently valid, recognised + * options are: + * + * - "none" + * - "any" + * - "https" + * - "email" + * - "moat" + * - "hyphae" + * + * If the option string is unrecognised, a warning will be logged and 0 is + * returned. If the option string contains an invalid character, -1 is + * returned. + **/ +STATIC int +check_bridge_distribution_setting(const char *bd) +{ + if (bd == NULL) + return 0; + + const char *RECOGNIZED[] = { + "none", "any", "https", "email", "moat", "hyphae" + }; + unsigned i; + for (i = 0; i < ARRAY_LENGTH(RECOGNIZED); ++i) { + if (!strcmp(bd, RECOGNIZED[i])) + return 0; + } + + const char *cp = bd; + // Method = (KeywordChar | "_") + + while (TOR_ISALNUM(*cp) || *cp == '-' || *cp == '_') + ++cp; + + if (*cp == 0) { + log_warn(LD_CONFIG, "Unrecognized BridgeDistribution value %s. I'll " + "assume you know what you are doing...", escaped(bd)); + return 0; // we reached the end of the string; all is well + } else { + return -1; // we found a bad character in the string. + } +} + +/** * Parse port configuration for a single port type. * * Read entries of the "FooPort" type from the list <b>ports</b>. Syntax is @@ -6507,7 +6739,7 @@ parse_port_config(smartlist_t *out, bind_ipv4_only = 0, bind_ipv6_only = 0, ipv4_traffic = 1, ipv6_traffic = 1, prefer_ipv6 = 0, dns_request = 1, onion_traffic = 1, - cache_ipv4 = 1, use_cached_ipv4 = 0, + cache_ipv4 = 0, use_cached_ipv4 = 0, cache_ipv6 = 0, use_cached_ipv6 = 0, prefer_ipv6_automap = 1, world_writable = 0, group_writable = 0, relax_dirmode_check = 0, @@ -6606,7 +6838,7 @@ parse_port_config(smartlist_t *out, } else if (!strcasecmp(elt, "AllAddrs")) { all_addrs = 1; -#endif +#endif /* 0 */ } else if (!strcasecmp(elt, "IPv4Only")) { bind_ipv4_only = 1; } else if (!strcasecmp(elt, "IPv6Only")) { @@ -6942,7 +7174,8 @@ parse_ports(or_options_t *options, int validate_only, options->SocksPort_lines, "Socks", CONN_TYPE_AP_LISTENER, "127.0.0.1", 9050, - CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES|gw_flag) < 0) { + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { *msg = tor_strdup("Invalid SocksPort configuration"); goto err; } @@ -6970,6 +7203,15 @@ parse_ports(or_options_t *options, int validate_only, *msg = tor_strdup("Invalid NatdPort configuration"); goto err; } + if (parse_port_config(ports, + options->HTTPTunnelPort_lines, + "HTTP Tunnel", CONN_TYPE_AP_HTTP_CONNECT_LISTENER, + "127.0.0.1", 0, + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { + *msg = tor_strdup("Invalid HTTPTunnelPort configuration"); + goto err; + } { unsigned control_port_flags = CL_PORT_NO_STREAM_OPTIONS | CL_PORT_WARN_NONLOCAL; @@ -7047,6 +7289,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1); options->NATDPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1); + options->HTTPTunnelPort_set = + !! count_real_listeners(ports, CONN_TYPE_AP_HTTP_CONNECT_LISTENER, 1); /* Use options->ControlSocket to test if a control socket is set */ options->ControlPort_set = !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0); @@ -7377,7 +7621,7 @@ normalize_data_directory(or_options_t *options) strlcpy(p,get_windows_conf_root(),MAX_PATH); options->DataDirectory = p; return 0; -#else +#else /* !(defined(_WIN32)) */ const char *d = options->DataDirectory; if (!d) d = "~/.tor"; @@ -7403,7 +7647,7 @@ normalize_data_directory(or_options_t *options) options->DataDirectory = fn; } return 0; -#endif +#endif /* defined(_WIN32) */ } /** Check and normalize the value of options->DataDirectory; return 0 if it @@ -7891,13 +8135,28 @@ parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) memset(&options->OutboundBindAddresses, 0, sizeof(options->OutboundBindAddresses)); } - parse_outbound_address_lines(options->OutboundBindAddress, - OUTBOUND_ADDR_EXIT_AND_OR, options, validate_only, msg); - parse_outbound_address_lines(options->OutboundBindAddressOR, - OUTBOUND_ADDR_OR, options, validate_only, msg); - parse_outbound_address_lines(options->OutboundBindAddressExit, - OUTBOUND_ADDR_EXIT, options, validate_only, msg); + + if (parse_outbound_address_lines(options->OutboundBindAddress, + OUTBOUND_ADDR_EXIT_AND_OR, options, + validate_only, msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressOR, + OUTBOUND_ADDR_OR, options, validate_only, + msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressExit, + OUTBOUND_ADDR_EXIT, options, validate_only, + msg) < 0) { + goto err; + } + return 0; + err: + return -1; } /** Load one of the geoip files, <a>family</a> determining which @@ -7919,10 +8178,10 @@ config_load_geoip_file_(sa_family_t family, } geoip_load_file(family, fname); tor_free(free_fname); -#else +#else /* !(defined(_WIN32)) */ (void)default_fname; geoip_load_file(family, fname); -#endif +#endif /* defined(_WIN32) */ } /** Load geoip files for IPv4 and IPv6 if <a>options</a> and @@ -7997,9 +8256,9 @@ init_cookie_authentication(const char *fname, const char *header, log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname)); } } -#else +#else /* !(!defined(_WIN32)) */ (void) group_readable; -#endif +#endif /* !defined(_WIN32) */ /* Success! */ log_info(LD_GENERAL, "Generated auth cookie file in '%s'.", escaped(fname)); diff --git a/src/or/config.h b/src/or/config.h index 27aec7fe3d..efdd8c59b0 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -18,6 +18,10 @@ #define KERNEL_MAY_SUPPORT_IPFW #endif +/** Lowest allowable value for HeartbeatPeriod; if this is too low, we might + * expose more information than we're comfortable with. */ +#define MIN_HEARTBEAT_PERIOD (30*60) + MOCK_DECL(const char*, get_dirportfrontpage, (void)); MOCK_DECL(const or_options_t *, get_options, (void)); MOCK_DECL(or_options_t *, get_options_mutable, (void)); @@ -27,6 +31,7 @@ const char *safe_str_client(const char *address); const char *safe_str(const char *address); const char *escaped_safe_str_client(const char *address); const char *escaped_safe_str(const char *address); +int get_protocol_warning_severity_level(void); const char *get_version(void); const char *get_short_version(void); setopt_err_t options_trial_assign(config_line_t *list, unsigned flags, @@ -198,7 +203,9 @@ STATIC int parse_port_config(smartlist_t *out, const char *defaultaddr, int defaultport, const unsigned flags); -#endif -#endif +STATIC int check_bridge_distribution_setting(const char *bd); +#endif /* defined(CONFIG_PRIVATE) */ + +#endif /* !defined(TOR_CONFIG_H) */ diff --git a/src/or/confparse.h b/src/or/confparse.h index 9c4205d07c..6f0b3b325c 100644 --- a/src/or/confparse.h +++ b/src/or/confparse.h @@ -40,6 +40,36 @@ typedef enum config_type_t { CONFIG_TYPE_OBSOLETE, /**< Obsolete (ignored) option. */ } config_type_t; +#ifdef TOR_UNIT_TESTS +/** + * Union used when building in test mode typechecking the members of a type + * used with confparse.c. See CONF_CHECK_VAR_TYPE for a description of how + * it is used. */ +typedef union { + char **STRING; + char **FILENAME; + int *UINT; /* yes, really: Even though the confparse type is called + * "UINT", it still uses the C int type -- it just enforces that + * the values are in range [0,INT_MAX]. + */ + int *INT; + int *PORT; + int *INTERVAL; + int *MSEC_INTERVAL; + uint64_t *MEMUNIT; + double *DOUBLE; + int *BOOL; + int *AUTOBOOL; + time_t *ISOTIME; + smartlist_t **CSV; + smartlist_t **CSV_INTERVAL; + config_line_t **LINELIST; + config_line_t **LINELIST_S; + config_line_t **LINELIST_V; + routerset_t **ROUTERSET; +} confparse_dummy_values_t; +#endif + /** An abbreviation for a configuration option allowed on the command line. */ typedef struct config_abbrev_t { const char *abbreviated; @@ -64,8 +94,52 @@ typedef struct config_var_t { * value. */ off_t var_offset; /**< Offset of the corresponding member of or_options_t. */ const char *initvalue; /**< String (or null) describing initial value. */ + +#ifdef TOR_UNIT_TESTS + /** Used for compiler-magic to typecheck the corresponding field in the + * corresponding struct. Only used in unit test mode, at compile-time. */ + confparse_dummy_values_t var_ptr_dummy; +#endif } config_var_t; +/* Macros to define extra members inside config_var_t fields, and at the + * end of a list of them. + */ +#ifdef TOR_UNIT_TESTS +/* This is a somewhat magic type-checking macro for users of confparse.c. + * It initializes a union member "confparse_dummy_values_t.conftype" with + * the address of a static member "tp_dummy.member". This + * will give a compiler warning unless the member field is of the correct + * type. + * + * (This warning is mandatory, because a type mismatch here violates the type + * compatibility constraint for simple assignment, and requires a diagnostic, + * according to the C spec.) + * + * For example, suppose you say: + * "CONF_CHECK_VAR_TYPE(or_options_t, STRING, Address)". + * Then this macro will evaluate to: + * { .STRING = &or_options_t_dummy.Address } + * And since confparse_dummy_values_t.STRING has type "char **", that + * expression will create a warning unless or_options_t.Address also + * has type "char *". + */ +#define CONF_CHECK_VAR_TYPE(tp, conftype, member) \ + { . conftype = &tp ## _dummy . member } +#define CONF_TEST_MEMBERS(tp, conftype, member) \ + , CONF_CHECK_VAR_TYPE(tp, conftype, member) +#define END_OF_CONFIG_VARS \ + { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL, { .INT=NULL } } +#define DUMMY_TYPECHECK_INSTANCE(tp) \ + static tp tp ## _dummy +#else +#define CONF_TEST_MEMBERS(tp, conftype, member) +#define END_OF_CONFIG_VARS { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } +/* Repeatedly declarable incomplete struct to absorb redundant semicolons */ +#define DUMMY_TYPECHECK_INSTANCE(tp) \ + struct tor_semicolon_eater +#endif + /** Type of a callback to validate whether a given configuration is * well-formed and consistent. See options_trial_assign() for documentation * of arguments. */ @@ -129,5 +203,5 @@ const char *config_expand_abbrev(const config_format_t *fmt, int command_line, int warn_obsolete); void warn_deprecated_option(const char *what, const char *why); -#endif +#endif /* !defined(TOR_CONFPARSE_H) */ diff --git a/src/or/connection.c b/src/or/connection.c index 192d705b5b..d2cf4fb416 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -37,7 +37,7 @@ * they call connection_stop_reading() or connection_stop_writing(). * * To queue data to be written on a connection, call - * connection_write_to_buf(). When data arrives, the + * connection_buf_add(). When data arrives, the * connection_process_inbuf() callback is invoked, which dispatches to a * type-specific function (such as connection_edge_process_inbuf() for * example). Connection types that need notice of when data has been written @@ -58,6 +58,7 @@ #include "or.h" #include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" /* * Define this so we get channel internal functions, since we're implementing * part of a subclass (channel_tls_t). @@ -84,7 +85,10 @@ #include "geoip.h" #include "main.h" #include "hs_common.h" +#include "hs_ident.h" #include "nodelist.h" +#include "proto_http.h" +#include "proto_socks.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -123,8 +127,9 @@ static int connection_finished_flushing(connection_t *conn); static int connection_flushed_some(connection_t *conn); static int connection_finished_connecting(connection_t *conn); static int connection_reached_eof(connection_t *conn); -static int connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, - int *socket_error); +static int connection_buf_read_from_socket(connection_t *conn, + ssize_t *max_to_read, + int *socket_error); static int connection_process_inbuf(connection_t *conn, int package_partial); static void client_check_address_changed(tor_socket_t sock); static void set_constrained_socket_buffers(tor_socket_t sock, int size); @@ -157,7 +162,8 @@ static smartlist_t *outgoing_addrs = NULL; case CONN_TYPE_CONTROL_LISTENER: \ case CONN_TYPE_AP_TRANS_LISTENER: \ case CONN_TYPE_AP_NATD_LISTENER: \ - case CONN_TYPE_AP_DNS_LISTENER + case CONN_TYPE_AP_DNS_LISTENER: \ + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER /**************************************************************/ @@ -184,6 +190,7 @@ conn_type_to_string(int type) case CONN_TYPE_CONTROL: return "Control"; case CONN_TYPE_EXT_OR: return "Extended OR"; case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return "HTTP tunnel listener"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -605,6 +612,7 @@ connection_free_(connection_t *conn) } if (CONN_IS_EDGE(conn)) { rend_data_free(TO_EDGE_CONN(conn)->rend_data); + hs_ident_edge_conn_free(TO_EDGE_CONN(conn)->hs_ident); } if (conn->type == CONN_TYPE_CONTROL) { control_connection_t *control_conn = TO_CONTROL_CONN(conn); @@ -636,6 +644,7 @@ connection_free_(connection_t *conn) } rend_data_free(dir_conn->rend_data); + hs_ident_dir_conn_free(dir_conn->hs_ident); if (dir_conn->guard_state) { /* Cancel before freeing, if it's still there. */ entry_guard_cancel(&dir_conn->guard_state); @@ -695,7 +704,7 @@ connection_free,(connection_t *conn)) connection_ap_warn_and_unmark_if_pending_circ(TO_ENTRY_CONN(conn), "connection_free"); } -#endif +#endif /* 1 */ connection_unregister_events(conn); connection_free_(conn); } @@ -807,7 +816,7 @@ connection_mark_for_close_(connection_t *conn, int line, const char *file) * CONN_TYPE_OR checks; this should be called when you either are sure that * if this is an or_connection_t the controlling channel has been notified * (e.g. with connection_or_notify_error()), or you actually are the - * connection_or_close_for_error() or connection_or_close_normally function. + * connection_or_close_for_error() or connection_or_close_normally() function. * For all other cases, use connection_mark_and_flush() instead, which * checks for or_connection_t properly, instead. See below. */ @@ -923,7 +932,7 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, *len_out = sizeof(struct sockaddr_un); return sockaddr; } -#else +#else /* !(defined(HAVE_SYS_UN_H) || defined(RUNNING_DOXYGEN)) */ static struct sockaddr * create_unix_sockaddr(const char *listenaddress, char **readable_address, socklen_t *len_out) @@ -936,7 +945,7 @@ create_unix_sockaddr(const char *listenaddress, char **readable_address, tor_fragile_assert(); return NULL; } -#endif /* HAVE_SYS_UN_H */ +#endif /* defined(HAVE_SYS_UN_H) || defined(RUNNING_DOXYGEN) */ /** Warn that an accept or a connect has failed because we're running out of * TCP sockets we can use on current system. Rate-limit these warnings so @@ -1051,7 +1060,7 @@ check_location_for_unix_socket(const or_options_t *options, const char *path, tor_free(p); return r; } -#endif +#endif /* defined(HAVE_SYS_UN_H) */ /** Tell the TCP stack that it shouldn't wait for a long time after * <b>sock</b> has closed before reusing its port. Return 0 on success, @@ -1074,7 +1083,7 @@ make_socket_reuseable(tor_socket_t sock) return -1; } return 0; -#endif +#endif /* defined(_WIN32) */ } #ifdef _WIN32 @@ -1095,12 +1104,12 @@ make_win32_socket_exclusive(tor_socket_t sock) return -1; } return 0; -#else +#else /* !(defined(SO_EXCLUSIVEADDRUSE)) */ (void) sock; return 0; -#endif +#endif /* defined(SO_EXCLUSIVEADDRUSE) */ } -#endif +#endif /* defined(_WIN32) */ /** Max backlog to pass to listen. We start at */ static int listen_limit = INT_MAX; @@ -1190,7 +1199,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, conn_type_to_string(type), tor_socket_strerror(errno)); } -#endif +#endif /* defined(_WIN32) */ #if defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) if (options->TransProxyType_parsed == TPT_TPROXY && @@ -1207,7 +1216,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(e), extra); } } -#endif +#endif /* defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) */ #ifdef IPV6_V6ONLY if (listensockaddr->sa_family == AF_INET6) { @@ -1222,7 +1231,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, /* Keep going; probably not harmful. */ } } -#endif +#endif /* defined(IPV6_V6ONLY) */ if (bind(s,listensockaddr,socklen) < 0) { const char *helpfulhint = ""; @@ -1325,7 +1334,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, goto err; } } -#endif +#endif /* defined(HAVE_PWD_H) */ { unsigned mode; @@ -1356,7 +1365,7 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(tor_socket_errno(s))); goto err; } -#endif /* HAVE_SYS_UN_H */ +#endif /* defined(HAVE_SYS_UN_H) */ } else { log_err(LD_BUG, "Got unexpected address family %d.", listensockaddr->sa_family); @@ -1703,6 +1712,8 @@ connection_init_accepted_conn(connection_t *conn, TO_ENTRY_CONN(conn)->is_transparent_ap = 1; conn->state = AP_CONN_STATE_NATD_WAIT; break; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: + conn->state = AP_CONN_STATE_HTTP_CONNECT_WAIT; } break; case CONN_TYPE_DIR: @@ -2141,7 +2152,7 @@ connection_proxy_connect(connection_t *conn, int type) fmt_addrport(&conn->addr, conn->port)); } - connection_write_to_buf(buf, strlen(buf), conn); + connection_buf_add(buf, strlen(buf), conn); conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK; break; } @@ -2207,7 +2218,7 @@ connection_proxy_connect(connection_t *conn, int type) buf[8] = 0; /* no userid */ } - connection_write_to_buf((char *)buf, buf_size, conn); + connection_buf_add((char *)buf, buf_size, conn); tor_free(buf); conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; @@ -2238,7 +2249,7 @@ connection_proxy_connect(connection_t *conn, int type) conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE; } - connection_write_to_buf((char *)buf, 2 + buf[1], conn); + connection_buf_add((char *)buf, 2 + buf[1], conn); break; } @@ -2344,7 +2355,7 @@ connection_send_socks5_connect(connection_t *conn) memcpy(buf + 20, &port, 2); } - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK; } @@ -2470,7 +2481,7 @@ connection_read_proxy_handshake(connection_t *conn) if (socks_args_string) tor_free(socks_args_string); - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK; ret = 0; @@ -2619,7 +2630,7 @@ retry_listener_ports(smartlist_t *old_conns, if (port->is_unix_addr && !geteuid() && (options->User) && strcmp(options->User, "root")) continue; -#endif +#endif /* !defined(_WIN32) */ if (port->is_unix_addr) { listensockaddr = (struct sockaddr *) @@ -3055,9 +3066,11 @@ connection_buckets_decrement(connection_t *conn, time_t now, (unsigned long)num_read, (unsigned long)num_written, conn_type_to_string(conn->type), conn_state_to_string(conn->type, conn->state)); - if (num_written >= INT_MAX) num_written = 1; - if (num_read >= INT_MAX) num_read = 1; - tor_fragile_assert(); + tor_assert_nonfatal_unreached(); + if (num_written >= INT_MAX) + num_written = 1; + if (num_read >= INT_MAX) + num_read = 1; } record_num_bytes_transferred_impl(conn, now, num_read, num_written); @@ -3369,7 +3382,7 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) /** Read bytes from conn-\>s and process them. * - * It calls connection_read_to_buf() to bring in any new bytes, + * It calls connection_buf_read_from_socket() to bring in any new bytes, * and then calls connection_process_inbuf() to process them. * * Mark the connection and return -1 if you want to close it, else @@ -3395,6 +3408,7 @@ connection_handle_read_impl(connection_t *conn) case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_AP_NATD_LISTENER: + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_AP); case CONN_TYPE_DIR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_DIR); @@ -3411,7 +3425,7 @@ connection_handle_read_impl(connection_t *conn) tor_assert(!conn->marked_for_close); before = buf_datalen(conn->inbuf); - if (connection_read_to_buf(conn, &max_to_read, &socket_error) < 0) { + if (connection_buf_read_from_socket(conn, &max_to_read, &socket_error) < 0) { /* There's a read error; kill the connection.*/ if (conn->type == CONN_TYPE_OR) { connection_or_notify_error(TO_OR_CONN(conn), @@ -3508,7 +3522,7 @@ connection_handle_read(connection_t *conn) * Return -1 if we want to break conn, else return 0. */ static int -connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, +connection_buf_read_from_socket(connection_t *conn, ssize_t *max_to_read, int *socket_error) { int result; @@ -3549,7 +3563,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, initial_size = buf_datalen(conn->inbuf); /* else open, or closing */ - result = read_to_buf_tls(or_conn->tls, at_most, conn->inbuf); + result = buf_read_from_tls(conn->inbuf, or_conn->tls, at_most); if (TOR_TLS_IS_ERROR(result) || result == TOR_TLS_CLOSE) or_conn->tls_error = result; else @@ -3585,10 +3599,8 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, connection_start_reading(conn); } /* we're already reading, one hopes */ - result = 0; break; case TOR_TLS_DONE: /* no data read, so nothing to process */ - result = 0; break; /* so we call bucket_decrement below */ default: break; @@ -3598,7 +3610,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* If we have any pending bytes, we read them now. This *can* * take us over our read allotment, but really we shouldn't be * believing that SSL bytes are the same as TCP bytes anyway. */ - int r2 = read_to_buf_tls(or_conn->tls, pending, conn->inbuf); + int r2 = buf_read_from_tls(conn->inbuf, or_conn->tls, pending); if (BUG(r2<0)) { log_warn(LD_BUG, "apparently, reading pending bytes can fail."); return -1; @@ -3610,7 +3622,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, result, (long)n_read, (long)n_written); } else if (conn->linked) { if (conn->linked_conn) { - result = move_buf_to_buf(conn->inbuf, conn->linked_conn->outbuf, + result = buf_move_to_buf(conn->inbuf, conn->linked_conn->outbuf, &conn->linked_conn->outbuf_flushlen); } else { result = 0; @@ -3628,8 +3640,10 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* !connection_speaks_cells, !conn->linked_conn. */ int reached_eof = 0; CONN_LOG_PROTECT(conn, - result = read_to_buf(conn->s, at_most, conn->inbuf, &reached_eof, - socket_error)); + result = buf_read_from_socket(conn->inbuf, conn->s, + at_most, + &reached_eof, + socket_error)); if (reached_eof) conn->inbuf_reached_eof = 1; @@ -3698,17 +3712,17 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /** A pass-through to fetch_from_buf. */ int -connection_fetch_from_buf(char *string, size_t len, connection_t *conn) +connection_buf_get_bytes(char *string, size_t len, connection_t *conn) { - return fetch_from_buf(string, len, conn->inbuf); + return buf_get_bytes(conn->inbuf, string, len); } -/** As fetch_from_buf_line(), but read from a connection's input buffer. */ +/** As buf_get_line(), but read from a connection's input buffer. */ int -connection_fetch_from_buf_line(connection_t *conn, char *data, +connection_buf_get_line(connection_t *conn, char *data, size_t *data_len) { - return fetch_from_buf_line(conn->inbuf, data, data_len); + return buf_get_line(conn->inbuf, data, data_len); } /** As fetch_from_buf_http, but fetches from a connection's input buffer_t as @@ -3745,7 +3759,7 @@ connection_outbuf_too_full(connection_t *conn) * * This function gets called either from conn_write_callback() in main.c * when libevent tells us that conn wants to write, or below - * from connection_write_to_buf() when an entire TLS record is ready. + * from connection_buf_add() when an entire TLS record is ready. * * Update <b>conn</b>-\>timestamp_lastwritten to now, and call flush_buf * or flush_buf_tls appropriately. If it succeeds and there are no more @@ -3856,7 +3870,7 @@ connection_handle_write_impl(connection_t *conn, int force) /* else open, or closing */ initial_size = buf_datalen(conn->outbuf); - result = flush_buf_tls(or_conn->tls, conn->outbuf, + result = buf_flush_to_tls(conn->outbuf, or_conn->tls, max_to_write, &conn->outbuf_flushlen); /* If we just flushed the last bytes, tell the channel on the @@ -3919,8 +3933,8 @@ connection_handle_write_impl(connection_t *conn, int force) result = (int)(initial_size-buf_datalen(conn->outbuf)); } else { CONN_LOG_PROTECT(conn, - result = flush_buf(conn->s, conn->outbuf, - max_to_write, &conn->outbuf_flushlen)); + result = buf_flush_to_socket(conn->outbuf, conn->s, + max_to_write, &conn->outbuf_flushlen)); if (result < 0) { if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); @@ -4060,11 +4074,11 @@ connection_write_to_buf_impl_,(const char *string, size_t len, if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); int done = zlib < 0; - CONN_LOG_PROTECT(conn, r = write_to_buf_compress(conn->outbuf, + CONN_LOG_PROTECT(conn, r = buf_add_compress(conn->outbuf, dir_conn->compress_state, string, len, done)); } else { - CONN_LOG_PROTECT(conn, r = write_to_buf(string, len, conn->outbuf)); + CONN_LOG_PROTECT(conn, r = buf_add(conn->outbuf, string, len)); } if (r < 0) { if (CONN_IS_EDGE(conn)) { @@ -4103,6 +4117,38 @@ connection_write_to_buf_impl_,(const char *string, size_t len, } } +#define CONN_GET_ALL_TEMPLATE(var, test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + smartlist_t *ret_conns = smartlist_new(); \ + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, var) { \ + if (var && (test) && !var->marked_for_close) \ + smartlist_add(ret_conns, var); \ + } SMARTLIST_FOREACH_END(var); \ + return ret_conns; \ + STMT_END + +/* Return a list of connections that aren't close and matches the given type + * and state. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_state(int type, int state) +{ + CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state)); +} + +/* Return a list of connections that aren't close and matches the given type + * and purpose. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_purpose(int type, int purpose) +{ + CONN_GET_ALL_TEMPLATE(conn, + (conn->type == type && conn->purpose == purpose)); +} + /** Return a connection_t * from get_connection_array() that satisfies test on * var, and that is not marked for close. */ #define CONN_GET_TEMPLATE(var, test) \ @@ -4287,6 +4333,7 @@ connection_is_listener(connection_t *conn) conn->type == CONN_TYPE_AP_TRANS_LISTENER || conn->type == CONN_TYPE_AP_DNS_LISTENER || conn->type == CONN_TYPE_AP_NATD_LISTENER || + conn->type == CONN_TYPE_AP_HTTP_CONNECT_LISTENER || conn->type == CONN_TYPE_DIR_LISTENER || conn->type == CONN_TYPE_CONTROL_LISTENER) return 1; @@ -4954,9 +5001,9 @@ assert_connection_ok(connection_t *conn, time_t now) /* buffers */ if (conn->inbuf) - assert_buf_ok(conn->inbuf); + buf_assert_ok(conn->inbuf); if (conn->outbuf) - assert_buf_ok(conn->outbuf); + buf_assert_ok(conn->outbuf); if (conn->type == CONN_TYPE_OR) { or_connection_t *or_conn = TO_OR_CONN(conn); @@ -5178,7 +5225,7 @@ clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, const char *source) { char dbuf[64]; - char *ext_source = NULL; + char *ext_source = NULL, *warn = NULL; format_time_interval(dbuf, sizeof(dbuf), apparent_skew); if (conn) tor_asprintf(&ext_source, "%s:%s:%d", source, conn->address, conn->port); @@ -5192,9 +5239,14 @@ clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, apparent_skew > 0 ? "ahead" : "behind", dbuf, apparent_skew > 0 ? "behind" : "ahead", (!conn || trusted) ? "" : ", or they are sending us the wrong time"); - if (trusted) + if (trusted) { control_event_general_status(LOG_WARN, "CLOCK_SKEW SKEW=%ld SOURCE=%s", apparent_skew, ext_source); + tor_asprintf(&warn, "Clock skew %ld in %s from %s", apparent_skew, + received, source); + control_event_bootstrap_problem(warn, "CLOCK_SKEW", conn, 1); + } + tor_free(warn); tor_free(ext_source); } diff --git a/src/or/connection.h b/src/or/connection.h index 36e45aef38..4a5bd6971b 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -123,8 +123,8 @@ void connection_bucket_refill(int seconds_elapsed, time_t now); int connection_handle_read(connection_t *conn); -int connection_fetch_from_buf(char *string, size_t len, connection_t *conn); -int connection_fetch_from_buf_line(connection_t *conn, char *data, +int connection_buf_get_bytes(char *string, size_t len, connection_t *conn); +int connection_buf_get_line(connection_t *conn, char *data, size_t *data_len); int connection_fetch_from_buf_http(connection_t *conn, char **headers_out, size_t max_headerlen, @@ -139,18 +139,18 @@ int connection_flush(connection_t *conn); MOCK_DECL(void, connection_write_to_buf_impl_, (const char *string, size_t len, connection_t *conn, int zlib)); /* DOCDOC connection_write_to_buf */ -static void connection_write_to_buf(const char *string, size_t len, +static void connection_buf_add(const char *string, size_t len, connection_t *conn); /* DOCDOC connection_write_to_buf_compress */ -static void connection_write_to_buf_compress(const char *string, size_t len, +static void connection_buf_add_compress(const char *string, size_t len, dir_connection_t *conn, int done); static inline void -connection_write_to_buf(const char *string, size_t len, connection_t *conn) +connection_buf_add(const char *string, size_t len, connection_t *conn) { connection_write_to_buf_impl_(string, len, conn, 0); } static inline void -connection_write_to_buf_compress(const char *string, size_t len, +connection_buf_add_compress(const char *string, size_t len, dir_connection_t *conn, int done) { connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); @@ -182,6 +182,8 @@ MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); +smartlist_t *connection_list_by_type_state(int type, int state); +smartlist_t *connection_list_by_type_purpose(int type, int purpose); smartlist_t *connection_dir_list_by_purpose_and_resource( int purpose, const char *resource); @@ -284,7 +286,7 @@ MOCK_DECL(STATIC int,connection_connect_sockaddr, MOCK_DECL(STATIC void, kill_conn_list_for_oos, (smartlist_t *conns)); MOCK_DECL(STATIC smartlist_t *, pick_oos_victims, (int n)); -#endif +#endif /* defined(CONNECTION_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONNECTION_H) */ diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 8480a35458..f178917f0b 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -76,9 +76,15 @@ #include "dirserv.h" #include "hibernate.h" #include "hs_common.h" +#include "hs_cache.h" +#include "hs_client.h" +#include "hs_circuit.h" #include "main.h" +#include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "proto_http.h" +#include "proto_socks.h" #include "reasons.h" #include "relay.h" #include "rendclient.h" @@ -109,7 +115,7 @@ #define TRANS_NETFILTER #define TRANS_NETFILTER_IPV6 #endif -#endif +#endif /* defined(HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H) */ #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) #include <net/if.h> @@ -152,7 +158,9 @@ connection_mark_unattached_ap_,(entry_connection_t *conn, int endreason, * but we should fix it someday anyway. */ if ((edge_conn->on_circuit != NULL || edge_conn->edge_has_sent_end) && connection_edge_is_rendezvous_stream(edge_conn)) { - rend_client_note_connection_attempt_ended(edge_conn->rend_data); + if (edge_conn->rend_data) { + rend_client_note_connection_attempt_ended(edge_conn->rend_data); + } } if (base_conn->marked_for_close) { @@ -236,6 +244,11 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_HTTP_CONNECT_WAIT: + if (connection_ap_process_http_connect(EDGE_TO_ENTRY_CONN(conn)) < 0) { + return -1; + } + return 0; case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) { @@ -485,6 +498,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: case AP_CONN_STATE_RESOLVE_WAIT: + case AP_CONN_STATE_HTTP_CONNECT_WAIT: return 0; default: log_warn(LD_BUG, "Called in unexpected state %d.",conn->base_.state); @@ -647,7 +661,7 @@ connection_ap_about_to_close(entry_connection_t *entry_conn) connection_ap_warn_and_unmark_if_pending_circ(entry_conn, "about_to_close"); } -#endif +#endif /* 1 */ control_event_stream_bandwidth(edge_conn); control_event_stream_status(entry_conn, STREAM_EVENT_CLOSED, @@ -857,9 +871,9 @@ connection_ap_rescan_and_attach_pending(void) entry_conn->marked_pending_circ_line = 0; \ entry_conn->marked_pending_circ_file = 0; \ } while (0) -#else +#else /* !(defined(DEBUGGING_17659)) */ #define UNMARK() do { } while (0) -#endif +#endif /* defined(DEBUGGING_17659) */ /** Tell any AP streams that are listed as waiting for a new circuit to try * again. If there is an available circuit for a stream, attach it. Otherwise, @@ -965,7 +979,7 @@ connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn, log_warn(LD_BUG, "(Previously called from %s:%d.)\n", f2 ? f2 : "<NULL>", entry_conn->marked_pending_circ_line); -#endif +#endif /* defined(DEBUGGING_17659) */ log_backtrace(LOG_WARN, LD_BUG, "To debug, this may help"); return; } @@ -1073,7 +1087,8 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) if (!entry_conn->chosen_exit_optional && !entry_conn->chosen_exit_retries) continue; - r1 = node_get_by_nickname(entry_conn->chosen_exit_name, 0); + r1 = node_get_by_nickname(entry_conn->chosen_exit_name, + NNF_NO_WARN_UNNAMED); r2 = node_get_by_id(info->identity_digest); if (!r1 || !r2 || r1 != r2) continue; @@ -1176,10 +1191,10 @@ consider_plaintext_ports(entry_connection_t *conn, uint16_t port) * See connection_ap_handshake_rewrite_and_attach()'s * documentation for arguments and return value. */ -int -connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath) +MOCK_IMPL(int, +connection_ap_rewrite_and_attach_if_allowed,(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)) { const or_options_t *options = get_options(); @@ -1222,10 +1237,9 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, /* Check for whether this is a .exit address. By default, those are * disallowed when they're coming straight from the client, but you're * allowed to have them in MapAddress commands and so forth. */ - if (!strcmpend(socks->address, ".exit") && !options->AllowDotExit) { + if (!strcmpend(socks->address, ".exit")) { log_warn(LD_APP, "The \".exit\" notation is disabled in Tor due to " - "security risks. Set AllowDotExit in your torrc to enable " - "it (at your own risk)."); + "security risks."); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); out->end_reason = END_STREAM_REASON_TORPROTOCOL; @@ -1391,6 +1405,199 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } +/** We just received a SOCKS request in <b>conn</b> to an onion address of type + * <b>addresstype</b>. Start connecting to the onion service. */ +static int +connection_ap_handle_onion(entry_connection_t *conn, + socks_request_t *socks, + origin_circuit_t *circ, + hostname_type_t addresstype) +{ + time_t now = approx_time(); + connection_t *base_conn = ENTRY_TO_CONN(conn); + + /* If .onion address requests are disabled, refuse the request */ + if (!conn->entry_cfg.onion_traffic) { + log_warn(LD_APP, "Onion address %s requested from a port with .onion " + "disabled", safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + + /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those + * for hidden service addresses. */ + if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { + /* if it's a resolve request, fail it right now, rather than + * building all the circuits and then realizing it won't work. */ + log_warn(LD_APP, + "Resolve requests to hidden services not allowed. Failing."); + connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR, + 0,NULL,-1,TIME_MAX); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_SOCKSPROTOCOL | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + return -1; + } + + /* If we were passed a circuit, then we need to fail. .onion addresses + * only work when we launch our own circuits for now. */ + if (circ) { + log_warn(LD_CONTROL, "Attachstream to a circuit is not " + "supported for .onion addresses currently. Failing."); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + } + + /* Interface: Regardless of HS version after the block below we should have + set onion_address, rend_cache_lookup_result, and descriptor_is_usable. */ + const char *onion_address = NULL; + int rend_cache_lookup_result = -ENOENT; + int descriptor_is_usable = 0; + + if (addresstype == ONION_V2_HOSTNAME) { /* it's a v2 hidden service */ + rend_cache_entry_t *entry = NULL; + /* Look up if we have client authorization configured for this hidden + * service. If we do, associate it with the rend_data. */ + rend_service_authorization_t *client_auth = + rend_client_lookup_service_authorization(socks->address); + + const uint8_t *cookie = NULL; + rend_auth_type_t auth_type = REND_NO_AUTH; + if (client_auth) { + log_info(LD_REND, "Using previously configured client authorization " + "for hidden service request."); + auth_type = client_auth->auth_type; + cookie = client_auth->descriptor_cookie; + } + + /* Fill in the rend_data field so we can start doing a connection to + * a hidden service. */ + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = + rend_data_client_create(socks->address, NULL, (char *) cookie, + auth_type); + if (rend_data == NULL) { + return -1; + } + onion_address = rend_data_get_address(rend_data); + log_info(LD_REND,"Got a hidden service request for ID '%s'", + safe_str_client(onion_address)); + + rend_cache_lookup_result = rend_cache_lookup_entry(onion_address,-1, + &entry); + if (!rend_cache_lookup_result && entry) { + descriptor_is_usable = rend_client_any_intro_points_usable(entry); + } + } else { /* it's a v3 hidden service */ + tor_assert(addresstype == ONION_V3_HOSTNAME); + const hs_descriptor_t *cached_desc = NULL; + int retval; + /* Create HS conn identifier with HS pubkey */ + hs_ident_edge_conn_t *hs_conn_ident = + tor_malloc_zero(sizeof(hs_ident_edge_conn_t)); + + retval = hs_parse_address(socks->address, &hs_conn_ident->identity_pk, + NULL, NULL); + if (retval < 0) { + log_warn(LD_GENERAL, "failed to parse hs address"); + tor_free(hs_conn_ident); + return -1; + } + ENTRY_TO_EDGE_CONN(conn)->hs_ident = hs_conn_ident; + + onion_address = socks->address; + + /* Check the v3 desc cache */ + cached_desc = hs_cache_lookup_as_client(&hs_conn_ident->identity_pk); + if (cached_desc) { + rend_cache_lookup_result = 0; + descriptor_is_usable = + hs_client_any_intro_points_usable(&hs_conn_ident->identity_pk, + cached_desc); + log_info(LD_GENERAL, "Found %s descriptor in cache for %s. %s.", + (descriptor_is_usable) ? "usable" : "unusable", + safe_str_client(onion_address), + (descriptor_is_usable) ? "Not fetching." : "Refecting."); + } else { + rend_cache_lookup_result = -ENOENT; + } + } + + /* Lookup the given onion address. If invalid, stop right now. + * Otherwise, we might have it in the cache or not. */ + unsigned int refetch_desc = 0; + if (rend_cache_lookup_result < 0) { + switch (-rend_cache_lookup_result) { + case EINVAL: + /* We should already have rejected this address! */ + log_warn(LD_BUG,"Invalid service name '%s'", + safe_str_client(onion_address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + return -1; + case ENOENT: + /* We didn't have this; we should look it up. */ + log_info(LD_REND, "No descriptor found in our cache for %s. Fetching.", + safe_str_client(onion_address)); + refetch_desc = 1; + break; + default: + log_warn(LD_BUG, "Unknown cache lookup error %d", + rend_cache_lookup_result); + return -1; + } + } + + /* Help predict that we'll want to do hidden service circuits in the + * future. We're not sure if it will need a stable circuit yet, but + * we know we'll need *something*. */ + rep_hist_note_used_internal(now, 0, 1); + + /* Now we have a descriptor but is it usable or not? If not, refetch. + * Also, a fetch could have been requested if the onion address was not + * found in the cache previously. */ + if (refetch_desc || !descriptor_is_usable) { + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); + connection_ap_mark_as_non_pending_circuit(conn); + base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; + if (addresstype == ONION_V2_HOSTNAME) { + tor_assert(edge_conn->rend_data); + rend_client_refetch_v2_renddesc(edge_conn->rend_data); + /* Whatever the result of the refetch, we don't go further. */ + return 0; + } else { + tor_assert(addresstype == ONION_V3_HOSTNAME); + tor_assert(edge_conn->hs_ident); + /* Attempt to fetch the hsv3 descriptor. Check the retval to see how it + * went and act accordingly. */ + int ret = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + switch (ret) { + case HS_CLIENT_FETCH_MISSING_INFO: + /* Keeping the connection in descriptor wait state is fine because + * once we get enough dirinfo or a new live consensus, the HS client + * subsystem is notified and every connection in that state will + * trigger a fetch for the service key. */ + case HS_CLIENT_FETCH_LAUNCHED: + case HS_CLIENT_FETCH_PENDING: + case HS_CLIENT_FETCH_HAVE_DESC: + return 0; + case HS_CLIENT_FETCH_ERROR: + case HS_CLIENT_FETCH_NO_HSDIRS: + case HS_CLIENT_FETCH_NOT_ALLOWED: + /* Can't proceed further and better close the SOCKS request. */ + return -1; + } + } + } + + /* We have the descriptor! So launch a connection to the HS. */ + log_info(LD_REND, "Descriptor is here. Great."); + + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + /* We'll try to attach it at the next event loop, or whenever + * we call connection_ap_attach_pending() */ + connection_ap_mark_as_pending_circuit(conn); + return 0; +} + /** Connection <b>conn</b> just finished its socks handshake, or the * controller asked us to take care of it. If <b>circ</b> is defined, * then that's where we'll want to attach it. Otherwise we have to @@ -1466,23 +1673,23 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, const node_t *node = NULL; /* If this .exit was added by an AUTOMAP, then it came straight from - * a user. Make sure that options->AllowDotExit permits that! */ - if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) { - /* Whoops; this one is stale. It must have gotten added earlier, - * when AllowDotExit was on. */ - log_warn(LD_APP,"Stale automapped address for '%s.exit', with " - "AllowDotExit disabled. Refusing.", + * a user. That's not safe. */ + if (exit_source == ADDRMAPSRC_AUTOMAP) { + /* Whoops; this one is stale. It must have gotten added earlier? + * (Probably this is not possible, since AllowDotExit no longer + * exists.) */ + log_warn(LD_APP,"Stale automapped address for '%s.exit'. Refusing.", safe_str_client(socks->address)); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + tor_assert_nonfatal_unreached(); return -1; } /* Double-check to make sure there are no .exits coming from * impossible/weird sources. */ - if (exit_source == ADDRMAPSRC_DNS || - (exit_source == ADDRMAPSRC_NONE && !options->AllowDotExit)) { + if (exit_source == ADDRMAPSRC_DNS || exit_source == ADDRMAPSRC_NONE) { /* It shouldn't be possible to get a .exit address from any of these * sources. */ log_warn(LD_BUG,"Address '%s.exit', with impossible source for the " @@ -1507,7 +1714,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (s[1] != '\0') { /* Looks like a real .exit one. */ conn->chosen_exit_name = tor_strdup(s+1); - node = node_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (exit_source == ADDRMAPSRC_TRACKEXIT) { /* We 5 tries before it expires the addressmap */ @@ -1528,7 +1735,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * form that means (foo's address).foo.exit. */ conn->chosen_exit_name = tor_strdup(socks->address); - node = node_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (node) { *socks->address = 0; node_get_address_string(node, socks->address, sizeof(socks->address)); @@ -1557,7 +1764,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* Now, we handle everything that isn't a .onion address. */ - if (addresstype != ONION_HOSTNAME) { + if (addresstype != ONION_V2_HOSTNAME && addresstype != ONION_V3_HOSTNAME) { /* Not a hidden-service request. It's either a hostname or an IP, * possibly with a .exit that we stripped off. We're going to check * if we're allowed to connect/resolve there, and then launch the @@ -1584,7 +1791,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ /* socks->address is a non-onion hostname or IP address. * If we can't do any non-onion requests, refuse the connection. @@ -1835,116 +2042,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return 0; } else { /* If we get here, it's a request for a .onion address! */ + tor_assert(addresstype == ONION_V2_HOSTNAME || + addresstype == ONION_V3_HOSTNAME); tor_assert(!automap); - - /* If .onion address requests are disabled, refuse the request */ - if (!conn->entry_cfg.onion_traffic) { - log_warn(LD_APP, "Onion address %s requested from a port with .onion " - "disabled", safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } - - /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those - * for hidden service addresses. */ - if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { - /* if it's a resolve request, fail it right now, rather than - * building all the circuits and then realizing it won't work. */ - log_warn(LD_APP, - "Resolve requests to hidden services not allowed. Failing."); - connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR, - 0,NULL,-1,TIME_MAX); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_SOCKSPROTOCOL | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return -1; - } - - /* If we were passed a circuit, then we need to fail. .onion addresses - * only work when we launch our own circuits for now. */ - if (circ) { - log_warn(LD_CONTROL, "Attachstream to a circuit is not " - "supported for .onion addresses currently. Failing."); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - } - - /* Look up if we have client authorization configured for this hidden - * service. If we do, associate it with the rend_data. */ - rend_service_authorization_t *client_auth = - rend_client_lookup_service_authorization(socks->address); - - const uint8_t *cookie = NULL; - rend_auth_type_t auth_type = REND_NO_AUTH; - if (client_auth) { - log_info(LD_REND, "Using previously configured client authorization " - "for hidden service request."); - auth_type = client_auth->auth_type; - cookie = client_auth->descriptor_cookie; - } - - /* Fill in the rend_data field so we can start doing a connection to - * a hidden service. */ - rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = - rend_data_client_create(socks->address, NULL, (char *) cookie, - auth_type); - if (rend_data == NULL) { - return -1; - } - const char *onion_address = rend_data_get_address(rend_data); - log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(onion_address)); - - /* Lookup the given onion address. If invalid, stop right now. - * Otherwise, we might have it in the cache or not. */ - unsigned int refetch_desc = 0; - rend_cache_entry_t *entry = NULL; - const int rend_cache_lookup_result = - rend_cache_lookup_entry(onion_address, -1, &entry); - if (rend_cache_lookup_result < 0) { - switch (-rend_cache_lookup_result) { - case EINVAL: - /* We should already have rejected this address! */ - log_warn(LD_BUG,"Invalid service name '%s'", - safe_str_client(onion_address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; - case ENOENT: - /* We didn't have this; we should look it up. */ - refetch_desc = 1; - break; - default: - log_warn(LD_BUG, "Unknown cache lookup error %d", - rend_cache_lookup_result); - return -1; - } - } - - /* Help predict that we'll want to do hidden service circuits in the - * future. We're not sure if it will need a stable circuit yet, but - * we know we'll need *something*. */ - rep_hist_note_used_internal(now, 0, 1); - - /* Now we have a descriptor but is it usable or not? If not, refetch. - * Also, a fetch could have been requested if the onion address was not - * found in the cache previously. */ - if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { - connection_ap_mark_as_non_pending_circuit(conn); - base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; - log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(onion_address)); - rend_client_refetch_v2_renddesc(rend_data); - return 0; - } - - /* We have the descriptor! So launch a connection to the HS. */ - base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - log_info(LD_REND, "Descriptor is here. Great."); - - /* We'll try to attach it at the next event loop, or whenever - * we call connection_ap_attach_pending() */ - connection_ap_mark_as_pending_circuit(conn); - return 0; + return connection_ap_handle_onion(conn, socks, circ, addresstype); } return 0; /* unreached but keeps the compiler happy */ @@ -1966,7 +2067,7 @@ get_pf_socket(void) #else /* works on NetBSD and FreeBSD */ pf = tor_open_cloexec("/dev/pf", O_RDWR, 0); -#endif +#endif /* defined(OpenBSD) */ if (pf < 0) { log_warn(LD_NET, "open(\"/dev/pf\") failed: %s", strerror(errno)); @@ -1976,9 +2077,10 @@ get_pf_socket(void) pf_socket = pf; return pf_socket; } -#endif +#endif /* defined(TRANS_PF) */ -#if defined(TRANS_NETFILTER) || defined(TRANS_PF) || defined(TRANS_TPROXY) +#if defined(TRANS_NETFILTER) || defined(TRANS_PF) || \ + defined(TRANS_TPROXY) /** Try fill in the address of <b>req</b> from the socket configured * with <b>conn</b>. */ static int @@ -1998,7 +2100,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) } goto done; } -#endif +#endif /* defined(TRANS_TPROXY) */ #ifdef TRANS_NETFILTER int rv = -1; @@ -2008,13 +2110,13 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IP, SO_ORIGINAL_DST, (struct sockaddr*)&orig_dst, &orig_dst_len); break; -#endif +#endif /* defined(TRANS_NETFILTER_IPV4) */ #ifdef TRANS_NETFILTER_IPV6 case AF_INET6: rv = getsockopt(ENTRY_TO_CONN(conn)->s, SOL_IPV6, IP6T_SO_ORIGINAL_DST, (struct sockaddr*)&orig_dst, &orig_dst_len); break; -#endif +#endif /* defined(TRANS_NETFILTER_IPV6) */ default: log_warn(LD_BUG, "Received transparent data from an unsuported socket family %d", @@ -2040,7 +2142,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) (void)req; log_warn(LD_BUG, "Unable to determine destination from socket."); return -1; -#endif +#endif /* defined(TRANS_NETFILTER) || ... */ done: tor_addr_from_sockaddr(&addr, (struct sockaddr*)&orig_dst, &req->port); @@ -2048,7 +2150,7 @@ destination_from_socket(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(TRANS_NETFILTER) || defined(TRANS_PF) || ... */ #ifdef TRANS_PF static int @@ -2082,7 +2184,7 @@ destination_from_pf(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(__FreeBSD__) */ memset(&pnl, 0, sizeof(pnl)); pnl.proto = IPPROTO_TCP; @@ -2131,7 +2233,7 @@ destination_from_pf(entry_connection_t *conn, socks_request_t *req) return 0; } -#endif +#endif /* defined(TRANS_PF) */ /** Fetch the original destination address and port from a * system-specific interface and put them into a @@ -2167,7 +2269,7 @@ connection_ap_get_original_destination(entry_connection_t *conn, log_warn(LD_BUG, "Called connection_ap_get_original_destination, but no " "transparent proxy method was configured."); return -1; -#endif +#endif /* defined(TRANS_NETFILTER) || ... */ } /** connection_edge_process_inbuf() found a conn in state @@ -2202,7 +2304,7 @@ connection_ap_handshake_process_socks(entry_connection_t *conn) if (socks->replylen) { had_reply = 1; - connection_write_to_buf((const char*)socks->reply, socks->replylen, + connection_buf_add((const char*)socks->reply, socks->replylen, base_conn); socks->replylen = 0; if (sockshere == -1) { @@ -2299,7 +2401,7 @@ connection_ap_process_natd(entry_connection_t *conn) /* look for LF-terminated "[DEST ip_addr port]" * where ip_addr is a dotted-quad and port is in string form */ - err = connection_fetch_from_buf_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); + err = connection_buf_get_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); if (err == 0) return 0; if (err < 0) { @@ -2348,6 +2450,108 @@ connection_ap_process_natd(entry_connection_t *conn) return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); } +/** Called on an HTTP CONNECT entry connection when some bytes have arrived, + * but we have not yet received a full HTTP CONNECT request. Try to parse an + * HTTP CONNECT request from the connection's inbuf. On success, set up the + * connection's socks_request field and try to attach the connection. On + * failure, send an HTTP reply, and mark the connection. + */ +STATIC int +connection_ap_process_http_connect(entry_connection_t *conn) +{ + if (BUG(ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_HTTP_CONNECT_WAIT)) + return -1; + + char *headers = NULL, *body = NULL; + char *command = NULL, *addrport = NULL; + char *addr = NULL; + size_t bodylen = 0; + + const char *errmsg = NULL; + int rv = 0; + + const int http_status = + fetch_from_buf_http(ENTRY_TO_CONN(conn)->inbuf, &headers, 8192, + &body, &bodylen, 1024, 0); + if (http_status < 0) { + /* Bad http status */ + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } else if (http_status == 0) { + /* no HTTP request yet. */ + goto done; + } + + const int cmd_status = parse_http_command(headers, &command, &addrport); + if (cmd_status < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + tor_assert(command); + tor_assert(addrport); + if (strcasecmp(command, "connect")) { + errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + goto err; + } + + tor_assert(conn->socks_request); + socks_request_t *socks = conn->socks_request; + uint16_t port; + if (tor_addr_port_split(LOG_WARN, addrport, &addr, &port) < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + if (strlen(addr) >= MAX_SOCKS_ADDR_LEN) { + errmsg = "HTTP/1.0 414 Request-URI Too Long\r\n\r\n"; + goto err; + } + + /* Abuse the 'username' and 'password' fields here. They are already an + * abuse. */ + { + char *authorization = http_get_header(headers, "Proxy-Authorization: "); + if (authorization) { + socks->username = authorization; // steal reference + socks->usernamelen = strlen(authorization); + } + char *isolation = http_get_header(headers, "X-Tor-Stream-Isolation: "); + if (isolation) { + socks->password = isolation; // steal reference + socks->passwordlen = strlen(isolation); + } + } + + socks->command = SOCKS_COMMAND_CONNECT; + socks->listener_type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; + strlcpy(socks->address, addr, sizeof(socks->address)); + socks->port = port; + + control_event_stream_status(conn, STREAM_EVENT_NEW, 0); + + rv = connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); + + // XXXX send a "100 Continue" message? + + goto done; + + err: + if (BUG(errmsg == NULL)) + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + log_warn(LD_EDGE, "Saying %s", escaped(errmsg)); + connection_buf_add(errmsg, strlen(errmsg), ENTRY_TO_CONN(conn)); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_HTTPPROTOCOL| + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + + done: + tor_free(headers); + tor_free(body); + tor_free(command); + tor_free(addrport); + tor_free(addr); + return rv; +} + /** Iterate over the two bytes of stream_id until we get one that is not * already in use; return it. Return 0 if can't get a unique stream_id. */ @@ -2455,8 +2659,8 @@ connection_ap_get_begincell_flags(entry_connection_t *ap_conn) * * If ap_conn is broken, mark it for close and return -1. Else return 0. */ -int -connection_ap_handshake_send_begin(entry_connection_t *ap_conn) +MOCK_IMPL(int, +connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)) { char payload[CELL_PAYLOAD_SIZE]; int payload_len; @@ -2967,15 +3171,22 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, return; } if (replylen) { /* we already have a reply in mind */ - connection_write_to_buf(reply, replylen, ENTRY_TO_CONN(conn)); + connection_buf_add(reply, replylen, ENTRY_TO_CONN(conn)); conn->socks_request->has_finished = 1; return; } - if (conn->socks_request->socks_version == 4) { + if (conn->socks_request->listener_type == + CONN_TYPE_AP_HTTP_CONNECT_LISTENER) { + const char *response = end_reason_to_http_connect_response_line(endreason); + if (!response) { + response = "HTTP/1.0 400 Bad Request\r\n\r\n"; + } + connection_buf_add(response, strlen(response), ENTRY_TO_CONN(conn)); + } else if (conn->socks_request->socks_version == 4) { memset(buf,0,SOCKS4_NETWORK_LEN); buf[1] = (status==SOCKS5_SUCCEEDED ? SOCKS4_GRANTED : SOCKS4_REJECT); /* leave version, destport, destip zero */ - connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); + connection_buf_add(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); } else if (conn->socks_request->socks_version == 5) { size_t buf_len; memset(buf,0,sizeof(buf)); @@ -2994,7 +3205,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, /* 4 bytes for the header, 2 bytes for the port, 16 for the address. */ buf_len = 22; } - connection_write_to_buf(buf,buf_len,ENTRY_TO_CONN(conn)); + connection_buf_add(buf,buf_len,ENTRY_TO_CONN(conn)); } /* If socks_version isn't 4 or 5, don't send anything. * This can happen in the case of AP bridges. */ @@ -3007,7 +3218,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, * <0 and set *<b>end_reason_out</b> to the end reason we should send back to * the client. * - * Return -1 in the case where want to send a RELAY_END cell, and < -1 when + * Return -1 in the case where we want to send a RELAY_END cell, and < -1 when * we don't. **/ STATIC int @@ -3066,6 +3277,88 @@ begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, return 0; } +/** For the given <b>circ</b> and the edge connection <b>conn</b>, setup the + * connection, attach it to the circ and connect it. Return 0 on success + * or END_CIRC_AT_ORIGIN if we can't find the requested hidden service port + * where the caller should close the circuit. */ +static int +handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn) +{ + int ret; + origin_circuit_t *origin_circ; + + assert_circuit_ok(circ); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(conn); + + log_debug(LD_REND, "Connecting the hidden service rendezvous circuit " + "to the service destination."); + + origin_circ = TO_ORIGIN_CIRCUIT(circ); + conn->base_.address = tor_strdup("(rendezvous)"); + conn->base_.state = EXIT_CONN_STATE_CONNECTING; + + /* The circuit either has an hs identifier for v3+ or a rend_data for legacy + * service. */ + if (origin_circ->rend_data) { + conn->rend_data = rend_data_dup(origin_circ->rend_data); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = rend_service_set_connection_addr_port(conn, origin_circ); + } else if (origin_circ->hs_ident) { + /* Setup the identifier to be the one for the circuit service. */ + conn->hs_ident = + hs_ident_edge_conn_new(&origin_circ->hs_ident->identity_pk); + tor_assert(connection_edge_is_rendezvous_stream(conn)); + ret = hs_service_set_conn_addr_port(origin_circ, conn); + } else { + /* We should never get here if the circuit's purpose is rendezvous. */ + tor_assert_nonfatal_unreached(); + return -1; + } + if (ret < 0) { + log_info(LD_REND, "Didn't find rendezvous service (addr%s, port %d)", + fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); + /* Send back reason DONE because we want to make hidden service port + * scanning harder thus instead of returning that the exit policy + * didn't match, which makes it obvious that the port is closed, + * return DONE and kill the circuit. That way, a user (malicious or + * not) needs one circuit per bad port unless it matches the policy of + * the hidden service. */ + relay_send_end_cell_from_edge(conn->stream_id, circ, + END_STREAM_REASON_DONE, + origin_circ->cpath->prev); + connection_free(TO_CONN(conn)); + + /* Drop the circuit here since it might be someone deliberately + * scanning the hidden service ports. Note that this mitigates port + * scanning by adding more work on the attacker side to successfully + * scan but does not fully solve it. */ + if (ret < -1) { + return END_CIRC_AT_ORIGIN; + } else { + return 0; + } + } + + /* Link the circuit and the connection crypt path. */ + conn->cpath_layer = origin_circ->cpath->prev; + + /* Add it into the linked list of p_streams on this circuit */ + conn->next_stream = origin_circ->p_streams; + origin_circ->p_streams = conn; + conn->on_circuit = circ; + assert_circuit_ok(circ); + + hs_inc_rdv_stream_counter(origin_circ); + + /* Connect tor to the hidden service destination. */ + connection_exit_connect(conn); + + /* For path bias: This circuit was used successfully */ + pathbias_mark_use_success(origin_circ); + return 0; +} + /** A relay 'begin' or 'begin_dir' cell has arrived, and either we are * an exit hop for the circuit, or we are the origin and it is a * rendezvous begin. @@ -3141,7 +3434,8 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) port = bcell.port; if (or_circ && or_circ->p_chan) { - if ((or_circ->is_first_hop || + const int client_chan = channel_is_client(or_circ->p_chan); + if ((client_chan || (!connection_or_digest_is_known_relay( or_circ->p_chan->identity_digest) && should_refuse_unknown_exits(options)))) { @@ -3151,10 +3445,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Attempt by %s to open a stream %s. Closing.", safe_str(channel_get_canonical_remote_descr(or_circ->p_chan)), - or_circ->is_first_hop ? "on first hop of circuit" : - "from unknown relay"); + client_chan ? "on first hop of circuit" : + "from unknown relay"); relay_send_end_cell_from_edge(rh.stream_id, circ, - or_circ->is_first_hop ? + client_chan ? END_STREAM_REASON_TORPROTOCOL : END_STREAM_REASON_MISC, NULL); @@ -3217,58 +3511,10 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ) n_stream->deliver_window = STREAMWINDOW_START; if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) { - tor_assert(origin_circ); - log_info(LD_REND,"begin is for rendezvous. configuring stream."); - n_stream->base_.address = tor_strdup("(rendezvous)"); - n_stream->base_.state = EXIT_CONN_STATE_CONNECTING; - n_stream->rend_data = rend_data_dup(origin_circ->rend_data); - tor_assert(connection_edge_is_rendezvous_stream(n_stream)); - assert_circuit_ok(circ); - - const int r = rend_service_set_connection_addr_port(n_stream, origin_circ); - if (r < 0) { - log_info(LD_REND,"Didn't find rendezvous service (port %d)", - n_stream->base_.port); - /* Send back reason DONE because we want to make hidden service port - * scanning harder thus instead of returning that the exit policy - * didn't match, which makes it obvious that the port is closed, - * return DONE and kill the circuit. That way, a user (malicious or - * not) needs one circuit per bad port unless it matches the policy of - * the hidden service. */ - relay_send_end_cell_from_edge(rh.stream_id, circ, - END_STREAM_REASON_DONE, - layer_hint); - connection_free(TO_CONN(n_stream)); - tor_free(address); - - /* Drop the circuit here since it might be someone deliberately - * scanning the hidden service ports. Note that this mitigates port - * scanning by adding more work on the attacker side to successfully - * scan but does not fully solve it. */ - if (r < -1) - return END_CIRC_AT_ORIGIN; - else - return 0; - } - assert_circuit_ok(circ); - log_debug(LD_REND,"Finished assigning addr/port"); - n_stream->cpath_layer = origin_circ->cpath->prev; /* link it */ - - /* add it into the linked list of p_streams on this circuit */ - n_stream->next_stream = origin_circ->p_streams; - n_stream->on_circuit = circ; - origin_circ->p_streams = n_stream; - assert_circuit_ok(circ); - - origin_circ->rend_data->nr_streams++; - - connection_exit_connect(n_stream); - - /* For path bias: This circuit was used successfully */ - pathbias_mark_use_success(origin_circ); - tor_free(address); - return 0; + /* We handle this circuit and stream in this function for all supported + * hidden service version. */ + return handle_hs_exit_conn(circ, n_stream); } tor_strlower(address); n_stream->base_.address = address; @@ -3566,8 +3812,12 @@ int connection_edge_is_rendezvous_stream(const edge_connection_t *conn) { tor_assert(conn); - if (conn->rend_data) + /* It should not be possible to set both of these structs */ + tor_assert_nonfatal(!(conn->rend_data && conn->hs_ident)); + + if (conn->rend_data || conn->hs_ident) { return 1; + } return 0; } @@ -3591,7 +3841,7 @@ connection_ap_can_use_exit(const entry_connection_t *conn, */ if (conn->chosen_exit_name) { const node_t *chosen_exit = - node_get_by_nickname(conn->chosen_exit_name, 1); + node_get_by_nickname(conn->chosen_exit_name, 0); if (!chosen_exit || tor_memneq(chosen_exit->identity, exit_node->identity, DIGEST_LEN)) { /* doesn't match */ @@ -3640,10 +3890,12 @@ connection_ap_can_use_exit(const entry_connection_t *conn, } /** If address is of the form "y.onion" with a well-formed handle y: - * Put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Put a NUL after y, lower-case it, and return ONION_V2_HOSTNAME or + * ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "x.y.onion" with a well-formed handle x: - * Drop "x.", put a NUL after y, lower-case it, and return ONION_HOSTNAME. + * Drop "x.", put a NUL after y, lower-case it, and return + * ONION_V2_HOSTNAME or ONION_V3_HOSTNAME depending on the HS version. * * If address is of the form "y.onion" with a badly-formed handle y: * Return BAD_HOSTNAME and log a message. @@ -3659,7 +3911,7 @@ parse_extended_hostname(char *address) { char *s; char *q; - char query[REND_SERVICE_ID_LEN_BASE32+1]; + char query[HS_SERVICE_ADDR_LEN_BASE32+1]; s = strrchr(address,'.'); if (!s) @@ -3679,14 +3931,17 @@ parse_extended_hostname(char *address) goto failed; /* reject sub-domain, as DNS does */ } q = (NULL == q) ? address : q + 1; - if (strlcpy(query, q, REND_SERVICE_ID_LEN_BASE32+1) >= - REND_SERVICE_ID_LEN_BASE32+1) + if (strlcpy(query, q, HS_SERVICE_ADDR_LEN_BASE32+1) >= + HS_SERVICE_ADDR_LEN_BASE32+1) goto failed; if (q != address) { memmove(address, q, strlen(q) + 1 /* also get \0 */); } - if (rend_valid_service_id(query)) { - return ONION_HOSTNAME; /* success */ + if (rend_valid_v2_service_id(query)) { + return ONION_V2_HOSTNAME; /* success */ + } + if (hs_address_is_valid(query)) { + return ONION_V3_HOSTNAME; } failed: /* otherwise, return to previous state and return 0 */ diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index e4780b3c7d..c6583d3845 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -33,7 +33,8 @@ int connection_edge_finished_connecting(edge_connection_t *conn); void connection_ap_about_to_close(entry_connection_t *edge_conn); void connection_exit_about_to_close(edge_connection_t *edge_conn); -int connection_ap_handshake_send_begin(entry_connection_t *ap_conn); +MOCK_DECL(int, + connection_ap_handshake_send_begin,(entry_connection_t *ap_conn)); int connection_ap_handshake_send_resolve(entry_connection_t *ap_conn); entry_connection_t *connection_ap_make_link(connection_t *partner, @@ -88,16 +89,18 @@ int connection_ap_process_transparent(entry_connection_t *conn); int address_is_invalid_destination(const char *address, int client); -int connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath); +MOCK_DECL(int, connection_ap_rewrite_and_attach_if_allowed, + (entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)); int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); /** Possible return values for parse_extended_hostname. */ typedef enum hostname_type_t { - NORMAL_HOSTNAME, ONION_HOSTNAME, EXIT_HOSTNAME, BAD_HOSTNAME + NORMAL_HOSTNAME, ONION_V2_HOSTNAME, ONION_V3_HOSTNAME, + EXIT_HOSTNAME, BAD_HOSTNAME } hostname_type_t; hostname_type_t parse_extended_hostname(char *address); @@ -186,7 +189,9 @@ typedef struct { STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn, rewrite_result_t *out); -#endif -#endif +STATIC int connection_ap_process_http_connect(entry_connection_t *conn); +#endif /* defined(CONNECTION_EDGE_PRIVATE) */ + +#endif /* !defined(TOR_CONNECTION_EDGE_H) */ diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 59a9bdffda..9e34063609 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -46,6 +46,7 @@ #include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" +#include "proto_cell.h" #include "reasons.h" #include "relay.h" #include "rephist.h" @@ -472,7 +473,7 @@ var_cell_pack_header(const var_cell_t *cell, char *hdr_out, int wide_circ_ids) var_cell_t * var_cell_new(uint16_t payload_len) { - size_t size = STRUCT_OFFSET(var_cell_t, payload) + payload_len; + size_t size = offsetof(var_cell_t, payload) + payload_len; var_cell_t *cell = tor_malloc_zero(size); cell->payload_len = payload_len; cell->command = 0; @@ -491,7 +492,7 @@ var_cell_copy(const var_cell_t *src) size_t size = 0; if (src != NULL) { - size = STRUCT_OFFSET(var_cell_t, payload) + src->payload_len; + size = offsetof(var_cell_t, payload) + src->payload_len; copy = tor_malloc_zero(size); copy->payload_len = src->payload_len; copy->command = src->command; @@ -726,7 +727,7 @@ connection_or_about_to_close(or_connection_t *or_conn) control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( orconn_end_reason_to_control_string(reason), reason, or_conn); } @@ -1112,7 +1113,7 @@ connection_or_connect_failed(or_connection_t *conn, { control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(get_options())) - control_event_bootstrap_problem(msg, reason, conn); + control_event_bootstrap_prob_or(msg, reason, conn); } /** <b>conn</b> got an error in connection_handle_read_impl() or @@ -1235,7 +1236,7 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port, fmt_addrport(&TO_CONN(conn)->addr, TO_CONN(conn)->port), transport_name, transport_name); - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( "Can't connect to bridge", END_OR_CONN_REASON_PT_MISSING, conn); @@ -1369,7 +1370,6 @@ connection_tls_start_handshake,(or_connection_t *conn, int receiving)) connection_start_reading(TO_CONN(conn)); log_debug(LD_HANDSHAKE,"starting TLS handshake on fd "TOR_SOCKET_T_FORMAT, conn->base_.s); - note_crypto_pk_op(receiving ? TLS_HANDSHAKE_S : TLS_HANDSHAKE_C); if (connection_tls_continue_handshake(conn) < 0) return -1; @@ -1550,6 +1550,7 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, if (identity_rcvd) { if (crypto_pk_get_digest(identity_rcvd, digest_rcvd_out) < 0) { + crypto_pk_free(identity_rcvd); return -1; } } else { @@ -1713,7 +1714,7 @@ connection_or_client_learned_peer_id(or_connection_t *conn, control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, END_OR_CONN_REASON_OR_IDENTITY); if (!authdir_mode_tests_reachability(options)) - control_event_bootstrap_problem( + control_event_bootstrap_prob_or( "Unexpected identity in router certificate", END_OR_CONN_REASON_OR_IDENTITY, conn); @@ -1742,8 +1743,9 @@ connection_or_client_learned_peer_id(or_connection_t *conn, return 0; } -/** Return when a client used this, for connection.c, since client_used - * is now one of the timestamps of channel_t */ +/** Return when we last used this channel for client activity (origin + * circuits). This is called from connection.c, since client_used is now one + * of the timestamps in channel_t */ time_t connection_or_client_used(or_connection_t *conn) @@ -1757,7 +1759,7 @@ connection_or_client_used(or_connection_t *conn) /** The v1/v2 TLS handshake is finished. * - * Make sure we are happy with the person we just handshaked with. + * Make sure we are happy with the peer we just handshaked with. * * If they initiated the connection, make sure they're not already connected, * then initialize conn from the information in router. @@ -1978,7 +1980,7 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) if (cell->command == CELL_PADDING) rep_hist_padding_count_write(PADDING_TYPE_CELL); - connection_write_to_buf(networkcell.body, cell_network_size, TO_CONN(conn)); + connection_buf_add(networkcell.body, cell_network_size, TO_CONN(conn)); /* Touch the channel's active timestamp if there is one */ if (conn->chan) { @@ -2008,8 +2010,8 @@ connection_or_write_var_cell_to_buf,(const var_cell_t *cell, tor_assert(cell); tor_assert(conn); n = var_cell_pack_header(cell, hdr, conn->wide_circ_ids); - connection_write_to_buf(hdr, n, TO_CONN(conn)); - connection_write_to_buf((char*)cell->payload, + connection_buf_add(hdr, n, TO_CONN(conn)); + connection_buf_add((char*)cell->payload, cell->payload_len, TO_CONN(conn)); if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_var_cell(conn, conn->handshake_state, cell, 0); @@ -2084,7 +2086,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); circuit_build_times_network_is_live(get_circuit_build_times_mutable()); - connection_fetch_from_buf(buf, cell_network_size, TO_CONN(conn)); + connection_buf_get_bytes(buf, cell_network_size, TO_CONN(conn)); /* retrieve cell info from buf (create the host-order struct from the * network-order string) */ diff --git a/src/or/connection_or.h b/src/or/connection_or.h index fe85a3f5fd..ee66b7c528 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -118,5 +118,5 @@ void connection_or_group_set_badness_(smartlist_t *group, int force); extern int certs_cell_ed25519_disabled_for_testing; #endif -#endif +#endif /* !defined(TOR_CONNECTION_OR_H) */ diff --git a/src/or/conscache.c b/src/or/conscache.c index 33a5495974..1a15126f97 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -15,7 +15,7 @@ * changes throughout our logic. */ #define MUST_UNMAP_TO_UNLINK -#endif +#endif /* defined(_WIN32) */ /** * A consensus_cache_entry_t is a reference-counted handle to an @@ -90,11 +90,11 @@ consensus_cache_open(const char *subdir, int max_entries) */ #define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; -#else +#else /* !(defined(MUST_UNMAP_TO_UNLINK)) */ /* Otherwise, we can just tell the storagedir to use the same limits * as this cache. */ storagedir_max_entries = max_entries; -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ cache->dir = storage_dir_new(directory, storagedir_max_entries); tor_free(directory); @@ -145,7 +145,7 @@ consensus_cache_register_with_sandbox(consensus_cache_t *cache, * conditional. */ tor_assert_nonfatal_unreached(); -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ return storage_dir_register_with_sandbox(cache->dir, cfg); } @@ -474,7 +474,7 @@ consensus_cache_get_n_filenames_available(consensus_cache_t *cache) return 0; #else tor_assert_nonfatal(max >= used); -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ return max - used; } @@ -495,7 +495,7 @@ consensus_cache_delete_pending(consensus_cache_t *cache, int force) if (ent->map) { force_ent = 0; } -#endif +#endif /* defined(MUST_UNMAP_TO_UNLINK) */ if (! force_ent) { if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { /* Somebody is using this entry right now */ @@ -622,5 +622,5 @@ consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent) return 0; } } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/conscache.h b/src/or/conscache.h index a0d74c4e08..3c89dedf43 100644 --- a/src/or/conscache.h +++ b/src/or/conscache.h @@ -58,5 +58,5 @@ int consensus_cache_entry_get_body(const consensus_cache_entry_t *ent, int consensus_cache_entry_is_mapped(consensus_cache_entry_t *ent); #endif -#endif +#endif /* !defined(TOR_CONSCACHE_H) */ diff --git a/src/or/consdiff.h b/src/or/consdiff.h index d05df74b75..eb772c0b2b 100644 --- a/src/or/consdiff.h +++ b/src/or/consdiff.h @@ -92,7 +92,7 @@ MOCK_DECL(STATIC int, MOCK_DECL(STATIC int, consensus_digest_eq,(const uint8_t *d1, const uint8_t *d2)); -#endif +#endif /* defined(CONSDIFF_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONSDIFF_H) */ diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index c5f55b6f3f..e539b61484 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -663,7 +663,7 @@ consdiffmgr_find_diff_from(consensus_cache_entry_t **entry_out, smartlist_free(matches); return result; -#endif +#endif /* 0 */ } /** diff --git a/src/or/consdiffmgr.h b/src/or/consdiffmgr.h index 079f9fe2d2..df569c8e23 100644 --- a/src/or/consdiffmgr.h +++ b/src/or/consdiffmgr.h @@ -68,7 +68,7 @@ STATIC int cdm_entry_get_sha3_value(uint8_t *digest_out, const char *label); STATIC int uncompress_or_copy(char **out, size_t *outlen, consensus_cache_entry_t *ent); -#endif +#endif /* defined(CONSDIFFMGR_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONSDIFFMGR_H) */ diff --git a/src/or/control.c b/src/or/control.c index 1837d49025..202366aaec 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -60,9 +60,12 @@ #include "hibernate.h" #include "hs_common.h" #include "main.h" +#include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "proto_control0.h" +#include "proto_http.h" #include "reasons.h" #include "rendclient.h" #include "rendcommon.h" @@ -354,7 +357,7 @@ static inline void connection_write_str_to_buf(const char *s, control_connection_t *conn) { size_t len = strlen(s); - connection_write_to_buf(s, len, TO_CONN(conn)); + connection_buf_add(s, len, TO_CONN(conn)); } /** Given a <b>len</b>-character string in <b>data</b>, made of lines @@ -367,16 +370,23 @@ connection_write_str_to_buf(const char *s, control_connection_t *conn) STATIC size_t write_escaped_data(const char *data, size_t len, char **out) { - size_t sz_out = len+8; + tor_assert(len < SIZE_MAX - 9); + size_t sz_out = len+8+1; char *outp; const char *start = data, *end; - int i; + size_t i; int start_of_line; - for (i=0; i<(int)len; ++i) { - if (data[i]== '\n') + for (i=0; i < len; ++i) { + if (data[i] == '\n') { sz_out += 2; /* Maybe add a CR; maybe add a dot. */ + if (sz_out >= SIZE_T_CEILING) { + log_warn(LD_BUG, "Input to write_escaped_data was too long"); + *out = tor_strdup(".\r\n"); + return 3; + } + } } - *out = outp = tor_malloc(sz_out+1); + *out = outp = tor_malloc(sz_out); end = data+len; start_of_line = 1; while (data < end) { @@ -402,7 +412,8 @@ write_escaped_data(const char *data, size_t len, char **out) *outp++ = '\r'; *outp++ = '\n'; *outp = '\0'; /* NUL-terminate just in case. */ - tor_assert((outp - *out) <= (int)sz_out); + tor_assert(outp >= *out); + tor_assert((size_t)(outp - *out) <= sz_out); return outp - *out; } @@ -556,7 +567,7 @@ connection_printf_to_buf(control_connection_t *conn, const char *format, ...) tor_assert(0); } - connection_write_to_buf(buf, (size_t)len, TO_CONN(conn)); + connection_buf_add(buf, (size_t)len, TO_CONN(conn)); tor_free(buf); } @@ -582,7 +593,7 @@ control_ports_write_to_file(void) smartlist_add_asprintf(lines, "UNIX_PORT=%s\n", conn->address); continue; } -#endif +#endif /* defined(AF_UNIX) */ smartlist_add_asprintf(lines, "PORT=%s:%d\n", conn->address, conn->port); } SMARTLIST_FOREACH_END(conn); @@ -599,7 +610,7 @@ control_ports_write_to_file(void) options->ControlPortWriteToFile); } } -#endif +#endif /* !defined(_WIN32) */ tor_free(joined); SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); smartlist_free(lines); @@ -781,7 +792,7 @@ queued_events_flush_all(int force) SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, control_conn) { if (control_conn->event_mask & bit) { - connection_write_to_buf(ev->msg, msg_len, TO_CONN(control_conn)); + connection_buf_add(ev->msg, msg_len, TO_CONN(control_conn)); } } SMARTLIST_FOREACH_END(control_conn); @@ -802,7 +813,7 @@ queued_events_flush_all(int force) } /** Libevent callback: Flushes pending events to controllers that are - * interested in them */ + * interested in them. */ static void flush_queued_events_cb(evutil_socket_t fd, short what, void *arg) { @@ -1063,7 +1074,7 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, tor_assert(strlen(tmp)>4); tmp[3] = ' '; msg = smartlist_join_strings(answers, "", 0, &msg_len); - connection_write_to_buf(msg, msg_len, TO_CONN(conn)); + connection_buf_add(msg, msg_len, TO_CONN(conn)); } else { connection_write_str_to_buf("250 OK\r\n", conn); } @@ -1145,7 +1156,6 @@ static const struct control_event_t control_event_table[] = { { EVENT_ERR_MSG, "ERR" }, { EVENT_NEW_DESC, "NEWDESC" }, { EVENT_ADDRMAP, "ADDRMAP" }, - { EVENT_AUTHDIR_NEWDESCS, "AUTHDIR_NEWDESCS" }, { EVENT_DESCCHANGED, "DESCCHANGED" }, { EVENT_NS, "NS" }, { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, @@ -1185,7 +1195,10 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(events, const char *, ev) { - if (!strcasecmp(ev, "EXTENDED")) { + if (!strcasecmp(ev, "EXTENDED") || + !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { + log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " + "supported.", ev); continue; } else { int i; @@ -1645,12 +1658,12 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, if (smartlist_len(reply)) { ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; r = smartlist_join_strings(reply, "\r\n", 1, &sz); - connection_write_to_buf(r, sz, TO_CONN(conn)); + connection_buf_add(r, sz, TO_CONN(conn)); tor_free(r); } else { const char *response = "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_write_to_buf(response, strlen(response), TO_CONN(conn)); + connection_buf_add(response, strlen(response), TO_CONN(conn)); } SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); @@ -1736,7 +1749,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, #else int myUid = geteuid(); tor_asprintf(answer, "%d", myUid); - #endif +#endif /* defined(_WIN32) */ } else if (!strcmp(question, "process/user")) { #ifdef _WIN32 *answer = tor_strdup(""); @@ -1749,7 +1762,7 @@ getinfo_helper_misc(control_connection_t *conn, const char *question, } else { *answer = tor_strdup(""); } - #endif +#endif /* defined(_WIN32) */ } else if (!strcmp(question, "process/descriptor-limit")) { int max_fds = get_max_sockets(); tor_asprintf(answer, "%d", max_fds); @@ -1885,27 +1898,42 @@ getinfo_helper_dir(control_connection_t *control_conn, (void) control_conn; if (!strcmpstart(question, "desc/id/")) { const routerinfo_t *ri = NULL; - const node_t *node = node_get_by_hex_id(question+strlen("desc/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0); if (node) ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/id/* " + "instead of desc/id/*."; + return 0; } } else if (!strcmpstart(question, "desc/name/")) { const routerinfo_t *ri = NULL; /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ const node_t *node = - node_get_by_nickname(question+strlen("desc/name/"), 1); + node_get_by_nickname(question+strlen("desc/name/"), 0); if (node) ri = node->ri; if (ri) { const char *body = signed_descriptor_get_body(&ri->cache_info); if (body) *answer = tor_strndup(body, ri->cache_info.signed_descriptor_len); + } else if (! we_fetch_router_descriptors(get_options())) { + /* Descriptors won't be available, provide proper error */ + *errmsg = "We fetch microdescriptors, not router " + "descriptors. You'll need to use md/name/* " + "instead of desc/name/*."; + return 0; } + } else if (!strcmp(question, "desc/download-enabled")) { + int r = we_fetch_router_descriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmp(question, "desc/all-recent")) { routerlist_t *routerlist = router_get_routerlist(); smartlist_t *sl = smartlist_new(); @@ -1975,7 +2003,7 @@ getinfo_helper_dir(control_connection_t *control_conn, return -1; } } else if (!strcmpstart(question, "md/id/")) { - const node_t *node = node_get_by_hex_id(question+strlen("md/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { @@ -1984,17 +2012,20 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "md/name/")) { /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ - const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 1); + const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0); /* XXXX duplicated code */ const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { *answer = tor_strndup(md->body, md->bodylen); } + } else if (!strcmp(question, "md/download-enabled")) { + int r = we_fetch_microdescriptors(get_options()); + tor_asprintf(answer, "%d", !!r); } else if (!strcmpstart(question, "desc-annotations/id/")) { const routerinfo_t *ri = NULL; const node_t *node = - node_get_by_hex_id(question+strlen("desc-annotations/id/")); + node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0); if (node) ri = node->ri; if (ri) { @@ -2127,7 +2158,7 @@ download_status_to_string(const download_status_t *dl) if (dl) { /* Get some substrings of the eventual output ready */ - format_iso_time(tbuf, dl->next_attempt_at); + format_iso_time(tbuf, download_status_get_next_attempt_at(dl)); switch (dl->schedule) { case DL_SCHED_GENERIC: @@ -2907,7 +2938,8 @@ getinfo_helper_sr(control_connection_t *control_conn, * *<b>a</b>. If an internal error occurs, return -1 and optionally set * *<b>error_out</b> to point to an error message to be delivered to the * controller. On success, _or if the key is not recognized_, return 0. Do not - * set <b>a</b> if the key is not recognized. + * set <b>a</b> if the key is not recognized but you may set <b>error_out</b> + * to improve the error message. */ typedef int (*getinfo_helper_t)(control_connection_t *, const char *q, char **a, @@ -3012,9 +3044,13 @@ static const getinfo_item_t getinfo_items[] = { PREFIX("desc/name/", dir, "Router descriptors by nickname."), ITEM("desc/all-recent", dir, "All non-expired, non-superseded router descriptors."), + ITEM("desc/download-enabled", dir, + "Do we try to download router descriptors?"), ITEM("desc/all-recent-extrainfo-hack", dir, NULL), /* Hack. */ PREFIX("md/id/", dir, "Microdescriptors by ID"), PREFIX("md/name/", dir, "Microdescriptors by name"), + ITEM("md/download-enabled", dir, + "Do we try to download microdescriptors?"), PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."), PREFIX("hs/client/desc/id", dir, "Hidden Service descriptor in client's cache by onion."), @@ -3162,7 +3198,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, smartlist_t *questions = smartlist_new(); smartlist_t *answers = smartlist_new(); smartlist_t *unrecognized = smartlist_new(); - char *msg = NULL, *ans = NULL; + char *ans = NULL; int i; (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ @@ -3177,20 +3213,26 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, goto done; } if (!ans) { - smartlist_add(unrecognized, (char*)q); + if (errmsg) /* use provided error message */ + smartlist_add_strdup(unrecognized, errmsg); + else /* use default error message */ + smartlist_add_asprintf(unrecognized, "Unrecognized key \"%s\"", q); } else { smartlist_add_strdup(answers, q); smartlist_add(answers, ans); } } SMARTLIST_FOREACH_END(q); + if (smartlist_len(unrecognized)) { + /* control-spec section 2.3, mid-reply '-' or end of reply ' ' */ for (i=0; i < smartlist_len(unrecognized)-1; ++i) connection_printf_to_buf(conn, - "552-Unrecognized key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); + "552-%s\r\n", + (char *)smartlist_get(unrecognized, i)); + connection_printf_to_buf(conn, - "552 Unrecognized key \"%s\"\r\n", - (char*)smartlist_get(unrecognized, i)); + "552 %s\r\n", + (char *)smartlist_get(unrecognized, i)); goto done; } @@ -3206,7 +3248,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, size_t esc_len; esc_len = write_escaped_data(v, strlen(v), &esc); connection_printf_to_buf(conn, "250+%s=\r\n", k); - connection_write_to_buf(esc, esc_len, TO_CONN(conn)); + connection_buf_add(esc, esc_len, TO_CONN(conn)); tor_free(esc); } } @@ -3217,8 +3259,8 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, smartlist_free(answers); SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); smartlist_free(questions); + SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp)); smartlist_free(unrecognized); - tor_free(msg); return 0; } @@ -3364,7 +3406,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, nodes = smartlist_new(); SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { - const node_t *node = node_get_by_nickname(n, 1); + const node_t *node = node_get_by_nickname(n, 0); if (!node) { connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); goto done; @@ -4105,7 +4147,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* Extract the first argument (either HSAddress or DescID). */ arg1 = smartlist_get(args, 0); /* Test if it's an HS address without the .onion part. */ - if (rend_valid_service_id(arg1)) { + if (rend_valid_v2_service_id(arg1)) { hsaddress = arg1; } else if (strcmpstart(arg1, v2_str) == 0 && rend_valid_descriptor_id(arg1 + v2_str_len) && @@ -4131,7 +4173,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, const char *server; server = arg + strlen(opt_server); - node = node_get_by_hex_id(server); + node = node_get_by_hex_id(server, 0); if (!node) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", server); @@ -4217,7 +4259,7 @@ handle_control_hspost(control_connection_t *conn, SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { if (!strcasecmpstart(arg, opt_server)) { const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server); + const node_t *node = node_get_by_hex_id(server, 0); if (!node || !node->rs) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", @@ -4749,7 +4791,7 @@ handle_control_del_onion(control_connection_t *conn, return 0; const char *service_id = smartlist_get(args, 0); - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { connection_printf_to_buf(conn, "512 Malformed Onion Service id\r\n"); goto out; } @@ -4890,6 +4932,38 @@ peek_connection_has_control0_command(connection_t *conn) return peek_buf_has_control0_command(conn->inbuf); } +static int +peek_connection_has_http_command(connection_t *conn) +{ + return peek_buf_has_http_command(conn->inbuf); +} + +static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy" + "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>Tor's ControlPort is not an HTTP proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>Tor's ControlPort is not an HTTP proxy</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use Tor's control port" + " as an HTTP proxy.\n" + "This is not correct: Tor's default SOCKS proxy port is 9050.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "<!-- Plus this comment, to make the body response more than 512 bytes, so " + " IE will be willing to display it. Comment comment comment comment " + " comment comment comment comment comment comment comment comment.-->\n" + "</p>\n" + "</body>\n" + "</html>\n"; + /** Called when data has arrived on a v1 control connection: Try to fetch * commands from conn->inbuf, and execute them. */ @@ -4923,12 +4997,21 @@ connection_control_process_inbuf(control_connection_t *conn) sizeof(buf)-6); body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); - connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); + connection_buf_add(buf, 4+body_len, TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); return 0; } + /* If the user has the HTTP proxy port and the control port confused. */ + if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && + peek_connection_has_http_command(TO_CONN(conn))) { + connection_write_str_to_buf(CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG, conn); + log_notice(LD_CONTROL, "Received HTTP request on ControlPort"); + connection_mark_and_flush(TO_CONN(conn)); + return 0; + } + again: while (1) { size_t last_idx; @@ -4936,7 +5019,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; - r = connection_fetch_from_buf_line(TO_CONN(conn), + r = connection_buf_get_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) @@ -5504,15 +5587,20 @@ control_event_stream_bandwidth(edge_connection_t *edge_conn) { circuit_t *circ; origin_circuit_t *ocirc; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { if (!edge_conn->n_read && !edge_conn->n_written) return 0; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", + "650 STREAM_BW "U64_FORMAT" %lu %lu %s\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written); + (unsigned long)edge_conn->n_written, + tbuf); circ = circuit_get_by_edge_conn(edge_conn); if (circ && CIRCUIT_IS_ORIGIN(circ)) { @@ -5534,6 +5622,8 @@ control_event_stream_bandwidth_used(void) if (EVENT_IS_INTERESTING(EVENT_STREAM_BANDWIDTH_USED)) { smartlist_t *conns = get_connection_array(); edge_connection_t *edge_conn; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { @@ -5543,11 +5633,14 @@ control_event_stream_bandwidth_used(void) if (!edge_conn->n_read && !edge_conn->n_written) continue; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_STREAM_BANDWIDTH_USED, - "650 STREAM_BW "U64_FORMAT" %lu %lu\r\n", + "650 STREAM_BW "U64_FORMAT" %lu %lu %s\r\n", U64_PRINTF_ARG(edge_conn->base_.global_identifier), (unsigned long)edge_conn->n_read, - (unsigned long)edge_conn->n_written); + (unsigned long)edge_conn->n_written, + tbuf); edge_conn->n_written = edge_conn->n_read = 0; } @@ -5563,6 +5656,8 @@ int control_event_circ_bandwidth_used(void) { origin_circuit_t *ocirc; + struct timeval now; + char tbuf[ISO_TIME_USEC_LEN+1]; if (!EVENT_IS_INTERESTING(EVENT_CIRC_BANDWIDTH_USED)) return 0; @@ -5572,11 +5667,15 @@ control_event_circ_bandwidth_used(void) ocirc = TO_ORIGIN_CIRCUIT(circ); if (!ocirc->n_read_circ_bw && !ocirc->n_written_circ_bw) continue; + tor_gettimeofday(&now); + format_iso_time_nospace_usec(tbuf, &now); send_control_event(EVENT_CIRC_BANDWIDTH_USED, - "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu\r\n", + "650 CIRC_BW ID=%d READ=%lu WRITTEN=%lu " + "TIME=%s\r\n", ocirc->global_identifier, (unsigned long)ocirc->n_read_circ_bw, - (unsigned long)ocirc->n_written_circ_bw); + (unsigned long)ocirc->n_written_circ_bw, + tbuf); ocirc->n_written_circ_bw = ocirc->n_read_circ_bw = 0; } SMARTLIST_FOREACH_END(circ); @@ -5747,7 +5846,7 @@ control_event_circuit_cell_stats(void) if (!get_options()->TestingEnableCellStatsEvent || !EVENT_IS_INTERESTING(EVENT_CELL_STATS)) return 0; - cell_stats = tor_malloc(sizeof(cell_stats_t));; + cell_stats = tor_malloc(sizeof(cell_stats_t)); SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) { if (!circ->testing_cell_stats) continue; @@ -5982,47 +6081,6 @@ control_event_address_mapped(const char *from, const char *to, time_t expires, return 0; } -/** The authoritative dirserver has received a new descriptor that - * has passed basic syntax checks and is properly self-signed. - * - * Notify any interested party of the new descriptor and what has - * been done with it, and also optionally give an explanation/reason. */ -int -control_event_or_authdir_new_descriptor(const char *action, - const char *desc, size_t desclen, - const char *msg) -{ - char firstline[1024]; - char *buf; - size_t totallen; - char *esc = NULL; - size_t esclen; - - if (!EVENT_IS_INTERESTING(EVENT_AUTHDIR_NEWDESCS)) - return 0; - - tor_snprintf(firstline, sizeof(firstline), - "650+AUTHDIR_NEWDESC=\r\n%s\r\n%s\r\n", - action, - msg ? msg : ""); - - /* Escape the server descriptor properly */ - esclen = write_escaped_data(desc, desclen, &esc); - - totallen = strlen(firstline) + esclen + 1; - buf = tor_malloc(totallen); - strlcpy(buf, firstline, totallen); - strlcpy(buf+strlen(firstline), esc, totallen); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - buf); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - "650 OK\r\n"); - tor_free(esc); - tor_free(buf); - - return 0; -} - /** Cached liveness for network liveness events and GETINFO */ @@ -6712,80 +6770,112 @@ control_event_bootstrap(bootstrap_status_t status, int progress) } /** Called when Tor has failed to make bootstrapping progress in a way - * that indicates a problem. <b>warn</b> gives a hint as to why, and - * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> - * is the connection that caused this problem. + * that indicates a problem. <b>warn</b> gives a human-readable hint + * as to why, and <b>reason</b> provides a controller-facing short + * tag. <b>conn</b> is the connection that caused this problem and + * can be NULL if a connection cannot be easily identified. */ -MOCK_IMPL(void, -control_event_bootstrap_problem, (const char *warn, int reason, - or_connection_t *or_conn)) +void +control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn) { int status = bootstrap_percent; const char *tag = "", *summary = ""; char buf[BOOTSTRAP_MSG_LEN]; const char *recommendation = "ignore"; int severity; + char *or_id = NULL, *hostaddr = NULL; + or_connection_t *or_conn = NULL; /* bootstrap_percent must not be in "undefined" state here. */ tor_assert(status >= 0); - if (or_conn->have_noted_bootstrap_problem) - return; - - or_conn->have_noted_bootstrap_problem = 1; - if (bootstrap_percent == 100) return; /* already bootstrapped; nothing to be done here. */ bootstrap_problems++; if (bootstrap_problems >= BOOTSTRAP_PROBLEM_THRESHOLD) - recommendation = "warn"; - - if (reason == END_OR_CONN_REASON_NO_ROUTE) - recommendation = "warn"; - - /* If we are using bridges and all our OR connections are now - closed, it means that we totally failed to connect to our - bridges. Throw a warning. */ - if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) - recommendation = "warn"; + dowarn = 1; if (we_are_hibernating()) - recommendation = "ignore"; + dowarn = 0; while (status>=0 && bootstrap_status_to_string(status, &tag, &summary) < 0) status--; /* find a recognized status string based on current progress */ status = bootstrap_percent; /* set status back to the actual number */ - severity = !strcmp(recommendation, "warn") ? LOG_WARN : LOG_INFO; + severity = dowarn ? LOG_WARN : LOG_INFO; + + if (dowarn) + recommendation = "warn"; + + if (conn && conn->type == CONN_TYPE_OR) { + /* XXX TO_OR_CONN can't deal with const */ + or_conn = TO_OR_CONN((connection_t *)conn); + or_id = tor_strdup(hex_str(or_conn->identity_digest, DIGEST_LEN)); + } else { + or_id = tor_strdup("?"); + } + + if (conn) + tor_asprintf(&hostaddr, "%s:%d", conn->address, (int)conn->port); + else + hostaddr = tor_strdup("?"); log_fn(severity, LD_CONTROL, "Problem bootstrapping. Stuck at %d%%: %s. (%s; %s; " - "count %d; recommendation %s; host %s at %s:%d)", - status, summary, warn, - orconn_end_reason_to_control_string(reason), + "count %d; recommendation %s; host %s at %s)", + status, summary, warn, reason, bootstrap_problems, recommendation, - hex_str(or_conn->identity_digest, DIGEST_LEN), - or_conn->base_.address, - or_conn->base_.port); + or_id, hostaddr); connection_or_report_broken_states(severity, LD_HANDSHAKE); tor_snprintf(buf, sizeof(buf), "BOOTSTRAP PROGRESS=%d TAG=%s SUMMARY=\"%s\" WARNING=\"%s\" REASON=%s " - "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s:%d\"", - bootstrap_percent, tag, summary, warn, - orconn_end_reason_to_control_string(reason), bootstrap_problems, + "COUNT=%d RECOMMENDATION=%s HOSTID=\"%s\" HOSTADDR=\"%s\"", + bootstrap_percent, tag, summary, warn, reason, bootstrap_problems, recommendation, - hex_str(or_conn->identity_digest, DIGEST_LEN), - or_conn->base_.address, - (int)or_conn->base_.port); + or_id, hostaddr); tor_snprintf(last_sent_bootstrap_message, sizeof(last_sent_bootstrap_message), "WARN %s", buf); control_event_client_status(LOG_WARN, "%s", buf); + + tor_free(hostaddr); + tor_free(or_id); +} + +/** Called when Tor has failed to make bootstrapping progress in a way + * that indicates a problem. <b>warn</b> gives a hint as to why, and + * <b>reason</b> provides an "or_conn_end_reason" tag. <b>or_conn</b> + * is the connection that caused this problem. + */ +MOCK_IMPL(void, +control_event_bootstrap_prob_or, (const char *warn, int reason, + or_connection_t *or_conn)) +{ + int dowarn = 0; + + if (or_conn->have_noted_bootstrap_problem) + return; + + or_conn->have_noted_bootstrap_problem = 1; + + if (reason == END_OR_CONN_REASON_NO_ROUTE) + dowarn = 1; + + /* If we are using bridges and all our OR connections are now + closed, it means that we totally failed to connect to our + bridges. Throw a warning. */ + if (get_options()->UseBridges && !any_other_active_or_conns(or_conn)) + dowarn = 1; + + control_event_bootstrap_problem(warn, + orconn_end_reason_to_control_string(reason), + TO_CONN(or_conn), dowarn); } /** We just generated a new summary of which countries we've seen clients @@ -7194,7 +7284,7 @@ control_event_hs_descriptor_upload_failed(const char *id_digest, id_digest); return; } - control_event_hs_descriptor_upload_end("UPLOAD_FAILED", onion_address, + control_event_hs_descriptor_upload_end("FAILED", onion_address, id_digest, reason); } @@ -7227,5 +7317,5 @@ control_testing_set_global_event_mask(uint64_t mask) { global_event_mask = mask; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/control.h b/src/or/control.h index 41a194bfcb..e957b593a6 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -33,7 +33,6 @@ void connection_control_closed(control_connection_t *conn); int connection_control_process_inbuf(control_connection_t *conn); -#define EVENT_AUTHDIR_NEWDESCS 0x000D #define EVENT_NS 0x000F int control_event_is_interesting(int event); @@ -64,10 +63,6 @@ int control_event_descriptors_changed(smartlist_t *routers); int control_event_address_mapped(const char *from, const char *to, time_t expires, const char *error, const int cached); -int control_event_or_authdir_new_descriptor(const char *action, - const char *desc, - size_t desclen, - const char *msg); int control_event_my_descriptor_changed(void); int control_event_network_liveness_update(int liveness); int control_event_networkstatus_changed(smartlist_t *statuses); @@ -104,9 +99,11 @@ void enable_control_logging(void); void monitor_owning_controller_process(const char *process_spec); int control_event_bootstrap(bootstrap_status_t status, int progress); -MOCK_DECL(void, control_event_bootstrap_problem,(const char *warn, +MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn, int reason, or_connection_t *or_conn)); +void control_event_bootstrap_problem(const char *warn, const char *reason, + const connection_t *conn, int dowarn); void control_event_clients_seen(const char *controller_str); void control_event_transport_launched(const char *mode, @@ -169,8 +166,8 @@ void control_free_all(void); #define EVENT_WARN_MSG 0x000A #define EVENT_ERR_MSG 0x000B #define EVENT_ADDRMAP 0x000C -/* Exposed above */ -// #define EVENT_AUTHDIR_NEWDESCS 0x000D +/* There was an AUTHDIR_NEWDESCS event, but it no longer exists. We + can reclaim 0x000D. */ #define EVENT_DESCCHANGED 0x000E /* Exposed above */ // #define EVENT_NS 0x000F @@ -228,7 +225,7 @@ MOCK_DECL(STATIC void, queue_control_event_string,(uint16_t event, char *msg)); void control_testing_set_global_event_mask(uint64_t mask); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /** Helper structure: temporarily stores cell statistics for a circuit. */ typedef struct cell_stats_t { @@ -295,7 +292,7 @@ STATIC int getinfo_helper_dir( const char *question, char **answer, const char **errmsg); -#endif +#endif /* defined(CONTROL_PRIVATE) */ -#endif +#endif /* !defined(TOR_CONTROL_H) */ diff --git a/src/or/cpuworker.c b/src/or/cpuworker.c index f5fff2b331..d9371b3446 100644 --- a/src/or/cpuworker.c +++ b/src/or/cpuworker.c @@ -382,7 +382,7 @@ cpuworker_onion_handshake_replyfn(void *work_) if (onionskin_answer(circ, &rpl.created_cell, - (const char*)rpl.keys, + (const char*)rpl.keys, sizeof(rpl.keys), rpl.rend_auth_material) < 0) { log_warn(LD_OR,"onionskin_answer failed. Closing."); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); @@ -484,7 +484,7 @@ queue_pending_tasks(void) if (!circ) return; - if (assign_onionskin_to_cpuworker(circ, onionskin)) + if (assign_onionskin_to_cpuworker(circ, onionskin) < 0) log_info(LD_OR,"assign_to_cpuworker failed. Ignoring."); } } diff --git a/src/or/cpuworker.h b/src/or/cpuworker.h index 320de9532f..d39851325f 100644 --- a/src/or/cpuworker.h +++ b/src/or/cpuworker.h @@ -33,5 +33,5 @@ void cpuworker_log_onionskin_overhead(int severity, int onionskin_type, const char *onionskin_type_name); void cpuworker_cancel_circ_handshake(or_circuit_t *circ); -#endif +#endif /* !defined(TOR_CPUWORKER_H) */ diff --git a/src/or/dircollate.c b/src/or/dircollate.c index 172364c5f5..d34ebe8af5 100644 --- a/src/or/dircollate.c +++ b/src/or/dircollate.c @@ -53,7 +53,7 @@ ddmap_entry_free(ddmap_entry_t *e) static ddmap_entry_t * ddmap_entry_new(int n_votes) { - return tor_malloc_zero(STRUCT_OFFSET(ddmap_entry_t, vrs_lst) + + return tor_malloc_zero(offsetof(ddmap_entry_t, vrs_lst) + sizeof(vote_routerstatus_t *) * n_votes); } diff --git a/src/or/dircollate.h b/src/or/dircollate.h index 52214282b9..7932e18998 100644 --- a/src/or/dircollate.h +++ b/src/or/dircollate.h @@ -62,7 +62,7 @@ struct dircollator_s { * identity digests .*/ smartlist_t *all_rsa_sha1_lst; }; -#endif +#endif /* defined(DIRCOLLATE_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRCOLLATE_H) */ diff --git a/src/or/directory.c b/src/or/directory.c index 5ceea2fb32..f14bdde90b 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -18,7 +18,6 @@ #include "consdiffmgr.h" #include "control.h" #include "compat.h" -#define DIRECTORY_PRIVATE #include "directory.h" #include "dirserv.h" #include "dirvote.h" @@ -26,6 +25,7 @@ #include "geoip.h" #include "hs_cache.h" #include "hs_common.h" +#include "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -104,7 +104,6 @@ static void directory_send_command(dir_connection_t *conn, int direct, const directory_request_t *request); static int body_is_plausible(const char *body, size_t body_len, int purpose); -static char *http_get_header(const char *headers, const char *which); static void http_set_address_origin(const char *headers, connection_t *conn); static void connection_dir_download_routerdesc_failed(dir_connection_t *conn); static void connection_dir_bridge_routerdesc_failed(dir_connection_t *conn); @@ -185,9 +184,12 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, case DIR_PURPOSE_FETCH_EXTRAINFO: case DIR_PURPOSE_FETCH_MICRODESC: return 0; + case DIR_PURPOSE_HAS_FETCHED_HSDESC: case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: case DIR_PURPOSE_UPLOAD_RENDDESC_V2: case DIR_PURPOSE_FETCH_RENDDESC_V2: + case DIR_PURPOSE_FETCH_HSDESC: + case DIR_PURPOSE_UPLOAD_HSDESC: return 1; case DIR_PURPOSE_SERVER: default: @@ -246,6 +248,10 @@ dir_conn_purpose_to_string(int purpose) return "hidden-service v2 descriptor fetch"; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: return "hidden-service v2 descriptor upload"; + case DIR_PURPOSE_FETCH_HSDESC: + return "hidden-service descriptor fetch"; + case DIR_PURPOSE_UPLOAD_HSDESC: + return "hidden-service descriptor upload"; case DIR_PURPOSE_FETCH_MICRODESC: return "microdescriptor fetch"; } @@ -1006,47 +1012,6 @@ directory_must_use_begindir(const or_options_t *options) return !public_server_mode(options); } -struct directory_request_t { - /** - * These fields specify which directory we're contacting. Routerstatus, - * if present, overrides the other fields. - * - * @{ */ - tor_addr_port_t or_addr_port; - tor_addr_port_t dir_addr_port; - char digest[DIGEST_LEN]; - - const routerstatus_t *routerstatus; - /** @} */ - /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what - * kind of operation we'll be doing (upload/download), and of what kind - * of document. */ - uint8_t dir_purpose; - /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo - * and extrainfo docs. */ - uint8_t router_purpose; - /** Enum: determines whether to anonymize, and whether to use dirport or - * orport. */ - dir_indirection_t indirection; - /** Alias to the variable part of the URL for this request */ - const char *resource; - /** Alias to the payload to upload (if any) */ - const char *payload; - /** Number of bytes to upload from payload</b> */ - size_t payload_len; - /** Value to send in an if-modified-since header, or 0 for none. */ - time_t if_modified_since; - /** Hidden-service-specific information */ - const rend_data_t *rend_query; - /** Extra headers to append to the request */ - config_line_t *additional_headers; - /** */ - /** Used internally to directory.c: gets informed when the attempt to - * connect to the directory succeeds or fails, if that attempt bears on the - * directory's usability as a directory guard. */ - circuit_guard_state_t *guard_state; -}; - /** Evaluate the situation and decide if we should use an encrypted * "begindir-style" connection for this directory request. * 0) If there is no DirPort, yes. @@ -1120,6 +1085,7 @@ directory_request_new(uint8_t dir_purpose) tor_assert(dir_purpose <= DIR_PURPOSE_MAX_); tor_assert(dir_purpose != DIR_PURPOSE_SERVER); tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2); + tor_assert(dir_purpose != DIR_PURPOSE_HAS_FETCHED_HSDESC); directory_request_t *result = tor_malloc_zero(sizeof(*result)); tor_addr_make_null(&result->or_addr_port.addr, AF_INET); @@ -1270,6 +1236,34 @@ directory_request_set_rend_query(directory_request_t *req, } req->rend_query = query; } +/** + * Set an object containing HS connection identifier to be associated with + * this request. Note that only an alias to <b>ident</b> is stored, so the + * <b>ident</b> object must outlive the request. + */ +void +directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_UPLOAD_HSDESC); + } + req->hs_ident = ident; +} +/** + * Set an object containing HS connection identifier to be associated with + * this fetch request. Note that only an alias to <b>ident</b> is stored, so + * the <b>ident</b> object must outlive the request. + */ +void +directory_request_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident) +{ + if (ident) { + tor_assert(req->dir_purpose == DIR_PURPOSE_FETCH_HSDESC); + } + req->hs_ident = ident; +} /** Set a static circuit_guard_state_t object to affliate with the request in * <b>req</b>. This object will receive notification when the attempt to * connect to the guard either succeeds or fails. */ @@ -1391,6 +1385,7 @@ directory_initiate_request,(directory_request_t *request)) const dir_indirection_t indirection = request->indirection; const char *resource = request->resource; const rend_data_t *rend_query = request->rend_query; + const hs_ident_dir_conn_t *hs_ident = request->hs_ident; circuit_guard_state_t *guard_state = request->guard_state; tor_assert(or_addr_port->port || dir_addr_port->port); @@ -1478,8 +1473,16 @@ directory_initiate_request,(directory_request_t *request)) conn->dirconn_direct = !anonymized_connection; /* copy rendezvous data, if any */ - if (rend_query) + if (rend_query) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!hs_ident); conn->rend_data = rend_data_dup(rend_query); + } + if (hs_ident) { + /* We can't have both v2 and v3+ identifier. */ + tor_assert_nonfatal(!rend_query); + conn->hs_ident = hs_ident_dir_conn_dup(hs_ident); + } if (!anonymized_connection && !use_begindir) { /* then we want to connect to dirport directly */ @@ -1831,12 +1834,25 @@ directory_send_command(dir_connection_t *conn, httpcommand = "GET"; tor_asprintf(&url, "/tor/rendezvous2/%s", resource); break; + case DIR_PURPOSE_FETCH_HSDESC: + tor_assert(resource); + tor_assert(strlen(resource) <= ED25519_BASE64_LEN); + tor_assert(!payload); + httpcommand = "GET"; + tor_asprintf(&url, "/tor/hs/3/%s", resource); + break; case DIR_PURPOSE_UPLOAD_RENDDESC_V2: tor_assert(!resource); tor_assert(payload); httpcommand = "POST"; url = tor_strdup("/tor/rendezvous2/publish"); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + tor_assert(resource); + tor_assert(payload); + httpcommand = "POST"; + tor_asprintf(&url, "/tor/hs/%s/publish", resource); + break; default: tor_assert(0); return; @@ -1854,11 +1870,11 @@ directory_send_command(dir_connection_t *conn, request_len = strlen(request); total_request_len += request_len; - connection_write_to_buf(request, request_len, TO_CONN(conn)); + connection_buf_add(request, request_len, TO_CONN(conn)); url_len = strlen(url); total_request_len += url_len; - connection_write_to_buf(url, url_len, TO_CONN(conn)); + connection_buf_add(url, url_len, TO_CONN(conn)); tor_free(url); if (!strcmp(httpcommand, "POST") || payload) { @@ -1875,11 +1891,11 @@ directory_send_command(dir_connection_t *conn, request_len = strlen(request); total_request_len += request_len; - connection_write_to_buf(request, request_len, TO_CONN(conn)); + connection_buf_add(request, request_len, TO_CONN(conn)); if (payload) { /* then send the payload afterwards too */ - connection_write_to_buf(payload, payload_len, TO_CONN(conn)); + connection_buf_add(payload, payload_len, TO_CONN(conn)); total_request_len += payload_len; } @@ -1908,15 +1924,41 @@ directory_send_command(dir_connection_t *conn, STATIC int parse_http_url(const char *headers, char **url) { + char *command = NULL; + if (parse_http_command(headers, &command, url) < 0) { + return -1; + } + if (strcmpstart(*url, "/tor/")) { + char *new_url = NULL; + tor_asprintf(&new_url, "/tor%s%s", + *url[0] == '/' ? "" : "/", + *url); + tor_free(*url); + *url = new_url; + } + tor_free(command); + return 0; +} + +/** Parse an HTTP request line at the start of a headers string. On failure, + * return -1. On success, set *<b>command_out</b> to a copy of the HTTP + * command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and + * return 0. */ +int +parse_http_command(const char *headers, char **command_out, char **url_out) +{ + const char *command, *end_of_command; char *s, *start, *tmp; s = (char *)eat_whitespace_no_nl(headers); if (!*s) return -1; + command = s; s = (char *)find_whitespace(s); /* get past GET/POST */ if (!*s) return -1; + end_of_command = s; s = (char *)eat_whitespace_no_nl(s); if (!*s) return -1; - start = s; /* this is it, assuming it's valid */ + start = s; /* this is the URL, assuming it's valid */ s = (char *)find_whitespace(start); if (!*s) return -1; @@ -1947,13 +1989,8 @@ parse_http_url(const char *headers, char **url) return -1; } - if (s-start < 5 || strcmpstart(start,"/tor/")) { /* need to rewrite it */ - *url = tor_malloc(s - start + 5); - strlcpy(*url,"/tor", s-start+5); - strlcat((*url)+4, start, s-start+1); - } else { - *url = tor_strndup(start, s-start); - } + *url_out = tor_memdup_nulterm(start, s-start); + *command_out = tor_memdup_nulterm(command, end_of_command - command); return 0; } @@ -1961,7 +1998,7 @@ parse_http_url(const char *headers, char **url) * <b>which</b>. The key should be given with a terminating colon and space; * this function copies everything after, up to but not including the * following \\r\\n. */ -static char * +char * http_get_header(const char *headers, const char *which) { const char *cp = headers; @@ -2159,16 +2196,6 @@ load_downloaded_routers(const char *body, smartlist_t *which, return added; } -/** A structure to hold arguments passed into each directory response - * handler */ -typedef struct response_handler_args_t { - int status_code; - const char *reason; - const char *body; - size_t body_len; - const char *headers; -} response_handler_args_t; - static int handle_response_fetch_consensus(dir_connection_t *, const response_handler_args_t *); static int handle_response_fetch_certificate(dir_connection_t *, @@ -2189,6 +2216,8 @@ static int handle_response_fetch_renddesc_v2(dir_connection_t *, const response_handler_args_t *); static int handle_response_upload_renddesc_v2(dir_connection_t *, const response_handler_args_t *); +static int handle_response_upload_hsdesc(dir_connection_t *, + const response_handler_args_t *); static int dir_client_decompress_response_body(char **bodyp, size_t *bodylenp, @@ -2489,6 +2518,12 @@ connection_dir_client_reached_eof(dir_connection_t *conn) case DIR_PURPOSE_UPLOAD_RENDDESC_V2: rv = handle_response_upload_renddesc_v2(conn, &args); break; + case DIR_PURPOSE_UPLOAD_HSDESC: + rv = handle_response_upload_hsdesc(conn, &args); + break; + case DIR_PURPOSE_FETCH_HSDESC: + rv = handle_response_fetch_hsdesc_v3(conn, &args); + break; default: tor_assert_nonfatal_unreached(); rv = -1; @@ -2811,7 +2846,7 @@ handle_response_fetch_desc(dir_connection_t *conn, conn->router_purpose, conn->base_.address)) { time_t now = approx_time(); - directory_info_has_arrived(now, 0, 0); + directory_info_has_arrived(now, 0, 1); } } } @@ -3035,6 +3070,60 @@ handle_response_upload_signatures(dir_connection_t *conn, } /** + * Handler function: processes a response to a request for a v3 hidden service + * descriptor. + **/ +STATIC int +handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + const char *body = args->body; + const size_t body_len = args->body_len; + + tor_assert(conn->hs_ident); + + log_info(LD_REND,"Received v3 hsdesc (body size %d, status %d (%s))", + (int)body_len, status_code, escaped(reason)); + + switch (status_code) { + case 200: + /* We got something: Try storing it in the cache. */ + if (hs_cache_store_as_client(body, &conn->hs_ident->identity_pk) < 0) { + log_warn(LD_REND, "Failed to store hidden service descriptor"); + } else { + log_info(LD_REND, "Stored hidden service descriptor successfully."); + TO_CONN(conn)->purpose = DIR_PURPOSE_HAS_FETCHED_HSDESC; + hs_client_desc_has_arrived(conn->hs_ident); + } + break; + case 404: + /* Not there. We'll retry when connection_about_to_close_connection() + * tries to clean this conn up. */ + log_info(LD_REND, "Fetching hidden service v3 descriptor not found: " + "Retrying at another directory."); + /* TODO: Inform the control port */ + break; + case 400: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status 400 (%s). Dirserver didn't like our " + "query? Retrying at another directory.", + escaped(reason)); + break; + default: + log_warn(LD_REND, "Fetching v3 hidden service descriptor failed: " + "http status %d (%s) response unexpected from HSDir server " + "'%s:%d'. Retrying at another directory.", + status_code, escaped(reason), TO_CONN(conn)->address, + TO_CONN(conn)->port); + break; + } + + return 0; +} + +/** * Handler function: processes a response to a request for a v2 hidden service * descriptor. **/ @@ -3181,6 +3270,53 @@ handle_response_upload_renddesc_v2(dir_connection_t *conn, return 0; } +/** + * Handler function: processes a response to a POST request to upload an + * hidden service descriptor. + **/ +static int +handle_response_upload_hsdesc(dir_connection_t *conn, + const response_handler_args_t *args) +{ + const int status_code = args->status_code; + const char *reason = args->reason; + + tor_assert(conn); + tor_assert(conn->base_.purpose == DIR_PURPOSE_UPLOAD_HSDESC); + + log_info(LD_REND, "Uploaded hidden service descriptor (status %d " + "(%s))", + status_code, escaped(reason)); + /* For this directory response, it MUST have an hidden service identifier on + * this connection. */ + tor_assert(conn->hs_ident); + switch (status_code) { + case 200: + log_info(LD_REND, "Uploading hidden service descriptor: " + "finished with status 200 (%s)", escaped(reason)); + /* XXX: Trigger control event. */ + break; + case 400: + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Uploading hidden service descriptor: http " + "status 400 (%s) response from dirserver " + "'%s:%d'. Malformed hidden service descriptor?", + escaped(reason), conn->base_.address, conn->base_.port); + /* XXX: Trigger control event. */ + break; + default: + log_warn(LD_REND, "Uploading hidden service descriptor: http " + "status %d (%s) response unexpected (server " + "'%s:%d').", + status_code, escaped(reason), conn->base_.address, + conn->base_.port); + /* XXX: Trigger control event. */ + break; + } + + return 0; +} + /** Called when a directory connection reaches EOF. */ int connection_dir_reached_eof(dir_connection_t *conn) @@ -3252,6 +3388,33 @@ connection_dir_process_inbuf(dir_connection_t *conn) return 0; } +/** We are closing a dir connection: If <b>dir_conn</b> is a dir connection + * that tried to fetch an HS descriptor, check if it successfuly fetched it, + * or if we need to try again. */ +static void +refetch_hsdesc_if_needed(dir_connection_t *dir_conn) +{ + connection_t *conn = TO_CONN(dir_conn); + + /* If we were trying to fetch a v2 rend desc and did not succeed, retry as + * needed. (If a fetch is successful, the connection state is changed to + * DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 or DIR_PURPOSE_HAS_FETCHED_HSDESC to + * mark that refetching is unnecessary.) */ + if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && + dir_conn->rend_data && + rend_valid_v2_service_id( + rend_data_get_address(dir_conn->rend_data))) { + rend_client_refetch_v2_renddesc(dir_conn->rend_data); + } + + /* Check for v3 rend desc fetch */ + if (conn->purpose == DIR_PURPOSE_FETCH_HSDESC && + dir_conn->hs_ident && + !ed25519_public_key_is_zero(&dir_conn->hs_ident->identity_pk)) { + hs_client_refetch_hsdesc(&dir_conn->hs_ident->identity_pk); + } +} + /** Called when we're about to finally unlink and free a directory connection: * perform necessary accounting and cleanup */ void @@ -3264,29 +3427,38 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) * failed: forget about this router, and maybe try again. */ connection_dir_request_failed(dir_conn); } - /* If we were trying to fetch a v2 rend desc and did not succeed, - * retry as needed. (If a fetch is successful, the connection state - * is changed to DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2 to mark that - * refetching is unnecessary.) */ - if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && - dir_conn->rend_data && - strlen(rend_data_get_address(dir_conn->rend_data)) == - REND_SERVICE_ID_LEN_BASE32) - rend_client_refetch_v2_renddesc(dir_conn->rend_data); + + refetch_hsdesc_if_needed(dir_conn); } /** Create an http response for the client <b>conn</b> out of * <b>status</b> and <b>reason_phrase</b>. Write it to <b>conn</b>. */ static void -write_http_status_line(dir_connection_t *conn, int status, +write_short_http_response(dir_connection_t *conn, int status, const char *reason_phrase) { char *buf = NULL; - tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n\r\n", - status, reason_phrase ? reason_phrase : "OK"); + char *datestring = NULL; + + IF_BUG_ONCE(!reason_phrase) { /* bullet-proofing */ + reason_phrase = "unspecified"; + } + + if (server_mode(get_options())) { + /* include the Date: header, but only if we're a relay or bridge */ + char datebuf[RFC1123_TIME_LEN+1]; + format_rfc1123_time(datebuf, time(NULL)); + tor_asprintf(&datestring, "Date: %s\r\n", datebuf); + } + + tor_asprintf(&buf, "HTTP/1.0 %d %s\r\n%s\r\n", + status, reason_phrase, datestring?datestring:""); + log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase); - connection_write_to_buf(buf, strlen(buf), TO_CONN(conn)); + connection_buf_add(buf, strlen(buf), TO_CONN(conn)); + + tor_free(datestring); tor_free(buf); } @@ -3360,7 +3532,7 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, memcpy(cp, "\r\n", 3); else tor_assert(0); - connection_write_to_buf(tmp, strlen(tmp), TO_CONN(conn)); + connection_buf_add(tmp, strlen(tmp), TO_CONN(conn)); } /** As write_http_response_header_impl, but sets encoding and content-typed @@ -3624,7 +3796,6 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, char *url, *url_mem, *header; time_t if_modified_since = 0; int zlib_compressed_in_url; - size_t url_len; unsigned compression_methods_supported; /* We ignore the body of a GET request. */ @@ -3636,7 +3807,7 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, conn->base_.state = DIR_CONN_STATE_SERVER_WRITING; if (parse_http_url(headers, &url) < 0) { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); return 0; } if ((header = http_get_header(headers, "If-Modified-Since: "))) { @@ -3655,12 +3826,13 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); url_mem = url; - url_len = strlen(url); + { + size_t url_len = strlen(url); - zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z"); - if (zlib_compressed_in_url) { - url[url_len-2] = '\0'; - url_len -= 2; + zlib_compressed_in_url = url_len > 2 && !strcmp(url+url_len-2, ".z"); + if (zlib_compressed_in_url) { + url[url_len-2] = '\0'; + } } if ((header = http_get_header(headers, "Accept-Encoding: "))) { @@ -3697,7 +3869,7 @@ directory_handle_command_get,(dir_connection_t *conn, const char *headers, } /* we didn't recognize the url */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); result = 0; done: @@ -3723,9 +3895,9 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) * this page no matter what.] */ write_http_response_header_impl(conn, dlen, "text/html", "identity", NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME); - connection_write_to_buf(frontpage, dlen, TO_CONN(conn)); + connection_buf_add(frontpage, dlen, TO_CONN(conn)); } else { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); } return 0; } @@ -4107,13 +4279,13 @@ handle_get_current_consensus(dir_connection_t *conn, parsed_consensus_request_t req; if (parse_consensus_request(&req, args) < 0) { - write_http_status_line(conn, 404, "Couldn't parse request"); + write_short_http_response(conn, 404, "Couldn't parse request"); goto done; } if (digest_list_contains_best_consensus(req.flav, req.diff_from_digests)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); goto done; } @@ -4128,7 +4300,7 @@ handle_get_current_consensus(dir_connection_t *conn, } if (req.diff_only && !cached_consensus) { - write_http_status_line(conn, 404, "No such diff available"); + write_short_http_response(conn, 404, "No such diff available"); // XXXX warn_consensus_is_too_old(v, req.flavor, now); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; @@ -4151,7 +4323,7 @@ handle_get_current_consensus(dir_connection_t *conn, if (cached_consensus && have_valid_until && !networkstatus_valid_until_is_reasonably_live(valid_until, now)) { - write_http_status_line(conn, 404, "Consensus is too old"); + write_short_http_response(conn, 404, "Consensus is too old"); warn_consensus_is_too_old(cached_consensus, req.flavor, now); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; @@ -4159,7 +4331,7 @@ handle_get_current_consensus(dir_connection_t *conn, if (cached_consensus && req.want_fps && !client_likes_consensus(cached_consensus, req.want_fps)) { - write_http_status_line(conn, 404, "Consensus not signed by sufficient " + write_short_http_response(conn, 404, "Consensus not signed by sufficient " "number of requested authorities"); geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS); goto done; @@ -4185,11 +4357,11 @@ handle_get_current_consensus(dir_connection_t *conn, &n_expired); if (!smartlist_len(conn->spool) && !n_expired) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND); goto done; } else if (!smartlist_len(conn->spool)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); geoip_note_ns_response(GEOIP_REJECT_NOT_MODIFIED); goto done; } @@ -4198,7 +4370,7 @@ handle_get_current_consensus(dir_connection_t *conn, log_debug(LD_DIRSERV, "Client asked for network status lists, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); geoip_note_ns_response(GEOIP_REJECT_BUSY); goto done; } @@ -4311,7 +4483,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) smartlist_free(fps); } if (!smartlist_len(dir_items) && !smartlist_len(items)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto vote_done; } @@ -4348,7 +4520,7 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) }); if (global_write_bucket_low(TO_CONN(conn), estimated_len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto vote_done; } write_http_response_header(conn, body_len ? body_len : -1, @@ -4360,15 +4532,15 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(estimated_len)); SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf_compress(c, strlen(c), conn, 0)); - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress(c, strlen(c), conn, 0)); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf(c, strlen(c), TO_CONN(conn))); + connection_buf_add(c, strlen(c), TO_CONN(conn))); } } else { SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, - connection_write_to_buf(compress_method != NO_METHOD ? + connection_buf_add(compress_method != NO_METHOD ? d->dir_compressed : d->dir, compress_method != NO_METHOD ? d->dir_compressed_len : d->dir_len, @@ -4405,14 +4577,14 @@ handle_get_microdesc(dir_connection_t *conn, const get_handler_args_t *args) compress_method != NO_METHOD, &size_guess, NULL); if (smartlist_len(conn->spool) == 0) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto done; } if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto done; } @@ -4504,13 +4676,14 @@ handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args) if (res < 0 || size_guess == 0 || smartlist_len(conn->spool) == 0) { if (msg == NULL) msg = "Not found"; - write_http_status_line(conn, 404, msg); + write_short_http_response(conn, 404, msg); } else { if (global_write_bucket_low(TO_CONN(conn), size_guess, 2)) { log_info(LD_DIRSERV, "Client asked for server descriptors, but we've been " "writing too many bytes lately. Sending 503 Dir busy."); - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, + "Directory busy, try again later"); dir_conn_clear_spool(conn); goto done; } @@ -4583,18 +4756,18 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) }); smartlist_free(fp_sks); } else { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); goto keys_done; } if (!smartlist_len(certs)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto keys_done; } SMARTLIST_FOREACH(certs, authority_cert_t *, c, if (c->cache_info.published_on < if_modified_since) SMARTLIST_DEL_CURRENT(certs, c)); if (!smartlist_len(certs)) { - write_http_status_line(conn, 304, "Not modified"); + write_short_http_response(conn, 304, "Not modified"); goto keys_done; } len = 0; @@ -4604,7 +4777,7 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) if (global_write_bucket_low(TO_CONN(conn), compress_method != NO_METHOD ? len/2 : len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later"); + write_short_http_response(conn, 503, "Directory busy, try again later"); goto keys_done; } @@ -4616,14 +4789,14 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(len)); SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf_compress( + connection_buf_add_compress( c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, conn, 0)); - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf(c->cache_info.signed_descriptor_body, + connection_buf_add(c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, TO_CONN(conn))); } @@ -4652,22 +4825,22 @@ handle_get_hs_descriptor_v2(dir_connection_t *conn, switch (rend_cache_lookup_v2_desc_as_dir(query, &descp)) { case 1: /* valid */ write_http_response_header(conn, strlen(descp), NO_METHOD, 0); - connection_write_to_buf(descp, strlen(descp), TO_CONN(conn)); + connection_buf_add(descp, strlen(descp), TO_CONN(conn)); break; case 0: /* well-formed but not present */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); break; case -1: /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); break; } } else { /* not well-formed */ - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); } goto done; } else { /* Not encrypted! */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); } done: return 0; @@ -4686,7 +4859,7 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, /* Reject unencrypted dir connections */ if (!connection_dir_is_encrypted(conn)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto done; } @@ -4698,13 +4871,13 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, retval = hs_cache_lookup_as_dir(HS_VERSION_THREE, pubkey_str, &desc_str); if (retval <= 0 || desc_str == NULL) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); goto done; } /* Found requested descriptor! Pass it to this nice client. */ write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0); - connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn)); + connection_buf_add(desc_str, strlen(desc_str), TO_CONN(conn)); done: return 0; @@ -4733,7 +4906,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, if (!header || tor_memneq(digest, options->BridgePassword_AuthDigest_, DIGEST256_LEN)) { - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); tor_free(header); goto done; } @@ -4743,7 +4916,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, status = networkstatus_getinfo_by_purpose("bridge", time(NULL)); size_t dlen = strlen(status); write_http_response_header(conn, dlen, NO_METHOD, 0); - connection_write_to_buf(status, dlen, TO_CONN(conn)); + connection_buf_add(status, dlen, TO_CONN(conn)); tor_free(status); goto done; } @@ -4760,7 +4933,7 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) const char robots[] = "User-agent: *\r\nDisallow: /\r\n"; size_t len = strlen(robots); write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME); - connection_write_to_buf(robots, len, TO_CONN(conn)); + connection_buf_add(robots, len, TO_CONN(conn)); } return 0; } @@ -4868,12 +5041,12 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (!public_server_mode(options)) { log_info(LD_DIR, "Rejected dir post request from %s " "since we're not a public relay.", conn->base_.address); - write_http_status_line(conn, 503, "Not acting as a public relay"); + write_short_http_response(conn, 503, "Not acting as a public relay"); goto done; } if (parse_http_url(headers, &url) < 0) { - write_http_status_line(conn, 400, "Bad request"); + write_short_http_response(conn, 400, "Bad request"); return 0; } log_debug(LD_DIRSERV,"rewritten url as '%s'.", escaped(url)); @@ -4884,10 +5057,10 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (rend_cache_store_v2_desc_as_dir(body) < 0) { log_warn(LD_REND, "Rejected v2 rend descriptor (body size %d) from %s.", (int)body_len, conn->base_.address); - write_http_status_line(conn, 400, + write_short_http_response(conn, 400, "Invalid v2 service descriptor rejected"); } else { - write_http_status_line(conn, 200, "Service descriptor (v2) stored"); + write_short_http_response(conn, 200, "Service descriptor (v2) stored"); log_info(LD_REND, "Handled v2 rendezvous descriptor post: accepted"); } goto done; @@ -4904,19 +5077,19 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, if (code != 200) { msg = "Invalid HS descriptor. Rejected."; } - write_http_status_line(conn, code, msg); + write_short_http_response(conn, code, msg); goto done; } if (!authdir_mode(options)) { /* we just provide cached directories; we don't want to * receive anything. */ - write_http_status_line(conn, 400, "Nonauthoritative directory does not " + write_short_http_response(conn, 400, "Nonauthoritative directory does not " "accept posted server descriptors"); goto done; } - if (authdir_mode_handles_descs(options, -1) && + if (authdir_mode(options) && !strcmp(url,"/tor/")) { /* server descriptor post */ const char *msg = "[None]"; uint8_t purpose = authdir_mode_bridge(options) ? @@ -4926,7 +5099,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, tor_assert(msg); if (r == ROUTER_ADDED_SUCCESSFULLY) { - write_http_status_line(conn, 200, msg); + write_short_http_response(conn, 200, msg); } else if (WRA_WAS_OUTDATED(r)) { write_http_response_header_impl(conn, -1, NULL, NULL, "X-Descriptor-Not-New: Yes\r\n", -1); @@ -4935,7 +5108,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, "Rejected router descriptor or extra-info from %s " "(\"%s\").", conn->base_.address, msg); - write_http_status_line(conn, 400, msg); + write_short_http_response(conn, 400, msg); } goto done; } @@ -4945,12 +5118,12 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, const char *msg = "OK"; int status; if (dirvote_add_vote(body, &msg, &status)) { - write_http_status_line(conn, status, "Vote stored"); + write_short_http_response(conn, status, "Vote stored"); } else { tor_assert(msg); log_warn(LD_DIRSERV, "Rejected vote from %s (\"%s\").", conn->base_.address, msg); - write_http_status_line(conn, status, msg); + write_short_http_response(conn, status, msg); } goto done; } @@ -4959,17 +5132,18 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, !strcmp(url,"/tor/post/consensus-signature")) { /* sigs on consensus. */ const char *msg = NULL; if (dirvote_add_signatures(body, conn->base_.address, &msg)>=0) { - write_http_status_line(conn, 200, msg?msg:"Signatures stored"); + write_short_http_response(conn, 200, msg?msg:"Signatures stored"); } else { log_warn(LD_DIR, "Unable to store signatures posted by %s: %s", conn->base_.address, msg?msg:"???"); - write_http_status_line(conn, 400, msg?msg:"Unable to store signatures"); + write_short_http_response(conn, 400, + msg?msg:"Unable to store signatures"); } goto done; } /* we didn't recognize the url */ - write_http_status_line(conn, 404, "Not found"); + write_short_http_response(conn, 404, "Not found"); done: tor_free(url); @@ -5122,7 +5296,7 @@ connection_dir_finished_connecting(dir_connection_t *conn) * Helper function for download_status_increment_failure(), * download_status_reset(), and download_status_increment_attempt(). */ STATIC const smartlist_t * -find_dl_schedule(download_status_t *dls, const or_options_t *options) +find_dl_schedule(const download_status_t *dls, const or_options_t *options) { switch (dls->schedule) { case DL_SCHED_GENERIC: @@ -5163,7 +5337,15 @@ find_dl_schedule(download_status_t *dls, const or_options_t *options) } } case DL_SCHED_BRIDGE: - return options->TestingBridgeDownloadSchedule; + if (options->UseBridges && num_bridges_usable(0) > 0) { + /* A bridge client that is sure that one or more of its bridges are + * running can afford to wait longer to update bridge descriptors. */ + return options->TestingBridgeDownloadSchedule; + } else { + /* A bridge client which might have no running bridges, must try to + * get bridge descriptors straight away. */ + return options->TestingBridgeBootstrapDownloadSchedule; + } default: tor_assert(0); } @@ -5191,59 +5373,76 @@ find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, const smartlist_t *schedule = find_dl_schedule(dls, options); tor_assert(schedule != NULL && smartlist_len(schedule) >= 2); *min = *((int *)(smartlist_get(schedule, 0))); + /* Increment on failure schedules always use exponential backoff, but they + * have a smaller limit when they're deterministic */ if (dls->backoff == DL_SCHED_DETERMINISTIC) *max = *((int *)((smartlist_get(schedule, smartlist_len(schedule) - 1)))); else *max = INT_MAX; } -/** Advance one delay step. The algorithm is to use the previous delay to - * compute an increment, we construct a value uniformly at random between - * delay and MAX(delay*2,delay+1). We then clamp that value to be no larger - * than max_delay, and return it. +/** As next_random_exponential_delay() below, but does not compute a random + * value. Instead, compute the range of values that + * next_random_exponential_delay() should use when computing its random value. + * Store the low bound into *<b>low_bound_out</b>, and the high bound into + * *<b>high_bound_out</b>. Guarantees that the low bound is strictly less + * than the high bound. */ +STATIC void +next_random_exponential_delay_range(int *low_bound_out, + int *high_bound_out, + int delay, + int base_delay) +{ + // This is the "decorrelated jitter" approach, from + // https://www.awsarchitectureblog.com/2015/03/backoff.html + // The formula is + // sleep = min(cap, random_between(base, sleep * 3)) + + const int delay_times_3 = delay < INT_MAX/3 ? delay * 3 : INT_MAX; + *low_bound_out = base_delay; + if (delay_times_3 > base_delay) { + *high_bound_out = delay_times_3; + } else { + *high_bound_out = base_delay+1; + } +} + +/** Advance one delay step. The algorithm will generate a random delay, + * such that each failure is possibly (random) longer than the ones before. + * + * We then clamp that value to be no larger than max_delay, and return it. + * + * The <b>base_delay</b> parameter is lowest possible delay time (can't be + * zero); the <b>backoff_position</b> parameter is the number of times we've + * generated a delay; and the <b>delay</b> argument is the most recently used + * delay. * * Requires that delay is less than INT_MAX, and delay is in [0,max_delay]. */ STATIC int -next_random_exponential_delay(int delay, int max_delay) +next_random_exponential_delay(int delay, + int base_delay, + int max_delay) { /* Check preconditions */ if (BUG(max_delay < 0)) max_delay = 0; if (BUG(delay > max_delay)) delay = max_delay; - if (delay == INT_MAX) - return INT_MAX; /* prevent overflow */ if (BUG(delay < 0)) delay = 0; - /* How much are we willing to add to the delay? */ - int max_increment; - int multiplier = 3; /* no more than quadruple the previous delay */ - if (get_options()->TestingTorNetwork) { - /* Decrease the multiplier in testing networks. This reduces the variance, - * so that bootstrap is more reliable. */ - multiplier = 2; /* no more than triple the previous delay */ - } + if (base_delay < 1) + base_delay = 1; - if (delay && delay < (INT_MAX-1) / multiplier) { - max_increment = delay * multiplier; - } else if (delay) { - max_increment = INT_MAX-1; - } else { - max_increment = 1; - } + int low_bound=0, high_bound=max_delay; - if (BUG(max_increment < 1)) - max_increment = 1; + next_random_exponential_delay_range(&low_bound, &high_bound, + delay, base_delay); - /* the + 1 here is so that we always wait longer than last time. */ - int increment = crypto_rand_int(max_increment)+1; + int rand_delay = crypto_rand_int_range(low_bound, high_bound); - if (increment < max_delay - delay) - return delay + increment; - else - return max_delay; + return MIN(rand_delay, max_delay); } /** Find the current delay for dls based on schedule or min_delay/ @@ -5292,7 +5491,7 @@ download_status_schedule_get_delay(download_status_t *dls, while (dls->last_backoff_position < dls_schedule_position) { /* Do one increment step */ - delay = next_random_exponential_delay(delay, max_delay); + delay = next_random_exponential_delay(delay, min_delay, max_delay); /* Update our position */ ++(dls->last_backoff_position); } @@ -5375,6 +5574,11 @@ download_status_increment_failure(download_status_t *dls, int status_code, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + /* count the failure */ if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) { ++dls->n_download_failures; @@ -5399,14 +5603,16 @@ download_status_increment_failure(download_status_t *dls, int status_code, download_status_log_helper(item, !dls->increment_on, "failed", "concurrently", dls->n_download_failures, - increment, dls->next_attempt_at, now); + increment, + download_status_get_next_attempt_at(dls), + now); if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { /* stop this schedule retrying on failure, it will launch concurrent * connections instead */ return TIME_MAX; } else { - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); } } @@ -5427,6 +5633,11 @@ download_status_increment_attempt(download_status_t *dls, const char *item, tor_assert(dls); + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { /* this schedule should retry on failure, and not launch any concurrent attempts */ @@ -5445,9 +5656,19 @@ download_status_increment_attempt(download_status_t *dls, const char *item, download_status_log_helper(item, dls->increment_on, "attempted", "on failure", dls->n_download_attempts, - delay, dls->next_attempt_at, now); + delay, download_status_get_next_attempt_at(dls), + now); - return dls->next_attempt_at; + return download_status_get_next_attempt_at(dls); +} + +static time_t +download_status_get_initial_delay_from_now(const download_status_t *dls) +{ + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + /* We use constant initial delays, even in exponential backoff + * schedules. */ + return time(NULL) + *(int *)smartlist_get(schedule, 0); } /** Reset <b>dls</b> so that it will be considered downloadable @@ -5459,8 +5680,8 @@ download_status_increment_attempt(download_status_t *dls, const char *item, * (We find the zeroth element of the download schedule, and set * next_attempt_at to be the appropriate offset from 'now'. In most * cases this means setting it to 'now', so the item will be immediately - * downloadable; in the case of bridge descriptors, the zeroth element - * is an hour from now.) */ + * downloadable; when using authorities with fallbacks, there is a few seconds' + * delay.) */ void download_status_reset(download_status_t *dls) { @@ -5468,11 +5689,9 @@ download_status_reset(download_status_t *dls) || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) return; /* Don't reset this. */ - const smartlist_t *schedule = find_dl_schedule(dls, get_options()); - dls->n_download_failures = 0; dls->n_download_attempts = 0; - dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); + dls->next_attempt_at = download_status_get_initial_delay_from_now(dls); dls->last_backoff_position = 0; dls->last_delay_used = 0; /* Don't reset dls->want_authority or dls->increment_on */ @@ -5499,6 +5718,12 @@ download_status_get_n_attempts(const download_status_t *dls) time_t download_status_get_next_attempt_at(const download_status_t *dls) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + /* so give the answer we would have given if it had been */ + return download_status_get_initial_delay_from_now(dls); + } + return dls->next_attempt_at; } diff --git a/src/or/directory.h b/src/or/directory.h index 571c30a0fc..5e6a91d3e7 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -12,6 +12,8 @@ #ifndef TOR_DIRECTORY_H #define TOR_DIRECTORY_H +#include "hs_ident.h" + int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, @@ -71,6 +73,10 @@ void directory_request_set_if_modified_since(directory_request_t *req, time_t if_modified_since); void directory_request_set_rend_query(directory_request_t *req, const rend_data_t *query); +void directory_request_upload_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); +void directory_request_fetch_set_hs_ident(directory_request_t *req, + const hs_ident_dir_conn_t *ident); void directory_request_set_routerstatus(directory_request_t *req, const routerstatus_t *rs); @@ -81,6 +87,9 @@ MOCK_DECL(void, directory_initiate_request, (directory_request_t *request)); int parse_http_response(const char *headers, int *code, time_t *date, compress_method_t *compression, char **response); +int parse_http_command(const char *headers, + char **command_out, char **url_out); +char *http_get_header(const char *headers, const char *which); int connection_dir_is_encrypted(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); @@ -123,12 +132,19 @@ time_t download_status_increment_attempt(download_status_t *dls, void download_status_reset(download_status_t *dls); static int download_status_is_ready(download_status_t *dls, time_t now, int max_failures); +time_t download_status_get_next_attempt_at(const download_status_t *dls); + /** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is * ready to get its download reattempted. */ static inline int download_status_is_ready(download_status_t *dls, time_t now, int max_failures) { + /* dls wasn't reset before it was used */ + if (dls->next_attempt_at == 0) { + download_status_reset(dls); + } + if (dls->backoff == DL_SCHED_DETERMINISTIC) { /* Deterministic schedules can hit an endpoint; exponential backoff * schedules just wait longer and longer. */ @@ -137,7 +153,7 @@ download_status_is_ready(download_status_t *dls, time_t now, if (!under_failure_limit) return 0; } - return dls->next_attempt_at <= now; + return download_status_get_next_attempt_at(dls) <= now; } static void download_status_mark_impossible(download_status_t *dl); @@ -151,13 +167,64 @@ download_status_mark_impossible(download_status_t *dl) int download_status_get_n_failures(const download_status_t *dls); int download_status_get_n_attempts(const download_status_t *dls); -time_t download_status_get_next_attempt_at(const download_status_t *dls); int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); #ifdef DIRECTORY_PRIVATE +/** A structure to hold arguments passed into each directory response + * handler */ +typedef struct response_handler_args_t { + int status_code; + const char *reason; + const char *body; + size_t body_len; + const char *headers; +} response_handler_args_t; + +struct directory_request_t { + /** + * These fields specify which directory we're contacting. Routerstatus, + * if present, overrides the other fields. + * + * @{ */ + tor_addr_port_t or_addr_port; + tor_addr_port_t dir_addr_port; + char digest[DIGEST_LEN]; + + const routerstatus_t *routerstatus; + /** @} */ + /** One of DIR_PURPOSE_* other than DIR_PURPOSE_SERVER. Describes what + * kind of operation we'll be doing (upload/download), and of what kind + * of document. */ + uint8_t dir_purpose; + /** One of ROUTER_PURPOSE_*; used for uploads and downloads of routerinfo + * and extrainfo docs. */ + uint8_t router_purpose; + /** Enum: determines whether to anonymize, and whether to use dirport or + * orport. */ + dir_indirection_t indirection; + /** Alias to the variable part of the URL for this request */ + const char *resource; + /** Alias to the payload to upload (if any) */ + const char *payload; + /** Number of bytes to upload from payload</b> */ + size_t payload_len; + /** Value to send in an if-modified-since header, or 0 for none. */ + time_t if_modified_since; + /** Hidden-service-specific information v2. */ + const rend_data_t *rend_query; + /** Extra headers to append to the request */ + config_line_t *additional_headers; + /** Hidden-service-specific information for v3+. */ + const hs_ident_dir_conn_t *hs_ident; + /** Used internally to directory.c: gets informed when the attempt to + * connect to the directory succeeds or fails, if that attempt bears on the + * directory's usability as a directory guard. */ + struct circuit_guard_state_t *guard_state; +}; + struct get_handler_args_t; STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, const struct get_handler_args_t *args); @@ -166,15 +233,15 @@ STATIC char *accept_encoding_header(void); STATIC int allowed_anonymous_connection_compression_method(compress_method_t); STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); -struct response_handler_args_t; - +STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, + const response_handler_args_t *args); STATIC int handle_response_fetch_microdesc(dir_connection_t *conn, - const struct response_handler_args_t *args); + const response_handler_args_t *args); #endif /* defined(DIRECTORY_PRIVATE) */ #ifdef TOR_UNIT_TESTS -/* Used only by test_dir.c */ +/* Used only by test_dir.c and test_hs_cache.c */ STATIC int parse_http_url(const char *headers, char **url); STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, @@ -198,18 +265,36 @@ STATIC char* authdir_type_to_string(dirinfo_type_t auth); STATIC const char * dir_conn_purpose_to_string(int purpose); STATIC int should_use_directory_guards(const or_options_t *options); STATIC compression_level_t choose_compression_level(ssize_t n_bytes); -STATIC const smartlist_t *find_dl_schedule(download_status_t *dls, +STATIC const smartlist_t *find_dl_schedule(const download_status_t *dls, const or_options_t *options); STATIC void find_dl_min_and_max_delay(download_status_t *dls, const or_options_t *options, int *min, int *max); -STATIC int next_random_exponential_delay(int delay, int max_delay); + +STATIC int next_random_exponential_delay(int delay, + int base_delay, + int max_delay); + +STATIC void next_random_exponential_delay_range(int *low_bound_out, + int *high_bound_out, + int delay, + int base_delay); STATIC int parse_hs_version_from_post(const char *url, const char *prefix, const char **end_pos); STATIC unsigned parse_accept_encoding_header(const char *h); -#endif +#endif /* defined(TOR_UNIT_TESTS) */ + +#if defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) +/* Used only by directory.c and test_dir.c */ + +/* no more than quadruple the previous delay (multiplier + 1) */ +#define DIR_DEFAULT_RANDOM_MULTIPLIER (3) +/* no more than triple the previous delay */ +#define DIR_TEST_NET_RANDOM_MULTIPLIER (2) + +#endif /* defined(TOR_UNIT_TESTS) || defined(DIRECTORY_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRECTORY_H) */ diff --git a/src/or/dirserv.c b/src/or/dirserv.c index acd00322a1..ddee92da55 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -338,7 +338,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg, } return FP_REJECT; } -#endif +#endif /* defined(DISABLE_DISABLING_ED25519) */ } } @@ -673,9 +673,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) ri->nickname, source, (int)ri->cache_info.signed_descriptor_len, MAX_DESCRIPTOR_UPLOAD_SIZE); *msg = "Router descriptor was too large."; - control_event_or_authdir_new_descriptor("REJECTED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -694,9 +691,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) router_describe(ri), source); *msg = "Not replacing router descriptor; no information has changed since " "the last one with this identity."; - control_event_or_authdir_new_descriptor("DROPPED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_IS_ALREADY_KNOWN; goto fail; } @@ -704,10 +698,19 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) /* Do keypinning again ... this time, to add the pin if appropriate */ int keypin_status; if (ri->cache_info.signing_key_cert) { + ed25519_public_key_t *pkey = &ri->cache_info.signing_key_cert->signing_key; + /* First let's validate this pubkey before pinning it */ + if (ed25519_validate_pubkey(pkey) < 0) { + log_warn(LD_DIRSERV, "Received bad key from %s (source %s)", + router_describe(ri), source); + routerinfo_free(ri); + return ROUTER_AUTHDIR_REJECTS; + } + + /* Now pin it! */ keypin_status = keypin_check_and_add( (const uint8_t*)ri->cache_info.identity_digest, - ri->cache_info.signing_key_cert->signing_key.pubkey, - ! key_pinning); + pkey->pubkey, ! key_pinning); } else { keypin_status = keypin_check_lone_rsa( (const uint8_t*)ri->cache_info.identity_digest); @@ -742,14 +745,11 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) r = router_add_to_routerlist(ri, msg, 0, 0); if (!WRA_WAS_ADDED(r)) { /* unless the routerinfo was fine, just out-of-date */ - if (WRA_WAS_REJECTED(r)) - control_event_or_authdir_new_descriptor("REJECTED", desc, desclen, *msg); log_info(LD_DIRSERV, "Did not add descriptor from '%s' (source: %s): %s.", nickname, source, *msg ? *msg : "(no message)"); } else { smartlist_t *changed; - control_event_or_authdir_new_descriptor("ACCEPTED", desc, desclen, *msg); changed = smartlist_new(); smartlist_add(changed, ri); @@ -2693,7 +2693,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) } cp+=strlen("bw="); - out->bw_kb = tor_parse_long(cp, 0, 0, LONG_MAX, &parse_ok, &endptr); + out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr); if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) { log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s", escaped(orig_line)); @@ -3605,9 +3605,9 @@ spooled_resource_flush_some(spooled_resource_t *spooled, return SRFS_DONE; } if (conn->compress_state) { - connection_write_to_buf_compress((const char*)body, bodylen, conn, 0); + connection_buf_add_compress((const char*)body, bodylen, conn, 0); } else { - connection_write_to_buf((const char*)body, bodylen, TO_CONN(conn)); + connection_buf_add((const char*)body, bodylen, TO_CONN(conn)); } return SRFS_DONE; } else { @@ -3644,11 +3644,11 @@ spooled_resource_flush_some(spooled_resource_t *spooled, return SRFS_ERR; ssize_t bytes = (ssize_t) MIN(DIRSERV_CACHED_DIR_CHUNK_SIZE, remaining); if (conn->compress_state) { - connection_write_to_buf_compress( + connection_buf_add_compress( ptr + spooled->cached_dir_offset, bytes, conn, 0); } else { - connection_write_to_buf(ptr + spooled->cached_dir_offset, + connection_buf_add(ptr + spooled->cached_dir_offset, bytes, TO_CONN(conn)); } spooled->cached_dir_offset += bytes; @@ -3913,7 +3913,7 @@ connection_dirserv_flushed_some(dir_connection_t *conn) if (conn->compress_state) { /* Flush the compression state: there could be more bytes pending in there, * and we don't want to omit bytes. */ - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress("", 0, conn, 1); tor_compress_free(conn->compress_state); conn->compress_state = NULL; } diff --git a/src/or/dirserv.h b/src/or/dirserv.h index 480174d5bb..46967a6cb2 100644 --- a/src/or/dirserv.h +++ b/src/or/dirserv.h @@ -182,7 +182,7 @@ STATIC int dirserv_has_measured_bw(const char *node_id); STATIC int dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, smartlist_t *vote_routerstatuses); -#endif +#endif /* defined(DIRSERV_PRIVATE) */ int dirserv_read_measured_bandwidths(const char *from_file, smartlist_t *routerstatuses); @@ -204,5 +204,5 @@ void dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn, void dirserv_spool_sort(dir_connection_t *conn); void dir_conn_clear_spool(dir_connection_t *conn); -#endif +#endif /* !defined(TOR_DIRSERV_H) */ diff --git a/src/or/dirvote.c b/src/or/dirvote.c index f5e29eb786..ce82a5ef4a 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -306,7 +306,6 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, signing_key_fingerprint); } - note_crypto_pk_op(SIGN_DIR); { char *sig = router_get_dirobj_signature(digest, DIGEST_LEN, private_signing_key); @@ -542,8 +541,8 @@ compute_routerstatus_consensus(smartlist_t *votes, int consensus_method, if (cur_n > most_n || (cur && cur_n == most_n && cur->status.published_on > most_published)) { most = cur; - most_n = cur_n; - most_published = cur->status.published_on; + // most_n = cur_n; // unused after this point. + // most_published = cur->status.published_on; // unused after this point. } tor_assert(most); @@ -737,12 +736,12 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list, } } SMARTLIST_FOREACH_END(k_v_pair); - if (n_found == 1) + if (n_found == 1) { return value; - else if (BUG(n_found > 1)) - return default_val; - else + } else { + tor_assert_nonfatal(n_found == 0); return default_val; + } } /** Minimum number of directory authorities voting for a parameter to @@ -2788,48 +2787,10 @@ dirvote_get_start_of_next_interval(time_t now, int interval, int offset) return next; } -/* Using the time <b>now</b>, return the next voting valid-after time. */ -time_t -get_next_valid_after_time(time_t now) -{ - time_t next_valid_after_time; - const or_options_t *options = get_options(); - voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); - tor_assert(new_voting_schedule); - - next_valid_after_time = new_voting_schedule->interval_starts; - voting_schedule_free(new_voting_schedule); - - return next_valid_after_time; -} - -static voting_schedule_t voting_schedule; - -/** Set voting_schedule to hold the timing for the next vote we should be - * doing. */ -void -dirvote_recalculate_timing(const or_options_t *options, time_t now) -{ - voting_schedule_t *new_voting_schedule; - - if (!authdir_mode_v3(options)) { - return; - } - - /* get the new voting schedule */ - new_voting_schedule = get_voting_schedule(options, now, LOG_NOTICE); - tor_assert(new_voting_schedule); - - /* Fill in the global static struct now */ - memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); - voting_schedule_free(new_voting_schedule); -} - /* Populate and return a new voting_schedule_t that can be used to schedule * voting. The object is allocated on the heap and it's the responsibility of * the caller to free it. Can't fail. */ -voting_schedule_t * +static voting_schedule_t * get_voting_schedule(const or_options_t *options, time_t now, int severity) { int interval, vote_delay, dist_delay; @@ -2884,7 +2845,7 @@ get_voting_schedule(const or_options_t *options, time_t now, int severity) /** Frees a voting_schedule_t. This should be used instead of the generic * tor_free. */ -void +static void voting_schedule_free(voting_schedule_t *voting_schedule_to_free) { if (!voting_schedule_to_free) @@ -2892,13 +2853,53 @@ voting_schedule_free(voting_schedule_t *voting_schedule_to_free) tor_free(voting_schedule_to_free); } +static voting_schedule_t voting_schedule; + +/* Using the time <b>now</b>, return the next voting valid-after time. */ +time_t +dirvote_get_next_valid_after_time(void) +{ + /* This is a safe guard in order to make sure that the voting schedule + * static object is at least initialized. Using this function with a zeroed + * voting schedule can lead to bugs. */ + if (tor_mem_is_zero((const char *) &voting_schedule, + sizeof(voting_schedule))) { + dirvote_recalculate_timing(get_options(), time(NULL)); + voting_schedule.created_on_demand = 1; + } + return voting_schedule.interval_starts; +} + +/** Set voting_schedule to hold the timing for the next vote we should be + * doing. All type of tor do that because HS subsystem needs the timing as + * well to function properly. */ +void +dirvote_recalculate_timing(const or_options_t *options, time_t now) +{ + voting_schedule_t *new_voting_schedule; + + /* get the new voting schedule */ + new_voting_schedule = get_voting_schedule(options, now, LOG_INFO); + tor_assert(new_voting_schedule); + + /* Fill in the global static struct now */ + memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); + voting_schedule_free(new_voting_schedule); +} + /** Entry point: Take whatever voting actions are pending as of <b>now</b>. */ void dirvote_act(const or_options_t *options, time_t now) { if (!authdir_mode_v3(options)) return; - if (!voting_schedule.voting_starts) { + tor_assert_nonfatal(voting_schedule.voting_starts); + /* If we haven't initialized this object through this codeflow, we need to + * recalculate the timings to match our vote. The reason to do that is if we + * have a voting schedule initialized 1 minute ago, the voting timings might + * not be aligned to what we should expect with "now". This is especially + * true for TestingTorNetwork using smaller timings. */ + if (voting_schedule.created_on_demand) { char *keys = list_v3_auth_ids(); authority_cert_t *c = get_my_v3_authority_cert(); log_notice(LD_DIR, "Scheduling voting. Known authority IDs are %s. " @@ -3994,14 +3995,15 @@ dirvote_format_all_microdesc_vote_lines(const routerinfo_t *ri, time_t now, while ((ep = entries)) { char buf[128]; vote_microdesc_hash_t *h; - dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md, - ep->low, ep->high); - h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); - h->microdesc_hash_line = tor_strdup(buf); - h->next = result; - result = h; - ep->md->last_listed = now; - smartlist_add(microdescriptors_out, ep->md); + if (dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md, + ep->low, ep->high) >= 0) { + h = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); + h->microdesc_hash_line = tor_strdup(buf); + h->next = result; + result = h; + ep->md->last_listed = now; + smartlist_add(microdescriptors_out, ep->md); + } entries = ep->next; tor_free(ep); } diff --git a/src/or/dirvote.h b/src/or/dirvote.h index e342dc78ea..72a35fea6d 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -168,12 +168,14 @@ typedef struct { int have_fetched_missing_signatures; /* True iff we have published our consensus. */ int have_published_consensus; -} voting_schedule_t; - -voting_schedule_t *get_voting_schedule(const or_options_t *options, - time_t now, int severity); -void voting_schedule_free(voting_schedule_t *voting_schedule_to_free); + /* True iff this voting schedule was set on demand meaning not through the + * normal vote operation of a dirauth or when a consensus is set. This only + * applies to a directory authority that needs to recalculate the voting + * timings only for the first vote even though this object was initilized + * prior to voting. */ + int created_on_demand; +} voting_schedule_t; void dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out); time_t dirvote_get_start_of_next_interval(time_t now, @@ -181,7 +183,7 @@ time_t dirvote_get_start_of_next_interval(time_t now, int offset); void dirvote_recalculate_timing(const or_options_t *options, time_t now); void dirvote_act(const or_options_t *options, time_t now); -time_t get_next_valid_after_time(time_t now); +time_t dirvote_get_next_valid_after_time(void); /* invoked on timers and by outside triggers. */ struct pending_vote_t * dirvote_add_vote(const char *vote_body, @@ -242,7 +244,7 @@ STATIC int networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, int64_t M, int64_t E, int64_t D, int64_t T, int64_t weight_scale); -#endif +#endif /* defined(DIRVOTE_PRIVATE) */ -#endif +#endif /* !defined(TOR_DIRVOTE_H) */ diff --git a/src/or/dns.c b/src/or/dns.c index b5344469b5..7dc3575f53 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -102,7 +102,7 @@ static void assert_cache_ok_(void); #define assert_cache_ok() assert_cache_ok_() #else #define assert_cache_ok() STMT_NIL -#endif +#endif /* defined(DEBUG_DNS_CACHE) */ static void assert_resolve_ok(cached_resolve_t *resolve); /** Hash table of cached_resolve objects. */ @@ -182,6 +182,18 @@ evdns_log_cb(int warn, const char *msg) } else if (!strcmp(msg, "All nameservers have failed")) { control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN"); all_down = 1; + } else if (!strcmpstart(msg, "Address mismatch on received DNS")) { + static ratelim_t mismatch_limit = RATELIM_INIT(3600); + const char *src = strstr(msg, " Apparent source"); + if (!src || get_options()->SafeLogging) { + src = ""; + } + log_fn_ratelim(&mismatch_limit, severity, LD_EXIT, + "eventdns: Received a DNS packet from " + "an IP address to which we did not send a request. This " + "could be a DNS spoofing attempt, or some kind of " + "misconfiguration.%s", src); + return; } tor_log(severity, LD_EXIT, "eventdns: %s", msg); } @@ -366,7 +378,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires) resolve->expire = expires; smartlist_pqueue_add(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx), + offsetof(cached_resolve_t, minheap_idx), resolve); } @@ -413,7 +425,7 @@ purge_expired_resolves(time_t now) break; smartlist_pqueue_pop(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx)); + offsetof(cached_resolve_t, minheap_idx)); if (resolve->state == CACHE_STATE_PENDING) { log_debug(LD_EXIT, @@ -949,14 +961,14 @@ assert_connection_edge_not_dns_pending(edge_connection_t *conn) for (pend = resolve->pending_connections; pend; pend = pend->next) { tor_assert(pend->conn != conn); } -#else +#else /* !(1) */ cached_resolve_t **resolve; HT_FOREACH(resolve, cache_map, &cache_root) { for (pend = (*resolve)->pending_connections; pend; pend = pend->next) { tor_assert(pend->conn != conn); } } -#endif +#endif /* 1 */ } /** Log an error and abort if any connection waiting for a DNS resolve is @@ -1384,7 +1396,7 @@ configure_nameservers(int force) evdns_base_load_hosts(the_evdns_base, sandbox_intern_string("/etc/hosts")); } -#endif +#endif /* defined(DNS_OPTION_HOSTSFILE) && defined(USE_LIBSECCOMP) */ log_info(LD_EXIT, "Parsing resolver configuration in '%s'", conf_fname); if ((r = evdns_base_resolv_conf_parse(the_evdns_base, flags, sandbox_intern_string(conf_fname)))) { @@ -1422,7 +1434,7 @@ configure_nameservers(int force) tor_free(resolv_conf_fname); resolv_conf_mtime = 0; } -#endif +#endif /* defined(_WIN32) */ #define SET(k,v) evdns_base_set_option(the_evdns_base, (k), (v)) @@ -1566,10 +1578,11 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses, escaped_safe_str(hostname)); tor_free(escaped_address); } else if (count) { - log_warn(LD_EXIT, "eventdns returned only non-IPv4 answers for %s.", + log_info(LD_EXIT, "eventdns returned only unrecognized answer types " + " for %s.", escaped_safe_str(string_address)); } else { - log_warn(LD_BUG, "eventdns returned no addresses or error for %s!", + log_info(LD_EXIT, "eventdns returned no addresses or error for %s.", escaped_safe_str(string_address)); } } @@ -1945,7 +1958,7 @@ dns_launch_wildcard_checks(void) launch_wildcard_check(8, 16, ipv6, ".com"); launch_wildcard_check(8, 16, ipv6, ".org"); launch_wildcard_check(8, 16, ipv6, ".net"); - } + } } } @@ -2038,7 +2051,7 @@ assert_resolve_ok(cached_resolve_t *resolve) tor_assert(!resolve->hostname); else tor_assert(!resolve->result_ipv4.addr_ipv4); -#endif +#endif /* 0 */ /*XXXXX ADD MORE */ } } @@ -2088,7 +2101,7 @@ assert_cache_ok_(void) smartlist_pqueue_assert_ok(cached_resolve_pqueue, compare_cached_resolves_by_expiry_, - STRUCT_OFFSET(cached_resolve_t, minheap_idx)); + offsetof(cached_resolve_t, minheap_idx)); SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, { @@ -2102,7 +2115,7 @@ assert_cache_ok_(void) }); } -#endif +#endif /* defined(DEBUG_DNS_CACHE) */ cached_resolve_t * dns_get_cache_entry(cached_resolve_t *query) diff --git a/src/or/dns.h b/src/or/dns.h index a81cbd20da..28d9f947b4 100644 --- a/src/or/dns.h +++ b/src/or/dns.h @@ -64,7 +64,7 @@ set_exitconn_info_from_resolve,(edge_connection_t *exitconn, MOCK_DECL(STATIC int, launch_resolve,(cached_resolve_t *resolve)); -#endif +#endif /* defined(DNS_PRIVATE) */ -#endif +#endif /* !defined(TOR_DNS_H) */ diff --git a/src/or/dns_structs.h b/src/or/dns_structs.h index dc00e9f7b9..e22f23ac15 100644 --- a/src/or/dns_structs.h +++ b/src/or/dns_structs.h @@ -98,5 +98,5 @@ typedef struct cached_resolve_t { int minheap_idx; } cached_resolve_t; -#endif +#endif /* !defined(TOR_DNS_STRUCTS_H) */ diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index 54a22a5150..d254717a54 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -226,10 +226,10 @@ dnsserv_launch_request(const char *name, int reverse, TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); } -#else +#else /* !(defined(AF_UNIX)) */ TO_CONN(conn)->port = control_conn->base_.port; TO_CONN(conn)->address = tor_addr_to_str_dup(&control_conn->base_.addr); -#endif +#endif /* defined(AF_UNIX) */ if (reverse) entry_conn->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; diff --git a/src/or/dnsserv.h b/src/or/dnsserv.h index 6c0643b8dc..2af366eee5 100644 --- a/src/or/dnsserv.h +++ b/src/or/dnsserv.h @@ -23,5 +23,5 @@ void dnsserv_reject_request(entry_connection_t *conn); int dnsserv_launch_request(const char *name, int is_reverse, control_connection_t *control_conn); -#endif +#endif /* !defined(TOR_DNSSERV_H) */ diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 2cbc8019d4..67b0259243 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -814,7 +814,7 @@ STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node) { - log_info(LD_GUARD, "Adding %s as to the entry guard sample set.", + log_info(LD_GUARD, "Adding %s to the entry guard sample set.", node_describe(node)); /* make sure that the guard is not already sampled. */ @@ -1841,7 +1841,7 @@ entry_guards_update_primary(guard_selection_t *gs) bool_eq(guard->is_primary, smartlist_contains(new_primary_guards, guard))); }); -#endif +#endif /* 1 */ int any_change = 0; if (smartlist_len(gs->primary_entry_guards) != @@ -3136,20 +3136,34 @@ entry_list_is_constrained(const or_options_t *options) } /** Return the number of bridges that have descriptors that are marked with - * purpose 'bridge' and are running. - */ -int -num_bridges_usable(void) + * purpose 'bridge' and are running. If use_maybe_reachable is + * true, include bridges that might be reachable in the count. + * Otherwise, if it is false, only include bridges that have recently been + * found running in the count. + * + * We use this function to decide if we're ready to start building + * circuits through our bridges, or if we need to wait until the + * directory "server/authority" requests finish. */ +MOCK_IMPL(int, +num_bridges_usable,(int use_maybe_reachable)) { int n_options = 0; - tor_assert(get_options()->UseBridges); + if (BUG(!get_options()->UseBridges)) { + return 0; + } guard_selection_t *gs = get_guard_selection_info(); - tor_assert(gs->type == GS_TYPE_BRIDGE); + if (BUG(gs->type != GS_TYPE_BRIDGE)) { + return 0; + } SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + /* Definitely not usable */ if (guard->is_reachable == GUARD_REACHABLE_NO) continue; + /* If we want to be really sure the bridges will work, skip maybes */ + if (!use_maybe_reachable && guard->is_reachable == GUARD_REACHABLE_MAYBE) + continue; if (tor_digest_is_zero(guard->identity)) continue; const node_t *node = node_get_by_id(guard->identity); @@ -3538,15 +3552,20 @@ guards_retry_optimistic(const or_options_t *options) } /** - * Return true iff we know enough directory information to construct - * circuits through all of the primary guards we'd currently use. - */ -int -guard_selection_have_enough_dir_info_to_build_circuits(guard_selection_t *gs) + * Check if we are missing any crucial dirinfo for the guard subsystem to + * work. Return NULL if everything went well, otherwise return a newly + * allocated string with an informative error message. In the latter case, use + * the genreal descriptor information <b>using_mds</b>, <b>num_present</b> and + * <b>num_usable</b> to improve the error message. */ +char * +guard_selection_get_err_str_if_dir_info_missing(guard_selection_t *gs, + int using_mds, + int num_present, int num_usable) { if (!gs->primary_guards_up_to_date) entry_guards_update_primary(gs); + char *ret_str = NULL; int n_missing_descriptors = 0; int n_considered = 0; int num_primary_to_check; @@ -3568,16 +3587,30 @@ guard_selection_have_enough_dir_info_to_build_circuits(guard_selection_t *gs) break; } SMARTLIST_FOREACH_END(guard); - return n_missing_descriptors == 0; + /* If we are not missing any descriptors, return NULL. */ + if (!n_missing_descriptors) { + return NULL; + } + + /* otherwise return a helpful error string */ + tor_asprintf(&ret_str, "We're missing descriptors for %d/%d of our " + "primary entry guards (total %sdescriptors: %d/%d).", + n_missing_descriptors, num_primary_to_check, + using_mds?"micro":"", num_present, num_usable); + + return ret_str; } /** As guard_selection_have_enough_dir_info_to_build_circuits, but uses * the default guard selection. */ -int -entry_guards_have_enough_dir_info_to_build_circuits(void) -{ - return guard_selection_have_enough_dir_info_to_build_circuits( - get_guard_selection_info()); +char * +entry_guards_get_err_str_if_dir_info_missing(int using_mds, + int num_present, int num_usable) +{ + return guard_selection_get_err_str_if_dir_info_missing( + get_guard_selection_info(), + using_mds, + num_present, num_usable); } /** Free one guard selection context */ diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 39bff14381..d909115b1f 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -316,7 +316,7 @@ struct circuit_guard_state_t { */ entry_guard_restriction_t *restrictions; }; -#endif +#endif /* defined(ENTRYNODES_PRIVATE) */ /* Common entry points for old and new guard code */ int guards_update_all(void); @@ -342,7 +342,7 @@ int num_live_entry_guards_for_guard_selection( guard_selection_t *gs, int for_directory); int num_live_entry_guards(int for_directory); -#endif +#endif /* 1 */ const node_t *entry_guard_find_node(const entry_guard_t *guard); const char *entry_guard_get_rsa_id_digest(const entry_guard_t *guard); @@ -383,8 +383,7 @@ void entry_guards_note_internet_connectivity(guard_selection_t *gs); int update_guard_selection_choice(const or_options_t *options); -/* Used by bridges.c only. */ -int num_bridges_usable(void); +MOCK_DECL(int,num_bridges_usable,(int use_maybe_reachable)); #ifdef ENTRYNODES_PRIVATE /** @@ -558,14 +557,12 @@ STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs, STATIC int entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b); STATIC char *getinfo_helper_format_single_entry_guard(const entry_guard_t *e); -STATIC entry_guard_restriction_t * -guard_create_exit_restriction(const uint8_t *exit_id); +STATIC entry_guard_restriction_t *guard_create_exit_restriction( + const uint8_t *exit_id); -STATIC entry_guard_restriction_t * -guard_create_dirserver_md_restriction(void); +STATIC entry_guard_restriction_t *guard_create_dirserver_md_restriction(void); -STATIC void -entry_guard_restriction_free(entry_guard_restriction_t *rst); +STATIC void entry_guard_restriction_free(entry_guard_restriction_t *rst); #endif /* defined(ENTRYNODES_PRIVATE) */ @@ -589,9 +586,11 @@ int getinfo_helper_entry_guards(control_connection_t *conn, int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); -int guard_selection_have_enough_dir_info_to_build_circuits( - guard_selection_t *gs); -int entry_guards_have_enough_dir_info_to_build_circuits(void); +char *entry_guards_get_err_str_if_dir_info_missing(int using_mds, + int num_present, int num_usable); +char *guard_selection_get_err_str_if_dir_info_missing(guard_selection_t *gs, + int using_mds, + int num_present, int num_usable); void entry_guards_free_all(void); @@ -614,5 +613,5 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw, int orig_bandwidth, uint32_t guardfraction_percentage); -#endif +#endif /* !defined(TOR_ENTRYNODES_H) */ diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c index b60d2e55c8..28377a3f36 100644 --- a/src/or/ext_orport.c +++ b/src/or/ext_orport.c @@ -23,15 +23,16 @@ #include "ext_orport.h" #include "control.h" #include "config.h" -#include "util.h" #include "main.h" +#include "proto_ext_or.h" +#include "util.h" /** Allocate and return a structure capable of holding an Extended * ORPort message of body length <b>len</b>. */ ext_or_cmd_t * ext_or_cmd_new(uint16_t len) { - size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; + size_t size = offsetof(ext_or_cmd_t, body) + len; ext_or_cmd_t *cmd = tor_malloc(size); cmd->len = len; return cmd; @@ -69,10 +70,10 @@ connection_write_ext_or_command(connection_t *conn, return -1; set_uint16(header, htons(command)); set_uint16(header+2, htons(bodylen)); - connection_write_to_buf(header, 4, conn); + connection_buf_add(header, 4, conn); if (bodylen) { tor_assert(body); - connection_write_to_buf(body, bodylen, conn); + connection_buf_add(body, bodylen, conn); } return 0; } @@ -170,7 +171,7 @@ connection_ext_or_auth_neg_auth_type(connection_t *conn) if (connection_get_inbuf_len(conn) < 1) return 0; - if (connection_fetch_from_buf(authtype, 1, conn) < 0) + if (connection_buf_get_bytes(authtype, 1, conn) < 0) return -1; log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); @@ -310,7 +311,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) return 0; - if (connection_fetch_from_buf(client_nonce, + if (connection_buf_get_bytes(client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) return -1; @@ -325,7 +326,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) &reply, &reply_len) < 0) return -1; - connection_write_to_buf(reply, reply_len, conn); + connection_buf_add(reply, reply_len, conn); memwipe(reply, 0, reply_len); tor_free(reply); @@ -347,9 +348,9 @@ static void connection_ext_or_auth_send_result(connection_t *conn, int success) { if (success) - connection_write_to_buf("\x01", 1, conn); + connection_buf_add("\x01", 1, conn); else - connection_write_to_buf("\x00", 1, conn); + connection_buf_add("\x00", 1, conn); } /** Receive the client's hash from <b>conn</b>, validate that it's @@ -367,7 +368,7 @@ connection_ext_or_auth_handle_client_hash(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) return 0; - if (connection_fetch_from_buf(provided_client_hash, + if (connection_buf_get_bytes(provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) return -1; @@ -459,6 +460,11 @@ connection_ext_or_handle_cmd_useraddr(connection_t *conn, tor_free(addr_str); if (res<0) return -1; + if (port == 0) { + log_warn(LD_GENERAL, "Server transport proxy gave us an empty port " + "in ExtORPort UserAddr command."); + // return -1; // enable this if nothing breaks after a while. + } res = tor_addr_parse(&addr, address_part); tor_free(address_part); @@ -637,7 +643,7 @@ connection_ext_or_start_auth(or_connection_t *or_conn) log_debug(LD_GENERAL, "ExtORPort authentication: Sending supported authentication types"); - connection_write_to_buf((const char *)authtypes, sizeof(authtypes), conn); + connection_buf_add((const char *)authtypes, sizeof(authtypes), conn); conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; return 0; diff --git a/src/or/ext_orport.h b/src/or/ext_orport.h index b2cd05db8f..af2b97e77c 100644 --- a/src/or/ext_orport.h +++ b/src/or/ext_orport.h @@ -36,7 +36,7 @@ STATIC int handle_client_auth_nonce(const char *client_nonce, extern uint8_t *ext_or_auth_cookie; extern int ext_or_auth_cookie_is_set; #endif -#endif +#endif /* defined(EXT_ORPORT_PRIVATE) */ -#endif +#endif /* !defined(EXT_ORPORT_H) */ diff --git a/src/or/fp_pair.h b/src/or/fp_pair.h index 4cea3eda6d..f7c060b459 100644 --- a/src/or/fp_pair.h +++ b/src/or/fp_pair.h @@ -41,5 +41,5 @@ void fp_pair_map_assert_ok(const fp_pair_map_t *map); #undef DECLARE_MAP_FNS -#endif +#endif /* !defined(_TOR_FP_PAIR_H) */ diff --git a/src/or/geoip.c b/src/or/geoip.c index 65d00b8659..3944b2cf69 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -1665,6 +1665,15 @@ getinfo_helper_geoip(control_connection_t *control_conn, sa_family_t family; tor_addr_t addr; question += strlen("ip-to-country/"); + + if (!strcmp(question, "ipv4-available") || + !strcmp(question, "ipv6-available")) { + family = !strcmp(question, "ipv4-available") ? AF_INET : AF_INET6; + const int available = geoip_is_loaded(family); + tor_asprintf(answer, "%d", !! available); + return 0; + } + family = tor_addr_parse(&addr, question); if (family != AF_INET && family != AF_INET6) { *errmsg = "Invalid address family"; diff --git a/src/or/geoip.h b/src/or/geoip.h index 55ca8ca28c..acf61b97ae 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -19,7 +19,7 @@ STATIC int geoip_parse_entry(const char *line, sa_family_t family); STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr); STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr); STATIC void clear_geoip_db(void); -#endif +#endif /* defined(GEOIP_PRIVATE) */ int should_record_bridge_info(const or_options_t *options); int geoip_load_file(sa_family_t family, const char *filename); MOCK_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr)); @@ -67,5 +67,5 @@ const char *geoip_get_bridge_stats_extrainfo(time_t); char *geoip_get_bridge_stats_controller(time_t); char *format_client_stats_heartbeat(time_t now); -#endif +#endif /* !defined(TOR_GEOIP_H) */ diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 8c48a6f47d..74ab766468 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -1124,5 +1124,5 @@ hibernate_set_state_for_testing_(hibernate_state_t newstate) { hibernate_state = newstate; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/hibernate.h b/src/or/hibernate.h index 8bdb65a927..85fb42864b 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -53,7 +53,7 @@ typedef enum { #ifdef TOR_UNIT_TESTS void hibernate_set_state_for_testing_(hibernate_state_t newstate); #endif -#endif +#endif /* defined(HIBERNATE_PRIVATE) */ -#endif +#endif /* !defined(TOR_HIBERNATE_H) */ diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 29681b42b5..3ebe13fb4d 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -9,15 +9,22 @@ /* For unit tests.*/ #define HS_CACHE_PRIVATE -#include "hs_cache.h" - #include "or.h" #include "config.h" +#include "hs_ident.h" #include "hs_common.h" +#include "hs_client.h" #include "hs_descriptor.h" #include "networkstatus.h" #include "rendcache.h" +#include "hs_cache.h" + +static int cached_client_descriptor_has_expired(time_t now, + const hs_cache_client_descriptor_t *cached_desc); + +/********************** Directory HS cache ******************/ + /* Directory descriptor cache. Map indexed by blinded key. */ static digest256map_t *hs_cache_v3_dir; @@ -98,7 +105,7 @@ cache_dir_desc_new(const char *desc) /* Return the size of a cache entry in bytes. */ static size_t -cache_get_entry_size(const hs_cache_dir_descriptor_t *entry) +cache_get_dir_entry_size(const hs_cache_dir_descriptor_t *entry) { return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data) + strlen(entry->encoded_desc)); @@ -124,15 +131,17 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) if (cache_entry->plaintext_data->revision_counter >= desc->plaintext_data->revision_counter) { log_info(LD_REND, "Descriptor revision counter in our cache is " - "greater or equal than the one we received. " - "Rejecting!"); + "greater or equal than the one we received (%d/%d). " + "Rejecting!", + (int)cache_entry->plaintext_data->revision_counter, + (int)desc->plaintext_data->revision_counter); goto err; } /* We now know that the descriptor we just received is a new one so * remove the entry we currently have from our cache so we can then * store the new one. */ remove_v3_desc_as_dir(cache_entry); - rend_cache_decrement_allocation(cache_get_entry_size(cache_entry)); + rend_cache_decrement_allocation(cache_get_dir_entry_size(cache_entry)); cache_dir_desc_free(cache_entry); } /* Store the descriptor we just got. We are sure here that either we @@ -142,7 +151,7 @@ cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) /* Update our total cache size with this entry for the OOM. This uses the * old HS protocol cache subsystem for which we are tied with. */ - rend_cache_increment_allocation(cache_get_entry_size(desc)); + rend_cache_increment_allocation(cache_get_dir_entry_size(desc)); /* XXX: Update HS statistics. We should have specific stats for v3. */ @@ -219,7 +228,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff) } /* Here, our entry has expired, remove and free. */ MAP_DEL_CURRENT(key); - entry_size = cache_get_entry_size(entry); + entry_size = cache_get_dir_entry_size(entry); bytes_removed += entry_size; /* Entry is not in the cache anymore, destroy it. */ cache_dir_desc_free(entry); @@ -228,8 +237,7 @@ cache_clean_v3_as_dir(time_t now, time_t global_cutoff) /* Logging. */ { char key_b64[BASE64_DIGEST256_LEN + 1]; - base64_encode(key_b64, sizeof(key_b64), (const char *) key, - DIGEST256_LEN, 0); + digest256_to_base64(key_b64, (const char *) key); log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache", safe_str_client(key_b64)); } @@ -313,6 +321,536 @@ hs_cache_clean_as_dir(time_t now) cache_clean_v3_as_dir(now, 0); } +/********************** Client-side HS cache ******************/ + +/* Client-side HS descriptor cache. Map indexed by service identity key. */ +static digest256map_t *hs_cache_v3_client; + +/* Client-side introduction point state cache. Map indexed by service public + * identity key (onion address). It contains hs_cache_client_intro_state_t + * objects all related to a specific service. */ +static digest256map_t *hs_cache_client_intro_state; + +/* Return the size of a client cache entry in bytes. */ +static size_t +cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) +{ + return sizeof(*entry) + + strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); +} + +/* Remove a given descriptor from our cache. */ +static void +remove_v3_desc_as_client(const hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_remove(hs_cache_v3_client, desc->key.pubkey); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_decrement_allocation(cache_get_client_entry_size(desc)); +} + +/* Store a given descriptor in our cache. */ +static void +store_v3_desc_as_client(hs_cache_client_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_set(hs_cache_v3_client, desc->key.pubkey, desc); + /* Update cache size with this entry for the OOM handler. */ + rend_cache_increment_allocation(cache_get_client_entry_size(desc)); +} + +/* Query our cache and return the entry or NULL if not found or if expired. */ +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key) +{ + time_t now = approx_time(); + hs_cache_client_descriptor_t *cached_desc; + + tor_assert(key); + + /* Do the lookup */ + cached_desc = digest256map_get(hs_cache_v3_client, key); + if (!cached_desc) { + return NULL; + } + + /* Don't return expired entries */ + if (cached_client_descriptor_has_expired(now, cached_desc)) { + return NULL; + } + + return cached_desc; +} + +/* Parse the encoded descriptor in <b>desc_str</b> using + * <b>service_identity_pk<b> to decrypt it first. + * + * If everything goes well, allocate and return a new + * hs_cache_client_descriptor_t object. In case of error, return NULL. */ +static hs_cache_client_descriptor_t * +cache_client_desc_new(const char *desc_str, + const ed25519_public_key_t *service_identity_pk) +{ + hs_descriptor_t *desc = NULL; + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + + /* Decode the descriptor we just fetched. */ + if (hs_client_decode_descriptor(desc_str, service_identity_pk, &desc) < 0) { + goto end; + } + tor_assert(desc); + + /* All is good: make a cache object for this descriptor */ + client_desc = tor_malloc_zero(sizeof(hs_cache_client_descriptor_t)); + ed25519_pubkey_copy(&client_desc->key, service_identity_pk); + /* Set expiration time for this cached descriptor to be the start of the next + * time period since that's when clients need to start using the next blinded + * pk of the service (and hence will need its next descriptor). */ + client_desc->expiration_ts = hs_get_start_time_of_next_time_period(0); + client_desc->desc = desc; + client_desc->encoded_desc = tor_strdup(desc_str); + + end: + return client_desc; +} + +/** Free memory allocated by <b>desc</b>. */ +static void +cache_client_desc_free(hs_cache_client_descriptor_t *desc) +{ + if (desc == NULL) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->key, 0, sizeof(desc->key)); + memwipe(desc->encoded_desc, 0, strlen(desc->encoded_desc)); + tor_free(desc->encoded_desc); + tor_free(desc); +} + +/** Helper function: Use by the free all function to clear the client cache */ +static void +cache_client_desc_free_(void *ptr) +{ + hs_cache_client_descriptor_t *desc = ptr; + cache_client_desc_free(desc); +} + +/* Return a newly allocated and initialized hs_cache_intro_state_t object. */ +static hs_cache_intro_state_t * +cache_intro_state_new(void) +{ + hs_cache_intro_state_t *state = tor_malloc_zero(sizeof(*state)); + state->created_ts = approx_time(); + return state; +} + +/* Free an hs_cache_intro_state_t object. */ +static void +cache_intro_state_free(hs_cache_intro_state_t *state) +{ + tor_free(state); +} + +/* Helper function: use by the free all function. */ +static void +cache_intro_state_free_(void *state) +{ + cache_intro_state_free(state); +} + +/* Return a newly allocated and initialized hs_cache_client_intro_state_t + * object. */ +static hs_cache_client_intro_state_t * +cache_client_intro_state_new(void) +{ + hs_cache_client_intro_state_t *cache = tor_malloc_zero(sizeof(*cache)); + cache->intro_points = digest256map_new(); + return cache; +} + +/* Free a cache client intro state object. */ +static void +cache_client_intro_state_free(hs_cache_client_intro_state_t *cache) +{ + if (cache == NULL) { + return; + } + digest256map_free(cache->intro_points, cache_intro_state_free_); + tor_free(cache); +} + +/* Helper function: use by the free all function. */ +static void +cache_client_intro_state_free_(void *entry) +{ + cache_client_intro_state_free(entry); +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, lookup the intro state object. Return 1 if + * found and put it in entry if not NULL. Return 0 if not found and entry is + * untouched. */ +static int +cache_client_intro_state_lookup(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **entry) +{ + hs_cache_intro_state_t *state; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the intro state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + goto not_found; + } + + /* From the cache we just found for the service, lookup in the introduction + * points map for the given authentication key. */ + state = digest256map_get(cache->intro_points, auth_key->pubkey); + if (state == NULL) { + goto not_found; + } + if (entry) { + *entry = state; + } + return 1; + not_found: + return 0; +} + +/* Note the given failure in state. */ +static void +cache_client_intro_state_note(hs_cache_intro_state_t *state, + rend_intro_point_failure_t failure) +{ + tor_assert(state); + switch (failure) { + case INTRO_POINT_FAILURE_GENERIC: + state->error = 1; + break; + case INTRO_POINT_FAILURE_TIMEOUT: + state->timed_out = 1; + break; + case INTRO_POINT_FAILURE_UNREACHABLE: + state->unreachable_count++; + break; + default: + tor_assert_nonfatal_unreached(); + return; + } +} + +/* For the given service identity key service_pk and an introduction + * authentication key auth_key, add an entry in the client intro state cache + * If no entry exists for the service, it will create one. If state is non + * NULL, it will point to the new intro state entry. */ +static void +cache_client_intro_state_add(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + hs_cache_intro_state_t **state) +{ + hs_cache_intro_state_t *entry, *old_entry; + hs_cache_client_intro_state_t *cache; + + tor_assert(service_pk); + tor_assert(auth_key); + + /* Lookup the state cache for this service key. */ + cache = digest256map_get(hs_cache_client_intro_state, service_pk->pubkey); + if (cache == NULL) { + cache = cache_client_intro_state_new(); + digest256map_set(hs_cache_client_intro_state, service_pk->pubkey, cache); + } + + entry = cache_intro_state_new(); + old_entry = digest256map_set(cache->intro_points, auth_key->pubkey, entry); + /* This should never happened because the code flow is to lookup the entry + * before adding it. But, just in case, non fatal assert and free it. */ + tor_assert_nonfatal(old_entry == NULL); + tor_free(old_entry); + + if (state) { + *state = entry; + } +} + +/* Remove every intro point state entry from cache that has been created + * before or at the cutoff. */ +static void +cache_client_intro_state_clean(time_t cutoff, + hs_cache_client_intro_state_t *cache) +{ + tor_assert(cache); + + DIGEST256MAP_FOREACH_MODIFY(cache->intro_points, key, + hs_cache_intro_state_t *, entry) { + if (entry->created_ts <= cutoff) { + cache_intro_state_free(entry); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Return true iff no intro points are in this cache. */ +static int +cache_client_intro_state_is_empty(const hs_cache_client_intro_state_t *cache) +{ + return digest256map_isempty(cache->intro_points); +} + +/** Check whether <b>client_desc</b> is useful for us, and store it in the + * client-side HS cache if so. The client_desc is freed if we already have a + * fresher (higher revision counter count) in the cache. */ +static int +cache_store_as_client(hs_cache_client_descriptor_t *client_desc) +{ + hs_cache_client_descriptor_t *cache_entry; + + /* TODO: Heavy code duplication with cache_store_as_dir(). Consider + * refactoring and uniting! */ + + tor_assert(client_desc); + + /* Check if we already have a descriptor from this HS in cache. If we do, + * check if this descriptor is newer than the cached one */ + cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey); + if (cache_entry != NULL) { + /* If we have an entry in our cache that has a revision counter greater + * than the one we just fetched, discard the one we fetched. */ + if (BUG(cache_entry->desc->plaintext_data.revision_counter > + client_desc->desc->plaintext_data.revision_counter)) { + cache_client_desc_free(client_desc); + goto done; + } + /* Remove old entry. Make space for the new one! */ + remove_v3_desc_as_client(cache_entry); + cache_client_desc_free(cache_entry); + } + + /* Store descriptor in cache */ + store_v3_desc_as_client(client_desc); + + done: + return 0; +} + +/* Return true iff the cached client descriptor at <b>cached_desc</b has + * expired. */ +static int +cached_client_descriptor_has_expired(time_t now, + const hs_cache_client_descriptor_t *cached_desc) +{ + /* We use the current consensus time to see if we should expire this + * descriptor since we use consensus time for all other parts of the protocol + * as well (e.g. to build the blinded key and compute time periods). */ + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + /* If we don't have a recent consensus, consider this entry expired since we + * will want to fetch a new HS desc when we get a live consensus. */ + if (!ns) { + return 1; + } + + if (cached_desc->expiration_ts <= ns->valid_after) { + return 1; + } + + return 0; +} + +/* clean the client cache using now as the current time. Return the total size + * of removed bytes from the cache. */ +static size_t +cache_clean_v3_as_client(time_t now) +{ + size_t bytes_removed = 0; + + if (!hs_cache_v3_client) { /* No cache to clean. Just return. */ + return 0; + } + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size; + + /* If the entry has not expired, continue to the next cached entry */ + if (!cached_client_descriptor_has_expired(now, entry)) { + continue; + } + /* Here, our entry has expired, remove and free. */ + MAP_DEL_CURRENT(key); + entry_size = cache_get_client_entry_size(entry); + bytes_removed += entry_size; + /* Entry is not in the cache anymore, destroy it. */ + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + /* Logging. */ + { + char key_b64[BASE64_DIGEST256_LEN + 1]; + digest256_to_base64(key_b64, (const char *) key); + log_info(LD_REND, "Removing hidden service v3 descriptor '%s' " + "from client cache", + safe_str_client(key_b64)); + } + } DIGEST256MAP_FOREACH_END; + + return bytes_removed; +} + +/** Public API: Given the HS ed25519 identity public key in <b>key</b>, return + * its HS descriptor if it's stored in our cache, or NULL if not. */ +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key) +{ + hs_cache_client_descriptor_t *cached_desc = NULL; + + tor_assert(key); + + cached_desc = lookup_v3_desc_as_client(key->pubkey); + if (cached_desc) { + tor_assert(cached_desc->desc); + return cached_desc->desc; + } + + return NULL; +} + +/** Public API: Given an encoded descriptor, store it in the client HS + * cache. Return -1 on error, 0 on success .*/ +int +hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk) +{ + hs_cache_client_descriptor_t *client_desc = NULL; + + tor_assert(desc_str); + tor_assert(identity_pk); + + /* Create client cache descriptor object */ + client_desc = cache_client_desc_new(desc_str, identity_pk); + if (!client_desc) { + log_warn(LD_GENERAL, "Failed to parse received descriptor %s.", + escaped(desc_str)); + goto err; + } + + /* Push it to the cache */ + if (cache_store_as_client(client_desc) < 0) { + goto err; + } + + return 0; + + err: + cache_client_desc_free(client_desc); + return -1; +} + +/* Clean all client caches using the current time now. */ +void +hs_cache_clean_as_client(time_t now) +{ + /* Start with v2 cache cleaning. */ + rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); + /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function + * to compute the cutoff by itself using the lifetime value. */ + cache_clean_v3_as_client(now); +} + +/* Purge the client descriptor cache. */ +void +hs_cache_purge_as_client(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size = cache_get_client_entry_size(entry); + MAP_DEL_CURRENT(key); + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client descriptor cache purged."); +} + +/* For a given service identity public key and an introduction authentication + * key, note the given failure in the client intro state cache. */ +void +hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure) +{ + int found; + hs_cache_intro_state_t *entry; + + tor_assert(service_pk); + tor_assert(auth_key); + + found = cache_client_intro_state_lookup(service_pk, auth_key, &entry); + if (!found) { + /* Create a new entry and add it to the cache. */ + cache_client_intro_state_add(service_pk, auth_key, &entry); + } + /* Note down the entry. */ + cache_client_intro_state_note(entry, failure); +} + +/* For a given service identity public key and an introduction authentication + * key, return true iff it is present in the failure cache. */ +const hs_cache_intro_state_t * +hs_cache_client_intro_state_find(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key) +{ + hs_cache_intro_state_t *state = NULL; + cache_client_intro_state_lookup(service_pk, auth_key, &state); + return state; +} + +/* Cleanup the client introduction state cache. */ +void +hs_cache_client_intro_state_clean(time_t now) +{ + time_t cutoff = now - HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE; + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + /* Cleanup intro points failure. */ + cache_client_intro_state_clean(cutoff, cache); + + /* Is this cache empty for this service key? If yes, remove it from the + * cache. Else keep it. */ + if (cache_client_intro_state_is_empty(cache)) { + cache_client_intro_state_free(cache); + MAP_DEL_CURRENT(key); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Purge the client introduction state cache. */ +void +hs_cache_client_intro_state_purge(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + MAP_DEL_CURRENT(key); + cache_client_intro_state_free(cache); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client introduction point state " + "cache purged."); +} + +/**************** Generics *********************************/ + /* Do a round of OOM cleanup on all directory caches. Return the amount of * removed bytes. It is possible that the returned value is lower than * min_remove_bytes if the caches get emptied out so the caller should be @@ -367,10 +905,7 @@ hs_cache_handle_oom(time_t now, size_t min_remove_bytes) return bytes_removed; } -/** - * Return the maximum size of an HS descriptor we are willing to accept as an - * HSDir. - */ +/* Return the maximum size of a v3 HS descriptor. */ unsigned int hs_cache_get_max_descriptor_size(void) { @@ -386,6 +921,12 @@ hs_cache_init(void) /* Calling this twice is very wrong code flow. */ tor_assert(!hs_cache_v3_dir); hs_cache_v3_dir = digest256map_new(); + + tor_assert(!hs_cache_v3_client); + hs_cache_v3_client = digest256map_new(); + + tor_assert(!hs_cache_client_intro_state); + hs_cache_client_intro_state = digest256map_new(); } /* Cleanup the hidden service cache subsystem. */ @@ -394,5 +935,12 @@ hs_cache_free_all(void) { digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_); hs_cache_v3_dir = NULL; + + digest256map_free(hs_cache_v3_client, cache_client_desc_free_); + hs_cache_v3_client = NULL; + + digest256map_free(hs_cache_client_intro_state, + cache_client_intro_state_free_); + hs_cache_client_intro_state = NULL; } diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index ed00424234..2dcc518a71 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -15,8 +15,34 @@ #include "crypto_ed25519.h" #include "hs_common.h" #include "hs_descriptor.h" +#include "rendcommon.h" #include "torcert.h" +/* This is the maximum time an introduction point state object can stay in the + * client cache in seconds (2 mins or 120 seconds). */ +#define HS_CACHE_CLIENT_INTRO_STATE_MAX_AGE (2 * 60) + +/* Introduction point state. */ +typedef struct hs_cache_intro_state_t { + /* When this entry was created and put in the cache. */ + time_t created_ts; + + /* Did it suffered a generic error? */ + unsigned int error : 1; + + /* Did it timed out? */ + unsigned int timed_out : 1; + + /* How many times we tried to reached it and it was unreachable. */ + uint32_t unreachable_count; +} hs_cache_intro_state_t; + +typedef struct hs_cache_client_intro_state_t { + /* Contains hs_cache_intro_state_t object indexed by introduction point + * authentication key. */ + digest256map_t *intro_points; +} hs_cache_client_intro_state_t; + /* Descriptor representation on the directory side which is a subset of * information that the HSDir can decode and serve it. */ typedef struct hs_cache_dir_descriptor_t { @@ -53,11 +79,49 @@ int hs_cache_store_as_dir(const char *desc); int hs_cache_lookup_as_dir(uint32_t version, const char *query, const char **desc_out); +const hs_descriptor_t * +hs_cache_lookup_as_client(const ed25519_public_key_t *key); +int hs_cache_store_as_client(const char *desc_str, + const ed25519_public_key_t *identity_pk); +void hs_cache_clean_as_client(time_t now); +void hs_cache_purge_as_client(void); + +/* Client failure cache. */ +void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key, + rend_intro_point_failure_t failure); +const hs_cache_intro_state_t *hs_cache_client_intro_state_find( + const ed25519_public_key_t *service_pk, + const ed25519_public_key_t *auth_key); +void hs_cache_client_intro_state_clean(time_t now); +void hs_cache_client_intro_state_purge(void); + #ifdef HS_CACHE_PRIVATE +/** Represents a locally cached HS descriptor on a hidden service client. */ +typedef struct hs_cache_client_descriptor_t { + /* This object is indexed using the service identity public key */ + ed25519_public_key_t key; + + /* When will this entry expire? We expire cached client descriptors in the + * start of the next time period, since that's when clients need to start + * using the next blinded key of the service. */ + time_t expiration_ts; + + /* The cached descriptor, this object is the owner. It can't be NULL. A + * cache object without a valid descriptor is not possible. */ + hs_descriptor_t *desc; + + /* Encoded descriptor in string form. Can't be NULL. */ + char *encoded_desc; +} hs_cache_client_descriptor_t; + STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff); -#endif /* HS_CACHE_PRIVATE */ +STATIC hs_cache_client_descriptor_t * +lookup_v3_desc_as_client(const uint8_t *key); + +#endif /* defined(HS_CACHE_PRIVATE) */ -#endif /* TOR_HS_CACHE_H */ +#endif /* !defined(TOR_HS_CACHE_H) */ diff --git a/src/or/hs_cell.c b/src/or/hs_cell.c new file mode 100644 index 0000000000..5244cfa3dd --- /dev/null +++ b/src/or/hs_cell.c @@ -0,0 +1,948 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.c + * \brief Hidden service API for cell creation and handling. + **/ + +#include "or.h" +#include "config.h" +#include "rendservice.h" +#include "replaycache.h" +#include "util.h" + +#include "hs_cell.h" +#include "hs_ntor.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" +#include "hs/cell_introduce1.h" +#include "hs/cell_rendezvous.h" + +/* Compute the MAC of an INTRODUCE cell in mac_out. The encoded_cell param is + * the cell content up to the ENCRYPTED section of length encoded_cell_len. + * The encrypted param is the start of the ENCRYPTED section of length + * encrypted_len. The mac_key is the key needed for the computation of the MAC + * derived from the ntor handshake of length mac_key_len. + * + * The length mac_out_len must be at least DIGEST256_LEN. */ +static void +compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, + const uint8_t *encrypted, size_t encrypted_len, + const uint8_t *mac_key, size_t mac_key_len, + uint8_t *mac_out, size_t mac_out_len) +{ + size_t offset = 0; + size_t mac_msg_len; + uint8_t mac_msg[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(encoded_cell); + tor_assert(encrypted); + tor_assert(mac_key); + tor_assert(mac_out); + tor_assert(mac_out_len >= DIGEST256_LEN); + + /* Compute the size of the message which is basically the entire cell until + * the MAC field of course. */ + mac_msg_len = encoded_cell_len + (encrypted_len - DIGEST256_LEN); + tor_assert(mac_msg_len <= sizeof(mac_msg)); + + /* First, put the encoded cell in the msg. */ + memcpy(mac_msg, encoded_cell, encoded_cell_len); + offset += encoded_cell_len; + /* Second, put the CLIENT_PK + ENCRYPTED_DATA but ommit the MAC field (which + * is junk at this point). */ + memcpy(mac_msg + offset, encrypted, (encrypted_len - DIGEST256_LEN)); + offset += (encrypted_len - DIGEST256_LEN); + tor_assert(offset == mac_msg_len); + + crypto_mac_sha3_256(mac_out, mac_out_len, + mac_key, mac_key_len, + mac_msg, mac_msg_len); + memwipe(mac_msg, 0, sizeof(mac_msg)); +} + +/* From a set of keys, subcredential and the ENCRYPTED section of an + * INTRODUCE2 cell, return a newly allocated intro cell keys structure. + * Finally, the client public key is copied in client_pk. On error, return + * NULL. */ +static hs_ntor_intro_cell_keys_t * +get_introduce2_key_material(const ed25519_public_key_t *auth_key, + const curve25519_keypair_t *enc_key, + const uint8_t *subcredential, + const uint8_t *encrypted_section, + curve25519_public_key_t *client_pk) +{ + hs_ntor_intro_cell_keys_t *keys; + + tor_assert(auth_key); + tor_assert(enc_key); + tor_assert(subcredential); + tor_assert(encrypted_section); + tor_assert(client_pk); + + keys = tor_malloc_zero(sizeof(*keys)); + + /* First bytes of the ENCRYPTED section are the client public key. */ + memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN); + + if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk, + subcredential, keys) < 0) { + /* Don't rely on the caller to wipe this on error. */ + memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); + tor_free(keys); + keys = NULL; + } + return keys; +} + +/* Using the given encryption key, decrypt the encrypted_section of length + * encrypted_section_len of an INTRODUCE2 cell and return a newly allocated + * buffer containing the decrypted data. On decryption failure, NULL is + * returned. */ +static uint8_t * +decrypt_introduce2(const uint8_t *enc_key, const uint8_t *encrypted_section, + size_t encrypted_section_len) +{ + uint8_t *decrypted = NULL; + crypto_cipher_t *cipher = NULL; + + tor_assert(enc_key); + tor_assert(encrypted_section); + + /* Decrypt ENCRYPTED section. */ + cipher = crypto_cipher_new_with_bits((char *) enc_key, + CURVE25519_PUBKEY_LEN * 8); + tor_assert(cipher); + + /* This is symmetric encryption so can't be bigger than the encrypted + * section length. */ + decrypted = tor_malloc_zero(encrypted_section_len); + if (crypto_cipher_decrypt(cipher, (char *) decrypted, + (const char *) encrypted_section, + encrypted_section_len) < 0) { + tor_free(decrypted); + decrypted = NULL; + goto done; + } + + done: + crypto_cipher_free(cipher); + return decrypted; +} + +/* Given a pointer to the decrypted data of the ENCRYPTED section of an + * INTRODUCE2 cell of length decrypted_len, parse and validate the cell + * content. Return a newly allocated cell structure or NULL on error. The + * circuit and service object are only used for logging purposes. */ +static trn_cell_introduce_encrypted_t * +parse_introduce2_encrypted(const uint8_t *decrypted_data, + size_t decrypted_len, const origin_circuit_t *circ, + const hs_service_t *service) +{ + trn_cell_introduce_encrypted_t *enc_cell = NULL; + + tor_assert(decrypted_data); + tor_assert(circ); + tor_assert(service); + + if (trn_cell_introduce_encrypted_parse(&enc_cell, decrypted_data, + decrypted_len) < 0) { + log_info(LD_REND, "Unable to parse the decrypted ENCRYPTED section of " + "the INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_get_onion_key_type(enc_cell) != + HS_CELL_ONION_KEY_TYPE_NTOR) { + log_info(LD_REND, "INTRODUCE2 onion key type is invalid. Got %u but " + "expected %u on circuit %u for service %s", + trn_cell_introduce_encrypted_get_onion_key_type(enc_cell), + HS_CELL_ONION_KEY_TYPE_NTOR, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + if (trn_cell_introduce_encrypted_getlen_onion_key(enc_cell) != + CURVE25519_PUBKEY_LEN) { + log_info(LD_REND, "INTRODUCE2 onion key length is invalid. Got %u but " + "expected %d on circuit %u for service %s", + (unsigned)trn_cell_introduce_encrypted_getlen_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN, TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* XXX: Validate NSPEC field as well. */ + + return enc_cell; + err: + trn_cell_introduce_encrypted_free(enc_cell); + return NULL; +} + +/* Build a legacy ESTABLISH_INTRO cell with the given circuit nonce and RSA + * encryption key. The encoded cell is put in cell_out that MUST at least be + * of the size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on + * success else a negative value and cell_out is untouched. */ +static ssize_t +build_legacy_establish_intro(const char *circ_nonce, crypto_pk_t *enc_key, + uint8_t *cell_out) +{ + ssize_t cell_len; + + tor_assert(circ_nonce); + tor_assert(enc_key); + tor_assert(cell_out); + + memwipe(cell_out, 0, RELAY_PAYLOAD_SIZE); + + cell_len = rend_service_encode_establish_intro_cell((char*)cell_out, + RELAY_PAYLOAD_SIZE, + enc_key, circ_nonce); + return cell_len; +} + +/* Parse an INTRODUCE2 cell from payload of size payload_len for the given + * service and circuit which are used only for logging purposes. The resulting + * parsed cell is put in cell_ptr_out. + * + * This function only parses prop224 INTRODUCE2 cells even when the intro point + * is a legacy intro point. That's because intro points don't actually care + * about the contents of the introduce cell. Legacy INTRODUCE cells are only + * used by the legacy system now. + * + * Return 0 on success else a negative value and cell_ptr_out is untouched. */ +static int +parse_introduce2_cell(const hs_service_t *service, + const origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len, + trn_cell_introduce1_t **cell_ptr_out) +{ + trn_cell_introduce1_t *cell = NULL; + + tor_assert(service); + tor_assert(circ); + tor_assert(payload); + tor_assert(cell_ptr_out); + + /* Parse the cell so we can start cell validation. */ + if (trn_cell_introduce1_parse(&cell, payload, payload_len) < 0) { + log_info(LD_PROTOCOL, "Unable to parse INTRODUCE2 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Success. */ + *cell_ptr_out = cell; + return 0; + err: + return -1; +} + +/* Set the onion public key onion_pk in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_onion_key(trn_cell_introduce_encrypted_t *cell, + const uint8_t *onion_pk) +{ + tor_assert(cell); + tor_assert(onion_pk); + /* There is only one possible key type for a non legacy cell. */ + trn_cell_introduce_encrypted_set_onion_key_type(cell, + HS_CELL_ONION_KEY_TYPE_NTOR); + trn_cell_introduce_encrypted_set_onion_key_len(cell, CURVE25519_PUBKEY_LEN); + trn_cell_introduce_encrypted_setlen_onion_key(cell, CURVE25519_PUBKEY_LEN); + memcpy(trn_cell_introduce_encrypted_getarray_onion_key(cell), onion_pk, + trn_cell_introduce_encrypted_getlen_onion_key(cell)); +} + +/* Set the link specifiers in lspecs in cell, the encrypted section of an + * INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_link_spec(trn_cell_introduce_encrypted_t *cell, + const smartlist_t *lspecs) +{ + tor_assert(cell); + tor_assert(lspecs); + tor_assert(smartlist_len(lspecs) > 0); + tor_assert(smartlist_len(lspecs) <= UINT8_MAX); + + uint8_t lspecs_num = (uint8_t) smartlist_len(lspecs); + trn_cell_introduce_encrypted_set_nspec(cell, lspecs_num); + /* We aren't duplicating the link specifiers object here which means that + * the ownership goes to the trn_cell_introduce_encrypted_t cell and those + * object will be freed when the cell is. */ + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, + trn_cell_introduce_encrypted_add_nspecs(cell, ls)); +} + +/* Set padding in the enc_cell only if needed that is the total length of both + * sections are below the mininum required for an INTRODUCE1 cell. */ +static void +introduce1_set_encrypted_padding(const trn_cell_introduce1_t *cell, + trn_cell_introduce_encrypted_t *enc_cell) +{ + tor_assert(cell); + tor_assert(enc_cell); + /* This is the length we expect to have once encoded of the whole cell. */ + ssize_t full_len = trn_cell_introduce1_encoded_len(cell) + + trn_cell_introduce_encrypted_encoded_len(enc_cell); + tor_assert(full_len > 0); + if (full_len < HS_CELL_INTRODUCE1_MIN_SIZE) { + size_t padding = HS_CELL_INTRODUCE1_MIN_SIZE - full_len; + trn_cell_introduce_encrypted_setlen_pad(enc_cell, padding); + memset(trn_cell_introduce_encrypted_getarray_pad(enc_cell), 0, + trn_cell_introduce_encrypted_getlen_pad(enc_cell)); + } +} + +/* Encrypt the ENCRYPTED payload and encode it in the cell using the enc_cell + * and the INTRODUCE1 data. + * + * This can't fail but it is very important that the caller sets every field + * in data so the computation of the INTRODUCE1 keys doesn't fail. */ +static void +introduce1_encrypt_and_encode(trn_cell_introduce1_t *cell, + const trn_cell_introduce_encrypted_t *enc_cell, + const hs_cell_introduce1_data_t *data) +{ + size_t offset = 0; + ssize_t encrypted_len; + ssize_t encoded_cell_len, encoded_enc_cell_len; + uint8_t encoded_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t encoded_enc_cell[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t *encrypted = NULL; + uint8_t mac[DIGEST256_LEN]; + crypto_cipher_t *cipher = NULL; + hs_ntor_intro_cell_keys_t keys; + + tor_assert(cell); + tor_assert(enc_cell); + tor_assert(data); + + /* Encode the cells up to now of what we have to we can perform the MAC + * computation on it. */ + encoded_cell_len = trn_cell_introduce1_encode(encoded_cell, + sizeof(encoded_cell), cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_cell_len > 0); + + encoded_enc_cell_len = + trn_cell_introduce_encrypted_encode(encoded_enc_cell, + sizeof(encoded_enc_cell), enc_cell); + /* We have a much more serious issue if this isn't true. */ + tor_assert(encoded_enc_cell_len > 0); + + /* Get the key material for the encryption. */ + if (hs_ntor_client_get_introduce1_keys(data->auth_pk, data->enc_pk, + data->client_kp, + data->subcredential, &keys) < 0) { + tor_assert_unreached(); + } + + /* Prepare cipher with the encryption key just computed. */ + cipher = crypto_cipher_new_with_bits((const char *) keys.enc_key, + sizeof(keys.enc_key) * 8); + tor_assert(cipher); + + /* Compute the length of the ENCRYPTED section which is the CLIENT_PK, + * ENCRYPTED_DATA and MAC length. */ + encrypted_len = sizeof(data->client_kp->pubkey) + encoded_enc_cell_len + + sizeof(mac); + tor_assert(encrypted_len < RELAY_PAYLOAD_SIZE); + encrypted = tor_malloc_zero(encrypted_len); + + /* Put the CLIENT_PK first. */ + memcpy(encrypted, data->client_kp->pubkey.public_key, + sizeof(data->client_kp->pubkey.public_key)); + offset += sizeof(data->client_kp->pubkey.public_key); + /* Then encrypt and set the ENCRYPTED_DATA. This can't fail. */ + crypto_cipher_encrypt(cipher, (char *) encrypted + offset, + (const char *) encoded_enc_cell, encoded_enc_cell_len); + crypto_cipher_free(cipher); + offset += encoded_enc_cell_len; + /* Compute MAC from the above and put it in the buffer. This function will + * make the adjustment to the encryptled_len to ommit the MAC length. */ + compute_introduce_mac(encoded_cell, encoded_cell_len, + encrypted, encrypted_len, + keys.mac_key, sizeof(keys.mac_key), + mac, sizeof(mac)); + memcpy(encrypted + offset, mac, sizeof(mac)); + offset += sizeof(mac); + tor_assert(offset == (size_t) encrypted_len); + + /* Set the ENCRYPTED section in the cell. */ + trn_cell_introduce1_setlen_encrypted(cell, encrypted_len); + memcpy(trn_cell_introduce1_getarray_encrypted(cell), + encrypted, encrypted_len); + + /* Cleanup. */ + memwipe(&keys, 0, sizeof(keys)); + memwipe(mac, 0, sizeof(mac)); + memwipe(encrypted, 0, sizeof(encrypted_len)); + memwipe(encoded_enc_cell, 0, sizeof(encoded_enc_cell)); + tor_free(encrypted); +} + +/* Using the INTRODUCE1 data, setup the ENCRYPTED section in cell. This means + * set it, encrypt it and encode it. */ +static void +introduce1_set_encrypted(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + trn_cell_introduce_encrypted_t *enc_cell; + trn_cell_extension_t *ext; + + tor_assert(cell); + tor_assert(data); + + enc_cell = trn_cell_introduce_encrypted_new(); + tor_assert(enc_cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce_encrypted_set_extensions(enc_cell, ext); + + /* Set the rendezvous cookie. */ + memcpy(trn_cell_introduce_encrypted_getarray_rend_cookie(enc_cell), + data->rendezvous_cookie, REND_COOKIE_LEN); + + /* Set the onion public key. */ + introduce1_set_encrypted_onion_key(enc_cell, data->onion_pk->public_key); + + /* Set the link specifiers. */ + introduce1_set_encrypted_link_spec(enc_cell, data->link_specifiers); + + /* Set padding. */ + introduce1_set_encrypted_padding(cell, enc_cell); + + /* Encrypt and encode it in the cell. */ + introduce1_encrypt_and_encode(cell, enc_cell, data); + + /* Cleanup. */ + trn_cell_introduce_encrypted_free(enc_cell); +} + +/* Set the authentication key in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_auth_key(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + /* There is only one possible type for a non legacy cell. */ + trn_cell_introduce1_set_auth_key_type(cell, HS_INTRO_AUTH_KEY_TYPE_ED25519); + trn_cell_introduce1_set_auth_key_len(cell, ED25519_PUBKEY_LEN); + trn_cell_introduce1_setlen_auth_key(cell, ED25519_PUBKEY_LEN); + memcpy(trn_cell_introduce1_getarray_auth_key(cell), + data->auth_pk->pubkey, trn_cell_introduce1_getlen_auth_key(cell)); +} + +/* Set the legacy ID field in the INTRODUCE1 cell from the given data. */ +static void +introduce1_set_legacy_id(trn_cell_introduce1_t *cell, + const hs_cell_introduce1_data_t *data) +{ + tor_assert(cell); + tor_assert(data); + + if (data->is_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(data->legacy_key, (char *) digest) < 0)) { + return; + } + memcpy(trn_cell_introduce1_getarray_legacy_key_id(cell), + digest, trn_cell_introduce1_getlen_legacy_key_id(cell)); + } else { + /* We have to zeroed the LEGACY_KEY_ID field. */ + memset(trn_cell_introduce1_getarray_legacy_key_id(cell), 0, + trn_cell_introduce1_getlen_legacy_key_id(cell)); + } +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point + * object. The encoded cell is put in cell_out that MUST at least be of the + * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else + * a negative value and cell_out is untouched. This function also supports + * legacy cell creation. */ +ssize_t +hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out) +{ + ssize_t cell_len = -1; + uint16_t sig_len = ED25519_SIG_LEN; + trn_cell_extension_t *ext; + trn_cell_establish_intro_t *cell = NULL; + + tor_assert(circ_nonce); + tor_assert(ip); + + /* Quickly handle the legacy IP. */ + if (ip->base.is_only_legacy) { + tor_assert(ip->legacy_key); + cell_len = build_legacy_establish_intro(circ_nonce, ip->legacy_key, + cell_out); + tor_assert(cell_len <= RELAY_PAYLOAD_SIZE); + /* Success or not we are done here. */ + goto done; + } + + /* Set extension data. None used here. */ + ext = trn_cell_extension_new(); + trn_cell_extension_set_num(ext, 0); + cell = trn_cell_establish_intro_new(); + trn_cell_establish_intro_set_extensions(cell, ext); + /* Set signature size. Array is then allocated in the cell. We need to do + * this early so we can use trunnel API to get the signature length. */ + trn_cell_establish_intro_set_sig_len(cell, sig_len); + trn_cell_establish_intro_setlen_sig(cell, sig_len); + + /* Set AUTH_KEY_TYPE: 2 means ed25519 */ + trn_cell_establish_intro_set_auth_key_type(cell, + HS_INTRO_AUTH_KEY_TYPE_ED25519); + + /* Set AUTH_KEY and AUTH_KEY_LEN field. Must also set byte-length of + * AUTH_KEY to match */ + { + uint16_t auth_key_len = ED25519_PUBKEY_LEN; + trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); + /* We do this call _after_ setting the length because it's reallocated at + * that point only. */ + uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); + memcpy(auth_key_ptr, ip->auth_key_kp.pubkey.pubkey, auth_key_len); + } + + /* Calculate HANDSHAKE_AUTH field (MAC). */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_mac_offset = + sig_len + sizeof(cell->sig_len) + + trn_cell_establish_intro_getlen_handshake_mac(cell); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}; + uint8_t mac[TRUNNEL_SHA3_256_LEN], *handshake_ptr; + + /* We first encode the current fields we have in the cell so we can + * compute the MAC using the raw bytes. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + /* Sanity check. */ + tor_assert(tmp_cell_enc_len > tmp_cell_mac_offset); + + /* Circuit nonce is always DIGEST_LEN according to tor-spec.txt. */ + crypto_mac_sha3_256(mac, sizeof(mac), + (uint8_t *) circ_nonce, DIGEST_LEN, + tmp_cell_enc, tmp_cell_enc_len - tmp_cell_mac_offset); + handshake_ptr = trn_cell_establish_intro_getarray_handshake_mac(cell); + memcpy(handshake_ptr, mac, sizeof(mac)); + + memwipe(mac, 0, sizeof(mac)); + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Calculate the cell signature SIG. */ + { + ssize_t tmp_cell_enc_len = 0; + ssize_t tmp_cell_sig_offset = (sig_len + sizeof(cell->sig_len)); + uint8_t tmp_cell_enc[RELAY_PAYLOAD_SIZE] = {0}, *sig_ptr; + ed25519_signature_t sig; + + /* We first encode the current fields we have in the cell so we can + * compute the signature from the raw bytes of the cell. */ + tmp_cell_enc_len = trn_cell_establish_intro_encode(tmp_cell_enc, + sizeof(tmp_cell_enc), + cell); + if (BUG(tmp_cell_enc_len < 0)) { + goto done; + } + + if (ed25519_sign_prefixed(&sig, tmp_cell_enc, + tmp_cell_enc_len - tmp_cell_sig_offset, + ESTABLISH_INTRO_SIG_PREFIX, &ip->auth_key_kp)) { + log_warn(LD_BUG, "Unable to make signature for ESTABLISH_INTRO cell."); + goto done; + } + /* Copy the signature into the cell. */ + sig_ptr = trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, sig_len); + + memwipe(tmp_cell_enc, 0, sizeof(tmp_cell_enc)); + } + + /* Encode the cell. Can't be bigger than a standard cell. */ + cell_len = trn_cell_establish_intro_encode(cell_out, RELAY_PAYLOAD_SIZE, + cell); + + done: + trn_cell_establish_intro_free(cell); + return cell_len; +} + +/* Parse the INTRO_ESTABLISHED cell in the payload of size payload_len. If we + * are successful at parsing it, return the length of the parsed cell else a + * negative value on error. */ +ssize_t +hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) +{ + ssize_t ret; + trn_cell_intro_established_t *cell = NULL; + + tor_assert(payload); + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. */ + ret = trn_cell_intro_established_parse(&cell, payload, payload_len); + if (ret >= 0) { + /* On success, we do not keep the cell, we just notify the caller that it + * was successfully parsed. */ + trn_cell_intro_established_free(cell); + } + return ret; +} + +/* Parsse the INTRODUCE2 cell using data which contains everything we need to + * do so and contains the destination buffers of information we extract and + * compute from the cell. Return 0 on success else a negative value. The + * service and circ are only used for logging purposes. */ +ssize_t +hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service) +{ + int ret = -1; + time_t elapsed; + uint8_t *decrypted = NULL; + size_t encrypted_section_len; + const uint8_t *encrypted_section; + trn_cell_introduce1_t *cell = NULL; + trn_cell_introduce_encrypted_t *enc_cell = NULL; + hs_ntor_intro_cell_keys_t *intro_keys = NULL; + + tor_assert(data); + tor_assert(circ); + tor_assert(service); + + /* Parse the cell into a decoded data structure pointed by cell_ptr. */ + if (parse_introduce2_cell(service, circ, data->payload, data->payload_len, + &cell) < 0) { + goto done; + } + + log_info(LD_REND, "Received a decodable INTRODUCE2 cell on circuit %u " + "for service %s. Decoding encrypted section...", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + + encrypted_section = trn_cell_introduce1_getconstarray_encrypted(cell); + encrypted_section_len = trn_cell_introduce1_getlen_encrypted(cell); + + /* Encrypted section must at least contain the CLIENT_PK and MAC which is + * defined in section 3.3.2 of the specification. */ + if (encrypted_section_len < (CURVE25519_PUBKEY_LEN + DIGEST256_LEN)) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted section length " + "for service %s. Dropping cell.", + safe_str_client(service->onion_address)); + goto done; + } + + /* Check our replay cache for this introduction point. */ + if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section, + encrypted_section_len, &elapsed)) { + log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the" + "same ENCRYPTED section was seen %ld seconds ago. " + "Dropping cell.", (long int) elapsed); + goto done; + } + + /* Build the key material out of the key material found in the cell. */ + intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, + data->subcredential, + encrypted_section, + &data->client_pk); + if (intro_keys == NULL) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to " + "compute key material on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Validate MAC from the cell and our computed key material. The MAC field + * in the cell is at the end of the encrypted section. */ + { + uint8_t mac[DIGEST256_LEN]; + /* The MAC field is at the very end of the ENCRYPTED section. */ + size_t mac_offset = encrypted_section_len - sizeof(mac); + /* Compute the MAC. Use the entire encoded payload with a length up to the + * ENCRYPTED section. */ + compute_introduce_mac(data->payload, + data->payload_len - encrypted_section_len, + encrypted_section, encrypted_section_len, + intro_keys->mac_key, sizeof(intro_keys->mac_key), + mac, sizeof(mac)); + if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) { + log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + } + + { + /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */ + const uint8_t *encrypted_data = + encrypted_section + sizeof(data->client_pk); + /* It's symmetric encryption so it's correct to use the ENCRYPTED length + * for decryption. Computes the length of ENCRYPTED_DATA meaning removing + * the CLIENT_PK and MAC length. */ + size_t encrypted_data_len = + encrypted_section_len - (sizeof(data->client_pk) + DIGEST256_LEN); + + /* This decrypts the ENCRYPTED_DATA section of the cell. */ + decrypted = decrypt_introduce2(intro_keys->enc_key, + encrypted_data, encrypted_data_len); + if (decrypted == NULL) { + log_info(LD_REND, "Unable to decrypt the ENCRYPTED section of an " + "INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Parse this blob into an encrypted cell structure so we can then extract + * the data we need out of it. */ + enc_cell = parse_introduce2_encrypted(decrypted, encrypted_data_len, + circ, service); + memwipe(decrypted, 0, encrypted_data_len); + if (enc_cell == NULL) { + goto done; + } + } + + /* XXX: Implement client authorization checks. */ + + /* Extract onion key and rendezvous cookie from the cell used for the + * rendezvous point circuit e2e encryption. */ + memcpy(data->onion_pk.public_key, + trn_cell_introduce_encrypted_getconstarray_onion_key(enc_cell), + CURVE25519_PUBKEY_LEN); + memcpy(data->rendezvous_cookie, + trn_cell_introduce_encrypted_getconstarray_rend_cookie(enc_cell), + sizeof(data->rendezvous_cookie)); + + /* Extract rendezvous link specifiers. */ + for (size_t idx = 0; + idx < trn_cell_introduce_encrypted_get_nspec(enc_cell); idx++) { + link_specifier_t *lspec = + trn_cell_introduce_encrypted_get_nspecs(enc_cell, idx); + smartlist_add(data->link_specifiers, hs_link_specifier_dup(lspec)); + } + + /* Success. */ + ret = 0; + log_info(LD_REND, "Valid INTRODUCE2 cell. Launching rendezvous circuit."); + + done: + if (intro_keys) { + memwipe(intro_keys, 0, sizeof(hs_ntor_intro_cell_keys_t)); + tor_free(intro_keys); + } + tor_free(decrypted); + trn_cell_introduce_encrypted_free(enc_cell); + trn_cell_introduce1_free(cell); + return ret; +} + +/* Build a RENDEZVOUS1 cell with the given rendezvous cookie and handshake + * info. The encoded cell is put in cell_out and the length of the data is + * returned. This can't fail. */ +ssize_t +hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_rendezvous1_t *cell; + + tor_assert(rendezvous_cookie); + tor_assert(rendezvous_handshake_info); + tor_assert(cell_out); + + cell = trn_cell_rendezvous1_new(); + /* Set the RENDEZVOUS_COOKIE. */ + memcpy(trn_cell_rendezvous1_getarray_rendezvous_cookie(cell), + rendezvous_cookie, rendezvous_cookie_len); + /* Set the HANDSHAKE_INFO. */ + trn_cell_rendezvous1_setlen_handshake_info(cell, + rendezvous_handshake_info_len); + memcpy(trn_cell_rendezvous1_getarray_handshake_info(cell), + rendezvous_handshake_info, rendezvous_handshake_info_len); + /* Encoding. */ + cell_len = trn_cell_rendezvous1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + tor_assert(cell_len > 0); + + trn_cell_rendezvous1_free(cell); + return cell_len; +} + +/* Build an INTRODUCE1 cell from the given data. The encoded cell is put in + * cell_out which must be of at least size RELAY_PAYLOAD_SIZE. On success, the + * encoded length is returned else a negative value and the content of + * cell_out should be ignored. */ +ssize_t +hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out) +{ + ssize_t cell_len; + trn_cell_introduce1_t *cell; + trn_cell_extension_t *ext; + + tor_assert(data); + tor_assert(cell_out); + + cell = trn_cell_introduce1_new(); + tor_assert(cell); + + /* Set extension data. None are used. */ + ext = trn_cell_extension_new(); + tor_assert(ext); + trn_cell_extension_set_num(ext, 0); + trn_cell_introduce1_set_extensions(cell, ext); + + /* Set the legacy ID field. */ + introduce1_set_legacy_id(cell, data); + + /* Set the authentication key. */ + introduce1_set_auth_key(cell, data); + + /* Set the encrypted section. This will set, encrypt and encode the + * ENCRYPTED section in the cell. After this, we'll be ready to encode. */ + introduce1_set_encrypted(cell, data); + + /* Final encoding. */ + cell_len = trn_cell_introduce1_encode(cell_out, RELAY_PAYLOAD_SIZE, cell); + + trn_cell_introduce1_free(cell); + return cell_len; +} + +/* Build an ESTABLISH_RENDEZVOUS cell from the given rendezvous_cookie. The + * encoded cell is put in cell_out which must be of at least + * RELAY_PAYLOAD_SIZE. On success, the encoded length is returned and the + * caller should clear up the content of the cell. + * + * This function can't fail. */ +ssize_t +hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out) +{ + tor_assert(rendezvous_cookie); + tor_assert(cell_out); + + memcpy(cell_out, rendezvous_cookie, HS_REND_COOKIE_LEN); + return HS_REND_COOKIE_LEN; +} + +/* Handle an INTRODUCE_ACK cell encoded in payload of length payload_len. + * Return the status code on success else a negative value if the cell as not + * decodable. */ +int +hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + trn_cell_introduce_ack_t *cell = NULL; + + tor_assert(payload); + + /* If it is a legacy IP, rend-spec.txt specifies that a ACK is 0 byte and a + * NACK is 1 byte. We can't use the legacy function for this so we have to + * do a special case. */ + if (payload_len <= 1) { + if (payload_len == 0) { + ret = HS_CELL_INTRO_ACK_SUCCESS; + } else { + ret = HS_CELL_INTRO_ACK_FAILURE; + } + goto end; + } + + if (trn_cell_introduce_ack_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid INTRODUCE_ACK cell. Unable to parse it."); + goto end; + } + + ret = trn_cell_introduce_ack_get_status(cell); + + end: + trn_cell_introduce_ack_free(cell); + return ret; +} + +/* Handle a RENDEZVOUS2 cell encoded in payload of length payload_len. On + * success, handshake_info contains the data in the HANDSHAKE_INFO field, and + * 0 is returned. On error, a negative value is returned. */ +int +hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, size_t handshake_info_len) +{ + int ret = -1; + trn_cell_rendezvous2_t *cell = NULL; + + tor_assert(payload); + tor_assert(handshake_info); + + if (trn_cell_rendezvous2_parse(&cell, payload, payload_len) < 0) { + log_info(LD_REND, "Invalid RENDEZVOUS2 cell. Unable to parse it."); + goto end; + } + + /* Static size, we should never have an issue with this else we messed up + * our code flow. */ + tor_assert(trn_cell_rendezvous2_getlen_handshake_info(cell) == + handshake_info_len); + memcpy(handshake_info, + trn_cell_rendezvous2_getconstarray_handshake_info(cell), + handshake_info_len); + ret = 0; + + end: + trn_cell_rendezvous2_free(cell); + return ret; +} + +/* Clear the given INTRODUCE1 data structure data. */ +void +hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data) +{ + if (data == NULL) { + return; + } + /* Object in this list have been moved to the cell object when building it + * so they've been freed earlier. We do that in order to avoid duplicating + * them leading to more memory and CPU time being used for nothing. */ + smartlist_free(data->link_specifiers); + /* The data object has no ownership of any members. */ + memwipe(data, 0, sizeof(hs_cell_introduce1_data_t)); +} + diff --git a/src/or/hs_cell.h b/src/or/hs_cell.h new file mode 100644 index 0000000000..958dde4ffc --- /dev/null +++ b/src/or/hs_cell.h @@ -0,0 +1,122 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cell.h + * \brief Header file containing cell data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CELL_H +#define TOR_HS_CELL_H + +#include "or.h" +#include "hs_service.h" + +/* An INTRODUCE1 cell requires at least this amount of bytes (see section + * 3.2.2 of the specification). Below this value, the cell must be padded. */ +#define HS_CELL_INTRODUCE1_MIN_SIZE 246 + +/* Status code of an INTRODUCE_ACK cell. */ +typedef enum { + HS_CELL_INTRO_ACK_SUCCESS = 0x0000, /* Cell relayed to service. */ + HS_CELL_INTRO_ACK_FAILURE = 0x0001, /* Service ID not recognized */ + HS_CELL_INTRO_ACK_BADFMT = 0x0002, /* Bad message format */ + HS_CELL_INTRO_ACK_NORELAY = 0x0003, /* Can't relay cell to service */ +} hs_cell_introd_ack_status_t; + +/* Onion key type found in the INTRODUCE1 cell. */ +typedef enum { + HS_CELL_ONION_KEY_TYPE_NTOR = 1, +} hs_cell_onion_key_type_t; + +/* This data structure contains data that we need to build an INTRODUCE1 cell + * used by the INTRODUCE1 build function. */ +typedef struct hs_cell_introduce1_data_t { + /* Is this a legacy introduction point? */ + unsigned int is_legacy : 1; + /* (Legacy only) The encryption key for a legacy intro point. Only set if + * is_legacy is true. */ + const crypto_pk_t *legacy_key; + /* Introduction point authentication public key. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption public key. */ + const curve25519_public_key_t *enc_pk; + /* Subcredentials of the service. */ + const uint8_t *subcredential; + /* Onion public key for the ntor handshake. */ + const curve25519_public_key_t *onion_pk; + /* Rendezvous cookie. */ + const uint8_t *rendezvous_cookie; + /* Public key put before the encrypted data (CLIENT_PK). */ + const curve25519_keypair_t *client_kp; + /* Rendezvous point link specifiers. */ + smartlist_t *link_specifiers; +} hs_cell_introduce1_data_t; + +/* This data structure contains data that we need to parse an INTRODUCE2 cell + * which is used by the INTRODUCE2 cell parsing function. On a successful + * parsing, the onion_pk and rendezvous_cookie will be populated with the + * computed key material from the cell data. This structure is only used during + * INTRO2 parsing and discarded after that. */ +typedef struct hs_cell_introduce2_data_t { + /*** Immutable Section: Set on structure init. ***/ + + /* Introduction point authentication public key. Pointer owned by the + introduction point object through which we received the INTRO2 cell. */ + const ed25519_public_key_t *auth_pk; + /* Introduction point encryption keypair for the ntor handshake. Pointer + owned by the introduction point object through which we received the + INTRO2 cell*/ + const curve25519_keypair_t *enc_kp; + /* Subcredentials of the service. Pointer owned by the descriptor that owns + the introduction point through which we received the INTRO2 cell. */ + const uint8_t *subcredential; + /* Payload of the received encoded cell. */ + const uint8_t *payload; + /* Size of the payload of the received encoded cell. */ + size_t payload_len; + + /*** Mutable Section: Set upon parsing INTRODUCE2 cell. ***/ + + /* Onion public key computed using the INTRODUCE2 encrypted section. */ + curve25519_public_key_t onion_pk; + /* Rendezvous cookie taken from the INTRODUCE2 encrypted section. */ + uint8_t rendezvous_cookie[REND_COOKIE_LEN]; + /* Client public key from the INTRODUCE2 encrypted section. */ + curve25519_public_key_t client_pk; + /* Link specifiers of the rendezvous point. Contains link_specifier_t. */ + smartlist_t *link_specifiers; + /* Replay cache of the introduction point. */ + replaycache_t *replay_cache; +} hs_cell_introduce2_data_t; + +/* Build cell API. */ +ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_intro_point_t *ip, + uint8_t *cell_out); +ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, + size_t rendezvous_cookie_len, + const uint8_t *rendezvous_handshake_info, + size_t rendezvous_handshake_info_len, + uint8_t *cell_out); +ssize_t hs_cell_build_introduce1(const hs_cell_introduce1_data_t *data, + uint8_t *cell_out); +ssize_t hs_cell_build_establish_rendezvous(const uint8_t *rendezvous_cookie, + uint8_t *cell_out); + +/* Parse cell API. */ +ssize_t hs_cell_parse_intro_established(const uint8_t *payload, + size_t payload_len); +ssize_t hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, + const origin_circuit_t *circ, + const hs_service_t *service); +int hs_cell_parse_introduce_ack(const uint8_t *payload, size_t payload_len); +int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, + uint8_t *handshake_info, + size_t handshake_info_len); + +/* Util API. */ +void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); + +#endif /* !defined(TOR_HS_CELL_H) */ + diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c new file mode 100644 index 0000000000..a58166ccde --- /dev/null +++ b/src/or/hs_circuit.c @@ -0,0 +1,1208 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.c + **/ + +#define HS_CIRCUIT_PRIVATE + +#include "or.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" +#include "policies.h" +#include "relay.h" +#include "rendservice.h" +#include "rephist.h" +#include "router.h" + +#include "hs_cell.h" +#include "hs_ident.h" +#include "hs_ntor.h" +#include "hs_service.h" +#include "hs_circuit.h" + +/* Trunnel. */ +#include "ed25519_cert.h" +#include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* A circuit is about to become an e2e rendezvous circuit. Check + * <b>circ_purpose</b> and ensure that it's properly set. Return true iff + * circuit purpose is properly set, otherwise return false. */ +static int +circuit_purpose_is_correct_for_rend(unsigned int circ_purpose, + int is_service_side) +{ + if (is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_S_CONNECT_REND) { + log_warn(LD_BUG, + "HS e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + if (!is_service_side) { + if (circ_purpose != CIRCUIT_PURPOSE_C_REND_READY && + circ_purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_BUG, + "Client e2e circuit setup with wrong purpose (%d)", circ_purpose); + return 0; + } + } + + return 1; +} + +/* Create and return a crypt path for the final hop of a v3 prop224 rendezvous + * circuit. Initialize the crypt path crypto using the output material from the + * ntor key exchange at <b>ntor_key_seed</b>. + * + * If <b>is_service_side</b> is set, we are the hidden service and the final + * hop of the rendezvous circuit is the client on the other side. */ +static crypt_path_t * +create_rend_cpath(const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + uint8_t keys[HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN]; + crypt_path_t *cpath = NULL; + + /* Do the key expansion */ + if (hs_ntor_circuit_key_expansion(ntor_key_seed, seed_len, + keys, sizeof(keys)) < 0) { + goto err; + } + + /* Setup the cpath */ + cpath = tor_malloc_zero(sizeof(crypt_path_t)); + cpath->magic = CRYPT_PATH_MAGIC; + + if (circuit_init_cpath_crypto(cpath, (char*)keys, sizeof(keys), + is_service_side, 1) < 0) { + tor_free(cpath); + goto err; + } + + err: + memwipe(keys, 0, sizeof(keys)); + return cpath; +} + +/* We are a v2 legacy HS client: Create and return a crypt path for the hidden + * service on the other side of the rendezvous circuit <b>circ</b>. Initialize + * the crypt path crypto using the body of the RENDEZVOUS1 cell at + * <b>rend_cell_body</b> (which must be at least DH_KEY_LEN+DIGEST_LEN bytes). + */ +static crypt_path_t * +create_rend_cpath_legacy(origin_circuit_t *circ, const uint8_t *rend_cell_body) +{ + crypt_path_t *hop = NULL; + char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; + + /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh + * handshake...*/ + tor_assert(circ->build_state); + tor_assert(circ->build_state->pending_final_cpath); + hop = circ->build_state->pending_final_cpath; + + tor_assert(hop->rend_dh_handshake_state); + if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, hop->rend_dh_handshake_state, + (char*)rend_cell_body, DH_KEY_LEN, + keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { + log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + goto err; + } + /* ... and set up cpath. */ + if (circuit_init_cpath_crypto(hop, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 0, 0) < 0) + goto err; + + /* Check whether the digest is right... */ + if (tor_memneq(keys, rend_cell_body+DH_KEY_LEN, DIGEST_LEN)) { + log_warn(LD_PROTOCOL, "Incorrect digest of key material."); + goto err; + } + + /* clean up the crypto stuff we just made */ + crypto_dh_free(hop->rend_dh_handshake_state); + hop->rend_dh_handshake_state = NULL; + + goto done; + + err: + hop = NULL; + + done: + memwipe(keys, 0, sizeof(keys)); + return hop; +} + +/* Append the final <b>hop</b> to the cpath of the rend <b>circ</b>, and mark + * <b>circ</b> ready for use to transfer HS relay cells. */ +static void +finalize_rend_circuit(origin_circuit_t *circ, crypt_path_t *hop, + int is_service_side) +{ + tor_assert(circ); + tor_assert(hop); + + /* Notify the circuit state machine that we are splicing this circuit */ + int new_circ_purpose = is_service_side ? + CIRCUIT_PURPOSE_S_REND_JOINED : CIRCUIT_PURPOSE_C_REND_JOINED; + circuit_change_purpose(TO_CIRCUIT(circ), new_circ_purpose); + + /* All is well. Extend the circuit. */ + hop->state = CPATH_STATE_OPEN; + /* Set the windows to default. */ + hop->package_window = circuit_initial_package_window(); + hop->deliver_window = CIRCWINDOW_START; + + /* Now that this circuit has finished connecting to its destination, + * make sure circuit_get_open_circ_or_launch is willing to return it + * so we can actually use it. */ + circ->hs_circ_has_timed_out = 0; + + /* Append the hop to the cpath of this circuit */ + onion_append_to_cpath(&circ->cpath, hop); + + /* In legacy code, 'pending_final_cpath' points to the final hop we just + * appended to the cpath. We set the original pointer to NULL so that we + * don't double free it. */ + if (circ->build_state) { + circ->build_state->pending_final_cpath = NULL; + } + + /* Finally, mark circuit as ready to be used for client streams */ + if (!is_service_side) { + circuit_try_attaching_streams(circ); + } +} + +/* For a given circuit and a service introduction point object, register the + * intro circuit to the circuitmap. This supports legacy intro point. */ +static void +register_intro_circ(const hs_service_intro_point_t *ip, + origin_circuit_t *circ) +{ + tor_assert(ip); + tor_assert(circ); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + return; + } + hs_circuitmap_register_intro_circ_v2_service_side(circ, digest); + } else { + hs_circuitmap_register_intro_circ_v3_service_side(circ, + &ip->auth_key_kp.pubkey); + } +} + +/* Return the number of opened introduction circuit for the given circuit that + * is matching its identity key. */ +static unsigned int +count_opened_desc_intro_point_circuits(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(service); + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + const circuit_t *circ; + const origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc == NULL) { + continue; + } + circ = TO_CIRCUIT(ocirc); + tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO); + /* Having a circuit not for the requested service is really bad. */ + tor_assert(ed25519_pubkey_eq(&service->keys.identity_pk, + ô->hs_ident->identity_pk)); + /* Only count opened circuit and skip circuit that will be closed. */ + if (!circ->marked_for_close && circ->state == CIRCUIT_STATE_OPEN) { + count++; + } + } DIGEST256MAP_FOREACH_END; + return count; +} + +/* From a given service, rendezvous cookie and handshake info, create a + * rendezvous point circuit identifier. This can't fail. */ +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys) +{ + hs_ident_circuit_t *ident; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + tor_assert(service); + tor_assert(rendezvous_cookie); + tor_assert(server_pk); + tor_assert(keys); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + /* Copy the RENDEZVOUS_COOKIE which is the unique identifier. */ + memcpy(ident->rendezvous_cookie, rendezvous_cookie, + sizeof(ident->rendezvous_cookie)); + /* Build the HANDSHAKE_INFO which looks like this: + * SERVER_PK [32 bytes] + * AUTH_INPUT_MAC [32 bytes] + */ + memcpy(handshake_info, server_pk->public_key, CURVE25519_PUBKEY_LEN); + memcpy(handshake_info + CURVE25519_PUBKEY_LEN, keys->rend_cell_auth_mac, + DIGEST256_LEN); + tor_assert(sizeof(ident->rendezvous_handshake_info) == + sizeof(handshake_info)); + memcpy(ident->rendezvous_handshake_info, handshake_info, + sizeof(ident->rendezvous_handshake_info)); + /* Finally copy the NTOR_KEY_SEED for e2e encryption on the circuit. */ + tor_assert(sizeof(ident->rendezvous_ntor_key_seed) == + sizeof(keys->ntor_key_seed)); + memcpy(ident->rendezvous_ntor_key_seed, keys->ntor_key_seed, + sizeof(ident->rendezvous_ntor_key_seed)); + return ident; +} + +/* From a given service and service intro point, create an introduction point + * circuit identifier. This can't fail. */ +static hs_ident_circuit_t * +create_intro_circuit_identifier(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_ident_circuit_t *ident; + + tor_assert(service); + tor_assert(ip); + + ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + ed25519_pubkey_copy(&ident->intro_auth_pk, &ip->auth_key_kp.pubkey); + + return ident; +} + +/* For a given introduction point and an introduction circuit, send the + * ESTABLISH_INTRO cell. The service object is used for logging. This can fail + * and if so, the circuit is closed and the intro point object is flagged + * that the circuit is not established anymore which is important for the + * retry mechanism. */ +static void +send_establish_intro(const hs_service_t *service, + hs_service_intro_point_t *ip, origin_circuit_t *circ) +{ + ssize_t cell_len; + uint8_t payload[RELAY_PAYLOAD_SIZE]; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + + /* Encode establish intro cell. */ + cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, + ip, payload); + if (cell_len < 0) { + log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " + "on circuit %u. Closing circuit.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + + /* Send the cell on the circuit. */ + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_INTRO, + (char *) payload, cell_len, + circ->cpath->prev) < 0) { + log_info(LD_REND, "Unable to send ESTABLISH_INTRO cell for service %s " + "on circuit %u.", + safe_str_client(service->onion_address), + TO_CIRCUIT(circ)->n_circ_id); + /* On error, the circuit has been closed. */ + goto done; + } + + /* Record the attempt to use this circuit. */ + pathbias_count_use_attempt(circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Return a string constant describing the anonymity of service. */ +static const char * +get_service_anonymity_string(const hs_service_t *service) +{ + if (service->config.is_single_onion) { + return "single onion"; + } else { + return "hidden"; + } +} + +/* For a given service, the ntor onion key and a rendezvous cookie, launch a + * circuit to the rendezvous point specified by the link specifiers. On + * success, a circuit identifier is attached to the circuit with the needed + * data. This function will try to open a circuit for a maximum value of + * MAX_REND_FAILURES then it will give up. */ +static void +launch_rendezvous_point_circuit(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const hs_cell_introduce2_data_t *data) +{ + int circ_needs_uptime; + time_t now = time(NULL); + extend_info_t *info = NULL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(data); + + circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); + + /* Get the extend info data structure for the chosen rendezvous point + * specified by the given link specifiers. */ + info = hs_get_extend_info_from_lspecs(data->link_specifiers, + &data->onion_pk, + service->config.is_single_onion); + if (info == NULL) { + /* We are done here, we can't extend to the rendezvous point. + * If you're running an IPv6-only v3 single onion service on 0.3.2 or with + * 0.3.2 clients, and somehow disable the option check, it will fail here. + */ + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Not enough info to open a circuit to a rendezvous point for " + "%s service %s.", + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + goto end; + } + + for (int i = 0; i < MAX_REND_FAILURES; i++) { + int circ_flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL; + if (circ_needs_uptime) { + circ_flags |= CIRCLAUNCH_NEED_UPTIME; + } + /* Firewall and policies are checked when getting the extend info. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, info, + circ_flags); + if (circ != NULL) { + /* Stop retrying, we have a circuit! */ + break; + } + } + if (circ == NULL) { + log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s " + "for %s service %s", + safe_str_client(extend_info_describe(info)), + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + goto end; + } + log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s " + "for %s service %s", + safe_str_client(extend_info_describe(info)), + safe_str_client(hex_str((const char *) data->rendezvous_cookie, + REND_COOKIE_LEN)), + get_service_anonymity_string(service), + safe_str_client(service->onion_address)); + tor_assert(circ->build_state); + /* Rendezvous circuit have a specific timeout for the time spent on trying + * to connect to the rendezvous point. */ + circ->build_state->expiry_time = now + MAX_REND_TIMEOUT; + + /* Create circuit identifier and key material. */ + { + hs_ntor_rend_cell_keys_t keys; + curve25519_keypair_t ephemeral_kp; + /* No need for extra strong, this is only for this circuit life time. This + * key will be used for the RENDEZVOUS1 cell that will be sent on the + * circuit once opened. */ + curve25519_keypair_generate(&ephemeral_kp, 0); + if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, + &ip->enc_key_kp, + &ephemeral_kp, &data->client_pk, + &keys) < 0) { + /* This should not really happened but just in case, don't make tor + * freak out, close the circuit and move on. */ + log_info(LD_REND, "Unable to get RENDEZVOUS1 key material for " + "service %s", + safe_str_client(service->onion_address)); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + circ->hs_ident = create_rp_circuit_identifier(service, + data->rendezvous_cookie, + &ephemeral_kp.pubkey, &keys); + memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); + memwipe(&keys, 0, sizeof(keys)); + tor_assert(circ->hs_ident); + } + + end: + extend_info_free(info); +} + +/* Return true iff the given service rendezvous circuit circ is allowed for a + * relaunch to the rendezvous point. */ +static int +can_relaunch_service_rendezvous_point(const origin_circuit_t *circ) +{ + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* XXX: Retrying under certain condition. This is related to #22455. */ + + /* Avoid to relaunch twice a circuit to the same rendezvous point at the + * same time. */ + if (circ->hs_service_side_rend_circ_has_been_relaunched) { + log_info(LD_REND, "Rendezvous circuit to %s has already been retried. " + "Skipping retry.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit))); + goto disallow; + } + + /* A failure count that has reached maximum allowed or circuit that expired, + * we skip relaunching. */ + if (circ->build_state->failure_count > MAX_REND_FAILURES || + circ->build_state->expiry_time <= time(NULL)) { + log_info(LD_REND, "Attempt to build a rendezvous circuit to %s has " + "failed with %d attempts and expiry time %ld. " + "Giving up building.", + safe_str_client( + extend_info_describe(circ->build_state->chosen_exit)), + circ->build_state->failure_count, + (long int) circ->build_state->expiry_time); + goto disallow; + } + + /* Allowed to relaunch. */ + return 1; + disallow: + return 0; +} + +/* Retry the rendezvous point of circ by launching a new circuit to it. */ +static void +retry_service_rendezvous_point(const origin_circuit_t *circ) +{ + int flags = 0; + origin_circuit_t *new_circ; + cpath_build_state_t *bstate; + + tor_assert(circ); + /* This is initialized when allocating an origin circuit. */ + tor_assert(circ->build_state); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Ease our life. */ + bstate = circ->build_state; + + log_info(LD_REND, "Retrying rendezvous point circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + + /* Get the current build state flags for the next circuit. */ + flags |= (bstate->need_uptime) ? CIRCLAUNCH_NEED_UPTIME : 0; + flags |= (bstate->need_capacity) ? CIRCLAUNCH_NEED_CAPACITY : 0; + flags |= (bstate->is_internal) ? CIRCLAUNCH_IS_INTERNAL : 0; + + /* We do NOT add the onehop tunnel flag even though it might be a single + * onion service. The reason is that if we failed once to connect to the RP + * with a direct connection, we consider that chances are that we will fail + * again so try a 3-hop circuit and hope for the best. Because the service + * has no anonymity (single onion), this change of behavior won't affect + * security directly. */ + + new_circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_CONNECT_REND, + bstate->chosen_exit, flags); + if (new_circ == NULL) { + log_warn(LD_REND, "Failed to launch rendezvous circuit to %s", + safe_str_client(extend_info_describe(bstate->chosen_exit))); + goto done; + } + + /* Transfer build state information to the new circuit state in part to + * catch any other failures. */ + new_circ->build_state->failure_count = bstate->failure_count++; + new_circ->build_state->expiry_time = bstate->expiry_time; + new_circ->hs_ident = hs_ident_circuit_dup(circ->hs_ident); + + done: + return; +} + +/* Using an extend info object ei, set all possible link specifiers in lspecs. + * legacy ID is mandatory thus MUST be present in ei. If IPv4 is not present, + * logs a BUG() warning, and returns an empty smartlist. Clients never make + * direct connections to rendezvous points, so they should always have an + * IPv4 address in ei. */ +static void +get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs) +{ + link_specifier_t *ls; + + tor_assert(ei); + tor_assert(lspecs); + + /* We require IPv4, we will add IPv6 support in a later tor version */ + if (BUG(!tor_addr_is_v4(&ei->addr))) { + return; + } + + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_IPV4); + link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr)); + link_specifier_set_un_ipv4_port(ls, ei->port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(ei->addr.addr.in_addr) + + sizeof(ei->port)); + smartlist_add(lspecs, ls); + + /* Legacy ID is mandatory. */ + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_LEGACY_ID); + memcpy(link_specifier_getarray_un_legacy_id(ls), ei->identity_digest, + link_specifier_getlen_un_legacy_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls)); + smartlist_add(lspecs, ls); + + /* ed25519 ID is only included if the extend_info has it. */ + if (!ed25519_public_key_is_zero(&ei->ed_identity)) { + ls = link_specifier_new(); + link_specifier_set_ls_type(ls, LS_ED25519_ID); + memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity, + link_specifier_getlen_un_ed25519_id(ls)); + link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls)); + smartlist_add(lspecs, ls); + } +} + +/* Using the given descriptor intro point ip, the extend information of the + * rendezvous point rp_ei and the service's subcredential, populate the + * already allocated intro1_data object with the needed key material and link + * specifiers. + * + * This can't fail but the ip MUST be a valid object containing the needed + * keys and authentication method. */ +static void +setup_introduce1_data(const hs_desc_intro_point_t *ip, + const extend_info_t *rp_ei, + const uint8_t *subcredential, + hs_cell_introduce1_data_t *intro1_data) +{ + smartlist_t *rp_lspecs; + + tor_assert(ip); + tor_assert(rp_ei); + tor_assert(subcredential); + tor_assert(intro1_data); + + /* Build the link specifiers from the extend information of the rendezvous + * circuit that we've picked previously. */ + rp_lspecs = smartlist_new(); + get_lspecs_from_extend_info(rp_ei, rp_lspecs); + + /* Populate the introduce1 data object. */ + memset(intro1_data, 0, sizeof(hs_cell_introduce1_data_t)); + if (ip->legacy.key != NULL) { + intro1_data->is_legacy = 1; + intro1_data->legacy_key = ip->legacy.key; + } + intro1_data->auth_pk = &ip->auth_key_cert->signed_key; + intro1_data->enc_pk = &ip->enc_key; + intro1_data->subcredential = subcredential; + intro1_data->onion_pk = &rp_ei->curve25519_onion_key; + intro1_data->link_specifiers = rp_lspecs; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return an introduction point circuit matching the given intro point object. + * NULL is returned is no such circuit can be found. */ +origin_circuit_t * +hs_circ_service_get_intro_circ(const hs_service_intro_point_t *ip) +{ + origin_circuit_t *circ = NULL; + + tor_assert(ip); + + if (ip->base.is_only_legacy) { + uint8_t digest[DIGEST_LEN]; + if (BUG(crypto_pk_get_digest(ip->legacy_key, (char *) digest) < 0)) { + goto end; + } + circ = hs_circuitmap_get_intro_circ_v2_service_side(digest); + } else { + circ = hs_circuitmap_get_intro_circ_v3_service_side( + &ip->auth_key_kp.pubkey); + } + end: + return circ; +} + +/* Called when we fail building a rendezvous circuit at some point other than + * the last hop: launches a new circuit to the same rendezvous point. This + * supports legacy service. + * + * We currently relaunch connections to rendezvous points if: + * - A rendezvous circuit timed out before connecting to RP. + * - The redenzvous circuit failed to connect to the RP. + * + * We avoid relaunching a connection to this rendezvous point if: + * - We have already tried MAX_REND_FAILURES times to connect to this RP. + * - We've been trying to connect to this RP for more than MAX_REND_TIMEOUT + * seconds + * - We've already retried this specific rendezvous circuit. + */ +void +hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Check if we are allowed to relaunch to the rendezvous point of circ. */ + if (!can_relaunch_service_rendezvous_point(circ)) { + goto done; + } + + /* Flag the circuit that we are relaunching so to avoid to relaunch twice a + * circuit to the same rendezvous point at the same time. */ + circ->hs_service_side_rend_circ_has_been_relaunched = 1; + + /* Legacy service don't have an hidden service ident. */ + if (circ->hs_ident) { + retry_service_rendezvous_point(circ); + } else { + rend_service_relaunch_rendezvous(circ); + } + + done: + return; +} + +/* For a given service and a service intro point, launch a circuit to the + * extend info ei. If the service is a single onion, a one-hop circuit will be + * requested. Return 0 if the circuit was successfully launched and tagged + * with the correct identifier. On error, a negative value is returned. */ +int +hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei) +{ + /* Standard flags for introduction circuit. */ + int ret = -1, circ_flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(ei); + + /* Update circuit flags in case of a single onion service that requires a + * direct connection. */ + if (service->config.is_single_onion) { + circ_flags |= CIRCLAUNCH_ONEHOP_TUNNEL; + } + + log_info(LD_REND, "Launching a circuit to intro point %s for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + + /* Note down the launch for the retry period. Even if the circuit fails to + * be launched, we still want to respect the retry period to avoid stress on + * the circuit subsystem. */ + service->state.num_intro_circ_launched++; + circ = circuit_launch_by_extend_info(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + ei, circ_flags); + if (circ == NULL) { + goto end; + } + + /* Setup the circuit identifier and attach it to it. */ + circ->hs_ident = create_intro_circuit_identifier(service, ip); + tor_assert(circ->hs_ident); + /* Register circuit in the global circuitmap. */ + register_intro_circ(ip, circ); + + /* Success. */ + ret = 0; + end: + return ret; +} + +/* Called when a service introduction point circuit is done building. Given + * the service and intro point object, this function will send the + * ESTABLISH_INTRO cell on the circuit. Return 0 on success. Return 1 if the + * circuit has been repurposed to General because we already have too many + * opened. */ +int +hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ) +{ + int ret = 0; + unsigned int num_intro_circ, num_needed_circ; + + tor_assert(service); + tor_assert(ip); + tor_assert(desc); + tor_assert(circ); + + /* Cound opened circuits that have sent ESTABLISH_INTRO cells or are already + * established introduction circuits */ + num_intro_circ = count_opened_desc_intro_point_circuits(service, desc); + num_needed_circ = service->config.num_intro_points; + if (num_intro_circ > num_needed_circ) { + /* There are too many opened valid intro circuit for what the service + * needs so repurpose this one. */ + + /* XXX: Legacy code checks options->ExcludeNodes and if not NULL it just + * closes the circuit. I have NO idea why it does that so it hasn't been + * added here. I can only assume in case our ExcludeNodes list changes but + * in that case, all circuit are flagged unusable (config.c). --dgoulet */ + + log_info(LD_CIRC | LD_REND, "Introduction circuit just opened but we " + "have enough for service %s. Repurposing " + "it to general and leaving internal.", + safe_str_client(service->onion_address)); + tor_assert(circ->build_state->is_internal); + /* Remove it from the circuitmap. */ + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + /* Cleaning up the hidden service identifier and repurpose. */ + hs_ident_circuit_free(circ->hs_ident); + circ->hs_ident = NULL; + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_GENERAL); + /* Inform that this circuit just opened for this new purpose. */ + circuit_has_opened(circ); + /* This return value indicate to the caller that the IP object should be + * removed from the service because it's corresponding circuit has just + * been repurposed. */ + ret = 1; + goto done; + } + + log_info(LD_REND, "Introduction circuit %u established for service %s.", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* Time to send an ESTABLISH_INTRO cell on this circuit. On error, this call + * makes sure the circuit gets closed. */ + send_establish_intro(service, ip, circ); + + done: + return ret; +} + +/* Called when a service rendezvous point circuit is done building. Given the + * service and the circuit, this function will send a RENDEZVOUS1 cell on the + * circuit using the information in the circuit identifier. If the cell can't + * be sent, the circuit is closed. */ +void +hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ) +{ + size_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(service); + tor_assert(circ); + tor_assert(circ->hs_ident); + + /* Some useful logging. */ + log_info(LD_REND, "Rendezvous circuit %u has opened with cookie %s " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN), + safe_str_client(service->onion_address)); + circuit_log_path(LOG_INFO, LD_REND, circ); + + /* This can't fail. */ + payload_len = hs_cell_build_rendezvous1( + circ->hs_ident->rendezvous_cookie, + sizeof(circ->hs_ident->rendezvous_cookie), + circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info), + payload); + + /* Pad the payload with random bytes so it matches the size of a legacy cell + * which is normally always bigger. Also, the size of a legacy cell is + * always smaller than the RELAY_PAYLOAD_SIZE so this is safe. */ + if (payload_len < HS_LEGACY_RENDEZVOUS_CELL_SIZE) { + crypto_rand((char *) payload + payload_len, + HS_LEGACY_RENDEZVOUS_CELL_SIZE - payload_len); + payload_len = HS_LEGACY_RENDEZVOUS_CELL_SIZE; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_RENDEZVOUS1, + (const char *) payload, payload_len, + circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send RENDEZVOUS1 cell on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Setup end-to-end rendezvous circuit between the client and us. */ + if (hs_circuit_setup_e2e_rend_circ(circ, + circ->hs_ident->rendezvous_ntor_key_seed, + sizeof(circ->hs_ident->rendezvous_ntor_key_seed), + 1) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); + goto done; + } + + done: + memwipe(payload, 0, sizeof(payload)); +} + +/* Circ has been expecting an INTRO_ESTABLISHED cell that just arrived. Handle + * the INTRO_ESTABLISHED cell payload of length payload_len arriving on the + * given introduction circuit circ. The service is only used for logging + * purposes. Return 0 on success else a negative value. */ +int +hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(service); + tor_assert(ip); + tor_assert(circ); + tor_assert(payload); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + goto done; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. For a legacy node, it's an empty payload so as long as we + * have the cell, we are good. */ + if (!ip->base.is_only_legacy && + hs_cell_parse_intro_established(payload, payload_len) < 0) { + log_warn(LD_REND, "Unable to parse the INTRO_ESTABLISHED cell on " + "circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto done; + } + + /* Switch the purpose to a fully working intro point. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_S_INTRO); + /* Getting a valid INTRODUCE_ESTABLISHED means we've successfully used the + * circuit so update our pathbias subsystem. */ + pathbias_mark_use_success(circ); + /* Success. */ + ret = 0; + + done: + return ret; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the INTRODUCE2 payload of size payload_len for the given + * circuit and service. This cell is associated with the intro point object ip + * and the subcredential. Return 0 on success else a negative value. */ +int +hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + time_t elapsed; + hs_cell_introduce2_data_t data; + + tor_assert(service); + tor_assert(circ); + tor_assert(ip); + tor_assert(subcredential); + tor_assert(payload); + + /* Populate the data structure with everything we need for the cell to be + * parsed, decrypted and key material computed correctly. */ + data.auth_pk = &ip->auth_key_kp.pubkey; + data.enc_kp = &ip->enc_key_kp; + data.subcredential = subcredential; + data.payload = payload; + data.payload_len = payload_len; + data.link_specifiers = smartlist_new(); + data.replay_cache = ip->replay_cache; + + if (hs_cell_parse_introduce2(&data, circ, service) < 0) { + goto done; + } + + /* Check whether we've seen this REND_COOKIE before to detect repeats. */ + if (replaycache_add_test_and_elapsed( + service->state.replay_cache_rend_cookie, + data.rendezvous_cookie, sizeof(data.rendezvous_cookie), + &elapsed)) { + /* A Tor client will send a new INTRODUCE1 cell with the same REND_COOKIE + * as its previous one if its intro circ times out while in state + * CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT. If we received the first + * INTRODUCE1 cell (the intro-point relay converts it into an INTRODUCE2 + * cell), we are already trying to connect to that rend point (and may + * have already succeeded); drop this cell. */ + log_info(LD_REND, "We received an INTRODUCE2 cell with same REND_COOKIE " + "field %ld seconds ago. Dropping cell.", + (long int) elapsed); + goto done; + } + + /* At this point, we just confirmed that the full INTRODUCE2 cell is valid + * so increment our counter that we've seen one on this intro point. */ + ip->introduce2_count++; + + /* Launch rendezvous circuit with the onion key and rend cookie. */ + launch_rendezvous_point_circuit(service, ip, &data); + /* Success. */ + ret = 0; + + done: + SMARTLIST_FOREACH(data.link_specifiers, link_specifier_t *, lspec, + link_specifier_free(lspec)); + smartlist_free(data.link_specifiers); + memwipe(&data, 0, sizeof(data)); + return ret; +} + +/* Circuit <b>circ</b> just finished the rend ntor key exchange. Use the key + * exchange output material at <b>ntor_key_seed</b> and setup <b>circ</b> to + * serve as a rendezvous end-to-end circuit between the client and the + * service. If <b>is_service_side</b> is set, then we are the hidden service + * and the other side is the client. + * + * Return 0 if the operation went well; in case of error return -1. */ +int +hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, size_t seed_len, + int is_service_side) +{ + if (BUG(!circuit_purpose_is_correct_for_rend(TO_CIRCUIT(circ)->purpose, + is_service_side))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath(ntor_key_seed, seed_len, + is_service_side); + if (!hop) { + log_warn(LD_REND, "Couldn't get v3 %s cpath!", + is_service_side ? "service-side" : "client-side"); + return -1; + } + + finalize_rend_circuit(circ, hop, is_service_side); + + return 0; +} + +/* We are a v2 legacy HS client and we just received a RENDEZVOUS1 cell + * <b>rend_cell_body</b> on <b>circ</b>. Finish up the DH key exchange and then + * extend the crypt path of <b>circ</b> so that the hidden service is on the + * other side. */ +int +hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body) +{ + + if (BUG(!circuit_purpose_is_correct_for_rend( + TO_CIRCUIT(circ)->purpose, 0))) { + return -1; + } + + crypt_path_t *hop = create_rend_cpath_legacy(circ, rend_cell_body); + if (!hop) { + log_warn(LD_GENERAL, "Couldn't get v2 cpath."); + return -1; + } + + finalize_rend_circuit(circ, hop, 0); + + return 0; +} + +/* Given the introduction circuit intro_circ, the rendezvous circuit + * rend_circ, a descriptor intro point object ip and the service's + * subcredential, send an INTRODUCE1 cell on intro_circ. + * + * This will also setup the circuit identifier on rend_circ containing the key + * material for the handshake and e2e encryption. Return 0 on success else + * negative value. Because relay_send_command_from_edge() closes the circuit + * on error, it is possible that intro_circ is closed on error. */ +int +hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential) +{ + int ret = -1; + ssize_t payload_len; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + hs_cell_introduce1_data_t intro1_data; + + tor_assert(intro_circ); + tor_assert(rend_circ); + tor_assert(ip); + tor_assert(subcredential); + + /* This takes various objects in order to populate the introduce1 data + * object which is used to build the content of the cell. */ + setup_introduce1_data(ip, rend_circ->build_state->chosen_exit, + subcredential, &intro1_data); + /* If we didn't get any link specifiers, it's because our extend info was + * bad. */ + if (BUG(!intro1_data.link_specifiers) || + !smartlist_len(intro1_data.link_specifiers)) { + log_warn(LD_REND, "Unable to get link specifiers for INTRODUCE1 cell on " + "circuit %u.", TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Final step before we encode a cell, we setup the circuit identifier which + * will generate both the rendezvous cookie and client keypair for this + * connection. Those are put in the ident. */ + intro1_data.rendezvous_cookie = rend_circ->hs_ident->rendezvous_cookie; + intro1_data.client_kp = &rend_circ->hs_ident->rendezvous_client_kp; + + memcpy(intro_circ->hs_ident->rendezvous_cookie, + rend_circ->hs_ident->rendezvous_cookie, + sizeof(intro_circ->hs_ident->rendezvous_cookie)); + + /* From the introduce1 data object, this will encode the INTRODUCE1 cell + * into payload which is then ready to be sent as is. */ + payload_len = hs_cell_build_introduce1(&intro1_data, payload); + if (BUG(payload_len < 0)) { + goto done; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(intro_circ), + RELAY_COMMAND_INTRODUCE1, + (const char *) payload, payload_len, + intro_circ->cpath->prev) < 0) { + /* On error, circuit is closed. */ + log_warn(LD_REND, "Unable to send INTRODUCE1 cell on circuit %u.", + TO_CIRCUIT(intro_circ)->n_circ_id); + goto done; + } + + /* Success. */ + ret = 0; + goto done; + + done: + hs_cell_introduce1_data_clear(&intro1_data); + memwipe(payload, 0, sizeof(payload)); + return ret; +} + +/* Send an ESTABLISH_RENDEZVOUS cell along the rendezvous circuit circ. On + * success, 0 is returned else -1 and the circuit is marked for close. */ +int +hs_circ_send_establish_rendezvous(origin_circuit_t *circ) +{ + ssize_t cell_len = 0; + uint8_t cell[RELAY_PAYLOAD_SIZE] = {0}; + + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + log_info(LD_REND, "Send an ESTABLISH_RENDEZVOUS cell on circuit %u", + TO_CIRCUIT(circ)->n_circ_id); + + /* Set timestamp_dirty, because circuit_expire_building expects it, + * and the rend cookie also means we've used the circ. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* We've attempted to use this circuit. Probe it if we fail */ + pathbias_count_use_attempt(circ); + + /* Generate the RENDEZVOUS_COOKIE and place it in the identifier so we can + * complete the handshake when receiving the acknowledgement. */ + crypto_rand((char *) circ->hs_ident->rendezvous_cookie, HS_REND_COOKIE_LEN); + /* Generate the client keypair. No need to be extra strong, not long term */ + curve25519_keypair_generate(&circ->hs_ident->rendezvous_client_kp, 0); + + cell_len = + hs_cell_build_establish_rendezvous(circ->hs_ident->rendezvous_cookie, + cell); + if (BUG(cell_len < 0)) { + goto err; + } + + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), + RELAY_COMMAND_ESTABLISH_RENDEZVOUS, + (const char *) cell, cell_len, + circ->cpath->prev) < 0) { + /* Circuit has been marked for close */ + log_warn(LD_REND, "Unable to send ESTABLISH_RENDEZVOUS cell on " + "circuit %u", TO_CIRCUIT(circ)->n_circ_id); + memwipe(cell, 0, cell_len); + goto err; + } + + memwipe(cell, 0, cell_len); + return 0; + err: + return -1; +} + +/* We are about to close or free this <b>circ</b>. Clean it up from any + * related HS data structures. This function can be called multiple times + * safely for the same circuit. */ +void +hs_circ_cleanup(circuit_t *circ) +{ + tor_assert(circ); + + /* If it's a service-side intro circ, notify the HS subsystem for the intro + * point circuit closing so it can be dealt with cleanly. */ + if (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || + circ->purpose == CIRCUIT_PURPOSE_S_INTRO) { + hs_service_intro_circ_has_closed(TO_ORIGIN_CIRCUIT(circ)); + } + + /* Clear HS circuitmap token for this circ (if any). Very important to be + * done after the HS subsystem has been notified of the close else the + * circuit will not be found. + * + * We do this at the close if possible because from that point on, the + * circuit is good as dead. We can't rely on removing it in the circuit + * free() function because we open a race window between the close and free + * where we can't register a new circuit for the same intro point. */ + if (circ->hs_token) { + hs_circuitmap_remove_circuit(circ); + } +} + diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h new file mode 100644 index 0000000000..63ff5e463c --- /dev/null +++ b/src/or/hs_circuit.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_circuit.h + * \brief Header file containing circuit data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_CIRCUIT_H +#define TOR_HS_CIRCUIT_H + +#include "or.h" +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_service.h" + +/* Cleanup function when the circuit is closed or/and freed. */ +void hs_circ_cleanup(circuit_t *circ); + +/* Circuit API. */ +int hs_circ_service_intro_has_opened(hs_service_t *service, + hs_service_intro_point_t *ip, + const hs_service_descriptor_t *desc, + origin_circuit_t *circ); +void hs_circ_service_rp_has_opened(const hs_service_t *service, + origin_circuit_t *circ); +int hs_circ_launch_intro_point(hs_service_t *service, + const hs_service_intro_point_t *ip, + extend_info_t *ei); +int hs_circ_launch_rendezvous_point(const hs_service_t *service, + const curve25519_public_key_t *onion_key, + const uint8_t *rendezvous_cookie); +void hs_circ_retry_service_rendezvous_point(origin_circuit_t *circ); + +origin_circuit_t *hs_circ_service_get_intro_circ( + const hs_service_intro_point_t *ip); + +/* Cell API. */ +int hs_circ_handle_intro_established(const hs_service_t *service, + const hs_service_intro_point_t *ip, + origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_circ_handle_introduce2(const hs_service_t *service, + const origin_circuit_t *circ, + hs_service_intro_point_t *ip, + const uint8_t *subcredential, + const uint8_t *payload, size_t payload_len); +int hs_circ_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ, + const hs_desc_intro_point_t *ip, + const uint8_t *subcredential); +int hs_circ_send_establish_rendezvous(origin_circuit_t *circ); + +/* e2e circuit API. */ + +int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, + const uint8_t *ntor_key_seed, + size_t seed_len, + int is_service_side); +int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, + const uint8_t *rend_cell_body); + +#ifdef HS_CIRCUIT_PRIVATE + +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys); + +#endif + +#endif /* !defined(TOR_HS_CIRCUIT_H) */ + diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c index ea66fb5194..97d6053e9b 100644 --- a/src/or/hs_circuitmap.c +++ b/src/or/hs_circuitmap.c @@ -5,8 +5,10 @@ * \file hs_circuitmap.c * * \brief Hidden service circuitmap: A hash table that maps binary tokens to - * introduction and rendezvous circuits; it's used both by relays acting as - * intro points and rendezvous points, and also by hidden services themselves. + * introduction and rendezvous circuits; it's used: + * (a) by relays acting as intro points and rendezvous points + * (b) by hidden services to find intro and rend circuits and + * (c) by HS clients to find rendezvous circuits. **/ #define HS_CIRCUITMAP_PRIVATE @@ -85,7 +87,7 @@ get_hs_circuitmap(void) return the_hs_circuitmap; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ /****************** HS circuitmap utility functions **************************/ @@ -404,6 +406,55 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) return circ; } +/* Public function: Return client-side rendezvous circuit with rendezvous + * <b>cookie</b>. It will look for circuits with the following purposes: + + * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received + * RENDEZVOUS_ESTABLISHED). Waiting for RENDEZVOUS2 from service, and for + * INTRODUCE_ACK from intro point. + * + * b) CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: Established rend circuit and + * introduce circuit acked. Waiting for RENDEZVOUS2 from service. + * + * c) CIRCUIT_PURPOSE_C_REND_JOINED: Established rend circuit and received + * RENDEZVOUS2 from service. + * + * d) CIRCUIT_PURPOSE_C_ESTABLISH_REND: Rend circuit open but not yet + * established. + * + * Return NULL if no such circuit is found in the circuitmap. */ +origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) +{ + origin_circuit_t *circ = NULL; + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_JOINED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_ESTABLISH_REND); + return circ; +} + /**** Public servide-side setters: */ /* Public function: Register v2 intro circuit with key <b>digest</b> to the @@ -439,6 +490,21 @@ hs_circuitmap_register_rend_circ_service_side(origin_circuit_t *circ, REND_TOKEN_LEN, cookie); } +/* Public function: Register rendezvous circuit with key <b>cookie</b> to the + * client-side circuitmap. */ +void +hs_circuitmap_register_rend_circ_client_side(origin_circuit_t *or_circ, + const uint8_t *cookie) +{ + circuit_t *circ = TO_CIRCUIT(or_circ); + { /* Basic circ purpose sanity checking */ + tor_assert_nonfatal(circ->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + } + + hs_circuitmap_register_circuit(circ, HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie); +} + /**** Misc public functions: */ /** Public function: Remove this circuit from the HS circuitmap. Clear its HS diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h index 33d5b64117..43b2947c17 100644 --- a/src/or/hs_circuitmap.h +++ b/src/or/hs_circuitmap.h @@ -43,6 +43,8 @@ struct origin_circuit_t * hs_circuitmap_get_intro_circ_v2_service_side(const uint8_t *digest); struct origin_circuit_t * hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie); +struct origin_circuit_t * +hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie); void hs_circuitmap_register_intro_circ_v2_service_side( struct origin_circuit_t *circ, @@ -53,6 +55,9 @@ void hs_circuitmap_register_intro_circ_v3_service_side( void hs_circuitmap_register_rend_circ_service_side( struct origin_circuit_t *circ, const uint8_t *cookie); +void hs_circuitmap_register_rend_circ_client_side( + struct origin_circuit_t *circ, + const uint8_t *cookie); void hs_circuitmap_remove_circuit(struct circuit_t *circ); @@ -76,6 +81,9 @@ typedef enum { HS_TOKEN_INTRO_V2_SERVICE_SIDE, /** A v3 introduction point pubkey on a hidden service (256bit) */ HS_TOKEN_INTRO_V3_SERVICE_SIDE, + + /** A rendezvous cookie on the client side (128bit) */ + HS_TOKEN_REND_CLIENT_SIDE, } hs_token_type_t; /** Represents a token used in the HS protocol. Each such token maps to a @@ -91,7 +99,7 @@ struct hs_token_s { uint8_t *token; }; -#endif /* HS_CIRCUITMAP_PRIVATE */ +#endif /* defined(HS_CIRCUITMAP_PRIVATE) */ #ifdef TOR_UNIT_TESTS @@ -99,5 +107,5 @@ hs_circuitmap_ht *get_hs_circuitmap(void); #endif /* TOR_UNIT_TESTS */ -#endif /* TOR_HS_CIRCUITMAP_H */ +#endif /* !defined(TOR_HS_CIRCUITMAP_H) */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c new file mode 100644 index 0000000000..9ac653c721 --- /dev/null +++ b/src/or/hs_client.c @@ -0,0 +1,1609 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.c + * \brief Implement next generation hidden service client functionality + **/ + +#define HS_CLIENT_PRIVATE + +#include "or.h" +#include "hs_circuit.h" +#include "hs_ident.h" +#include "connection_edge.h" +#include "container.h" +#include "rendclient.h" +#include "hs_descriptor.h" +#include "hs_cache.h" +#include "hs_cell.h" +#include "hs_ident.h" +#include "config.h" +#include "directory.h" +#include "hs_client.h" +#include "router.h" +#include "routerset.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "connection.h" +#include "nodelist.h" +#include "circpathbias.h" +#include "connection.h" +#include "hs_ntor.h" +#include "circuitbuild.h" +#include "networkstatus.h" +#include "reasons.h" + +/* Return a human-readable string for the client fetch status code. */ +static const char * +fetch_status_to_string(hs_client_fetch_status_t status) +{ + switch (status) { + case HS_CLIENT_FETCH_ERROR: + return "Internal error"; + case HS_CLIENT_FETCH_LAUNCHED: + return "Descriptor fetch launched"; + case HS_CLIENT_FETCH_HAVE_DESC: + return "Already have descriptor"; + case HS_CLIENT_FETCH_NO_HSDIRS: + return "No more HSDir available to query"; + case HS_CLIENT_FETCH_NOT_ALLOWED: + return "Fetching descriptors is not allowed"; + case HS_CLIENT_FETCH_MISSING_INFO: + return "Missing directory information"; + case HS_CLIENT_FETCH_PENDING: + return "Pending descriptor fetch"; + default: + return "(Unknown client fetch status code)"; + } +} + +/* Return true iff tor should close the SOCKS request(s) for the descriptor + * fetch that ended up with this given status code. */ +static int +fetch_status_should_close_socks(hs_client_fetch_status_t status) +{ + switch (status) { + case HS_CLIENT_FETCH_NO_HSDIRS: + /* No more HSDir to query, we can't complete the SOCKS request(s). */ + case HS_CLIENT_FETCH_ERROR: + /* The fetch triggered an internal error. */ + case HS_CLIENT_FETCH_NOT_ALLOWED: + /* Client is not allowed to fetch (FetchHidServDescriptors 0). */ + goto close; + case HS_CLIENT_FETCH_MISSING_INFO: + case HS_CLIENT_FETCH_HAVE_DESC: + case HS_CLIENT_FETCH_PENDING: + case HS_CLIENT_FETCH_LAUNCHED: + /* The rest doesn't require tor to close the SOCKS request(s). */ + goto no_close; + } + + no_close: + return 0; + close: + return 1; +} + +/* Cancel all descriptor fetches currently in progress. */ +static void +cancel_descriptor_fetches(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + log_debug(LD_REND, "Marking for close a directory connection fetching " + "a hidden service descriptor for service %s.", + safe_str_client(ed25519_fmt(&ident->identity_pk))); + connection_mark_for_close(conn); + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + log_info(LD_REND, "Hidden service client descriptor fetches cancelled."); +} + +/* Get all connections that are waiting on a circuit and flag them back to + * waiting for a hidden service descriptor for the given service key + * service_identity_pk. */ +static void +flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk) +{ + tor_assert(service_identity_pk); + + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + edge_connection_t *edge_conn; + if (BUG(!CONN_IS_EDGE(conn))) { + continue; + } + edge_conn = TO_EDGE_CONN(conn); + if (edge_conn->hs_ident && + ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk, + service_identity_pk)) { + connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); + conn->state = AP_CONN_STATE_RENDDESC_WAIT; + } + } SMARTLIST_FOREACH_END(conn); + + smartlist_free(conns); +} + +/* Remove tracked HSDir requests from our history for this hidden service + * identity public key. */ +static void +purge_hid_serv_request(const ed25519_public_key_t *identity_pk) +{ + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + ed25519_public_key_t blinded_pk; + + tor_assert(identity_pk); + + /* Get blinded pubkey of hidden service. It is possible that we just moved + * to a new time period meaning that we won't be able to purge the request + * from the previous time period. That is fine because they will expire at + * some point and we don't care about those anymore. */ + hs_build_blinded_pubkey(identity_pk, NULL, 0, + hs_get_time_period_num(0), &blinded_pk); + if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) { + return; + } + /* Purge last hidden service request from cache for this blinded key. */ + hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk); +} + +/* Return true iff there is at least one pending directory descriptor request + * for the service identity_pk. */ +static int +directory_request_is_pending(const ed25519_public_key_t *identity_pk) +{ + int ret = 0; + smartlist_t *conns = + connection_list_by_type_purpose(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + if (!ed25519_pubkey_eq(identity_pk, &ident->identity_pk)) { + continue; + } + ret = 1; + break; + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + return ret; +} + +/* We failed to fetch a descriptor for the service with <b>identity_pk</b> + * because of <b>status</b>. Find all pending SOCKS connections for this + * service that are waiting on the descriptor and close them with + * <b>reason</b>. */ +static void +close_all_socks_conns_waiting_for_desc(const ed25519_public_key_t *identity_pk, + hs_client_fetch_status_t status, + int reason) +{ + unsigned int count = 0; + time_t now = approx_time(); + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we tried to get the descriptor */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + /* Unattach the entry connection which will close for the reason. */ + connection_mark_unattached_ap(entry_conn, reason); + count++; + } SMARTLIST_FOREACH_END(base_conn); + + if (count > 0) { + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + hs_build_address(identity_pk, HS_VERSION_THREE, onion_address); + log_notice(LD_REND, "Closed %u streams for service %s.onion " + "for reason %s. Fetch status: %s.", + count, safe_str_client(onion_address), + stream_end_reason_to_string(reason), + fetch_status_to_string(status)); + } + + /* No ownership of the object(s) in this list. */ + smartlist_free(conns); +} + +/* Find all pending SOCKS connection waiting for a descriptor and retry them + * all. This is called when the directory information changed. */ +static void +retry_all_socks_conn_waiting_for_desc(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_AP, AP_CONN_STATE_RENDDESC_WAIT); + + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + hs_client_fetch_status_t status; + const edge_connection_t *edge_conn = + ENTRY_TO_EDGE_CONN(TO_ENTRY_CONN(base_conn)); + + /* Ignore non HS or non v3 connection. */ + if (edge_conn->hs_ident == NULL) { + continue; + } + /* In this loop, we will possibly try to fetch a descriptor for the + * pending connections because we just got more directory information. + * However, the refetch process can cleanup all SOCKS request so the same + * service if an internal error happens. Thus, we can end up with closed + * connections in our list. */ + if (base_conn->marked_for_close) { + continue; + } + + /* XXX: There is an optimization we could do which is that for a service + * key, we could check if we can fetch and remember that decision. */ + + /* Order a refetch in case it works this time. */ + status = hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk); + if (BUG(status == HS_CLIENT_FETCH_HAVE_DESC)) { + /* This case is unique because it can NOT happen in theory. Once we get + * a new descriptor, the HS client subsystem is notified immediately and + * the connections waiting for it are handled which means the state will + * change from renddesc wait state. Log this and continue to next + * connection. */ + continue; + } + /* In the case of an error, either all SOCKS connections have been + * closed or we are still missing directory information. Leave the + * connection in renddesc wait state so when we get more info, we'll be + * able to try it again. */ + } SMARTLIST_FOREACH_END(base_conn); + + /* We don't have ownership of those objects. */ + smartlist_free(conns); +} + +/* A v3 HS circuit successfully connected to the hidden service. Update the + * stream state at <b>hs_conn_ident</b> appropriately. */ +static void +note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) +{ + tor_assert(hs_conn_ident); + + /* Remove from the hid serv cache all requests for that service so we can + * query the HSDir again later on for various reasons. */ + purge_hid_serv_request(&hs_conn_ident->identity_pk); + + /* The v2 subsystem cleans up the intro point time out flag at this stage. + * We don't try to do it here because we still need to keep intact the intro + * point state for future connections. Even though we are able to connect to + * the service, doesn't mean we should reset the timed out intro points. + * + * It is not possible to have successfully connected to an intro point + * present in our cache that was on error or timed out. Every entry in that + * cache have a 2 minutes lifetime so ultimately the intro point(s) state + * will be reset and thus possible to be retried. */ +} + +/* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its + * descriptor by launching a dir connection to <b>hsdir</b>. Return a + * hs_client_fetch_status_t status code depending on how it went. */ +static hs_client_fetch_status_t +directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, + const routerstatus_t *hsdir) +{ + uint64_t current_time_period = hs_get_time_period_num(0); + ed25519_public_key_t blinded_pubkey; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + hs_ident_dir_conn_t hs_conn_dir_ident; + int retval; + + tor_assert(hsdir); + tor_assert(onion_identity_pk); + + /* Get blinded pubkey */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return HS_CLIENT_FETCH_ERROR; + } + + /* Copy onion pk to a dir_ident so that we attach it to the dir conn */ + hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey, + &hs_conn_dir_ident); + + /* Setup directory request */ + directory_request_t *req = + directory_request_new(DIR_PURPOSE_FETCH_HSDESC); + directory_request_set_routerstatus(req, hsdir); + directory_request_set_indirection(req, DIRIND_ANONYMOUS); + directory_request_set_resource(req, base64_blinded_pubkey); + directory_request_fetch_set_hs_ident(req, &hs_conn_dir_ident); + directory_initiate_request(req); + directory_request_free(req); + + log_info(LD_REND, "Descriptor fetch request for service %s with blinded " + "key %s to directory %s", + safe_str_client(ed25519_fmt(onion_identity_pk)), + safe_str_client(base64_blinded_pubkey), + safe_str_client(routerstatus_describe(hsdir))); + + /* Cleanup memory. */ + memwipe(&blinded_pubkey, 0, sizeof(blinded_pubkey)); + memwipe(base64_blinded_pubkey, 0, sizeof(base64_blinded_pubkey)); + memwipe(&hs_conn_dir_ident, 0, sizeof(hs_conn_dir_ident)); + + return HS_CLIENT_FETCH_LAUNCHED; +} + +/** Return the HSDir we should use to fetch the descriptor of the hidden + * service with identity key <b>onion_identity_pk</b>. */ +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) +{ + int retval; + char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; + uint64_t current_time_period = hs_get_time_period_num(0); + smartlist_t *responsible_hsdirs; + ed25519_public_key_t blinded_pubkey; + routerstatus_t *hsdir_rs = NULL; + + tor_assert(onion_identity_pk); + + responsible_hsdirs = smartlist_new(); + + /* Get blinded pubkey of hidden service */ + hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, + current_time_period, &blinded_pubkey); + /* ...and base64 it. */ + retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); + if (BUG(retval < 0)) { + return NULL; + } + + /* Get responsible hsdirs of service for this time period */ + hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, + 0, 1, responsible_hsdirs); + + log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.", + smartlist_len(responsible_hsdirs)); + + /* Pick an HSDir from the responsible ones. The ownership of + * responsible_hsdirs is given to this function so no need to free it. */ + hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey); + + return hsdir_rs; +} + +/** Fetch a v3 descriptor using the given <b>onion_identity_pk</b>. + * + * On success, HS_CLIENT_FETCH_LAUNCHED is returned. Otherwise, an error from + * hs_client_fetch_status_t is returned. */ +MOCK_IMPL(STATIC hs_client_fetch_status_t, +fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)) +{ + routerstatus_t *hsdir_rs =NULL; + + tor_assert(onion_identity_pk); + + hsdir_rs = pick_hsdir_v3(onion_identity_pk); + if (!hsdir_rs) { + log_info(LD_REND, "Couldn't pick a v3 hsdir."); + return HS_CLIENT_FETCH_NO_HSDIRS; + } + + return directory_launch_v3_desc_fetch(onion_identity_pk, hsdir_rs); +} + +/* Make sure that the given v3 origin circuit circ is a valid correct + * introduction circuit. This will BUG() on any problems and hard assert if + * the anonymity of the circuit is not ok. Return 0 on success else -1 where + * the circuit should be mark for closed immediately. */ +static int +intro_circ_is_ok(const origin_circuit_t *circ) +{ + int ret = 0; + + tor_assert(circ); + + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCING && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACKED)) { + ret = -1; + } + if (BUG(circ->hs_ident == NULL)) { + ret = -1; + } + if (BUG(!hs_ident_intro_circ_is_valid(circ->hs_ident))) { + ret = -1; + } + + /* This can stop the tor daemon but we want that since if we don't have + * anonymity on this circuit, something went really wrong. */ + assert_circ_anonymity_ok(circ, get_options()); + return ret; +} + +/* Find a descriptor intro point object that matches the given ident in the + * given descriptor desc. Return NULL if not found. */ +static const hs_desc_intro_point_t * +find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, + const hs_descriptor_t *desc) +{ + const hs_desc_intro_point_t *intro_point = NULL; + + tor_assert(ident); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (ed25519_pubkey_eq(&ident->intro_auth_pk, + &ip->auth_key_cert->signed_key)) { + intro_point = ip; + break; + } + } SMARTLIST_FOREACH_END(ip); + + return intro_point; +} + +/* Find a descriptor intro point object from the descriptor object desc that + * matches the given legacy identity digest in legacy_id. Return NULL if not + * found. */ +static hs_desc_intro_point_t * +find_desc_intro_point_by_legacy_id(const char *legacy_id, + const hs_descriptor_t *desc) +{ + hs_desc_intro_point_t *ret_ip = NULL; + + tor_assert(legacy_id); + tor_assert(desc); + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + hs_desc_intro_point_t *, ip) { + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + /* Not all tor node have an ed25519 identity key so we still rely on the + * legacy identity digest. */ + if (lspec->type != LS_LEGACY_ID) { + continue; + } + if (fast_memneq(legacy_id, lspec->u.legacy_id, DIGEST_LEN)) { + break; + } + /* Found it. */ + ret_ip = ip; + goto end; + } SMARTLIST_FOREACH_END(lspec); + } SMARTLIST_FOREACH_END(ip); + + end: + return ret_ip; +} + +/* Send an INTRODUCE1 cell along the intro circuit and populate the rend + * circuit identifier with the needed key material for the e2e encryption. + * Return 0 on success, -1 if there is a transient error such that an action + * has been taken to recover and -2 if there is a permanent error indicating + * that both circuits were closed. */ +static int +send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + int status; + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + const ed25519_public_key_t *service_identity_pk = NULL; + const hs_desc_intro_point_t *ip; + + tor_assert(rend_circ); + if (intro_circ_is_ok(intro_circ) < 0) { + goto perm_err; + } + + service_identity_pk = &intro_circ->hs_ident->identity_pk; + /* For logging purposes. There will be a time where the hs_ident will have a + * version number but for now there is none because it's all v3. */ + hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address); + + log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u", + safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id); + + /* 1) Get descriptor from our cache. */ + const hs_descriptor_t *desc = + hs_cache_lookup_as_client(service_identity_pk); + if (desc == NULL || !hs_client_any_intro_points_usable(service_identity_pk, + desc)) { + log_info(LD_REND, "Request to %s %s. Trying to fetch a new descriptor.", + safe_str_client(onion_address), + (desc) ? "didn't have usable intro points" : + "didn't have a descriptor"); + hs_client_refetch_hsdesc(service_identity_pk); + /* We just triggered a refetch, make sure every connections are back + * waiting for that descriptor. */ + flag_all_conn_wait_desc(service_identity_pk); + /* We just asked for a refetch so this is a transient error. */ + goto tran_err; + } + + /* We need to find which intro point in the descriptor we are connected to + * on intro_circ. */ + ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc); + if (BUG(ip == NULL)) { + /* If we can find a descriptor from this introduction circuit ident, we + * must have a valid intro point object. Permanent error. */ + goto perm_err; + } + + /* Send the INTRODUCE1 cell. */ + if (hs_circ_send_introduce1(intro_circ, rend_circ, ip, + desc->subcredential) < 0) { + /* Unable to send the cell, the intro circuit has been marked for close so + * this is a permanent error. */ + tor_assert_nonfatal(TO_CIRCUIT(intro_circ)->marked_for_close); + goto perm_err; + } + + /* Cell has been sent successfully. Copy the introduction point + * authentication and encryption key in the rendezvous circuit identifier so + * we can compute the ntor keys when we receive the RENDEZVOUS2 cell. */ + memcpy(&rend_circ->hs_ident->intro_enc_pk, &ip->enc_key, + sizeof(rend_circ->hs_ident->intro_enc_pk)); + ed25519_pubkey_copy(&rend_circ->hs_ident->intro_auth_pk, + &intro_circ->hs_ident->intro_auth_pk); + + /* Now, we wait for an ACK or NAK on this circuit. */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_INTRODUCE_ACK_WAIT state. */ + TO_CIRCUIT(intro_circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(intro_circ); + + /* Success. */ + status = 0; + goto end; + + perm_err: + /* Permanent error: it is possible that the intro circuit was closed prior + * because we weren't able to send the cell. Make sure we don't double close + * it which would result in a warning. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL); + } + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_INTERNAL); + status = -2; + goto end; + + tran_err: + status = -1; + + end: + memwipe(onion_address, 0, sizeof(onion_address)); + return status; +} + +/* Using the introduction circuit circ, setup the authentication key of the + * intro point this circuit has extended to. */ +static void +setup_intro_circ_auth_key(origin_circuit_t *circ) +{ + const hs_descriptor_t *desc; + const hs_desc_intro_point_t *ip; + + tor_assert(circ); + + desc = hs_cache_lookup_as_client(&circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* Opening intro circuit without the descriptor is no good... */ + goto end; + } + + /* We will go over every intro point and try to find which one is linked to + * that circuit. Those lists are small so it's not that expensive. */ + ip = find_desc_intro_point_by_legacy_id( + circ->build_state->chosen_exit->identity_digest, desc); + if (ip) { + /* We got it, copy its authentication key to the identifier. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_cert->signed_key); + goto end; + } + + /* Reaching this point means we didn't find any intro point for this circuit + * which is not suppose to happen. */ + tor_assert_nonfatal_unreached(); + + end: + return; +} + +/* Called when an introduction circuit has opened. */ +static void +client_intro_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_INTRODUCING); + log_info(LD_REND, "Introduction circuit %u has opened. Attaching streams.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + + /* This is an introduction circuit so we'll attach the correct + * authentication key to the circuit identifier so it can be identified + * properly later on. */ + setup_intro_circ_auth_key(circ); + + connection_ap_attach_pending(1); +} + +/* Called when a rendezvous circuit has opened. */ +static void +client_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + + const extend_info_t *rp_ei = circ->build_state->chosen_exit; + + /* Check that we didn't accidentally choose a node that does not understand + * the v3 rendezvous protocol */ + if (rp_ei) { + const node_t *rp_node = node_get_by_id(rp_ei->identity_digest); + if (rp_node) { + if (BUG(!node_supports_v3_rendezvous_point(rp_node))) { + return; + } + } + } + + log_info(LD_REND, "Rendezvous circuit has opened to %s.", + safe_str_client(extend_info_describe(rp_ei))); + + /* Ignore returned value, nothing we can really do. On failure, the circuit + * will be marked for close. */ + hs_circ_send_establish_rendezvous(circ); + + /* Register rend circuit in circuitmap if it's still alive. */ + if (!TO_CIRCUIT(circ)->marked_for_close) { + hs_circuitmap_register_rend_circ_client_side(circ, + circ->hs_ident->rendezvous_cookie); + } +} + +/* This is an helper function that convert a descriptor intro point object ip + * to a newly allocated extend_info_t object fully initialized. Return NULL if + * we can't convert it for which chances are that we are missing or malformed + * link specifiers. */ +STATIC extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip) +{ + extend_info_t *ei; + smartlist_t *lspecs = smartlist_new(); + + tor_assert(ip); + + /* We first encode the descriptor link specifiers into the binary + * representation which is a trunnel object. */ + SMARTLIST_FOREACH_BEGIN(ip->link_specifiers, + const hs_desc_link_specifier_t *, desc_lspec) { + link_specifier_t *lspec = hs_desc_lspec_to_trunnel(desc_lspec); + smartlist_add(lspecs, lspec); + } SMARTLIST_FOREACH_END(desc_lspec); + + /* Explicitely put the direct connection option to 0 because this is client + * side and there is no such thing as a non anonymous client. */ + ei = hs_get_extend_info_from_lspecs(lspecs, &ip->onion_key, 0); + + SMARTLIST_FOREACH(lspecs, link_specifier_t *, ls, link_specifier_free(ls)); + smartlist_free(lspecs); + return ei; +} + +/* Return true iff the intro point ip for the service service_pk is usable. + * This function checks if the intro point is in the client intro state cache + * and checks at the failures. It is considered usable if: + * - No error happened (INTRO_POINT_FAILURE_GENERIC) + * - It is not flagged as timed out (INTRO_POINT_FAILURE_TIMEOUT) + * - The unreachable count is lower than + * MAX_INTRO_POINT_REACHABILITY_FAILURES (INTRO_POINT_FAILURE_UNREACHABLE) + */ +static int +intro_point_is_usable(const ed25519_public_key_t *service_pk, + const hs_desc_intro_point_t *ip) +{ + const hs_cache_intro_state_t *state; + + tor_assert(service_pk); + tor_assert(ip); + + state = hs_cache_client_intro_state_find(service_pk, + &ip->auth_key_cert->signed_key); + if (state == NULL) { + /* This means we've never encountered any problem thus usable. */ + goto usable; + } + if (state->error) { + log_info(LD_REND, "Intro point with auth key %s had an error. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->timed_out) { + log_info(LD_REND, "Intro point with auth key %s timed out. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + if (state->unreachable_count >= MAX_INTRO_POINT_REACHABILITY_FAILURES) { + log_info(LD_REND, "Intro point with auth key %s unreachable. Not usable", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key))); + goto not_usable; + } + + usable: + return 1; + not_usable: + return 0; +} + +/* Using a descriptor desc, return a newly allocated extend_info_t object of a + * randomly picked introduction point from its list. Return NULL if none are + * usable. */ +STATIC extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk) +{ + extend_info_t *ei = NULL, *ei_excluded = NULL; + smartlist_t *usable_ips = NULL; + const hs_descriptor_t *desc; + const hs_desc_encrypted_data_t *enc_data; + const or_options_t *options = get_options(); + /* Calculate the onion address for logging purposes */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + tor_assert(service_pk); + + desc = hs_cache_lookup_as_client(service_pk); + /* Assume the service is v3 if the descriptor is missing. This is ok, + * because we only use the address in log messages */ + hs_build_address(service_pk, + desc ? desc->plaintext_data.version : HS_VERSION_THREE, + onion_address); + if (desc == NULL || !hs_client_any_intro_points_usable(service_pk, + desc)) { + log_info(LD_REND, "Unable to randomly select an introduction point " + "for service %s because descriptor %s. We can't connect.", + safe_str_client(onion_address), + (desc) ? "doesn't have any usable intro points" + : "is missing (assuming v3 onion address)"); + goto end; + } + + enc_data = &desc->encrypted_data; + usable_ips = smartlist_new(); + smartlist_add_all(usable_ips, enc_data->intro_points); + while (smartlist_len(usable_ips) != 0) { + int idx; + const hs_desc_intro_point_t *ip; + + /* Pick a random intro point and immediately remove it from the usable + * list so we don't pick it again if we have to iterate more. */ + idx = crypto_rand_int(smartlist_len(usable_ips)); + ip = smartlist_get(usable_ips, idx); + smartlist_del(usable_ips, idx); + + /* We need to make sure we have a usable intro points which is in a good + * state in our cache. */ + if (!intro_point_is_usable(service_pk, ip)) { + continue; + } + + /* Generate an extend info object from the intro point object. */ + ei = desc_intro_point_to_extend_info(ip); + if (ei == NULL) { + /* We can get here for instance if the intro point is a private address + * and we aren't allowed to extend to those. */ + log_info(LD_REND, "Unable to select introduction point with auth key %s " + "for service %s, because we could not extend to it.", + safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)), + safe_str_client(onion_address)); + continue; + } + + /* Test the pick against ExcludeNodes. */ + if (routerset_contains_extendinfo(options->ExcludeNodes, ei)) { + /* If this pick is in the ExcludeNodes list, we keep its reference so if + * we ever end up not being able to pick anything else and StrictNodes is + * unset, we'll use it. */ + if (ei_excluded) { + /* If something was already here free it. After the loop is gone we + * will examine the last excluded intro point, and that's fine since + * that's random anyway */ + extend_info_free(ei_excluded); + } + ei_excluded = ei; + continue; + } + + /* Good pick! Let's go with this. */ + goto end; + } + + /* Reaching this point means a couple of things. Either we can't use any of + * the intro point listed because the IP address can't be extended to or it + * is listed in the ExcludeNodes list. In the later case, if StrictNodes is + * set, we are forced to not use anything. */ + ei = ei_excluded; + if (options->StrictNodes) { + log_warn(LD_REND, "Every introduction point for service %s is in the " + "ExcludeNodes set and StrictNodes is set. We can't connect.", + safe_str_client(onion_address)); + extend_info_free(ei); + ei = NULL; + } else { + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service " + "%s is unusable or we can't extend to it. We can't connect.", + safe_str_client(onion_address)); + } + + end: + smartlist_free(usable_ips); + memwipe(onion_address, 0, sizeof(onion_address)); + return ei; +} + +/* For this introduction circuit, we'll look at if we have any usable + * introduction point left for this service. If so, we'll use the circuit to + * re-extend to a new intro point. Else, we'll close the circuit and its + * corresponding rendezvous circuit. Return 0 if we are re-extending else -1 + * if we are closing the circuits. + * + * This is called when getting an INTRODUCE_ACK cell with a NACK. */ +static int +close_or_reextend_intro_circ(origin_circuit_t *intro_circ) +{ + int ret = -1; + const hs_descriptor_t *desc; + origin_circuit_t *rend_circ; + + tor_assert(intro_circ); + + desc = hs_cache_lookup_as_client(&intro_circ->hs_ident->identity_pk); + if (BUG(desc == NULL)) { + /* We can't continue without a descriptor. */ + goto close; + } + /* We still have the descriptor, great! Let's try to see if we can + * re-extend by looking up if there are any usable intro points. */ + if (!hs_client_any_intro_points_usable(&intro_circ->hs_ident->identity_pk, + desc)) { + goto close; + } + /* Try to re-extend now. */ + if (hs_client_reextend_intro_circuit(intro_circ) < 0) { + goto close; + } + /* Success on re-extending. Don't return an error. */ + ret = 0; + goto end; + + close: + /* Change the intro circuit purpose before so we don't report an intro point + * failure again triggering an extra descriptor fetch. The circuit can + * already be closed on failure to re-extend. */ + if (!TO_CIRCUIT(intro_circ)->marked_for_close) { + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + } + /* Close the related rendezvous circuit. */ + rend_circ = hs_circuitmap_get_rend_circ_client_side( + intro_circ->hs_ident->rendezvous_cookie); + /* The rendezvous circuit might have collapsed while the INTRODUCE_ACK was + * inflight so we can't expect one every time. */ + if (rend_circ) { + circuit_mark_for_close(TO_CIRCUIT(rend_circ), END_CIRC_REASON_FINISHED); + } + + end: + return ret; +} + +/* Called when we get an INTRODUCE_ACK success status code. Do the appropriate + * actions for the rendezvous point and finally close intro_circ. */ +static void +handle_introduce_ack_success(origin_circuit_t *intro_circ) +{ + origin_circuit_t *rend_circ = NULL; + + tor_assert(intro_circ); + + log_info(LD_REND, "Received INTRODUCE_ACK ack! Informing rendezvous"); + + /* Get the rendezvous circuit for this rendezvous cookie. */ + uint8_t *rendezvous_cookie = intro_circ->hs_ident->rendezvous_cookie; + rend_circ = hs_circuitmap_get_rend_circ_client_side(rendezvous_cookie); + if (rend_circ == NULL) { + log_warn(LD_REND, "Can't find any rendezvous circuit. Stopping"); + goto end; + } + + assert_circ_anonymity_ok(rend_circ, get_options()); + + /* It is possible to get a RENDEZVOUS2 cell before the INTRODUCE_ACK which + * means that the circuit will be joined and already transmitting data. In + * that case, simply skip the purpose change and close the intro circuit + * like it should be. */ + if (TO_CIRCUIT(rend_circ)->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) { + goto end; + } + circuit_change_purpose(TO_CIRCUIT(rend_circ), + CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the + * CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED state. */ + TO_CIRCUIT(rend_circ)->timestamp_dirty = time(NULL); + + end: + /* We don't need the intro circuit anymore. It did what it had to do! */ + circuit_change_purpose(TO_CIRCUIT(intro_circ), + CIRCUIT_PURPOSE_C_INTRODUCE_ACKED); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_FINISHED); + + /* XXX: Close pending intro circuits we might have in parallel. */ + return; +} + +/* Called when we get an INTRODUCE_ACK failure status code. Depending on our + * failure cache status, either close the circuit or re-extend to a new + * introduction point. */ +static void +handle_introduce_ack_bad(origin_circuit_t *circ, int status) +{ + tor_assert(circ); + + log_info(LD_REND, "Received INTRODUCE_ACK nack by %s. Reason: %u", + safe_str_client(extend_info_describe(circ->build_state->chosen_exit)), + status); + + /* It's a NAK. The introduction point didn't relay our request. */ + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); + + /* Note down this failure in the intro point failure cache. Depending on how + * many times we've tried this intro point, close it or reextend. */ + hs_cache_client_intro_state_note(&circ->hs_ident->identity_pk, + &circ->hs_ident->intro_auth_pk, + INTRO_POINT_FAILURE_GENERIC); +} + +/* Called when we get an INTRODUCE_ACK on the intro circuit circ. The encoded + * cell is in payload of length payload_len. Return 0 on success else a + * negative value. The circuit is either close or reuse to re-extend to a new + * introduction point. */ +static int +handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int status, ret = -1; + + tor_assert(circ); + tor_assert(circ->build_state); + tor_assert(circ->build_state->chosen_exit); + assert_circ_anonymity_ok(circ, get_options()); + tor_assert(payload); + + status = hs_cell_parse_introduce_ack(payload, payload_len); + switch (status) { + case HS_CELL_INTRO_ACK_SUCCESS: + ret = 0; + handle_introduce_ack_success(circ); + goto end; + case HS_CELL_INTRO_ACK_FAILURE: + case HS_CELL_INTRO_ACK_BADFMT: + case HS_CELL_INTRO_ACK_NORELAY: + handle_introduce_ack_bad(circ, status); + /* We are going to see if we have to close the circuits (IP and RP) or we + * can re-extend to a new intro point. */ + ret = close_or_reextend_intro_circ(circ); + break; + default: + log_info(LD_PROTOCOL, "Unknown INTRODUCE_ACK status code %u from %s", + status, + safe_str_client(extend_info_describe(circ->build_state->chosen_exit))); + break; + } + + end: + return ret; +} + +/* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The + * encoded cell is in payload of length payload_len. Return 0 on success or a + * negative value on error. On error, the circuit is marked for close. */ +STATIC int +handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + curve25519_public_key_t server_pk; + uint8_t auth_mac[DIGEST256_LEN] = {0}; + uint8_t handshake_info[CURVE25519_PUBKEY_LEN + sizeof(auth_mac)] = {0}; + hs_ntor_rend_cell_keys_t keys; + const hs_ident_circuit_t *ident; + + tor_assert(circ); + tor_assert(payload); + + /* Make things easier. */ + ident = circ->hs_ident; + tor_assert(ident); + + if (hs_cell_parse_rendezvous2(payload, payload_len, handshake_info, + sizeof(handshake_info)) < 0) { + goto err; + } + /* Get from the handshake info the SERVER_PK and AUTH_MAC. */ + memcpy(&server_pk, handshake_info, CURVE25519_PUBKEY_LEN); + memcpy(auth_mac, handshake_info + CURVE25519_PUBKEY_LEN, sizeof(auth_mac)); + + /* Generate the handshake info. */ + if (hs_ntor_client_get_rendezvous1_keys(&ident->intro_auth_pk, + &ident->rendezvous_client_kp, + &ident->intro_enc_pk, &server_pk, + &keys) < 0) { + log_info(LD_REND, "Unable to compute the rendezvous keys."); + goto err; + } + + /* Critical check, make sure that the MAC matches what we got with what we + * computed just above. */ + if (!hs_ntor_client_rendezvous2_mac_is_good(&keys, auth_mac)) { + log_info(LD_REND, "Invalid MAC in RENDEZVOUS2. Rejecting cell."); + goto err; + } + + /* Setup the e2e encryption on the circuit and finalize its state. */ + if (hs_circuit_setup_e2e_rend_circ(circ, keys.ntor_key_seed, + sizeof(keys.ntor_key_seed), 0) < 0) { + log_info(LD_REND, "Unable to setup the e2e encryption."); + goto err; + } + /* Success. Hidden service connection finalized! */ + ret = 0; + goto end; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + end: + memwipe(&keys, 0, sizeof(keys)); + return ret; +} + +/* Return true iff the client can fetch a descriptor for this service public + * identity key and status_out if not NULL is untouched. If the client can + * _not_ fetch the descriptor and if status_out is not NULL, it is set with + * the fetch status code. */ +static unsigned int +can_client_refetch_desc(const ed25519_public_key_t *identity_pk, + hs_client_fetch_status_t *status_out) +{ + hs_client_fetch_status_t status; + + tor_assert(identity_pk); + + /* Are we configured to fetch descriptors? */ + if (!get_options()->FetchHidServDescriptors) { + log_warn(LD_REND, "We received an onion address for a hidden service " + "descriptor but we are configured to not fetch."); + status = HS_CLIENT_FETCH_NOT_ALLOWED; + goto cannot; + } + + /* Without a live consensus we can't do any client actions. It is needed to + * compute the hashring for a service. */ + if (!networkstatus_get_live_consensus(approx_time())) { + log_info(LD_REND, "Can't fetch descriptor for service %s because we " + "are missing a live consensus. Stalling connection.", + safe_str_client(ed25519_fmt(identity_pk))); + status = HS_CLIENT_FETCH_MISSING_INFO; + goto cannot; + } + + if (!router_have_minimum_dir_info()) { + log_info(LD_REND, "Can't fetch descriptor for service %s because we " + "dont have enough descriptors. Stalling connection.", + safe_str_client(ed25519_fmt(identity_pk))); + status = HS_CLIENT_FETCH_MISSING_INFO; + goto cannot; + } + + /* Check if fetching a desc for this HS is useful to us right now */ + { + const hs_descriptor_t *cached_desc = NULL; + cached_desc = hs_cache_lookup_as_client(identity_pk); + if (cached_desc && hs_client_any_intro_points_usable(identity_pk, + cached_desc)) { + log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor " + "but we already have a usable descriptor."); + status = HS_CLIENT_FETCH_HAVE_DESC; + goto cannot; + } + } + + /* Don't try to refetch while we have a pending request for it. */ + if (directory_request_is_pending(identity_pk)) { + log_info(LD_REND, "Already a pending directory request. Waiting on it."); + status = HS_CLIENT_FETCH_PENDING; + goto cannot; + } + + /* Yes, client can fetch! */ + return 1; + cannot: + if (status_out) { + *status_out = status; + } + return 0; +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/** A circuit just finished connecting to a hidden service that the stream + * <b>conn</b> has been waiting for. Let the HS subsystem know about this. */ +void +hs_client_note_connection_attempt_succeeded(const edge_connection_t *conn) +{ + tor_assert(connection_edge_is_rendezvous_stream(conn)); + + if (BUG(conn->rend_data && conn->hs_ident)) { + log_warn(LD_BUG, "Stream had both rend_data and hs_ident..." + "Prioritizing hs_ident"); + } + + if (conn->hs_ident) { /* It's v3: pass it to the prop224 handler */ + note_connection_attempt_succeeded(conn->hs_ident); + return; + } else if (conn->rend_data) { /* It's v2: pass it to the legacy handler */ + rend_client_note_connection_attempt_ended(conn->rend_data); + return; + } +} + +/* With the given encoded descriptor in desc_str and the service key in + * service_identity_pk, decode the descriptor and set the desc pointer with a + * newly allocated descriptor object. + * + * Return 0 on success else a negative value and desc is set to NULL. */ +int +hs_client_decode_descriptor(const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc) +{ + int ret; + uint8_t subcredential[DIGEST256_LEN]; + ed25519_public_key_t blinded_pubkey; + + tor_assert(desc_str); + tor_assert(service_identity_pk); + tor_assert(desc); + + /* Create subcredential for this HS so that we can decrypt */ + { + uint64_t current_time_period = hs_get_time_period_num(0); + hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, + &blinded_pubkey); + hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); + } + + /* Parse descriptor */ + ret = hs_desc_decode_descriptor(desc_str, subcredential, desc); + memwipe(subcredential, 0, sizeof(subcredential)); + if (ret < 0) { + log_warn(LD_GENERAL, "Could not parse received descriptor as client."); + if (get_options()->SafeLogging_ == SAFELOG_SCRUB_NONE) { + log_warn(LD_GENERAL, "%s", escaped(desc_str)); + } + goto err; + } + + /* Make sure the descriptor signing key cross certifies with the computed + * blinded key. Without this validation, anyone knowing the subcredential + * and onion address can forge a descriptor. */ + if (tor_cert_checksig((*desc)->plaintext_data.signing_key_cert, + &blinded_pubkey, approx_time()) < 0) { + log_warn(LD_GENERAL, "Descriptor signing key certificate signature " + "doesn't validate with computed blinded key."); + goto err; + } + + return 0; + err: + return -1; +} + +/* Return true iff there are at least one usable intro point in the service + * descriptor desc. */ +int +hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc) +{ + tor_assert(service_pk); + tor_assert(desc); + + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + if (intro_point_is_usable(service_pk, ip)) { + goto usable; + } + } SMARTLIST_FOREACH_END(ip); + + return 0; + usable: + return 1; +} + +/** Launch a connection to a hidden service directory to fetch a hidden + * service descriptor using <b>identity_pk</b> to get the necessary keys. + * + * A hs_client_fetch_status_t code is returned. */ +int +hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) +{ + hs_client_fetch_status_t status; + + tor_assert(identity_pk); + + if (!can_client_refetch_desc(identity_pk, &status)) { + return status; + } + + /* Try to fetch the desc and if we encounter an unrecoverable error, mark + * the desc as unavailable for now. */ + status = fetch_v3_desc(identity_pk); + if (fetch_status_should_close_socks(status)) { + close_all_socks_conns_waiting_for_desc(identity_pk, status, + END_STREAM_REASON_RESOLVEFAILED); + /* Remove HSDir fetch attempts so that we can retry later if the user + * wants us to regardless of if we closed any connections. */ + purge_hid_serv_request(identity_pk); + } + return status; +} + +/* This is called when we are trying to attach an AP connection to these + * hidden service circuits from connection_ap_handshake_attach_circuit(). + * Return 0 on success, -1 for a transient error that is actions were + * triggered to recover or -2 for a permenent error where both circuits will + * marked for close. + * + * The following supports every hidden service version. */ +int +hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ) +{ + return (intro_circ->hs_ident) ? send_introduce1(intro_circ, rend_circ) : + rend_client_send_introduction(intro_circ, + rend_circ); +} + +/* Called when the client circuit circ has been established. It can be either + * an introduction or rendezvous circuit. This function handles all hidden + * service versions. */ +void +hs_client_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_C_INTRODUCING: + if (circ->hs_ident) { + client_intro_circ_has_opened(circ); + } else { + rend_client_introcirc_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_C_ESTABLISH_REND: + if (circ->hs_ident) { + client_rendezvous_circ_has_opened(circ); + } else { + rend_client_rendcirc_has_opened(circ); + } + break; + default: + tor_assert_nonfatal_unreached(); + } +} + +/* Called when we receive a RENDEZVOUS_ESTABLISHED cell. Change the state of + * the circuit to CIRCUIT_PURPOSE_C_REND_READY. Return 0 on success else a + * negative value and the circuit marked for close. */ +int +hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + tor_assert(circ); + tor_assert(payload); + + (void) payload_len; + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { + log_warn(LD_PROTOCOL, "Got a RENDEZVOUS_ESTABLISHED but we were not " + "expecting one. Closing circuit."); + goto err; + } + + log_info(LD_REND, "Received an RENDEZVOUS_ESTABLISHED. This circuit is " + "now ready for rendezvous."); + circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); + + /* Set timestamp_dirty, because circuit_expire_building expects it to + * specify when a circuit entered the _C_REND_READY state. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + + /* From a path bias point of view, this circuit is now successfully used. + * Waiting any longer opens us up to attacks from malicious hidden services. + * They could induce the client to attempt to connect to their hidden + * service and never reply to the client's rend requests */ + pathbias_mark_use_success(circ); + + /* If we already have the introduction circuit built, make sure we send + * the INTRODUCE cell _now_ */ + connection_ap_attach_pending(1); + + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* This is called when a descriptor has arrived following a fetch request and + * has been stored in the client cache. Every entry connection that matches + * the service identity key in the ident will get attached to the hidden + * service circuit. */ +void +hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) +{ + time_t now = time(NULL); + smartlist_t *conns = NULL; + + tor_assert(ident); + + conns = connection_list_by_type_state(CONN_TYPE_AP, + AP_CONN_STATE_RENDDESC_WAIT); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, base_conn) { + const hs_descriptor_t *desc; + entry_connection_t *entry_conn = TO_ENTRY_CONN(base_conn); + const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); + + /* Only consider the entry connections that matches the service for which + * we just fetched its descriptor. */ + if (!edge_conn->hs_ident || + !ed25519_pubkey_eq(&ident->identity_pk, + &edge_conn->hs_ident->identity_pk)) { + continue; + } + assert_connection_ok(base_conn, now); + + /* We were just called because we stored the descriptor for this service + * so not finding a descriptor means we have a bigger problem. */ + desc = hs_cache_lookup_as_client(&ident->identity_pk); + if (BUG(desc == NULL)) { + goto end; + } + + if (!hs_client_any_intro_points_usable(&ident->identity_pk, desc)) { + log_info(LD_REND, "Hidden service descriptor is unusable. " + "Closing streams."); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_RESOLVEFAILED); + /* We are unable to use the descriptor so remove the directory request + * from the cache so the next connection can try again. */ + note_connection_attempt_succeeded(edge_conn->hs_ident); + goto end; + } + + log_info(LD_REND, "Descriptor has arrived. Launching circuits."); + + /* Because the connection can now proceed to opening circuit and + * ultimately connect to the service, reset those timestamp so the + * connection is considered "fresh" and can continue without being closed + * too early. */ + base_conn->timestamp_created = now; + base_conn->timestamp_lastread = now; + base_conn->timestamp_lastwritten = now; + /* Change connection's state into waiting for a circuit. */ + base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; + + connection_ap_mark_as_pending_circuit(entry_conn); + } SMARTLIST_FOREACH_END(base_conn); + + end: + /* We don't have ownership of the objects in this list. */ + smartlist_free(conns); +} + +/* Return a newly allocated extend_info_t for a randomly chosen introduction + * point for the given edge connection identifier ident. Return NULL if we + * can't pick any usable introduction points. */ +extend_info_t * +hs_client_get_random_intro_from_edge(const edge_connection_t *edge_conn) +{ + tor_assert(edge_conn); + + return (edge_conn->hs_ident) ? + client_get_random_intro(&edge_conn->hs_ident->identity_pk) : + rend_client_get_random_intro(edge_conn->rend_data); +} +/* Called when get an INTRODUCE_ACK cell on the introduction circuit circ. + * Return 0 on success else a negative value is returned. The circuit will be + * closed or reuse to extend again to another intro point. */ +int +hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { + log_warn(LD_PROTOCOL, "Unexpected INTRODUCE_ACK on circuit %u.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + ret = (circ->hs_ident) ? handle_introduce_ack(circ, payload, payload_len) : + rend_client_introduction_acked(circ, payload, + payload_len); + /* For path bias: This circuit was used successfully. NACK or ACK counts. */ + pathbias_mark_use_success(circ); + + end: + return ret; +} + +/* Called when get a RENDEZVOUS2 cell on the rendezvous circuit circ. Return + * 0 on success else a negative value is returned. The circuit will be closed + * on error. */ +int +hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Circuit can possibly be in both state because we could receive a + * RENDEZVOUS2 cell before the INTRODUCE_ACK has been received. */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY && + TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) { + log_warn(LD_PROTOCOL, "Unexpected RENDEZVOUS2 cell on circuit %u. " + "Closing circuit.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + goto end; + } + + log_info(LD_REND, "Got RENDEZVOUS2 cell from hidden service on circuit %u.", + TO_CIRCUIT(circ)->n_circ_id); + + ret = (circ->hs_ident) ? handle_rendezvous2(circ, payload, payload_len) : + rend_client_receive_rendezvous(circ, payload, + payload_len); + end: + return ret; +} + +/* Extend the introduction circuit circ to another valid introduction point + * for the hidden service it is trying to connect to, or mark it and launch a + * new circuit if we can't extend it. Return 0 on success or possible + * success. Return -1 and mark the introduction circuit for close on permanent + * failure. + * + * On failure, the caller is responsible for marking the associated rendezvous + * circuit for close. */ +int +hs_client_reextend_intro_circuit(origin_circuit_t *circ) +{ + int ret = -1; + extend_info_t *ei; + + tor_assert(circ); + + ei = (circ->hs_ident) ? + client_get_random_intro(&circ->hs_ident->identity_pk) : + rend_client_get_random_intro(circ->rend_data); + if (ei == NULL) { + log_warn(LD_REND, "No usable introduction points left. Closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + goto end; + } + + if (circ->remaining_relay_early_cells) { + log_info(LD_REND, "Re-extending circ %u, this time to %s.", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(extend_info_describe(ei))); + ret = circuit_extend_to_new_exit(circ, ei); + if (ret == 0) { + /* We were able to extend so update the timestamp so we avoid expiring + * this circuit too early. The intro circuit is short live so the + * linkability issue is minimized, we just need the circuit to hold a + * bit longer so we can introduce. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + } + } else { + log_info(LD_REND, "Closing intro circ %u (out of RELAY_EARLY cells).", + (unsigned int) TO_CIRCUIT(circ)->n_circ_id); + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); + /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ + ret = 0; + } + + end: + extend_info_free(ei); + return ret; +} + +/* Release all the storage held by the client subsystem. */ +void +hs_client_free_all(void) +{ + /* Purge the hidden service request cache. */ + hs_purge_last_hid_serv_requests(); +} + +/* Purge all potentially remotely-detectable state held in the hidden + * service client code. Called on SIGNAL NEWNYM. */ +void +hs_client_purge_state(void) +{ + /* v2 subsystem. */ + rend_client_purge_state(); + + /* Cancel all descriptor fetches. Do this first so once done we are sure + * that our descriptor cache won't modified. */ + cancel_descriptor_fetches(); + /* Purge the introduction point state cache. */ + hs_cache_client_intro_state_purge(); + /* Purge the descriptor cache. */ + hs_cache_purge_as_client(); + /* Purge the last hidden service request cache. */ + hs_purge_last_hid_serv_requests(); + + log_info(LD_REND, "Hidden service client state has been purged."); +} + +/* Called when our directory information has changed. */ +void +hs_client_dir_info_changed(void) +{ + /* We have possibly reached the minimum directory information or new + * consensus so retry all pending SOCKS connection in + * AP_CONN_STATE_RENDDESC_WAIT state in order to fetch the descriptor. */ + retry_all_socks_conn_waiting_for_desc(); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h new file mode 100644 index 0000000000..2523568ad1 --- /dev/null +++ b/src/or/hs_client.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_client.h + * \brief Header file containing client data for the HS subsytem. + **/ + +#ifndef TOR_HS_CLIENT_H +#define TOR_HS_CLIENT_H + +#include "crypto_ed25519.h" +#include "hs_descriptor.h" +#include "hs_ident.h" + +/* Status code of a descriptor fetch request. */ +typedef enum { + /* Something internally went wrong. */ + HS_CLIENT_FETCH_ERROR = -1, + /* The fetch request has been launched successfully. */ + HS_CLIENT_FETCH_LAUNCHED = 0, + /* We already have a usable descriptor. No fetch. */ + HS_CLIENT_FETCH_HAVE_DESC = 1, + /* No more HSDir available to query. */ + HS_CLIENT_FETCH_NO_HSDIRS = 2, + /* The fetch request is not allowed. */ + HS_CLIENT_FETCH_NOT_ALLOWED = 3, + /* We are missing information to be able to launch a request. */ + HS_CLIENT_FETCH_MISSING_INFO = 4, + /* There is a pending fetch for the requested service. */ + HS_CLIENT_FETCH_PENDING = 5, +} hs_client_fetch_status_t; + +void hs_client_note_connection_attempt_succeeded( + const edge_connection_t *conn); + +int hs_client_decode_descriptor( + const char *desc_str, + const ed25519_public_key_t *service_identity_pk, + hs_descriptor_t **desc); +int hs_client_any_intro_points_usable(const ed25519_public_key_t *service_pk, + const hs_descriptor_t *desc); +int hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk); +void hs_client_dir_info_changed(void); + +int hs_client_send_introduce1(origin_circuit_t *intro_circ, + origin_circuit_t *rend_circ); + +void hs_client_circuit_has_opened(origin_circuit_t *circ); + +int hs_client_receive_rendezvous_acked(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_client_receive_introduce_ack(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_client_receive_rendezvous2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident); + +extend_info_t *hs_client_get_random_intro_from_edge( + const edge_connection_t *edge_conn); + +int hs_client_reextend_intro_circuit(origin_circuit_t *circ); + +void hs_client_purge_state(void); + +void hs_client_free_all(void); + +#ifdef HS_CLIENT_PRIVATE + +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk); + +STATIC extend_info_t * +client_get_random_intro(const ed25519_public_key_t *service_pk); + +STATIC extend_info_t * +desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip); + +STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len); + +MOCK_DECL(STATIC hs_client_fetch_status_t, + fetch_v3_desc, (const ed25519_public_key_t *onion_identity_pk)); + +#endif /* defined(HS_CLIENT_PRIVATE) */ + +#endif /* !defined(TOR_HS_CLIENT_H) */ + diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 7cef5a8e2c..ad783a36fb 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -14,9 +14,167 @@ #include "or.h" #include "config.h" +#include "circuitbuild.h" #include "networkstatus.h" +#include "nodelist.h" +#include "hs_cache.h" #include "hs_common.h" +#include "hs_client.h" +#include "hs_ident.h" +#include "hs_service.h" +#include "hs_circuitmap.h" +#include "policies.h" #include "rendcommon.h" +#include "rendservice.h" +#include "routerset.h" +#include "router.h" +#include "routerset.h" +#include "shared_random.h" +#include "shared_random_state.h" + +/* Trunnel */ +#include "ed25519_cert.h" + +/* Ed25519 Basepoint value. Taken from section 5 of + * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03 */ +static const char *str_ed25519_basepoint = + "(15112221349535400772501151409588531511" + "454012693041857206046113283949847762202, " + "463168356949264781694283940034751631413" + "07993866256225615783033603165251855960)"; + +#ifdef HAVE_SYS_UN_H + +/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, + * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success + * else return -ENOSYS if AF_UNIX is not supported (see function in the + * #else statement below). */ +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + tor_assert(ports); + tor_assert(p); + tor_assert(p->is_unix_addr); + + smartlist_add(ports, p); + return 0; +} + +/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 + * on success else return -ENOSYS if AF_UNIX is not supported (see function + * in the #else statement below). */ +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + tor_assert(conn); + tor_assert(p); + tor_assert(p->is_unix_addr); + + conn->base_.socket_family = AF_UNIX; + tor_addr_make_unspec(&conn->base_.addr); + conn->base_.port = 1; + conn->base_.address = tor_strdup(p->unix_addr); + return 0; +} + +#else /* !(defined(HAVE_SYS_UN_H)) */ + +static int +set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) +{ + (void) conn; + (void) p; + return -ENOSYS; +} + +static int +add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) +{ + (void) ports; + (void) p; + return -ENOSYS; +} + +#endif /* defined(HAVE_SYS_UN_H) */ + +/* Helper function: The key is a digest that we compare to a node_t object + * current hsdir_index. */ +static int +compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->fetch, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_first_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_first, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_second_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_second, DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects current hsdir_index. */ +static int +compare_node_fetch_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->fetch, + node2->hsdir_index->fetch, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_first_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_first, + node2->hsdir_index->store_first, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_second_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_second, + node2->hsdir_index->store_second, + DIGEST256_LEN); +} + +/* Allocate and return a string containing the path to filename in directory. + * This function will never return NULL. The caller must free this path. */ +char * +hs_path_from_filename(const char *directory, const char *filename) +{ + char *file_path = NULL; + + tor_assert(directory); + tor_assert(filename); + + tor_asprintf(&file_path, "%s%s%s", directory, PATH_SEPARATOR, filename); + return file_path; +} /* Make sure that the directory for <b>service</b> is private, using the config * <b>username</b>. @@ -56,6 +214,17 @@ hs_check_service_private_dir(const char *username, const char *path, STATIC uint64_t get_time_period_length(void) { + /* If we are on a test network, make the time period smaller than normal so + that we actually see it rotate. Specifically, make it the same length as + an SRV protocol run. */ + if (get_options()->TestingTorNetwork) { + unsigned run_duration = sr_state_get_protocol_run_duration(); + /* An SRV run should take more than a minute (it's 24 rounds) */ + tor_assert_nonfatal(run_duration > 60); + /* Turn it from seconds to minutes before returning: */ + return sr_state_get_protocol_run_duration() / 60; + } + int32_t time_period_length = networkstatus_get_param(NULL, "hsdir_interval", HS_TIME_PERIOD_LENGTH_DEFAULT, HS_TIME_PERIOD_LENGTH_MIN, @@ -66,18 +235,34 @@ get_time_period_length(void) return (uint64_t) time_period_length; } -/** Get the HS time period number at time <b>now</b> */ -STATIC uint64_t -get_time_period_num(time_t now) +/** Get the HS time period number at time <b>now</b>. If <b>now</b> is not set, + * we try to get the time ourselves from a live consensus. */ +uint64_t +hs_get_time_period_num(time_t now) { uint64_t time_period_num; + time_t current_time; + + /* If no time is specified, set current time based on consensus time, and + * only fall back to system time if that fails. */ + if (now != 0) { + current_time = now; + } else { + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + current_time = ns ? ns->valid_after : approx_time(); + } + + /* Start by calculating minutes since the epoch */ uint64_t time_period_length = get_time_period_length(); - uint64_t minutes_since_epoch = now / 60; + uint64_t minutes_since_epoch = current_time / 60; - /* Now subtract half a day to fit the prop224 time period schedule (see - * section [TIME-PERIODS]). */ - tor_assert(minutes_since_epoch > HS_TIME_PERIOD_ROTATION_OFFSET); - minutes_since_epoch -= HS_TIME_PERIOD_ROTATION_OFFSET; + /* Apply the rotation offset as specified by prop224 (section + * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV + * publication */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + time_period_rotation_offset /= 60; /* go from seconds to minutes */ + tor_assert(minutes_since_epoch > time_period_rotation_offset); + minutes_since_epoch -= time_period_rotation_offset; /* Calculate the time period */ time_period_num = minutes_since_epoch / time_period_length; @@ -85,11 +270,38 @@ get_time_period_num(time_t now) } /** Get the number of the _upcoming_ HS time period, given that the current - * time is <b>now</b>. */ + * time is <b>now</b>. If <b>now</b> is not set, we try to get the time from a + * live consensus. */ uint64_t hs_get_next_time_period_num(time_t now) { - return get_time_period_num(now) + 1; + return hs_get_time_period_num(now) + 1; +} + +/* Get the number of the _previous_ HS time period, given that the current time + * is <b>now</b>. If <b>now</b> is not set, we try to get the time from a live + * consensus. */ +uint64_t +hs_get_previous_time_period_num(time_t now) +{ + return hs_get_time_period_num(now) - 1; +} + +/* Return the start time of the upcoming time period based on <b>now</b>. If + <b>now</b> is not set, we try to get the time ourselves from a live + consensus. */ +time_t +hs_get_start_time_of_next_time_period(time_t now) +{ + uint64_t time_period_length = get_time_period_length(); + + /* Get start time of next time period */ + uint64_t next_time_period_num = hs_get_next_time_period_num(now); + uint64_t start_of_next_tp_in_mins = next_time_period_num *time_period_length; + + /* Apply rotation offset as specified by prop224 section [TIME-PERIODS] */ + unsigned int time_period_rotation_offset = sr_state_get_phase_duration(); + return (time_t)(start_of_next_tp_in_mins * 60 + time_period_rotation_offset); } /* Create a new rend_data_t for a specific given <b>version</b>. @@ -344,3 +556,1238 @@ rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) } } +/* Using the given time period number, compute the disaster shared random + * value and put it in srv_out. It MUST be at least DIGEST256_LEN bytes. */ +static void +compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + crypto_digest_t *digest; + + tor_assert(srv_out); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + + /* Start setting up payload: + * H("shared-random-disaster" | INT_8(period_length) | INT_8(period_num)) */ + crypto_digest_add_bytes(digest, HS_SRV_DISASTER_PREFIX, + HS_SRV_DISASTER_PREFIX_LEN); + + /* Setup INT_8(period_length) | INT_8(period_num) */ + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) srv_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/** Due to the high cost of computing the disaster SRV and that potentially we + * would have to do it thousands of times in a row, we always cache the + * computer disaster SRV (and its corresponding time period num) in case we + * want to reuse it soon after. We need to cache two SRVs, one for each active + * time period. + */ +static uint8_t cached_disaster_srv[2][DIGEST256_LEN]; +static uint64_t cached_time_period_nums[2] = {0}; + +/** Compute the disaster SRV value for this <b>time_period_num</b> and put it + * in <b>srv_out</b> (of size at least DIGEST256_LEN). First check our caches + * to see if we have already computed it. */ +STATIC void +get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) +{ + if (time_period_num == cached_time_period_nums[0]) { + memcpy(srv_out, cached_disaster_srv[0], DIGEST256_LEN); + return; + } else if (time_period_num == cached_time_period_nums[1]) { + memcpy(srv_out, cached_disaster_srv[1], DIGEST256_LEN); + return; + } else { + int replace_idx; + // Replace the lower period number. + if (cached_time_period_nums[0] <= cached_time_period_nums[1]) { + replace_idx = 0; + } else { + replace_idx = 1; + } + cached_time_period_nums[replace_idx] = time_period_num; + compute_disaster_srv(time_period_num, cached_disaster_srv[replace_idx]); + memcpy(srv_out, cached_disaster_srv[replace_idx], DIGEST256_LEN); + return; + } +} + +#ifdef TOR_UNIT_TESTS + +/** Get the first cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_first_cached_disaster_srv(void) +{ + return cached_disaster_srv[0]; +} + +/** Get the second cached disaster SRV. Only used by unittests. */ +STATIC uint8_t * +get_second_cached_disaster_srv(void) +{ + return cached_disaster_srv[1]; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + +/* When creating a blinded key, we need a parameter which construction is as + * follow: H(pubkey | [secret] | ed25519-basepoint | nonce). + * + * The nonce has a pre-defined format which uses the time period number + * period_num and the start of the period in second start_time_period. + * + * The secret of size secret_len is optional meaning that it can be NULL and + * thus will be ignored for the param construction. + * + * The result is put in param_out. */ +static void +build_blinded_key_param(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t period_num, uint64_t period_length, + uint8_t *param_out) +{ + size_t offset = 0; + const char blind_str[] = "Derive temporary signing key"; + uint8_t nonce[HS_KEYBLIND_NONCE_LEN]; + crypto_digest_t *digest; + + tor_assert(pubkey); + tor_assert(param_out); + + /* Create the nonce N. The construction is as follow: + * N = "key-blind" || INT_8(period_num) || INT_8(period_length) */ + memcpy(nonce, HS_KEYBLIND_NONCE_PREFIX, HS_KEYBLIND_NONCE_PREFIX_LEN); + offset += HS_KEYBLIND_NONCE_PREFIX_LEN; + set_uint64(nonce + offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(nonce + offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == HS_KEYBLIND_NONCE_LEN); + + /* Generate the parameter h and the construction is as follow: + * h = H(BLIND_STRING | pubkey | [secret] | ed25519-basepoint | N) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, blind_str, sizeof(blind_str)); + crypto_digest_add_bytes(digest, (char *) pubkey, ED25519_PUBKEY_LEN); + /* Optional secret. */ + if (secret) { + crypto_digest_add_bytes(digest, (char *) secret, secret_len); + } + crypto_digest_add_bytes(digest, str_ed25519_basepoint, + strlen(str_ed25519_basepoint)); + crypto_digest_add_bytes(digest, (char *) nonce, sizeof(nonce)); + + /* Extract digest and put it in the param. */ + crypto_digest_get_digest(digest, (char *) param_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(nonce, 0, sizeof(nonce)); +} + +/* Using an ed25519 public key and version to build the checksum of an + * address. Put in checksum_out. Format is: + * SHA3-256(".onion checksum" || PUBKEY || VERSION) + * + * checksum_out must be large enough to receive 32 bytes (DIGEST256_LEN). */ +static void +build_hs_checksum(const ed25519_public_key_t *key, uint8_t version, + uint8_t *checksum_out) +{ + size_t offset = 0; + char data[HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN]; + + /* Build checksum data. */ + memcpy(data, HS_SERVICE_ADDR_CHECKSUM_PREFIX, + HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN); + offset += HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN; + memcpy(data + offset, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + set_uint8(data + offset, version); + offset += sizeof(version); + tor_assert(offset == HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN); + + /* Hash the data payload to create the checksum. */ + crypto_digest256((char *) checksum_out, data, sizeof(data), + DIGEST_SHA3_256); +} + +/* Using an ed25519 public key, checksum and version to build the binary + * representation of a service address. Put in addr_out. Format is: + * addr_out = PUBKEY || CHECKSUM || VERSION + * + * addr_out must be large enough to receive HS_SERVICE_ADDR_LEN bytes. */ +static void +build_hs_address(const ed25519_public_key_t *key, const uint8_t *checksum, + uint8_t version, char *addr_out) +{ + size_t offset = 0; + + tor_assert(key); + tor_assert(checksum); + + memcpy(addr_out, key->pubkey, ED25519_PUBKEY_LEN); + offset += ED25519_PUBKEY_LEN; + memcpy(addr_out + offset, checksum, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + set_uint8(addr_out + offset, version); + offset += sizeof(uint8_t); + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Helper for hs_parse_address(): Using a binary representation of a service + * address, parse its content into the key_out, checksum_out and version_out. + * Any out variable can be NULL in case the caller would want only one field. + * checksum_out MUST at least be 2 bytes long. address must be at least + * HS_SERVICE_ADDR_LEN bytes but doesn't need to be NUL terminated. */ +static void +hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + size_t offset = 0; + + tor_assert(address); + + if (key_out) { + /* First is the key. */ + memcpy(key_out->pubkey, address, ED25519_PUBKEY_LEN); + } + offset += ED25519_PUBKEY_LEN; + if (checksum_out) { + /* Followed by a 2 bytes checksum. */ + memcpy(checksum_out, address + offset, HS_SERVICE_ADDR_CHECKSUM_LEN_USED); + } + offset += HS_SERVICE_ADDR_CHECKSUM_LEN_USED; + if (version_out) { + /* Finally, version value is 1 byte. */ + *version_out = get_uint8(address + offset); + } + offset += sizeof(uint8_t); + /* Extra safety. */ + tor_assert(offset == HS_SERVICE_ADDR_LEN); +} + +/* Using the given identity public key and a blinded public key, compute the + * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). + * This can't fail. */ +void +hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out) +{ + uint8_t credential[DIGEST256_LEN]; + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(subcred_out); + + /* First, build the credential. Construction is as follow: + * credential = H("credential" | public-identity-key) */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_CREDENTIAL_PREFIX, + HS_CREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) credential, DIGEST256_LEN); + crypto_digest_free(digest); + + /* Now, compute the subcredential. Construction is as follow: + * subcredential = H("subcredential" | credential | blinded-public-key). */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_SUBCREDENTIAL_PREFIX, + HS_SUBCREDENTIAL_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) credential, + sizeof(credential)); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); + crypto_digest_free(digest); + + memwipe(credential, 0, sizeof(credential)); +} + +/* From the given list of hidden service ports, find the ones that much the + * given edge connection conn, pick one at random and use it to set the + * connection address. Return 0 on success or -1 if none. */ +int +hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) +{ + rend_service_port_config_t *chosen_port; + unsigned int warn_once = 0; + smartlist_t *matching_ports; + + tor_assert(ports); + tor_assert(conn); + + matching_ports = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (TO_CONN(conn)->port != p->virtual_port) { + continue; + } + if (!(p->is_unix_addr)) { + smartlist_add(matching_ports, p); + } else { + if (add_unix_port(matching_ports, p)) { + if (!warn_once) { + /* Unix port not supported so warn only once. */ + log_warn(LD_REND, "Saw AF_UNIX virtual port mapping for port %d " + "which is unsupported on this platform. " + "Ignoring it.", + TO_CONN(conn)->port); + } + warn_once++; + } + } + } SMARTLIST_FOREACH_END(p); + + chosen_port = smartlist_choose(matching_ports); + smartlist_free(matching_ports); + if (chosen_port) { + if (!(chosen_port->is_unix_addr)) { + /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ + tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr); + TO_CONN(conn)->port = chosen_port->real_port; + } else { + if (set_unix_port(conn, chosen_port)) { + /* Simply impossible to end up here else we were able to add a Unix + * port without AF_UNIX support... ? */ + tor_assert(0); + } + } + } + return (chosen_port) ? 0 : -1; +} + +/* Using a base32 representation of a service address, parse its content into + * the key_out, checksum_out and version_out. Any out variable can be NULL in + * case the caller would want only one field. checksum_out MUST at least be 2 + * bytes long. + * + * Return 0 if parsing went well; return -1 in case of error. */ +int +hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + char decoded[HS_SERVICE_ADDR_LEN]; + + tor_assert(address); + + /* Obvious length check. */ + if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) { + log_warn(LD_REND, "Service address %s has an invalid length. " + "Expected %lu but got %lu.", + escaped_safe_str(address), + (unsigned long) HS_SERVICE_ADDR_LEN_BASE32, + (unsigned long) strlen(address)); + goto invalid; + } + + /* Decode address so we can extract needed fields. */ + if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) < 0) { + log_warn(LD_REND, "Service address %s can't be decoded.", + escaped_safe_str(address)); + goto invalid; + } + + /* Parse the decoded address into the fields we need. */ + hs_parse_address_impl(decoded, key_out, checksum_out, version_out); + + return 0; + invalid: + return -1; +} + +/* Validate a given onion address. The length, the base32 decoding and + * checksum are validated. Return 1 if valid else 0. */ +int +hs_address_is_valid(const char *address) +{ + uint8_t version; + uint8_t checksum[HS_SERVICE_ADDR_CHECKSUM_LEN_USED]; + uint8_t target_checksum[DIGEST256_LEN]; + ed25519_public_key_t service_pubkey; + + /* Parse the decoded address into the fields we need. */ + if (hs_parse_address(address, &service_pubkey, checksum, &version) < 0) { + goto invalid; + } + + /* Get the checksum it's suppose to be and compare it with what we have + * encoded in the address. */ + build_hs_checksum(&service_pubkey, version, target_checksum); + if (tor_memcmp(checksum, target_checksum, sizeof(checksum))) { + log_warn(LD_REND, "Service address %s invalid checksum.", + escaped_safe_str(address)); + goto invalid; + } + + /* Validate that this pubkey does not have a torsion component. We need to do + * this on the prop224 client-side so that attackers can't give equivalent + * forms of an onion address to users. */ + if (ed25519_validate_pubkey(&service_pubkey) < 0) { + log_warn(LD_REND, "Service address %s has bad pubkey .", + escaped_safe_str(address)); + goto invalid; + } + + /* Valid address. */ + return 1; + invalid: + return 0; +} + +/* Build a service address using an ed25519 public key and a given version. + * The returned address is base32 encoded and put in addr_out. The caller MUST + * make sure the addr_out is at least HS_SERVICE_ADDR_LEN_BASE32 + 1 long. + * + * Format is as follow: + * base32(PUBKEY || CHECKSUM || VERSION) + * CHECKSUM = H(".onion checksum" || PUBKEY || VERSION) + * */ +void +hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out) +{ + uint8_t checksum[DIGEST256_LEN]; + char address[HS_SERVICE_ADDR_LEN]; + + tor_assert(key); + tor_assert(addr_out); + + /* Get the checksum of the address. */ + build_hs_checksum(key, version, checksum); + /* Get the binary address representation. */ + build_hs_address(key, checksum, version, address); + + /* Encode the address. addr_out will be NUL terminated after this. */ + base32_encode(addr_out, HS_SERVICE_ADDR_LEN_BASE32 + 1, address, + sizeof(address)); + /* Validate what we just built. */ + tor_assert(hs_address_is_valid(addr_out)); +} + +/* Return a newly allocated copy of lspec. */ +link_specifier_t * +hs_link_specifier_dup(const link_specifier_t *lspec) +{ + link_specifier_t *dup = link_specifier_new(); + memcpy(dup, lspec, sizeof(*dup)); + /* The unrecognized field is a dynamic array so make sure to copy its + * content and not the pointer. */ + link_specifier_setlen_un_unrecognized( + dup, link_specifier_getlen_un_unrecognized(lspec)); + if (link_specifier_getlen_un_unrecognized(dup)) { + memcpy(link_specifier_getarray_un_unrecognized(dup), + link_specifier_getconstarray_un_unrecognized(lspec), + link_specifier_getlen_un_unrecognized(dup)); + } + return dup; +} + +/* From a given ed25519 public key pk and an optional secret, compute a + * blinded public key and put it in blinded_pk_out. This is only useful to + * the client side because the client only has access to the identity public + * key of the service. */ +void +hs_build_blinded_pubkey(const ed25519_public_key_t *pk, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *blinded_pk_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(pk); + tor_assert(blinded_pk_out); + tor_assert(!tor_mem_is_zero((char *) pk, ED25519_PUBKEY_LEN)); + + build_blinded_key_param(pk, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_public_blind(blinded_pk_out, pk, param); + + memwipe(param, 0, sizeof(param)); +} + +/* From a given ed25519 keypair kp and an optional secret, compute a blinded + * keypair for the current time period and put it in blinded_kp_out. This is + * only useful by the service side because the client doesn't have access to + * the identity secret key. */ +void +hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *blinded_kp_out) +{ + /* Our blinding key API requires a 32 bytes parameter. */ + uint8_t param[DIGEST256_LEN]; + + tor_assert(kp); + tor_assert(blinded_kp_out); + /* Extra safety. A zeroed key is bad. */ + tor_assert(!tor_mem_is_zero((char *) &kp->pubkey, ED25519_PUBKEY_LEN)); + tor_assert(!tor_mem_is_zero((char *) &kp->seckey, ED25519_SECKEY_LEN)); + + build_blinded_key_param(&kp->pubkey, secret, secret_len, + time_period_num, get_time_period_length(), param); + ed25519_keypair_blind(blinded_kp_out, kp, param); + + memwipe(param, 0, sizeof(param)); +} + +/* Return true if we are currently in the time segment between a new time + * period and a new SRV (in the real network that happens between 12:00 and + * 00:00 UTC). Here is a diagram showing exactly when this returns true: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^^^^^^^^^^^^ ^^^^^^^^^^^^ | + * | | + * +------------------------------------------------------------------+ + */ +MOCK_IMPL(int, +hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now)) +{ + time_t valid_after; + time_t srv_start_time, tp_start_time; + + if (!consensus) { + consensus = networkstatus_get_live_consensus(now); + if (!consensus) { + return 0; + } + } + + /* Get start time of next TP and of current SRV protocol run, and check if we + * are between them. */ + valid_after = consensus->valid_after; + srv_start_time = + sr_state_get_start_time_of_current_protocol_run(valid_after); + tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); + + if (valid_after >= srv_start_time && valid_after < tp_start_time) { + return 0; + } + + return 1; +} + +/* Return 1 if any virtual port in ports needs a circuit with good uptime. + * Else return 0. */ +int +hs_service_requires_uptime_circ(const smartlist_t *ports) +{ + tor_assert(ports); + + SMARTLIST_FOREACH_BEGIN(ports, rend_service_port_config_t *, p) { + if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, + p->virtual_port)) { + return 1; + } + } SMARTLIST_FOREACH_END(p); + return 0; +} + +/* Build hs_index which is used to find the responsible hsdirs. This index + * value is used to select the responsible HSDir where their hsdir_index is + * closest to this value. + * SHA3-256("store-at-idx" | blinded_public_key | + * INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) ) + * + * hs_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hs_index(uint64_t replica, const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out) +{ + crypto_digest_t *digest; + + tor_assert(blinded_pk); + tor_assert(hs_index_out); + + /* Build hs_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HS_INDEX_PREFIX, HS_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, + ED25519_PUBKEY_LEN); + + /* Now setup INT_8(replicanum) | INT_8(period_length) | INT_8(period_num) */ + { + uint64_t period_length = get_time_period_length(); + char buf[sizeof(uint64_t)*3]; + size_t offset = 0; + set_uint64(buf, tor_htonll(replica)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_length)); + offset += sizeof(uint64_t); + set_uint64(buf+offset, tor_htonll(period_num)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(buf)); + + crypto_digest_add_bytes(digest, buf, sizeof(buf)); + } + + crypto_digest_get_digest(digest, (char *) hs_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Build hsdir_index which is used to find the responsible hsdirs. This is the + * index value that is compare to the hs_index when selecting an HSDir. + * SHA3-256("node-idx" | node_identity | + * shared_random_value | INT_8(period_length) | INT_8(period_num) ) + * + * hsdir_index_out must be large enough to receive DIGEST256_LEN bytes. */ +void +hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv_value, uint64_t period_num, + uint8_t *hsdir_index_out) +{ + crypto_digest_t *digest; + + tor_assert(identity_pk); + tor_assert(srv_value); + tor_assert(hsdir_index_out); + + /* Build hsdir_index. See construction at top of function comment. */ + digest = crypto_digest256_new(DIGEST_SHA3_256); + crypto_digest_add_bytes(digest, HSDIR_INDEX_PREFIX, HSDIR_INDEX_PREFIX_LEN); + crypto_digest_add_bytes(digest, (const char *) identity_pk->pubkey, + ED25519_PUBKEY_LEN); + crypto_digest_add_bytes(digest, (const char *) srv_value, DIGEST256_LEN); + + { + uint64_t time_period_length = get_time_period_length(); + char period_stuff[sizeof(uint64_t)*2]; + size_t offset = 0; + set_uint64(period_stuff, tor_htonll(period_num)); + offset += sizeof(uint64_t); + set_uint64(period_stuff+offset, tor_htonll(time_period_length)); + offset += sizeof(uint64_t); + tor_assert(offset == sizeof(period_stuff)); + + crypto_digest_add_bytes(digest, period_stuff, sizeof(period_stuff)); + } + + crypto_digest_get_digest(digest, (char *) hsdir_index_out, DIGEST256_LEN); + crypto_digest_free(digest); +} + +/* Return a newly allocated buffer containing the current shared random value + * or if not present, a disaster value is computed using the given time period + * number. If a consensus is provided in <b>ns</b>, use it to get the SRV + * value. This function can't fail. */ +uint8_t * +hs_get_current_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *current_srv = sr_get_current(ns); + + if (current_srv) { + memcpy(sr_value, current_srv->value, sizeof(current_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return a newly allocated buffer containing the previous shared random + * value or if not present, a disaster value is computed using the given time + * period number. This function can't fail. */ +uint8_t * +hs_get_previous_srv(uint64_t time_period_num, const networkstatus_t *ns) +{ + uint8_t *sr_value = tor_malloc_zero(DIGEST256_LEN); + const sr_srv_t *previous_srv = sr_get_previous(ns); + + if (previous_srv) { + memcpy(sr_value, previous_srv->value, sizeof(previous_srv->value)); + } else { + /* Disaster mode. */ + get_disaster_srv(time_period_num, sr_value); + } + return sr_value; +} + +/* Return the number of replicas defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_n_replicas(void) +{ + /* The [1,16] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_n_replicas", + HS_DEFAULT_HSDIR_N_REPLICAS, 1, 16); +} + +/* Return the spread fetch value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_fetch(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_fetch", + HS_DEFAULT_HSDIR_SPREAD_FETCH, 1, 128); +} + +/* Return the spread store value defined by a consensus parameter or the + * default value. */ +int32_t +hs_get_hsdir_spread_store(void) +{ + /* The [1,128] range is a specification requirement. */ + return networkstatus_get_param(NULL, "hsdir_spread_store", + HS_DEFAULT_HSDIR_SPREAD_STORE, 1, 128); +} + +/** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index. + * Return 0 if everything is as expected, else return -1. */ +static int +node_has_hsdir_index(const node_t *node) +{ + tor_assert(node_supports_v3_hsdir(node)); + + /* A node can't have an HSDir index without a descriptor since we need desc + * to get its ed25519 key */ + if (!node_has_descriptor(node)) { + return 0; + } + + /* At this point, since the node has a desc, this node must also have an + * hsdir index. If not, something went wrong, so BUG out. */ + if (BUG(node->hsdir_index == NULL)) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->fetch, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_first, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_second, + DIGEST256_LEN))) { + return 0; + } + + return 1; +} + +/* For a given blinded key and time period number, get the responsible HSDir + * and put their routerstatus_t object in the responsible_dirs list. If + * 'use_second_hsdir_index' is true, use the second hsdir_index of the node_t + * is used. If 'for_fetching' is true, the spread fetch consensus parameter is + * used else the spread store is used which is only for upload. This function + * can't fail but it is possible that the responsible_dirs list contains fewer + * nodes than expected. + * + * This function goes over the latest consensus routerstatus list and sorts it + * by their node_t hsdir_index then does a binary search to find the closest + * node. All of this makes it a bit CPU intensive so use it wisely. */ +void +hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs) +{ + smartlist_t *sorted_nodes; + /* The compare function used for the smartlist bsearch. We have two + * different depending on is_next_period. */ + int (*cmp_fct)(const void *, const void **); + + tor_assert(blinded_pk); + tor_assert(responsible_dirs); + + sorted_nodes = smartlist_new(); + + /* Add every node_t that support HSDir v3 for which we do have a valid + * hsdir_index already computed for them for this consensus. */ + { + networkstatus_t *c = networkstatus_get_latest_consensus(); + if (!c || smartlist_len(c->routerstatus_list) == 0) { + log_warn(LD_REND, "No valid consensus so we can't get the responsible " + "hidden service directories."); + goto done; + } + SMARTLIST_FOREACH_BEGIN(c->routerstatus_list, const routerstatus_t *, rs) { + /* Even though this node_t object won't be modified and should be const, + * we can't add const object in a smartlist_t. */ + node_t *n = node_get_mutable_by_id(rs->identity_digest); + tor_assert(n); + if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { + if (!node_has_hsdir_index(n)) { + log_info(LD_GENERAL, "Node %s was found without hsdir index.", + node_describe(n)); + continue; + } + smartlist_add(sorted_nodes, n); + } + } SMARTLIST_FOREACH_END(rs); + } + if (smartlist_len(sorted_nodes) == 0) { + log_warn(LD_REND, "No nodes found to be HSDir or supporting v3."); + goto done; + } + + /* First thing we have to do is sort all node_t by hsdir_index. The + * is_next_period tells us if we want the current or the next one. Set the + * bsearch compare function also while we are at it. */ + if (for_fetching) { + smartlist_sort(sorted_nodes, compare_node_fetch_hsdir_index); + cmp_fct = compare_digest_to_fetch_hsdir_index; + } else if (use_second_hsdir_index) { + smartlist_sort(sorted_nodes, compare_node_store_second_hsdir_index); + cmp_fct = compare_digest_to_store_second_hsdir_index; + } else { + smartlist_sort(sorted_nodes, compare_node_store_first_hsdir_index); + cmp_fct = compare_digest_to_store_first_hsdir_index; + } + + /* For all replicas, we'll select a set of HSDirs using the consensus + * parameters and the sorted list. The replica starting at value 1 is + * defined by the specification. */ + for (int replica = 1; replica <= hs_get_hsdir_n_replicas(); replica++) { + int idx, start, found, n_added = 0; + uint8_t hs_index[DIGEST256_LEN] = {0}; + /* Number of node to add to the responsible dirs list depends on if we are + * trying to fetch or store. A client always fetches. */ + int n_to_add = (for_fetching) ? hs_get_hsdir_spread_fetch() : + hs_get_hsdir_spread_store(); + + /* Get the index that we should use to select the node. */ + hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index); + /* The compare function pointer has been set correctly earlier. */ + start = idx = smartlist_bsearch_idx(sorted_nodes, hs_index, cmp_fct, + &found); + /* Getting the length of the list if no member is greater than the key we + * are looking for so start at the first element. */ + if (idx == smartlist_len(sorted_nodes)) { + start = idx = 0; + } + while (n_added < n_to_add) { + const node_t *node = smartlist_get(sorted_nodes, idx); + /* If the node has already been selected which is possible between + * replicas, the specification says to skip over. */ + if (!smartlist_contains(responsible_dirs, node->rs)) { + smartlist_add(responsible_dirs, node->rs); + ++n_added; + } + if (++idx == smartlist_len(sorted_nodes)) { + /* Wrap if we've reached the end of the list. */ + idx = 0; + } + if (idx == start) { + /* We've gone over the whole list, stop and avoid infinite loop. */ + break; + } + } + } + + done: + smartlist_free(sorted_nodes); +} + +/*********************** HSDir request tracking ***************************/ + +/** Return the period for which a hidden service directory cannot be queried + * for the same descriptor ID again, taking TestingTorNetwork into account. */ +time_t +hs_hsdir_requery_period(const or_options_t *options) +{ + tor_assert(options); + + if (options->TestingTorNetwork) { + return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; + } else { + return REND_HID_SERV_DIR_REQUERY_PERIOD; + } +} + +/** Tracks requests for fetching hidden service descriptors. It's used by + * hidden service clients, to avoid querying HSDirs that have already failed + * giving back a descriptor. The same data structure is used to track both v2 + * and v3 HS descriptor requests. + * + * The string map is a key/value store that contains the last request times to + * hidden service directories for certain queries. Specifically: + * + * key = base32(hsdir_identity) + base32(hs_identity) + * value = time_t of last request for that hs_identity to that HSDir + * + * where 'hsdir_identity' is the identity digest of the HSDir node, and + * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519 + * blinded public key of the HS in the v3 case. */ +static strmap_t *last_hid_serv_requests_ = NULL; + +/** Returns last_hid_serv_requests_, initializing it to a new strmap if + * necessary. */ +STATIC strmap_t * +get_last_hid_serv_requests(void) +{ + if (!last_hid_serv_requests_) + last_hid_serv_requests_ = strmap_new(); + return last_hid_serv_requests_; +} + +/** Look up the last request time to hidden service directory <b>hs_dir</b> + * for descriptor request key <b>req_key_str</b> which is the descriptor ID + * for a v2 service or the blinded key for v3. If <b>set</b> is non-zero, + * assign the current time <b>now</b> and return that. Otherwise, return the + * most recent request time, or 0 if no such request has been sent before. */ +time_t +hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *req_key_str, + time_t now, int set) +{ + char hsdir_id_base32[BASE32_DIGEST_LEN + 1]; + char *hsdir_desc_comb_id = NULL; + time_t *last_request_ptr; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + /* Create the key */ + base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), + hs_dir->identity_digest, DIGEST_LEN); + tor_asprintf(&hsdir_desc_comb_id, "%s%s", hsdir_id_base32, req_key_str); + + if (set) { + time_t *oldptr; + last_request_ptr = tor_malloc_zero(sizeof(time_t)); + *last_request_ptr = now; + oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, + last_request_ptr); + tor_free(oldptr); + } else { + last_request_ptr = strmap_get(last_hid_serv_requests, + hsdir_desc_comb_id); + } + + tor_free(hsdir_desc_comb_id); + return (last_request_ptr) ? *last_request_ptr : 0; +} + +/** Clean the history of request times to hidden service directories, so that + * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD + * seconds any more. */ +void +hs_clean_last_hid_serv_requests(time_t now) +{ + strmap_iter_t *iter; + time_t cutoff = now - hs_hsdir_requery_period(get_options()); + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + time_t *ent; + strmap_iter_get(iter, &key, &val); + ent = (time_t *) val; + if (*ent < cutoff) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(ent); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Remove all requests related to the descriptor request key string + * <b>req_key_str</b> from the history of times of requests to hidden service + * directories. + * + * This is called from rend_client_note_connection_attempt_ended(), which + * must be idempotent, so any future changes to this function must leave it + * idempotent too. */ +void +hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str) +{ + strmap_iter_t *iter; + strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); + + for (iter = strmap_iter_init(last_hid_serv_requests); + !strmap_iter_done(iter); ) { + const char *key; + void *val; + strmap_iter_get(iter, &key, &val); + + /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of + * semantic, see #23305. */ + + /* This strmap contains variable-sized elements so this is a basic length + * check on the strings we are about to compare. The key is variable sized + * since it's composed as follows: + * key = base32(hsdir_identity) + base32(req_key_str) + * where 'req_key_str' is the descriptor ID of the HS in the v2 case, or + * the ed25519 blinded public key of the HS in the v3 case. */ + if (strlen(key) < REND_DESC_ID_V2_LEN_BASE32 + strlen(req_key_str)) { + iter = strmap_iter_next(last_hid_serv_requests, iter); + continue; + } + + /* Check if the tracked request matches our request key */ + if (tor_memeq(key + REND_DESC_ID_V2_LEN_BASE32, req_key_str, + strlen(req_key_str))) { + iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); + tor_free(val); + } else { + iter = strmap_iter_next(last_hid_serv_requests, iter); + } + } +} + +/** Purge the history of request times to hidden service directories, + * so that future lookups of an HS descriptor will not fail because we + * accessed all of the HSDir relays responsible for the descriptor + * recently. */ +void +hs_purge_last_hid_serv_requests(void) +{ + /* Don't create the table if it doesn't exist yet (and it may very + * well not exist if the user hasn't accessed any HSes)... */ + strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; + /* ... and let get_last_hid_serv_requests re-create it for us if + * necessary. */ + last_hid_serv_requests_ = NULL; + + if (old_last_hid_serv_requests != NULL) { + log_info(LD_REND, "Purging client last-HS-desc-request-time table"); + strmap_free(old_last_hid_serv_requests, tor_free_); + } +} + +/***********************************************************************/ + +/** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the + * one that we should use to fetch a descriptor right now. Take into account + * previous failed attempts at fetching this descriptor from HSDirs using the + * string identifier <b>req_key_str</b>. + * + * Steals ownership of <b>responsible_dirs</b>. + * + * Return the routerstatus of the chosen HSDir if successful, otherwise return + * NULL if no HSDirs are worth trying right now. */ +routerstatus_t * +hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) +{ + smartlist_t *usable_responsible_dirs = smartlist_new(); + const or_options_t *options = get_options(); + routerstatus_t *hs_dir; + time_t now = time(NULL); + int excluded_some; + + tor_assert(req_key_str); + + /* Clean outdated request history first. */ + hs_clean_last_hid_serv_requests(now); + + /* Only select those hidden service directories to which we did not send a + * request recently and for which we have a router descriptor here. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { + time_t last = hs_lookup_last_hid_serv_request(dir, req_key_str, 0, 0); + const node_t *node = node_get_by_id(dir->identity_digest); + if (last + hs_hsdir_requery_period(options) >= now || + !node || !node_has_descriptor(node)) { + SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + continue; + } + if (!routerset_contains_node(options->ExcludeNodes, node)) { + smartlist_add(usable_responsible_dirs, dir); + } + } SMARTLIST_FOREACH_END(dir); + + excluded_some = + smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); + + hs_dir = smartlist_choose(usable_responsible_dirs); + if (!hs_dir && !options->StrictNodes) { + hs_dir = smartlist_choose(responsible_dirs); + } + + smartlist_free(responsible_dirs); + smartlist_free(usable_responsible_dirs); + if (!hs_dir) { + log_info(LD_REND, "Could not pick one of the responsible hidden " + "service directories, because we requested them all " + "recently without success."); + if (options->StrictNodes && excluded_some) { + log_warn(LD_REND, "Could not pick a hidden service directory for the " + "requested hidden service: they are all either down or " + "excluded, and StrictNodes is set."); + } + } else { + /* Remember that we are requesting a descriptor from this hidden service + * directory now. */ + hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1); + } + + return hs_dir; +} + +/* From a list of link specifier, an onion key and if we are requesting a + * direct connection (ex: single onion service), return a newly allocated + * extend_info_t object. This function always returns an extend info with + * an IPv4 address, or NULL. + * + * It performs the following checks: + * if either IPv4 or legacy ID is missing, return NULL. + * if direct_conn, and we can't reach the IPv4 address, return NULL. + */ +extend_info_t * +hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn) +{ + int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0; + char legacy_id[DIGEST_LEN] = {0}; + uint16_t port_v4 = 0; + tor_addr_t addr_v4; + ed25519_public_key_t ed25519_pk; + extend_info_t *info = NULL; + + tor_assert(lspecs); + + SMARTLIST_FOREACH_BEGIN(lspecs, const link_specifier_t *, ls) { + switch (link_specifier_get_ls_type(ls)) { + case LS_IPV4: + /* Skip if we already seen a v4. */ + if (have_v4) continue; + tor_addr_from_ipv4h(&addr_v4, + link_specifier_get_un_ipv4_addr(ls)); + port_v4 = link_specifier_get_un_ipv4_port(ls); + have_v4 = 1; + break; + case LS_LEGACY_ID: + /* Make sure we do have enough bytes for the legacy ID. */ + if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) { + break; + } + memcpy(legacy_id, link_specifier_getconstarray_un_legacy_id(ls), + sizeof(legacy_id)); + have_legacy_id = 1; + break; + case LS_ED25519_ID: + memcpy(ed25519_pk.pubkey, + link_specifier_getconstarray_un_ed25519_id(ls), + ED25519_PUBKEY_LEN); + have_ed25519_id = 1; + break; + default: + /* Ignore unknown. */ + break; + } + } SMARTLIST_FOREACH_END(ls); + + /* Legacy ID is mandatory, and we require IPv4. */ + if (!have_v4 || !have_legacy_id) { + goto done; + } + + /* We know we have IPv4, because we just checked. */ + if (!direct_conn) { + /* All clients can extend to any IPv4 via a 3-hop path. */ + goto validate; + } else if (direct_conn && + fascist_firewall_allows_address_addr(&addr_v4, port_v4, + FIREWALL_OR_CONNECTION, + 0, 0)) { + /* Direct connection and we can reach it in IPv4 so go for it. */ + goto validate; + + /* We will add support for falling back to a 3-hop path in a later + * release. */ + } else { + /* If we can't reach IPv4, return NULL. */ + goto done; + } + + /* We will add support for IPv6 in a later release. */ + + validate: + /* We'll validate now that the address we've picked isn't a private one. If + * it is, are we allowing to extend to private address? */ + if (!extend_info_addr_is_allowed(&addr_v4)) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Requested address is private and we are not allowed to extend to " + "it: %s:%u", fmt_addr(&addr_v4), port_v4); + goto done; + } + + /* We do have everything for which we think we can connect successfully. */ + info = extend_info_new(NULL, legacy_id, + (have_ed25519_id) ? &ed25519_pk : NULL, NULL, + onion_key, &addr_v4, port_v4); + done: + return info; +} + +/***********************************************************************/ + +/* Initialize the entire HS subsytem. This is called in tor_init() before any + * torrc options are loaded. Only for >= v3. */ +void +hs_init(void) +{ + hs_circuitmap_init(); + hs_service_init(); + hs_cache_init(); +} + +/* Release and cleanup all memory of the HS subsystem (all version). This is + * called by tor_free_all(). */ +void +hs_free_all(void) +{ + hs_circuitmap_free_all(); + hs_service_free_all(); + hs_cache_free_all(); + hs_client_free_all(); +} + +/* For the given origin circuit circ, decrement the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_dec_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams--; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams--; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + +/* For the given origin circuit circ, increment the number of rendezvous + * stream counter. This handles every hidden service version. */ +void +hs_inc_rdv_stream_counter(origin_circuit_t *circ) +{ + tor_assert(circ); + + if (circ->rend_data) { + circ->rend_data->nr_streams++; + } else if (circ->hs_ident) { + circ->hs_ident->num_rdv_streams++; + } else { + /* Should not be called if this circuit is not for hidden service. */ + tor_assert_nonfatal_unreached(); + } +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h index a8fded652a..b81c03d508 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -11,15 +11,21 @@ #include "or.h" +/* Trunnel */ +#include "ed25519_cert.h" + /* Protocol version 2. Use this instead of hardcoding "2" in the code base, * this adds a clearer semantic to the value when used. */ #define HS_VERSION_TWO 2 /* Version 3 of the protocol (prop224). */ #define HS_VERSION_THREE 3 +/* Earliest and latest version we support. */ +#define HS_VERSION_MIN HS_VERSION_TWO +#define HS_VERSION_MAX HS_VERSION_THREE /** Try to maintain this many intro points per service by default. */ #define NUM_INTRO_POINTS_DEFAULT 3 -/** Maximum number of intro points per service. */ +/** Maximum number of intro points per generic and version 2 service. */ #define NUM_INTRO_POINTS_MAX 10 /** Number of extra intro points we launch if our set of intro nodes is empty. * See proposal 155, section 4. */ @@ -46,12 +52,136 @@ #define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ /* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ + +/* Prefix of the onion address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX ".onion checksum" +/* Length of the checksum prefix minus the NUL terminated byte. */ +#define HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN \ + (sizeof(HS_SERVICE_ADDR_CHECKSUM_PREFIX) - 1) +/* Length of the resulting checksum of the address. The construction of this + * checksum looks like: + * CHECKSUM = ".onion checksum" || PUBKEY || VERSION + * where VERSION is 1 byte. This is pre-hashing. */ +#define HS_SERVICE_ADDR_CHECKSUM_INPUT_LEN \ + (HS_SERVICE_ADDR_CHECKSUM_PREFIX_LEN + ED25519_PUBKEY_LEN + sizeof(uint8_t)) +/* The amount of bytes we use from the address checksum. */ +#define HS_SERVICE_ADDR_CHECKSUM_LEN_USED 2 +/* Length of the binary encoded service address which is of course before the + * base32 encoding. Construction is: + * PUBKEY || CHECKSUM || VERSION + * with 1 byte VERSION and 2 bytes CHECKSUM. The following is 35 bytes. */ +#define HS_SERVICE_ADDR_LEN \ + (ED25519_PUBKEY_LEN + HS_SERVICE_ADDR_CHECKSUM_LEN_USED + sizeof(uint8_t)) +/* Length of 'y' portion of 'y.onion' URL. This is base32 encoded and the + * length ends up to 56 bytes (not counting the terminated NUL byte.) */ +#define HS_SERVICE_ADDR_LEN_BASE32 \ + (CEIL_DIV(HS_SERVICE_ADDR_LEN * 8, 5)) + +/* The default HS time period length */ +#define HS_TIME_PERIOD_LENGTH_DEFAULT 1440 /* 1440 minutes == one day */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MIN 30 /* minutes */ +/* The minimum time period length as seen in prop224 section [TIME-PERIODS] */ +#define HS_TIME_PERIOD_LENGTH_MAX (60 * 24 * 10) /* 10 days or 14400 minutes */ /* The time period rotation offset as seen in prop224 section [TIME-PERIODS] */ #define HS_TIME_PERIOD_ROTATION_OFFSET (12 * 60) /* minutes */ +/* Keyblinding parameter construction is as follow: + * "key-blind" || INT_8(period_num) || INT_8(start_period_sec) */ +#define HS_KEYBLIND_NONCE_PREFIX "key-blind" +#define HS_KEYBLIND_NONCE_PREFIX_LEN (sizeof(HS_KEYBLIND_NONCE_PREFIX) - 1) +#define HS_KEYBLIND_NONCE_LEN \ + (HS_KEYBLIND_NONCE_PREFIX_LEN + sizeof(uint64_t) + sizeof(uint64_t)) + +/* Credential and subcredential prefix value. */ +#define HS_CREDENTIAL_PREFIX "credential" +#define HS_CREDENTIAL_PREFIX_LEN (sizeof(HS_CREDENTIAL_PREFIX) - 1) +#define HS_SUBCREDENTIAL_PREFIX "subcredential" +#define HS_SUBCREDENTIAL_PREFIX_LEN (sizeof(HS_SUBCREDENTIAL_PREFIX) - 1) + +/* Node hidden service stored at index prefix value. */ +#define HS_INDEX_PREFIX "store-at-idx" +#define HS_INDEX_PREFIX_LEN (sizeof(HS_INDEX_PREFIX) - 1) + +/* Node hidden service directory index prefix value. */ +#define HSDIR_INDEX_PREFIX "node-idx" +#define HSDIR_INDEX_PREFIX_LEN (sizeof(HSDIR_INDEX_PREFIX) - 1) + +/* Prefix of the shared random value disaster mode. */ +#define HS_SRV_DISASTER_PREFIX "shared-random-disaster" +#define HS_SRV_DISASTER_PREFIX_LEN (sizeof(HS_SRV_DISASTER_PREFIX) - 1) + +/* Default value of number of hsdir replicas (hsdir_n_replicas). */ +#define HS_DEFAULT_HSDIR_N_REPLICAS 2 +/* Default value of hsdir spread store (hsdir_spread_store). */ +#define HS_DEFAULT_HSDIR_SPREAD_STORE 4 +/* Default value of hsdir spread fetch (hsdir_spread_fetch). */ +#define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 + +/* The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is + * bigger than the 84 bytes needed for version 3 so we need to pad up to that + * length so it is indistinguishable between versions. */ +#define HS_LEGACY_RENDEZVOUS_CELL_SIZE \ + (REND_COOKIE_LEN + DH_KEY_LEN + DIGEST_LEN) + +/* Type of authentication key used by an introduction point. */ +typedef enum { + HS_AUTH_KEY_TYPE_LEGACY = 1, + HS_AUTH_KEY_TYPE_ED25519 = 2, +} hs_auth_key_type_t; + +/* Represents the mapping from a virtual port of a rendezvous service to a + * real port on some IP. */ +typedef struct rend_service_port_config_t { + /* The incoming HS virtual port we're mapping */ + uint16_t virtual_port; + /* Is this an AF_UNIX port? */ + unsigned int is_unix_addr:1; + /* The outgoing TCP port to use, if !is_unix_addr */ + uint16_t real_port; + /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ + tor_addr_t real_addr; + /* The socket path to connect to, if is_unix_addr */ + char unix_addr[FLEXIBLE_ARRAY_MEMBER]; +} rend_service_port_config_t; + +/* Hidden service directory index used in a node_t which is set once we set + * the consensus. */ +typedef struct hsdir_index_t { + /* HSDir index to use when fetching a descriptor. */ + uint8_t fetch[DIGEST256_LEN]; + + /* HSDir index used by services to store their first and second + * descriptor. The first descriptor is chronologically older than the second + * one and uses older TP and SRV values. */ + uint8_t store_first[DIGEST256_LEN]; + uint8_t store_second[DIGEST256_LEN]; +} hsdir_index_t; + +void hs_init(void); +void hs_free_all(void); + +void hs_cleanup_circ(circuit_t *circ); + int hs_check_service_private_dir(const char *username, const char *path, unsigned int dir_group_readable, unsigned int create); +char *hs_path_from_filename(const char *directory, const char *filename); +void hs_build_address(const ed25519_public_key_t *key, uint8_t version, + char *addr_out); +int hs_address_is_valid(const char *address); +int hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out); + +void hs_build_blinded_pubkey(const ed25519_public_key_t *pubkey, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_public_key_t *pubkey_out); +void hs_build_blinded_keypair(const ed25519_keypair_t *kp, + const uint8_t *secret, size_t secret_len, + uint64_t time_period_num, + ed25519_keypair_t *kp_out); +int hs_service_requires_uptime_circ(const smartlist_t *ports); void rend_data_free(rend_data_t *data); rend_data_t *rend_data_dup(const rend_data_t *data); @@ -69,18 +199,84 @@ const char *rend_data_get_desc_id(const rend_data_t *rend_data, const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out); +routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32); + +void hs_get_subcredential(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + uint8_t *subcred_out); + +uint64_t hs_get_previous_time_period_num(time_t now); +uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); +time_t hs_get_start_time_of_next_time_period(time_t now); + +link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); + +MOCK_DECL(int, hs_in_period_between_tp_and_srv, + (const networkstatus_t *consensus, time_t now)); + +uint8_t *hs_get_current_srv(uint64_t time_period_num, + const networkstatus_t *ns); +uint8_t *hs_get_previous_srv(uint64_t time_period_num, + const networkstatus_t *ns); + +void hs_build_hsdir_index(const ed25519_public_key_t *identity_pk, + const uint8_t *srv, uint64_t period_num, + uint8_t *hsdir_index_out); +void hs_build_hs_index(uint64_t replica, + const ed25519_public_key_t *blinded_pk, + uint64_t period_num, uint8_t *hs_index_out); + +int32_t hs_get_hsdir_n_replicas(void); +int32_t hs_get_hsdir_spread_fetch(void); +int32_t hs_get_hsdir_spread_store(void); + +void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, + uint64_t time_period_num, + int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs); +routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, + const char *req_key_str); + +time_t hs_hsdir_requery_period(const or_options_t *options); +time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, + const char *desc_id_base32, + time_t now, int set); +void hs_clean_last_hid_serv_requests(time_t now); +void hs_purge_hid_serv_from_last_hid_serv_requests(const char *desc_id); +void hs_purge_last_hid_serv_requests(void); + +int hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn); + +void hs_inc_rdv_stream_counter(origin_circuit_t *circ); +void hs_dec_rdv_stream_counter(origin_circuit_t *circ); + +extend_info_t *hs_get_extend_info_from_lspecs(const smartlist_t *lspecs, + const curve25519_public_key_t *onion_key, + int direct_conn); #ifdef HS_COMMON_PRIVATE +STATIC void get_disaster_srv(uint64_t time_period_num, uint8_t *srv_out); + +/** The period for which a hidden service directory cannot be queried for + * the same descriptor ID again. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) +/** Test networks generate a new consensus every 5 or 10 seconds. + * So allow them to requery HSDirs much faster. */ +#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) + #ifdef TOR_UNIT_TESTS +STATIC strmap_t *get_last_hid_serv_requests(void); STATIC uint64_t get_time_period_length(void); -STATIC uint64_t get_time_period_num(time_t now); -#endif /* TOR_UNIT_TESTS */ +STATIC uint8_t *get_first_cached_disaster_srv(void); +STATIC uint8_t *get_second_cached_disaster_srv(void); + +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* HS_COMMON_PRIVATE */ +#endif /* defined(HS_COMMON_PRIVATE) */ -#endif /* TOR_HS_COMMON_H */ +#endif /* !defined(TOR_HS_COMMON_H) */ diff --git a/src/or/hs_config.c b/src/or/hs_config.c new file mode 100644 index 0000000000..fa5c1ab176 --- /dev/null +++ b/src/or/hs_config.c @@ -0,0 +1,590 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.c + * \brief Implement hidden service configuration subsystem. + * + * \details + * + * This file has basically one main entry point: hs_config_service_all(). It + * takes the torrc options and configure hidden service from it. In validate + * mode, nothing is added to the global service list or keys are not generated + * nor loaded. + * + * A service is configured in two steps. It is first created using the tor + * options and then put in a staging list. It will stay there until + * hs_service_load_all_keys() is called. That function is responsible to + * load/generate the keys for the service in the staging list and if + * successful, transfert the service to the main global service list where + * at that point it is ready to be used. + * + * Configuration functions are per-version and there is a main generic one for + * every option that is common to all version (config_generic_service). + **/ + +#define HS_CONFIG_PRIVATE + +#include "hs_common.h" +#include "hs_config.h" +#include "hs_service.h" +#include "rendservice.h" + +/* Using the given list of services, stage them into our global state. Every + * service version are handled. This function can remove entries in the given + * service_list. + * + * Staging a service means that we take all services in service_list and we + * put them in the staging list (global) which acts as a temporary list that + * is used by the service loading key process. In other words, staging a + * service puts it in a list to be considered when loading the keys and then + * moved to the main global list. */ +static void +stage_services(smartlist_t *service_list) +{ + tor_assert(service_list); + + /* This is v2 specific. Trigger service pruning which will make sure the + * just configured services end up in the main global list. It should only + * be done in non validation mode because v2 subsystem handles service + * object differently. */ + rend_service_prune_list(); + + /* Cleanup v2 service from the list, we don't need those object anymore + * because we validated them all against the others and we want to stage + * only >= v3 service. And remember, v2 has a different object type which is + * shadow copied from an hs_service_t type. */ + SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) { + if (s->config.version == HS_VERSION_TWO) { + SMARTLIST_DEL_CURRENT(service_list, s); + hs_service_free(s); + } + } SMARTLIST_FOREACH_END(s); + + /* This is >= v3 specific. Using the newly configured service list, stage + * them into our global state. Every object ownership is lost after. */ + hs_service_stage_services(service_list); +} + +/* Validate the given service against all service in the given list. If the + * service is ephemeral, this function ignores it. Services with the same + * directory path aren't allowed and will return an error. If a duplicate is + * found, 1 is returned else 0 if none found. */ +static int +service_is_duplicate_in_list(const smartlist_t *service_list, + const hs_service_t *service) +{ + int ret = 0; + + tor_assert(service_list); + tor_assert(service); + + /* Ephemeral service don't have a directory configured so no need to check + * for a service in the list having the same path. */ + if (service->config.is_ephemeral) { + goto end; + } + + /* XXX: Validate if we have any service that has the given service dir path. + * This has two problems: + * + * a) It's O(n^2), but the same comment from the bottom of + * rend_config_services() should apply. + * + * b) We only compare directory paths as strings, so we can't + * detect two distinct paths that specify the same directory + * (which can arise from symlinks, case-insensitivity, bind + * mounts, etc.). + * + * It also can't detect that two separate Tor instances are trying + * to use the same HiddenServiceDir; for that, we would need a + * lock file. But this is enough to detect a simple mistake that + * at least one person has actually made. */ + SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) { + if (!strcmp(s->config.directory_path, service->config.directory_path)) { + log_warn(LD_REND, "Another hidden service is already configured " + "for directory %s", + escaped(service->config.directory_path)); + ret = 1; + goto end; + } + } SMARTLIST_FOREACH_END(s); + + end: + return ret; +} + +/* Helper function: Given an configuration option name, its value, a minimum + * min and a maxium max, parse the value as a uint64_t. On success, ok is set + * to 1 and ret is the parsed value. On error, ok is set to 0 and ret must be + * ignored. This function logs both on error and success. */ +static uint64_t +helper_parse_uint64(const char *opt, const char *value, uint64_t min, + uint64_t max, int *ok) +{ + uint64_t ret = 0; + + tor_assert(opt); + tor_assert(value); + tor_assert(ok); + + *ok = 0; + ret = tor_parse_uint64(value, 10, min, max, ok, NULL); + if (!*ok) { + log_warn(LD_CONFIG, "%s must be between %" PRIu64 " and %"PRIu64 + ", not %s.", + opt, min, max, value); + goto err; + } + log_info(LD_CONFIG, "%s was parsed to %" PRIu64, opt, ret); + err: + return ret; +} + +/* Return true iff the given options starting at line_ for a hidden service + * contains at least one invalid option. Each hidden service option don't + * apply to all versions so this function can find out. The line_ MUST start + * right after the HiddenServiceDir line of this service. + * + * This is mainly for usability so we can inform the user of any invalid + * option for the hidden service version instead of silently ignoring. */ +static int +config_has_invalid_options(const config_line_t *line_, + const hs_service_t *service) +{ + int ret = 0; + const char **optlist; + const config_line_t *line; + + tor_assert(service); + tor_assert(service->config.version <= HS_VERSION_MAX); + + /* List of options that a v3 service doesn't support thus must exclude from + * its configuration. */ + const char *opts_exclude_v3[] = { + "HiddenServiceAuthorizeClient", + NULL /* End marker. */ + }; + + /* Defining the size explicitly allows us to take advantage of the compiler + * which warns us if we ever bump the max version but forget to grow this + * array. The plus one is because we have a version 0 :). */ + struct { + const char **list; + } exclude_lists[HS_VERSION_MAX + 1] = { + { NULL }, /* v0. */ + { NULL }, /* v1. */ + { NULL }, /* v2 */ + { opts_exclude_v3 }, /* v3. */ + }; + + optlist = exclude_lists[service->config.version].list; + if (optlist == NULL) { + /* No exclude options to look at for this version. */ + goto end; + } + for (int i = 0; optlist[i]; i++) { + const char *opt = optlist[i]; + for (line = line_; line; line = line->next) { + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + goto end; + } + if (!strcasecmp(line->key, opt)) { + log_warn(LD_CONFIG, "Hidden service option %s is incompatible with " + "version %" PRIu32 " of service in %s", + opt, service->config.version, + service->config.directory_path); + ret = 1; + /* Continue the loop so we can find all possible options. */ + continue; + } + } + } + end: + return ret; +} + +/* Validate service configuration. This is used when loading the configuration + * and once we've setup a service object, it's config object is passed to this + * function for further validation. This does not validate service key + * material. Return 0 if valid else -1 if invalid. */ +static int +config_validate_service(const hs_service_config_t *config) +{ + tor_assert(config); + + /* Amount of ports validation. */ + if (!config->ports || smartlist_len(config->ports) == 0) { + log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", + escaped(config->directory_path)); + goto invalid; + } + + /* Valid. */ + return 0; + invalid: + return -1; +} + +/* Configuration funcion for a version 3 service. The line_ must be pointing + * to the directive directly after a HiddenServiceDir. That way, when hitting + * the next HiddenServiceDir line or reaching the end of the list of lines, we + * know that we have to stop looking for more options. The given service + * object must be already allocated and passed through + * config_generic_service() prior to calling this function. + * + * Return 0 on success else a negative value. */ +static int +config_service_v3(const config_line_t *line_, + hs_service_config_t *config) +{ + int have_num_ip = 0; + const char *dup_opt_seen = NULL; + const config_line_t *line; + + tor_assert(config); + + for (line = line_; line; line = line->next) { + int ok = 0; + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* We just hit the next hidden service, stop right now. */ + break; + } + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + config->num_intro_points = + (unsigned int) helper_parse_uint64(line->key, line->value, + NUM_INTRO_POINTS_DEFAULT, + HS_CONFIG_V3_MAX_INTRO_POINTS, + &ok); + if (!ok || have_num_ip) { + if (have_num_ip) + dup_opt_seen = line->key; + goto err; + } + have_num_ip = 1; + continue; + } + } + + /* We do not load the key material for the service at this stage. This is + * done later once tor can confirm that it is in a running state. */ + + /* We are about to return a fully configured service so do one last pass of + * validation at it. */ + if (config_validate_service(config) < 0) { + goto err; + } + + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given options in line_ and options. This is + * called for any service regardless of its version which means that all + * directives in this function are generic to any service version. This + * function will also check the validity of the service directory path. + * + * The line_ must be pointing to the directive directly after a + * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or + * reaching the end of the list of lines, we know that we have to stop looking + * for more options. + * + * Return 0 on success else -1. */ +static int +config_generic_service(const config_line_t *line_, + const or_options_t *options, + hs_service_t *service) +{ + int dir_seen = 0; + const config_line_t *line; + hs_service_config_t *config; + /* If this is set, we've seen a duplicate of this option. Keep the string + * so we can log the directive. */ + const char *dup_opt_seen = NULL; + /* These variables will tell us if we ever have duplicate. */ + int have_version = 0, have_allow_unknown_ports = 0; + int have_dir_group_read = 0, have_max_streams = 0; + int have_max_streams_close = 0; + + tor_assert(line_); + tor_assert(options); + tor_assert(service); + + /* Makes thing easier. */ + config = &service->config; + + /* The first line starts with HiddenServiceDir so we consider what's next is + * the configuration of the service. */ + for (line = line_; line ; line = line->next) { + int ok = 0; + + /* This indicate that we have a new service to configure. */ + if (!strcasecmp(line->key, "HiddenServiceDir")) { + /* This function only configures one service at a time so if we've + * already seen one, stop right now. */ + if (dir_seen) { + break; + } + /* Ok, we've seen one and we are about to configure it. */ + dir_seen = 1; + config->directory_path = tor_strdup(line->value); + log_info(LD_CONFIG, "HiddenServiceDir=%s. Configuring...", + escaped(config->directory_path)); + continue; + } + if (BUG(!dir_seen)) { + goto err; + } + /* Version of the service. */ + if (!strcasecmp(line->key, "HiddenServiceVersion")) { + service->config.version = + (uint32_t) helper_parse_uint64(line->key, line->value, HS_VERSION_MIN, + HS_VERSION_MAX, &ok); + if (!ok || have_version) { + if (have_version) + dup_opt_seen = line->key; + goto err; + } + have_version = 1; + continue; + } + /* Virtual port. */ + if (!strcasecmp(line->key, "HiddenServicePort")) { + char *err_msg = NULL; + /* XXX: Can we rename this? */ + rend_service_port_config_t *portcfg = + rend_service_parse_port_config(line->value, " ", &err_msg); + if (!portcfg) { + if (err_msg) { + log_warn(LD_CONFIG, "%s", err_msg); + } + tor_free(err_msg); + goto err; + } + tor_assert(!err_msg); + smartlist_add(config->ports, portcfg); + log_info(LD_CONFIG, "HiddenServicePort=%s for %s", + line->value, escaped(config->directory_path)); + continue; + } + /* Do we allow unknown ports. */ + if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { + config->allow_unknown_ports = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_allow_unknown_ports) { + if (have_allow_unknown_ports) + dup_opt_seen = line->key; + goto err; + } + have_allow_unknown_ports = 1; + continue; + } + /* Directory group readable. */ + if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) { + config->dir_group_readable = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_dir_group_read) { + if (have_dir_group_read) + dup_opt_seen = line->key; + goto err; + } + have_dir_group_read = 1; + continue; + } + /* Maximum streams per circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { + config->max_streams_per_rdv_circuit = + helper_parse_uint64(line->key, line->value, 0, + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT, &ok); + if (!ok || have_max_streams) { + if (have_max_streams) + dup_opt_seen = line->key; + goto err; + } + have_max_streams = 1; + continue; + } + /* Maximum amount of streams before we close the circuit. */ + if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { + config->max_streams_close_circuit = + (unsigned int) helper_parse_uint64(line->key, line->value, 0, 1, &ok); + if (!ok || have_max_streams_close) { + if (have_max_streams_close) + dup_opt_seen = line->key; + goto err; + } + have_max_streams_close = 1; + continue; + } + } + + /* Check if we are configured in non anonymous mode meaning every service + * becomes a single onion service. */ + if (rend_service_non_anonymous_mode_enabled(options)) { + config->is_single_onion = 1; + /* We will add support for IPv6-only v3 single onion services in a future + * Tor version. This won't catch "ReachableAddresses reject *4", but that + * option doesn't work anyway. */ + if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) { + log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not " + "supported. Set HiddenServiceSingleHopMode 0 and " + "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1."); + goto err; + } + } + + /* Success */ + return 0; + err: + if (dup_opt_seen) { + log_warn(LD_CONFIG, "Duplicate directive %s.", dup_opt_seen); + } + return -1; +} + +/* Configure a service using the given line and options. This function will + * call the corresponding configuration function for a specific service + * version and validate the service against the other ones. On success, add + * the service to the given list and return 0. On error, nothing is added to + * the list and a negative value is returned. */ +static int +config_service(const config_line_t *line, const or_options_t *options, + smartlist_t *service_list) +{ + int ret; + hs_service_t *service = NULL; + + tor_assert(line); + tor_assert(options); + tor_assert(service_list); + + /* We have a new hidden service. */ + service = hs_service_new(options); + /* We'll configure that service as a generic one and then pass it to a + * specific function according to the configured version number. */ + if (config_generic_service(line, options, service) < 0) { + goto err; + } + tor_assert(service->config.version <= HS_VERSION_MAX); + /* Before we configure the service on a per-version basis, we'll make + * sure that this set of options for a service are valid that is for + * instance an option only for v2 is not used for v3. */ + if (config_has_invalid_options(line->next, service)) { + goto err; + } + /* Check permission on service directory that was just parsed. And this must + * be done regardless of the service version. Do not ask for the directory + * to be created, this is done when the keys are loaded because we could be + * in validation mode right now. */ + if (hs_check_service_private_dir(options->User, + service->config.directory_path, + service->config.dir_group_readable, + 0) < 0) { + goto err; + } + /* Different functions are in charge of specific options for a version. We + * start just after the service directory line so once we hit another + * directory line, the function knows that it has to stop parsing. */ + switch (service->config.version) { + case HS_VERSION_TWO: + ret = rend_config_service(line->next, options, &service->config); + break; + case HS_VERSION_THREE: + ret = config_service_v3(line->next, &service->config); + break; + default: + /* We do validate before if we support the parsed version. */ + tor_assert_nonfatal_unreached(); + goto err; + } + if (ret < 0) { + goto err; + } + /* We'll check if this service can be kept depending on the others + * configured previously. */ + if (service_is_duplicate_in_list(service_list, service)) { + goto err; + } + /* Passes, add it to the given list. */ + smartlist_add(service_list, service); + return 0; + + err: + hs_service_free(service); + return -1; +} + +/* From a set of <b>options</b>, setup every hidden service found. Return 0 on + * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and + * return as normal, but don't actually change the configured services. */ +int +hs_config_service_all(const or_options_t *options, int validate_only) +{ + int dir_option_seen = 0, ret = -1; + const config_line_t *line; + smartlist_t *new_service_list = NULL; + + tor_assert(options); + + /* Newly configured service are put in that list which is then used for + * validation and staging for >= v3. */ + new_service_list = smartlist_new(); + + for (line = options->RendConfigLines; line; line = line->next) { + /* Ignore all directives that aren't the start of a service. */ + if (strcasecmp(line->key, "HiddenServiceDir")) { + if (!dir_option_seen) { + log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", + line->key); + goto err; + } + continue; + } + /* Flag that we've seen a directory directive and we'll use it to make + * sure that the torrc options ordering is actually valid. */ + dir_option_seen = 1; + + /* Try to configure this service now. On success, it will be added to the + * list and validated against the service in that same list. */ + if (config_service(line, options, new_service_list) < 0) { + goto err; + } + } + + /* In non validation mode, we'll stage those services we just successfully + * configured. Service ownership is transfered from the list to the global + * state. If any service is invalid, it will be removed from the list and + * freed. All versions are handled in that function. */ + if (!validate_only) { + stage_services(new_service_list); + } else { + /* We've just validated that we were able to build a clean working list of + * services. We don't need those objects anymore. */ + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, + hs_service_free(s)); + /* For the v2 subsystem, the configuration function adds the service + * object to the staging list and it is transferred in the main list + * through the prunning process. In validation mode, we thus have to purge + * the staging list so it's not kept in memory as valid service. */ + rend_service_free_staging_list(); + } + + /* Success. Note that the service list has no ownership of its content. */ + ret = 0; + goto end; + + err: + SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s)); + + end: + smartlist_free(new_service_list); + /* Tor main should call the free all function on error. */ + return ret; +} + diff --git a/src/or/hs_config.h b/src/or/hs_config.h new file mode 100644 index 0000000000..6cd7aed460 --- /dev/null +++ b/src/or/hs_config.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_config.h + * \brief Header file containing configuration ABI/API for the HS subsytem. + **/ + +#ifndef TOR_HS_CONFIG_H +#define TOR_HS_CONFIG_H + +#include "or.h" + +/* Max value for HiddenServiceMaxStreams */ +#define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 +/* Maximum number of intro points per version 3 services. */ +#define HS_CONFIG_V3_MAX_INTRO_POINTS 20 + +/* API */ + +int hs_config_service_all(const or_options_t *options, int validate_only); + +#endif /* !defined(TOR_HS_CONFIG_H) */ + diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c index fae527b2db..1708866944 100644 --- a/src/or/hs_descriptor.c +++ b/src/or/hs_descriptor.c @@ -55,13 +55,14 @@ /* For unit tests.*/ #define HS_DESCRIPTOR_PRIVATE -#include "hs_descriptor.h" - #include "or.h" #include "ed25519_cert.h" /* Trunnel interface. */ +#include "hs_descriptor.h" +#include "circuitbuild.h" #include "parsecommon.h" #include "rendcache.h" #include "hs_cache.h" +#include "hs_config.h" #include "torcert.h" /* tor_cert_encode_ed22519() */ /* Constant string value used for the descriptor format. */ @@ -77,6 +78,7 @@ #define str_intro_auth_required "intro-auth-required" #define str_single_onion "single-onion-service" #define str_intro_point "introduction-point" +#define str_ip_onion_key "onion-key" #define str_ip_auth_key "auth-key" #define str_ip_enc_key "enc-key" #define str_ip_enc_key_cert "enc-key-cert" @@ -135,6 +137,7 @@ static token_rule_t hs_desc_encrypted_v3_token_table[] = { /* Descriptor ruleset for the introduction points section. */ static token_rule_t hs_desc_intro_point_v3_token_table[] = { T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), + T1N(str_ip_onion_key, R3_INTRO_ONION_KEY, GE(2), OBJ_OK), T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), T1(str_ip_enc_key, R3_INTRO_ENC_KEY, GE(2), OBJ_OK), T1(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERT, ARGS, OBJ_OK), @@ -143,29 +146,6 @@ static token_rule_t hs_desc_intro_point_v3_token_table[] = { END_OF_TABLE }; -/* Free a descriptor intro point object. */ -STATIC void -desc_intro_point_free(hs_desc_intro_point_t *ip) -{ - if (!ip) { - return; - } - if (ip->link_specifiers) { - SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, - ls, tor_free(ls)); - smartlist_free(ip->link_specifiers); - } - tor_cert_free(ip->auth_key_cert); - tor_cert_free(ip->enc_key_cert); - if (ip->legacy.key) { - crypto_pk_free(ip->legacy.key); - } - if (ip->legacy.cert.encoded) { - tor_free(ip->legacy.cert.encoded); - } - tor_free(ip); -} - /* Free the content of the plaintext section of a descriptor. */ STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) @@ -196,7 +176,7 @@ desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) } if (desc->intro_points) { SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, - desc_intro_point_free(ip)); + hs_desc_intro_point_free(ip)); smartlist_free(desc->intro_points); } memwipe(desc, 0, sizeof(*desc)); @@ -255,7 +235,7 @@ build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); offset += sizeof(desc->subcredential); /* Copy revision counter value. */ - set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); + set_uint64(dst + offset, tor_htonll(desc->plaintext_data.revision_counter)); offset += sizeof(uint64_t); tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); } @@ -351,42 +331,10 @@ encode_link_specifiers(const smartlist_t *specs) SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *, spec) { - link_specifier_t *ls = link_specifier_new(); - link_specifier_set_ls_type(ls, spec->type); - - switch (spec->type) { - case LS_IPV4: - link_specifier_set_un_ipv4_addr(ls, - tor_addr_to_ipv4h(&spec->u.ap.addr)); - link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); - /* Four bytes IPv4 and two bytes port. */ - link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + - sizeof(spec->u.ap.port)); - break; - case LS_IPV6: - { - size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); - const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); - uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); - memcpy(ipv6_array, in6_addr, addr_len); - link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); - /* Sixteen bytes IPv6 and two bytes port. */ - link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); - break; - } - case LS_LEGACY_ID: - { - size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); - uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); - memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); - link_specifier_set_ls_len(ls, legacy_id_len); - break; + link_specifier_t *ls = hs_desc_lspec_to_trunnel(spec); + if (ls) { + link_specifier_list_add_spec(lslist, ls); } - default: - tor_assert(0); - } - - link_specifier_list_add_spec(lslist, ls); } SMARTLIST_FOREACH_END(spec); { @@ -478,6 +426,26 @@ encode_enc_key(const hs_desc_intro_point_t *ip) return encoded; } +/* Encode an introduction point onion key. Return a newly allocated string + * with it. On failure, return NULL. */ +static char * +encode_onion_key(const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL; + char key_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + + tor_assert(ip); + + /* Base64 encode the encryption key for the "onion-key" field. */ + if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) { + goto done; + } + tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64); + + done: + return encoded; +} + /* Encode an introduction point object and return a newly allocated string * with it. On failure, return NULL. */ static char * @@ -497,6 +465,16 @@ encode_intro_point(const ed25519_public_key_t *sig_key, tor_free(ls_str); } + /* Onion key encoding. */ + { + char *encoded_onion_key = encode_onion_key(ip); + if (encoded_onion_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_onion_key); + tor_free(encoded_onion_key); + } + /* Authentication key encoding. */ { char *encoded_cert; @@ -987,6 +965,10 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_assert(encoded_out); tor_assert(desc->plaintext_data.version == 3); + if (BUG(desc->subcredential == NULL)) { + goto err; + } + /* Build the non-encrypted values. */ { char *encoded_cert; @@ -1133,6 +1115,15 @@ decode_link_specifiers(const char *encoded) memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls), sizeof(hs_spec->u.legacy_id)); break; + case LS_ED25519_ID: + /* Both are known at compile time so let's make sure they are the same + * else we can copy memory out of bound. */ + tor_assert(link_specifier_getlen_un_ed25519_id(ls) == + sizeof(hs_spec->u.ed25519_id)); + memcpy(hs_spec->u.ed25519_id, + link_specifier_getconstarray_un_ed25519_id(ls), + sizeof(hs_spec->u.ed25519_id)); + break; default: goto err; } @@ -1311,13 +1302,17 @@ encrypted_data_length_is_valid(size_t len) * <b>encrypted_blob_size</b>. Use the descriptor object <b>desc</b> to * generate the right decryption keys; set <b>decrypted_out</b> to the * plaintext. If <b>is_superencrypted_layer</b> is set, this is the outter - * encrypted layer of the descriptor. */ -static size_t -decrypt_desc_layer(const hs_descriptor_t *desc, - const uint8_t *encrypted_blob, - size_t encrypted_blob_size, - int is_superencrypted_layer, - char **decrypted_out) + * encrypted layer of the descriptor. + * + * On any error case, including an empty output, return 0 and set + * *<b>decrypted_out</b> to NULL. + */ +MOCK_IMPL(STATIC size_t, +decrypt_desc_layer,(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out)) { uint8_t *decrypted = NULL; uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN]; @@ -1391,6 +1386,11 @@ decrypt_desc_layer(const hs_descriptor_t *desc, } } + if (result_len == 0) { + /* Treat this as an error, so that somebody will free the output. */ + goto err; + } + /* Make sure to NUL terminate the string. */ decrypted[encrypted_len] = '\0'; *decrypted_out = (char *) decrypted; @@ -1625,6 +1625,50 @@ decode_intro_legacy_key(const directory_token_t *tok, return -1; } +/* Dig into the descriptor <b>tokens</b> to find the onion key we should use + * for this intro point, and set it into <b>onion_key_out</b>. Return 0 if it + * was found and well-formed, otherwise return -1 in case of errors. */ +static int +set_intro_point_onion_key(curve25519_public_key_t *onion_key_out, + const smartlist_t *tokens) +{ + int retval = -1; + smartlist_t *onion_keys = NULL; + + tor_assert(onion_key_out); + + onion_keys = find_all_by_keyword(tokens, R3_INTRO_ONION_KEY); + if (!onion_keys) { + log_warn(LD_REND, "Descriptor did not contain intro onion keys"); + goto err; + } + + SMARTLIST_FOREACH_BEGIN(onion_keys, directory_token_t *, tok) { + /* This field is using GE(2) so for possible forward compatibility, we + * accept more fields but must be at least 2. */ + tor_assert(tok->n_args >= 2); + + /* Try to find an ntor key, it's the only recognized type right now */ + if (!strcmp(tok->args[0], "ntor")) { + if (curve25519_public_from_base64(onion_key_out, tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor onion-key is invalid"); + goto err; + } + /* Got the onion key! Set the appropriate retval */ + retval = 0; + } + } SMARTLIST_FOREACH_END(tok); + + /* Log an error if we didn't find it :( */ + if (retval < 0) { + log_warn(LD_REND, "Descriptor did not contain ntor onion keys"); + } + + err: + smartlist_free(onion_keys); + return retval; +} + /* Given the start of a section and the end of it, decode a single * introduction point from that section. Return a newly allocated introduction * point object containing the decoded data. Return NULL if the section can't @@ -1650,17 +1694,24 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Ok we seem to have a well formed section containing enough tokens to * parse. Allocate our IP object and try to populate it. */ - ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t)); + ip = hs_desc_intro_point_new(); /* "introduction-point" SP link-specifiers NL */ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT); tor_assert(tok->n_args == 1); + /* Our constructor creates this list by default so free it. */ + smartlist_free(ip->link_specifiers); ip->link_specifiers = decode_link_specifiers(tok->args[0]); if (!ip->link_specifiers) { log_warn(LD_REND, "Introduction point has invalid link specifiers"); goto err; } + /* "onion-key" SP ntor SP key NL */ + if (set_intro_point_onion_key(&ip->onion_key, tokens) < 0) { + goto err; + } + /* "auth-key" NL certificate NL */ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY); tor_assert(tok->object_body); @@ -1732,7 +1783,7 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) goto done; err: - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; done: @@ -1747,18 +1798,13 @@ decode_introduction_point(const hs_descriptor_t *desc, const char *start) /* Given a descriptor string at <b>data</b>, decode all possible introduction * points that we can find. Add the introduction point object to desc_enc as we - * find them. Return 0 on success. - * - * On error, a negative value is returned. It is possible that some intro - * point object have been added to the desc_enc, they should be considered - * invalid. One single bad encoded introduction point will make this function - * return an error. */ -STATIC int + * find them. This function can't fail and it is possible that zero + * introduction points can be decoded. */ +static void decode_intro_points(const hs_descriptor_t *desc, hs_desc_encrypted_data_t *desc_enc, const char *data) { - int retval = -1; smartlist_t *chunked_desc = smartlist_new(); smartlist_t *intro_points = smartlist_new(); @@ -1799,22 +1845,19 @@ decode_intro_points(const hs_descriptor_t *desc, SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) { hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point); if (!ip) { - /* Malformed introduction point section. Stop right away, this - * descriptor shouldn't be used. */ - goto err; + /* Malformed introduction point section. We'll ignore this introduction + * point and continue parsing. New or unknown fields are possible for + * forward compatibility. */ + continue; } smartlist_add(desc_enc->intro_points, ip); } SMARTLIST_FOREACH_END(intro_point); done: - retval = 0; - - err: SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a)); smartlist_free(chunked_desc); SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a)); smartlist_free(intro_points); - return retval; } /* Return 1 iff the given base64 encoded signature in b64_sig from the encoded * descriptor in encoded_desc validates the descriptor content. */ @@ -2041,14 +2084,14 @@ desc_decode_encrypted_v3(const hs_descriptor_t *desc, /* Initialize the descriptor's introduction point list before we start * decoding. Having 0 intro point is valid. Then decode them all. */ desc_encrypted_out->intro_points = smartlist_new(); - if (decode_intro_points(desc, desc_encrypted_out, message) < 0) { - goto err; - } + decode_intro_points(desc, desc_encrypted_out, message); + /* Validation of maximum introduction points allowed. */ - if (smartlist_len(desc_encrypted_out->intro_points) > MAX_INTRO_POINTS) { + if (smartlist_len(desc_encrypted_out->intro_points) > + HS_CONFIG_V3_MAX_INTRO_POINTS) { log_warn(LD_REND, "Service descriptor contains too many introduction " "points. Maximum allowed is %d but we have %d", - MAX_INTRO_POINTS, + HS_CONFIG_V3_MAX_INTRO_POINTS, smartlist_len(desc_encrypted_out->intro_points)); goto err; } @@ -2223,7 +2266,7 @@ hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, hs_descriptor_t **desc_out) { - int ret; + int ret = -1; hs_descriptor_t *desc; tor_assert(encoded); @@ -2231,10 +2274,13 @@ hs_desc_decode_descriptor(const char *encoded, desc = tor_malloc_zero(sizeof(hs_descriptor_t)); /* Subcredentials are optional. */ - if (subcredential) { - memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + if (BUG(!subcredential)) { + log_warn(LD_GENERAL, "Tried to decrypt without subcred. Impossible!"); + goto err; } + memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); if (ret < 0) { goto err; @@ -2280,10 +2326,10 @@ static int * * Return 0 on success and encoded_out is a valid pointer. On error, -1 is * returned and encoded_out is set to NULL. */ -int -hs_desc_encode_descriptor(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, - char **encoded_out) +MOCK_IMPL(int, +hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)) { int ret = -1; uint32_t version; @@ -2360,3 +2406,197 @@ hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) data->superencrypted_blob_size); } +/* Return the size in bytes of the given encrypted data object. Used by OOM + * subsystem. */ +static size_t +hs_desc_encrypted_obj_size(const hs_desc_encrypted_data_t *data) +{ + tor_assert(data); + size_t intro_size = 0; + if (data->intro_auth_types) { + intro_size += + smartlist_len(data->intro_auth_types) * sizeof(intro_auth_types); + } + if (data->intro_points) { + /* XXX could follow pointers here and get more accurate size */ + intro_size += + smartlist_len(data->intro_points) * sizeof(hs_desc_intro_point_t); + } + + return sizeof(*data) + intro_size; +} + +/* Return the size in bytes of the given descriptor object. Used by OOM + * subsystem. */ + size_t +hs_desc_obj_size(const hs_descriptor_t *data) +{ + tor_assert(data); + return (hs_desc_plaintext_obj_size(&data->plaintext_data) + + hs_desc_encrypted_obj_size(&data->encrypted_data) + + sizeof(data->subcredential)); +} + +/* Return a newly allocated descriptor intro point. */ +hs_desc_intro_point_t * +hs_desc_intro_point_new(void) +{ + hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); + ip->link_specifiers = smartlist_new(); + return ip; +} + +/* Free a descriptor intro point object. */ +void +hs_desc_intro_point_free(hs_desc_intro_point_t *ip) +{ + if (ip == NULL) { + return; + } + if (ip->link_specifiers) { + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, + ls, hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + } + tor_cert_free(ip->auth_key_cert); + tor_cert_free(ip->enc_key_cert); + crypto_pk_free(ip->legacy.key); + tor_free(ip->legacy.cert.encoded); + tor_free(ip); +} + +/* Free the given descriptor link specifier. */ +void +hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls) +{ + if (ls == NULL) { + return; + } + tor_free(ls); +} + +/* Return a newly allocated descriptor link specifier using the given extend + * info and requested type. Return NULL on error. */ +hs_desc_link_specifier_t * +hs_desc_link_specifier_new(const extend_info_t *info, uint8_t type) +{ + hs_desc_link_specifier_t *ls = NULL; + + tor_assert(info); + + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = type; + switch (ls->type) { + case LS_IPV4: + if (info->addr.family != AF_INET) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_IPV6: + if (info->addr.family != AF_INET6) { + goto err; + } + tor_addr_copy(&ls->u.ap.addr, &info->addr); + ls->u.ap.port = info->port; + break; + case LS_LEGACY_ID: + /* Bug out if the identity digest is not set */ + if (BUG(tor_mem_is_zero(info->identity_digest, + sizeof(info->identity_digest)))) { + goto err; + } + memcpy(ls->u.legacy_id, info->identity_digest, sizeof(ls->u.legacy_id)); + break; + case LS_ED25519_ID: + /* ed25519 keys are optional for intro points */ + if (ed25519_public_key_is_zero(&info->ed_identity)) { + goto err; + } + memcpy(ls->u.ed25519_id, info->ed_identity.pubkey, + sizeof(ls->u.ed25519_id)); + break; + default: + /* Unknown type is code flow error. */ + tor_assert(0); + } + + return ls; + err: + tor_free(ls); + return NULL; +} + +/* From the given descriptor, remove and free every introduction point. */ +void +hs_descriptor_clear_intro_points(hs_descriptor_t *desc) +{ + smartlist_t *ips; + + tor_assert(desc); + + ips = desc->encrypted_data.intro_points; + if (ips) { + SMARTLIST_FOREACH(ips, hs_desc_intro_point_t *, + ip, hs_desc_intro_point_free(ip)); + smartlist_clear(ips); + } +} + +/* From a descriptor link specifier object spec, returned a newly allocated + * link specifier object that is the encoded representation of spec. Return + * NULL on error. */ +link_specifier_t * +hs_desc_lspec_to_trunnel(const hs_desc_link_specifier_t *spec) +{ + tor_assert(spec); + + link_specifier_t *ls = link_specifier_new(); + link_specifier_set_ls_type(ls, spec->type); + + switch (spec->type) { + case LS_IPV4: + link_specifier_set_un_ipv4_addr(ls, + tor_addr_to_ipv4h(&spec->u.ap.addr)); + link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + + sizeof(spec->u.ap.port)); + break; + case LS_IPV6: + { + size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); + const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); + uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); + memcpy(ipv6_array, in6_addr, addr_len); + link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); + /* Sixteen bytes IPv6 and two bytes port. */ + link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); + break; + } + case LS_LEGACY_ID: + { + size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); + uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); + memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); + link_specifier_set_ls_len(ls, legacy_id_len); + break; + } + case LS_ED25519_ID: + { + size_t ed25519_id_len = link_specifier_getlen_un_ed25519_id(ls); + uint8_t *ed25519_id_array = link_specifier_getarray_un_ed25519_id(ls); + memcpy(ed25519_id_array, spec->u.ed25519_id, ed25519_id_len); + link_specifier_set_ls_len(ls, ed25519_id_len); + break; + } + default: + tor_assert_nonfatal_unreached(); + link_specifier_free(ls); + ls = NULL; + } + + return ls; +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 136477ae3a..52bec8e244 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -18,17 +18,26 @@ #include "crypto_ed25519.h" #include "torcert.h" +/* Trunnel */ +struct link_specifier_t; + /* The earliest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 /* The latest descriptor format version we support. */ #define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 +/* Default lifetime of a descriptor in seconds. The valus is set at 3 hours + * which is 180 minutes or 10800 seconds. */ +#define HS_DESC_DEFAULT_LIFETIME (3 * 60 * 60) /* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours * which is 720 minutes or 43200 seconds. */ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) /* Lifetime of certificate in the descriptor. This defines the lifetime of the - * descriptor signing key and the cross certification cert of that key. */ -#define HS_DESC_CERT_LIFETIME (24 * 60 * 60) + * descriptor signing key and the cross certification cert of that key. It is + * set to 54 hours because a descriptor can be around for 48 hours and because + * consensuses are used after the hour, add an extra 6 hours to give some time + * for the service to stop using it. */ +#define HS_DESC_CERT_LIFETIME (54 * 60 * 60) /* Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 /* Length of the secret input needed for the KDF construction which derives @@ -65,12 +74,14 @@ typedef struct hs_desc_link_specifier_t { * specification. */ uint8_t type; - /* It's either an address/port or a legacy identity fingerprint. */ + /* It must be one of these types, can't be more than one. */ union { /* IP address and port of the relay use to extend. */ tor_addr_port_t ap; /* Legacy identity. A 20-byte SHA1 identity fingerprint. */ uint8_t legacy_id[DIGEST_LEN]; + /* ed25519 identity. A 32-byte key. */ + uint8_t ed25519_id[ED25519_PUBKEY_LEN]; } u; } hs_desc_link_specifier_t; @@ -80,6 +91,10 @@ typedef struct hs_desc_intro_point_t { * contains hs_desc_link_specifier_t object. It MUST have at least one. */ smartlist_t *link_specifiers; + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + /* Authentication key used to establish the introduction point circuit and * cross-certifies the blinded public key for the replica thus signed by * the blinded key and in turn signs it. */ @@ -197,9 +212,15 @@ void hs_descriptor_free(hs_descriptor_t *desc); void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc); void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc); -int hs_desc_encode_descriptor(const hs_descriptor_t *desc, - const ed25519_keypair_t *signing_kp, - char **encoded_out); +void hs_desc_link_specifier_free(hs_desc_link_specifier_t *ls); +hs_desc_link_specifier_t *hs_desc_link_specifier_new( + const extend_info_t *info, uint8_t type); +void hs_descriptor_clear_intro_points(hs_descriptor_t *desc); + +MOCK_DECL(int, + hs_desc_encode_descriptor,(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out)); int hs_desc_decode_descriptor(const char *encoded, const uint8_t *subcredential, @@ -209,8 +230,15 @@ int hs_desc_decode_plaintext(const char *encoded, int hs_desc_decode_encrypted(const hs_descriptor_t *desc, hs_desc_encrypted_data_t *desc_out); +size_t hs_desc_obj_size(const hs_descriptor_t *data); size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data); +hs_desc_intro_point_t *hs_desc_intro_point_new(void); +void hs_desc_intro_point_free(hs_desc_intro_point_t *ip); + +link_specifier_t *hs_desc_lspec_to_trunnel( + const hs_desc_link_specifier_t *spec); + #ifdef HS_DESCRIPTOR_PRIVATE /* Encoding. */ @@ -223,21 +251,23 @@ STATIC smartlist_t *decode_link_specifiers(const char *encoded); STATIC hs_desc_intro_point_t *decode_introduction_point( const hs_descriptor_t *desc, const char *text); -STATIC int decode_intro_points(const hs_descriptor_t *desc, - hs_desc_encrypted_data_t *desc_enc, - const char *data); STATIC int encrypted_data_length_is_valid(size_t len); STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type); STATIC int desc_sig_is_valid(const char *b64_sig, const ed25519_public_key_t *signing_pubkey, const char *encoded_desc, size_t encoded_len); -STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip); STATIC size_t decode_superencrypted(const char *message, size_t message_len, uint8_t **encrypted_out); STATIC void desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc); -#endif /* HS_DESCRIPTOR_PRIVATE */ +MOCK_DECL(STATIC size_t, decrypt_desc_layer,(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out)); + +#endif /* defined(HS_DESCRIPTOR_PRIVATE) */ -#endif /* TOR_HS_DESCRIPTOR_H */ +#endif /* !defined(TOR_HS_DESCRIPTOR_H) */ diff --git a/src/or/hs_ident.c b/src/or/hs_ident.c new file mode 100644 index 0000000000..b0e4e36a9b --- /dev/null +++ b/src/or/hs_ident.c @@ -0,0 +1,126 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.c + * \brief Contains circuit and connection identifier code for the whole HS + * subsytem. + **/ + +#include "hs_ident.h" + +/* Return a newly allocated circuit identifier. The given public key is copied + * identity_pk into the identifier. */ +hs_ident_circuit_t * +hs_ident_circuit_new(const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type) +{ + tor_assert(circuit_type == HS_IDENT_CIRCUIT_INTRO || + circuit_type == HS_IDENT_CIRCUIT_RENDEZVOUS); + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ident->circuit_type = circuit_type; + return ident; +} + +/* Free the given circuit identifier. */ +void +hs_ident_circuit_free(hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_circuit_t)); + tor_free(ident); +} + +/* For a given circuit identifier src, return a newly allocated copy of it. + * This can't fail. */ +hs_ident_circuit_t * +hs_ident_circuit_dup(const hs_ident_circuit_t *src) +{ + hs_ident_circuit_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* For a given directory connection identifier src, return a newly allocated + * copy of it. This can't fail. */ +hs_ident_dir_conn_t * +hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src) +{ + hs_ident_dir_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + memcpy(ident, src, sizeof(*ident)); + return ident; +} + +/* Free the given directory connection identifier. */ +void +hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_dir_conn_t)); + tor_free(ident); +} + +/* Initialized the allocated ident object with identity_pk and blinded_pk. + * None of them can be NULL since a valid directory connection identifier must + * have all fields set. */ +void +hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + hs_ident_dir_conn_t *ident) +{ + tor_assert(identity_pk); + tor_assert(blinded_pk); + tor_assert(ident); + + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + ed25519_pubkey_copy(&ident->blinded_pk, blinded_pk); +} + +/* Return a newly allocated edge connection identifier. The given public key + * identity_pk is copied into the identifier. */ +hs_ident_edge_conn_t * +hs_ident_edge_conn_new(const ed25519_public_key_t *identity_pk) +{ + hs_ident_edge_conn_t *ident = tor_malloc_zero(sizeof(*ident)); + ed25519_pubkey_copy(&ident->identity_pk, identity_pk); + return ident; +} + +/* Free the given edge connection identifier. */ +void +hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident) +{ + if (ident == NULL) { + return; + } + memwipe(ident, 0, sizeof(hs_ident_edge_conn_t)); + tor_free(ident); +} + +/* Return true if the given ident is valid for an introduction circuit. */ +int +hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident) +{ + if (ident == NULL) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->identity_pk)) { + goto invalid; + } + + if (ed25519_public_key_is_zero(&ident->intro_auth_pk)) { + goto invalid; + } + + /* Valid. */ + return 1; + invalid: + return 0; +} + diff --git a/src/or/hs_ident.h b/src/or/hs_ident.h new file mode 100644 index 0000000000..03150d25ea --- /dev/null +++ b/src/or/hs_ident.h @@ -0,0 +1,141 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ident.h + * \brief Header file containing circuit and connection identifier data for + * the whole HS subsytem. + * + * \details + * This interface is used to uniquely identify a hidden service on a circuit + * or connection using the service identity public key. Once the circuit or + * connection subsystem calls in the hidden service one, we use those + * identifiers to lookup the corresponding objects like service, intro point + * and descriptor. + * + * Furthermore, the circuit identifier holds cryptographic material needed for + * the e2e encryption on the rendezvous circuit which is set once the + * rendezvous circuit has opened and ready to be used. + **/ + +#ifndef TOR_HS_IDENT_H +#define TOR_HS_IDENT_H + +#include "crypto.h" +#include "crypto_ed25519.h" + +#include "hs_common.h" + +/* Length of the rendezvous cookie that is used to connect circuits at the + * rendezvous point. */ +#define HS_REND_COOKIE_LEN DIGEST_LEN + +/* Type of circuit an hs_ident_t object is associated with. */ +typedef enum { + HS_IDENT_CIRCUIT_INTRO = 1, + HS_IDENT_CIRCUIT_RENDEZVOUS = 2, +} hs_ident_circuit_type_t; + +/* Client and service side circuit identifier that is used for hidden service + * circuit establishment. Not all fields contain data, it depends on the + * circuit purpose. This is attached to an origin_circuit_t. All fields are + * used by both client and service. */ +typedef struct hs_ident_circuit_t { + /* (All circuit) The public key used to uniquely identify the service. It is + * the one found in the onion address. */ + ed25519_public_key_t identity_pk; + + /* (All circuit) The type of circuit this identifier is attached to. + * Accessors of the fields in this object assert non fatal on this circuit + * type. In other words, if a rendezvous field is being accessed, the + * circuit type MUST BE of type HS_IDENT_CIRCUIT_RENDEZVOUS. This value is + * set when an object is initialized in its constructor. */ + hs_ident_circuit_type_t circuit_type; + + /* (All circuit) Introduction point authentication key. It's also needed on + * the rendezvous circuit for the ntor handshake. It's used as the unique key + * of the introduction point so it should not be shared between multiple + * intro points. */ + ed25519_public_key_t intro_auth_pk; + + /* (Only client rendezvous circuit) Introduction point encryption public + * key. We keep it in the rendezvous identifier for the ntor handshake. */ + curve25519_public_key_t intro_enc_pk; + + /* (Only rendezvous circuit) Rendezvous cookie sent from the client to the + * service with an INTRODUCE1 cell and used by the service in an + * RENDEZVOUS1 cell. */ + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + + /* (Only service rendezvous circuit) The HANDSHAKE_INFO needed in the + * RENDEZVOUS1 cell of the service. The construction is as follows: + * SERVER_PK [32 bytes] + * AUTH_MAC [32 bytes] + */ + uint8_t rendezvous_handshake_info[CURVE25519_PUBKEY_LEN + DIGEST256_LEN]; + + /* (Only client rendezvous circuit) Client ephemeral keypair needed for the + * e2e encryption with the service. */ + curve25519_keypair_t rendezvous_client_kp; + + /* (Only rendezvous circuit) The NTOR_KEY_SEED needed for key derivation for + * the e2e encryption with the client on the circuit. */ + uint8_t rendezvous_ntor_key_seed[DIGEST256_LEN]; + + /* (Only rendezvous circuit) Number of streams associated with this + * rendezvous circuit. We track this because there is a check on a maximum + * value. */ + uint64_t num_rdv_streams; +} hs_ident_circuit_t; + +/* Client and service side directory connection identifier used for a + * directory connection to identify which service is being queried. This is + * attached to a dir_connection_t. */ +typedef struct hs_ident_dir_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* The blinded public key used to uniquely identify the descriptor that this + * directory connection identifier is for. Only used by the service-side code + * to fine control descriptor uploads. */ + ed25519_public_key_t blinded_pk; + + /* XXX: Client authorization. */ +} hs_ident_dir_conn_t; + +/* Client and service side edge connection identifier used for an edge + * connection to identify which service is being queried. This is attached to + * a edge_connection_t. */ +typedef struct hs_ident_edge_conn_t { + /* The public key used to uniquely identify the service. It is the one found + * in the onion address. */ + ed25519_public_key_t identity_pk; + + /* XXX: Client authorization. */ +} hs_ident_edge_conn_t; + +/* Circuit identifier API. */ +hs_ident_circuit_t *hs_ident_circuit_new( + const ed25519_public_key_t *identity_pk, + hs_ident_circuit_type_t circuit_type); +void hs_ident_circuit_free(hs_ident_circuit_t *ident); +hs_ident_circuit_t *hs_ident_circuit_dup(const hs_ident_circuit_t *src); + +/* Directory connection identifier API. */ +hs_ident_dir_conn_t *hs_ident_dir_conn_dup(const hs_ident_dir_conn_t *src); +void hs_ident_dir_conn_free(hs_ident_dir_conn_t *ident); +void hs_ident_dir_conn_init(const ed25519_public_key_t *identity_pk, + const ed25519_public_key_t *blinded_pk, + hs_ident_dir_conn_t *ident); + +/* Edge connection identifier API. */ +hs_ident_edge_conn_t *hs_ident_edge_conn_new( + const ed25519_public_key_t *identity_pk); +void hs_ident_edge_conn_free(hs_ident_edge_conn_t *ident); + +/* Validators */ +int hs_ident_intro_circ_is_valid(const hs_ident_circuit_t *ident); + +#endif /* !defined(TOR_HS_IDENT_H) */ + diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c index 06f8a2c3ad..4a5202f614 100644 --- a/src/or/hs_intropoint.c +++ b/src/or/hs_intropoint.c @@ -24,6 +24,7 @@ #include "hs/cell_introduce1.h" #include "hs_circuitmap.h" +#include "hs_descriptor.h" #include "hs_intropoint.h" #include "hs_common.h" @@ -595,3 +596,18 @@ hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request, return -1; } +/* Clear memory allocated by the given intropoint object ip (but don't free the + * object itself). */ +void +hs_intropoint_clear(hs_intropoint_t *ip) +{ + if (ip == NULL) { + return; + } + tor_cert_free(ip->auth_key_cert); + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, ls, + hs_desc_link_specifier_free(ls)); + smartlist_free(ip->link_specifiers); + memset(ip, 0, sizeof(hs_intropoint_t)); +} + diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h index 163ed810e7..749d1530e1 100644 --- a/src/or/hs_intropoint.h +++ b/src/or/hs_intropoint.h @@ -9,12 +9,15 @@ #ifndef TOR_HS_INTRO_H #define TOR_HS_INTRO_H +#include "crypto_curve25519.h" +#include "torcert.h" + /* Authentication key type in an ESTABLISH_INTRO cell. */ -enum hs_intro_auth_key_type { +typedef enum { HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00, HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01, HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02, -}; +} hs_intro_auth_key_type_t; /* INTRODUCE_ACK status code. */ typedef enum { @@ -24,6 +27,18 @@ typedef enum { HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003, } hs_intro_ack_status_t; +/* Object containing introduction point common data between the service and + * the client side. */ +typedef struct hs_intropoint_t { + /* Does this intro point only supports legacy ID ?. */ + unsigned int is_only_legacy : 1; + + /* Authentication key certificate from the descriptor. */ + tor_cert_t *auth_key_cert; + /* A list of link specifier. */ + smartlist_t *link_specifiers; +} hs_intropoint_t; + int hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request, size_t request_len); @@ -35,6 +50,9 @@ MOCK_DECL(int, hs_intro_send_intro_established_cell,(or_circuit_t *circ)); /* also used by rendservice.c */ int hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ); +hs_intropoint_t *hs_intro_new(void); +void hs_intropoint_clear(hs_intropoint_t *ip); + #ifdef HS_INTROPOINT_PRIVATE #include "hs/cell_establish_intro.h" @@ -55,7 +73,7 @@ STATIC int handle_introduce1(or_circuit_t *client_circ, STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell); STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ); -#endif /* HS_INTROPOINT_PRIVATE */ +#endif /* defined(HS_INTROPOINT_PRIVATE) */ -#endif /* TOR_HS_INTRO_H */ +#endif /* !defined(TOR_HS_INTRO_H) */ diff --git a/src/or/hs_ntor.c b/src/or/hs_ntor.c index 119899817e..a416bc46c3 100644 --- a/src/or/hs_ntor.c +++ b/src/or/hs_ntor.c @@ -578,49 +578,41 @@ hs_ntor_client_rendezvous2_mac_is_good( /* Input length to KDF for key expansion */ #define NTOR_KEY_EXPANSION_KDF_INPUT_LEN (DIGEST256_LEN + M_HSEXPAND_LEN) -/* Output length of KDF for key expansion */ -#define NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN (DIGEST256_LEN*3+CIPHER256_KEY_LEN*2) - -/** Given the rendezvous key material in <b>hs_ntor_rend_cell_keys</b>, do the - * circuit key expansion as specified by section '4.2.1. Key expansion' and - * return a hs_ntor_rend_circuit_keys_t structure with the computed keys. */ -hs_ntor_rend_circuit_keys_t * -hs_ntor_circuit_key_expansion( - const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys) + +/** Given the rendezvous key seed in <b>ntor_key_seed</b> (of size + * DIGEST256_LEN), do the circuit key expansion as specified by section + * '4.2.1. Key expansion' and place the keys in <b>keys_out</b> (which must be + * of size HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN). + * + * Return 0 if things went well, else return -1. */ +int +hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, size_t seed_len, + uint8_t *keys_out, size_t keys_out_len) { uint8_t *ptr; uint8_t kdf_input[NTOR_KEY_EXPANSION_KDF_INPUT_LEN]; - uint8_t keys[NTOR_KEY_EXPANSION_KDF_OUTPUT_LEN]; crypto_xof_t *xof; - hs_ntor_rend_circuit_keys_t *rend_circuit_keys = NULL; + + /* Sanity checks on lengths to make sure we are good */ + if (BUG(seed_len != DIGEST256_LEN)) { + return -1; + } + if (BUG(keys_out_len != HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN)) { + return -1; + } /* Let's build the input to the KDF */ ptr = kdf_input; - APPEND(ptr, hs_ntor_rend_cell_keys->ntor_key_seed, DIGEST256_LEN); + APPEND(ptr, ntor_key_seed, DIGEST256_LEN); APPEND(ptr, M_HSEXPAND, strlen(M_HSEXPAND)); tor_assert(ptr == kdf_input + sizeof(kdf_input)); /* Generate the keys */ xof = crypto_xof_new(); crypto_xof_add_bytes(xof, kdf_input, sizeof(kdf_input)); - crypto_xof_squeeze_bytes(xof, keys, sizeof(keys)); + crypto_xof_squeeze_bytes(xof, keys_out, HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN); crypto_xof_free(xof); - /* Generate keys structure and assign keys to it */ - rend_circuit_keys = tor_malloc_zero(sizeof(hs_ntor_rend_circuit_keys_t)); - ptr = keys; - memcpy(rend_circuit_keys->KH, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN;; - memcpy(rend_circuit_keys->Df, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN; - memcpy(rend_circuit_keys->Db, ptr, DIGEST256_LEN); - ptr += DIGEST256_LEN; - memcpy(rend_circuit_keys->Kf, ptr, CIPHER256_KEY_LEN); - ptr += CIPHER256_KEY_LEN; - memcpy(rend_circuit_keys->Kb, ptr, CIPHER256_KEY_LEN); - ptr += CIPHER256_KEY_LEN; - tor_assert(ptr == keys + sizeof(keys)); - - return rend_circuit_keys; + return 0; } diff --git a/src/or/hs_ntor.h b/src/or/hs_ntor.h index cd75f46a4c..77e544a130 100644 --- a/src/or/hs_ntor.h +++ b/src/or/hs_ntor.h @@ -6,6 +6,10 @@ #include "or.h" +/* Output length of KDF for key expansion */ +#define HS_NTOR_KEY_EXPANSION_KDF_OUT_LEN \ + (DIGEST256_LEN*2 + CIPHER256_KEY_LEN*2) + /* Key material needed to encode/decode INTRODUCE1 cells */ typedef struct { /* Key used for encryption of encrypted INTRODUCE1 blob */ @@ -23,21 +27,6 @@ typedef struct { uint8_t ntor_key_seed[DIGEST256_LEN]; } hs_ntor_rend_cell_keys_t; -/* Key material resulting from key expansion as detailed in section "4.2.1. Key - * expansion" of rend-spec-ng.txt. */ -typedef struct { - /* Per-circuit key material used in ESTABLISH_INTRO cell */ - uint8_t KH[DIGEST256_LEN]; - /* Authentication key for outgoing RELAY cells */ - uint8_t Df[DIGEST256_LEN]; - /* Authentication key for incoming RELAY cells */ - uint8_t Db[DIGEST256_LEN]; - /* Encryption key for outgoing RELAY cells */ - uint8_t Kf[CIPHER256_KEY_LEN]; - /* Decryption key for incoming RELAY cells */ - uint8_t Kb[CIPHER256_KEY_LEN]; -} hs_ntor_rend_circuit_keys_t; - int hs_ntor_client_get_introduce1_keys( const ed25519_public_key_t *intro_auth_pubkey, const curve25519_public_key_t *intro_enc_pubkey, @@ -66,12 +55,13 @@ int hs_ntor_service_get_rendezvous1_keys( const curve25519_public_key_t *client_ephemeral_enc_pubkey, hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys_out); -hs_ntor_rend_circuit_keys_t *hs_ntor_circuit_key_expansion( - const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys); +int hs_ntor_circuit_key_expansion(const uint8_t *ntor_key_seed, + size_t seed_len, + uint8_t *keys_out, size_t keys_out_len); int hs_ntor_client_rendezvous2_mac_is_good( const hs_ntor_rend_cell_keys_t *hs_ntor_rend_cell_keys, const uint8_t *rcvd_mac); -#endif +#endif /* !defined(TOR_HS_NTOR_H) */ diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 205ef11c92..dd836bf842 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -6,169 +6,3373 @@ * \brief Implement next generation hidden service functionality **/ +#define HS_SERVICE_PRIVATE + #include "or.h" +#include "circpathbias.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" +#include "config.h" +#include "connection.h" +#include "directory.h" +#include "main.h" +#include "networkstatus.h" +#include "nodelist.h" #include "relay.h" #include "rendservice.h" -#include "circuitlist.h" -#include "circpathbias.h" +#include "router.h" +#include "routerkeys.h" +#include "routerlist.h" +#include "shared_random_state.h" +#include "statefile.h" +#include "hs_circuit.h" +#include "hs_common.h" +#include "hs_config.h" +#include "hs_circuit.h" +#include "hs_descriptor.h" +#include "hs_ident.h" #include "hs_intropoint.h" #include "hs_service.h" -#include "hs_common.h" -#include "hs/cell_establish_intro.h" +/* Trunnel */ +#include "ed25519_cert.h" #include "hs/cell_common.h" +#include "hs/cell_establish_intro.h" + +/* Helper macro. Iterate over every service in the global map. The var is the + * name of the service pointer. */ +#define FOR_EACH_SERVICE_BEGIN(var) \ + STMT_BEGIN \ + hs_service_t **var##_iter, *var; \ + HT_FOREACH(var##_iter, hs_service_ht, hs_service_map) { \ + var = *var##_iter; +#define FOR_EACH_SERVICE_END } STMT_END ; + +/* Helper macro. Iterate over both current and previous descriptor of a + * service. The var is the name of the descriptor pointer. This macro skips + * any descriptor object of the service that is NULL. */ +#define FOR_EACH_DESCRIPTOR_BEGIN(service, var) \ + STMT_BEGIN \ + hs_service_descriptor_t *var; \ + for (int var ## _loop_idx = 0; var ## _loop_idx < 2; \ + ++var ## _loop_idx) { \ + (var ## _loop_idx == 0) ? (var = service->desc_current) : \ + (var = service->desc_next); \ + if (var == NULL) continue; +#define FOR_EACH_DESCRIPTOR_END } STMT_END ; + +/* Onion service directory file names. */ +static const char fname_keyfile_prefix[] = "hs_ed25519"; +static const char fname_hostname[] = "hostname"; +static const char address_tld[] = "onion"; + +/* Staging list of service object. When configuring service, we add them to + * this list considered a staging area and they will get added to our global + * map once the keys have been loaded. These two steps are seperated because + * loading keys requires that we are an actual running tor process. */ +static smartlist_t *hs_service_staging_list; + +/** True if the list of available router descriptors might have changed which + * might result in an altered hash ring. Check if the hash ring changed and + * reupload if needed */ +static int consider_republishing_hs_descriptors = 0; -/* XXX We don't currently use these functions, apart from generating unittest - data. When we start implementing the service-side support for prop224 we - should revisit these functions and use them. */ +static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); +static void move_descriptors(hs_service_t *src, hs_service_t *dst); -/** Given an ESTABLISH_INTRO <b>cell</b>, encode it and place its payload in - * <b>buf_out</b> which has size <b>buf_out_len</b>. Return the number of - * bytes written, or a negative integer if there was an error. */ -ssize_t -get_establish_intro_payload(uint8_t *buf_out, size_t buf_out_len, - const trn_cell_establish_intro_t *cell) +/* Helper: Function to compare two objects in the service map. Return 1 if the + * two service have the same master public identity key. */ +static inline int +hs_service_ht_eq(const hs_service_t *first, const hs_service_t *second) { - ssize_t bytes_used = 0; + tor_assert(first); + tor_assert(second); + /* Simple key compare. */ + return ed25519_pubkey_eq(&first->keys.identity_pk, + &second->keys.identity_pk); +} + +/* Helper: Function for the service hash table code below. The key used is the + * master public identity key which is ultimately the onion address. */ +static inline unsigned int +hs_service_ht_hash(const hs_service_t *service) +{ + tor_assert(service); + return (unsigned int) siphash24g(service->keys.identity_pk.pubkey, + sizeof(service->keys.identity_pk.pubkey)); +} + +/* This is _the_ global hash map of hidden services which indexed the service + * contained in it by master public identity key which is roughly the onion + * address of the service. */ +static struct hs_service_ht *hs_service_map; + +/* Register the service hash table. */ +HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */ + hs_service_t, /* Object contained in the map. */ + hs_service_node, /* The name of the HT_ENTRY member. */ + hs_service_ht_hash, /* Hashing function. */ + hs_service_ht_eq) /* Compare function for objects. */ + +HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node, + hs_service_ht_hash, hs_service_ht_eq, + 0.6, tor_reallocarray, tor_free_) - if (buf_out_len < RELAY_PAYLOAD_SIZE) { +/* Query the given service map with a public key and return a service object + * if found else NULL. It is also possible to set a directory path in the + * search query. If pk is NULL, then it will be set to zero indicating the + * hash table to compare the directory path instead. */ +STATIC hs_service_t * +find_service(hs_service_ht *map, const ed25519_public_key_t *pk) +{ + hs_service_t dummy_service; + tor_assert(map); + tor_assert(pk); + memset(&dummy_service, 0, sizeof(dummy_service)); + ed25519_pubkey_copy(&dummy_service.keys.identity_pk, pk); + return HT_FIND(hs_service_ht, map, &dummy_service); +} + +/* Register the given service in the given map. If the service already exists + * in the map, -1 is returned. On success, 0 is returned and the service + * ownership has been transfered to the global map. */ +STATIC int +register_service(hs_service_ht *map, hs_service_t *service) +{ + tor_assert(map); + tor_assert(service); + tor_assert(!ed25519_public_key_is_zero(&service->keys.identity_pk)); + + if (find_service(map, &service->keys.identity_pk)) { + /* Existing service with the same key. Do not register it. */ return -1; } + /* Taking ownership of the object at this point. */ + HT_INSERT(hs_service_ht, map, service); + return 0; +} - bytes_used = trn_cell_establish_intro_encode(buf_out, buf_out_len, - cell); - return bytes_used; +/* Remove a given service from the given map. If service is NULL or the + * service key is unset, return gracefully. */ +STATIC void +remove_service(hs_service_ht *map, hs_service_t *service) +{ + hs_service_t *elm; + + tor_assert(map); + + /* Ignore if no service or key is zero. */ + if (BUG(service == NULL) || + BUG(ed25519_public_key_is_zero(&service->keys.identity_pk))) { + return; + } + + elm = HT_REMOVE(hs_service_ht, map, service); + if (elm) { + tor_assert(elm == service); + } else { + log_warn(LD_BUG, "Could not find service in the global map " + "while removing service %s", + escaped(service->config.directory_path)); + } } -/* Set the cell extensions of <b>cell</b>. */ +/* Set the default values for a service configuration object <b>c</b>. */ static void -set_trn_cell_extensions(trn_cell_establish_intro_t *cell) +set_service_default_config(hs_service_config_t *c, + const or_options_t *options) { - trn_cell_extension_t *trn_cell_extensions = trn_cell_extension_new(); + (void) options; + tor_assert(c); + c->ports = smartlist_new(); + c->directory_path = NULL; + c->max_streams_per_rdv_circuit = 0; + c->max_streams_close_circuit = 0; + c->num_intro_points = NUM_INTRO_POINTS_DEFAULT; + c->allow_unknown_ports = 0; + c->is_single_onion = 0; + c->dir_group_readable = 0; + c->is_ephemeral = 0; +} - /* For now, we don't use extensions at all. */ - trn_cell_extensions->num = 0; /* It's already zeroed, but be explicit. */ - trn_cell_establish_intro_set_extensions(cell, trn_cell_extensions); +/* From a service configuration object config, clear everything from it + * meaning free allocated pointers and reset the values. */ +static void +service_clear_config(hs_service_config_t *config) +{ + if (config == NULL) { + return; + } + tor_free(config->directory_path); + if (config->ports) { + SMARTLIST_FOREACH(config->ports, rend_service_port_config_t *, p, + rend_service_port_config_free(p);); + smartlist_free(config->ports); + } + memset(config, 0, sizeof(*config)); } -/** Given the circuit handshake info in <b>circuit_key_material</b>, create and - * return an ESTABLISH_INTRO cell. Return NULL if something went wrong. The - * returned cell is allocated on the heap and it's the responsibility of the - * caller to free it. */ -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len) +/* Helper function to return a human readable description of the given intro + * point object. + * + * This function is not thread-safe. Each call to this invalidates the + * previous values returned by it. */ +static const char * +describe_intro_point(const hs_service_intro_point_t *ip) { - trn_cell_establish_intro_t *cell = NULL; - ssize_t encoded_len; + /* Hex identity digest of the IP prefixed by the $ sign and ends with NUL + * byte hence the plus two. */ + static char buf[HEX_DIGEST_LEN + 2]; + const char *legacy_id = NULL; - log_warn(LD_GENERAL, - "Generating ESTABLISH_INTRO cell (key_material_len: %u)", - (unsigned) circuit_key_material_len); + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, lspec) { + if (lspec->type == LS_LEGACY_ID) { + legacy_id = (const char *) lspec->u.legacy_id; + break; + } + } SMARTLIST_FOREACH_END(lspec); - /* Generate short-term keypair for use in ESTABLISH_INTRO */ - ed25519_keypair_t key_struct; - if (ed25519_keypair_generate(&key_struct, 0) < 0) { - goto err; + /* For now, we only print the identity digest but we could improve this with + * much more information such as the ed25519 identity has well. */ + buf[0] = '$'; + if (legacy_id) { + base16_encode(buf + 1, HEX_DIGEST_LEN + 1, legacy_id, DIGEST_LEN); } - cell = trn_cell_establish_intro_new(); + return buf; +} - /* Set AUTH_KEY_TYPE: 2 means ed25519 */ - trn_cell_establish_intro_set_auth_key_type(cell, - HS_INTRO_AUTH_KEY_TYPE_ED25519); +/* Return the lower bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ +static int32_t +get_intro_point_min_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_introduce2", + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} - /* Set AUTH_KEY_LEN field */ - /* Must also set byte-length of AUTH_KEY to match */ - int auth_key_len = ED25519_PUBKEY_LEN; - trn_cell_establish_intro_set_auth_key_len(cell, auth_key_len); - trn_cell_establish_intro_setlen_auth_key(cell, auth_key_len); +/* Return the upper bound of maximum INTRODUCE2 cells per circuit before we + * rotate intro point (defined by a consensus parameter or the default + * value). */ +static int32_t +get_intro_point_max_introduce2(void) +{ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_introduce2", + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS, + 0, INT32_MAX); +} - /* Set AUTH_KEY field */ - uint8_t *auth_key_ptr = trn_cell_establish_intro_getarray_auth_key(cell); - memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len); +/* Return the minimum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ +static int32_t +get_intro_point_min_lifetime(void) +{ +#define MIN_INTRO_POINT_LIFETIME_TESTING 10 + if (get_options()->TestingTorNetwork) { + return MIN_INTRO_POINT_LIFETIME_TESTING; + } - /* No cell extensions needed */ - set_trn_cell_extensions(cell); + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_min_lifetime", + INTRO_POINT_LIFETIME_MIN_SECONDS, + 0, INT32_MAX); +} - /* Set signature size. - We need to do this up here, because _encode() needs it and we need to call - _encode() to calculate the MAC and signature. - */ - int sig_len = ED25519_SIG_LEN; - trn_cell_establish_intro_set_sig_len(cell, sig_len); - trn_cell_establish_intro_setlen_sig(cell, sig_len); +/* Return the maximum lifetime in seconds of an introduction point defined by a + * consensus parameter or the default value. */ +static int32_t +get_intro_point_max_lifetime(void) +{ +#define MAX_INTRO_POINT_LIFETIME_TESTING 30 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_LIFETIME_TESTING; + } - /* XXX How to make this process easier and nicer? */ + /* The [0, 2147483647] range is quite large to accomodate anything we decide + * in the future. */ + return networkstatus_get_param(NULL, "hs_intro_max_lifetime", + INTRO_POINT_LIFETIME_MAX_SECONDS, + 0, INT32_MAX); +} - /* Calculate the cell MAC (aka HANDSHAKE_AUTH). */ - { - /* To calculate HANDSHAKE_AUTH, we dump the cell in bytes, and then derive - the MAC from it. */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - uint8_t mac[TRUNNEL_SHA3_256_LEN]; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell."); +/* Return the number of extra introduction point defined by a consensus + * parameter or the default value. */ +static int32_t +get_intro_point_num_extra(void) +{ + /* The [0, 128] range bounds the number of extra introduction point allowed. + * Above 128 intro points, it's getting a bit crazy. */ + return networkstatus_get_param(NULL, "hs_intro_num_extra", + NUM_INTRO_POINTS_EXTRA, 0, 128); +} + +/* Helper: Function that needs to return 1 for the HT for each loop which + * frees every service in an hash map. */ +static int +ht_free_service_(struct hs_service_t *service, void *data) +{ + (void) data; + hs_service_free(service); + /* This function MUST return 1 so the given object is then removed from the + * service map leading to this free of the object being safe. */ + return 1; +} + +/* Free every service that can be found in the global map. Once done, clear + * and free the global map. */ +static void +service_free_all(void) +{ + if (hs_service_map) { + /* The free helper function returns 1 so this is safe. */ + hs_service_ht_HT_FOREACH_FN(hs_service_map, ht_free_service_, NULL); + HT_CLEAR(hs_service_ht, hs_service_map); + tor_free(hs_service_map); + hs_service_map = NULL; + } + + if (hs_service_staging_list) { + /* Cleanup staging list. */ + SMARTLIST_FOREACH(hs_service_staging_list, hs_service_t *, s, + hs_service_free(s)); + smartlist_free(hs_service_staging_list); + hs_service_staging_list = NULL; + } +} + +/* Free a given service intro point object. */ +STATIC void +service_intro_point_free(hs_service_intro_point_t *ip) +{ + if (!ip) { + return; + } + memwipe(&ip->auth_key_kp, 0, sizeof(ip->auth_key_kp)); + memwipe(&ip->enc_key_kp, 0, sizeof(ip->enc_key_kp)); + crypto_pk_free(ip->legacy_key); + replaycache_free(ip->replay_cache); + hs_intropoint_clear(&ip->base); + tor_free(ip); +} + +/* Helper: free an hs_service_intro_point_t object. This function is used by + * digest256map_free() which requires a void * pointer. */ +static void +service_intro_point_free_(void *obj) +{ + service_intro_point_free(obj); +} + +/* Return a newly allocated service intro point and fully initialized from the + * given extend_info_t ei if non NULL. If is_legacy is true, we also generate + * the legacy key. On error, NULL is returned. + * + * If ei is NULL, returns a hs_service_intro_point_t with an empty link + * specifier list and no onion key. (This is used for testing.) + * + * ei must be an extend_info_t containing an IPv4 address. (We will add supoort + * for IPv6 in a later release.) When calling extend_info_from_node(), pass + * 0 in for_direct_connection to make sure ei always has an IPv4 address. */ +STATIC hs_service_intro_point_t * +service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy) +{ + hs_desc_link_specifier_t *ls; + hs_service_intro_point_t *ip; + + ip = tor_malloc_zero(sizeof(*ip)); + /* We'll create the key material. No need for extra strong, those are short + * term keys. */ + ed25519_keypair_generate(&ip->auth_key_kp, 0); + + { /* Set introduce2 max cells limit */ + int32_t min_introduce2_cells = get_intro_point_min_introduce2(); + int32_t max_introduce2_cells = get_intro_point_max_introduce2(); + if (BUG(max_introduce2_cells < min_introduce2_cells)) { + goto err; + } + ip->introduce2_max = crypto_rand_int_range(min_introduce2_cells, + max_introduce2_cells); + } + { /* Set intro point lifetime */ + int32_t intro_point_min_lifetime = get_intro_point_min_lifetime(); + int32_t intro_point_max_lifetime = get_intro_point_max_lifetime(); + if (BUG(intro_point_max_lifetime < intro_point_min_lifetime)) { + goto err; + } + ip->time_to_expire = time(NULL) + + crypto_rand_int_range(intro_point_min_lifetime,intro_point_max_lifetime); + } + + ip->replay_cache = replaycache_new(0, 0); + + /* Initialize the base object. We don't need the certificate object. */ + ip->base.link_specifiers = smartlist_new(); + + /* Generate the encryption key for this intro point. */ + curve25519_keypair_generate(&ip->enc_key_kp, 0); + /* Figure out if this chosen node supports v3 or is legacy only. */ + if (is_legacy) { + ip->base.is_only_legacy = 1; + /* Legacy mode that is doesn't support v3+ with ed25519 auth key. */ + ip->legacy_key = crypto_pk_new(); + if (crypto_pk_generate_key(ip->legacy_key) < 0) { goto err; } + } + + if (ei == NULL) { + goto done; + } + + /* We'll try to add all link specifiers. Legacy is mandatory. + * IPv4 or IPv6 is required, and we always send IPv4. */ + ls = hs_desc_link_specifier_new(ei, LS_IPV4); + /* It is impossible to have an extend info object without a v4. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + ls = hs_desc_link_specifier_new(ei, LS_LEGACY_ID); + /* It is impossible to have an extend info object without an identity + * digest. */ + if (BUG(!ls)) { + goto err; + } + smartlist_add(ip->base.link_specifiers, ls); + + /* ed25519 identity key is optional for intro points */ + ls = hs_desc_link_specifier_new(ei, LS_ED25519_ID); + if (ls) { + smartlist_add(ip->base.link_specifiers, ls); + } + + /* IPv6 is not supported in this release. */ + + /* Finally, copy onion key from the extend_info_t object. */ + memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key)); + + done: + return ip; + err: + service_intro_point_free(ip); + return NULL; +} + +/* Add the given intro point object to the given intro point map. The intro + * point MUST have its RSA encryption key set if this is a legacy type or the + * authentication key set otherwise. */ +STATIC void +service_intro_point_add(digest256map_t *map, hs_service_intro_point_t *ip) +{ + hs_service_intro_point_t *old_ip_entry; + + tor_assert(map); + tor_assert(ip); + + old_ip_entry = digest256map_set(map, ip->auth_key_kp.pubkey.pubkey, ip); + /* Make sure we didn't just try to double-add an intro point */ + tor_assert_nonfatal(!old_ip_entry); +} + +/* For a given service, remove the intro point from that service's descriptors + * (check both current and next descriptor) */ +STATIC void +service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + tor_assert(service); + tor_assert(ip); + + /* Trying all descriptors. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We'll try to remove the descriptor on both descriptors which is not + * very expensive to do instead of doing loopup + remove. */ + digest256map_remove(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey); + } FOR_EACH_DESCRIPTOR_END; +} + +/* For a given service and authentication key, return the intro point or NULL + * if not found. This will check both descriptors in the service. */ +STATIC hs_service_intro_point_t * +service_intro_point_find(const hs_service_t *service, + const ed25519_public_key_t *auth_key) +{ + hs_service_intro_point_t *ip = NULL; + + tor_assert(service); + tor_assert(auth_key); + + /* Trying all descriptors to find the right intro point. + * + * Even if we use the same node as intro point in both descriptors, the node + * will have a different intro auth key for each descriptor since we generate + * a new one everytime we pick an intro point. + * + * After #22893 gets implemented, intro points will be moved to be + * per-service instead of per-descriptor so this function will need to + * change. + */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if ((ip = digest256map_get(desc->intro_points.map, + auth_key->pubkey)) != NULL) { + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return ip; +} + +/* For a given service and intro point, return the descriptor for which the + * intro point is assigned to. NULL is returned if not found. */ +STATIC hs_service_descriptor_t * +service_desc_find_by_intro(const hs_service_t *service, + const hs_service_intro_point_t *ip) +{ + hs_service_descriptor_t *descp = NULL; + + tor_assert(service); + tor_assert(ip); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + if (digest256map_get(desc->intro_points.map, + ip->auth_key_kp.pubkey.pubkey)) { + descp = desc; + break; + } + } FOR_EACH_DESCRIPTOR_END; + + return descp; +} + +/* From a circuit identifier, get all the possible objects associated with the + * ident. If not NULL, service, ip or desc are set if the object can be found. + * They are untouched if they can't be found. + * + * This is an helper function because we do those lookups often so it's more + * convenient to simply call this functions to get all the things at once. */ +STATIC void +get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc) +{ + hs_service_t *s; + + tor_assert(ident); + + /* Get service object from the circuit identifier. */ + s = find_service(hs_service_map, &ident->identity_pk); + if (s && service) { + *service = s; + } + + /* From the service object, get the intro point object of that circuit. The + * following will query both descriptors intro points list. */ + if (s && ip) { + *ip = service_intro_point_find(s, &ident->intro_auth_pk); + } + + /* Get the descriptor for this introduction point and service. */ + if (s && ip && *ip && desc) { + *desc = service_desc_find_by_intro(s, *ip); + } +} + +/* From a given intro point, return the first link specifier of type + * encountered in the link specifier list. Return NULL if it can't be found. + * + * The caller does NOT have ownership of the object, the intro point does. */ +static hs_desc_link_specifier_t * +get_link_spec_by_type(const hs_service_intro_point_t *ip, uint8_t type) +{ + hs_desc_link_specifier_t *lnk_spec = NULL; + + tor_assert(ip); + + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + hs_desc_link_specifier_t *, ls) { + if (ls->type == type) { + lnk_spec = ls; + goto end; + } + } SMARTLIST_FOREACH_END(ls); + + end: + return lnk_spec; +} + +/* Given a service intro point, return the node_t associated to it. This can + * return NULL if the given intro point has no legacy ID or if the node can't + * be found in the consensus. */ +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip) +{ + const hs_desc_link_specifier_t *ls; + + tor_assert(ip); + + ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + if (BUG(!ls)) { + return NULL; + } + /* XXX In the future, we want to only use the ed25519 ID (#22173). */ + return node_get_by_id((const char *) ls->u.legacy_id); +} + +/* Given a service intro point, return the extend_info_t for it. This can + * return NULL if the node can't be found for the intro point or the extend + * info can't be created for the found node. If direct_conn is set, the extend + * info is validated on if we can connect directly. */ +static extend_info_t * +get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, + unsigned int direct_conn) +{ + extend_info_t *info = NULL; + const node_t *node; + + tor_assert(ip); + + node = get_node_from_intro_point(ip); + if (node == NULL) { + /* This can happen if the relay serving as intro point has been removed + * from the consensus. In that case, the intro point will be removed from + * the descriptor during the scheduled events. */ + goto end; + } + + /* In the case of a direct connection (single onion service), it is possible + * our firewall policy won't allow it so this can return a NULL value. */ + info = extend_info_from_node(node, direct_conn); + + end: + return info; +} + +/* Return the number of introduction points that are established for the + * given descriptor. */ +static unsigned int +count_desc_circuit_established(const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + + tor_assert(desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + count += ip->circuit_established; + } DIGEST256MAP_FOREACH_END; + + return count; +} + +/* For a given service and descriptor of that service, close all active + * directory connections. */ +static void +close_directory_connections(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + unsigned int count = 0; + smartlist_t *dir_conns; + + tor_assert(service); + tor_assert(desc); + + /* Close pending HS desc upload connections for the blinded key of 'desc'. */ + dir_conns = connection_list_by_type_purpose(CONN_TYPE_DIR, + DIR_PURPOSE_UPLOAD_HSDESC); + SMARTLIST_FOREACH_BEGIN(dir_conns, connection_t *, conn) { + dir_connection_t *dir_conn = TO_DIR_CONN(conn); + if (ed25519_pubkey_eq(&dir_conn->hs_ident->identity_pk, + &service->keys.identity_pk) && + ed25519_pubkey_eq(&dir_conn->hs_ident->blinded_pk, + &desc->blinded_kp.pubkey)) { + connection_mark_for_close(conn); + count++; + continue; + } + } SMARTLIST_FOREACH_END(conn); + + log_info(LD_REND, "Closed %u active service directory connections for " + "descriptor %s of service %s", + count, safe_str_client(ed25519_fmt(&desc->blinded_kp.pubkey)), + safe_str_client(service->onion_address)); + /* We don't have ownership of the objects in this list. */ + smartlist_free(dir_conns); +} + +/* Close all rendezvous circuits for the given service. */ +static void +close_service_rp_circuits(hs_service_t *service) +{ + origin_circuit_t *ocirc = NULL; + + tor_assert(service); + + /* The reason we go over all circuit instead of using the circuitmap API is + * because most hidden service circuits are rendezvous circuits so there is + * no real improvement at getting all rendezvous circuits from the + * circuitmap and then going over them all to find the right ones. + * Furthermore, another option would have been to keep a list of RP cookies + * for a service but it creates an engineering complexity since we don't + * have a "RP circuit closed" event to clean it up properly so we avoid a + * memory DoS possibility. */ + + while ((ocirc = circuit_get_next_service_rp_circ(ocirc))) { + /* Only close circuits that are v3 and for this service. */ + if (ocirc->hs_ident != NULL && + ed25519_pubkey_eq(ô->hs_ident->identity_pk, + &service->keys.identity_pk)) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, it is removed from the + * hs circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } +} + +/* Close the circuit(s) for the given map of introduction points. */ +static void +close_intro_circuits(hs_service_intropoints_t *intro_points) +{ + tor_assert(intro_points); + + DIGEST256MAP_FOREACH(intro_points->map, key, + const hs_service_intro_point_t *, ip) { + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc) { + /* Reason is FINISHED because service has been removed and thus the + * circuit is considered old/uneeded. When freed, the circuit is removed + * from the HS circuitmap. */ + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + } DIGEST256MAP_FOREACH_END; +} + +/* Close all introduction circuits for the given service. */ +static void +close_service_intro_circuits(hs_service_t *service) +{ + tor_assert(service); + + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + close_intro_circuits(&desc->intro_points); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Close any circuits related to the given service. */ +static void +close_service_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* Only support for version >= 3. */ + if (BUG(service->config.version < HS_VERSION_THREE)) { + return; + } + /* Close intro points. */ + close_service_intro_circuits(service); + /* Close rendezvous points. */ + close_service_rp_circuits(service); +} + +/* Move every ephemeral services from the src service map to the dst service + * map. It is possible that a service can't be register to the dst map which + * won't stop the process of moving them all but will trigger a log warn. */ +static void +move_ephemeral_services(hs_service_ht *src, hs_service_ht *dst) +{ + hs_service_t **iter, **next; + + tor_assert(src); + tor_assert(dst); + + /* Iterate over the map to find ephemeral service and move them to the other + * map. We loop using this method to have a safe removal process. */ + for (iter = HT_START(hs_service_ht, src); iter != NULL; iter = next) { + hs_service_t *s = *iter; + if (!s->config.is_ephemeral) { + /* Yeah, we are in a very manual loop :). */ + next = HT_NEXT(hs_service_ht, src, iter); + continue; + } + /* Remove service from map and then register to it to the other map. + * Reminder that "*iter" and "s" are the same thing. */ + next = HT_NEXT_RMV(hs_service_ht, src, iter); + if (register_service(dst, s) < 0) { + log_warn(LD_BUG, "Ephemeral service key is already being used. " + "Skipping."); + } + } +} + +/* Return a const string of the directory path escaped. If this is an + * ephemeral service, it returns "[EPHEMERAL]". This can only be called from + * the main thread because escaped() uses a static variable. */ +static const char * +service_escaped_dir(const hs_service_t *s) +{ + return (s->config.is_ephemeral) ? "[EPHEMERAL]" : + escaped(s->config.directory_path); +} + +/** Move the hidden service state from <b>src</b> to <b>dst</b>. We do this + * when we receive a SIGHUP: <b>dst</b> is the post-HUP service */ +static void +move_hs_state(hs_service_t *src_service, hs_service_t *dst_service) +{ + tor_assert(src_service); + tor_assert(dst_service); + + hs_service_state_t *src = &src_service->state; + hs_service_state_t *dst = &dst_service->state; + + /* Let's do a shallow copy */ + dst->intro_circ_retry_started_time = src->intro_circ_retry_started_time; + dst->num_intro_circ_launched = src->num_intro_circ_launched; + dst->replay_cache_rend_cookie = src->replay_cache_rend_cookie; + + src->replay_cache_rend_cookie = NULL; /* steal pointer reference */ +} + +/* Register services that are in the staging list. Once this function returns, + * the global service map will be set with the right content and all non + * surviving services will be cleaned up. */ +static void +register_all_services(void) +{ + struct hs_service_ht *new_service_map; - /* sanity check */ - tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN); + tor_assert(hs_service_staging_list); - /* Calculate MAC of all fields before HANDSHAKE_AUTH */ - crypto_mac_sha3_256(mac, sizeof(mac), - circuit_key_material, circuit_key_material_len, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN)); - /* Write the MAC to the cell */ - uint8_t *handshake_ptr = - trn_cell_establish_intro_getarray_handshake_mac(cell); - memcpy(handshake_ptr, mac, sizeof(mac)); + /* We'll save us some allocation and computing time. */ + if (smartlist_len(hs_service_staging_list) == 0) { + return; } - /* Calculate the cell signature */ + /* Allocate a new map that will replace the current one. */ + new_service_map = tor_malloc_zero(sizeof(*new_service_map)); + HT_INIT(hs_service_ht, new_service_map); + + /* First step is to transfer all ephemeral services from the current global + * map to the new one we are constructing. We do not prune ephemeral + * services as the only way to kill them is by deleting it from the control + * port or stopping the tor daemon. */ + move_ephemeral_services(hs_service_map, new_service_map); + + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, snew) { + hs_service_t *s; + + /* Check if that service is already in our global map and if so, we'll + * transfer the intro points to it. */ + s = find_service(hs_service_map, &snew->keys.identity_pk); + if (s) { + /* Pass ownership of the descriptors from s (the current service) to + * snew (the newly configured one). */ + move_descriptors(s, snew); + move_hs_state(s, snew); + /* Remove the service from the global map because after this, we need to + * go over the remaining service in that map that aren't surviving the + * reload to close their circuits. */ + remove_service(hs_service_map, s); + hs_service_free(s); + } + /* Great, this service is now ready to be added to our new map. */ + if (BUG(register_service(new_service_map, snew) < 0)) { + /* This should never happen because prior to registration, we validate + * every service against the entire set. Not being able to register a + * service means we failed to validate correctly. In that case, don't + * break tor and ignore the service but tell user. */ + log_warn(LD_BUG, "Unable to register service with directory %s", + service_escaped_dir(snew)); + SMARTLIST_DEL_CURRENT(hs_service_staging_list, snew); + hs_service_free(snew); + } + } SMARTLIST_FOREACH_END(snew); + + /* Close any circuits associated with the non surviving services. Every + * service in the current global map are roaming. */ + FOR_EACH_SERVICE_BEGIN(service) { + close_service_circuits(service); + } FOR_EACH_SERVICE_END; + + /* Time to make the switch. We'll clear the staging list because its content + * has now changed ownership to the map. */ + smartlist_clear(hs_service_staging_list); + service_free_all(); + hs_service_map = new_service_map; +} + +/* Write the onion address of a given service to the given filename fname_ in + * the service directory. Return 0 on success else -1 on error. */ +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_) +{ + int ret = -1; + char *fname = NULL; + char *addr_buf = NULL; + + tor_assert(service); + tor_assert(fname_); + + /* Construct the full address with the onion tld and write the hostname file + * to disk. */ + tor_asprintf(&addr_buf, "%s.%s\n", service->onion_address, address_tld); + /* Notice here that we use the given "fname_". */ + fname = hs_path_from_filename(service->config.directory_path, fname_); + if (write_str_to_file(fname, addr_buf, 0) < 0) { + log_warn(LD_REND, "Could not write onion address to hostname file %s", + escaped(fname)); + goto end; + } + +#ifndef _WIN32 + if (service->config.dir_group_readable) { + /* Mode to 0640. */ + if (chmod(fname, S_IRUSR | S_IWUSR | S_IRGRP) < 0) { + log_warn(LD_FS, "Unable to make onion service hostname file %s " + "group-readable.", escaped(fname)); + } + } +#endif /* !defined(_WIN32) */ + + /* Success. */ + ret = 0; + end: + tor_free(fname); + tor_free(addr_buf); + return ret; +} + +/* Load and/or generate private keys for the given service. On success, the + * hostname file will be written to disk along with the master private key iff + * the service is not configured for offline keys. Return 0 on success else -1 + * on failure. */ +static int +load_service_keys(hs_service_t *service) +{ + int ret = -1; + char *fname = NULL; + ed25519_keypair_t *kp; + const hs_service_config_t *config; + + tor_assert(service); + + config = &service->config; + + /* Create and fix permission on service directory. We are about to write + * files to that directory so make sure it exists and has the right + * permissions. We do this here because at this stage we know that Tor is + * actually running and the service we have has been validated. */ + if (BUG(hs_check_service_private_dir(get_options()->User, + config->directory_path, + config->dir_group_readable, 1) < 0)) { + goto end; + } + + /* Try to load the keys from file or generate it if not found. */ + fname = hs_path_from_filename(config->directory_path, fname_keyfile_prefix); + /* Don't ask for key creation, we want to know if we were able to load it or + * we had to generate it. Better logging! */ + kp = ed_key_init_from_file(fname, INIT_ED_KEY_SPLIT, LOG_INFO, NULL, 0, 0, + 0, NULL); + if (!kp) { + log_info(LD_REND, "Unable to load keys from %s. Generating it...", fname); + /* We'll now try to generate the keys and for it we want the strongest + * randomness for it. The keypair will be written in different files. */ + uint32_t key_flags = INIT_ED_KEY_CREATE | INIT_ED_KEY_EXTRA_STRONG | + INIT_ED_KEY_SPLIT; + kp = ed_key_init_from_file(fname, key_flags, LOG_WARN, NULL, 0, 0, 0, + NULL); + if (!kp) { + log_warn(LD_REND, "Unable to generate keys and save in %s.", fname); + goto end; + } + } + + /* Copy loaded or generated keys to service object. */ + ed25519_pubkey_copy(&service->keys.identity_pk, &kp->pubkey); + memcpy(&service->keys.identity_sk, &kp->seckey, + sizeof(service->keys.identity_sk)); + /* This does a proper memory wipe. */ + ed25519_keypair_free(kp); + + /* Build onion address from the newly loaded keys. */ + tor_assert(service->config.version <= UINT8_MAX); + hs_build_address(&service->keys.identity_pk, + (uint8_t) service->config.version, + service->onion_address); + + /* Write onion address to hostname file. */ + if (write_address_to_file(service, fname_hostname) < 0) { + goto end; + } + + /* Succes. */ + ret = 0; + end: + tor_free(fname); + return ret; +} + +/* Free a given service descriptor object and all key material is wiped. */ +STATIC void +service_descriptor_free(hs_service_descriptor_t *desc) +{ + if (!desc) { + return; + } + hs_descriptor_free(desc->desc); + memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp)); + memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp)); + /* Cleanup all intro points. */ + digest256map_free(desc->intro_points.map, service_intro_point_free_); + digestmap_free(desc->intro_points.failed_id, tor_free_); + if (desc->previous_hsdirs) { + SMARTLIST_FOREACH(desc->previous_hsdirs, char *, s, tor_free(s)); + smartlist_free(desc->previous_hsdirs); + } + tor_free(desc); +} + +/* Return a newly allocated service descriptor object. */ +STATIC hs_service_descriptor_t * +service_descriptor_new(void) +{ + hs_service_descriptor_t *sdesc = tor_malloc_zero(sizeof(*sdesc)); + sdesc->desc = tor_malloc_zero(sizeof(hs_descriptor_t)); + /* Initialize the intro points map. */ + sdesc->intro_points.map = digest256map_new(); + sdesc->intro_points.failed_id = digestmap_new(); + sdesc->previous_hsdirs = smartlist_new(); + return sdesc; +} + +/* Move descriptor(s) from the src service to the dst service. We do this + * during SIGHUP when we re-create our hidden services. */ +static void +move_descriptors(hs_service_t *src, hs_service_t *dst) +{ + tor_assert(src); + tor_assert(dst); + + if (src->desc_current) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_current)) { + service_descriptor_free(dst->desc_current); + } + dst->desc_current = src->desc_current; + src->desc_current = NULL; + } + + if (src->desc_next) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_next)) { + service_descriptor_free(dst->desc_next); + } + dst->desc_next = src->desc_next; + src->desc_next = NULL; + } +} + +/* From the given service, remove all expired failing intro points for each + * descriptor. */ +static void +remove_expired_failing_intro(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* For both descriptors, cleanup the failing intro points list. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + DIGESTMAP_FOREACH_MODIFY(desc->intro_points.failed_id, key, time_t *, t) { + time_t failure_time = *t; + if ((failure_time + INTRO_CIRC_RETRY_PERIOD) <= now) { + MAP_DEL_CURRENT(key); + tor_free(t); + } + } DIGESTMAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* For the given descriptor desc, put all node_t object found from its failing + * intro point list and put them in the given node_list. */ +static void +setup_intro_point_exclude_list(const hs_service_descriptor_t *desc, + smartlist_t *node_list) +{ + tor_assert(desc); + tor_assert(node_list); + + DIGESTMAP_FOREACH(desc->intro_points.failed_id, key, time_t *, t) { + (void) t; /* Make gcc happy. */ + const node_t *node = node_get_by_id(key); + if (node) { + smartlist_add(node_list, (void *) node); + } + } DIGESTMAP_FOREACH_END; +} + +/* For the given failing intro point ip, we add its time of failure to the + * failed map and index it by identity digest (legacy ID) in the descriptor + * desc failed id map. */ +static void +remember_failing_intro_point(const hs_service_intro_point_t *ip, + hs_service_descriptor_t *desc, time_t now) +{ + time_t *time_of_failure, *prev_ptr; + const hs_desc_link_specifier_t *legacy_ls; + + tor_assert(ip); + tor_assert(desc); + + time_of_failure = tor_malloc_zero(sizeof(time_t)); + *time_of_failure = now; + legacy_ls = get_link_spec_by_type(ip, LS_LEGACY_ID); + tor_assert(legacy_ls); + prev_ptr = digestmap_set(desc->intro_points.failed_id, + (const char *) legacy_ls->u.legacy_id, + time_of_failure); + tor_free(prev_ptr); +} + +/* Copy the descriptor link specifier object from src to dst. */ +static void +link_specifier_copy(hs_desc_link_specifier_t *dst, + const hs_desc_link_specifier_t *src) +{ + tor_assert(dst); + tor_assert(src); + memcpy(dst, src, sizeof(hs_desc_link_specifier_t)); +} + +/* Using a given descriptor signing keypair signing_kp, a service intro point + * object ip and the time now, setup the content of an already allocated + * descriptor intro desc_ip. + * + * Return 0 on success else a negative value. */ +static int +setup_desc_intro_point(const ed25519_keypair_t *signing_kp, + const hs_service_intro_point_t *ip, + time_t now, hs_desc_intro_point_t *desc_ip) +{ + int ret = -1; + time_t nearest_hour = now - (now % 3600); + + tor_assert(signing_kp); + tor_assert(ip); + tor_assert(desc_ip); + + /* Copy the onion key. */ + memcpy(&desc_ip->onion_key, &ip->onion_key, sizeof(desc_ip->onion_key)); + + /* Key and certificate material. */ + desc_ip->auth_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_AUTH_HS_IP_KEY, + &ip->auth_key_kp.pubkey, + nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->auth_key_cert == NULL) { + log_warn(LD_REND, "Unable to create intro point auth-key certificate"); + goto done; + } + + /* Copy link specifier(s). */ + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + const hs_desc_link_specifier_t *, ls) { + hs_desc_link_specifier_t *dup = tor_malloc_zero(sizeof(*dup)); + link_specifier_copy(dup, ls); + smartlist_add(desc_ip->link_specifiers, dup); + } SMARTLIST_FOREACH_END(ls); + + /* For a legacy intro point, we'll use an RSA/ed cross certificate. */ + if (ip->base.is_only_legacy) { + desc_ip->legacy.key = crypto_pk_dup_key(ip->legacy_key); + /* Create cross certification cert. */ + ssize_t cert_len = tor_make_rsa_ed25519_crosscert( + &signing_kp->pubkey, + desc_ip->legacy.key, + nearest_hour + HS_DESC_CERT_LIFETIME, + &desc_ip->legacy.cert.encoded); + if (cert_len < 0) { + log_warn(LD_REND, "Unable to create enc key legacy cross cert."); + goto done; + } + desc_ip->legacy.cert.len = cert_len; + } + + /* Encryption key and its cross certificate. */ { - /* To calculate the sig we follow the same procedure as above. We first - dump the cell up to the sig, and then calculate the sig */ - uint8_t cell_bytes_tmp[RELAY_PAYLOAD_SIZE] = {0}; - ed25519_signature_t sig; - - encoded_len = trn_cell_establish_intro_encode(cell_bytes_tmp, - sizeof(cell_bytes_tmp), - cell); - if (encoded_len < 0) { - log_warn(LD_OR, "Unable to pre-encode ESTABLISH_INTRO cell (2)."); - goto err; + ed25519_public_key_t ed25519_pubkey; + + /* Use the public curve25519 key. */ + memcpy(&desc_ip->enc_key, &ip->enc_key_kp.pubkey, + sizeof(desc_ip->enc_key)); + /* The following can't fail. */ + ed25519_public_key_from_curve25519_public_key(&ed25519_pubkey, + &ip->enc_key_kp.pubkey, + 0); + desc_ip->enc_key_cert = tor_cert_create(signing_kp, + CERT_TYPE_CROSS_HS_IP_KEYS, + &ed25519_pubkey, nearest_hour, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (desc_ip->enc_key_cert == NULL) { + log_warn(LD_REND, "Unable to create enc key curve25519 cross cert."); + goto done; } + } + /* Success. */ + ret = 0; - tor_assert(encoded_len > ED25519_SIG_LEN); + done: + return ret; +} - if (ed25519_sign_prefixed(&sig, - cell_bytes_tmp, - encoded_len - - (ED25519_SIG_LEN + sizeof(cell->sig_len)), - ESTABLISH_INTRO_SIG_PREFIX, - &key_struct)) { - log_warn(LD_BUG, "Unable to gen signature for ESTABLISH_INTRO cell."); - goto err; +/* Using the given descriptor from the given service, build the descriptor + * intro point list so we can then encode the descriptor for publication. This + * function does not pick intro points, they have to be in the descriptor + * current map. Cryptographic material (keys) must be initialized in the + * descriptor for this function to make sense. */ +static void +build_desc_intro_points(const hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + hs_desc_encrypted_data_t *encrypted; + + tor_assert(service); + tor_assert(desc); + + /* Ease our life. */ + encrypted = &desc->desc->encrypted_data; + /* Cleanup intro points, we are about to set them from scratch. */ + hs_descriptor_clear_intro_points(desc->desc); + + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + const hs_service_intro_point_t *, ip) { + hs_desc_intro_point_t *desc_ip = hs_desc_intro_point_new(); + if (setup_desc_intro_point(&desc->signing_kp, ip, now, desc_ip) < 0) { + hs_desc_intro_point_free(desc_ip); + continue; } + /* We have a valid descriptor intro point. Add it to the list. */ + smartlist_add(encrypted->intro_points, desc_ip); + } DIGEST256MAP_FOREACH_END; +} + +/* Populate the descriptor encrypted section fomr the given service object. + * This will generate a valid list of introduction points that can be used + * after for circuit creation. Return 0 on success else -1 on error. */ +static int +build_service_desc_encrypted(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + hs_desc_encrypted_data_t *encrypted; - /* And write the signature to the cell */ - uint8_t *sig_ptr = trn_cell_establish_intro_getarray_sig(cell); - memcpy(sig_ptr, sig.sig, sig_len); + tor_assert(service); + tor_assert(desc); + + encrypted = &desc->desc->encrypted_data; + + encrypted->create2_ntor = 1; + encrypted->single_onion_service = service->config.is_single_onion; + + /* Setup introduction points from what we have in the service. */ + if (encrypted->intro_points == NULL) { + encrypted->intro_points = smartlist_new(); } + /* We do NOT build introduction point yet, we only do that once the circuit + * have been opened. Until we have the right number of introduction points, + * we do not encode anything in the descriptor. */ + + /* XXX: Support client authorization (#20700). */ + encrypted->intro_auth_types = NULL; + return 0; +} + +/* Populare the descriptor plaintext section from the given service object. + * The caller must make sure that the keys in the descriptors are valid that + * is are non-zero. Return 0 on success else -1 on error. */ +static int +build_service_desc_plaintext(const hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + int ret = -1; + hs_desc_plaintext_data_t *plaintext; - /* We are done! Return the cell! */ - return cell; + tor_assert(service); + tor_assert(desc); + /* XXX: Use a "assert_desc_ok()" ? */ + tor_assert(!tor_mem_is_zero((char *) &desc->blinded_kp, + sizeof(desc->blinded_kp))); + tor_assert(!tor_mem_is_zero((char *) &desc->signing_kp, + sizeof(desc->signing_kp))); + + /* Set the subcredential. */ + hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey, + desc->desc->subcredential); + + plaintext = &desc->desc->plaintext_data; + + plaintext->version = service->config.version; + plaintext->lifetime_sec = HS_DESC_DEFAULT_LIFETIME; + plaintext->signing_key_cert = + tor_cert_create(&desc->blinded_kp, CERT_TYPE_SIGNING_HS_DESC, + &desc->signing_kp.pubkey, now, HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + if (plaintext->signing_key_cert == NULL) { + log_warn(LD_REND, "Unable to create descriptor signing certificate for " + "service %s", + safe_str_client(service->onion_address)); + goto end; + } + /* Copy public key material to go in the descriptor. */ + ed25519_pubkey_copy(&plaintext->signing_pubkey, &desc->signing_kp.pubkey); + ed25519_pubkey_copy(&plaintext->blinded_pubkey, &desc->blinded_kp.pubkey); + /* Success. */ + ret = 0; + + end: + return ret; +} +/* For the given service and descriptor object, create the key material which + * is the blinded keypair and the descriptor signing keypair. Return 0 on + * success else -1 on error where the generated keys MUST be ignored. */ +static int +build_service_desc_keys(const hs_service_t *service, + hs_service_descriptor_t *desc, + uint64_t time_period_num) +{ + int ret = 0; + ed25519_keypair_t kp; + + tor_assert(desc); + tor_assert(!tor_mem_is_zero((char *) &service->keys.identity_pk, + ED25519_PUBKEY_LEN)); + + /* XXX: Support offline key feature (#18098). */ + + /* Copy the identity keys to the keypair so we can use it to create the + * blinded key. */ + memcpy(&kp.pubkey, &service->keys.identity_pk, sizeof(kp.pubkey)); + memcpy(&kp.seckey, &service->keys.identity_sk, sizeof(kp.seckey)); + /* Build blinded keypair for this time period. */ + hs_build_blinded_keypair(&kp, NULL, 0, time_period_num, &desc->blinded_kp); + /* Let's not keep too much traces of our keys in memory. */ + memwipe(&kp, 0, sizeof(kp)); + + /* No need for extra strong, this is a temporary key only for this + * descriptor. Nothing long term. */ + if (ed25519_keypair_generate(&desc->signing_kp, 0) < 0) { + log_warn(LD_REND, "Can't generate descriptor signing keypair for " + "service %s", + safe_str_client(service->onion_address)); + ret = -1; + } + + return ret; +} + +/* Given a service and the current time, build a descriptor for the service. + * This function does not pick introduction point, this needs to be done by + * the update function. On success, desc_out will point to the newly allocated + * descriptor object. + * + * This can error if we are unable to create keys or certificate. */ +static void +build_service_descriptor(hs_service_t *service, time_t now, + uint64_t time_period_num, + hs_service_descriptor_t **desc_out) +{ + char *encoded_desc; + hs_service_descriptor_t *desc; + + tor_assert(service); + tor_assert(desc_out); + + desc = service_descriptor_new(); + desc->time_period_num = time_period_num; + + /* Create the needed keys so we can setup the descriptor content. */ + if (build_service_desc_keys(service, desc, time_period_num) < 0) { + goto err; + } + /* Setup plaintext descriptor content. */ + if (build_service_desc_plaintext(service, desc, now) < 0) { + goto err; + } + /* Setup encrypted descriptor content. */ + if (build_service_desc_encrypted(service, desc) < 0) { + goto err; + } + + /* Set the revision counter for this descriptor */ + set_descriptor_revision_counter(desc->desc); + + /* Let's make sure that we've created a descriptor that can actually be + * encoded properly. This function also checks if the encoded output is + * decodable after. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto err; + } + tor_free(encoded_desc); + + /* Assign newly built descriptor to the next slot. */ + *desc_out = desc; + return; + + err: + service_descriptor_free(desc); +} + +/* Build both descriptors for the given service that has just booted up. + * Because it's a special case, it deserves its special function ;). */ +static void +build_descriptors_for_new_service(hs_service_t *service, time_t now) +{ + uint64_t current_desc_tp, next_desc_tp; + + tor_assert(service); + /* These are the conditions for a new service. */ + tor_assert(!service->desc_current); + tor_assert(!service->desc_next); + + /* + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | A B | + * +------------------------------------------------------------------+ + * + * Case A: The service boots up before a new time period, the current time + * period is thus TP#1 and the next is TP#2 which for both we have access to + * their SRVs. + * + * Case B: The service boots up inside TP#2, we can't use the TP#3 for the + * next descriptor because we don't have the SRV#3 so the current should be + * TP#1 and next TP#2. + */ + + if (hs_in_period_between_tp_and_srv(NULL, now)) { + /* Case B from the above, inside of the new time period. */ + current_desc_tp = hs_get_previous_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_time_period_num(0); /* TP#2 */ + } else { + /* Case A from the above, outside of the new time period. */ + current_desc_tp = hs_get_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_next_time_period_num(0); /* TP#2 */ + } + + /* Build descriptors. */ + build_service_descriptor(service, now, current_desc_tp, + &service->desc_current); + build_service_descriptor(service, now, next_desc_tp, + &service->desc_next); + log_info(LD_REND, "Hidden service %s has just started. Both descriptors " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); +} + +/* Build descriptors for each service if needed. There are conditions to build + * a descriptor which are details in the function. */ +STATIC void +build_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + + /* A service booting up will have both descriptors to NULL. No other cases + * makes both descriptor non existent. */ + if (service->desc_current == NULL && service->desc_next == NULL) { + build_descriptors_for_new_service(service, now); + continue; + } + + /* Reaching this point means we are pass bootup so at runtime. We should + * *never* have an empty current descriptor. If the next descriptor is + * empty, we'll try to build it for the next time period. This only + * happens when we rotate meaning that we are guaranteed to have a new SRV + * at that point for the next time period. */ + tor_assert(service->desc_current); + + if (service->desc_next == NULL) { + build_service_descriptor(service, now, hs_get_next_time_period_num(0), + &service->desc_next); + log_info(LD_REND, "Hidden service %s next descriptor successfully " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); + } + } FOR_EACH_DESCRIPTOR_END; +} + +/* Randomly pick a node to become an introduction point but not present in the + * given exclude_nodes list. The chosen node is put in the exclude list + * regardless of success or not because in case of failure, the node is simply + * unsusable from that point on. + * + * If direct_conn is set, try to pick a node that our local firewall/policy + * allows us to connect to directly. If we can't find any, return NULL. + * This function supports selecting dual-stack nodes for direct single onion + * service IPv6 connections. But it does not send IPv6 addresses in link + * specifiers. (Current clients don't use IPv6 addresses to extend, and + * direct client connections to intro points are not supported.) + * + * Return a newly allocated service intro point ready to be used for encoding. + * Return NULL on error. */ +static hs_service_intro_point_t * +pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes) +{ + const node_t *node; + extend_info_t *info = NULL; + hs_service_intro_point_t *ip = NULL; + /* Normal 3-hop introduction point flags. */ + router_crn_flags_t flags = CRN_NEED_UPTIME | CRN_NEED_DESC; + /* Single onion flags. */ + router_crn_flags_t direct_flags = flags | CRN_PREF_ADDR | CRN_DIRECT_CONN; + + node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes, + direct_conn ? direct_flags : flags); + /* Unable to find a node. When looking for a node for a direct connection, + * we could try a 3-hop path instead. We'll add support for this in a later + * release. */ + if (!node) { + goto err; + } + + /* We have a suitable node, add it to the exclude list. We do this *before* + * we can validate the extend information because even in case of failure, + * we don't want to use that node anymore. */ + smartlist_add(exclude_nodes, (void *) node); + + /* We do this to ease our life but also this call makes appropriate checks + * of the node object such as validating ntor support for instance. + * + * We must provide an extend_info for clients to connect over a 3-hop path, + * so we don't pass direct_conn here. */ + info = extend_info_from_node(node, 0); + if (BUG(info == NULL)) { + goto err; + } + + /* Let's do a basic sanity check here so that we don't end up advertising the + * ed25519 identity key of relays that don't actually support the link + * protocol */ + if (!node_supports_ed25519_link_authentication(node)) { + tor_assert_nonfatal(ed25519_public_key_is_zero(&info->ed_identity)); + } else { + /* Make sure we *do* have an ed key if we support the link authentication. + * Sending an empty key would result in a failure to extend. */ + tor_assert_nonfatal(!ed25519_public_key_is_zero(&info->ed_identity)); + } + + /* Create our objects and populate them with the node information. */ + ip = service_intro_point_new(info, !node_supports_ed25519_hs_intro(node)); + if (ip == NULL) { + goto err; + } + + log_info(LD_REND, "Picked intro point: %s", extend_info_describe(info)); + extend_info_free(info); + return ip; err: - trn_cell_establish_intro_free(cell); + service_intro_point_free(ip); + extend_info_free(info); return NULL; } +/* For a given descriptor from the given service, pick any needed intro points + * and update the current map with those newly picked intro points. Return the + * number node that might have been added to the descriptor current map. */ +static unsigned int +pick_needed_intro_points(hs_service_t *service, + hs_service_descriptor_t *desc) +{ + int i = 0, num_needed_ip; + smartlist_t *exclude_nodes = smartlist_new(); + + tor_assert(service); + tor_assert(desc); + + /* Compute how many intro points we actually need to open. */ + num_needed_ip = service->config.num_intro_points - + digest256map_size(desc->intro_points.map); + if (BUG(num_needed_ip < 0)) { + /* Let's not make tor freak out here and just skip this. */ + goto done; + } + + /* We want to end up with config.num_intro_points intro points, but if we + * have no intro points at all (chances are they all cycled or we are + * starting up), we launch get_intro_point_num_extra() extra circuits and + * use the first config.num_intro_points that complete. See proposal #155, + * section 4 for the rationale of this which is purely for performance. + * + * The ones after the first config.num_intro_points will be converted to + * 'General' internal circuits and then we'll drop them from the list of + * intro points. */ + if (digest256map_size(desc->intro_points.map) == 0) { + num_needed_ip += get_intro_point_num_extra(); + } + + /* Build an exclude list of nodes of our intro point(s). The expiring intro + * points are OK to pick again because this is afterall a concept of round + * robin so they are considered valid nodes to pick again. */ + DIGEST256MAP_FOREACH(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *intro_node = get_node_from_intro_point(ip); + if (intro_node) { + smartlist_add(exclude_nodes, (void*)intro_node); + } + } DIGEST256MAP_FOREACH_END; + /* Also, add the failing intro points that our descriptor encounteered in + * the exclude node list. */ + setup_intro_point_exclude_list(desc, exclude_nodes); + + for (i = 0; i < num_needed_ip; i++) { + hs_service_intro_point_t *ip; + + /* This function will add the picked intro point node to the exclude nodes + * list so we don't pick the same one at the next iteration. */ + ip = pick_intro_point(service->config.is_single_onion, exclude_nodes); + if (ip == NULL) { + /* If we end up unable to pick an introduction point it is because we + * can't find suitable node and calling this again is highly unlikely to + * give us a valid node all of the sudden. */ + log_info(LD_REND, "Unable to find a suitable node to be an " + "introduction point for service %s.", + safe_str_client(service->onion_address)); + goto done; + } + /* Valid intro point object, add it to the descriptor current map. */ + service_intro_point_add(desc->intro_points.map, ip); + } + /* We've successfully picked all our needed intro points thus none are + * missing which will tell our upload process to expect the number of + * circuits to be the number of configured intro points circuits and not the + * number of intro points object that we have. */ + desc->missing_intro_points = 0; + + /* Success. */ + done: + /* We don't have ownership of the node_t object in this list. */ + smartlist_free(exclude_nodes); + return i; +} + +/** Clear previous cached HSDirs in <b>desc</b>. */ +static void +service_desc_clear_previous_hsdirs(hs_service_descriptor_t *desc) +{ + if (BUG(!desc->previous_hsdirs)) { + return; + } + + SMARTLIST_FOREACH(desc->previous_hsdirs, char*, s, tor_free(s)); + smartlist_clear(desc->previous_hsdirs); +} + +/** Note that we attempted to upload <b>desc</b> to <b>hsdir</b>. */ +static void +service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir->identity); + + if (BUG(!desc->previous_hsdirs)) { + return; + } + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + smartlist_add_strdup(desc->previous_hsdirs, b64_digest); + } +} + +/** Schedule an upload of <b>desc</b>. If <b>descriptor_changed</b> is set, it + * means that this descriptor is dirty. */ +STATIC void +service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed) + +{ + desc->next_upload_time = now; + + /* If the descriptor changed, clean up the old HSDirs list. We want to + * re-upload no matter what. */ + if (descriptor_changed) { + service_desc_clear_previous_hsdirs(desc); + } +} + +/* Update the given descriptor from the given service. The possible update + * actions includes: + * - Picking missing intro points if needed. + * - Incrementing the revision counter if needed. + */ +static void +update_service_descriptor(hs_service_t *service, + hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + tor_assert(desc->desc); + + num_intro_points = digest256map_size(desc->intro_points.map); + + /* Pick any missing introduction point(s). */ + if (num_intro_points < service->config.num_intro_points) { + unsigned int num_new_intro_points = pick_needed_intro_points(service, + desc); + if (num_new_intro_points != 0) { + log_info(LD_REND, "Service %s just picked %u intro points and wanted " + "%u for %s descriptor. It currently has %d intro " + "points. Launching ESTABLISH_INTRO circuit shortly.", + safe_str_client(service->onion_address), + num_new_intro_points, + service->config.num_intro_points - num_intro_points, + (desc == service->desc_current) ? "current" : "next", + num_intro_points); + /* We'll build those introduction point into the descriptor once we have + * confirmation that the circuits are opened and ready. However, + * indicate that this descriptor should be uploaded from now on. */ + service_desc_schedule_upload(desc, now, 1); + } + /* Were we able to pick all the intro points we needed? If not, we'll + * flag the descriptor that it's missing intro points because it + * couldn't pick enough which will trigger a descriptor upload. */ + if ((num_new_intro_points + num_intro_points) < + service->config.num_intro_points) { + desc->missing_intro_points = 1; + } + } +} + +/* Update descriptors for each service if needed. */ +STATIC void +update_all_descriptors(time_t now) +{ + FOR_EACH_SERVICE_BEGIN(service) { + /* We'll try to update each descriptor that is if certain conditions apply + * in order for the descriptor to be updated. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + update_service_descriptor(service, desc, now); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; +} + +/* Return true iff the given intro point has expired that is it has been used + * for too long or we've reached our max seen INTRODUCE2 cell. */ +STATIC int +intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now) +{ + tor_assert(ip); + + if (ip->introduce2_count >= ip->introduce2_max) { + goto expired; + } + + if (ip->time_to_expire <= now) { + goto expired; + } + + /* Not expiring. */ + return 0; + expired: + return 1; +} + +/* Go over the given set of intro points for each service and remove any + * invalid ones. The conditions for removal are: + * + * - The node doesn't exists anymore (not in consensus) + * OR + * - The intro point maximum circuit retry count has been reached and no + * circuit can be found associated with it. + * OR + * - The intro point has expired and we should pick a new one. + * + * If an intro point is removed, the circuit (if any) is immediately close. + * If a circuit can't be found, the intro point is kept if it hasn't reached + * its maximum circuit retry value and thus should be retried. */ +static void +cleanup_intro_points(hs_service_t *service, time_t now) +{ + /* List of intro points to close. We can't mark the intro circuits for close + * in the modify loop because doing so calls + * hs_service_intro_circ_has_closed() which does a digest256map_get() on the + * intro points map (that we are iterating over). This can't be done in a + * single iteration after a MAP_DEL_CURRENT, the object will still be + * returned leading to a use-after-free. So, we close the circuits and free + * the intro points after the loop if any. */ + smartlist_t *ips_to_free = smartlist_new(); + + tor_assert(service); + + /* For both descriptors, cleanup the intro points. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Go over the current intro points we have, make sure they are still + * valid and remove any of them that aren't. */ + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + const node_t *node = get_node_from_intro_point(ip); + int has_expired = intro_point_should_expire(ip, now); + + /* We cleanup an intro point if it has expired or if we do not know the + * node_t anymore (removed from our latest consensus) or if we've + * reached the maximum number of retry with a non existing circuit. */ + if (has_expired || node == NULL || + ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + log_info(LD_REND, "Intro point %s%s (retried: %u times). " + "Removing it.", + describe_intro_point(ip), + has_expired ? " has expired" : + (node == NULL) ? " fell off the consensus" : "", + ip->circuit_retries); + + /* We've retried too many times, remember it as a failed intro point + * so we don't pick it up again for INTRO_CIRC_RETRY_PERIOD sec. */ + if (ip->circuit_retries > MAX_INTRO_POINT_CIRCUIT_RETRIES) { + remember_failing_intro_point(ip, desc, approx_time()); + } + + /* Remove intro point from descriptor map and add it to the list of + * ips to free for which we'll also try to close the intro circuit. */ + MAP_DEL_CURRENT(key); + smartlist_add(ips_to_free, ip); + } + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; + + /* Go over the intro points to free and close their circuit if any. */ + SMARTLIST_FOREACH_BEGIN(ips_to_free, hs_service_intro_point_t *, ip) { + /* See if we need to close the intro point circuit as well */ + + /* XXX: Legacy code does NOT close circuits like this: it keeps the circuit + * open until a new descriptor is uploaded and then closed all expiring + * intro point circuit. Here, we close immediately and because we just + * discarded the intro point, a new one will be selected, a new descriptor + * created and uploaded. There is no difference to an attacker between the + * timing of a new consensus and intro point rotation (possibly?). */ + origin_circuit_t *ocirc = hs_circ_service_get_intro_circ(ip); + if (ocirc && !TO_CIRCUIT(ocirc)->marked_for_close) { + circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED); + } + + /* Cleanup the intro point */ + service_intro_point_free(ip); + } SMARTLIST_FOREACH_END(ip); + + smartlist_free(ips_to_free); +} + +/* Set the next rotation time of the descriptors for the given service for the + * time now. */ +static void +set_rotation_time(hs_service_t *service, time_t now) +{ + time_t valid_after; + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + if (ns) { + valid_after = ns->valid_after; + } else { + valid_after = now; + } + + tor_assert(service); + service->state.next_rotation_time = + sr_state_get_start_time_of_current_protocol_run(valid_after) + + sr_state_get_protocol_run_duration(); + + { + char fmt_time[ISO_TIME_LEN + 1]; + format_local_iso_time(fmt_time, service->state.next_rotation_time); + log_info(LD_REND, "Next descriptor rotation time set to %s for %s", + fmt_time, safe_str_client(service->onion_address)); + } +} + +/* Return true iff the service should rotate its descriptor. The time now is + * only used to fetch the live consensus and if none can be found, this + * returns false. */ +static unsigned int +should_rotate_descriptors(hs_service_t *service, time_t now) +{ + const networkstatus_t *ns; + + tor_assert(service); + + ns = networkstatus_get_live_consensus(now); + if (ns == NULL) { + goto no_rotation; + } + + if (ns->valid_after >= service->state.next_rotation_time) { + goto rotation; + } + + no_rotation: + return 0; + rotation: + return 1; +} + +/* Rotate the service descriptors of the given service. The current descriptor + * will be freed, the next one put in as the current and finally the next + * descriptor pointer is NULLified. */ +static void +rotate_service_descriptors(hs_service_t *service, time_t now) +{ + if (service->desc_current) { + /* Close all IP circuits for the descriptor. */ + close_intro_circuits(&service->desc_current->intro_points); + /* We don't need this one anymore, we won't serve any clients coming with + * this service descriptor. */ + service_descriptor_free(service->desc_current); + } + /* The next one become the current one and emptying the next will trigger + * a descriptor creation for it. */ + service->desc_current = service->desc_next; + service->desc_next = NULL; + + /* We've just rotated, set the next time for the rotation. */ + set_rotation_time(service, now); +} + +/* Rotate descriptors for each service if needed. A non existing current + * descriptor will trigger a descriptor build for the next time period. */ +STATIC void +rotate_all_descriptors(time_t now) +{ + /* XXX We rotate all our service descriptors at once. In the future it might + * be wise, to rotate service descriptors independently to hide that all + * those descriptors are on the same tor instance */ + + FOR_EACH_SERVICE_BEGIN(service) { + + /* Note for a service booting up: Both descriptors are NULL in that case + * so this function might return true if we are in the timeframe for a + * rotation leading to basically swapping two NULL pointers which is + * harmless. However, the side effect is that triggering a rotation will + * update the service state and avoid doing anymore rotations after the + * two descriptors have been built. */ + if (!should_rotate_descriptors(service, now)) { + continue; + } + + tor_assert(service->desc_current); + tor_assert(service->desc_next); + + log_info(LD_REND, "Time to rotate our descriptors (%p / %p) for %s", + service->desc_current, service->desc_next, + safe_str_client(service->onion_address)); + + rotate_service_descriptors(service, now); + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all our services are up + * to date and ready for the other scheduled events. This includes looking at + * the introduction points status and descriptor rotation time. */ +STATIC void +run_housekeeping_event(time_t now) +{ + /* Note that nothing here opens circuit(s) nor uploads descriptor(s). We are + * simply moving things around or removing unneeded elements. */ + + FOR_EACH_SERVICE_BEGIN(service) { + + /* If the service is starting off, set the rotation time. We can't do that + * at configure time because the get_options() needs to be set for setting + * that time that uses the voting interval. */ + if (service->state.next_rotation_time == 0) { + /* Set the next rotation time of the descriptors. If it's Oct 25th + * 23:47:00, the next rotation time is when the next SRV is computed + * which is at Oct 26th 00:00:00 that is in 13 minutes. */ + set_rotation_time(service, now); + } + + /* Cleanup invalid intro points from the service descriptor. */ + cleanup_intro_points(service, now); + + /* Remove expired failing intro point from the descriptor failed list. We + * reset them at each INTRO_CIRC_RETRY_PERIOD. */ + remove_expired_failing_intro(service, now); + + /* At this point, the service is now ready to go through the scheduled + * events guaranteeing a valid state. Intro points might be missing from + * the descriptors after the cleanup but the update/build process will + * make sure we pick those missing ones. */ + } FOR_EACH_SERVICE_END; +} + +/* Scheduled event run from the main loop. Make sure all descriptors are up to + * date. Once this returns, each service descriptor needs to be considered for + * new introduction circuits and then for upload. */ +static void +run_build_descriptor_event(time_t now) +{ + /* For v2 services, this step happens in the upload event. */ + + /* Run v3+ events. */ + /* We start by rotating the descriptors only if needed. */ + rotate_all_descriptors(now); + + /* Then, we'll try to build new descriptors that we might need. The + * condition is that the next descriptor is non existing because it has + * been rotated or we just started up. */ + build_all_descriptors(now); + + /* Finally, we'll check if we should update the descriptors. Missing + * introduction points will be picked in this function which is useful for + * newly built descriptors. */ + update_all_descriptors(now); +} + +/* For the given service, launch any intro point circuits that could be + * needed. This considers every descriptor of the service. */ +static void +launch_intro_point_circuits(hs_service_t *service) +{ + tor_assert(service); + + /* For both descriptors, try to launch any missing introduction point + * circuits using the current map. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* Keep a ref on if we need a direct connection. We use this often. */ + unsigned int direct_conn = service->config.is_single_onion; + + DIGEST256MAP_FOREACH_MODIFY(desc->intro_points.map, key, + hs_service_intro_point_t *, ip) { + extend_info_t *ei; + + /* Skip the intro point that already has an existing circuit + * (established or not). */ + if (hs_circ_service_get_intro_circ(ip)) { + continue; + } + + ei = get_extend_info_from_intro_point(ip, direct_conn); + if (ei == NULL) { + /* This is possible if we can get a node_t but not the extend info out + * of it. In this case, we remove the intro point and a new one will + * be picked at the next main loop callback. */ + MAP_DEL_CURRENT(key); + service_intro_point_free(ip); + continue; + } + + /* Launch a circuit to the intro point. */ + ip->circuit_retries++; + if (hs_circ_launch_intro_point(service, ip, ei) < 0) { + log_info(LD_REND, "Unable to launch intro circuit to node %s " + "for service %s.", + safe_str_client(extend_info_describe(ei)), + safe_str_client(service->onion_address)); + /* Intro point will be retried if possible after this. */ + } + extend_info_free(ei); + } DIGEST256MAP_FOREACH_END; + } FOR_EACH_DESCRIPTOR_END; +} + +/* Don't try to build more than this many circuits before giving up for a + * while. Dynamically calculated based on the configured number of intro + * points for the given service and how many descriptor exists. The default + * use case of 3 introduction points and two descriptors will allow 28 + * circuits for a retry period (((3 + 2) + (3 * 3)) * 2). */ +static unsigned int +get_max_intro_circ_per_period(const hs_service_t *service) +{ + unsigned int count = 0; + unsigned int multiplier = 0; + unsigned int num_wanted_ip; + + tor_assert(service); + tor_assert(service->config.num_intro_points <= + HS_CONFIG_V3_MAX_INTRO_POINTS); + +/* For a testing network, allow to do it for the maximum amount so circuit + * creation and rotation and so on can actually be tested without limit. */ +#define MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING -1 + if (get_options()->TestingTorNetwork) { + return MAX_INTRO_POINT_CIRCUIT_RETRIES_TESTING; + } + + num_wanted_ip = service->config.num_intro_points; + + /* The calculation is as follow. We have a number of intro points that we + * want configured as a torrc option (num_intro_points). We then add an + * extra value so we can launch multiple circuits at once and pick the + * quickest ones. For instance, we want 3 intros, we add 2 extra so we'll + * pick 5 intros and launch 5 circuits. */ + count += (num_wanted_ip + get_intro_point_num_extra()); + + /* Then we add the number of retries that is possible to do for each intro + * point. If we want 3 intros, we'll allow 3 times the number of possible + * retry. */ + count += (num_wanted_ip * MAX_INTRO_POINT_CIRCUIT_RETRIES); + + /* Then, we multiply by a factor of 2 if we have both descriptor or 0 if we + * have none. */ + multiplier += (service->desc_current) ? 1 : 0; + multiplier += (service->desc_next) ? 1 : 0; + + return (count * multiplier); +} + +/* For the given service, return 1 if the service is allowed to launch more + * introduction circuits else 0 if the maximum has been reached for the retry + * period of INTRO_CIRC_RETRY_PERIOD. */ +STATIC int +can_service_launch_intro_circuit(hs_service_t *service, time_t now) +{ + tor_assert(service); + + /* Consider the intro circuit retry period of the service. */ + if (now > (service->state.intro_circ_retry_started_time + + INTRO_CIRC_RETRY_PERIOD)) { + service->state.intro_circ_retry_started_time = now; + service->state.num_intro_circ_launched = 0; + goto allow; + } + /* Check if we can still launch more circuits in this period. */ + if (service->state.num_intro_circ_launched <= + get_max_intro_circ_per_period(service)) { + goto allow; + } + + /* Rate limit log that we've reached our circuit creation limit. */ + { + char *msg; + time_t elapsed_time = now - service->state.intro_circ_retry_started_time; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, now))) { + log_info(LD_REND, "Hidden service %s exceeded its circuit launch limit " + "of %u per %d seconds. It launched %u circuits in " + "the last %ld seconds. Will retry in %ld seconds.", + safe_str_client(service->onion_address), + get_max_intro_circ_per_period(service), + INTRO_CIRC_RETRY_PERIOD, + service->state.num_intro_circ_launched, + (long int) elapsed_time, + (long int) (INTRO_CIRC_RETRY_PERIOD - elapsed_time)); + tor_free(msg); + } + } + + /* Not allow. */ + return 0; + allow: + return 1; +} + +/* Scheduled event run from the main loop. Make sure we have all the circuits + * we need for each service. */ +static void +run_build_circuit_event(time_t now) +{ + /* Make sure we can actually have enough information or able to build + * internal circuits as required by services. */ + if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN || + !have_completed_a_circuit()) { + return; + } + + /* Run v2 check. */ + if (rend_num_services() > 0) { + rend_consider_services_intro_points(now); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* For introduction circuit, we need to make sure we don't stress too much + * circuit creation so make sure this service is respecting that limit. */ + if (can_service_launch_intro_circuit(service, now)) { + /* Launch intro point circuits if needed. */ + launch_intro_point_circuits(service); + /* Once the circuits have opened, we'll make sure to update the + * descriptor intro point list and cleanup any extraneous. */ + } + } FOR_EACH_SERVICE_END; +} + +/* Encode and sign the service descriptor desc and upload it to the given + * hidden service directory. This does nothing if PublishHidServDescriptors + * is false. */ +static void +upload_descriptor_to_hsdir(const hs_service_t *service, + hs_service_descriptor_t *desc, const node_t *hsdir) +{ + char version_str[4] = {0}, *encoded_desc = NULL; + directory_request_t *dir_req; + hs_ident_dir_conn_t ident; + + tor_assert(service); + tor_assert(desc); + tor_assert(hsdir); + + memset(&ident, 0, sizeof(ident)); + + /* Let's avoid doing that if tor is configured to not publish. */ + if (!get_options()->PublishHidServDescriptors) { + log_info(LD_REND, "Service %s not publishing descriptor. " + "PublishHidServDescriptors is set to 1.", + safe_str_client(service->onion_address)); + goto end; + } + + /* First of all, we'll encode the descriptor. This should NEVER fail but + * just in case, let's make sure we have an actual usable descriptor. */ + if (BUG(hs_desc_encode_descriptor(desc->desc, &desc->signing_kp, + &encoded_desc) < 0)) { + goto end; + } + + /* Setup the connection identifier. */ + hs_ident_dir_conn_init(&service->keys.identity_pk, &desc->blinded_kp.pubkey, + &ident); + + /* This is our resource when uploading which is used to construct the URL + * with the version number: "/tor/hs/<version>/publish". */ + tor_snprintf(version_str, sizeof(version_str), "%u", + service->config.version); + + /* Build the directory request for this HSDir. */ + dir_req = directory_request_new(DIR_PURPOSE_UPLOAD_HSDESC); + directory_request_set_routerstatus(dir_req, hsdir->rs); + directory_request_set_indirection(dir_req, DIRIND_ANONYMOUS); + directory_request_set_resource(dir_req, version_str); + directory_request_set_payload(dir_req, encoded_desc, + strlen(encoded_desc)); + /* The ident object is copied over the directory connection object once + * the directory request is initiated. */ + directory_request_upload_set_hs_ident(dir_req, &ident); + + /* Initiate the directory request to the hsdir.*/ + directory_initiate_request(dir_req); + directory_request_free(dir_req); + + /* Add this node to previous_hsdirs list */ + service_desc_note_upload(desc, hsdir); + + /* Logging so we know where it was sent. */ + { + int is_next_desc = (service->desc_next == desc); + const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->store_second: + hsdir->hsdir_index->store_first; + log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 + " initiated upload request to %s with index %s", + safe_str_client(service->onion_address), + (is_next_desc) ? "next" : "current", + desc->desc->plaintext_data.revision_counter, + safe_str_client(node_describe(hsdir)), + safe_str_client(hex_str((const char *) index, 32))); + } + + /* XXX: Inform control port of the upload event (#20699). */ + end: + tor_free(encoded_desc); + return; +} + +/** Return a newly-allocated string for our state file which contains revision + * counter information for <b>desc</b>. The format is: + * + * HidServRevCounter <blinded_pubkey> <rev_counter> + */ +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc) +{ + char *state_str = NULL; + char blinded_pubkey_b64[ED25519_BASE64_LEN+1]; + uint64_t rev_counter = desc->desc->plaintext_data.revision_counter; + const ed25519_public_key_t *blinded_pubkey = &desc->blinded_kp.pubkey; + + /* Turn the blinded key into b64 so that we save it on state */ + tor_assert(blinded_pubkey); + if (ed25519_public_to_base64(blinded_pubkey_b64, blinded_pubkey) < 0) { + goto done; + } + + /* Format is: <blinded key> <rev counter> */ + tor_asprintf(&state_str, "%s %" PRIu64, blinded_pubkey_b64, rev_counter); + + log_info(LD_GENERAL, "[!] Adding rev counter %" PRIu64 " for %s!", + rev_counter, blinded_pubkey_b64); + + done: + return state_str; +} + +/** Update HS descriptor revision counters in our state by removing the old + * ones and writing down the ones that are currently active. */ +static void +update_revision_counters_in_state(void) +{ + config_line_t *lines = NULL; + config_line_t **nextline = &lines; + or_state_t *state = get_or_state(); + + /* Prepare our state structure with the rev counters */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* We don't want to save zero counters */ + if (desc->desc->plaintext_data.revision_counter == 0) { + continue; + } + + *nextline = tor_malloc_zero(sizeof(config_line_t)); + (*nextline)->key = tor_strdup("HidServRevCounter"); + (*nextline)->value = encode_desc_rev_counter_for_state(desc); + nextline = &(*nextline)->next; + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* Remove the old rev counters, and replace them with the new ones */ + config_free_lines(state->HidServRevCounter); + state->HidServRevCounter = lines; + + /* Set the state as dirty since we just edited it */ + if (!get_options()->AvoidDiskWrites) { + or_state_mark_dirty(state, 0); + } +} + +/** Scan the string <b>state_line</b> for the revision counter of the service + * with <b>blinded_pubkey</b>. Set <b>service_found_out</b> to True if the + * line is relevant to this service, and return the cached revision + * counter. Else set <b>service_found_out</b> to False. */ +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out) +{ + smartlist_t *items = NULL; + int ok; + ed25519_public_key_t pubkey_in_state; + uint64_t rev_counter = 0; + + tor_assert(service_found_out); + tor_assert(state_line); + tor_assert(blinded_pubkey); + + /* Assume that the line is not for this service */ + *service_found_out = 0; + + /* Start parsing the state line */ + items = smartlist_new(); + smartlist_split_string(items, state_line, NULL, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1); + if (smartlist_len(items) < 2) { + log_warn(LD_GENERAL, "Incomplete rev counter line. Ignoring."); + goto done; + } + + char *b64_key_str = smartlist_get(items, 0); + char *saved_rev_counter_str = smartlist_get(items, 1); + + /* Parse blinded key to check if it's for this hidden service */ + if (ed25519_public_from_base64(&pubkey_in_state, b64_key_str) < 0) { + log_warn(LD_GENERAL, "Unable to base64 key in revcount line. Ignoring."); + goto done; + } + /* State line not for this hidden service */ + if (!ed25519_pubkey_eq(&pubkey_in_state, blinded_pubkey)) { + goto done; + } + + rev_counter = tor_parse_uint64(saved_rev_counter_str, + 10, 0, UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_GENERAL, "Unable to parse rev counter. Ignoring."); + goto done; + } + + /* Since we got this far, the line was for this service */ + *service_found_out = 1; + + log_info(LD_GENERAL, "Found rev counter for %s: %" PRIu64, + b64_key_str, rev_counter); + + done: + tor_assert(items); + SMARTLIST_FOREACH(items, char*, s, tor_free(s)); + smartlist_free(items); + + return rev_counter; +} + +/** Dig into our state file and find the current revision counter for the + * service with blinded key <b>blinded_pubkey</b>. If no revision counter is + * found, return 0. */ +static uint64_t +get_rev_counter_for_service(const ed25519_public_key_t *blinded_pubkey) +{ + or_state_t *state = get_or_state(); + config_line_t *line; + + /* Set default value for rev counters (if not found) to 0 */ + uint64_t final_rev_counter = 0; + + for (line = state->HidServRevCounter ; line ; line = line->next) { + int service_found = 0; + uint64_t rev_counter = 0; + + tor_assert(!strcmp(line->key, "HidServRevCounter")); + + /* Scan all the HidServRevCounter lines till we find the line for this + service: */ + rev_counter = check_state_line_for_service_rev_counter(line->value, + blinded_pubkey, + &service_found); + if (service_found) { + final_rev_counter = rev_counter; + goto done; + } + } + + done: + return final_rev_counter; +} + +/** Update the value of the revision counter for <b>hs_desc</b> and save it on + our state file. */ +static void +increment_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + /* Increment the revision counter of <b>hs_desc</b> so the next update (which + * will trigger an upload) will have the right value. We do this at this + * stage to only do it once because a descriptor can have many updates before + * being uploaded. By doing it at upload, we are sure to only increment by 1 + * and thus avoid leaking how many operations we made on the descriptor from + * the previous one before uploading. */ + rev_counter++; + hs_desc->plaintext_data.revision_counter = rev_counter; + + update_revision_counters_in_state(); +} + +/** Set the revision counter in <b>hs_desc</b>, using the state file to find + * the current counter value if it exists. */ +static void +set_descriptor_revision_counter(hs_descriptor_t *hs_desc) +{ + /* Find stored rev counter if it exists */ + uint64_t rev_counter = + get_rev_counter_for_service(&hs_desc->plaintext_data.blinded_pubkey); + + hs_desc->plaintext_data.revision_counter = rev_counter; +} + +/* Encode and sign the service descriptor desc and upload it to the + * responsible hidden service directories. If for_next_period is true, the set + * of directories are selected using the next hsdir_index. This does nothing + * if PublishHidServDescriptors is false. */ +STATIC void +upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc) +{ + smartlist_t *responsible_dirs = NULL; + + tor_assert(service); + tor_assert(desc); + + /* We'll first cancel any directory request that are ongoing for this + * descriptor. It is possible that we can trigger multiple uploads in a + * short time frame which can lead to a race where the second upload arrives + * before the first one leading to a 400 malformed descriptor response from + * the directory. Closing all pending requests avoids that. */ + close_directory_connections(service, desc); + + /* Get our list of responsible HSDir. */ + responsible_dirs = smartlist_new(); + /* The parameter 0 means that we aren't a client so tell the function to use + * the spread store consensus paremeter. */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /** Clear list of previous hsdirs since we are about to upload to a new + * list. Let's keep it up to date. */ + service_desc_clear_previous_hsdirs(desc); + + /* For each responsible HSDir we have, initiate an upload command. */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, + hsdir_rs) { + const node_t *hsdir_node = node_get_by_id(hsdir_rs->identity_digest); + /* Getting responsible hsdir implies that the node_t object exists for the + * routerstatus_t found in the consensus else we have a problem. */ + tor_assert(hsdir_node); + /* Upload this descriptor to the chosen directory. */ + upload_descriptor_to_hsdir(service, desc, hsdir_node); + } SMARTLIST_FOREACH_END(hsdir_rs); + + /* Set the next upload time for this descriptor. Even if we are configured + * to not upload, we still want to follow the right cycle of life for this + * descriptor. */ + desc->next_upload_time = + (time(NULL) + crypto_rand_int_range(HS_SERVICE_NEXT_UPLOAD_TIME_MIN, + HS_SERVICE_NEXT_UPLOAD_TIME_MAX)); + { + char fmt_next_time[ISO_TIME_LEN+1]; + format_local_iso_time(fmt_next_time, desc->next_upload_time); + log_debug(LD_REND, "Service %s set to upload a descriptor at %s", + safe_str_client(service->onion_address), fmt_next_time); + } + + /* Update the revision counter of this descriptor */ + increment_descriptor_revision_counter(desc->desc); + + smartlist_free(responsible_dirs); + return; +} + +/** The set of HSDirs have changed: check if the change affects our descriptor + * HSDir placement, and if it does, reupload the desc. */ +STATIC int +service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + int should_reupload = 0; + smartlist_t *responsible_dirs = smartlist_new(); + + /* No desc upload has happened yet: it will happen eventually */ + if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) { + goto done; + } + + /* Get list of responsible hsdirs */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /* Check if any new hsdirs have been added to the responsible hsdirs set: + * Iterate over the list of new hsdirs, and reupload if any of them is not + * present in the list of previous hsdirs. + */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) { + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir_rs->identity_digest); + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + should_reupload = 1; + break; + } + } SMARTLIST_FOREACH_END(hsdir_rs); + + done: + smartlist_free(responsible_dirs); + + return should_reupload; +} + +/* Return 1 if the given descriptor from the given service can be uploaded + * else return 0 if it can not. */ +static int +should_service_upload_descriptor(const hs_service_t *service, + const hs_service_descriptor_t *desc, time_t now) +{ + unsigned int num_intro_points; + + tor_assert(service); + tor_assert(desc); + + /* If this descriptors has missing intro points that is that it couldn't get + * them all when it was time to pick them, it means that we should upload + * instead of waiting an arbitrary amount of time breaking the service. + * Else, if we have no missing intro points, we use the value taken from the + * service configuration. */ + if (desc->missing_intro_points) { + num_intro_points = digest256map_size(desc->intro_points.map); + } else { + num_intro_points = service->config.num_intro_points; + } + + /* This means we tried to pick intro points but couldn't get any so do not + * upload descriptor in this case. We need at least one for the service to + * be reachable. */ + if (desc->missing_intro_points && num_intro_points == 0) { + goto cannot; + } + + /* Check if all our introduction circuit have been established for all the + * intro points we have selected. */ + if (count_desc_circuit_established(desc) != num_intro_points) { + goto cannot; + } + + /* Is it the right time to upload? */ + if (desc->next_upload_time > now) { + goto cannot; + } + + /* Don't upload desc if we don't have a live consensus */ + if (!networkstatus_get_live_consensus(now)) { + goto cannot; + } + + /* Do we know enough router descriptors to have adequate vision of the HSDir + hash ring? */ + if (!router_have_minimum_dir_info()) { + goto cannot; + } + + /* Can upload! */ + return 1; + cannot: + return 0; +} + +/* Scheduled event run from the main loop. Try to upload the descriptor for + * each service. */ +STATIC void +run_upload_descriptor_event(time_t now) +{ + /* v2 services use the same function for descriptor creation and upload so + * we do everything here because the intro circuits were checked before. */ + if (rend_num_services() > 0) { + rend_consider_services_upload(now); + rend_consider_descriptor_republication(); + } + + /* Run v3+ check. */ + FOR_EACH_SERVICE_BEGIN(service) { + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + /* If we were asked to re-examine the hash ring, and it changed, then + schedule an upload */ + if (consider_republishing_hs_descriptors && + service_desc_hsdirs_changed(service, desc)) { + service_desc_schedule_upload(desc, now, 0); + } + + /* Can this descriptor be uploaded? */ + if (!should_service_upload_descriptor(service, desc, now)) { + continue; + } + + log_info(LD_REND, "Initiating upload for hidden service %s descriptor " + "for service %s with %u/%u introduction points%s.", + (desc == service->desc_current) ? "current" : "next", + safe_str_client(service->onion_address), + digest256map_size(desc->intro_points.map), + service->config.num_intro_points, + (desc->missing_intro_points) ? " (couldn't pick more)" : ""); + + /* At this point, we have to upload the descriptor so start by building + * the intro points descriptor section which we are now sure to be + * accurate because all circuits have been established. */ + build_desc_intro_points(service, desc, now); + + upload_descriptor_to_all(service, desc); + } FOR_EACH_DESCRIPTOR_END; + } FOR_EACH_SERVICE_END; + + /* We are done considering whether to republish rend descriptors */ + consider_republishing_hs_descriptors = 0; +} + +/* Called when the introduction point circuit is done building and ready to be + * used. */ +static void +service_intro_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + /* Let's do some basic sanity checking of the circ state */ + if (BUG(!circ->cpath)) { + return; + } + if (BUG(TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)) { + return; + } + if (BUG(!circ->hs_ident)) { + return; + } + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + log_warn(LD_REND, "Unknown introduction point auth key on circuit %u " + "for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* We can't have an IP object without a descriptor. */ + tor_assert(desc); + + if (hs_circ_service_intro_has_opened(service, ip, desc, circ)) { + /* Getting here means that the circuit has been re-purposed because we + * have enough intro circuit opened. Remove the IP from the service. */ + service_intro_point_remove(service, ip); + service_intro_point_free(ip); + } + + goto done; + + err: + /* Close circuit, we can't use it. */ + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* Called when a rendezvous circuit is done building and ready to be used. */ +static void +service_rendezvous_circ_has_opened(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(circ->cpath); + /* Getting here means this is a v3 rendezvous circuit. */ + tor_assert(circ->hs_ident); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); + + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + pathbias_count_use_attempt(circ); + + /* Get the corresponding service and intro point. */ + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the rendezvous " + "circuit %u with cookie %s. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id, + hex_str((const char *) circ->hs_ident->rendezvous_cookie, + REND_COOKIE_LEN)); + goto err; + } + + /* If the cell can't be sent, the circuit will be closed within this + * function. */ + hs_circ_service_rp_has_opened(service, circ); + goto done; + + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOSUCHSERVICE); + done: + return; +} + +/* We've been expecting an INTRO_ESTABLISHED cell on this circuit and it just + * arrived. Handle the INTRO_ESTABLISHED cell arriving on the given + * introduction circuit. Return 0 on success else a negative value. */ +static int +service_handle_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); + + /* We need the service and intro point for this cell. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, NULL); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_REND, "Unknown service identity key %s on the introduction " + "circuit %u. Can't find onion service.", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_REND, "Introduction circuit established without an intro " + "point object on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + + /* Try to parse the payload into a cell making sure we do actually have a + * valid cell. On success, the ip object and circuit purpose is updated to + * reflect the fact that the introduction circuit is established. */ + if (hs_circ_handle_intro_established(service, ip, circ, payload, + payload_len) < 0) { + goto err; + } + + /* Flag that we have an established circuit for this intro point. This value + * is what indicates the upload scheduled event if we are ready to build the + * intro point into the descriptor and upload. */ + ip->circuit_established = 1; + + log_info(LD_REND, "Successfully received an INTRO_ESTABLISHED cell " + "on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + return 0; + + err: + return -1; +} + +/* We just received an INTRODUCE2 cell on the established introduction circuit + * circ. Handle the cell and return 0 on success else a negative value. */ +static int +service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + tor_assert(payload); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO); + + /* We'll need every object associated with this circuit. */ + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + + /* Get service object from the circuit identifier. */ + if (service == NULL) { + log_warn(LD_BUG, "Unknown service identity key %s when handling " + "an INTRODUCE2 cell on circuit %u", + safe_str_client(ed25519_fmt(&circ->hs_ident->identity_pk)), + TO_CIRCUIT(circ)->n_circ_id); + goto err; + } + if (ip == NULL) { + /* We don't recognize the key. */ + log_warn(LD_BUG, "Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit %u for service %s", + TO_CIRCUIT(circ)->n_circ_id, + safe_str_client(service->onion_address)); + goto err; + } + /* If we have an IP object, we MUST have a descriptor object. */ + tor_assert(desc); + + /* The following will parse, decode and launch the rendezvous point circuit. + * Both current and legacy cells are handled. */ + if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, + payload, payload_len) < 0) { + goto err; + } + + return 0; + err: + return -1; +} + +/* Add to list every filename used by service. This is used by the sandbox + * subsystem. */ +static void +service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) +{ + const char *s_dir; + char fname[128] = {0}; + + tor_assert(service); + tor_assert(list); + + /* Ease our life. */ + s_dir = service->config.directory_path; + /* The hostname file. */ + smartlist_add(list, hs_path_from_filename(s_dir, fname_hostname)); + /* The key files splitted in two. */ + tor_snprintf(fname, sizeof(fname), "%s_secret_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); + tor_snprintf(fname, sizeof(fname), "%s_public_key", fname_keyfile_prefix); + smartlist_add(list, hs_path_from_filename(s_dir, fname)); +} + +/* ========== */ +/* Public API */ +/* ========== */ + +/* Return the number of service we have configured and usable. */ +unsigned int +hs_service_get_num_services(void) +{ + if (hs_service_map == NULL) { + return 0; + } + return HT_SIZE(hs_service_map); +} + +/* Called once an introduction circuit is closed. If the circuit doesn't have + * a v3 identifier, it is ignored. */ +void +hs_service_intro_circ_has_closed(origin_circuit_t *circ) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_service_descriptor_t *desc = NULL; + + tor_assert(circ); + + if (circ->hs_ident == NULL) { + /* This is not a v3 circuit, ignore. */ + goto end; + } + + get_objects_from_ident(circ->hs_ident, &service, &ip, &desc); + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on intro circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + goto end; + } + if (ip == NULL) { + /* The introduction point object has already been removed probably by our + * cleanup process so ignore. */ + goto end; + } + /* Can't have an intro point object without a descriptor. */ + tor_assert(desc); + + /* Circuit disappeared so make sure the intro point is updated. By + * keeping the object in the descriptor, we'll be able to retry. */ + ip->circuit_established = 0; + + end: + return; +} + +/* Given conn, a rendezvous edge connection acting as an exit stream, look up + * the hidden service for the circuit circ, and look up the port and address + * based on the connection port. Assign the actual connection address. + * + * Return 0 on success. Return -1 on failure and the caller should NOT close + * the circuit. Return -2 on failure and the caller MUST close the circuit for + * security reasons. */ +int +hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn) +{ + hs_service_t *service = NULL; + + tor_assert(circ); + tor_assert(conn); + tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_REND_JOINED); + tor_assert(circ->hs_ident); + + get_objects_from_ident(circ->hs_ident, &service, NULL, NULL); + + if (service == NULL) { + log_warn(LD_REND, "Unable to find any hidden service associated " + "identity key %s on rendezvous circuit %u.", + ed25519_fmt(&circ->hs_ident->identity_pk), + TO_CIRCUIT(circ)->n_circ_id); + /* We want the caller to close the circuit because it's not a valid + * service so no danger. Attempting to bruteforce the entire key space by + * opening circuits to learn which service is being hosted here is + * impractical. */ + goto err_close; + } + + /* Enforce the streams-per-circuit limit, and refuse to provide a mapping if + * this circuit will exceed the limit. */ + if (service->config.max_streams_per_rdv_circuit > 0 && + (circ->hs_ident->num_rdv_streams >= + service->config.max_streams_per_rdv_circuit)) { +#define MAX_STREAM_WARN_INTERVAL 600 + static struct ratelim_t stream_ratelim = + RATELIM_INIT(MAX_STREAM_WARN_INTERVAL); + log_fn_ratelim(&stream_ratelim, LOG_WARN, LD_REND, + "Maximum streams per circuit limit reached on " + "rendezvous circuit %u for service %s. Circuit has " + "%" PRIu64 " out of %" PRIu64 " streams. %s.", + TO_CIRCUIT(circ)->n_circ_id, + service->onion_address, + circ->hs_ident->num_rdv_streams, + service->config.max_streams_per_rdv_circuit, + service->config.max_streams_close_circuit ? + "Closing circuit" : "Ignoring open stream request"); + if (service->config.max_streams_close_circuit) { + /* Service explicitly configured to close immediately. */ + goto err_close; + } + /* Exceeding the limit makes tor silently ignore the stream creation + * request and keep the circuit open. */ + goto err_no_close; + } + + /* Find a virtual port of that service mathcing the one in the connection if + * succesful, set the address in the connection. */ + if (hs_set_conn_addr_port(service->config.ports, conn) < 0) { + log_info(LD_REND, "No virtual port mapping exists for port %d for " + "hidden service %s.", + TO_CONN(conn)->port, service->onion_address); + if (service->config.allow_unknown_ports) { + /* Service explicitly allow connection to unknown ports so close right + * away because we do not care about port mapping. */ + goto err_close; + } + /* If the service didn't explicitly allow it, we do NOT close the circuit + * here to raise the bar in terms of performance for port mapping. */ + goto err_no_close; + } + + /* Success. */ + return 0; + err_close: + /* Indicate the caller that the circuit should be closed. */ + return -2; + err_no_close: + /* Indicate the caller to NOT close the circuit. */ + return -1; +} + +/* Add to file_list every filename used by a configured hidden service, and to + * dir_list every directory path used by a configured hidden service. This is + * used by the sandbox subsystem to whitelist those. */ +void +hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list) +{ + tor_assert(file_list); + tor_assert(dir_list); + + /* Add files and dirs for legacy services. */ + rend_services_add_filenames_to_lists(file_list, dir_list); + + /* Add files and dirs for v3+. */ + FOR_EACH_SERVICE_BEGIN(service) { + /* Skip ephemeral service, they don't touch the disk. */ + if (service->config.is_ephemeral) { + continue; + } + service_add_fnames_to_list(service, file_list); + smartlist_add_strdup(dir_list, service->config.directory_path); + } FOR_EACH_DESCRIPTOR_END; +} + +/* Called when our internal view of the directory has changed. We might have + * received a new batch of descriptors which might affect the shape of the + * HSDir hash ring. Signal that we should reexamine the hash ring and + * re-upload our HS descriptors if needed. */ +void +hs_service_dir_info_changed(void) +{ + if (hs_service_get_num_services() > 0) { + /* New directory information usually goes every consensus so rate limit + * every 30 minutes to not be too conservative. */ + static struct ratelim_t dir_info_changed_ratelim = RATELIM_INIT(30 * 60); + log_fn_ratelim(&dir_info_changed_ratelim, LOG_INFO, LD_REND, + "New dirinfo arrived: consider reuploading descriptor"); + consider_republishing_hs_descriptors = 1; + } +} + +/* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and + * launch a circuit to the rendezvous point. */ +int +hs_service_receive_introduce2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + /* Do some initial validation and logging before we parse the cell */ + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto done; + } + + if (circ->hs_ident) { + ret = service_handle_introduce2(circ, payload, payload_len); + } else { + ret = rend_service_receive_introduction(circ, payload, payload_len); + } + + done: + return ret; +} + +/* Called when we get an INTRO_ESTABLISHED cell. Mark the circuit as an + * established introduction point. Return 0 on success else a negative value + * and the circuit is closed. */ +int +hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len) +{ + int ret = -1; + + tor_assert(circ); + tor_assert(payload); + + if (TO_CIRCUIT(circ)->purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { + log_warn(LD_PROTOCOL, "Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose %d", + TO_CIRCUIT(circ)->purpose); + goto err; + } + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + if (circ->hs_ident) { + ret = service_handle_intro_established(circ, payload, payload_len); + } else { + ret = rend_service_intro_established(circ, payload, payload_len); + } + + if (ret < 0) { + goto err; + } + return 0; + err: + circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); + return -1; +} + +/* Called when any kind of hidden service circuit is done building thus + * opened. This is the entry point from the circuit subsystem. */ +void +hs_service_circuit_has_opened(origin_circuit_t *circ) +{ + tor_assert(circ); + + /* Handle both version. v2 uses rend_data and v3 uses the hs circuit + * identifier hs_ident. Can't be both. */ + switch (TO_CIRCUIT(circ)->purpose) { + case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO: + if (circ->hs_ident) { + service_intro_circ_has_opened(circ); + } else { + rend_service_intro_has_opened(circ); + } + break; + case CIRCUIT_PURPOSE_S_CONNECT_REND: + if (circ->hs_ident) { + service_rendezvous_circ_has_opened(circ); + } else { + rend_service_rendezvous_has_opened(circ); + } + break; + default: + tor_assert(0); + } +} + +/* Load and/or generate keys for all onion services including the client + * authorization if any. Return 0 on success, -1 on failure. */ +int +hs_service_load_all_keys(void) +{ + /* Load v2 service keys if we have v2. */ + if (rend_num_services() != 0) { + if (rend_service_load_all_keys(NULL) < 0) { + goto err; + } + } + + /* Load or/and generate them for v3+. */ + SMARTLIST_FOREACH_BEGIN(hs_service_staging_list, hs_service_t *, service) { + /* Ignore ephemeral service, they already have their keys set. */ + if (service->config.is_ephemeral) { + continue; + } + log_info(LD_REND, "Loading v3 onion service keys from %s", + service_escaped_dir(service)); + if (load_service_keys(service) < 0) { + goto err; + } + /* XXX: Load/Generate client authorization keys. (#20700) */ + } SMARTLIST_FOREACH_END(service); + + /* Final step, the staging list contains service in a quiescent state that + * is ready to be used. Register them to the global map. Once this is over, + * the staging list will be cleaned up. */ + register_all_services(); + + /* All keys have been loaded successfully. */ + return 0; + err: + return -1; +} + +/* Put all service object in the given service list. After this, the caller + * looses ownership of every elements in the list and responsible to free the + * list pointer. */ +void +hs_service_stage_services(const smartlist_t *service_list) +{ + tor_assert(service_list); + /* This list is freed at registration time but this function can be called + * multiple time. */ + if (hs_service_staging_list == NULL) { + hs_service_staging_list = smartlist_new(); + } + /* Add all service object to our staging list. Caller is responsible for + * freeing the service_list. */ + smartlist_add_all(hs_service_staging_list, service_list); +} + +/* Allocate and initilize a service object. The service configuration will + * contain the default values. Return the newly allocated object pointer. This + * function can't fail. */ +hs_service_t * +hs_service_new(const or_options_t *options) +{ + hs_service_t *service = tor_malloc_zero(sizeof(hs_service_t)); + /* Set default configuration value. */ + set_service_default_config(&service->config, options); + /* Set the default service version. */ + service->config.version = HS_SERVICE_DEFAULT_VERSION; + /* Allocate the CLIENT_PK replay cache in service state. */ + service->state.replay_cache_rend_cookie = + replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL); + + return service; +} + +/* Free the given <b>service</b> object and all its content. This function + * also takes care of wiping service keys from memory. It is safe to pass a + * NULL pointer. */ +void +hs_service_free(hs_service_t *service) +{ + if (service == NULL) { + return; + } + + /* Free descriptors. Go over both descriptor with this loop. */ + FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { + service_descriptor_free(desc); + } FOR_EACH_DESCRIPTOR_END; + + /* Free service configuration. */ + service_clear_config(&service->config); + + /* Free replay cache from state. */ + if (service->state.replay_cache_rend_cookie) { + replaycache_free(service->state.replay_cache_rend_cookie); + } + + /* Wipe service keys. */ + memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); + + tor_free(service); +} + +/* Periodic callback. Entry point from the main loop to the HS service + * subsystem. This is call every second. This is skipped if tor can't build a + * circuit or the network is disabled. */ +void +hs_service_run_scheduled_events(time_t now) +{ + /* First thing we'll do here is to make sure our services are in a + * quiescent state for the scheduled events. */ + run_housekeeping_event(now); + + /* Order matters here. We first make sure the descriptor object for each + * service contains the latest data. Once done, we check if we need to open + * new introduction circuit. Finally, we try to upload the descriptor for + * each service. */ + + /* Make sure descriptors are up to date. */ + run_build_descriptor_event(now); + /* Make sure services have enough circuits. */ + run_build_circuit_event(now); + /* Upload the descriptors if needed/possible. */ + run_upload_descriptor_event(now); +} + +/* Initialize the service HS subsystem. */ +void +hs_service_init(void) +{ + /* Should never be called twice. */ + tor_assert(!hs_service_map); + tor_assert(!hs_service_staging_list); + + /* v2 specific. */ + rend_service_init(); + + hs_service_map = tor_malloc_zero(sizeof(struct hs_service_ht)); + HT_INIT(hs_service_ht, hs_service_map); + + hs_service_staging_list = smartlist_new(); +} + +/* Release all global storage of the hidden service subsystem. */ +void +hs_service_free_all(void) +{ + rend_service_free_all(); + service_free_all(); +} + +#ifdef TOR_UNIT_TESTS + +/* Return the global service map size. Only used by unit test. */ +STATIC unsigned int +get_hs_service_map_size(void) +{ + return HT_SIZE(hs_service_map); +} + +/* Return the staging list size. Only used by unit test. */ +STATIC int +get_hs_service_staging_list_size(void) +{ + return smartlist_len(hs_service_staging_list); +} + +STATIC hs_service_ht * +get_hs_service_map(void) +{ + return hs_service_map; +} + +STATIC hs_service_t * +get_first_service(void) +{ + hs_service_t **obj = HT_START(hs_service_ht, hs_service_map); + if (obj == NULL) { + return NULL; + } + return *obj; +} + +#endif /* defined(TOR_UNIT_TESTS) */ + diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 3302592762..ed1053d850 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -3,25 +3,352 @@ /** * \file hs_service.h - * \brief Header file for hs_service.c. + * \brief Header file containing service data for the HS subsytem. **/ #ifndef TOR_HS_SERVICE_H #define TOR_HS_SERVICE_H -#include "or.h" +#include "crypto_curve25519.h" +#include "crypto_ed25519.h" +#include "replaycache.h" + +#include "hs_common.h" +#include "hs_descriptor.h" +#include "hs_ident.h" +#include "hs_intropoint.h" + +/* Trunnel */ #include "hs/cell_establish_intro.h" -/* These functions are only used by unit tests and we need to expose them else - * hs_service.o ends up with no symbols in libor.a which makes clang throw a - * warning at compile time. See #21825. */ +/* When loading and configuring a service, this is the default version it will + * be configured for as it is possible that no HiddenServiceVersion is + * present. */ +#define HS_SERVICE_DEFAULT_VERSION HS_VERSION_TWO + +/* As described in the specification, service publishes their next descriptor + * at a random time between those two values (in seconds). */ +#define HS_SERVICE_NEXT_UPLOAD_TIME_MIN (60 * 60) +#define HS_SERVICE_NEXT_UPLOAD_TIME_MAX (120 * 60) + +/* Service side introduction point. */ +typedef struct hs_service_intro_point_t { + /* Top level intropoint "shared" data between client/service. */ + hs_intropoint_t base; + + /* Onion key of the introduction point used to extend to it for the ntor + * handshake. */ + curve25519_public_key_t onion_key; + + /* Authentication keypair used to create the authentication certificate + * which is published in the descriptor. */ + ed25519_keypair_t auth_key_kp; + + /* Encryption keypair for the "ntor" type. */ + curve25519_keypair_t enc_key_kp; + + /* Legacy key if that intro point doesn't support v3. This should be used if + * the base object legacy flag is set. */ + crypto_pk_t *legacy_key; + + /* Amount of INTRODUCE2 cell accepted from this intro point. */ + uint64_t introduce2_count; + + /* Maximum number of INTRODUCE2 cell this intro point should accept. */ + uint64_t introduce2_max; + + /* The time at which this intro point should expire and stop being used. */ + time_t time_to_expire; + + /* The amount of circuit creation we've made to this intro point. This is + * incremented every time we do a circuit relaunch on this intro point which + * is triggered when the circuit dies but the node is still in the + * consensus. After MAX_INTRO_POINT_CIRCUIT_RETRIES, we give up on it. */ + uint32_t circuit_retries; + + /* Set if this intro point has an established circuit. */ + unsigned int circuit_established : 1; + + /* Replay cache recording the encrypted part of an INTRODUCE2 cell that the + * circuit associated with this intro point has received. This is used to + * prevent replay attacks. */ + replaycache_t *replay_cache; +} hs_service_intro_point_t; + +/* Object handling introduction points of a service. */ +typedef struct hs_service_intropoints_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t retry_period_started; + + /* Number of circuit we've launched during a single retry period. */ + unsigned int num_circuits_launched; + + /* Contains the current hs_service_intro_point_t objects indexed by + * authentication public key. */ + digest256map_t *map; + + /* Contains node's identity key digest that were introduction point for this + * descriptor but were retried to many times. We keep those so we avoid + * re-picking them over and over for a circuit retry period. + * XXX: Once we have #22173, change this to only use ed25519 identity. */ + digestmap_t *failed_id; +} hs_service_intropoints_t; + +/* Representation of a service descriptor. */ +typedef struct hs_service_descriptor_t { + /* Decoded descriptor. This object is used for encoding when the service + * publishes the descriptor. */ + hs_descriptor_t *desc; + + /* Descriptor signing keypair. */ + ed25519_keypair_t signing_kp; + + /* Blinded keypair derived from the master identity public key. */ + ed25519_keypair_t blinded_kp; + + /* When is the next time when we should upload the descriptor. */ + time_t next_upload_time; + + /* Introduction points assign to this descriptor which contains + * hs_service_intropoints_t object indexed by authentication key (the RSA + * key if the node is legacy). */ + hs_service_intropoints_t intro_points; + + /* The time period number this descriptor has been created for. */ + uint64_t time_period_num; + + /* True iff we have missing intro points for this descriptor because we + * couldn't pick any nodes. */ + unsigned int missing_intro_points : 1; + + /** List of the responsible HSDirs (their b64ed identity digest) last time we + * uploaded this descriptor. If the set of responsible HSDirs is different + * from this list, this means we received new dirinfo and we need to + * reupload our descriptor. */ + smartlist_t *previous_hsdirs; +} hs_service_descriptor_t; + +/* Service key material. */ +typedef struct hs_service_keys_t { + /* Master identify public key. */ + ed25519_public_key_t identity_pk; + /* Master identity private key. */ + ed25519_secret_key_t identity_sk; + /* True iff the key is kept offline which means the identity_sk MUST not be + * used in that case. */ + unsigned int is_identify_key_offline : 1; +} hs_service_keys_t; + +/* Service configuration. The following are set from the torrc options either + * set by the configuration file or by the control port. Nothing else should + * change those values. */ +typedef struct hs_service_config_t { + /* Protocol version of the service. Specified by HiddenServiceVersion + * option. */ + uint32_t version; + + /* List of rend_service_port_config_t */ + smartlist_t *ports; + + /* Path on the filesystem where the service persistent data is stored. NULL + * if the service is ephemeral. Specified by HiddenServiceDir option. */ + char *directory_path; + + /* The maximum number of simultaneous streams per rendezvous circuit that + * are allowed to be created. No limit if 0. Specified by + * HiddenServiceMaxStreams option. */ + uint64_t max_streams_per_rdv_circuit; + + /* If true, we close circuits that exceed the max_streams_per_rdv_circuit + * limit. Specified by HiddenServiceMaxStreamsCloseCircuit option. */ + unsigned int max_streams_close_circuit : 1; + + /* How many introduction points this service has. Specified by + * HiddenServiceNumIntroductionPoints option. */ + unsigned int num_intro_points; + + /* True iff we allow request made on unknown ports. Specified by + * HiddenServiceAllowUnknownPorts option. */ + unsigned int allow_unknown_ports : 1; + + /* If true, this service is a Single Onion Service. Specified by + * HiddenServiceSingleHopMode and HiddenServiceNonAnonymousMode options. */ + unsigned int is_single_onion : 1; + + /* If true, allow group read permissions on the directory_path. Specified by + * HiddenServiceDirGroupReadable option. */ + unsigned int dir_group_readable : 1; + + /* Is this service ephemeral? */ + unsigned int is_ephemeral : 1; +} hs_service_config_t; + +/* Service state. */ +typedef struct hs_service_state_t { + /* The time at which we've started our retry period to build circuits. We + * don't want to stress circuit creation so we can only retry for a certain + * time and then after we stop and wait. */ + time_t intro_circ_retry_started_time; + + /* Number of circuit we've launched during a single retry period. This + * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */ + unsigned int num_intro_circ_launched; + + /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect + * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point + * through two or more different introduction points; when they do, this + * keeps us from launching multiple simultaneous attempts to connect to the + * same rend point. */ + replaycache_t *replay_cache_rend_cookie; + + /* When is the next time we should rotate our descriptors. This is has to be + * done at the start time of the next SRV protocol run. */ + time_t next_rotation_time; +} hs_service_state_t; + +/* Representation of a service running on this tor instance. */ +typedef struct hs_service_t { + /* Onion address base32 encoded and NUL terminated. We keep it for logging + * purposes so we don't have to build it everytime. */ + char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + /* Hashtable node: use to look up the service by its master public identity + * key in the service global map. */ + HT_ENTRY(hs_service_t) hs_service_node; + + /* Service state which contains various flags and counters. */ + hs_service_state_t state; + + /* Key material of the service. */ + hs_service_keys_t keys; + + /* Configuration of the service. */ + hs_service_config_t config; + + /* Current descriptor. */ + hs_service_descriptor_t *desc_current; + /* Next descriptor. */ + hs_service_descriptor_t *desc_next; + + /* XXX: Credential (client auth.) #20700. */ + +} hs_service_t; + +/* For the service global hash map, we define a specific type for it which + * will make it safe to use and specific to some controlled parameters such as + * the hashing function and how to compare services. */ +typedef HT_HEAD(hs_service_ht, hs_service_t) hs_service_ht; + +/* API */ + +/* Global initializer and cleanup function. */ +void hs_service_init(void); +void hs_service_free_all(void); + +/* Service new/free functions. */ +hs_service_t *hs_service_new(const or_options_t *options); +void hs_service_free(hs_service_t *service); + +unsigned int hs_service_get_num_services(void); +void hs_service_stage_services(const smartlist_t *service_list); +int hs_service_load_all_keys(void); +void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, + smartlist_t *dir_list); +int hs_service_set_conn_addr_port(const origin_circuit_t *circ, + edge_connection_t *conn); + +void hs_service_dir_info_changed(void); +void hs_service_run_scheduled_events(time_t now); +void hs_service_circuit_has_opened(origin_circuit_t *circ); +int hs_service_receive_intro_established(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); +int hs_service_receive_introduce2(origin_circuit_t *circ, + const uint8_t *payload, + size_t payload_len); + +void hs_service_intro_circ_has_closed(origin_circuit_t *circ); + +#ifdef HS_SERVICE_PRIVATE + +#ifdef TOR_UNIT_TESTS + +/* Useful getters for unit tests. */ +STATIC unsigned int get_hs_service_map_size(void); +STATIC int get_hs_service_staging_list_size(void); +STATIC hs_service_ht *get_hs_service_map(void); +STATIC hs_service_t *get_first_service(void); + +/* Service accessors. */ +STATIC hs_service_t *find_service(hs_service_ht *map, + const ed25519_public_key_t *pk); +STATIC void remove_service(hs_service_ht *map, hs_service_t *service); +STATIC int register_service(hs_service_ht *map, hs_service_t *service); +/* Service introduction point functions. */ +STATIC hs_service_intro_point_t *service_intro_point_new( + const extend_info_t *ei, + unsigned int is_legacy); +STATIC void service_intro_point_free(hs_service_intro_point_t *ip); +STATIC void service_intro_point_add(digest256map_t *map, + hs_service_intro_point_t *ip); +STATIC void service_intro_point_remove(const hs_service_t *service, + const hs_service_intro_point_t *ip); +STATIC hs_service_intro_point_t *service_intro_point_find( + const hs_service_t *service, + const ed25519_public_key_t *auth_key); +STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( + const hs_service_t *service, + const hs_ident_circuit_t *ident); +/* Service descriptor functions. */ +STATIC hs_service_descriptor_t *service_descriptor_new(void); +STATIC hs_service_descriptor_t *service_desc_find_by_intro( + const hs_service_t *service, + const hs_service_intro_point_t *ip); +/* Helper functions. */ +STATIC void get_objects_from_ident(const hs_ident_circuit_t *ident, + hs_service_t **service, + hs_service_intro_point_t **ip, + hs_service_descriptor_t **desc); +STATIC const node_t * +get_node_from_intro_point(const hs_service_intro_point_t *ip); +STATIC int can_service_launch_intro_circuit(hs_service_t *service, + time_t now); +STATIC int intro_point_should_expire(const hs_service_intro_point_t *ip, + time_t now); +STATIC void run_housekeeping_event(time_t now); +STATIC void rotate_all_descriptors(time_t now); +STATIC void build_all_descriptors(time_t now); +STATIC void update_all_descriptors(time_t now); +STATIC void run_upload_descriptor_event(time_t now); + +STATIC char * +encode_desc_rev_counter_for_state(const hs_service_descriptor_t *desc); + +STATIC void service_descriptor_free(hs_service_descriptor_t *desc); + +STATIC uint64_t +check_state_line_for_service_rev_counter(const char *state_line, + const ed25519_public_key_t *blinded_pubkey, + int *service_found_out); + +STATIC int +write_address_to_file(const hs_service_t *service, const char *fname_); + +STATIC void upload_descriptor_to_all(const hs_service_t *service, + hs_service_descriptor_t *desc); + +STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc, + time_t now, + int descriptor_changed); + +STATIC int service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc); + +#endif /* defined(TOR_UNIT_TESTS) */ -trn_cell_establish_intro_t * -generate_establish_intro_cell(const uint8_t *circuit_key_material, - size_t circuit_key_material_len); -ssize_t -get_establish_intro_payload(uint8_t *buf, size_t buf_len, - const trn_cell_establish_intro_t *cell); +#endif /* defined(HS_SERVICE_PRIVATE) */ -#endif /* TOR_HS_SERVICE_H */ +#endif /* !defined(TOR_HS_SERVICE_H) */ diff --git a/src/or/include.am b/src/or/include.am index 1ef5afa013..7216aba9af 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -20,7 +20,6 @@ EXTRA_DIST+= src/or/ntmain.c src/or/Makefile.nmake LIBTOR_A_SOURCES = \ src/or/addressmap.c \ src/or/bridges.c \ - src/or/buffers.c \ src/or/channel.c \ src/or/channelpadding.c \ src/or/channeltls.c \ @@ -50,16 +49,21 @@ LIBTOR_A_SOURCES = \ src/or/dnsserv.c \ src/or/fp_pair.c \ src/or/geoip.c \ - src/or/hs_intropoint.c \ - src/or/hs_circuitmap.c \ - src/or/hs_ntor.c \ - src/or/hs_service.c \ src/or/entrynodes.c \ src/or/ext_orport.c \ src/or/hibernate.c \ src/or/hs_cache.c \ + src/or/hs_cell.c \ + src/or/hs_circuit.c \ + src/or/hs_circuitmap.c \ + src/or/hs_client.c \ src/or/hs_common.c \ + src/or/hs_config.c \ src/or/hs_descriptor.c \ + src/or/hs_ident.c \ + src/or/hs_intropoint.c \ + src/or/hs_ntor.c \ + src/or/hs_service.c \ src/or/keypin.c \ src/or/main.c \ src/or/microdesc.c \ @@ -74,6 +78,11 @@ LIBTOR_A_SOURCES = \ src/or/parsecommon.c \ src/or/periodic.c \ src/or/protover.c \ + src/or/proto_cell.c \ + src/or/proto_control0.c \ + src/or/proto_ext_or.c \ + src/or/proto_http.c \ + src/or/proto_socks.c \ src/or/policies.c \ src/or/reasons.c \ src/or/relay.c \ @@ -90,6 +99,8 @@ LIBTOR_A_SOURCES = \ src/or/routerparse.c \ src/or/routerset.c \ src/or/scheduler.c \ + src/or/scheduler_kist.c \ + src/or/scheduler_vanilla.c \ src/or/statefile.c \ src/or/status.c \ src/or/torcert.c \ @@ -122,10 +133,11 @@ src_or_tor_LDADD = src/or/libtor.a src/common/libor.a src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ - @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ if COVERAGE_ENABLED src_or_tor_cov_SOURCES = src/or/tor_main.c @@ -144,7 +156,6 @@ endif ORHEADERS = \ src/or/addressmap.h \ src/or/bridges.h \ - src/or/buffers.h \ src/or/channel.h \ src/or/channelpadding.h \ src/or/channeltls.h \ @@ -180,12 +191,17 @@ ORHEADERS = \ src/or/entrynodes.h \ src/or/hibernate.h \ src/or/hs_cache.h \ + src/or/hs_cell.h \ + src/or/hs_config.h \ + src/or/hs_circuit.h \ + src/or/hs_circuitmap.h \ + src/or/hs_client.h \ src/or/hs_common.h \ src/or/hs_descriptor.h \ - src/or/hs_intropoint.h \ - src/or/hs_circuitmap.h \ - src/or/hs_ntor.h \ - src/or/hs_service.h \ + src/or/hs_ident.h \ + src/or/hs_intropoint.h \ + src/or/hs_ntor.h \ + src/or/hs_service.h \ src/or/keypin.h \ src/or/main.h \ src/or/microdesc.h \ @@ -204,6 +220,11 @@ ORHEADERS = \ src/or/periodic.h \ src/or/policies.h \ src/or/protover.h \ + src/or/proto_cell.h \ + src/or/proto_control0.h \ + src/or/proto_ext_or.h \ + src/or/proto_http.h \ + src/or/proto_socks.h \ src/or/reasons.h \ src/or/relay.h \ src/or/rendcache.h \ diff --git a/src/or/keypin.h b/src/or/keypin.h index 2564f5befb..fbb77e5c35 100644 --- a/src/or/keypin.h +++ b/src/or/keypin.h @@ -41,7 +41,7 @@ STATIC keypin_ent_t * keypin_parse_journal_line(const char *cp); STATIC int keypin_load_journal_impl(const char *data, size_t size); MOCK_DECL(STATIC void, keypin_add_entry_to_map, (keypin_ent_t *ent)); -#endif +#endif /* defined(KEYPIN_PRIVATE) */ -#endif +#endif /* !defined(TOR_KEYPIN_H) */ diff --git a/src/or/main.c b/src/or/main.c index 0d91803d4e..c340e4128b 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -52,6 +52,7 @@ #include "backtrace.h" #include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" #include "channel.h" #include "channeltls.h" #include "channelpadding.h" @@ -80,6 +81,7 @@ #include "hibernate.h" #include "hs_cache.h" #include "hs_circuitmap.h" +#include "hs_client.h" #include "keypin.h" #include "main.h" #include "microdesc.h" @@ -120,9 +122,9 @@ * Coverity. Here's a kludge to unconfuse it. */ # define __INCLUDE_LEVEL__ 2 -# endif +#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */ #include <systemd/sd-daemon.h> -#endif +#endif /* defined(HAVE_SYSTEMD) */ void evdns_shutdown(int); @@ -740,7 +742,7 @@ conn_read_callback(evutil_socket_t fd, short event, void *_conn) "(fd %d); removing", conn_type_to_string(conn->type), (int)conn->s); tor_fragile_assert(); -#endif +#endif /* !defined(_WIN32) */ if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); connection_mark_for_close(conn); @@ -835,7 +837,7 @@ conn_close_if_marked(int i) (int)conn->outbuf_flushlen, conn->marked_for_close_file, conn->marked_for_close); if (conn->linked_conn) { - retval = move_buf_to_buf(conn->linked_conn->inbuf, conn->outbuf, + retval = buf_move_to_buf(conn->linked_conn->inbuf, conn->outbuf, &conn->outbuf_flushlen); if (retval >= 0) { /* The linked conn will notice that it has data when it notices that @@ -849,12 +851,13 @@ conn_close_if_marked(int i) connection_wants_to_flush(conn)); } else if (connection_speaks_cells(conn)) { if (conn->state == OR_CONN_STATE_OPEN) { - retval = flush_buf_tls(TO_OR_CONN(conn)->tls, conn->outbuf, sz, + retval = buf_flush_to_tls(conn->outbuf, TO_OR_CONN(conn)->tls, sz, &conn->outbuf_flushlen); } else retval = -1; /* never flush non-open broken tls connections */ } else { - retval = flush_buf(conn->s, conn->outbuf, sz, &conn->outbuf_flushlen); + retval = buf_flush_to_socket(conn->outbuf, conn->s, sz, + &conn->outbuf_flushlen); } if (retval >= 0 && /* Technically, we could survive things like TLS_WANT_WRITE here. But don't bother for now. */ @@ -1142,7 +1145,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); - rend_client_purge_state(); + hs_client_purge_state(); time_of_last_signewnym = now; signewnym_is_pending = 0; @@ -1194,6 +1197,7 @@ CALLBACK(heartbeat); CALLBACK(clean_consdiffmgr); CALLBACK(reset_padding_counts); CALLBACK(check_canonical_channels); +CALLBACK(hs_service); #undef CALLBACK @@ -1229,6 +1233,7 @@ static periodic_event_item_t periodic_events[] = { CALLBACK(clean_consdiffmgr), CALLBACK(reset_padding_counts), CALLBACK(check_canonical_channels), + CALLBACK(hs_service), END_OF_PERIODIC_EVENTS }; #undef CALLBACK @@ -1461,12 +1466,6 @@ run_scheduled_events(time_t now) /* 6. And remove any marked circuits... */ circuit_close_all_marked(); - /* 7. And upload service descriptors if necessary. */ - if (have_completed_a_circuit() && !net_is_disabled()) { - rend_consider_services_upload(now); - rend_consider_descriptor_republication(); - } - /* 8. and blow away any connections that need to die. have to do this now, * because if we marked a conn for close and left its socket -1, then * we'll pass it to poll/select and bad things will happen. @@ -1713,7 +1712,7 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options) * networkstatus_get_reasonably_live_consensus(), but that value is way * way too high. Arma: is the bridge issue there resolved yet? -NM */ #define NS_EXPIRY_SLOP (24*60*60) - if (ns && ns->valid_until < now+NS_EXPIRY_SLOP && + if (ns && ns->valid_until < (now - NS_EXPIRY_SLOP) && router_have_minimum_dir_info()) { router_dir_info_changed(); } @@ -1831,8 +1830,8 @@ clean_caches_callback(time_t now, const or_options_t *options) { /* Remove old information from rephist and the rend cache. */ rep_history_clean(now - options->RephistTrackTime); - rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); rend_cache_clean(now, REND_CACHE_TYPE_SERVICE); + hs_cache_clean_as_client(now); hs_cache_clean_as_dir(now); microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) @@ -1851,6 +1850,7 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options) * clean it as soon as we can since we want to make sure the client waits * as little as possible for reachability reasons. */ rend_cache_failure_clean(now); + hs_cache_client_intro_state_clean(now); return 30; } @@ -2040,7 +2040,8 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) { if (net_is_disabled() || ! server_mode(options) || - ! options->PortForwarding) { + ! options->PortForwarding || + options->NoExec) { return PERIODIC_EVENT_NO_UPDATE; } /* 11. check the port forwarding app */ @@ -2060,6 +2061,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) /** * Periodic callback: write the heartbeat message in the logs. + * + * If writing the heartbeat message to the logs fails for some reason, retry + * again after <b>MIN_HEARTBEAT_PERIOD</b> seconds. */ static int heartbeat_callback(time_t now, const or_options_t *options) @@ -2071,14 +2075,20 @@ heartbeat_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } - /* Write the heartbeat message */ + /* Skip the first one. */ if (first) { - first = 0; /* Skip the first one. */ - } else { - log_heartbeat(now); + first = 0; + return options->HeartbeatPeriod; } - return options->HeartbeatPeriod; + /* Write the heartbeat message */ + if (log_heartbeat(now) == 0) { + return options->HeartbeatPeriod; + } else { + /* If we couldn't write the heartbeat log message, try again in the minimum + * interval of time. */ + return MIN_HEARTBEAT_PERIOD; + } } #define CDM_CLEAN_CALLBACK_INTERVAL 600 @@ -2092,6 +2102,29 @@ clean_consdiffmgr_callback(time_t now, const or_options_t *options) return CDM_CLEAN_CALLBACK_INTERVAL; } +/* + * Periodic callback: Run scheduled events for HS service. This is called + * every second. + */ +static int +hs_service_callback(time_t now, const or_options_t *options) +{ + (void) options; + + /* We need to at least be able to build circuits and that we actually have + * a working network. */ + if (!have_completed_a_circuit() || net_is_disabled() || + networkstatus_get_live_consensus(now) == NULL) { + goto end; + } + + hs_service_run_scheduled_events(now); + + end: + /* Every 1 second. */ + return 1; +} + /** Timer: used to invoke second_elapsed_callback() once per second. */ static periodic_timer_t *second_timer = NULL; /** Number of libevent errors in the last second: we die if we get too many. */ @@ -2194,7 +2227,7 @@ systemd_watchdog_callback(periodic_timer_t *timer, void *arg) (void)arg; sd_notify(0, "WATCHDOG=1"); } -#endif +#endif /* defined(HAVE_SYSTEMD_209) */ /** Timer: used to invoke refill_callback(). */ static periodic_timer_t *refill_timer = NULL; @@ -2258,7 +2291,7 @@ got_libevent_error(void) } return 0; } -#endif +#endif /* !defined(_WIN32) */ #define UPTIME_CUTOFF_FOR_NEW_BANDWIDTH_TEST (6*60*60) @@ -2355,7 +2388,7 @@ do_hup(void) tor_free(msg); } } - if (authdir_mode_handles_descs(options, -1)) { + if (authdir_mode(options)) { /* reload the approved-routers file */ if (dirserv_load_fingerprint_file() < 0) { /* warnings are logged from dirserv_load_fingerprint_file() directly */ @@ -2499,9 +2532,6 @@ do_main_loop(void) } } - /* Initialize relay-side HS circuitmap */ - hs_circuitmap_init(); - /* set up once-a-second callback. */ if (! second_timer) { struct timeval one_second; @@ -2535,7 +2565,7 @@ do_main_loop(void) tor_assert(systemd_watchdog_timer); } } -#endif +#endif /* defined(HAVE_SYSTEMD_209) */ if (!refill_timer) { struct timeval refill_interval; @@ -2563,7 +2593,7 @@ do_main_loop(void) log_info(LD_GENERAL, "Systemd NOTIFY_SOCKET not present."); } } -#endif +#endif /* defined(HAVE_SYSTEMD) */ return run_main_loop_until_done(); } @@ -2616,7 +2646,7 @@ run_main_loop_once(void) log_warn(LD_NET, "EINVAL from libevent: should you upgrade libevent?"); if (got_libevent_error()) return -1; -#endif +#endif /* !defined(_WIN32) */ } else { tor_assert_nonfatal_once(! ERRNO_IS_EINPROGRESS(e)); log_debug(LD_NET,"libevent call interrupted."); @@ -2877,7 +2907,6 @@ dumpstats(int severity) rep_hist_dump_stats(now,severity); rend_service_dump_stats(severity); - dump_pk_ops(severity); dump_distinct_digest_count(severity); } @@ -2973,7 +3002,7 @@ handle_signals(int is_parent) #ifdef SIGXFSZ sigaction(SIGXFSZ, &action, NULL); #endif -#endif +#endif /* !defined(_WIN32) */ } } @@ -3014,9 +3043,10 @@ tor_init(int argc, char *argv[]) rep_hist_init(); /* Initialize the service cache. */ rend_cache_init(); - hs_cache_init(); addressmap_init(); /* Init the client dns cache. Do it always, since it's * cheap. */ + /* Initialize the HS subsystem. */ + hs_init(); { /* We search for the "quiet" option first, since it decides whether we @@ -3216,10 +3246,8 @@ tor_free_all(int postfork) networkstatus_free_all(); addressmap_free_all(); dirserv_free_all(); - rend_service_free_all(); rend_cache_free_all(); rend_service_authorization_free_all(); - hs_cache_free_all(); rep_hist_free_all(); dns_free_all(); clear_pending_onions(); @@ -3232,7 +3260,6 @@ tor_free_all(int postfork) connection_edge_free_all(); scheduler_free_all(); nodelist_free_all(); - hs_circuitmap_free_all(); microdesc_free_all(); routerparse_free_all(); ext_orport_free_all(); @@ -3241,6 +3268,7 @@ tor_free_all(int postfork) protover_free_all(); bridges_free_all(); consdiffmgr_free_all(); + hs_free_all(); if (!postfork) { config_free_all(); or_state_free_all(); @@ -3478,7 +3506,7 @@ sandbox_init_filter(void) if (options->BridgeAuthoritativeDir) OPEN_DATADIR_SUFFIX("networkstatus-bridges", ".tmp"); - if (authdir_mode_handles_descs(options, -1)) + if (authdir_mode(options)) OPEN_DATADIR("approved-routers"); if (options->ServerDNSResolvConfFile) @@ -3550,7 +3578,7 @@ sandbox_init_filter(void) { smartlist_t *files = smartlist_new(); smartlist_t *dirs = smartlist_new(); - rend_services_add_filenames_to_lists(files, dirs); + hs_service_lists_fnames_for_sandbox(files, dirs); SMARTLIST_FOREACH(files, char *, file_name, { char *tmp_name = NULL; tor_asprintf(&tmp_name, "%s.tmp", file_name); @@ -3702,7 +3730,7 @@ tor_main(int argc, char *argv[]) setdeppolicy(3); } } -#endif +#endif /* defined(_WIN32) */ configure_backtrace_handler(get_version()); @@ -3718,14 +3746,14 @@ tor_main(int argc, char *argv[]) int r = crypto_use_tor_alloc_functions(); tor_assert(r == 0); } -#endif +#endif /* defined(USE_DMALLOC) */ #ifdef NT_SERVICE { int done = 0; result = nt_service_parse_options(argc, argv, &done); if (done) return result; } -#endif +#endif /* defined(NT_SERVICE) */ if (tor_init(argc, argv)<0) return -1; @@ -3754,6 +3782,10 @@ tor_main(int argc, char *argv[]) case CMD_KEYGEN: result = load_ed_keys(get_options(), time(NULL)) < 0; break; + case CMD_KEY_EXPIRATION: + init_keys(); + result = log_cert_expiration(); + break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); break; diff --git a/src/or/main.h b/src/or/main.h index 57aa372750..132ab12bbb 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -95,7 +95,7 @@ STATIC void teardown_periodic_events(void); #ifdef TOR_UNIT_TESTS extern smartlist_t *connection_array; #endif -#endif +#endif /* defined(MAIN_PRIVATE) */ -#endif +#endif /* !defined(TOR_MAIN_H) */ diff --git a/src/or/microdesc.c b/src/or/microdesc.c index 32242d0054..fe327c6c82 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -977,7 +977,7 @@ update_microdesc_downloads(time_t now) smartlist_free(missing); } -/** For every microdescriptor listed in the current microdecriptor consensus, +/** For every microdescriptor listed in the current microdescriptor consensus, * update its last_listed field to be at least as recent as the publication * time of the current microdescriptor consensus. */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 36e62020e3..e0a3e4cdc6 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -61,22 +61,17 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" #include "shared_random.h" #include "transports.h" #include "torcert.h" #include "channelpadding.h" -/** Map from lowercase nickname to identity digest of named server, if any. */ -static strmap_t *named_server_map = NULL; -/** Map from lowercase nickname to (void*)1 for all names that are listed - * as unnamed for some server in the consensus. */ -static strmap_t *unnamed_server_map = NULL; - /** Most recently received and validated v3 "ns"-flavored consensus network * status. */ STATIC networkstatus_t *current_ns_consensus = NULL; -/** Most recently received and validated v3 "microdec"-flavored consensus +/** Most recently received and validated v3 "microdesc"-flavored consensus * network status. */ STATIC networkstatus_t *current_md_consensus = NULL; @@ -142,7 +137,6 @@ static int have_warned_about_old_version = 0; * listed by the authorities. */ static int have_warned_about_new_version = 0; -static void routerstatus_list_update_named_server_map(void); static void update_consensus_bootstrap_multiple_downloads( time_t now, const or_options_t *options); @@ -250,13 +244,6 @@ router_reload_consensus_networkstatus(void) } } - if (!networkstatus_get_latest_consensus()) { - if (!named_server_map) - named_server_map = strmap_new(); - if (!unnamed_server_map) - unnamed_server_map = strmap_new(); - } - update_certificate_downloads(time(NULL)); routers_update_all_from_networkstatus(time(NULL), 3); @@ -794,41 +781,6 @@ router_get_consensus_status_by_id(const char *digest) return router_get_mutable_consensus_status_by_id(digest); } -/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding routerstatus_t, or NULL if none exists. Warn the - * user if <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ -const routerstatus_t * -router_get_consensus_status_by_nickname(const char *nickname, - int warn_if_unnamed) -{ - const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); - if (node) - return node->rs; - else - return NULL; -} - -/** Return the identity digest that's mapped to officially by - * <b>nickname</b>. */ -const char * -networkstatus_get_router_digest_by_nickname(const char *nickname) -{ - if (!named_server_map) - return NULL; - return strmap_get_lc(named_server_map, nickname); -} - -/** Return true iff <b>nickname</b> is disallowed from being the nickname - * of any server. */ -int -networkstatus_nickname_is_unnamed(const char *nickname) -{ - if (!unnamed_server_map) - return 0; - return strmap_get_lc(unnamed_server_map, nickname) != NULL; -} - /** How frequently do directory authorities re-download fresh networkstatus * documents? */ #define AUTHORITY_NS_CACHE_INTERVAL (10*60) @@ -1257,7 +1209,9 @@ should_delay_dir_fetches(const or_options_t *options, const char **msg_out) } if (options->UseBridges) { - if (!any_bridge_descriptors_known()) { + /* If we know that none of our bridges can possibly work, avoid fetching + * directory documents. But if some of them might work, try again. */ + if (num_bridges_usable(1) == 0) { if (msg_out) { *msg_out = "No running bridges"; } @@ -1393,14 +1347,21 @@ networkstatus_get_latest_consensus_by_flavor,(consensus_flavor_t f)) MOCK_IMPL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)) { - if (networkstatus_get_latest_consensus() && - networkstatus_get_latest_consensus()->valid_after <= now && - now <= networkstatus_get_latest_consensus()->valid_until) - return networkstatus_get_latest_consensus(); + networkstatus_t *ns = networkstatus_get_latest_consensus(); + if (ns && networkstatus_is_live(ns, now)) + return ns; else return NULL; } +/** Given a consensus in <b>ns</b>, return true iff currently live and + * unexpired. */ +int +networkstatus_is_live(const networkstatus_t *ns, time_t now) +{ + return (ns->valid_after <= now && now <= ns->valid_until); +} + /** Determine if <b>consensus</b> is valid or expired recently enough that * we can still use it. * @@ -1603,6 +1564,15 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c, smartlist_free(changed); } +/* Called when the consensus has changed from old_c to new_c. */ +static void +notify_networkstatus_changed(const networkstatus_t *old_c, + const networkstatus_t *new_c) +{ + notify_control_networkstatus_changed(old_c, new_c); + scheduler_notify_networkstatus_changed(old_c, new_c); +} + /** Copy all the ancillary information (like router download status and so on) * from <b>old_c</b> to <b>new_c</b>. */ static void @@ -1660,7 +1630,7 @@ networkstatus_set_current_consensus_from_ns(networkstatus_t *c, } return current_md_consensus ? 0 : -1; } -#endif //TOR_UNIT_TESTS +#endif /* defined(TOR_UNIT_TESTS) */ /** * Return true if any option is set in <b>options</b> to make us behave @@ -1676,7 +1646,8 @@ any_client_port_set(const or_options_t *options) options->TransPort_set || options->NATDPort_set || options->ControlPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); } /** @@ -1783,7 +1754,7 @@ networkstatus_set_current_consensus(const char *consensus, if (from_cache && !was_waiting_for_certs) { /* We previously stored this; check _now_ to make sure that version-kills - * really work. This happens even before we check signatures: we did so + * really work. This happens even before we check signatures: we did so * before when we stored this to disk. This does mean an attacker who can * write to the datadir can make us not start: such an attacker could * already harm us by replacing our guards, which would be worse. */ @@ -1927,8 +1898,7 @@ networkstatus_set_current_consensus(const char *consensus, const int is_usable_flavor = flav == usable_consensus_flavor(); if (is_usable_flavor) { - notify_control_networkstatus_changed( - networkstatus_get_latest_consensus(), c); + notify_networkstatus_changed(networkstatus_get_latest_consensus(), c); } if (flav == FLAV_NS) { if (current_ns_consensus) { @@ -1971,14 +1941,17 @@ networkstatus_set_current_consensus(const char *consensus, } if (is_usable_flavor) { + /* The "current" consensus has just been set and it is a usable flavor so + * the first thing we need to do is recalculate the voting schedule static + * object so we can use the timings in there needed by some subsystems + * such as hidden service and shared random. */ + dirvote_recalculate_timing(options, now); + nodelist_set_consensus(c); /* XXXXNM Microdescs: needs a non-ns variant. ???? NM*/ update_consensus_networkstatus_fetch_time(now); - dirvote_recalculate_timing(options, now); - routerstatus_list_update_named_server_map(); - /* Update ewma and adjust policy if needed; first cache the old value */ old_ewma_enabled = cell_ewma_enabled(); /* Change the cell EWMA settings */ @@ -2031,14 +2004,16 @@ networkstatus_set_current_consensus(const char *consensus, char tbuf[ISO_TIME_LEN+1]; char dbuf[64]; long delta = now - c->valid_after; + char *flavormsg = NULL; format_iso_time(tbuf, c->valid_after); format_time_interval(dbuf, sizeof(dbuf), delta); log_warn(LD_GENERAL, "Our clock is %s behind the time published in the " "consensus network status document (%s UTC). Tor needs an " "accurate clock to work correctly. Please check your time and " "date settings!", dbuf, tbuf); - control_event_general_status(LOG_WARN, - "CLOCK_SKEW MIN_SKEW=%ld SOURCE=CONSENSUS", delta); + tor_asprintf(&flavormsg, "%s flavor consensus", flavor); + clock_skew_warning(NULL, delta, 1, LD_GENERAL, flavormsg, "CONSENSUS"); + tor_free(flavormsg); } /* We got a new consesus. Reset our md fetch fail cache */ @@ -2149,31 +2124,6 @@ routers_update_all_from_networkstatus(time_t now, int dir_version) } } -/** Update our view of the list of named servers from the most recently - * retrieved networkstatus consensus. */ -static void -routerstatus_list_update_named_server_map(void) -{ - networkstatus_t *ns = networkstatus_get_latest_consensus(); - if (!ns) - return; - - strmap_free(named_server_map, tor_free_); - named_server_map = strmap_new(); - strmap_free(unnamed_server_map, NULL); - unnamed_server_map = strmap_new(); - smartlist_t *rslist = ns->routerstatus_list; - SMARTLIST_FOREACH_BEGIN(rslist, const routerstatus_t *, rs) { - if (rs->is_named) { - strmap_set_lc(named_server_map, rs->nickname, - tor_memdup(rs->identity_digest, DIGEST_LEN)); - } - if (rs->is_unnamed) { - strmap_set_lc(unnamed_server_map, rs->nickname, (void*)1); - } - } SMARTLIST_FOREACH_END(rs); -} - /** Given a list <b>routers</b> of routerinfo_t *, update each status field * according to our current consensus networkstatus. May re-order * <b>routers</b>. */ @@ -2382,9 +2332,9 @@ get_net_param_from_list(smartlist_t *net_params, const char *param_name, * Make sure the value parsed from the consensus is at least * <b>min_val</b> and at most <b>max_val</b> and raise/cap the parsed value * if necessary. */ -int32_t -networkstatus_get_param(const networkstatus_t *ns, const char *param_name, - int32_t default_val, int32_t min_val, int32_t max_val) +MOCK_IMPL(int32_t, +networkstatus_get_param, (const networkstatus_t *ns, const char *param_name, + int32_t default_val, int32_t min_val, int32_t max_val)) { if (!ns) /* if they pass in null, go find it ourselves */ ns = networkstatus_get_latest_consensus(); @@ -2551,7 +2501,8 @@ getinfo_helper_networkstatus(control_connection_t *conn, } status = router_get_consensus_status_by_id(d); } else if (!strcmpstart(question, "ns/name/")) { - status = router_get_consensus_status_by_nickname(question+8, 0); + const node_t *n = node_get_by_nickname(question+8, 0); + status = n ? n->rs : NULL; } else if (!strcmpstart(question, "ns/purpose/")) { *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL)); return *answer ? 0 : -1; @@ -2658,8 +2609,5 @@ networkstatus_free_all(void) } tor_free(waiting->body); } - - strmap_free(named_server_map, tor_free_); - strmap_free(unnamed_server_map, NULL); } diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index e774c4d266..39a0f753d8 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -62,11 +62,6 @@ const routerstatus_t *router_get_consensus_status_by_descriptor_digest( MOCK_DECL(routerstatus_t *, router_get_mutable_consensus_status_by_descriptor_digest, (networkstatus_t *consensus, const char *digest)); -const routerstatus_t *router_get_consensus_status_by_nickname( - const char *nickname, - int warn_if_unnamed); -const char *networkstatus_get_router_digest_by_nickname(const char *nickname); -int networkstatus_nickname_is_unnamed(const char *nickname); int we_want_to_fetch_flavor(const or_options_t *options, int flavor); int we_want_to_fetch_unknown_auth_certs(const or_options_t *options); void networkstatus_consensus_download_failed(int status_code, @@ -81,6 +76,7 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus,(void)); MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, (consensus_flavor_t f)); MOCK_DECL(networkstatus_t *, networkstatus_get_live_consensus,(time_t now)); +int networkstatus_is_live(const networkstatus_t *ns, time_t now); int networkstatus_consensus_reasonably_live(const networkstatus_t *consensus, time_t now); int networkstatus_valid_until_is_reasonably_live(time_t valid_until, @@ -113,10 +109,9 @@ void signed_descs_update_status_from_consensus_networkstatus( char *networkstatus_getinfo_helper_single(const routerstatus_t *rs); char *networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now); void networkstatus_dump_bridge_status_to_file(time_t now); -int32_t networkstatus_get_param(const networkstatus_t *ns, - const char *param_name, - int32_t default_val, int32_t min_val, - int32_t max_val); +MOCK_DECL(int32_t, networkstatus_get_param, + (const networkstatus_t *ns, const char *param_name, + int32_t default_val, int32_t min_val, int32_t max_val)); int32_t networkstatus_get_overridable_param(const networkstatus_t *ns, int32_t torrc_value, const char *param_name, @@ -142,8 +137,8 @@ STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c, const char *flavor); extern networkstatus_t *current_ns_consensus; extern networkstatus_t *current_md_consensus; -#endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(NETWORKSTATUS_PRIVATE) */ -#endif +#endif /* !defined(TOR_NETWORKSTATUS_H) */ diff --git a/src/or/nodelist.c b/src/or/nodelist.c index d09989d93f..f2e979be8b 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -38,6 +38,8 @@ * used for authorities and fallback directories.) */ +#define NODELIST_PRIVATE + #include "or.h" #include "address.h" #include "config.h" @@ -45,6 +47,8 @@ #include "dirserv.h" #include "entrynodes.h" #include "geoip.h" +#include "hs_common.h" +#include "hs_client.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -54,6 +58,7 @@ #include "rendservice.h" #include "router.h" #include "routerlist.h" +#include "routerparse.h" #include "routerset.h" #include "torcert.h" @@ -90,7 +95,14 @@ typedef struct nodelist_t { smartlist_t *nodes; /* Hash table to map from node ID digest to node. */ HT_HEAD(nodelist_map, node_t) nodes_by_id; - + /* Hash table to map from node Ed25519 ID to node. + * + * Whenever a node's routerinfo or microdescriptor is about to change, + * you should remove it from this map with node_remove_from_ed25519_map(). + * Whenever a node's routerinfo or microdescriptor has just chaned, + * you should add it to this map with node_add_to_ed25519_map(). + */ + HT_HEAD(nodelist_ed_map, node_t) nodes_by_ed_id; } nodelist_t; static inline unsigned int @@ -109,6 +121,23 @@ HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq) HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, 0.6, tor_reallocarray_, tor_free_) +static inline unsigned int +node_ed_id_hash(const node_t *node) +{ + return (unsigned) siphash24g(node->ed25519_id.pubkey, ED25519_PUBKEY_LEN); +} + +static inline unsigned int +node_ed_id_eq(const node_t *node1, const node_t *node2) +{ + return ed25519_pubkey_eq(&node1->ed25519_id, &node2->ed25519_id); +} + +HT_PROTOTYPE(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq) +HT_GENERATE2(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_) + /** The global nodelist. */ static nodelist_t *the_nodelist=NULL; @@ -119,6 +148,7 @@ init_nodelist(void) if (PREDICT_UNLIKELY(the_nodelist == NULL)) { the_nodelist = tor_malloc_zero(sizeof(nodelist_t)); HT_INIT(nodelist_map, &the_nodelist->nodes_by_id); + HT_INIT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); the_nodelist->nodes = smartlist_new(); } } @@ -136,6 +166,21 @@ node_get_mutable_by_id(const char *identity_digest) return node; } +/** As node_get_by_ed25519_id, but returns a non-const pointer */ +node_t * +node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id) +{ + node_t search, *node; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return NULL; + if (BUG(ed_id == NULL) || BUG(ed25519_public_key_is_zero(ed_id))) + return NULL; + + memcpy(&search.ed25519_id, ed_id, sizeof(search.ed25519_id)); + node = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, &search); + return node; +} + /** Return the node_t whose identity is <b>identity_digest</b>, or NULL * if no such node exists. */ MOCK_IMPL(const node_t *, @@ -144,6 +189,14 @@ node_get_by_id,(const char *identity_digest)) return node_get_mutable_by_id(identity_digest); } +/** Return the node_t whose ed25519 identity is <b>ed_id</b>, or NULL + * if no such node exists. */ +MOCK_IMPL(const node_t *, +node_get_by_ed25519_id,(const ed25519_public_key_t *ed_id)) +{ + return node_get_mutable_by_ed25519_id(ed_id); +} + /** Internal: return the node_t whose identity_digest is * <b>identity_digest</b>. If none exists, create a new one, add it to the * nodelist, and return it. @@ -164,12 +217,181 @@ node_get_or_create(const char *identity_digest) smartlist_add(the_nodelist->nodes, node); node->nodelist_idx = smartlist_len(the_nodelist->nodes) - 1; + node->hsdir_index = tor_malloc_zero(sizeof(hsdir_index_t)); node->country = -1; return node; } +/** Remove <b>node</b> from the ed25519 map (if it present), and + * set its ed25519_id field to zero. */ +static int +node_remove_from_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + int rv = 0; + node_t *search = + HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(search != node)) { + goto clear_and_return; + } + + search = HT_REMOVE(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + tor_assert(search == node); + rv = 1; + + clear_and_return: + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return rv; +} + +/** If <b>node</b> has an ed25519 id, and it is not already in the ed25519 id + * map, set its ed25519_id field, and add it to the ed25519 map. + */ +static int +node_add_to_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (! ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + const ed25519_public_key_t *key = node_get_ed25519_id(node); + if (!key) { + return 0; + } + + node_t *old; + memcpy(&node->ed25519_id, key, sizeof(node->ed25519_id)); + old = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(old)) { + /* XXXX order matters here, and this may mean that authorities aren't + * pinning. */ + if (old != node) + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return 0; + } + + HT_INSERT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + return 1; +} + +/* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index + * for the node, both current and next if possible. This can only fails if the + * node_t ed25519 identity key can't be found which would be a bug. */ +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns) +{ + time_t now = approx_time(); + const ed25519_public_key_t *node_identity_pk; + uint8_t *fetch_srv = NULL, *store_first_srv = NULL, *store_second_srv = NULL; + uint64_t next_time_period_num, current_time_period_num; + uint64_t fetch_tp, store_first_tp, store_second_tp; + + tor_assert(node); + tor_assert(ns); + + if (!networkstatus_is_live(ns, now)) { + static struct ratelim_t live_consensus_ratelim = RATELIM_INIT(30 * 60); + log_fn_ratelim(&live_consensus_ratelim, LOG_INFO, LD_GENERAL, + "Not setting hsdir index with a non-live consensus."); + goto done; + } + + node_identity_pk = node_get_ed25519_id(node); + if (node_identity_pk == NULL) { + log_debug(LD_GENERAL, "ed25519 identity public key not found when " + "trying to build the hsdir indexes for node %s", + node_describe(node)); + goto done; + } + + /* Get the current and next time period number. */ + current_time_period_num = hs_get_time_period_num(0); + next_time_period_num = hs_get_next_time_period_num(0); + + /* We always use the current time period for fetching descs */ + fetch_tp = current_time_period_num; + + /* Now extract the needed SRVs and time periods for building hsdir indices */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + fetch_srv = hs_get_current_srv(fetch_tp, ns); + + store_first_tp = hs_get_previous_time_period_num(0); + store_second_tp = current_time_period_num; + } else { + fetch_srv = hs_get_previous_srv(fetch_tp, ns); + + store_first_tp = current_time_period_num; + store_second_tp = next_time_period_num; + } + + /* We always use the old SRV for storing the first descriptor and the latest + * SRV for storing the second descriptor */ + store_first_srv = hs_get_previous_srv(store_first_tp, ns); + store_second_srv = hs_get_current_srv(store_second_tp, ns); + + /* Build the fetch index. */ + hs_build_hsdir_index(node_identity_pk, fetch_srv, fetch_tp, + node->hsdir_index->fetch); + + /* If we are in the time segment between SRV#N and TP#N, the fetch index is + the same as the first store index */ + if (!hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_first, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_first)); + } else { + hs_build_hsdir_index(node_identity_pk, store_first_srv, store_first_tp, + node->hsdir_index->store_first); + } + + /* If we are in the time segment between TP#N and SRV#N+1, the fetch index is + the same as the second store index */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_second, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_second)); + } else { + hs_build_hsdir_index(node_identity_pk, store_second_srv, store_second_tp, + node->hsdir_index->store_second); + } + + done: + tor_free(fetch_srv); + tor_free(store_first_srv); + tor_free(store_second_srv); + return; +} + +/** Recompute all node hsdir indices. */ +void +nodelist_recompute_all_hsdir_indices(void) +{ + networkstatus_t *consensus; + if (!the_nodelist) { + return; + } + + /* Get a live consensus. Abort if not found */ + consensus = networkstatus_get_live_consensus(approx_time()); + if (!consensus) { + return; + } + + /* Recompute all hsdir indices */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + node_set_hsdir_index(node, consensus); + } SMARTLIST_FOREACH_END(node); +} + /** Called when a node's address changes. */ static void node_addrs_changed(node_t *node) @@ -194,6 +416,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) id_digest = ri->cache_info.identity_digest; node = node_get_or_create(id_digest); + node_remove_from_ed25519_map(node); + if (node->ri) { if (!routers_have_same_or_addrs(node->ri, ri)) { node_addrs_changed(node); @@ -207,6 +431,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) } node->ri = ri; + node_add_to_ed25519_map(node); + if (node->country == -1) node_set_country(node); @@ -216,6 +442,14 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) dirserv_set_node_flags_from_authoritative_status(node, status); } + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (node->rs && node->rs->supports_v3_hsdir) { + node_set_hsdir_index(node, + networkstatus_get_latest_consensus()); + } + return node; } @@ -242,11 +476,21 @@ nodelist_add_microdesc(microdesc_t *md) return NULL; node = node_get_mutable_by_id(rs->identity_digest); if (node) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; + node->md = md; md->held_by_nodes++; + /* Setting the HSDir index requires the ed25519 identity key which can + * only be found either in the ri or md. This is why this is called here. + * Only nodes supporting HSDir=2 protocol version needs this index. */ + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } + node_add_to_ed25519_map(node); } + return node; } @@ -274,15 +518,20 @@ nodelist_set_consensus(networkstatus_t *ns) if (ns->flavor == FLAV_MICRODESC) { if (node->md == NULL || tor_memneq(node->md->digest,rs->descriptor_digest,DIGEST256_LEN)) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; node->md = microdesc_cache_lookup_by_digest256(NULL, rs->descriptor_digest); if (node->md) node->md->held_by_nodes++; + node_add_to_ed25519_map(node); } } + if (rs->supports_v3_hsdir) { + node_set_hsdir_index(node, ns); + } node_set_country(node); /* If we're not an authdir, believe others. */ @@ -341,6 +590,9 @@ nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md) if (node && node->md == md) { node->md = NULL; md->held_by_nodes--; + if (! node_get_ed25519_id(node)) { + node_remove_from_ed25519_map(node); + } } } @@ -369,6 +621,7 @@ nodelist_drop_node(node_t *node, int remove_from_ht) tmp = HT_REMOVE(nodelist_map, &the_nodelist->nodes_by_id, node); tor_assert(tmp == node); } + node_remove_from_ed25519_map(node); idx = node->nodelist_idx; tor_assert(idx >= 0); @@ -410,6 +663,7 @@ node_free(node_t *node) if (node->md) node->md->held_by_nodes--; tor_assert(node->nodelist_idx == -1); + tor_free(node->hsdir_index); tor_free(node); } @@ -451,6 +705,7 @@ nodelist_free_all(void) return; HT_CLEAR(nodelist_map, &the_nodelist->nodes_by_id); + HT_CLEAR(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { node->nodelist_idx = -1; node_free(node); @@ -515,9 +770,27 @@ nodelist_assert_ok(void) tor_assert(node_sl_idx == node->nodelist_idx); } SMARTLIST_FOREACH_END(node); + /* Every node listed with an ed25519 identity should be listed by that + * identity. + */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (!ed25519_public_key_is_zero(&node->ed25519_id)) { + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + } SMARTLIST_FOREACH_END(node); + + node_t **idx; + HT_FOREACH(idx, nodelist_ed_map, &the_nodelist->nodes_by_ed_id) { + node_t *node = *idx; + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + tor_assert((long)smartlist_len(the_nodelist->nodes) == (long)HT_SIZE(&the_nodelist->nodes_by_id)); + tor_assert((long)smartlist_len(the_nodelist->nodes) >= + (long)HT_SIZE(&the_nodelist->nodes_by_ed_id)); + digestmap_free(dm, NULL); } @@ -534,28 +807,23 @@ nodelist_get_list,(void)) /** Given a hex-encoded nickname of the format DIGEST, $DIGEST, $DIGEST=name, * or $DIGEST~name, return the node with the matching identity digest and * nickname (if any). Return NULL if no such node exists, or if <b>hex_id</b> - * is not well-formed. */ + * is not well-formed. DOCDOC flags */ const node_t * -node_get_by_hex_id(const char *hex_id) +node_get_by_hex_id(const char *hex_id, unsigned flags) { char digest_buf[DIGEST_LEN]; char nn_buf[MAX_NICKNAME_LEN+1]; char nn_char='\0'; + (void) flags; // XXXX + if (hex_digest_nickname_decode(hex_id, digest_buf, &nn_char, nn_buf)==0) { const node_t *node = node_get_by_id(digest_buf); if (!node) return NULL; - if (nn_char) { - const char *real_name = node_get_nickname(node); - if (!real_name || strcasecmp(real_name, nn_buf)) - return NULL; - if (nn_char == '=') { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nn_buf); - if (!named_id || tor_memneq(named_id, digest_buf, DIGEST_LEN)) - return NULL; - } + if (nn_char == '=') { + /* "=" indicates a Named relay, but there aren't any of those now. */ + return NULL; } return node; } @@ -564,42 +832,27 @@ node_get_by_hex_id(const char *hex_id) } /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding node_t, or NULL if none exists. Warn the user if - * <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ + * the corresponding node_t, or NULL if none exists. Warn the user if they + * have specified a router by nickname, unless the NNF_NO_WARN_UNNAMED bit is + * set in <b>flags</b>. */ MOCK_IMPL(const node_t *, -node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) +node_get_by_nickname,(const char *nickname, unsigned flags)) { + const int warn_if_unnamed = !(flags & NNF_NO_WARN_UNNAMED); + if (!the_nodelist) return NULL; /* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */ { const node_t *node; - if ((node = node_get_by_hex_id(nickname)) != NULL) + if ((node = node_get_by_hex_id(nickname, flags)) != NULL) return node; } if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) return NULL; - /* Okay, so if we get here, the nickname is just a nickname. Is there - * a binding for it in the consensus? */ - { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nickname); - if (named_id) - return node_get_by_id(named_id); - } - - /* Is it marked as owned-by-someone-else? */ - if (networkstatus_nickname_is_unnamed(nickname)) { - log_info(LD_GENERAL, "The name %s is listed as Unnamed: there is some " - "router that holds it, but not one listed in the current " - "consensus.", escaped(nickname)); - return NULL; - } - /* Okay, so the name is not canonical for anybody. */ { smartlist_t *matches = smartlist_new(); @@ -630,11 +883,9 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) if (! node->name_lookup_warned) { base16_encode(fp, sizeof(fp), node->identity, DIGEST_LEN); log_warn(LD_CONFIG, - "You specified a server \"%s\" by name, but the directory " - "authorities do not have any key registered for this " - "nickname -- so it could be used by any server, not just " - "the one you meant. " - "To make sure you get the same server in the future, refer " + "You specified a relay \"%s\" by name, but nicknames can be " + "used by any relay, not just the one you meant. " + "To make sure you get the same relay in the future, refer " "to it by key, as \"$%s\".", nickname, fp); node->name_lookup_warned = 1; } @@ -653,22 +904,37 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) const ed25519_public_key_t * node_get_ed25519_id(const node_t *node) { + const ed25519_public_key_t *ri_pk = NULL; + const ed25519_public_key_t *md_pk = NULL; if (node->ri) { if (node->ri->cache_info.signing_key_cert) { - const ed25519_public_key_t *pk = - &node->ri->cache_info.signing_key_cert->signing_key; - if (BUG(ed25519_public_key_is_zero(pk))) - goto try_the_md; - return pk; + ri_pk = &node->ri->cache_info.signing_key_cert->signing_key; + if (BUG(ed25519_public_key_is_zero(ri_pk))) + ri_pk = NULL; } } - try_the_md: + if (node->md) { if (node->md->ed25519_identity_pkey) { - return node->md->ed25519_identity_pkey; + md_pk = node->md->ed25519_identity_pkey; } } - return NULL; + + if (ri_pk && md_pk) { + if (ed25519_pubkey_eq(ri_pk, md_pk)) { + return ri_pk; + } else { + /* This can happen if the relay gets flagged NoEdConsensus which will be + * triggered on all relays of the network. Thus a protocol warning. */ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Inconsistent ed25519 identities in the nodelist"); + return NULL; + } + } else if (ri_pk) { + return ri_pk; + } else { + return md_pk; + } } /** Return true iff this node's Ed25519 identity matches <b>id</b>. @@ -707,6 +973,79 @@ node_supports_ed25519_link_authentication(const node_t *node) return 0; } +/** Return true iff <b>node</b> supports the hidden service directory version + * 3 protocol (proposal 224). */ +int +node_supports_v3_hsdir(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_hsdir; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + /* Bug #22447 forces us to filter on tor version: + * If platform is a Tor version, and older than 0.3.0.8, return False. + * Else, obey the protocol list. */ + if (node->ri->platform) { + if (!strcmpstart(node->ri->platform, "Tor ") && + !tor_version_as_new_as(node->ri->platform, "0.3.0.8")) { + return 0; + } + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSDIR, PROTOVER_HSDIR_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + +/** Return true iff <b>node</b> supports ed25519 authentication as an hidden + * service introduction point.*/ +int +node_supports_ed25519_hs_intro(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_ed25519_hs_intro; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSINTRO, PROTOVER_HS_INTRO_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + +/** Return true iff <b>node</b> supports to be a rendezvous point for hidden + * service version 3 (HSRend=2). */ +int +node_supports_v3_rendezvous_point(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_rendezvous_point; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSREND, + PROTOVER_HS_RENDEZVOUS_POINT_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + /** Return the RSA ID key's SHA1 digest for the provided node. */ const uint8_t * node_get_rsa_id_digest(const node_t *node) @@ -728,21 +1067,6 @@ node_get_nickname(const node_t *node) return NULL; } -/** Return true iff the nickname of <b>node</b> is canonical, based on the - * latest consensus. */ -int -node_is_named(const node_t *node) -{ - const char *named_id; - const char *nickname = node_get_nickname(node); - if (!nickname) - return 0; - named_id = networkstatus_get_router_digest_by_nickname(nickname); - if (!named_id) - return 0; - return tor_memeq(named_id, node->identity, DIGEST_LEN); -} - /** Return true iff <b>node</b> appears to be a directory authority or * directory cache */ int @@ -790,13 +1114,12 @@ node_get_verbose_nickname(const node_t *node, char *verbose_name_out) { const char *nickname = node_get_nickname(node); - int is_named = node_is_named(node); verbose_name_out[0] = '$'; base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, node->identity, DIGEST_LEN); if (!nickname) return; - verbose_name_out[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + verbose_name_out[1+HEX_DIGEST_LEN] = '~'; strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); } @@ -1356,8 +1679,7 @@ node_nickname_matches(const node_t *node, const char *nickname) return 1; return hex_digest_nickname_matches(nickname, node->identity, - n, - node_is_named(node)); + n); } /** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ @@ -1459,7 +1781,7 @@ nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { const node_t *node2; const smartlist_t *family2; - if (!(node2 = node_get_by_nickname(name, 0))) + if (!(node2 = node_get_by_nickname(name, NNF_NO_WARN_UNNAMED))) continue; if (!(family2 = node_get_declared_family(node2))) continue; @@ -1611,8 +1933,8 @@ static char dir_info_status[512] = ""; * no exits in the consensus." * To obtain the final weighted bandwidth, we multiply the * weighted bandwidth fraction for each position (guard, middle, exit). */ -int -router_have_minimum_dir_info(void) +MOCK_IMPL(int, +router_have_minimum_dir_info,(void)) { static int logged_delay=0; const char *delay_fetches_msg = NULL; @@ -1659,6 +1981,8 @@ router_dir_info_changed(void) { need_to_update_have_min_dir_info = 1; rend_hsdir_routers_changed(); + hs_service_dir_info_changed(); + hs_client_dir_info_changed(); } /** Return a string describing what we're missing before we have enough @@ -1968,6 +2292,7 @@ update_router_have_minimum_dir_info(void) { time_t now = time(NULL); int res; + int num_present=0, num_usable=0; const or_options_t *options = get_options(); const networkstatus_t *consensus = networkstatus_get_reasonably_live_consensus(now,usable_consensus_flavor()); @@ -1986,17 +2311,9 @@ update_router_have_minimum_dir_info(void) using_md = consensus->flavor == FLAV_MICRODESC; - if (! entry_guards_have_enough_dir_info_to_build_circuits()) { - strlcpy(dir_info_status, "We're missing descriptors for some of our " - "primary entry guards", sizeof(dir_info_status)); - res = 0; - goto done; - } - /* Check fraction of available paths */ { char *status = NULL; - int num_present=0, num_usable=0; double paths = compute_frac_paths_available(consensus, options, now, &num_present, &num_usable, &status); @@ -2017,6 +2334,18 @@ update_router_have_minimum_dir_info(void) res = 1; } + { /* Check entry guard dirinfo status */ + char *guard_error = entry_guards_get_err_str_if_dir_info_missing(using_md, + num_present, + num_usable); + if (guard_error) { + strlcpy(dir_info_status, guard_error, sizeof(dir_info_status)); + tor_free(guard_error); + res = 0; + goto done; + } + } + done: /* If paths have just become available in this update. */ diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 95ae778a5b..754990ac8d 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -18,7 +18,14 @@ node_t *node_get_mutable_by_id(const char *identity_digest); MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); -const node_t *node_get_by_hex_id(const char *identity_digest); +node_t *node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id); +MOCK_DECL(const node_t *, node_get_by_ed25519_id, + (const ed25519_public_key_t *ed_id)); + +#define NNF_NO_WARN_UNNAMED (1u<<0) + +const node_t *node_get_by_hex_id(const char *identity_digest, + unsigned flags); node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); void nodelist_set_consensus(networkstatus_t *ns); @@ -28,16 +35,17 @@ void nodelist_remove_routerinfo(routerinfo_t *ri); void nodelist_purge(void); smartlist_t *nodelist_find_nodes_with_microdesc(const microdesc_t *md); +void nodelist_recompute_all_hsdir_indices(void); + void nodelist_free_all(void); void nodelist_assert_ok(void); MOCK_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unnamed)); + (const char *nickname, unsigned flags)); void node_get_verbose_nickname(const node_t *node, char *verbose_name_out); void node_get_verbose_nickname_by_id(const char *id_digest, char *verbose_name_out); -int node_is_named(const node_t *node); int node_is_dir(const node_t *node); int node_has_descriptor(const node_t *node); int node_get_purpose(const node_t *node); @@ -58,6 +66,9 @@ const ed25519_public_key_t *node_get_ed25519_id(const node_t *node); int node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id); int node_supports_ed25519_link_authentication(const node_t *node); +int node_supports_v3_hsdir(const node_t *node); +int node_supports_ed25519_hs_intro(const node_t *node); +int node_supports_v3_rendezvous_point(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); int node_has_ipv6_addr(const node_t *node); @@ -103,7 +114,7 @@ int addrs_in_same_network_family(const tor_addr_t *a1, * no exits in the consensus, we wait for enough info to create internal * paths, and should avoid creating exit paths, as they will simply fail. * We make sure we create all available circuit types at the same time. */ -int router_have_minimum_dir_info(void); +MOCK_DECL(int, router_have_minimum_dir_info,(void)); /** Set to CONSENSUS_PATH_EXIT if there is at least one exit node * in the consensus. We update this flag in compute_frac_paths_available if @@ -131,5 +142,16 @@ void router_dir_info_changed(void); const char *get_dir_info_status_string(void); int count_loading_descriptors_progress(void); -#endif +#ifdef NODELIST_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns); + +#endif /* defined(TOR_UNIT_TESTS) */ + +#endif /* defined(NODELIST_PRIVATE) */ + +#endif /* !defined(TOR_NODELIST_H) */ diff --git a/src/or/ntmain.c b/src/or/ntmain.c index d0d5276c48..508e5844eb 100644 --- a/src/or/ntmain.c +++ b/src/or/ntmain.c @@ -329,9 +329,10 @@ nt_service_main(void) case CMD_VERIFY_CONFIG: case CMD_DUMP_CONFIG: case CMD_KEYGEN: + case CMD_KEY_EXPIRATION: log_err(LD_CONFIG, "Unsupported command (--list-fingerprint, " - "--hash-password, --keygen, --dump-config, or --verify-config) " - "in NT service."); + "--hash-password, --keygen, --dump-config, --verify-config, " + "or --key-expiration) in NT service."); break; case CMD_RUN_UNITTESTS: default: @@ -501,7 +502,7 @@ nt_service_command_line(int *using_default_torrc) tor_exe_ascii[sizeof(tor_exe_ascii)-1] = '\0'; #else strlcpy(tor_exe_ascii, tor_exe, sizeof(tor_exe_ascii)); -#endif +#endif /* defined(UNICODE) */ /* Allocate a string for the NT service command line and */ /* Format the service command */ @@ -777,5 +778,5 @@ nt_service_parse_options(int argc, char **argv, int *should_exit) return 0; } -#endif +#endif /* defined(_WIN32) */ diff --git a/src/or/ntmain.h b/src/or/ntmain.h index 4b771b1828..81b7159855 100644 --- a/src/or/ntmain.h +++ b/src/or/ntmain.h @@ -22,7 +22,7 @@ int nt_service_is_stopping(void); void nt_service_set_state(DWORD state); #else #define nt_service_is_stopping() 0 -#endif +#endif /* defined(NT_SERVICE) */ -#endif +#endif /* !defined(TOR_NTMAIN_H) */ diff --git a/src/or/onion.c b/src/or/onion.c index a98b97cb1d..7e1e89df1b 100644 --- a/src/or/onion.c +++ b/src/or/onion.c @@ -1219,7 +1219,7 @@ extend_cell_format(uint8_t *command_out, uint16_t *len_out, *command_out = RELAY_COMMAND_EXTEND; *len_out = 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN; set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr)); - set_uint16(p+4, ntohs(cell_in->orport_ipv4.port)); + set_uint16(p+4, htons(cell_in->orport_ipv4.port)); if (cell_in->create_cell.handshake_type == ONION_HANDSHAKE_TYPE_NTOR) { memcpy(p+6, NTOR_CREATE_MAGIC, 16); memcpy(p+22, cell_in->create_cell.onionskin, NTOR_ONIONSKIN_LEN); diff --git a/src/or/onion.h b/src/or/onion.h index 37a7b08cb6..95544dfac1 100644 --- a/src/or/onion.h +++ b/src/or/onion.h @@ -119,5 +119,5 @@ int extend_cell_format(uint8_t *command_out, uint16_t *len_out, int extended_cell_format(uint8_t *command_out, uint16_t *len_out, uint8_t *payload_out, const extended_cell_t *cell_in); -#endif +#endif /* !defined(TOR_ONION_H) */ diff --git a/src/or/onion_fast.h b/src/or/onion_fast.h index b31f8e9492..3a5aefea3f 100644 --- a/src/or/onion_fast.h +++ b/src/or/onion_fast.h @@ -35,5 +35,5 @@ int fast_client_handshake(const fast_handshake_state_t *handshake_state, size_t key_out_len, const char **msg_out); -#endif +#endif /* !defined(TOR_ONION_FAST_H) */ diff --git a/src/or/onion_ntor.h b/src/or/onion_ntor.h index 158c499de4..02dea2dfc1 100644 --- a/src/or/onion_ntor.h +++ b/src/or/onion_ntor.h @@ -55,7 +55,7 @@ struct ntor_handshake_state_t { curve25519_public_key_t pubkey_X; /** @} */ }; -#endif +#endif /* defined(ONION_NTOR_PRIVATE) */ -#endif +#endif /* !defined(TOR_ONION_NTOR_H) */ diff --git a/src/or/onion_tap.c b/src/or/onion_tap.c index 294fc0df6d..c71fa236ed 100644 --- a/src/or/onion_tap.c +++ b/src/or/onion_tap.c @@ -72,10 +72,8 @@ onion_skin_TAP_create(crypto_pk_t *dest_router_key, if (crypto_dh_get_public(dh, challenge, dhbytes)) goto err; - note_crypto_pk_op(ENC_ONIONSKIN); - /* set meeting point, meeting cookie, etc here. Leave zero for now. */ - if (crypto_pk_public_hybrid_encrypt(dest_router_key, onion_skin_out, + if (crypto_pk_obsolete_public_hybrid_encrypt(dest_router_key, onion_skin_out, TAP_ONIONSKIN_CHALLENGE_LEN, challenge, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1)<0) @@ -124,8 +122,7 @@ onion_skin_TAP_server_handshake( k = i==0?private_key:prev_private_key; if (!k) break; - note_crypto_pk_op(DEC_ONIONSKIN); - len = crypto_pk_private_hybrid_decrypt(k, challenge, + len = crypto_pk_obsolete_private_hybrid_decrypt(k, challenge, TAP_ONIONSKIN_CHALLENGE_LEN, onion_skin, TAP_ONIONSKIN_CHALLENGE_LEN, diff --git a/src/or/onion_tap.h b/src/or/onion_tap.h index bd625231f4..713c1d7391 100644 --- a/src/or/onion_tap.h +++ b/src/or/onion_tap.h @@ -34,5 +34,5 @@ int onion_skin_TAP_client_handshake(crypto_dh_t *handshake_state, size_t key_out_len, const char **msg_out); -#endif +#endif /* !defined(TOR_ONION_TAP_H) */ diff --git a/src/or/or.h b/src/or/or.h index 95281e701f..03e20b80de 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -64,7 +64,7 @@ #include <process.h> #include <direct.h> #include <windows.h> -#endif +#endif /* defined(_WIN32) */ #include "crypto.h" #include "crypto_format.h" @@ -226,8 +226,10 @@ typedef enum { #define CONN_TYPE_EXT_OR 16 /** Type for sockets listening for Extended ORPort connections. */ #define CONN_TYPE_EXT_OR_LISTENER 17 +/** Type for sockets listening for HTTP CONNECT tunnel connections. */ +#define CONN_TYPE_AP_HTTP_CONNECT_LISTENER 18 -#define CONN_TYPE_MAX_ 17 +#define CONN_TYPE_MAX_ 19 /* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * connection_t. */ @@ -348,7 +350,9 @@ typedef enum { /** State for a transparent natd connection: waiting for original * destination. */ #define AP_CONN_STATE_NATD_WAIT 12 -#define AP_CONN_STATE_MAX_ 12 +/** State for an HTTP tunnel: waiting for an HTTP CONNECT command. */ +#define AP_CONN_STATE_HTTP_CONNECT_WAIT 13 +#define AP_CONN_STATE_MAX_ 13 /** True iff the AP_CONN_STATE_* value <b>s</b> means that the corresponding * edge connection is not attached to any circuit. */ @@ -421,15 +425,23 @@ typedef enum { #define DIR_PURPOSE_FETCH_RENDDESC_V2 18 /** A connection to a directory server: download a microdescriptor. */ #define DIR_PURPOSE_FETCH_MICRODESC 19 -#define DIR_PURPOSE_MAX_ 19 +/** A connection to a hidden service directory: upload a v3 descriptor. */ +#define DIR_PURPOSE_UPLOAD_HSDESC 20 +/** A connection to a hidden service directory: fetch a v3 descriptor. */ +#define DIR_PURPOSE_FETCH_HSDESC 21 +/** A connection to a directory server: set after a hidden service descriptor + * is downloaded. */ +#define DIR_PURPOSE_HAS_FETCHED_HSDESC 22 +#define DIR_PURPOSE_MAX_ 22 /** True iff <b>p</b> is a purpose corresponding to uploading * data to a directory server. */ #define DIR_PURPOSE_IS_UPLOAD(p) \ ((p)==DIR_PURPOSE_UPLOAD_DIR || \ (p)==DIR_PURPOSE_UPLOAD_VOTE || \ - (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ - (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2) + (p)==DIR_PURPOSE_UPLOAD_SIGNATURES || \ + (p)==DIR_PURPOSE_UPLOAD_RENDDESC_V2 || \ + (p)==DIR_PURPOSE_UPLOAD_HSDESC) #define EXIT_PURPOSE_MIN_ 1 /** This exit stream wants to do an ordinary connect. */ @@ -640,6 +652,10 @@ typedef enum { /** The target address is in a private network (like 127.0.0.1 or 10.0.0.1); * you don't want to do that over a randomly chosen exit */ #define END_STREAM_REASON_PRIVATE_ADDR 262 +/** This is an HTTP tunnel connection and the client used or misused HTTP in a + * way we can't handle. + */ +#define END_STREAM_REASON_HTTPPROTOCOL 263 /** Bitwise-and this value with endreason to mask out all flags. */ #define END_STREAM_REASON_MASK 511 @@ -731,15 +747,15 @@ typedef enum { #define REND_NUMBER_OF_CONSECUTIVE_REPLICAS 3 /** Length of v2 descriptor ID (32 base32 chars = 160 bits). */ -#define REND_DESC_ID_V2_LEN_BASE32 32 +#define REND_DESC_ID_V2_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the base32-encoded secret ID part of versioned hidden service * descriptors. */ -#define REND_SECRET_ID_PART_LEN_BASE32 32 +#define REND_SECRET_ID_PART_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the base32-encoded hash of an introduction point's * identity key. */ -#define REND_INTRO_POINT_ID_LEN_BASE32 32 +#define REND_INTRO_POINT_ID_LEN_BASE32 BASE32_DIGEST_LEN /** Length of the descriptor cookie that is used for client authorization * to hidden services. */ @@ -846,6 +862,13 @@ rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d) return DOWNCAST(rend_data_v2_t, d); } +/* Stub because we can't include hs_ident.h. */ +struct hs_ident_edge_conn_t; +struct hs_ident_dir_conn_t; +struct hs_ident_circuit_t; +/* Stub because we can't include hs_common.h. */ +struct hsdir_index_t; + /** Time interval for tracking replays of DH public keys received in * INTRODUCE2 cells. Used only to avoid launching multiple * simultaneous attempts to connect to the same rendezvous point. */ @@ -1179,11 +1202,8 @@ typedef struct { uint16_t length; /**< How long is the payload body? */ } relay_header_t; -typedef struct buf_t buf_t; typedef struct socks_request_t socks_request_t; -#define buf_t buf_t - typedef struct entry_port_cfg_t { /* Client port types (socks, dns, trans, natd) only: */ uint8_t isolation_flags; /**< Zero or more isolation flags */ @@ -1243,6 +1263,8 @@ typedef struct server_port_cfg_t { #define CONTROL_CONNECTION_MAGIC 0x8abc765du #define LISTENER_CONNECTION_MAGIC 0x1a1ac741u +struct buf_t; + /** Description of a connection to another host or process, and associated * data. * @@ -1314,8 +1336,9 @@ typedef struct connection_t { struct event *read_event; /**< Libevent event structure. */ struct event *write_event; /**< Libevent event structure. */ - buf_t *inbuf; /**< Buffer holding data read over this connection. */ - buf_t *outbuf; /**< Buffer holding data to write over this connection. */ + struct buf_t *inbuf; /**< Buffer holding data read over this connection. */ + struct buf_t *outbuf; /**< Buffer holding data to write over this + * connection. */ size_t outbuf_flushlen; /**< How much data should we try to flush from the * outbuf? */ time_t timestamp_lastread; /**< When was the last time libevent said we could @@ -1410,7 +1433,7 @@ typedef struct listener_connection_t { * session as described in RFC 5705. * * Not used by today's tors, since everything that supports this - * also supports ED25519_SHA3_5705, which is better. + * also supports ED25519_SHA256_5705, which is better. **/ #define AUTHTYPE_RSA_SHA256_RFC5705 2 /** As AUTHTYPE_RSA_SHA256_RFC5705, but uses an Ed25519 identity key to @@ -1648,6 +1671,11 @@ typedef struct edge_connection_t { * an exit)? */ rend_data_t *rend_data; + /* Hidden service connection identifier for edge connections. Used by the HS + * client-side code to identify client SOCKS connections and by the + * service-side code to match HS circuits with their streams. */ + struct hs_ident_edge_conn_t *hs_ident; + uint32_t address_ttl; /**< TTL for address-to-addr mapping on exit * connection. Exit connections only. */ uint32_t begincell_flags; /** Flags sent or received in the BEGIN cell @@ -1717,11 +1745,11 @@ typedef struct entry_connection_t { /** For AP connections only: buffer for data that we have sent * optimistically, which we might need to re-send if we have to * retry this connection. */ - buf_t *pending_optimistic_data; + struct buf_t *pending_optimistic_data; /* For AP connections only: buffer for data that we previously sent * optimistically which we are currently re-sending as we retry this * connection. */ - buf_t *sending_optimistic_data; + struct buf_t *sending_optimistic_data; /** If this is a DNSPort connection, this field holds the pending DNS * request that we're going to try to answer. */ @@ -1798,6 +1826,11 @@ typedef struct dir_connection_t { /** What rendezvous service are we querying for? */ rend_data_t *rend_data; + /* Hidden service connection identifier for dir connections: Used by HS + client-side code to fetch HS descriptors, and by the service-side code to + upload descriptors. */ + struct hs_ident_dir_conn_t *hs_ident; + /** If this is a one-hop connection, tracks the state of the directory guard * for this connection (if any). */ struct circuit_guard_state_t *guard_state; @@ -1816,7 +1849,7 @@ typedef struct dir_connection_t { /** Number of RELAY_DATA cells sent. */ uint32_t data_cells_sent; -#endif +#endif /* defined(MEASUREMENTS_21206) */ } dir_connection_t; /** Subtype of connection_t for an connection to a controller. */ @@ -2073,7 +2106,9 @@ typedef struct download_status_t { * or after each failure? */ download_schedule_backoff_bitfield_t backoff : 1; /**< do we use the * deterministic schedule, or random - * exponential backoffs? */ + * exponential backoffs? + * Increment on failure schedules + * always use exponential backoff. */ uint8_t last_backoff_position; /**< number of attempts/failures, depending * on increment_on, when we last recalculated * the delay. Only updated if backoff @@ -2309,6 +2344,11 @@ typedef struct routerstatus_t { * requires HSDir=2. */ unsigned int supports_v3_hsdir : 1; + /** True iff this router has a protocol list that allows it to be an hidden + * service rendezvous point supporting version 3 as seen in proposal 224. + * This requires HSRend=2. */ + unsigned int supports_v3_rendezvous_point: 1; + unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with @@ -2434,6 +2474,8 @@ typedef struct node_t { /** Used to look up the node_t by its identity digest. */ HT_ENTRY(node_t) ht_ent; + /** Used to look up the node_t by its ed25519 identity digest. */ + HT_ENTRY(node_t) ed_ht_ent; /** Position of the node within the list of nodes */ int nodelist_idx; @@ -2441,6 +2483,13 @@ typedef struct node_t { * identity may exist at a time. */ char identity[DIGEST_LEN]; + /** The ed25519 identity of this node_t. This field is nonzero iff we + * currently have an ed25519 identity for this node in either md or ri, + * _and_ this node has been inserted to the ed25519-to-node map in the + * nodelist. + */ + ed25519_public_key_t ed25519_id; + microdesc_t *md; routerinfo_t *ri; routerstatus_t *rs; @@ -2488,6 +2537,10 @@ typedef struct node_t { time_t last_reachable; /* IPv4. */ time_t last_reachable6; /* IPv6. */ + /* Hidden service directory index data. This is used by a service or client + * in order to know what's the hs directory index for this node at the time + * the consensus is set. */ + struct hsdir_index_t *hsdir_index; } node_t; /** Linked list of microdesc hash lines for a single router in a directory @@ -3201,6 +3254,10 @@ typedef struct origin_circuit_t { /** Holds all rendezvous data on either client or service side. */ rend_data_t *rend_data; + /** Holds hidden service identifier on either client or service side. This + * is for both introduction and rendezvous circuit. */ + struct hs_ident_circuit_t *hs_ident; + /** Holds the data that the entry guard system uses to track the * status of the guard this circuit is using, and thereby to determine * whether this circuit can be used. */ @@ -3431,9 +3488,6 @@ typedef struct or_circuit_t { /* We have already received an INTRODUCE1 cell on this circuit. */ unsigned int already_received_introduce1 : 1; - /** True iff this circuit was made with a CREATE_FAST cell. */ - unsigned int is_first_hop : 1; - /** If set, this circuit carries HS traffic. Consider it in any HS * statistics. */ unsigned int circuit_carries_hs_traffic_stats : 1; @@ -3582,7 +3636,8 @@ typedef struct { enum { CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG, - CMD_KEYGEN + CMD_KEYGEN, + CMD_KEY_EXPIRATION, } command; char *command_arg; /**< Argument for command-line option. */ @@ -3664,8 +3719,8 @@ typedef struct { config_line_t *SocksPort_lines; /** Ports to listen on for transparent pf/netfilter connections. */ config_line_t *TransPort_lines; - const char *TransProxyType; /**< What kind of transparent proxy - * implementation are we using? */ + char *TransProxyType; /**< What kind of transparent proxy + * implementation are we using? */ /** Parsed value of TransProxyType. */ enum { TPT_DEFAULT, @@ -3675,6 +3730,8 @@ typedef struct { } TransProxyType_parsed; config_line_t *NATDPort_lines; /**< Ports to listen on for transparent natd * connections. */ + /** Ports to listen on for HTTP Tunnel connections. */ + config_line_t *HTTPTunnelPort_lines; config_line_t *ControlPort_lines; /**< Ports to listen on for control * connections. */ config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on @@ -3701,7 +3758,8 @@ typedef struct { * configured in one of the _lines options above. * For client ports, also true if there is a unix socket configured. * If you are checking for client ports, you may want to use: - * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set + * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set || + * HTTPTunnelPort_set * rather than SocksPort_set. * * @{ @@ -3714,6 +3772,7 @@ typedef struct { unsigned int DirPort_set : 1; unsigned int DNSPort_set : 1; unsigned int ExtORPort_set : 1; + unsigned int HTTPTunnelPort_set : 1; /**@}*/ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ @@ -3726,6 +3785,10 @@ typedef struct { int BridgeAuthoritativeDir; /**< Boolean: is this an authoritative directory * that aggregates bridge descriptors? */ + /** If set on a bridge relay, it will include this value on a new + * "bridge-distribution-request" line in its bridge descriptor. */ + char *BridgeDistribution; + /** If set on a bridge authority, it will answer requests on its dirport * for bridge statuses -- but only if the requests use this password. */ char *BridgePassword; @@ -4035,8 +4098,6 @@ typedef struct { int Sandbox; /**< Boolean: should sandboxing be enabled? */ int SafeSocks; /**< Boolean: should we outright refuse application * connections that use socks4 or socks5-with-local-dns? */ -#define LOG_PROTOCOL_WARN (get_options()->ProtocolWarnings ? \ - LOG_WARN : LOG_INFO) int ProtocolWarnings; /**< Boolean: when other parties screw up the Tor * protocol, is it a warn or an info in our logs? */ int TestSocks; /**< Boolean: when we get a socks connection, do we loudly @@ -4122,13 +4183,6 @@ typedef struct { * if we are a cache). For authorities, this is always true. */ int DownloadExtraInfo; - /** If true, we convert "www.google.com.foo.exit" addresses on the - * socks/trans/natd ports into "www.google.com" addresses that - * exit from the node "foo". Disabled by default since attacking - * websites and exit relays can use it to manipulate your path - * selection. */ - int AllowDotExit; - /** If true, we're configured to collect statistics on clients * requesting network statuses from us as directory. */ int DirReqStatistics_option; @@ -4293,6 +4347,10 @@ typedef struct { * altered on testing networks. */ smartlist_t *TestingBridgeDownloadSchedule; + /** Schedule for when clients should download bridge descriptors when they + * have no running bridges. Only altered on testing networks. */ + smartlist_t *TestingBridgeBootstrapDownloadSchedule; + /** When directory clients have only a few descriptors to request, they * batch them until they have more, or until this amount of time has * passed. Only altered on testing networks. */ @@ -4497,19 +4555,6 @@ typedef struct { /** How long (seconds) do we keep a guard before picking a new one? */ int GuardLifetime; - /** Low-water mark for global scheduler - start sending when estimated - * queued size falls below this threshold. - */ - uint64_t SchedulerLowWaterMark__; - /** High-water mark for global scheduler - stop sending when estimated - * queued size exceeds this threshold. - */ - uint64_t SchedulerHighWaterMark__; - /** Flush size for global scheduler - flush this many cells at a time - * when sending. - */ - int SchedulerMaxFlushCells__; - /** Is this an exit node? This is a tristate, where "1" means "yes, and use * the default exit policy if none is given" and "0" means "no; exit policy * is 'reject *'" and "auto" (-1) means "same as 1, but warn the user." @@ -4578,8 +4623,29 @@ typedef struct { * consensuses around so that we can generate diffs from them. If 0, * use the default. */ int MaxConsensusAgeForDiffs; + + /** Bool (default: 0). Tells Tor to never try to exec another program. + */ + int NoExec; + + /** Have the KIST scheduler run every X milliseconds. If less than zero, do + * not use the KIST scheduler but use the old vanilla scheduler instead. If + * zero, do what the consensus says and fall back to using KIST as if this is + * set to "10 msec" if the consensus doesn't say anything. */ + int KISTSchedRunInterval; + + /** A multiplier for the KIST per-socket limit calculation. */ + double KISTSockBufSizeFactor; + + /** The list of scheduler type string ordered by priority that is first one + * has to be tried first. Default: KIST,KISTLite,Vanilla */ + smartlist_t *Schedulers; + /* An ordered list of scheduler_types mapped from Schedulers. */ + smartlist_t *SchedulerTypes_; } or_options_t; +#define LOG_PROTOCOL_WARN (get_protocol_warning_severity_level()) + /** Persistent state for an onion router, as saved to disk. */ typedef struct { uint32_t magic_; @@ -4609,6 +4675,9 @@ typedef struct { config_line_t *TransportProxies; + /** Cached revision counters for active hidden services on this host */ + config_line_t *HidServRevCounter; + /** These fields hold information on the history of bandwidth usage for * servers. The "Ends" fields hold the time when we last updated the * bandwidth usage. The "Interval" fields hold the granularity, in seconds, @@ -4636,8 +4705,8 @@ typedef struct { /** Build time histogram */ config_line_t * BuildtimeHistogram; - unsigned int TotalBuildTimes; - unsigned int CircuitBuildAbandonedCount; + int TotalBuildTimes; + int CircuitBuildAbandonedCount; /** What version of Tor wrote this state file? */ char *TorVersion; @@ -4998,7 +5067,7 @@ typedef struct measured_bw_line_t { long int bw_kb; } measured_bw_line_t; -#endif +#endif /* defined(DIRSERV_PRIVATE) */ /********************************* dirvote.c ************************/ @@ -5353,7 +5422,10 @@ typedef enum { CRN_PREF_ADDR = 1<<7, /* On clients, only provide nodes that we can connect to directly, based on * our firewall rules */ - CRN_DIRECT_CONN = 1<<8 + CRN_DIRECT_CONN = 1<<8, + /* On clients, only provide nodes with HSRend >= 2 protocol version which + * is required for hidden service version >= 3. */ + CRN_RENDEZVOUS_V3 = 1<<9, } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ @@ -5408,5 +5480,5 @@ typedef struct tor_version_t { char git_tag[DIGEST_LEN]; } tor_version_t; -#endif +#endif /* !defined(TOR_OR_H) */ diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c index 7959867875..6c3dd3100e 100644 --- a/src/or/parsecommon.c +++ b/src/or/parsecommon.c @@ -161,6 +161,7 @@ get_token_arguments(memarea_t *area, directory_token_t *tok, char *cp = mem; int j = 0; char *args[MAX_ARGS]; + memset(args, 0, sizeof(args)); while (*cp) { if (j == MAX_ARGS) return -1; @@ -436,7 +437,7 @@ find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) * in the same order in which they occur in <b>s</b>. Otherwise return * NULL. */ smartlist_t * -find_all_by_keyword(smartlist_t *s, directory_keyword k) +find_all_by_keyword(const smartlist_t *s, directory_keyword k) { smartlist_t *out = NULL; SMARTLIST_FOREACH(s, directory_token_t *, t, diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h index b9f1613457..903d94478b 100644 --- a/src/or/parsecommon.h +++ b/src/or/parsecommon.h @@ -160,6 +160,7 @@ typedef enum { R3_INTRO_AUTH_REQUIRED, R3_SINGLE_ONION_SERVICE, R3_INTRODUCTION_POINT, + R3_INTRO_ONION_KEY, R3_INTRO_AUTH_KEY, R3_INTRO_ENC_KEY, R3_INTRO_ENC_KEY_CERT, @@ -315,7 +316,7 @@ directory_token_t *find_by_keyword_(smartlist_t *s, directory_token_t *find_opt_by_keyword(smartlist_t *s, directory_keyword keyword); -smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k); +smartlist_t * find_all_by_keyword(const smartlist_t *s, directory_keyword k); -#endif /* TOR_PARSECOMMON_H */ +#endif /* !defined(TOR_PARSECOMMON_H) */ diff --git a/src/or/periodic.h b/src/or/periodic.h index 88d00cc7e9..8baf3994eb 100644 --- a/src/or/periodic.h +++ b/src/or/periodic.h @@ -33,5 +33,5 @@ void periodic_event_setup(periodic_event_item_t *event); void periodic_event_destroy(periodic_event_item_t *event); void periodic_event_reschedule(periodic_event_item_t *event); -#endif +#endif /* !defined(TOR_PERIODIC_H) */ diff --git a/src/or/policies.c b/src/or/policies.c index 3d49a6110c..78451db8fc 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -2186,21 +2186,16 @@ exit_policy_is_general_exit_helper(smartlist_t *policy, int port) } /** Return true iff <b>ri</b> is "useful as an exit node", meaning - * it allows exit to at least one /8 address space for at least - * two of ports 80, 443, and 6667. */ + * it allows exit to at least one /8 address space for each of ports 80 + * and 443. */ int exit_policy_is_general_exit(smartlist_t *policy) { - static const int ports[] = { 80, 443, 6667 }; - int n_allowed = 0; - int i; if (!policy) /*XXXX disallow NULL policies? */ return 0; - for (i = 0; i < 3; ++i) { - n_allowed += exit_policy_is_general_exit_helper(policy, ports[i]); - } - return n_allowed >= 2; + return (exit_policy_is_general_exit_helper(policy, 80) && + exit_policy_is_general_exit_helper(policy, 443)); } /** Return false if <b>policy</b> might permit access to some addr:port; @@ -2731,7 +2726,7 @@ parse_short_policy(const char *summary) } { - size_t size = STRUCT_OFFSET(short_policy_t, entries) + + size_t size = offsetof(short_policy_t, entries) + sizeof(short_policy_entry_t)*(n_entries); result = tor_malloc_zero(size); diff --git a/src/or/policies.h b/src/or/policies.h index ce08d497e9..52ff4e2f99 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -141,7 +141,7 @@ STATIC const tor_addr_port_t * fascist_firewall_choose_address( firewall_connection_t fw_connection, int pref_only, int pref_ipv6); -#endif +#endif /* defined(POLICIES_PRIVATE) */ -#endif +#endif /* !defined(TOR_POLICIES_H) */ diff --git a/src/or/proto_cell.c b/src/or/proto_cell.c new file mode 100644 index 0000000000..75eb2a7e7f --- /dev/null +++ b/src/or/proto_cell.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_cell.h" + +#include "connection_or.h" + +/** True iff the cell command <b>command</b> is one that implies a + * variable-length cell in Tor link protocol <b>linkproto</b>. */ +static inline int +cell_command_is_var_length(uint8_t command, int linkproto) +{ + /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells + * work as implemented here. If it's 1, there are no variable-length cells. + * Tor does not support other versions right now, and so can't negotiate + * them. + */ + switch (linkproto) { + case 1: + /* Link protocol version 1 has no variable-length cells. */ + return 0; + case 2: + /* In link protocol version 2, VERSIONS is the only variable-length cell */ + return command == CELL_VERSIONS; + case 0: + case 3: + default: + /* In link protocol version 3 and later, and in version "unknown", + * commands 128 and higher indicate variable-length. VERSIONS is + * grandfathered in. */ + return command == CELL_VERSIONS || command >= 128; + } +} + +/** Check <b>buf</b> for a variable-length cell according to the rules of link + * protocol version <b>linkproto</b>. If one is found, pull it off the buffer + * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. + * Return 0 if whatever is on the start of buf_t is not a variable-length + * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start + * of a variable-length cell on <b>buf</b>, but the whole thing isn't there + * yet. */ +int +fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) +{ + char hdr[VAR_CELL_MAX_HEADER_SIZE]; + var_cell_t *result; + uint8_t command; + uint16_t length; + const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; + const int circ_id_len = get_circ_id_size(wide_circ_ids); + const unsigned header_len = get_var_cell_header_size(wide_circ_ids); + *out = NULL; + if (buf_datalen(buf) < header_len) + return 0; + buf_peek(buf, hdr, header_len); + + command = get_uint8(hdr + circ_id_len); + if (!(cell_command_is_var_length(command, linkproto))) + return 0; + + length = ntohs(get_uint16(hdr + circ_id_len + 1)); + if (buf_datalen(buf) < (size_t)(header_len+length)) + return 1; + + result = var_cell_new(length); + result->command = command; + if (wide_circ_ids) + result->circ_id = ntohl(get_uint32(hdr)); + else + result->circ_id = ntohs(get_uint16(hdr)); + + buf_drain(buf, header_len); + buf_peek(buf, (char*) result->payload, length); + buf_drain(buf, length); + + *out = result; + return 1; +} + diff --git a/src/or/proto_cell.h b/src/or/proto_cell.h new file mode 100644 index 0000000000..bbc14b9a02 --- /dev/null +++ b/src/or/proto_cell.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CELL_H +#define TOR_PROTO_CELL_H + +struct buf_t; +struct var_cell_t; + +int fetch_var_cell_from_buf(struct buf_t *buf, struct var_cell_t **out, + int linkproto); + +#endif /* !defined(TOR_PROTO_CELL_H) */ + diff --git a/src/or/proto_control0.c b/src/or/proto_control0.c new file mode 100644 index 0000000000..c17ba34948 --- /dev/null +++ b/src/or/proto_control0.c @@ -0,0 +1,26 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_control0.h" + +/** Return 1 iff buf looks more like it has an (obsolete) v0 controller + * command on it than any valid v1 controller command. */ +int +peek_buf_has_control0_command(buf_t *buf) +{ + if (buf_datalen(buf) >= 4) { + char header[4]; + uint16_t cmd; + buf_peek(buf, header, sizeof(header)); + cmd = ntohs(get_uint16(header+2)); + if (cmd <= 0x14) + return 1; /* This is definitely not a v1 control command. */ + } + return 0; +} + diff --git a/src/or/proto_control0.h b/src/or/proto_control0.h new file mode 100644 index 0000000000..0cc8eacad0 --- /dev/null +++ b/src/or/proto_control0.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CONTROL0_H +#define TOR_PROTO_CONTROL0_H + +struct buf_t; +int peek_buf_has_control0_command(struct buf_t *buf); + +#endif /* !defined(TOR_PROTO_CONTROL0_H) */ + diff --git a/src/or/proto_ext_or.c b/src/or/proto_ext_or.c new file mode 100644 index 0000000000..057cf109ec --- /dev/null +++ b/src/or/proto_ext_or.c @@ -0,0 +1,40 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "ext_orport.h" +#include "proto_ext_or.h" + +/** The size of the header of an Extended ORPort message: 2 bytes for + * COMMAND, 2 bytes for BODYLEN */ +#define EXT_OR_CMD_HEADER_SIZE 4 + +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + + if (buf_datalen(buf) < EXT_OR_CMD_HEADER_SIZE) + return 0; + buf_peek(buf, hdr, sizeof(hdr)); + len = ntohs(get_uint16(hdr+2)); + if (buf_datalen(buf) < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + buf_drain(buf, EXT_OR_CMD_HEADER_SIZE); + buf_get_bytes(buf, (*out)->body, len); + return 1; +} + diff --git a/src/or/proto_ext_or.h b/src/or/proto_ext_or.h new file mode 100644 index 0000000000..cc504d18e3 --- /dev/null +++ b/src/or/proto_ext_or.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_EXT_OR_H +#define TOR_PROTO_EXT_OR_H + +struct buf_t; +struct ext_or_cmt_t; + +int fetch_ext_or_command_from_buf(struct buf_t *buf, + struct ext_or_cmd_t **out); + +#endif /* !defined(TOR_PROTO_EXT_OR_H) */ + diff --git a/src/or/proto_http.c b/src/or/proto_http.c new file mode 100644 index 0000000000..3762429e1e --- /dev/null +++ b/src/or/proto_http.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define PROTO_HTTP_PRIVATE +#include "or.h" +#include "buffers.h" +#include "proto_http.h" + +/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ +int +peek_buf_has_http_command(const buf_t *buf) +{ + if (buf_peek_startswith(buf, "CONNECT ") || + buf_peek_startswith(buf, "DELETE ") || + buf_peek_startswith(buf, "GET ") || + buf_peek_startswith(buf, "POST ") || + buf_peek_startswith(buf, "PUT " )) + return 1; + return 0; +} + +/** There is a (possibly incomplete) http statement on <b>buf</b>, of the + * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) + * If a) the headers include a Content-Length field and all bytes in + * the body are present, or b) there's no Content-Length field and + * all headers are present, then: + * + * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. + * - memdup body into <b>*body_out</b>, and NUL-terminate it. + * - Then remove them from <b>buf</b>, and return 1. + * + * - If headers or body is NULL, discard that part of the buf. + * - If a headers or body doesn't fit in the arg, return -1. + * (We ensure that the headers or body don't exceed max len, + * _even if_ we're planning to discard them.) + * - If force_complete is true, then succeed even if not all of the + * content has arrived. + * + * Else, change nothing and return 0. + */ +int +fetch_from_buf_http(buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + const char *headers; + size_t headerlen, bodylen, contentlen=0; + int crlf_offset; + int r; + + if (buf_datalen(buf) == 0) + return 0; + + crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); + if (crlf_offset > (int)max_headerlen || + (crlf_offset < 0 && buf_datalen(buf) > max_headerlen)) { + log_debug(LD_HTTP,"headers too long."); + return -1; + } else if (crlf_offset < 0) { + log_debug(LD_HTTP,"headers not all here yet."); + return 0; + } + /* Okay, we have a full header. Make sure it all appears in the first + * chunk. */ + headerlen = crlf_offset + 4; + size_t headers_in_chunk = 0; + buf_pullup(buf, headerlen, &headers, &headers_in_chunk); + + bodylen = buf_datalen(buf) - headerlen; + log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); + + if (max_headerlen <= headerlen) { + log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", + (int)headerlen, (int)max_headerlen-1); + return -1; + } + if (max_bodylen <= bodylen) { + log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", + (int)bodylen, (int)max_bodylen-1); + return -1; + } + + r = buf_http_find_content_length(headers, headerlen, &contentlen); + if (r == -1) { + log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " + "someone is trying to crash us."); + return -1; + } else if (r == 1) { + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } else { + tor_assert(r == 0); + /* Leave bodylen alone */ + } + + /* all happy. copy into the appropriate places, and return 1 */ + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + buf_get_bytes(buf, *headers_out, headerlen); + (*headers_out)[headerlen] = 0; /* NUL terminate it */ + } + if (body_out) { + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + buf_get_bytes(buf, *body_out, bodylen); + (*body_out)[bodylen] = 0; /* NUL terminate it */ + } + return 1; +} + +/** + * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at + * <b>headers</b>, looking for a "Content-Length" header. Try to set + * *<b>result_out</b> to the numeric value of that header if possible. + * Return -1 if the header was malformed, 0 if it was missing, and 1 if + * it was present and well-formed. + */ +STATIC int +buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out) +{ + const char *p, *newline; + char *len_str, *eos=NULL; + size_t remaining, result; + int ok; + *result_out = 0; /* The caller shouldn't look at this unless the + * return value is 1, but let's prevent confusion */ + +#define CONTENT_LENGTH "\r\nContent-Length: " + p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); + if (p == NULL) + return 0; + + tor_assert(p >= headers && p < headers+headerlen); + remaining = (headers+headerlen)-p; + p += strlen(CONTENT_LENGTH); + remaining -= strlen(CONTENT_LENGTH); + + newline = memchr(p, '\n', remaining); + if (newline == NULL) + return -1; + + len_str = tor_memdup_nulterm(p, newline-p); + /* We limit the size to INT_MAX because other parts of the buffer.c + * code don't like buffers to be any bigger than that. */ + result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); + if (eos && !tor_strisspace(eos)) { + ok = 0; + } else { + *result_out = result; + } + tor_free(len_str); + + return ok ? 1 : -1; +} + diff --git a/src/or/proto_http.h b/src/or/proto_http.h new file mode 100644 index 0000000000..805686070f --- /dev/null +++ b/src/or/proto_http.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_HTTP_H +#define TOR_PROTO_HTTP_H + +struct buf_t; + +int fetch_from_buf_http(struct buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete); +int peek_buf_has_http_command(const struct buf_t *buf); + +#ifdef PROTO_HTTP_PRIVATE +STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out); +#endif + +#endif /* !defined(TOR_PROTO_HTTP_H) */ + diff --git a/src/or/proto_socks.c b/src/or/proto_socks.c new file mode 100644 index 0000000000..7649fcc4be --- /dev/null +++ b/src/or/proto_socks.c @@ -0,0 +1,710 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "addressmap.h" +#include "buffers.h" +#include "control.h" +#include "config.h" +#include "ext_orport.h" +#include "proto_socks.h" +#include "reasons.h" + +static void socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason); + +static int parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out); +static int parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out); +/** + * Wait this many seconds before warning the user about using SOCKS unsafely + * again. */ +#define SOCKS_WARN_INTERVAL 5 + +/** Warn that the user application has made an unsafe socks request using + * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than + * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ +static void +log_unsafe_socks_warning(int socks_protocol, const char *address, + uint16_t port, int safe_socks) +{ + static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); + + if (safe_socks) { + log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, + "Your application (using socks%d to port %d) is giving " + "Tor only an IP address. Applications that do DNS resolves " + "themselves may leak information. Consider using Socks4A " + "(e.g. via privoxy or socat) instead. For more information, " + "please see https://wiki.torproject.org/TheOnionRouter/" + "TorFAQ#SOCKSAndDNS.%s", + socks_protocol, + (int)port, + safe_socks ? " Rejecting." : ""); + } + control_event_client_status(LOG_WARN, + "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", + socks_protocol, address, (int)port); +} + +/** Do not attempt to parse socks messages longer than this. This value is + * actually significantly higher than the longest possible socks message. */ +#define MAX_SOCKS_MESSAGE_LEN 512 + +/** Return a new socks_request_t. */ +socks_request_t * +socks_request_new(void) +{ + return tor_malloc_zero(sizeof(socks_request_t)); +} + +/** Free all storage held in the socks_request_t <b>req</b>. */ +void +socks_request_free(socks_request_t *req) +{ + if (!req) + return; + if (req->username) { + memwipe(req->username, 0x10, req->usernamelen); + tor_free(req->username); + } + if (req->password) { + memwipe(req->password, 0x04, req->passwordlen); + tor_free(req->password); + } + memwipe(req, 0xCC, sizeof(socks_request_t)); + tor_free(req); +} + +/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one + * of the forms + * - socks4: "socksheader username\\0" + * - socks4a: "socksheader username\\0 destaddr\\0" + * - socks5 phase one: "version #methods methods" + * - socks5 phase two: "version command 0 addresstype..." + * If it's a complete and valid handshake, and destaddr fits in + * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, + * assign to <b>req</b>, and return 1. + * + * If it's invalid or too big, return -1. + * + * Else it's not all there yet, leave buf alone and return 0. + * + * If you want to specify the socks reply, write it into <b>req->reply</b> + * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. + * + * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether + * the connection is possibly leaking DNS requests locally or not. + * + * If <b>safe_socks</b> is true, then reject unsafe socks protocols. + * + * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are + * undefined. + */ +int +fetch_from_buf_socks(buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks) +{ + int res; + ssize_t n_drain; + size_t want_length = 128; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) /* version and another byte */ + return 0; + + do { + n_drain = 0; + buf_pullup(buf, want_length, &head, &datalen); + tor_assert(head && datalen >= 2); + want_length = 0; + + res = parse_socks(head, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + buf_clear(buf); + else if (n_drain > 0) + buf_drain(buf, n_drain); + + } while (res == 0 && head && want_length < buf_datalen(buf) && + buf_datalen(buf) >= 2); + + return res; +} + +/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and + * have Tor send it as error response to <b>req</b>. + */ +static void +socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason) +{ + req->replylen = 10; + memset(req->reply,0,10); + + req->reply[0] = 0x05; // VER field. + req->reply[1] = reason; // REP field. + req->reply[3] = 0x01; // ATYP field. +} + +static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>This is a SOCKS Proxy, Not An HTTP Proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>This is a SOCKs proxy, not an HTTP proxy.</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use this Tor port as\n" + "an HTTP proxy.\n" + "</p><p>\n" + "This is not correct: This port is configured as a SOCKS proxy, not\n" + "an HTTP proxy. If you need an HTTP proxy tunnel, use the HTTPTunnelPort\n" + "configuration option in place of, or in addition to, SOCKSPort.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "</p>\n" + "</body>\n" + "</html>\n"; + +/** Implementation helper to implement fetch_from_*_socks. Instead of looking + * at a buffer's contents, we look at the <b>datalen</b> bytes of data in + * <b>data</b>. Instead of removing data from the buffer, we set + * <b>drain_out</b> to the amount of data that should be removed (or -1 if the + * buffer should be cleared). Instead of pulling more data into the first + * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes + * we'd like to see in the input buffer, if they're available. */ +static int +parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out) +{ + unsigned int len; + char tmpbuf[TOR_ADDR_BUF_LEN+1]; + tor_addr_t destaddr; + uint32_t destip; + uint8_t socksver; + char *next, *startaddr; + unsigned char usernamelen, passlen; + struct in_addr in; + + if (datalen < 2) { + /* We always need at least 2 bytes. */ + *want_length_out = 2; + return 0; + } + + if (req->socks_version == 5 && !req->got_auth) { + /* See if we have received authentication. Strictly speaking, we should + also check whether we actually negotiated username/password + authentication. But some broken clients will send us authentication + even if we negotiated SOCKS_NO_AUTH. */ + if (*data == 1) { /* username/pass version 1 */ + /* Format is: authversion [1 byte] == 1 + usernamelen [1 byte] + username [usernamelen bytes] + passlen [1 byte] + password [passlen bytes] */ + usernamelen = (unsigned char)*(data + 1); + if (datalen < 2u + usernamelen + 1u) { + *want_length_out = 2u + usernamelen + 1u; + return 0; + } + passlen = (unsigned char)*(data + 2u + usernamelen); + if (datalen < 2u + usernamelen + 1u + passlen) { + *want_length_out = 2u + usernamelen + 1u + passlen; + return 0; + } + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 1; /* authversion == 1 */ + req->reply[1] = 0; /* authentication successful */ + log_debug(LD_APP, + "socks5: Accepted username/password without checking."); + if (usernamelen) { + req->username = tor_memdup(data+2u, usernamelen); + req->usernamelen = usernamelen; + } + if (passlen) { + req->password = tor_memdup(data+3u+usernamelen, passlen); + req->passwordlen = passlen; + } + *drain_out = 2u + usernamelen + 1u + passlen; + req->got_auth = 1; + *want_length_out = 7; /* Minimal socks5 command. */ + return 0; + } else if (req->auth_type == SOCKS_USER_PASS) { + /* unknown version byte */ + log_warn(LD_APP, "Socks5 username/password version %d not recognized; " + "rejecting.", (int)*data); + return -1; + } + } + + socksver = *data; + + switch (socksver) { /* which version of socks? */ + case 5: /* socks5 */ + + if (req->socks_version != 5) { /* we need to negotiate a method */ + unsigned char nummethods = (unsigned char)*(data+1); + int have_user_pass, have_no_auth; + int r=0; + tor_assert(!req->socks_version); + if (datalen < 2u+nummethods) { + *want_length_out = 2u+nummethods; + return 0; + } + if (!nummethods) + return -1; + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; /* socks5 reply */ + have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); + have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); + if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { + req->auth_type = SOCKS_USER_PASS; + req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" + auth method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); + r=0; + } else if (have_no_auth) { + req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth + method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); + r=0; + } else { + log_warn(LD_APP, + "socks5: offered methods don't include 'no auth' or " + "username/password. Rejecting."); + req->reply[1] = '\xFF'; /* reject all methods */ + r=-1; + } + /* Remove packet from buf. Some SOCKS clients will have sent extra + * junk at this point; let's hope it's an authentication message. */ + *drain_out = 2u + nummethods; + + return r; + } + if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { + log_warn(LD_APP, + "socks5: negotiated authentication, but none provided"); + return -1; + } + /* we know the method; read in the request */ + log_debug(LD_APP,"socks5: checking request"); + if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ + *want_length_out = 7; + return 0; /* not yet */ + } + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE && + req->command != SOCKS_COMMAND_RESOLVE_PTR) { + /* not a connect or resolve or a resolve_ptr? we don't support it. */ + socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); + + log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", + req->command); + return -1; + } + switch (*(data+3)) { /* address type */ + case 1: /* IPv4 address */ + case 4: /* IPv6 address */ { + const int is_v6 = *(data+3) == 4; + const unsigned addrlen = is_v6 ? 16 : 4; + log_debug(LD_APP,"socks5: ipv4 address type"); + if (datalen < 6+addrlen) {/* ip/port there? */ + *want_length_out = 6+addrlen; + return 0; /* not yet */ + } + + if (is_v6) + tor_addr_from_ipv6_bytes(&destaddr, data+4); + else + tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); + + tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); + + if (BUG(strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- This branch is unreachable, given the + * size of tmpbuf and the actual value of MAX_SOCKS_ADDR_LEN */ + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_APP, + "socks5 IP takes %d bytes, which doesn't fit in %d. " + "Rejecting.", + (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); + return -1; + /* LCOV_EXCL_STOP */ + } + strlcpy(req->address,tmpbuf,sizeof(req->address)); + req->port = ntohs(get_uint16(data+4+addrlen)); + *drain_out = 6+addrlen; + if (req->command != SOCKS_COMMAND_RESOLVE_PTR && + !addressmap_have_mapping(req->address,0)) { + log_unsafe_socks_warning(5, req->address, req->port, safe_socks); + if (safe_socks) { + socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); + return -1; + } + } + return 1; + } + case 3: /* fqdn */ + log_debug(LD_APP,"socks5: fqdn address type"); + if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); + log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " + "hostname type. Rejecting."); + return -1; + } + len = (unsigned char)*(data+4); + if (datalen < 7+len) { /* addr/port there? */ + *want_length_out = 7+len; + return 0; /* not yet */ + } + if (BUG(len+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- unreachable, since len is at most 255, + * and MAX_SOCKS_ADDR_LEN is 256. */ + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_APP, + "socks5 hostname is %d bytes, which doesn't fit in " + "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); + return -1; + /* LCOV_EXCL_STOP */ + } + memcpy(req->address,data+5,len); + req->address[len] = 0; + req->port = ntohs(get_uint16(data+5+len)); + *drain_out = 5+len+2; + + if (!string_is_valid_hostname(req->address)) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + + log_warn(LD_PROTOCOL, + "Your application (using socks5 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks5 to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + return 1; + default: /* unsupported */ + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); + log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", + (int) *(data+3)); + return -1; + } + tor_assert(0); + break; + case 4: { /* socks4 */ + enum {socks4, socks4a} socks4_prot = socks4a; + const char *authstart, *authend; + /* http://ss5.sourceforge.net/socks4.protocol.txt */ + /* http://ss5.sourceforge.net/socks4A.protocol.txt */ + + req->socks_version = 4; + if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ + *want_length_out = SOCKS4_NETWORK_LEN; + return 0; /* not yet */ + } + // buf_pullup(buf, 1280); + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE) { + /* not a connect or resolve? we don't support it. (No resolve_ptr with + * socks4.) */ + log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", + req->command); + return -1; + } + + req->port = ntohs(get_uint16(data+2)); + destip = ntohl(get_uint32(data+4)); + if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { + log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); + return -1; + } + if (destip >> 8) { + log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); + in.s_addr = htonl(destip); + tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + if (BUG(strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN)) { + /* LCOV_EXCL_START -- This branch is unreachable, given the + * size of tmpbuf and the actual value of MAX_SOCKS_ADDR_LEN */ + log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", + (int)strlen(tmpbuf)); + return -1; + /* LCOV_EXCL_STOP */ + } + log_debug(LD_APP, + "socks4: successfully read destip (%s)", + safe_str_client(tmpbuf)); + socks4_prot = socks4; + } + + authstart = data + SOCKS4_NETWORK_LEN; + next = memchr(authstart, 0, + datalen-SOCKS4_NETWORK_LEN); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP, "Socks4 user name too long; rejecting."); + return -1; + } + log_debug(LD_APP,"socks4: Username not here yet."); + *want_length_out = datalen+1024; /* More than we need, but safe */ + return 0; + } + authend = next; + tor_assert(next < data+datalen); + + startaddr = NULL; + if (socks4_prot != socks4a && + !addressmap_have_mapping(tmpbuf,0)) { + log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); + + if (safe_socks) + return -1; + } + if (socks4_prot == socks4a) { + if (next+1 == data+datalen) { + log_debug(LD_APP,"socks4: No part of destaddr here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + startaddr = next+1; + next = memchr(startaddr, 0, data + datalen - startaddr); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP,"socks4: Destaddr too long."); + return -1; + } + log_debug(LD_APP,"socks4: Destaddr not all here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { + log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); + return -1; + } + // tor_assert(next < buf->cur+buf_datalen(buf)); + + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks4a to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + } + log_debug(LD_APP,"socks4: Everything is here. Success."); + strlcpy(req->address, startaddr ? startaddr : tmpbuf, + sizeof(req->address)); + if (!string_is_valid_hostname(req->address)) { + log_warn(LD_PROTOCOL, + "Your application (using socks4 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (authend != authstart) { + req->got_auth = 1; + req->usernamelen = authend - authstart; + req->username = tor_memdup(authstart, authend - authstart); + } + /* next points to the final \0 on inbuf */ + *drain_out = next - data + 1; + return 1; + } + case 'G': /* get */ + case 'H': /* head */ + case 'P': /* put/post */ + case 'C': /* connect */ + strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG, + MAX_SOCKS_REPLY_LEN); + req->replylen = strlen((char*)req->reply)+1; + /* fall through */ + default: /* version is not socks4 or socks5 */ + log_warn(LD_APP, + "Socks version %d not recognized. (This port is not an " + "HTTP proxy; did you want to use HTTPTunnelPort?)", + *(data)); + { + /* Tell the controller the first 8 bytes. */ + char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); + control_event_client_status(LOG_WARN, + "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", + escaped(tmp)); + tor_free(tmp); + } + return -1; + } +} + +/** Inspect a reply from SOCKS server stored in <b>buf</b> according + * to <b>state</b>, removing the protocol data upon success. Return 0 on + * incomplete response, 1 on success and -1 on error, in which case + * <b>reason</b> is set to a descriptive message (free() when finished + * with it). + * + * As a special case, 2 is returned when user/pass is required + * during SOCKS5 handshake and user/pass is configured. + */ +int +fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) +{ + ssize_t drain = 0; + int r; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) + return 0; + + buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, &head, &datalen); + tor_assert(head && datalen >= 2); + + r = parse_socks_client((uint8_t*)head, datalen, + state, reason, &drain); + if (drain > 0) + buf_drain(buf, drain); + else if (drain < 0) + buf_clear(buf); + + return r; +} + +/** Implementation logic for fetch_from_*_socks_client. */ +static int +parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out) +{ + unsigned int addrlen; + *drain_out = 0; + if (datalen < 2) + return 0; + + switch (state) { + case PROXY_SOCKS4_WANT_CONNECT_OK: + /* Wait for the complete response */ + if (datalen < 8) + return 0; + + if (data[1] != 0x5a) { + *reason = tor_strdup(socks4_response_code_to_string(data[1])); + return -1; + } + + /* Success */ + *drain_out = 8; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: + /* we don't have any credentials */ + if (data[1] != 0x00) { + *reason = tor_strdup("server doesn't support any of our " + "available authentication methods"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: + /* we have a username and password. return 1 if we can proceed without + * providing authentication, or 2 otherwise. */ + switch (data[1]) { + case 0x00: + log_info(LD_NET, "SOCKS 5 client: we have auth details but server " + "doesn't require authentication."); + *drain_out = -1; + return 1; + case 0x02: + log_info(LD_NET, "SOCKS 5 client: need authentication."); + *drain_out = -1; + return 2; + /* fall through */ + } + + *reason = tor_strdup("server doesn't support any of our available " + "authentication methods"); + return -1; + + case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: + /* handle server reply to rfc1929 authentication */ + if (data[1] != 0x00) { + *reason = tor_strdup("authentication failed"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: authentication successful."); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_CONNECT_OK: + /* response is variable length. BND.ADDR, etc, isn't needed + * (don't bother with buf_pullup()), but make sure to eat all + * the data used */ + + /* wait for address type field to arrive */ + if (datalen < 4) + return 0; + + switch (data[3]) { + case 0x01: /* ip4 */ + addrlen = 4; + break; + case 0x04: /* ip6 */ + addrlen = 16; + break; + case 0x03: /* fqdn (can this happen here?) */ + if (datalen < 5) + return 0; + addrlen = 1 + data[4]; + break; + default: + *reason = tor_strdup("invalid response to connect request"); + return -1; + } + + /* wait for address and port */ + if (datalen < 6 + addrlen) + return 0; + + if (data[1] != 0x00) { + *reason = tor_strdup(socks5_response_code_to_string(data[1])); + return -1; + } + + *drain_out = 6 + addrlen; + return 1; + } + + /* LCOV_EXCL_START */ + /* shouldn't get here if the input state is one we know about... */ + tor_assert(0); + + return -1; + /* LCOV_EXCL_STOP */ +} + diff --git a/src/or/proto_socks.h b/src/or/proto_socks.h new file mode 100644 index 0000000000..a714151414 --- /dev/null +++ b/src/or/proto_socks.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_SOCKS_H +#define TOR_PROTO_SOCKS_H + +struct socks_request_t; +struct buf_t; + +struct socks_request_t *socks_request_new(void); +void socks_request_free(struct socks_request_t *req); +int fetch_from_buf_socks(struct buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks); +int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); + +#endif /* !defined(TOR_PROTO_SOCKS_H) */ + diff --git a/src/or/protover.h b/src/or/protover.h index 22667bed79..657977279e 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -17,6 +17,13 @@ /* This is a guess. */ #define FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS "0.2.9.3-alpha" +/** The protover version number that signifies HSDir support for HSv3 */ +#define PROTOVER_HSDIR_V3 2 +/** The protover version number that signifies HSv3 intro point support */ +#define PROTOVER_HS_INTRO_V3 4 +/** The protover version number that signifies HSv3 rendezvous point support */ +#define PROTOVER_HS_RENDEZVOUS_POINT_V3 2 + /** List of recognized subprotocols. */ typedef enum protocol_type_t { PRT_LINK, @@ -68,7 +75,7 @@ STATIC void proto_entry_free(proto_entry_t *entry); STATIC char *encode_protocol_list(const smartlist_t *sl); STATIC const char *protocol_type_to_str(protocol_type_t pr); STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out); -#endif +#endif /* defined(PROTOVER_PRIVATE) */ -#endif +#endif /* !defined(TOR_PROTOVER_H) */ diff --git a/src/or/reasons.c b/src/or/reasons.c index e6c325f1b3..03d49418da 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -45,6 +45,8 @@ stream_end_reason_to_control_string(int reason) case END_STREAM_REASON_CANT_ATTACH: return "CANT_ATTACH"; case END_STREAM_REASON_NET_UNREACHABLE: return "NET_UNREACHABLE"; case END_STREAM_REASON_SOCKSPROTOCOL: return "SOCKS_PROTOCOL"; + // XXXX Controlspec + case END_STREAM_REASON_HTTPPROTOCOL: return "HTTP_PROTOCOL"; case END_STREAM_REASON_PRIVATE_ADDR: return "PRIVATE_ADDR"; @@ -138,6 +140,11 @@ stream_end_reason_to_socks5_response(int reason) return SOCKS5_NET_UNREACHABLE; case END_STREAM_REASON_SOCKSPROTOCOL: return SOCKS5_GENERAL_ERROR; + case END_STREAM_REASON_HTTPPROTOCOL: + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + return SOCKS5_GENERAL_ERROR; + // LCOV_EXCL_STOP case END_STREAM_REASON_PRIVATE_ADDR: return SOCKS5_GENERAL_ERROR; @@ -160,7 +167,7 @@ stream_end_reason_to_socks5_response(int reason) #else #define E_CASE(s) case s #define S_CASE(s) case s -#endif +#endif /* defined(_WIN32) */ /** Given an errno from a failed exit connection, return a reason code * appropriate for use in a RELAY END cell. */ @@ -442,3 +449,48 @@ bandwidth_weight_rule_to_string(bandwidth_weight_rule_t rule) } } +/** Given a RELAY_END reason value, convert it to an HTTP response to be + * send over an HTTP tunnel connection. */ +const char * +end_reason_to_http_connect_response_line(int endreason) +{ + endreason &= END_STREAM_REASON_MASK; + /* XXXX these are probably all wrong. Should they all be 502? */ + switch (endreason) { + case 0: + return "HTTP/1.0 200 OK\r\n\r\n"; + case END_STREAM_REASON_MISC: + return "HTTP/1.0 500 Internal Server Error\r\n\r\n"; + case END_STREAM_REASON_RESOLVEFAILED: + return "HTTP/1.0 404 Not Found (resolve failed)\r\n\r\n"; + case END_STREAM_REASON_NOROUTE: + return "HTTP/1.0 404 Not Found (no route)\r\n\r\n"; + case END_STREAM_REASON_CONNECTREFUSED: + return "HTTP/1.0 403 Forbidden (connection refused)\r\n\r\n"; + case END_STREAM_REASON_EXITPOLICY: + return "HTTP/1.0 403 Forbidden (exit policy)\r\n\r\n"; + case END_STREAM_REASON_DESTROY: + return "HTTP/1.0 502 Bad Gateway (destroy cell received)\r\n\r\n"; + case END_STREAM_REASON_DONE: + return "HTTP/1.0 502 Bad Gateway (unexpected close)\r\n\r\n"; + case END_STREAM_REASON_TIMEOUT: + return "HTTP/1.0 504 Gateway Timeout\r\n\r\n"; + case END_STREAM_REASON_HIBERNATING: + return "HTTP/1.0 502 Bad Gateway (hibernating server)\r\n\r\n"; + case END_STREAM_REASON_INTERNAL: + return "HTTP/1.0 502 Bad Gateway (internal error)\r\n\r\n"; + case END_STREAM_REASON_RESOURCELIMIT: + return "HTTP/1.0 502 Bad Gateway (resource limit)\r\n\r\n"; + case END_STREAM_REASON_CONNRESET: + return "HTTP/1.0 403 Forbidden (connection reset)\r\n\r\n"; + case END_STREAM_REASON_TORPROTOCOL: + return "HTTP/1.0 502 Bad Gateway (tor protocol violation)\r\n\r\n"; + case END_STREAM_REASON_ENTRYPOLICY: + return "HTTP/1.0 403 Forbidden (entry policy violation)\r\n\r\n"; + case END_STREAM_REASON_NOTDIRECTORY: /* Fall Through */ + default: + tor_assert_nonfatal_unreached(); + return "HTTP/1.0 500 Internal Server Error (weird end reason)\r\n\r\n"; + } +} + diff --git a/src/or/reasons.h b/src/or/reasons.h index 1cadf4e89e..3d6ba8fc83 100644 --- a/src/or/reasons.h +++ b/src/or/reasons.h @@ -26,6 +26,7 @@ const char *socks4_response_code_to_string(uint8_t code); const char *socks5_response_code_to_string(uint8_t code); const char *bandwidth_weight_rule_to_string(enum bandwidth_weight_rule_t rule); +const char *end_reason_to_http_connect_response_line(int endreason); -#endif +#endif /* !defined(TOR_REASONS_H) */ diff --git a/src/or/relay.c b/src/or/relay.c index a86ae9bc43..66e10567c1 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -185,18 +185,12 @@ relay_digest_matches(crypto_digest_t *digest, cell_t *cell) /** Apply <b>cipher</b> to CELL_PAYLOAD_SIZE bytes of <b>in</b> * (in place). * - * If <b>encrypt_mode</b> is 1 then encrypt, else decrypt. - * - * Returns 0. + * Note that we use the same operation for encrypting and for decrypting. */ -static int -relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in, - int encrypt_mode) +static void +relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in) { - (void)encrypt_mode; crypto_cipher_crypt_inplace(cipher, (char*) in, CELL_PAYLOAD_SIZE); - - return 0; } /** @@ -450,8 +444,8 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, do { /* Remember: cpath is in forward order, that is, first hop first. */ tor_assert(thishop); - if (relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0) - return -1; + /* decrypt one layer */ + relay_crypt_one_payload(thishop->b_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -468,19 +462,14 @@ relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, log_fn(LOG_PROTOCOL_WARN, LD_OR, "Incoming cell at client not recognized. Closing."); return -1; - } else { /* we're in the middle. Just one crypt. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, - cell->payload, 1) < 0) - return -1; -// log_fn(LOG_DEBUG,"Skipping recognized check, because we're not " -// "the client."); + } else { + /* We're in the middle. Encrypt one layer. */ + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->p_crypto, cell->payload); } } else /* cell_direction == CELL_DIRECTION_OUT */ { - /* we're in the middle. Just one crypt. */ + /* We're in the middle. Decrypt one layer. */ - if (relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, - cell->payload, 0) < 0) - return -1; + relay_crypt_one_payload(TO_OR_CIRCUIT(circ)->n_crypto, cell->payload); relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { @@ -516,7 +505,15 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, chan = circ->n_chan; if (!chan) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d has n_chan==NULL." - " Dropping.", filename, lineno); + " Dropping. Circuit is in state %s (%d), and is " + "%smarked for close. (%s:%d, %d)", filename, lineno, + circuit_state_to_string(circ->state), circ->state, + circ->marked_for_close ? "" : "not ", + circ->marked_for_close_file?circ->marked_for_close_file:"", + circ->marked_for_close, circ->marked_for_close_reason); + if (CIRCUIT_IS_ORIGIN(circ)) { + circuit_log_path(LOG_WARN, LD_BUG, TO_ORIGIN_CIRCUIT(circ)); + } log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } @@ -533,11 +530,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, /* moving from farthest to nearest hop */ do { tor_assert(thishop); - /* XXXX RD This is a bug, right? */ - log_debug(LD_OR,"crypting a layer of the relay cell."); - if (relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) { - return -1; - } + log_debug(LD_OR,"encrypting a layer of the relay cell."); + relay_crypt_one_payload(thishop->f_crypto, cell->payload); thishop = thishop->prev; } while (thishop != TO_ORIGIN_CIRCUIT(circ)->cpath->prev); @@ -554,8 +548,8 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, or_circ = TO_OR_CIRCUIT(circ); chan = or_circ->p_chan; relay_set_digest(or_circ->p_digest, cell); - if (relay_crypt_one_payload(or_circ->p_crypto, cell->payload, 1) < 0) - return -1; + /* encrypt one layer */ + relay_crypt_one_payload(or_circ->p_crypto, cell->payload); } ++stats_n_relay_cells_relayed; @@ -843,7 +837,7 @@ connection_edge_send_command(edge_connection_t *fromconn, if (linked_conn && linked_conn->type == CONN_TYPE_DIR) { ++(TO_DIR_CONN(linked_conn)->data_cells_sent); } -#endif +#endif /* defined(MEASUREMENTS_21206) */ return relay_send_command_from_edge(fromconn->stream_id, circ, relay_command, payload, @@ -1495,8 +1489,9 @@ connection_edge_process_relay_cell_not_open( circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); /* don't send a socks reply to transparent conns */ tor_assert(entry_conn->socks_request != NULL); - if (!entry_conn->socks_request->has_finished) + if (!entry_conn->socks_request->has_finished) { connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0); + } /* Was it a linked dir conn? If so, a dir request just started to * fetch something; this could be a bootstrap status milestone. */ @@ -1698,7 +1693,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } stats_n_data_bytes_received += rh.length; - connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), + connection_buf_add((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); #ifdef MEASUREMENTS_21206 @@ -1709,7 +1704,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (linked_conn && linked_conn->type == CONN_TYPE_DIR) { ++(TO_DIR_CONN(linked_conn)->data_cells_received); } -#endif +#endif /* defined(MEASUREMENTS_21206) */ if (!optimistic_data) { /* Only send a SENDME if we're not getting optimistic data; otherwise @@ -2066,13 +2061,13 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, /* XXXX We could be more efficient here by sometimes packing * previously-sent optimistic data in the same cell with data * from the inbuf. */ - fetch_from_buf(payload, length, entry_conn->sending_optimistic_data); + buf_get_bytes(entry_conn->sending_optimistic_data, payload, length); if (!buf_datalen(entry_conn->sending_optimistic_data)) { buf_free(entry_conn->sending_optimistic_data); entry_conn->sending_optimistic_data = NULL; } } else { - connection_fetch_from_buf(payload, length, TO_CONN(conn)); + connection_buf_get_bytes(payload, length, TO_CONN(conn)); } log_debug(domain,TOR_SOCKET_T_FORMAT": Packaging %d bytes (%d waiting).", @@ -2084,7 +2079,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, retry */ if (!entry_conn->pending_optimistic_data) entry_conn->pending_optimistic_data = buf_new(); - write_to_buf(payload, length, entry_conn->pending_optimistic_data); + buf_add(entry_conn->pending_optimistic_data, payload, length); } if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, @@ -2407,7 +2402,7 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) assert_circuit_mux_okay(chan) #else #define assert_cmux_ok_paranoid(chan) -#endif +#endif /* defined(ACTIVE_CIRCUITS_PARANOIA) */ /** The total number of cells we have allocated. */ static size_t total_cells_allocated = 0; @@ -2950,7 +2945,7 @@ get_max_middle_cells(void) { return ORCIRC_MAX_MIDDLE_CELLS; } -#endif +#endif /* 0 */ /** Add <b>cell</b> to the queue of <b>circ</b> writing to <b>chan</b> * transmitting in <b>direction</b>. */ @@ -3060,7 +3055,7 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, } } } -#endif +#endif /* 0 */ cell_queue_append_packed_copy(circ, queue, exitward, cell, chan->wide_circ_ids, 1); diff --git a/src/or/relay.h b/src/or/relay.h index 9dc0b5d3a2..4cc1a0fbdb 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -112,7 +112,7 @@ STATIC packed_cell_t *cell_queue_pop(cell_queue_t *queue); STATIC destroy_cell_t *destroy_cell_queue_pop(destroy_cell_queue_t *queue); STATIC size_t cell_queues_get_total_allocation(void); STATIC int cell_queues_check_size(void); -#endif +#endif /* defined(RELAY_PRIVATE) */ -#endif +#endif /* !defined(TOR_RELAY_H) */ diff --git a/src/or/rendcache.c b/src/or/rendcache.c index 11b60b36a1..b98b2bccfa 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -303,7 +303,7 @@ void rend_cache_purge(void) { if (rend_cache) { - log_info(LD_REND, "Purging HS descriptor cache"); + log_info(LD_REND, "Purging HS v2 descriptor cache"); strmap_free(rend_cache, rend_cache_entry_free_); } rend_cache = strmap_new(); @@ -315,7 +315,7 @@ void rend_cache_failure_purge(void) { if (rend_cache_failure) { - log_info(LD_REND, "Purging HS failure cache"); + log_info(LD_REND, "Purging HS v2 failure cache"); strmap_free(rend_cache_failure, rend_cache_failure_entry_free_); } rend_cache_failure = strmap_new(); @@ -512,7 +512,7 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) tor_assert(rend_cache); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } @@ -558,7 +558,7 @@ rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e) tor_assert(rend_cache_local_service); tor_assert(query); - if (!rend_valid_service_id(query)) { + if (!rend_valid_v2_service_id(query)) { ret = -EINVAL; goto end; } diff --git a/src/or/rendcache.h b/src/or/rendcache.h index 1bd3be2243..5b13eadfa1 100644 --- a/src/or/rendcache.h +++ b/src/or/rendcache.h @@ -115,8 +115,8 @@ extern strmap_t *rend_cache; extern strmap_t *rend_cache_failure; extern digestmap_t *rend_cache_v2_dir; extern size_t rend_cache_total_allocation; -#endif -#endif +#endif /* defined(TOR_UNIT_TESTS) */ +#endif /* defined(RENDCACHE_PRIVATE) */ -#endif /* TOR_RENDCACHE_H */ +#endif /* !defined(TOR_RENDCACHE_H) */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 9e2daf0380..3274819241 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -17,6 +17,8 @@ #include "connection_edge.h" #include "directory.h" #include "hs_common.h" +#include "hs_circuit.h" +#include "hs_client.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -41,7 +43,7 @@ rend_client_purge_state(void) rend_cache_purge(); rend_cache_failure_purge(); rend_client_cancel_descriptor_fetches(); - rend_client_purge_last_hid_serv_requests(); + hs_purge_last_hid_serv_requests(); } /** Called when we've established a circuit to an introduction point: @@ -88,46 +90,6 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) return 0; } -/** Extend the introduction circuit <b>circ</b> to another valid - * introduction point for the hidden service it is trying to connect - * to, or mark it and launch a new circuit if we can't extend it. - * Return 0 on success or possible success. Return -1 and mark the - * introduction circuit for close on permanent failure. - * - * On failure, the caller is responsible for marking the associated - * rendezvous circuit for close. */ -static int -rend_client_reextend_intro_circuit(origin_circuit_t *circ) -{ - extend_info_t *extend_info; - int result; - extend_info = rend_client_get_random_intro(circ->rend_data); - if (!extend_info) { - log_warn(LD_REND, - "No usable introduction points left for %s. Closing.", - safe_str_client(rend_data_get_address(circ->rend_data))); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); - return -1; - } - // XXX: should we not re-extend if hs_circ_has_timed_out? - if (circ->remaining_relay_early_cells) { - log_info(LD_REND, - "Re-extending circ %u, this time to %s.", - (unsigned)circ->base_.n_circ_id, - safe_str_client(extend_info_describe(extend_info))); - result = circuit_extend_to_new_exit(circ, extend_info); - } else { - log_info(LD_REND, - "Closing intro circ %u (out of RELAY_EARLY cells).", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); - /* connection_ap_handshake_attach_circuit will launch a new intro circ. */ - result = 0; - } - extend_info_free(extend_info); - return result; -} - /** Called when we're trying to connect an ap conn; sends an INTRODUCE1 cell * down introcirc if possible. */ @@ -201,7 +163,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, introcirc->build_state->chosen_exit)), smartlist_len(entry->parsed->intro_nodes)); - if (rend_client_reextend_intro_circuit(introcirc)) { + if (hs_client_reextend_intro_circuit(introcirc)) { status = -2; goto perm_err; } else { @@ -290,10 +252,9 @@ rend_client_send_introduction(origin_circuit_t *introcirc, goto perm_err; } - note_crypto_pk_op(REND_CLIENT); - /*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg, + /*XXX maybe give crypto_pk_obsolete_public_hybrid_encrypt a max_len arg, * to avoid buffer overflows? */ - r = crypto_pk_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN, + r = crypto_pk_obsolete_public_hybrid_encrypt(intro_key, payload+DIGEST_LEN, sizeof(payload)-DIGEST_LEN, tmp, (int)(dh_offset+DH_KEY_LEN), @@ -396,23 +357,11 @@ rend_client_introduction_acked(origin_circuit_t *circ, origin_circuit_t *rendcirc; (void) request; // XXXX Use this. - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { - log_warn(LD_PROTOCOL, - "Received REND_INTRODUCE_ACK on unexpected circuit %u.", - (unsigned)circ->base_.n_circ_id); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - tor_assert(circ->build_state); tor_assert(circ->build_state->chosen_exit); assert_circ_anonymity_ok(circ, options); tor_assert(circ->rend_data); - /* For path bias: This circuit was used successfully. Valid - * nacks and acks count. */ - pathbias_mark_use_success(circ); - if (request_len == 0) { /* It's an ACK; the introduction point relayed our introduction request. */ /* Locate the rend circ which is waiting to hear about this ack, @@ -454,7 +403,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, INTRO_POINT_FAILURE_GENERIC)>0) { /* There are introduction points left. Re-extend the circuit to * another intro point and try again. */ - int result = rend_client_reextend_intro_circuit(circ); + int result = hs_client_reextend_intro_circuit(circ); /* XXXX If that call failed, should we close the rend circuit, * too? */ return result; @@ -470,230 +419,6 @@ rend_client_introduction_acked(origin_circuit_t *circ, return 0; } -/** The period for which a hidden service directory cannot be queried for - * the same descriptor ID again. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD (15 * 60) -/** Test networks generate a new consensus every 5 or 10 seconds. - * So allow them to requery HSDirs much faster. */ -#define REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING (5) - -/** Return the period for which a hidden service directory cannot be queried - * for the same descriptor ID again, taking TestingTorNetwork into account. */ -static time_t -hsdir_requery_period(const or_options_t *options) -{ - tor_assert(options); - - if (options->TestingTorNetwork) { - return REND_HID_SERV_DIR_REQUERY_PERIOD_TESTING; - } else { - return REND_HID_SERV_DIR_REQUERY_PERIOD; - } -} - -/** Contains the last request times to hidden service directories for - * certain queries; each key is a string consisting of the - * concatenation of a base32-encoded HS directory identity digest and - * base32-encoded HS descriptor ID; each value is a pointer to a time_t - * holding the time of the last request for that descriptor ID to that - * HS directory. */ -static strmap_t *last_hid_serv_requests_ = NULL; - -/** Returns last_hid_serv_requests_, initializing it to a new strmap if - * necessary. */ -static strmap_t * -get_last_hid_serv_requests(void) -{ - if (!last_hid_serv_requests_) - last_hid_serv_requests_ = strmap_new(); - return last_hid_serv_requests_; -} - -#define LAST_HID_SERV_REQUEST_KEY_LEN (REND_DESC_ID_V2_LEN_BASE32 + \ - REND_DESC_ID_V2_LEN_BASE32) - -/** Look up the last request time to hidden service directory <b>hs_dir</b> - * for descriptor ID <b>desc_id_base32</b>. If <b>set</b> is non-zero, - * assign the current time <b>now</b> and return that. Otherwise, return the - * most recent request time, or 0 if no such request has been sent before. - */ -static time_t -lookup_last_hid_serv_request(routerstatus_t *hs_dir, - const char *desc_id_base32, - time_t now, int set) -{ - char hsdir_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - char hsdir_desc_comb_id[LAST_HID_SERV_REQUEST_KEY_LEN + 1]; - time_t *last_request_ptr; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - base32_encode(hsdir_id_base32, sizeof(hsdir_id_base32), - hs_dir->identity_digest, DIGEST_LEN); - tor_snprintf(hsdir_desc_comb_id, sizeof(hsdir_desc_comb_id), "%s%s", - hsdir_id_base32, - desc_id_base32); - /* XXX++?? tor_assert(strlen(hsdir_desc_comb_id) == - LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (set) { - time_t *oldptr; - last_request_ptr = tor_malloc_zero(sizeof(time_t)); - *last_request_ptr = now; - oldptr = strmap_set(last_hid_serv_requests, hsdir_desc_comb_id, - last_request_ptr); - tor_free(oldptr); - } else - last_request_ptr = strmap_get_lc(last_hid_serv_requests, - hsdir_desc_comb_id); - return (last_request_ptr) ? *last_request_ptr : 0; -} - -/** Clean the history of request times to hidden service directories, so that - * it does not contain requests older than REND_HID_SERV_DIR_REQUERY_PERIOD - * seconds any more. */ -static void -directory_clean_last_hid_serv_requests(time_t now) -{ - strmap_iter_t *iter; - time_t cutoff = now - hsdir_requery_period(get_options()); - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - time_t *ent; - strmap_iter_get(iter, &key, &val); - ent = (time_t *) val; - if (*ent < cutoff) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(ent); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Remove all requests related to the descriptor ID <b>desc_id</b> from the - * history of times of requests to hidden service directories. - * <b>desc_id</b> is an unencoded descriptor ID of size DIGEST_LEN. - * - * This is called from rend_client_note_connection_attempt_ended(), which - * must be idempotent, so any future changes to this function must leave it - * idempotent too. */ -static void -purge_hid_serv_from_last_hid_serv_requests(const char *desc_id) -{ - strmap_iter_t *iter; - strmap_t *last_hid_serv_requests = get_last_hid_serv_requests(); - char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - - /* Key is stored with the base32 encoded desc_id. */ - base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, - DIGEST_LEN); - for (iter = strmap_iter_init(last_hid_serv_requests); - !strmap_iter_done(iter); ) { - const char *key; - void *val; - strmap_iter_get(iter, &key, &val); - /* XXX++?? tor_assert(strlen(key) == LAST_HID_SERV_REQUEST_KEY_LEN); */ - if (tor_memeq(key + LAST_HID_SERV_REQUEST_KEY_LEN - - REND_DESC_ID_V2_LEN_BASE32, - desc_id_base32, - REND_DESC_ID_V2_LEN_BASE32)) { - iter = strmap_iter_next_rmv(last_hid_serv_requests, iter); - tor_free(val); - } else { - iter = strmap_iter_next(last_hid_serv_requests, iter); - } - } -} - -/** Purge the history of request times to hidden service directories, - * so that future lookups of an HS descriptor will not fail because we - * accessed all of the HSDir relays responsible for the descriptor - * recently. */ -void -rend_client_purge_last_hid_serv_requests(void) -{ - /* Don't create the table if it doesn't exist yet (and it may very - * well not exist if the user hasn't accessed any HSes)... */ - strmap_t *old_last_hid_serv_requests = last_hid_serv_requests_; - /* ... and let get_last_hid_serv_requests re-create it for us if - * necessary. */ - last_hid_serv_requests_ = NULL; - - if (old_last_hid_serv_requests != NULL) { - log_info(LD_REND, "Purging client last-HS-desc-request-time table"); - strmap_free(old_last_hid_serv_requests, tor_free_); - } -} - -/** This returns a good valid hs dir that should be used for the given - * descriptor id. - * - * Return NULL on error else the hsdir node pointer. */ -static routerstatus_t * -pick_hsdir(const char *desc_id, const char *desc_id_base32) -{ - smartlist_t *responsible_dirs = smartlist_new(); - smartlist_t *usable_responsible_dirs = smartlist_new(); - const or_options_t *options = get_options(); - routerstatus_t *hs_dir; - time_t now = time(NULL); - int excluded_some; - - tor_assert(desc_id); - tor_assert(desc_id_base32); - - /* Determine responsible dirs. Even if we can't get all we want, work with - * the ones we have. If it's empty, we'll notice below. */ - hid_serv_get_responsible_directories(responsible_dirs, desc_id); - - /* Clean request history first. */ - directory_clean_last_hid_serv_requests(now); - - /* Only select those hidden service directories to which we did not send a - * request recently and for which we have a router descriptor here. */ - SMARTLIST_FOREACH_BEGIN(responsible_dirs, routerstatus_t *, dir) { - time_t last = lookup_last_hid_serv_request(dir, desc_id_base32, - 0, 0); - const node_t *node = node_get_by_id(dir->identity_digest); - if (last + hsdir_requery_period(options) >= now || - !node || !node_has_descriptor(node)) { - SMARTLIST_DEL_CURRENT(responsible_dirs, dir); - continue; - } - if (!routerset_contains_node(options->ExcludeNodes, node)) { - smartlist_add(usable_responsible_dirs, dir); - } - } SMARTLIST_FOREACH_END(dir); - - excluded_some = - smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); - - hs_dir = smartlist_choose(usable_responsible_dirs); - if (!hs_dir && !options->StrictNodes) { - hs_dir = smartlist_choose(responsible_dirs); - } - - smartlist_free(responsible_dirs); - smartlist_free(usable_responsible_dirs); - if (!hs_dir) { - log_info(LD_REND, "Could not pick one of the responsible hidden " - "service directories, because we requested them all " - "recently without success."); - if (options->StrictNodes && excluded_some) { - log_warn(LD_REND, "Could not pick a hidden service directory for the " - "requested hidden service: they are all either down or " - "excluded, and StrictNodes is set."); - } - } else { - /* Remember that we are requesting a descriptor from this hidden service - * directory now. */ - lookup_last_hid_serv_request(hs_dir, desc_id_base32, now, 1); - } - - return hs_dir; -} - /** Determine the responsible hidden service directories for <b>desc_id</b> * and fetch the descriptor with that ID from one of them. Only * send a request to a hidden service directory that we have not yet tried @@ -715,7 +440,7 @@ directory_get_from_hs_dir(const char *desc_id, const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS; #else const int how_to_fetch = DIRIND_ANONYMOUS; -#endif +#endif /* defined(ENABLE_TOR2WEB_MODE) */ tor_assert(desc_id); tor_assert(rend_query); @@ -726,7 +451,12 @@ directory_get_from_hs_dir(const char *desc_id, /* Automatically pick an hs dir if none given. */ if (!rs_hsdir) { - hs_dir = pick_hsdir(desc_id, desc_id_base32); + /* Determine responsible dirs. Even if we can't get all we want, work with + * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */ + smartlist_t *responsible_dirs = smartlist_new(); + hid_serv_get_responsible_directories(responsible_dirs, desc_id); + + hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ control_event_hs_descriptor_failed(rend_query, NULL, "QUERY_NO_HSDIR"); @@ -791,6 +521,20 @@ directory_get_from_hs_dir(const char *desc_id, return 1; } +/** Remove tracked HSDir requests from our history for this hidden service + * descriptor <b>desc_id</b> (of size DIGEST_LEN) */ +static void +purge_v2_hidserv_req(const char *desc_id) +{ + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + + /* The hsdir request tracker stores v2 keys using the base32 encoded + desc_id. Do it: */ + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, + DIGEST_LEN); + hs_purge_hid_serv_from_last_hid_serv_requests(desc_id_base32); +} + /** Fetch a v2 descriptor using the given descriptor id. If any hsdir(s) are * given, they will be used instead. * @@ -865,8 +609,7 @@ fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs) sizeof(descriptor_id)) != 0) { /* Not equal from what we currently have so purge the last hid serv * request cache and update the descriptor ID with the new value. */ - purge_hid_serv_from_last_hid_serv_requests( - rend_data->descriptor_id[chosen_replica]); + purge_v2_hidserv_req(rend_data->descriptor_id[chosen_replica]); memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id, sizeof(rend_data->descriptor_id[chosen_replica])); } @@ -1112,118 +855,24 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, return 1; } -/** Called when we receive a RENDEZVOUS_ESTABLISHED cell; changes the state of - * the circuit to C_REND_READY. - */ -int -rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, - size_t request_len) -{ - (void) request; - (void) request_len; - /* we just got an ack for our establish-rendezvous. switch purposes. */ - if (circ->base_.purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND) { - log_warn(LD_PROTOCOL,"Got a rendezvous ack when we weren't expecting one. " - "Closing circ."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for " - "rendezvous."); - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_READY); - /* Set timestamp_dirty, because circuit_expire_building expects it - * to specify when a circuit entered the _C_REND_READY state. */ - circ->base_.timestamp_dirty = time(NULL); - - /* From a path bias point of view, this circuit is now successfully used. - * Waiting any longer opens us up to attacks from malicious hidden services. - * They could induce the client to attempt to connect to their hidden - * service and never reply to the client's rend requests */ - pathbias_mark_use_success(circ); - - /* XXXX++ This is a pretty brute-force approach. It'd be better to - * attach only the connections that are waiting on this circuit, rather - * than trying to attach them all. See comments bug 743. */ - /* If we already have the introduction circuit built, make sure we send - * the INTRODUCE cell _now_ */ - connection_ap_attach_pending(1); - return 0; -} - /** The service sent us a rendezvous cell; join the circuits. */ int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len) { - crypt_path_t *hop; - char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; - - if ((circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY && - circ->base_.purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) - || !circ->build_state->pending_final_cpath) { - log_warn(LD_PROTOCOL,"Got rendezvous2 cell from hidden service, but not " - "expecting it. Closing."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); - return -1; - } - if (request_len != DH_KEY_LEN+DIGEST_LEN) { log_warn(LD_PROTOCOL,"Incorrect length (%d) on RENDEZVOUS2 cell.", (int)request_len); goto err; } - log_info(LD_REND,"Got RENDEZVOUS2 cell from hidden service."); - - /* first DH_KEY_LEN bytes are g^y from the service. Finish the dh - * handshake...*/ - tor_assert(circ->build_state); - tor_assert(circ->build_state->pending_final_cpath); - hop = circ->build_state->pending_final_cpath; - tor_assert(hop->rend_dh_handshake_state); - if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, - hop->rend_dh_handshake_state, (char*)request, - DH_KEY_LEN, - keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) { - log_warn(LD_GENERAL, "Couldn't complete DH handshake."); + if (hs_circuit_setup_e2e_rend_circ_legacy_client(circ, request) < 0) { + log_warn(LD_GENERAL, "Failed to setup circ"); goto err; } - /* ... and set up cpath. */ - if (circuit_init_cpath_crypto(hop, keys+DIGEST_LEN, 0)<0) - goto err; - - /* Check whether the digest is right... */ - if (tor_memneq(keys, request+DH_KEY_LEN, DIGEST_LEN)) { - log_warn(LD_PROTOCOL, "Incorrect digest of key material."); - goto err; - } - - crypto_dh_free(hop->rend_dh_handshake_state); - hop->rend_dh_handshake_state = NULL; - - /* All is well. Extend the circuit. */ - circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_REND_JOINED); - hop->state = CPATH_STATE_OPEN; - /* set the windows to default. these are the windows - * that the client thinks the service has. - */ - hop->package_window = circuit_initial_package_window(); - hop->deliver_window = CIRCWINDOW_START; - - /* Now that this circuit has finished connecting to its destination, - * make sure circuit_get_open_circ_or_launch is willing to return it - * so we can actually use it. */ - circ->hs_circ_has_timed_out = 0; - - onion_append_to_cpath(&circ->cpath, hop); - circ->build_state->pending_final_cpath = NULL; /* prevent double-free */ - - circuit_try_attaching_streams(circ); - - memwipe(keys, 0, sizeof(keys)); return 0; + err: - memwipe(keys, 0, sizeof(keys)); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL); return -1; } @@ -1310,14 +959,14 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data) for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id); replica++) { const char *desc_id = rend_data_v2->descriptor_id[replica]; - purge_hid_serv_from_last_hid_serv_requests(desc_id); + purge_v2_hidserv_req(desc_id); } log_info(LD_REND, "Connection attempt for %s has ended; " "cleaning up temporary state.", safe_str_client(onion_address)); } else { /* We only have an ID for a fetch. Probably used by HSFETCH. */ - purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch); + purge_v2_hidserv_req(rend_data_v2->desc_id_fetch); } } @@ -1516,7 +1165,7 @@ rend_parse_service_authorization(const or_options_t *options, goto err; } strlcpy(auth->onion_address, onion_address, REND_SERVICE_ID_LEN_BASE32+1); - if (!rend_valid_service_id(auth->onion_address)) { + if (!rend_valid_v2_service_id(auth->onion_address)) { log_warn(LD_CONFIG, "Onion address has wrong format: '%s'", onion_address); goto err; @@ -1569,7 +1218,7 @@ rend_client_allow_non_anonymous_connection(const or_options_t *options) #else (void)options; return 0; -#endif +#endif /* defined(NON_ANONYMOUS_MODE_ENABLED) */ } /* At compile-time, was non-anonymous mode enabled via @@ -1584,6 +1233,6 @@ rend_client_non_anonymous_mode_enabled(const or_options_t *options) return 1; #else return 0; -#endif +#endif /* defined(NON_ANONYMOUS_MODE_ENABLED) */ } diff --git a/src/or/rendclient.h b/src/or/rendclient.h index ff0f4084fd..e8495ce09c 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -24,15 +24,11 @@ int rend_client_introduction_acked(origin_circuit_t *circ, void rend_client_refetch_v2_renddesc(rend_data_t *rend_query); int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs); void rend_client_cancel_descriptor_fetches(void); -void rend_client_purge_last_hid_serv_requests(void); int rend_client_report_intro_point_failure(extend_info_t *failed_intro, rend_data_t *rend_data, unsigned int failure_type); -int rend_client_rendezvous_acked(origin_circuit_t *circ, - const uint8_t *request, - size_t request_len); int rend_client_receive_rendezvous(origin_circuit_t *circ, const uint8_t *request, size_t request_len); @@ -54,5 +50,5 @@ void rend_service_authorization_free_all(void); int rend_client_allow_non_anonymous_connection(const or_options_t *options); int rend_client_non_anonymous_mode_enabled(const or_options_t *options); -#endif +#endif /* !defined(TOR_RENDCLIENT_H) */ diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index d18356e67a..458a90058f 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -8,6 +8,8 @@ * introducers, services, clients, and rendezvous points. **/ +#define RENDCOMMON_PRIVATE + #include "or.h" #include "circuitbuild.h" #include "config.h" @@ -17,6 +19,7 @@ #include "rendcommon.h" #include "rendmid.h" #include "hs_intropoint.h" +#include "hs_client.h" #include "rendservice.h" #include "rephist.h" #include "router.h" @@ -395,7 +398,7 @@ rend_encrypt_v2_intro_points_stealth(char **encrypted_out, /** Attempt to parse the given <b>desc_str</b> and return true if this * succeeds, false otherwise. */ -static int +STATIC int rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc) { rend_service_descriptor_t *test_parsed = NULL; @@ -696,7 +699,7 @@ rend_get_service_id(crypto_pk_t *pk, char *out) /** Return true iff <b>query</b> is a syntactically valid service ID (as * generated by rend_get_service_id). */ int -rend_valid_service_id(const char *query) +rend_valid_v2_service_id(const char *query) { if (strlen(query) != REND_SERVICE_ID_LEN_BASE32) return 0; @@ -778,11 +781,11 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_INTRODUCE2: if (origin_circ) - r = rend_service_receive_introduction(origin_circ,payload,length); + r = hs_service_receive_introduce2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRODUCE_ACK: if (origin_circ) - r = rend_client_introduction_acked(origin_circ,payload,length); + r = hs_client_receive_introduce_ack(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS1: if (or_circ) @@ -790,15 +793,15 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, break; case RELAY_COMMAND_RENDEZVOUS2: if (origin_circ) - r = rend_client_receive_rendezvous(origin_circ,payload,length); + r = hs_client_receive_rendezvous2(origin_circ,payload,length); break; case RELAY_COMMAND_INTRO_ESTABLISHED: if (origin_circ) - r = rend_service_intro_established(origin_circ,payload,length); + r = hs_service_receive_intro_established(origin_circ,payload,length); break; case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: if (origin_circ) - r = rend_client_rendezvous_acked(origin_circ,payload,length); + r = hs_client_receive_rendezvous_acked(origin_circ,payload,length); break; default: tor_fragile_assert(); @@ -991,7 +994,7 @@ rend_non_anonymous_mode_enabled(const or_options_t *options) * service. */ void -assert_circ_anonymity_ok(origin_circuit_t *circ, +assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options) { tor_assert(options); diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 94c2480d86..c35d7272fd 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -30,7 +30,7 @@ void rend_encoded_v2_service_descriptor_free( rend_encoded_v2_service_descriptor_t *desc); void rend_intro_point_free(rend_intro_point_t *intro); -int rend_valid_service_id(const char *query); +int rend_valid_v2_service_id(const char *query); int rend_valid_descriptor_id(const char *query); int rend_valid_client_name(const char *client_name); int rend_encode_v2_descriptors(smartlist_t *descs_out, @@ -60,8 +60,15 @@ int rend_auth_decode_cookie(const char *cookie_in, int rend_allow_non_anonymous_connection(const or_options_t* options); int rend_non_anonymous_mode_enabled(const or_options_t *options); -void assert_circ_anonymity_ok(origin_circuit_t *circ, +void assert_circ_anonymity_ok(const origin_circuit_t *circ, const or_options_t *options); -#endif +#ifdef RENDCOMMON_PRIVATE + +STATIC int +rend_desc_v2_is_parsable(rend_encoded_v2_service_descriptor_t *desc); + +#endif /* defined(RENDCOMMON_PRIVATE) */ + +#endif /* !defined(TOR_RENDCOMMON_H) */ diff --git a/src/or/rendmid.c b/src/or/rendmid.c index 23c3deddaa..66d2f93113 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -71,7 +71,6 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, goto err; } /* Rest of body: signature of previous data */ - note_crypto_pk_op(REND_MID); if (crypto_pk_public_checksig_digest(pk, (char*)request, 2+asn1len+DIGEST_LEN, (char*)(request+2+DIGEST_LEN+asn1len), diff --git a/src/or/rendmid.h b/src/or/rendmid.h index daf9e2885e..6cc1fc8d95 100644 --- a/src/or/rendmid.h +++ b/src/or/rendmid.h @@ -21,5 +21,5 @@ int rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, int rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, size_t request_len); -#endif +#endif /* !defined(TOR_RENDMID_H) */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index b1e8a2f0cd..2cdafa6666 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -18,6 +18,7 @@ #include "control.h" #include "directory.h" #include "hs_common.h" +#include "hs_config.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -82,22 +83,6 @@ static smartlist_t* rend_get_service_list_mutable( smartlist_t* substitute_service_list); static int rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted); -/** Represents the mapping from a virtual port of a rendezvous service to - * a real port on some IP. - */ -struct rend_service_port_config_s { - /* The incoming HS virtual port we're mapping */ - uint16_t virtual_port; - /* Is this an AF_UNIX port? */ - unsigned int is_unix_addr:1; - /* The outgoing TCP port to use, if !is_unix_addr */ - uint16_t real_port; - /* The outgoing IPv4 or IPv6 address to use, if !is_unix_addr */ - tor_addr_t real_addr; - /* The socket path to connect to, if is_unix_addr */ - char unix_addr[FLEXIBLE_ARRAY_MEMBER]; -}; - /* Hidden service directory file names: * new file names should be added to rend_service_add_filenames_to_list() * for sandboxing purposes. */ @@ -163,7 +148,7 @@ rend_service_escaped_dir(const struct rend_service_t *s) /** Return the number of rendezvous services we have configured. */ int -num_rend_services(void) +rend_num_services(void) { if (!rend_service_list) return 0; @@ -231,18 +216,41 @@ rend_service_free(rend_service_t *service) tor_free(service); } -/** Release all the storage held in rend_service_list. - */ +/* Release all the storage held in rend_service_staging_list. */ +void +rend_service_free_staging_list(void) +{ + if (rend_service_staging_list) { + SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t*, ptr, + rend_service_free(ptr)); + smartlist_free(rend_service_staging_list); + rend_service_staging_list = NULL; + } +} + +/** Release all the storage held in both rend_service_list and + * rend_service_staging_list. */ void rend_service_free_all(void) { - if (!rend_service_list) - return; + if (rend_service_list) { + SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, + rend_service_free(ptr)); + smartlist_free(rend_service_list); + rend_service_list = NULL; + } + rend_service_free_staging_list(); +} + +/* Initialize the subsystem. */ +void +rend_service_init(void) +{ + tor_assert(!rend_service_list); + tor_assert(!rend_service_staging_list); - SMARTLIST_FOREACH(rend_service_list, rend_service_t*, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_list); - rend_service_list = NULL; + rend_service_list = smartlist_new(); + rend_service_staging_list = smartlist_new(); } /* Validate a <b>service</b>. Use the <b>service_list</b> to make sure there @@ -252,8 +260,6 @@ static int rend_validate_service(const smartlist_t *service_list, const rend_service_t *service) { - int dupe = 0; - tor_assert(service_list); tor_assert(service); @@ -286,34 +292,6 @@ rend_validate_service(const smartlist_t *service_list, goto invalid; } - /* XXX This duplicate check has two problems: - * - * a) It's O(n^2), but the same comment from the bottom of - * rend_config_services() should apply. - * - * b) We only compare directory paths as strings, so we can't - * detect two distinct paths that specify the same directory - * (which can arise from symlinks, case-insensitivity, bind - * mounts, etc.). - * - * It also can't detect that two separate Tor instances are trying - * to use the same HiddenServiceDir; for that, we would need a - * lock file. But this is enough to detect a simple mistake that - * at least one person has actually made. - */ - if (!rend_service_is_ephemeral(service)) { - /* Skip dupe for ephemeral services. */ - SMARTLIST_FOREACH(service_list, rend_service_t *, ptr, - dupe = dupe || - !strcmp(ptr->directory, service->directory)); - if (dupe) { - log_warn(LD_REND, "Another hidden service is already configured for " - "directory %s.", - rend_service_escaped_dir(service)); - goto invalid; - } - } - /* Valid. */ return 0; invalid: @@ -335,6 +313,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) /* We must have a service list, even if it's a temporary one, so we can * check for duplicate services */ if (BUG(!s_list)) { + rend_service_free(service); return -1; } @@ -496,39 +475,28 @@ rend_service_port_config_free(rend_service_port_config_t *p) tor_free(p); } -/* Check the directory for <b>service</b>, and add the service to - * <b>service_list</b>, or to the global list if <b>service_list</b> is NULL. - * Only add the service to the list if <b>validate_only</b> is false. - * If <b>validate_only</b> is true, free the service. - * If <b>service</b> is NULL, ignore it, and return 0. - * Returns 0 on success, and -1 on failure. - * Takes ownership of <b>service</b>, either freeing it, or adding it to the - * global service list. - */ -STATIC int -rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only) +/* Copy relevant data from service src to dst while pruning the service lists. + * This should only be called during the pruning process which takes existing + * services and copy their data to the newly configured services. The src + * service replaycache will be set to NULL after this call. */ +static void +copy_service_on_prunning(rend_service_t *dst, rend_service_t *src) { - if (!service) { - /* It is ok for a service to be NULL, this means there are no services */ - return 0; - } - - if (rend_service_check_private_dir(options, service, !validate_only) - < 0) { - rend_service_free(service); - return -1; - } - - smartlist_t *s_list = rend_get_service_list_mutable(service_list); - /* We must have a service list, even if it's a temporary one, so we can - * check for duplicate services */ - if (BUG(!s_list)) { - return -1; - } - return rend_add_service(s_list, service); + tor_assert(dst); + tor_assert(src); + + /* Keep the timestamps for when the content changed and the next upload + * time so we can properly upload the descriptor if needed for the new + * service object. */ + dst->desc_is_dirty = src->desc_is_dirty; + dst->next_upload_time = src->next_upload_time; + /* Move the replaycache to the new object. */ + dst->accepted_intro_dh_parts = src->accepted_intro_dh_parts; + src->accepted_intro_dh_parts = NULL; + /* Copy intro point information to destination service. */ + dst->intro_period_started = src->intro_period_started; + dst->n_intro_circuits_launched = src->n_intro_circuits_launched; + dst->n_intro_points_wanted = src->n_intro_points_wanted; } /* Helper: Actual implementation of the pruning on reload which we've @@ -604,6 +572,10 @@ rend_service_prune_list_impl_(void) smartlist_clear(old->intro_nodes); smartlist_add_all(new->expiring_nodes, old->expiring_nodes); smartlist_clear(old->expiring_nodes); + + /* Copy needed information from old to new. */ + copy_service_on_prunning(new, old); + /* This regular service will survive the closing IPs step after. */ smartlist_add(surviving_services, old); break; @@ -614,7 +586,10 @@ rend_service_prune_list_impl_(void) * matching surviving configured service. If not, close the circuit. */ while ((ocirc = circuit_get_next_service_intro_circ(ocirc))) { int keep_it = 0; - tor_assert(ocirc->rend_data); + if (ocirc->rend_data == NULL) { + /* This is a v3 circuit, ignore it. */ + continue; + } SMARTLIST_FOREACH_BEGIN(surviving_services, const rend_service_t *, s) { if (rend_circuit_pk_digest_eq(ocirc, (uint8_t *) s->pk_digest)) { /* Keep this circuit as we have a matching configured service. */ @@ -657,19 +632,54 @@ rend_service_prune_list(void) } } -/** Set up rend_service_list, based on the values of HiddenServiceDir and - * HiddenServicePort in <b>options</b>. Return 0 on success and -1 on - * failure. (If <b>validate_only</b> is set, parse, warn and return as - * normal, but don't actually change the configured services.) - */ +/* Copy all the relevant data that the hs_service object contains over to the + * rend_service_t object. The reason to do so is because when configuring a + * service, we go through a generic handler that creates an hs_service_t + * object which so we have to copy the parsed values to a rend service object + * which is version 2 specific. */ +static void +service_config_shadow_copy(rend_service_t *service, + hs_service_config_t *config) +{ + tor_assert(service); + tor_assert(config); + + service->directory = tor_strdup(config->directory_path); + service->dir_group_readable = config->dir_group_readable; + service->allow_unknown_ports = config->allow_unknown_ports; + /* This value can't go above HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT (65535) + * if the code flow is right so this cast is safe. But just in case, we'll + * check it. */ + service->max_streams_per_circuit = (int) config->max_streams_per_rdv_circuit; + if (BUG(config->max_streams_per_rdv_circuit > + HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) { + service->max_streams_per_circuit = HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT; + } + service->max_streams_close_circuit = config->max_streams_close_circuit; + service->n_intro_points_wanted = config->num_intro_points; + /* Switching ownership of the ports to the rend service object. */ + smartlist_add_all(service->ports, config->ports); + smartlist_free(config->ports); + config->ports = NULL; +} + +/* Parse the hidden service configuration starting at <b>line_</b> using the + * already configured generic service configuration in <b>config</b>. This + * function will translate the config object to a rend_service_t and add it to + * the temporary list if valid. If <b>validate_only</b> is set, parse, warn + * and return as normal but don't actually add the service to the list. */ int -rend_config_services(const or_options_t *options, int validate_only) +rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config) { - config_line_t *line; + const config_line_t *line; rend_service_t *service = NULL; - rend_service_port_config_t *portcfg; - int ok = 0; - int rv = -1; + + /* line_ can be NULL which would mean that the service configuration only + * have one line that is the directory directive. */ + tor_assert(options); + tor_assert(config); /* Use the staging service list so that we can check then do the pruning * process using the main list at the end. */ @@ -677,100 +687,23 @@ rend_config_services(const or_options_t *options, int validate_only) rend_service_staging_list = smartlist_new(); } - for (line = options->RendConfigLines; line; line = line->next) { + /* Initialize service. */ + service = tor_malloc_zero(sizeof(rend_service_t)); + service->intro_period_started = time(NULL); + service->ports = smartlist_new(); + /* From the hs_service object which has been used to load the generic + * options, we'll copy over the useful data to the rend_service_t object. */ + service_config_shadow_copy(service, config); + + for (line = line_; line; line = line->next) { if (!strcasecmp(line->key, "HiddenServiceDir")) { - if (service) { - /* Validate and register the service we just finished parsing this - * code registers every service except the last one parsed, which is - * validated and registered below the loop. */ - if (rend_validate_service(rend_service_staging_list, service) < 0) { - goto free_and_return; - } - if (rend_service_check_dir_and_add(rend_service_staging_list, options, - service, validate_only) < 0) { - /* The above frees the service on error so nullify the pointer. */ - service = NULL; - goto free_and_return; - } - } - service = tor_malloc_zero(sizeof(rend_service_t)); - service->directory = tor_strdup(line->value); - service->ports = smartlist_new(); - service->intro_period_started = time(NULL); - service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT; - continue; - } - if (!service) { - log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive", - line->key); - goto free_and_return; + /* We just hit the next hidden service, stop right now. */ + break; } - if (!strcasecmp(line->key, "HiddenServicePort")) { - char *err_msg = NULL; - portcfg = rend_service_parse_port_config(line->value, " ", &err_msg); - if (!portcfg) { - if (err_msg) - log_warn(LD_CONFIG, "%s", err_msg); - tor_free(err_msg); - goto free_and_return; - } - tor_assert(!err_msg); - smartlist_add(service->ports, portcfg); - } else if (!strcasecmp(line->key, "HiddenServiceAllowUnknownPorts")) { - service->allow_unknown_ports = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceAllowUnknownPorts should be 0 or 1, not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceAllowUnknownPorts=%d for %s", - (int)service->allow_unknown_ports, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, - "HiddenServiceDirGroupReadable")) { - service->dir_group_readable = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceDirGroupReadable should be 0 or 1, not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceDirGroupReadable=%d for %s", - service->dir_group_readable, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { - service->max_streams_per_circuit = (int)tor_parse_long(line->value, - 10, 0, 65535, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceMaxStreams should be between 0 and %d, not %s", - 65535, line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceMaxStreams=%d for %s", - service->max_streams_per_circuit, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { - service->max_streams_close_circuit = (int)tor_parse_long(line->value, - 10, 0, 1, &ok, NULL); - if (!ok) { - log_warn(LD_CONFIG, - "HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, " - "not %s", - line->value); - goto free_and_return; - } - log_info(LD_CONFIG, - "HiddenServiceMaxStreamsCloseCircuit=%d for %s", - (int)service->max_streams_close_circuit, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + /* Number of introduction points. */ + if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { + int ok = 0; + /* Those are specific defaults for version 2. */ service->n_intro_points_wanted = (unsigned int) tor_parse_long(line->value, 10, 0, NUM_INTRO_POINTS_MAX, &ok, NULL); @@ -779,22 +712,22 @@ rend_config_services(const or_options_t *options, int validate_only) "HiddenServiceNumIntroductionPoints " "should be between %d and %d, not %s", 0, NUM_INTRO_POINTS_MAX, line->value); - goto free_and_return; + goto err; } log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s", - service->n_intro_points_wanted, - rend_service_escaped_dir(service)); - } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { + service->n_intro_points_wanted, escaped(service->directory)); + continue; + } + if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { /* Parse auth type and comma-separated list of client names and add a * rend_authorized_client_t for each client to the service's list * of authorized clients. */ smartlist_t *type_names_split, *clients; const char *authname; - int num_clients; if (service->auth_type != REND_NO_AUTH) { log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient " "lines for a single service."); - goto free_and_return; + goto err; } type_names_split = smartlist_new(); smartlist_split_string(type_names_split, line->value, " ", 0, 2); @@ -802,7 +735,8 @@ rend_config_services(const or_options_t *options, int validate_only) log_warn(LD_BUG, "HiddenServiceAuthorizeClient has no value. This " "should have been prevented when parsing the " "configuration."); - goto free_and_return; + smartlist_free(type_names_split); + goto err; } authname = smartlist_get(type_names_split, 0); if (!strcasecmp(authname, "basic")) { @@ -816,7 +750,7 @@ rend_config_services(const or_options_t *options, int validate_only) (char *) smartlist_get(type_names_split, 0)); SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); smartlist_free(type_names_split); - goto free_and_return; + goto err; } service->clients = smartlist_new(); if (smartlist_len(type_names_split) < 2) { @@ -833,14 +767,15 @@ rend_config_services(const or_options_t *options, int validate_only) SMARTLIST_FOREACH(type_names_split, char *, cp, tor_free(cp)); smartlist_free(type_names_split); /* Remove duplicate client names. */ - num_clients = smartlist_len(clients); - smartlist_sort_strings(clients); - smartlist_uniq_strings(clients); - if (smartlist_len(clients) < num_clients) { - log_info(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " - "duplicate client name(s); removing.", - num_clients - smartlist_len(clients)); - num_clients = smartlist_len(clients); + { + int num_clients = smartlist_len(clients); + smartlist_sort_strings(clients); + smartlist_uniq_strings(clients); + if (smartlist_len(clients) < num_clients) { + log_info(LD_CONFIG, "HiddenServiceAuthorizeClient contains %d " + "duplicate client name(s); removing.", + num_clients - smartlist_len(clients)); + } } SMARTLIST_FOREACH_BEGIN(clients, const char *, client_name) { @@ -853,7 +788,7 @@ rend_config_services(const or_options_t *options, int validate_only) client_name, REND_CLIENTNAME_MAX_LEN); SMARTLIST_FOREACH(clients, char *, cp, tor_free(cp)); smartlist_free(clients); - goto free_and_return; + goto err; } client = tor_malloc_zero(sizeof(rend_authorized_client_t)); client->client_name = tor_strdup(client_name); @@ -875,56 +810,29 @@ rend_config_services(const or_options_t *options, int validate_only) smartlist_len(service->clients), service->auth_type == REND_BASIC_AUTH ? 512 : 16, service->auth_type == REND_BASIC_AUTH ? "basic" : "stealth"); - goto free_and_return; - } - } else { - tor_assert(!strcasecmp(line->key, "HiddenServiceVersion")); - if (strcmp(line->value, "2")) { - log_warn(LD_CONFIG, - "The only supported HiddenServiceVersion is 2."); - goto free_and_return; + goto err; } + continue; } } - /* Validate the last service that we just parsed. */ - if (service && - rend_validate_service(rend_service_staging_list, service) < 0) { - goto free_and_return; - } - /* register the final service after we have finished parsing all services - * this code only registers the last service, other services are registered - * within the loop. It is ok for this service to be NULL, it is ignored. */ - if (rend_service_check_dir_and_add(rend_service_staging_list, options, - service, validate_only) < 0) { - /* Service object is freed on error so nullify pointer. */ - service = NULL; - goto free_and_return; + /* Validate the service just parsed. */ + if (rend_validate_service(rend_service_staging_list, service) < 0) { + /* Service is in the staging list so don't try to free it. */ + goto err; } - /* The service is in the staging list so nullify pointer to avoid double - * free of this object in case of error because we lost ownership of it at - * this point. */ - service = NULL; - /* Free the newly added services if validating */ - if (validate_only) { - rv = 0; - goto free_and_return; + /* Add it to the temporary list which we will use to prune our current + * list if any after configuring all services. */ + if (rend_add_service(rend_service_staging_list, service) < 0) { + /* The object has been freed on error already. */ + service = NULL; + goto err; } - /* This could be a reload of configuration so try to prune the main list - * using the staging one. And we know we are not in validate mode here. - * After this, the main and staging list will point to the right place and - * be in a quiescent usable state. */ - rend_service_prune_list(); - return 0; - free_and_return: + err: rend_service_free(service); - SMARTLIST_FOREACH(rend_service_staging_list, rend_service_t *, ptr, - rend_service_free(ptr)); - smartlist_free(rend_service_staging_list); - rend_service_staging_list = NULL; - return rv; + return -1; } /** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using @@ -1013,7 +921,7 @@ int rend_service_del_ephemeral(const char *service_id) { rend_service_t *s; - if (!rend_valid_service_id(service_id)) { + if (!rend_valid_v2_service_id(service_id)) { log_warn(LD_CONFIG, "Requested malformed Onion Service id for removal."); return -1; } @@ -1038,8 +946,8 @@ rend_service_del_ephemeral(const char *service_id) (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); - tor_assert(oc->rend_data); - if (!rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { + if (oc->rend_data == NULL || + !rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { continue; } log_debug(LD_REND, "Closing intro point %s for service %s.", @@ -1177,15 +1085,8 @@ rend_service_update_descriptor(rend_service_t *service) static char * rend_service_path(const rend_service_t *service, const char *file_name) { - char *file_path = NULL; - tor_assert(service->directory); - - /* Can never fail: asserts rather than leaving file_path NULL. */ - tor_asprintf(&file_path, "%s%s%s", - service->directory, PATH_SEPARATOR, file_name); - - return file_path; + return hs_path_from_filename(service->directory, file_name); } /* Allocate and return a string containing the path to the single onion @@ -1555,9 +1456,9 @@ rend_service_load_keys(rend_service_t *s) char *fname = NULL; char buf[128]; - /* Make sure the directory was created and single onion poisoning was - * checked before calling this function */ - if (BUG(rend_service_check_private_dir(get_options(), s, 0) < 0)) + /* Create the directory if needed which will also poison it in case of + * single onion service. */ + if (rend_service_check_private_dir(get_options(), s, 1) < 0) goto err; /* Load key */ @@ -1586,7 +1487,7 @@ rend_service_load_keys(rend_service_t *s) log_warn(LD_FS,"Unable to make hidden hostname file %s group-readable.", fname); } -#endif +#endif /* !defined(_WIN32) */ /* If client authorization is configured, load or generate keys. */ if (s->auth_type != REND_NO_AUTH) { @@ -1815,24 +1716,6 @@ rend_service_get_by_service_id(const char *id) return NULL; } -/** Return 1 if any virtual port in <b>service</b> wants a circuit - * to have good uptime. Else return 0. - */ -static int -rend_service_requires_uptime(rend_service_t *service) -{ - int i; - rend_service_port_config_t *p; - - for (i=0; i < smartlist_len(service->ports); ++i) { - p = smartlist_get(service->ports, i); - if (smartlist_contains_int_as_string(get_options()->LongLivedPorts, - p->virtual_port)) - return 1; - } - return 0; -} - /** Check client authorization of a given <b>descriptor_cookie</b> of * length <b>cookie_len</b> for <b>service</b>. Return 1 for success * and 0 for failure. */ @@ -2152,7 +2035,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, goto err; } - circ_needs_uptime = rend_service_requires_uptime(service); + circ_needs_uptime = hs_service_requires_uptime_circ(service->ports); /* help predict this next time */ rep_hist_note_used_internal(now, circ_needs_uptime, 1); @@ -2204,7 +2087,9 @@ rend_service_receive_introduction(origin_circuit_t *circuit, cpath->rend_dh_handshake_state = dh; dh = NULL; - if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0) + if (circuit_init_cpath_crypto(cpath, + keys+DIGEST_LEN, sizeof(keys)-DIGEST_LEN, + 1, 0)<0) goto err; memcpy(cpath->rend_circ_nonce, keys, DIGEST_LEN); @@ -2267,7 +2152,7 @@ find_rp_for_intro(const rend_intro_cell_t *intro, if (intro->version == 0 || intro->version == 1) { rp_nickname = (const char *)(intro->u.v0_v1.rp); - node = node_get_by_nickname(rp_nickname, 0); + node = node_get_by_nickname(rp_nickname, NNF_NO_WARN_UNNAMED); if (!node) { if (err_msg_out) { tor_asprintf(&err_msg, @@ -2859,10 +2744,8 @@ rend_service_decrypt_intro( } /* Decrypt the encrypted part */ - - note_crypto_pk_op(REND_SERVER); result = - crypto_pk_private_hybrid_decrypt( + crypto_pk_obsolete_private_hybrid_decrypt( key, (char *)buf, sizeof(buf), (const char *)(intro->ciphertext), intro->ciphertext_len, PK_PKCS1_OAEP_PADDING, 1); @@ -3056,29 +2939,6 @@ rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc) tor_assert(oldcirc->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); - /* Don't relaunch the same rend circ twice. */ - if (oldcirc->hs_service_side_rend_circ_has_been_relaunched) { - log_info(LD_REND, "Rendezvous circuit to %s has already been relaunched; " - "not relaunching it again.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldcirc->hs_service_side_rend_circ_has_been_relaunched = 1; - - if (!oldcirc->build_state || - oldcirc->build_state->failure_count > MAX_REND_FAILURES || - oldcirc->build_state->expiry_time < time(NULL)) { - log_info(LD_REND, - "Attempt to build circuit to %s for rendezvous has failed " - "too many times or expired; giving up.", - oldcirc->build_state ? - safe_str(extend_info_describe(oldcirc->build_state->chosen_exit)) - : "*unknown*"); - return; - } - oldstate = oldcirc->build_state; tor_assert(oldstate); @@ -3246,10 +3106,11 @@ count_intro_point_circuits(const rend_service_t *service) crypto material. On success, fill <b>cell_body_out</b> and return the number of bytes written. On fail, return -1. */ -STATIC ssize_t -encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, char *rend_circ_nonce) +ssize_t +rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce) { int retval = -1; int r; @@ -3274,7 +3135,6 @@ encode_establish_intro_cell_legacy(char *cell_body_out, if (crypto_digest(cell_body_out+len, auth, DIGEST_LEN+9)) goto err; len += 20; - note_crypto_pk_op(REND_SERVER); r = crypto_pk_private_sign_digest(intro_key, cell_body_out+len, cell_body_out_len - len, cell_body_out, len); @@ -3387,7 +3247,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) /* Send the ESTABLISH_INTRO cell */ { ssize_t len; - len = encode_establish_intro_cell_legacy(buf, sizeof(buf), + len = rend_service_encode_establish_intro_cell(buf, sizeof(buf), circuit->intro_key, circuit->cpath->prev->rend_circ_nonce); if (len < 0) { @@ -3505,9 +3365,10 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) NULL); rend_cookie = circuit->rend_data->rend_cookie; - /* Declare the circuit dirty to avoid reuse, and for path-bias */ - if (!circuit->base_.timestamp_dirty) - circuit->base_.timestamp_dirty = time(NULL); + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + circuit->base_.timestamp_dirty = time(NULL); /* This may be redundant */ pathbias_count_use_attempt(circuit); @@ -3567,7 +3428,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) /* Send the cell */ if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit), RELAY_COMMAND_RENDEZVOUS1, - buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN, + buf, HS_LEGACY_RENDEZVOUS_CELL_SIZE, circuit->cpath->prev)<0) { log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell."); goto done; @@ -4123,10 +3984,9 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted) * This is called once a second by the main loop. */ void -rend_consider_services_intro_points(void) +rend_consider_services_intro_points(time_t now) { int i; - time_t now; const or_options_t *options = get_options(); /* Are we in single onion mode? */ const int allow_direct = rend_service_allow_non_anonymous_connection( @@ -4143,7 +4003,6 @@ rend_consider_services_intro_points(void) exclude_nodes = smartlist_new(); retry_nodes = smartlist_new(); - now = time(NULL); SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, service) { int r; @@ -4421,60 +4280,6 @@ rend_service_dump_stats(int severity) } } -#ifdef HAVE_SYS_UN_H - -/** Given <b>ports</b>, a smarlist containing rend_service_port_config_t, - * add the given <b>p</b>, a AF_UNIX port to the list. Return 0 on success - * else return -ENOSYS if AF_UNIX is not supported (see function in the - * #else statement below). */ -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - tor_assert(ports); - tor_assert(p); - tor_assert(p->is_unix_addr); - - smartlist_add(ports, p); - return 0; -} - -/** Given <b>conn</b> set it to use the given port <b>p</b> values. Return 0 - * on success else return -ENOSYS if AF_UNIX is not supported (see function - * in the #else statement below). */ -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - tor_assert(conn); - tor_assert(p); - tor_assert(p->is_unix_addr); - - conn->base_.socket_family = AF_UNIX; - tor_addr_make_unspec(&conn->base_.addr); - conn->base_.port = 1; - conn->base_.address = tor_strdup(p->unix_addr); - return 0; -} - -#else /* defined(HAVE_SYS_UN_H) */ - -static int -set_unix_port(edge_connection_t *conn, rend_service_port_config_t *p) -{ - (void) conn; - (void) p; - return -ENOSYS; -} - -static int -add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) -{ - (void) ports; - (void) p; - return -ENOSYS; -} - -#endif /* HAVE_SYS_UN_H */ - /** Given <b>conn</b>, a rendezvous exit stream, look up the hidden service for * 'circ', and look up the port and address based on conn-\>port. * Assign the actual conn-\>addr and conn-\>port. Return -2 on failure @@ -4487,15 +4292,11 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, { rend_service_t *service; char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; - smartlist_t *matching_ports; - rend_service_port_config_t *chosen_port; - unsigned int warn_once = 0; const char *rend_pk_digest; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); tor_assert(circ->rend_data); log_debug(LD_REND,"beginning to hunt for addr/port"); - /* XXX: This is version 2 specific (only one supported). */ rend_pk_digest = (char *) rend_data_get_pk_digest(circ->rend_data, NULL); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, rend_pk_digest, REND_SERVICE_ID_LEN); @@ -4525,41 +4326,9 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, return service->max_streams_close_circuit ? -2 : -1; } } - matching_ports = smartlist_new(); - SMARTLIST_FOREACH(service->ports, rend_service_port_config_t *, p, - { - if (conn->base_.port != p->virtual_port) { - continue; - } - if (!(p->is_unix_addr)) { - smartlist_add(matching_ports, p); - } else { - if (add_unix_port(matching_ports, p)) { - if (!warn_once) { - /* Unix port not supported so warn only once. */ - log_warn(LD_REND, - "Saw AF_UNIX virtual port mapping for port %d on service " - "%s, which is unsupported on this platform. Ignoring it.", - conn->base_.port, serviceid); - } - warn_once++; - } - } - }); - chosen_port = smartlist_choose(matching_ports); - smartlist_free(matching_ports); - if (chosen_port) { - if (!(chosen_port->is_unix_addr)) { - /* Get a non-AF_UNIX connection ready for connection_exit_connect() */ - tor_addr_copy(&conn->base_.addr, &chosen_port->real_addr); - conn->base_.port = chosen_port->real_port; - } else { - if (set_unix_port(conn, chosen_port)) { - /* Simply impossible to end up here else we were able to add a Unix - * port without AF_UNIX support... ? */ - tor_assert(0); - } - } + + if (hs_set_conn_addr_port(service->ports, conn) == 0) { + /* Successfully set the port to the connection. We are done. */ return 0; } @@ -4635,5 +4404,5 @@ set_rend_rend_service_staging_list(smartlist_t *new_list) rend_service_staging_list = new_list; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/rendservice.h b/src/or/rendservice.h index 1583a6010b..5946e31861 100644 --- a/src/or/rendservice.h +++ b/src/or/rendservice.h @@ -13,11 +13,9 @@ #define TOR_RENDSERVICE_H #include "or.h" +#include "hs_service.h" typedef struct rend_intro_cell_s rend_intro_cell_t; -typedef struct rend_service_port_config_s rend_service_port_config_t; - -#ifdef RENDSERVICE_PRIVATE /* This can be used for both INTRODUCE1 and INTRODUCE2 */ @@ -63,6 +61,8 @@ struct rend_intro_cell_s { uint8_t dh[DH_KEY_LEN]; }; +#ifdef RENDSERVICE_PRIVATE + /** Represents a single hidden service running at this OP. */ typedef struct rend_service_t { /* Fields specified in config file */ @@ -119,37 +119,32 @@ typedef struct rend_service_t { STATIC void rend_service_free(rend_service_t *service); STATIC char *rend_service_sos_poison_path(const rend_service_t *service); -STATIC int rend_service_check_dir_and_add(smartlist_t *service_list, - const or_options_t *options, - rend_service_t *service, - int validate_only); STATIC int rend_service_verify_single_onion_poison( const rend_service_t *s, const or_options_t *options); STATIC int rend_service_poison_new_single_onion_dir( const rend_service_t *s, const or_options_t* options); -STATIC ssize_t encode_establish_intro_cell_legacy(char *cell_body_out, - size_t cell_body_out_len, - crypto_pk_t *intro_key, - char *rend_circ_nonce); #ifdef TOR_UNIT_TESTS STATIC void set_rend_service_list(smartlist_t *new_list); STATIC void set_rend_rend_service_staging_list(smartlist_t *new_list); STATIC void rend_service_prune_list_impl_(void); -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* RENDSERVICE_PRIVATE */ +#endif /* defined(RENDSERVICE_PRIVATE) */ -int num_rend_services(void); -int rend_config_services(const or_options_t *options, int validate_only); +int rend_num_services(void); +int rend_config_service(const config_line_t *line_, + const or_options_t *options, + hs_service_config_t *config); void rend_service_prune_list(void); +void rend_service_free_staging_list(void); int rend_service_load_all_keys(const smartlist_t *service_list); void rend_services_add_filenames_to_lists(smartlist_t *open_lst, smartlist_t *stat_lst); -void rend_consider_services_intro_points(void); +void rend_consider_services_intro_points(time_t now); void rend_consider_services_upload(time_t now); void rend_hsdir_routers_changed(void); void rend_consider_descriptor_republication(void); @@ -172,6 +167,10 @@ rend_intro_cell_t * rend_service_begin_parse_intro(const uint8_t *request, char **err_msg_out); int rend_service_parse_intro_plaintext(rend_intro_cell_t *intro, char **err_msg_out); +ssize_t rend_service_encode_establish_intro_cell(char *cell_body_out, + size_t cell_body_out_len, + crypto_pk_t *intro_key, + const char *rend_circ_nonce); int rend_service_validate_intro_late(const rend_intro_cell_t *intro, char **err_msg_out); void rend_service_relaunch_rendezvous(origin_circuit_t *oldcirc); @@ -179,6 +178,7 @@ int rend_service_set_connection_addr_port(edge_connection_t *conn, origin_circuit_t *circ); void rend_service_dump_stats(int severity); void rend_service_free_all(void); +void rend_service_init(void); rend_service_port_config_t *rend_service_parse_port_config(const char *string, const char *sep, @@ -214,5 +214,5 @@ int rend_service_allow_non_anonymous_connection(const or_options_t *options); int rend_service_reveal_startup_time(const or_options_t *options); int rend_service_non_anonymous_mode_enabled(const or_options_t *options); -#endif +#endif /* !defined(TOR_RENDSERVICE_H) */ diff --git a/src/or/rephist.c b/src/or/rephist.c index ffc1867955..345722d8ce 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -1811,7 +1811,7 @@ static time_t last_prediction_add_time=0; int predicted_ports_prediction_time_remaining(time_t now) { - time_t idle_delta = now - last_prediction_add_time; + time_t idle_delta; /* Protect against overflow of return value. This can happen if the clock * jumps backwards in time. Update the last prediction time (aka last @@ -1821,6 +1821,8 @@ predicted_ports_prediction_time_remaining(time_t now) if (last_prediction_add_time > now) { last_prediction_add_time = now; idle_delta = 0; + } else { + idle_delta = now - last_prediction_add_time; } /* Protect against underflow of the return value. This can happen for very @@ -2064,105 +2066,6 @@ rep_hist_circbuilding_dormant(time_t now) return 1; } -/** Structure to track how many times we've done each public key operation. */ -static struct { - /** How many directory objects have we signed? */ - unsigned long n_signed_dir_objs; - /** How many routerdescs have we signed? */ - unsigned long n_signed_routerdescs; - /** How many directory objects have we verified? */ - unsigned long n_verified_dir_objs; - /** How many routerdescs have we verified */ - unsigned long n_verified_routerdescs; - /** How many onionskins have we encrypted to build circuits? */ - unsigned long n_onionskins_encrypted; - /** How many onionskins have we decrypted to do circuit build requests? */ - unsigned long n_onionskins_decrypted; - /** How many times have we done the TLS handshake as a client? */ - unsigned long n_tls_client_handshakes; - /** How many times have we done the TLS handshake as a server? */ - unsigned long n_tls_server_handshakes; - /** How many PK operations have we done as a hidden service client? */ - unsigned long n_rend_client_ops; - /** How many PK operations have we done as a hidden service midpoint? */ - unsigned long n_rend_mid_ops; - /** How many PK operations have we done as a hidden service provider? */ - unsigned long n_rend_server_ops; -} pk_op_counts = {0,0,0,0,0,0,0,0,0,0,0}; - -/** Increment the count of the number of times we've done <b>operation</b>. */ -void -note_crypto_pk_op(pk_op_t operation) -{ - switch (operation) - { - case SIGN_DIR: - pk_op_counts.n_signed_dir_objs++; - break; - case SIGN_RTR: - pk_op_counts.n_signed_routerdescs++; - break; - case VERIFY_DIR: - pk_op_counts.n_verified_dir_objs++; - break; - case VERIFY_RTR: - pk_op_counts.n_verified_routerdescs++; - break; - case ENC_ONIONSKIN: - pk_op_counts.n_onionskins_encrypted++; - break; - case DEC_ONIONSKIN: - pk_op_counts.n_onionskins_decrypted++; - break; - case TLS_HANDSHAKE_C: - pk_op_counts.n_tls_client_handshakes++; - break; - case TLS_HANDSHAKE_S: - pk_op_counts.n_tls_server_handshakes++; - break; - case REND_CLIENT: - pk_op_counts.n_rend_client_ops++; - break; - case REND_MID: - pk_op_counts.n_rend_mid_ops++; - break; - case REND_SERVER: - pk_op_counts.n_rend_server_ops++; - break; - default: - log_warn(LD_BUG, "Unknown pk operation %d", operation); - } -} - -/** Log the number of times we've done each public/private-key operation. */ -void -dump_pk_ops(int severity) -{ - tor_log(severity, LD_HIST, - "PK operations: %lu directory objects signed, " - "%lu directory objects verified, " - "%lu routerdescs signed, " - "%lu routerdescs verified, " - "%lu onionskins encrypted, " - "%lu onionskins decrypted, " - "%lu client-side TLS handshakes, " - "%lu server-side TLS handshakes, " - "%lu rendezvous client operations, " - "%lu rendezvous middle operations, " - "%lu rendezvous server operations.", - pk_op_counts.n_signed_dir_objs, - pk_op_counts.n_verified_dir_objs, - pk_op_counts.n_signed_routerdescs, - pk_op_counts.n_verified_routerdescs, - pk_op_counts.n_onionskins_encrypted, - pk_op_counts.n_onionskins_decrypted, - pk_op_counts.n_tls_client_handshakes, - pk_op_counts.n_tls_server_handshakes, - pk_op_counts.n_rend_client_ops, - pk_op_counts.n_rend_mid_ops, - pk_op_counts.n_rend_server_ops); -} - /*** Exit port statistics ***/ /* Some constants */ @@ -2651,7 +2554,7 @@ rep_hist_format_buffer_stats(time_t now) processed_cells_string, queued_cells_string, time_in_queue_string, - (number_of_circuits + SHARES - 1) / SHARES); + CEIL_DIV(number_of_circuits, SHARES)); tor_free(processed_cells_string); tor_free(queued_cells_string); tor_free(time_in_queue_string); diff --git a/src/or/rephist.h b/src/or/rephist.h index 2b1c2e7ec7..496e366865 100644 --- a/src/or/rephist.h +++ b/src/or/rephist.h @@ -62,9 +62,6 @@ int any_predicted_circuits(time_t now); int rep_hist_circbuilding_dormant(time_t now); int predicted_ports_prediction_time_remaining(time_t now); -void note_crypto_pk_op(pk_op_t operation); -void dump_pk_ops(int severity); - void rep_hist_exit_stats_init(time_t now); void rep_hist_reset_exit_stats(time_t now); void rep_hist_exit_stats_term(void); @@ -146,5 +143,5 @@ void rep_hist_reset_padding_counts(void); void rep_hist_prep_published_padding_counts(time_t now); void rep_hist_padding_count_timers(uint64_t num_timers); -#endif +#endif /* !defined(TOR_REPHIST_H) */ diff --git a/src/or/replaycache.h b/src/or/replaycache.h index 0d637939a4..1cae3497ae 100644 --- a/src/or/replaycache.h +++ b/src/or/replaycache.h @@ -29,7 +29,7 @@ struct replaycache_s { digest256map_t *digests_seen; }; -#endif /* REPLAYCACHE_PRIVATE */ +#endif /* defined(REPLAYCACHE_PRIVATE) */ /* replaycache_t free/new */ @@ -51,7 +51,7 @@ STATIC int replaycache_add_and_test_internal( STATIC void replaycache_scrub_if_needed_internal( time_t present, replaycache_t *r); -#endif /* REPLAYCACHE_PRIVATE */ +#endif /* defined(REPLAYCACHE_PRIVATE) */ /* * replaycache_t methods @@ -62,5 +62,5 @@ int replaycache_add_test_and_elapsed( replaycache_t *r, const void *data, size_t len, time_t *elapsed); void replaycache_scrub_if_needed(replaycache_t *r); -#endif +#endif /* !defined(TOR_REPLAYCACHE_H) */ diff --git a/src/or/router.c b/src/or/router.c index 55b58e5d11..010ee339a0 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1073,7 +1073,7 @@ init_keys(void) /* 4. Build our router descriptor. */ /* Must be called after keys are initialized. */ mydesc = router_get_my_descriptor(); - if (authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL)) { + if (authdir_mode_v3(options)) { const char *m = NULL; routerinfo_t *ri; /* We need to add our own fingerprint so it gets recognized. */ @@ -1604,32 +1604,19 @@ authdir_mode_v3(const or_options_t *options) { return authdir_mode(options) && options->V3AuthoritativeDir != 0; } -/** Return true iff we are a v3 directory authority. */ -int -authdir_mode_any_main(const or_options_t *options) -{ - return options->V3AuthoritativeDir; -} -/** Return true if we believe ourselves to be any kind of - * authoritative directory beyond just a hidserv authority. */ -int -authdir_mode_any_nonhidserv(const or_options_t *options) -{ - return options->BridgeAuthoritativeDir || - authdir_mode_any_main(options); -} /** Return true iff we are an authoritative directory server that is * authoritative about receiving and serving descriptors of type - * <b>purpose</b> on its dirport. Use -1 for "any purpose". */ + * <b>purpose</b> on its dirport. + */ int authdir_mode_handles_descs(const or_options_t *options, int purpose) { - if (purpose < 0) - return authdir_mode_any_nonhidserv(options); + if (BUG(purpose < 0)) /* Deprecated. */ + return authdir_mode(options); else if (purpose == ROUTER_PURPOSE_GENERAL) - return authdir_mode_any_main(options); + return authdir_mode_v3(options); else if (purpose == ROUTER_PURPOSE_BRIDGE) - return (options->BridgeAuthoritativeDir); + return authdir_mode_bridge(options); else return 0; } @@ -1641,7 +1628,7 @@ authdir_mode_publishes_statuses(const or_options_t *options) { if (authdir_mode_bridge(options)) return 0; - return authdir_mode_any_nonhidserv(options); + return authdir_mode(options); } /** Return true iff we are an authoritative directory server that * tests reachability of the descriptors it learns about. @@ -1649,7 +1636,7 @@ authdir_mode_publishes_statuses(const or_options_t *options) int authdir_mode_tests_reachability(const or_options_t *options) { - return authdir_mode_handles_descs(options, -1); + return authdir_mode(options); } /** Return true iff we believe ourselves to be a bridge authoritative * directory server. @@ -1877,7 +1864,7 @@ static const char *desc_gen_reason = NULL; * now. */ static time_t desc_clean_since = 0; /** Why did we mark the descriptor dirty? */ -static const char *desc_dirty_reason = NULL; +static const char *desc_dirty_reason = "Tor just started"; /** Boolean: do we need to regenerate the above? */ static int desc_needs_upload = 0; @@ -1958,7 +1945,7 @@ router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port) desc_routerinfo->ipv6_exit_policy && compare_tor_addr_to_short_policy(addr, port, me->ipv6_exit_policy) != ADDR_POLICY_ACCEPTED; -#endif +#endif /* 0 */ } else { return -1; } @@ -2300,7 +2287,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) if (!strcasecmp(name, options->Nickname)) continue; /* Don't list ourself, that's redundant */ else - member = node_get_by_nickname(name, 1); + member = node_get_by_nickname(name, 0); if (!member) { int is_legal = is_legal_nickname_or_hexdigest(name); if (!smartlist_contains_string(warned_nonexistent_family, name) && @@ -2971,10 +2958,13 @@ router_dump_router_to_string(routerinfo_t *router, if (options->BridgeRelay) { const char *bd; - if (options->PublishServerDescriptor_ & BRIDGE_DIRINFO) + if (options->BridgeDistribution && strlen(options->BridgeDistribution)) { + bd = options->BridgeDistribution; + } else { bd = "any"; - else - bd = "none"; + } + if (strchr(bd, '\n') || strchr(bd, '\r')) + bd = escaped(bd); smartlist_add_asprintf(chunks, "bridge-distribution-request %s\n", bd); } @@ -3039,7 +3029,6 @@ router_dump_router_to_string(routerinfo_t *router, crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1); - note_crypto_pk_op(SIGN_RTR); { char *sig; if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) { @@ -3070,7 +3059,7 @@ router_dump_router_to_string(routerinfo_t *router, tor_free(s_dup); routerinfo_free(ri_tmp); } -#endif +#endif /* defined(DEBUG_ROUTER_DUMP_ROUTER_TO_STRING) */ goto done; @@ -3520,7 +3509,7 @@ router_get_description(char *buf, const routerinfo_t *ri) return "<null>"; return format_node_description(buf, ri->cache_info.identity_digest, - router_is_named(ri), + 0, ri->nickname, NULL, ri->addr); @@ -3630,7 +3619,7 @@ routerstatus_describe(const routerstatus_t *rs) return routerstatus_get_description(buf, rs); } -/** Return a human-readable description of the extend_info_t <b>ri</b>. +/** Return a human-readable description of the extend_info_t <b>ei</b>. * * This function is not thread-safe. Each call to this function invalidates * previous values returned by this function. @@ -3646,21 +3635,16 @@ extend_info_describe(const extend_info_t *ei) * verbose representation of the identity of <b>router</b>. The format is: * A dollar sign. * The upper-case hexadecimal encoding of the SHA1 hash of router's identity. - * A "=" if the router is named; a "~" if it is not. + * A "=" if the router is named (no longer implemented); a "~" if it is not. * The router's nickname. **/ void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { - const char *good_digest = networkstatus_get_router_digest_by_nickname( - router->nickname); - int is_named = good_digest && tor_memeq(good_digest, - router->cache_info.identity_digest, - DIGEST_LEN); buf[0] = '$'; base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + buf[1+HEX_DIGEST_LEN] = '~'; strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); } diff --git a/src/or/router.h b/src/or/router.h index 9c5def5218..3351400911 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -54,8 +54,6 @@ int net_is_disabled(void); int authdir_mode(const or_options_t *options); int authdir_mode_v3(const or_options_t *options); -int authdir_mode_any_main(const or_options_t *options); -int authdir_mode_any_nonhidserv(const or_options_t *options); int authdir_mode_handles_descs(const or_options_t *options, int purpose); int authdir_mode_publishes_statuses(const or_options_t *options); int authdir_mode_tests_reachability(const or_options_t *options); @@ -162,5 +160,5 @@ STATIC void get_platform_str(char *platform, size_t len); STATIC int router_write_fingerprint(int hashed); #endif -#endif +#endif /* !defined(TOR_ROUTER_H) */ diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index fd4c6ce0dd..7295c19653 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -1134,7 +1134,109 @@ init_mock_ed_keys(const crypto_pk_t *rsa_identity_key) } #undef MAKEKEY #undef MAKECERT -#endif +#endif /* defined(TOR_UNIT_TESTS) */ + +/** + * Print the ISO8601-formated <b>expiration</b> for a certificate with + * some <b>description</b> to stdout. + * + * For example, for a signing certificate, this might print out: + * signing-cert-expiry: 2017-07-25 08:30:15 UTC + */ +static void +print_cert_expiration(const char *expiration, + const char *description) +{ + fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration); +} + +/** + * Log when a certificate, <b>cert</b>, with some <b>description</b> and + * stored in a file named <b>fname</b>, is going to expire. + */ +static void +log_ed_cert_expiration(const tor_cert_t *cert, + const char *description, + const char *fname) { + char expiration[ISO_TIME_LEN+1]; + + if (BUG(!cert)) { /* If the specified key hasn't been loaded */ + log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.", + description); + } else { + format_local_iso_time(expiration, cert->valid_until); + log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.", + description, fname, expiration); + print_cert_expiration(expiration, description); + } +} + +/** + * Log when our master signing key certificate expires. Used when tor is given + * the --key-expiration command-line option. + * + * Returns 0 on success and 1 on failure. + */ +static int +log_master_signing_key_cert_expiration(const or_options_t *options) +{ + const tor_cert_t *signing_key; + char *fn = NULL; + int failed = 0; + time_t now = approx_time(); + + fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert"); + + /* Try to grab our cached copy of the key. */ + signing_key = get_master_signing_key_cert(); + + tor_assert(server_identity_key_is_set()); + + /* Load our keys from disk, if necessary. */ + if (!signing_key) { + failed = load_ed_keys(options, now) < 0; + signing_key = get_master_signing_key_cert(); + } + + /* If we do have a signing key, log the expiration time. */ + if (signing_key) { + log_ed_cert_expiration(signing_key, "signing", fn); + } else { + log_warn(LD_OR, "Could not load signing key certificate from %s, so " \ + "we couldn't learn anything about certificate expiration.", fn); + } + + tor_free(fn); + + return failed; +} + +/** + * Log when a key certificate expires. Used when tor is given the + * --key-expiration command-line option. + * + * If an command argument is given, which should specify the type of + * key to get expiry information about (currently supported arguments + * are "sign"), get info about that type of certificate. Otherwise, + * print info about the supported arguments. + * + * Returns 0 on success and -1 on failure. + */ +int +log_cert_expiration(void) +{ + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + + if (!strcmp(arg, "sign")) { + return log_master_signing_key_cert_expiration(options); + } else { + fprintf(stderr, "No valid argument to --key-expiration found!\n"); + fprintf(stderr, "Currently recognised arguments are: 'sign'\n"); + + return -1; + } +} const ed25519_public_key_t * get_master_identity_key(void) @@ -1160,7 +1262,7 @@ get_master_identity_keypair(void) { return master_identity_key; } -#endif +#endif /* defined(TOR_UNIT_TESTS) */ const ed25519_keypair_t * get_master_signing_keypair(void) diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index c10cf32a71..3e67952ea0 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -63,6 +63,7 @@ MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert, const ed25519_public_key_t *master_id_pkey, const uint8_t *rsa_id_digest)); +int log_cert_expiration(void); int load_ed_keys(const or_options_t *options, time_t now); int should_make_new_ed_keys(const or_options_t *options, const time_t now); @@ -80,5 +81,5 @@ const ed25519_keypair_t *get_master_identity_keypair(void); void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key); #endif -#endif +#endif /* !defined(TOR_ROUTERKEYS_H) */ diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 4f4d417cce..af4f67dc12 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -150,7 +150,8 @@ typedef struct cert_list_t cert_list_t; /* static function prototypes */ static int compute_weighted_bandwidths(const smartlist_t *sl, bandwidth_weight_rule_t rule, - double **bandwidths_out); + double **bandwidths_out, + double *total_bandwidth_out); static const routerstatus_t *router_pick_trusteddirserver_impl( const smartlist_t *sourcelist, dirinfo_type_t auth, int flags, int *n_busy_out); @@ -1872,7 +1873,7 @@ router_picked_poor_directory_log(const routerstatus_t *rs) || !router_have_minimum_dir_info()) { return; } -#endif +#endif /* !LOG_FALSE_POSITIVES_DURING_BOOTSTRAP */ /* We couldn't find a node, or the one we have doesn't fit our preferences. * Sometimes this is normal, sometimes it can be a reachability issue. */ @@ -2306,7 +2307,7 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) { /* XXXX MOVE ? */ node_t fake_node; - const node_t *node = node_get_by_id(router->cache_info.identity_digest);; + const node_t *node = node_get_by_id(router->cache_info.identity_digest); if (node == NULL) { memset(&fake_node, 0, sizeof(fake_node)); fake_node.ri = (routerinfo_t *)router; @@ -2506,7 +2507,7 @@ smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl, double *bandwidths_dbl=NULL; uint64_t *bandwidths_u64=NULL; - if (compute_weighted_bandwidths(sl, rule, &bandwidths_dbl) < 0) + if (compute_weighted_bandwidths(sl, rule, &bandwidths_dbl, NULL) < 0) return NULL; bandwidths_u64 = tor_calloc(smartlist_len(sl), sizeof(uint64_t)); @@ -2526,18 +2527,24 @@ smartlist_choose_node_by_bandwidth_weights(const smartlist_t *sl, * smartlist_choose_node_by_bandwidth_weights, compute weighted bandwidth * values for each node and store them in a freshly allocated * *<b>bandwidths_out</b> of the same length as <b>sl</b>, and holding results - * as doubles. Return 0 on success, -1 on failure. */ + * as doubles. If <b>total_bandwidth_out</b> is non-NULL, set it to the total + * of all the bandwidths. + * Return 0 on success, -1 on failure. */ static int compute_weighted_bandwidths(const smartlist_t *sl, bandwidth_weight_rule_t rule, - double **bandwidths_out) + double **bandwidths_out, + double *total_bandwidth_out) { int64_t weight_scale; double Wg = -1, Wm = -1, We = -1, Wd = -1; double Wgb = -1, Wmb = -1, Web = -1, Wdb = -1; - uint64_t weighted_bw = 0; guardfraction_bandwidth_t guardfraction_bw; - double *bandwidths; + double *bandwidths = NULL; + double total_bandwidth = 0.0; + + tor_assert(sl); + tor_assert(bandwidths_out); /* Can't choose exit and guard at same time */ tor_assert(rule == NO_WEIGHTING || @@ -2546,6 +2553,12 @@ compute_weighted_bandwidths(const smartlist_t *sl, rule == WEIGHT_FOR_MID || rule == WEIGHT_FOR_DIR); + *bandwidths_out = NULL; + + if (total_bandwidth_out) { + *total_bandwidth_out = 0.0; + } + if (smartlist_len(sl) == 0) { log_info(LD_CIRC, "Empty routerlist passed in to consensus weight node " @@ -2706,17 +2719,22 @@ compute_weighted_bandwidths(const smartlist_t *sl, final_weight = weight*this_bw; } - bandwidths[node_sl_idx] = final_weight + 0.5; + bandwidths[node_sl_idx] = final_weight; + total_bandwidth += final_weight; } SMARTLIST_FOREACH_END(node); log_debug(LD_CIRC, "Generated weighted bandwidths for rule %s based " "on weights " - "Wg=%f Wm=%f We=%f Wd=%f with total bw "U64_FORMAT, + "Wg=%f Wm=%f We=%f Wd=%f with total bw %f", bandwidth_weight_rule_to_string(rule), - Wg, Wm, We, Wd, U64_PRINTF_ARG(weighted_bw)); + Wg, Wm, We, Wd, total_bandwidth); *bandwidths_out = bandwidths; + if (total_bandwidth_out) { + *total_bandwidth_out = total_bandwidth; + } + return 0; } @@ -2733,7 +2751,8 @@ frac_nodes_with_descriptors(const smartlist_t *sl, if (smartlist_len(sl) == 0) return 0.0; - if (compute_weighted_bandwidths(sl, rule, &bandwidths) < 0) { + if (compute_weighted_bandwidths(sl, rule, &bandwidths, &total) < 0 || + total <= 0.0) { int n_with_descs = 0; SMARTLIST_FOREACH(sl, const node_t *, node, { if (node_has_descriptor(node)) @@ -2742,19 +2761,14 @@ frac_nodes_with_descriptors(const smartlist_t *sl, return ((double)n_with_descs) / (double)smartlist_len(sl); } - total = present = 0.0; + present = 0.0; SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { - const double bw = bandwidths[node_sl_idx]; - total += bw; if (node_has_descriptor(node)) - present += bw; + present += bandwidths[node_sl_idx]; } SMARTLIST_FOREACH_END(node); tor_free(bandwidths); - if (total < 1.0) - return 0; - return present / total; } @@ -2799,6 +2813,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int need_desc = (flags & CRN_NEED_DESC) != 0; const int pref_addr = (flags & CRN_PREF_ADDR) != 0; const int direct_conn = (flags & CRN_DIRECT_CONN) != 0; + const int rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); @@ -2810,12 +2825,19 @@ router_choose_random_node(smartlist_t *excludedsmartlist, rule = weight_for_exit ? WEIGHT_FOR_EXIT : (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID); - /* Exclude relays that allow single hop exit circuits. This is an obsolete - * option since 0.2.9.2-alpha and done by default in 0.3.1.0-alpha. */ - SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { if (node_allows_single_hop_exits(node)) { + /* Exclude relays that allow single hop exit circuits. This is an + * obsolete option since 0.2.9.2-alpha and done by default in + * 0.3.1.0-alpha. */ smartlist_add(excludednodes, node); - }); + } else if (rendezvous_v3 && + !node_supports_v3_rendezvous_point(node)) { + /* Exclude relays that do not support to rendezvous for a hidden service + * version 3. */ + smartlist_add(excludednodes, node); + } + } SMARTLIST_FOREACH_END(node); /* If the node_t is not found we won't be to exclude ourself but we * won't be able to pick ourself in router_choose_random_node() so @@ -2929,7 +2951,7 @@ hex_digest_nickname_decode(const char *hexdigest, * <b>hexdigest</b> is malformed, or it doesn't match. */ int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named) + const char *nickname) { char digest[DIGEST_LEN]; char nn_char='\0'; @@ -2938,30 +2960,20 @@ hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, if (hex_digest_nickname_decode(hexdigest, digest, &nn_char, nn_buf) == -1) return 0; - if (nn_char == '=' || nn_char == '~') { - if (!nickname) + if (nn_char == '=') { + return 0; + } + + if (nn_char == '~') { + if (!nickname) // XXX This seems wrong. -NM return 0; if (strcasecmp(nn_buf, nickname)) return 0; - if (nn_char == '=' && !is_named) - return 0; } return tor_memeq(digest, identity_digest, DIGEST_LEN); } -/** Return true iff <b>router</b> is listed as named in the current - * consensus. */ -int -router_is_named(const routerinfo_t *router) -{ - const char *digest = - networkstatus_get_router_digest_by_nickname(router->nickname); - - return (digest && - tor_memeq(digest, router->cache_info.identity_digest, DIGEST_LEN)); -} - /** Return true iff <b>digest</b> is the digest of the identity key of a * trusted directory matching at least one bit of <b>type</b>. If <b>type</b> * is zero (NO_DIRINFO), or ALL_DIRINFO, any authority is okay. */ @@ -4057,7 +4069,7 @@ routerlist_remove_old_cached_routers_with_id(time_t now, signed_descriptor_t *r = smartlist_get(lst, i); tor_assert(tor_memeq(ident, r->identity_digest, DIGEST_LEN)); } -#endif +#endif /* 1 */ /* Check whether we need to do anything at all. */ { int mdpr = directory_caches_dir_info(get_options()) ? 2 : 1; @@ -4974,8 +4986,9 @@ max_dl_per_request(const or_options_t *options, int purpose) } /** Don't split our requests so finely that we are requesting fewer than - * this number per server. */ -#define MIN_DL_PER_REQUEST 4 + * this number per server. (Grouping more than this at once leads to + * diminishing returns.) */ +#define MIN_DL_PER_REQUEST 32 /** To prevent a single screwy cache from confusing us by selective reply, * try to split our requests into at least this many requests. */ #define MIN_REQUESTS 3 @@ -5041,7 +5054,7 @@ launch_descriptor_downloads(int purpose, } } - if (!authdir_mode_any_nonhidserv(options)) { + if (!authdir_mode(options)) { /* If we wind up going to the authorities, we want to only open one * connection to each authority at a time, so that we don't overload * them. We do this by setting PDS_NO_EXISTING_SERVERDESC_FETCH @@ -5063,8 +5076,9 @@ launch_descriptor_downloads(int purpose, if (n_per_request > max_dl_per_req) n_per_request = max_dl_per_req; - if (n_per_request < MIN_DL_PER_REQUEST) - n_per_request = MIN_DL_PER_REQUEST; + if (n_per_request < MIN_DL_PER_REQUEST) { + n_per_request = MIN(MIN_DL_PER_REQUEST, n_downloadable); + } if (n_downloadable > n_per_request) req_plural = rtr_plural = "s"; @@ -5172,7 +5186,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, smartlist_add(downloadable, rs->descriptor_digest); } SMARTLIST_FOREACH_END(rsp); - if (!authdir_mode_handles_descs(options, ROUTER_PURPOSE_GENERAL) + if (!authdir_mode_v3(options) && smartlist_len(no_longer_old)) { routerlist_t *rl = router_get_routerlist(); log_info(LD_DIR, "%d router descriptors listed in consensus are " diff --git a/src/or/routerlist.h b/src/or/routerlist.h index e0ed4e623a..8384c7eb8c 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -80,7 +80,6 @@ const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -int router_is_named(const routerinfo_t *router); int router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type); #define router_digest_is_trusted_dir(d) \ @@ -228,7 +227,7 @@ int hex_digest_nickname_decode(const char *hexdigest, char *nickname_out); int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named); + const char *nickname); #ifdef ROUTERLIST_PRIVATE STATIC int choose_array_element_by_weight(const uint64_t *entries, @@ -252,7 +251,7 @@ MOCK_DECL(STATIC void, initiate_descriptor_downloads, STATIC int router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc, int microdesc); -#endif +#endif /* defined(ROUTERLIST_PRIVATE) */ -#endif +#endif /* !defined(TOR_ROUTERLIST_H) */ diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 22521a3069..15cdb0bbde 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -388,9 +388,9 @@ static int check_signature_token(const char *digest, log_debug(LD_MM, "Area for %s has %lu allocated; using %lu.", \ name, (unsigned long)alloc, (unsigned long)used); \ STMT_END -#else +#else /* !(defined(DEBUG_AREA_ALLOC)) */ #define DUMP_AREA(a,name) STMT_NIL -#endif +#endif /* defined(DEBUG_AREA_ALLOC) */ /* Dump mechanism for unparseable descriptors */ @@ -701,7 +701,7 @@ dump_desc_populate_one_file, (const char *dirname, const char *f)) goto done; /* LCOV_EXCL_STOP */ } -#endif +#endif /* SIZE_MAX > UINT64_MAX */ if (BUG(st.st_size < 0)) { /* LCOV_EXCL_START * Should be impossible, since the OS isn't supposed to be b0rken. */ @@ -1425,9 +1425,9 @@ dump_distinct_digest_count(int severity) verified_digests = digestmap_new(); tor_log(severity, LD_GENERAL, "%d *distinct* router digests verified", digestmap_size(verified_digests)); -#else +#else /* !(defined(COUNT_DISTINCT_DIGESTS)) */ (void)severity; /* suppress "unused parameter" warning */ -#endif +#endif /* defined(COUNT_DISTINCT_DIGESTS) */ } /** Try to find an IPv6 OR port in <b>list</b> of directory_token_t's @@ -1996,7 +1996,6 @@ router_parse_entry_from_string(const char *s, const char *end, } tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE); - note_crypto_pk_op(VERIFY_RTR); #ifdef COUNT_DISTINCT_DIGESTS if (!verified_digests) verified_digests = digestmap_new(); @@ -2231,7 +2230,6 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, } if (key) { - note_crypto_pk_op(VERIFY_RTR); if (check_signature_token(digest, DIGEST_LEN, tok, key, 0, "extra-info") < 0) goto err; @@ -2708,7 +2706,11 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->supports_ed25519_hs_intro = protocol_list_supports_protocol(tok->args[0], PRT_HSINTRO, 4); rs->supports_v3_hsdir = - protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, 2); + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HSDIR_V3); + rs->supports_v3_rendezvous_point = + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HS_RENDEZVOUS_POINT_V3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -2720,6 +2722,12 @@ routerstatus_parse_entry_from_string(memarea_t *area, tor_version_as_new_as(tok->args[0], "0.2.4.8-alpha"); rs->protocols_known = 1; } + if (!strcmpstart(tok->args[0], "Tor ") && found_protocol_list) { + /* Bug #22447 forces us to filter on this version. */ + if (!tor_version_as_new_as(tok->args[0], "0.3.0.8")) { + rs->supports_v3_hsdir = 0; + } + } if (vote_rs) { vote_rs->version = tor_strdup(tok->args[0]); } @@ -2856,7 +2864,6 @@ compare_vote_routerstatus_entries(const void **_a, const void **_b) int networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) { - int64_t weight_scale; int64_t G=0, M=0, E=0, D=0, T=0; double Wgg, Wgm, Wgd, Wmg, Wmm, Wme, Wmd, Weg, Wem, Wee, Wed; double Gtotal=0, Mtotal=0, Etotal=0; @@ -2864,7 +2871,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) int valid = 1; (void) consensus_method; - weight_scale = networkstatus_get_weight_scale_param(ns); + const int64_t weight_scale = networkstatus_get_weight_scale_param(ns); + tor_assert(weight_scale >= 1); Wgg = networkstatus_get_bw_weight(ns, "Wgg", -1); Wgm = networkstatus_get_bw_weight(ns, "Wgm", -1); Wgd = networkstatus_get_bw_weight(ns, "Wgd", -1); @@ -2926,7 +2934,7 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) } Wgg /= weight_scale; - Wgm /= weight_scale; + Wgm /= weight_scale; (void) Wgm; // unused from here on. Wgd /= weight_scale; Wmg /= weight_scale; @@ -2934,8 +2942,8 @@ networkstatus_verify_bw_weights(networkstatus_t *ns, int consensus_method) Wme /= weight_scale; Wmd /= weight_scale; - Weg /= weight_scale; - Wem /= weight_scale; + Weg /= weight_scale; (void) Weg; // unused from here on. + Wem /= weight_scale; (void) Wem; // unused from here on. Wee /= weight_scale; Wed /= weight_scale; @@ -3360,8 +3368,8 @@ extract_shared_random_srvs(networkstatus_t *ns, smartlist_t *tokens) voter_identity = "consensus"; } - /* We extract both and on error, everything is stopped because it means - * the votes is malformed for the shared random value(s). */ + /* We extract both, and on error everything is stopped because it means + * the vote is malformed for the shared random value(s). */ if (extract_one_srv(tokens, K_PREVIOUS_SRV, &ns->sr_info.previous_srv) < 0) { log_warn(LD_DIR, "SR: Unable to parse previous SRV from %s", voter_identity); @@ -5288,7 +5296,6 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, } /* Parse and verify signature. */ tok = find_by_keyword(tokens, R_SIGNATURE); - note_crypto_pk_op(VERIFY_RTR); if (check_signature_token(desc_hash, DIGEST_LEN, tok, result->pk, 0, "v2 rendezvous service descriptor") < 0) goto err; diff --git a/src/or/routerparse.h b/src/or/routerparse.h index 088f773c5e..c4c8635f4b 100644 --- a/src/or/routerparse.h +++ b/src/or/routerparse.h @@ -133,9 +133,9 @@ MOCK_DECL(STATIC int, router_compute_hash_final,(char *digest, digest_algorithm_t alg)); MOCK_DECL(STATIC int, signed_digest_equals, (const uint8_t *d1, const uint8_t *d2, size_t len)); -#endif +#endif /* defined(ROUTERPARSE_PRIVATE) */ #define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1" -#endif +#endif /* !defined(TOR_ROUTERPARSE_H) */ diff --git a/src/or/routerset.c b/src/or/routerset.c index 4906c6a51d..54e26ef943 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -362,7 +362,7 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, /* No routers are specified by type; all are given by name or digest. * we can do a lookup in O(len(routerset)). */ SMARTLIST_FOREACH(routerset->list, const char *, name, { - const node_t *node = node_get_by_nickname(name, 1); + const node_t *node = node_get_by_nickname(name, 0); if (node) { if (!running_only || node->is_running) if (!routerset_contains_node(excludeset, node)) diff --git a/src/or/routerset.h b/src/or/routerset.h index a63677b471..d8819ef3fd 100644 --- a/src/or/routerset.h +++ b/src/or/routerset.h @@ -82,6 +82,6 @@ struct routerset_t { * reloaded. */ bitarray_t *countries; }; -#endif -#endif +#endif /* defined(ROUTERSET_PRIVATE) */ +#endif /* !defined(TOR_ROUTERSET_H) */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index fac545fba7..cbf51447bf 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -1,46 +1,55 @@ -/* * Copyright (c) 2013-2017, The Tor Project, Inc. */ +/* Copyright (c) 2013-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #include "or.h" - -#define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */ -#include "channel.h" +#include "config.h" #include "compat_libevent.h" #define SCHEDULER_PRIVATE_ +#define SCHEDULER_KIST_PRIVATE #include "scheduler.h" +#include "main.h" +#include "buffers.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channeltls.h" #include <event2/event.h> -/* - * Scheduler high/low watermarks - */ - -static uint32_t sched_q_low_water = 16384; -static uint32_t sched_q_high_water = 32768; - -/* - * Maximum cells to flush in a single call to channel_flush_some_cells(); - * setting this low means more calls, but too high and we could overshoot - * sched_q_high_water. - */ - -static uint32_t sched_max_flush_cells = 16; - /** * \file scheduler.c * \brief Channel scheduling system: decides which channels should send and * receive when. * - * This module implements a scheduler algorithm, to decide - * which channels should send/receive when. + * This module is the global/common parts of the scheduling system. This system + * is what decides what channels get to send cells on their circuits and when. + * + * Terms: + * - "Scheduling system": the collection of scheduler*.{h,c} files and their + * aggregate behavior. + * - "Scheduler implementation": a scheduler_t. The scheduling system has one + * active scheduling implementation at a time. + * + * In this file you will find state that any scheduler implementation can have + * access to as well as the functions the rest of Tor uses to interact with the + * scheduling system. * * The earliest versions of Tor approximated a kind of round-robin system - * among active connections, but only approximated it. + * among active connections, but only approximated it. It would only consider + * one connection (roughly equal to a channel in today's terms) at a time, and + * thus could only prioritize circuits against others on the same connection. + * + * Then in response to the KIST paper[0], Tor implemented a global + * circuit scheduler. It was supposed to prioritize circuits across many + * channels, but wasn't effective. It is preserved in scheduler_vanilla.c. * - * Now, write scheduling works by keeping track of which channels can - * accept cells, and have cells to write. From the scheduler's perspective, - * a channel can be in four possible states: + * [0]: http://www.robgjansen.com/publications/kist-sec2014.pdf + * + * Then we actually got around to implementing KIST for real. We decided to + * modularize the scheduler so new ones can be implemented. You can find KIST + * in scheduler_kist.c. + * + * Channels have one of four scheduling states based on whether or not they + * have cells to send and whether or not they are able to send. * * <ol> * <li> @@ -90,7 +99,7 @@ static uint32_t sched_max_flush_cells = 16; * <ul> * <li>Not open for writes/no cells by arrival of cells on an attached * circuit - * <li> Open for writes/has cells by filling an output buffer without + * <li>Open for writes/has cells by filling an output buffer without * draining all cells from attached circuits * </ul> * <li> Transitions to: @@ -107,9 +116,9 @@ static uint32_t sched_max_flush_cells = 16; * SCHED_CHAN_PENDING. * <li>Transitions from: * <ul> - * <li> Not open for writes/has cells by the connection_or_flushed_some() + * <li>Not open for writes/has cells by the connection_or_flushed_some() * path - * <li> Open for writes/no cells by the append_cell_to_circuit_queue() + * <li>Open for writes/no cells by the append_cell_to_circuit_queue() * path * </ul> * <li> Transitions to: @@ -125,85 +134,246 @@ static uint32_t sched_max_flush_cells = 16; * </ol> * * Other event-driven parts of the code move channels between these scheduling - * states by calling scheduler functions; the scheduler only runs on open-for- - * writes/has-cells channels and is the only path for those to transition to - * other states. The scheduler_run() function gives us the opportunity to do - * scheduling work, and is called from other scheduler functions whenever a - * state transition occurs, and periodically from the main event loop. + * states by calling scheduler functions. The scheduling system builds up a + * list of channels in the SCHED_CHAN_PENDING state that the scheduler + * implementation should then use when it runs. Scheduling implementations need + * to properly update channel states during their scheduler_t->run() function + * as that is the only opportunity for channels to move from SCHED_CHAN_PENDING + * to any other state. + * + * The remainder of this file is a small amount of state that any scheduler + * implementation should have access to, and the functions the rest of Tor uses + * to interact with the scheduling system. */ -/* Scheduler global data structures */ +/***************************************************************************** + * Scheduling system state + * + * State that can be accessed from any scheduler implementation (but not + * outside the scheduling system) + *****************************************************************************/ -/* +/** DOCDOC */ +STATIC const scheduler_t *the_scheduler; + +/** * We keep a list of channels that are pending - i.e, have cells to write - * and can accept them to send. The enum scheduler_state in channel_t + * and can accept them to send. The enum scheduler_state in channel_t * is reserved for our use. + * + * Priority queue of channels that can write and have cells (pending work) */ - -/* Pqueue of channels that can write and have cells (pending work) */ STATIC smartlist_t *channels_pending = NULL; -/* +/** * This event runs the scheduler from its callback, and is manually * activated whenever a channel enters open for writes/cells to send. */ - STATIC struct event *run_sched_ev = NULL; -/* - * Queue heuristic; this is not the queue size, but an 'effective queuesize' - * that ages out contributions from stalled channels. - */ +static int have_logged_kist_suddenly_disabled = 0; -STATIC uint64_t queue_heuristic = 0; +/***************************************************************************** + * Scheduling system static function definitions + * + * Functions that can only be accessed from this file. + *****************************************************************************/ -/* - * Timestamp for last queue heuristic update +/** Return a human readable string for the given scheduler type. */ +static const char * +get_scheduler_type_string(scheduler_types_t type) +{ + switch (type) { + case SCHEDULER_VANILLA: + return "Vanilla"; + case SCHEDULER_KIST: + return "KIST"; + case SCHEDULER_KIST_LITE: + return "KISTLite"; + case SCHEDULER_NONE: + /* fallthrough */ + default: + tor_assert_unreached(); + return "(N/A)"; + } +} + +/** + * Scheduler event callback; this should get triggered once per event loop + * if any scheduling work was created during the event loop. */ +static void +scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +{ + (void) fd; + (void) events; + (void) arg; -STATIC time_t queue_heuristic_timestamp = 0; + log_debug(LD_SCHED, "Scheduler event callback called"); -/* Scheduler static function declarations */ + /* Run the scheduler. This is a mandatory function. */ -static void scheduler_evt_callback(evutil_socket_t fd, - short events, void *arg); -static int scheduler_more_work(void); -static void scheduler_retrigger(void); -#if 0 -static void scheduler_trigger(void); -#endif + /* We might as well assert on this. If this function doesn't exist, no cells + * are getting scheduled. Things are very broken. scheduler_t says the run() + * function is mandatory. */ + tor_assert(the_scheduler->run); + the_scheduler->run(); -/* Scheduler function implementations */ + /* Schedule itself back in if it has more work. */ -/** Free everything and shut down the scheduling system */ + /* Again, might as well assert on this mandatory scheduler_t function. If it + * doesn't exist, there's no way to tell libevent to run the scheduler again + * in the future. */ + tor_assert(the_scheduler->schedule); + the_scheduler->schedule(); +} -void -scheduler_free_all(void) +/** Using the global options, select the scheduler we should be using. */ +static void +select_scheduler(void) { - log_debug(LD_SCHED, "Shutting down scheduler"); - - if (run_sched_ev) { - if (event_del(run_sched_ev) < 0) { - log_warn(LD_BUG, "Problem deleting run_sched_ev"); + scheduler_t *new_scheduler = NULL; + +#ifdef TOR_UNIT_TESTS + /* This is hella annoying to set in the options for every test that passes + * through the scheduler and there are many so if we don't explicitly have + * a list of types set, just put the vanilla one. */ + if (get_options()->SchedulerTypes_ == NULL) { + the_scheduler = get_vanilla_scheduler(); + return; + } +#endif /* defined(TOR_UNIT_TESTS) */ + + /* This list is ordered that is first entry has the first priority. Thus, as + * soon as we find a scheduler type that we can use, we use it and stop. */ + SMARTLIST_FOREACH_BEGIN(get_options()->SchedulerTypes_, int *, type) { + switch (*type) { + case SCHEDULER_VANILLA: + new_scheduler = get_vanilla_scheduler(); + goto end; + case SCHEDULER_KIST: + if (!scheduler_can_use_kist()) { +#ifdef HAVE_KIST_SUPPORT + if (!have_logged_kist_suddenly_disabled) { + /* We should only log this once in most cases. If it was the kernel + * losing support for kist that caused scheduler_can_use_kist() to + * return false, then this flag makes sure we only log this message + * once. If it was the consensus that switched from "yes use kist" + * to "no don't use kist", then we still set the flag so we log + * once, but we unset the flag elsewhere if we ever can_use_kist() + * again. + */ + have_logged_kist_suddenly_disabled = 1; + log_notice(LD_SCHED, "Scheduler type KIST has been disabled by " + "the consensus or no kernel support."); + } +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + log_info(LD_SCHED, "Scheduler type KIST not built in"); +#endif /* defined(HAVE_KIST_SUPPORT) */ + continue; + } + /* This flag will only get set in one of two cases: + * 1 - the kernel lost support for kist. In that case, we don't expect to + * ever end up here + * 2 - the consensus went from "yes use kist" to "no don't use kist". + * We might end up here if the consensus changes back to "yes", in which + * case we might want to warn the user again if it goes back to "no" + * yet again. Thus we unset the flag */ + have_logged_kist_suddenly_disabled = 0; + new_scheduler = get_kist_scheduler(); + scheduler_kist_set_full_mode(); + goto end; + case SCHEDULER_KIST_LITE: + new_scheduler = get_kist_scheduler(); + scheduler_kist_set_lite_mode(); + goto end; + case SCHEDULER_NONE: + /* fallthrough */ + default: + /* Our option validation should have caught this. */ + tor_assert_unreached(); } - tor_event_free(run_sched_ev); - run_sched_ev = NULL; + } SMARTLIST_FOREACH_END(type); + + end: + if (new_scheduler == NULL) { + log_err(LD_SCHED, "Tor was unable to select a scheduler type. Please " + "make sure Schedulers is correctly configured with " + "what Tor does support."); + /* We weren't able to choose a scheduler which means that none of the ones + * set in Schedulers are supported or usable. We will respect the user + * wishes of using what it has been configured and don't do a sneaky + * fallback. Because this can be changed at runtime, we have to stop tor + * right now. */ + exit(1); } - if (channels_pending) { - smartlist_free(channels_pending); - channels_pending = NULL; - } + /* Set the chosen scheduler. */ + the_scheduler = new_scheduler; } /** - * Comparison function to use when sorting pending channels + * Helper function called from a few different places. It changes the + * scheduler implementation, if necessary. And if it did, it then tells the + * old one to free its state and the new one to initialize. */ +static void +set_scheduler(void) +{ + const scheduler_t *old_scheduler = the_scheduler; + scheduler_types_t old_scheduler_type = SCHEDULER_NONE; + + /* We keep track of the type in order to log only if the type switched. We + * can't just use the scheduler pointers because KIST and KISTLite share the + * same object. */ + if (the_scheduler) { + old_scheduler_type = the_scheduler->type; + } -MOCK_IMPL(STATIC int, + /* From the options, select the scheduler type to set. */ + select_scheduler(); + tor_assert(the_scheduler); + + /* We look at the pointer difference in case the old sched and new sched + * share the same scheduler object, as is the case with KIST and KISTLite. */ + if (old_scheduler != the_scheduler) { + /* Allow the old scheduler to clean up, if needed. */ + if (old_scheduler && old_scheduler->free_all) { + old_scheduler->free_all(); + } + + /* Initialize the new scheduler. */ + if (the_scheduler->init) { + the_scheduler->init(); + } + } + + /* Finally we notice log if we switched schedulers. We use the type in case + * two schedulers share a scheduler object. */ + if (old_scheduler_type != the_scheduler->type) { + log_notice(LD_CONFIG, "Scheduler type %s has been enabled.", + get_scheduler_type_string(the_scheduler->type)); + } +} + +/***************************************************************************** + * Scheduling system private function definitions + * + * Functions that can only be accessed from scheduler*.c + *****************************************************************************/ + +/** Return the pending channel list. */ +smartlist_t * +get_channels_pending(void) +{ + return channels_pending; +} + +/** Comparison function to use when sorting pending channels. */ +MOCK_IMPL(int, scheduler_compare_channels, (const void *c1_v, const void *c2_v)) { - channel_t *c1 = NULL, *c2 = NULL; + const channel_t *c1 = NULL, *c2 = NULL; /* These are a workaround for -Wbad-function-cast throwing a fit */ const circuitmux_policy_t *p1, *p2; uintptr_t p1_i, p2_i; @@ -211,11 +381,8 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v)) tor_assert(c1_v); tor_assert(c2_v); - c1 = (channel_t *)(c1_v); - c2 = (channel_t *)(c2_v); - - tor_assert(c1); - tor_assert(c2); + c1 = (const channel_t *)(c1_v); + c2 = (const channel_t *)(c2_v); if (c1 != c2) { if (circuitmux_get_policy(c1->cmux) == @@ -242,36 +409,84 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v)) } } -/* - * Scheduler event callback; this should get triggered once per event loop - * if any scheduling work was created during the event loop. - */ +/***************************************************************************** + * Scheduling system global functions + * + * Functions that can be accessed from anywhere in Tor. + *****************************************************************************/ -static void -scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +/** + * This is how the scheduling system is notified of Tor's configuration + * changing. For example: a SIGHUP was issued. + */ +void +scheduler_conf_changed(void) { - (void)fd; - (void)events; - (void)arg; - log_debug(LD_SCHED, "Scheduler event callback called"); + /* Let the scheduler decide what it should do. */ + set_scheduler(); - tor_assert(run_sched_ev); + /* Then tell the (possibly new) scheduler that we have new options. */ + if (the_scheduler->on_new_options) { + the_scheduler->on_new_options(); + } +} - /* Run the scheduler */ - scheduler_run(); +/** + * Whenever we get a new consensus, this function is called. + */ +void +scheduler_notify_networkstatus_changed(const networkstatus_t *old_c, + const networkstatus_t *new_c) +{ + /* Maybe the consensus param made us change the scheduler. */ + set_scheduler(); - /* Do we have more work to do? */ - if (scheduler_more_work()) scheduler_retrigger(); + /* Then tell the (possibly new) scheduler that we have a new consensus */ + if (the_scheduler->on_new_consensus) { + the_scheduler->on_new_consensus(old_c, new_c); + } } -/** Mark a channel as no longer ready to accept writes */ +/** + * Free everything scheduling-related from main.c. Note this is only called + * when Tor is shutting down, while scheduler_t->free_all() is called both when + * Tor is shutting down and when we are switching schedulers. + */ +void +scheduler_free_all(void) +{ + log_debug(LD_SCHED, "Shutting down scheduler"); + if (run_sched_ev) { + if (event_del(run_sched_ev) < 0) { + log_warn(LD_BUG, "Problem deleting run_sched_ev"); + } + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } + + if (channels_pending) { + /* We don't have ownership of the objects in this list. */ + smartlist_free(channels_pending); + channels_pending = NULL; + } + + if (the_scheduler && the_scheduler->free_all) { + the_scheduler->free_all(); + } + the_scheduler = NULL; +} + +/** Mark a channel as no longer ready to accept writes. */ MOCK_IMPL(void, scheduler_channel_doesnt_want_writes,(channel_t *chan)) { - tor_assert(chan); - - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } /* If it's already in pending, we can put it in waiting_to_write */ if (chan->scheduler_state == SCHED_CHAN_PENDING) { @@ -282,7 +497,7 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan)) */ smartlist_pqueue_remove(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; log_debug(LD_SCHED, @@ -304,17 +519,18 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan)) } } -/** Mark a channel as having waiting cells */ - +/** Mark a channel as having waiting cells. */ MOCK_IMPL(void, scheduler_channel_has_waiting_cells,(channel_t *chan)) { - int became_pending = 0; - - tor_assert(chan); - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } - /* First, check if this one also writeable */ + /* First, check if it's also writeable */ if (chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS) { /* * It's in channels_waiting_for_cells, so it shouldn't be in any of @@ -324,13 +540,15 @@ scheduler_channel_has_waiting_cells,(channel_t *chan)) chan->scheduler_state = SCHED_CHAN_PENDING; smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_for_cells " "to pending", U64_PRINTF_ARG(chan->global_identifier), chan); - became_pending = 1; + /* If we made a channel pending, we potentially have scheduling work to + * do. */ + the_scheduler->schedule(); } else { /* * It's not in waiting_for_cells, so it can't become pending; it's @@ -345,250 +563,119 @@ scheduler_channel_has_waiting_cells,(channel_t *chan)) U64_PRINTF_ARG(chan->global_identifier), chan); } } +} - /* - * If we made a channel pending, we potentially have scheduling work - * to do. - */ - if (became_pending) scheduler_retrigger(); +/** Add the scheduler event to the set of pending events with next_run being + * the longest time libevent should wait before triggering the event. */ +void +scheduler_ev_add(const struct timeval *next_run) +{ + tor_assert(run_sched_ev); + tor_assert(next_run); + if (BUG(event_add(run_sched_ev, next_run) < 0)) { + log_warn(LD_SCHED, "Adding to libevent failed. Next run time was set to: " + "%ld.%06ld", next_run->tv_sec, (long)next_run->tv_usec); + return; + } } -/** Set up the scheduling system */ +/** Make the scheduler event active with the given flags. */ +void +scheduler_ev_active(int flags) +{ + tor_assert(run_sched_ev); + event_active(run_sched_ev, flags, 1); +} +/* + * Initialize everything scheduling-related from config.c. Note this is only + * called when Tor is starting up, while scheduler_t->init() is called both + * when Tor is starting up and when we are switching schedulers. + */ void scheduler_init(void) { log_debug(LD_SCHED, "Initting scheduler"); - tor_assert(!run_sched_ev); + // Two '!' because we really do want to check if the pointer is non-NULL + IF_BUG_ONCE(!!run_sched_ev) { + log_warn(LD_SCHED, "We should not already have a libevent scheduler event." + "I'll clean the old one up, but this is odd."); + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } run_sched_ev = tor_event_new(tor_libevent_get_base(), -1, 0, scheduler_evt_callback, NULL); - channels_pending = smartlist_new(); - queue_heuristic = 0; - queue_heuristic_timestamp = approx_time(); -} - -/** Check if there's more scheduling work */ - -static int -scheduler_more_work(void) -{ - tor_assert(channels_pending); - return ((scheduler_get_queue_heuristic() < sched_q_low_water) && - ((smartlist_len(channels_pending) > 0))) ? 1 : 0; + set_scheduler(); } -/** Retrigger the scheduler in a way safe to use from the callback */ - -static void -scheduler_retrigger(void) -{ - tor_assert(run_sched_ev); - event_active(run_sched_ev, EV_TIMEOUT, 1); -} - -/** Notify the scheduler of a channel being closed */ - +/* + * If a channel is going away, this is how the scheduling system is informed + * so it can do any freeing necessary. This ultimately calls + * scheduler_t->on_channel_free() so the current scheduler can release any + * state specific to this channel. + */ MOCK_IMPL(void, scheduler_release_channel,(channel_t *chan)) { - tor_assert(chan); - tor_assert(channels_pending); - - if (chan->scheduler_state == SCHED_CHAN_PENDING) { - smartlist_pqueue_remove(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), - chan); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; } - chan->scheduler_state = SCHED_CHAN_IDLE; -} - -/** Run the scheduling algorithm if necessary */ - -MOCK_IMPL(void, -scheduler_run, (void)) -{ - int n_cells, n_chans_before, n_chans_after; - uint64_t q_len_before, q_heur_before, q_len_after, q_heur_after; - ssize_t flushed, flushed_this_time; - smartlist_t *to_readd = NULL; - channel_t *chan = NULL; - - log_debug(LD_SCHED, "We have a chance to run the scheduler"); - - if (scheduler_get_queue_heuristic() < sched_q_low_water) { - n_chans_before = smartlist_len(channels_pending); - q_len_before = channel_get_global_queue_estimate(); - q_heur_before = scheduler_get_queue_heuristic(); - - while (scheduler_get_queue_heuristic() <= sched_q_high_water && - smartlist_len(channels_pending) > 0) { - /* Pop off a channel */ - chan = smartlist_pqueue_pop(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx)); - tor_assert(chan); - - /* Figure out how many cells we can write */ - n_cells = channel_num_cells_writeable(chan); - if (n_cells > 0) { - log_debug(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "%d cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); - - flushed = 0; - while (flushed < n_cells && - scheduler_get_queue_heuristic() <= sched_q_high_water) { - flushed_this_time = - channel_flush_some_cells(chan, - MIN(sched_max_flush_cells, - (size_t) n_cells - flushed)); - if (flushed_this_time <= 0) break; - flushed += flushed_this_time; - } - - if (flushed < n_cells) { - /* We ran out of cells to flush */ - chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "entered waiting_for_cells from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - /* The channel may still have some cells */ - if (channel_more_to_flush(chan)) { - /* The channel goes to either pending or waiting_to_write */ - if (channel_num_cells_writeable(chan) > 0) { - /* Add it back to pending later */ - if (!to_readd) to_readd = smartlist_new(); - smartlist_add(to_readd, chan); - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "is still pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - /* It's waiting to be able to write more */ - chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "entered waiting_to_write from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } - } else { - /* No cells left; it can go to idle or waiting_for_cells */ - if (channel_num_cells_writeable(chan) > 0) { - /* - * It can still accept writes, so it goes to - * waiting_for_cells - */ - chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "entered waiting_for_cells from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - /* - * We exactly filled up the output queue with all available - * cells; go to idle. - */ - chan->scheduler_state = SCHED_CHAN_IDLE; - log_debug(LD_SCHED, - "Channel " U64_FORMAT " at %p " - "become idle from pending", - U64_PRINTF_ARG(chan->global_identifier), - chan); - } - } - } - - log_debug(LD_SCHED, - "Scheduler flushed %d cells onto pending channel " - U64_FORMAT " at %p", - (int)flushed, U64_PRINTF_ARG(chan->global_identifier), - chan); - } else { - log_info(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "no cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan); - /* Put it back to WAITING_TO_WRITE */ - chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; - } - } - - /* Readd any channels we need to */ - if (to_readd) { - SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { - readd_chan->scheduler_state = SCHED_CHAN_PENDING; - smartlist_pqueue_add(channels_pending, - scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), - readd_chan); - } SMARTLIST_FOREACH_END(readd_chan); - smartlist_free(to_readd); + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + if (SCHED_BUG(smartlist_pos(channels_pending, chan) == -1, chan)) { + log_warn(LD_SCHED, "Scheduler asked to release channel %" PRIu64 " " + "but it wasn't in channels_pending", + chan->global_identifier); + } else { + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + chan); } - - n_chans_after = smartlist_len(channels_pending); - q_len_after = channel_get_global_queue_estimate(); - q_heur_after = scheduler_get_queue_heuristic(); - log_debug(LD_SCHED, - "Scheduler handled %d of %d pending channels, queue size from " - U64_FORMAT " to " U64_FORMAT ", queue heuristic from " - U64_FORMAT " to " U64_FORMAT, - n_chans_before - n_chans_after, n_chans_before, - U64_PRINTF_ARG(q_len_before), U64_PRINTF_ARG(q_len_after), - U64_PRINTF_ARG(q_heur_before), U64_PRINTF_ARG(q_heur_after)); } -} - -/** Trigger the scheduling event so we run the scheduler later */ - -#if 0 -static void -scheduler_trigger(void) -{ - log_debug(LD_SCHED, "Triggering scheduler event"); - - tor_assert(run_sched_ev); - event_add(run_sched_ev, EV_TIMEOUT, 1); + if (the_scheduler->on_channel_free) { + the_scheduler->on_channel_free(chan); + } + chan->scheduler_state = SCHED_CHAN_IDLE; } -#endif /** Mark a channel as ready to accept writes */ void scheduler_channel_wants_writes(channel_t *chan) { - int became_pending = 0; - - tor_assert(chan); - tor_assert(channels_pending); + IF_BUG_ONCE(!chan) { + return; + } + IF_BUG_ONCE(!channels_pending) { + return; + } /* If it's already in waiting_to_write, we can put it in pending */ if (chan->scheduler_state == SCHED_CHAN_WAITING_TO_WRITE) { /* * It can write now, so it goes to channels_pending. */ + log_debug(LD_SCHED, "chan=%" PRIu64 " became pending", + chan->global_identifier); smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); chan->scheduler_state = SCHED_CHAN_PENDING; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_to_write " "to pending", U64_PRINTF_ARG(chan->global_identifier), chan); - became_pending = 1; + /* We just made a channel pending, we have scheduling work to do. */ + the_scheduler->schedule(); } else { /* * It's not in SCHED_CHAN_WAITING_TO_WRITE, so it can't become pending; @@ -602,137 +689,70 @@ scheduler_channel_wants_writes(channel_t *chan) U64_PRINTF_ARG(chan->global_identifier), chan); } } +} - /* - * If we made a channel pending, we potentially have scheduling work - * to do. - */ - if (became_pending) scheduler_retrigger(); +/* Log warn the given channel and extra scheduler context as well. This is + * used by SCHED_BUG() in order to be able to extract as much information as + * we can when we hit a bug. Channel chan can be NULL. */ +void +scheduler_bug_occurred(const channel_t *chan) +{ + char buf[128]; + + if (chan != NULL) { + const size_t outbuf_len = + buf_datalen(TO_CONN(BASE_CHAN_TO_TLS((channel_t *) chan)->conn)->outbuf); + tor_snprintf(buf, sizeof(buf), + "Channel %" PRIu64 " in state %s and scheduler state %d." + " Num cells on cmux: %d. Connection outbuf len: %lu.", + chan->global_identifier, + channel_state_to_string(chan->state), + chan->scheduler_state, circuitmux_num_cells(chan->cmux), + (unsigned long)outbuf_len); + } + + { + char *msg; + /* Rate limit every 60 seconds. If we start seeing this every 60 sec, we + * know something is stuck/wrong. It *should* be loud but not too much. */ + static ratelim_t rlimit = RATELIM_INIT(60); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + log_warn(LD_BUG, "%s Num pending channels: %d. " + "Channel in pending list: %s.%s", + (chan != NULL) ? buf : "No channel in bug context.", + smartlist_len(channels_pending), + (smartlist_pos(channels_pending, chan) == -1) ? "no" : "yes", + msg); + tor_free(msg); + } + } } -/** - * Notify the scheduler that a channel's position in the pqueue may have - * changed - */ +#ifdef TOR_UNIT_TESTS +/* + * Notify scheduler that a channel's queue position may have changed. + */ void scheduler_touch_channel(channel_t *chan) { - tor_assert(chan); + IF_BUG_ONCE(!chan) { + return; + } if (chan->scheduler_state == SCHED_CHAN_PENDING) { /* Remove and re-add it */ smartlist_pqueue_remove(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); smartlist_pqueue_add(channels_pending, scheduler_compare_channels, - STRUCT_OFFSET(channel_t, sched_heap_idx), + offsetof(channel_t, sched_heap_idx), chan); } /* else no-op, since it isn't in the queue */ } -/** - * Notify the scheduler of a queue size adjustment, to recalculate the - * queue heuristic. - */ - -void -scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj) -{ - time_t now = approx_time(); - - log_debug(LD_SCHED, - "Queue size adjustment by %s" U64_FORMAT " for channel " - U64_FORMAT, - (dir >= 0) ? "+" : "-", - U64_PRINTF_ARG(adj), - U64_PRINTF_ARG(chan->global_identifier)); - - /* Get the queue heuristic up to date */ - scheduler_update_queue_heuristic(now); - - /* Adjust as appropriate */ - if (dir >= 0) { - /* Increasing it */ - queue_heuristic += adj; - } else { - /* Decreasing it */ - if (queue_heuristic > adj) queue_heuristic -= adj; - else queue_heuristic = 0; - } - - log_debug(LD_SCHED, - "Queue heuristic is now " U64_FORMAT, - U64_PRINTF_ARG(queue_heuristic)); -} - -/** - * Query the current value of the queue heuristic - */ - -STATIC uint64_t -scheduler_get_queue_heuristic(void) -{ - time_t now = approx_time(); - - scheduler_update_queue_heuristic(now); - - return queue_heuristic; -} - -/** - * Adjust the queue heuristic value to the present time - */ - -STATIC void -scheduler_update_queue_heuristic(time_t now) -{ - time_t diff; - - if (queue_heuristic_timestamp == 0) { - /* - * Nothing we can sensibly do; must not have been initted properly. - * Oh well. - */ - queue_heuristic_timestamp = now; - } else if (queue_heuristic_timestamp < now) { - diff = now - queue_heuristic_timestamp; - /* - * This is a simple exponential age-out; the other proposed alternative - * was a linear age-out using the bandwidth history in rephist.c; I'm - * going with this out of concern that if an adversary can jam the - * scheduler long enough, it would cause the bandwidth to drop to - * zero and render the aging mechanism ineffective thereafter. - */ - if (0 <= diff && diff < 64) queue_heuristic >>= diff; - else queue_heuristic = 0; - - queue_heuristic_timestamp = now; - - log_debug(LD_SCHED, - "Queue heuristic is now " U64_FORMAT, - U64_PRINTF_ARG(queue_heuristic)); - } - /* else no update needed, or time went backward */ -} - -/** - * Set scheduler watermarks and flush size - */ - -void -scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush) -{ - /* Sanity assertions - caller should ensure these are true */ - tor_assert(lo > 0); - tor_assert(hi > lo); - tor_assert(max_flush > 0); - - sched_q_low_water = lo; - sched_q_high_water = hi; - sched_max_flush_cells = max_flush; -} +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/scheduler.h b/src/or/scheduler.h index e29c13de7e..47c98f096a 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -1,9 +1,9 @@ -/* * Copyright (c) 2013-2017, The Tor Project, Inc. */ +/* * Copyright (c) 2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file scheduler.h - * \brief Header file for scheduler.c + * \brief Header file for scheduler*.c **/ #ifndef TOR_SCHEDULER_H @@ -13,45 +13,205 @@ #include "channel.h" #include "testsupport.h" -/* Global-visibility scheduler functions */ +/** Scheduler type, we build an ordered list with those values from the + * parsed strings in Schedulers. The reason to do such a thing is so we can + * quickly and without parsing strings select the scheduler at anytime. */ +typedef enum { + SCHEDULER_NONE = -1, + SCHEDULER_VANILLA = 1, + SCHEDULER_KIST = 2, + SCHEDULER_KIST_LITE = 3, +} scheduler_types_t; -/* Set up and shut down the scheduler from main.c */ -void scheduler_free_all(void); -void scheduler_init(void); -MOCK_DECL(void, scheduler_run, (void)); +/** + * A scheduler implementation is a collection of function pointers. If you + * would like to add a new scheduler called foo, create scheduler_foo.c, + * implement at least the mandatory ones, and implement get_foo_scheduler() + * that returns a complete scheduler_t for your foo scheduler. See + * scheduler_kist.c for an example. + * + * These function pointers SHOULD NOT be used anywhere outside of the + * scheduling source files. The rest of Tor should communicate with the + * scheduling system through the functions near the bottom of this file, and + * those functions will call into the current scheduler implementation as + * necessary. + * + * If your scheduler doesn't need to implement something (for example: it + * doesn't create any state for itself, thus it has nothing to free when Tor + * is shutting down), then set that function pointer to NULL. + */ +typedef struct scheduler_s { + /* Scheduler type. This is used for logging when the scheduler is switched + * during runtime. */ + scheduler_types_t type; -/* Mark channels as having cells or wanting/not wanting writes */ -MOCK_DECL(void,scheduler_channel_doesnt_want_writes,(channel_t *chan)); -MOCK_DECL(void,scheduler_channel_has_waiting_cells,(channel_t *chan)); -void scheduler_channel_wants_writes(channel_t *chan); + /* (Optional) To be called when we want to prepare a scheduler for use. + * Perhaps Tor just started and we are the lucky chosen scheduler, or + * perhaps Tor is switching to this scheduler. No matter the case, this is + * where we would prepare any state and initialize parameters. You might + * think of this as the opposite of free_all(). */ + void (*init)(void); -/* Notify the scheduler of a channel being closed */ -MOCK_DECL(void,scheduler_release_channel,(channel_t *chan)); + /* (Optional) To be called when we want to tell the scheduler to delete all + * of its state (if any). Perhaps Tor is shutting down or perhaps we are + * switching schedulers. */ + void (*free_all)(void); -/* Notify scheduler of queue size adjustments */ -void scheduler_adjust_queue_size(channel_t *chan, int dir, uint64_t adj); + /* (Mandatory) Libevent controls the main event loop in Tor, and this is + * where we register with libevent the next execution of run_sched_ev [which + * ultimately calls run()]. */ + void (*schedule)(void); -/* Notify scheduler that a channel's queue position may have changed */ -void scheduler_touch_channel(channel_t *chan); + /* (Mandatory) This is the heart of a scheduler! This is where the + * excitement happens! Here libevent has given us the chance to execute, and + * we should do whatever we need to do in order to move some cells from + * their circuit queues to output buffers in an intelligent manner. We + * should do this quickly. When we are done, we'll try to schedule() ourself + * if more work needs to be done to setup the next scheduling run. */ + void (*run)(void); + + /* + * External event not related to the scheduler but that can influence it. + */ + + /* (Optional) To be called whenever Tor finds out about a new consensus. + * First the scheduling system as a whole will react to the new consensus + * and change the scheduler if needed. After that, the current scheduler + * (which might be new) will call this so it has the chance to react to the + * new consensus too. If there's a consensus parameter that your scheduler + * wants to keep an eye on, this is where you should check for it. */ + void (*on_new_consensus)(const networkstatus_t *old_c, + const networkstatus_t *new_c); + + /* (Optional) To be called when a channel is being freed. Sometimes channels + * go away (for example: the relay on the other end is shutting down). If + * the scheduler keeps any channel-specific state and has memory to free + * when channels go away, implement this and free it here. */ + void (*on_channel_free)(const channel_t *); -/* Adjust the watermarks from config file*/ -void scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush); + /* (Optional) To be called whenever Tor is reloading configuration options. + * For example: SIGHUP was issued and Tor is rereading its torrc. A + * scheduler should use this as an opportunity to parse and cache torrc + * options so that it doesn't have to call get_options() all the time. */ + void (*on_new_options)(void); +} scheduler_t; -/* Things only scheduler.c and its test suite should see */ +/***************************************************************************** + * Globally visible scheduler variables/values + * + * These are variables/constants that all of Tor should be able to see. + *****************************************************************************/ +/* Default interval that KIST runs (in ms). */ +#define KIST_SCHED_RUN_INTERVAL_DEFAULT 10 +/* Minimum interval that KIST runs. This value disables KIST. */ +#define KIST_SCHED_RUN_INTERVAL_MIN 0 +/* Maximum interval that KIST runs (in ms). */ +#define KIST_SCHED_RUN_INTERVAL_MAX 100 + +/***************************************************************************** + * Globally visible scheduler functions + * + * These functions are how the rest of Tor communicates with the scheduling + * system. + *****************************************************************************/ + +void scheduler_init(void); +void scheduler_free_all(void); +void scheduler_conf_changed(void); +void scheduler_notify_networkstatus_changed(const networkstatus_t *old_c, + const networkstatus_t *new_c); +MOCK_DECL(void, scheduler_release_channel, (channel_t *chan)); + +/* + * Ways for a channel to interact with the scheduling system. A channel only + * really knows (i) whether or not it has cells it wants to send, and + * (ii) whether or not it would like to write. + */ +void scheduler_channel_wants_writes(channel_t *chan); +MOCK_DECL(void, scheduler_channel_doesnt_want_writes, (channel_t *chan)); +MOCK_DECL(void, scheduler_channel_has_waiting_cells, (channel_t *chan)); + +/***************************************************************************** + * Private scheduler functions + * + * These functions are only visible to the scheduling system, the current + * scheduler implementation, and tests. + *****************************************************************************/ #ifdef SCHEDULER_PRIVATE_ -MOCK_DECL(STATIC int, scheduler_compare_channels, + +/********************************* + * Defined in scheduler.c + *********************************/ + +/* Triggers a BUG() and extra information with chan if available. */ +#define SCHED_BUG(cond, chan) \ + (PREDICT_UNLIKELY(cond) ? \ + ((BUG(cond)) ? (scheduler_bug_occurred(chan), 1) : 0) : 0) + +void scheduler_bug_occurred(const channel_t *chan); + +smartlist_t *get_channels_pending(void); +MOCK_DECL(int, scheduler_compare_channels, (const void *c1_v, const void *c2_v)); -STATIC uint64_t scheduler_get_queue_heuristic(void); -STATIC void scheduler_update_queue_heuristic(time_t now); +void scheduler_ev_active(int flags); +void scheduler_ev_add(const struct timeval *next_run); #ifdef TOR_UNIT_TESTS extern smartlist_t *channels_pending; extern struct event *run_sched_ev; -extern uint64_t queue_heuristic; -extern time_t queue_heuristic_timestamp; -#endif -#endif +extern const scheduler_t *the_scheduler; +void scheduler_touch_channel(channel_t *chan); +#endif /* defined(TOR_UNIT_TESTS) */ + +/********************************* + * Defined in scheduler_kist.c + *********************************/ + +#ifdef SCHEDULER_KIST_PRIVATE + +/* Socket table entry which holds information of a channel's socket and kernel + * TCP information. Only used by KIST. */ +typedef struct socket_table_ent_s { + HT_ENTRY(socket_table_ent_s) node; + const channel_t *chan; + /* Amount written this scheduling run */ + uint64_t written; + /* Amount that can be written this scheduling run */ + uint64_t limit; + /* TCP info from the kernel */ + uint32_t cwnd; + uint32_t unacked; + uint32_t mss; + uint32_t notsent; +} socket_table_ent_t; + +typedef HT_HEAD(outbuf_table_s, outbuf_table_ent_s) outbuf_table_t; + +MOCK_DECL(int, channel_should_write_to_kernel, + (outbuf_table_t *table, channel_t *chan)); +MOCK_DECL(void, channel_write_to_kernel, (channel_t *chan)); +MOCK_DECL(void, update_socket_info_impl, (socket_table_ent_t *ent)); + +int scheduler_can_use_kist(void); +void scheduler_kist_set_full_mode(void); +void scheduler_kist_set_lite_mode(void); +scheduler_t *get_kist_scheduler(void); +int kist_scheduler_run_interval(const networkstatus_t *ns); + +#ifdef TOR_UNIT_TESTS +extern int32_t sched_run_interval; +#endif /* TOR_UNIT_TESTS */ + +#endif /* defined(SCHEDULER_KIST_PRIVATE) */ + +/********************************* + * Defined in scheduler_vanilla.c + *********************************/ + +scheduler_t *get_vanilla_scheduler(void); + +#endif /* defined(SCHEDULER_PRIVATE_) */ #endif /* !defined(TOR_SCHEDULER_H) */ diff --git a/src/or/scheduler_kist.c b/src/or/scheduler_kist.c new file mode 100644 index 0000000000..19a8db5170 --- /dev/null +++ b/src/or/scheduler_kist.c @@ -0,0 +1,832 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define SCHEDULER_KIST_PRIVATE + +#include <event2/event.h> + +#include "or.h" +#include "buffers.h" +#include "config.h" +#include "connection.h" +#include "networkstatus.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#include "channeltls.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +#define TLS_PER_CELL_OVERHEAD 29 + +#ifdef HAVE_KIST_SUPPORT +/* Kernel interface needed for KIST. */ +#include <netinet/tcp.h> +#include <linux/sockios.h> +#endif /* HAVE_KIST_SUPPORT */ + +/***************************************************************************** + * Data structures and supporting functions + *****************************************************************************/ + +/* Socket_table hash table stuff. The socket_table keeps track of per-socket + * limit information imposed by kist and used by kist. */ + +static uint32_t +socket_table_ent_hash(const socket_table_ent_t *ent) +{ + return (uint32_t)ent->chan->global_identifier; +} + +static unsigned +socket_table_ent_eq(const socket_table_ent_t *a, const socket_table_ent_t *b) +{ + return a->chan == b->chan; +} + +typedef HT_HEAD(socket_table_s, socket_table_ent_s) socket_table_t; + +static socket_table_t socket_table = HT_INITIALIZER(); + +HT_PROTOTYPE(socket_table_s, socket_table_ent_s, node, socket_table_ent_hash, + socket_table_ent_eq) +HT_GENERATE2(socket_table_s, socket_table_ent_s, node, socket_table_ent_hash, + socket_table_ent_eq, 0.6, tor_reallocarray, tor_free_) + +/* outbuf_table hash table stuff. The outbuf_table keeps track of which + * channels have data sitting in their outbuf so the kist scheduler can force + * a write from outbuf to kernel periodically during a run and at the end of a + * run. */ + +typedef struct outbuf_table_ent_s { + HT_ENTRY(outbuf_table_ent_s) node; + channel_t *chan; +} outbuf_table_ent_t; + +static uint32_t +outbuf_table_ent_hash(const outbuf_table_ent_t *ent) +{ + return (uint32_t)ent->chan->global_identifier; +} + +static unsigned +outbuf_table_ent_eq(const outbuf_table_ent_t *a, const outbuf_table_ent_t *b) +{ + return a->chan->global_identifier == b->chan->global_identifier; +} + +HT_PROTOTYPE(outbuf_table_s, outbuf_table_ent_s, node, outbuf_table_ent_hash, + outbuf_table_ent_eq) +HT_GENERATE2(outbuf_table_s, outbuf_table_ent_s, node, outbuf_table_ent_hash, + outbuf_table_ent_eq, 0.6, tor_reallocarray, tor_free_) + +/***************************************************************************** + * Other internal data + *****************************************************************************/ + +/* Store the last time the scheduler was run so we can decide when to next run + * the scheduler based on it. */ +static monotime_t scheduler_last_run; +/* This is a factor for the extra_space calculation in kist per-socket limits. + * It is the number of extra congestion windows we want to write to the kernel. + */ +static double sock_buf_size_factor = 1.0; +/* How often the scheduler runs. */ +STATIC int sched_run_interval = KIST_SCHED_RUN_INTERVAL_DEFAULT; + +#ifdef HAVE_KIST_SUPPORT +/* Indicate if KIST lite mode is on or off. We can disable it at runtime. + * Important to have because of the KISTLite -> KIST possible transition. */ +static unsigned int kist_lite_mode = 0; +/* Indicate if we don't have the kernel support. This can happen if the kernel + * changed and it doesn't recognized the values passed to the syscalls needed + * by KIST. In that case, fallback to the naive approach. */ +static unsigned int kist_no_kernel_support = 0; +#else /* !(defined(HAVE_KIST_SUPPORT)) */ +static unsigned int kist_lite_mode = 1; +#endif /* defined(HAVE_KIST_SUPPORT) */ + +/***************************************************************************** + * Internally called function implementations + *****************************************************************************/ + +/* Little helper function to get the length of a channel's output buffer */ +static inline size_t +channel_outbuf_length(channel_t *chan) +{ + /* In theory, this can not happen because we can not scheduler a channel + * without a connection that has its outbuf initialized. Just in case, bug + * on this so we can understand a bit more why it happened. */ + if (SCHED_BUG(BASE_CHAN_TO_TLS(chan)->conn == NULL, chan)) { + return 0; + } + return buf_datalen(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn)->outbuf); +} + +/* Little helper function for HT_FOREACH_FN. */ +static int +each_channel_write_to_kernel(outbuf_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + channel_write_to_kernel(ent->chan); + return 0; /* Returning non-zero removes the element from the table. */ +} + +/* Free the given outbuf table entry ent. */ +static int +free_outbuf_info_by_ent(outbuf_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + log_debug(LD_SCHED, "Freeing outbuf table entry from chan=%" PRIu64, + ent->chan->global_identifier); + tor_free(ent); + return 1; /* So HT_FOREACH_FN will remove the element */ +} + +/* Free the given socket table entry ent. */ +static int +free_socket_info_by_ent(socket_table_ent_t *ent, void *data) +{ + (void) data; /* Make compiler happy. */ + log_debug(LD_SCHED, "Freeing socket table entry from chan=%" PRIu64, + ent->chan->global_identifier); + tor_free(ent); + return 1; /* So HT_FOREACH_FN will remove the element */ +} + +/* Clean up socket_table. Probably because the KIST sched impl is going away */ +static void +free_all_socket_info(void) +{ + HT_FOREACH_FN(socket_table_s, &socket_table, free_socket_info_by_ent, NULL); + HT_CLEAR(socket_table_s, &socket_table); +} + +static socket_table_ent_t * +socket_table_search(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t search, *ent = NULL; + search.chan = chan; + ent = HT_FIND(socket_table_s, table, &search); + return ent; +} + +/* Free a socket entry in table for the given chan. */ +static void +free_socket_info_by_chan(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (!ent) + return; + log_debug(LD_SCHED, "scheduler free socket info for chan=%" PRIu64, + chan->global_identifier); + HT_REMOVE(socket_table_s, table, ent); + free_socket_info_by_ent(ent, NULL); +} + +/* Perform system calls for the given socket in order to calculate kist's + * per-socket limit as documented in the function body. */ +MOCK_IMPL(void, +update_socket_info_impl, (socket_table_ent_t *ent)) +{ +#ifdef HAVE_KIST_SUPPORT + int64_t tcp_space, extra_space; + const tor_socket_t sock = + TO_CONN(BASE_CHAN_TO_TLS((channel_t *) ent->chan)->conn)->s; + struct tcp_info tcp; + socklen_t tcp_info_len = sizeof(tcp); + + if (kist_no_kernel_support || kist_lite_mode) { + goto fallback; + } + + /* Gather information */ + if (getsockopt(sock, SOL_TCP, TCP_INFO, (void *)&(tcp), &tcp_info_len) < 0) { + if (errno == EINVAL) { + /* Oops, this option is not provided by the kernel, we'll have to + * disable KIST entirely. This can happen if tor was built on a machine + * with the support previously or if the kernel was updated and lost the + * support. */ + log_notice(LD_SCHED, "Looks like our kernel doesn't have the support " + "for KIST anymore. We will fallback to the naive " + "approach. Remove KIST from the Schedulers list " + "to disable."); + kist_no_kernel_support = 1; + } + goto fallback; + } + if (ioctl(sock, SIOCOUTQNSD, &(ent->notsent)) < 0) { + if (errno == EINVAL) { + log_notice(LD_SCHED, "Looks like our kernel doesn't have the support " + "for KIST anymore. We will fallback to the naive " + "approach. Remove KIST from the Schedulers list " + "to disable."); + /* Same reason as the above. */ + kist_no_kernel_support = 1; + } + goto fallback; + } + ent->cwnd = tcp.tcpi_snd_cwnd; + ent->unacked = tcp.tcpi_unacked; + ent->mss = tcp.tcpi_snd_mss; + + /* In order to reduce outbound kernel queuing delays and thus improve Tor's + * ability to prioritize circuits, KIST wants to set a socket write limit + * that is near the amount that the socket would be able to immediately send + * into the Internet. + * + * We first calculate how much the socket could send immediately (assuming + * completely full packets) according to the congestion window and the number + * of unacked packets. + * + * Then we add a little extra space in a controlled way. We do this so any + * when the kernel gets ACKs back for data currently sitting in the "TCP + * space", it will already have some more data to send immediately. It will + * not have to wait for the scheduler to run again. The amount of extra space + * is a factor of the current congestion window. With the suggested + * sock_buf_size_factor value of 1.0, we allow at most 2*cwnd bytes to sit in + * the kernel: 1 cwnd on the wire waiting for ACKs and 1 cwnd ready and + * waiting to be sent when those ACKs finally come. + * + * In the below diagram, we see some bytes in the TCP-space (denoted by '*') + * that have be sent onto the wire and are waiting for ACKs. We have a little + * more room in "TCP space" that we can fill with data that will be + * immediately sent. We also see the "extra space" KIST calculates. The sum + * of the empty "TCP space" and the "extra space" is the kist-imposed write + * limit for this socket. + * + * <----------------kernel-outbound-socket-queue----------------| + * <*********---------------------------------------------------| + * |----TCP-space-----|----extra-space-----| + * |------------------| + * ^ ((cwnd - unacked) * mss) bytes + * |--------------------| + * ^ ((cwnd * mss) * factor) bytes + */ + + /* These values from the kernel are uint32_t, they will always fit into a + * int64_t tcp_space variable but if the congestion window cwnd is smaller + * than the unacked packets, the remaining TCP space is set to 0. */ + if (ent->cwnd >= ent->unacked) { + tcp_space = (ent->cwnd - ent->unacked) * (int64_t)(ent->mss); + } else { + tcp_space = 0; + } + + /* The clamp_double_to_int64 makes sure the first part fits into an int64_t. + * In fact, if sock_buf_size_factor is still forced to be >= 0 in config.c, + * then it will be positive for sure. Then we subtract a uint32_t. Getting a + * negative value is OK, see after how it is being handled. */ + extra_space = + clamp_double_to_int64( + (ent->cwnd * (int64_t)ent->mss) * sock_buf_size_factor) - + ent->notsent; + if ((tcp_space + extra_space) < 0) { + /* This means that the "notsent" queue is just too big so we shouldn't put + * more in the kernel for now. */ + ent->limit = 0; + } else { + /* The positive sum of two int64_t will always fit into an uint64_t. + * And we know this will always be positive, since we checked above. */ + ent->limit = (uint64_t)tcp_space + (uint64_t)extra_space; + } + return; + +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + goto fallback; +#endif /* defined(HAVE_KIST_SUPPORT) */ + + fallback: + /* If all of a sudden we don't have kist support, we just zero out all the + * variables for this socket since we don't know what they should be. + * We also effectively allow the socket write as much as it wants to the + * kernel, effectively returning it to vanilla scheduler behavior. Writes + * are still limited by the lower layers of Tor: socket blocking, full + * outbuf, etc. */ + ent->cwnd = ent->unacked = ent->mss = ent->notsent = 0; + ent->limit = INT_MAX; +} + +/* Given a socket that isn't in the table, add it. + * Given a socket that is in the table, re-init values that need init-ing + * every scheduling run + */ +static void +init_socket_info(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (!ent) { + log_debug(LD_SCHED, "scheduler init socket info for chan=%" PRIu64, + chan->global_identifier); + ent = tor_malloc_zero(sizeof(*ent)); + ent->chan = chan; + HT_INSERT(socket_table_s, table, ent); + } + ent->written = 0; +} + +/* Add chan to the outbuf table if it isn't already in it. If it is, then don't + * do anything */ +static void +outbuf_table_add(outbuf_table_t *table, channel_t *chan) +{ + outbuf_table_ent_t search, *ent; + search.chan = chan; + ent = HT_FIND(outbuf_table_s, table, &search); + if (!ent) { + log_debug(LD_SCHED, "scheduler init outbuf info for chan=%" PRIu64, + chan->global_identifier); + ent = tor_malloc_zero(sizeof(*ent)); + ent->chan = chan; + HT_INSERT(outbuf_table_s, table, ent); + } +} + +static void +outbuf_table_remove(outbuf_table_t *table, channel_t *chan) +{ + outbuf_table_ent_t search, *ent; + search.chan = chan; + ent = HT_FIND(outbuf_table_s, table, &search); + if (ent) { + HT_REMOVE(outbuf_table_s, table, ent); + free_outbuf_info_by_ent(ent, NULL); + } +} + +/* Set the scheduler running interval. */ +static void +set_scheduler_run_interval(const networkstatus_t *ns) +{ + int old_sched_run_interval = sched_run_interval; + sched_run_interval = kist_scheduler_run_interval(ns); + if (old_sched_run_interval != sched_run_interval) { + log_info(LD_SCHED, "Scheduler KIST changing its running interval " + "from %" PRId32 " to %" PRId32, + old_sched_run_interval, sched_run_interval); + } +} + +/* Return true iff the channel hasn’t hit its kist-imposed write limit yet */ +static int +socket_can_write(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return 1; // Just return true, saying that kist wouldn't limit the socket + } + + /* We previously calculated a write limit for this socket. In the below + * calculation, first determine how much room is left in bytes. Then divide + * that by the amount of space a cell takes. If there's room for at least 1 + * cell, then KIST will allow the socket to write. */ + int64_t kist_limit_space = + (int64_t) (ent->limit - ent->written) / + (CELL_MAX_NETWORK_SIZE + TLS_PER_CELL_OVERHEAD); + return kist_limit_space > 0; +} + +/* Update the channel's socket kernel information. */ +static void +update_socket_info(socket_table_t *table, const channel_t *chan) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return; // Whelp. Entry didn't exist for some reason so nothing to do. + } + update_socket_info_impl(ent); +} + +/* Increment the channel's socket written value by the number of bytes. */ +static void +update_socket_written(socket_table_t *table, channel_t *chan, size_t bytes) +{ + socket_table_ent_t *ent = NULL; + ent = socket_table_search(table, chan); + if (SCHED_BUG(!ent, chan)) { + return; // Whelp. Entry didn't exist so nothing to do. + } + + log_debug(LD_SCHED, "chan=%" PRIu64 " wrote %lu bytes, old was %" PRIi64, + chan->global_identifier, (unsigned long) bytes, ent->written); + + ent->written += bytes; +} + +/* + * A naive KIST impl would write every single cell all the way to the kernel. + * That would take a lot of system calls. A less bad KIST impl would write a + * channel's outbuf to the kernel only when we are switching to a different + * channel. But if we have two channels with equal priority, we end up writing + * one cell for each and bouncing back and forth. This KIST impl avoids that + * by only writing a channel's outbuf to the kernel if it has 8 cells or more + * in it. + */ +MOCK_IMPL(int, channel_should_write_to_kernel, + (outbuf_table_t *table, channel_t *chan)) +{ + outbuf_table_add(table, chan); + /* CELL_MAX_NETWORK_SIZE * 8 because we only want to write the outbuf to the + * kernel if there's 8 or more cells waiting */ + return channel_outbuf_length(chan) > (CELL_MAX_NETWORK_SIZE * 8); +} + +/* Little helper function to write a channel's outbuf all the way to the + * kernel */ +MOCK_IMPL(void, channel_write_to_kernel, (channel_t *chan)) +{ + log_debug(LD_SCHED, "Writing %lu bytes to kernel for chan %" PRIu64, + (unsigned long)channel_outbuf_length(chan), + chan->global_identifier); + connection_handle_write(TO_CONN(BASE_CHAN_TO_TLS(chan)->conn), 0); +} + +/* Return true iff the scheduler has work to perform. */ +static int +have_work(void) +{ + smartlist_t *cp = get_channels_pending(); + IF_BUG_ONCE(!cp) { + return 0; // channels_pending doesn't exist so... no work? + } + return smartlist_len(cp) > 0; +} + +/* Function of the scheduler interface: free_all() */ +static void +kist_free_all(void) +{ + free_all_socket_info(); +} + +/* Function of the scheduler interface: on_channel_free() */ +static void +kist_on_channel_free(const channel_t *chan) +{ + free_socket_info_by_chan(&socket_table, chan); +} + +/* Function of the scheduler interface: on_new_consensus() */ +static void +kist_scheduler_on_new_consensus(const networkstatus_t *old_c, + const networkstatus_t *new_c) +{ + (void) old_c; + (void) new_c; + + set_scheduler_run_interval(new_c); +} + +/* Function of the scheduler interface: on_new_options() */ +static void +kist_scheduler_on_new_options(void) +{ + sock_buf_size_factor = get_options()->KISTSockBufSizeFactor; + + /* Calls kist_scheduler_run_interval which calls get_options(). */ + set_scheduler_run_interval(NULL); +} + +/* Function of the scheduler interface: init() */ +static void +kist_scheduler_init(void) +{ + /* When initializing the scheduler, the last run could be 0 because it is + * declared static or a value in the past that was set when it was last + * used. In both cases, we want to initialize it to now so we don't risk + * using the value 0 which doesn't play well with our monotonic time + * interface. + * + * One side effect is that the first scheduler run will be at the next tick + * that is in now + 10 msec (KIST_SCHED_RUN_INTERVAL_DEFAULT) by default. */ + monotime_get(&scheduler_last_run); + + kist_scheduler_on_new_options(); + IF_BUG_ONCE(sched_run_interval == 0) { + log_warn(LD_SCHED, "We are initing the KIST scheduler and noticed the " + "KISTSchedRunInterval is telling us to not use KIST. That's " + "weird! We'll continue using KIST, but at %" PRId32 "ms.", + KIST_SCHED_RUN_INTERVAL_DEFAULT); + sched_run_interval = KIST_SCHED_RUN_INTERVAL_DEFAULT; + } +} + +/* Function of the scheduler interface: schedule() */ +static void +kist_scheduler_schedule(void) +{ + struct monotime_t now; + struct timeval next_run; + int64_t diff; + + if (!have_work()) { + return; + } + monotime_get(&now); + + /* If time is really monotonic, we can never have now being smaller than the + * last scheduler run. The scheduler_last_run at first is set to 0. + * Unfortunately, not all platforms guarantee monotonic time so we log at + * info level but don't make it more noisy. */ + diff = monotime_diff_msec(&scheduler_last_run, &now); + if (diff < 0) { + log_info(LD_SCHED, "Monotonic time between now and last run of scheduler " + "is negative: %" PRId64 ". Setting diff to 0.", diff); + diff = 0; + } + if (diff < sched_run_interval) { + next_run.tv_sec = 0; + /* Takes 1000 ms -> us. This will always be valid because diff can NOT be + * negative and can NOT be bigger than sched_run_interval so values can + * only go from 1000 usec (diff set to interval - 1) to 100000 usec (diff + * set to 0) for the maximum allowed run interval (100ms). */ + next_run.tv_usec = (int) ((sched_run_interval - diff) * 1000); + /* Re-adding an event reschedules it. It does not duplicate it. */ + scheduler_ev_add(&next_run); + } else { + scheduler_ev_active(EV_TIMEOUT); + } +} + +/* Function of the scheduler interface: run() */ +static void +kist_scheduler_run(void) +{ + /* Define variables */ + channel_t *chan = NULL; // current working channel + /* The last distinct chan served in a sched loop. */ + channel_t *prev_chan = NULL; + int flush_result; // temporarily store results from flush calls + /* Channels to be re-adding to pending at the end */ + smartlist_t *to_readd = NULL; + smartlist_t *cp = get_channels_pending(); + + outbuf_table_t outbuf_table = HT_INITIALIZER(); + + /* For each pending channel, collect new kernel information */ + SMARTLIST_FOREACH_BEGIN(cp, const channel_t *, pchan) { + init_socket_info(&socket_table, pchan); + update_socket_info(&socket_table, pchan); + } SMARTLIST_FOREACH_END(pchan); + + log_debug(LD_SCHED, "Running the scheduler. %d channels pending", + smartlist_len(cp)); + + /* The main scheduling loop. Loop until there are no more pending channels */ + while (smartlist_len(cp) > 0) { + /* get best channel */ + chan = smartlist_pqueue_pop(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx)); + if (SCHED_BUG(!chan, NULL)) { + /* Some-freaking-how a NULL got into the channels_pending. That should + * never happen, but it should be harmless to ignore it and keep looping. + */ + continue; + } + outbuf_table_add(&outbuf_table, chan); + + /* if we have switched to a new channel, consider writing the previous + * channel's outbuf to the kernel. */ + if (!prev_chan) { + prev_chan = chan; + } + if (prev_chan != chan) { + if (channel_should_write_to_kernel(&outbuf_table, prev_chan)) { + channel_write_to_kernel(prev_chan); + outbuf_table_remove(&outbuf_table, prev_chan); + } + prev_chan = chan; + } + + /* Only flush and write if the per-socket limit hasn't been hit */ + if (socket_can_write(&socket_table, chan)) { + /* flush to channel queue/outbuf */ + flush_result = (int)channel_flush_some_cells(chan, 1); // 1 for num cells + /* XXX: While flushing cells, it is possible that the connection write + * fails leading to the channel to be closed which triggers a release + * and free its entry in the socket table. And because of a engineering + * design issue, the error is not propagated back so we don't get an + * error at this point. So before we continue, make sure the channel is + * open and if not just ignore it. See #23751. */ + if (!CHANNEL_IS_OPEN(chan)) { + /* Channel isn't open so we put it back in IDLE mode. It is either + * renegotiating its TLS session or about to be released. */ + chan->scheduler_state = SCHED_CHAN_IDLE; + continue; + } + /* flush_result has the # cells flushed */ + if (flush_result > 0) { + update_socket_written(&socket_table, chan, flush_result * + (CELL_MAX_NETWORK_SIZE + TLS_PER_CELL_OVERHEAD)); + } else { + /* XXX: This can happen because tor sometimes does flush in an + * opportunistic way cells from the circuit to the outbuf so the + * channel can end up here without having anything to flush nor needed + * to write to the kernel. Hopefully we'll fix that soon but for now + * we have to handle this case which happens kind of often. */ + log_debug(LD_SCHED, + "We didn't flush anything on a chan that we think " + "can write and wants to write. The channel's state is '%s' " + "and in scheduler state %d. We're going to mark it as " + "waiting_for_cells (as that's most likely the issue) and " + "stop scheduling it this round.", + channel_state_to_string(chan->state), + chan->scheduler_state); + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + continue; + } + } + + /* Decide what to do with the channel now */ + + if (!channel_more_to_flush(chan) && + !socket_can_write(&socket_table, chan)) { + + /* Case 1: no more cells to send, and cannot write */ + + /* + * You might think we should put the channel in SCHED_CHAN_IDLE. And + * you're probably correct. While implementing KIST, we found that the + * scheduling system would sometimes lose track of channels when we did + * that. We suspect it has to do with the difference between "can't + * write because socket/outbuf is full" and KIST's "can't write because + * we've arbitrarily decided that that's enough for now." Sometimes + * channels run out of cells at the same time they hit their + * kist-imposed write limit and maybe the rest of Tor doesn't put the + * channel back in pending when it is supposed to. + * + * This should be investigated again. It is as simple as changing + * SCHED_CHAN_WAITING_FOR_CELLS to SCHED_CHAN_IDLE and seeing if Tor + * starts having serious throughput issues. Best done in shadow/chutney. + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_for_cells", + chan->global_identifier); + } else if (!channel_more_to_flush(chan)) { + + /* Case 2: no more cells to send, but still open for writes */ + + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_for_cells", + chan->global_identifier); + } else if (!socket_can_write(&socket_table, chan)) { + + /* Case 3: cells to send, but cannot write */ + + /* + * We want to write, but can't. If we left the channel in + * channels_pending, we would never exit the scheduling loop. We need to + * add it to a temporary list of channels to be added to channels_pending + * after the scheduling loop is over. They can hopefully be taken care of + * in the next scheduling round. + */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + if (!to_readd) { + to_readd = smartlist_new(); + } + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, "chan=%" PRIu64 " now waiting_to_write", + chan->global_identifier); + } else { + + /* Case 4: cells to send, and still open for writes */ + + chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), chan); + } + } /* End of main scheduling loop */ + + /* Write the outbuf of any channels that still have data */ + HT_FOREACH_FN(outbuf_table_s, &outbuf_table, each_channel_write_to_kernel, + NULL); + /* We are done with it. */ + HT_FOREACH_FN(outbuf_table_s, &outbuf_table, free_outbuf_info_by_ent, NULL); + HT_CLEAR(outbuf_table_s, &outbuf_table); + + log_debug(LD_SCHED, "len pending=%d, len to_readd=%d", + smartlist_len(cp), + (to_readd ? smartlist_len(to_readd) : -1)); + + /* Re-add any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { + readd_chan->scheduler_state = SCHED_CHAN_PENDING; + if (!smartlist_contains(cp, readd_chan)) { + smartlist_pqueue_add(cp, scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), readd_chan); + } + } SMARTLIST_FOREACH_END(readd_chan); + smartlist_free(to_readd); + } + + monotime_get(&scheduler_last_run); +} + +/***************************************************************************** + * Externally called function implementations not called through scheduler_t + *****************************************************************************/ + +/* Stores the kist scheduler function pointers. */ +static scheduler_t kist_scheduler = { + .type = SCHEDULER_KIST, + .free_all = kist_free_all, + .on_channel_free = kist_on_channel_free, + .init = kist_scheduler_init, + .on_new_consensus = kist_scheduler_on_new_consensus, + .schedule = kist_scheduler_schedule, + .run = kist_scheduler_run, + .on_new_options = kist_scheduler_on_new_options, +}; + +/* Return the KIST scheduler object. If it didn't exists, return a newly + * allocated one but init() is not called. */ +scheduler_t * +get_kist_scheduler(void) +{ + return &kist_scheduler; +} + +/* Check the torrc (and maybe consensus) for the configured KIST scheduler run + * interval. + * - If torrc > 0, then return the positive torrc value (should use KIST, and + * should use the set value) + * - If torrc == 0, then look in the consensus for what the value should be. + * - If == 0, then return 0 (don't use KIST) + * - If > 0, then return the positive consensus value + * - If consensus doesn't say anything, return 10 milliseconds, default. + */ +int +kist_scheduler_run_interval(const networkstatus_t *ns) +{ + int run_interval = get_options()->KISTSchedRunInterval; + + if (run_interval != 0) { + log_debug(LD_SCHED, "Found KISTSchedRunInterval=%" PRId32 " in torrc. " + "Using that.", run_interval); + return run_interval; + } + + log_debug(LD_SCHED, "KISTSchedRunInterval=0, turning to the consensus."); + + /* Will either be the consensus value or the default. Note that 0 can be + * returned which means the consensus wants us to NOT use KIST. */ + return networkstatus_get_param(ns, "KISTSchedRunInterval", + KIST_SCHED_RUN_INTERVAL_DEFAULT, + KIST_SCHED_RUN_INTERVAL_MIN, + KIST_SCHED_RUN_INTERVAL_MAX); +} + +/* Set KISTLite mode that is KIST without kernel support. */ +void +scheduler_kist_set_lite_mode(void) +{ + kist_lite_mode = 1; + kist_scheduler.type = SCHEDULER_KIST_LITE; + log_info(LD_SCHED, + "Setting KIST scheduler without kernel support (KISTLite mode)"); +} + +/* Set KIST mode that is KIST with kernel support. */ +void +scheduler_kist_set_full_mode(void) +{ + kist_lite_mode = 0; + kist_scheduler.type = SCHEDULER_KIST; + log_info(LD_SCHED, + "Setting KIST scheduler with kernel support (KIST mode)"); +} + +#ifdef HAVE_KIST_SUPPORT + +/* Return true iff the scheduler subsystem should use KIST. */ +int +scheduler_can_use_kist(void) +{ + if (kist_no_kernel_support) { + /* We have no kernel support so we can't use KIST. */ + return 0; + } + + /* We do have the support, time to check if we can get the interval that the + * consensus can be disabling. */ + int run_interval = kist_scheduler_run_interval(NULL); + log_debug(LD_SCHED, "Determined KIST sched_run_interval should be " + "%" PRId32 ". Can%s use KIST.", + run_interval, (run_interval > 0 ? "" : " not")); + return run_interval > 0; +} + +#else /* !(defined(HAVE_KIST_SUPPORT)) */ + +int +scheduler_can_use_kist(void) +{ + return 0; +} + +#endif /* defined(HAVE_KIST_SUPPORT) */ + diff --git a/src/or/scheduler_vanilla.c b/src/or/scheduler_vanilla.c new file mode 100644 index 0000000000..303b3dbba8 --- /dev/null +++ b/src/or/scheduler_vanilla.c @@ -0,0 +1,197 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include <event2/event.h> + +#include "or.h" +#include "config.h" +#define TOR_CHANNEL_INTERNAL_ +#include "channel.h" +#define SCHEDULER_PRIVATE_ +#include "scheduler.h" + +/***************************************************************************** + * Other internal data + *****************************************************************************/ + +/* Maximum cells to flush in a single call to channel_flush_some_cells(); */ +#define MAX_FLUSH_CELLS 1000 + +/***************************************************************************** + * Externally called function implementations + *****************************************************************************/ + +/* Return true iff the scheduler has work to perform. */ +static int +have_work(void) +{ + smartlist_t *cp = get_channels_pending(); + IF_BUG_ONCE(!cp) { + return 0; // channels_pending doesn't exist so... no work? + } + return smartlist_len(cp) > 0; +} + +/** Re-trigger the scheduler in a way safe to use from the callback */ + +static void +vanilla_scheduler_schedule(void) +{ + if (!have_work()) { + return; + } + + /* Activate our event so it can process channels. */ + scheduler_ev_active(EV_TIMEOUT); +} + +static void +vanilla_scheduler_run(void) +{ + int n_cells, n_chans_before, n_chans_after; + ssize_t flushed, flushed_this_time; + smartlist_t *cp = get_channels_pending(); + smartlist_t *to_readd = NULL; + channel_t *chan = NULL; + + log_debug(LD_SCHED, "We have a chance to run the scheduler"); + + n_chans_before = smartlist_len(cp); + + while (smartlist_len(cp) > 0) { + /* Pop off a channel */ + chan = smartlist_pqueue_pop(cp, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx)); + IF_BUG_ONCE(!chan) { + /* Some-freaking-how a NULL got into the channels_pending. That should + * never happen, but it should be harmless to ignore it and keep looping. + */ + continue; + } + + /* Figure out how many cells we can write */ + n_cells = channel_num_cells_writeable(chan); + if (n_cells > 0) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "%d cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + + flushed = 0; + while (flushed < n_cells) { + flushed_this_time = + channel_flush_some_cells(chan, + MIN(MAX_FLUSH_CELLS, (size_t) n_cells - flushed)); + if (flushed_this_time <= 0) break; + flushed += flushed_this_time; + } + + if (flushed < n_cells) { + /* We ran out of cells to flush */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* The channel may still have some cells */ + if (channel_more_to_flush(chan)) { + /* The channel goes to either pending or waiting_to_write */ + if (channel_num_cells_writeable(chan) > 0) { + /* Add it back to pending later */ + if (!to_readd) to_readd = smartlist_new(); + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "is still pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* It's waiting to be able to write more */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_to_write from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } else { + /* No cells left; it can go to idle or waiting_for_cells */ + if (channel_num_cells_writeable(chan) > 0) { + /* + * It can still accept writes, so it goes to + * waiting_for_cells + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* + * We exactly filled up the output queue with all available + * cells; go to idle. + */ + chan->scheduler_state = SCHED_CHAN_IDLE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "become idle from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } + } + + log_debug(LD_SCHED, + "Scheduler flushed %d cells onto pending channel " + U64_FORMAT " at %p", + (int)flushed, U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + log_info(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "no cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan); + /* Put it back to WAITING_TO_WRITE */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + } + } + + /* Readd any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, readd_chan) { + readd_chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(cp, + scheduler_compare_channels, + offsetof(channel_t, sched_heap_idx), + readd_chan); + } SMARTLIST_FOREACH_END(readd_chan); + smartlist_free(to_readd); + } + + n_chans_after = smartlist_len(cp); + log_debug(LD_SCHED, "Scheduler handled %d of %d pending channels", + n_chans_before - n_chans_after, n_chans_before); +} + +/* Stores the vanilla scheduler function pointers. */ +static scheduler_t vanilla_scheduler = { + .type = SCHEDULER_VANILLA, + .free_all = NULL, + .on_channel_free = NULL, + .init = NULL, + .on_new_consensus = NULL, + .schedule = vanilla_scheduler_schedule, + .run = vanilla_scheduler_run, + .on_new_options = NULL, +}; + +scheduler_t * +get_vanilla_scheduler(void) +{ + return &vanilla_scheduler; +} + diff --git a/src/or/shared_random.c b/src/or/shared_random.c index 25ca0611cd..b3f62a8fd8 100644 --- a/src/or/shared_random.c +++ b/src/or/shared_random.c @@ -1333,13 +1333,7 @@ sr_act_post_consensus(const networkstatus_t *consensus) } /* Prepare our state so that it's ready for the next voting period. */ - { - voting_schedule_t *voting_schedule = - get_voting_schedule(options,time(NULL), LOG_NOTICE); - time_t interval_starts = voting_schedule->interval_starts; - sr_state_update(interval_starts); - voting_schedule_free(voting_schedule); - } + sr_state_update(dirvote_get_next_valid_after_time()); } /* Initialize shared random subsystem. This MUST be called early in the boot @@ -1390,6 +1384,52 @@ sr_get_previous_for_control(void) return srv_str; } +/* Return current shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_current(const networkstatus_t *ns) +{ + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.current_srv; + } + return NULL; +} + +/* Return previous shared random value from the latest consensus. Caller can + * NOT keep a reference to the returned pointer. Return NULL if none. */ +const sr_srv_t * +sr_get_previous(const networkstatus_t *ns) +{ + const networkstatus_t *consensus; + + /* Use provided ns else get a live one */ + if (ns) { + consensus = ns; + } else { + consensus = networkstatus_get_live_consensus(approx_time()); + } + /* Ideally we would never be asked for an SRV without a live consensus. Make + * sure this assumption is correct. */ + tor_assert_nonfatal(consensus); + + if (consensus) { + return consensus->sr_info.previous_srv; + } + return NULL; +} + #ifdef TOR_UNIT_TESTS /* Set the global value of number of SRV agreements so the test can play @@ -1401,5 +1441,5 @@ set_num_srv_agreements(int32_t value) num_srv_agreements_from_vote = value; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/shared_random.h b/src/or/shared_random.h index 1f027c70e0..c0992489cb 100644 --- a/src/or/shared_random.h +++ b/src/or/shared_random.h @@ -130,6 +130,9 @@ sr_commit_t *sr_generate_our_commit(time_t timestamp, char *sr_get_current_for_control(void); char *sr_get_previous_for_control(void); +const sr_srv_t *sr_get_current(const networkstatus_t *ns); +const sr_srv_t *sr_get_previous(const networkstatus_t *ns); + #ifdef SHARED_RANDOM_PRIVATE /* Encode */ @@ -157,7 +160,7 @@ STATIC int should_keep_commit(const sr_commit_t *commit, sr_phase_t phase); STATIC void save_commit_during_reveal_phase(const sr_commit_t *commit); -#endif /* SHARED_RANDOM_PRIVATE */ +#endif /* defined(SHARED_RANDOM_PRIVATE) */ #ifdef TOR_UNIT_TESTS @@ -165,5 +168,5 @@ void set_num_srv_agreements(int32_t value); #endif /* TOR_UNIT_TESTS */ -#endif /* TOR_SHARED_RANDOM_H */ +#endif /* !defined(TOR_SHARED_RANDOM_H) */ diff --git a/src/or/shared_random_state.c b/src/or/shared_random_state.c index 89d2e8d7f6..ae904cfda3 100644 --- a/src/or/shared_random_state.c +++ b/src/or/shared_random_state.c @@ -40,10 +40,14 @@ static const char dstate_commit_key[] = "Commit"; static const char dstate_prev_srv_key[] = "SharedRandPreviousValue"; static const char dstate_cur_srv_key[] = "SharedRandCurrentValue"; +/** dummy instance of sr_disk_state_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t); + /* These next two are duplicates or near-duplicates from config.c */ #define VAR(name, conftype, member, initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(sr_disk_state_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(sr_disk_state_t, member), \ + initvalue CONF_TEST_MEMBERS(sr_disk_state_t, conftype, member) } /* As VAR, but the option name and member name are the same. */ #define V(member, conftype, initvalue) \ VAR(#member, conftype, member, initvalue) @@ -70,21 +74,22 @@ static config_var_t state_vars[] = { V(SharedRandValues, LINELIST_V, NULL), VAR("SharedRandPreviousValue",LINELIST_S, SharedRandValues, NULL), VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + END_OF_CONFIG_VARS }; /* "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ static config_var_t state_extra_var = { "__extra", CONFIG_TYPE_LINELIST, - STRUCT_OFFSET(sr_disk_state_t, ExtraLines), NULL + offsetof(sr_disk_state_t, ExtraLines), NULL + CONF_TEST_MEMBERS(sr_disk_state_t, LINELIST, ExtraLines) }; /* Configuration format of sr_disk_state_t. */ static const config_format_t state_format = { sizeof(sr_disk_state_t), SR_DISK_STATE_MAGIC, - STRUCT_OFFSET(sr_disk_state_t, magic_), + offsetof(sr_disk_state_t, magic_), NULL, NULL, state_vars, @@ -133,27 +138,56 @@ get_voting_interval(void) /* Given the time <b>now</b>, return the start time of the current round of * the SR protocol. For example, if it's 23:47:08, the current round thus * started at 23:47:00 for a voting interval of 10 seconds. */ -static time_t -get_start_time_of_current_round(time_t now) +STATIC time_t +get_start_time_of_current_round(void) { const or_options_t *options = get_options(); int voting_interval = get_voting_interval(); - voting_schedule_t *new_voting_schedule = - get_voting_schedule(options, now, LOG_INFO); - tor_assert(new_voting_schedule); - /* First, get the start time of the next round */ - time_t next_start = new_voting_schedule->interval_starts; + time_t next_start = dirvote_get_next_valid_after_time(); /* Now roll back next_start by a voting interval to find the start time of the current round. */ time_t curr_start = dirvote_get_start_of_next_interval( next_start - voting_interval - 1, voting_interval, options->TestingV3AuthVotingStartOffset); + return curr_start; +} - voting_schedule_free(new_voting_schedule); +/** Return the start time of the current SR protocol run. For example, if the + * time is 23/06/2017 23:47:08 and a full SR protocol run is 24 hours, this + * function should return 23/06/2017 00:00:00. */ +time_t +sr_state_get_start_time_of_current_protocol_run(time_t now) +{ + int total_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + int voting_interval = get_voting_interval(); + /* Find the time the current round started. */ + time_t beginning_of_current_round = get_start_time_of_current_round(); - return curr_start; + /* Get current SR protocol round */ + int current_round = (now / voting_interval) % total_rounds; + + /* Get start time by subtracting the time elapsed from the beginning of the + protocol run */ + time_t time_elapsed_since_start_of_run = current_round * voting_interval; + return beginning_of_current_round - time_elapsed_since_start_of_run; +} + +/** Return the time (in seconds) it takes to complete a full SR protocol phase + * (e.g. the commit phase). */ +unsigned int +sr_state_get_phase_duration(void) +{ + return SHARED_RANDOM_N_ROUNDS * get_voting_interval(); +} + +/** Return the time (in seconds) it takes to complete a full SR protocol run */ +unsigned int +sr_state_get_protocol_run_duration(void) +{ + int total_protocol_rounds = SHARED_RANDOM_N_ROUNDS * SHARED_RANDOM_N_PHASES; + return total_protocol_rounds * get_voting_interval(); } /* Return the time we should expire the state file created at <b>now</b>. @@ -167,7 +201,7 @@ get_state_valid_until_time(time_t now) voting_interval = get_voting_interval(); /* Find the time the current round started. */ - beginning_of_current_round = get_start_time_of_current_round(now); + beginning_of_current_round = get_start_time_of_current_round(); /* Find how many rounds are left till the end of the protocol run */ current_round = (now / voting_interval) % total_rounds; @@ -1329,7 +1363,7 @@ sr_state_init(int save_to_disk, int read_from_disk) /* We have a state in memory, let's make sure it's updated for the current * and next voting round. */ { - time_t valid_after = get_next_valid_after_time(now); + time_t valid_after = dirvote_get_next_valid_after_time(); sr_state_update(valid_after); } return 0; @@ -1355,5 +1389,5 @@ get_sr_state(void) return sr_state; } -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ diff --git a/src/or/shared_random_state.h b/src/or/shared_random_state.h index 3526ad47d3..866725c435 100644 --- a/src/or/shared_random_state.h +++ b/src/or/shared_random_state.h @@ -77,7 +77,7 @@ typedef struct sr_state_t { typedef struct sr_disk_state_t { uint32_t magic_; /* Version of the protocol. */ - uint32_t Version; + int Version; /* Version of our running tor. */ char *TorVersion; /* Creation time of this state */ @@ -121,11 +121,16 @@ int sr_state_is_initialized(void); void sr_state_save(void); void sr_state_free(void); +time_t sr_state_get_start_time_of_current_protocol_run(time_t now); +unsigned int sr_state_get_phase_duration(void); +unsigned int sr_state_get_protocol_run_duration(void); + #ifdef SHARED_RANDOM_STATE_PRIVATE STATIC int disk_state_load_from_disk_impl(const char *fname); STATIC sr_phase_t get_sr_protocol_phase(time_t valid_after); +STATIC time_t get_start_time_of_current_round(void); STATIC time_t get_state_valid_until_time(time_t now); STATIC const char *get_phase_str(sr_phase_t phase); @@ -134,14 +139,14 @@ STATIC void new_protocol_run(time_t valid_after); STATIC void state_rotate_srv(void); STATIC int is_phase_transition(sr_phase_t next_phase); -#endif /* SHARED_RANDOM_STATE_PRIVATE */ +#endif /* defined(SHARED_RANDOM_STATE_PRIVATE) */ #ifdef TOR_UNIT_TESTS STATIC void set_sr_phase(sr_phase_t phase); STATIC sr_state_t *get_sr_state(void); -#endif /* TOR_UNIT_TESTS */ +#endif /* defined(TOR_UNIT_TESTS) */ -#endif /* TOR_SHARED_RANDOM_STATE_H */ +#endif /* !defined(TOR_SHARED_RANDOM_STATE_H) */ diff --git a/src/or/statefile.c b/src/or/statefile.c index d0606b3012..97bd9cac36 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -34,6 +34,7 @@ #include "config.h" #include "confparse.h" #include "connection.h" +#include "control.h" #include "entrynodes.h" #include "hibernate.h" #include "rephist.h" @@ -53,10 +54,14 @@ static config_abbrev_t state_abbrevs_[] = { { NULL, NULL, 0, 0}, }; +/** dummy instance of or_state_t, used for type-checking its + * members with CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(or_state_t); + /*XXXX these next two are duplicates or near-duplicates from config.c */ #define VAR(name,conftype,member,initvalue) \ - { name, CONFIG_TYPE_ ## conftype, STRUCT_OFFSET(or_state_t, member), \ - initvalue } + { name, CONFIG_TYPE_ ## conftype, offsetof(or_state_t, member), \ + initvalue CONF_TEST_MEMBERS(or_state_t, conftype, member) } /** As VAR, but the option name and member name are the same. */ #define V(member,conftype,initvalue) \ VAR(#member, conftype, member, initvalue) @@ -85,6 +90,8 @@ static config_var_t state_vars_[] = { VAR("TransportProxy", LINELIST_S, TransportProxies, NULL), V(TransportProxies, LINELIST_V, NULL), + V(HidServRevCounter, LINELIST, NULL), + V(BWHistoryReadEnds, ISOTIME, NULL), V(BWHistoryReadInterval, UINT, "900"), V(BWHistoryReadValues, CSV, ""), @@ -113,7 +120,8 @@ static config_var_t state_vars_[] = { V(CircuitBuildAbandonedCount, UINT, "0"), VAR("CircuitBuildTimeBin", LINELIST_S, BuildtimeHistogram, NULL), VAR("BuildtimeHistogram", LINELIST_V, BuildtimeHistogram, NULL), - { NULL, CONFIG_TYPE_OBSOLETE, 0, NULL } + + END_OF_CONFIG_VARS }; #undef VAR @@ -131,14 +139,15 @@ static int or_state_validate_cb(void *old_options, void *options, /** "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ static config_var_t state_extra_var = { - "__extra", CONFIG_TYPE_LINELIST, STRUCT_OFFSET(or_state_t, ExtraLines), NULL + "__extra", CONFIG_TYPE_LINELIST, offsetof(or_state_t, ExtraLines), NULL + CONF_TEST_MEMBERS(or_state_t, LINELIST, ExtraLines) }; /** Configuration format for or_state_t. */ static const config_format_t state_format = { sizeof(or_state_t), OR_STATE_MAGIC, - STRUCT_OFFSET(or_state_t, magic_), + offsetof(or_state_t, magic_), state_abbrevs_, NULL, state_vars_, @@ -402,10 +411,15 @@ or_state_load(void) log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); /* Warn the user if their clock has been set backwards, * they could be tricked into using old consensuses */ - time_t apparent_skew = new_state->LastWritten - time(NULL); - if (apparent_skew > 0) + time_t apparent_skew = time(NULL) - new_state->LastWritten; + if (apparent_skew < 0) { + /* Initialize bootstrap event reporting because we might call + * clock_skew_warning() before the bootstrap state is + * initialized, causing an assertion failure. */ + control_event_bootstrap(BOOTSTRAP_STATUS_STARTING, 0); clock_skew_warning(NULL, (long)apparent_skew, 1, LD_GENERAL, "local state file", fname); + } } else { log_info(LD_GENERAL, "Initialized state"); } @@ -657,8 +671,6 @@ save_transport_to_state(const char *transport, *next = line = tor_malloc_zero(sizeof(config_line_t)); line->key = tor_strdup("TransportProxy"); tor_asprintf(&line->value, "%s %s", transport, fmt_addrport(addr, port)); - - next = &(line->next); } if (!get_options()->AvoidDiskWrites) diff --git a/src/or/statefile.h b/src/or/statefile.h index 10c09324bc..574afb3622 100644 --- a/src/or/statefile.h +++ b/src/or/statefile.h @@ -24,5 +24,5 @@ STATIC void or_state_free(or_state_t *state); STATIC or_state_t *or_state_new(void); #endif -#endif +#endif /* !defined(TOR_STATEFILE_H) */ diff --git a/src/or/status.h b/src/or/status.h index c1a0033ce0..49da6abc0f 100644 --- a/src/or/status.h +++ b/src/or/status.h @@ -14,5 +14,5 @@ STATIC char *secs_to_uptime(long secs); STATIC char *bytes_to_usage(uint64_t bytes); #endif -#endif +#endif /* !defined(TOR_STATUS_H) */ diff --git a/src/or/torcert.c b/src/or/torcert.c index 658e620ca5..befb39d6e8 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -76,29 +76,38 @@ tor_cert_sign_impl(const ed25519_keypair_t *signing_key, ed25519_signature_t signature; if (ed25519_sign(&signature, encoded, real_len-ED25519_SIG_LEN, signing_key)<0) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Can't sign certificate"); goto err; + /* LCOV_EXCL_STOP */ } memcpy(sig, signature.sig, ED25519_SIG_LEN); torcert = tor_cert_parse(encoded, real_len); if (! torcert) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Generated a certificate we cannot parse"); goto err; + /* LCOV_EXCL_STOP */ } if (tor_cert_checksig(torcert, &signing_key->pubkey, now) < 0) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Generated a certificate whose signature we can't check"); goto err; + /* LCOV_EXCL_STOP */ } tor_free(encoded); goto done; + /* LCOV_EXCL_START */ err: tor_cert_free(torcert); torcert = NULL; + /* LCOV_EXCL_STOP */ + done: ed25519_cert_free(cert); tor_free(encoded); @@ -356,12 +365,12 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, * * Return 0 on success, negative on failure. */ -int -rsa_ed25519_crosscert_check(const uint8_t *crosscert, - const size_t crosscert_len, - const crypto_pk_t *rsa_id_key, - const ed25519_public_key_t *master_key, - const time_t reject_if_expired_before) +MOCK_IMPL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)) { rsa_ed_crosscert_t *cc = NULL; int rv; @@ -393,7 +402,7 @@ rsa_ed25519_crosscert_check(const uint8_t *crosscert, } const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc); - const uint64_t expiration_time = expiration_date * 3600; + const uint64_t expiration_time = ((uint64_t)expiration_date) * 3600; if (reject_if_expired_before < 0 || expiration_time < (uint64_t)reject_if_expired_before) { @@ -675,8 +684,10 @@ tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out) if (base64_encode(ed_cert_b64, ed_cert_b64_len, (const char *) cert->encoded, cert->encoded_len, BASE64_ENCODE_MULTILINE) < 0) { + /* LCOV_EXCL_START */ log_err(LD_BUG, "Couldn't base64-encode ed22519 cert!"); goto err; + /* LCOV_EXCL_STOP */ } /* Put everything together in a NUL terminated string. */ diff --git a/src/or/torcert.h b/src/or/torcert.h index 51f7665f1e..c77ae2089d 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -75,11 +75,12 @@ ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, const crypto_pk_t *rsa_key, time_t expires, uint8_t **cert); -int rsa_ed25519_crosscert_check(const uint8_t *crosscert, - const size_t crosscert_len, - const crypto_pk_t *rsa_id_key, - const ed25519_public_key_t *master_key, - const time_t reject_if_expired_before); +MOCK_DECL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)); or_handshake_certs_t *or_handshake_certs_new(void); void or_handshake_certs_free(or_handshake_certs_t *certs); @@ -100,5 +101,5 @@ void or_handshake_certs_check_both(int severity, int tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out); -#endif +#endif /* !defined(TORCERT_H_INCLUDED) */ diff --git a/src/or/transports.c b/src/or/transports.c index 31849a8d15..5fb24e11a0 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -527,12 +527,12 @@ launch_managed_proxy(managed_proxy_t *mp) (const char **)mp->argv, env, &mp->process_handle); -#else +#else /* !(defined(_WIN32)) */ retval = tor_spawn_background(mp->argv[0], (const char **)mp->argv, env, &mp->process_handle); -#endif +#endif /* defined(_WIN32) */ process_environment_free(env); @@ -1094,8 +1094,6 @@ parse_smethod_line(const char *line, managed_proxy_t *mp) transport = transport_new(&tor_addr, port, method_name, PROXY_NONE, args_string); - if (!transport) - goto err; smartlist_add(mp->transports, transport); @@ -1186,8 +1184,6 @@ parse_cmethod_line(const char *line, managed_proxy_t *mp) } transport = transport_new(&tor_addr, port, method_name, socks_ver, NULL); - if (!transport) - goto err; smartlist_add(mp->transports, transport); diff --git a/src/or/transports.h b/src/or/transports.h index 44a9626e50..e368e447c3 100644 --- a/src/or/transports.h +++ b/src/or/transports.h @@ -133,7 +133,7 @@ STATIC char* get_pt_proxy_uri(void); STATIC void free_execve_args(char **arg); -#endif +#endif /* defined(PT_PRIVATE) */ -#endif +#endif /* !defined(TOR_TRANSPORTS_H) */ diff --git a/src/rust/tor_util/include.am b/src/rust/tor_util/include.am index f0cd63920c..ec3898577b 100644 --- a/src/rust/tor_util/include.am +++ b/src/rust/tor_util/include.am @@ -4,7 +4,7 @@ EXTRA_DIST +=\ src/rust/tor_util/ffi.rs \ src/rust/tor_util/rust_string.rs -src/rust/target/release/libtor_util.a: FORCE +src/rust/target/release/@TOR_RUST_UTIL_STATIC_NAME@: FORCE ( cd "$(abs_top_srcdir)/src/rust/tor_util" ; \ CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \ CARGO_HOME="$(abs_top_builddir)/src/rust" \ diff --git a/src/test/bench.c b/src/test/bench.c index a44dc94a61..b7b123eee2 100644 --- a/src/test/bench.c +++ b/src/test/bench.c @@ -58,7 +58,7 @@ perftime(void) return timespec_to_nsec(&ts) - nanostart; } -#else +#else /* !(defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)) */ static struct timeval tv_start = { 0, 0 }; static void reset_perftime(void) @@ -73,7 +73,7 @@ perftime(void) timersub(&now, &tv_start, &out); return ((uint64_t)out.tv_sec)*1000000000 + out.tv_usec*1000; } -#endif +#endif /* defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID) */ #define NANOCOUNT(start,end,iters) \ ( ((double)((end)-(start))) / (iters) ) @@ -200,6 +200,7 @@ bench_onion_ntor_impl(void) curve25519_public_key_generate(&keypair2.pubkey, &keypair2.seckey); dimap_add_entry(&keymap, keypair1.pubkey.public_key, &keypair1); dimap_add_entry(&keymap, keypair2.pubkey.public_key, &keypair2); + crypto_rand((char *)nodeid, sizeof(nodeid)); reset_perftime(); start = perftime(); diff --git a/src/test/ed25519_exts_ref.py b/src/test/ed25519_exts_ref.py index af5010415e..f84d3002d3 100644 --- a/src/test/ed25519_exts_ref.py +++ b/src/test/ed25519_exts_ref.py @@ -32,8 +32,7 @@ def curve25519ToEd25519(c, sign): return encodepoint([x,y]) def blindESK(esk, param): - h = H("Derive temporary signing key" + param) - mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) + mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2)) s = decodeint(esk[:32]) s_prime = (s * mult) % ell k = esk[32:] @@ -42,8 +41,7 @@ def blindESK(esk, param): return encodeint(s_prime) + k_prime def blindPK(pk, param): - h = H("Derive temporary signing key" + param) - mult = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) + mult = 2**(b-2) + sum(2**i * bit(param,i) for i in range(3,b-2)) P = decodepoint(pk) return encodepoint(scalarmult(P, mult)) @@ -69,6 +67,11 @@ def signatureWithESK(m,h,pk): def newSK(): return os.urandom(32) +def random_scalar(entropy_f): # 0..L-1 inclusive + # reduce the bias to a safe level by generating 256 extra bits + oversized = int(binascii.hexlify(entropy_f(32+32)), 16) + return oversized % ell + # ------------------------------------------------------------ MSG = "This is extremely silly. But it is also incredibly serious business!" @@ -126,6 +129,31 @@ class SelfTest(unittest.TestCase): self._testSignatures(besk, bpk) + def testIdentity(self): + # Base point: + # B is the unique point (x, 4/5) \in E for which x is positive + By = 4 * inv(5) + Bx = xrecover(By) + B = [Bx % q,By % q] + + # Get identity E by doing: E = l*B, where l is the group order + identity = scalarmult(B, ell) + + # Get identity E by doing: E = l*A, where A is a random point + sk = newSK() + pk = decodepoint(publickey(sk)) + identity2 = scalarmult(pk, ell) + + # Check that identities match + assert(identity == identity2) + # Check that identity is the point (0,1) + assert(identity == [0L,1L]) + + # Check identity element: a*E = E, where a is a random scalar + scalar = random_scalar(os.urandom) + result = scalarmult(identity, scalar) + assert(result == identity == identity2) + # ------------------------------------------------------------ # From pprint.pprint([ binascii.b2a_hex(os.urandom(32)) for _ in xrange(8) ]) diff --git a/src/test/ed25519_vectors.inc b/src/test/ed25519_vectors.inc index 760bafb971..60c863beba 100644 --- a/src/test/ed25519_vectors.inc +++ b/src/test/ed25519_vectors.inc @@ -91,21 +91,21 @@ static const char *ED25519_BLINDING_PARAMS[] = { * blinding parameter. */ static const char *ED25519_BLINDED_SECRET_KEYS[] = { - "014e83abadb2ca9a27e0ffe23920333d817729f48700e97656ec2823d694050e171d43" + "293c3acff4e902f6f63ddc5d5caa2a57e771db4f24de65d4c28df3232f47fa01171d43" "f24e3f53e70ec7ac280044ac77d4942dee5d6807118a59bdf3ee647e89", - "fad8cca0b4335847795288b1452508752b253e64e6c7c78d4a02dbbd7d46aa0eb8ceff" + "38b88f9f9440358da544504ee152fb475528f7c51c285bd1c68b14ade8e29a07b8ceff" "20dfcf53eb52b891fc078c934efbf0353af7242e7dc51bb32a093afa29", - "116eb0ae0a4a91763365bdf86db427b00862db448487808788cc339ac10e5e089217f5" + "4d03ce16a3f3249846aac9de0a0075061495c3b027248eeee47da4ddbaf9e0049217f5" "2e92797462bd890fc274672e05c98f2c82970d640084781334aae0f940", - "bd1fbb0ee5acddc4adbcf5f33e95d9445f40326ce579fdd764a24483a9ccb20f509ece" + "51d7db01aaa0d937a9fd7c8c7381445a14d8fa61f43347af5460d7cd8fda9904509ece" "e77082ce088f7c19d5a00e955eeef8df6fa41686abc1030c2d76807733", - "237f5345cefe8573ce9fa7e216381a1172796c9e3f70668ab503b1352952530fb57b95" + "1f76cab834e222bd2546efa7e073425680ab88df186ff41327d3e40770129b00b57b95" "a440570659a440a3e4771465022a8e67af86bdf2d0990c54e7bb87ff9a", - "ba8ff23bc4ad2b739e1ccffc9fbc7837053ea81cdfdb15073f56411cfbae1d0ec492fc" + "c23588c23ee76093419d07b27c6df5922a03ac58f96c53671456a7d1bdbf560ec492fc" "87d5ec2a1b185ca5a40541fdef0b1e128fd5c2380c888bfa924711bcab", - "0fa68f969de038c7a90a4a74ee6167c77582006f2dedecc1956501ba6b6fb10391b476" + "3ed249c6932d076e1a2f6916975914b14e8c739da00992358b8f37d3e790650691b476" "8f8e556d78f4bdcb9a13b6f6066fe81d3134ae965dc48cd0785b3af2b8", - "deaa3456d1c21944d5dcd361a646858c6cf9336b0a6851d925717eb1ae186902053d9c" + "288cbfd923cb286d48c084555b5bdd06c05e92fb81acdb45271367f57515380e053d9c" "00c81e1331c06ab50087be8cfc7dc11691b132614474f1aa9c2503cccd", }; @@ -115,14 +115,14 @@ static const char *ED25519_BLINDED_SECRET_KEYS[] = { * blinding parameter. */ static const char *ED25519_BLINDED_PUBLIC_KEYS[] = { - "722d6da6348e618967ef782e71061e27163a8b35f21856475d9d2023f65b6495", - "1dffa0586da6cbfcff2024eedf4fc6c818242d9a82dbbe635d6da1b975a1160d", - "5ed81f98fed5a6acda4ea6da2c34fab0ab359d950c510c256473f1f33ff438b4", - "6e6f92a54fb282120c46d9603df41135f025bc1f58f283809d04be96aeb04040", - "cda236f28edc4c7e02d18007b8dab49d669265b0f7aefb1824d7cc8e73a2cd63", - "367b03b17b67ca7329b89a520bdab91782402a41cd67264e34b5541a4b3f875b", - "8d486b03ac4e3b486b7a1d563706c7fdac75aee789a7cf6f22789eedeff61a31", - "9f297ff0aa2ceda91c5ab1b6446f12533d145940de6d850dc323417afde0cb78", + "1fc1fa4465bd9d4956fdbdc9d3acb3c7019bb8d5606b951c2e1dfe0b42eaeb41", + "1cbbd4a88ce8f165447f159d9f628ada18674158c4f7c5ead44ce8eb0fa6eb7e", + "c5419ad133ffde7e0ac882055d942f582054132b092de377d587435722deb028", + "3e08d0dc291066272e313014bfac4d39ad84aa93c038478a58011f431648105f", + "59381f06acb6bf1389ba305f70874eed3e0f2ab57cdb7bc69ed59a9b8899ff4d", + "2b946a484344eb1c17c89dd8b04196a84f3b7222c876a07a4cece85f676f87d9", + "c6b585129b135f8769df2eba987e76e089e80ba3a2a6729134d3b28008ac098e", + "0eefdc795b59cabbc194c6174e34ba9451e8355108520554ec285acabebb34ac", }; /** diff --git a/src/test/fuzz/dict/hsdescv3 b/src/test/fuzz/dict/hsdescv3 new file mode 100644 index 0000000000..84e8db578a --- /dev/null +++ b/src/test/fuzz/dict/hsdescv3 @@ -0,0 +1,6 @@ +"hs-descriptor" +"descriptor-lifetime" +"descriptor-signing-key-cert" +"revision-counter" +"superencrypted" +"signature" diff --git a/src/test/fuzz/fuzz_hsdescv3.c b/src/test/fuzz/fuzz_hsdescv3.c new file mode 100644 index 0000000000..428774e330 --- /dev/null +++ b/src/test/fuzz/fuzz_hsdescv3.c @@ -0,0 +1,99 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define ROUTERPARSE_PRIVATE +#define HS_DESCRIPTOR_PRIVATE + +#include "or.h" +#include "ed25519_cert.h" /* Trunnel interface. */ +#include "crypto_ed25519.h" +#include "hs_descriptor.h" +#include "routerparse.h" +#include "util.h" + +#include "fuzzing.h" + +static void +mock_dump_desc__nodump(const char *desc, const char *type) +{ + (void)desc; + (void)type; +} + +static int +mock_rsa_ed25519_crosscert_check(const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before) +{ + (void) crosscert; + (void) crosscert_len; + (void) rsa_id_key; + (void) master_key; + (void) reject_if_expired_before; + return 0; +} + +static size_t +mock_decrypt_desc_layer(const hs_descriptor_t *desc, + const uint8_t *encrypted_blob, + size_t encrypted_blob_size, + int is_superencrypted_layer, + char **decrypted_out) +{ + (void)is_superencrypted_layer; + (void)desc; + const size_t overhead = HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN; + if (encrypted_blob_size < overhead) + return 0; + *decrypted_out = tor_memdup_nulterm( + encrypted_blob + HS_DESC_ENCRYPTED_SALT_LEN, + encrypted_blob_size - overhead); + size_t result = strlen(*decrypted_out); + if (result) { + return result; + } else { + tor_free(*decrypted_out); + return 0; + } +} + +int +fuzz_init(void) +{ + disable_signature_checking(); + MOCK(dump_desc, mock_dump_desc__nodump); + MOCK(rsa_ed25519_crosscert_check, mock_rsa_ed25519_crosscert_check); + MOCK(decrypt_desc_layer, mock_decrypt_desc_layer); + ed25519_init(); + return 0; +} + +int +fuzz_cleanup(void) +{ + return 0; +} + +int +fuzz_main(const uint8_t *data, size_t sz) +{ + hs_descriptor_t *desc = NULL; + uint8_t subcredential[DIGEST256_LEN]; + + char *fuzzing_data = tor_memdup_nulterm(data, sz); + memset(subcredential, 'A', sizeof(subcredential)); + + hs_desc_decode_descriptor(fuzzing_data, subcredential, &desc); + if (desc) { + log_debug(LD_GENERAL, "Decoding okay"); + hs_descriptor_free(desc); + } else { + log_debug(LD_GENERAL, "Decoding failed"); + } + + tor_free(fuzzing_data); + return 0; +} + diff --git a/src/test/fuzz/fuzz_http_connect.c b/src/test/fuzz/fuzz_http_connect.c new file mode 100644 index 0000000000..dc674070b2 --- /dev/null +++ b/src/test/fuzz/fuzz_http_connect.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define BUFFERS_PRIVATE +#define CONNECTION_EDGE_PRIVATE + +#include "or.h" +#include "backtrace.h" +#include "buffers.h" +#include "config.h" +#include "connection.h" +#include "connection_edge.h" +#include "proto_socks.h" +#include "torlog.h" + +#include "fuzzing.h" + +static void +mock_connection_write_to_buf_impl_(const char *string, size_t len, + connection_t *conn, int compressed) +{ + log_debug(LD_GENERAL, "%sResponse:\n%u\nConnection: %p\n%s\n", + compressed ? "Compressed " : "", (unsigned)len, conn, string); +} + +static void +mock_connection_mark_unattached_ap_(entry_connection_t *conn, int endreason, + int line, const char *file) +{ + (void)conn; + (void)endreason; + (void)line; + (void)file; +} + +static int +mock_connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath) +{ + (void)conn; + (void)circ; + (void)cpath; + return 0; +} + +int +fuzz_init(void) +{ + /* Set up fake response handler */ + MOCK(connection_write_to_buf_impl_, mock_connection_write_to_buf_impl_); + /* Set up the fake handler functions */ + MOCK(connection_mark_unattached_ap_, mock_connection_mark_unattached_ap_); + MOCK(connection_ap_rewrite_and_attach_if_allowed, + mock_connection_ap_rewrite_and_attach_if_allowed); + + return 0; +} + +int +fuzz_cleanup(void) +{ + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(connection_mark_unattached_ap_); + UNMOCK(connection_ap_rewrite_and_attach_if_allowed); + return 0; +} + +int +fuzz_main(const uint8_t *stdin_buf, size_t data_size) +{ + entry_connection_t conn; + + /* Set up the fake connection */ + memset(&conn, 0, sizeof(conn)); + conn.edge_.base_.type = CONN_TYPE_AP; + conn.edge_.base_.state = AP_CONN_STATE_HTTP_CONNECT_WAIT; + conn.socks_request = tor_malloc_zero(sizeof(socks_request_t)); + conn.socks_request->listener_type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; + + conn.edge_.base_.inbuf = buf_new_with_data((char*)stdin_buf, data_size); + if (!conn.edge_.base_.inbuf) { + log_debug(LD_GENERAL, "Zero-Length-Input\n"); + goto done; + } + + /* Parse the headers */ + int rv = connection_ap_process_http_connect(&conn); + + /* TODO: check the output is correctly parsed based on the input */ + + log_debug(LD_GENERAL, "Result:\n%d\n", rv); + + goto done; + + done: + /* Reset. */ + socks_request_free(conn.socks_request); + buf_free(conn.edge_.base_.inbuf); + conn.edge_.base_.inbuf = NULL; + + return 0; +} + diff --git a/src/test/fuzz/fuzzing_common.c b/src/test/fuzz/fuzzing_common.c index 7aee92df63..1e98eb6c85 100644 --- a/src/test/fuzz/fuzzing_common.c +++ b/src/test/fuzz/fuzzing_common.c @@ -28,8 +28,9 @@ mock_crypto_pk_public_checksig__nocheck(const crypto_pk_t *env, char *to, (void)fromlen; /* We could look at from[0..fromlen-1] ... */ tor_assert(tolen >= crypto_pk_keysize(env)); - memset(to, 0x01, 20); - return 20; + size_t siglen = MIN(20, crypto_pk_keysize(env)); + memset(to, 0x01, siglen); + return (int)siglen; } static int @@ -107,7 +108,7 @@ global_init(void) configure_backtrace_handler(get_version()); /* set up the options. */ - mock_options = tor_malloc(sizeof(or_options_t)); + mock_options = tor_malloc_zero(sizeof(or_options_t)); MOCK(get_options, mock_get_options); /* Make BUG() and nonfatal asserts crash */ diff --git a/src/test/fuzz/include.am b/src/test/fuzz/include.am index 2961dab56f..cd16dc05be 100644 --- a/src/test/fuzz/include.am +++ b/src/test/fuzz/include.am @@ -15,13 +15,13 @@ FUZZING_LIBS = \ src/common/libor-ctime-testing.a \ src/common/libor-event-testing.a \ src/trunnel/libor-trunnel-testing.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ - @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ + @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ @CURVE25519_LIBS@ \ @TOR_SYSTEMD_LIBS@ \ @TOR_LZMA_LIBS@ \ - @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_ZSTD_LIBS@ oss-fuzz-prereqs: \ src/or/libtor-testing.a \ @@ -94,6 +94,14 @@ src_test_fuzz_fuzz_hsdescv2_CFLAGS = $(FUZZING_CFLAGS) src_test_fuzz_fuzz_hsdescv2_LDFLAGS = $(FUZZING_LDFLAG) src_test_fuzz_fuzz_hsdescv2_LDADD = $(FUZZING_LIBS) +src_test_fuzz_fuzz_hsdescv3_SOURCES = \ + src/test/fuzz/fuzzing_common.c \ + src/test/fuzz/fuzz_hsdescv3.c +src_test_fuzz_fuzz_hsdescv3_CPPFLAGS = $(FUZZING_CPPFLAGS) +src_test_fuzz_fuzz_hsdescv3_CFLAGS = $(FUZZING_CFLAGS) +src_test_fuzz_fuzz_hsdescv3_LDFLAGS = $(FUZZING_LDFLAG) +src_test_fuzz_fuzz_hsdescv3_LDADD = $(FUZZING_LIBS) + src_test_fuzz_fuzz_http_SOURCES = \ src/test/fuzz/fuzzing_common.c \ src/test/fuzz/fuzz_http.c @@ -102,6 +110,14 @@ src_test_fuzz_fuzz_http_CFLAGS = $(FUZZING_CFLAGS) src_test_fuzz_fuzz_http_LDFLAGS = $(FUZZING_LDFLAG) src_test_fuzz_fuzz_http_LDADD = $(FUZZING_LIBS) +src_test_fuzz_fuzz_http_connect_SOURCES = \ + src/test/fuzz/fuzzing_common.c \ + src/test/fuzz/fuzz_http_connect.c +src_test_fuzz_fuzz_http_connect_CPPFLAGS = $(FUZZING_CPPFLAGS) +src_test_fuzz_fuzz_http_connect_CFLAGS = $(FUZZING_CFLAGS) +src_test_fuzz_fuzz_http_connect_LDFLAGS = $(FUZZING_LDFLAG) +src_test_fuzz_fuzz_http_connect_LDADD = $(FUZZING_LIBS) + src_test_fuzz_fuzz_iptsv2_SOURCES = \ src/test/fuzz/fuzzing_common.c \ src/test/fuzz/fuzz_iptsv2.c @@ -133,7 +149,9 @@ FUZZERS = \ src/test/fuzz/fuzz-diff-apply \ src/test/fuzz/fuzz-extrainfo \ src/test/fuzz/fuzz-hsdescv2 \ + src/test/fuzz/fuzz-hsdescv3 \ src/test/fuzz/fuzz-http \ + src/test/fuzz/fuzz-http-connect \ src/test/fuzz/fuzz-iptsv2 \ src/test/fuzz/fuzz-microdesc \ src/test/fuzz/fuzz-vrs @@ -183,6 +201,13 @@ src_test_fuzz_lf_fuzz_hsdescv2_CFLAGS = $(LIBFUZZER_CFLAGS) src_test_fuzz_lf_fuzz_hsdescv2_LDFLAGS = $(LIBFUZZER_LDFLAG) src_test_fuzz_lf_fuzz_hsdescv2_LDADD = $(LIBFUZZER_LIBS) +src_test_fuzz_lf_fuzz_hsdescv3_SOURCES = \ + $(src_test_fuzz_fuzz_hsdescv3_SOURCES) +src_test_fuzz_lf_fuzz_hsdescv3_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) +src_test_fuzz_lf_fuzz_hsdescv3_CFLAGS = $(LIBFUZZER_CFLAGS) +src_test_fuzz_lf_fuzz_hsdescv3_LDFLAGS = $(LIBFUZZER_LDFLAG) +src_test_fuzz_lf_fuzz_hsdescv3_LDADD = $(LIBFUZZER_LIBS) + src_test_fuzz_lf_fuzz_http_SOURCES = \ $(src_test_fuzz_fuzz_http_SOURCES) src_test_fuzz_lf_fuzz_http_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) @@ -190,6 +215,13 @@ src_test_fuzz_lf_fuzz_http_CFLAGS = $(LIBFUZZER_CFLAGS) src_test_fuzz_lf_fuzz_http_LDFLAGS = $(LIBFUZZER_LDFLAG) src_test_fuzz_lf_fuzz_http_LDADD = $(LIBFUZZER_LIBS) +src_test_fuzz_lf_fuzz_http_connect_SOURCES = \ + $(src_test_fuzz_fuzz_http_connect_SOURCES) +src_test_fuzz_lf_fuzz_http_connect_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) +src_test_fuzz_lf_fuzz_http_connect_CFLAGS = $(LIBFUZZER_CFLAGS) +src_test_fuzz_lf_fuzz_http_connect_LDFLAGS = $(LIBFUZZER_LDFLAG) +src_test_fuzz_lf_fuzz_http_connect_LDADD = $(LIBFUZZER_LIBS) + src_test_fuzz_lf_fuzz_iptsv2_SOURCES = \ $(src_test_fuzz_fuzz_iptsv2_SOURCES) src_test_fuzz_lf_fuzz_iptsv2_CPPFLAGS = $(LIBFUZZER_CPPFLAGS) @@ -218,7 +250,9 @@ LIBFUZZER_FUZZERS = \ src/test/fuzz/lf-fuzz-diff-apply \ src/test/fuzz/lf-fuzz-extrainfo \ src/test/fuzz/lf-fuzz-hsdescv2 \ + src/test/fuzz/lf-fuzz-hsdescv3 \ src/test/fuzz/lf-fuzz-http \ + src/test/fuzz/lf-fuzz-http-connect \ src/test/fuzz/lf-fuzz-iptsv2 \ src/test/fuzz/lf-fuzz-microdesc \ src/test/fuzz/lf-fuzz-vrs @@ -260,11 +294,21 @@ src_test_fuzz_liboss_fuzz_hsdescv2_a_SOURCES = \ src_test_fuzz_liboss_fuzz_hsdescv2_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) src_test_fuzz_liboss_fuzz_hsdescv2_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) +src_test_fuzz_liboss_fuzz_hsdescv3_a_SOURCES = \ + $(src_test_fuzz_fuzz_hsdescv3_SOURCES) +src_test_fuzz_liboss_fuzz_hsdescv3_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) +src_test_fuzz_liboss_fuzz_hsdescv3_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) + src_test_fuzz_liboss_fuzz_http_a_SOURCES = \ $(src_test_fuzz_fuzz_http_SOURCES) src_test_fuzz_liboss_fuzz_http_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) src_test_fuzz_liboss_fuzz_http_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) +src_test_fuzz_liboss_fuzz_http_connect_a_SOURCES = \ + $(src_test_fuzz_fuzz_http_connect_SOURCES) +src_test_fuzz_liboss_fuzz_http_connect_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) +src_test_fuzz_liboss_fuzz_http_connect_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS) + src_test_fuzz_liboss_fuzz_iptsv2_a_SOURCES = \ $(src_test_fuzz_fuzz_iptsv2_SOURCES) src_test_fuzz_liboss_fuzz_iptsv2_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS) @@ -287,7 +331,9 @@ OSS_FUZZ_FUZZERS = \ src/test/fuzz/liboss-fuzz-diff-apply.a \ src/test/fuzz/liboss-fuzz-extrainfo.a \ src/test/fuzz/liboss-fuzz-hsdescv2.a \ + src/test/fuzz/liboss-fuzz-hsdescv3.a \ src/test/fuzz/liboss-fuzz-http.a \ + src/test/fuzz/liboss-fuzz-http-connect.a \ src/test/fuzz/liboss-fuzz-iptsv2.a \ src/test/fuzz/liboss-fuzz-microdesc.a \ src/test/fuzz/liboss-fuzz-vrs.a diff --git a/src/test/hs_build_address.py b/src/test/hs_build_address.py new file mode 100644 index 0000000000..7ff22c3a9a --- /dev/null +++ b/src/test/hs_build_address.py @@ -0,0 +1,38 @@ +import sys +import hashlib +import struct +import base64 + +# Python 3.6+, the SHA3 is available in hashlib natively. Else this requires +# the pysha3 package (pip install pysha3). +if sys.version_info < (3, 6): + import sha3 + +# Test vector to make sure the right sha3 version will be used. pysha3 < 1.0 +# used the old Keccak implementation. During the finalization of SHA3, NIST +# changed the delimiter suffix from 0x01 to 0x06. The Keccak sponge function +# stayed the same. pysha3 1.0 provides the previous Keccak hash, too. +TEST_VALUE = "e167f68d6563d75bb25f3aa49c29ef612d41352dc00606de7cbd630bb2665f51" +if TEST_VALUE != sha3.sha3_256(b"Hello World").hexdigest(): + print("pysha3 version is < 1.0. Please install from:") + print("https://github.com/tiran/pysha3https://github.com/tiran/pysha3") + sys.exit(1) + +# Checksum is built like so: +# CHECKSUM = SHA3(".onion checksum" || PUBKEY || VERSION) +PREFIX = ".onion checksum".encode() +# 32 bytes ed25519 pubkey from first test vector of +# https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-02#section-6 +PUBKEY = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a".decode('hex') +# Version 3 is proposal224 +VERSION = 3 + +data = struct.pack('15s32sb', PREFIX, PUBKEY, VERSION) +checksum = hashlib.sha3_256(data).digest() + +# Onion address is built like so: +# onion_address = base32(PUBKEY || CHECKSUM || VERSION) + ".onion" +address = struct.pack('!32s2sb', PUBKEY, checksum, VERSION) +onion_addr = base64.b32encode(address).decode().lower() + +print("%s" % (onion_addr)) diff --git a/src/test/hs_indexes.py b/src/test/hs_indexes.py new file mode 100644 index 0000000000..af0b81f8de --- /dev/null +++ b/src/test/hs_indexes.py @@ -0,0 +1,70 @@ +# +# The hidden service subsystem has two type of index. The first type is a +# value that each node in the network gets assigned to using their identity +# key which is their position in the hashring. (hs_build_hsdir_index()). +# +# The second type is a value that both the client and service computes to +# store/fetch the descriptor on the hashring. (hs_build_hs_index()). +# + +import sys +import hashlib +import struct +import base64 + +# Python 3.6+, the SHA3 is available in hashlib natively. Else this requires +# the pysha3 package (pip install pysha3). +if sys.version_info < (3, 6): + import sha3 + # Test vector to make sure the right sha3 version will be used. pysha3 < 1.0 + # used the old Keccak implementation. During the finalization of SHA3, NIST + # changed the delimiter suffix from 0x01 to 0x06. The Keccak sponge function + # stayed the same. pysha3 1.0 provides the previous Keccak hash, too. + TEST_VALUE = "e167f68d6563d75bb25f3aa49c29ef612d41352dc00606de7cbd630bb2665f51" + if TEST_VALUE != sha3.sha3_256(b"Hello World").hexdigest(): + print("pysha3 version is < 1.0. Please install from:") + print("https://github.com/tiran/pysha3https://github.com/tiran/pysha3") + sys.exit(1) + +# The first index we'll build is the position index in the hashring that is +# constructed by the hs_build_hsdir_index() function. Construction is: +# SHA3-256("node-idx" | node_identity | +# shared_random_value | INT_8(period_length) | INT_8(period_num) ) + +PREFIX = "node-idx".encode() +# 32 bytes ed25519 pubkey. +IDENTITY = ("\x42" * 32).encode() +# SRV is 32 bytes. +SRV = ("\x43" * 32).encode() +# Time period length is a 8 bytes value. +PERIOD_LEN = 1440 +# Period number is a 8 bytes value. +PERIOD_NUM = 42 + +data = struct.pack('!8s32s32sQQ', PREFIX, IDENTITY, SRV, PERIOD_NUM, + PERIOD_LEN) +hsdir_index = hashlib.sha3_256(data).hexdigest() + +print("[hs_build_hsdir_index] %s" % (hsdir_index)) + +# The second index we'll build is where the HS stores and the client fetches +# the descriptor on the hashring. It is constructed by the hs_build_hs_index() +# function and the construction is: +# SHA3-256("store-at-idx" | blinded_public_key | +# INT_8(replicanum) | INT_8(period_num) | INT_8(period_length) ) + +PREFIX = "store-at-idx".encode() +# 32 bytes ed25519 pubkey. +PUBKEY = ("\x42" * 32).encode() +# Replica number is a 8 bytes value. +REPLICA_NUM = 1 +# Time period length is a 8 bytes value. +PERIOD_LEN = 1440 +# Period number is a 8 bytes value. +PERIOD_NUM = 42 + +data = struct.pack('!12s32sQQQ', PREFIX, PUBKEY, REPLICA_NUM, PERIOD_LEN, + PERIOD_NUM) +hs_index = hashlib.sha3_256(data).hexdigest() + +print("[hs_build_hs_index] %s" % (hs_index)) diff --git a/src/test/hs_test_helpers.c b/src/test/hs_test_helpers.c index 3f0d6a9413..9355971267 100644 --- a/src/test/hs_test_helpers.c +++ b/src/test/hs_test_helpers.c @@ -6,6 +6,7 @@ #include "test.h" #include "torcert.h" +#include "hs_common.h" #include "hs_test_helpers.h" hs_desc_intro_point_t * @@ -15,31 +16,31 @@ hs_helper_build_intro_point(const ed25519_keypair_t *signing_kp, time_t now, int ret; ed25519_keypair_t auth_kp; hs_desc_intro_point_t *intro_point = NULL; - hs_desc_intro_point_t *ip = tor_malloc_zero(sizeof(*ip)); - ip->link_specifiers = smartlist_new(); + hs_desc_intro_point_t *ip = hs_desc_intro_point_new(); + /* For a usable intro point we need at least two link specifiers: One legacy + * keyid and one ipv4 */ { - hs_desc_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls)); - if (legacy) { - ls->type = LS_LEGACY_ID; - memcpy(ls->u.legacy_id, "0299F268FCA9D55CD157976D39AE92B4B455B3A8", - DIGEST_LEN); - } else { - ls->u.ap.port = 9001; - int family = tor_addr_parse(&ls->u.ap.addr, addr); - switch (family) { - case AF_INET: - ls->type = LS_IPV4; + hs_desc_link_specifier_t *ls_legacy = tor_malloc_zero(sizeof(*ls_legacy)); + hs_desc_link_specifier_t *ls_v4 = tor_malloc_zero(sizeof(*ls_v4)); + ls_legacy->type = LS_LEGACY_ID; + memcpy(ls_legacy->u.legacy_id, "0299F268FCA9D55CD157976D39AE92B4B455B3A8", + DIGEST_LEN); + ls_v4->u.ap.port = 9001; + int family = tor_addr_parse(&ls_v4->u.ap.addr, addr); + switch (family) { + case AF_INET: + ls_v4->type = LS_IPV4; break; case AF_INET6: - ls->type = LS_IPV6; + ls_v4->type = LS_IPV6; break; default: /* Stop the test, not suppose to have an error. */ tt_int_op(family, OP_EQ, AF_INET); - } } - smartlist_add(ip->link_specifiers, ls); + smartlist_add(ip->link_specifiers, ls_legacy); + smartlist_add(ip->link_specifiers, ls_v4); } ret = ed25519_keypair_generate(&auth_kp, 0); @@ -94,8 +95,7 @@ static hs_descriptor_t * hs_helper_build_hs_desc_impl(unsigned int no_ip, const ed25519_keypair_t *signing_kp) { - int ret; - time_t now = time(NULL); + time_t now = approx_time(); ed25519_keypair_t blinded_kp; hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc)); @@ -105,8 +105,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, memcpy(&desc->plaintext_data.signing_pubkey, &signing_kp->pubkey, sizeof(ed25519_public_key_t)); - ret = ed25519_keypair_generate(&blinded_kp, 0); - tt_int_op(ret, ==, 0); + uint64_t current_time_period = hs_get_time_period_num(0); + hs_build_blinded_keypair(signing_kp, NULL, 0, + current_time_period, &blinded_kp); /* Copy only the public key into the descriptor. */ memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey, sizeof(ed25519_public_key_t)); @@ -119,6 +120,9 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, desc->plaintext_data.revision_counter = 42; desc->plaintext_data.lifetime_sec = 3 * 60 * 60; + hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey, + desc->subcredential); + /* Setup encrypted data section. */ desc->encrypted_data.create2_ntor = 1; desc->encrypted_data.intro_auth_types = smartlist_new(); @@ -134,7 +138,7 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, smartlist_add(desc->encrypted_data.intro_points, hs_helper_build_intro_point(signing_kp, now, "3.2.1.4", 1)); smartlist_add(desc->encrypted_data.intro_points, - hs_helper_build_intro_point(signing_kp, now, "", 1)); + hs_helper_build_intro_point(signing_kp, now, "5.6.7.8", 1)); } descp = desc; @@ -142,6 +146,21 @@ hs_helper_build_hs_desc_impl(unsigned int no_ip, return descp; } +/** Helper function to get the HS subcredential using the identity keypair of + * an HS. Used to decrypt descriptors in unittests. */ +void +hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp, + uint8_t *subcred_out) +{ + ed25519_keypair_t blinded_kp; + uint64_t current_time_period = hs_get_time_period_num(approx_time()); + hs_build_blinded_keypair(signing_kp, NULL, 0, + current_time_period, &blinded_kp); + + hs_get_subcredential(&signing_kp->pubkey, &blinded_kp.pubkey, + subcred_out); +} + /* Build a descriptor with introduction points. */ hs_descriptor_t * hs_helper_build_hs_desc_with_ip(const ed25519_keypair_t *signing_kp) diff --git a/src/test/hs_test_helpers.h b/src/test/hs_test_helpers.h index a7fedab136..b1b0490f05 100644 --- a/src/test/hs_test_helpers.h +++ b/src/test/hs_test_helpers.h @@ -17,6 +17,9 @@ hs_descriptor_t *hs_helper_build_hs_desc_with_ip( const ed25519_keypair_t *signing_kp); void hs_helper_desc_equal(const hs_descriptor_t *desc1, const hs_descriptor_t *desc2); +void +hs_helper_get_subcred_from_identity_keypair(ed25519_keypair_t *signing_kp, + uint8_t *subcred_out); -#endif /* TOR_HS_TEST_HELPERS_H */ +#endif /* !defined(TOR_HS_TEST_HELPERS_H) */ diff --git a/src/test/include.am b/src/test/include.am index d5ae0bec1c..610918e9da 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -34,16 +34,20 @@ endif TESTS += src/test/test src/test/test-slow src/test/test-memwipe \ src/test/test_workqueue \ src/test/test_keygen.sh \ + src/test/test_key_expiration.sh \ src/test/test-timers \ $(TESTSCRIPTS) # These flavors are run using automake's test-driver and test-network.sh -TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-min single-onion +TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-v2-min hs-v3-min \ + single-onion-v23 # only run if we can ping6 ::1 (localhost) -TEST_CHUTNEY_FLAVORS_IPV6 = bridges+ipv6-min ipv6-exit-min hs-ipv6 \ - single-onion-ipv6 +# IPv6-only v3 single onion services don't work yet, so we don't test the +# single-onion-v23-ipv6-md flavor +TEST_CHUTNEY_FLAVORS_IPV6 = bridges+ipv6-min ipv6-exit-min hs-v23-ipv6-md \ + single-onion-ipv6-md # only run if we can find a stable (or simply another) version of tor -TEST_CHUTNEY_FLAVORS_MIXED = mixed +TEST_CHUTNEY_FLAVORS_MIXED = mixed+hs-v23 ### This is a lovely feature, but it requires automake >= 1.12, and Tor ### doesn't require that yet. @@ -114,7 +118,12 @@ src_test_test_SOURCES = \ src/test/test_guardfraction.c \ src/test/test_extorport.c \ src/test/test_hs.c \ + src/test/test_hs_common.c \ + src/test/test_hs_config.c \ + src/test/test_hs_cell.c \ + src/test/test_hs_ntor.c \ src/test/test_hs_service.c \ + src/test/test_hs_client.c \ src/test/test_hs_intropoint.c \ src/test/test_handles.c \ src/test/test_hs_cache.c \ @@ -130,6 +139,8 @@ src_test_test_SOURCES = \ src/test/test_options.c \ src/test/test_policy.c \ src/test/test_procmon.c \ + src/test/test_proto_http.c \ + src/test/test_proto_misc.c \ src/test/test_protover.c \ src/test/test_pt.c \ src/test/test_pubsub.c \ @@ -137,6 +148,7 @@ src_test_test_SOURCES = \ src/test/test_relaycell.c \ src/test/test_rendcache.c \ src/test/test_replay.c \ + src/test/test_router.c \ src/test/test_routerkeys.c \ src/test/test_routerlist.c \ src/test/test_routerset.c \ @@ -191,9 +203,10 @@ src_test_test_switch_id_LDFLAGS = @TOR_LDFLAGS_zlib@ src_test_test_switch_id_LDADD = \ src/common/libor-testing.a \ src/common/libor-ctime-testing.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ - @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_LIB_WS32@ @TOR_LIB_USERENV@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ @@ -206,10 +219,11 @@ src_test_test_LDADD = src/or/libtor-testing.a \ src/common/libor-event-testing.a \ src/trunnel/libor-trunnel-testing.a \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_SYSTEMD_LIBS@ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ \ + @TOR_SYSTEMD_LIBS@ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ src_test_test_slow_CPPFLAGS = $(src_test_test_CPPFLAGS) src_test_test_slow_CFLAGS = $(src_test_test_CFLAGS) @@ -231,10 +245,11 @@ src_test_bench_LDADD = src/or/libtor.a src/common/libor.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_SYSTEMD_LIBS@ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ \ + @TOR_SYSTEMD_LIBS@ @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ src_test_test_workqueue_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ @@ -244,10 +259,11 @@ src_test_test_workqueue_LDADD = src/or/libtor-testing.a \ src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event-testing.a \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ \ - $(rust_ldadd) + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ \ + @TOR_LZMA_LIBS@ @TOR_ZSTD_LIBS@ src_test_test_timers_CPPFLAGS = $(src_test_test_CPPFLAGS) src_test_test_timers_CFLAGS = $(src_test_test_CFLAGS) @@ -256,10 +272,11 @@ src_test_test_timers_LDADD = \ src/common/libor-ctime-testing.a \ src/common/libor-event-testing.a \ src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_LZMA_LIBS@ \ - $(rust_ldadd) + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ \ + @TOR_LZMA_LIBS@ src_test_test_timers_LDFLAGS = $(src_test_test_LDFLAGS) noinst_HEADERS+= \ @@ -270,6 +287,7 @@ noinst_HEADERS+= \ src/test/test.h \ src/test/test_helpers.h \ src/test/test_dir_common.h \ + src/test/test_connection.h \ src/test/test_descriptors.inc \ src/test/example_extrainfo.inc \ src/test/failing_routerdescs.inc \ @@ -286,10 +304,10 @@ src_test_test_ntor_cl_LDADD = src/or/libtor.a src/common/libor.a \ src/common/libor-ctime.a \ src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ - @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - @TOR_LZMA_LIBS@ \ - $(rust_ldadd) + @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ \ + @CURVE25519_LIBS@ @TOR_LZMA_LIBS@ src_test_test_ntor_cl_AM_CPPFLAGS = \ -I"$(top_srcdir)/src/or" @@ -309,9 +327,9 @@ src_test_test_bt_cl_SOURCES = src/test/test_bt_cl.c src_test_test_bt_cl_LDADD = src/common/libor-testing.a \ src/common/libor-ctime-testing.a \ src/trace/libor-trace.a \ + $(rust_ldadd) \ @TOR_LIB_MATH@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ \ - $(rust_ldadd) + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ src_test_test_bt_cl_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) src_test_test_bt_cl_CPPFLAGS= $(src_test_AM_CPPFLAGS) $(TEST_CPPFLAGS) @@ -319,10 +337,13 @@ EXTRA_DIST += \ src/test/bt_test.py \ src/test/ntor_ref.py \ src/test/hs_ntor_ref.py \ + src/test/hs_build_address.py \ + src/test/hs_indexes.py \ src/test/fuzz_static_testcases.sh \ src/test/slownacl_curve25519.py \ src/test/zero_length_keys.sh \ src/test/test_keygen.sh \ + src/test/test_key_expiration.sh \ src/test/test_zero_length_keys.sh \ src/test/test_ntor.sh src/test/test_hs_ntor.sh src/test/test_bt.sh \ src/test/test-network.sh \ diff --git a/src/test/log_test_helpers.h b/src/test/log_test_helpers.h index f7798c0249..70c584eb37 100644 --- a/src/test/log_test_helpers.h +++ b/src/test/log_test_helpers.h @@ -101,5 +101,5 @@ void mock_dump_saved_logs(void); assert_log_predicate(!mock_saved_log_has_entry(), \ "expected log to not contain entries"); -#endif +#endif /* !defined(TOR_LOG_TEST_HELPERS_H) */ diff --git a/src/test/rend_test_helpers.c b/src/test/rend_test_helpers.c index f7880046fb..095bfecf21 100644 --- a/src/test/rend_test_helpers.c +++ b/src/test/rend_test_helpers.c @@ -71,3 +71,19 @@ create_descriptor(rend_service_descriptor_t **generated, char **service_id, crypto_pk_free(pk2); } +rend_data_t * +mock_rend_data(const char *onion_address) +{ + rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data)); + rend_data_t *rend_query = &v2_data->base_; + rend_query->version = 2; + + strlcpy(v2_data->onion_address, onion_address, + sizeof(v2_data->onion_address)); + v2_data->auth_type = REND_NO_AUTH; + rend_query->hsdirs_fp = smartlist_new(); + smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa", + DIGEST_LEN)); + return rend_query; +} + diff --git a/src/test/rend_test_helpers.h b/src/test/rend_test_helpers.h index 486adba436..abf4324988 100644 --- a/src/test/rend_test_helpers.h +++ b/src/test/rend_test_helpers.h @@ -10,6 +10,7 @@ void generate_desc(int time_diff, rend_encoded_v2_service_descriptor_t **desc, char **service_id, int intro_points); void create_descriptor(rend_service_descriptor_t **generated, char **service_id, int intro_points); +rend_data_t *mock_rend_data(const char *onion_address); -#endif +#endif /* !defined(TOR_REND_TEST_HELPERS_H) */ diff --git a/src/test/test-child.c b/src/test/test-child.c index f0bdb3ea26..f78a829107 100644 --- a/src/test/test-child.c +++ b/src/test/test-child.c @@ -8,7 +8,7 @@ #include <windows.h> #else #include <unistd.h> -#endif +#endif /* defined(_WIN32) */ #include <string.h> #ifdef _WIN32 diff --git a/src/test/test.c b/src/test/test.c index 68f5f90fd7..da4f7a0553 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -20,7 +20,7 @@ #include <direct.h> #else #include <dirent.h> -#endif +#endif /* defined(_WIN32) */ /* These macros pull in declarations for some functions and structures that * are typically file-private. */ @@ -142,7 +142,8 @@ test_bad_onion_handshake(void *arg) /* Server: Case 1: the encrypted data is degenerate. */ memset(junk_buf, 0, sizeof(junk_buf)); - crypto_pk_public_hybrid_encrypt(pk, junk_buf2, TAP_ONIONSKIN_CHALLENGE_LEN, + crypto_pk_obsolete_public_hybrid_encrypt(pk, + junk_buf2, TAP_ONIONSKIN_CHALLENGE_LEN, junk_buf, DH_KEY_LEN, PK_PKCS1_OAEP_PADDING, 1); tt_int_op(-1, OP_EQ, onion_skin_TAP_server_handshake(junk_buf2, pk, NULL, @@ -408,11 +409,11 @@ test_circuit_timeout(void *arg) } while (fabs(circuit_build_times_cdf(&initial, timeout0) - circuit_build_times_cdf(&initial, timeout1)) > 0.02); - tt_assert(estimate.total_build_times <= CBT_NCIRCUITS_TO_OBSERVE); + tt_int_op(estimate.total_build_times, OP_LE, CBT_NCIRCUITS_TO_OBSERVE); circuit_build_times_update_state(&estimate, state); circuit_build_times_free_timeouts(&final); - tt_assert(circuit_build_times_parse_state(&final, state) == 0); + tt_int_op(circuit_build_times_parse_state(&final, state), OP_EQ, 0); circuit_build_times_update_alpha(&final); timeout2 = circuit_build_times_calculate_timeout(&final, @@ -490,7 +491,7 @@ test_circuit_timeout(void *arg) } } - tt_assert(estimate.liveness.after_firsthop_idx == 0); + tt_int_op(estimate.liveness.after_firsthop_idx, OP_EQ, 0); tt_assert(final.liveness.after_firsthop_idx == CBT_DEFAULT_MAX_RECENT_TIMEOUT_COUNT-1); @@ -533,25 +534,8 @@ test_rend_fns(void *arg) size_t intro_points_size; size_t encoded_size; int i; - char address1[] = "fooaddress.onion"; - char address2[] = "aaaaaaaaaaaaaaaa.onion"; - char address3[] = "fooaddress.exit"; - char address4[] = "www.torproject.org"; - char address5[] = "foo.abcdefghijklmnop.onion"; - char address6[] = "foo.bar.abcdefghijklmnop.onion"; - char address7[] = ".abcdefghijklmnop.onion"; (void)arg; - tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1)); - tt_assert(ONION_HOSTNAME == parse_extended_hostname(address2)); - tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa"); - tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3)); - tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4)); - tt_assert(ONION_HOSTNAME == parse_extended_hostname(address5)); - tt_str_op(address5,OP_EQ, "abcdefghijklmnop"); - tt_assert(ONION_HOSTNAME == parse_extended_hostname(address6)); - tt_str_op(address6,OP_EQ, "abcdefghijklmnop"); - tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7)); /* Initialize the service cache. */ rend_cache_init(); @@ -587,20 +571,21 @@ test_rend_fns(void *arg) intro->intro_key = crypto_pk_dup_key(pk2); smartlist_add(generated->intro_nodes, intro); } - tt_assert(rend_encode_v2_descriptors(descs, generated, now, 0, - REND_NO_AUTH, NULL, NULL) > 0); - tt_assert(rend_compute_v2_desc_id(computed_desc_id, service_id_base32, - NULL, now, 0) == 0); + int rv = rend_encode_v2_descriptors(descs, generated, now, 0, + REND_NO_AUTH, NULL, NULL); + tt_int_op(rv, OP_GT, 0); + rv = rend_compute_v2_desc_id(computed_desc_id, service_id_base32, NULL, + now, 0); + tt_int_op(rv, OP_EQ, 0); tt_mem_op(((rend_encoded_v2_service_descriptor_t *) smartlist_get(descs, 0))->desc_id, OP_EQ, computed_desc_id, DIGEST_LEN); - tt_assert(rend_parse_v2_service_descriptor(&parsed, parsed_desc_id, - &intro_points_encrypted, - &intro_points_size, - &encoded_size, - &next_desc, - ((rend_encoded_v2_service_descriptor_t *) - smartlist_get(descs, 0))->desc_str, 1) == 0); + rv = rend_parse_v2_service_descriptor(&parsed, parsed_desc_id, + &intro_points_encrypted, &intro_points_size, &encoded_size, + &next_desc, + ((rend_encoded_v2_service_descriptor_t *)smartlist_get(descs, 0)) + ->desc_str, 1); + tt_int_op(rv, OP_EQ, 0); tt_assert(parsed); tt_mem_op(((rend_encoded_v2_service_descriptor_t *) smartlist_get(descs, 0))->desc_id,OP_EQ, parsed_desc_id, DIGEST_LEN); @@ -802,7 +787,7 @@ test_geoip(void *arg) /* Start testing bridge statistics by making sure that we don't output * bridge stats without initializing them. */ s = geoip_format_bridge_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Initialize stats and generate the bridge-stats history string out of * the connecting clients added above. */ @@ -816,7 +801,7 @@ test_geoip(void *arg) * string anymore. */ geoip_bridge_stats_term(); s = geoip_format_bridge_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Stop being a bridge and start being a directory mirror that gathers * directory request statistics. */ @@ -830,7 +815,7 @@ test_geoip(void *arg) SET_TEST_ADDRESS(100); geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, NULL, now); s = geoip_format_dirreq_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Initialize stats, note one connecting client, and generate the * dirreq-stats history string. */ @@ -847,7 +832,7 @@ test_geoip(void *arg) SET_TEST_ADDRESS(101); geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, &addr, NULL, now); s = geoip_format_dirreq_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Re-start stats, add a connecting client, reset stats, and make sure * that we get an all empty history string. */ @@ -883,7 +868,7 @@ test_geoip(void *arg) SET_TEST_ADDRESS(100); geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); s = geoip_format_entry_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Initialize stats, note one connecting client, and generate the * entry-stats history string. */ @@ -900,7 +885,7 @@ test_geoip(void *arg) SET_TEST_ADDRESS(101); geoip_note_client_seen(GEOIP_CLIENT_CONNECT, &addr, NULL, now); s = geoip_format_entry_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Re-start stats, add a connecting client, reset stats, and make sure * that we get an all empty history string. */ @@ -1011,7 +996,7 @@ test_stats(void *arg) rep_hist_note_exit_stream_opened(80); rep_hist_note_exit_bytes(80, 100, 10000); s = rep_hist_format_exit_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Initialize stats, note some streams and bytes, and generate history * string. */ @@ -1049,7 +1034,7 @@ test_stats(void *arg) rep_hist_exit_stats_term(); rep_hist_note_exit_bytes(80, 100, 10000); s = rep_hist_format_exit_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Re-start stats, add some bytes, reset stats, and see what history we * get when observing no streams or bytes at all. */ @@ -1068,7 +1053,7 @@ test_stats(void *arg) * conn stats without initializing them. */ rep_hist_note_or_conn_bytes(1, 20, 400, now); s = rep_hist_format_conn_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Initialize stats, note bytes, and generate history string. */ rep_hist_conn_stats_init(now); @@ -1085,7 +1070,7 @@ test_stats(void *arg) rep_hist_conn_stats_term(); rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); s = rep_hist_format_conn_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Re-start stats, add some bytes, reset stats, and see what history we * get when observing no bytes at all. */ @@ -1103,7 +1088,7 @@ test_stats(void *arg) * stats without initializing them. */ rep_hist_add_buffer_stats(2.0, 2.0, 20); s = rep_hist_format_buffer_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Initialize stats, add statistics for a single circuit, and generate * the history string. */ @@ -1138,7 +1123,7 @@ test_stats(void *arg) rep_hist_buffer_stats_term(); rep_hist_add_buffer_stats(2.0, 2.0, 20); s = rep_hist_format_buffer_stats(now + 86400); - tt_assert(!s); + tt_ptr_op(s, OP_EQ, NULL); /* Re-start stats, add statistics for one circuit, reset stats, and make * sure that the history has all zeros. */ @@ -1213,8 +1198,13 @@ struct testgroup_t testgroups[] = { { "extorport/", extorport_tests }, { "legacy_hs/", hs_tests }, { "hs_cache/", hs_cache }, + { "hs_cell/", hs_cell_tests }, + { "hs_common/", hs_common_tests }, + { "hs_config/", hs_config_tests }, { "hs_descriptor/", hs_descriptor }, + { "hs_ntor/", hs_ntor_tests }, { "hs_service/", hs_service_tests }, + { "hs_client/", hs_client_tests }, { "hs_intropoint/", hs_intropoint_tests }, { "introduce/", introduce_tests }, { "keypin/", keypin_tests }, @@ -1225,12 +1215,15 @@ struct testgroup_t testgroups[] = { { "options/", options_tests }, { "policy/" , policy_tests }, { "procmon/", procmon_tests }, + { "proto/http/", proto_http_tests }, + { "proto/misc/", proto_misc_tests }, { "protover/", protover_tests }, { "pt/", pt_tests }, { "relay/" , relay_tests }, { "relaycell/", relaycell_tests }, { "rend_cache/", rend_cache_tests }, { "replaycache/", replaycache_tests }, + { "router/", router_tests }, { "routerkeys/", routerkeys_tests }, { "routerlist/", routerlist_tests }, { "routerset/" , routerset_tests }, diff --git a/src/test/test.h b/src/test/test.h index 6abaf39e6f..634f3e7b02 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -45,8 +45,8 @@ * you're doing. */ #define tt_double_eq(a,b) \ STMT_BEGIN \ - tt_double_op((a), >=, (b)); \ - tt_double_op((a), <=, (b)); \ + tt_double_op((a), OP_GE, (b)); \ + tt_double_op((a), OP_LE, (b)); \ STMT_END #ifdef _MSC_VER @@ -55,7 +55,7 @@ #else #define U64_PRINTF_TYPE unsigned long long #define I64_PRINTF_TYPE long long -#endif +#endif /* defined(_MSC_VER) */ #define tt_size_op(a,op,b) \ tt_assert_test_fmt_type(a,b,#a" "#op" "#b,size_t,(val1_ op val2_), \ @@ -207,8 +207,13 @@ extern struct testcase_t guardfraction_tests[]; extern struct testcase_t extorport_tests[]; extern struct testcase_t hs_tests[]; extern struct testcase_t hs_cache[]; +extern struct testcase_t hs_cell_tests[]; +extern struct testcase_t hs_common_tests[]; +extern struct testcase_t hs_config_tests[]; extern struct testcase_t hs_descriptor[]; +extern struct testcase_t hs_ntor_tests[]; extern struct testcase_t hs_service_tests[]; +extern struct testcase_t hs_client_tests[]; extern struct testcase_t hs_intropoint_tests[]; extern struct testcase_t introduce_tests[]; extern struct testcase_t keypin_tests[]; @@ -221,6 +226,8 @@ extern struct testcase_t oos_tests[]; extern struct testcase_t options_tests[]; extern struct testcase_t policy_tests[]; extern struct testcase_t procmon_tests[]; +extern struct testcase_t proto_http_tests[]; +extern struct testcase_t proto_misc_tests[]; extern struct testcase_t protover_tests[]; extern struct testcase_t pubsub_tests[]; extern struct testcase_t pt_tests[]; @@ -264,5 +271,5 @@ extern const char AUTHORITY_SIGNKEY_3[]; extern const char AUTHORITY_SIGNKEY_C_DIGEST[]; extern const char AUTHORITY_SIGNKEY_C_DIGEST256[]; -#endif +#endif /* !defined(TOR_TEST_H) */ diff --git a/src/test/test_addr.c b/src/test/test_addr.c index 2f591bdfe7..e1a40b7e60 100644 --- a/src/test/test_addr.c +++ b/src/test/test_addr.c @@ -451,10 +451,10 @@ test_addr_ip6_helpers(void *arg) "::ffff:6.0.0.0"); /* XXXX wrong. */ tor_addr_parse_mask_ports("[::ffff:2.3.4.5]", 0, &t1, NULL, NULL, NULL); tor_addr_parse_mask_ports("2.3.4.5", 0, &t2, NULL, NULL, NULL); - tt_assert(tor_addr_compare(&t1, &t2, CMP_SEMANTIC) == 0); + tt_int_op(tor_addr_compare(&t1, &t2, CMP_SEMANTIC), OP_EQ, 0); tor_addr_parse_mask_ports("[::ffff:2.3.4.4]", 0, &t1, NULL, NULL, NULL); tor_addr_parse_mask_ports("2.3.4.5", 0, &t2, NULL, NULL, NULL); - tt_assert(tor_addr_compare(&t1, &t2, CMP_SEMANTIC) < 0); + tt_int_op(tor_addr_compare(&t1, &t2, CMP_SEMANTIC), OP_LT, 0); /* test compare_masked */ test_addr_compare_masked("ffff::", OP_EQ, "ffff::0", 128); @@ -637,7 +637,7 @@ test_addr_ip6_helpers(void *arg) /* Try some long addresses. */ r=tor_addr_parse_mask_ports("[ffff:1111:1111:1111:1111:1111:1111:1111]", 0, &t1, NULL, NULL, NULL); - tt_assert(r == AF_INET6); + tt_int_op(r, OP_EQ, AF_INET6); r=tor_addr_parse_mask_ports("[ffff:1111:1111:1111:1111:1111:1111:11111]", 0, &t1, NULL, NULL, NULL); tt_int_op(r, OP_EQ, -1); @@ -686,38 +686,38 @@ test_addr_ip6_helpers(void *arg) tt_int_op(r, OP_EQ, -1); r=tor_addr_parse_mask_ports("*6",0,&t1, &mask, NULL, NULL); tt_int_op(r, OP_EQ, -1); - tt_assert(r == -1); + tt_int_op(r, OP_EQ, -1); /* Try a mask with a wildcard. */ r=tor_addr_parse_mask_ports("*/16",0,&t1, &mask, NULL, NULL); - tt_assert(r == -1); + tt_int_op(r, OP_EQ, -1); r=tor_addr_parse_mask_ports("*4/16",TAPMP_EXTENDED_STAR, &t1, &mask, NULL, NULL); - tt_assert(r == -1); + tt_int_op(r, OP_EQ, -1); r=tor_addr_parse_mask_ports("*6/30",TAPMP_EXTENDED_STAR, &t1, &mask, NULL, NULL); - tt_assert(r == -1); + tt_int_op(r, OP_EQ, -1); /* Basic mask tests*/ r=tor_addr_parse_mask_ports("1.1.2.2/31",0,&t1, &mask, NULL, NULL); - tt_assert(r == AF_INET); + tt_int_op(r, OP_EQ, AF_INET); tt_int_op(mask,OP_EQ,31); tt_int_op(tor_addr_family(&t1),OP_EQ,AF_INET); tt_int_op(tor_addr_to_ipv4h(&t1),OP_EQ,0x01010202); r=tor_addr_parse_mask_ports("3.4.16.032:1-2",0,&t1, &mask, &port1, &port2); - tt_assert(r == AF_INET); + tt_int_op(r, OP_EQ, AF_INET); tt_int_op(mask,OP_EQ,32); tt_int_op(tor_addr_family(&t1),OP_EQ,AF_INET); tt_int_op(tor_addr_to_ipv4h(&t1),OP_EQ,0x03041020); - tt_assert(port1 == 1); - tt_assert(port2 == 2); + tt_uint_op(port1, OP_EQ, 1); + tt_uint_op(port2, OP_EQ, 2); r=tor_addr_parse_mask_ports("1.1.2.3/255.255.128.0",0,&t1, &mask,NULL,NULL); - tt_assert(r == AF_INET); + tt_int_op(r, OP_EQ, AF_INET); tt_int_op(mask,OP_EQ,17); tt_int_op(tor_addr_family(&t1),OP_EQ,AF_INET); tt_int_op(tor_addr_to_ipv4h(&t1),OP_EQ,0x01010203); r=tor_addr_parse_mask_ports("[efef::]/112",0,&t1, &mask, &port1, &port2); - tt_assert(r == AF_INET6); - tt_assert(port1 == 1); - tt_assert(port2 == 65535); + tt_int_op(r, OP_EQ, AF_INET6); + tt_uint_op(port1, OP_EQ, 1); + tt_uint_op(port2, OP_EQ, 65535); /* Try regular wildcard behavior without TAPMP_EXTENDED_STAR */ r=tor_addr_parse_mask_ports("*:80-443",0,&t1,&mask,&port1,&port2); tt_int_op(r,OP_EQ,AF_INET); /* Old users of this always get inet */ @@ -752,15 +752,17 @@ test_addr_ip6_helpers(void *arg) tt_int_op(port2,OP_EQ,65535); /* make sure inet address lengths >= max */ - tt_assert(INET_NTOA_BUF_LEN >= sizeof("255.255.255.255")); - tt_assert(TOR_ADDR_BUF_LEN >= - sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")); + tt_int_op(INET_NTOA_BUF_LEN, OP_GE, sizeof("255.255.255.255")); + tt_int_op(TOR_ADDR_BUF_LEN, OP_GE, + sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")); tt_assert(sizeof(tor_addr_t) >= sizeof(struct in6_addr)); /* get interface addresses */ r = get_interface_address6(LOG_DEBUG, AF_INET, &t1); + tt_int_op(r, OP_LE, 0); // "it worked or it didn't" i = get_interface_address6(LOG_DEBUG, AF_INET6, &t2); + tt_int_op(i, OP_LE, 0); // "it worked or it didn't" TT_BLATHER(("v4 address: %s (family=%d)", fmt_addr(&t1), tor_addr_family(&t1))); @@ -1010,7 +1012,7 @@ test_addr_sockaddr_to_str(void *arg) s_un.sun_family = AF_UNIX; strlcpy(s_un.sun_path, "/here/is/a/path", sizeof(s_un.sun_path)); CHECK(s_un, "unix:/here/is/a/path"); -#endif +#endif /* defined(HAVE_SYS_UN_H) */ memset(&sin6,0,sizeof(sin6)); sin6.sin6_family = AF_INET6; diff --git a/src/test/test_address.c b/src/test/test_address.c index 50a0574522..f36ff6998b 100644 --- a/src/test/test_address.c +++ b/src/test/test_address.c @@ -21,7 +21,7 @@ #include <sys/ioctl.h> #endif #include <net/if.h> -#endif +#endif /* defined(HAVE_IFCONF_TO_SMARTLIST) */ #include "or.h" #include "address.h" @@ -224,7 +224,7 @@ test_address_ifaddrs_to_smartlist(void *arg) smartlist = ifaddrs_to_smartlist(ifa, AF_UNSPEC); tt_assert(smartlist); - tt_assert(smartlist_len(smartlist) == 3); + tt_int_op(smartlist_len(smartlist), OP_EQ, 3); sockaddr_to_check = tor_malloc(sizeof(struct sockaddr_in6)); @@ -233,7 +233,7 @@ test_address_ifaddrs_to_smartlist(void *arg) tor_addr_to_sockaddr(tor_addr,0,sockaddr_to_check, sizeof(struct sockaddr_in)); - tt_int_op(addr_len,==,sizeof(struct sockaddr_in)); + tt_int_op(addr_len,OP_EQ,sizeof(struct sockaddr_in)); tt_assert(sockaddr_in_are_equal((struct sockaddr_in *)sockaddr_to_check, ipv4_sockaddr_local)); @@ -242,7 +242,7 @@ test_address_ifaddrs_to_smartlist(void *arg) tor_addr_to_sockaddr(tor_addr,0,sockaddr_to_check, sizeof(struct sockaddr_in)); - tt_int_op(addr_len,==,sizeof(struct sockaddr_in)); + tt_int_op(addr_len,OP_EQ,sizeof(struct sockaddr_in)); tt_assert(sockaddr_in_are_equal((struct sockaddr_in *)sockaddr_to_check, ipv4_sockaddr_remote)); @@ -251,7 +251,7 @@ test_address_ifaddrs_to_smartlist(void *arg) tor_addr_to_sockaddr(tor_addr,0,sockaddr_to_check, sizeof(struct sockaddr_in6)); - tt_int_op(addr_len,==,sizeof(struct sockaddr_in6)); + tt_int_op(addr_len,OP_EQ,sizeof(struct sockaddr_in6)); tt_assert(sockaddr_in6_are_equal((struct sockaddr_in6*)sockaddr_to_check, ipv6_sockaddr)); @@ -305,7 +305,7 @@ test_address_get_if_addrs_ifaddrs(void *arg) return; } -#endif +#endif /* defined(HAVE_IFADDRS_TO_SMARTLIST) */ #ifdef HAVE_IP_ADAPTER_TO_SMARTLIST @@ -319,7 +319,7 @@ test_address_get_if_addrs_win32(void *arg) results = get_interface_addresses_win32(LOG_ERR, AF_UNSPEC); - tt_int_op(smartlist_len(results),>=,1); + tt_int_op(smartlist_len(results),OP_GE,1); tt_assert(smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_null_tor_addr(results)); @@ -384,7 +384,7 @@ test_address_ip_adapter_addresses_to_smartlist(void *arg) result = ip_adapter_addresses_to_smartlist(addrs1); tt_assert(result); - tt_assert(smartlist_len(result) == 3); + tt_int_op(smartlist_len(result), OP_EQ, 3); tor_addr = smartlist_get(result,0); @@ -421,7 +421,7 @@ test_address_ip_adapter_addresses_to_smartlist(void *arg) tor_free(sockaddr_to_check); return; } -#endif +#endif /* defined(HAVE_IP_ADAPTER_TO_SMARTLIST) */ #ifdef HAVE_IFCONF_TO_SMARTLIST @@ -456,14 +456,14 @@ test_address_ifreq_to_smartlist(void *arg) ifc->ifc_ifcu.ifcu_req = ifr; results = ifreq_to_smartlist(ifc->ifc_buf,ifc->ifc_len); - tt_int_op(smartlist_len(results),==,1); + tt_int_op(smartlist_len(results),OP_EQ,1); tor_addr = smartlist_get(results, 0); addr_len = tor_addr_to_sockaddr(tor_addr,0,(struct sockaddr *)sockaddr_to_check, sizeof(struct sockaddr_in)); - tt_int_op(addr_len,==,sizeof(struct sockaddr_in)); + tt_int_op(addr_len,OP_EQ,sizeof(struct sockaddr_in)); tt_assert(sockaddr_in_are_equal(sockaddr,sockaddr_to_check)); ifr = tor_realloc(ifr,2*sizeof(struct ifreq)); @@ -479,14 +479,14 @@ test_address_ifreq_to_smartlist(void *arg) smartlist_free(results); results = ifreq_to_smartlist(ifc->ifc_buf,ifc->ifc_len); - tt_int_op(smartlist_len(results),==,2); + tt_int_op(smartlist_len(results),OP_EQ,2); tor_addr = smartlist_get(results, 0); addr_len = tor_addr_to_sockaddr(tor_addr,0,(struct sockaddr *)sockaddr_to_check, sizeof(struct sockaddr_in)); - tt_int_op(addr_len,==,sizeof(struct sockaddr_in)); + tt_int_op(addr_len,OP_EQ,sizeof(struct sockaddr_in)); tt_assert(sockaddr_in_are_equal(sockaddr,sockaddr_to_check)); tor_addr = smartlist_get(results, 1); @@ -494,7 +494,7 @@ test_address_ifreq_to_smartlist(void *arg) tor_addr_to_sockaddr(tor_addr,0,(struct sockaddr *)sockaddr_to_check, sizeof(struct sockaddr_in)); - tt_int_op(addr_len,==,sizeof(struct sockaddr_in)); + tt_int_op(addr_len,OP_EQ,sizeof(struct sockaddr_in)); tt_assert(sockaddr_in_are_equal(sockaddr_eth1,sockaddr_to_check)); done: @@ -543,7 +543,7 @@ test_address_get_if_addrs_ioctl(void *arg) return; } -#endif +#endif /* defined(HAVE_IFCONF_TO_SMARTLIST) */ #define FAKE_SOCKET_FD (42) @@ -633,7 +633,7 @@ test_address_udp_socket_trick_whitebox(void *arg) get_interface_address6_via_udp_socket_hack(LOG_DEBUG, AF_INET, addr_from_hack); - tt_int_op(hack_retval,==,0); + tt_int_op(hack_retval,OP_EQ,0); tt_assert(tor_addr_eq_ipv4h(addr_from_hack, 0x1720f676)); /* Now, lets do an IPv6 case. */ @@ -648,7 +648,7 @@ test_address_udp_socket_trick_whitebox(void *arg) get_interface_address6_via_udp_socket_hack(LOG_DEBUG, AF_INET6, addr_from_hack); - tt_int_op(hack_retval,==,0); + tt_int_op(hack_retval,OP_EQ,0); tor_addr_to_sockaddr(addr_from_hack,0,(struct sockaddr *)ipv6_to_check, sizeof(struct sockaddr_in6)); @@ -693,7 +693,7 @@ test_address_udp_socket_trick_blackbox(void *arg) AF_INET, &addr4_to_check); - tt_int_op(retval,==,retval_reference); + tt_int_op(retval,OP_EQ,retval_reference); tt_assert( (retval == -1 && retval_reference == -1) || (tor_addr_compare(&addr4,&addr4_to_check,CMP_EXACT) == 0) ); @@ -702,11 +702,11 @@ test_address_udp_socket_trick_blackbox(void *arg) AF_INET6, &addr6_to_check); - tt_int_op(retval,==,retval_reference); + tt_int_op(retval,OP_EQ,retval_reference); tt_assert( (retval == -1 && retval_reference == -1) || (tor_addr_compare(&addr6,&addr6_to_check,CMP_EXACT) == 0) ); -#else +#else /* !(0) */ /* Both of the blackbox test cases fail horribly if: * * The host has no external addreses. * * There are multiple interfaces with either AF_INET or AF_INET6. @@ -721,7 +721,7 @@ test_address_udp_socket_trick_blackbox(void *arg) (void)addr6_to_check; (void)addr6; (void) retval_reference; -#endif +#endif /* 0 */ /* When family is neither AF_INET nor AF_INET6, we want _hack to * fail and return -1. @@ -730,7 +730,7 @@ test_address_udp_socket_trick_blackbox(void *arg) retval = get_interface_address6_via_udp_socket_hack(LOG_DEBUG, AF_INET+AF_INET6,&addr4); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); done: return; @@ -745,11 +745,11 @@ test_address_get_if_addrs_list_internal(void *arg) results = get_interface_address_list(LOG_ERR, 1); - tt_assert(results != NULL); + tt_ptr_op(results, OP_NE, NULL); /* When the network is down, a system might not have any non-local * non-multicast addresseses, not even internal ones. * Unit tests shouldn't fail because of this. */ - tt_int_op(smartlist_len(results),>=,0); + tt_int_op(smartlist_len(results),OP_GE,0); tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); @@ -776,9 +776,9 @@ test_address_get_if_addrs_list_no_internal(void *arg) results = get_interface_address_list(LOG_ERR, 0); - tt_assert(results != NULL); + tt_ptr_op(results, OP_NE, NULL); /* Work even on systems with only internal IPv4 addresses */ - tt_int_op(smartlist_len(results),>=,0); + tt_int_op(smartlist_len(results),OP_GE,0); tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); @@ -818,9 +818,9 @@ test_address_get_if_addrs6_list_internal(void *arg) } teardown_capture_of_logs(); - tt_assert(results != NULL); + tt_ptr_op(results, OP_NE, NULL); /* Work even on systems without IPv6 interfaces */ - tt_int_op(smartlist_len(results),>=,0); + tt_int_op(smartlist_len(results),OP_GE,0); tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); @@ -861,9 +861,9 @@ test_address_get_if_addrs6_list_no_internal(void *arg) } teardown_capture_of_logs(); - tt_assert(results != NULL); + tt_ptr_op(results, OP_NE, NULL); /* Work even on systems without IPv6 interfaces */ - tt_int_op(smartlist_len(results),>=,0); + tt_int_op(smartlist_len(results),OP_GE,0); tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); @@ -927,18 +927,18 @@ test_address_get_if_addrs_internal_fail(void *arg) mock_get_interface_address6_via_udp_socket_hack_fail); results1 = get_interface_address6_list(LOG_ERR, AF_INET6, 1); - tt_assert(results1 != NULL); - tt_int_op(smartlist_len(results1),==,0); + tt_ptr_op(results1, OP_NE, NULL); + tt_int_op(smartlist_len(results1),OP_EQ,0); results2 = get_interface_address_list(LOG_ERR, 1); - tt_assert(results2 != NULL); - tt_int_op(smartlist_len(results2),==,0); + tt_ptr_op(results2, OP_NE, NULL); + tt_int_op(smartlist_len(results2),OP_EQ,0); rv = get_interface_address6(LOG_ERR, AF_INET6, &ipv6_addr); - tt_assert(rv == -1); + tt_int_op(rv, OP_EQ, -1); rv = get_interface_address(LOG_ERR, &ipv4h_addr); - tt_assert(rv == -1); + tt_int_op(rv, OP_EQ, -1); done: UNMOCK(get_interface_addresses_raw); @@ -961,12 +961,12 @@ test_address_get_if_addrs_no_internal_fail(void *arg) mock_get_interface_address6_via_udp_socket_hack_fail); results1 = get_interface_address6_list(LOG_ERR, AF_INET6, 0); - tt_assert(results1 != NULL); - tt_int_op(smartlist_len(results1),==,0); + tt_ptr_op(results1, OP_NE, NULL); + tt_int_op(smartlist_len(results1),OP_EQ,0); results2 = get_interface_address_list(LOG_ERR, 0); - tt_assert(results2 != NULL); - tt_int_op(smartlist_len(results2),==,0); + tt_ptr_op(results2, OP_NE, NULL); + tt_int_op(smartlist_len(results2),OP_EQ,0); done: UNMOCK(get_interface_addresses_raw); diff --git a/src/test/test_bt_cl.c b/src/test/test_bt_cl.c index ed588ecc5b..b5c8d7cf9e 100644 --- a/src/test/test_bt_cl.c +++ b/src/test/test_bt_cl.c @@ -38,7 +38,7 @@ crash(int x) * don't need to see us dereference NULL. */ #else *(volatile int *)0 = 0; -#endif +#endif /* defined(__clang_analyzer__) || defined(__COVERITY__) */ } else if (crashtype == 1) { tor_assert(1 == 0); } else if (crashtype == -1) { diff --git a/src/test/test_buffers.c b/src/test/test_buffers.c index 07114a8571..9f4e19bc5f 100644 --- a/src/test/test_buffers.c +++ b/src/test/test_buffers.c @@ -4,9 +4,12 @@ /* See LICENSE for licensing information */ #define BUFFERS_PRIVATE +#define PROTO_HTTP_PRIVATE #include "or.h" #include "buffers.h" -#include "ext_orport.h" +#include "buffers_tls.h" +#include "proto_http.h" +#include "proto_socks.h" #include "test.h" /** Run unit tests for buffers.c */ @@ -38,15 +41,15 @@ test_buffers_basic(void *arg) for (j=0;j<256;++j) { str[j] = (char)j; } - write_to_buf(str, 256, buf); - write_to_buf(str, 256, buf); + buf_add(buf, str, 256); + buf_add(buf, str, 256); tt_int_op(buf_datalen(buf),OP_EQ, 512); - fetch_from_buf(str2, 200, buf); + buf_get_bytes(buf, str2, 200); tt_mem_op(str,OP_EQ, str2, 200); tt_int_op(buf_datalen(buf),OP_EQ, 312); memset(str2, 0, sizeof(str2)); - fetch_from_buf(str2, 256, buf); + buf_get_bytes(buf, str2, 256); tt_mem_op(str+200,OP_EQ, str2, 56); tt_mem_op(str,OP_EQ, str2+56, 200); tt_int_op(buf_datalen(buf),OP_EQ, 56); @@ -54,16 +57,16 @@ test_buffers_basic(void *arg) /* Okay, now we should be 512 bytes into the 4096-byte buffer. If we add * another 3584 bytes, we hit the end. */ for (j=0;j<15;++j) { - write_to_buf(str, 256, buf); + buf_add(buf, str, 256); } - assert_buf_ok(buf); + buf_assert_ok(buf); tt_int_op(buf_datalen(buf),OP_EQ, 3896); - fetch_from_buf(str2, 56, buf); + buf_get_bytes(buf, str2, 56); tt_int_op(buf_datalen(buf),OP_EQ, 3840); tt_mem_op(str+200,OP_EQ, str2, 56); for (j=0;j<15;++j) { memset(str2, 0, sizeof(str2)); - fetch_from_buf(str2, 256, buf); + buf_get_bytes(buf, str2, 256); tt_mem_op(str,OP_EQ, str2, 256); } tt_int_op(buf_datalen(buf),OP_EQ, 0); @@ -73,38 +76,38 @@ test_buffers_basic(void *arg) /* Okay, now make sure growing can work. */ buf = buf_new_with_capacity(16); //test_eq(buf_capacity(buf), 16); - write_to_buf(str+1, 255, buf); + buf_add(buf, str+1, 255); //test_eq(buf_capacity(buf), 256); - fetch_from_buf(str2, 254, buf); + buf_get_bytes(buf, str2, 254); tt_mem_op(str+1,OP_EQ, str2, 254); //test_eq(buf_capacity(buf), 256); - assert_buf_ok(buf); - write_to_buf(str, 32, buf); + buf_assert_ok(buf); + buf_add(buf, str, 32); //test_eq(buf_capacity(buf), 256); - assert_buf_ok(buf); - write_to_buf(str, 256, buf); - assert_buf_ok(buf); + buf_assert_ok(buf); + buf_add(buf, str, 256); + buf_assert_ok(buf); //test_eq(buf_capacity(buf), 512); tt_int_op(buf_datalen(buf),OP_EQ, 33+256); - fetch_from_buf(str2, 33, buf); + buf_get_bytes(buf, str2, 33); tt_int_op(*str2,OP_EQ, str[255]); tt_mem_op(str2+1,OP_EQ, str, 32); //test_eq(buf_capacity(buf), 512); tt_int_op(buf_datalen(buf),OP_EQ, 256); - fetch_from_buf(str2, 256, buf); + buf_get_bytes(buf, str2, 256); tt_mem_op(str,OP_EQ, str2, 256); /* now try shrinking: case 1. */ buf_free(buf); buf = buf_new_with_capacity(33668); for (j=0;j<67;++j) { - write_to_buf(str,255, buf); + buf_add(buf, str,255); } //test_eq(buf_capacity(buf), 33668); tt_int_op(buf_datalen(buf),OP_EQ, 17085); for (j=0; j < 40; ++j) { - fetch_from_buf(str2, 255,buf); + buf_get_bytes(buf, str2, 255); tt_mem_op(str2,OP_EQ, str, 255); } @@ -112,18 +115,18 @@ test_buffers_basic(void *arg) buf_free(buf); buf = buf_new_with_capacity(33668); for (j=0;j<67;++j) { - write_to_buf(str,255, buf); + buf_add(buf, str, 255); } for (j=0; j < 20; ++j) { - fetch_from_buf(str2, 255,buf); + buf_get_bytes(buf, str2, 255); tt_mem_op(str2,OP_EQ, str, 255); } for (j=0;j<80;++j) { - write_to_buf(str,255, buf); + buf_add(buf, str, 255); } //test_eq(buf_capacity(buf),33668); for (j=0; j < 120; ++j) { - fetch_from_buf(str2, 255,buf); + buf_get_bytes(buf, str2, 255); tt_mem_op(str2,OP_EQ, str, 255); } @@ -132,27 +135,27 @@ test_buffers_basic(void *arg) buf = buf_new_with_capacity(4096); buf2 = buf_new_with_capacity(4096); for (j=0;j<100;++j) - write_to_buf(str, 255, buf); + buf_add(buf, str, 255); tt_int_op(buf_datalen(buf),OP_EQ, 25500); for (j=0;j<100;++j) { r = 10; - move_buf_to_buf(buf2, buf, &r); + buf_move_to_buf(buf2, buf, &r); tt_int_op(r,OP_EQ, 0); } tt_int_op(buf_datalen(buf),OP_EQ, 24500); tt_int_op(buf_datalen(buf2),OP_EQ, 1000); for (j=0;j<3;++j) { - fetch_from_buf(str2, 255, buf2); + buf_get_bytes(buf2, str2, 255); tt_mem_op(str2,OP_EQ, str, 255); } r = 8192; /*big move*/ - move_buf_to_buf(buf2, buf, &r); + buf_move_to_buf(buf2, buf, &r); tt_int_op(r,OP_EQ, 0); r = 30000; /* incomplete move */ - move_buf_to_buf(buf2, buf, &r); + buf_move_to_buf(buf2, buf, &r); tt_int_op(r,OP_EQ, 13692); for (j=0;j<97;++j) { - fetch_from_buf(str2, 255, buf2); + buf_get_bytes(buf2, str2, 255); tt_mem_op(str2,OP_EQ, str, 255); } buf_free(buf); @@ -162,7 +165,7 @@ test_buffers_basic(void *arg) buf = buf_new_with_capacity(5); cp = "Testing. This is a moderately long Testing string."; for (j = 0; cp[j]; j++) - write_to_buf(cp+j, 1, buf); + buf_add(buf, cp+j, 1); tt_int_op(0,OP_EQ, buf_find_string_offset(buf, "Testing", 7)); tt_int_op(1,OP_EQ, buf_find_string_offset(buf, "esting", 6)); tt_int_op(1,OP_EQ, buf_find_string_offset(buf, "est", 3)); @@ -180,7 +183,7 @@ test_buffers_basic(void *arg) { char *mem = tor_malloc_zero(65536); buf = buf_new(); - write_to_buf(mem, 65536, buf); + buf_add(buf, mem, 65536); tor_free(mem); tt_int_op(buf_datalen(buf), OP_EQ, 65536); @@ -215,8 +218,7 @@ test_buffer_pullup(void *arg) /* There are a bunch of cases for pullup. One is the trivial case. Let's mess around with an empty buffer. */ - buf_pullup(buf, 16); - buf_get_first_chunk_data(buf, &cp, &sz); + buf_pullup(buf, 16, &cp, &sz); tt_ptr_op(cp, OP_EQ, NULL); tt_uint_op(sz, OP_EQ, 0); @@ -227,65 +229,62 @@ test_buffer_pullup(void *arg) /* Let's add some data. */ crypto_rand(stuff, 16384); - write_to_buf(stuff, 3000, buf); - write_to_buf(stuff+3000, 3000, buf); - buf_get_first_chunk_data(buf, &cp, &sz); + buf_add(buf, stuff, 3000); + buf_add(buf, stuff+3000, 3000); + buf_pullup(buf, 0, &cp, &sz); tt_ptr_op(cp, OP_NE, NULL); tt_int_op(sz, OP_LE, 4096); /* Make room for 3000 bytes in the first chunk, so that the pullup-move code * can get tested. */ - tt_int_op(fetch_from_buf(tmp, 3000, buf), OP_EQ, 3000); + tt_int_op(buf_get_bytes(buf, tmp, 3000), OP_EQ, 3000); tt_mem_op(tmp,OP_EQ, stuff, 3000); - buf_pullup(buf, 2048); - assert_buf_ok(buf); - buf_get_first_chunk_data(buf, &cp, &sz); + buf_pullup(buf, 2048, &cp, &sz); + buf_assert_ok(buf); tt_ptr_op(cp, OP_NE, NULL); tt_int_op(sz, OP_GE, 2048); tt_mem_op(cp,OP_EQ, stuff+3000, 2048); tt_int_op(3000, OP_EQ, buf_datalen(buf)); - tt_int_op(fetch_from_buf(tmp, 3000, buf), OP_EQ, 0); + tt_int_op(buf_get_bytes(buf, tmp, 3000), OP_EQ, 0); tt_mem_op(tmp,OP_EQ, stuff+3000, 2048); buf_free(buf); /* Now try the large-chunk case. */ buf = buf_new_with_capacity(3000); /* rounds up to next power of 2. */ - write_to_buf(stuff, 4000, buf); - write_to_buf(stuff+4000, 4000, buf); - write_to_buf(stuff+8000, 4000, buf); - write_to_buf(stuff+12000, 4000, buf); + buf_add(buf, stuff, 4000); + buf_add(buf, stuff+4000, 4000); + buf_add(buf, stuff+8000, 4000); + buf_add(buf, stuff+12000, 4000); tt_int_op(buf_datalen(buf), OP_EQ, 16000); - buf_get_first_chunk_data(buf, &cp, &sz); + buf_pullup(buf, 0, &cp, &sz); tt_ptr_op(cp, OP_NE, NULL); tt_int_op(sz, OP_LE, 4096); - buf_pullup(buf, 12500); - assert_buf_ok(buf); - buf_get_first_chunk_data(buf, &cp, &sz); + buf_pullup(buf, 12500, &cp, &sz); + buf_assert_ok(buf); tt_ptr_op(cp, OP_NE, NULL); tt_int_op(sz, OP_GE, 12500); tt_mem_op(cp,OP_EQ, stuff, 12500); tt_int_op(buf_datalen(buf), OP_EQ, 16000); - fetch_from_buf(tmp, 12400, buf); + buf_get_bytes(buf, tmp, 12400); tt_mem_op(tmp,OP_EQ, stuff, 12400); tt_int_op(buf_datalen(buf), OP_EQ, 3600); - fetch_from_buf(tmp, 3500, buf); + buf_get_bytes(buf, tmp, 3500); tt_mem_op(tmp,OP_EQ, stuff+12400, 3500); - fetch_from_buf(tmp, 100, buf); + buf_get_bytes(buf, tmp, 100); tt_mem_op(tmp,OP_EQ, stuff+15900, 10); buf_free(buf); /* Make sure that the pull-up-whole-buffer case works */ buf = buf_new_with_capacity(3000); /* rounds up to next power of 2. */ - write_to_buf(stuff, 4000, buf); - write_to_buf(stuff+4000, 4000, buf); - fetch_from_buf(tmp, 100, buf); /* dump 100 bytes from first chunk */ - buf_pullup(buf, 16000); /* Way too much. */ - assert_buf_ok(buf); - buf_get_first_chunk_data(buf, &cp, &sz); + buf_add(buf, stuff, 4000); + buf_add(buf, stuff+4000, 4000); + buf_get_bytes(buf, tmp, 100); /* dump 100 bytes from first chunk */ + buf_pullup(buf, 16000, &cp, &sz); + buf_assert_ok(buf); tt_ptr_op(cp, OP_NE, NULL); tt_int_op(sz, OP_EQ, 7900); tt_mem_op(cp,OP_EQ, stuff+100, 7900); @@ -321,23 +320,23 @@ test_buffer_copy(void *arg) /* Now try with a short buffer. */ s = "And now comes an act of enormous enormance!"; len = strlen(s); - write_to_buf(s, len, buf); + buf_add(buf, s, len); tt_int_op(len, OP_EQ, buf_datalen(buf)); /* Add junk to buf2 so we can test replacing.*/ - write_to_buf("BLARG", 5, buf2); + buf_add(buf2, "BLARG", 5); tt_int_op(0, OP_EQ, buf_set_to_copy(&buf2, buf)); tt_int_op(len, OP_EQ, buf_datalen(buf2)); - fetch_from_buf(b, len, buf2); + buf_get_bytes(buf2, b, len); tt_mem_op(b, OP_EQ, s, len); /* Now free buf2 and retry so we can test allocating */ buf_free(buf2); buf2 = NULL; tt_int_op(0, OP_EQ, buf_set_to_copy(&buf2, buf)); tt_int_op(len, OP_EQ, buf_datalen(buf2)); - fetch_from_buf(b, len, buf2); + buf_get_bytes(buf2, b, len); tt_mem_op(b, OP_EQ, s, len); /* Clear buf for next test */ - fetch_from_buf(b, len, buf); + buf_get_bytes(buf, b, len); tt_int_op(buf_datalen(buf),OP_EQ,0); /* Okay, now let's try a bigger buffer. */ @@ -347,13 +346,13 @@ test_buffer_copy(void *arg) len = strlen(s); for (i = 0; i < 256; ++i) { b[0]=i; - write_to_buf(b, 1, buf); - write_to_buf(s, len, buf); + buf_add(buf, b, 1); + buf_add(buf, s, len); } tt_int_op(0, OP_EQ, buf_set_to_copy(&buf2, buf)); tt_int_op(buf_datalen(buf2), OP_EQ, buf_datalen(buf)); for (i = 0; i < 256; ++i) { - fetch_from_buf(b, len+1, buf2); + buf_get_bytes(buf2, b, len+1); tt_int_op((unsigned char)b[0],OP_EQ,i); tt_mem_op(b+1, OP_EQ, s, len); } @@ -366,79 +365,6 @@ test_buffer_copy(void *arg) } static void -test_buffer_ext_or_cmd(void *arg) -{ - ext_or_cmd_t *cmd = NULL; - buf_t *buf = buf_new(); - char *tmp = NULL; - (void) arg; - - /* Empty -- should give "not there. */ - tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_EQ, cmd); - - /* Three bytes: shouldn't work. */ - write_to_buf("\x00\x20\x00", 3, buf); - tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_EQ, cmd); - tt_int_op(3, OP_EQ, buf_datalen(buf)); - - /* 0020 0000: That's a nil command. It should work. */ - write_to_buf("\x00", 1, buf); - tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_NE, cmd); - tt_int_op(0x20, OP_EQ, cmd->cmd); - tt_int_op(0, OP_EQ, cmd->len); - tt_int_op(0, OP_EQ, buf_datalen(buf)); - ext_or_cmd_free(cmd); - cmd = NULL; - - /* Now try a length-6 command with one byte missing. */ - write_to_buf("\x10\x21\x00\x06""abcde", 9, buf); - tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_EQ, cmd); - write_to_buf("f", 1, buf); - tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_NE, cmd); - tt_int_op(0x1021, OP_EQ, cmd->cmd); - tt_int_op(6, OP_EQ, cmd->len); - tt_mem_op("abcdef", OP_EQ, cmd->body, 6); - tt_int_op(0, OP_EQ, buf_datalen(buf)); - ext_or_cmd_free(cmd); - cmd = NULL; - - /* Now try a length-10 command with 4 extra bytes. */ - write_to_buf("\xff\xff\x00\x0aloremipsum\x10\x00\xff\xff", 18, buf); - tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_NE, cmd); - tt_int_op(0xffff, OP_EQ, cmd->cmd); - tt_int_op(10, OP_EQ, cmd->len); - tt_mem_op("loremipsum", OP_EQ, cmd->body, 10); - tt_int_op(4, OP_EQ, buf_datalen(buf)); - ext_or_cmd_free(cmd); - cmd = NULL; - - /* Finally, let's try a maximum-length command. We already have the header - * waiting. */ - tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tmp = tor_malloc_zero(65535); - write_to_buf(tmp, 65535, buf); - tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); - tt_ptr_op(NULL, OP_NE, cmd); - tt_int_op(0x1000, OP_EQ, cmd->cmd); - tt_int_op(0xffff, OP_EQ, cmd->len); - tt_mem_op(tmp, OP_EQ, cmd->body, 65535); - tt_int_op(0, OP_EQ, buf_datalen(buf)); - ext_or_cmd_free(cmd); - cmd = NULL; - - done: - ext_or_cmd_free(cmd); - buf_free(buf); - tor_free(tmp); -} - -static void test_buffer_allocation_tracking(void *arg) { char *junk = tor_malloc(16384); @@ -458,36 +384,36 @@ test_buffer_allocation_tracking(void *arg) tt_int_op(buf_allocation(buf1), OP_EQ, 0); tt_int_op(buf_get_total_allocation(), OP_EQ, 0); - write_to_buf(junk, 4000, buf1); - write_to_buf(junk, 4000, buf1); - write_to_buf(junk, 4000, buf1); - write_to_buf(junk, 4000, buf1); + buf_add(buf1, junk, 4000); + buf_add(buf1, junk, 4000); + buf_add(buf1, junk, 4000); + buf_add(buf1, junk, 4000); tt_int_op(buf_allocation(buf1), OP_EQ, 16384); - fetch_from_buf(junk, 100, buf1); + buf_get_bytes(buf1, junk, 100); tt_int_op(buf_allocation(buf1), OP_EQ, 16384); /* still 4 4k chunks */ tt_int_op(buf_get_total_allocation(), OP_EQ, 16384); - fetch_from_buf(junk, 4096, buf1); /* drop a 1k chunk... */ + buf_get_bytes(buf1, junk, 4096); /* drop a 1k chunk... */ tt_int_op(buf_allocation(buf1), OP_EQ, 3*4096); /* now 3 4k chunks */ tt_int_op(buf_get_total_allocation(), OP_EQ, 12288); /* that chunk was really freed. */ - write_to_buf(junk, 4000, buf2); + buf_add(buf2, junk, 4000); tt_int_op(buf_allocation(buf2), OP_EQ, 4096); /* another 4k chunk. */ /* * We bounce back up to 16384 by allocating a new chunk. */ tt_int_op(buf_get_total_allocation(), OP_EQ, 16384); - write_to_buf(junk, 4000, buf2); + buf_add(buf2, junk, 4000); tt_int_op(buf_allocation(buf2), OP_EQ, 8192); /* another 4k chunk. */ tt_int_op(buf_get_total_allocation(), OP_EQ, 5*4096); /* that chunk was new. */ /* Make a really huge buffer */ for (i = 0; i < 1000; ++i) { - write_to_buf(junk, 4000, buf2); + buf_add(buf2, junk, 4000); } tt_int_op(buf_allocation(buf2), OP_GE, 4008000); tt_int_op(buf_get_total_allocation(), OP_GE, 4008000); @@ -530,7 +456,7 @@ test_buffer_time_tracking(void *arg) tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC)); tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+1000)); - write_to_buf("ABCDEFG", 7, buf); + buf_add(buf, "ABCDEFG", 7); tt_int_op(1000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+1000)); buf2 = buf_copy(buf); @@ -541,7 +467,7 @@ test_buffer_time_tracking(void *arg) /* Now add more bytes; enough to overflow the first chunk. */ monotime_coarse_set_mock_time_nsec(START_NSEC + 123 * (uint64_t)1000000); for (i = 0; i < 600; ++i) - write_to_buf("ABCDEFG", 7, buf); + buf_add(buf, "ABCDEFG", 7); tt_int_op(4207, OP_EQ, buf_datalen(buf)); /* The oldest bytes are still in the front. */ @@ -549,12 +475,12 @@ test_buffer_time_tracking(void *arg) /* Once those bytes are dropped, the chunk is still on the first * timestamp. */ - fetch_from_buf(tmp, 100, buf); + buf_get_bytes(buf, tmp, 100); tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2000)); /* But once we discard the whole first chunk, we get the data in the second * chunk. */ - fetch_from_buf(tmp, 4000, buf); + buf_get_bytes(buf, tmp, 4000); tt_int_op(107, OP_EQ, buf_datalen(buf)); tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2123)); @@ -562,12 +488,12 @@ test_buffer_time_tracking(void *arg) its time gets updated */ monotime_coarse_set_mock_time_nsec(START_NSEC + 5617 * (uint64_t)1000000); for (i = 0; i < 600; ++i) - write_to_buf("ABCDEFG", 7, buf); + buf_add(buf, "ABCDEFG", 7); tt_int_op(4307, OP_EQ, buf_datalen(buf)); tt_int_op(2000, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+2123)); - fetch_from_buf(tmp, 4000, buf); - fetch_from_buf(tmp, 306, buf); + buf_get_bytes(buf, tmp, 4000); + buf_get_bytes(buf, tmp, 306); tt_int_op(0, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+5617)); tt_int_op(383, OP_EQ, buf_get_oldest_chunk_timestamp(buf, START_MSEC+6000)); @@ -593,23 +519,23 @@ test_buffers_compress_fin_at_chunk_end_impl(compress_method_t method, sz = buf_get_default_chunk_size(buf); msg = tor_malloc_zero(sz); - write_to_buf(msg, 1, buf); + buf_add(buf, msg, 1); tt_assert(buf->head); /* Fill up the chunk so the compression stuff won't fit in one chunk. */ tt_uint_op(buf->head->memlen, OP_LT, sz); headerjunk = buf->head->memlen - 7; - write_to_buf(msg, headerjunk-1, buf); + buf_add(buf, msg, headerjunk-1); tt_uint_op(buf->head->datalen, OP_EQ, headerjunk); tt_uint_op(buf_datalen(buf), OP_EQ, headerjunk); /* Write an empty string, with finalization on. */ compress_state = tor_compress_new(1, method, level); - tt_int_op(write_to_buf_compress(buf, compress_state, "", 0, 1), OP_EQ, 0); + tt_int_op(buf_add_compress(buf, compress_state, "", 0, 1), OP_EQ, 0); in_len = buf_datalen(buf); contents = tor_malloc(in_len); - tt_int_op(fetch_from_buf(contents, in_len, buf), OP_EQ, 0); + tt_int_op(buf_get_bytes(buf, contents, in_len), OP_EQ, 0); if (method == NO_METHOD) { tt_uint_op(in_len, OP_EQ, headerjunk); @@ -652,23 +578,23 @@ test_buffers_compress_impl(compress_method_t method, msg = tor_malloc(512); crypto_rand(msg, 512); - tt_int_op(write_to_buf_compress(buf, compress_state, + tt_int_op(buf_add_compress(buf, compress_state, msg, 128, 0), OP_EQ, 0); - tt_int_op(write_to_buf_compress(buf, compress_state, + tt_int_op(buf_add_compress(buf, compress_state, msg+128, 128, 0), OP_EQ, 0); - tt_int_op(write_to_buf_compress(buf, compress_state, + tt_int_op(buf_add_compress(buf, compress_state, msg+256, 256, 0), OP_EQ, 0); done = !finalize_with_nil; - tt_int_op(write_to_buf_compress(buf, compress_state, + tt_int_op(buf_add_compress(buf, compress_state, "all done", 9, done), OP_EQ, 0); if (finalize_with_nil) { - tt_int_op(write_to_buf_compress(buf, compress_state, "", 0, 1), OP_EQ, 0); + tt_int_op(buf_add_compress(buf, compress_state, "", 0, 1), OP_EQ, 0); } in_len = buf_datalen(buf); contents = tor_malloc(in_len); - tt_int_op(fetch_from_buf(contents, in_len, buf), OP_EQ, 0); + tt_int_op(buf_get_bytes(buf, contents, in_len), OP_EQ, 0); tt_int_op(0, OP_EQ, tor_uncompress(&expanded, &out_len, contents, in_len, @@ -762,11 +688,11 @@ test_buffers_tls_read_mocked(void *arg) buf = buf_new(); next_reply_val[0] = 1024; - tt_int_op(128, ==, read_to_buf_tls(NULL, 128, buf)); + tt_int_op(128, OP_EQ, buf_read_from_tls(buf, NULL, 128)); next_reply_val[0] = 5000; next_reply_val[1] = 5000; - tt_int_op(6000, ==, read_to_buf_tls(NULL, 6000, buf)); + tt_int_op(6000, OP_EQ, buf_read_from_tls(buf, NULL, 6000)); done: UNMOCK(tor_tls_read); @@ -780,17 +706,17 @@ test_buffers_chunk_size(void *arg) (void)arg; const int min = 256; const int max = 65536; - tt_uint_op(preferred_chunk_size(3), OP_EQ, min); - tt_uint_op(preferred_chunk_size(25), OP_EQ, min); - tt_uint_op(preferred_chunk_size(0), OP_EQ, min); - tt_uint_op(preferred_chunk_size(256), OP_EQ, 512); - tt_uint_op(preferred_chunk_size(65400), OP_EQ, max); + tt_uint_op(buf_preferred_chunk_size(3), OP_EQ, min); + tt_uint_op(buf_preferred_chunk_size(25), OP_EQ, min); + tt_uint_op(buf_preferred_chunk_size(0), OP_EQ, min); + tt_uint_op(buf_preferred_chunk_size(256), OP_EQ, 512); + tt_uint_op(buf_preferred_chunk_size(65400), OP_EQ, max); /* Here, we're implicitly saying that the chunk header overhead is * between 1 and 100 bytes. 24..48 would probably be more accurate. */ - tt_uint_op(preferred_chunk_size(65536), OP_GT, 65536); - tt_uint_op(preferred_chunk_size(65536), OP_LT, 65536+100); - tt_uint_op(preferred_chunk_size(165536), OP_GT, 165536); - tt_uint_op(preferred_chunk_size(165536), OP_LT, 165536+100); + tt_uint_op(buf_preferred_chunk_size(65536), OP_GT, 65536); + tt_uint_op(buf_preferred_chunk_size(65536), OP_LT, 65536+100); + tt_uint_op(buf_preferred_chunk_size(165536), OP_GT, 165536); + tt_uint_op(buf_preferred_chunk_size(165536), OP_LT, 165536+100); done: ; } @@ -831,18 +757,44 @@ test_buffers_find_contentlen(void *arg) r = buf_http_find_content_length(tmp, headerlen, &sz); tor_free(tmp); log_debug(LD_DIR, "%d: %s", i, escaped(results[i].headers)); - tt_int_op(r, ==, results[i].r); - tt_int_op(sz, ==, results[i].contentlen); + tt_int_op(r, OP_EQ, results[i].r); + tt_int_op(sz, OP_EQ, results[i].contentlen); } done: ; } +static void +test_buffer_peek_startswith(void *arg) +{ + (void)arg; + buf_t *buf; + buf = buf_new(); + tt_ptr_op(buf, OP_NE, NULL); + + tt_assert(buf_peek_startswith(buf, "")); + tt_assert(! buf_peek_startswith(buf, "X")); + + buf_add(buf, "Tor", 3); + + tt_assert(buf_peek_startswith(buf, "")); + tt_assert(buf_peek_startswith(buf, "T")); + tt_assert(buf_peek_startswith(buf, "To")); + tt_assert(buf_peek_startswith(buf, "Tor")); + tt_assert(! buf_peek_startswith(buf, "Top")); + tt_assert(! buf_peek_startswith(buf, "For")); + tt_assert(! buf_peek_startswith(buf, "Tork")); + tt_assert(! buf_peek_startswith(buf, "Torpor")); + + done: + buf_free(buf); +} + struct testcase_t buffer_tests[] = { { "basic", test_buffers_basic, TT_FORK, NULL, NULL }, { "copy", test_buffer_copy, TT_FORK, NULL, NULL }, { "pullup", test_buffer_pullup, TT_FORK, NULL, NULL }, - { "ext_or_cmd", test_buffer_ext_or_cmd, TT_FORK, NULL, NULL }, + { "startswith", test_buffer_peek_startswith, 0, NULL, NULL }, { "allocation_tracking", test_buffer_allocation_tracking, TT_FORK, NULL, NULL }, { "time_tracking", test_buffer_time_tracking, TT_FORK, NULL, NULL }, diff --git a/src/test/test_channel.c b/src/test/test_channel.c index f5999b8e67..023c2950c9 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -86,7 +86,7 @@ channel_note_destroy_not_pending_mock(channel_t *ch, static const char * chan_test_describe_transport(channel_t *ch) { - tt_assert(ch != NULL); + tt_ptr_op(ch, OP_NE, NULL); done: return "Fake channel for unit tests"; @@ -100,7 +100,7 @@ chan_test_describe_transport(channel_t *ch) static void chan_test_channel_dump_statistics_mock(channel_t *chan, int severity) { - tt_assert(chan != NULL); + tt_ptr_op(chan, OP_NE, NULL); (void)severity; @@ -125,7 +125,7 @@ chan_test_channel_flush_from_first_active_circuit_mock(channel_t *chan, int result = 0, c = 0; packed_cell_t *cell = NULL; - tt_assert(chan != NULL); + tt_ptr_op(chan, OP_NE, NULL); if (test_target_cmux != NULL && test_target_cmux == chan->cmux) { while (c <= max && test_cmux_cells > 0) { @@ -154,7 +154,7 @@ chan_test_circuitmux_num_cells_mock(circuitmux_t *cmux) { unsigned int result = 0; - tt_assert(cmux != NULL); + tt_ptr_op(cmux, OP_NE, NULL); if (cmux != NULL) { if (cmux == test_target_cmux) { result = test_cmux_cells; @@ -193,7 +193,7 @@ chan_test_cell_handler(channel_t *ch, static void chan_test_dumpstats(channel_t *ch, int severity) { - tt_assert(ch != NULL); + tt_ptr_op(ch, OP_NE, NULL); (void)severity; @@ -268,7 +268,7 @@ static const char * chan_test_get_remote_descr(channel_t *ch, int flags) { tt_assert(ch); - tt_int_op(flags & ~(GRD_FLAG_ORIGINAL | GRD_FLAG_ADDR_ONLY), ==, 0); + tt_int_op(flags & ~(GRD_FLAG_ORIGINAL | GRD_FLAG_ADDR_ONLY), OP_EQ, 0); done: return "Fake channel for unit tests; no real endpoint"; @@ -286,7 +286,7 @@ chan_test_get_overhead_estimate(channel_t *ch) static int chan_test_is_canonical(channel_t *ch, int req) { - tt_assert(ch != NULL); + tt_ptr_op(ch, OP_NE, NULL); tt_assert(req == 0 || req == 1); done: @@ -380,7 +380,7 @@ chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell) void make_fake_cell(cell_t *c) { - tt_assert(c != NULL); + tt_ptr_op(c, OP_NE, NULL); c->circ_id = 1; c->command = CELL_RELAY; @@ -397,7 +397,7 @@ make_fake_cell(cell_t *c) void make_fake_var_cell(var_cell_t *c) { - tt_assert(c != NULL); + tt_ptr_op(c, OP_NE, NULL); c->circ_id = 1; c->command = CELL_VERSIONS; @@ -552,24 +552,24 @@ test_channel_dumpstats(void *arg) channel_dumpstats(LOG_DEBUG); /* Assert that we hit the mock */ - tt_int_op(dump_statistics_mock_matches, ==, 1); + tt_int_op(dump_statistics_mock_matches, OP_EQ, 1); /* Close the channel */ channel_mark_for_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); /* Try again and hit the finished channel */ channel_dumpstats(LOG_DEBUG); - tt_int_op(dump_statistics_mock_matches, ==, 2); + tt_int_op(dump_statistics_mock_matches, OP_EQ, 2); channel_run_cleanup(); ch = NULL; /* Now we should hit nothing */ channel_dumpstats(LOG_DEBUG); - tt_int_op(dump_statistics_mock_matches, ==, 2); + tt_int_op(dump_statistics_mock_matches, OP_EQ, 2); /* Unmock */ UNMOCK(channel_dump_statistics); @@ -594,7 +594,7 @@ test_channel_dumpstats(void *arg) old_count = test_cells_written; channel_write_cell(ch, cell); cell = NULL; - tt_int_op(test_cells_written, ==, old_count + 1); + tt_int_op(test_cells_written, OP_EQ, old_count + 1); tt_assert(ch->n_bytes_xmitted > 0); tt_assert(ch->n_cells_xmitted > 0); @@ -602,14 +602,15 @@ test_channel_dumpstats(void *arg) channel_set_cell_handlers(ch, chan_test_cell_handler, chan_test_var_cell_handler); - tt_ptr_op(channel_get_cell_handler(ch), ==, chan_test_cell_handler); - tt_ptr_op(channel_get_var_cell_handler(ch), ==, chan_test_var_cell_handler); + tt_ptr_op(channel_get_cell_handler(ch), OP_EQ, chan_test_cell_handler); + tt_ptr_op(channel_get_var_cell_handler(ch), OP_EQ, + chan_test_var_cell_handler); cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); old_count = test_chan_fixed_cells_recved; channel_queue_cell(ch, cell); tor_free(cell); - tt_int_op(test_chan_fixed_cells_recved, ==, old_count + 1); + tt_int_op(test_chan_fixed_cells_recved, OP_EQ, old_count + 1); tt_assert(ch->n_bytes_recved > 0); tt_assert(ch->n_cells_recved > 0); @@ -619,13 +620,13 @@ test_channel_dumpstats(void *arg) ch->is_canonical = chan_test_is_canonical; old_count = test_dumpstats_calls; channel_dump_statistics(ch, LOG_DEBUG); - tt_int_op(test_dumpstats_calls, ==, old_count + 1); + tt_int_op(test_dumpstats_calls, OP_EQ, old_count + 1); /* Close the channel */ channel_mark_for_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -664,21 +665,21 @@ test_channel_flush(void *arg) make_fake_cell(cell); channel_write_cell(ch, cell); /* It should be queued, so assert that we didn't write it */ - tt_int_op(test_cells_written, ==, init_count); + tt_int_op(test_cells_written, OP_EQ, init_count); /* Queue a var cell */ v_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); make_fake_var_cell(v_cell); channel_write_var_cell(ch, v_cell); /* It should be queued, so assert that we didn't write it */ - tt_int_op(test_cells_written, ==, init_count); + tt_int_op(test_cells_written, OP_EQ, init_count); /* Try a packed cell now */ p_cell = packed_cell_new(); tt_assert(p_cell); channel_write_packed_cell(ch, p_cell); /* It should be queued, so assert that we didn't write it */ - tt_int_op(test_cells_written, ==, init_count); + tt_int_op(test_cells_written, OP_EQ, init_count); /* Now allow writes through again */ test_chan_accept_cells = 1; @@ -687,7 +688,7 @@ test_channel_flush(void *arg) channel_flush_cells(ch); /* All three should have gone through */ - tt_int_op(test_cells_written, ==, init_count + 3); + tt_int_op(test_cells_written, OP_EQ, init_count + 3); done: tor_free(ch); @@ -728,9 +729,9 @@ test_channel_flushmux(void *arg) result = channel_flush_some_cells(ch, 1); - tt_int_op(result, ==, 1); - tt_int_op(test_cells_written, ==, old_count + 1); - tt_int_op(test_cmux_cells, ==, 0); + tt_int_op(result, OP_EQ, 1); + tt_int_op(test_cells_written, OP_EQ, old_count + 1); + tt_int_op(test_cmux_cells, OP_EQ, 0); /* Now try it without accepting to force them into the queue */ test_chan_accept_cells = 0; @@ -740,19 +741,19 @@ test_channel_flushmux(void *arg) result = channel_flush_some_cells(ch, 1); /* We should not have actually flushed any */ - tt_int_op(result, ==, 0); - tt_int_op(test_cells_written, ==, old_count + 1); + tt_int_op(result, OP_EQ, 0); + tt_int_op(test_cells_written, OP_EQ, old_count + 1); /* But we should have gotten to the fake cellgen loop */ - tt_int_op(test_cmux_cells, ==, 0); + tt_int_op(test_cmux_cells, OP_EQ, 0); /* ...and we should have a queued cell */ q_len_after = chan_cell_queue_len(&(ch->outgoing_queue)); - tt_int_op(q_len_after, ==, q_len_before + 1); + tt_int_op(q_len_after, OP_EQ, q_len_before + 1); /* Now accept cells again and drain the queue */ test_chan_accept_cells = 1; channel_flush_cells(ch); - tt_int_op(test_cells_written, ==, old_count + 2); - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); + tt_int_op(test_cells_written, OP_EQ, old_count + 2); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 0); test_target_cmux = NULL; test_cmux_cells = 0; @@ -803,16 +804,17 @@ test_channel_incoming(void *arg) chan_test_cell_handler, chan_test_var_cell_handler); /* Test cell handler getters */ - tt_ptr_op(channel_get_cell_handler(ch), ==, chan_test_cell_handler); - tt_ptr_op(channel_get_var_cell_handler(ch), ==, chan_test_var_cell_handler); + tt_ptr_op(channel_get_cell_handler(ch), OP_EQ, chan_test_cell_handler); + tt_ptr_op(channel_get_var_cell_handler(ch), OP_EQ, + chan_test_var_cell_handler); /* Try to register it */ channel_register(ch); tt_assert(ch->registered); /* Open it */ - channel_change_state(ch, CHANNEL_STATE_OPEN); - tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN); /* Receive a fixed cell */ cell = tor_malloc_zero(sizeof(cell_t)); @@ -820,7 +822,7 @@ test_channel_incoming(void *arg) old_count = test_chan_fixed_cells_recved; channel_queue_cell(ch, cell); tor_free(cell); - tt_int_op(test_chan_fixed_cells_recved, ==, old_count + 1); + tt_int_op(test_chan_fixed_cells_recved, OP_EQ, old_count + 1); /* Receive a variable-size cell */ var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); @@ -828,13 +830,13 @@ test_channel_incoming(void *arg) old_count = test_chan_var_cells_recved; channel_queue_var_cell(ch, var_cell); tor_free(cell); - tt_int_op(test_chan_var_cells_recved, ==, old_count + 1); + tt_int_op(test_chan_var_cells_recved, OP_EQ, old_count + 1); /* Close it */ channel_mark_for_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -896,13 +898,13 @@ test_channel_lifecycle(void *arg) make_fake_cell(cell); old_count = test_cells_written; channel_write_cell(ch1, cell); - tt_int_op(old_count, ==, test_cells_written); + tt_int_op(old_count, OP_EQ, test_cells_written); /* Move it to OPEN and flush */ - channel_change_state(ch1, CHANNEL_STATE_OPEN); + channel_change_state_open(ch1); /* Queue should drain */ - tt_int_op(old_count + 1, ==, test_cells_written); + tt_int_op(old_count + 1, OP_EQ, test_cells_written); /* Get another one */ ch2 = new_fake_channel(); @@ -915,41 +917,42 @@ test_channel_lifecycle(void *arg) tt_assert(ch2->registered); /* Check counters */ - tt_int_op(test_doesnt_want_writes_count, ==, init_doesnt_want_writes_count); - tt_int_op(test_releases_count, ==, init_releases_count); + tt_int_op(test_doesnt_want_writes_count, OP_EQ, + init_doesnt_want_writes_count); + tt_int_op(test_releases_count, OP_EQ, init_releases_count); /* Move ch1 to MAINT */ channel_change_state(ch1, CHANNEL_STATE_MAINT); - tt_int_op(test_doesnt_want_writes_count, ==, + tt_int_op(test_doesnt_want_writes_count, OP_EQ, init_doesnt_want_writes_count + 1); - tt_int_op(test_releases_count, ==, init_releases_count); + tt_int_op(test_releases_count, OP_EQ, init_releases_count); /* Move ch2 to OPEN */ - channel_change_state(ch2, CHANNEL_STATE_OPEN); - tt_int_op(test_doesnt_want_writes_count, ==, + channel_change_state_open(ch2); + tt_int_op(test_doesnt_want_writes_count, OP_EQ, init_doesnt_want_writes_count + 1); - tt_int_op(test_releases_count, ==, init_releases_count); + tt_int_op(test_releases_count, OP_EQ, init_releases_count); /* Move ch1 back to OPEN */ - channel_change_state(ch1, CHANNEL_STATE_OPEN); - tt_int_op(test_doesnt_want_writes_count, ==, + channel_change_state_open(ch1); + tt_int_op(test_doesnt_want_writes_count, OP_EQ, init_doesnt_want_writes_count + 1); - tt_int_op(test_releases_count, ==, init_releases_count); + tt_int_op(test_releases_count, OP_EQ, init_releases_count); /* Mark ch2 for close */ channel_mark_for_close(ch2); - tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSING); - tt_int_op(test_doesnt_want_writes_count, ==, + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_CLOSING); + tt_int_op(test_doesnt_want_writes_count, OP_EQ, init_doesnt_want_writes_count + 1); - tt_int_op(test_releases_count, ==, init_releases_count + 1); + tt_int_op(test_releases_count, OP_EQ, init_releases_count + 1); /* Shut down channels */ channel_free_all(); ch1 = ch2 = NULL; - tt_int_op(test_doesnt_want_writes_count, ==, + tt_int_op(test_doesnt_want_writes_count, OP_EQ, init_doesnt_want_writes_count + 1); /* channel_free() calls scheduler_release_channel() */ - tt_int_op(test_releases_count, ==, init_releases_count + 4); + tt_int_op(test_releases_count, OP_EQ, init_releases_count + 4); done: free_fake_channel(ch1); @@ -1001,11 +1004,11 @@ test_channel_lifecycle_2(void *arg) /* Try to close it */ channel_mark_for_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); /* Finish closing it */ chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -1018,13 +1021,13 @@ test_channel_lifecycle_2(void *arg) tt_assert(ch->registered); /* Finish opening it */ - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); /* Error exit from lower layer */ chan_test_error(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_ERROR); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_ERROR); channel_run_cleanup(); ch = NULL; @@ -1037,20 +1040,20 @@ test_channel_lifecycle_2(void *arg) tt_assert(ch->registered); /* Finish opening it */ - channel_change_state(ch, CHANNEL_STATE_OPEN); - tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN); /* Go to maintenance state */ channel_change_state(ch, CHANNEL_STATE_MAINT); - tt_int_op(ch->state, ==, CHANNEL_STATE_MAINT); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_MAINT); /* Lower layer close */ channel_mark_for_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); /* Finish */ chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -1066,20 +1069,20 @@ test_channel_lifecycle_2(void *arg) tt_assert(ch->registered); /* Finish opening it */ - channel_change_state(ch, CHANNEL_STATE_OPEN); - tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN); /* Go to maintenance state */ channel_change_state(ch, CHANNEL_STATE_MAINT); - tt_int_op(ch->state, ==, CHANNEL_STATE_MAINT); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_MAINT); /* Lower layer close */ channel_close_from_lower_layer(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); /* Finish */ chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -1092,20 +1095,20 @@ test_channel_lifecycle_2(void *arg) tt_assert(ch->registered); /* Finish opening it */ - channel_change_state(ch, CHANNEL_STATE_OPEN); - tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN); /* Go to maintenance state */ channel_change_state(ch, CHANNEL_STATE_MAINT); - tt_int_op(ch->state, ==, CHANNEL_STATE_MAINT); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_MAINT); /* Lower layer close */ chan_test_error(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); /* Finish */ chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_ERROR); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_ERROR); channel_run_cleanup(); ch = NULL; @@ -1142,11 +1145,11 @@ test_channel_multi(void *arg) /* Initial queue size update */ channel_update_xmit_queue_size(ch1); - tt_u64_op(ch1->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch1->bytes_queued_for_xmit, OP_EQ, 0); channel_update_xmit_queue_size(ch2); - tt_u64_op(ch2->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch2->bytes_queued_for_xmit, OP_EQ, 0); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 0); + tt_u64_op(global_queue_estimate, OP_EQ, 0); /* Queue some cells, check queue estimates */ cell = tor_malloc_zero(sizeof(cell_t)); @@ -1159,10 +1162,10 @@ test_channel_multi(void *arg) channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - tt_u64_op(ch1->bytes_queued_for_xmit, ==, 0); - tt_u64_op(ch2->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch1->bytes_queued_for_xmit, OP_EQ, 0); + tt_u64_op(ch2->bytes_queued_for_xmit, OP_EQ, 0); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 0); + tt_u64_op(global_queue_estimate, OP_EQ, 0); /* Stop accepting cells at lower layer */ test_chan_accept_cells = 0; @@ -1173,18 +1176,18 @@ test_channel_multi(void *arg) channel_write_cell(ch1, cell); channel_update_xmit_queue_size(ch1); - tt_u64_op(ch1->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch1->bytes_queued_for_xmit, OP_EQ, 512); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 512); + tt_u64_op(global_queue_estimate, OP_EQ, 512); cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); channel_write_cell(ch2, cell); channel_update_xmit_queue_size(ch2); - tt_u64_op(ch2->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch2->bytes_queued_for_xmit, OP_EQ, 512); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 1024); + tt_u64_op(global_queue_estimate, OP_EQ, 1024); /* Allow cells through again */ test_chan_accept_cells = 1; @@ -1195,10 +1198,10 @@ test_channel_multi(void *arg) /* Update and check queue sizes */ channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - tt_u64_op(ch1->bytes_queued_for_xmit, ==, 512); - tt_u64_op(ch2->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch1->bytes_queued_for_xmit, OP_EQ, 512); + tt_u64_op(ch2->bytes_queued_for_xmit, OP_EQ, 0); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 512); + tt_u64_op(global_queue_estimate, OP_EQ, 512); /* Flush chan 1 */ channel_flush_cells(ch1); @@ -1206,10 +1209,10 @@ test_channel_multi(void *arg) /* Update and check queue sizes */ channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - tt_u64_op(ch1->bytes_queued_for_xmit, ==, 0); - tt_u64_op(ch2->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch1->bytes_queued_for_xmit, OP_EQ, 0); + tt_u64_op(ch2->bytes_queued_for_xmit, OP_EQ, 0); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 0); + tt_u64_op(global_queue_estimate, OP_EQ, 0); /* Now block again */ test_chan_accept_cells = 0; @@ -1227,10 +1230,10 @@ test_channel_multi(void *arg) /* Check the estimates */ channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - tt_u64_op(ch1->bytes_queued_for_xmit, ==, 512); - tt_u64_op(ch2->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch1->bytes_queued_for_xmit, OP_EQ, 512); + tt_u64_op(ch2->bytes_queued_for_xmit, OP_EQ, 512); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 1024); + tt_u64_op(global_queue_estimate, OP_EQ, 1024); /* Now close channel 2; it should be subtracted from the global queue */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); @@ -1238,7 +1241,7 @@ test_channel_multi(void *arg) UNMOCK(scheduler_release_channel); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 512); + tt_u64_op(global_queue_estimate, OP_EQ, 512); /* * Since the fake channels aren't registered, channel_free_all() can't @@ -1249,7 +1252,7 @@ test_channel_multi(void *arg) UNMOCK(scheduler_release_channel); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 0); + tt_u64_op(global_queue_estimate, OP_EQ, 0); /* Now free everything */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); @@ -1297,7 +1300,7 @@ test_channel_queue_impossible(void *arg) old_count = test_cells_written; /* Assert that the queue is initially empty */ - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 0); /* Get a fresh cell and write it to the channel*/ cell = tor_malloc_zero(sizeof(cell_t)); @@ -1306,11 +1309,11 @@ test_channel_queue_impossible(void *arg) channel_write_cell(ch, cell); /* Now it should be queued */ - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); tt_assert(q); if (q) { - tt_int_op(q->type, ==, CELL_QUEUE_FIXED); + tt_int_op(q->type, OP_EQ, CELL_QUEUE_FIXED); tt_assert((uintptr_t)q->u.fixed.cell == cellintptr); } /* Do perverse things to it */ @@ -1322,9 +1325,9 @@ test_channel_queue_impossible(void *arg) * gets thrown away properly. */ test_chan_accept_cells = 1; - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); tt_assert(test_cells_written == old_count); - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 0); /* Same thing but for a var_cell */ @@ -1336,11 +1339,11 @@ test_channel_queue_impossible(void *arg) channel_write_var_cell(ch, var_cell); /* Check that it's queued */ - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); tt_assert(q); if (q) { - tt_int_op(q->type, ==, CELL_QUEUE_VAR); + tt_int_op(q->type, OP_EQ, CELL_QUEUE_VAR); tt_assert((uintptr_t)q->u.var.var_cell == cellintptr); } @@ -1350,9 +1353,9 @@ test_channel_queue_impossible(void *arg) /* Let it drain and check that the bad entry is discarded */ test_chan_accept_cells = 1; - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); tt_assert(test_cells_written == old_count); - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 0); /* Same thing with a packed_cell */ @@ -1364,11 +1367,11 @@ test_channel_queue_impossible(void *arg) channel_write_packed_cell(ch, packed_cell); /* Check that it's queued */ - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); tt_assert(q); if (q) { - tt_int_op(q->type, ==, CELL_QUEUE_PACKED); + tt_int_op(q->type, OP_EQ, CELL_QUEUE_PACKED); tt_assert((uintptr_t)q->u.packed.packed_cell == cellintptr); } @@ -1378,9 +1381,9 @@ test_channel_queue_impossible(void *arg) /* Let it drain and check that the bad entry is discarded */ test_chan_accept_cells = 1; - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); tt_assert(test_cells_written == old_count); - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 0); /* Unknown cell type case */ test_chan_accept_cells = 0; @@ -1391,11 +1394,11 @@ test_channel_queue_impossible(void *arg) channel_write_cell(ch, cell); /* Check that it's queued */ - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); tt_assert(q); if (q) { - tt_int_op(q->type, ==, CELL_QUEUE_FIXED); + tt_int_op(q->type, OP_EQ, CELL_QUEUE_FIXED); tt_assert((uintptr_t)q->u.fixed.cell == cellintptr); } /* Clobber it, including the queue entry type */ @@ -1406,11 +1409,11 @@ test_channel_queue_impossible(void *arg) /* Let it drain and check that the bad entry is discarded */ test_chan_accept_cells = 1; tor_capture_bugs_(1); - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); tt_assert(test_cells_written == old_count); - tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), OP_EQ, 0); - tt_int_op(smartlist_len(tor_get_captured_bug_log_()), ==, 1); + tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); tor_end_capture_bugs_(); done: @@ -1455,16 +1458,16 @@ test_channel_queue_incoming(void *arg) ch->cmux = circuitmux_alloc(); /* Test cell handler getters */ - tt_ptr_op(channel_get_cell_handler(ch), ==, NULL); - tt_ptr_op(channel_get_var_cell_handler(ch), ==, NULL); + tt_ptr_op(channel_get_cell_handler(ch), OP_EQ, NULL); + tt_ptr_op(channel_get_var_cell_handler(ch), OP_EQ, NULL); /* Try to register it */ channel_register(ch); tt_assert(ch->registered); /* Open it */ - channel_change_state(ch, CHANNEL_STATE_OPEN); - tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_OPEN); /* Assert that the incoming queue is empty */ tt_assert(TOR_SIMPLEQ_EMPTY(&(ch->incoming_queue))); @@ -1475,7 +1478,7 @@ test_channel_queue_incoming(void *arg) channel_queue_cell(ch, cell); /* Assert that the incoming queue has one entry */ - tt_int_op(chan_cell_queue_len(&(ch->incoming_queue)), ==, 1); + tt_int_op(chan_cell_queue_len(&(ch->incoming_queue)), OP_EQ, 1); /* Queue an incoming var cell */ var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); @@ -1483,7 +1486,7 @@ test_channel_queue_incoming(void *arg) channel_queue_var_cell(ch, var_cell); /* Assert that the incoming queue has two entries */ - tt_int_op(chan_cell_queue_len(&(ch->incoming_queue)), ==, 2); + tt_int_op(chan_cell_queue_len(&(ch->incoming_queue)), OP_EQ, 2); /* * Install cell handlers; this will drain the queue, so save the old @@ -1494,12 +1497,13 @@ test_channel_queue_incoming(void *arg) channel_set_cell_handlers(ch, chan_test_cell_handler, chan_test_var_cell_handler); - tt_ptr_op(channel_get_cell_handler(ch), ==, chan_test_cell_handler); - tt_ptr_op(channel_get_var_cell_handler(ch), ==, chan_test_var_cell_handler); + tt_ptr_op(channel_get_cell_handler(ch), OP_EQ, chan_test_cell_handler); + tt_ptr_op(channel_get_var_cell_handler(ch), OP_EQ, + chan_test_var_cell_handler); /* Assert cells were received */ - tt_int_op(test_chan_fixed_cells_recved, ==, old_fixed_count + 1); - tt_int_op(test_chan_var_cells_recved, ==, old_var_count + 1); + tt_int_op(test_chan_fixed_cells_recved, OP_EQ, old_fixed_count + 1); + tt_int_op(test_chan_var_cells_recved, OP_EQ, old_var_count + 1); /* * Assert that the pointers are different from the cells we allocated; @@ -1508,17 +1512,17 @@ test_channel_queue_incoming(void *arg) * delivery. These pointers will have already been freed by the time * we get here, so don't dereference them. */ - tt_ptr_op(test_chan_last_seen_fixed_cell_ptr, !=, cell); - tt_ptr_op(test_chan_last_seen_var_cell_ptr, !=, var_cell); + tt_ptr_op(test_chan_last_seen_fixed_cell_ptr, OP_NE, cell); + tt_ptr_op(test_chan_last_seen_var_cell_ptr, OP_NE, var_cell); /* Assert queue is now empty */ tt_assert(TOR_SIMPLEQ_EMPTY(&(ch->incoming_queue))); /* Close it; this contains an assertion that the incoming queue is empty */ channel_mark_for_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, OP_EQ, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -1548,14 +1552,14 @@ test_channel_queue_size(void *arg) /* Initial queue size update */ channel_update_xmit_queue_size(ch); - tt_u64_op(ch->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 0); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 0); + tt_u64_op(global_queue_estimate, OP_EQ, 0); /* Test the call-through to our fake lower layer */ n = channel_num_cells_writeable(ch); /* chan_test_num_cells_writeable() always returns 32 */ - tt_int_op(n, ==, 32); + tt_int_op(n, OP_EQ, 32); /* * Now we queue some cells and check that channel_num_cells_writeable() @@ -1574,72 +1578,73 @@ test_channel_queue_size(void *arg) old_count = test_cells_written; channel_write_cell(ch, cell); /* Assert that it got queued, not written through, correctly */ - tt_int_op(test_cells_written, ==, old_count); + tt_int_op(test_cells_written, OP_EQ, old_count); /* Now check chan_test_num_cells_writeable() again */ n = channel_num_cells_writeable(ch); - tt_int_op(n, ==, 0); /* Should return 0 since we're in CHANNEL_STATE_MAINT */ + /* Should return 0 since we're in CHANNEL_STATE_MAINT */ + tt_int_op(n, OP_EQ, 0); /* Update queue size estimates */ channel_update_xmit_queue_size(ch); /* One cell, times an overhead factor of 1.0 */ - tt_u64_op(ch->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 512); /* Try a different overhead factor */ test_overhead_estimate = 0.5; /* This one should be ignored since it's below 1.0 */ channel_update_xmit_queue_size(ch); - tt_u64_op(ch->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 512); /* Now try a larger one */ test_overhead_estimate = 2.0; channel_update_xmit_queue_size(ch); - tt_u64_op(ch->bytes_queued_for_xmit, ==, 1024); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 1024); /* Go back to 1.0 */ test_overhead_estimate = 1.0; channel_update_xmit_queue_size(ch); - tt_u64_op(ch->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 512); /* Check the global estimate too */ global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 512); + tt_u64_op(global_queue_estimate, OP_EQ, 512); /* Go to open */ old_count = test_cells_written; - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); /* * It should try to write, but we aren't accepting cells right now, so * it'll requeue */ - tt_int_op(test_cells_written, ==, old_count); + tt_int_op(test_cells_written, OP_EQ, old_count); /* Check the queue size again */ channel_update_xmit_queue_size(ch); - tt_u64_op(ch->bytes_queued_for_xmit, ==, 512); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 512); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 512); + tt_u64_op(global_queue_estimate, OP_EQ, 512); /* * Now the cell is in the queue, and we're open, so we should get 31 * writeable cells. */ n = channel_num_cells_writeable(ch); - tt_int_op(n, ==, 31); + tt_int_op(n, OP_EQ, 31); /* Accept cells again */ test_chan_accept_cells = 1; /* ...and re-process the queue */ old_count = test_cells_written; channel_flush_cells(ch); - tt_int_op(test_cells_written, ==, old_count + 1); + tt_int_op(test_cells_written, OP_EQ, old_count + 1); /* Should have 32 writeable now */ n = channel_num_cells_writeable(ch); - tt_int_op(n, ==, 32); + tt_int_op(n, OP_EQ, 32); /* Should have queue size estimate of zero */ channel_update_xmit_queue_size(ch); - tt_u64_op(ch->bytes_queued_for_xmit, ==, 0); + tt_u64_op(ch->bytes_queued_for_xmit, OP_EQ, 0); global_queue_estimate = channel_get_global_queue_estimate(); - tt_u64_op(global_queue_estimate, ==, 0); + tt_u64_op(global_queue_estimate, OP_EQ, 0); /* Okay, now we're done with this one */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); @@ -1706,7 +1711,7 @@ test_channel_write(void *arg) * gets drained from the queue. */ test_chan_accept_cells = 1; - channel_change_state(ch, CHANNEL_STATE_OPEN); + channel_change_state_open(ch); tt_assert(test_cells_written == old_count + 1); /* @@ -1780,7 +1785,7 @@ test_channel_id_map(void *arg) ed25519_public_key_t ed_zero; memset(&ed_zero, 0, sizeof(ed_zero)); - tt_assert(sizeof(rsa_id[0]) == DIGEST_LEN); // Do I remember C? + tt_int_op(DIGEST_LEN, OP_EQ, sizeof(rsa_id[0])); // Do I remember C? for (i = 0; i < N_CHAN; ++i) { crypto_rand(rsa_id[i], DIGEST_LEN); @@ -1822,7 +1827,7 @@ test_channel_id_map(void *arg) ch = channel_next_with_rsa_identity(ch); tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]); ch = channel_next_with_rsa_identity(ch); - tt_assert(ch == NULL); + tt_ptr_op(ch, OP_EQ, NULL); /* As above, but with zero Ed25519 ID (meaning "any ID") */ tt_ptr_op(chan[0], OP_EQ, @@ -1838,7 +1843,7 @@ test_channel_id_map(void *arg) ch = channel_next_with_rsa_identity(ch); tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]); ch = channel_next_with_rsa_identity(ch); - tt_assert(ch == NULL); + tt_ptr_op(ch, OP_EQ, NULL); /* Lookup nonexistent RSA identity */ tt_ptr_op(NULL, OP_EQ, diff --git a/src/test/test_channelpadding.c b/src/test/test_channelpadding.c index d54c9cc52c..d5713688a0 100644 --- a/src/test/test_channelpadding.c +++ b/src/test/test_channelpadding.c @@ -193,7 +193,8 @@ static void setup_mock_network(void) { routerstatus_t *relay; - connection_array = smartlist_new(); + if (!connection_array) + connection_array = smartlist_new(); relay1_relay2 = (channel_t*)new_fake_channeltls(2); relay1_relay2->write_cell = mock_channel_write_cell_relay1; @@ -280,7 +281,8 @@ test_channelpadding_timers(void *arg) tor_libevent_postfork(); - connection_array = smartlist_new(); + if (!connection_array) + connection_array = smartlist_new(); monotime_init(); monotime_enable_test_mocking(); @@ -570,7 +572,8 @@ test_channelpadding_consensus(void *arg) monotime_coarse_set_mock_time_nsec(1); timers_initialize(); - connection_array = smartlist_new(); + if (!connection_array) + connection_array = smartlist_new(); chan = (channel_t*)new_fake_channeltls(0); channel_timestamp_active(chan); @@ -928,7 +931,8 @@ test_channelpadding_decide_to_pad_channel(void *arg) */ channel_t *chan; int64_t new_time; - connection_array = smartlist_new(); + if (!connection_array) + connection_array = smartlist_new(); (void)arg; tor_libevent_postfork(); diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index 96c5eba9a5..94f1893cae 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -72,7 +72,7 @@ test_channeltls_create(void *arg) /* Try connecting */ ch = channel_tls_connect(&test_addr, 567, test_digest, NULL); - tt_assert(ch != NULL); + tt_ptr_op(ch, OP_NE, NULL); done: if (ch) { @@ -121,7 +121,7 @@ test_channeltls_num_bytes_queued(void *arg) /* Try connecting */ ch = channel_tls_connect(&test_addr, 567, test_digest, NULL); - tt_assert(ch != NULL); + tt_ptr_op(ch, OP_NE, NULL); /* * Next, we have to test ch->num_bytes_queued, which is @@ -132,7 +132,7 @@ test_channeltls_num_bytes_queued(void *arg) tt_assert(ch->num_bytes_queued != NULL); tlschan = BASE_CHAN_TO_TLS(ch); - tt_assert(tlschan != NULL); + tt_ptr_op(tlschan, OP_NE, NULL); if (TO_CONN(tlschan->conn)->outbuf == NULL) { /* We need an outbuf to make sure buf_datalen() gets called */ fake_outbuf = 1; @@ -142,7 +142,7 @@ test_channeltls_num_bytes_queued(void *arg) tlschan_buf_datalen_mock_size = 1024; MOCK(buf_datalen, tlschan_buf_datalen_mock); len = ch->num_bytes_queued(ch); - tt_int_op(len, ==, tlschan_buf_datalen_mock_size); + tt_int_op(len, OP_EQ, tlschan_buf_datalen_mock_size); /* * We also cover num_cells_writeable here; since wide_circ_ids = 0 on * the fake tlschans, cell_network_size returns 512, and so with @@ -151,7 +151,7 @@ test_channeltls_num_bytes_queued(void *arg) * - 2 cells. */ n = ch->num_cells_writeable(ch); - tt_int_op(n, ==, CEIL_DIV(OR_CONN_HIGHWATER, 512) - 2); + tt_int_op(n, OP_EQ, CEIL_DIV(OR_CONN_HIGHWATER, 512) - 2); UNMOCK(buf_datalen); tlschan_buf_datalen_mock_target = NULL; tlschan_buf_datalen_mock_size = 0; @@ -206,11 +206,11 @@ test_channeltls_overhead_estimate(void *arg) /* Try connecting */ ch = channel_tls_connect(&test_addr, 567, test_digest, NULL); - tt_assert(ch != NULL); + tt_ptr_op(ch, OP_NE, NULL); /* First case: silly low ratios should get clamped to 1.0 */ tlschan = BASE_CHAN_TO_TLS(ch); - tt_assert(tlschan != NULL); + tt_ptr_op(tlschan, OP_NE, NULL); tlschan->conn->bytes_xmitted = 128; tlschan->conn->bytes_xmitted_by_tls = 64; r = ch->get_overhead_estimate(ch); @@ -273,10 +273,10 @@ tlschan_connection_or_connect_mock(const tor_addr_t *addr, or_connection_t *result = NULL; (void) ed_id; // XXXX Not yet used. - tt_assert(addr != NULL); - tt_assert(port != 0); - tt_assert(digest != NULL); - tt_assert(tlschan != NULL); + tt_ptr_op(addr, OP_NE, NULL); + tt_uint_op(port, OP_NE, 0); + tt_ptr_op(digest, OP_NE, NULL); + tt_ptr_op(tlschan, OP_NE, NULL); /* Make a fake orconn */ result = tor_malloc_zero(sizeof(*result)); @@ -301,11 +301,11 @@ tlschan_fake_close_method(channel_t *chan) { channel_tls_t *tlschan = NULL; - tt_assert(chan != NULL); - tt_int_op(chan->magic, ==, TLS_CHAN_MAGIC); + tt_ptr_op(chan, OP_NE, NULL); + tt_int_op(chan->magic, OP_EQ, TLS_CHAN_MAGIC); tlschan = BASE_CHAN_TO_TLS(chan); - tt_assert(tlschan != NULL); + tt_ptr_op(tlschan, OP_NE, NULL); /* Just free the fake orconn */ tor_free(tlschan->conn->base_.address); @@ -320,7 +320,7 @@ tlschan_fake_close_method(channel_t *chan) static int tlschan_is_local_addr_mock(const tor_addr_t *addr) { - tt_assert(addr != NULL); + tt_ptr_op(addr, OP_NE, NULL); done: return tlschan_local; diff --git a/src/test/test_checkdir.c b/src/test/test_checkdir.c index 38f3360b61..bf6a8376b3 100644 --- a/src/test/test_checkdir.c +++ b/src/test/test_checkdir.c @@ -20,7 +20,7 @@ #define umask(mask) ((void)0) #else #define tt_int_op_nowin(a,op,b) tt_int_op((a),op,(b)) -#endif +#endif /* defined(_WIN32) */ /** Run unit tests for private dir permission enforcement logic. */ static void diff --git a/src/test/test_circuitlist.c b/src/test/test_circuitlist.c index 344ab27921..f622704ec5 100644 --- a/src/test/test_circuitlist.c +++ b/src/test/test_circuitlist.c @@ -180,6 +180,7 @@ static void test_rend_token_maps(void *arg) { or_circuit_t *c1, *c2, *c3, *c4; + origin_circuit_t *c5; const uint8_t tok1[REND_TOKEN_LEN] = "The cat can't tell y"; const uint8_t tok2[REND_TOKEN_LEN] = "ou its name, and it "; const uint8_t tok3[REND_TOKEN_LEN] = "doesn't really care."; @@ -194,6 +195,7 @@ test_rend_token_maps(void *arg) c2 = or_circuit_new(0, NULL); c3 = or_circuit_new(0, NULL); c4 = or_circuit_new(0, NULL); + c5 = origin_circuit_new(); /* Make sure we really filled up the tok* variables */ tt_int_op(tok1[REND_TOKEN_LEN-1], OP_EQ, 'y'); @@ -264,6 +266,13 @@ test_rend_token_maps(void *arg) tt_ptr_op(TO_CIRCUIT(c4)->hs_token, OP_EQ, NULL); tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2_relay_side(tok3)); + /* Now let's do a check for the client-side rend circuitmap */ + c5->base_.purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND; + hs_circuitmap_register_rend_circ_client_side(c5, tok1); + + tt_ptr_op(c5, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok1)); + tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ_client_side(tok2)); + done: if (c1) circuit_free(TO_CIRCUIT(c1)); @@ -273,6 +282,8 @@ test_rend_token_maps(void *arg) circuit_free(TO_CIRCUIT(c3)); if (c4) circuit_free(TO_CIRCUIT(c4)); + if (c5) + circuit_free(TO_CIRCUIT(c5)); } static void diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index 99abdbc685..854f725054 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -49,8 +49,8 @@ test_cmux_destroy_cell_queue(void *arg) ch->wide_circ_ids = 1; circ = circuitmux_get_first_active_circuit(cmux, &cq); - tt_assert(!circ); - tt_assert(!cq); + tt_ptr_op(circ, OP_EQ, NULL); + tt_ptr_op(cq, OP_EQ, NULL); circuitmux_append_destroy_cell(ch, cmux, 100, 10); circuitmux_append_destroy_cell(ch, cmux, 190, 6); @@ -59,7 +59,7 @@ test_cmux_destroy_cell_queue(void *arg) tt_int_op(circuitmux_num_cells(cmux), OP_EQ, 3); circ = circuitmux_get_first_active_circuit(cmux, &cq); - tt_assert(!circ); + tt_ptr_op(circ, OP_EQ, NULL); tt_assert(cq); tt_int_op(cq->n, OP_EQ, 3); diff --git a/src/test/test_circuituse.c b/src/test/test_circuituse.c index 5cc9fe571e..df1b43807f 100644 --- a/src/test/test_circuituse.c +++ b/src/test/test_circuituse.c @@ -22,7 +22,7 @@ test_circuit_is_available_for_use_ret_false_when_marked_for_close(void *arg) circuit_t *circ = tor_malloc(sizeof(circuit_t)); circ->marked_for_close = 1; - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: tor_free(circ); @@ -36,7 +36,7 @@ test_circuit_is_available_for_use_ret_false_when_timestamp_dirty(void *arg) circuit_t *circ = tor_malloc(sizeof(circuit_t)); circ->timestamp_dirty = 1; - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: tor_free(circ); @@ -50,7 +50,7 @@ test_circuit_is_available_for_use_ret_false_for_non_general_purpose(void *arg) circuit_t *circ = tor_malloc(sizeof(circuit_t)); circ->purpose = CIRCUIT_PURPOSE_REND_POINT_WAITING; - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: tor_free(circ); @@ -64,7 +64,7 @@ test_circuit_is_available_for_use_ret_false_for_non_general_origin(void *arg) circuit_t *circ = tor_malloc(sizeof(circuit_t)); circ->purpose = CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT; - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: tor_free(circ); @@ -78,7 +78,7 @@ test_circuit_is_available_for_use_ret_false_for_non_origin_purpose(void *arg) circuit_t *circ = tor_malloc(sizeof(circuit_t)); circ->purpose = CIRCUIT_PURPOSE_OR; - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: tor_free(circ); @@ -92,7 +92,7 @@ test_circuit_is_available_for_use_ret_false_unusable_for_new_conns(void *arg) circuit_t *circ = dummy_origin_circuit_new(30); mark_circuit_unusable_for_new_conns(TO_ORIGIN_CIRCUIT(circ)); - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: circuit_free(circ); @@ -108,7 +108,7 @@ test_circuit_is_available_for_use_returns_false_for_onehop_tunnel(void *arg) oc->build_state = tor_malloc_zero(sizeof(cpath_build_state_t)); oc->build_state->onehop_tunnel = 1; - tt_int_op(0, ==, circuit_is_available_for_use(circ)); + tt_int_op(0, OP_EQ, circuit_is_available_for_use(circ)); done: circuit_free(circ); @@ -124,7 +124,7 @@ test_circuit_is_available_for_use_returns_true_for_clean_circuit(void *arg) oc->build_state = tor_malloc_zero(sizeof(cpath_build_state_t)); oc->build_state->onehop_tunnel = 0; - tt_int_op(1, ==, circuit_is_available_for_use(circ)); + tt_int_op(1, OP_EQ, circuit_is_available_for_use(circ)); done: circuit_free(circ); @@ -165,7 +165,8 @@ test_needs_exit_circuits_ret_false_for_predicted_ports_and_path(void *arg) int needs_capacity = 0; time_t now = time(NULL); - tt_int_op(0, ==, needs_exit_circuits(now, &needs_uptime, &needs_capacity)); + tt_int_op(0, OP_EQ, + needs_exit_circuits(now, &needs_uptime, &needs_capacity)); done: UNMOCK(circuit_all_predicted_ports_handled); @@ -183,7 +184,8 @@ test_needs_exit_circuits_ret_false_for_non_exit_consensus_path(void *arg) MOCK(router_have_consensus_path, mock_router_have_unknown_consensus_path); time_t now = time(NULL); - tt_int_op(0, ==, needs_exit_circuits(now, &needs_uptime, &needs_capacity)); + tt_int_op(0, OP_EQ, + needs_exit_circuits(now, &needs_uptime, &needs_capacity)); done: UNMOCK(circuit_all_predicted_ports_handled); @@ -202,7 +204,8 @@ test_needs_exit_circuits_ret_true_for_predicted_ports_and_path(void *arg) MOCK(router_have_consensus_path, mock_router_have_exit_consensus_path); time_t now = time(NULL); - tt_int_op(1, ==, needs_exit_circuits(now, &needs_uptime, &needs_capacity)); + tt_int_op(1, OP_EQ, + needs_exit_circuits(now, &needs_uptime, &needs_capacity)); done: UNMOCK(circuit_all_predicted_ports_handled); @@ -214,7 +217,7 @@ test_needs_circuits_for_build_ret_false_consensus_path_unknown(void *arg) { (void)arg; MOCK(router_have_consensus_path, mock_router_have_unknown_consensus_path); - tt_int_op(0, ==, needs_circuits_for_build(0)); + tt_int_op(0, OP_EQ, needs_circuits_for_build(0)); done: ; } @@ -223,7 +226,7 @@ test_needs_circuits_for_build_ret_false_if_num_less_than_max(void *arg) { (void)arg; MOCK(router_have_consensus_path, mock_router_have_exit_consensus_path); - tt_int_op(0, ==, needs_circuits_for_build(13)); + tt_int_op(0, OP_EQ, needs_circuits_for_build(13)); done: UNMOCK(router_have_consensus_path); } @@ -233,7 +236,7 @@ test_needs_circuits_for_build_returns_true_when_more_are_needed(void *arg) { (void)arg; MOCK(router_have_consensus_path, mock_router_have_exit_consensus_path); - tt_int_op(1, ==, needs_circuits_for_build(0)); + tt_int_op(1, OP_EQ, needs_circuits_for_build(0)); done: UNMOCK(router_have_consensus_path); } diff --git a/src/test/test_config.c b/src/test/test_config.c index beaab00f73..e7380c1d14 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -279,7 +279,7 @@ test_config_check_or_create_data_subdir(void *arg) tt_assert(!is_private_dir(subpath)); tt_assert(!check_or_create_data_subdir(subdir)); tt_assert(is_private_dir(subpath)); -#endif +#endif /* !defined (_WIN32) */ done: rmdir(subpath); @@ -368,12 +368,12 @@ good_bridge_line_test(const char *string, const char *test_addrport, /* If we were asked to validate a digest, but we did not get a digest after parsing, we failed. */ if (test_digest && tor_digest_is_zero(bridge_line->digest)) - tt_assert(0); + tt_abort(); /* If we were not asked to validate a digest, and we got a digest after parsing, we failed again. */ if (!test_digest && !tor_digest_is_zero(bridge_line->digest)) - tt_assert(0); + tt_abort(); /* If we were asked to validate a digest, and we got a digest after parsing, make sure it's correct. */ @@ -387,17 +387,17 @@ good_bridge_line_test(const char *string, const char *test_addrport, /* If we were asked to validate a transport name, make sure tha it matches with the transport name that was parsed. */ if (test_transport && !bridge_line->transport_name) - tt_assert(0); + tt_abort(); if (!test_transport && bridge_line->transport_name) - tt_assert(0); + tt_abort(); if (test_transport) tt_str_op(test_transport,OP_EQ, bridge_line->transport_name); /* Validate the SOCKS argument smartlist. */ if (test_socks_args && !bridge_line->socks_args) - tt_assert(0); + tt_abort(); if (!test_socks_args && bridge_line->socks_args) - tt_assert(0); + tt_abort(); if (test_socks_args) tt_assert(smartlist_strings_eq(test_socks_args, bridge_line->socks_args)); @@ -415,7 +415,7 @@ bad_bridge_line_test(const char *string) bridge_line_t *bridge_line = parse_bridge_line(string); if (bridge_line) TT_FAIL(("%s was supposed to fail, but it didn't.", string)); - tt_assert(!bridge_line); + tt_ptr_op(bridge_line, OP_EQ, NULL); done: bridge_line_free(bridge_line); @@ -523,18 +523,18 @@ test_config_parse_transport_options_line(void *arg) { /* too small line */ options_sl = get_options_from_transport_options_line("valley", NULL); - tt_assert(!options_sl); + tt_ptr_op(options_sl, OP_EQ, NULL); } { /* no k=v values */ options_sl = get_options_from_transport_options_line("hit it!", NULL); - tt_assert(!options_sl); + tt_ptr_op(options_sl, OP_EQ, NULL); } { /* correct line, but wrong transport specified */ options_sl = get_options_from_transport_options_line("trebuchet k=v", "rook"); - tt_assert(!options_sl); + tt_ptr_op(options_sl, OP_EQ, NULL); } { /* correct -- no transport specified */ @@ -658,84 +658,85 @@ test_config_parse_transport_plugin_line(void *arg) /* Bad transport lines - too short */ r = parse_transport_line(options, "bad", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "bad", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "bad bad", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "bad bad", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); /* Test transport list parsing */ r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 1, 0); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 1, 1); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); r = parse_transport_line(options, "transport_1,transport_2 exec /usr/bin/fake-transport", 1, 0); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); r = parse_transport_line(options, "transport_1,transport_2 exec /usr/bin/fake-transport", 1, 1); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* Bad transport identifiers */ r = parse_transport_line(options, "transport_* exec /usr/bin/fake-transport", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "transport_* exec /usr/bin/fake-transport", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); /* Check SOCKS cases for client transport */ r = parse_transport_line(options, "transport_1 socks4 1.2.3.4:567", 1, 0); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); r = parse_transport_line(options, "transport_1 socks5 1.2.3.4:567", 1, 0); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* Proxy case for server transport */ r = parse_transport_line(options, "transport_1 proxy 1.2.3.4:567", 1, 1); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* Multiple-transport error exit */ r = parse_transport_line(options, "transport_1,transport_2 socks5 1.2.3.4:567", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "transport_1,transport_2 proxy 1.2.3.4:567", 1, 1); + tt_int_op(r, OP_LT, 0); /* No port error exit */ r = parse_transport_line(options, "transport_1 socks5 1.2.3.4", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "transport_1 proxy 1.2.3.4", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); /* Unparsable address error exit */ r = parse_transport_line(options, "transport_1 socks5 1.2.3:6x7", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "transport_1 proxy 1.2.3:6x7", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); /* "Strange {Client|Server}TransportPlugin field" error exit */ r = parse_transport_line(options, "transport_1 foo bar", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "transport_1 foo bar", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); /* No sandbox mode error exit */ tmp = options->Sandbox; options->Sandbox = 1; r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 1, 0); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 1, 1); - tt_assert(r < 0); + tt_int_op(r, OP_LT, 0); options->Sandbox = tmp; /* @@ -747,7 +748,7 @@ test_config_parse_transport_plugin_line(void *arg) pt_kickstart_proxy_mock_call_count; r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 0, 1); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_assert(pt_kickstart_proxy_mock_call_count == old_pt_kickstart_proxy_mock_call_count + 1); UNMOCK(pt_kickstart_proxy); @@ -755,7 +756,7 @@ test_config_parse_transport_plugin_line(void *arg) /* This one hits a log line in the !validate_only case only */ r = parse_transport_line(options, "transport_1 proxy 1.2.3.4:567", 0, 1); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* Check mocked client transport cases */ MOCK(pt_kickstart_proxy, pt_kickstart_proxy_mock); @@ -773,7 +774,7 @@ test_config_parse_transport_plugin_line(void *arg) r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 0, 0); /* Should have succeeded */ - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* transport_is_needed() should have been called */ tt_assert(transport_is_needed_mock_call_count == old_transport_is_needed_mock_call_count + 1); @@ -797,7 +798,7 @@ test_config_parse_transport_plugin_line(void *arg) r = parse_transport_line(options, "transport_1 exec /usr/bin/fake-transport", 0, 0); /* Should have succeeded */ - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* * transport_is_needed() and pt_kickstart_proxy() should have been * called. @@ -821,7 +822,7 @@ test_config_parse_transport_plugin_line(void *arg) r = parse_transport_line(options, "transport_1 socks5 1.2.3.4:567", 0, 0); /* Should have succeeded */ - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); /* * transport_is_needed() and transport_add_from_config() should have * been called. @@ -1139,7 +1140,7 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(retval == 0); - tt_want_str_op(method_used,==,"CONFIGURED"); + tt_want_str_op(method_used,OP_EQ,"CONFIGURED"); tt_want(hostname_out == NULL); tt_assert(resolved_addr == 0x80348069); @@ -1164,8 +1165,8 @@ test_config_resolve_my_address(void *arg) tt_want(retval == 0); tt_want(n_hostname_01010101 == prev_n_hostname_01010101 + 1); - tt_want_str_op(method_used,==,"RESOLVED"); - tt_want_str_op(hostname_out,==,"www.torproject.org"); + tt_want_str_op(method_used,OP_EQ,"RESOLVED"); + tt_want_str_op(hostname_out,OP_EQ,"www.torproject.org"); tt_assert(resolved_addr == 0x01010101); UNMOCK(tor_lookup_hostname); @@ -1196,8 +1197,8 @@ test_config_resolve_my_address(void *arg) tt_want(retval == 0); tt_want(n_gethostname_replacement == prev_n_gethostname_replacement + 1); tt_want(n_hostname_01010101 == prev_n_hostname_01010101 + 1); - tt_want_str_op(method_used,==,"GETHOSTNAME"); - tt_want_str_op(hostname_out,==,"onionrouter!"); + tt_want_str_op(method_used,OP_EQ,"GETHOSTNAME"); + tt_want_str_op(hostname_out,OP_EQ,"onionrouter!"); tt_assert(resolved_addr == 0x01010101); UNMOCK(tor_gethostname); @@ -1219,7 +1220,7 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(resolved_addr == 0); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); tor_free(options->Address); tor_free(hostname_out); @@ -1241,7 +1242,7 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(n_hostname_failure == prev_n_hostname_failure + 1); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); UNMOCK(tor_lookup_hostname); @@ -1262,7 +1263,7 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(n_gethostname_failure == prev_n_gethostname_failure + 1); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); UNMOCK(tor_gethostname); tor_free(hostname_out); @@ -1285,11 +1286,11 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(retval == 0); - tt_want_int_op(n_gethostname_replacement, ==, + tt_want_int_op(n_gethostname_replacement, OP_EQ, prev_n_gethostname_replacement + 1); - tt_want_int_op(n_get_interface_address, ==, + tt_want_int_op(n_get_interface_address, OP_EQ, prev_n_get_interface_address + 1); - tt_want_str_op(method_used,==,"INTERFACE"); + tt_want_str_op(method_used,OP_EQ,"INTERFACE"); tt_want(hostname_out == NULL); tt_assert(resolved_addr == 0x08080808); @@ -1315,7 +1316,7 @@ test_config_resolve_my_address(void *arg) prev_n_get_interface_address_failure + 1); tt_want(n_gethostname_replacement == prev_n_gethostname_replacement + 1); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); UNMOCK(get_interface_address); tor_free(hostname_out); @@ -1345,7 +1346,7 @@ test_config_resolve_my_address(void *arg) tt_want(n_hostname_failure == prev_n_hostname_failure + 1); tt_want(n_gethostname_replacement == prev_n_gethostname_replacement + 1); tt_want(retval == 0); - tt_want_str_op(method_used,==,"INTERFACE"); + tt_want_str_op(method_used,OP_EQ,"INTERFACE"); tt_assert(resolved_addr == 0x09090909); UNMOCK(tor_lookup_hostname); @@ -1375,7 +1376,7 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(n_hostname_failure == prev_n_hostname_failure + 1); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); UNMOCK(tor_gethostname); UNMOCK(tor_lookup_hostname); @@ -1419,9 +1420,9 @@ test_config_resolve_my_address(void *arg) tt_want(n_hostname_localhost == prev_n_hostname_localhost + 1); tt_want(n_get_interface_address6 == prev_n_get_interface_address6 + 1); - tt_str_op(method_used,==,"INTERFACE"); - tt_assert(!hostname_out); - tt_assert(retval == 0); + tt_str_op(method_used,OP_EQ,"INTERFACE"); + tt_ptr_op(hostname_out, OP_EQ, NULL); + tt_int_op(retval, OP_EQ, 0); /* * CASE 11b: @@ -1446,7 +1447,7 @@ test_config_resolve_my_address(void *arg) tt_want(n_get_interface_address6_failure == prev_n_get_interface_address6_failure + 1); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); UNMOCK(tor_gethostname); UNMOCK(tor_lookup_hostname); @@ -1475,7 +1476,7 @@ test_config_resolve_my_address(void *arg) &method_used,&hostname_out); tt_want(n_gethostname_localhost == prev_n_gethostname_localhost + 1); - tt_assert(retval == -1); + tt_int_op(retval, OP_EQ, -1); UNMOCK(tor_gethostname); @@ -1510,18 +1511,18 @@ test_config_adding_trusted_dir_server(void *arg) NULL, V3_DIRINFO, 1.0); tt_assert(ds); dir_server_add(ds); - tt_assert(get_n_authorities(V3_DIRINFO) == 1); - tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 1); + tt_int_op(get_n_authorities(V3_DIRINFO), OP_EQ, 1); + tt_int_op(smartlist_len(router_get_fallback_dir_servers()), OP_EQ, 1); /* create a trusted ds with an IPv6 address and port */ rv = tor_addr_port_parse(LOG_WARN, "[::1]:9061", &ipv6.addr, &ipv6.port, -1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, &ipv6, digest, NULL, V3_DIRINFO, 1.0); tt_assert(ds); dir_server_add(ds); - tt_assert(get_n_authorities(V3_DIRINFO) == 2); - tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 2); + tt_int_op(get_n_authorities(V3_DIRINFO), OP_EQ, 2); + tt_int_op(smartlist_len(router_get_fallback_dir_servers()), OP_EQ, 2); done: clear_dir_servers(); @@ -1543,21 +1544,21 @@ test_config_adding_fallback_dir_server(void *arg) routerlist_free_all(); rv = tor_addr_parse(&ipv4, "127.0.0.1"); - tt_assert(rv == AF_INET); + tt_int_op(rv, OP_EQ, AF_INET); /* create a trusted ds without an IPv6 address and port */ ds = fallback_dir_server_new(&ipv4, 9059, 9060, NULL, digest, 1.0); tt_assert(ds); dir_server_add(ds); - tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 1); + tt_int_op(smartlist_len(router_get_fallback_dir_servers()), OP_EQ, 1); /* create a trusted ds with an IPv6 address and port */ rv = tor_addr_port_parse(LOG_WARN, "[::1]:9061", &ipv6.addr, &ipv6.port, -1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); ds = fallback_dir_server_new(&ipv4, 9059, 9060, &ipv6, digest, 1.0); tt_assert(ds); dir_server_add(ds); - tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 2); + tt_int_op(smartlist_len(router_get_fallback_dir_servers()), OP_EQ, 2); done: clear_dir_servers(); @@ -1588,14 +1589,14 @@ test_config_parsing_trusted_dir_server(void *arg) rv = parse_dir_authority_line(TEST_DIR_AUTH_LINE_START TEST_DIR_AUTH_LINE_END, V3_DIRINFO, 1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); /* parse a trusted dir server with an IPv6 address and port */ rv = parse_dir_authority_line(TEST_DIR_AUTH_LINE_START TEST_DIR_AUTH_IPV6_FLAG TEST_DIR_AUTH_LINE_END, V3_DIRINFO, 1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); /* Since we are only validating, there is no cleanup. */ done: @@ -1623,13 +1624,13 @@ test_config_parsing_fallback_dir_server(void *arg) /* parse a trusted dir server without an IPv6 address and port */ rv = parse_dir_fallback_line(TEST_DIR_FALLBACK_LINE, 1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); /* parse a trusted dir server with an IPv6 address and port */ rv = parse_dir_fallback_line(TEST_DIR_FALLBACK_LINE TEST_DIR_FALLBACK_IPV6_FLAG, 1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); /* Since we are only validating, there is no cleanup. */ done: @@ -1649,8 +1650,8 @@ test_config_adding_default_trusted_dir_servers(void *arg) /* Assume we only have one bridge authority */ add_default_trusted_dir_authorities(BRIDGE_DIRINFO); - tt_assert(get_n_authorities(BRIDGE_DIRINFO) == 1); - tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 1); + tt_int_op(get_n_authorities(BRIDGE_DIRINFO), OP_EQ, 1); + tt_int_op(smartlist_len(router_get_fallback_dir_servers()), OP_EQ, 1); /* Assume we have eight V3 authorities */ add_default_trusted_dir_authorities(V3_DIRINFO); @@ -1838,7 +1839,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 1); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -1857,7 +1858,7 @@ test_config_adding_dir_servers(void *arg) 1 : 0) ); /* If we have no default bridge authority, something has gone wrong */ - tt_assert(n_default_alt_bridge_authority >= 1); + tt_int_op(n_default_alt_bridge_authority, OP_GE, 1); /* Count v3 Authorities */ SMARTLIST_FOREACH(fallback_servers, @@ -1869,7 +1870,7 @@ test_config_adding_dir_servers(void *arg) 1 : 0) ); /* If we have no default authorities, something has gone really wrong */ - tt_assert(n_default_alt_dir_authority >= 1); + tt_int_op(n_default_alt_dir_authority, OP_GE, 1); /* Calculate Fallback Directory Count */ n_default_fallback_dir = (smartlist_len(fallback_servers) - @@ -1879,7 +1880,7 @@ test_config_adding_dir_servers(void *arg) * or some authorities aren't being added as fallback directories. * (networkstatus_consensus_can_use_extra_fallbacks depends on all * authorities being fallback directories.) */ - tt_assert(n_default_fallback_dir >= 0); + tt_int_op(n_default_fallback_dir, OP_GE, 0); } } @@ -1920,7 +1921,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -1929,7 +1930,7 @@ test_config_adding_dir_servers(void *arg) /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); /* D0, (No B1), (No A2) */ - tt_assert(smartlist_len(dir_servers) == 1); + tt_int_op(smartlist_len(dir_servers), OP_EQ, 1); /* DirAuthority - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -1941,7 +1942,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 1); + tt_int_op(found_D0, OP_EQ, 1); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -1953,7 +1954,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -1965,14 +1966,14 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); } { /* fallback_dir_servers */ const smartlist_t *fallback_servers = router_get_fallback_dir_servers(); /* D0, (No B1), (No A2), Custom Fallback */ - tt_assert(smartlist_len(fallback_servers) == 2); + tt_int_op(smartlist_len(fallback_servers), OP_EQ, 2); /* DirAuthority - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -1984,7 +1985,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 1); + tt_int_op(found_D0, OP_EQ, 1); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -1996,7 +1997,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2008,7 +2009,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* Custom FallbackDir - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2020,7 +2021,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 1); + tt_int_op(found_non_default_fallback, OP_EQ, 1); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2032,7 +2033,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); } } @@ -2061,7 +2062,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we just have the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); @@ -2070,7 +2071,7 @@ test_config_adding_dir_servers(void *arg) /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); /* D0, (No B1), (No A2) */ - tt_assert(smartlist_len(dir_servers) == 1); + tt_int_op(smartlist_len(dir_servers), OP_EQ, 1); /* DirAuthority - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -2082,7 +2083,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 1); + tt_int_op(found_D0, OP_EQ, 1); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2094,7 +2095,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2106,14 +2107,14 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); } { /* fallback_dir_servers */ const smartlist_t *fallback_servers = router_get_fallback_dir_servers(); /* D0, (No B1), (No A2), (No Fallback) */ - tt_assert(smartlist_len(fallback_servers) == 1); + tt_int_op(smartlist_len(fallback_servers), OP_EQ, 1); /* DirAuthority - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -2125,7 +2126,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 1); + tt_int_op(found_D0, OP_EQ, 1); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2137,7 +2138,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2149,7 +2150,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* (No Custom FallbackDir) - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2161,7 +2162,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 0); + tt_int_op(found_non_default_fallback, OP_EQ, 0); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2173,7 +2174,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); } } @@ -2202,7 +2203,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -2211,7 +2212,7 @@ test_config_adding_dir_servers(void *arg) /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); /* (No D0), B1, A2 */ - tt_assert(smartlist_len(dir_servers) == 2); + tt_int_op(smartlist_len(dir_servers), OP_EQ, 2); /* (No DirAuthority) - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -2223,7 +2224,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2235,7 +2236,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2247,14 +2248,14 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); } { /* fallback_dir_servers */ const smartlist_t *fallback_servers = router_get_fallback_dir_servers(); /* (No D0), B1, A2, Custom Fallback */ - tt_assert(smartlist_len(fallback_servers) == 3); + tt_int_op(smartlist_len(fallback_servers), OP_EQ, 3); /* (No DirAuthority) - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -2266,7 +2267,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2278,7 +2279,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2290,7 +2291,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); /* Custom FallbackDir - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2302,7 +2303,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 1); + tt_int_op(found_non_default_fallback, OP_EQ, 1); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2314,7 +2315,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); } } @@ -2344,7 +2345,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); @@ -2353,7 +2354,7 @@ test_config_adding_dir_servers(void *arg) /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); /* (No D0), B1, A2 */ - tt_assert(smartlist_len(dir_servers) == 2); + tt_int_op(smartlist_len(dir_servers), OP_EQ, 2); /* (No DirAuthority) - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -2365,7 +2366,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2377,7 +2378,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2389,14 +2390,14 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); } { /* fallback_dir_servers */ const smartlist_t *fallback_servers = router_get_fallback_dir_servers(); /* (No D0), B1, A2, (No Fallback) */ - tt_assert(smartlist_len(fallback_servers) == 2); + tt_int_op(smartlist_len(fallback_servers), OP_EQ, 2); /* (No DirAuthority) - D0 - dir_port: 60090 */ int found_D0 = 0; @@ -2408,7 +2409,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2420,7 +2421,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2432,7 +2433,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); /* (No Custom FallbackDir) - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2444,7 +2445,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 0); + tt_int_op(found_non_default_fallback, OP_EQ, 0); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2456,7 +2457,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); } } @@ -2496,7 +2497,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -2517,7 +2518,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2529,7 +2530,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2541,7 +2542,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default v3 non-Bridge directory authorities, so let's assume that @@ -2567,7 +2568,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2579,7 +2580,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2591,7 +2592,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* Custom FallbackDir - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2603,7 +2604,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 1); + tt_int_op(found_non_default_fallback, OP_EQ, 1); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2615,7 +2616,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default v3 non-Bridge directory authorities, so let's assume that @@ -2650,7 +2651,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 1); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -2671,7 +2672,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2683,7 +2684,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2695,7 +2696,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default v3 non-Bridge directory authorities, so let's assume that @@ -2721,7 +2722,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* AlternateBridgeAuthority - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2733,7 +2734,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 1); + tt_int_op(found_B1, OP_EQ, 1); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2745,7 +2746,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* (No Custom FallbackDir) - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2757,7 +2758,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 0); + tt_int_op(found_non_default_fallback, OP_EQ, 0); /* Default FallbackDir - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2769,7 +2770,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 1); + tt_int_op(found_default_fallback, OP_EQ, 1); /* There's no easy way of checking that we have included all the * default v3 non-Bridge directory authorities, so let's assume that @@ -2813,7 +2814,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -2835,7 +2836,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2847,7 +2848,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2859,7 +2860,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); /* There's no easy way of checking that we have included all the * default Bridge authorities (except for hard-coding tonga's details), @@ -2886,7 +2887,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -2898,7 +2899,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -2910,7 +2911,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); /* Custom FallbackDir - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -2922,7 +2923,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 1); + tt_int_op(found_non_default_fallback, OP_EQ, 1); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -2934,7 +2935,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default Bridge authorities (except for hard-coding tonga's details), @@ -2970,7 +2971,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we just have the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); @@ -2993,7 +2994,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -3005,7 +3006,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -3017,7 +3018,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); /* There's no easy way of checking that we have included all the * default Bridge authorities (except for hard-coding tonga's details), @@ -3044,7 +3045,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -3056,7 +3057,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* AlternateDirAuthority - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -3068,7 +3069,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 1); + tt_int_op(found_A2, OP_EQ, 1); /* (No Custom FallbackDir) - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -3080,7 +3081,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 0); + tt_int_op(found_non_default_fallback, OP_EQ, 0); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -3092,7 +3093,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default Bridge authorities (except for hard-coding tonga's details), @@ -3136,7 +3137,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must not have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 0); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -3158,7 +3159,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -3170,7 +3171,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -3182,7 +3183,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default Bridge & V3 Directory authorities, so let's assume that @@ -3209,7 +3210,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -3221,7 +3222,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -3233,7 +3234,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* Custom FallbackDir - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -3245,7 +3246,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 1); + tt_int_op(found_non_default_fallback, OP_EQ, 1); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -3257,7 +3258,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 0); + tt_int_op(found_default_fallback, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default Bridge & V3 Directory authorities, so let's assume that @@ -3299,7 +3300,7 @@ test_config_adding_dir_servers(void *arg) /* check outcome */ /* we must have added the default fallback dirs */ - tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + tt_int_op(n_add_default_fallback_dir_servers_known_default, OP_EQ, 1); /* we have more fallbacks than just the authorities */ tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); @@ -3321,7 +3322,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -3333,7 +3334,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -3345,7 +3346,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* There's no easy way of checking that we have included all the * default Bridge & V3 Directory authorities, so let's assume that @@ -3372,7 +3373,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60090 ? 1 : 0) ); - tt_assert(found_D0 == 0); + tt_int_op(found_D0, OP_EQ, 0); /* (No AlternateBridgeAuthority) - B1 - dir_port: 60091 */ int found_B1 = 0; @@ -3384,7 +3385,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60091 ? 1 : 0) ); - tt_assert(found_B1 == 0); + tt_int_op(found_B1, OP_EQ, 0); /* (No AlternateDirAuthority) - A2 - dir_port: 60092 */ int found_A2 = 0; @@ -3396,7 +3397,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60092 ? 1 : 0) ); - tt_assert(found_A2 == 0); + tt_int_op(found_A2, OP_EQ, 0); /* Custom FallbackDir - No Nickname - dir_port: 60093 */ int found_non_default_fallback = 0; @@ -3408,7 +3409,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60093 ? 1 : 0) ); - tt_assert(found_non_default_fallback == 0); + tt_int_op(found_non_default_fallback, OP_EQ, 0); /* (No Default FallbackDir) - No Nickname - dir_port: 60099 */ int found_default_fallback = 0; @@ -3420,7 +3421,7 @@ test_config_adding_dir_servers(void *arg) (ds->dir_port == 60099 ? 1 : 0) ); - tt_assert(found_default_fallback == 1); + tt_int_op(found_default_fallback, OP_EQ, 1); /* There's no easy way of checking that we have included all the * default Bridge & V3 Directory authorities, and the default @@ -3477,7 +3478,7 @@ test_config_default_dir_servers(void *arg) opts = NULL; /* assume a release will never go out with less than 7 authorities */ - tt_assert(trusted_count >= 7); + tt_int_op(trusted_count, OP_GE, 7); /* if we disable the default fallbacks, there must not be any extra */ tt_assert(fallback_count == trusted_count); @@ -3490,7 +3491,7 @@ test_config_default_dir_servers(void *arg) opts = NULL; /* assume a release will never go out with less than 7 authorities */ - tt_assert(trusted_count >= 7); + tt_int_op(trusted_count, OP_GE, 7); /* XX/teor - allow for default fallbacks to be added without breaking * the unit tests. Set a minimum fallback count once the list is stable. */ tt_assert(fallback_count >= trusted_count); @@ -3559,18 +3560,18 @@ test_config_directory_fetch(void *arg) options->ClientOnly = 1; tt_assert(server_mode(options) == 0); tt_assert(public_server_mode(options) == 0); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 1); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 1); /* Bridge Clients can use multiple directory mirrors for bootstrap */ memset(options, 0, sizeof(or_options_t)); options->UseBridges = 1; tt_assert(server_mode(options) == 0); tt_assert(public_server_mode(options) == 0); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 1); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 1); /* Bridge Relays (Bridges) must act like clients, and use multiple * directory mirrors for bootstrap */ @@ -3579,9 +3580,9 @@ test_config_directory_fetch(void *arg) options->ORPort_set = 1; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 0); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 1); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 1); /* Clients set to FetchDirInfoEarly must fetch it from the authorities, * but can use multiple authorities for bootstrap */ @@ -3589,9 +3590,9 @@ test_config_directory_fetch(void *arg) options->FetchDirInfoEarly = 1; tt_assert(server_mode(options) == 0); tt_assert(public_server_mode(options) == 0); - tt_assert(directory_fetches_from_authorities(options) == 1); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 1); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 1); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 1); /* OR servers only fetch the consensus from the authorities when they don't * know their own address, but never use multiple directories for bootstrap @@ -3602,16 +3603,16 @@ test_config_directory_fetch(void *arg) mock_router_pick_published_address_result = -1; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 1); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 1); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); mock_router_pick_published_address_result = 0; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); /* Exit OR servers only fetch the consensus from the authorities when they * refuse unknown exits, but never use multiple directories for bootstrap @@ -3629,17 +3630,17 @@ test_config_directory_fetch(void *arg) options->RefuseUnknownExits = 1; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 1); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 1); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); options->RefuseUnknownExits = 0; mock_router_pick_published_address_result = 0; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); /* Dir servers fetch the consensus from the authorities, unless they are not * advertising themselves (hibernating) or have no routerinfo or are not @@ -3658,26 +3659,26 @@ test_config_directory_fetch(void *arg) mock_router_get_my_routerinfo_result = &routerinfo; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 1); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 1); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); mock_advertised_server_mode_result = 0; routerinfo.dir_port = 1; mock_router_get_my_routerinfo_result = &routerinfo; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); mock_advertised_server_mode_result = 1; mock_router_get_my_routerinfo_result = NULL; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); mock_advertised_server_mode_result = 1; routerinfo.dir_port = 0; @@ -3685,9 +3686,9 @@ test_config_directory_fetch(void *arg) mock_router_get_my_routerinfo_result = &routerinfo; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 0); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 0); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); mock_advertised_server_mode_result = 1; routerinfo.dir_port = 1; @@ -3695,9 +3696,9 @@ test_config_directory_fetch(void *arg) mock_router_get_my_routerinfo_result = &routerinfo; tt_assert(server_mode(options) == 1); tt_assert(public_server_mode(options) == 1); - tt_assert(directory_fetches_from_authorities(options) == 1); - tt_assert(networkstatus_consensus_can_use_multiple_directories(options) - == 0); + tt_int_op(directory_fetches_from_authorities(options), OP_EQ, 1); + tt_int_op(networkstatus_consensus_can_use_multiple_directories(options), + OP_EQ, 0); done: tor_free(options); @@ -3744,119 +3745,119 @@ test_config_port_cfg_line_extract_addrport(void *arg) tt_int_op(port_cfg_line_extract_addrport("", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "");; + tt_str_op(a, OP_EQ, ""); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("hello", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "hello");; + tt_str_op(a, OP_EQ, "hello"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport(" flipperwalt gersplut", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "flipperwalt");; + tt_str_op(a, OP_EQ, "flipperwalt"); tt_str_op(rest, OP_EQ, "gersplut"); tor_free(a); tt_int_op(port_cfg_line_extract_addrport(" flipperwalt \t gersplut", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "flipperwalt");; + tt_str_op(a, OP_EQ, "flipperwalt"); tt_str_op(rest, OP_EQ, "gersplut"); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("flipperwalt \t gersplut", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "flipperwalt");; + tt_str_op(a, OP_EQ, "flipperwalt"); tt_str_op(rest, OP_EQ, "gersplut"); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:flipperwalt \t gersplut", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "flipperwalt");; + tt_str_op(a, OP_EQ, "flipperwalt"); tt_str_op(rest, OP_EQ, "gersplut"); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("lolol", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:lolol", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:lolol ", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport(" unix:lolol", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("foobar:lolol", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, "foobar:lolol");; + tt_str_op(a, OP_EQ, "foobar:lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport(":lolol", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 0); - tt_str_op(a, OP_EQ, ":lolol");; + tt_str_op(a, OP_EQ, ":lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:\"lolol\"", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:\"lolol\" ", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, ""); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:\"lolol\" foo ", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lolol");; + tt_str_op(a, OP_EQ, "lolol"); tt_str_op(rest, OP_EQ, "foo "); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:\"lol ol\" foo ", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lol ol");; + tt_str_op(a, OP_EQ, "lol ol"); tt_str_op(rest, OP_EQ, "foo "); tor_free(a); tt_int_op(port_cfg_line_extract_addrport("unix:\"lol\\\" ol\" foo ", &a, &unixy, &rest), OP_EQ, 0); tt_int_op(unixy, OP_EQ, 1); - tt_str_op(a, OP_EQ, "lol\" ol");; + tt_str_op(a, OP_EQ, "lol\" ol"); tt_str_op(rest, OP_EQ, "foo "); tor_free(a); @@ -4002,9 +4003,9 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(port_cfg->entry_cfg.dns_request, OP_EQ, 1); tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 1); tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1); - tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1); + tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.prefer_ipv6_virtaddr, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ // Test failure if we have no ipv4 and no ipv6 and no onion (DNS only) config_free_lines(config_port_invalid); config_port_invalid = NULL; @@ -4076,7 +4077,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ // Test success with quoted unix: address. config_free_lines(config_port_valid); config_port_valid = NULL; @@ -4098,7 +4099,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ // Test failure with broken quoted unix: address. config_free_lines(config_port_valid); config_port_valid = NULL; @@ -4143,7 +4144,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.onion_traffic, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ // Test success with no ipv4 but take ipv6 config_free_lines(config_port_valid); config_port_valid = NULL; @@ -4162,7 +4163,7 @@ test_config_parse_port_config__ports__ports_given(void *data) port_cfg = (port_cfg_t *)smartlist_get(slout, 0); tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ // Test success with both ipv4 and ipv6 config_free_lines(config_port_valid); config_port_valid = NULL; @@ -4181,7 +4182,7 @@ test_config_parse_port_config__ports__ports_given(void *data) port_cfg = (port_cfg_t *)smartlist_get(slout, 0); tt_int_op(port_cfg->entry_cfg.ipv4_traffic, OP_EQ, 1); tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ // Test failure if we specify world writable for an IP Port config_free_lines(config_port_invalid); config_port_invalid = NULL; @@ -4345,7 +4346,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(ret, OP_EQ, 0); tt_int_op(smartlist_len(slout), OP_EQ, 1); port_cfg = (port_cfg_t *)smartlist_get(slout, 0); - tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 1); + tt_int_op(port_cfg->entry_cfg.cache_ipv4_answers, OP_EQ, 0); tt_int_op(port_cfg->entry_cfg.cache_ipv6_answers, OP_EQ, 1); // Test success with no cache ipv4 DNS @@ -4645,7 +4646,7 @@ test_config_parse_port_config__ports__ports_given(void *data) tt_int_op(smartlist_len(slout), OP_EQ, 1); port_cfg = (port_cfg_t *)smartlist_get(slout, 0); tt_int_op(port_cfg->is_group_writable, OP_EQ, 1); -#endif +#endif /* defined(_WIN32) */ done: if (slout) @@ -4816,6 +4817,7 @@ test_config_include_limit(void *data) (void)data; config_line_t *result = NULL; + char *torrc_path = NULL; char *dir = tor_strdup(get_fname("test_include_limit")); tt_ptr_op(dir, OP_NE, NULL); @@ -4825,8 +4827,7 @@ test_config_include_limit(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char torrc_path[PATH_MAX+1]; - tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir); + tor_asprintf(&torrc_path, "%s"PATH_SEPARATOR"torrc", dir); char torrc_contents[1000]; tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", torrc_path); @@ -4837,6 +4838,7 @@ test_config_include_limit(void *data) done: config_free_lines(result); + tor_free(torrc_path); tor_free(dir); } @@ -4847,6 +4849,7 @@ test_config_include_does_not_exist(void *data) config_line_t *result = NULL; char *dir = tor_strdup(get_fname("test_include_does_not_exist")); + char *missing_path = NULL; tt_ptr_op(dir, OP_NE, NULL); #ifdef _WIN32 @@ -4855,9 +4858,7 @@ test_config_include_does_not_exist(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char missing_path[PATH_MAX+1]; - tor_snprintf(missing_path, sizeof(missing_path), "%s"PATH_SEPARATOR"missing", - dir); + tor_asprintf(&missing_path, "%s"PATH_SEPARATOR"missing", dir); char torrc_contents[1000]; tor_snprintf(torrc_contents, sizeof(torrc_contents), "%%include %s", missing_path); @@ -4868,6 +4869,7 @@ test_config_include_does_not_exist(void *data) done: config_free_lines(result); tor_free(dir); + tor_free(missing_path); } static void @@ -4877,6 +4879,7 @@ test_config_include_error_in_included_file(void *data) config_line_t *result = NULL; char *dir = tor_strdup(get_fname("test_error_in_included_file")); + char *invalid_path = NULL; tt_ptr_op(dir, OP_NE, NULL); #ifdef _WIN32 @@ -4885,9 +4888,7 @@ test_config_include_error_in_included_file(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char invalid_path[PATH_MAX+1]; - tor_snprintf(invalid_path, sizeof(invalid_path), "%s"PATH_SEPARATOR"invalid", - dir); + tor_asprintf(&invalid_path, "%s"PATH_SEPARATOR"invalid", dir); tt_int_op(write_str_to_file(invalid_path, "unclosed \"", 0), OP_EQ, 0); char torrc_contents[1000]; @@ -4900,6 +4901,7 @@ test_config_include_error_in_included_file(void *data) done: config_free_lines(result); tor_free(dir); + tor_free(invalid_path); } static void @@ -4908,6 +4910,8 @@ test_config_include_empty_file_folder(void *data) (void)data; config_line_t *result = NULL; + char *folder_path = NULL; + char *file_path = NULL; char *dir = tor_strdup(get_fname("test_include_empty_file_folder")); tt_ptr_op(dir, OP_NE, NULL); @@ -4917,17 +4921,13 @@ test_config_include_empty_file_folder(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char folder_path[PATH_MAX+1]; - tor_snprintf(folder_path, sizeof(folder_path), "%s"PATH_SEPARATOR"empty_dir", - dir); + tor_asprintf(&folder_path, "%s"PATH_SEPARATOR"empty_dir", dir); #ifdef _WIN32 tt_int_op(mkdir(folder_path), OP_EQ, 0); #else tt_int_op(mkdir(folder_path, 0700), OP_EQ, 0); #endif - char file_path[PATH_MAX+1]; - tor_snprintf(file_path, sizeof(file_path), "%s"PATH_SEPARATOR"empty_file", - dir); + tor_asprintf(&file_path, "%s"PATH_SEPARATOR"empty_file", dir); tt_int_op(write_str_to_file(file_path, "", 0), OP_EQ, 0); char torrc_contents[1000]; @@ -4944,15 +4944,57 @@ test_config_include_empty_file_folder(void *data) done: config_free_lines(result); + tor_free(folder_path); + tor_free(file_path); tor_free(dir); } +#ifndef _WIN32 +static void +test_config_include_no_permission(void *data) +{ + (void)data; + config_line_t *result = NULL; + + char *folder_path = NULL; + char *dir = NULL; + if (geteuid() == 0) + tt_skip(); + + dir = tor_strdup(get_fname("test_include_forbidden_folder")); + tt_ptr_op(dir, OP_NE, NULL); + + tt_int_op(mkdir(dir, 0700), OP_EQ, 0); + + tor_asprintf(&folder_path, "%s"PATH_SEPARATOR"forbidden_dir", dir); + tt_int_op(mkdir(folder_path, 0100), OP_EQ, 0); + + char torrc_contents[1000]; + tor_snprintf(torrc_contents, sizeof(torrc_contents), + "%%include %s\n", + folder_path); + + int include_used; + tt_int_op(config_get_lines_include(torrc_contents, &result, 0,&include_used), + OP_EQ, -1); + tt_ptr_op(result, OP_EQ, NULL); + + done: + config_free_lines(result); + tor_free(folder_path); + if (dir) + chmod(dir, 0700); + tor_free(dir); +} +#endif + static void test_config_include_recursion_before_after(void *data) { (void)data; config_line_t *result = NULL; + char *torrc_path = NULL; char *dir = tor_strdup(get_fname("test_include_recursion_before_after")); tt_ptr_op(dir, OP_NE, NULL); @@ -4962,8 +5004,7 @@ test_config_include_recursion_before_after(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char torrc_path[PATH_MAX+1]; - tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir); + tor_asprintf(&torrc_path, "%s"PATH_SEPARATOR"torrc", dir); char file_contents[1000]; const int limit = MAX_INCLUDE_RECURSION_LEVEL; @@ -4982,9 +5023,10 @@ test_config_include_recursion_before_after(void *data) } if (i > 1) { - char file_path[PATH_MAX+1]; - tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i); + char *file_path = NULL; + tor_asprintf(&file_path, "%s%d", torrc_path, i); tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0); + tor_free(file_path); } } @@ -5008,6 +5050,7 @@ test_config_include_recursion_before_after(void *data) done: config_free_lines(result); tor_free(dir); + tor_free(torrc_path); } static void @@ -5016,6 +5059,7 @@ test_config_include_recursion_after_only(void *data) (void)data; config_line_t *result = NULL; + char *torrc_path = NULL; char *dir = tor_strdup(get_fname("test_include_recursion_after_only")); tt_ptr_op(dir, OP_NE, NULL); @@ -5025,8 +5069,7 @@ test_config_include_recursion_after_only(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char torrc_path[PATH_MAX+1]; - tor_snprintf(torrc_path, sizeof(torrc_path), "%s"PATH_SEPARATOR"torrc", dir); + tor_asprintf(&torrc_path, "%s"PATH_SEPARATOR"torrc", dir); char file_contents[1000]; const int limit = MAX_INCLUDE_RECURSION_LEVEL; @@ -5045,9 +5088,10 @@ test_config_include_recursion_after_only(void *data) } if (i > 1) { - char file_path[PATH_MAX+1]; - tor_snprintf(file_path, sizeof(file_path), "%s%d", torrc_path, i); + char *file_path = NULL; + tor_asprintf(&file_path, "%s%d", torrc_path, i); tt_int_op(write_str_to_file(file_path, file_contents, 0), OP_EQ, 0); + tor_free(file_path); } } @@ -5071,6 +5115,7 @@ test_config_include_recursion_after_only(void *data) done: config_free_lines(result); tor_free(dir); + tor_free(torrc_path); } static void @@ -5079,6 +5124,9 @@ test_config_include_folder_order(void *data) (void)data; config_line_t *result = NULL; + char *torrcd = NULL; + char *path = NULL; + char *path2 = NULL; char *dir = tor_strdup(get_fname("test_include_folder_order")); tt_ptr_op(dir, OP_NE, NULL); @@ -5088,8 +5136,7 @@ test_config_include_folder_order(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char torrcd[PATH_MAX+1]; - tor_snprintf(torrcd, sizeof(torrcd), "%s"PATH_SEPARATOR"%s", dir, "torrc.d"); + tor_asprintf(&torrcd, "%s"PATH_SEPARATOR"%s", dir, "torrc.d"); #ifdef _WIN32 tt_int_op(mkdir(torrcd), OP_EQ, 0); @@ -5098,9 +5145,7 @@ test_config_include_folder_order(void *data) #endif // test that files in subfolders are ignored - char path[PATH_MAX+1]; - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, - "subfolder"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "subfolder"); #ifdef _WIN32 tt_int_op(mkdir(path), OP_EQ, 0); @@ -5108,27 +5153,31 @@ test_config_include_folder_order(void *data) tt_int_op(mkdir(path, 0700), OP_EQ, 0); #endif - char path2[PATH_MAX+1]; - tor_snprintf(path2, sizeof(path2), "%s"PATH_SEPARATOR"%s", path, - "01_ignore"); + tor_asprintf(&path2, "%s"PATH_SEPARATOR"%s", path, "01_ignore"); tt_int_op(write_str_to_file(path2, "ShouldNotSee 1\n", 0), OP_EQ, 0); + tor_free(path); // test that files starting with . are ignored - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, ".dot"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, ".dot"); tt_int_op(write_str_to_file(path, "ShouldNotSee 2\n", 0), OP_EQ, 0); + tor_free(path); // test file order - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "01_1st"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "01_1st"); tt_int_op(write_str_to_file(path, "Test 1\n", 0), OP_EQ, 0); + tor_free(path); - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "02_2nd"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "02_2nd"); tt_int_op(write_str_to_file(path, "Test 2\n", 0), OP_EQ, 0); + tor_free(path); - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "aa_3rd"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "aa_3rd"); tt_int_op(write_str_to_file(path, "Test 3\n", 0), OP_EQ, 0); + tor_free(path); - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", torrcd, "ab_4th"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", torrcd, "ab_4th"); tt_int_op(write_str_to_file(path, "Test 4\n", 0), OP_EQ, 0); + tor_free(path); char torrc_contents[1000]; tor_snprintf(torrc_contents, sizeof(torrc_contents), @@ -5154,6 +5203,9 @@ test_config_include_folder_order(void *data) done: config_free_lines(result); + tor_free(torrcd); + tor_free(path); + tor_free(path2); tor_free(dir); } @@ -5285,6 +5337,7 @@ test_config_include_flag_torrc_only(void *data) (void)data; char *errmsg = NULL; + char *path = NULL; char *dir = tor_strdup(get_fname("test_include_flag_torrc_only")); tt_ptr_op(dir, OP_NE, NULL); @@ -5294,8 +5347,7 @@ test_config_include_flag_torrc_only(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char path[PATH_MAX+1]; - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", dir, "dummy"); tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0); char conf_empty[1000]; @@ -5315,6 +5367,7 @@ test_config_include_flag_torrc_only(void *data) done: tor_free(errmsg); + tor_free(path); tor_free(dir); } @@ -5324,6 +5377,7 @@ test_config_include_flag_defaults_only(void *data) (void)data; char *errmsg = NULL; + char *path = NULL; char *dir = tor_strdup(get_fname("test_include_flag_defaults_only")); tt_ptr_op(dir, OP_NE, NULL); @@ -5333,8 +5387,7 @@ test_config_include_flag_defaults_only(void *data) tt_int_op(mkdir(dir, 0700), OP_EQ, 0); #endif - char path[PATH_MAX+1]; - tor_snprintf(path, sizeof(path), "%s"PATH_SEPARATOR"%s", dir, "dummy"); + tor_asprintf(&path, "%s"PATH_SEPARATOR"%s", dir, "dummy"); tt_int_op(write_str_to_file(path, "\n", 0), OP_EQ, 0); char conf_empty[1000]; @@ -5354,9 +5407,115 @@ test_config_include_flag_defaults_only(void *data) done: tor_free(errmsg); + tor_free(path); tor_free(dir); } +static void +test_config_dup_and_filter(void *arg) +{ + (void)arg; + /* Test normal input. */ + config_line_t *line = NULL; + config_line_append(&line, "abc", "def"); + config_line_append(&line, "ghi", "jkl"); + config_line_append(&line, "ABCD", "mno"); + + config_line_t *line_dup = config_lines_dup_and_filter(line, "aBc"); + tt_ptr_op(line_dup, OP_NE, NULL); + tt_ptr_op(line_dup->next, OP_NE, NULL); + tt_ptr_op(line_dup->next->next, OP_EQ, NULL); + + tt_str_op(line_dup->key, OP_EQ, "abc"); + tt_str_op(line_dup->value, OP_EQ, "def"); + tt_str_op(line_dup->next->key, OP_EQ, "ABCD"); + tt_str_op(line_dup->next->value, OP_EQ, "mno"); + + /* empty output */ + config_free_lines(line_dup); + line_dup = config_lines_dup_and_filter(line, "skdjfsdkljf"); + tt_ptr_op(line_dup, OP_EQ, NULL); + + /* empty input */ + config_free_lines(line_dup); + line_dup = config_lines_dup_and_filter(NULL, "abc"); + tt_ptr_op(line_dup, OP_EQ, NULL); + + done: + config_free_lines(line); + config_free_lines(line_dup); +} + +/* If we're not configured to be a bridge, but we set + * BridgeDistribution, then options_validate () should return -1. */ +static void +test_config_check_bridge_distribution_setting_not_a_bridge(void *arg) +{ + or_options_t* options = get_options_mutable(); + or_options_t* old_options = options; + or_options_t* default_options = options; + char* message = NULL; + int ret; + + (void)arg; + + options->BridgeRelay = 0; + options->BridgeDistribution = (char*)("https"); + + ret = options_validate(old_options, options, default_options, 0, &message); + + tt_int_op(ret, OP_EQ, -1); + tt_str_op(message, OP_EQ, "You set BridgeDistribution, but you " + "didn't set BridgeRelay!"); + done: + tor_free(message); + options->BridgeDistribution = NULL; +} + +/* If the BridgeDistribution setting was valid, 0 should be returned. */ +static void +test_config_check_bridge_distribution_setting_valid(void *arg) +{ + int ret = check_bridge_distribution_setting("https"); + + (void)arg; + + tt_int_op(ret, OP_EQ, 0); + done: + return; +} + +/* If the BridgeDistribution setting was invalid, -1 should be returned. */ +static void +test_config_check_bridge_distribution_setting_invalid(void *arg) +{ + int ret = check_bridge_distribution_setting("hyphens-are-allowed"); + + (void)arg; + + tt_int_op(ret, OP_EQ, 0); + + ret = check_bridge_distribution_setting("asterisks*are*forbidden"); + + tt_int_op(ret, OP_EQ, -1); + done: + return; +} + +/* If the BridgeDistribution setting was unrecognised, a warning should be + * logged and 0 should be returned. */ +static void +test_config_check_bridge_distribution_setting_unrecognised(void *arg) +{ + int ret = check_bridge_distribution_setting("unicorn"); + + (void)arg; + + tt_int_op(ret, OP_EQ, 0); + done: + return; +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } @@ -5387,6 +5546,9 @@ struct testcase_t config_tests[] = { CONFIG_TEST(include_does_not_exist, 0), CONFIG_TEST(include_error_in_included_file, 0), CONFIG_TEST(include_empty_file_folder, 0), +#ifndef _WIN32 + CONFIG_TEST(include_no_permission, 0), +#endif CONFIG_TEST(include_recursion_before_after, 0), CONFIG_TEST(include_recursion_after_only, 0), CONFIG_TEST(include_folder_order, 0), @@ -5396,6 +5558,11 @@ struct testcase_t config_tests[] = { CONFIG_TEST(include_flag_both_without, TT_FORK), CONFIG_TEST(include_flag_torrc_only, TT_FORK), CONFIG_TEST(include_flag_defaults_only, TT_FORK), + CONFIG_TEST(dup_and_filter, 0), + CONFIG_TEST(check_bridge_distribution_setting_not_a_bridge, TT_FORK), + CONFIG_TEST(check_bridge_distribution_setting_valid, 0), + CONFIG_TEST(check_bridge_distribution_setting_invalid, 0), + CONFIG_TEST(check_bridge_distribution_setting_unrecognised, 0), END_OF_TESTCASES }; diff --git a/src/test/test_connection.c b/src/test/test_connection.c index 7e5193b203..314b90cfda 100644 --- a/src/test/test_connection.c +++ b/src/test/test_connection.c @@ -17,9 +17,8 @@ #include "rendcache.h" #include "directory.h" -static void test_conn_lookup_addr_helper(const char *address, - int family, - tor_addr_t *addr); +#include "test_connection.h" +#include "test_helpers.h" static void * test_conn_get_basic_setup(const struct testcase_t *tc); static int test_conn_get_basic_teardown(const struct testcase_t *tc, @@ -62,48 +61,7 @@ static int test_conn_get_rsrc_teardown(const struct testcase_t *tc, #define TEST_CONN_UNATTACHED_STATE (AP_CONN_STATE_CIRCUIT_WAIT) #define TEST_CONN_ATTACHED_STATE (AP_CONN_STATE_CONNECT_WAIT) -#define TEST_CONN_FD_INIT 50 -static int mock_connection_connect_sockaddr_called = 0; -static int fake_socket_number = TEST_CONN_FD_INIT; - -static int -mock_connection_connect_sockaddr(connection_t *conn, - const struct sockaddr *sa, - socklen_t sa_len, - const struct sockaddr *bindaddr, - socklen_t bindaddr_len, - int *socket_error) -{ - (void)sa_len; - (void)bindaddr; - (void)bindaddr_len; - - tor_assert(conn); - tor_assert(sa); - tor_assert(socket_error); - - mock_connection_connect_sockaddr_called++; - - conn->s = fake_socket_number++; - tt_assert(SOCKET_OK(conn->s)); - /* We really should call tor_libevent_initialize() here. Because we don't, - * we are relying on other parts of the code not checking if the_event_base - * (and therefore event->ev_base) is NULL. */ - tt_assert(connection_add_connecting(conn) == 0); - - done: - /* Fake "connected" status */ - return 1; -} - -static int -fake_close_socket(evutil_socket_t sock) -{ - (void)sock; - return 0; -} - -static void +void test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr) { int rv = 0; @@ -112,7 +70,7 @@ test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr) rv = tor_addr_lookup(address, family, addr); /* XXXX - should we retry on transient failure? */ - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); tt_assert(tor_addr_is_loopback(addr)); tt_assert(tor_addr_is_v4(addr)); @@ -122,51 +80,6 @@ test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr) tor_addr_make_null(addr, TEST_CONN_FAMILY); } -static connection_t * -test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose) -{ - connection_t *conn = NULL; - tor_addr_t addr; - int socket_err = 0; - int in_progress = 0; - - MOCK(connection_connect_sockaddr, - mock_connection_connect_sockaddr); - MOCK(tor_close_socket, fake_close_socket); - - init_connection_lists(); - - conn = connection_new(type, TEST_CONN_FAMILY); - tt_assert(conn); - - test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr); - tt_assert(!tor_addr_is_null(&addr)); - - tor_addr_copy_tight(&conn->addr, &addr); - conn->port = TEST_CONN_PORT; - mock_connection_connect_sockaddr_called = 0; - in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr, - TEST_CONN_PORT, &socket_err); - tt_assert(mock_connection_connect_sockaddr_called == 1); - tt_assert(!socket_err); - tt_assert(in_progress == 0 || in_progress == 1); - - /* fake some of the attributes so the connection looks OK */ - conn->state = state; - conn->purpose = purpose; - assert_connection_ok(conn, time(NULL)); - - UNMOCK(connection_connect_sockaddr); - UNMOCK(tor_close_socket); - return conn; - - /* On failure */ - done: - UNMOCK(connection_connect_sockaddr); - UNMOCK(tor_close_socket); - return NULL; -} - static void * test_conn_get_basic_setup(const struct testcase_t *tc) { @@ -379,7 +292,7 @@ test_conn_download_status_teardown(const struct testcase_t *tc, void *arg) /* connection_free_() cleans up requested_resource */ rv = test_conn_get_rsrc_teardown(tc, conn); - tt_assert(rv == 1); + tt_int_op(rv, OP_EQ, 1); } } SMARTLIST_FOREACH_END(conn); @@ -453,12 +366,13 @@ test_conn_get_basic(void *arg) * its attributes, but get NULL when we supply a different value. */ tt_assert(connection_get_by_global_id(conn->global_identifier) == conn); - tt_assert(connection_get_by_global_id(!conn->global_identifier) == NULL); + tt_ptr_op(connection_get_by_global_id(!conn->global_identifier), OP_EQ, + NULL); tt_assert(connection_get_by_type(conn->type) == conn); tt_assert(connection_get_by_type(TEST_CONN_TYPE) == conn); - tt_assert(connection_get_by_type(!conn->type) == NULL); - tt_assert(connection_get_by_type(!TEST_CONN_TYPE) == NULL); + tt_ptr_op(connection_get_by_type(!conn->type), OP_EQ, NULL); + tt_ptr_op(connection_get_by_type(!TEST_CONN_TYPE), OP_EQ, NULL); tt_assert(connection_get_by_type_state(conn->type, conn->state) == conn); @@ -572,7 +486,7 @@ test_conn_get_rend(void *arg) #define sl_is_conn_assert(sl_input, conn) \ do { \ the_sl = (sl_input); \ - tt_assert(smartlist_len((the_sl)) == 1); \ + tt_int_op(smartlist_len((the_sl)), OP_EQ, 1); \ tt_assert(smartlist_get((the_sl), 0) == (conn)); \ smartlist_free(the_sl); the_sl = NULL; \ } while (0) @@ -580,7 +494,7 @@ test_conn_get_rend(void *arg) #define sl_no_conn_assert(sl_input) \ do { \ the_sl = (sl_input); \ - tt_assert(smartlist_len((the_sl)) == 0); \ + tt_int_op(smartlist_len((the_sl)), OP_EQ, 0); \ smartlist_free(the_sl); the_sl = NULL; \ } while (0) @@ -626,43 +540,32 @@ test_conn_get_rsrc(void *arg) TEST_CONN_RSRC_2, !TEST_CONN_STATE)); - tt_assert(connection_dir_count_by_purpose_and_resource( - conn->base_.purpose, - conn->requested_resource) - == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - TEST_CONN_RSRC) - == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - !conn->base_.purpose, - "") - == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - !TEST_CONN_RSRC_PURPOSE, - TEST_CONN_RSRC_2) - == 0); - - tt_assert(connection_dir_count_by_purpose_resource_and_state( - conn->base_.purpose, - conn->requested_resource, - conn->base_.state) - == 1); - tt_assert(connection_dir_count_by_purpose_resource_and_state( - TEST_CONN_RSRC_PURPOSE, - TEST_CONN_RSRC, - TEST_CONN_STATE) - == 1); - tt_assert(connection_dir_count_by_purpose_resource_and_state( - !conn->base_.purpose, - "", - !conn->base_.state) - == 0); - tt_assert(connection_dir_count_by_purpose_resource_and_state( - !TEST_CONN_RSRC_PURPOSE, - TEST_CONN_RSRC_2, - !TEST_CONN_STATE) - == 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + conn->base_.purpose, conn->requested_resource), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, TEST_CONN_RSRC), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + !conn->base_.purpose, ""), + OP_EQ, 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, TEST_CONN_RSRC_2), + OP_EQ, 0); + + tt_int_op(connection_dir_count_by_purpose_resource_and_state( + conn->base_.purpose, conn->requested_resource, + conn->base_.state), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, TEST_CONN_RSRC, TEST_CONN_STATE), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_resource_and_state( + !conn->base_.purpose, "", !conn->base_.state), + OP_EQ, 0); + tt_int_op(connection_dir_count_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, TEST_CONN_RSRC_2, !TEST_CONN_STATE), + OP_EQ, 0); done: smartlist_free(the_sl); @@ -688,117 +591,127 @@ test_conn_download_status(void *arg) const char *other_res = networkstatus_get_flavor_name(other_flavor); /* no connections */ - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* one connection, not downloading */ conn = test_conn_download_status_add_a_connection(res); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* one connection, downloading but not linked (not possible on a client, * but possible on a relay) */ conn->base_.state = TEST_CONN_DL_STATE; - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* one connection, downloading and linked, but not yet attached */ ap_conn = test_conn_get_linked_connection(TO_CONN(conn), TEST_CONN_UNATTACHED_STATE); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* one connection, downloading and linked and attached */ ap_conn->state = TEST_CONN_ATTACHED_STATE; - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* one connection, linked and attached but not downloading */ conn->base_.state = TEST_CONN_STATE; - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* two connections, both not downloading */ conn2 = test_conn_download_status_add_a_connection(res); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 2); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 2); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* two connections, one downloading */ conn->base_.state = TEST_CONN_DL_STATE; - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 2); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 2); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); conn->base_.state = TEST_CONN_STATE; /* more connections, all not downloading */ /* ignore the return value, it's free'd using the connection list */ (void)test_conn_download_status_add_a_connection(res); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 0); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 3); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 0); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 3); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* more connections, one downloading */ conn->base_.state = TEST_CONN_DL_STATE; - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 3); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 3); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* more connections, two downloading (should never happen, but needs * to be tested for completeness) */ @@ -806,38 +719,41 @@ test_conn_download_status(void *arg) /* ignore the return value, it's free'd using the connection list */ (void)test_conn_get_linked_connection(TO_CONN(conn2), TEST_CONN_ATTACHED_STATE); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 3); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 3); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); conn->base_.state = TEST_CONN_STATE; /* more connections, a different one downloading */ - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 3); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 0); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 3); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 0); /* a connection for the other flavor (could happen if a client is set to * cache directory documents), one preferred flavor downloading */ conn4 = test_conn_download_status_add_a_connection(other_res); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 0); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 3); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 1); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 0); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 3); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 1); /* a connection for the other flavor (could happen if a client is set to * cache directory documents), both flavors downloading @@ -846,14 +762,15 @@ test_conn_download_status(void *arg) /* ignore the return value, it's free'd using the connection list */ (void)test_conn_get_linked_connection(TO_CONN(conn4), TEST_CONN_ATTACHED_STATE); - tt_assert(networkstatus_consensus_is_already_downloading(res) == 1); - tt_assert(networkstatus_consensus_is_already_downloading(other_res) == 1); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - res) == 3); - tt_assert(connection_dir_count_by_purpose_and_resource( - TEST_CONN_RSRC_PURPOSE, - other_res) == 1); + tt_int_op(networkstatus_consensus_is_already_downloading(res), OP_EQ, 1); + tt_int_op(networkstatus_consensus_is_already_downloading(other_res), OP_EQ, + 1); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, res), + OP_EQ, 3); + tt_int_op(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, other_res), + OP_EQ, 1); done: /* the teardown function removes all the connections in the global list*/; diff --git a/src/test/test_connection.h b/src/test/test_connection.h new file mode 100644 index 0000000000..392783b53b --- /dev/null +++ b/src/test/test_connection.h @@ -0,0 +1,13 @@ +/* Copyright (c) 2014-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** Some constants used by test_connection and helpers */ +#define TEST_CONN_FAMILY (AF_INET) +#define TEST_CONN_ADDRESS "127.0.0.1" +#define TEST_CONN_PORT (12345) +#define TEST_CONN_ADDRESS_PORT "127.0.0.1:12345" +#define TEST_CONN_FD_INIT 50 + +void test_conn_lookup_addr_helper(const char *address, + int family, tor_addr_t *addr); + diff --git a/src/test/test_conscache.c b/src/test/test_conscache.c index aee1ba8a06..ddb1bc53c1 100644 --- a/src/test/test_conscache.c +++ b/src/test/test_conscache.c @@ -200,7 +200,7 @@ test_conscache_cleanup(void *arg) tt_assert(e_tmp); tt_assert(consensus_cache_entry_is_mapped(e_tmp)); e_tmp = consensus_cache_find_first(cache, "index", "7"); - tt_assert(e_tmp == NULL); // not found because pending deletion. + tt_ptr_op(e_tmp, OP_EQ, NULL); // not found because pending deletion. /* Delete the pending-deletion items. */ consensus_cache_delete_pending(cache, 0); @@ -212,9 +212,9 @@ test_conscache_cleanup(void *arg) tt_int_op(n, OP_EQ, 20 - 2); /* 1 entry was deleted; 1 is not-found. */ } e_tmp = consensus_cache_find_first(cache, "index", "7"); // refcnt == 1... - tt_assert(e_tmp == NULL); // so deleted. + tt_ptr_op(e_tmp, OP_EQ, NULL); // so deleted. e_tmp = consensus_cache_find_first(cache, "index", "14"); // refcnt == 2 - tt_assert(e_tmp == NULL); // not deleted; but not found. + tt_ptr_op(e_tmp, OP_EQ, NULL); // not deleted; but not found. /* Now do lazy unmapping. */ // should do nothing. diff --git a/src/test/test_consdiff.c b/src/test/test_consdiff.c index 7cf8d6ba2b..fda3a7f186 100644 --- a/src/test/test_consdiff.c +++ b/src/test/test_consdiff.c @@ -19,28 +19,29 @@ test_consdiff_smartlist_slice(void *arg) { smartlist_t *sl = smartlist_new(); smartlist_slice_t *sls; + int items[6] = {0,0,0,0,0,0}; /* Create a regular smartlist. */ (void)arg; - smartlist_add(sl, (void*)1); - smartlist_add(sl, (void*)2); - smartlist_add(sl, (void*)3); - smartlist_add(sl, (void*)4); - smartlist_add(sl, (void*)5); + smartlist_add(sl, &items[1]); + smartlist_add(sl, &items[2]); + smartlist_add(sl, &items[3]); + smartlist_add(sl, &items[4]); + smartlist_add(sl, &items[5]); /* See if the slice was done correctly. */ sls = smartlist_slice(sl, 2, 5); tt_ptr_op(sl, OP_EQ, sls->list); - tt_ptr_op((void*)3, OP_EQ, smartlist_get(sls->list, sls->offset)); - tt_ptr_op((void*)5, OP_EQ, + tt_ptr_op(&items[3], OP_EQ, smartlist_get(sls->list, sls->offset)); + tt_ptr_op(&items[5], OP_EQ, smartlist_get(sls->list, sls->offset + (sls->len-1))); tor_free(sls); /* See that using -1 as the end does get to the last element. */ sls = smartlist_slice(sl, 2, -1); tt_ptr_op(sl, OP_EQ, sls->list); - tt_ptr_op((void*)3, OP_EQ, smartlist_get(sls->list, sls->offset)); - tt_ptr_op((void*)5, OP_EQ, + tt_ptr_op(&items[3], OP_EQ, smartlist_get(sls->list, sls->offset)); + tt_ptr_op(&items[5], OP_EQ, smartlist_get(sls->list, sls->offset + (sls->len-1))); done: @@ -1107,7 +1108,7 @@ test_consdiff_apply_diff(void *arg) tt_ptr_op(NULL, OP_EQ, cons2); expect_log_msg_containing("Could not compute digests of the consensus " "resulting from applying a consensus diff."); -#endif +#endif /* 0 */ /* Very simple test, only to see that nothing errors. */ smartlist_clear(diff); diff --git a/src/test/test_consdiffmgr.c b/src/test/test_consdiffmgr.c index 963a6e427a..80d3f943ab 100644 --- a/src/test/test_consdiffmgr.c +++ b/src/test/test_consdiffmgr.c @@ -230,7 +230,7 @@ test_consdiffmgr_init_failure(void *arg) done: tor_end_capture_bugs_(); } -#endif +#endif /* 0 */ static void test_consdiffmgr_sha3_helper(void *arg) @@ -325,8 +325,8 @@ test_consdiffmgr_add(void *arg) tt_mem_op(body, OP_EQ, "quux", 4); /* Try looking up another entry, but fail */ - tt_assert(NULL == cdm_cache_lookup_consensus(FLAV_MICRODESC, now-60)); - tt_assert(NULL == cdm_cache_lookup_consensus(FLAV_NS, now-61)); + tt_ptr_op(cdm_cache_lookup_consensus(FLAV_MICRODESC, now - 60), OP_EQ, NULL); + tt_ptr_op(cdm_cache_lookup_consensus(FLAV_NS, now - 61), OP_EQ, NULL); done: networkstatus_vote_free(ns_tmp); diff --git a/src/test/test_containers.c b/src/test/test_containers.c index 54484a2a91..c4dba73750 100644 --- a/src/test/test_containers.c +++ b/src/test/test_containers.c @@ -510,22 +510,22 @@ test_container_smartlist_pos(void *arg) smartlist_add_strdup(sl, "function"); /* Test string_pos */ - tt_int_op(smartlist_string_pos(NULL, "Fred"), ==, -1); - tt_int_op(smartlist_string_pos(sl, "Fred"), ==, -1); - tt_int_op(smartlist_string_pos(sl, "This"), ==, 0); - tt_int_op(smartlist_string_pos(sl, "a"), ==, 2); - tt_int_op(smartlist_string_pos(sl, "function"), ==, 6); + tt_int_op(smartlist_string_pos(NULL, "Fred"), OP_EQ, -1); + tt_int_op(smartlist_string_pos(sl, "Fred"), OP_EQ, -1); + tt_int_op(smartlist_string_pos(sl, "This"), OP_EQ, 0); + tt_int_op(smartlist_string_pos(sl, "a"), OP_EQ, 2); + tt_int_op(smartlist_string_pos(sl, "function"), OP_EQ, 6); /* Test pos */ - tt_int_op(smartlist_pos(NULL, "Fred"), ==, -1); - tt_int_op(smartlist_pos(sl, "Fred"), ==, -1); - tt_int_op(smartlist_pos(sl, "This"), ==, -1); - tt_int_op(smartlist_pos(sl, "a"), ==, -1); - tt_int_op(smartlist_pos(sl, "function"), ==, -1); - tt_int_op(smartlist_pos(sl, smartlist_get(sl,0)), ==, 0); - tt_int_op(smartlist_pos(sl, smartlist_get(sl,2)), ==, 2); - tt_int_op(smartlist_pos(sl, smartlist_get(sl,5)), ==, 5); - tt_int_op(smartlist_pos(sl, smartlist_get(sl,6)), ==, 6); + tt_int_op(smartlist_pos(NULL, "Fred"), OP_EQ, -1); + tt_int_op(smartlist_pos(sl, "Fred"), OP_EQ, -1); + tt_int_op(smartlist_pos(sl, "This"), OP_EQ, -1); + tt_int_op(smartlist_pos(sl, "a"), OP_EQ, -1); + tt_int_op(smartlist_pos(sl, "function"), OP_EQ, -1); + tt_int_op(smartlist_pos(sl, smartlist_get(sl,0)), OP_EQ, 0); + tt_int_op(smartlist_pos(sl, smartlist_get(sl,2)), OP_EQ, 2); + tt_int_op(smartlist_pos(sl, smartlist_get(sl,5)), OP_EQ, 5); + tt_int_op(smartlist_pos(sl, smartlist_get(sl,6)), OP_EQ, 6); done: SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); @@ -681,7 +681,7 @@ test_container_pqueue(void *arg) { smartlist_t *sl = smartlist_new(); int (*cmp)(const void *, const void*); - const int offset = STRUCT_OFFSET(pq_entry_t, idx); + const int offset = offsetof(pq_entry_t, idx); #define ENTRY(s) pq_entry_t s = { #s, -1 } ENTRY(cows); ENTRY(zebras); @@ -990,13 +990,13 @@ test_container_order_functions(void *arg) tt_assert(15 == median_time(times, 5)); int32_t int32s[] = { -5, -10, -50, 100 }; - tt_int_op(-5, ==, median_int32(int32s, 1)); - tt_int_op(-10, ==, median_int32(int32s, 2)); - tt_int_op(-10, ==, median_int32(int32s, 3)); - tt_int_op(-10, ==, median_int32(int32s, 4)); + tt_int_op(-5, OP_EQ, median_int32(int32s, 1)); + tt_int_op(-10, OP_EQ, median_int32(int32s, 2)); + tt_int_op(-10, OP_EQ, median_int32(int32s, 3)); + tt_int_op(-10, OP_EQ, median_int32(int32s, 4)); long longs[] = { -30, 30, 100, -100, 7 }; - tt_int_op(7, ==, find_nth_long(longs, 5, 2)); + tt_int_op(7, OP_EQ, find_nth_long(longs, 5, 2)); done: ; @@ -1106,7 +1106,7 @@ test_container_fp_pair_map(void *arg) tt_int_op(fp_pair_map_size(map),OP_EQ, 4); fp_pair_map_assert_ok(map); fp_pair_map_set(map, &fp5, v104); - fp_pair_map_set(map, &fp6, v105); + fp_pair_map_set_by_digests(map, fp6.first, fp6.second, v105); fp_pair_map_assert_ok(map); /* Test iterator. */ @@ -1124,7 +1124,8 @@ test_container_fp_pair_map(void *arg) /* Make sure we removed fp2, but not the others. */ tt_ptr_op(fp_pair_map_get(map, &fp2),OP_EQ, NULL); - tt_ptr_op(fp_pair_map_get(map, &fp5),OP_EQ, v104); + tt_ptr_op(fp_pair_map_get_by_digests(map, fp5.first, fp5.second), + OP_EQ, v104); fp_pair_map_assert_ok(map); /* Clean up after ourselves. */ @@ -1153,31 +1154,31 @@ test_container_smartlist_most_frequent(void *arg) const char *cp; cp = smartlist_get_most_frequent_string_(sl, &count); - tt_int_op(count, ==, 0); - tt_ptr_op(cp, ==, NULL); + tt_int_op(count, OP_EQ, 0); + tt_ptr_op(cp, OP_EQ, NULL); /* String must be sorted before we call get_most_frequent */ smartlist_split_string(sl, "abc:def:ghi", ":", 0, 0); cp = smartlist_get_most_frequent_string_(sl, &count); - tt_int_op(count, ==, 1); - tt_str_op(cp, ==, "ghi"); /* Ties broken in favor of later element */ + tt_int_op(count, OP_EQ, 1); + tt_str_op(cp, OP_EQ, "ghi"); /* Ties broken in favor of later element */ smartlist_split_string(sl, "def:ghi", ":", 0, 0); smartlist_sort_strings(sl); cp = smartlist_get_most_frequent_string_(sl, &count); - tt_int_op(count, ==, 2); - tt_ptr_op(cp, !=, NULL); - tt_str_op(cp, ==, "ghi"); /* Ties broken in favor of later element */ + tt_int_op(count, OP_EQ, 2); + tt_ptr_op(cp, OP_NE, NULL); + tt_str_op(cp, OP_EQ, "ghi"); /* Ties broken in favor of later element */ smartlist_split_string(sl, "def:abc:qwop", ":", 0, 0); smartlist_sort_strings(sl); cp = smartlist_get_most_frequent_string_(sl, &count); - tt_int_op(count, ==, 3); - tt_ptr_op(cp, !=, NULL); - tt_str_op(cp, ==, "def"); /* No tie */ + tt_int_op(count, OP_EQ, 3); + tt_ptr_op(cp, OP_NE, NULL); + tt_str_op(cp, OP_EQ, "def"); /* No tie */ done: SMARTLIST_FOREACH(sl, char *, str, tor_free(str)); @@ -1206,7 +1207,7 @@ test_container_smartlist_sort_ptrs(void *arg) smartlist_shuffle(sl); smartlist_sort_pointers(sl); for (j = 0; j < ARRAY_LENGTH(arrayptrs); ++j) { - tt_ptr_op(smartlist_get(sl, j), ==, arrayptrs[j]); + tt_ptr_op(smartlist_get(sl, j), OP_EQ, arrayptrs[j]); } } @@ -1232,11 +1233,11 @@ test_container_smartlist_strings_eq(void *arg) } while (0) /* Both NULL, so equal */ - tt_int_op(1, ==, smartlist_strings_eq(NULL, NULL)); + tt_int_op(1, OP_EQ, smartlist_strings_eq(NULL, NULL)); /* One NULL, not equal. */ - tt_int_op(0, ==, smartlist_strings_eq(NULL, sl1)); - tt_int_op(0, ==, smartlist_strings_eq(sl1, NULL)); + tt_int_op(0, OP_EQ, smartlist_strings_eq(NULL, sl1)); + tt_int_op(0, OP_EQ, smartlist_strings_eq(sl1, NULL)); /* Both empty, both equal. */ EQ_SHOULD_SAY("", "", 1); diff --git a/src/test/test_controller.c b/src/test/test_controller.c index 592f91a988..472fcb8c53 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -31,7 +31,7 @@ test_add_onion_helper_keyarg(void *arg) tt_assert(pk); tt_str_op(key_new_alg, OP_EQ, "RSA1024"); tt_assert(key_new_blob); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); /* Test "BEST" key generation (Assumes BEST = RSA1024). */ crypto_pk_free(pk); @@ -41,7 +41,7 @@ test_add_onion_helper_keyarg(void *arg) tt_assert(pk); tt_str_op(key_new_alg, OP_EQ, "RSA1024"); tt_assert(key_new_blob); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); /* Test discarding the private key. */ crypto_pk_free(pk); @@ -49,17 +49,17 @@ test_add_onion_helper_keyarg(void *arg) pk = add_onion_helper_keyarg("NEW:BEST", 1, &key_new_alg, &key_new_blob, &err_msg); tt_assert(pk); - tt_assert(!key_new_alg); - tt_assert(!key_new_blob); - tt_assert(!err_msg); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); + tt_ptr_op(err_msg, OP_EQ, NULL); /* Test generating a invalid key type. */ crypto_pk_free(pk); pk = add_onion_helper_keyarg("NEW:RSA512", 0, &key_new_alg, &key_new_blob, &err_msg); - tt_assert(!pk); - tt_assert(!key_new_alg); - tt_assert(!key_new_blob); + tt_ptr_op(pk, OP_EQ, NULL); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_assert(err_msg); /* Test loading a RSA1024 key. */ @@ -70,10 +70,10 @@ test_add_onion_helper_keyarg(void *arg) pk2 = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, &err_msg); tt_assert(pk2); - tt_assert(!key_new_alg); - tt_assert(!key_new_blob); - tt_assert(!err_msg); - tt_assert(crypto_pk_cmp_keys(pk, pk2) == 0); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); + tt_ptr_op(err_msg, OP_EQ, NULL); + tt_int_op(crypto_pk_cmp_keys(pk, pk2), OP_EQ, 0); /* Test loading a invalid key type. */ tor_free(arg_str); @@ -81,9 +81,9 @@ test_add_onion_helper_keyarg(void *arg) tor_asprintf(&arg_str, "RSA512:%s", encoded); pk = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, &err_msg); - tt_assert(!pk); - tt_assert(!key_new_alg); - tt_assert(!key_new_blob); + tt_ptr_op(pk, OP_EQ, NULL); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_assert(err_msg); /* Test loading a invalid key. */ @@ -94,9 +94,9 @@ test_add_onion_helper_keyarg(void *arg) tor_asprintf(&arg_str, "RSA1024:%s", encoded); pk = add_onion_helper_keyarg(arg_str, 0, &key_new_alg, &key_new_blob, &err_msg); - tt_assert(!pk); - tt_assert(!key_new_alg); - tt_assert(!key_new_blob); + tt_ptr_op(pk, OP_EQ, NULL); + tt_ptr_op(key_new_alg, OP_EQ, NULL); + tt_ptr_op(key_new_blob, OP_EQ, NULL); tt_assert(err_msg); done: @@ -123,13 +123,13 @@ test_getinfo_helper_onion(void *arg) /* successfully get an empty answer */ rt = getinfo_helper_onions(&dummy, "onions/current", &answer, &errmsg); - tt_assert(rt == 0); + tt_int_op(rt, OP_EQ, 0); tt_str_op(answer, OP_EQ, ""); tor_free(answer); /* successfully get an empty answer */ rt = getinfo_helper_onions(&dummy, "onions/detached", &answer, &errmsg); - tt_assert(rt == 0); + tt_int_op(rt, OP_EQ, 0); tt_str_op(answer, OP_EQ, ""); tor_free(answer); @@ -138,7 +138,7 @@ test_getinfo_helper_onion(void *arg) dummy.ephemeral_onion_services = smartlist_new(); smartlist_add(dummy.ephemeral_onion_services, service_id); rt = getinfo_helper_onions(&dummy, "onions/current", &answer, &errmsg); - tt_assert(rt == 0); + tt_int_op(rt, OP_EQ, 0); tt_str_op(answer, OP_EQ, "dummy_onion_id"); done: @@ -159,25 +159,25 @@ test_rend_service_parse_port_config(void *arg) /* Test "VIRTPORT" only. */ cfg = rend_service_parse_port_config("80", sep, &err_msg); tt_assert(cfg); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); /* Test "VIRTPORT,TARGET" (Target is port). */ rend_service_port_config_free(cfg); cfg = rend_service_parse_port_config("80,8080", sep, &err_msg); tt_assert(cfg); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); /* Test "VIRTPORT,TARGET" (Target is IPv4:port). */ rend_service_port_config_free(cfg); cfg = rend_service_parse_port_config("80,192.0.2.1:8080", sep, &err_msg); tt_assert(cfg); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); /* Test "VIRTPORT,TARGET" (Target is IPv6:port). */ rend_service_port_config_free(cfg); cfg = rend_service_parse_port_config("80,[2001:db8::1]:8080", sep, &err_msg); tt_assert(cfg); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); rend_service_port_config_free(cfg); cfg = NULL; @@ -186,13 +186,13 @@ test_rend_service_parse_port_config(void *arg) /* Test empty config. */ rend_service_port_config_free(cfg); cfg = rend_service_parse_port_config("", sep, &err_msg); - tt_assert(!cfg); + tt_ptr_op(cfg, OP_EQ, NULL); tt_assert(err_msg); /* Test invalid port. */ tor_free(err_msg); cfg = rend_service_parse_port_config("90001", sep, &err_msg); - tt_assert(!cfg); + tt_ptr_op(cfg, OP_EQ, NULL); tt_assert(err_msg); tor_free(err_msg); @@ -204,7 +204,7 @@ test_rend_service_parse_port_config(void *arg) cfg = rend_service_parse_port_config("100 unix:\"/tmp/foo bar\"", " ", &err_msg); tt_assert(cfg); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); rend_service_port_config_free(cfg); cfg = NULL; @@ -213,14 +213,14 @@ test_rend_service_parse_port_config(void *arg) cfg = rend_service_parse_port_config("100 unix:\"/tmp/foo bar\"", " ", &err_msg); tt_assert(cfg); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); rend_service_port_config_free(cfg); cfg = NULL; /* quoted unix port, missing end quote */ cfg = rend_service_parse_port_config("100 unix:\"/tmp/foo bar", " ", &err_msg); - tt_assert(!cfg); + tt_ptr_op(cfg, OP_EQ, NULL); tt_str_op(err_msg, OP_EQ, "Couldn't process address <unix:\"/tmp/foo bar> " "from hidden service configuration"); tor_free(err_msg); @@ -230,7 +230,7 @@ test_rend_service_parse_port_config(void *arg) cfg = rend_service_parse_port_config("100 foo!!.example.com:9000", " ", &err_msg); UNMOCK(tor_addr_lookup); - tt_assert(!cfg); + tt_ptr_op(cfg, OP_EQ, NULL); tt_str_op(err_msg, OP_EQ, "Unparseable address in hidden service port " "configuration."); tor_free(err_msg); @@ -238,7 +238,7 @@ test_rend_service_parse_port_config(void *arg) /* bogus port port */ cfg = rend_service_parse_port_config("100 99999", " ", &err_msg); - tt_assert(!cfg); + tt_ptr_op(cfg, OP_EQ, NULL); tt_str_op(err_msg, OP_EQ, "Unparseable or out-of-range port \"99999\" " "in hidden service port configuration."); tor_free(err_msg); @@ -261,7 +261,7 @@ test_add_onion_helper_clientauth(void *arg) client = add_onion_helper_clientauth("alice", &created, &err_msg); tt_assert(client); tt_assert(created); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); rend_authorized_client_free(client); /* Test "ClientName:Blob" */ @@ -269,26 +269,26 @@ test_add_onion_helper_clientauth(void *arg) &created, &err_msg); tt_assert(client); tt_assert(!created); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); rend_authorized_client_free(client); /* Test invalid client names */ client = add_onion_helper_clientauth("no*asterisks*allowed", &created, &err_msg); - tt_assert(!client); + tt_ptr_op(client, OP_EQ, NULL); tt_assert(err_msg); tor_free(err_msg); /* Test invalid auth cookie */ client = add_onion_helper_clientauth("alice:12345", &created, &err_msg); - tt_assert(!client); + tt_ptr_op(client, OP_EQ, NULL); tt_assert(err_msg); tor_free(err_msg); /* Test invalid syntax */ client = add_onion_helper_clientauth(":475hGBHPlq7Mc0cRZitK/B", &created, &err_msg); - tt_assert(!client); + tt_ptr_op(client, OP_EQ, NULL); tt_assert(err_msg); tor_free(err_msg); @@ -544,7 +544,7 @@ cert_dl_status_def_for_auth_mock(const char *digest) download_status_t *dl = NULL; char digest_str[HEX_DIGEST_LEN+1]; - tt_assert(digest != NULL); + tt_ptr_op(digest, OP_NE, NULL); base16_encode(digest_str, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN); digest_str[HEX_DIGEST_LEN] = '\0'; @@ -568,7 +568,7 @@ cert_dl_status_sks_for_auth_id_mock(const char *digest) char *tmp; int len; - tt_assert(digest != NULL); + tt_ptr_op(digest, OP_NE, NULL); base16_encode(digest_str, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN); digest_str[HEX_DIGEST_LEN] = '\0'; @@ -622,11 +622,11 @@ cert_dl_status_fp_sk_mock(const char *fp_digest, const char *sk_digest) * dl status we want. */ - tt_assert(fp_digest != NULL); + tt_ptr_op(fp_digest, OP_NE, NULL); base16_encode(fp_digest_str, HEX_DIGEST_LEN + 1, fp_digest, DIGEST_LEN); fp_digest_str[HEX_DIGEST_LEN] = '\0'; - tt_assert(sk_digest != NULL); + tt_ptr_op(sk_digest, OP_NE, NULL); base16_encode(sk_digest_str, HEX_DIGEST_LEN + 1, sk_digest, DIGEST_LEN); sk_digest_str[HEX_DIGEST_LEN] = '\0'; @@ -706,7 +706,7 @@ descbr_get_dl_by_digest_mock(const char *digest) char digest_str[HEX_DIGEST_LEN+1]; if (!disable_descbr) { - tt_assert(digest != NULL); + tt_ptr_op(digest, OP_NE, NULL); base16_encode(digest_str, HEX_DIGEST_LEN + 1, digest, DIGEST_LEN); digest_str[HEX_DIGEST_LEN] = '\0'; @@ -773,7 +773,7 @@ test_download_status_consensus(void *arg) /* Check that the unknown prefix case works; no mocks needed yet */ getinfo_helper_downloads(&dummy, "downloads/foo", &answer, &errmsg); - tt_assert(answer == NULL); + tt_ptr_op(answer, OP_EQ, NULL); tt_str_op(errmsg, OP_EQ, "Unknown download status query"); setup_ns_mocks(); @@ -788,8 +788,8 @@ test_download_status_consensus(void *arg) sizeof(download_status_t)); getinfo_helper_downloads(&dummy, "downloads/networkstatus/ns", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_1_str); tor_free(answer); errmsg = NULL; @@ -799,8 +799,8 @@ test_download_status_consensus(void *arg) sizeof(download_status_t)); getinfo_helper_downloads(&dummy, "downloads/networkstatus/microdesc", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_2_str); tor_free(answer); errmsg = NULL; @@ -810,8 +810,8 @@ test_download_status_consensus(void *arg) sizeof(download_status_t)); getinfo_helper_downloads(&dummy, "downloads/networkstatus/ns/bootstrap", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_3_str); tor_free(answer); errmsg = NULL; @@ -822,8 +822,8 @@ test_download_status_consensus(void *arg) getinfo_helper_downloads(&dummy, "downloads/networkstatus/microdesc/bootstrap", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_4_str); tor_free(answer); errmsg = NULL; @@ -834,8 +834,8 @@ test_download_status_consensus(void *arg) getinfo_helper_downloads(&dummy, "downloads/networkstatus/ns/running", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_5_str); tor_free(answer); errmsg = NULL; @@ -846,8 +846,8 @@ test_download_status_consensus(void *arg) getinfo_helper_downloads(&dummy, "downloads/networkstatus/microdesc/running", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_6_str); tor_free(answer); errmsg = NULL; @@ -855,8 +855,8 @@ test_download_status_consensus(void *arg) /* Now check the error case */ getinfo_helper_downloads(&dummy, "downloads/networkstatus/foo", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Unknown flavor"); errmsg = NULL; @@ -890,8 +890,8 @@ test_download_status_cert(void *arg) getinfo_helper_downloads(&dummy, "downloads/cert/fps", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, auth_id_digest_expected_list); tor_free(answer); errmsg = NULL; @@ -900,10 +900,10 @@ test_download_status_cert(void *arg) memcpy(&auth_def_cert_download_status_1, &dls_sample_1, sizeof(download_status_t)); tor_asprintf(&question, "downloads/cert/fp/%s", auth_id_digest_1_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_1_str); tor_free(question); tor_free(answer); @@ -913,10 +913,10 @@ test_download_status_cert(void *arg) memcpy(&auth_def_cert_download_status_2, &dls_sample_2, sizeof(download_status_t)); tor_asprintf(&question, "downloads/cert/fp/%s", auth_id_digest_2_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_2_str); tor_free(question); tor_free(answer); @@ -924,10 +924,10 @@ test_download_status_cert(void *arg) /* Case 4 - list of signing key digests for 1st auth id */ tor_asprintf(&question, "downloads/cert/fp/%s/sks", auth_id_digest_1_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, auth_1_sk_digest_expected_list); tor_free(question); tor_free(answer); @@ -935,10 +935,10 @@ test_download_status_cert(void *arg) /* Case 5 - list of signing key digests for 2nd auth id */ tor_asprintf(&question, "downloads/cert/fp/%s/sks", auth_id_digest_2_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, auth_2_sk_digest_expected_list); tor_free(question); tor_free(answer); @@ -949,10 +949,10 @@ test_download_status_cert(void *arg) sizeof(download_status_t)); tor_asprintf(&question, "downloads/cert/fp/%s/%s", auth_id_digest_1_str, auth_1_sk_1_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_3_str); tor_free(question); tor_free(answer); @@ -963,10 +963,10 @@ test_download_status_cert(void *arg) sizeof(download_status_t)); tor_asprintf(&question, "downloads/cert/fp/%s/%s", auth_id_digest_1_str, auth_1_sk_2_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_4_str); tor_free(question); tor_free(answer); @@ -977,10 +977,10 @@ test_download_status_cert(void *arg) sizeof(download_status_t)); tor_asprintf(&question, "downloads/cert/fp/%s/%s", auth_id_digest_2_str, auth_2_sk_1_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_5_str); tor_free(question); tor_free(answer); @@ -991,10 +991,10 @@ test_download_status_cert(void *arg) sizeof(download_status_t)); tor_asprintf(&question, "downloads/cert/fp/%s/%s", auth_id_digest_2_str, auth_2_sk_2_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_6_str); tor_free(question); tor_free(answer); @@ -1005,8 +1005,8 @@ test_download_status_cert(void *arg) /* Case 1 - query is garbage after downloads/cert/ part */ getinfo_helper_downloads(&dummy, "downloads/cert/blahdeblah", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Unknown certificate download status query"); errmsg = NULL; @@ -1016,8 +1016,8 @@ test_download_status_cert(void *arg) */ getinfo_helper_downloads(&dummy, "downloads/cert/fp/2B1D36D32B2942406", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like a digest"); errmsg = NULL; @@ -1028,8 +1028,8 @@ test_download_status_cert(void *arg) getinfo_helper_downloads(&dummy, "downloads/cert/fp/82F52AF55D250115FE44D3GC81D49643241D56A1", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like a digest"); errmsg = NULL; @@ -1040,8 +1040,8 @@ test_download_status_cert(void *arg) getinfo_helper_downloads(&dummy, "downloads/cert/fp/AC4F23B5745BDD2A77997B85B1FD85D05C2E0F61", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Failed to get download status for this authority identity digest"); errmsg = NULL; @@ -1053,8 +1053,8 @@ test_download_status_cert(void *arg) getinfo_helper_downloads(&dummy, "downloads/cert/fp/82F52AF55D250115FE44D3GC81D49643241D56A1/blah", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like an identity digest"); errmsg = NULL; @@ -1065,8 +1065,8 @@ test_download_status_cert(void *arg) getinfo_helper_downloads(&dummy, "downloads/cert/fp/82F52AF55D25/blah", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like an identity digest"); errmsg = NULL; @@ -1077,8 +1077,8 @@ test_download_status_cert(void *arg) getinfo_helper_downloads(&dummy, "downloads/cert/fp/AC4F23B5745BDD2A77997B85B1FD85D05C2E0F61/sks", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Failed to get list of signing key digests for this authority " "identity digest"); @@ -1092,8 +1092,8 @@ test_download_status_cert(void *arg) "downloads/cert/fp/AC4F23B5745BDD2A77997B85B1FD85D05C2E0F61/" "82F52AF55D250115FE44D3GC81D49643241D56A1", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like a signing key digest"); errmsg = NULL; @@ -1105,8 +1105,8 @@ test_download_status_cert(void *arg) "downloads/cert/fp/AC4F23B5745BDD2A77997B85B1FD85D05C2E0F61/" "82F52AF55D250115FE44D", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like a signing key digest"); errmsg = NULL; @@ -1118,8 +1118,8 @@ test_download_status_cert(void *arg) "downloads/cert/fp/C6B05DF332F74DB9A13498EE3BBC7AA2F69FCB45/" "3A214FC21AE25B012C2ECCB5F4EC8A3602D0545D", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Failed to get download status for this identity/" "signing key digest pair"); @@ -1133,8 +1133,8 @@ test_download_status_cert(void *arg) "downloads/cert/fp/63CDD326DFEF0CA020BDD3FEB45A3286FE13A061/" "3A214FC21AE25B012C2ECCB5F4EC8A3602D0545D", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Failed to get download status for this identity/" "signing key digest pair"); @@ -1148,8 +1148,8 @@ test_download_status_cert(void *arg) "downloads/cert/fp/63CDD326DFEF0CA020BDD3FEB45A3286FE13A061/" "9451B8F1B10952384EB58B5F230C0BB701626C9B", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Failed to get download status for this identity/" "signing key digest pair"); @@ -1185,8 +1185,8 @@ test_download_status_desc(void *arg) getinfo_helper_downloads(&dummy, "downloads/desc/descs", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, descbr_expected_list); tor_free(answer); errmsg = NULL; @@ -1195,10 +1195,10 @@ test_download_status_desc(void *arg) memcpy(&descbr_digest_1_dl, &dls_sample_1, sizeof(download_status_t)); tor_asprintf(&question, "downloads/desc/%s", descbr_digest_1_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_1_str); tor_free(question); tor_free(answer); @@ -1208,10 +1208,10 @@ test_download_status_desc(void *arg) memcpy(&descbr_digest_2_dl, &dls_sample_2, sizeof(download_status_t)); tor_asprintf(&question, "downloads/desc/%s", descbr_digest_2_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_2_str); tor_free(question); tor_free(answer); @@ -1222,8 +1222,8 @@ test_download_status_desc(void *arg) /* Case 1 - non-digest-length garbage after downloads/desc */ getinfo_helper_downloads(&dummy, "downloads/desc/blahdeblah", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Unknown router descriptor download status query"); errmsg = NULL; @@ -1232,8 +1232,8 @@ test_download_status_desc(void *arg) &dummy, "downloads/desc/774EC52FD9A5B80A6FACZE536616E8022E3470AG", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like a digest"); errmsg = NULL; @@ -1242,8 +1242,8 @@ test_download_status_desc(void *arg) &dummy, "downloads/desc/B05B46135B0B2C04EBE1DD6A6AE4B12D7CD2226A", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "No such descriptor digest found"); errmsg = NULL; @@ -1252,8 +1252,8 @@ test_download_status_desc(void *arg) getinfo_helper_downloads(&dummy, "downloads/desc/descs", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "We don't seem to have a networkstatus-flavored consensus"); errmsg = NULL; @@ -1289,8 +1289,8 @@ test_download_status_bridge(void *arg) getinfo_helper_downloads(&dummy, "downloads/bridge/bridges", &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, descbr_expected_list); tor_free(answer); errmsg = NULL; @@ -1299,10 +1299,10 @@ test_download_status_bridge(void *arg) memcpy(&descbr_digest_1_dl, &dls_sample_3, sizeof(download_status_t)); tor_asprintf(&question, "downloads/bridge/%s", descbr_digest_1_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_3_str); tor_free(question); tor_free(answer); @@ -1312,10 +1312,10 @@ test_download_status_bridge(void *arg) memcpy(&descbr_digest_2_dl, &dls_sample_4, sizeof(download_status_t)); tor_asprintf(&question, "downloads/bridge/%s", descbr_digest_2_str); - tt_assert(question != NULL); + tt_ptr_op(question, OP_NE, NULL); getinfo_helper_downloads(&dummy, question, &answer, &errmsg); - tt_assert(answer != NULL); - tt_assert(errmsg == NULL); + tt_ptr_op(answer, OP_NE, NULL); + tt_ptr_op(errmsg, OP_EQ, NULL); tt_str_op(answer, OP_EQ, dls_sample_4_str); tor_free(question); tor_free(answer); @@ -1326,8 +1326,8 @@ test_download_status_bridge(void *arg) /* Case 1 - non-digest-length garbage after downloads/bridge */ getinfo_helper_downloads(&dummy, "downloads/bridge/blahdeblah", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "Unknown bridge descriptor download status query"); errmsg = NULL; @@ -1336,8 +1336,8 @@ test_download_status_bridge(void *arg) &dummy, "downloads/bridge/774EC52FD9A5B80A6FACZE536616E8022E3470AG", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "That didn't look like a digest"); errmsg = NULL; @@ -1346,8 +1346,8 @@ test_download_status_bridge(void *arg) &dummy, "downloads/bridge/B05B46135B0B2C04EBE1DD6A6AE4B12D7CD2226A", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "No such bridge identity digest found"); errmsg = NULL; @@ -1356,8 +1356,8 @@ test_download_status_bridge(void *arg) getinfo_helper_downloads(&dummy, "downloads/bridge/bridges", &answer, &errmsg); - tt_assert(answer == NULL); - tt_assert(errmsg != NULL); + tt_ptr_op(answer, OP_EQ, NULL); + tt_ptr_op(errmsg, OP_NE, NULL); tt_str_op(errmsg, OP_EQ, "We don't seem to be using bridges"); errmsg = NULL; disable_descbr = 0; diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index ec9d4e2709..c8443fd3bb 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -1139,7 +1139,7 @@ test_crypto_mac_sha3(void *arg) result = crypto_digest256(hmac_manual, all, all_len, DIGEST_SHA3_256); tor_free(key_msg_concat); tor_free(all); - tt_int_op(result, ==, 0); + tt_int_op(result, OP_EQ, 0); } /* Now compare the two results */ @@ -1208,12 +1208,12 @@ test_crypto_pk(void *arg) tt_assert(! crypto_pk_write_private_key_to_filename(pk1, get_fname("pkey1"))); /* failing case for read: can't read. */ - tt_assert(crypto_pk_read_private_key_from_filename(pk2, - get_fname("xyzzy")) < 0); + tt_int_op(crypto_pk_read_private_key_from_filename(pk2, get_fname("xyzzy")), + OP_LT, 0); write_str_to_file(get_fname("xyzzy"), "foobar", 6); /* Failing case for read: no key. */ - tt_assert(crypto_pk_read_private_key_from_filename(pk2, - get_fname("xyzzy")) < 0); + tt_int_op(crypto_pk_read_private_key_from_filename(pk2, get_fname("xyzzy")), + OP_LT, 0); tt_assert(! crypto_pk_read_private_key_from_filename(pk2, get_fname("pkey1"))); tt_int_op(15,OP_EQ, @@ -1245,17 +1245,17 @@ test_crypto_pk(void *arg) i = crypto_pk_asn1_encode(pk1, data1, 1024); tt_int_op(i, OP_GT, 0); pk2 = crypto_pk_asn1_decode(data1, i); - tt_assert(crypto_pk_cmp_keys(pk1,pk2) == 0); + tt_int_op(crypto_pk_cmp_keys(pk1, pk2), OP_EQ, 0); /* Try with hybrid encryption wrappers. */ crypto_rand(data1, 1024); for (i = 85; i < 140; ++i) { memset(data2,0,1024); memset(data3,0,1024); - len = crypto_pk_public_hybrid_encrypt(pk1,data2,sizeof(data2), + len = crypto_pk_obsolete_public_hybrid_encrypt(pk1,data2,sizeof(data2), data1,i,PK_PKCS1_OAEP_PADDING,0); tt_int_op(len, OP_GE, 0); - len = crypto_pk_private_hybrid_decrypt(pk1,data3,sizeof(data3), + len = crypto_pk_obsolete_private_hybrid_decrypt(pk1,data3,sizeof(data3), data2,len,PK_PKCS1_OAEP_PADDING,1); tt_int_op(len,OP_EQ, i); tt_mem_op(data1,OP_EQ, data3,i); @@ -1264,9 +1264,9 @@ test_crypto_pk(void *arg) /* Try copy_full */ crypto_pk_free(pk2); pk2 = crypto_pk_copy_full(pk1); - tt_assert(pk2 != NULL); + tt_ptr_op(pk2, OP_NE, NULL); tt_ptr_op(pk1, OP_NE, pk2); - tt_assert(crypto_pk_cmp_keys(pk1,pk2) == 0); + tt_int_op(crypto_pk_cmp_keys(pk1, pk2), OP_EQ, 0); done: if (pk1) @@ -1344,17 +1344,17 @@ test_crypto_pk_base64(void *arg) /* Test decoding a valid key. */ pk2 = crypto_pk_base64_decode(encoded, strlen(encoded)); tt_assert(pk2); - tt_assert(crypto_pk_cmp_keys(pk1,pk2) == 0); + tt_int_op(crypto_pk_cmp_keys(pk1, pk2), OP_EQ, 0); crypto_pk_free(pk2); /* Test decoding a invalid key (not Base64). */ static const char *invalid_b64 = "The key is in another castle!"; pk2 = crypto_pk_base64_decode(invalid_b64, strlen(invalid_b64)); - tt_assert(!pk2); + tt_ptr_op(pk2, OP_EQ, NULL); /* Test decoding a truncated Base64 blob. */ pk2 = crypto_pk_base64_decode(encoded, strlen(encoded)/2); - tt_assert(!pk2); + tt_ptr_op(pk2, OP_EQ, NULL); done: crypto_pk_free(pk1); @@ -1383,7 +1383,7 @@ do_truncate(const char *fname, size_t len) tor_free(bytes); return r; } -#endif +#endif /* defined(HAVE_TRUNCATE) */ /** Sanity check for crypto pk digests */ static void @@ -1406,6 +1406,7 @@ test_crypto_digests(void *arg) AUTHORITY_SIGNKEY_A_DIGEST, HEX_DIGEST_LEN); r = crypto_pk_get_common_digests(k, &pkey_digests); + tt_int_op(r, OP_EQ, 0); tt_mem_op(hex_str(pkey_digests.d[DIGEST_SHA1], DIGEST_LEN),OP_EQ, AUTHORITY_SIGNKEY_A_DIGEST, HEX_DIGEST_LEN); @@ -1495,7 +1496,7 @@ test_crypto_formats(void *arg) tt_mem_op(data1,OP_EQ, data3, DIGEST_LEN); tt_int_op(99,OP_EQ, data3[DIGEST_LEN+1]); - tt_assert(digest_from_base64(data3, "###") < 0); + tt_int_op(digest_from_base64(data3, "###"), OP_LT, 0); /* Encoding SHA256 */ crypto_rand(data2, DIGEST256_LEN); @@ -1906,7 +1907,7 @@ test_crypto_curve25519_impl(void *arg) "e0544770bc7de853b38f9100489e3e79"; const char e1e2k_expected[] = "cd6e8269104eb5aaee886bd2071fba88" "bd13861475516bc2cd2b6e005e805064"; -#else +#else /* !(defined(SLOW_CURVE25519_TEST)) */ const int loop_max=200; const char e1_expected[] = "bc7112cde03f97ef7008cad1bdc56be3" "c6a1037d74cceb3712e9206871dcf654"; @@ -1914,7 +1915,7 @@ test_crypto_curve25519_impl(void *arg) "8e3ee1a63c7d14274ea5d4c67f065467"; const char e1e2k_expected[] = "7ddb98bd89025d2347776b33901b3e7e" "c0ee98cb2257a4545c0cfb2ca3e1812b"; -#endif +#endif /* defined(SLOW_CURVE25519_TEST) */ unsigned char e1k[32]; unsigned char e2k[32]; @@ -2170,6 +2171,9 @@ test_crypto_ed25519_simple(void *arg) tt_int_op(0, OP_EQ, ed25519_public_key_generate(&pub1, &sec1)); tt_int_op(0, OP_EQ, ed25519_public_key_generate(&pub2, &sec1)); + tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, 0); + tt_int_op(ed25519_validate_pubkey(&pub2), OP_EQ, 0); + tt_mem_op(pub1.pubkey, OP_EQ, pub2.pubkey, sizeof(pub1.pubkey)); tt_assert(ed25519_pubkey_eq(&pub1, &pub2)); tt_assert(ed25519_pubkey_eq(&pub1, &pub1)); @@ -2541,6 +2545,39 @@ test_crypto_ed25519_blinding(void *arg) ; } +/** Test that our blinding functions will fail if we pass them bad pubkeys */ +static void +test_crypto_ed25519_blinding_fail(void *arg) +{ + int retval; + uint8_t param[32] = {2}; + ed25519_public_key_t pub; + ed25519_public_key_t pub_blinded; + + (void)arg; + + /* This point is not on the curve: the blind routines should fail */ + const char badkey[] = + "e19c65de75c68cf3b7643ea732ba9eb1a3d20d6d57ba223c2ece1df66feb5af0"; + retval = base16_decode((char*)pub.pubkey, sizeof(pub.pubkey), + badkey, strlen(badkey)); + tt_int_op(retval, OP_EQ, sizeof(pub.pubkey)); + retval = ed25519_public_blind(&pub_blinded, &pub, param); + tt_int_op(retval, OP_EQ, -1); + + /* This point is legit: blind routines should be happy */ + const char goodkey[] = + "4ba2e44760dff4c559ef3c38768c1c14a8a54740c782c8d70803e9d6e3ad8794"; + retval = base16_decode((char*)pub.pubkey, sizeof(pub.pubkey), + goodkey, strlen(goodkey)); + tt_int_op(retval, OP_EQ, sizeof(pub.pubkey)); + retval = ed25519_public_blind(&pub_blinded, &pub, param); + tt_int_op(retval, OP_EQ, 0); + + done: + ; +} + static void test_crypto_ed25519_testvectors(void *arg) { @@ -2558,6 +2595,8 @@ test_crypto_ed25519_testvectors(void *arg) ed25519_signature_t sig; int sign; + memset(&curvekp, 0xd0, sizeof(curvekp)); + #define DECODE(p,s) base16_decode((char*)(p),sizeof(p),(s),strlen(s)) #define EQ(a,h) test_memeq_hex((const char*)(a), (h)) @@ -2639,8 +2678,8 @@ test_crypto_ed25519_storage(void *arg) tor_free(tag); /* whitebox test: truncated keys. */ - tt_int_op(0, ==, do_truncate(fname_1, 40)); - tt_int_op(0, ==, do_truncate(fname_2, 40)); + tt_int_op(0, OP_EQ, do_truncate(fname_1, 40)); + tt_int_op(0, OP_EQ, do_truncate(fname_2, 40)); tt_int_op(-1, OP_EQ, ed25519_pubkey_read_from_file(&pub, &tag, fname_2)); tt_ptr_op(tag, OP_EQ, NULL); tor_free(tag); @@ -2832,6 +2871,67 @@ crypto_rand_check_failure_mode_predict(void) #undef FAILURE_MODE_BUFFER_SIZE +/** Test that our ed25519 validation function rejects evil public keys and + * accepts good ones. */ +static void +test_crypto_ed25519_validation(void *arg) +{ + (void) arg; + + int retval; + ed25519_public_key_t pub1; + + /* See https://lists.torproject.org/pipermail/tor-dev/2017-April/012230.html + for a list of points with torsion components in ed25519. */ + + { /* Point with torsion component (order 8l) */ + const char badkey[] = + "300ef2e64e588e1df55b48e4da0416ffb64cc85d5b00af6463d5cc6c2b1c185e"; + retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey), + badkey, strlen(badkey)); + tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey)); + tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1); + } + + { /* Point with torsion component (order 4l) */ + const char badkey[] = + "f43e3a046db8749164c6e69b193f1e942c7452e7d888736f40b98093d814d5e7"; + retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey), + badkey, strlen(badkey)); + tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey)); + tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1); + } + + { /* Point with torsion component (order 2l) */ + const char badkey[] = + "c9fff3af0471c28e33e98c2043e44f779d0427b1e37c521a6bddc011ed1869af"; + retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey), + badkey, strlen(badkey)); + tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey)); + tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1); + } + + { /* This point is not even on the curve */ + const char badkey[] = + "e19c65de75c68cf3b7643ea732ba9eb1a3d20d6d57ba223c2ece1df66feb5af0"; + retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey), + badkey, strlen(badkey)); + tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey)); + tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, -1); + } + + { /* This one is a good key */ + const char goodkey[] = + "4ba2e44760dff4c559ef3c38768c1c14a8a54740c782c8d70803e9d6e3ad8794"; + retval = base16_decode((char*)pub1.pubkey, sizeof(pub1.pubkey), + goodkey, strlen(goodkey)); + tt_int_op(retval, OP_EQ, sizeof(pub1.pubkey)); + tt_int_op(ed25519_validate_pubkey(&pub1), OP_EQ, 0); + } + + done: ; +} + static void test_crypto_failure_modes(void *arg) { @@ -2839,17 +2939,17 @@ test_crypto_failure_modes(void *arg) (void)arg; rv = crypto_early_init(); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); /* Check random works */ rv = crypto_rand_check_failure_mode_zero(); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); rv = crypto_rand_check_failure_mode_identical(); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); rv = crypto_rand_check_failure_mode_predict(); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); done: ; @@ -2917,7 +3017,9 @@ struct testcase_t crypto_tests[] = { ED25519_TEST(encode, 0), ED25519_TEST(convert, 0), ED25519_TEST(blinding, 0), + ED25519_TEST(blinding_fail, 0), ED25519_TEST(testvectors, 0), + ED25519_TEST(validation, 0), { "ed25519_storage", test_crypto_ed25519_storage, 0, NULL, NULL }, { "siphash", test_crypto_siphash, 0, NULL, NULL }, { "failure_modes", test_crypto_failure_modes, TT_FORK, NULL, NULL }, diff --git a/src/test/test_crypto_openssl.c b/src/test/test_crypto_openssl.c index 3d7d2b4639..090cb4242b 100644 --- a/src/test/test_crypto_openssl.c +++ b/src/test/test_crypto_openssl.c @@ -26,7 +26,7 @@ test_crypto_rng_engine(void *arg) memset(&dummy_method, 0, sizeof(dummy_method)); /* We should be a no-op if we're already on RAND_OpenSSL */ - tt_int_op(0, ==, crypto_force_rand_ssleay()); + tt_int_op(0, OP_EQ, crypto_force_rand_ssleay()); tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); /* We should correct the method if it's a dummy. */ @@ -34,11 +34,11 @@ test_crypto_rng_engine(void *arg) #ifdef LIBRESSL_VERSION_NUMBER /* On libressl, you can't override the RNG. */ tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); - tt_int_op(0, ==, crypto_force_rand_ssleay()); + tt_int_op(0, OP_EQ, crypto_force_rand_ssleay()); #else tt_assert(RAND_get_rand_method() == &dummy_method); - tt_int_op(1, ==, crypto_force_rand_ssleay()); -#endif + tt_int_op(1, OP_EQ, crypto_force_rand_ssleay()); +#endif /* defined(LIBRESSL_VERSION_NUMBER) */ tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); /* Make sure we aren't calling dummy_method */ diff --git a/src/test/test_crypto_slow.c b/src/test/test_crypto_slow.c index 75c6ba9aaa..2afb71ff5a 100644 --- a/src/test/test_crypto_slow.c +++ b/src/test/test_crypto_slow.c @@ -164,10 +164,10 @@ test_libscrypt_eq_openssl(void *arg) EVP_PBE_scrypt((const char *)"", 0, (const unsigned char *)"", 0, N, r, p, maxmem, buf2, dk_len); - tt_int_op(libscrypt_retval, ==, 0); - tt_int_op(openssl_retval, ==, 1); + tt_int_op(libscrypt_retval, OP_EQ, 0); + tt_int_op(openssl_retval, OP_EQ, 1); - tt_mem_op(buf1, ==, buf2, 64); + tt_mem_op(buf1, OP_EQ, buf2, 64); memset(buf1,0,64); memset(buf2,0,64); @@ -185,10 +185,10 @@ test_libscrypt_eq_openssl(void *arg) (const unsigned char *)"NaCl", strlen("NaCl"), N, r, p, maxmem, buf2, dk_len); - tt_int_op(libscrypt_retval, ==, 0); - tt_int_op(openssl_retval, ==, 1); + tt_int_op(libscrypt_retval, OP_EQ, 0); + tt_int_op(openssl_retval, OP_EQ, 1); - tt_mem_op(buf1, ==, buf2, 64); + tt_mem_op(buf1, OP_EQ, buf2, 64); memset(buf1,0,64); memset(buf2,0,64); @@ -210,10 +210,10 @@ test_libscrypt_eq_openssl(void *arg) strlen("SodiumChloride"), N, r, p, maxmem, buf2, dk_len); - tt_int_op(libscrypt_retval, ==, 0); - tt_int_op(openssl_retval, ==, 1); + tt_int_op(libscrypt_retval, OP_EQ, 0); + tt_int_op(openssl_retval, OP_EQ, 1); - tt_mem_op(buf1, ==, buf2, 64); + tt_mem_op(buf1, OP_EQ, buf2, 64); memset(buf1,0,64); memset(buf2,0,64); @@ -234,15 +234,15 @@ test_libscrypt_eq_openssl(void *arg) strlen("SodiumChloride"), N, r, p, maxmem, buf2, dk_len); - tt_int_op(libscrypt_retval, ==, 0); - tt_int_op(openssl_retval, ==, 1); + tt_int_op(libscrypt_retval, OP_EQ, 0); + tt_int_op(openssl_retval, OP_EQ, 1); - tt_mem_op(buf1, ==, buf2, 64); + tt_mem_op(buf1, OP_EQ, buf2, 64); done: return; } -#endif +#endif /* defined(HAVE_LIBSCRYPT) && defined(HAVE_EVP_PBE_SCRYPT) */ static void test_crypto_s2k_errors(void *arg) @@ -283,7 +283,7 @@ test_crypto_s2k_errors(void *arg) "ABC", 3, 0)); tt_int_op(S2K_TRUNCATED, OP_EQ, secret_to_key_new(buf, 50, &sz, "ABC", 3, S2K_FLAG_LOW_MEM)); -#endif +#endif /* defined(HAVE_LIBSCRYPT) */ tt_int_op(S2K_TRUNCATED, OP_EQ, secret_to_key_new(buf, 37, &sz, "ABC", 3, S2K_FLAG_USE_PBKDF2)); tt_int_op(S2K_TRUNCATED, OP_EQ, secret_to_key_new(buf, 29, &sz, @@ -318,7 +318,7 @@ test_crypto_s2k_errors(void *arg) tt_int_op(S2K_BAD_PARAMS, OP_EQ, secret_to_key_derivekey(buf2, sizeof(buf2), buf, 19, "ABC", 3)); -#endif +#endif /* defined(HAVE_LIBSCRYPT) */ done: ; @@ -516,7 +516,7 @@ test_crypto_ed25519_fuzz_donna(void *arg) unsigned i; (void)arg; - tt_assert(sizeof(msg) == iters); + tt_uint_op(iters, OP_EQ, sizeof(msg)); crypto_rand((char*) msg, sizeof(msg)); /* Fuzz Ed25519-donna vs ref10, alternating the implementation used to @@ -600,7 +600,7 @@ struct testcase_t slow_crypto_tests[] = { #ifdef HAVE_EVP_PBE_SCRYPT { "libscrypt_eq_openssl", test_libscrypt_eq_openssl, 0, NULL, NULL }, #endif -#endif +#endif /* defined(HAVE_LIBSCRYPT) */ { "s2k_pbkdf2", test_crypto_s2k_general, 0, &passthrough_setup, (void*)"pbkdf2" }, { "s2k_rfc2440_general", test_crypto_s2k_general, 0, &passthrough_setup, diff --git a/src/test/test_dir.c b/src/test/test_dir.c index a9d9cba7df..997b110d62 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -18,6 +18,7 @@ #define RELAY_PRIVATE #include "or.h" +#include "bridges.h" #include "confparse.h" #include "config.h" #include "control.h" @@ -275,8 +276,8 @@ test_dir_formats(void *arg) tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate); tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst); tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity); - tt_assert(crypto_pk_cmp_keys(rp1->onion_pkey, pk1) == 0); - tt_assert(crypto_pk_cmp_keys(rp1->identity_pkey, pk2) == 0); + tt_int_op(crypto_pk_cmp_keys(rp1->onion_pkey, pk1), OP_EQ, 0); + tt_int_op(crypto_pk_cmp_keys(rp1->identity_pkey, pk2), OP_EQ, 0); tt_assert(rp1->supports_tunnelled_dir_requests); //tt_assert(rp1->exit_policy == NULL); tor_free(buf); @@ -294,9 +295,9 @@ test_dir_formats(void *arg) strlcat(buf2, "master-key-ed25519 ", sizeof(buf2)); { char k[ED25519_BASE64_LEN+1]; - tt_assert(ed25519_public_to_base64(k, - &r2->cache_info.signing_key_cert->signing_key) - >= 0); + tt_int_op(ed25519_public_to_base64(k, + &r2->cache_info.signing_key_cert->signing_key), + OP_GE, 0); strlcat(buf2, k, sizeof(buf2)); strlcat(buf2, "\n", sizeof(buf2)); } @@ -392,8 +393,8 @@ test_dir_formats(void *arg) tt_mem_op(rp2->onion_curve25519_pkey->public_key,OP_EQ, r2->onion_curve25519_pkey->public_key, CURVE25519_PUBKEY_LEN); - tt_assert(crypto_pk_cmp_keys(rp2->onion_pkey, pk2) == 0); - tt_assert(crypto_pk_cmp_keys(rp2->identity_pkey, pk1) == 0); + tt_int_op(crypto_pk_cmp_keys(rp2->onion_pkey, pk2), OP_EQ, 0); + tt_int_op(crypto_pk_cmp_keys(rp2->identity_pkey, pk1), OP_EQ, 0); tt_assert(rp2->supports_tunnelled_dir_requests); tt_int_op(smartlist_len(rp2->exit_policy),OP_EQ, 2); @@ -422,7 +423,7 @@ test_dir_formats(void *arg) add_fingerprint_to_dir(buf, fingerprint_list, 0); } -#endif +#endif /* 0 */ dirserv_free_fingerprint_list(); done: @@ -478,34 +479,34 @@ test_dir_routerinfo_parsing(void *arg) routerinfo_free(ri); ri = router_parse_entry_from_string(EX_RI_MINIMAL, NULL, 0, 0, "@purpose bridge\n", NULL); - tt_assert(ri != NULL); + tt_ptr_op(ri, OP_NE, NULL); tt_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); routerinfo_free(ri); /* bad annotations prepended. */ ri = router_parse_entry_from_string(EX_RI_MINIMAL, NULL, 0, 0, "@purpose\n", NULL); - tt_assert(ri == NULL); + tt_ptr_op(ri, OP_EQ, NULL); /* bad annotations on router. */ ri = router_parse_entry_from_string("@purpose\nrouter x\n", NULL, 0, 1, NULL, NULL); - tt_assert(ri == NULL); + tt_ptr_op(ri, OP_EQ, NULL); /* unwanted annotations on router. */ ri = router_parse_entry_from_string("@purpose foo\nrouter x\n", NULL, 0, 0, NULL, NULL); - tt_assert(ri == NULL); + tt_ptr_op(ri, OP_EQ, NULL); /* No signature. */ ri = router_parse_entry_from_string("router x\n", NULL, 0, 0, NULL, NULL); - tt_assert(ri == NULL); + tt_ptr_op(ri, OP_EQ, NULL); /* Not a router */ routerinfo_free(ri); ri = router_parse_entry_from_string("hello\n", NULL, 0, 0, NULL, NULL); - tt_assert(ri == NULL); + tt_ptr_op(ri, OP_EQ, NULL); CHECK_FAIL(EX_RI_BAD_SIG1, 1); CHECK_FAIL(EX_RI_BAD_SIG2, 1); @@ -632,11 +633,11 @@ test_dir_extrainfo_parsing(void *arg) ADD(EX_EI_ED_MISPLACED_SIG); CHECK_OK(EX_EI_MINIMAL); - tt_assert(!ei->pending_sig); + tt_ptr_op(ei->pending_sig, OP_EQ, NULL); CHECK_OK(EX_EI_MAXIMAL); - tt_assert(!ei->pending_sig); + tt_ptr_op(ei->pending_sig, OP_EQ, NULL); CHECK_OK(EX_EI_GOOD_ED_EI); - tt_assert(!ei->pending_sig); + tt_ptr_op(ei->pending_sig, OP_EQ, NULL); CHECK_FAIL(EX_EI_BAD_SIG1,1); CHECK_FAIL(EX_EI_BAD_SIG2,1); @@ -1537,12 +1538,12 @@ test_dir_measured_bw_kb(void *arg) (void)arg; for (i = 0; strcmp(lines_fail[i], "end"); i++) { //fprintf(stderr, "Testing: %s\n", lines_fail[i]); - tt_assert(measured_bw_line_parse(&mbwl, lines_fail[i]) == -1); + tt_int_op(measured_bw_line_parse(&mbwl, lines_fail[i]), OP_EQ, -1); } for (i = 0; strcmp(lines_pass[i], "end"); i++) { //fprintf(stderr, "Testing: %s %d\n", lines_pass[i], TOR_ISSPACE('\n')); - tt_assert(measured_bw_line_parse(&mbwl, lines_pass[i]) == 0); + tt_int_op(measured_bw_line_parse(&mbwl, lines_pass[i]), OP_EQ, 0); tt_assert(mbwl.bw_kb == 1024); tt_assert(strcmp(mbwl.node_hex, "557365204145532d32353620696e73746561642e") == 0); @@ -1785,7 +1786,7 @@ test_dir_param_voting_lookup(void *arg) dirvote_get_intermediate_param_value(lst, "moomin", -100)); tt_int_op(smartlist_len(tor_get_captured_bug_log_()), OP_EQ, 1); tt_str_op(smartlist_get(tor_get_captured_bug_log_(), 0), OP_EQ, - "!(n_found > 1)"); + "n_found == 0"); tor_end_capture_bugs_(); /* There is no 'fred=', so that is treated as not existing. */ tt_int_op(-100, OP_EQ, @@ -1873,8 +1874,7 @@ vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now) measured_bw_line_t mbw; memset(mbw.node_id, 33, sizeof(mbw.node_id)); mbw.bw_kb = 1024; - tt_assert(measured_bw_line_apply(&mbw, - v->routerstatus_list) == 1); + tt_int_op(measured_bw_line_apply(&mbw, v->routerstatus_list), OP_EQ, 1); } else if (voter == 2 || voter == 3) { /* Monkey around with the list a bit */ vrs = smartlist_get(v->routerstatus_list, 2); @@ -1990,7 +1990,7 @@ test_consensus_for_v3ns(networkstatus_t *con, time_t now) (void)now; tt_assert(con); - tt_assert(!con->cert); + tt_ptr_op(con->cert, OP_EQ, NULL); tt_int_op(2,OP_EQ, smartlist_len(con->routerstatus_list)); /* There should be two listed routers: one with identity 3, one with * identity 5. */ @@ -2060,7 +2060,7 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now) /* XXXX check version */ } else { /* Weren't expecting this... */ - tt_assert(0); + tt_abort(); } done: @@ -2252,6 +2252,7 @@ test_dir_networkstatus_compute_bw_weights_v10(void *arg) tt_i64_op(G+M+E+D, OP_EQ, T); ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T, weight_scale); + tt_assert(ret); tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=883 Wbe=0 " "Wbg=3673 Wbm=10000 Wdb=10000 Web=10000 Wed=8233 Wee=10000 Weg=8233 " "Wem=10000 Wgb=10000 Wgd=883 Wgg=6327 Wgm=6327 Wmb=10000 Wmd=883 Wme=0 " @@ -2268,6 +2269,7 @@ test_dir_networkstatus_compute_bw_weights_v10(void *arg) tt_i64_op(G+M+E+D, OP_EQ, T); ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T, weight_scale); + tt_assert(ret); tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 Wbe=0 " "Wbg=4194 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 " "Wem=10000 Wgb=10000 Wgd=0 Wgg=5806 Wgm=5806 Wmb=10000 Wmd=0 Wme=0 " @@ -2284,6 +2286,7 @@ test_dir_networkstatus_compute_bw_weights_v10(void *arg) tt_i64_op(G+M+E+D, OP_EQ, T); ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T, weight_scale); + tt_assert(ret); tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=317 " "Wbe=5938 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=9366 Wee=4061 " "Weg=9366 Wem=4061 Wgb=10000 Wgd=317 Wgg=10000 Wgm=10000 Wmb=10000 " @@ -2304,6 +2307,7 @@ test_dir_networkstatus_compute_bw_weights_v10(void *arg) "Wbe=0 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=3333 Wee=10000 Weg=3333 " "Wem=10000 Wgb=10000 Wgd=3333 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=3333 " "Wme=0 Wmg=0 Wmm=10000\n"); + tt_assert(ret); done: SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp)); @@ -2375,6 +2379,7 @@ test_a_networkstatus( sign_skey_2 = crypto_pk_new(); sign_skey_3 = crypto_pk_new(); sign_skey_leg1 = pk_generate(4); + dirvote_recalculate_timing(get_options(), now); sr_state_init(0, 0); tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_1, @@ -3004,7 +3009,7 @@ gen_routerstatus_for_umbw(int idx, time_t now) break; default: /* Shouldn't happen */ - tt_assert(0); + tt_abort(); } if (vrs) { vrs->microdesc = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); @@ -3144,7 +3149,7 @@ test_vrs_for_umbw(vote_routerstatus_t *vrs, int voter, time_t now) tt_int_op(rs->bandwidth_kb,OP_EQ, max_unmeasured_bw_kb / 2); tt_int_op(vrs->measured_bw_kb,OP_EQ, 0); } else { - tt_assert(0); + tt_abort(); } done: @@ -3160,9 +3165,9 @@ test_consensus_for_umbw(networkstatus_t *con, time_t now) (void)now; tt_assert(con); - tt_assert(!con->cert); + tt_ptr_op(con->cert, OP_EQ, NULL); // tt_assert(con->consensus_method >= MIN_METHOD_TO_CLIP_UNMEASURED_BW_KB); - tt_assert(con->consensus_method >= 16); + tt_int_op(con->consensus_method, OP_GE, 16); tt_int_op(4,OP_EQ, smartlist_len(con->routerstatus_list)); /* There should be four listed routers; all voters saw the same in this */ @@ -3259,7 +3264,7 @@ test_routerstatus_for_umbw(routerstatus_t *rs, time_t now) tt_assert(rs->bw_is_unmeasured); } else { /* Weren't expecting this... */ - tt_assert(0); + tt_abort(); } done: @@ -3366,7 +3371,7 @@ mock_get_options(void) static void reset_routerstatus(routerstatus_t *rs, const char *hex_identity_digest, - int32_t ipv4_addr) + uint32_t ipv4_addr) { memset(rs, 0, sizeof(routerstatus_t)); base16_decode(rs->identity_digest, sizeof(rs->identity_digest), @@ -3427,15 +3432,17 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) * Return values are {2, 3, 4} */ /* We want 3 ("*" means match all addresses) */ - tt_assert(routerset_contains_routerstatus(routerset_all, rs_a, 0) == 3); - tt_assert(routerset_contains_routerstatus(routerset_all, rs_b, 0) == 3); + tt_int_op(routerset_contains_routerstatus(routerset_all, rs_a, 0), OP_EQ, 3); + tt_int_op(routerset_contains_routerstatus(routerset_all, rs_b, 0), OP_EQ, 3); /* We want 4 (match id_digest [or nickname]) */ - tt_assert(routerset_contains_routerstatus(routerset_a, rs_a, 0) == 4); - tt_assert(routerset_contains_routerstatus(routerset_a, rs_b, 0) == 0); + tt_int_op(routerset_contains_routerstatus(routerset_a, rs_a, 0), OP_EQ, 4); + tt_int_op(routerset_contains_routerstatus(routerset_a, rs_b, 0), OP_EQ, 0); - tt_assert(routerset_contains_routerstatus(routerset_none, rs_a, 0) == 0); - tt_assert(routerset_contains_routerstatus(routerset_none, rs_b, 0) == 0); + tt_int_op(routerset_contains_routerstatus(routerset_none, rs_a, 0), OP_EQ, + 0); + tt_int_op(routerset_contains_routerstatus(routerset_none, rs_b, 0), OP_EQ, + 0); /* Check that "*" sets flags on all routers: Exit * Check the flags aren't being confused with each other */ @@ -3447,17 +3454,17 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) mock_options->TestingDirAuthVoteExitIsStrict = 0; dirserv_set_routerstatus_testing(rs_a); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 2); + tt_int_op(mock_get_options_calls, OP_EQ, 2); - tt_assert(rs_a->is_exit == 1); - tt_assert(rs_b->is_exit == 1); + tt_uint_op(rs_a->is_exit, OP_EQ, 1); + tt_uint_op(rs_b->is_exit, OP_EQ, 1); /* Be paranoid - check no other flags are set */ - tt_assert(rs_a->is_possible_guard == 0); - tt_assert(rs_b->is_possible_guard == 0); - tt_assert(rs_a->is_hs_dir == 0); - tt_assert(rs_b->is_hs_dir == 0); + tt_uint_op(rs_a->is_possible_guard, OP_EQ, 0); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 0); + tt_uint_op(rs_a->is_hs_dir, OP_EQ, 0); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 0); /* Check that "*" sets flags on all routers: Guard & HSDir * Cover the remaining flags in one test */ @@ -3471,17 +3478,17 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) mock_options->TestingDirAuthVoteHSDirIsStrict = 0; dirserv_set_routerstatus_testing(rs_a); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 2); + tt_int_op(mock_get_options_calls, OP_EQ, 2); - tt_assert(rs_a->is_possible_guard == 1); - tt_assert(rs_b->is_possible_guard == 1); - tt_assert(rs_a->is_hs_dir == 1); - tt_assert(rs_b->is_hs_dir == 1); + tt_uint_op(rs_a->is_possible_guard, OP_EQ, 1); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 1); + tt_uint_op(rs_a->is_hs_dir, OP_EQ, 1); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 1); /* Be paranoid - check exit isn't set */ - tt_assert(rs_a->is_exit == 0); - tt_assert(rs_b->is_exit == 0); + tt_uint_op(rs_a->is_exit, OP_EQ, 0); + tt_uint_op(rs_b->is_exit, OP_EQ, 0); /* Check routerset A sets all flags on router A, * but leaves router B unmodified */ @@ -3497,16 +3504,16 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) mock_options->TestingDirAuthVoteHSDirIsStrict = 0; dirserv_set_routerstatus_testing(rs_a); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 2); + tt_int_op(mock_get_options_calls, OP_EQ, 2); - tt_assert(rs_a->is_exit == 1); - tt_assert(rs_b->is_exit == 0); - tt_assert(rs_a->is_possible_guard == 1); - tt_assert(rs_b->is_possible_guard == 0); - tt_assert(rs_a->is_hs_dir == 1); - tt_assert(rs_b->is_hs_dir == 0); + tt_uint_op(rs_a->is_exit, OP_EQ, 1); + tt_uint_op(rs_b->is_exit, OP_EQ, 0); + tt_uint_op(rs_a->is_possible_guard, OP_EQ, 1); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 0); + tt_uint_op(rs_a->is_hs_dir, OP_EQ, 1); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 0); /* Check routerset A unsets all flags on router B when Strict is set */ reset_options(mock_options, &mock_get_options_calls); @@ -3524,11 +3531,11 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) rs_b->is_hs_dir = 1; dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); - tt_assert(rs_b->is_exit == 0); - tt_assert(rs_b->is_possible_guard == 0); - tt_assert(rs_b->is_hs_dir == 0); + tt_uint_op(rs_b->is_exit, OP_EQ, 0); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 0); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 0); /* Check routerset A doesn't modify flags on router B without Strict set */ reset_options(mock_options, &mock_get_options_calls); @@ -3546,11 +3553,11 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) rs_b->is_hs_dir = 1; dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); - tt_assert(rs_b->is_exit == 1); - tt_assert(rs_b->is_possible_guard == 1); - tt_assert(rs_b->is_hs_dir == 1); + tt_uint_op(rs_b->is_exit, OP_EQ, 1); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 1); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 1); /* Check the empty routerset zeroes all flags * on routers A & B with Strict set */ @@ -3569,11 +3576,11 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) rs_b->is_hs_dir = 1; dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); - tt_assert(rs_b->is_exit == 0); - tt_assert(rs_b->is_possible_guard == 0); - tt_assert(rs_b->is_hs_dir == 0); + tt_uint_op(rs_b->is_exit, OP_EQ, 0); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 0); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 0); /* Check the empty routerset doesn't modify any flags * on A or B without Strict set */ @@ -3593,16 +3600,16 @@ test_dir_dirserv_set_routerstatus_testing(void *arg) rs_b->is_hs_dir = 1; dirserv_set_routerstatus_testing(rs_a); - tt_assert(mock_get_options_calls == 1); + tt_int_op(mock_get_options_calls, OP_EQ, 1); dirserv_set_routerstatus_testing(rs_b); - tt_assert(mock_get_options_calls == 2); + tt_int_op(mock_get_options_calls, OP_EQ, 2); - tt_assert(rs_a->is_exit == 0); - tt_assert(rs_a->is_possible_guard == 0); - tt_assert(rs_a->is_hs_dir == 0); - tt_assert(rs_b->is_exit == 1); - tt_assert(rs_b->is_possible_guard == 1); - tt_assert(rs_b->is_hs_dir == 1); + tt_uint_op(rs_a->is_exit, OP_EQ, 0); + tt_uint_op(rs_a->is_possible_guard, OP_EQ, 0); + tt_uint_op(rs_a->is_hs_dir, OP_EQ, 0); + tt_uint_op(rs_b->is_exit, OP_EQ, 1); + tt_uint_op(rs_b->is_possible_guard, OP_EQ, 1); + tt_uint_op(rs_b->is_hs_dir, OP_EQ, 1); done: tor_free(mock_options); @@ -3658,7 +3665,7 @@ test_dir_http_handling(void *args) "User-Agent: Mozilla/5.0 (Windows;" " U; Windows NT 6.1; en-US; rv:1.9.1.5)\r\n", &url),OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); /* Bad headers */ tt_int_op(parse_http_url("GET /a/b/c.txt\r\n" @@ -3666,23 +3673,23 @@ test_dir_http_handling(void *args) "User-Agent: Mozilla/5.0 (Windows;" " U; Windows NT 6.1; en-US; rv:1.9.1.5)\r\n", &url),OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); tt_int_op(parse_http_url("GET /tor/a/b/c.txt", &url),OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); tt_int_op(parse_http_url("GET /tor/a/b/c.txt HTTP/1.1", &url),OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); tt_int_op(parse_http_url("GET /tor/a/b/c.txt HTTP/1.1x\r\n", &url), OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); tt_int_op(parse_http_url("GET /tor/a/b/c.txt HTTP/1.", &url),OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); tt_int_op(parse_http_url("GET /tor/a/b/c.txt HTTP/1.\r", &url),OP_EQ, -1); - tt_assert(!url); + tt_ptr_op(url, OP_EQ, NULL); done: tor_free(url); @@ -3695,8 +3702,8 @@ test_dir_purpose_needs_anonymity_returns_true_by_default(void *arg) tor_capture_bugs_(1); setup_full_capture_of_logs(LOG_WARN); - tt_int_op(1, ==, purpose_needs_anonymity(0, 0, NULL)); - tt_int_op(1, ==, smartlist_len(tor_get_captured_bug_log_())); + tt_int_op(1, OP_EQ, purpose_needs_anonymity(0, 0, NULL)); + tt_int_op(1, OP_EQ, smartlist_len(tor_get_captured_bug_log_())); expect_single_log_msg_containing("Called with dir_purpose=0"); tor_end_capture_bugs_(); @@ -3710,11 +3717,12 @@ test_dir_purpose_needs_anonymity_returns_true_for_bridges(void *arg) { (void)arg; - tt_int_op(1, ==, purpose_needs_anonymity(0, ROUTER_PURPOSE_BRIDGE, NULL)); - tt_int_op(1, ==, purpose_needs_anonymity(0, ROUTER_PURPOSE_BRIDGE, + tt_int_op(1, OP_EQ, purpose_needs_anonymity(0, ROUTER_PURPOSE_BRIDGE, NULL)); + tt_int_op(1, OP_EQ, purpose_needs_anonymity(0, ROUTER_PURPOSE_BRIDGE, "foobar")); - tt_int_op(1, ==, purpose_needs_anonymity(DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2, - ROUTER_PURPOSE_BRIDGE, NULL)); + tt_int_op(1, OP_EQ, + purpose_needs_anonymity(DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2, + ROUTER_PURPOSE_BRIDGE, NULL)); done: ; } @@ -3722,7 +3730,7 @@ static void test_dir_purpose_needs_anonymity_returns_false_for_own_bridge_desc(void *arg) { (void)arg; - tt_int_op(0, ==, purpose_needs_anonymity(DIR_PURPOSE_FETCH_SERVERDESC, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, "authority.z")); done: ; @@ -3733,12 +3741,12 @@ test_dir_purpose_needs_anonymity_returns_true_for_sensitive_purpose(void *arg) { (void)arg; - tt_int_op(1, ==, purpose_needs_anonymity( + tt_int_op(1, OP_EQ, purpose_needs_anonymity( DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2, ROUTER_PURPOSE_GENERAL, NULL)); - tt_int_op(1, ==, purpose_needs_anonymity( + tt_int_op(1, OP_EQ, purpose_needs_anonymity( DIR_PURPOSE_UPLOAD_RENDDESC_V2, 0, NULL)); - tt_int_op(1, ==, purpose_needs_anonymity( + tt_int_op(1, OP_EQ, purpose_needs_anonymity( DIR_PURPOSE_FETCH_RENDDESC_V2, 0, NULL)); done: ; } @@ -3748,24 +3756,25 @@ test_dir_purpose_needs_anonymity_ret_false_for_non_sensitive_conn(void *arg) { (void)arg; - tt_int_op(0, ==, purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_DIR, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_DIR, ROUTER_PURPOSE_GENERAL, NULL)); - tt_int_op(0, ==, purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_VOTE, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, + purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_VOTE, 0, NULL)); + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_SIGNATURES, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL)); - tt_int_op(0, ==, purpose_needs_anonymity( + tt_int_op(0, OP_EQ, purpose_needs_anonymity( DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_CONSENSUS, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_SERVERDESC, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_EXTRAINFO, 0, NULL)); - tt_int_op(0, ==, + tt_int_op(0, OP_EQ, purpose_needs_anonymity(DIR_PURPOSE_FETCH_MICRODESC, 0, NULL)); done: ; } @@ -3818,9 +3827,9 @@ test_dir_packages(void *arg) (void)arg; #define BAD(s) \ - tt_int_op(0, ==, validate_recommended_package_line(s)); + tt_int_op(0, OP_EQ, validate_recommended_package_line(s)); #define GOOD(s) \ - tt_int_op(1, ==, validate_recommended_package_line(s)); + tt_int_op(1, OP_EQ, validate_recommended_package_line(s)); GOOD("tor 0.2.6.3-alpha " "http://torproject.example.com/dist/tor-0.2.6.3-alpha.tar.gz " "sha256=sssdlkfjdsklfjdskfljasdklfj"); @@ -3937,7 +3946,7 @@ test_dir_packages(void *arg) res = compute_consensus_package_lines(votes); tt_assert(res); - tt_str_op(res, ==, + tt_str_op(res, OP_EQ, "package cbc 99.1.11.1.1 http://example.com/cbc/ cubehash=ahooy sha512=m\n" "package clownshoes 22alpha3 http://quumble.example.com/ blake2=fooz\n" "package clownshoes 22alpha4 http://quumble.example.cam/ blake2=fooa\n" @@ -4109,47 +4118,105 @@ test_dir_download_status_schedule(void *arg) } static void -test_dir_download_status_random_backoff(void *arg) +download_status_random_backoff_helper(int min_delay, int max_delay) { download_status_t dls_random = { 0, 0, 0, DL_SCHED_GENERIC, DL_WANT_AUTHORITY, DL_SCHED_INCREMENT_FAILURE, DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 }; int increment = -1; - int old_increment; + int old_increment = -1; time_t current_time = time(NULL); - const int min_delay = 0; - const int max_delay = 1000000; - - (void)arg; /* Check the random backoff cases */ - old_increment = 0; + int n_attempts = 0; do { increment = download_status_schedule_get_delay(&dls_random, NULL, min_delay, max_delay, current_time); + + log_debug(LD_DIR, "Min: %d, Max: %d, Inc: %d, Old Inc: %d", + min_delay, max_delay, increment, old_increment); + + /* Regression test for 20534 and friends + * increment must always increase after the first */ + if (dls_random.last_backoff_position > 0 && max_delay > 0) { + /* Always increment the exponential backoff */ + tt_int_op(increment, OP_GE, 1); + } + /* Test */ tt_int_op(increment, OP_GE, min_delay); tt_int_op(increment, OP_LE, max_delay); - tt_int_op(increment, OP_GE, old_increment); - /* We at most quadruple, and maybe add one */ - tt_int_op(increment, OP_LE, 4 * old_increment + 1); /* Advance */ - current_time += increment; - ++(dls_random.n_download_attempts); - ++(dls_random.n_download_failures); + if (dls_random.n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD - 1) { + ++(dls_random.n_download_attempts); + ++(dls_random.n_download_failures); + } /* Try another maybe */ old_increment = increment; - } while (increment < max_delay); + } while (increment < max_delay && ++n_attempts < 1000); done: return; } static void +test_dir_download_status_random_backoff(void *arg) +{ + (void)arg; + + /* Do a standard test */ + download_status_random_backoff_helper(0, 1000000); + /* Regression test for 20534 and friends: + * try tighter bounds */ + download_status_random_backoff_helper(0, 100); + /* regression tests for 17750: initial delay */ + download_status_random_backoff_helper(10, 1000); + download_status_random_backoff_helper(20, 30); + + /* Pathological cases */ + download_status_random_backoff_helper(0, 0); + download_status_random_backoff_helper(1, 1); + download_status_random_backoff_helper(0, INT_MAX); + download_status_random_backoff_helper(INT_MAX/2, INT_MAX); +} + +static void +test_dir_download_status_random_backoff_ranges(void *arg) +{ + (void)arg; + int lo, hi; + next_random_exponential_delay_range(&lo, &hi, 0, 10); + tt_int_op(lo, OP_EQ, 10); + tt_int_op(hi, OP_EQ, 11); + + next_random_exponential_delay_range(&lo, &hi, 6, 10); + tt_int_op(lo, OP_EQ, 10); + tt_int_op(hi, OP_EQ, 6*3); + + next_random_exponential_delay_range(&lo, &hi, 13, 10); + tt_int_op(lo, OP_EQ, 10); + tt_int_op(hi, OP_EQ, 13 * 3); + + next_random_exponential_delay_range(&lo, &hi, 37, 10); + tt_int_op(lo, OP_EQ, 10); + tt_int_op(hi, OP_EQ, 111); + + next_random_exponential_delay_range(&lo, &hi, 123, 10); + tt_int_op(lo, OP_EQ, 10); + tt_int_op(hi, OP_EQ, 369); + + next_random_exponential_delay_range(&lo, &hi, INT_MAX-5, 10); + tt_int_op(lo, OP_EQ, 10); + tt_int_op(hi, OP_EQ, INT_MAX); + done: + ; +} + +static void test_dir_download_status_increment(void *arg) { (void)arg; @@ -4161,32 +4228,97 @@ test_dir_download_status_increment(void *arg) DL_WANT_ANY_DIRSERVER, DL_SCHED_INCREMENT_ATTEMPT, DL_SCHED_DETERMINISTIC, 0, 0 }; + download_status_t dls_exp = { 0, 0, 0, DL_SCHED_GENERIC, + DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT, + DL_SCHED_RANDOM_EXPONENTIAL, 0, 0 }; + int no_delay = 0; int delay0 = -1; int delay1 = -1; int delay2 = -1; smartlist_t *schedule = smartlist_new(); + smartlist_t *schedule_no_initial_delay = smartlist_new(); or_options_t test_options; time_t next_at = TIME_MAX; time_t current_time = time(NULL); - /* Provide some values for the schedule */ + /* Provide some values for the schedules */ delay0 = 10; delay1 = 99; delay2 = 20; - /* Make the schedule */ + /* Make the schedules */ smartlist_add(schedule, (void *)&delay0); smartlist_add(schedule, (void *)&delay1); smartlist_add(schedule, (void *)&delay2); + smartlist_add(schedule_no_initial_delay, (void *)&no_delay); + smartlist_add(schedule_no_initial_delay, (void *)&delay1); + smartlist_add(schedule_no_initial_delay, (void *)&delay2); + /* Put it in the options */ mock_options = &test_options; reset_options(mock_options, &mock_get_options_calls); + mock_options->TestingBridgeBootstrapDownloadSchedule = schedule; mock_options->TestingClientDownloadSchedule = schedule; - mock_options->TestingBridgeDownloadSchedule = schedule; MOCK(get_options, mock_get_options); + /* Check that the initial value of the schedule is the first value used, + * whether or not it was reset before being used */ + + /* regression test for 17750: no initial delay */ + mock_options->TestingClientDownloadSchedule = schedule_no_initial_delay; + mock_get_options_calls = 0; + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_failure) + >= current_time + no_delay); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + != TIME_MAX); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); + + /* regression test for 17750: initial delay */ + mock_options->TestingClientDownloadSchedule = schedule; + mock_get_options_calls = 0; + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_failure) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + != TIME_MAX); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); + + /* regression test for 17750: exponential, no initial delay */ + mock_options->TestingClientDownloadSchedule = schedule_no_initial_delay; + mock_get_options_calls = 0; + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_exp) + >= current_time + no_delay); + tt_assert(download_status_get_next_attempt_at(&dls_exp) + != TIME_MAX); + tt_int_op(download_status_get_n_failures(&dls_exp), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_exp), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); + + /* regression test for 17750: exponential, initial delay */ + mock_options->TestingClientDownloadSchedule = schedule; + mock_get_options_calls = 0; + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_exp) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_exp) + != TIME_MAX); + tt_int_op(download_status_get_n_failures(&dls_exp), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_exp), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); + /* Check that a failure reset works */ mock_get_options_calls = 0; download_status_reset(&dls_failure); @@ -4196,76 +4328,76 @@ test_dir_download_status_increment(void *arg) >= current_time + delay0); tt_assert(download_status_get_next_attempt_at(&dls_failure) != TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_failure) == 0); - tt_assert(download_status_get_n_attempts(&dls_failure) == 0); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* avoid timing inconsistencies */ dls_failure.next_attempt_at = current_time + delay0; /* check that a reset schedule becomes ready at the right time */ - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay0 - 1, - 1) == 0); - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay0, - 1) == 1); - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay0 + 1, - 1) == 1); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay0 - 1, 1), + OP_EQ, 0); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay0, 1), + OP_EQ, 1); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay0 + 1, 1), + OP_EQ, 1); /* Check that a failure increment works */ mock_get_options_calls = 0; next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, current_time); tt_assert(next_at == current_time + delay1); - tt_assert(download_status_get_n_failures(&dls_failure) == 1); - tt_assert(download_status_get_n_attempts(&dls_failure) == 1); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 1); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 1); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* check that an incremented schedule becomes ready at the right time */ - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay1 - 1, - 1) == 0); - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay1, - 1) == 1); - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay1 + 1, - 1) == 1); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay1 - 1, 1), + OP_EQ, 0); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay1, 1), + OP_EQ, 1); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay1 + 1, 1), + OP_EQ, 1); /* check that a schedule isn't ready if it's had too many failures */ - tt_assert(download_status_is_ready(&dls_failure, - current_time + delay1 + 10, - 0) == 0); + tt_int_op(download_status_is_ready(&dls_failure, + current_time + delay1 + 10, 0), + OP_EQ, 0); /* Check that failure increments do happen on 503 for clients, and * attempt increments do too. */ mock_get_options_calls = 0; next_at = download_status_increment_failure(&dls_failure, 503, "test", 0, current_time); - tt_i64_op(next_at, ==, current_time + delay2); - tt_int_op(download_status_get_n_failures(&dls_failure), ==, 2); - tt_int_op(download_status_get_n_attempts(&dls_failure), ==, 2); - tt_assert(mock_get_options_calls >= 1); + tt_i64_op(next_at, OP_EQ, current_time + delay2); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 2); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 2); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check that failure increments do happen on 503 for servers */ mock_get_options_calls = 0; next_at = download_status_increment_failure(&dls_failure, 503, "test", 1, current_time); tt_assert(next_at == current_time + delay2); - tt_assert(download_status_get_n_failures(&dls_failure) == 3); - tt_assert(download_status_get_n_attempts(&dls_failure) == 3); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 3); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 3); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check what happens when we run off the end of the schedule */ mock_get_options_calls = 0; next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, current_time); tt_assert(next_at == current_time + delay2); - tt_assert(download_status_get_n_failures(&dls_failure) == 4); - tt_assert(download_status_get_n_attempts(&dls_failure) == 4); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 4); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 4); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check what happens when we hit the failure limit */ mock_get_options_calls = 0; @@ -4273,22 +4405,22 @@ test_dir_download_status_increment(void *arg) next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, current_time); tt_assert(next_at == TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_failure) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(download_status_get_n_attempts(&dls_failure) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check that a failure reset doesn't reset at the limit */ mock_get_options_calls = 0; download_status_reset(&dls_failure); tt_assert(download_status_get_next_attempt_at(&dls_failure) == TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_failure) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(download_status_get_n_attempts(&dls_failure) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(mock_get_options_calls == 0); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(mock_get_options_calls, OP_EQ, 0); /* Check that a failure reset resets just before the limit */ mock_get_options_calls = 0; @@ -4301,19 +4433,20 @@ test_dir_download_status_increment(void *arg) >= current_time + delay0); tt_assert(download_status_get_next_attempt_at(&dls_failure) != TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_failure) == 0); - tt_assert(download_status_get_n_attempts(&dls_failure) == 0); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check that failure increments do happen on attempt-based schedules, * but that the retry is set at the end of time */ + mock_options->UseBridges = 1; mock_get_options_calls = 0; next_at = download_status_increment_failure(&dls_attempt, 404, "test", 0, current_time); tt_assert(next_at == TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_attempt) == 1); - tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); - tt_assert(mock_get_options_calls == 0); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, 1); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check that an attempt reset works */ mock_get_options_calls = 0; @@ -4324,65 +4457,65 @@ test_dir_download_status_increment(void *arg) >= current_time + delay0); tt_assert(download_status_get_next_attempt_at(&dls_attempt) != TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_attempt) == 0); - tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* avoid timing inconsistencies */ dls_attempt.next_attempt_at = current_time + delay0; /* check that a reset schedule becomes ready at the right time */ - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay0 - 1, - 1) == 0); - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay0, - 1) == 1); - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay0 + 1, - 1) == 1); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay0 - 1, 1), + OP_EQ, 0); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay0, 1), + OP_EQ, 1); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay0 + 1, 1), + OP_EQ, 1); /* Check that an attempt increment works */ mock_get_options_calls = 0; next_at = download_status_increment_attempt(&dls_attempt, "test", current_time); tt_assert(next_at == current_time + delay1); - tt_assert(download_status_get_n_failures(&dls_attempt) == 0); - tt_assert(download_status_get_n_attempts(&dls_attempt) == 1); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, 1); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* check that an incremented schedule becomes ready at the right time */ - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay1 - 1, - 1) == 0); - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay1, - 1) == 1); - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay1 + 1, - 1) == 1); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay1 - 1, 1), + OP_EQ, 0); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay1, 1), + OP_EQ, 1); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay1 + 1, 1), + OP_EQ, 1); /* check that a schedule isn't ready if it's had too many attempts */ - tt_assert(download_status_is_ready(&dls_attempt, - current_time + delay1 + 10, - 0) == 0); + tt_int_op(download_status_is_ready(&dls_attempt, + current_time + delay1 + 10, 0), + OP_EQ, 0); /* Check what happens when we reach then run off the end of the schedule */ mock_get_options_calls = 0; next_at = download_status_increment_attempt(&dls_attempt, "test", current_time); tt_assert(next_at == current_time + delay2); - tt_assert(download_status_get_n_failures(&dls_attempt) == 0); - tt_assert(download_status_get_n_attempts(&dls_attempt) == 2); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, 2); + tt_int_op(mock_get_options_calls, OP_GE, 1); mock_get_options_calls = 0; next_at = download_status_increment_attempt(&dls_attempt, "test", current_time); tt_assert(next_at == current_time + delay2); - tt_assert(download_status_get_n_failures(&dls_attempt) == 0); - tt_assert(download_status_get_n_attempts(&dls_attempt) == 3); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, 3); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check what happens when we hit the attempt limit */ mock_get_options_calls = 0; @@ -4390,22 +4523,22 @@ test_dir_download_status_increment(void *arg) next_at = download_status_increment_attempt(&dls_attempt, "test", current_time); tt_assert(next_at == TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_attempt) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(download_status_get_n_attempts(&dls_attempt) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(mock_get_options_calls, OP_GE, 1); /* Check that an attempt reset doesn't reset at the limit */ mock_get_options_calls = 0; download_status_reset(&dls_attempt); tt_assert(download_status_get_next_attempt_at(&dls_attempt) == TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_attempt) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(download_status_get_n_attempts(&dls_attempt) - == IMPOSSIBLE_TO_DOWNLOAD); - tt_assert(mock_get_options_calls == 0); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, + IMPOSSIBLE_TO_DOWNLOAD); + tt_int_op(mock_get_options_calls, OP_EQ, 0); /* Check that an attempt reset resets just before the limit */ mock_get_options_calls = 0; @@ -4418,9 +4551,10 @@ test_dir_download_status_increment(void *arg) >= current_time + delay0); tt_assert(download_status_get_next_attempt_at(&dls_attempt) != TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_attempt) == 0); - tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); - tt_assert(mock_get_options_calls >= 1); + tt_int_op(download_status_get_n_failures(&dls_attempt), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_attempt), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_GE, 1); + mock_options->UseBridges = 0; /* Check that attempt increments don't happen on failure-based schedules, * and that the attempt is set at the end of time */ @@ -4433,13 +4567,14 @@ test_dir_download_status_increment(void *arg) "schedule."); teardown_capture_of_logs(); tt_assert(next_at == TIME_MAX); - tt_assert(download_status_get_n_failures(&dls_failure) == 0); - tt_assert(download_status_get_n_attempts(&dls_failure) == 0); - tt_assert(mock_get_options_calls == 0); + tt_int_op(download_status_get_n_failures(&dls_failure), OP_EQ, 0); + tt_int_op(download_status_get_n_attempts(&dls_failure), OP_EQ, 0); + tt_int_op(mock_get_options_calls, OP_EQ, 0); done: /* the pointers in schedule are allocated on the stack */ smartlist_free(schedule); + smartlist_free(schedule_no_initial_delay); UNMOCK(get_options); mock_options = NULL; mock_get_options_calls = 0; @@ -4748,13 +4883,13 @@ mock_get_datadir_fname(const or_options_t *options, * Assert we were called like get_datadir_fname2() or get_datadir_fname(), * since that's all we implement here. */ - tt_assert(options != NULL); - tt_assert(sub1 != NULL); + tt_ptr_op(options, OP_NE, NULL); + tt_ptr_op(sub1, OP_NE, NULL); /* * No particular assertions about sub2, since we could be in the * get_datadir_fname() or get_datadir_fname2() case. */ - tt_assert(suffix == NULL); + tt_ptr_op(suffix, OP_EQ, NULL); /* Just duplicate the basename and return it for this mock */ if (sub2) { @@ -4781,7 +4916,7 @@ mock_unlink_reset(void) static int mock_unlink(const char *path) { - tt_assert(path != NULL); + tt_ptr_op(path, OP_NE, NULL); tor_free(last_unlinked_path); last_unlinked_path = tor_strdup(path); @@ -4810,8 +4945,8 @@ mock_write_str_to_file(const char *path, const char *str, int bin) (void)bin; - tt_assert(path != NULL); - tt_assert(str != NULL); + tt_ptr_op(path, OP_NE, NULL); + tt_ptr_op(str, OP_NE, NULL); len = strlen(str); crypto_digest256((char *)hash, str, len, DIGEST_SHA256); @@ -4937,7 +5072,7 @@ test_dir_dump_unparseable_descriptors(void *data) * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -4950,21 +5085,21 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_1)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_1)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_1_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -4972,8 +5107,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); /* * (2) Fire off dump_desc() twice; this still should trigger no cleanup. @@ -4985,14 +5120,14 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_2)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_2)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_2_hash, DIGEST_SHA256); /* Second time */ @@ -5001,21 +5136,22 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_2) + strlen(test_desc_3)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_2) + strlen(test_desc_3)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_3_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -5023,8 +5159,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); /* * (3) Three calls to dump_desc cause a FIFO cleanup @@ -5036,14 +5172,14 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_4)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_4)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_4_hash, DIGEST_SHA256); /* Second time */ @@ -5052,14 +5188,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_4) + strlen(test_desc_1)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_4) + strlen(test_desc_1)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_1_hash, DIGEST_SHA256); /* Third time - we should unlink the dump of test_desc_4 here */ @@ -5068,21 +5205,22 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_1) + strlen(test_desc_2)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_1) + strlen(test_desc_2)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 1); - tt_int_op(write_str_count, ==, 3); + tt_int_op(unlinked_count, OP_EQ, 1); + tt_int_op(write_str_count, OP_EQ, 3); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_2_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -5090,8 +5228,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); /* * (4) But repeating one (A B B) doesn't overflow and cleanup @@ -5103,14 +5241,14 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_3)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_3)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_3_hash, DIGEST_SHA256); /* Second time */ @@ -5119,14 +5257,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_3) + strlen(test_desc_4)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_3) + strlen(test_desc_4)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_4_hash, DIGEST_SHA256); /* Third time */ @@ -5135,21 +5274,22 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_3) + strlen(test_desc_4)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_3) + strlen(test_desc_4)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_4_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -5157,8 +5297,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); /* * (5) Same for the (A B A) repetition @@ -5170,14 +5310,14 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_1)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_1)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_1_hash, DIGEST_SHA256); /* Second time */ @@ -5186,14 +5326,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_1) + strlen(test_desc_2)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_1) + strlen(test_desc_2)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_2_hash, DIGEST_SHA256); /* Third time */ @@ -5202,21 +5343,22 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_1) + strlen(test_desc_2)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_1) + strlen(test_desc_2)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_2_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -5224,8 +5366,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); /* * (6) (A B B C) triggering overflow on C causes A, not B to be unlinked @@ -5237,14 +5379,14 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_3)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_3)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_3_hash, DIGEST_SHA256); /* Second time */ @@ -5253,14 +5395,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_3) + strlen(test_desc_4)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_3) + strlen(test_desc_4)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_4_hash, DIGEST_SHA256); /* Third time */ @@ -5269,14 +5412,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_3) + strlen(test_desc_4)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_3) + strlen(test_desc_4)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_4_hash, DIGEST_SHA256); /* Fourth time - we should unlink the dump of test_desc_3 here */ @@ -5285,21 +5429,22 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_4) + strlen(test_desc_1)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_4) + strlen(test_desc_1)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 1); - tt_int_op(write_str_count, ==, 3); + tt_int_op(unlinked_count, OP_EQ, 1); + tt_int_op(write_str_count, OP_EQ, 3); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_1_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -5307,8 +5452,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); /* * (7) (A B A C) triggering overflow on C causes B, not A to be unlinked @@ -5320,14 +5465,14 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_2)); + tt_u64_op(len_descs_dumped, OP_EQ, strlen(test_desc_2)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 1); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 1); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 1); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_2_hash, DIGEST_SHA256); /* Second time */ @@ -5336,14 +5481,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_2) + strlen(test_desc_3)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_2) + strlen(test_desc_3)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_3_hash, DIGEST_SHA256); /* Third time */ @@ -5352,14 +5498,15 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_2) + strlen(test_desc_3)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_2) + strlen(test_desc_3)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 2); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 2); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_3_hash, DIGEST_SHA256); /* Fourth time - we should unlink the dump of test_desc_3 here */ @@ -5368,21 +5515,22 @@ test_dir_dump_unparseable_descriptors(void *data) /* * Assert things about the FIFO state */ - tt_u64_op(len_descs_dumped, ==, strlen(test_desc_2) + strlen(test_desc_4)); + tt_u64_op(len_descs_dumped, OP_EQ, + strlen(test_desc_2) + strlen(test_desc_4)); tt_assert(descs_dumped != NULL && smartlist_len(descs_dumped) == 2); /* * Assert things about the mocks */ - tt_int_op(unlinked_count, ==, 1); - tt_int_op(write_str_count, ==, 3); + tt_int_op(unlinked_count, OP_EQ, 1); + tt_int_op(write_str_count, OP_EQ, 3); tt_mem_op(last_write_str_hash, OP_EQ, test_desc_4_hash, DIGEST_SHA256); /* * Reset the FIFO and check its state */ dump_desc_fifo_cleanup(); - tt_u64_op(len_descs_dumped, ==, 0); + tt_u64_op(len_descs_dumped, OP_EQ, 0); tt_assert(descs_dumped == NULL || smartlist_len(descs_dumped) == 0); /* @@ -5390,8 +5538,8 @@ test_dir_dump_unparseable_descriptors(void *data) */ mock_unlink_reset(); mock_write_str_to_file_reset(); - tt_int_op(unlinked_count, ==, 0); - tt_int_op(write_str_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); + tt_int_op(write_str_count, OP_EQ, 0); done: @@ -5438,7 +5586,7 @@ read_file_to_str_mock(const char *filename, int flags, char *result = NULL; /* Insist we got a filename */ - tt_assert(filename != NULL); + tt_ptr_op(filename, OP_NE, NULL); /* We ignore flags */ (void)flags; @@ -5501,53 +5649,53 @@ test_dir_populate_dump_desc_fifo(void *data) reset_read_file_to_str_mock(); /* Check state of unlink mock */ - tt_int_op(unlinked_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); /* Some cases that should fail before trying to read the file */ ent = dump_desc_populate_one_file(dirname, "bar"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 1); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 0); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 1); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 0); ent = dump_desc_populate_one_file(dirname, "unparseable-desc"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 2); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 0); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 2); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 0); ent = dump_desc_populate_one_file(dirname, "unparseable-desc.baz"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 3); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 0); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 3); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 0); ent = dump_desc_populate_one_file( dirname, "unparseable-desc.08AE85E90461F59E"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 4); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 0); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 4); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 0); ent = dump_desc_populate_one_file( dirname, "unparseable-desc.08AE85E90461F59EDF0981323F3A70D02B55AB54B44B04F" "287D72F7B72F242E85C8CB0EDA8854A99"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 5); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 0); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 5); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 0); /* This is a correct-length digest but base16_decode() will fail */ ent = dump_desc_populate_one_file( dirname, "unparseable-desc.68219B8BGE64B705A6FFC728C069DC596216D60A7D7520C" "D5ECE250D912E686B"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 6); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 0); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 6); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 0); /* This one has a correctly formed filename and should try reading */ @@ -5556,10 +5704,10 @@ test_dir_populate_dump_desc_fifo(void *data) dirname, "unparseable-desc.DF0981323F3A70D02B55AB54B44B04F287D72F7B72F242E" "85C8CB0EDA8854A99"); - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 7); - tt_int_op(read_count, ==, 0); - tt_int_op(read_call_count, ==, 1); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 7); + tt_int_op(read_count, OP_EQ, 0); + tt_int_op(read_call_count, OP_EQ, 1); /* This read will succeed but the digest won't match the file content */ fname = @@ -5572,10 +5720,10 @@ test_dir_populate_dump_desc_fifo(void *data) file_stat.st_mtime = 123456; ent = dump_desc_populate_one_file(dirname, fname); enforce_expected_filename = 0; - tt_assert(ent == NULL); - tt_int_op(unlinked_count, ==, 8); - tt_int_op(read_count, ==, 1); - tt_int_op(read_call_count, ==, 2); + tt_ptr_op(ent, OP_EQ, NULL); + tt_int_op(unlinked_count, OP_EQ, 8); + tt_int_op(read_count, OP_EQ, 1); + tt_int_op(read_call_count, OP_EQ, 2); tor_free(expected_filename); tor_free(file_content); @@ -5588,13 +5736,13 @@ test_dir_populate_dump_desc_fifo(void *data) file_content_len = strlen(file_content); file_stat.st_mtime = 789012; ent = dump_desc_populate_one_file(dirname, fname); - tt_assert(ent != NULL); - tt_int_op(unlinked_count, ==, 8); - tt_int_op(read_count, ==, 2); - tt_int_op(read_call_count, ==, 3); + tt_ptr_op(ent, OP_NE, NULL); + tt_int_op(unlinked_count, OP_EQ, 8); + tt_int_op(read_count, OP_EQ, 2); + tt_int_op(read_call_count, OP_EQ, 3); tt_str_op(ent->filename, OP_EQ, expected_filename); - tt_int_op(ent->len, ==, file_content_len); - tt_int_op(ent->when, ==, file_stat.st_mtime); + tt_int_op(ent->len, OP_EQ, file_content_len); + tt_int_op(ent->when, OP_EQ, file_stat.st_mtime); tor_free(ent->filename); tor_free(ent); tor_free(expected_filename); @@ -5603,9 +5751,9 @@ test_dir_populate_dump_desc_fifo(void *data) * Reset the mocks and check their state */ mock_unlink_reset(); - tt_int_op(unlinked_count, ==, 0); + tt_int_op(unlinked_count, OP_EQ, 0); reset_read_file_to_str_mock(); - tt_int_op(read_count, ==, 0); + tt_int_op(read_count, OP_EQ, 0); done: @@ -5727,9 +5875,18 @@ mock_networkstatus_consensus_can_use_extra_fallbacks( return mock_networkstatus_consensus_can_use_extra_fallbacks_value; } -/* data is a 2 character nul-terminated string. +static int mock_num_bridges_usable_value = 0; +static int +mock_num_bridges_usable(int use_maybe_reachable) +{ + (void)use_maybe_reachable; + return mock_num_bridges_usable_value; +} + +/* data is a 3 character nul-terminated string. * If data[0] is 'b', set bootstrapping, anything else means not bootstrapping * If data[1] is 'f', set extra fallbacks, anything else means no extra + * If data[2] is 'f', set running bridges, anything else means no extra * fallbacks. */ static void @@ -5737,7 +5894,7 @@ test_dir_find_dl_schedule(void* data) { const char *str = (const char *)data; - tt_assert(strlen(data) == 2); + tt_assert(strlen(data) == 3); if (str[0] == 'b') { mock_networkstatus_consensus_is_bootstrapping_value = 1; @@ -5751,15 +5908,24 @@ test_dir_find_dl_schedule(void* data) mock_networkstatus_consensus_can_use_extra_fallbacks_value = 0; } + if (str[2] == 'r') { + /* Any positive, non-zero value should work */ + mock_num_bridges_usable_value = 2; + } else { + mock_num_bridges_usable_value = 0; + } + MOCK(networkstatus_consensus_is_bootstrapping, mock_networkstatus_consensus_is_bootstrapping); MOCK(networkstatus_consensus_can_use_extra_fallbacks, mock_networkstatus_consensus_can_use_extra_fallbacks); + MOCK(num_bridges_usable, + mock_num_bridges_usable); download_status_t dls; smartlist_t server, client, server_cons, client_cons; smartlist_t client_boot_auth_only_cons, client_boot_auth_cons; - smartlist_t client_boot_fallback_cons, bridge; + smartlist_t client_boot_fallback_cons, bridge, bridge_bootstrap; mock_options = tor_malloc(sizeof(or_options_t)); reset_options(mock_options, &mock_get_options_calls); @@ -5776,6 +5942,7 @@ test_dir_find_dl_schedule(void* data) mock_options->ClientBootstrapConsensusFallbackDownloadSchedule = &client_boot_fallback_cons; mock_options->TestingBridgeDownloadSchedule = &bridge; + mock_options->TestingBridgeBootstrapDownloadSchedule = &bridge_bootstrap; dls.schedule = DL_SCHED_GENERIC; /* client */ @@ -5864,11 +6031,17 @@ test_dir_find_dl_schedule(void* data) dls.schedule = DL_SCHED_BRIDGE; /* client */ mock_options->ClientOnly = 1; - tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &bridge); + mock_options->UseBridges = 1; + if (num_bridges_usable(0) > 0) { + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &bridge); + } else { + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &bridge_bootstrap); + } done: UNMOCK(networkstatus_consensus_is_bootstrapping); UNMOCK(networkstatus_consensus_can_use_extra_fallbacks); + UNMOCK(num_bridges_usable); UNMOCK(get_options); tor_free(mock_options); mock_options = NULL; @@ -5978,6 +6151,29 @@ test_dir_post_parsing(void *arg) ; } +static void +test_dir_platform_str(void *arg) +{ + char platform[256]; + (void)arg; + platform[0] = 0; + get_platform_str(platform, sizeof(platform)); + tt_int_op((int)strlen(platform), OP_GT, 0); + tt_assert(!strcmpstart(platform, "Tor ")); + + tor_version_t ver; + // make sure this is a tor version, a real actual tor version. + tt_int_op(tor_version_parse_platform(platform, &ver, 1), OP_EQ, 1); + + TT_BLATHER(("%d.%d.%d.%d", ver.major, ver.minor, ver.micro, ver.patchlevel)); + + // Handle an example version. + tt_int_op(tor_version_parse_platform( + "Tor 0.3.3.3 (foo) (git-xyzzy) on a potato", &ver, 1), OP_EQ, 1); + done: + ; +} + #define DIR_LEGACY(name) \ { #name, test_dir_ ## name , TT_FORK, NULL, NULL } @@ -6022,7 +6218,8 @@ struct testcase_t dir_tests[] = { DIR(packages, 0), DIR(download_status_schedule, 0), DIR(download_status_random_backoff, 0), - DIR(download_status_increment, 0), + DIR(download_status_random_backoff_ranges, 0), + DIR(download_status_increment, TT_FORK), DIR(authdir_type_to_string, 0), DIR(conn_purpose_to_string, 0), DIR(should_use_directory_guards, 0), @@ -6033,12 +6230,17 @@ struct testcase_t dir_tests[] = { DIR(dump_unparseable_descriptors, 0), DIR(populate_dump_desc_fifo, 0), DIR(populate_dump_desc_fifo_2, 0), - DIR_ARG(find_dl_schedule, TT_FORK, "bf"), - DIR_ARG(find_dl_schedule, TT_FORK, "ba"), - DIR_ARG(find_dl_schedule, TT_FORK, "cf"), - DIR_ARG(find_dl_schedule, TT_FORK, "ca"), + DIR_ARG(find_dl_schedule, TT_FORK, "bfd"), + DIR_ARG(find_dl_schedule, TT_FORK, "bad"), + DIR_ARG(find_dl_schedule, TT_FORK, "cfd"), + DIR_ARG(find_dl_schedule, TT_FORK, "cad"), + DIR_ARG(find_dl_schedule, TT_FORK, "bfr"), + DIR_ARG(find_dl_schedule, TT_FORK, "bar"), + DIR_ARG(find_dl_schedule, TT_FORK, "cfr"), + DIR_ARG(find_dl_schedule, TT_FORK, "car"), DIR(assumed_flags, 0), DIR(networkstatus_compute_bw_weights_v10, 0), + DIR(platform_str, 0), END_OF_TESTCASES }; diff --git a/src/test/test_dir_common.c b/src/test/test_dir_common.c index fca70249bd..fdf43533a8 100644 --- a/src/test/test_dir_common.c +++ b/src/test/test_dir_common.c @@ -146,7 +146,7 @@ dir_common_gen_routerstatus_for_v3ns(int idx, time_t now) break; default: /* Shouldn't happen */ - tt_assert(0); + tt_abort(); } if (vrs) { vrs->microdesc = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); diff --git a/src/test/test_dir_handle_get.c b/src/test/test_dir_handle_get.c index 75fe6249ad..fe26657ad8 100644 --- a/src/test/test_dir_handle_get.c +++ b/src/test/test_dir_handle_get.c @@ -28,6 +28,7 @@ #include "entrynodes.h" #include "routerparse.h" #include "networkstatus.h" +#include "proto_http.h" #include "geoip.h" #include "dirserv.h" #include "dirvote.h" @@ -38,7 +39,7 @@ #include <direct.h> #else #include <dirent.h> -#endif +#endif /* defined(_WIN32) */ #ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS DISABLE_GCC_WARNING(overlength-strings) @@ -869,7 +870,7 @@ test_dir_handle_get_server_descriptors_authority(void* data) mock_routerinfo->cache_info.signed_descriptor_body = tor_strdup(TEST_DESCRIPTOR); mock_routerinfo->cache_info.signed_descriptor_len = - strlen(TEST_DESCRIPTOR) - annotation_len;; + strlen(TEST_DESCRIPTOR) - annotation_len; mock_routerinfo->cache_info.annotations_len = annotation_len; mock_routerinfo->cache_info.published_on = time(NULL); @@ -1794,7 +1795,7 @@ status_vote_current_consensus_ns_test(char **header, char **body, dirserv_set_cached_consensus_networkstatus(NETWORK_STATUS, "ns", &digests, sha3, time(NULL)); -#endif +#endif /* 0 */ networkstatus_t *ns = tor_malloc_zero(sizeof(networkstatus_t)); ns->type = NS_TYPE_CONSENSUS; ns->flavor = FLAV_NS; diff --git a/src/test/test_dns.c b/src/test/test_dns.c index 6a8e92cb47..19dcb02931 100644 --- a/src/test/test_dns.c +++ b/src/test/test_dns.c @@ -18,9 +18,9 @@ NS(test_main)(void *arg) uint32_t ttl_mid = MIN_DNS_TTL_AT_EXIT / 2 + MAX_DNS_TTL_AT_EXIT / 2; - tt_int_op(dns_clip_ttl(MIN_DNS_TTL_AT_EXIT - 1),==,MIN_DNS_TTL_AT_EXIT); - tt_int_op(dns_clip_ttl(ttl_mid),==,MAX_DNS_TTL_AT_EXIT); - tt_int_op(dns_clip_ttl(MAX_DNS_TTL_AT_EXIT + 1),==,MAX_DNS_TTL_AT_EXIT); + tt_int_op(dns_clip_ttl(MIN_DNS_TTL_AT_EXIT - 1),OP_EQ,MIN_DNS_TTL_AT_EXIT); + tt_int_op(dns_clip_ttl(ttl_mid),OP_EQ,MAX_DNS_TTL_AT_EXIT); + tt_int_op(dns_clip_ttl(MAX_DNS_TTL_AT_EXIT + 1),OP_EQ,MAX_DNS_TTL_AT_EXIT); done: return; @@ -172,10 +172,10 @@ NS(test_main)(void *arg) retval = dns_resolve(exitconn); - tt_int_op(retval,==,1); - tt_str_op(resolved_name,==,last_resolved_hostname); + tt_int_op(retval,OP_EQ,1); + tt_str_op(resolved_name,OP_EQ,last_resolved_hostname); tt_assert(conn_for_resolved_cell == exitconn); - tt_int_op(n_send_resolved_hostname_cell_replacement,==, + tt_int_op(n_send_resolved_hostname_cell_replacement,OP_EQ, prev_n_send_resolved_hostname_cell_replacement + 1); tt_assert(exitconn->on_circuit == NULL); @@ -201,12 +201,12 @@ NS(test_main)(void *arg) retval = dns_resolve(exitconn); - tt_int_op(retval,==,1); + tt_int_op(retval,OP_EQ,1); tt_assert(conn_for_resolved_cell == exitconn); - tt_int_op(n_send_resolved_cell_replacement,==, + tt_int_op(n_send_resolved_cell_replacement,OP_EQ, prev_n_send_resolved_cell_replacement + 1); tt_assert(last_resolved == fake_resolved); - tt_int_op(last_answer_type,==,0xff); + tt_int_op(last_answer_type,OP_EQ,0xff); tt_assert(exitconn->on_circuit == NULL); /* CASE 3: The purpose of exit connection is not EXIT_PURPOSE_RESOLVE @@ -229,12 +229,12 @@ NS(test_main)(void *arg) retval = dns_resolve(exitconn); - tt_int_op(retval,==,1); + tt_int_op(retval,OP_EQ,1); tt_assert(on_circuit->n_streams == exitconn); tt_assert(exitconn->next_stream == nextconn); - tt_int_op(prev_n_send_resolved_cell_replacement,==, + tt_int_op(prev_n_send_resolved_cell_replacement,OP_EQ, n_send_resolved_cell_replacement); - tt_int_op(prev_n_send_resolved_hostname_cell_replacement,==, + tt_int_op(prev_n_send_resolved_hostname_cell_replacement,OP_EQ, n_send_resolved_hostname_cell_replacement); /* CASE 4: _impl returns 0. @@ -253,8 +253,8 @@ NS(test_main)(void *arg) retval = dns_resolve(exitconn); - tt_int_op(retval,==,0); - tt_int_op(exitconn->base_.state,==,EXIT_CONN_STATE_RESOLVING); + tt_int_op(retval,OP_EQ,0); + tt_int_op(exitconn->base_.state,OP_EQ,EXIT_CONN_STATE_RESOLVING); tt_assert(on_circuit->resolving_streams == exitconn); tt_assert(exitconn->next_stream == nextconn); @@ -278,12 +278,12 @@ NS(test_main)(void *arg) retval = dns_resolve(exitconn); - tt_int_op(retval,==,-1); - tt_int_op(n_send_resolved_cell_replacement,==, + tt_int_op(retval,OP_EQ,-1); + tt_int_op(n_send_resolved_cell_replacement,OP_EQ, prev_n_send_resolved_cell_replacement + 1); - tt_int_op(last_answer_type,==,RESOLVED_TYPE_ERROR); - tt_int_op(n_dns_cancel_pending_resolve_replacement,==,1); - tt_int_op(n_connection_free,==,prev_n_connection_free + 1); + tt_int_op(last_answer_type,OP_EQ,RESOLVED_TYPE_ERROR); + tt_int_op(n_dns_cancel_pending_resolve_replacement,OP_EQ,1); + tt_int_op(n_connection_free,OP_EQ,prev_n_connection_free + 1); tt_assert(last_freed_conn == TO_CONN(exitconn)); done: @@ -351,9 +351,9 @@ NS(test_main)(void *arg) resolved_addr = &(exitconn->base_.addr); - tt_int_op(retval,==,1); + tt_int_op(retval,OP_EQ,1); tt_assert(tor_addr_eq(resolved_addr, (const tor_addr_t *)&addr_to_compare)); - tt_int_op(exitconn->address_ttl,==,DEFAULT_DNS_TTL); + tt_int_op(exitconn->address_ttl,OP_EQ,DEFAULT_DNS_TTL); done: tor_free(on_circ); @@ -393,7 +393,7 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, NULL); - tt_int_op(retval,==,-1); + tt_int_op(retval,OP_EQ,-1); done: tor_free(TO_CONN(exitconn)->address); @@ -436,7 +436,7 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, NULL); - tt_int_op(retval,==,-1); + tt_int_op(retval,OP_EQ,-1); done: NS_UNMOCK(router_my_exit_policy_is_reject_star); @@ -478,7 +478,7 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, NULL); - tt_int_op(retval,==,-1); + tt_int_op(retval,OP_EQ,-1); tor_free(TO_CONN(exitconn)->address); @@ -488,7 +488,7 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, NULL); - tt_int_op(retval,==,-1); + tt_int_op(retval,OP_EQ,-1); done: NS_UNMOCK(router_my_exit_policy_is_reject_star); @@ -546,8 +546,8 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, NULL); - tt_int_op(retval,==,0); - tt_int_op(made_pending,==,1); + tt_int_op(retval,OP_EQ,0); + tt_int_op(made_pending,OP_EQ,1); pending_conn = cache_entry->pending_connections; @@ -628,8 +628,8 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, &resolve_out); - tt_int_op(retval,==,0); - tt_int_op(made_pending,==,0); + tt_int_op(retval,OP_EQ,0); + tt_int_op(made_pending,OP_EQ,0); tt_assert(resolve_out == cache_entry); tt_assert(last_exitconn == exitconn); @@ -699,8 +699,8 @@ NS(test_main)(void *arg) retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, NULL); - tt_int_op(retval,==,0); - tt_int_op(made_pending,==,1); + tt_int_op(retval,OP_EQ,0); + tt_int_op(made_pending,OP_EQ,1); cache_entry = dns_get_cache_entry(&query); @@ -712,7 +712,7 @@ NS(test_main)(void *arg) tt_assert(pending_conn->conn == exitconn); tt_assert(last_launched_resolve == cache_entry); - tt_str_op(cache_entry->address,==,TO_CONN(exitconn)->address); + tt_str_op(cache_entry->address,OP_EQ,TO_CONN(exitconn)->address); done: NS_UNMOCK(router_my_exit_policy_is_reject_star); diff --git a/src/test/test_entryconn.c b/src/test/test_entryconn.c index 12a631630b..c29b1a7126 100644 --- a/src/test/test_entryconn.c +++ b/src/test/test_entryconn.c @@ -14,6 +14,10 @@ #include "confparse.h" #include "connection.h" #include "connection_edge.h" +#include "nodelist.h" + +#include "hs_cache.h" +#include "rendcache.h" static void * entryconn_rewrite_setup(const struct testcase_t *tc) @@ -72,7 +76,6 @@ test_entryconn_rewrite_bad_dotexit(void *arg) entry_connection_t *ec = arg; rewrite_result_t rr; - get_options_mutable()->AllowDotExit = 0; tt_assert(ec->socks_request); strlcpy(ec->socks_request->address, "www.TORproject.org.foo.exit", sizeof(ec->socks_request->address)); @@ -282,7 +285,7 @@ test_entryconn_rewrite_automap_reverse(void *arg) done: connection_free_(ENTRY_TO_CONN(ec2)); } -#endif +#endif /* 0 */ /* Rewrite because of cached DNS entry. */ static void @@ -476,7 +479,7 @@ test_entryconn_rewrite_reject_internal_reverse(void *arg) ; } -/* Rewrite into .exit because of virtual address mapping */ +/* Rewrite into .exit because of virtual address mapping. */ static void test_entryconn_rewrite_automap_exit(void *arg) { @@ -487,43 +490,21 @@ test_entryconn_rewrite_automap_exit(void *arg) ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); - get_options_mutable()->AutomapHostsOnResolve = 1; - get_options_mutable()->AllowDotExit = 1; smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes, ".EXIT"); parse_virtual_addr_network("127.1.0.0/16", AF_INET, 0, &msg); - /* Automap this on resolve. */ + /* Try to automap this on resolve. */ strlcpy(ec->socks_request->address, "website.example.exit", sizeof(ec->socks_request->address)); ec->socks_request->command = SOCKS_COMMAND_RESOLVE; connection_ap_handshake_rewrite(ec, &rr); - tt_int_op(rr.automap, OP_EQ, 1); - tt_int_op(rr.should_close, OP_EQ, 0); - tt_int_op(rr.end_reason, OP_EQ, 0); - tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); - tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); - tt_str_op(rr.orig_address, OP_EQ, "website.example.exit"); - tt_str_op(ec->original_dest_address, OP_EQ, "website.example.exit"); - - tt_assert(!strcmpstart(ec->socks_request->address,"127.1.")); - - /* Connect to it and make sure we get the original address back. */ - strlcpy(ec2->socks_request->address, ec->socks_request->address, - sizeof(ec2->socks_request->address)); - - ec2->socks_request->command = SOCKS_COMMAND_CONNECT; - connection_ap_handshake_rewrite(ec2, &rr); - + /* Make sure it isn't allowed -- there is no longer an AllowDotExit + * option. */ tt_int_op(rr.automap, OP_EQ, 0); - tt_int_op(rr.should_close, OP_EQ, 0); - tt_int_op(rr.end_reason, OP_EQ, 0); - tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); - tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_AUTOMAP); - tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address); - tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address); - tt_str_op(ec2->socks_request->address, OP_EQ, "website.example.exit"); + tt_int_op(rr.should_close, OP_EQ, 1); + tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_TORPROTOCOL); done: connection_free_(ENTRY_TO_CONN(ec2)); @@ -573,7 +554,6 @@ test_entryconn_rewrite_mapaddress_automap_onion(void *arg) ec4 = entry_connection_new(CONN_TYPE_AP, AF_INET); get_options_mutable()->AutomapHostsOnResolve = 1; - get_options_mutable()->AllowDotExit = 1; smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes, ".onion"); parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg); @@ -743,6 +723,88 @@ test_entryconn_rewrite_mapaddress_automap_onion4(void *arg) test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1); } +/** Test that rewrite functions can handle v2 addresses */ +static void +test_entryconn_rewrite_onion_v2(void *arg) +{ + int retval; + entry_connection_t *conn = arg; + + (void) arg; + + rend_cache_init(); + + /* Make a SOCKS request */ + conn->socks_request->command = SOCKS_COMMAND_CONNECT; + strlcpy(conn->socks_request->address, + "pqeed46efnwmfuid.onion", + sizeof(conn->socks_request->address)); + + /* Make an onion connection using the SOCKS request */ + conn->entry_cfg.onion_traffic = 1; + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT; + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data); + + /* Handle SOCKS and rewrite! */ + retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL); + tt_int_op(retval, OP_EQ, 0); + + /* Check connection state after rewrite */ + tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT); + /* check that the address got rewritten */ + tt_str_op(conn->socks_request->address, OP_EQ, + "pqeed46efnwmfuid"); + /* check that HS information got attached to the connection */ + tt_assert(ENTRY_TO_EDGE_CONN(conn)->rend_data); + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident); + + done: + rend_cache_free_all(); + /* 'conn' is cleaned by handler */ +} + +/** Test that rewrite functions can handle v3 onion addresses */ +static void +test_entryconn_rewrite_onion_v3(void *arg) +{ + int retval; + entry_connection_t *conn = arg; + + (void) arg; + + hs_cache_init(); + + /* Make a SOCKS request */ + conn->socks_request->command = SOCKS_COMMAND_CONNECT; + strlcpy(conn->socks_request->address, + "git.25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid.onion", + sizeof(conn->socks_request->address)); + + /* Make an onion connection using the SOCKS request */ + conn->entry_cfg.onion_traffic = 1; + ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_SOCKS_WAIT; + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data); + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->hs_ident); + + /* Handle SOCKS and rewrite! */ + retval = connection_ap_handshake_rewrite_and_attach(conn, NULL, NULL); + tt_int_op(retval, OP_EQ, 0); + + /* Check connection state after rewrite. It should be in waiting for + * descriptor state. */ + tt_int_op(ENTRY_TO_CONN(conn)->state, OP_EQ, AP_CONN_STATE_RENDDESC_WAIT); + /* check that the address got rewritten */ + tt_str_op(conn->socks_request->address, OP_EQ, + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid"); + /* check that HS information got attached to the connection */ + tt_assert(ENTRY_TO_EDGE_CONN(conn)->hs_ident); + tt_assert(!ENTRY_TO_EDGE_CONN(conn)->rend_data); + + done: + hs_free_all(); + /* 'conn' is cleaned by handler */ +} + #define REWRITE(name) \ { #name, test_entryconn_##name, TT_FORK, &test_rewrite_setup, NULL } @@ -763,6 +825,8 @@ struct testcase_t entryconn_tests[] = { REWRITE(rewrite_mapaddress_automap_onion2), REWRITE(rewrite_mapaddress_automap_onion3), REWRITE(rewrite_mapaddress_automap_onion4), + REWRITE(rewrite_onion_v2), + REWRITE(rewrite_onion_v3), END_OF_TESTCASES }; diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 1f008d93b3..80ebebe3f8 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -7,6 +7,7 @@ #define STATEFILE_PRIVATE #define ENTRYNODES_PRIVATE #define ROUTERLIST_PRIVATE +#define DIRECTORY_PRIVATE #include "or.h" #include "test.h" @@ -15,6 +16,7 @@ #include "circuitlist.h" #include "config.h" #include "confparse.h" +#include "directory.h" #include "entrynodes.h" #include "nodelist.h" #include "networkstatus.h" @@ -129,6 +131,14 @@ big_fake_network_setup(const struct testcase_t *testcase) n->rs->has_bandwidth = 1; n->rs->bandwidth_kb = 30; + /* Make a random nickname for each node */ + { + char nickname_binary[8]; + crypto_rand(nickname_binary, sizeof(nickname_binary)); + base64_encode(n->rs->nickname, sizeof(n->rs->nickname), + nickname_binary, sizeof(nickname_binary), 0); + } + /* Call half of the nodes a possible guard. */ if (i % 2 == 0) { n->is_possible_guard = 1; @@ -455,29 +465,29 @@ test_entry_guard_parse_from_state_failure(void *arg) /* no selection */ eg = entry_guard_parse_from_state( "rsa_id=596f75206d6179206e656564206120686f626270"); - tt_assert(! eg); + tt_ptr_op(eg, OP_EQ, NULL); /* no RSA ID. */ eg = entry_guard_parse_from_state("in=default nickname=Fred"); - tt_assert(! eg); + tt_ptr_op(eg, OP_EQ, NULL); /* Bad RSA ID: bad character. */ eg = entry_guard_parse_from_state( "in=default " "rsa_id=596f75206d6179206e656564206120686f62627q"); - tt_assert(! eg); + tt_ptr_op(eg, OP_EQ, NULL); /* Bad RSA ID: too long.*/ eg = entry_guard_parse_from_state( "in=default " "rsa_id=596f75206d6179206e656564206120686f6262703"); - tt_assert(! eg); + tt_ptr_op(eg, OP_EQ, NULL); /* Bad RSA ID: too short.*/ eg = entry_guard_parse_from_state( "in=default " "rsa_id=596f75206d6179206e65656420612"); - tt_assert(! eg); + tt_ptr_op(eg, OP_EQ, NULL); done: entry_guard_free(eg); @@ -595,20 +605,20 @@ test_entry_guard_parse_from_state_full(void *arg) MOCK(get_or_state, get_or_state_replacement); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_assert(lines); state->Guard = lines; /* Try it first without setting the result. */ r = entry_guards_parse_state(state, 0, &msg); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); guard_selection_t *gs_br = get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0); - tt_assert(!gs_br); + tt_ptr_op(gs_br, OP_EQ, NULL); r = entry_guards_parse_state(state, 1, &msg); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); gs_br = get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0); guard_selection_t *gs_df = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); @@ -625,7 +635,7 @@ test_entry_guard_parse_from_state_full(void *arg) /* Try again; make sure it doesn't double-add the guards. */ r = entry_guards_parse_state(state, 1, &msg); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); gs_br = get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0); gs_df = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); tt_assert(gs_br); @@ -730,7 +740,7 @@ test_entry_guard_parse_from_state_broken(void *arg) MOCK(get_or_state, get_or_state_replacement); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_assert(lines); state->Guard = lines; @@ -742,7 +752,7 @@ test_entry_guard_parse_from_state_broken(void *arg) /* And we shouldn't have made anything. */ guard_selection_t *gs_df = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); - tt_assert(gs_df == NULL); + tt_ptr_op(gs_df, OP_EQ, NULL); tor_free(msg); /* Now see about the set case (which shouldn't happen IRL) */ @@ -750,7 +760,7 @@ test_entry_guard_parse_from_state_broken(void *arg) tt_int_op(r, OP_LT, 0); tt_ptr_op(msg, OP_NE, NULL); gs_df = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); - tt_assert(gs_df != NULL); + tt_ptr_op(gs_df, OP_NE, NULL); tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 1); done: @@ -767,26 +777,26 @@ test_entry_guard_get_guard_selection_by_name(void *arg) guard_selection_t *gs1, *gs2, *gs3; gs1 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 0); - tt_assert(gs1 == NULL); + tt_ptr_op(gs1, OP_EQ, NULL); gs1 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 1); - tt_assert(gs1 != NULL); + tt_ptr_op(gs1, OP_NE, NULL); gs2 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 1); tt_assert(gs2 == gs1); gs2 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 0); tt_assert(gs2 == gs1); gs2 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 0); - tt_assert(gs2 == NULL); + tt_ptr_op(gs2, OP_EQ, NULL); gs2 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 1); - tt_assert(gs2 != NULL); + tt_ptr_op(gs2, OP_NE, NULL); tt_assert(gs2 != gs1); gs3 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 0); tt_assert(gs3 == gs2); gs3 = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 0); - tt_assert(gs3 == NULL); + tt_ptr_op(gs3, OP_EQ, NULL); gs3 = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 1); - tt_assert(gs3 != NULL); + tt_ptr_op(gs3, OP_NE, NULL); tt_assert(gs3 != gs2); tt_assert(gs3 != gs1); tt_assert(gs3 == get_guard_selection_info()); @@ -847,16 +857,16 @@ test_entry_guard_add_single_guard(void *arg) tt_i64_op(g1->sampled_on_date, OP_GE, now - 12*86400); tt_i64_op(g1->sampled_on_date, OP_LE, now); tt_str_op(g1->sampled_by_version, OP_EQ, VERSION); - tt_assert(g1->currently_listed == 1); + tt_uint_op(g1->currently_listed, OP_EQ, 1); tt_i64_op(g1->confirmed_on_date, OP_EQ, 0); tt_int_op(g1->confirmed_idx, OP_EQ, -1); tt_int_op(g1->last_tried_to_connect, OP_EQ, 0); tt_uint_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); tt_i64_op(g1->failing_since, OP_EQ, 0); - tt_assert(g1->is_filtered_guard == 1); - tt_assert(g1->is_usable_filtered_guard == 1); - tt_assert(g1->is_primary == 0); - tt_assert(g1->extra_state_fields == NULL); + tt_uint_op(g1->is_filtered_guard, OP_EQ, 1); + tt_uint_op(g1->is_usable_filtered_guard, OP_EQ, 1); + tt_uint_op(g1->is_primary, OP_EQ, 0); + tt_ptr_op(g1->extra_state_fields, OP_EQ, NULL); /* Make sure it got added. */ tt_int_op(1, OP_EQ, smartlist_len(gs->sampled_entry_guards)); @@ -886,16 +896,16 @@ test_entry_guard_node_filter(void *arg) g[i] = entry_guard_add_to_sample(gs, n[i]); // everything starts out filtered-in - tt_assert(g[i]->is_filtered_guard == 1); - tt_assert(g[i]->is_usable_filtered_guard == 1); + tt_uint_op(g[i]->is_filtered_guard, OP_EQ, 1); + tt_uint_op(g[i]->is_usable_filtered_guard, OP_EQ, 1); } tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, NUM); /* Make sure refiltering doesn't hurt */ entry_guards_update_filtered_sets(gs); for (i = 0; i < NUM; ++i) { - tt_assert(g[i]->is_filtered_guard == 1); - tt_assert(g[i]->is_usable_filtered_guard == 1); + tt_uint_op(g[i]->is_filtered_guard, OP_EQ, 1); + tt_uint_op(g[i]->is_usable_filtered_guard, OP_EQ, 1); } tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, NUM); @@ -917,6 +927,7 @@ test_entry_guard_node_filter(void *arg) routerset_parse(get_options_mutable()->ExcludeNodes, "144.144.0.0/16", ""); /* 4: Bridge. */ + get_options_mutable()->UseBridges = 1; sweep_bridge_list(); bl = tor_malloc_zero(sizeof(bridge_line_t)); tor_addr_from_ipv4h(&bl->addr, n[4]->rs->addr); @@ -924,6 +935,7 @@ test_entry_guard_node_filter(void *arg) memcpy(bl->digest, n[4]->identity, 20); bridge_add_from_config(bl); bl = NULL; // prevent free. + get_options_mutable()->UseBridges = 0; /* 5: Unreachable. This stays in the filter, but isn't in usable-filtered */ g[5]->last_tried_to_connect = approx_time(); // prevent retry. @@ -948,8 +960,8 @@ test_entry_guard_node_filter(void *arg) }); entry_guards_update_filtered_sets(gs); for (i = 0; i < NUM; ++i) { - tt_assert(g[i]->is_filtered_guard == 0); - tt_assert(g[i]->is_usable_filtered_guard == 0); + tt_uint_op(g[i]->is_filtered_guard, OP_EQ, 0); + tt_uint_op(g[i]->is_usable_filtered_guard, OP_EQ, 0); } tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 0); @@ -992,7 +1004,7 @@ test_entry_guard_expand_sample(void *arg) // Nothing became unusable/unfiltered, so a subsequent expand should // make no changes. guard = entry_guards_expand_sample(gs); - tt_assert(! guard); // no guard was added. + tt_ptr_op(guard, OP_EQ, NULL); // no guard was added. tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, num_reachable_filtered_guards(gs, NULL)); @@ -1016,7 +1028,7 @@ test_entry_guard_expand_sample(void *arg) // Still idempotent. guard = entry_guards_expand_sample(gs); - tt_assert(! guard); // no guard was added. + tt_ptr_op(guard, OP_EQ, NULL); // no guard was added. tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ, num_reachable_filtered_guards(gs, NULL)); @@ -1520,7 +1532,7 @@ test_entry_guard_retry_unreachable(void *arg) entry_guards_expand_sample(gs); /* Let's say that we have two guards, and they're down. */ - time_t start = approx_time();; + time_t start = approx_time(); entry_guard_t *g1 = smartlist_get(gs->sampled_entry_guards, 0); entry_guard_t *g2 = smartlist_get(gs->sampled_entry_guards, 1); entry_guard_t *g3 = smartlist_get(gs->sampled_entry_guards, 2); @@ -1637,6 +1649,27 @@ test_entry_guard_manage_primary(void *arg) tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx)); }); + /* Do some dirinfo checks */ + { + /* Check that we have all required dirinfo for the primaries (that's done + * in big_fake_network_setup()) */ + char *dir_info_str = + guard_selection_get_err_str_if_dir_info_missing(gs, 0, 0, 0); + tt_assert(!dir_info_str); + + /* Now artificially remove the first primary's descriptor and re-check */ + entry_guard_t *first_primary; + first_primary = smartlist_get(gs->primary_entry_guards, 0); + /* Change the first primary's identity digest so that the mocked functions + * can't find its descriptor */ + memset(first_primary->identity, 9, sizeof(first_primary->identity)); + dir_info_str =guard_selection_get_err_str_if_dir_info_missing(gs, 1, 2, 3); + tt_str_op(dir_info_str, OP_EQ, + "We're missing descriptors for 1/2 of our primary entry guards " + "(total microdescriptors: 2/3)."); + tor_free(dir_info_str); + } + done: guard_selection_free(gs); smartlist_free(prev_guards); @@ -1696,6 +1729,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) /* Simpler cases: no gaurds are confirmed yet. */ (void)arg; guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); + entry_guard_restriction_t *rst = NULL; /* simple starting configuration */ entry_guards_update_primary(gs); @@ -1707,7 +1741,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_assert(g); tt_assert(g->is_primary); tt_int_op(g->confirmed_idx, OP_EQ, -1); - tt_assert(g->is_pending == 0); // primary implies non-pending. + tt_uint_op(g->is_pending, OP_EQ, 0); // primary implies non-pending. tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time()); @@ -1727,7 +1761,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_assert(g2); tt_assert(g2->is_primary); tt_int_op(g2->confirmed_idx, OP_EQ, -1); - tt_assert(g2->is_pending == 0); // primary implies non-pending. + tt_uint_op(g2->is_pending, OP_EQ, 0); // primary implies non-pending. tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); @@ -1755,7 +1789,7 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_assert(g2); tt_assert(!g2->is_primary); tt_int_op(g2->confirmed_idx, OP_EQ, -1); - tt_assert(g2->is_pending == 1); + tt_uint_op(g2->is_pending, OP_EQ, 1); tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time()); tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE); @@ -1777,14 +1811,13 @@ test_entry_guard_select_for_circuit_no_confirmed(void *arg) tt_ptr_op(g2, OP_EQ, g); /* But if we impose a restriction, we don't get the same guard */ - entry_guard_restriction_t rst; - memset(&rst, 0, sizeof(rst)); - memcpy(rst.exclude_id, g->identity, DIGEST_LEN); - g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, &rst, &state); + rst = guard_create_exit_restriction((uint8_t*)g->identity); + g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, rst, &state); tt_ptr_op(g2, OP_NE, g); done: guard_selection_free(gs); + entry_guard_restriction_free(rst); } static void @@ -1794,6 +1827,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) guards, we use a confirmed guard. */ (void)arg; int i; + entry_guard_restriction_t *rst = NULL; guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL); const int N_CONFIRMED = 10; @@ -1813,7 +1847,7 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) tt_assert(g); tt_assert(g->is_primary); tt_int_op(g->confirmed_idx, OP_EQ, 0); - tt_assert(g->is_pending == 0); // primary implies non-pending. + tt_uint_op(g->is_pending, OP_EQ, 0); // primary implies non-pending. tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time()); tt_ptr_op(g, OP_EQ, smartlist_get(gs->primary_entry_guards, 0)); @@ -1854,10 +1888,8 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) get_options_mutable()->EnforceDistinctSubnets = 0; g = smartlist_get(gs->confirmed_entry_guards, smartlist_len(gs->primary_entry_guards)+2); - entry_guard_restriction_t rst; - memset(&rst, 0, sizeof(rst)); - memcpy(rst.exclude_id, g->identity, DIGEST_LEN); - g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, &rst, &state); + rst = guard_create_exit_restriction((uint8_t*)g->identity); + g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, rst, &state); tt_ptr_op(g2, OP_NE, NULL); tt_ptr_op(g2, OP_NE, g); tt_int_op(g2->confirmed_idx, OP_EQ, @@ -1883,13 +1915,13 @@ test_entry_guard_select_for_circuit_confirmed(void *arg) // Regression test for bug 22753/TROVE-2017-006. get_options_mutable()->EnforceDistinctSubnets = 1; g = smartlist_get(gs->confirmed_entry_guards, 0); - memset(&rst, 0, sizeof(rst)); - memcpy(rst.exclude_id, g->identity, DIGEST_LEN); - g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, &rst, &state); + memcpy(rst->exclude_id, g->identity, DIGEST_LEN); + g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, rst, &state); tt_ptr_op(g2, OP_EQ, NULL); done: guard_selection_free(gs); + entry_guard_restriction_free(rst); } static void @@ -1913,7 +1945,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) int r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &node, &guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_assert(node); tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); @@ -1945,7 +1977,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) update_approx_time(start+35); r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &node, &guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_assert(node); tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); @@ -1981,7 +2013,7 @@ test_entry_guard_select_for_circuit_highlevel_primary(void *arg) update_approx_time(start+60); r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &node, &guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_assert(node); tt_assert(guard); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); @@ -2036,7 +2068,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) &node, &guard); tt_assert(node); tt_assert(guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); entry_guard_failed(&guard); circuit_guard_state_free(guard); @@ -2050,7 +2082,7 @@ test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg) &node, &guard); tt_assert(node); tt_assert(guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); entry_guard_t *g = entry_guard_handle_get(guard->guard); tt_assert(g); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); @@ -2102,7 +2134,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) &node, &guard); tt_assert(node); tt_assert(guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); g = entry_guard_handle_get(guard->guard); make_guard_confirmed(gs, g); @@ -2119,7 +2151,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) &node, &guard); tt_assert(node); tt_assert(guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); g = entry_guard_handle_get(guard->guard); tt_int_op(g->is_primary, OP_EQ, 0); @@ -2145,7 +2177,7 @@ test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg) /* Have a circuit to a primary guard succeed. */ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &node, &guard2); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION); u = entry_guard_succeeded(&guard2); tt_assert(u == GUARD_USABLE_NOW); @@ -2194,7 +2226,7 @@ test_entry_guard_select_and_cancel(void *arg) &node, &guard); tt_assert(node); tt_assert(guard); - tt_assert(r == 0); + tt_int_op(r, OP_EQ, 0); tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD); g = entry_guard_handle_get(guard->guard); tt_int_op(g->is_primary, OP_EQ, 0); @@ -2202,7 +2234,7 @@ test_entry_guard_select_and_cancel(void *arg) /* Whoops! We should never have asked for this guard. Cancel the request! */ entry_guard_cancel(&guard); - tt_assert(guard == NULL); + tt_ptr_op(guard, OP_EQ, NULL); tt_int_op(g->is_primary, OP_EQ, 0); tt_int_op(g->is_pending, OP_EQ, 0); @@ -2470,9 +2502,7 @@ test_entry_guard_upgrade_not_blocked_by_restricted_circ_complete(void *arg) /* Once more, let circ1 become complete. But this time, we'll claim * that circ2 was restricted to not use the same guard as circ1. */ data->guard2_state->restrictions = - tor_malloc_zero(sizeof(entry_guard_restriction_t)); - memcpy(data->guard2_state->restrictions->exclude_id, - data->guard1->identity, DIGEST_LEN); + guard_create_exit_restriction((uint8_t*)data->guard1->identity); smartlist_t *result = smartlist_new(); int r; @@ -2581,9 +2611,7 @@ test_entry_guard_upgrade_not_blocked_by_restricted_circ_pending(void *arg) } data->guard2_state->restrictions = - tor_malloc_zero(sizeof(entry_guard_restriction_t)); - memcpy(data->guard2_state->restrictions->exclude_id, - data->guard1->identity, DIGEST_LEN); + guard_create_exit_restriction((uint8_t*)data->guard1->identity); smartlist_t *result = smartlist_new(); int r; @@ -2651,6 +2679,114 @@ test_enty_guard_should_expire_waiting(void *arg) tor_free(fake_state); } +static void +mock_directory_initiate_request(directory_request_t *req) +{ + if (req->guard_state) { + circuit_guard_state_free(req->guard_state); + } +} + +static networkstatus_t *mock_ns_val = NULL; +static networkstatus_t * +mock_ns_get_by_flavor(consensus_flavor_t f) +{ + (void)f; + return mock_ns_val; +} + +/** Test that when we fetch microdescriptors we skip guards that have + * previously failed to serve us needed microdescriptors. */ +static void +test_entry_guard_outdated_dirserver_exclusion(void *arg) +{ + int retval; + response_handler_args_t *args = NULL; + dir_connection_t *conn = NULL; + (void) arg; + + /* Test prep: Make a new guard selection */ + guard_selection_t *gs = get_guard_selection_by_name("default", + GS_TYPE_NORMAL, 1); + + /* ... we want to use entry guards */ + or_options_t *options = get_options_mutable(); + options->UseEntryGuards = 1; + options->UseBridges = 0; + + /* ... prepare some md digests we want to download in the future */ + smartlist_t *digests = smartlist_new(); + const char *prose = "unhurried and wise, we perceive."; + for (int i = 0; i < 20; i++) { + smartlist_add(digests, (char*)prose); + } + + tt_int_op(smartlist_len(digests), OP_EQ, 20); + + /* ... now mock some functions */ + mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t)); + MOCK(networkstatus_get_latest_consensus_by_flavor, mock_ns_get_by_flavor); + MOCK(directory_initiate_request, mock_directory_initiate_request); + + /* Test logic: + * 0. Create a proper guard set and primary guard list. + * 1. Pretend to fail microdescriptor fetches from all the primary guards. + * 2. Order another microdescriptor fetch and make sure that primary guards + * get skipped since they failed previous fetches. + */ + + { /* Setup primary guard list */ + int i; + entry_guards_update_primary(gs); + for (i = 0; i < DFLT_N_PRIMARY_GUARDS; ++i) { + entry_guard_t *guard = smartlist_get(gs->sampled_entry_guards, i); + make_guard_confirmed(gs, guard); + } + entry_guards_update_primary(gs); + } + + { + /* Fail microdesc fetches with all the primary guards */ + args = tor_malloc_zero(sizeof(response_handler_args_t)); + args->status_code = 404; + args->reason = NULL; + args->body = NULL; + args->body_len = 0; + + conn = tor_malloc_zero(sizeof(dir_connection_t)); + conn->requested_resource = tor_strdup("d/jlinblackorigami"); + conn->base_.purpose = DIR_PURPOSE_FETCH_MICRODESC; + + /* Pretend to fail fetches with all primary guards */ + SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards,const entry_guard_t *,g) { + memcpy(conn->identity_digest, g->identity, DIGEST_LEN); + + retval = handle_response_fetch_microdesc(conn, args); + tt_int_op(retval, OP_EQ, 0); + } SMARTLIST_FOREACH_END(g); + } + + { + /* Now order the final md download */ + setup_full_capture_of_logs(LOG_INFO); + initiate_descriptor_downloads(NULL, DIR_PURPOSE_FETCH_MICRODESC, + digests, 3, 7, 0); + + /* ... and check that because we failed to fetch microdescs from all our + * primaries, we didnt end up selecting a primary for fetching dir info */ + expect_log_msg_containing("No primary or confirmed guards available."); + teardown_capture_of_logs(); + } + + done: + smartlist_free(digests); + tor_free(args); + if (conn) { + tor_free(conn->requested_resource); + tor_free(conn); + } +} + static const struct testcase_setup_t big_fake_network = { big_fake_network_setup, big_fake_network_cleanup }; @@ -2711,6 +2847,7 @@ struct testcase_t entrynodes_tests[] = { BFN_TEST(select_for_circuit_highlevel_primary_retry), BFN_TEST(select_and_cancel), BFN_TEST(drop_guards), + BFN_TEST(outdated_dirserver_exclusion), UPGRADE_TEST(upgrade_a_circuit, "c1-done c2-done"), UPGRADE_TEST(upgrade_blocked_by_live_primary_guards, "c1-done c2-done"), diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c index fc9f27a5ac..e18deb2700 100644 --- a/src/test/test_extorport.c +++ b/src/test/test_extorport.c @@ -78,7 +78,7 @@ connection_write_to_buf_impl_replacement(const char *string, size_t len, tor_assert(string); tor_assert(conn); - write_to_buf(string, len, conn->outbuf); + buf_add(conn->outbuf, string, len); } static char * @@ -89,7 +89,7 @@ buf_get_contents(buf_t *buf, size_t *sz_out) if (*sz_out >= ULONG_MAX) return NULL; /* C'mon, really? */ out = tor_malloc(*sz_out + 1); - if (fetch_from_buf(out, (unsigned long)*sz_out, buf) != 0) { + if (buf_get_bytes(buf, out, (unsigned long)*sz_out) != 0) { tor_free(out); return NULL; } @@ -399,14 +399,14 @@ handshake_start(or_connection_t *conn, int receiving) #define WRITE(s,n) \ do { \ - write_to_buf((s), (n), TO_CONN(conn)->inbuf); \ + buf_add(TO_CONN(conn)->inbuf, (s), (n)); \ } while (0) #define CONTAINS(s,n) \ do { \ tt_int_op((n), OP_LE, sizeof(b)); \ tt_int_op(buf_datalen(TO_CONN(conn)->outbuf), OP_EQ, (n)); \ if ((n)) { \ - fetch_from_buf(b, (n), TO_CONN(conn)->outbuf); \ + buf_get_bytes(TO_CONN(conn)->outbuf, b, (n)); \ tt_mem_op(b, OP_EQ, (s), (n)); \ } \ } while (0) @@ -497,14 +497,14 @@ test_ext_or_handshake(void *arg) "te road There is always another ", 64); /* Send the wrong response. */ WRITE("not with a bang but a whimper...", 32); - MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + MOCK(control_event_bootstrap_prob_or, ignore_bootstrap_problem); tt_int_op(-1, OP_EQ, connection_ext_or_process_inbuf(conn)); CONTAINS("\x00", 1); tt_assert(TO_CONN(conn)->marked_for_close); /* XXXX Hold-open-until-flushed. */ close_closeable_connections(); conn = NULL; - UNMOCK(control_event_bootstrap_problem); + UNMOCK(control_event_bootstrap_prob_or); MOCK(connection_start_reading, note_read_started); MOCK(connection_stop_reading, note_read_stopped); @@ -552,26 +552,26 @@ test_ext_or_handshake(void *arg) do_ext_or_handshake(conn); /* USERADDR command with an extra NUL byte */ WRITE("\x00\x01\x00\x0d""1.2.3.4:5678\x00", 17); - MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + MOCK(control_event_bootstrap_prob_or, ignore_bootstrap_problem); tt_int_op(-1, OP_EQ, connection_ext_or_process_inbuf(conn)); CONTAINS("", 0); tt_assert(TO_CONN(conn)->marked_for_close); close_closeable_connections(); conn = NULL; - UNMOCK(control_event_bootstrap_problem); + UNMOCK(control_event_bootstrap_prob_or); /* Now fail the TRANSPORT command. */ conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); do_ext_or_handshake(conn); /* TRANSPORT command with an extra NUL byte */ WRITE("\x00\x02\x00\x08""rfc1149\x00", 12); - MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + MOCK(control_event_bootstrap_prob_or, ignore_bootstrap_problem); tt_int_op(-1, OP_EQ, connection_ext_or_process_inbuf(conn)); CONTAINS("", 0); tt_assert(TO_CONN(conn)->marked_for_close); close_closeable_connections(); conn = NULL; - UNMOCK(control_event_bootstrap_problem); + UNMOCK(control_event_bootstrap_prob_or); /* Now fail the TRANSPORT command. */ conn = or_connection_new(CONN_TYPE_EXT_OR, AF_INET); @@ -579,13 +579,13 @@ test_ext_or_handshake(void *arg) /* TRANSPORT command with transport name with symbols (not a C-identifier) */ WRITE("\x00\x02\x00\x07""rf*1149", 11); - MOCK(control_event_bootstrap_problem, ignore_bootstrap_problem); + MOCK(control_event_bootstrap_prob_or, ignore_bootstrap_problem); tt_int_op(-1, OP_EQ, connection_ext_or_process_inbuf(conn)); CONTAINS("", 0); tt_assert(TO_CONN(conn)->marked_for_close); close_closeable_connections(); conn = NULL; - UNMOCK(control_event_bootstrap_problem); + UNMOCK(control_event_bootstrap_prob_or); done: UNMOCK(connection_write_to_buf_impl_); diff --git a/src/test/test_guardfraction.c b/src/test/test_guardfraction.c index 56006f3cc3..51ca8f08ec 100644 --- a/src/test/test_guardfraction.c +++ b/src/test/test_guardfraction.c @@ -38,10 +38,10 @@ gen_vote_routerstatus_for_tests(const char *digest_in_hex, int is_guard) rs->is_possible_guard = is_guard; /* Fill in the fpr */ - tt_int_op(strlen(digest_in_hex), ==, HEX_DIGEST_LEN); + tt_int_op(strlen(digest_in_hex), OP_EQ, HEX_DIGEST_LEN); retval = base16_decode(digest_tmp, sizeof(digest_tmp), digest_in_hex, HEX_DIGEST_LEN); - tt_int_op(retval, ==, sizeof(digest_tmp)); + tt_int_op(retval, OP_EQ, sizeof(digest_tmp)); memcpy(rs->identity_digest, digest_tmp, DIGEST_LEN); } @@ -88,7 +88,7 @@ test_parse_guardfraction_file_bad(void *arg) yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); tor_free(guardfraction_bad); /* This one does not have a date! Parsing should fail. */ @@ -100,7 +100,7 @@ test_parse_guardfraction_file_bad(void *arg) "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n"); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); tor_free(guardfraction_bad); /* This one has an incomplete n-inputs line, but parsing should @@ -114,7 +114,7 @@ test_parse_guardfraction_file_bad(void *arg) yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, 2); + tt_int_op(retval, OP_EQ, 2); tor_free(guardfraction_bad); /* This one does not have a fingerprint in the guard line! */ @@ -126,7 +126,7 @@ test_parse_guardfraction_file_bad(void *arg) yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); tor_free(guardfraction_bad); /* This one does not even have an integer guardfraction value. */ @@ -139,7 +139,7 @@ test_parse_guardfraction_file_bad(void *arg) yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, 1); + tt_int_op(retval, OP_EQ, 1); tor_free(guardfraction_bad); /* This one is not a percentage (not in [0, 100]) */ @@ -152,7 +152,7 @@ test_parse_guardfraction_file_bad(void *arg) yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, 1); + tt_int_op(retval, OP_EQ, 1); tor_free(guardfraction_bad); /* This one is not a percentage either (not in [0, 100]) */ @@ -164,7 +164,7 @@ test_parse_guardfraction_file_bad(void *arg) yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); done: tor_free(guardfraction_bad); @@ -216,14 +216,14 @@ test_parse_guardfraction_file_good(void *arg) /* Read the guardfraction file */ retval = dirserv_read_guardfraction_file_from_str(guardfraction_good, routerstatuses); - tt_int_op(retval, ==, 1); + tt_int_op(retval, OP_EQ, 1); { /* Test that routerstatus fields got filled properly */ /* The guardfraction fields of the guard should be filled. */ tt_assert(vrs_guard->status.has_guardfraction); tt_int_op(vrs_guard->status.guardfraction_percentage, - ==, + OP_EQ, guardfraction_value); /* The guard that was not in the guardfraction file should not have @@ -252,12 +252,12 @@ test_get_guardfraction_bandwidth(void *arg) guard_get_guardfraction_bandwidth(&gf_bw, orig_bw, 25); - tt_int_op(gf_bw.guard_bw, ==, 250); - tt_int_op(gf_bw.non_guard_bw, ==, 750); + tt_int_op(gf_bw.guard_bw, OP_EQ, 250); + tt_int_op(gf_bw.non_guard_bw, OP_EQ, 750); /* Also check the 'guard_bw + non_guard_bw == original_bw' * invariant. */ - tt_int_op(gf_bw.non_guard_bw + gf_bw.guard_bw, ==, orig_bw); + tt_int_op(gf_bw.non_guard_bw + gf_bw.guard_bw, OP_EQ, orig_bw); done: ; @@ -295,9 +295,9 @@ test_parse_guardfraction_consensus(void *arg) retval = routerstatus_parse_guardfraction(guardfraction_str_good, NULL, NULL, &rs_good); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); tt_assert(rs_good.has_guardfraction); - tt_int_op(rs_good.guardfraction_percentage, ==, 66); + tt_int_op(rs_good.guardfraction_percentage, OP_EQ, 66); } { /* Properly formatted GuardFraction but router is not a @@ -309,7 +309,7 @@ test_parse_guardfraction_consensus(void *arg) retval = routerstatus_parse_guardfraction(guardfraction_str_good, NULL, NULL, &rs_no_guard); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); tt_assert(!rs_no_guard.has_guardfraction); expect_single_log_msg_containing("Got GuardFraction for non-guard . " "This is not supposed to happen."); @@ -323,7 +323,7 @@ test_parse_guardfraction_consensus(void *arg) retval = routerstatus_parse_guardfraction(guardfraction_str_bad1, NULL, NULL, &rs_bad1); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); tt_assert(!rs_bad1.has_guardfraction); } @@ -334,7 +334,7 @@ test_parse_guardfraction_consensus(void *arg) retval = routerstatus_parse_guardfraction(guardfraction_str_bad2, NULL, NULL, &rs_bad2); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); tt_assert(!rs_bad2.has_guardfraction); } @@ -375,27 +375,27 @@ test_should_apply_guardfraction(void *arg) /* If torrc option is set to yes, we should always use * guardfraction.*/ options->UseGuardFraction = 1; - tt_int_op(should_apply_guardfraction(&vote_disabled), ==, 1); + tt_int_op(should_apply_guardfraction(&vote_disabled), OP_EQ, 1); /* If torrc option is set to no, we should never use * guardfraction.*/ options->UseGuardFraction = 0; - tt_int_op(should_apply_guardfraction(&vote_enabled), ==, 0); + tt_int_op(should_apply_guardfraction(&vote_enabled), OP_EQ, 0); /* Now let's test torrc option set to auto. */ options->UseGuardFraction = -1; /* If torrc option is set to auto, and consensus parameter is set to * yes, we should use guardfraction. */ - tt_int_op(should_apply_guardfraction(&vote_enabled), ==, 1); + tt_int_op(should_apply_guardfraction(&vote_enabled), OP_EQ, 1); /* If torrc option is set to auto, and consensus parameter is set to * no, we should use guardfraction. */ - tt_int_op(should_apply_guardfraction(&vote_disabled), ==, 0); + tt_int_op(should_apply_guardfraction(&vote_disabled), OP_EQ, 0); /* If torrc option is set to auto, and consensus parameter is not * set, we should fallback to "no". */ - tt_int_op(should_apply_guardfraction(&vote_missing), ==, 0); + tt_int_op(should_apply_guardfraction(&vote_missing), OP_EQ, 0); done: SMARTLIST_FOREACH(vote_enabled.net_params, char *, cp, tor_free(cp)); diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c index 9fada5a675..0da9cf64d0 100644 --- a/src/test/test_helpers.c +++ b/src/test/test_helpers.c @@ -7,16 +7,25 @@ */ #define ROUTERLIST_PRIVATE +#define CONFIG_PRIVATE +#define CONNECTION_PRIVATE +#define MAIN_PRIVATE + #include "orconfig.h" #include "or.h" +#include "buffers.h" +#include "config.h" +#include "confparse.h" +#include "connection.h" +#include "main.h" +#include "nodelist.h" #include "relay.h" #include "routerlist.h" -#include "nodelist.h" -#include "buffers.h" #include "test.h" #include "test_helpers.h" +#include "test_connection.h" #ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS DISABLE_GCC_WARNING(overlength-strings) @@ -74,16 +83,16 @@ helper_setup_fake_routerlist(void) retval = router_load_routers_from_string(TEST_DESCRIPTORS, NULL, SAVED_IN_JOURNAL, NULL, 0, NULL); - tt_int_op(retval, ==, HELPER_NUMBER_OF_DESCRIPTORS); + tt_int_op(retval, OP_EQ, HELPER_NUMBER_OF_DESCRIPTORS); /* Sanity checking of routerlist and nodelist. */ our_routerlist = router_get_routerlist(); - tt_int_op(smartlist_len(our_routerlist->routers), ==, + tt_int_op(smartlist_len(our_routerlist->routers), OP_EQ, HELPER_NUMBER_OF_DESCRIPTORS); routerlist_assert_ok(our_routerlist); our_nodelist = nodelist_get_list(); - tt_int_op(smartlist_len(our_nodelist), ==, HELPER_NUMBER_OF_DESCRIPTORS); + tt_int_op(smartlist_len(our_nodelist), OP_EQ, HELPER_NUMBER_OF_DESCRIPTORS); /* Mark all routers as non-guards but up and running! */ SMARTLIST_FOREACH_BEGIN(our_nodelist, node_t *, node) { @@ -105,7 +114,7 @@ connection_write_to_buf_mock(const char *string, size_t len, tor_assert(string); tor_assert(conn); - write_to_buf(string, len, conn->outbuf); + buf_add(conn->outbuf, string, len); } /* Set up a fake origin circuit with the specified number of cells, @@ -143,3 +152,128 @@ mock_tor_addr_lookup__fail_on_bad_addrs(const char *name, return tor_addr_lookup__real(name, family, out); } +/*********** Helper funcs for making new connections/streams *****************/ + +/* Helper for test_conn_get_connection() */ +static int +fake_close_socket(evutil_socket_t sock) +{ + (void)sock; + return 0; +} + +static int mock_connection_connect_sockaddr_called = 0; +static int fake_socket_number = TEST_CONN_FD_INIT; + +/* Helper for test_conn_get_connection() */ +static int +mock_connection_connect_sockaddr(connection_t *conn, + const struct sockaddr *sa, + socklen_t sa_len, + const struct sockaddr *bindaddr, + socklen_t bindaddr_len, + int *socket_error) +{ + (void)sa_len; + (void)bindaddr; + (void)bindaddr_len; + + tor_assert(conn); + tor_assert(sa); + tor_assert(socket_error); + + mock_connection_connect_sockaddr_called++; + + conn->s = fake_socket_number++; + tt_assert(SOCKET_OK(conn->s)); + /* We really should call tor_libevent_initialize() here. Because we don't, + * we are relying on other parts of the code not checking if the_event_base + * (and therefore event->ev_base) is NULL. */ + tt_int_op(connection_add_connecting(conn), OP_EQ, 0); + + done: + /* Fake "connected" status */ + return 1; +} + +/** Create and return a new connection/stream */ +connection_t * +test_conn_get_connection(uint8_t state, uint8_t type, uint8_t purpose) +{ + connection_t *conn = NULL; + tor_addr_t addr; + int socket_err = 0; + int in_progress = 0; + + MOCK(connection_connect_sockaddr, + mock_connection_connect_sockaddr); + MOCK(tor_close_socket, fake_close_socket); + + init_connection_lists(); + + conn = connection_new(type, TEST_CONN_FAMILY); + tt_assert(conn); + + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr); + tt_assert(!tor_addr_is_null(&addr)); + + tor_addr_copy_tight(&conn->addr, &addr); + conn->port = TEST_CONN_PORT; + mock_connection_connect_sockaddr_called = 0; + in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr, + TEST_CONN_PORT, &socket_err); + tt_int_op(mock_connection_connect_sockaddr_called, OP_EQ, 1); + tt_assert(!socket_err); + tt_assert(in_progress == 0 || in_progress == 1); + + /* fake some of the attributes so the connection looks OK */ + conn->state = state; + conn->purpose = purpose; + assert_connection_ok(conn, time(NULL)); + + UNMOCK(connection_connect_sockaddr); + UNMOCK(tor_close_socket); + return conn; + + /* On failure */ + done: + UNMOCK(connection_connect_sockaddr); + UNMOCK(tor_close_socket); + return NULL; +} + +/* Helper function to parse a set of torrc options in a text format and return + * a newly allocated or_options_t object containing the configuration. On + * error, NULL is returned indicating that the conf couldn't be parsed + * properly. */ +or_options_t * +helper_parse_options(const char *conf) +{ + int ret = 0; + char *msg = NULL; + or_options_t *opt = NULL; + config_line_t *line = NULL; + + /* Kind of pointless to call this with a NULL value. */ + tt_assert(conf); + + opt = options_new(); + tt_assert(opt); + ret = config_get_lines(conf, &line, 1); + if (ret != 0) { + goto done; + } + ret = config_assign(&options_format, opt, line, 0, &msg); + if (ret != 0) { + goto done; + } + + done: + config_free_lines(line); + if (ret != 0) { + or_options_free(opt); + opt = NULL; + } + return opt; +} + diff --git a/src/test/test_helpers.h b/src/test/test_helpers.h index 4621631cc1..9bc8553257 100644 --- a/src/test/test_helpers.h +++ b/src/test/test_helpers.h @@ -1,9 +1,11 @@ -/* Copyright (c) 2014-2017, The Tor Project, Inc. */ +/* Copyright (c) 2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #ifndef TOR_TEST_HELPERS_H #define TOR_TEST_HELPERS_H +#include "or.h" + const char *get_yesterday_date_str(void); circuit_t * dummy_origin_circuit_new(int num_cells); @@ -20,7 +22,11 @@ void connection_write_to_buf_mock(const char *string, size_t len, int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name, uint16_t family, tor_addr_t *out); +connection_t *test_conn_get_connection(uint8_t state, + uint8_t type, uint8_t purpose); +or_options_t *helper_parse_options(const char *conf); + extern const char TEST_DESCRIPTORS[]; -#endif +#endif /* !defined(TOR_TEST_HELPERS_H) */ diff --git a/src/test/test_hs.c b/src/test/test_hs.c index 5aae6c5b97..7737499f50 100644 --- a/src/test/test_hs.c +++ b/src/test/test_hs.c @@ -8,7 +8,9 @@ #define CONTROL_PRIVATE #define CIRCUITBUILD_PRIVATE +#define RENDCOMMON_PRIVATE #define RENDSERVICE_PRIVATE +#define HS_SERVICE_PRIVATE #include "or.h" #include "test.h" @@ -32,8 +34,9 @@ #define STR_HSDIR_NONE_EXIST_LONGNAME \ "$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" -/* DuckDuckGo descriptor as an example. */ -static const char *hs_desc_content = "\ +/* DuckDuckGo descriptor as an example. This one has extra "\r" at the end so + * the control port is happy. */ +static const char *hs_desc_content_control = "\ rendezvous-service-descriptor g5ojobzupf275beh5ra72uyhb3dkpxwg\r\n\ version 2\r\n\ permanent-key\r\n\ @@ -94,6 +97,68 @@ PcftsZf2ztN0sbNCtPgDL3d0PqvxY3iHTQAI8EbaGq/IAJUZ8U4y963dD5+Bn6JQ\r\n\ myE3ctmh0vy5+QxSiRjmQBkuEpCyks7LvWvHYrhnmcg=\r\n\ -----END SIGNATURE-----"; +/* DuckDuckGo descriptor as an example. */ +static const char *hs_desc_content = "\ +rendezvous-service-descriptor g5ojobzupf275beh5ra72uyhb3dkpxwg\n\ +version 2\n\ +permanent-key\n\ +-----BEGIN RSA PUBLIC KEY-----\n\ +MIGJAoGBAJ/SzzgrXPxTlFrKVhXh3buCWv2QfcNgncUpDpKouLn3AtPH5Ocys0jE\n\ +aZSKdvaiQ62md2gOwj4x61cFNdi05tdQjS+2thHKEm/KsB9BGLSLBNJYY356bupg\n\ +I5gQozM65ENelfxYlysBjJ52xSDBd8C4f/p9umdzaaaCmzXG/nhzAgMBAAE=\n\ +-----END RSA PUBLIC KEY-----\n\ +secret-id-part anmjoxxwiupreyajjt5yasimfmwcnxlf\n\ +publication-time 2015-03-11 19:00:00\n\ +protocol-versions 2,3\n\ +introduction-points\n\ +-----BEGIN MESSAGE-----\n\ +aW50cm9kdWN0aW9uLXBvaW50IDd1bnd4cmg2dG5kNGh6eWt1Z3EzaGZzdHduc2ll\n\ +cmhyCmlwLWFkZHJlc3MgMTg4LjEzOC4xMjEuMTE4Cm9uaW9uLXBvcnQgOTAwMQpv\n\ +bmlvbi1rZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dC\n\ +QUxGRVVyeVpDbk9ROEhURmV5cDVjMTRObWVqL1BhekFLTTBxRENTNElKUWh0Y3g1\n\ +NXpRSFdOVWIKQ2hHZ0JqR1RjV3ZGRnA0N3FkdGF6WUZhVXE2c0lQKzVqeWZ5b0Q4\n\ +UmJ1bzBwQmFWclJjMmNhYUptWWM0RDh6Vgpuby9sZnhzOVVaQnZ1cWY4eHIrMDB2\n\ +S0JJNmFSMlA2OE1WeDhrMExqcUpUU2RKOE9idm9yQWdNQkFBRT0KLS0tLS1FTkQg\n\ +UlNBIFBVQkxJQyBLRVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQ\n\ +VUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTnJHb0ozeTlHNXQzN2F2ekI1cTlwN1hG\n\ +VUplRUVYMUNOaExnWmJXWGJhVk5OcXpoZFhyL0xTUQppM1Z6dW5OaUs3cndUVnE2\n\ +K2QyZ1lRckhMMmIvMXBBY3ZKWjJiNSs0bTRRc0NibFpjRENXTktRbHJnRWN5WXRJ\n\ +CkdscXJTbFFEaXA0ZnNrUFMvNDVkWTI0QmJsQ3NGU1k3RzVLVkxJck4zZFpGbmJr\n\ +NEZIS1hBZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJv\n\ +ZHVjdGlvbi1wb2ludCBiNGM3enlxNXNheGZzN2prNXFibG1wN3I1b3pwdHRvagpp\n\ +cC1hZGRyZXNzIDEwOS4xNjkuNDUuMjI2Cm9uaW9uLXBvcnQgOTAwMQpvbmlvbi1r\n\ +ZXkKLS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JR0pBb0dCQU8xSXpw\n\ +WFFUTUY3RXZUb1NEUXpzVnZiRVFRQUQrcGZ6NzczMVRXZzVaUEJZY1EyUkRaeVp4\n\ +OEQKNUVQSU1FeUE1RE83cGd0ak5LaXJvYXJGMC8yempjMkRXTUlSaXZyU29YUWVZ\n\ +ZXlMM1pzKzFIajJhMDlCdkYxZAp6MEswblRFdVhoNVR5V3lyMHdsbGI1SFBnTlI0\n\ +MS9oYkprZzkwZitPVCtIeGhKL1duUml2QWdNQkFBRT0KLS0tLS1FTkQgUlNBIFBV\n\ +QkxJQyBLRVktLS0tLQpzZXJ2aWNlLWtleQotLS0tLUJFR0lOIFJTQSBQVUJMSUMg\n\ +S0VZLS0tLS0KTUlHSkFvR0JBSzNWZEJ2ajFtQllLL3JrcHNwcm9Ub0llNUtHVmth\n\ +QkxvMW1tK1I2YUVJek1VZFE1SjkwNGtyRwpCd3k5NC8rV0lGNFpGYXh5Z2phejl1\n\ +N2pKY1k3ZGJhd1pFeG1hYXFCRlRwL2h2ZG9rcHQ4a1ByRVk4OTJPRHJ1CmJORUox\n\ +N1FPSmVMTVZZZk5Kcjl4TWZCQ3JQai8zOGh2RUdrbWVRNmRVWElvbVFNaUJGOVRB\n\ +Z01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0tLS0tCmludHJvZHVjdGlv\n\ +bi1wb2ludCBhdjVtcWl0Y2Q3cjJkandsYmN0c2Jlc2R3eGt0ZWtvegppcC1hZGRy\n\ +ZXNzIDE0NC43Ni44LjczCm9uaW9uLXBvcnQgNDQzCm9uaW9uLWtleQotLS0tLUJF\n\ +R0lOIFJTQSBQVUJMSUMgS0VZLS0tLS0KTUlHSkFvR0JBTzVweVZzQmpZQmNmMXBE\n\ +dklHUlpmWXUzQ05nNldka0ZLMGlvdTBXTGZtejZRVDN0NWhzd3cyVwpjejlHMXhx\n\ +MmN0Nkd6VWkrNnVkTDlITTRVOUdHTi9BbW8wRG9GV1hKWHpBQkFXd2YyMVdsd1lW\n\ +eFJQMHRydi9WCkN6UDkzcHc5OG5vSmdGUGRUZ05iMjdKYmVUZENLVFBrTEtscXFt\n\ +b3NveUN2RitRa25vUS9BZ01CQUFFPQotLS0tLUVORCBSU0EgUFVCTElDIEtFWS0t\n\ +LS0tCnNlcnZpY2Uta2V5Ci0tLS0tQkVHSU4gUlNBIFBVQkxJQyBLRVktLS0tLQpN\n\ +SUdKQW9HQkFMVjNKSmtWN3lTNU9jc1lHMHNFYzFQOTVRclFRR3ZzbGJ6Wi9zRGxl\n\ +RlpKYXFSOUYvYjRUVERNClNGcFMxcU1GbldkZDgxVmRGMEdYRmN2WVpLamRJdHU2\n\ +SndBaTRJeEhxeXZtdTRKdUxrcXNaTEFLaXRLVkx4eGsKeERlMjlDNzRWMmJrOTRJ\n\ +MEgybTNKS2tzTHVwc3VxWWRVUmhOVXN0SElKZmgyZmNIalF0bEFnTUJBQUU9Ci0t\n\ +LS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0KCg==\n\ +-----END MESSAGE-----\n\ +signature\n\ +-----BEGIN SIGNATURE-----\n\ +d4OuCE5OLAOnRB6cQN6WyMEmg/BHem144Vec+eYgeWoKwx3MxXFplUjFxgnMlmwN\n\ +PcftsZf2ztN0sbNCtPgDL3d0PqvxY3iHTQAI8EbaGq/IAJUZ8U4y963dD5+Bn6JQ\n\ +myE3ctmh0vy5+QxSiRjmQBkuEpCyks7LvWvHYrhnmcg=\n\ +-----END SIGNATURE-----"; + /* Helper global variable for hidden service descriptor event test. * It's used as a pointer to dynamically created message buffer in * send_control_event_string_replacement function, which mocks @@ -125,6 +190,30 @@ node_describe_longname_by_id_replacement(const char *id_digest) } } +/** Test that we can parse a hardcoded v2 HS desc. */ +static void +test_hs_parse_static_v2_desc(void *arg) +{ + int ret; + rend_encoded_v2_service_descriptor_t desc; + + (void) arg; + + /* Test an obviously not parseable string */ + desc.desc_str = tor_strdup("ceci n'est pas un HS descriptor"); + ret = rend_desc_v2_is_parsable(&desc); + tor_free(desc.desc_str); + tt_int_op(ret, OP_EQ, 0); + + /* Test an actual descriptor */ + desc.desc_str = tor_strdup(hs_desc_content); + ret = rend_desc_v2_is_parsable(&desc); + tor_free(desc.desc_str); + tt_int_op(ret, OP_EQ, 1); + + done: ; +} + /** Make sure each hidden service descriptor async event generation * * function generates the message in expected format. @@ -161,7 +250,7 @@ test_hs_desc_event(void *arg) ret = rend_compute_v2_desc_id(rend_query.descriptor_id[0], rend_query.onion_address, NULL, 0, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); base32_encode(desc_id_base32, sizeof(desc_id_base32), rend_query.descriptor_id[0], DIGEST_LEN); /* Make sure rend_compute_v2_desc_id works properly. */ @@ -235,10 +324,10 @@ test_hs_desc_event(void *arg) /* test valid content. */ control_event_hs_descriptor_content(rend_query.onion_address, STR_HS_CONTENT_DESC_ID, HSDIR_EXIST_ID, - hs_desc_content); + hs_desc_content_control); tor_asprintf(&exp_msg, "650+HS_DESC_CONTENT " STR_HS_ADDR " "\ STR_HS_CONTENT_DESC_ID " " STR_HSDIR_EXIST_LONGNAME\ - "\r\n%s\r\n.\r\n650 OK\r\n", hs_desc_content); + "\r\n%s\r\n.\r\n650 OK\r\n", hs_desc_content_control); tt_assert(received_msg); tt_str_op(received_msg, OP_EQ, exp_msg); @@ -274,14 +363,14 @@ test_pick_tor2web_rendezvous_node(void *arg) retval = routerset_parse(options->Tor2webRendezvousPoints, tor2web_rendezvous_str, "test_tor2web_rp"); - tt_int_op(retval, >=, 0); + tt_int_op(retval, OP_GE, 0); /* Pick rendezvous point. Make sure the correct one is picked. Repeat many times to make sure it works properly. */ for (i = 0; i < 50 ; i++) { chosen_rp = pick_tor2web_rendezvous_node(flags, options); tt_assert(chosen_rp); - tt_str_op(chosen_rp->ri->nickname, ==, tor2web_rendezvous_str); + tt_str_op(chosen_rp->ri->nickname, OP_EQ, tor2web_rendezvous_str); } done: @@ -309,13 +398,13 @@ test_pick_bad_tor2web_rendezvous_node(void *arg) retval = routerset_parse(options->Tor2webRendezvousPoints, tor2web_rendezvous_str, "test_tor2web_rp"); - tt_int_op(retval, >=, 0); + tt_int_op(retval, OP_GE, 0); /* Pick rendezvous point. Since Tor2webRendezvousPoints was set to a dummy value, we shouldn't find any eligible RPs. */ for (i = 0; i < 50 ; i++) { chosen_rp = pick_tor2web_rendezvous_node(flags, options); - tt_assert(!chosen_rp); + tt_ptr_op(chosen_rp, OP_EQ, NULL); } done: @@ -346,30 +435,30 @@ test_hs_rend_data(void *arg) REND_NO_AUTH); tt_assert(client); rend_data_v2_t *client_v2 = TO_REND_DATA_V2(client); - tt_int_op(client_v2->auth_type, ==, REND_NO_AUTH); + tt_int_op(client_v2->auth_type, OP_EQ, REND_NO_AUTH); tt_str_op(client_v2->onion_address, OP_EQ, STR_HS_ADDR); tt_mem_op(client_v2->desc_id_fetch, OP_EQ, desc_id, sizeof(desc_id)); tt_mem_op(client_v2->descriptor_cookie, OP_EQ, client_cookie, sizeof(client_cookie)); tt_assert(client->hsdirs_fp); - tt_int_op(smartlist_len(client->hsdirs_fp), ==, 0); + tt_int_op(smartlist_len(client->hsdirs_fp), OP_EQ, 0); for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) { int ret = rend_compute_v2_desc_id(desc_id, client_v2->onion_address, client_v2->descriptor_cookie, now, rep); /* That shouldn't never fail. */ - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); tt_mem_op(client_v2->descriptor_id[rep], OP_EQ, desc_id, sizeof(desc_id)); } /* The rest should be zeroed because this is a client request. */ - tt_int_op(tor_digest_is_zero(client_v2->rend_pk_digest), ==, 1); - tt_int_op(tor_digest_is_zero(client->rend_cookie), ==, 1); + tt_int_op(tor_digest_is_zero(client_v2->rend_pk_digest), OP_EQ, 1); + tt_int_op(tor_digest_is_zero(client->rend_cookie), OP_EQ, 1); /* Test dup(). */ client_dup = rend_data_dup(client); tt_assert(client_dup); rend_data_v2_t *client_dup_v2 = TO_REND_DATA_V2(client_dup); - tt_int_op(client_dup_v2->auth_type, ==, client_v2->auth_type); + tt_int_op(client_dup_v2->auth_type, OP_EQ, client_v2->auth_type); tt_str_op(client_dup_v2->onion_address, OP_EQ, client_v2->onion_address); tt_mem_op(client_dup_v2->desc_id_fetch, OP_EQ, client_v2->desc_id_fetch, sizeof(client_dup_v2->desc_id_fetch)); @@ -378,14 +467,14 @@ test_hs_rend_data(void *arg) sizeof(client_dup_v2->descriptor_cookie)); tt_assert(client_dup->hsdirs_fp); - tt_int_op(smartlist_len(client_dup->hsdirs_fp), ==, 0); + tt_int_op(smartlist_len(client_dup->hsdirs_fp), OP_EQ, 0); for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) { tt_mem_op(client_dup_v2->descriptor_id[rep], OP_EQ, client_v2->descriptor_id[rep], DIGEST_LEN); } /* The rest should be zeroed because this is a client request. */ - tt_int_op(tor_digest_is_zero(client_dup_v2->rend_pk_digest), ==, 1); - tt_int_op(tor_digest_is_zero(client_dup->rend_cookie), ==, 1); + tt_int_op(tor_digest_is_zero(client_dup_v2->rend_pk_digest), OP_EQ, 1); + tt_int_op(tor_digest_is_zero(client_dup->rend_cookie), OP_EQ, 1); rend_data_free(client); client = NULL; rend_data_free(client_dup); @@ -401,19 +490,19 @@ test_hs_rend_data(void *arg) client = rend_data_client_create(NULL, desc_id, NULL, REND_BASIC_AUTH); tt_assert(client); client_v2 = TO_REND_DATA_V2(client); - tt_int_op(client_v2->auth_type, ==, REND_BASIC_AUTH); - tt_int_op(strlen(client_v2->onion_address), ==, 0); + tt_int_op(client_v2->auth_type, OP_EQ, REND_BASIC_AUTH); + tt_int_op(strlen(client_v2->onion_address), OP_EQ, 0); tt_mem_op(client_v2->desc_id_fetch, OP_EQ, desc_id, sizeof(desc_id)); tt_int_op(tor_mem_is_zero(client_v2->descriptor_cookie, - sizeof(client_v2->descriptor_cookie)), ==, 1); + sizeof(client_v2->descriptor_cookie)), OP_EQ, 1); tt_assert(client->hsdirs_fp); - tt_int_op(smartlist_len(client->hsdirs_fp), ==, 0); + tt_int_op(smartlist_len(client->hsdirs_fp), OP_EQ, 0); for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) { - tt_int_op(tor_digest_is_zero(client_v2->descriptor_id[rep]), ==, 1); + tt_int_op(tor_digest_is_zero(client_v2->descriptor_id[rep]), OP_EQ, 1); } /* The rest should be zeroed because this is a client request. */ - tt_int_op(tor_digest_is_zero(client_v2->rend_pk_digest), ==, 1); - tt_int_op(tor_digest_is_zero(client->rend_cookie), ==, 1); + tt_int_op(tor_digest_is_zero(client_v2->rend_pk_digest), OP_EQ, 1); + tt_int_op(tor_digest_is_zero(client->rend_cookie), OP_EQ, 1); rend_data_free(client); client = NULL; @@ -427,38 +516,38 @@ test_hs_rend_data(void *arg) rend_cookie, REND_NO_AUTH); tt_assert(service); rend_data_v2_t *service_v2 = TO_REND_DATA_V2(service); - tt_int_op(service_v2->auth_type, ==, REND_NO_AUTH); + tt_int_op(service_v2->auth_type, OP_EQ, REND_NO_AUTH); tt_str_op(service_v2->onion_address, OP_EQ, STR_HS_ADDR); tt_mem_op(service_v2->rend_pk_digest, OP_EQ, rend_pk_digest, sizeof(rend_pk_digest)); tt_mem_op(service->rend_cookie, OP_EQ, rend_cookie, sizeof(rend_cookie)); tt_assert(service->hsdirs_fp); - tt_int_op(smartlist_len(service->hsdirs_fp), ==, 0); + tt_int_op(smartlist_len(service->hsdirs_fp), OP_EQ, 0); for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) { - tt_int_op(tor_digest_is_zero(service_v2->descriptor_id[rep]), ==, 1); + tt_int_op(tor_digest_is_zero(service_v2->descriptor_id[rep]), OP_EQ, 1); } /* The rest should be zeroed because this is a service request. */ - tt_int_op(tor_digest_is_zero(service_v2->descriptor_cookie), ==, 1); - tt_int_op(tor_digest_is_zero(service_v2->desc_id_fetch), ==, 1); + tt_int_op(tor_digest_is_zero(service_v2->descriptor_cookie), OP_EQ, 1); + tt_int_op(tor_digest_is_zero(service_v2->desc_id_fetch), OP_EQ, 1); /* Test dup(). */ service_dup = rend_data_dup(service); rend_data_v2_t *service_dup_v2 = TO_REND_DATA_V2(service_dup); tt_assert(service_dup); - tt_int_op(service_dup_v2->auth_type, ==, service_v2->auth_type); + tt_int_op(service_dup_v2->auth_type, OP_EQ, service_v2->auth_type); tt_str_op(service_dup_v2->onion_address, OP_EQ, service_v2->onion_address); tt_mem_op(service_dup_v2->rend_pk_digest, OP_EQ, service_v2->rend_pk_digest, sizeof(service_dup_v2->rend_pk_digest)); tt_mem_op(service_dup->rend_cookie, OP_EQ, service->rend_cookie, sizeof(service_dup->rend_cookie)); tt_assert(service_dup->hsdirs_fp); - tt_int_op(smartlist_len(service_dup->hsdirs_fp), ==, 0); + tt_int_op(smartlist_len(service_dup->hsdirs_fp), OP_EQ, 0); for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) { - tt_int_op(tor_digest_is_zero(service_dup_v2->descriptor_id[rep]), ==, 1); + tt_assert(tor_digest_is_zero(service_dup_v2->descriptor_id[rep])); } /* The rest should be zeroed because this is a service request. */ - tt_int_op(tor_digest_is_zero(service_dup_v2->descriptor_cookie), ==, 1); - tt_int_op(tor_digest_is_zero(service_dup_v2->desc_id_fetch), ==, 1); + tt_int_op(tor_digest_is_zero(service_dup_v2->descriptor_cookie), OP_EQ, 1); + tt_int_op(tor_digest_is_zero(service_dup_v2->desc_id_fetch), OP_EQ, 1); done: rend_data_free(service); @@ -497,7 +586,7 @@ test_hs_auth_cookies(void *arg) re = rend_auth_decode_cookie(TEST_COOKIE_ENCODED, raw_cookie, &auth_type, &err_msg); tt_assert(!re); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); tt_mem_op(raw_cookie, OP_EQ, TEST_COOKIE_RAW, REND_DESC_COOKIE_LEN); tt_int_op(auth_type, OP_EQ, REND_BASIC_AUTH); memset(raw_cookie, 0, sizeof(raw_cookie)); @@ -505,7 +594,7 @@ test_hs_auth_cookies(void *arg) re = rend_auth_decode_cookie(TEST_COOKIE_ENCODED_STEALTH, raw_cookie, &auth_type, &err_msg); tt_assert(!re); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); tt_mem_op(raw_cookie, OP_EQ, TEST_COOKIE_RAW, REND_DESC_COOKIE_LEN); tt_int_op(auth_type, OP_EQ, REND_STEALTH_AUTH); memset(raw_cookie, 0, sizeof(raw_cookie)); @@ -514,7 +603,7 @@ test_hs_auth_cookies(void *arg) re = rend_auth_decode_cookie(TEST_COOKIE_ENCODED "==", raw_cookie, NULL, &err_msg); tt_assert(!re); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); tt_mem_op(raw_cookie, OP_EQ, TEST_COOKIE_RAW, REND_DESC_COOKIE_LEN); /* Decoding with an unknown type should fail */ @@ -574,31 +663,22 @@ test_single_onion_poisoning(void *arg) char *poison_path = NULL; char *err_msg = NULL; - /* No services, no service to verify, no problem! */ - mock_options->HiddenServiceSingleHopMode = 0; - mock_options->HiddenServiceNonAnonymousMode = 0; - ret = rend_config_services(mock_options, 1); - tt_assert(ret == 0); - - /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; - ret = rend_config_services(mock_options, 1); - tt_assert(ret == 0); /* Create the data directory, and, if the correct bit in arg is set, * create a directory for that service. * The data directory is required for the lockfile, which is used when * loading keys. */ ret = check_private_dir(mock_options->DataDirectory, CPD_CREATE, NULL); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); if (create_dir_mask & CREATE_HS_DIR1) { ret = check_private_dir(dir1, CPD_CREATE, NULL); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); } if (create_dir_mask & CREATE_HS_DIR2) { ret = check_private_dir(dir2, CPD_CREATE, NULL); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); } service_1->directory = dir1; @@ -611,190 +691,194 @@ test_single_onion_poisoning(void *arg) rend_service_port_config_t *port1 = rend_service_parse_port_config("80", " ", &err_msg); tt_assert(port1); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); smartlist_add(service_1->ports, port1); rend_service_port_config_t *port2 = rend_service_parse_port_config("90", " ", &err_msg); /* Add port to service 2 */ tt_assert(port2); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); smartlist_add(service_2->ports, port2); /* No services, a service to verify, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Add the first service */ - ret = rend_service_check_dir_and_add(services, mock_options, service_1, 0); - tt_assert(ret == 0); + ret = hs_check_service_private_dir(mock_options->User, service_1->directory, + service_1->dir_group_readable, 1); + tt_int_op(ret, OP_EQ, 0); + smartlist_add(services, service_1); /* But don't add the second service yet. */ /* Service directories, but no previous keys, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Poison! Poison! Poison! * This can only be done in HiddenServiceSingleHopMode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Poisoning twice is a no-op. */ ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Poisoned service directories, but no previous keys, no problem! */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Either way, no problem. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Now add some keys, and we'll have a problem. */ ret = rend_service_load_all_keys(services); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Poisoned service directories with previous keys are not allowed. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret < 0); + tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* But they are allowed if we're in non-anonymous mode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Re-poisoning directories with existing keys is a no-op, because * directories with existing keys are ignored. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* And it keeps the poison. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Now add the second service: it has no key and no poison file */ - ret = rend_service_check_dir_and_add(services, mock_options, service_2, 0); - tt_assert(ret == 0); + ret = hs_check_service_private_dir(mock_options->User, service_2->directory, + service_2->dir_group_readable, 1); + tt_int_op(ret, OP_EQ, 0); + smartlist_add(services, service_2); /* A new service, and an existing poisoned service. Not ok. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret < 0); + tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* But ok to add in non-anonymous mode. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Now remove the poisoning from the first service, and we have the opposite * problem. */ poison_path = rend_service_sos_poison_path(service_1); tt_assert(poison_path); ret = unlink(poison_path); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Unpoisoned service directories with previous keys are ok, as are empty * directories. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* But the existing unpoisoned key is not ok in non-anonymous mode, even if * there is an empty service. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret < 0); + tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Poisoning directories with existing keys is a no-op, because directories * with existing keys are ignored. But the new directory should poison. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_poison_new_single_onion_dir(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* And the old directory remains unpoisoned. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret < 0); + tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* And the new directory should be ignored, because it has no key. */ mock_options->HiddenServiceSingleHopMode = 0; mock_options->HiddenServiceNonAnonymousMode = 0; ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Re-poisoning directories without existing keys is a no-op. */ mock_options->HiddenServiceSingleHopMode = 1; mock_options->HiddenServiceNonAnonymousMode = 1; ret = rend_service_poison_new_single_onion_dir(service_1, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = rend_service_poison_new_single_onion_dir(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* And the old directory remains unpoisoned. */ ret = rend_service_verify_single_onion_poison(service_1, mock_options); - tt_assert(ret < 0); + tt_int_op(ret, OP_LT, 0); ret = rend_service_verify_single_onion_poison(service_2, mock_options); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); done: /* The test harness deletes the directories at exit */ @@ -942,6 +1026,8 @@ test_prune_services_on_reload(void *arg) struct testcase_t hs_tests[] = { { "hs_rend_data", test_hs_rend_data, TT_FORK, NULL, NULL }, + { "hs_parse_static_v2_desc", test_hs_parse_static_v2_desc, TT_FORK, + NULL, NULL }, { "hs_desc_event", test_hs_desc_event, TT_FORK, NULL, NULL }, { "pick_tor2web_rendezvous_node", test_pick_tor2web_rendezvous_node, TT_FORK, diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index 40f50b322a..91b13be862 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -7,13 +7,16 @@ */ #define CONNECTION_PRIVATE +#define DIRECTORY_PRIVATE #define HS_CACHE_PRIVATE #include "ed25519_cert.h" #include "hs_cache.h" #include "rendcache.h" #include "directory.h" +#include "networkstatus.h" #include "connection.h" +#include "proto_http.h" #include "hs_test_helpers.h" #include "test_helpers.h" @@ -54,7 +57,7 @@ test_directory(void *arg) init_test(); /* Generate a valid descriptor with normal values. */ ret = ed25519_keypair_generate(&signing_kp1, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1); tt_assert(desc1); ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str); @@ -78,7 +81,7 @@ test_directory(void *arg) /* Tell our OOM to run and to at least remove a byte which will result in * removing the descriptor from our cache. */ oom_size = hs_cache_handle_oom(time(NULL), 1); - tt_int_op(oom_size, >=, 1); + tt_int_op(oom_size, OP_GE, 1); ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), NULL); tt_int_op(ret, OP_EQ, 0); } @@ -87,7 +90,7 @@ test_directory(void *arg) { ed25519_keypair_t signing_kp_zero; ret = ed25519_keypair_generate(&signing_kp_zero, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); hs_descriptor_t *desc_zero_lifetime; desc_zero_lifetime = hs_helper_build_hs_desc_with_ip(&signing_kp_zero); tt_assert(desc_zero_lifetime); @@ -115,7 +118,7 @@ test_directory(void *arg) tt_int_op(ret, OP_EQ, 0); /* Cleanup our entire cache. */ oom_size = hs_cache_handle_oom(time(NULL), 1); - tt_int_op(oom_size, >=, 1); + tt_int_op(oom_size, OP_GE, 1); hs_descriptor_free(desc_zero_lifetime); tor_free(desc_zero_lifetime_str); } @@ -177,7 +180,7 @@ test_clean_as_dir(void *arg) /* Generate a valid descriptor with values. */ ret = ed25519_keypair_generate(&signing_kp1, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); desc1 = hs_helper_build_hs_desc_with_ip(&signing_kp1); tt_assert(desc1); ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str); @@ -187,21 +190,21 @@ test_clean_as_dir(void *arg) /* With the lifetime being 3 hours, a cleanup shouldn't remove it. */ ret = cache_clean_v3_as_dir(now, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); /* Should be present after clean up. */ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), NULL); tt_int_op(ret, OP_EQ, 1); /* Set a cutoff 100 seconds in the past. It should not remove the entry * since the entry is still recent enough. */ ret = cache_clean_v3_as_dir(now, now - 100); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); /* Should be present after clean up. */ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), NULL); tt_int_op(ret, OP_EQ, 1); /* Set a cutoff of 100 seconds in the future. It should remove the entry * that we've just added since it's not too old for the cutoff. */ ret = cache_clean_v3_as_dir(now, now + 100); - tt_int_op(ret, >, 0); + tt_int_op(ret, OP_GT, 0); /* Shouldn't be present after clean up. */ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), NULL); tt_int_op(ret, OP_EQ, 0); @@ -231,7 +234,7 @@ helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key) retval = ed25519_public_to_base64(hsdir_cache_key, blinded_key); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); tor_asprintf(&hsdir_query_str, GET("/tor/hs/3/%s"), hsdir_cache_key); } @@ -290,7 +293,7 @@ test_upload_and_download_hs_desc(void *arg) { ed25519_keypair_t signing_kp; retval = ed25519_keypair_generate(&signing_kp, 0); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, @@ -301,7 +304,7 @@ test_upload_and_download_hs_desc(void *arg) /* Publish descriptor to the HSDir */ { retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str); - tt_int_op(retval, ==, 200); + tt_int_op(retval, OP_EQ, 200); } /* Simulate a fetch of the previously published descriptor */ @@ -342,6 +345,7 @@ test_hsdir_revision_counter_check(void *arg) hs_descriptor_t *published_desc = NULL; char *published_desc_str = NULL; + uint8_t subcredential[DIGEST256_LEN]; char *received_desc_str = NULL; hs_descriptor_t *received_desc = NULL; @@ -353,7 +357,7 @@ test_hsdir_revision_counter_check(void *arg) /* Generate a valid descriptor with normal values. */ { retval = ed25519_keypair_generate(&signing_kp, 0); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); tt_assert(published_desc); retval = hs_desc_encode_descriptor(published_desc, &signing_kp, @@ -364,13 +368,13 @@ test_hsdir_revision_counter_check(void *arg) /* Publish descriptor to the HSDir */ { retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str); - tt_int_op(retval, ==, 200); + tt_int_op(retval, OP_EQ, 200); } /* Try publishing again with the same revision counter: Should fail. */ { retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str); - tt_int_op(retval, ==, 400); + tt_int_op(retval, OP_EQ, 400); } /* Fetch the published descriptor and validate the revision counter. */ @@ -378,14 +382,16 @@ test_hsdir_revision_counter_check(void *arg) const ed25519_public_key_t *blinded_key; blinded_key = &published_desc->plaintext_data.blinded_pubkey; + hs_get_subcredential(&signing_kp.pubkey, blinded_key, subcredential); received_desc_str = helper_fetch_desc_from_hsdir(blinded_key); - retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc); - tt_int_op(retval, ==, 0); + retval = hs_desc_decode_descriptor(received_desc_str, + subcredential, &received_desc); + tt_int_op(retval, OP_EQ, 0); tt_assert(received_desc); /* Check that the revision counter is correct */ - tt_u64_op(received_desc->plaintext_data.revision_counter, ==, 42); + tt_u64_op(received_desc->plaintext_data.revision_counter, OP_EQ, 42); hs_descriptor_free(received_desc); received_desc = NULL; @@ -401,7 +407,7 @@ test_hsdir_revision_counter_check(void *arg) tt_int_op(retval, OP_EQ, 0); retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str); - tt_int_op(retval, ==, 200); + tt_int_op(retval, OP_EQ, 200); } /* Again, fetch the published descriptor and perform the revision counter @@ -412,12 +418,13 @@ test_hsdir_revision_counter_check(void *arg) blinded_key = &published_desc->plaintext_data.blinded_pubkey; received_desc_str = helper_fetch_desc_from_hsdir(blinded_key); - retval = hs_desc_decode_descriptor(received_desc_str,NULL, &received_desc); - tt_int_op(retval, ==, 0); + retval = hs_desc_decode_descriptor(received_desc_str, + subcredential, &received_desc); + tt_int_op(retval, OP_EQ, 0); tt_assert(received_desc); /* Check that the revision counter is the latest */ - tt_u64_op(received_desc->plaintext_data.revision_counter, ==, 1313); + tt_u64_op(received_desc->plaintext_data.revision_counter, OP_EQ, 1313); } done: @@ -427,6 +434,115 @@ test_hsdir_revision_counter_check(void *arg) tor_free(published_desc_str); } +static networkstatus_t mock_ns; + +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + return &mock_ns; +} + +/** Test that we can store HS descriptors in the client HS cache. */ +static void +test_client_cache(void *arg) +{ + int retval; + ed25519_keypair_t signing_kp; + hs_descriptor_t *published_desc = NULL; + char *published_desc_str = NULL; + uint8_t wanted_subcredential[DIGEST256_LEN]; + response_handler_args_t *args = NULL; + dir_connection_t *conn = NULL; + + (void) arg; + + /* Initialize HSDir cache subsystem */ + init_test(); + + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + /* Set consensus time */ + parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); + parse_rfc1123_time("Sat, 26 Oct 1985 16:00:00 UTC", + &mock_ns.valid_until); + + /* Generate a valid descriptor with normal values. */ + { + retval = ed25519_keypair_generate(&signing_kp, 0); + tt_int_op(retval, OP_EQ, 0); + published_desc = hs_helper_build_hs_desc_with_ip(&signing_kp); + tt_assert(published_desc); + retval = hs_desc_encode_descriptor(published_desc, &signing_kp, + &published_desc_str); + tt_int_op(retval, OP_EQ, 0); + memcpy(wanted_subcredential, published_desc->subcredential, DIGEST256_LEN); + tt_assert(!tor_mem_is_zero((char*)wanted_subcredential, DIGEST256_LEN)); + } + + /* Test handle_response_fetch_hsdesc_v3() */ + { + args = tor_malloc_zero(sizeof(response_handler_args_t)); + args->status_code = 200; + args->reason = NULL; + args->body = published_desc_str; + args->body_len = strlen(published_desc_str); + + conn = tor_malloc_zero(sizeof(dir_connection_t)); + conn->hs_ident = tor_malloc_zero(sizeof(hs_ident_dir_conn_t)); + ed25519_pubkey_copy(&conn->hs_ident->identity_pk, &signing_kp.pubkey); + } + + /* store the descriptor! */ + retval = handle_response_fetch_hsdesc_v3(conn, args); + tt_int_op(retval, == , 0); + + /* Progress time a bit and attempt to clean cache: our desc should not be + * cleaned since we still in the same TP. */ + { + parse_rfc1123_time("Sat, 27 Oct 1985 02:00:00 UTC", + &mock_ns.valid_after); + parse_rfc1123_time("Sat, 27 Oct 1985 03:00:00 UTC", + &mock_ns.fresh_until); + parse_rfc1123_time("Sat, 27 Oct 1985 05:00:00 UTC", + &mock_ns.valid_until); + + /* fetch the descriptor and make sure it's there */ + const hs_descriptor_t *cached_desc = NULL; + cached_desc = hs_cache_lookup_as_client(&signing_kp.pubkey); + tt_assert(cached_desc); + tt_mem_op(cached_desc->subcredential, OP_EQ, wanted_subcredential, + DIGEST256_LEN); + } + + /* Progress time to next TP and check that desc was cleaned */ + { + parse_rfc1123_time("Sat, 27 Oct 1985 12:00:00 UTC", + &mock_ns.valid_after); + parse_rfc1123_time("Sat, 27 Oct 1985 13:00:00 UTC", + &mock_ns.fresh_until); + parse_rfc1123_time("Sat, 27 Oct 1985 15:00:00 UTC", + &mock_ns.valid_until); + + const hs_descriptor_t *cached_desc = NULL; + cached_desc = hs_cache_lookup_as_client(&signing_kp.pubkey); + tt_assert(!cached_desc); + } + + done: + tor_free(args); + hs_descriptor_free(published_desc); + tor_free(published_desc_str); + if (conn) { + tor_free(conn->hs_ident); + tor_free(conn); + } +} + struct testcase_t hs_cache[] = { /* Encoding tests. */ { "directory", test_directory, TT_FORK, @@ -437,6 +553,8 @@ struct testcase_t hs_cache[] = { NULL, NULL }, { "upload_and_download_hs_desc", test_upload_and_download_hs_desc, TT_FORK, NULL, NULL }, + { "client_cache", test_client_cache, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c new file mode 100644 index 0000000000..1b3c788a67 --- /dev/null +++ b/src/test/test_hs_cell.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_cell.c + * \brief Test hidden service cell functionality. + */ + +#define HS_INTROPOINT_PRIVATE +#define HS_SERVICE_PRIVATE + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" + +#include "crypto_ed25519.h" +#include "hs_cell.h" +#include "hs_intropoint.h" +#include "hs_service.h" + +/* Trunnel. */ +#include "hs/cell_establish_intro.h" + +/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we + * parse it from the receiver side. */ +static void +test_gen_establish_intro_cell(void *arg) +{ + (void) arg; + ssize_t ret; + char circ_nonce[DIGEST_LEN] = {0}; + uint8_t buf[RELAY_PAYLOAD_SIZE]; + trn_cell_establish_intro_t *cell_in = NULL; + + crypto_rand(circ_nonce, sizeof(circ_nonce)); + + /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we + attempt to parse it. */ + { + /* We only need the auth key pair here. */ + hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0); + /* Auth key pair is generated in the constructor so we are all set for + * using this IP object. */ + ret = hs_cell_build_establish_intro(circ_nonce, ip, buf); + service_intro_point_free(ip); + tt_u64_op(ret, OP_GT, 0); + } + + /* Check the contents of the cell */ + { + /* First byte is the auth key type: make sure its correct */ + tt_int_op(buf[0], OP_EQ, HS_INTRO_AUTH_KEY_TYPE_ED25519); + /* Next two bytes is auth key len */ + tt_int_op(ntohs(get_uint16(buf+1)), OP_EQ, ED25519_PUBKEY_LEN); + /* Skip to the number of extensions: no extensions */ + tt_int_op(buf[35], OP_EQ, 0); + /* Skip to the sig len. Make sure it's the size of an ed25519 sig */ + tt_int_op(ntohs(get_uint16(buf+35+1+32)), OP_EQ, ED25519_SIG_LEN); + } + + /* Parse it as the receiver */ + { + ret = trn_cell_establish_intro_parse(&cell_in, buf, sizeof(buf)); + tt_u64_op(ret, OP_GT, 0); + + ret = verify_establish_intro_cell(cell_in, + (const uint8_t *) circ_nonce, + sizeof(circ_nonce)); + tt_u64_op(ret, OP_EQ, 0); + } + + done: + trn_cell_establish_intro_free(cell_in); +} + +/* Mocked ed25519_sign_prefixed() function that always fails :) */ +static int +mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out, + const uint8_t *msg, size_t msg_len, + const char *prefix_str, + const ed25519_keypair_t *keypair) { + (void) signature_out; + (void) msg; + (void) msg_len; + (void) prefix_str; + (void) keypair; + return -1; +} + +/** We simulate a failure to create an ESTABLISH_INTRO cell */ +static void +test_gen_establish_intro_cell_bad(void *arg) +{ + (void) arg; + ssize_t cell_len = 0; + trn_cell_establish_intro_t *cell = NULL; + char circ_nonce[DIGEST_LEN] = {0}; + hs_service_intro_point_t *ip = NULL; + + MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); + + crypto_rand(circ_nonce, sizeof(circ_nonce)); + + setup_full_capture_of_logs(LOG_WARN); + /* Easiest way to make that function fail is to mock the + ed25519_sign_prefixed() function and make it fail. */ + cell = trn_cell_establish_intro_new(); + tt_assert(cell); + ip = service_intro_point_new(NULL, 0); + cell_len = hs_cell_build_establish_intro(circ_nonce, ip, NULL); + service_intro_point_free(ip); + expect_log_msg_containing("Unable to make signature for " + "ESTABLISH_INTRO cell."); + teardown_capture_of_logs(); + tt_i64_op(cell_len, OP_EQ, -1); + + done: + trn_cell_establish_intro_free(cell); + UNMOCK(ed25519_sign_prefixed); +} + +struct testcase_t hs_cell_tests[] = { + { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, + NULL, NULL }, + { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_client.c b/src/test/test_hs_client.c new file mode 100644 index 0000000000..750920fac0 --- /dev/null +++ b/src/test/test_hs_client.c @@ -0,0 +1,599 @@ +/* Copyright (c) 2016-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_client.c + * \brief Test prop224 HS client functionality. + */ + +#define CRYPTO_PRIVATE +#define MAIN_PRIVATE +#define HS_CLIENT_PRIVATE +#define TOR_CHANNEL_INTERNAL_ +#define CIRCUITBUILD_PRIVATE +#define CIRCUITLIST_PRIVATE +#define CONNECTION_PRIVATE + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" +#include "rend_test_helpers.h" +#include "hs_test_helpers.h" + +#include "config.h" +#include "crypto.h" +#include "channeltls.h" +#include "main.h" +#include "nodelist.h" +#include "routerset.h" + +#include "hs_circuit.h" +#include "hs_client.h" +#include "hs_ident.h" +#include "hs_cache.h" +#include "circuitlist.h" +#include "circuitbuild.h" +#include "connection.h" +#include "connection_edge.h" +#include "networkstatus.h" + +static int +mock_connection_ap_handshake_send_begin(entry_connection_t *ap_conn) +{ + (void) ap_conn; + return 0; +} + +static networkstatus_t mock_ns; + +/* Always return NULL. */ +static networkstatus_t * +mock_networkstatus_get_live_consensus_false(time_t now) +{ + (void) now; + return NULL; +} + +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + return &mock_ns; +} + +/* Test helper function: Setup a circuit and a stream with the same hidden + * service destination, and put them in <b>circ_out</b> and + * <b>conn_out</b>. Make the stream wait for circuits to be established to the + * hidden service. */ +static int +helper_get_circ_and_stream_for_test(origin_circuit_t **circ_out, + connection_t **conn_out, + int is_legacy) +{ + int retval; + channel_tls_t *n_chan=NULL; + rend_data_t *conn_rend_data = NULL; + origin_circuit_t *or_circ = NULL; + connection_t *conn = NULL; + ed25519_public_key_t service_pk; + + /* Make a dummy connection stream and make it wait for our circuit */ + conn = test_conn_get_connection(AP_CONN_STATE_CIRCUIT_WAIT, + CONN_TYPE_AP /* ??? */, + 0); + if (is_legacy) { + /* Legacy: Setup rend_data of stream */ + char service_id[REND_SERVICE_ID_LEN_BASE32+1] = {0}; + TO_EDGE_CONN(conn)->rend_data = mock_rend_data(service_id); + conn_rend_data = TO_EDGE_CONN(conn)->rend_data; + } else { + /* prop224: Setup hs conn identifier on the stream */ + ed25519_secret_key_t sk; + tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0)); + tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk)); + + /* Setup hs_conn_identifier of stream */ + TO_EDGE_CONN(conn)->hs_ident = hs_ident_edge_conn_new(&service_pk); + } + + /* Make it wait for circuit */ + connection_ap_mark_as_pending_circuit(TO_ENTRY_CONN(conn)); + + /* This is needed to silence a BUG warning from + connection_edge_update_circuit_isolation() */ + TO_ENTRY_CONN(conn)->original_dest_address = + tor_strdup(TO_ENTRY_CONN(conn)->socks_request->address); + + /****************************************************/ + + /* Now make dummy circuit */ + or_circ = origin_circuit_new(); + + or_circ->base_.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED; + + or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t)); + or_circ->build_state->is_internal = 1; + + if (is_legacy) { + /* Legacy: Setup rend data and final cpath */ + or_circ->build_state->pending_final_cpath = + tor_malloc_zero(sizeof(crypt_path_t)); + or_circ->build_state->pending_final_cpath->magic = CRYPT_PATH_MAGIC; + or_circ->build_state->pending_final_cpath->rend_dh_handshake_state = + crypto_dh_new(DH_TYPE_REND); + tt_assert( + or_circ->build_state->pending_final_cpath->rend_dh_handshake_state); + retval = crypto_dh_generate_public( + or_circ->build_state->pending_final_cpath->rend_dh_handshake_state); + tt_int_op(retval, OP_EQ, 0); + or_circ->rend_data = rend_data_dup(conn_rend_data); + } else { + /* prop224: Setup hs ident on the circuit */ + or_circ->hs_ident = hs_ident_circuit_new(&service_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + } + + TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN; + + /* fake n_chan */ + n_chan = tor_malloc_zero(sizeof(channel_tls_t)); + n_chan->base_.global_identifier = 1; + or_circ->base_.n_chan = &(n_chan->base_); + + *circ_out = or_circ; + *conn_out = conn; + + return 0; + + done: + /* something failed */ + return -1; +} + +/* Test: Ensure that setting up legacy e2e rendezvous circuits works + * correctly. */ +static void +test_e2e_rend_circuit_setup_legacy(void *arg) +{ + ssize_t retval; + origin_circuit_t *or_circ = NULL; + connection_t *conn = NULL; + + (void) arg; + + /** In this test we create a v2 legacy HS stream and a circuit with the same + * hidden service destination. We make the stream wait for circuits to be + * established to the hidden service, and then we complete the circuit using + * the hs_circuit_setup_e2e_rend_circ_legacy_client() function. We then + * check that the end-to-end cpath was setup correctly and that the stream + * was attached to the circuit as expected. */ + + MOCK(connection_ap_handshake_send_begin, + mock_connection_ap_handshake_send_begin); + + /* Setup */ + retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 1); + tt_int_op(retval, OP_EQ, 0); + tt_assert(or_circ); + tt_assert(conn); + + /* Check number of hops */ + retval = cpath_get_n_hops(&or_circ->cpath); + tt_int_op(retval, OP_EQ, 0); + + /* Check that our stream is not attached on any circuits */ + tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, NULL); + + /********************************************** */ + + /* Make a good RENDEZVOUS1 cell body because it needs to pass key exchange + * digest verification... */ + uint8_t rend_cell_body[DH_KEY_LEN+DIGEST_LEN] = {2}; + { + char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; + crypto_dh_t *dh_state = + or_circ->build_state->pending_final_cpath->rend_dh_handshake_state; + /* compute and overwrite digest of cell body with the right value */ + retval = crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh_state, + (char*)rend_cell_body, DH_KEY_LEN, + keys, DIGEST_LEN+CPATH_KEY_MATERIAL_LEN); + tt_int_op(retval, OP_GT, 0); + memcpy(rend_cell_body+DH_KEY_LEN, keys, DIGEST_LEN); + } + + /* Setup the circuit */ + retval = hs_circuit_setup_e2e_rend_circ_legacy_client(or_circ, + rend_cell_body); + tt_int_op(retval, OP_EQ, 0); + + /**********************************************/ + + /* See that a hop was added to the circuit's cpath */ + retval = cpath_get_n_hops(&or_circ->cpath); + tt_int_op(retval, OP_EQ, 1); + + /* Check the digest algo */ + tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest), + OP_EQ, DIGEST_SHA1); + tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest), + OP_EQ, DIGEST_SHA1); + tt_assert(or_circ->cpath->f_crypto); + tt_assert(or_circ->cpath->b_crypto); + + /* Ensure that circ purpose was changed */ + tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED); + + /* Test that stream got attached */ + tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ)); + + done: + connection_free_(conn); + if (or_circ) + tor_free(TO_CIRCUIT(or_circ)->n_chan); + circuit_free(TO_CIRCUIT(or_circ)); +} + +/* Test: Ensure that setting up v3 rendezvous circuits works correctly. */ +static void +test_e2e_rend_circuit_setup(void *arg) +{ + uint8_t ntor_key_seed[DIGEST256_LEN] = {0}; + origin_circuit_t *or_circ = NULL; + int retval; + connection_t *conn = NULL; + + (void) arg; + + /** In this test we create a prop224 v3 HS stream and a circuit with the same + * hidden service destination. We make the stream wait for circuits to be + * established to the hidden service, and then we complete the circuit using + * the hs_circuit_setup_e2e_rend_circ() function. We then check that the + * end-to-end cpath was setup correctly and that the stream was attached to + * the circuit as expected. */ + + MOCK(connection_ap_handshake_send_begin, + mock_connection_ap_handshake_send_begin); + + /* Setup */ + retval = helper_get_circ_and_stream_for_test( &or_circ, &conn, 0); + tt_int_op(retval, OP_EQ, 0); + tt_assert(or_circ); + tt_assert(conn); + + /* Check number of hops: There should be no hops yet to this circ */ + retval = cpath_get_n_hops(&or_circ->cpath); + tt_int_op(retval, OP_EQ, 0); + tt_ptr_op(or_circ->cpath, OP_EQ, NULL); + + /* Check that our stream is not attached on any circuits */ + tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, NULL); + + /**********************************************/ + + /* Setup the circuit */ + retval = hs_circuit_setup_e2e_rend_circ(or_circ, + ntor_key_seed, sizeof(ntor_key_seed), + 0); + tt_int_op(retval, OP_EQ, 0); + + /**********************************************/ + + /* See that a hop was added to the circuit's cpath */ + retval = cpath_get_n_hops(&or_circ->cpath); + tt_int_op(retval, OP_EQ, 1); + + /* Check that the crypt path has prop224 algorithm parameters */ + tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest), + OP_EQ, DIGEST_SHA3_256); + tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest), + OP_EQ, DIGEST_SHA3_256); + tt_assert(or_circ->cpath->f_crypto); + tt_assert(or_circ->cpath->b_crypto); + + /* Ensure that circ purpose was changed */ + tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_C_REND_JOINED); + + /* Test that stream got attached */ + tt_ptr_op(TO_EDGE_CONN(conn)->on_circuit, OP_EQ, TO_CIRCUIT(or_circ)); + + done: + connection_free_(conn); + if (or_circ) + tor_free(TO_CIRCUIT(or_circ)->n_chan); + circuit_free(TO_CIRCUIT(or_circ)); +} + +/** Test client logic for picking intro points from a descriptor. Also test how + * ExcludeNodes and intro point failures affect picking intro points. */ +static void +test_client_pick_intro(void *arg) +{ + int ret; + ed25519_keypair_t service_kp; + hs_descriptor_t *desc = NULL; + + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + (void) arg; + + hs_init(); + + /* Generate service keypair */ + tt_int_op(0, OP_EQ, ed25519_keypair_generate(&service_kp, 0)); + + /* Set time */ + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + + update_approx_time(mock_ns.fresh_until-10); + time_t now = approx_time(); + + /* Test logic: + * + * 1) Add our desc with intro points to the HS cache. + * + * 2) Mark all descriptor intro points except _the chosen one_ as + * failed. Then query the desc to get a random intro: check that we got + * _the chosen one_. Then fail the chosen one as well, and see that no + * intros are returned. + * + * 3) Then clean the intro state cache and get an intro point. + * + * 4) Try fetching an intro with the wrong service key: shouldn't work + * + * 5) Set StrictNodes and put all our intro points in ExcludeNodes: see that + * nothing is returned. + */ + + /* 1) Add desc to HS cache */ + { + char *encoded = NULL; + desc = hs_helper_build_hs_desc_with_ip(&service_kp); + ret = hs_desc_encode_descriptor(desc, &service_kp, &encoded); + tt_int_op(ret, OP_EQ, 0); + tt_assert(encoded); + + /* store it */ + hs_cache_store_as_client(encoded, &service_kp.pubkey); + + /* fetch it to make sure it works */ + const hs_descriptor_t *fetched_desc = + hs_cache_lookup_as_client(&service_kp.pubkey); + tt_assert(fetched_desc); + tt_mem_op(fetched_desc->subcredential, OP_EQ, desc->subcredential, + DIGEST256_LEN); + tt_assert(!tor_mem_is_zero((char*)fetched_desc->subcredential, + DIGEST256_LEN)); + tor_free(encoded); + } + + /* 2) Mark all intro points except _the chosen one_ as failed. Then query the + * desc and get a random intro: check that we got _the chosen one_. */ + { + /* Pick the chosen intro point and get its ei */ + hs_desc_intro_point_t *chosen_intro_point = + smartlist_get(desc->encrypted_data.intro_points, 0); + extend_info_t *chosen_intro_ei = + desc_intro_point_to_extend_info(chosen_intro_point); + tt_assert(chosen_intro_point); + tt_assert(chosen_intro_ei); + + /* Now mark all other intro points as failed */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + hs_desc_intro_point_t *, ip) { + /* Skip the chosen intro point */ + if (ip == chosen_intro_point) { + continue; + } + ed25519_public_key_t *intro_auth_key = &ip->auth_key_cert->signed_key; + hs_cache_client_intro_state_note(&service_kp.pubkey, + intro_auth_key, + INTRO_POINT_FAILURE_GENERIC); + } SMARTLIST_FOREACH_END(ip); + + /* Try to get a random intro: Should return the chosen one! */ + extend_info_t *ip = client_get_random_intro(&service_kp.pubkey); + tor_assert(ip); + tt_assert(!tor_mem_is_zero((char*)ip->identity_digest, DIGEST_LEN)); + tt_mem_op(ip->identity_digest, OP_EQ, chosen_intro_ei->identity_digest, + DIGEST_LEN); + + extend_info_free(chosen_intro_ei); + extend_info_free(ip); + + /* Now also mark the chosen one as failed: See that we can't get any intro + points anymore. */ + hs_cache_client_intro_state_note(&service_kp.pubkey, + &chosen_intro_point->auth_key_cert->signed_key, + INTRO_POINT_FAILURE_TIMEOUT); + ip = client_get_random_intro(&service_kp.pubkey); + tor_assert(!ip); + } + + /* 3) Clean the intro state cache and get an intro point */ + { + /* Pretend we are 5 mins in the future and order a cleanup of the intro + * state. This should clean up the intro point failures and allow us to get + * an intro. */ + hs_cache_client_intro_state_clean(now + 5*60); + + /* Get an intro. It should work! */ + extend_info_t *ip = client_get_random_intro(&service_kp.pubkey); + tor_assert(ip); + extend_info_free(ip); + } + + /* 4) Try fetching an intro with the wrong service key: shouldn't work */ + { + ed25519_keypair_t dummy_kp; + tt_int_op(0, OP_EQ, ed25519_keypair_generate(&dummy_kp, 0)); + extend_info_t *ip = client_get_random_intro(&dummy_kp.pubkey); + tor_assert(!ip); + } + + /* 5) Set StrictNodes and put all our intro points in ExcludeNodes: see that + * nothing is returned. */ + { + get_options_mutable()->ExcludeNodes = routerset_new(); + get_options_mutable()->StrictNodes = 1; + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + hs_desc_intro_point_t *, ip) { + extend_info_t *intro_ei = desc_intro_point_to_extend_info(ip); + if (intro_ei) { + const char *ptr; + char ip_addr[TOR_ADDR_BUF_LEN]; + /* We need to decorate in case it is an IPv6 else routerset_parse() + * doesn't like it. */ + ptr = tor_addr_to_str(ip_addr, &intro_ei->addr, sizeof(ip_addr), 1); + tt_assert(ptr == ip_addr); + ret = routerset_parse(get_options_mutable()->ExcludeNodes, + ip_addr, ""); + tt_int_op(ret, OP_EQ, 0); + extend_info_free(intro_ei); + } + } SMARTLIST_FOREACH_END(ip); + + extend_info_t *ip = client_get_random_intro(&service_kp.pubkey); + tt_assert(!ip); + } + + done: + hs_descriptor_free(desc); +} + +static int +mock_router_have_minimum_dir_info_false(void) +{ + return 0; +} +static int +mock_router_have_minimum_dir_info_true(void) +{ + return 1; +} + +static hs_client_fetch_status_t +mock_fetch_v3_desc_error(const ed25519_public_key_t *key) +{ + (void) key; + return HS_CLIENT_FETCH_ERROR; +} + +static void +mock_connection_mark_unattached_ap_(entry_connection_t *conn, int endreason, + int line, const char *file) +{ + (void) line; + (void) file; + conn->edge_.end_reason = endreason; +} + +static void +test_descriptor_fetch(void *arg) +{ + int ret; + entry_connection_t *ec = NULL; + ed25519_public_key_t service_pk; + ed25519_secret_key_t service_sk; + + (void) arg; + + hs_init(); + memset(&service_sk, 'A', sizeof(service_sk)); + ret = ed25519_public_key_generate(&service_pk, &service_sk); + tt_int_op(ret, OP_EQ, 0); + + /* Initialize this so get_voting_interval() doesn't freak out. */ + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + + ec = entry_connection_new(CONN_TYPE_AP, AF_INET); + tt_assert(ec); + ENTRY_TO_EDGE_CONN(ec)->hs_ident = hs_ident_edge_conn_new(&service_pk); + tt_assert(ENTRY_TO_EDGE_CONN(ec)->hs_ident); + TO_CONN(ENTRY_TO_EDGE_CONN(ec))->state = AP_CONN_STATE_RENDDESC_WAIT; + smartlist_add(get_connection_array(), &ec->edge_.base_); + + /* 1. FetchHidServDescriptors is false so we shouldn't be able to fetch. */ + get_options_mutable()->FetchHidServDescriptors = 0; + ret = hs_client_refetch_hsdesc(&service_pk); + tt_int_op(ret, OP_EQ, HS_CLIENT_FETCH_NOT_ALLOWED); + get_options_mutable()->FetchHidServDescriptors = 1; + + /* 2. We don't have a live consensus. */ + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus_false); + ret = hs_client_refetch_hsdesc(&service_pk); + UNMOCK(networkstatus_get_live_consensus); + tt_int_op(ret, OP_EQ, HS_CLIENT_FETCH_MISSING_INFO); + + /* From now on, return a live consensus. */ + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + /* 3. Not enough dir information. */ + MOCK(router_have_minimum_dir_info, + mock_router_have_minimum_dir_info_false); + ret = hs_client_refetch_hsdesc(&service_pk); + UNMOCK(router_have_minimum_dir_info); + tt_int_op(ret, OP_EQ, HS_CLIENT_FETCH_MISSING_INFO); + + /* From now on, we do have enough directory information. */ + MOCK(router_have_minimum_dir_info, + mock_router_have_minimum_dir_info_true); + + /* 4. We do have a pending directory request. */ + { + dir_connection_t *dir_conn = dir_connection_new(AF_INET); + dir_conn->hs_ident = tor_malloc_zero(sizeof(hs_ident_dir_conn_t)); + TO_CONN(dir_conn)->purpose = DIR_PURPOSE_FETCH_HSDESC; + ed25519_pubkey_copy(&dir_conn->hs_ident->identity_pk, &service_pk); + smartlist_add(get_connection_array(), TO_CONN(dir_conn)); + ret = hs_client_refetch_hsdesc(&service_pk); + smartlist_remove(get_connection_array(), TO_CONN(dir_conn)); + connection_free_(TO_CONN(dir_conn)); + tt_int_op(ret, OP_EQ, HS_CLIENT_FETCH_PENDING); + } + + /* 5. We'll trigger an error on the fetch_desc_v3 and force to close all + * pending SOCKS request. */ + MOCK(router_have_minimum_dir_info, + mock_router_have_minimum_dir_info_true); + MOCK(fetch_v3_desc, mock_fetch_v3_desc_error); + MOCK(connection_mark_unattached_ap_, + mock_connection_mark_unattached_ap_); + ret = hs_client_refetch_hsdesc(&service_pk); + UNMOCK(fetch_v3_desc); + UNMOCK(connection_mark_unattached_ap_); + tt_int_op(ret, OP_EQ, HS_CLIENT_FETCH_ERROR); + /* The close waiting for descriptor function has been called. */ + tt_int_op(ec->edge_.end_reason, OP_EQ, END_STREAM_REASON_RESOLVEFAILED); + + done: + connection_free_(ENTRY_TO_CONN(ec)); + UNMOCK(networkstatus_get_live_consensus); + UNMOCK(router_have_minimum_dir_info); + hs_free_all(); +} + +struct testcase_t hs_client_tests[] = { + { "e2e_rend_circuit_setup_legacy", test_e2e_rend_circuit_setup_legacy, + TT_FORK, NULL, NULL }, + { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, + TT_FORK, NULL, NULL }, + { "client_pick_intro", test_client_pick_intro, + TT_FORK, NULL, NULL }, + { "descriptor_fetch", test_descriptor_fetch, + TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c new file mode 100644 index 0000000000..b9b76d15bd --- /dev/null +++ b/src/test/test_hs_common.c @@ -0,0 +1,1822 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_common.c + * \brief Test hidden service common functionalities. + */ + +#define HS_COMMON_PRIVATE +#define HS_CLIENT_PRIVATE +#define HS_SERVICE_PRIVATE +#define NODELIST_PRIVATE + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" +#include "hs_test_helpers.h" + +#include "connection_edge.h" +#include "hs_common.h" +#include "hs_client.h" +#include "hs_service.h" +#include "config.h" +#include "networkstatus.h" +#include "directory.h" +#include "dirvote.h" +#include "nodelist.h" +#include "routerlist.h" +#include "statefile.h" +#include "circuitlist.h" +#include "shared_random.h" +#include "util.h" + +/** Test the validation of HS v3 addresses */ +static void +test_validate_address(void *arg) +{ + int ret; + + (void) arg; + + /* Address too short and too long. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid("blah"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("has an invalid length"); + teardown_capture_of_logs(); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "p3xnclpu4mu22dwaurjtsybyqk4xfjmcfz6z62yl24uwmhjatiwnlnadb"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("has an invalid length"); + teardown_capture_of_logs(); + + /* Invalid checksum (taken from prop224) */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "l5satjgud6gucryazcyvyvhuxhr74u6ygigiuyixe3a6ysis67ororad"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("invalid checksum"); + teardown_capture_of_logs(); + + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "btojiu7nu5y5iwut64eufevogqdw4wmqzugnoluw232r4t3ecsfv37ad"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("invalid checksum"); + teardown_capture_of_logs(); + + /* Non base32 decodable string. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_address_is_valid( + "????????????????????????????????????????????????????????"); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("can't be decoded"); + teardown_capture_of_logs(); + + /* Valid address. */ + ret = hs_address_is_valid( + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid"); + tt_int_op(ret, OP_EQ, 1); + + done: + ; +} + +static int +mock_write_str_to_file(const char *path, const char *str, int bin) +{ + (void)bin; + tt_str_op(path, OP_EQ, "/double/five"PATH_SEPARATOR"squared"); + tt_str_op(str, OP_EQ, + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid.onion\n"); + + done: + return 0; +} + +/** Test building HS v3 onion addresses. Uses test vectors from the + * ./hs_build_address.py script. */ +static void +test_build_address(void *arg) +{ + int ret; + char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + ed25519_public_key_t pubkey; + /* hex-encoded ed25519 pubkey used in hs_build_address.py */ + char pubkey_hex[] = + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"; + hs_service_t *service = NULL; + + (void) arg; + + MOCK(write_str_to_file, mock_write_str_to_file); + + /* The following has been created with hs_build_address.py script that + * follows proposal 224 specification to build an onion address. */ + static const char *test_addr = + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid"; + + /* Let's try to build the same onion address as the script */ + base16_decode((char*)pubkey.pubkey, sizeof(pubkey.pubkey), + pubkey_hex, strlen(pubkey_hex)); + hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr); + tt_str_op(test_addr, OP_EQ, onion_addr); + /* Validate that address. */ + ret = hs_address_is_valid(onion_addr); + tt_int_op(ret, OP_EQ, 1); + + service = tor_malloc_zero(sizeof(hs_service_t)); + memcpy(service->onion_address, onion_addr, sizeof(service->onion_address)); + tor_asprintf(&service->config.directory_path, "/double/five"); + ret = write_address_to_file(service, "squared"); + tt_int_op(ret, OP_EQ, 0); + + done: + hs_service_free(service); +} + +/** Test that our HS time period calculation functions work properly */ +static void +test_time_period(void *arg) +{ + (void) arg; + uint64_t tn; + int retval; + time_t fake_time, correct_time, start_time; + + /* Let's do the example in prop224 section [TIME-PERIODS] */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", + &fake_time); + tt_int_op(retval, OP_EQ, 0); + + /* Check that the time period number is right */ + tn = hs_get_time_period_num(fake_time); + tt_u64_op(tn, OP_EQ, 16903); + + /* Increase current time to 11:59:59 UTC and check that the time period + number is still the same */ + fake_time += 3599; + tn = hs_get_time_period_num(fake_time); + tt_u64_op(tn, OP_EQ, 16903); + + { /* Check start time of next time period */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC", + &correct_time); + tt_int_op(retval, OP_EQ, 0); + + start_time = hs_get_start_time_of_next_time_period(fake_time); + tt_int_op(start_time, OP_EQ, correct_time); + } + + /* Now take time to 12:00:00 UTC and check that the time period rotated */ + fake_time += 1; + tn = hs_get_time_period_num(fake_time); + tt_u64_op(tn, OP_EQ, 16904); + + /* Now also check our hs_get_next_time_period_num() function */ + tn = hs_get_next_time_period_num(fake_time); + tt_u64_op(tn, OP_EQ, 16905); + + { /* Check start time of next time period again */ + retval = parse_rfc1123_time("Wed, 14 Apr 2016 12:00:00 UTC", + &correct_time); + tt_int_op(retval, OP_EQ, 0); + + start_time = hs_get_start_time_of_next_time_period(fake_time); + tt_int_op(start_time, OP_EQ, correct_time); + } + + /* Now do another sanity check: The time period number at the start of the + * next time period, must be the same time period number as the one returned + * from hs_get_next_time_period_num() */ + { + time_t next_tp_start = hs_get_start_time_of_next_time_period(fake_time); + tt_u64_op(hs_get_time_period_num(next_tp_start), OP_EQ, + hs_get_next_time_period_num(fake_time)); + } + + done: + ; +} + +/** Test that we can correctly find the start time of the next time period */ +static void +test_start_time_of_next_time_period(void *arg) +{ + (void) arg; + int retval; + time_t fake_time; + char tbuf[ISO_TIME_LEN + 1]; + time_t next_tp_start_time; + + /* Do some basic tests */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", + &fake_time); + tt_int_op(retval, OP_EQ, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + /* Compare it with the correct result */ + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-13 12:00:00", OP_EQ, tbuf); + + /* Another test with an edge-case time (start of TP) */ + retval = parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC", + &fake_time); + tt_int_op(retval, OP_EQ, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-14 12:00:00", OP_EQ, tbuf); + + { + /* Now pretend we are on a testing network and alter the voting schedule to + be every 10 seconds. This means that a time period has length 10*24 + seconds (4 minutes). It also means that we apply a rotational offset of + 120 seconds to the time period, so that it starts at 00:02:00 instead of + 00:00:00. */ + or_options_t *options = get_options_mutable(); + options->TestingTorNetwork = 1; + options->V3AuthVotingInterval = 10; + options->TestingV3AuthInitialVotingInterval = 10; + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:00:00 UTC", + &fake_time); + tt_int_op(retval, OP_EQ, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + /* Compare it with the correct result */ + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-13 00:02:00", OP_EQ, tbuf); + + retval = parse_rfc1123_time("Wed, 13 Apr 2016 00:02:00 UTC", + &fake_time); + tt_int_op(retval, OP_EQ, 0); + next_tp_start_time = hs_get_start_time_of_next_time_period(fake_time); + /* Compare it with the correct result */ + format_iso_time(tbuf, next_tp_start_time); + tt_str_op("2016-04-13 00:06:00", OP_EQ, tbuf); + } + + done: + ; +} + +/* Cleanup the global nodelist. It also frees the "md" in the node_t because + * we allocate the memory in helper_add_hsdir_to_networkstatus(). */ +static void +cleanup_nodelist(void) +{ + smartlist_t *nodelist = nodelist_get_list(); + SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) { + tor_free(node->md); + node->md = NULL; + } SMARTLIST_FOREACH_END(node); + nodelist_free_all(); +} + +static void +helper_add_hsdir_to_networkstatus(networkstatus_t *ns, + int identity_idx, + const char *nickname, + int is_hsdir) +{ + routerstatus_t *rs = tor_malloc_zero(sizeof(routerstatus_t)); + routerinfo_t *ri = tor_malloc_zero(sizeof(routerinfo_t)); + uint8_t identity[DIGEST_LEN]; + tor_addr_t ipv4_addr; + + memset(identity, identity_idx, sizeof(identity)); + + memcpy(rs->identity_digest, identity, DIGEST_LEN); + rs->is_hs_dir = is_hsdir; + rs->supports_v3_hsdir = 1; + strlcpy(rs->nickname, nickname, sizeof(rs->nickname)); + tor_addr_parse(&ipv4_addr, "1.2.3.4"); + ri->addr = tor_addr_to_ipv4h(&ipv4_addr); + rs->addr = tor_addr_to_ipv4h(&ipv4_addr); + ri->nickname = tor_strdup(nickname); + ri->protocol_list = tor_strdup("HSDir=1-2 LinkAuth=3"); + memcpy(ri->cache_info.identity_digest, identity, DIGEST_LEN); + ri->cache_info.signing_key_cert = tor_malloc_zero(sizeof(tor_cert_t)); + /* Needed for the HSDir index computation. */ + memset(&ri->cache_info.signing_key_cert->signing_key, + identity_idx, ED25519_PUBKEY_LEN); + tt_assert(nodelist_set_routerinfo(ri, NULL)); + node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest); + tt_assert(node); + node->rs = rs; + /* We need this to exist for node_has_descriptor() to return true. */ + node->md = tor_malloc_zero(sizeof(microdesc_t)); + /* Do this now the nodelist_set_routerinfo() function needs a "rs" to set + * the indexes which it doesn't have when it is called. */ + node_set_hsdir_index(node, ns); + node->ri = NULL; + smartlist_add(ns->routerstatus_list, rs); + + done: + routerinfo_free(ri); +} + +static networkstatus_t *mock_ns = NULL; + +static networkstatus_t * +mock_networkstatus_get_latest_consensus(void) +{ + time_t now = approx_time(); + + /* If initialized, return it */ + if (mock_ns) { + return mock_ns; + } + + /* Initialize fake consensus */ + mock_ns = tor_malloc_zero(sizeof(networkstatus_t)); + + /* This consensus is live */ + mock_ns->valid_after = now-1; + mock_ns->fresh_until = now+1; + mock_ns->valid_until = now+2; + /* Create routerstatus list */ + mock_ns->routerstatus_list = smartlist_new(); + mock_ns->type = NS_TYPE_CONSENSUS; + + return mock_ns; +} + +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + + tt_assert(mock_ns); + + done: + return mock_ns; +} + +/** Test the responsible HSDirs calculation function */ +static void +test_responsible_hsdirs(void *arg) +{ + time_t now = approx_time(); + smartlist_t *responsible_dirs = smartlist_new(); + networkstatus_t *ns = NULL; + int retval; + + (void) arg; + + hs_init(); + + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + + ns = networkstatus_get_latest_consensus(); + + { /* First router: HSdir */ + helper_add_hsdir_to_networkstatus(ns, 1, "igor", 1); + } + + { /* Second HSDir */ + helper_add_hsdir_to_networkstatus(ns, 2, "victor", 1); + } + + { /* Third relay but not HSDir */ + helper_add_hsdir_to_networkstatus(ns, 3, "spyro", 0); + } + + ed25519_keypair_t kp; + retval = ed25519_keypair_generate(&kp, 0); + tt_int_op(retval, OP_EQ , 0); + + uint64_t time_period_num = hs_get_time_period_num(now); + hs_get_responsible_hsdirs(&kp.pubkey, time_period_num, + 0, 0, responsible_dirs); + + /* Make sure that we only found 2 responsible HSDirs. + * The third relay was not an hsdir! */ + tt_int_op(smartlist_len(responsible_dirs), OP_EQ, 2); + + /** TODO: Build a bigger network and do more tests here */ + + done: + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_free(responsible_dirs); + smartlist_clear(ns->routerstatus_list); + networkstatus_vote_free(mock_ns); + cleanup_nodelist(); +} + +static void +mock_directory_initiate_request(directory_request_t *req) +{ + (void)req; + return; +} + +static int +mock_hs_desc_encode_descriptor(const hs_descriptor_t *desc, + const ed25519_keypair_t *signing_kp, + char **encoded_out) +{ + (void)desc; + (void)signing_kp; + + tor_asprintf(encoded_out, "lulu"); + return 0; +} + +static or_state_t dummy_state; + +/* Mock function to get fake or state (used for rev counters) */ +static or_state_t * +get_or_state_replacement(void) +{ + return &dummy_state; +} + +static int +mock_router_have_minimum_dir_info(void) +{ + return 1; +} + +/** Test that we correctly detect when the HSDir hash ring changes so that we + * reupload our descriptor. */ +static void +test_desc_reupload_logic(void *arg) +{ + networkstatus_t *ns = NULL; + + (void) arg; + + hs_init(); + + MOCK(router_have_minimum_dir_info, + mock_router_have_minimum_dir_info); + MOCK(get_or_state, + get_or_state_replacement); + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + MOCK(directory_initiate_request, + mock_directory_initiate_request); + MOCK(hs_desc_encode_descriptor, + mock_hs_desc_encode_descriptor); + + ns = networkstatus_get_latest_consensus(); + + /** Test logic: + * 1) Upload descriptor to HSDirs + * CHECK that previous_hsdirs list was populated. + * 2) Then call router_dir_info_changed() without an HSDir set change. + * CHECK that no reuplod occurs. + * 3) Now change the HSDir set, and call dir_info_changed() again. + * CHECK that reupload occurs. + * 4) Finally call service_desc_schedule_upload(). + * CHECK that previous_hsdirs list was cleared. + **/ + + /* Let's start by building our descriptor and service */ + hs_service_descriptor_t *desc = service_descriptor_new(); + hs_service_t *service = NULL; + /* hex-encoded ed25519 pubkey used in hs_build_address.py */ + char pubkey_hex[] = + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"; + char onion_addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + ed25519_public_key_t pubkey; + base16_decode((char*)pubkey.pubkey, sizeof(pubkey.pubkey), + pubkey_hex, strlen(pubkey_hex)); + hs_build_address(&pubkey, HS_VERSION_THREE, onion_addr); + service = tor_malloc_zero(sizeof(hs_service_t)); + memcpy(service->onion_address, onion_addr, sizeof(service->onion_address)); + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + service->desc_current = desc; + /* Also add service to service map */ + hs_service_ht *service_map = get_hs_service_map(); + tt_assert(service_map); + tt_int_op(hs_service_get_num_services(), OP_EQ, 0); + register_service(service_map, service); + tt_int_op(hs_service_get_num_services(), OP_EQ, 1); + + /* Now let's create our hash ring: */ + { + helper_add_hsdir_to_networkstatus(ns, 1, "dingus", 1); + helper_add_hsdir_to_networkstatus(ns, 2, "clive", 1); + helper_add_hsdir_to_networkstatus(ns, 3, "aaron", 1); + helper_add_hsdir_to_networkstatus(ns, 4, "lizzie", 1); + helper_add_hsdir_to_networkstatus(ns, 5, "daewon", 1); + helper_add_hsdir_to_networkstatus(ns, 6, "clarke", 1); + } + + /* Now let's upload our desc to all hsdirs */ + upload_descriptor_to_all(service, desc); + /* Check that previous hsdirs were populated */ + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 6); + + /* Poison next upload time so that we can see if it was changed by + * router_dir_info_changed(). No changes in hash ring so far, so the upload + * time should stay as is. */ + desc->next_upload_time = 42; + router_dir_info_changed(); + tt_int_op(desc->next_upload_time, OP_EQ, 42); + + /* Now change the HSDir hash ring by swapping nora for aaron. + * Start by clearing the hash ring */ + { + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(ns->routerstatus_list); + cleanup_nodelist(); + routerlist_free_all(); + } + + { /* Now add back all the nodes */ + helper_add_hsdir_to_networkstatus(ns, 1, "dingus", 1); + helper_add_hsdir_to_networkstatus(ns, 2, "clive", 1); + helper_add_hsdir_to_networkstatus(ns, 4, "lizzie", 1); + helper_add_hsdir_to_networkstatus(ns, 5, "daewon", 1); + helper_add_hsdir_to_networkstatus(ns, 6, "clarke", 1); + helper_add_hsdir_to_networkstatus(ns, 7, "nora", 1); + } + + /* Now call service_desc_hsdirs_changed() and see that it detected the hash + ring change */ + time_t now = approx_time(); + tt_assert(now); + tt_int_op(service_desc_hsdirs_changed(service, desc), OP_EQ, 1); + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 6); + + /* Now order another upload and see that we keep having 6 prev hsdirs */ + upload_descriptor_to_all(service, desc); + /* Check that previous hsdirs were populated */ + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 6); + + /* Now restore the HSDir hash ring to its original state by swapping back + aaron for nora */ + /* First clear up the hash ring */ + { + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(ns->routerstatus_list); + cleanup_nodelist(); + routerlist_free_all(); + } + + { /* Now populate the hash ring again */ + helper_add_hsdir_to_networkstatus(ns, 1, "dingus", 1); + helper_add_hsdir_to_networkstatus(ns, 2, "clive", 1); + helper_add_hsdir_to_networkstatus(ns, 3, "aaron", 1); + helper_add_hsdir_to_networkstatus(ns, 4, "lizzie", 1); + helper_add_hsdir_to_networkstatus(ns, 5, "daewon", 1); + helper_add_hsdir_to_networkstatus(ns, 6, "clarke", 1); + } + + /* Check that our algorithm catches this change of hsdirs */ + tt_int_op(service_desc_hsdirs_changed(service, desc), OP_EQ, 1); + + /* Now pretend that the descriptor changed, and order a reupload to all + HSDirs. Make sure that the set of previous HSDirs was cleared. */ + service_desc_schedule_upload(desc, now, 1); + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 0); + + /* Now reupload again: see that the prev hsdir set got populated again. */ + upload_descriptor_to_all(service, desc); + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 6); + + done: + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(ns->routerstatus_list); + networkstatus_vote_free(ns); + cleanup_nodelist(); + hs_free_all(); +} + +/** Test disaster SRV computation and caching */ +static void +test_disaster_srv(void *arg) +{ + uint8_t *cached_disaster_srv_one = NULL; + uint8_t *cached_disaster_srv_two = NULL; + uint8_t srv_one[DIGEST256_LEN] = {0}; + uint8_t srv_two[DIGEST256_LEN] = {0}; + uint8_t srv_three[DIGEST256_LEN] = {0}; + uint8_t srv_four[DIGEST256_LEN] = {0}; + uint8_t srv_five[DIGEST256_LEN] = {0}; + + (void) arg; + + /* Get the cached SRVs: we gonna use them later for verification */ + cached_disaster_srv_one = get_first_cached_disaster_srv(); + cached_disaster_srv_two = get_second_cached_disaster_srv(); + + /* Compute some srvs */ + get_disaster_srv(1, srv_one); + get_disaster_srv(2, srv_two); + + /* Check that the cached ones where updated */ + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN); + + /* Ask for an SRV that has already been computed */ + get_disaster_srv(2, srv_two); + /* and check that the cache entries have not changed */ + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_one, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN); + + /* Ask for a new SRV */ + get_disaster_srv(3, srv_three); + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_two, DIGEST256_LEN); + + /* Ask for another SRV: none of the original SRVs should now be cached */ + get_disaster_srv(4, srv_four); + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_three, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN); + + /* Ask for yet another SRV */ + get_disaster_srv(5, srv_five); + tt_mem_op(cached_disaster_srv_one, OP_EQ, srv_five, DIGEST256_LEN); + tt_mem_op(cached_disaster_srv_two, OP_EQ, srv_four, DIGEST256_LEN); + + done: + ; +} + +/** Test our HS descriptor request tracker by making various requests and + * checking whether they get tracked properly. */ +static void +test_hid_serv_request_tracker(void *arg) +{ + (void) arg; + time_t retval; + routerstatus_t *hsdir = NULL, *hsdir2 = NULL, *hsdir3 = NULL; + time_t now = approx_time(); + + const char *req_key_str_first = + "vd4zb6zesaubtrjvdqcr2w7x7lhw2up4Xnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs"; + const char *req_key_str_second = + "g53o7iavcd62oihswhr24u6czmqws5kpXnw4526ThUNbL5o1go+EdUuEqlKxHkNbnK41pRzizzs"; + const char *req_key_str_small = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"; + + /*************************** basic test *******************************/ + + /* Get request tracker and make sure it's empty */ + strmap_t *request_tracker = get_last_hid_serv_requests(); + tt_int_op(strmap_size(request_tracker),OP_EQ, 0); + + /* Let's register a hid serv request */ + hsdir = tor_malloc_zero(sizeof(routerstatus_t)); + memset(hsdir->identity_digest, 'Z', DIGEST_LEN); + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first, + now, 1); + tt_int_op(retval, OP_EQ, now); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /* Let's lookup a non-existent hidserv request */ + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_second, + now+1, 0); + tt_int_op(retval, OP_EQ, 0); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /* Let's lookup a real hidserv request */ + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first, + now+2, 0); + tt_int_op(retval, OP_EQ, now); /* we got it */ + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + + /**********************************************************************/ + + /* Let's add another request for the same HS but on a different HSDir. */ + hsdir2 = tor_malloc_zero(sizeof(routerstatus_t)); + memset(hsdir2->identity_digest, 2, DIGEST_LEN); + retval = hs_lookup_last_hid_serv_request(hsdir2, req_key_str_first, + now+3, 1); + tt_int_op(retval, OP_EQ, now+3); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + + /* Check that we can clean the first request based on time */ + hs_clean_last_hid_serv_requests(now+3+REND_HID_SERV_DIR_REQUERY_PERIOD); + tt_int_op(strmap_size(request_tracker),OP_EQ, 1); + /* Check that it doesn't exist anymore */ + retval = hs_lookup_last_hid_serv_request(hsdir, req_key_str_first, + now+2, 0); + tt_int_op(retval, OP_EQ, 0); + + /* Now let's add a smaller req key str */ + hsdir3 = tor_malloc_zero(sizeof(routerstatus_t)); + memset(hsdir3->identity_digest, 3, DIGEST_LEN); + retval = hs_lookup_last_hid_serv_request(hsdir3, req_key_str_small, + now+4, 1); + tt_int_op(retval, OP_EQ, now+4); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + + /*************************** deleting entries **************************/ + + /* Add another request with very short key */ + retval = hs_lookup_last_hid_serv_request(hsdir, "l", now, 1); + tt_int_op(retval, OP_EQ, now); + tt_int_op(strmap_size(request_tracker),OP_EQ, 3); + + /* Try deleting entries with a dummy key. Check that our previous requests + * are still there */ + tor_capture_bugs_(1); + hs_purge_hid_serv_from_last_hid_serv_requests("a"); + tt_int_op(strmap_size(request_tracker),OP_EQ, 3); + tor_end_capture_bugs_(); + + /* Try another dummy key. Check that requests are still there */ + { + char dummy[2000]; + memset(dummy, 'Z', 2000); + dummy[1999] = '\x00'; + hs_purge_hid_serv_from_last_hid_serv_requests(dummy); + tt_int_op(strmap_size(request_tracker),OP_EQ, 3); + } + + /* Another dummy key! */ + hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_second); + tt_int_op(strmap_size(request_tracker),OP_EQ, 3); + + /* Now actually delete a request! */ + hs_purge_hid_serv_from_last_hid_serv_requests(req_key_str_first); + tt_int_op(strmap_size(request_tracker),OP_EQ, 2); + + /* Purge it all! */ + hs_purge_last_hid_serv_requests(); + request_tracker = get_last_hid_serv_requests(); + tt_int_op(strmap_size(request_tracker),OP_EQ, 0); + + done: + tor_free(hsdir); + tor_free(hsdir2); + tor_free(hsdir3); +} + +static void +test_parse_extended_hostname(void *arg) +{ + (void) arg; + + char address1[] = "fooaddress.onion"; + char address2[] = "aaaaaaaaaaaaaaaa.onion"; + char address3[] = "fooaddress.exit"; + char address4[] = "www.torproject.org"; + char address5[] = "foo.abcdefghijklmnop.onion"; + char address6[] = "foo.bar.abcdefghijklmnop.onion"; + char address7[] = ".abcdefghijklmnop.onion"; + char address8[] = + "www.25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid.onion"; + + tt_assert(BAD_HOSTNAME == parse_extended_hostname(address1)); + tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address2)); + tt_str_op(address2,OP_EQ, "aaaaaaaaaaaaaaaa"); + tt_assert(EXIT_HOSTNAME == parse_extended_hostname(address3)); + tt_assert(NORMAL_HOSTNAME == parse_extended_hostname(address4)); + tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address5)); + tt_str_op(address5,OP_EQ, "abcdefghijklmnop"); + tt_assert(ONION_V2_HOSTNAME == parse_extended_hostname(address6)); + tt_str_op(address6,OP_EQ, "abcdefghijklmnop"); + tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7)); + tt_assert(ONION_V3_HOSTNAME == parse_extended_hostname(address8)); + tt_str_op(address8, OP_EQ, + "25njqamcweflpvkl73j4szahhihoc4xt3ktcgjnpaingr5yhkenl5sid"); + + done: ; +} + +static void +test_time_between_tp_and_srv(void *arg) +{ + int ret; + networkstatus_t ns; + (void) arg; + + /* This function should be returning true where "^" are: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^^^^^^^^^^^^ ^^^^^^^^^^^^ | + * | | + * +------------------------------------------------------------------+ + */ + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 00:00:00 UTC", &ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 01:00:00 UTC", &ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), ns.valid_after); + ret = hs_in_period_between_tp_and_srv(&ns, 0); + tt_int_op(ret, OP_EQ, 0); + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 11:00:00 UTC", &ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 12:00:00 UTC", &ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), ns.valid_after); + ret = hs_in_period_between_tp_and_srv(&ns, 0); + tt_int_op(ret, OP_EQ, 0); + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 12:00:00 UTC", &ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", &ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), ns.valid_after); + ret = hs_in_period_between_tp_and_srv(&ns, 0); + tt_int_op(ret, OP_EQ, 1); + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 23:00:00 UTC", &ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 27 Oct 1985 00:00:00 UTC", &ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), ns.valid_after); + ret = hs_in_period_between_tp_and_srv(&ns, 0); + tt_int_op(ret, OP_EQ, 1); + + ret = parse_rfc1123_time("Sat, 27 Oct 1985 00:00:00 UTC", &ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 27 Oct 1985 01:00:00 UTC", &ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), ns.valid_after); + ret = hs_in_period_between_tp_and_srv(&ns, 0); + tt_int_op(ret, OP_EQ, 0); + + done: + ; +} + +/************ Reachability Test (it is huge) ****************/ + +/* Simulate different consensus for client and service. Used by the + * reachability test. The SRV and responsible HSDir list are used by all + * reachability tests so make them common to simplify setup and teardown. */ +static networkstatus_t *mock_service_ns = NULL; +static networkstatus_t *mock_client_ns = NULL; +static sr_srv_t current_srv, previous_srv; +static smartlist_t *service_responsible_hsdirs = NULL; +static smartlist_t *client_responsible_hsdirs = NULL; + +static networkstatus_t * +mock_networkstatus_get_live_consensus_service(time_t now) +{ + (void) now; + + if (mock_service_ns) { + return mock_service_ns; + } + + mock_service_ns = tor_malloc_zero(sizeof(networkstatus_t)); + mock_service_ns->routerstatus_list = smartlist_new(); + mock_service_ns->type = NS_TYPE_CONSENSUS; + + return mock_service_ns; +} + +static networkstatus_t * +mock_networkstatus_get_latest_consensus_service(void) +{ + return mock_networkstatus_get_live_consensus_service(0); +} + +static networkstatus_t * +mock_networkstatus_get_live_consensus_client(time_t now) +{ + (void) now; + + if (mock_client_ns) { + return mock_client_ns; + } + + mock_client_ns = tor_malloc_zero(sizeof(networkstatus_t)); + mock_client_ns->routerstatus_list = smartlist_new(); + mock_client_ns->type = NS_TYPE_CONSENSUS; + + return mock_client_ns; +} + +static networkstatus_t * +mock_networkstatus_get_latest_consensus_client(void) +{ + return mock_networkstatus_get_live_consensus_client(0); +} + +/* Mock function because we are not trying to test the close circuit that does + * an awful lot of checks on the circuit object. */ +static void +mock_circuit_mark_for_close(circuit_t *circ, int reason, int line, + const char *file) +{ + (void) circ; + (void) reason; + (void) line; + (void) file; + return; +} + +/* Initialize a big HSDir V3 hash ring. */ +static void +helper_initialize_big_hash_ring(networkstatus_t *ns) +{ + int ret; + + /* Generate 250 hsdirs! :) */ + for (int counter = 1 ; counter < 251 ; counter++) { + /* Let's generate random nickname for each hsdir... */ + char nickname_binary[8]; + char nickname_str[13] = {0}; + crypto_rand(nickname_binary, sizeof(nickname_binary)); + ret = base64_encode(nickname_str, sizeof(nickname_str), + nickname_binary, sizeof(nickname_binary), 0); + tt_int_op(ret, OP_EQ, 12); + helper_add_hsdir_to_networkstatus(ns, counter, nickname_str, 1); + } + + /* Make sure we have 200 hsdirs in our list */ + tt_int_op(smartlist_len(ns->routerstatus_list), OP_EQ, 250); + + done: + ; +} + +/** Initialize service and publish its descriptor as needed. Return the newly + * allocated service object to the caller. */ +static hs_service_t * +helper_init_service(time_t now) +{ + int retval; + hs_service_t *service = hs_service_new(get_options()); + tt_assert(service); + service->config.version = HS_VERSION_THREE; + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + /* Register service to global map. */ + retval = register_service(get_hs_service_map(), service); + tt_int_op(retval, OP_EQ, 0); + + /* Initialize service descriptor */ + build_all_descriptors(now); + tt_assert(service->desc_current); + tt_assert(service->desc_next); + + done: + return service; +} + +/* Helper function to set the RFC 1123 time string into t. */ +static void +set_consensus_times(const char *time, time_t *t) +{ + tt_assert(time); + tt_assert(t); + + int ret = parse_rfc1123_time(time, t); + tt_int_op(ret, OP_EQ, 0); + + done: + return; +} + +/* Helper function to cleanup the mock consensus (client and service) */ +static void +cleanup_mock_ns(void) +{ + if (mock_service_ns) { + SMARTLIST_FOREACH(mock_service_ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(mock_service_ns->routerstatus_list); + mock_service_ns->sr_info.current_srv = NULL; + mock_service_ns->sr_info.previous_srv = NULL; + networkstatus_vote_free(mock_service_ns); + mock_service_ns = NULL; + } + + if (mock_client_ns) { + SMARTLIST_FOREACH(mock_client_ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(mock_client_ns->routerstatus_list); + mock_client_ns->sr_info.current_srv = NULL; + mock_client_ns->sr_info.previous_srv = NULL; + networkstatus_vote_free(mock_client_ns); + mock_client_ns = NULL; + } +} + +/* Helper function to setup a reachability test. Once called, the + * cleanup_reachability_test MUST be called at the end. */ +static void +setup_reachability_test(void) +{ + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(get_or_state, get_or_state_replacement); + + hs_init(); + + /* Baseline to start with. */ + memset(¤t_srv, 0, sizeof(current_srv)); + memset(&previous_srv, 1, sizeof(previous_srv)); + + /* Initialize the consensuses. */ + mock_networkstatus_get_latest_consensus_service(); + mock_networkstatus_get_latest_consensus_client(); + + service_responsible_hsdirs = smartlist_new(); + client_responsible_hsdirs = smartlist_new(); +} + +/* Helper function to cleanup a reachability test initial setup. */ +static void +cleanup_reachability_test(void) +{ + smartlist_free(service_responsible_hsdirs); + service_responsible_hsdirs = NULL; + smartlist_free(client_responsible_hsdirs); + client_responsible_hsdirs = NULL; + hs_free_all(); + cleanup_mock_ns(); + UNMOCK(get_or_state); + UNMOCK(circuit_mark_for_close_); +} + +/* A reachability test always check if the resulting service and client + * responsible HSDir for the given parameters are equal. + * + * Return true iff the same exact nodes are in both list. */ +static int +are_responsible_hsdirs_equal(void) +{ + int count = 0; + tt_int_op(smartlist_len(client_responsible_hsdirs), OP_EQ, 6); + tt_int_op(smartlist_len(service_responsible_hsdirs), OP_EQ, 8); + + SMARTLIST_FOREACH_BEGIN(client_responsible_hsdirs, + const routerstatus_t *, c_rs) { + SMARTLIST_FOREACH_BEGIN(service_responsible_hsdirs, + const routerstatus_t *, s_rs) { + if (tor_memeq(c_rs->identity_digest, s_rs->identity_digest, + DIGEST_LEN)) { + count++; + break; + } + } SMARTLIST_FOREACH_END(s_rs); + } SMARTLIST_FOREACH_END(c_rs); + + done: + return (count == 6); +} + +/* Tor doesn't use such a function to get the previous HSDir, it is only used + * in node_set_hsdir_index(). We need it here so we can test the reachability + * scenario 6 that requires the previous time period to compute the list of + * responsible HSDir because of the client state timing. */ +static uint64_t +get_previous_time_period(time_t now) +{ + return hs_get_time_period_num(now) - 1; +} + +/* Configuration of a reachability test scenario. */ +typedef struct reachability_cfg_t { + /* Consensus timings to be set. They have to be compliant with + * RFC 1123 time format. */ + const char *service_valid_after; + const char *service_valid_until; + const char *client_valid_after; + const char *client_valid_until; + + /* SRVs that the service and client should use. */ + sr_srv_t *service_current_srv; + sr_srv_t *service_previous_srv; + sr_srv_t *client_current_srv; + sr_srv_t *client_previous_srv; + + /* A time period function for the service to use for this scenario. For a + * successful reachability test, the client always use the current time + * period thus why no client function. */ + uint64_t (*service_time_period_fn)(time_t); + + /* Is the client and service expected to be in a new time period. After + * setting the consensus time, the reachability test checks + * hs_in_period_between_tp_and_srv() and test the returned value against + * this. */ + unsigned int service_in_new_tp; + unsigned int client_in_new_tp; + + /* Some scenario requires a hint that the client, because of its consensus + * time, will request the "next" service descriptor so this indicates if it + * is the case or not. */ + unsigned int client_fetch_next_desc; +} reachability_cfg_t; + +/* Some defines to help with semantic while reading a configuration below. */ +#define NOT_IN_NEW_TP 0 +#define IN_NEW_TP 1 +#define DONT_NEED_NEXT_DESC 0 +#define NEED_NEXT_DESC 1 + +static reachability_cfg_t reachability_scenarios[] = { + /* Scenario 1 + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + * + * S: Service, C: Client + * + * Service consensus valid_after time is set to 13:00 and client to 15:00, + * both are after TP#1 thus have access to SRV#1. Service and client should + * be using TP#1. + */ + + { "Sat, 26 Oct 1985 13:00:00 UTC", /* Service valid_after */ + "Sat, 26 Oct 1985 14:00:00 UTC", /* Service valid_until */ + "Sat, 26 Oct 1985 15:00:00 UTC", /* Client valid_after */ + "Sat, 26 Oct 1985 16:00:00 UTC", /* Client valid_until. */ + ¤t_srv, NULL, /* Service current and previous SRV */ + ¤t_srv, NULL, /* Client current and previous SRV */ + hs_get_time_period_num, /* Service time period function. */ + IN_NEW_TP, /* Is service in new TP? */ + IN_NEW_TP, /* Is client in new TP? */ + NEED_NEXT_DESC }, + + /* Scenario 2 + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + * + * S: Service, C: Client + * + * Service consensus valid_after time is set to 23:00 and client to 01:00, + * which makes the client after the SRV#2 and the service just before. The + * service should only be using TP#1. The client should be using TP#1. + */ + + { "Sat, 26 Oct 1985 23:00:00 UTC", /* Service valid_after */ + "Sat, 27 Oct 1985 00:00:00 UTC", /* Service valid_until */ + "Sat, 27 Oct 1985 01:00:00 UTC", /* Client valid_after */ + "Sat, 27 Oct 1985 02:00:00 UTC", /* Client valid_until. */ + &previous_srv, NULL, /* Service current and previous SRV */ + ¤t_srv, &previous_srv, /* Client current and previous SRV */ + hs_get_time_period_num, /* Service time period function. */ + IN_NEW_TP, /* Is service in new TP? */ + NOT_IN_NEW_TP, /* Is client in new TP? */ + NEED_NEXT_DESC }, + + /* Scenario 3 + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + * + * S: Service, C: Client + * + * Service consensus valid_after time is set to 03:00 and client to 05:00, + * which makes both after SRV#2. The service should be using TP#1 as its + * current time period. The client should be using TP#1. + */ + + { "Sat, 27 Oct 1985 03:00:00 UTC", /* Service valid_after */ + "Sat, 27 Oct 1985 04:00:00 UTC", /* Service valid_until */ + "Sat, 27 Oct 1985 05:00:00 UTC", /* Client valid_after */ + "Sat, 27 Oct 1985 06:00:00 UTC", /* Client valid_until. */ + ¤t_srv, &previous_srv, /* Service current and previous SRV */ + ¤t_srv, &previous_srv, /* Client current and previous SRV */ + hs_get_time_period_num, /* Service time period function. */ + NOT_IN_NEW_TP, /* Is service in new TP? */ + NOT_IN_NEW_TP, /* Is client in new TP? */ + DONT_NEED_NEXT_DESC }, + + /* Scenario 4 + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + * + * S: Service, C: Client + * + * Service consensus valid_after time is set to 11:00 and client to 13:00, + * which makes the service before TP#2 and the client just after. The + * service should be using TP#1 as its current time period and TP#2 as the + * next. The client should be using TP#2 time period. + */ + + { "Sat, 27 Oct 1985 11:00:00 UTC", /* Service valid_after */ + "Sat, 27 Oct 1985 12:00:00 UTC", /* Service valid_until */ + "Sat, 27 Oct 1985 13:00:00 UTC", /* Client valid_after */ + "Sat, 27 Oct 1985 14:00:00 UTC", /* Client valid_until. */ + ¤t_srv, &previous_srv, /* Service current and previous SRV */ + ¤t_srv, &previous_srv, /* Client current and previous SRV */ + hs_get_next_time_period_num, /* Service time period function. */ + NOT_IN_NEW_TP, /* Is service in new TP? */ + IN_NEW_TP, /* Is client in new TP? */ + NEED_NEXT_DESC }, + + /* Scenario 5 + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | C S | + * +------------------------------------------------------------------+ + * + * S: Service, C: Client + * + * Service consensus valid_after time is set to 01:00 and client to 23:00, + * which makes the service after SRV#2 and the client just before. The + * service should be using TP#1 as its current time period and TP#2 as the + * next. The client should be using TP#1 time period. + */ + + { "Sat, 27 Oct 1985 01:00:00 UTC", /* Service valid_after */ + "Sat, 27 Oct 1985 02:00:00 UTC", /* Service valid_until */ + "Sat, 26 Oct 1985 23:00:00 UTC", /* Client valid_after */ + "Sat, 27 Oct 1985 00:00:00 UTC", /* Client valid_until. */ + ¤t_srv, &previous_srv, /* Service current and previous SRV */ + &previous_srv, NULL, /* Client current and previous SRV */ + hs_get_time_period_num, /* Service time period function. */ + NOT_IN_NEW_TP, /* Is service in new TP? */ + IN_NEW_TP, /* Is client in new TP? */ + DONT_NEED_NEXT_DESC }, + + /* Scenario 6 + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | C S | + * +------------------------------------------------------------------+ + * + * S: Service, C: Client + * + * Service consensus valid_after time is set to 13:00 and client to 11:00, + * which makes the service outside after TP#2 and the client just before. + * The service should be using TP#1 as its current time period and TP#2 as + * its next. The client should be using TP#1 time period. + */ + + { "Sat, 27 Oct 1985 13:00:00 UTC", /* Service valid_after */ + "Sat, 27 Oct 1985 14:00:00 UTC", /* Service valid_until */ + "Sat, 27 Oct 1985 11:00:00 UTC", /* Client valid_after */ + "Sat, 27 Oct 1985 12:00:00 UTC", /* Client valid_until. */ + ¤t_srv, &previous_srv, /* Service current and previous SRV */ + ¤t_srv, &previous_srv, /* Client current and previous SRV */ + get_previous_time_period, /* Service time period function. */ + IN_NEW_TP, /* Is service in new TP? */ + NOT_IN_NEW_TP, /* Is client in new TP? */ + DONT_NEED_NEXT_DESC }, + + /* End marker. */ + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0} +}; + +/* Run a single reachability scenario. num_scenario is the corresponding + * scenario number from the documentation. It is used to log it in case of + * failure so we know which scenario fails. */ +static int +run_reachability_scenario(const reachability_cfg_t *cfg, int num_scenario) +{ + int ret = -1; + hs_service_t *service; + uint64_t service_tp, client_tp; + ed25519_public_key_t service_blinded_pk, client_blinded_pk; + + setup_reachability_test(); + + tt_assert(cfg); + + /* Set service consensus time. */ + set_consensus_times(cfg->service_valid_after, + &mock_service_ns->valid_after); + set_consensus_times(cfg->service_valid_until, + &mock_service_ns->valid_until); + set_consensus_times(cfg->service_valid_until, + &mock_service_ns->fresh_until); + dirvote_recalculate_timing(get_options(), mock_service_ns->valid_after); + /* Set client consensus time. */ + set_consensus_times(cfg->client_valid_after, + &mock_client_ns->valid_after); + set_consensus_times(cfg->client_valid_until, + &mock_client_ns->valid_until); + set_consensus_times(cfg->client_valid_until, + &mock_client_ns->fresh_until); + dirvote_recalculate_timing(get_options(), mock_client_ns->valid_after); + + /* New time period checks for this scenario. */ + tt_int_op(hs_in_period_between_tp_and_srv(mock_service_ns, 0), OP_EQ, + cfg->service_in_new_tp); + tt_int_op(hs_in_period_between_tp_and_srv(mock_client_ns, 0), OP_EQ, + cfg->client_in_new_tp); + + /* Set the SRVs for this scenario. */ + mock_client_ns->sr_info.current_srv = cfg->client_current_srv; + mock_client_ns->sr_info.previous_srv = cfg->client_previous_srv; + mock_service_ns->sr_info.current_srv = cfg->service_current_srv; + mock_service_ns->sr_info.previous_srv = cfg->service_previous_srv; + + /* Initialize a service to get keys. */ + service = helper_init_service(time(NULL)); + + /* + * === Client setup === + */ + + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus_client); + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus_client); + + /* Make networkstatus_is_live() happy. */ + update_approx_time(mock_client_ns->valid_after); + /* Initialize a big hashring for this consensus with the hsdir index set. */ + helper_initialize_big_hash_ring(mock_client_ns); + + /* Client ONLY use the current time period. This is the whole point of these + * reachability test that is to make sure the client can always reach the + * service using only its current time period. */ + client_tp = hs_get_time_period_num(0); + + hs_build_blinded_pubkey(&service->keys.identity_pk, NULL, 0, + client_tp, &client_blinded_pk); + hs_get_responsible_hsdirs(&client_blinded_pk, client_tp, 0, 1, + client_responsible_hsdirs); + /* Cleanup the nodelist so we can let the service computes its own set of + * node with its own hashring. */ + cleanup_nodelist(); + tt_int_op(smartlist_len(client_responsible_hsdirs), OP_EQ, 6); + + UNMOCK(networkstatus_get_latest_consensus); + UNMOCK(networkstatus_get_live_consensus); + + /* + * === Service setup === + */ + + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus_service); + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus_service); + + /* Make networkstatus_is_live() happy. */ + update_approx_time(mock_service_ns->valid_after); + /* Initialize a big hashring for this consensus with the hsdir index set. */ + helper_initialize_big_hash_ring(mock_service_ns); + + service_tp = cfg->service_time_period_fn(0); + + hs_build_blinded_pubkey(&service->keys.identity_pk, NULL, 0, + service_tp, &service_blinded_pk); + + /* A service builds two lists of responsible HSDir, for the current and the + * next descriptor. Depending on the scenario, the client timing indicate if + * it is fetching the current or the next descriptor so we use the + * "client_fetch_next_desc" to know which one the client is trying to get to + * confirm that the service computes the same hashring for the same blinded + * key and service time period function. */ + hs_get_responsible_hsdirs(&service_blinded_pk, service_tp, + cfg->client_fetch_next_desc, 0, + service_responsible_hsdirs); + cleanup_nodelist(); + tt_int_op(smartlist_len(service_responsible_hsdirs), OP_EQ, 8); + + UNMOCK(networkstatus_get_latest_consensus); + UNMOCK(networkstatus_get_live_consensus); + + /* Some testing of the values we just got from the client and service. */ + tt_mem_op(&client_blinded_pk, OP_EQ, &service_blinded_pk, + ED25519_PUBKEY_LEN); + tt_int_op(are_responsible_hsdirs_equal(), OP_EQ, 1); + + /* Everything went well. */ + ret = 0; + + done: + cleanup_reachability_test(); + if (ret == -1) { + /* Do this so we can know which scenario failed. */ + char msg[32]; + tor_snprintf(msg, sizeof(msg), "Scenario %d failed", num_scenario); + tt_fail_msg(msg); + } + return ret; +} + +static void +test_reachability(void *arg) +{ + (void) arg; + + /* NOTE: An important axiom to understand here is that SRV#N must only be + * used with TP#N value. For example, SRV#2 with TP#1 should NEVER be used + * together. The HSDir index computation is based on this axiom.*/ + + for (int i = 0; reachability_scenarios[i].service_valid_after; ++i) { + int ret = run_reachability_scenario(&reachability_scenarios[i], i + 1); + if (ret < 0) { + return; + } + } +} + +/** Pick an HSDir for service with <b>onion_identity_pk</b> as a client. Put + * its identity digest in <b>hsdir_digest_out</b>. */ +static void +helper_client_pick_hsdir(const ed25519_public_key_t *onion_identity_pk, + char *hsdir_digest_out) +{ + tt_assert(onion_identity_pk); + + routerstatus_t *client_hsdir = pick_hsdir_v3(onion_identity_pk); + tt_assert(client_hsdir); + digest_to_base64(hsdir_digest_out, client_hsdir->identity_digest); + + done: + ; +} + +static void +test_hs_indexes(void *arg) +{ + int ret; + uint64_t period_num = 42; + ed25519_public_key_t pubkey; + + (void) arg; + + /* Build the hs_index */ + { + uint8_t hs_index[DIGEST256_LEN]; + const char *b32_test_vector = + "37e5cbbd56a22823714f18f1623ece5983a0d64c78495a8cfab854245e5f9a8a"; + char test_vector[DIGEST256_LEN]; + ret = base16_decode(test_vector, sizeof(test_vector), b32_test_vector, + strlen(b32_test_vector)); + tt_int_op(ret, OP_EQ, sizeof(test_vector)); + /* Our test vector uses a public key set to 32 bytes of \x42. */ + memset(&pubkey, '\x42', sizeof(pubkey)); + hs_build_hs_index(1, &pubkey, period_num, hs_index); + tt_mem_op(hs_index, OP_EQ, test_vector, sizeof(hs_index)); + } + + /* Build the hsdir_index */ + { + uint8_t srv[DIGEST256_LEN]; + uint8_t hsdir_index[DIGEST256_LEN]; + const char *b32_test_vector = + "db475361014a09965e7e5e4d4a25b8f8d4b8f16cb1d8a7e95eed50249cc1a2d5"; + char test_vector[DIGEST256_LEN]; + ret = base16_decode(test_vector, sizeof(test_vector), b32_test_vector, + strlen(b32_test_vector)); + tt_int_op(ret, OP_EQ, sizeof(test_vector)); + /* Our test vector uses a public key set to 32 bytes of \x42. */ + memset(&pubkey, '\x42', sizeof(pubkey)); + memset(srv, '\x43', sizeof(srv)); + hs_build_hsdir_index(&pubkey, srv, period_num, hsdir_index); + tt_mem_op(hsdir_index, OP_EQ, test_vector, sizeof(hsdir_index)); + } + + done: + ; +} + +#define EARLY_IN_SRV_TO_TP 0 +#define LATE_IN_SRV_TO_TP 1 +#define EARLY_IN_TP_TO_SRV 2 +#define LATE_IN_TP_TO_SRV 3 + +/** Set the consensus and system time based on <b>position</b>. See the + * following diagram for details: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|----------$===========| | + * | | + * | | + * +------------------------------------------------------------------+ + */ +static time_t +helper_set_consensus_and_system_time(networkstatus_t *ns, int position) +{ + time_t real_time = 0; + + /* The period between SRV#N and TP#N is from 00:00 to 12:00 UTC. Consensus + * valid_after is what matters here, the rest is just to specify the voting + * period correctly. */ + if (position == LATE_IN_SRV_TO_TP) { + parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", &ns->valid_after); + parse_rfc1123_time("Wed, 13 Apr 2016 12:00:00 UTC", &ns->fresh_until); + parse_rfc1123_time("Wed, 13 Apr 2016 14:00:00 UTC", &ns->valid_until); + } else if (position == EARLY_IN_TP_TO_SRV) { + parse_rfc1123_time("Wed, 13 Apr 2016 13:00:00 UTC", &ns->valid_after); + parse_rfc1123_time("Wed, 13 Apr 2016 14:00:00 UTC", &ns->fresh_until); + parse_rfc1123_time("Wed, 13 Apr 2016 16:00:00 UTC", &ns->valid_until); + } else if (position == LATE_IN_TP_TO_SRV) { + parse_rfc1123_time("Wed, 13 Apr 2016 23:00:00 UTC", &ns->valid_after); + parse_rfc1123_time("Wed, 14 Apr 2016 00:00:00 UTC", &ns->fresh_until); + parse_rfc1123_time("Wed, 14 Apr 2016 02:00:00 UTC", &ns->valid_until); + } else if (position == EARLY_IN_SRV_TO_TP) { + parse_rfc1123_time("Wed, 14 Apr 2016 01:00:00 UTC", &ns->valid_after); + parse_rfc1123_time("Wed, 14 Apr 2016 02:00:00 UTC", &ns->fresh_until); + parse_rfc1123_time("Wed, 14 Apr 2016 04:00:00 UTC", &ns->valid_until); + } else { + tt_assert(0); + } + dirvote_recalculate_timing(get_options(), ns->valid_after); + + /* Set system time: pretend to be just 2 minutes before consensus expiry */ + real_time = ns->valid_until - 120; + update_approx_time(real_time); + + done: + return real_time; +} + +/** Helper function that carries out the actual test for + * test_client_service_sync() */ +static void +helper_test_hsdir_sync(networkstatus_t *ns, + int service_position, int client_position, + int client_fetches_next_desc) +{ + hs_service_descriptor_t *desc; + int retval; + + /** Test logic: + * 1) Initialize service time: consensus and system time. + * 1.1) Initialize service hash ring + * 2) Initialize service and publish descriptors. + * 3) Initialize client time: consensus and system time. + * 3.1) Initialize client hash ring + * 4) Try to fetch descriptor as client, and CHECK that the HSDir picked by + * the client was also picked by service. + */ + + /* 1) Initialize service time: consensus and real time */ + time_t now = helper_set_consensus_and_system_time(ns, service_position); + helper_initialize_big_hash_ring(ns); + + /* 2) Initialize service */ + hs_service_t *service = helper_init_service(now); + desc = client_fetches_next_desc ? service->desc_next : service->desc_current; + + /* Now let's upload our desc to all hsdirs */ + upload_descriptor_to_all(service, desc); + /* Cleanup right now so we don't memleak on error. */ + cleanup_nodelist(); + /* Check that previous hsdirs were populated */ + tt_int_op(smartlist_len(desc->previous_hsdirs), OP_EQ, 8); + + /* 3) Initialize client time */ + helper_set_consensus_and_system_time(ns, client_position); + + cleanup_nodelist(); + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(ns->routerstatus_list); + helper_initialize_big_hash_ring(ns); + + /* 4) Pick 6 HSDirs as a client and check that they were also chosen by the + service. */ + for (int y = 0 ; y < 6 ; y++) { + char client_hsdir_b64_digest[BASE64_DIGEST_LEN+1] = {0}; + helper_client_pick_hsdir(&service->keys.identity_pk, + client_hsdir_b64_digest); + + /* CHECK: Go through the hsdirs chosen by the service and make sure that it + * contains the one picked by the client! */ + retval = smartlist_contains_string(desc->previous_hsdirs, + client_hsdir_b64_digest); + tt_int_op(retval, OP_EQ, 1); + } + + /* Finally, try to pick a 7th hsdir and see that NULL is returned since we + * exhausted all of them: */ + tt_assert(!pick_hsdir_v3(&service->keys.identity_pk)); + + done: + /* At the end: free all services and initialize the subsystem again, we will + * need it for next scenario. */ + cleanup_nodelist(); + hs_service_free_all(); + hs_service_init(); + SMARTLIST_FOREACH(ns->routerstatus_list, + routerstatus_t *, rs, routerstatus_free(rs)); + smartlist_clear(ns->routerstatus_list); +} + +/** This test ensures that client and service will pick the same HSDirs, under + * various timing scenarios: + * a) Scenario where both client and service are in the time segment between + * SRV#N and TP#N: + * b) Scenario where both client and service are in the time segment between + * TP#N and SRV#N+1. + * c) Scenario where service is between SRV#N and TP#N, but client is between + * TP#N and SRV#N+1. + * d) Scenario where service is between TP#N and SRV#N+1, but client is + * between SRV#N and TP#N. + * + * This test is important because it tests that upload_descriptor_to_all() is + * in synch with pick_hsdir_v3(). That's not the case for the + * test_reachability() test which only compares the responsible hsdir sets. + */ +static void +test_client_service_hsdir_set_sync(void *arg) +{ + networkstatus_t *ns = NULL; + + (void) arg; + + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + MOCK(get_or_state, + get_or_state_replacement); + MOCK(hs_desc_encode_descriptor, + mock_hs_desc_encode_descriptor); + MOCK(directory_initiate_request, + mock_directory_initiate_request); + + hs_init(); + + /* Initialize a big hash ring: we want it to be big so that client and + * service cannot accidentally select the same HSDirs */ + ns = networkstatus_get_latest_consensus(); + tt_assert(ns); + + /** Now test the various synch scenarios. See the helper function for more + details: */ + + /* a) Scenario where both client and service are in the time segment between + * SRV#N and TP#N. At this time the client fetches the first HS desc: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + */ + helper_test_hsdir_sync(ns, LATE_IN_SRV_TO_TP, LATE_IN_SRV_TO_TP, 0); + + /* b) Scenario where both client and service are in the time segment between + * TP#N and SRV#N+1. At this time the client fetches the second HS + * desc: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + */ + helper_test_hsdir_sync(ns, LATE_IN_TP_TO_SRV, LATE_IN_TP_TO_SRV, 1); + + /* c) Scenario where service is between SRV#N and TP#N, but client is + * between TP#N and SRV#N+1. Client is forward in time so it fetches the + * second HS desc. + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + */ + helper_test_hsdir_sync(ns, LATE_IN_SRV_TO_TP, EARLY_IN_TP_TO_SRV, 1); + + /* d) Scenario where service is between TP#N and SRV#N+1, but client is + * between SRV#N and TP#N. Client is backwards in time so it fetches the + * first HS desc. + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | C S | + * +------------------------------------------------------------------+ + */ + helper_test_hsdir_sync(ns, EARLY_IN_TP_TO_SRV, LATE_IN_SRV_TO_TP, 0); + + /* e) Scenario where service is between SRV#N and TP#N, but client is + * between TP#N-1 and SRV#3. Client is backwards in time so it fetches + * the first HS desc. + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | C S | + * +------------------------------------------------------------------+ + */ + helper_test_hsdir_sync(ns, EARLY_IN_SRV_TO_TP, LATE_IN_TP_TO_SRV, 0); + + /* f) Scenario where service is between TP#N and SRV#N+1, but client is + * between SRV#N+1 and TP#N+1. Client is forward in time so it fetches + * the second HS desc. + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | S C | + * +------------------------------------------------------------------+ + */ + helper_test_hsdir_sync(ns, LATE_IN_TP_TO_SRV, EARLY_IN_SRV_TO_TP, 1); + + done: + networkstatus_vote_free(ns); + nodelist_free_all(); + hs_free_all(); +} + +struct testcase_t hs_common_tests[] = { + { "build_address", test_build_address, TT_FORK, + NULL, NULL }, + { "validate_address", test_validate_address, TT_FORK, + NULL, NULL }, + { "time_period", test_time_period, TT_FORK, + NULL, NULL }, + { "start_time_of_next_time_period", test_start_time_of_next_time_period, + TT_FORK, NULL, NULL }, + { "responsible_hsdirs", test_responsible_hsdirs, TT_FORK, + NULL, NULL }, + { "desc_reupload_logic", test_desc_reupload_logic, TT_FORK, + NULL, NULL }, + { "disaster_srv", test_disaster_srv, TT_FORK, + NULL, NULL }, + { "hid_serv_request_tracker", test_hid_serv_request_tracker, TT_FORK, + NULL, NULL }, + { "parse_extended_hostname", test_parse_extended_hostname, TT_FORK, + NULL, NULL }, + { "time_between_tp_and_srv", test_time_between_tp_and_srv, TT_FORK, + NULL, NULL }, + { "reachability", test_reachability, TT_FORK, + NULL, NULL }, + { "client_service_hsdir_set_sync", test_client_service_hsdir_set_sync, + TT_FORK, NULL, NULL }, + { "hs_indexes", test_hs_indexes, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_config.c b/src/test/test_hs_config.c new file mode 100644 index 0000000000..a76be301d3 --- /dev/null +++ b/src/test/test_hs_config.c @@ -0,0 +1,487 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_config.c + * \brief Test hidden service configuration functionality. + */ + +#define CONFIG_PRIVATE +#define HS_SERVICE_PRIVATE + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" + +#include "config.h" +#include "hs_common.h" +#include "hs_config.h" +#include "hs_service.h" +#include "rendservice.h" + +static int +helper_config_service(const char *conf, int validate_only) +{ + int ret = 0; + or_options_t *options = NULL; + tt_assert(conf); + options = helper_parse_options(conf); + tt_assert(options); + ret = hs_config_service_all(options, validate_only); + done: + or_options_free(options); + return ret; +} + +static void +test_invalid_service(void *arg) +{ + int ret; + + (void) arg; + + /* Try with a missing port configuration. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 1\n"; /* Wrong not supported version. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceVersion must be between 2 and 3"); + teardown_capture_of_logs(); + } + + /* Bad value of HiddenServiceAllowUnknownPorts. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServiceAllowUnknownPorts 2\n"; /* Should be 0 or 1. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceAllowUnknownPorts must be " + "between 0 and 1, not 2"); + teardown_capture_of_logs(); + } + + /* Bad value of HiddenServiceDirGroupReadable */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServiceDirGroupReadable 2\n"; /* Should be 0 or 1. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceDirGroupReadable must be " + "between 0 and 1, not 2"); + teardown_capture_of_logs(); + } + + /* Bad value of HiddenServiceMaxStreamsCloseCircuit */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServiceMaxStreamsCloseCircuit 2\n"; /* Should be 0 or 1. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceMaxStreamsCloseCircuit must " + "be between 0 and 1, not 2"); + teardown_capture_of_logs(); + } + + /* Too much max streams. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceMaxStreams 65536\n"; /* One too many. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceMaxStreams must be between " + "0 and 65535, not 65536"); + teardown_capture_of_logs(); + } + + /* Duplicate directory directive. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 81\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Another hidden service is already " + "configured for directory"); + teardown_capture_of_logs(); + } + + /* Bad port. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 65536\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Missing or invalid port"); + teardown_capture_of_logs(); + } + + /* Out of order directives. */ + { + const char *conf = + "HiddenServiceVersion 2\n" + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServicePort 80\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceVersion with no preceding " + "HiddenServiceDir directive"); + teardown_capture_of_logs(); + } + + done: + ; +} + +static void +test_valid_service(void *arg) +{ + int ret; + + (void) arg; + + /* Mix of v2 and v3. Still valid. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 81\n" + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 82\n"; + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, 0); + } + + done: + ; +} + +static void +test_invalid_service_v2(void *arg) +{ + int validate_only = 1, ret; + + (void) arg; + + /* Try with a missing port configuration. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("with no ports configured."); + teardown_capture_of_logs(); + } + + /* Too many introduction points. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceNumIntroductionPoints 11\n"; /* One too many. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceNumIntroductionPoints should " + "be between 0 and 10, not 11"); + teardown_capture_of_logs(); + } + + /* Too little introduction points. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceNumIntroductionPoints -1\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceNumIntroductionPoints should " + "be between 0 and 10, not -1"); + teardown_capture_of_logs(); + } + + /* Bad authorized client type. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceAuthorizeClient blah alice,bob\n"; /* blah is no good. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceAuthorizeClient contains " + "unrecognized auth-type"); + teardown_capture_of_logs(); + } + + done: + ; +} + +static void +test_valid_service_v2(void *arg) +{ + int ret; + + (void) arg; + + /* Valid complex configuration. Basic client authorization. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServicePort 22 localhost:22\n" +#ifdef HAVE_SYS_UN_H + "HiddenServicePort 42 unix:/path/to/socket\n" +#endif + "HiddenServiceAuthorizeClient basic alice,bob,eve\n" + "HiddenServiceAllowUnknownPorts 1\n" + "HiddenServiceMaxStreams 42\n" + "HiddenServiceMaxStreamsCloseCircuit 0\n" + "HiddenServiceDirGroupReadable 1\n" + "HiddenServiceNumIntroductionPoints 7\n"; + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, 0); + } + + /* Valid complex configuration. Stealth client authorization. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 65535\n" + "HiddenServicePort 22 1.1.1.1:22\n" +#ifdef HAVE_SYS_UN_H + "HiddenServicePort 9000 unix:/path/to/socket\n" +#endif + "HiddenServiceAuthorizeClient stealth charlie,romeo\n" + "HiddenServiceAllowUnknownPorts 0\n" + "HiddenServiceMaxStreams 42\n" + "HiddenServiceMaxStreamsCloseCircuit 0\n" + "HiddenServiceDirGroupReadable 1\n" + "HiddenServiceNumIntroductionPoints 8\n"; + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, 0); + } + + done: + ; +} + +static void +test_invalid_service_v3(void *arg) +{ + int validate_only = 1, ret; + + (void) arg; + + /* Try with a missing port configuration. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 3\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("with no ports configured."); + teardown_capture_of_logs(); + } + + /* Too many introduction points. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 80\n" + "HiddenServiceNumIntroductionPoints 21\n"; /* One too many. */ + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceNumIntroductionPoints must " + "be between 3 and 20, not 21."); + teardown_capture_of_logs(); + } + + /* Too little introduction points. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 80\n" + "HiddenServiceNumIntroductionPoints 1\n"; + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, validate_only); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceNumIntroductionPoints must " + "be between 3 and 20, not 1."); + teardown_capture_of_logs(); + } + + done: + ; +} + +static void +test_valid_service_v3(void *arg) +{ + int ret; + + (void) arg; + + /* Valid complex configuration. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 80\n" + "HiddenServicePort 22 localhost:22\n" +#ifdef HAVE_SYS_UN_H + "HiddenServicePort 42 unix:/path/to/socket\n" +#endif + "HiddenServiceAllowUnknownPorts 1\n" + "HiddenServiceMaxStreams 42\n" + "HiddenServiceMaxStreamsCloseCircuit 0\n" + "HiddenServiceDirGroupReadable 1\n" + "HiddenServiceNumIntroductionPoints 7\n"; + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, 0); + } + + /* Valid complex configuration. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 65535\n" + "HiddenServicePort 22 1.1.1.1:22\n" +#ifdef HAVE_SYS_UN_H + "HiddenServicePort 9000 unix:/path/to/socket\n" +#endif + "HiddenServiceAllowUnknownPorts 0\n" + "HiddenServiceMaxStreams 42\n" + "HiddenServiceMaxStreamsCloseCircuit 0\n" + "HiddenServiceDirGroupReadable 1\n" + "HiddenServiceNumIntroductionPoints 20\n"; + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, 0); + } + + /* Mix of v2 and v3. Still valid. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 80\n" + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 81\n" + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 2\n" + "HiddenServicePort 82\n"; + ret = helper_config_service(conf, 1); + tt_int_op(ret, OP_EQ, 0); + } + + done: + ; +} + +static void +test_staging_service_v3(void *arg) +{ + int ret; + + (void) arg; + + /* We don't validate a service object, this is the service test that are in + * charge of doing so. We just check for the stable state after + * registration. */ + + hs_init(); + + /* Time for a valid v3 service that should get staged. */ + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 65535\n" + "HiddenServicePort 22 1.1.1.1:22\n" +#ifdef HAVE_SYS_UN_H + "HiddenServicePort 9000 unix:/path/to/socket\n" +#endif + "HiddenServiceAllowUnknownPorts 0\n" + "HiddenServiceMaxStreams 42\n" + "HiddenServiceMaxStreamsCloseCircuit 0\n" + "HiddenServiceDirGroupReadable 1\n" + "HiddenServiceNumIntroductionPoints 20\n"; + ret = helper_config_service(conf, 0); + tt_int_op(ret, OP_EQ, 0); + /* Ok, we have a service in our map! Registration went well. */ + tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); + /* Make sure we don't have a magic v2 service out of this. */ + tt_int_op(rend_num_services(), OP_EQ, 0); + + done: + hs_free_all(); +} + +struct testcase_t hs_config_tests[] = { + /* Invalid service not specific to any version. */ + { "invalid_service", test_invalid_service, TT_FORK, + NULL, NULL }, + { "valid_service", test_valid_service, TT_FORK, + NULL, NULL }, + + /* Test case only for version 2. */ + { "invalid_service_v2", test_invalid_service_v2, TT_FORK, + NULL, NULL }, + { "valid_service_v2", test_valid_service_v2, TT_FORK, + NULL, NULL }, + + /* Test case only for version 3. */ + { "invalid_service_v3", test_invalid_service_v3, TT_FORK, + NULL, NULL }, + { "valid_service_v3", test_valid_service_v3, TT_FORK, + NULL, NULL }, + + /* Test service staging. */ + { "staging_service_v3", test_staging_service_v3, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index 7d7ec7d9db..9ec183db06 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -54,7 +54,7 @@ test_cert_encoding(void *arg) /* Test the certificate encoding function. */ ret = tor_cert_encode_ed22519(cert, &encoded); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); /* Validated the certificate string. */ { @@ -63,7 +63,7 @@ test_cert_encoding(void *arg) size_t b64_cert_len; tor_cert_t *parsed_cert; - tt_int_op(strcmpstart(pos, "-----BEGIN ED25519 CERT-----\n"), ==, 0); + tt_int_op(strcmpstart(pos, "-----BEGIN ED25519 CERT-----\n"), OP_EQ, 0); pos += strlen("-----BEGIN ED25519 CERT-----\n"); /* Isolate the base64 encoded certificate and try to decode it. */ @@ -72,24 +72,25 @@ test_cert_encoding(void *arg) b64_cert = pos; b64_cert_len = end - pos; ret = base64_decode(buf, sizeof(buf), b64_cert, b64_cert_len); - tt_int_op(ret, >, 0); + tt_int_op(ret, OP_GT, 0); /* Parseable? */ parsed_cert = tor_cert_parse((uint8_t *) buf, ret); tt_assert(parsed_cert); /* Signature is valid? */ ret = tor_cert_checksig(parsed_cert, &kp.pubkey, now + 10); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); ret = tor_cert_eq(cert, parsed_cert); - tt_int_op(ret, ==, 1); + tt_int_op(ret, OP_EQ, 1); /* The cert did have the signing key? */ ret= ed25519_pubkey_eq(&parsed_cert->signing_key, &kp.pubkey); - tt_int_op(ret, ==, 1); + tt_int_op(ret, OP_EQ, 1); tor_cert_free(parsed_cert); /* Get to the end part of the certificate. */ pos += b64_cert_len; - tt_int_op(strcmpstart(pos, "-----END ED25519 CERT-----"), ==, 0); + tt_int_op(strcmpstart(pos, "-----END ED25519 CERT-----"), OP_EQ, 0); pos += strlen("-----END ED25519 CERT-----"); + tt_str_op(pos, OP_EQ, ""); } done: @@ -188,22 +189,22 @@ test_link_specifier(void *arg) spec.type = LS_IPV4; ret = tor_addr_parse(&spec.u.ap.addr, "1.2.3.4"); - tt_int_op(ret, ==, AF_INET); + tt_int_op(ret, OP_EQ, AF_INET); b64 = encode_link_specifiers(link_specifiers); tt_assert(b64); /* Decode it and validate the format. */ ret = base64_decode(buf, sizeof(buf), b64, strlen(b64)); - tt_int_op(ret, >, 0); + tt_int_op(ret, OP_GT, 0); /* First byte is the number of link specifier. */ - tt_int_op(get_uint8(buf), ==, 1); + tt_int_op(get_uint8(buf), OP_EQ, 1); ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1); - tt_int_op(ret, ==, 8); + tt_int_op(ret, OP_EQ, 8); /* Should be 2 bytes for port and 4 bytes for IPv4. */ - tt_int_op(link_specifier_get_ls_len(ls), ==, 6); + tt_int_op(link_specifier_get_ls_len(ls), OP_EQ, 6); ipv4 = link_specifier_get_un_ipv4_addr(ls); - tt_int_op(tor_addr_to_ipv4h(&spec.u.ap.addr), ==, ipv4); - tt_int_op(link_specifier_get_un_ipv4_port(ls), ==, spec.u.ap.port); + tt_int_op(tor_addr_to_ipv4h(&spec.u.ap.addr), OP_EQ, ipv4); + tt_int_op(link_specifier_get_un_ipv4_port(ls), OP_EQ, spec.u.ap.port); link_specifier_free(ls); tor_free(b64); @@ -217,24 +218,25 @@ test_link_specifier(void *arg) spec.type = LS_IPV6; ret = tor_addr_parse(&spec.u.ap.addr, "[1:2:3:4::]"); - tt_int_op(ret, ==, AF_INET6); + tt_int_op(ret, OP_EQ, AF_INET6); b64 = encode_link_specifiers(link_specifiers); tt_assert(b64); /* Decode it and validate the format. */ ret = base64_decode(buf, sizeof(buf), b64, strlen(b64)); - tt_int_op(ret, >, 0); + tt_int_op(ret, OP_GT, 0); /* First byte is the number of link specifier. */ - tt_int_op(get_uint8(buf), ==, 1); + tt_int_op(get_uint8(buf), OP_EQ, 1); ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1); - tt_int_op(ret, ==, 20); + tt_int_op(ret, OP_EQ, 20); /* Should be 2 bytes for port and 16 bytes for IPv6. */ - tt_int_op(link_specifier_get_ls_len(ls), ==, 18); + tt_int_op(link_specifier_get_ls_len(ls), OP_EQ, 18); for (unsigned int i = 0; i < sizeof(ipv6); i++) { ipv6[i] = link_specifier_get_un_ipv6_addr(ls, i); } - tt_mem_op(tor_addr_to_in6_addr8(&spec.u.ap.addr), ==, ipv6, sizeof(ipv6)); - tt_int_op(link_specifier_get_un_ipv6_port(ls), ==, spec.u.ap.port); + tt_mem_op(tor_addr_to_in6_addr8(&spec.u.ap.addr), OP_EQ, ipv6, + sizeof(ipv6)); + tt_int_op(link_specifier_get_un_ipv6_port(ls), OP_EQ, spec.u.ap.port); link_specifier_free(ls); tor_free(b64); @@ -253,12 +255,12 @@ test_link_specifier(void *arg) /* Decode it and validate the format. */ ret = base64_decode(buf, sizeof(buf), b64, strlen(b64)); - tt_int_op(ret, >, 0); + tt_int_op(ret, OP_GT, 0); /* First byte is the number of link specifier. */ - tt_int_op(get_uint8(buf), ==, 1); + tt_int_op(get_uint8(buf), OP_EQ, 1); ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1); /* 20 bytes digest + 1 byte type + 1 byte len. */ - tt_int_op(ret, ==, 22); + tt_int_op(ret, OP_EQ, 22); tt_int_op(link_specifier_getlen_un_legacy_id(ls), OP_EQ, DIGEST_LEN); /* Digest length is 20 bytes. */ tt_int_op(link_specifier_get_ls_len(ls), OP_EQ, DIGEST_LEN); @@ -284,10 +286,10 @@ test_encode_descriptor(void *arg) (void) arg; ret = ed25519_keypair_generate(&signing_kp, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); desc = hs_helper_build_hs_desc_with_ip(&signing_kp); ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); tt_assert(encoded); done: @@ -304,23 +306,27 @@ test_decode_descriptor(void *arg) hs_descriptor_t *desc = NULL; hs_descriptor_t *decoded = NULL; hs_descriptor_t *desc_no_ip = NULL; + uint8_t subcredential[DIGEST256_LEN]; (void) arg; ret = ed25519_keypair_generate(&signing_kp, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); desc = hs_helper_build_hs_desc_with_ip(&signing_kp); + hs_helper_get_subcred_from_identity_keypair(&signing_kp, + subcredential); + /* Give some bad stuff to the decoding function. */ - ret = hs_desc_decode_descriptor("hladfjlkjadf", NULL, &decoded); + ret = hs_desc_decode_descriptor("hladfjlkjadf", subcredential, &decoded); tt_int_op(ret, OP_EQ, -1); ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); tt_assert(encoded); - ret = hs_desc_decode_descriptor(encoded, NULL, &decoded); - tt_int_op(ret, ==, 0); + ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded); + tt_int_op(ret, OP_EQ, 0); tt_assert(decoded); hs_helper_desc_equal(desc, decoded); @@ -329,16 +335,18 @@ test_decode_descriptor(void *arg) { ed25519_keypair_t signing_kp_no_ip; ret = ed25519_keypair_generate(&signing_kp_no_ip, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); + hs_helper_get_subcred_from_identity_keypair(&signing_kp_no_ip, + subcredential); desc_no_ip = hs_helper_build_hs_desc_no_ip(&signing_kp_no_ip); tt_assert(desc_no_ip); tor_free(encoded); ret = hs_desc_encode_descriptor(desc_no_ip, &signing_kp_no_ip, &encoded); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); tt_assert(encoded); hs_descriptor_free(decoded); - ret = hs_desc_decode_descriptor(encoded, NULL, &decoded); - tt_int_op(ret, ==, 0); + ret = hs_desc_decode_descriptor(encoded, subcredential, &decoded); + tt_int_op(ret, OP_EQ, 0); tt_assert(decoded); } @@ -430,12 +438,12 @@ test_decode_invalid_intro_point(void *arg) hs_descriptor_free(desc); desc = NULL; ret = ed25519_keypair_generate(&signing_kp, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); desc = hs_helper_build_hs_desc_with_ip(&signing_kp); const char *junk = "this is not a descriptor"; ip = decode_introduction_point(desc, junk); - tt_assert(!ip); - desc_intro_point_free(ip); + tt_ptr_op(ip, OP_EQ, NULL); + hs_desc_intro_point_free(ip); ip = NULL; } @@ -450,10 +458,10 @@ test_decode_invalid_intro_point(void *arg) encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); tt_assert(encoded_ip); ip = decode_introduction_point(desc, encoded_ip); - tt_assert(!ip); + tt_ptr_op(ip, OP_EQ, NULL); tor_free(encoded_ip); smartlist_free(lines); - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); ip = NULL; } @@ -477,7 +485,7 @@ test_decode_invalid_intro_point(void *arg) encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); tt_assert(encoded_ip); ip = decode_introduction_point(desc, encoded_ip); - tt_assert(!ip); + tt_ptr_op(ip, OP_EQ, NULL); tor_free(encoded_ip); smartlist_free(lines); } @@ -495,7 +503,7 @@ test_decode_invalid_intro_point(void *arg) encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); tt_assert(encoded_ip); ip = decode_introduction_point(desc, encoded_ip); - tt_assert(!ip); + tt_ptr_op(ip, OP_EQ, NULL); tor_free(encoded_ip); smartlist_free(lines); } @@ -512,7 +520,7 @@ test_decode_invalid_intro_point(void *arg) encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); tt_assert(encoded_ip); ip = decode_introduction_point(desc, encoded_ip); - tt_assert(!ip); + tt_ptr_op(ip, OP_EQ, NULL); tor_free(encoded_ip); smartlist_free(lines); } @@ -529,7 +537,7 @@ test_decode_invalid_intro_point(void *arg) encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); tt_assert(encoded_ip); ip = decode_introduction_point(desc, encoded_ip); - tt_assert(!ip); + tt_ptr_op(ip, OP_EQ, NULL); tor_free(encoded_ip); smartlist_free(lines); } @@ -546,14 +554,14 @@ test_decode_invalid_intro_point(void *arg) encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out); tt_assert(encoded_ip); ip = decode_introduction_point(desc, encoded_ip); - tt_assert(!ip); + tt_ptr_op(ip, OP_EQ, NULL); tor_free(encoded_ip); smartlist_free(lines); } done: hs_descriptor_free(desc); - desc_intro_point_free(ip); + hs_desc_intro_point_free(ip); } /** Make sure we fail gracefully when decoding the bad desc from #23233. */ @@ -624,7 +632,7 @@ test_decode_plaintext(void *arg) { size_t big = 64000; /* Must always be bigger than HS_DESC_MAX_LEN. */ - tt_int_op(HS_DESC_MAX_LEN, <, big); + tt_int_op(HS_DESC_MAX_LEN, OP_LT, big); char *plaintext = tor_malloc_zero(big); memset(plaintext, 'a', big); plaintext[big - 1] = '\0'; @@ -684,7 +692,7 @@ test_validate_cert(void *arg) (void) arg; ret = ed25519_keypair_generate(&kp, 0); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); /* Cert of type CERT_TYPE_AUTH_HS_IP_KEY. */ cert = tor_cert_create(&kp, CERT_TYPE_AUTH_HS_IP_KEY, @@ -735,16 +743,16 @@ test_desc_signature(void *arg) tor_asprintf(&data, "This is a signed descriptor\n"); ret = ed25519_sign_prefixed(&sig, (const uint8_t *) data, strlen(data), "Tor onion service descriptor sig v3", &kp); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); ret = ed25519_signature_to_base64(sig_b64, &sig); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); /* Build the descriptor that should be valid. */ tor_asprintf(&desc, "%ssignature %s\n", data, sig_b64); ret = desc_sig_is_valid(sig_b64, &kp.pubkey, desc, strlen(desc)); - tt_int_op(ret, ==, 1); + tt_int_op(ret, OP_EQ, 1); /* Junk signature. */ ret = desc_sig_is_valid("JUNK", &kp.pubkey, desc, strlen(desc)); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); done: tor_free(desc); @@ -806,8 +814,8 @@ test_parse_hs_desc_superencrypted(void *arg) retval = decode_superencrypted(bad_superencrypted_text1, strlen(bad_superencrypted_text1), &encrypted_out); - tt_u64_op(retval, ==, 0); - tt_assert(!encrypted_out); + tt_u64_op(retval, OP_EQ, 0); + tt_ptr_op(encrypted_out, OP_EQ, NULL); expect_log_msg_containing("Unrecognized desc auth type"); teardown_capture_of_logs(); } @@ -817,8 +825,8 @@ test_parse_hs_desc_superencrypted(void *arg) retval = decode_superencrypted(bad_superencrypted_text2, strlen(bad_superencrypted_text2), &encrypted_out); - tt_u64_op(retval, ==, 0); - tt_assert(!encrypted_out); + tt_u64_op(retval, OP_EQ, 0); + tt_ptr_op(encrypted_out, OP_EQ, NULL); expect_log_msg_containing("Bogus desc auth key in HS desc"); teardown_capture_of_logs(); } @@ -828,8 +836,8 @@ test_parse_hs_desc_superencrypted(void *arg) retval = decode_superencrypted(bad_superencrypted_text3, strlen(bad_superencrypted_text3), &encrypted_out); - tt_u64_op(retval, ==, 0); - tt_assert(!encrypted_out); + tt_u64_op(retval, OP_EQ, 0); + tt_ptr_op(encrypted_out, OP_EQ, NULL); expect_log_msg_containing("Length of descriptor\'s encrypted data " "is too small."); teardown_capture_of_logs(); @@ -840,7 +848,7 @@ test_parse_hs_desc_superencrypted(void *arg) strlen(correct_superencrypted_text), &encrypted_out); - tt_u64_op(retval, ==, strlen(correct_encrypted_plaintext)); + tt_u64_op(retval, OP_EQ, strlen(correct_encrypted_plaintext)); tt_mem_op(encrypted_out, OP_EQ, correct_encrypted_plaintext, strlen(correct_encrypted_plaintext)); diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c index c6197875b5..0cae2de7e1 100644 --- a/src/test/test_hs_intropoint.c +++ b/src/test/test_hs_intropoint.c @@ -17,21 +17,66 @@ #include "log_test_helpers.h" #include "or.h" +#include "circuitlist.h" +#include "circuituse.h" #include "ht.h" +#include "relay.h" +#include "rendservice.h" + +#include "hs_cell.h" +#include "hs_circuitmap.h" +#include "hs_common.h" +#include "hs_intropoint.h" +#include "hs_service.h" /* Trunnel. */ #include "hs/cell_establish_intro.h" #include "hs/cell_introduce1.h" #include "hs/cell_common.h" -#include "hs_service.h" -#include "hs_common.h" -#include "hs_circuitmap.h" -#include "hs_intropoint.h" -#include "circuitlist.h" -#include "circuituse.h" -#include "rendservice.h" -#include "relay.h" +static size_t +new_establish_intro_cell(const char *circ_nonce, + trn_cell_establish_intro_t **cell_out) +{ + ssize_t cell_len = 0; + uint8_t buf[RELAY_PAYLOAD_SIZE] = {0}; + trn_cell_establish_intro_t *cell = NULL; + hs_service_intro_point_t *ip = NULL; + + /* Auth key pair is generated in the constructor so we are all set for + * using this IP object. */ + ip = service_intro_point_new(NULL, 0); + tt_assert(ip); + cell_len = hs_cell_build_establish_intro(circ_nonce, ip, buf); + tt_i64_op(cell_len, OP_GT, 0); + + cell_len = trn_cell_establish_intro_parse(&cell, buf, sizeof(buf)); + tt_i64_op(cell_len, OP_GT, 0); + tt_assert(cell); + *cell_out = cell; + + done: + service_intro_point_free(ip); + return cell_len; +} + +static ssize_t +new_establish_intro_encoded_cell(const char *circ_nonce, uint8_t *cell_out) +{ + ssize_t cell_len = 0; + hs_service_intro_point_t *ip = NULL; + + /* Auth key pair is generated in the constructor so we are all set for + * using this IP object. */ + ip = service_intro_point_new(NULL, 0); + tt_assert(ip); + cell_len = hs_cell_build_establish_intro(circ_nonce, ip, cell_out); + tt_i64_op(cell_len, OP_GT, 0); + + done: + service_intro_point_free(ip); + return cell_len; +} /* Mock function to avoid networking in unittests */ static int @@ -122,50 +167,43 @@ static void test_establish_intro_wrong_purpose(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; + uint8_t cell_body[RELAY_PAYLOAD_SIZE]; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); (void)arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - memcpy(intro_circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + memcpy(intro_circ->rend_circ_nonce, circ_nonce, DIGEST_LEN); /* Set a bad circuit purpose!! :) */ circuit_change_purpose(TO_CIRCUIT(intro_circ), CIRCUIT_PURPOSE_INTRO_POINT); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body); + tt_i64_op(cell_len, OP_GT, 0); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); expect_log_msg_containing("Rejecting ESTABLISH_INTRO on non-OR circuit."); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } /* Prepare a circuit for accepting an ESTABLISH_INTRO cell */ static void -helper_prepare_circ_for_intro(or_circuit_t *circ, - uint8_t *circuit_key_material) +helper_prepare_circ_for_intro(or_circuit_t *circ, const char *circ_nonce) { /* Prepare the circuit for the incoming ESTABLISH_INTRO */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR); - memcpy(circ->rend_circ_nonce, circuit_key_material, DIGEST_LEN); + memcpy(circ->rend_circ_nonce, circ_nonce, DIGEST_LEN); } /* Send an empty ESTABLISH_INTRO cell. Should fail. */ @@ -173,21 +211,21 @@ static void test_establish_intro_wrong_keytype(void *arg) { int retval; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); + char circ_nonce[DIGEST_LEN] = {0}; - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); - retval = hs_intro_received_establish_intro(intro_circ, (uint8_t*)"", 0); + retval = hs_intro_received_establish_intro(intro_circ, (uint8_t *) "", 0); expect_log_msg_containing("Empty ESTABLISH_INTRO cell."); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: circuit_free(TO_CIRCUIT(intro_circ)); @@ -198,26 +236,21 @@ static void test_establish_intro_wrong_keytype2(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + * attempt to parse it. */ + cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body); + tt_i64_op(cell_len, OP_GT, 0); /* Mutate the auth key type! :) */ cell_body[0] = 42; @@ -227,10 +260,9 @@ test_establish_intro_wrong_keytype2(void *arg) retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); expect_log_msg_containing("Unrecognized AUTH_KEY_TYPE 42."); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -239,26 +271,27 @@ static void test_establish_intro_wrong_mac(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; - uint8_t cell_body[RELAY_PAYLOAD_SIZE]; + char circ_nonce[DIGEST_LEN] = {0}; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + uint8_t cell_body[RELAY_PAYLOAD_SIZE]; + trn_cell_establish_intro_t *cell = NULL; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_i64_op(cell_len, OP_GT, 0); + tt_assert(cell); + /* Mangle one byte of the MAC. */ uint8_t *handshake_ptr = - trn_cell_establish_intro_getarray_handshake_mac(establish_intro_cell); + trn_cell_establish_intro_getarray_handshake_mac(cell); handshake_ptr[TRUNNEL_SHA3_256_LEN - 1]++; /* We need to resign the payload with that change. */ { @@ -269,26 +302,26 @@ test_establish_intro_wrong_mac(void *arg) retval = ed25519_keypair_generate(&key_struct, 0); tt_int_op(retval, OP_EQ, 0); uint8_t *auth_key_ptr = - trn_cell_establish_intro_getarray_auth_key(establish_intro_cell); + trn_cell_establish_intro_getarray_auth_key(cell); memcpy(auth_key_ptr, key_struct.pubkey.pubkey, ED25519_PUBKEY_LEN); /* Encode payload so we can sign it. */ - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_i64_op(cell_len, OP_GT, 0); retval = ed25519_sign_prefixed(&sig, cell_body, cell_len - - (ED25519_SIG_LEN + - sizeof(establish_intro_cell->sig_len)), + (ED25519_SIG_LEN + sizeof(cell->sig_len)), ESTABLISH_INTRO_SIG_PREFIX, &key_struct); tt_int_op(retval, OP_EQ, 0); /* And write the signature to the cell */ uint8_t *sig_ptr = - trn_cell_establish_intro_getarray_sig(establish_intro_cell); - memcpy(sig_ptr, sig.sig, establish_intro_cell->sig_len); + trn_cell_establish_intro_getarray_sig(cell); + memcpy(sig_ptr, sig.sig, cell->sig_len); /* Re-encode with the new signature. */ - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_i64_op(cell_len, OP_GT, 0); } /* Receive the cell. Should fail because our MAC is wrong. */ @@ -296,10 +329,10 @@ test_establish_intro_wrong_mac(void *arg) retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); expect_log_msg_containing("ESTABLISH_INTRO handshake_auth not as expected"); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -309,42 +342,42 @@ static void test_establish_intro_wrong_auth_key_len(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; size_t bad_auth_key_len = ED25519_PUBKEY_LEN - 1; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + trn_cell_establish_intro_t *cell = NULL; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_i64_op(cell_len, OP_GT, 0); + tt_assert(cell); + /* Mangle the auth key length. */ - trn_cell_establish_intro_set_auth_key_len(establish_intro_cell, - bad_auth_key_len); - trn_cell_establish_intro_setlen_auth_key(establish_intro_cell, - bad_auth_key_len); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + trn_cell_establish_intro_set_auth_key_len(cell, bad_auth_key_len); + trn_cell_establish_intro_setlen_auth_key(cell, bad_auth_key_len); + /* Encode cell. */ + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); expect_log_msg_containing("ESTABLISH_INTRO auth key length is invalid"); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -354,40 +387,42 @@ static void test_establish_intro_wrong_sig_len(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; size_t bad_sig_len = ED25519_SIG_LEN - 1; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + trn_cell_establish_intro_t *cell = NULL; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_i64_op(cell_len, OP_GT, 0); + tt_assert(cell); + /* Mangle the signature length. */ - trn_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len); - trn_cell_establish_intro_setlen_sig(establish_intro_cell, bad_sig_len); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + trn_cell_establish_intro_set_sig_len(cell, bad_sig_len); + trn_cell_establish_intro_setlen_sig(cell, bad_sig_len); + /* Encode cell. */ + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); expect_log_msg_containing("ESTABLISH_INTRO sig len is invalid"); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); + trn_cell_establish_intro_free(cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -397,39 +432,34 @@ static void test_establish_intro_wrong_sig(void *arg) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; - or_circuit_t *intro_circ = or_circuit_new(0,NULL);; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); - (void)arg; + (void) arg; /* Get the auth key of the intro point */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + cell_len = new_establish_intro_encoded_cell(circ_nonce, cell_body); + tt_i64_op(cell_len, OP_GT, 0); /* Mutate the last byte (signature)! :) */ - cell_body[cell_len-1]++; + cell_body[cell_len - 1]++; /* Receive the cell. Should fail. */ setup_full_capture_of_logs(LOG_INFO); - retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); + retval = hs_intro_received_establish_intro(intro_circ, cell_body, + (size_t)cell_len); expect_log_msg_containing("Failed to verify ESTABLISH_INTRO cell."); teardown_capture_of_logs(); - tt_int_op(retval, ==, -1); + tt_int_op(retval, OP_EQ, -1); done: - trn_cell_establish_intro_free(establish_intro_cell); circuit_free(TO_CIRCUIT(intro_circ)); } @@ -439,32 +469,33 @@ static trn_cell_establish_intro_t * helper_establish_intro_v3(or_circuit_t *intro_circ) { int retval; - trn_cell_establish_intro_t *establish_intro_cell = NULL; + char circ_nonce[DIGEST_LEN] = {0}; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + trn_cell_establish_intro_t *cell = NULL; tt_assert(intro_circ); /* Prepare the circuit for the incoming ESTABLISH_INTRO */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ - establish_intro_cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(establish_intro_cell); - cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body), - establish_intro_cell); - tt_int_op(cell_len, >, 0); + * attempt to parse it. */ + cell_len = new_establish_intro_cell(circ_nonce, &cell); + tt_i64_op(cell_len, OP_GT, 0); + tt_assert(cell); + cell_len = trn_cell_establish_intro_encode(cell_body, sizeof(cell_body), + cell); + tt_int_op(cell_len, OP_GT, 0); /* Receive the cell */ - retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len); - tt_int_op(retval, ==, 0); + retval = hs_intro_received_establish_intro(intro_circ, cell_body, + (size_t) cell_len); + tt_int_op(retval, OP_EQ, 0); done: - return establish_intro_cell; + return cell; } /* Helper function: Send a well-formed v2 ESTABLISH_INTRO cell to @@ -476,28 +507,28 @@ helper_establish_intro_v2(or_circuit_t *intro_circ) int retval; uint8_t cell_body[RELAY_PAYLOAD_SIZE]; ssize_t cell_len = 0; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; tt_assert(intro_circ); /* Prepare the circuit for the incoming ESTABLISH_INTRO */ - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); - helper_prepare_circ_for_intro(intro_circ, circuit_key_material); + crypto_rand(circ_nonce, sizeof(circ_nonce)); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); /* Send legacy establish_intro */ key1 = pk_generate(0); - /* Use old circuit_key_material why not */ - cell_len = encode_establish_intro_cell_legacy((char*)cell_body, - sizeof(cell_body), - key1, - (char *) circuit_key_material); - tt_int_op(cell_len, >, 0); + /* Use old circ_nonce why not */ + cell_len = rend_service_encode_establish_intro_cell( + (char*)cell_body, + sizeof(cell_body), key1, + circ_nonce); + tt_int_op(cell_len, OP_GT, 0); /* Receive legacy establish_intro */ retval = hs_intro_received_establish_intro(intro_circ, - cell_body, cell_len); - tt_int_op(retval, ==, 0); + cell_body, (size_t) cell_len); + tt_int_op(retval, OP_EQ, 0); done: return key1; @@ -516,7 +547,7 @@ test_circuitmap_free_all(void) tt_assert(the_hs_circuitmap); hs_circuitmap_free_all(); the_hs_circuitmap = get_hs_circuitmap(); - tt_assert(!the_hs_circuitmap); + tt_ptr_op(the_hs_circuitmap, OP_EQ, NULL); done: ; } @@ -548,10 +579,10 @@ test_intro_point_registration(void *arg) { the_hs_circuitmap = get_hs_circuitmap(); tt_assert(the_hs_circuitmap); - tt_int_op(0, ==, HT_SIZE(the_hs_circuitmap)); + tt_int_op(0, OP_EQ, HT_SIZE(the_hs_circuitmap)); /* Do a circuitmap query in any case */ returned_intro_circ =hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key); - tt_ptr_op(returned_intro_circ, ==, NULL); + tt_ptr_op(returned_intro_circ, OP_EQ, NULL); } /* Create a v3 intro point */ @@ -563,12 +594,12 @@ test_intro_point_registration(void *arg) /* Check that the intro point was registered on the HS circuitmap */ the_hs_circuitmap = get_hs_circuitmap(); tt_assert(the_hs_circuitmap); - tt_int_op(1, ==, HT_SIZE(the_hs_circuitmap)); + tt_int_op(1, OP_EQ, HT_SIZE(the_hs_circuitmap)); get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, establish_intro_cell); returned_intro_circ = hs_circuitmap_get_intro_circ_v3_relay_side(&auth_key); - tt_ptr_op(intro_circ, ==, returned_intro_circ); + tt_ptr_op(intro_circ, OP_EQ, returned_intro_circ); } /* Create a v2 intro point */ @@ -583,14 +614,14 @@ test_intro_point_registration(void *arg) /* Check that the circuitmap now has two elements */ the_hs_circuitmap = get_hs_circuitmap(); tt_assert(the_hs_circuitmap); - tt_int_op(2, ==, HT_SIZE(the_hs_circuitmap)); + tt_int_op(2, OP_EQ, HT_SIZE(the_hs_circuitmap)); /* Check that the new element is our legacy intro circuit. */ retval = crypto_pk_get_digest(legacy_auth_key, key_digest); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); returned_intro_circ = hs_circuitmap_get_intro_circ_v2_relay_side((uint8_t*)key_digest); - tt_ptr_op(legacy_intro_circ, ==, returned_intro_circ); + tt_ptr_op(legacy_intro_circ, OP_EQ, returned_intro_circ); } /* XXX Continue test and try to register a second v3 intro point with the @@ -765,6 +796,7 @@ test_received_introduce1_handling(void *arg) /* Too small request length. An INTRODUCE1 expect at the very least a * DIGEST_LEN size. */ { + memset(buf, 0, sizeof(buf)); circ = helper_create_intro_circuit(); ret = hs_intro_received_introduce1(circ, buf, DIGEST_LEN - 1); tt_int_op(ret, OP_EQ, -1); @@ -778,6 +810,7 @@ test_received_introduce1_handling(void *arg) { circ = helper_create_intro_circuit(); uint8_t test[2]; /* Too small request. */ + memset(test, 0, sizeof(test)); ret = handle_introduce1(circ, test, sizeof(test)); tor_free(circ->p_chan); circuit_free(TO_CIRCUIT(circ)); diff --git a/src/test/test_hs_ntor.c b/src/test/test_hs_ntor.c new file mode 100644 index 0000000000..8eee54d4b4 --- /dev/null +++ b/src/test/test_hs_ntor.c @@ -0,0 +1,114 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_hs_ntor.c + * \brief Test hidden service ntor functionality. + */ + +#include "test.h" +#include "test_helpers.h" +#include "log_test_helpers.h" + +#include "hs_ntor.h" + +/* Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1 + * cell, and verify the proper derivation of decryption keys on the other end. + * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify + * the proper verification on the other end. */ +static void +test_hs_ntor(void *arg) +{ + int retval; + + uint8_t subcredential[DIGEST256_LEN]; + + ed25519_keypair_t service_intro_auth_keypair; + curve25519_keypair_t service_intro_enc_keypair; + curve25519_keypair_t service_ephemeral_rend_keypair; + + curve25519_keypair_t client_ephemeral_enc_keypair; + + hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys; + hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys; + + hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys; + hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys; + + (void) arg; + + /* Generate fake data for this unittest */ + { + /* Generate fake subcredential */ + memset(subcredential, 'Z', DIGEST256_LEN); + + /* service */ + curve25519_keypair_generate(&service_intro_enc_keypair, 0); + ed25519_keypair_generate(&service_intro_auth_keypair, 0); + curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0); + /* client */ + curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0); + } + + /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */ + retval = + hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair.pubkey, + &client_ephemeral_enc_keypair, + subcredential, + &client_hs_ntor_intro_cell_keys); + tt_int_op(retval, OP_EQ, 0); + + /* Service: Simulate the decryption of the received INTRODUCE1 */ + retval = + hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair, + &client_ephemeral_enc_keypair.pubkey, + subcredential, + &service_hs_ntor_intro_cell_keys); + tt_int_op(retval, OP_EQ, 0); + + /* Test that the INTRODUCE1 encryption/mac keys match! */ + tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ, + service_hs_ntor_intro_cell_keys.enc_key, + CIPHER256_KEY_LEN); + tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ, + service_hs_ntor_intro_cell_keys.mac_key, + DIGEST256_LEN); + + /* Service: Simulate creation of RENDEZVOUS1 key material. */ + retval = + hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, + &service_intro_enc_keypair, + &service_ephemeral_rend_keypair, + &client_ephemeral_enc_keypair.pubkey, + &service_hs_ntor_rend_cell_keys); + tt_int_op(retval, OP_EQ, 0); + + /* Client: Simulate the verification of a received RENDEZVOUS1 cell */ + retval = + hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, + &client_ephemeral_enc_keypair, + &service_intro_enc_keypair.pubkey, + &service_ephemeral_rend_keypair.pubkey, + &client_hs_ntor_rend_cell_keys); + tt_int_op(retval, OP_EQ, 0); + + /* Test that the RENDEZVOUS1 key material match! */ + tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ, + service_hs_ntor_rend_cell_keys.rend_cell_auth_mac, + DIGEST256_LEN); + tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ, + service_hs_ntor_rend_cell_keys.ntor_key_seed, + DIGEST256_LEN); + done: + ; +} + +struct testcase_t hs_ntor_tests[] = { + { "hs_ntor", test_hs_ntor, TT_FORK, + NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index fcfb3b992d..8407eccfa8 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -6,243 +6,1615 @@ * \brief Test hidden service functionality. */ +#define CIRCUITBUILD_PRIVATE +#define CIRCUITLIST_PRIVATE +#define CONFIG_PRIVATE +#define CONNECTION_PRIVATE +#define CRYPTO_PRIVATE #define HS_COMMON_PRIVATE #define HS_SERVICE_PRIVATE #define HS_INTROPOINT_PRIVATE +#define HS_CIRCUIT_PRIVATE +#define MAIN_PRIVATE +#define NETWORKSTATUS_PRIVATE +#define STATEFILE_PRIVATE +#define TOR_CHANNEL_INTERNAL_ +#define HS_CLIENT_PRIVATE #include "test.h" +#include "test_helpers.h" #include "log_test_helpers.h" +#include "rend_test_helpers.h" +#include "hs_test_helpers.h" + +#include "or.h" +#include "config.h" +#include "circuitbuild.h" +#include "circuitlist.h" +#include "circuituse.h" #include "crypto.h" +#include "dirvote.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "relay.h" -#include "hs/cell_establish_intro.h" #include "hs_common.h" -#include "hs_service.h" +#include "hs_config.h" +#include "hs_ident.h" #include "hs_intropoint.h" - #include "hs_ntor.h" +#include "hs_circuit.h" +#include "hs_service.h" +#include "hs_client.h" +#include "main.h" +#include "rendservice.h" +#include "statefile.h" +#include "shared_random_state.h" + +/* Trunnel */ +#include "hs/cell_establish_intro.h" + +static networkstatus_t mock_ns; + +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + return &mock_ns; +} + +static or_state_t *dummy_state = NULL; + +/* Mock function to get fake or state (used for rev counters) */ +static or_state_t * +get_or_state_replacement(void) +{ + return dummy_state; +} + +/* Mock function because we are not trying to test the close circuit that does + * an awful lot of checks on the circuit object. */ +static void +mock_circuit_mark_for_close(circuit_t *circ, int reason, int line, + const char *file) +{ + (void) circ; + (void) reason; + (void) line; + (void) file; + return; +} + +static int +mock_relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ, + uint8_t relay_command, const char *payload, + size_t payload_len, + crypt_path_t *cpath_layer, + const char *filename, int lineno) +{ + (void) stream_id; + (void) circ; + (void) relay_command; + (void) payload; + (void) payload_len; + (void) cpath_layer; + (void) filename; + (void) lineno; + return 0; +} -/** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we - * parse it from the receiver side. */ +/* Helper: from a set of options in conf, configure a service which will add + * it to the staging list of the HS subsytem. */ +static int +helper_config_service(const char *conf) +{ + int ret = 0; + or_options_t *options = NULL; + tt_assert(conf); + options = helper_parse_options(conf); + tt_assert(options); + ret = hs_config_service_all(options, 0); + done: + or_options_free(options); + return ret; +} + +/* Test: Ensure that setting up rendezvous circuits works correctly. */ static void -test_gen_establish_intro_cell(void *arg) +test_e2e_rend_circuit_setup(void *arg) { + ed25519_public_key_t service_pk; + origin_circuit_t *or_circ; + int retval; + + /** In this test we create a v3 prop224 service-side rendezvous circuit. + * We simulate an HS ntor key exchange with a client, and check that + * the circuit was setup correctly and is ready to accept rendezvous data */ + (void) arg; - ssize_t retval; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; - uint8_t buf[RELAY_PAYLOAD_SIZE]; - trn_cell_establish_intro_t *cell_out = NULL; - trn_cell_establish_intro_t *cell_in = NULL; - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); + /* Now make dummy circuit */ + { + or_circ = origin_circuit_new(); - /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we - attempt to parse it. */ + or_circ->base_.purpose = CIRCUIT_PURPOSE_S_CONNECT_REND; + + or_circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t)); + or_circ->build_state->is_internal = 1; + + /* prop224: Setup hs conn identifier on the stream */ + ed25519_secret_key_t sk; + tt_int_op(0, OP_EQ, ed25519_secret_key_generate(&sk, 0)); + tt_int_op(0, OP_EQ, ed25519_public_key_generate(&service_pk, &sk)); + + or_circ->hs_ident = hs_ident_circuit_new(&service_pk, + HS_IDENT_CIRCUIT_RENDEZVOUS); + + TO_CIRCUIT(or_circ)->state = CIRCUIT_STATE_OPEN; + } + + /* Check number of hops */ + retval = cpath_get_n_hops(&or_circ->cpath); + tt_int_op(retval, OP_EQ, 0); + + /* Setup the circuit: do the ntor key exchange */ { - cell_out = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - tt_assert(cell_out); + uint8_t ntor_key_seed[DIGEST256_LEN] = {2}; + retval = hs_circuit_setup_e2e_rend_circ(or_circ, + ntor_key_seed, sizeof(ntor_key_seed), + 1); + tt_int_op(retval, OP_EQ, 0); + } + + /* See that a hop was added to the circuit's cpath */ + retval = cpath_get_n_hops(&or_circ->cpath); + tt_int_op(retval, OP_EQ, 1); + + /* Check the digest algo */ + tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->f_digest), + OP_EQ, DIGEST_SHA3_256); + tt_int_op(crypto_digest_get_algorithm(or_circ->cpath->b_digest), + OP_EQ, DIGEST_SHA3_256); + tt_assert(or_circ->cpath->f_crypto); + tt_assert(or_circ->cpath->b_crypto); + + /* Ensure that circ purpose was changed */ + tt_int_op(or_circ->base_.purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED); + + done: + circuit_free(TO_CIRCUIT(or_circ)); +} + +/* Helper: Return a newly allocated and initialized origin circuit with + * purpose and flags. A default HS identifier is set to an ed25519 + * authentication key for introduction point. */ +static origin_circuit_t * +helper_create_origin_circuit(int purpose, int flags) +{ + origin_circuit_t *circ = NULL; + + circ = origin_circuit_init(purpose, flags); + tt_assert(circ); + circ->cpath = tor_malloc_zero(sizeof(crypt_path_t)); + circ->cpath->magic = CRYPT_PATH_MAGIC; + circ->cpath->state = CPATH_STATE_OPEN; + circ->cpath->package_window = circuit_initial_package_window(); + circ->cpath->deliver_window = CIRCWINDOW_START; + circ->cpath->prev = circ->cpath; + /* Random nonce. */ + crypto_rand(circ->cpath->prev->rend_circ_nonce, DIGEST_LEN); + /* Create a default HS identifier. */ + circ->hs_ident = tor_malloc_zero(sizeof(hs_ident_circuit_t)); + + done: + return circ; +} + +/* Helper: Return a newly allocated service object with the identity keypair + * sets and the current descriptor. Then register it to the global map. + * Caller should us hs_free_all() to free this service or remove it from the + * global map before freeing. */ +static hs_service_t * +helper_create_service(void) +{ + /* Set a service for this circuit. */ + hs_service_t *service = hs_service_new(get_options()); + tt_assert(service); + service->config.version = HS_VERSION_THREE; + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + service->desc_current = service_descriptor_new(); + tt_assert(service->desc_current); + /* Register service to global map. */ + int ret = register_service(get_hs_service_map(), service); + tt_int_op(ret, OP_EQ, 0); + + done: + return service; +} + +/* Helper: Return a newly allocated service intro point with two link + * specifiers, one IPv4 and one legacy ID set to As. */ +static hs_service_intro_point_t * +helper_create_service_ip(void) +{ + hs_desc_link_specifier_t *ls; + hs_service_intro_point_t *ip = service_intro_point_new(NULL, 0); + tt_assert(ip); + /* Add a first unused link specifier. */ + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = LS_IPV4; + smartlist_add(ip->base.link_specifiers, ls); + /* Add a second link specifier used by a test. */ + ls = tor_malloc_zero(sizeof(*ls)); + ls->type = LS_LEGACY_ID; + memset(ls->u.legacy_id, 'A', sizeof(ls->u.legacy_id)); + smartlist_add(ip->base.link_specifiers, ls); + + done: + return ip; +} + +static void +test_load_keys(void *arg) +{ + int ret; + char *conf = NULL; + char *hsdir_v2 = tor_strdup(get_fname("hs2")); + char *hsdir_v3 = tor_strdup(get_fname("hs3")); + char addr[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + (void) arg; + + /* We'll register two services, a v2 and a v3, then we'll load keys and + * validate that both are in a correct state. */ + + hs_init(); + +#define conf_fmt \ + "HiddenServiceDir %s\n" \ + "HiddenServiceVersion %d\n" \ + "HiddenServicePort 65535\n" + + /* v2 service. */ + tor_asprintf(&conf, conf_fmt, hsdir_v2, HS_VERSION_TWO); + ret = helper_config_service(conf); + tor_free(conf); + tt_int_op(ret, OP_EQ, 0); + /* This one should now be registered into the v2 list. */ + tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 0); + tt_int_op(rend_num_services(), OP_EQ, 1); + + /* v3 service. */ + tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE); + ret = helper_config_service(conf); + tor_free(conf); + tt_int_op(ret, OP_EQ, 0); + /* It's in staging? */ + tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); + + /* Load the keys for these. After that, the v3 service should be registered + * in the global map. */ + hs_service_load_all_keys(); + tt_int_op(get_hs_service_map_size(), OP_EQ, 1); + hs_service_t *s = get_first_service(); + tt_assert(s); + + /* Ok we have the service object. Validate few things. */ + tt_assert(!tor_mem_is_zero(s->onion_address, sizeof(s->onion_address))); + tt_int_op(hs_address_is_valid(s->onion_address), OP_EQ, 1); + tt_assert(!tor_mem_is_zero((char *) s->keys.identity_sk.seckey, + ED25519_SECKEY_LEN)); + tt_assert(!tor_mem_is_zero((char *) s->keys.identity_pk.pubkey, + ED25519_PUBKEY_LEN)); + /* Check onion address from identity key. */ + hs_build_address(&s->keys.identity_pk, s->config.version, addr); + tt_int_op(hs_address_is_valid(addr), OP_EQ, 1); + tt_str_op(addr, OP_EQ, s->onion_address); + + done: + tor_free(hsdir_v2); + tor_free(hsdir_v3); + hs_free_all(); +} + +static void +test_access_service(void *arg) +{ + int ret; + char *conf = NULL; + char *hsdir_v3 = tor_strdup(get_fname("hs3")); + hs_service_ht *global_map; + hs_service_t *s = NULL; + + (void) arg; + + /* We'll register two services, a v2 and a v3, then we'll load keys and + * validate that both are in a correct state. */ - retval = get_establish_intro_payload(buf, sizeof(buf), cell_out); - tt_int_op(retval, >=, 0); + hs_init(); + +#define conf_fmt \ + "HiddenServiceDir %s\n" \ + "HiddenServiceVersion %d\n" \ + "HiddenServicePort 65535\n" + + /* v3 service. */ + tor_asprintf(&conf, conf_fmt, hsdir_v3, HS_VERSION_THREE); + ret = helper_config_service(conf); + tor_free(conf); + tt_int_op(ret, OP_EQ, 0); + /* It's in staging? */ + tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1); + + /* Load the keys for these. After that, the v3 service should be registered + * in the global map. */ + hs_service_load_all_keys(); + tt_int_op(get_hs_service_map_size(), OP_EQ, 1); + s = get_first_service(); + tt_assert(s); + global_map = get_hs_service_map(); + tt_assert(global_map); + + /* From here, we'll try the service accessors. */ + hs_service_t *query = find_service(global_map, &s->keys.identity_pk); + tt_assert(query); + tt_mem_op(query, OP_EQ, s, sizeof(hs_service_t)); + /* Remove service, check if it actually works and then put it back. */ + remove_service(global_map, s); + tt_int_op(get_hs_service_map_size(), OP_EQ, 0); + query = find_service(global_map, &s->keys.identity_pk); + tt_ptr_op(query, OP_EQ, NULL); + + /* Register back the service in the map. */ + ret = register_service(global_map, s); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(get_hs_service_map_size(), OP_EQ, 1); + /* Twice should fail. */ + ret = register_service(global_map, s); + tt_int_op(ret, OP_EQ, -1); + /* Remove service from map so we don't double free on cleanup. */ + remove_service(global_map, s); + tt_int_op(get_hs_service_map_size(), OP_EQ, 0); + query = find_service(global_map, &s->keys.identity_pk); + tt_ptr_op(query, OP_EQ, NULL); + /* Let's try to remove twice for fun. */ + setup_full_capture_of_logs(LOG_WARN); + remove_service(global_map, s); + expect_log_msg_containing("Could not find service in the global map"); + teardown_capture_of_logs(); + + done: + hs_service_free(s); + tor_free(hsdir_v3); + hs_free_all(); +} + +/** Test that we can create intro point objects, index them and find them */ +static void +test_service_intro_point(void *arg) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + + (void) arg; + + /* Test simple creation of an object. */ + { + time_t now = time(NULL); + ip = helper_create_service_ip(); + tt_assert(ip); + /* Make sure the authentication keypair is not zeroes. */ + tt_int_op(tor_mem_is_zero((const char *) &ip->auth_key_kp, + sizeof(ed25519_keypair_t)), OP_EQ, 0); + /* The introduce2_max MUST be in that range. */ + tt_u64_op(ip->introduce2_max, OP_GE, + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS); + tt_u64_op(ip->introduce2_max, OP_LE, + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS); + /* Time to expire MUST also be in that range. We add 5 seconds because + * there could be a gap between setting now and the time taken in + * service_intro_point_new. On ARM, it can be surprisingly slow... */ + tt_u64_op(ip->time_to_expire, OP_GE, + now + INTRO_POINT_LIFETIME_MIN_SECONDS + 5); + tt_u64_op(ip->time_to_expire, OP_LE, + now + INTRO_POINT_LIFETIME_MAX_SECONDS + 5); + tt_assert(ip->replay_cache); + tt_assert(ip->base.link_specifiers); + /* By default, this is NOT a legacy object. */ + tt_int_op(ip->base.is_only_legacy, OP_EQ, 0); } - /* Parse it as the receiver */ + /* Test functions that uses a service intropoints map with that previously + * created object (non legacy). */ { - ssize_t parse_result = trn_cell_establish_intro_parse(&cell_in, - buf, sizeof(buf)); - tt_int_op(parse_result, >=, 0); + ed25519_public_key_t garbage = { {0} }; + hs_service_intro_point_t *query; + + service = hs_service_new(get_options()); + tt_assert(service); + service->desc_current = service_descriptor_new(); + tt_assert(service->desc_current); + /* Add intropoint to descriptor map. */ + service_intro_point_add(service->desc_current->intro_points.map, ip); + query = service_intro_point_find(service, &ip->auth_key_kp.pubkey); + tt_mem_op(query, OP_EQ, ip, sizeof(hs_service_intro_point_t)); + query = service_intro_point_find(service, &garbage); + tt_ptr_op(query, OP_EQ, NULL); + + /* While at it, can I find the descriptor with the intro point? */ + hs_service_descriptor_t *desc_lookup = + service_desc_find_by_intro(service, ip); + tt_mem_op(service->desc_current, OP_EQ, desc_lookup, + sizeof(hs_service_descriptor_t)); - retval = verify_establish_intro_cell(cell_in, - circuit_key_material, - sizeof(circuit_key_material)); - tt_int_op(retval, >=, 0); + /* Remove object from service descriptor and make sure it is out. */ + service_intro_point_remove(service, ip); + query = service_intro_point_find(service, &ip->auth_key_kp.pubkey); + tt_ptr_op(query, OP_EQ, NULL); } done: - trn_cell_establish_intro_free(cell_out); - trn_cell_establish_intro_free(cell_in); + /* If the test succeed, this object is no longer referenced in the service + * so we can free it without use after free. Else, it might explode because + * it's still in the service descriptor map. */ + service_intro_point_free(ip); + hs_service_free(service); } -/* Mocked ed25519_sign_prefixed() function that always fails :) */ -static int -mock_ed25519_sign_prefixed(ed25519_signature_t *signature_out, - const uint8_t *msg, size_t msg_len, - const char *prefix_str, - const ed25519_keypair_t *keypair) { - (void) signature_out; - (void) msg; - (void) msg_len; - (void) prefix_str; - (void) keypair; - return -1; +static node_t mock_node; +static const node_t * +mock_node_get_by_id(const char *digest) +{ + (void) digest; + memset(mock_node.identity, 'A', DIGEST_LEN); + /* Only return the matchin identity of As */ + if (!tor_memcmp(mock_node.identity, digest, DIGEST_LEN)) { + return &mock_node; + } + return NULL; } -/** We simulate a failure to create an ESTABLISH_INTRO cell */ static void -test_gen_establish_intro_cell_bad(void *arg) +test_helper_functions(void *arg) { + int ret; + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL; + hs_ident_circuit_t ident; + (void) arg; - trn_cell_establish_intro_t *cell = NULL; - uint8_t circuit_key_material[DIGEST_LEN] = {0}; - MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); + MOCK(node_get_by_id, mock_node_get_by_id); + + hs_service_init(); + + service = helper_create_service(); + + ip = helper_create_service_ip(); + /* Immediately add the intro point to the service so the free service at the + * end cleans it as well. */ + service_intro_point_add(service->desc_current->intro_points.map, ip); + + /* Setup the circuit identifier. */ + ed25519_pubkey_copy(&ident.intro_auth_pk, &ip->auth_key_kp.pubkey); + ed25519_pubkey_copy(&ident.identity_pk, &service->keys.identity_pk); - crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material)); + /* Testing get_objects_from_ident(). */ + { + hs_service_t *s_lookup = NULL; + hs_service_intro_point_t *ip_lookup = NULL; + hs_service_descriptor_t *desc_lookup = NULL; + + get_objects_from_ident(&ident, &s_lookup, &ip_lookup, &desc_lookup); + tt_mem_op(s_lookup, OP_EQ, service, sizeof(hs_service_t)); + tt_mem_op(ip_lookup, OP_EQ, ip, sizeof(hs_service_intro_point_t)); + tt_mem_op(desc_lookup, OP_EQ, service->desc_current, + sizeof(hs_service_descriptor_t)); + /* Reset */ + s_lookup = NULL; ip_lookup = NULL; desc_lookup = NULL; + + /* NULL parameter should work. */ + get_objects_from_ident(&ident, NULL, &ip_lookup, &desc_lookup); + tt_mem_op(ip_lookup, OP_EQ, ip, sizeof(hs_service_intro_point_t)); + tt_mem_op(desc_lookup, OP_EQ, service->desc_current, + sizeof(hs_service_descriptor_t)); + /* Reset. */ + s_lookup = NULL; ip_lookup = NULL; desc_lookup = NULL; + + /* Break the ident and we should find nothing. */ + memset(&ident, 0, sizeof(ident)); + get_objects_from_ident(&ident, &s_lookup, &ip_lookup, &desc_lookup); + tt_ptr_op(s_lookup, OP_EQ, NULL); + tt_ptr_op(ip_lookup, OP_EQ, NULL); + tt_ptr_op(desc_lookup, OP_EQ, NULL); + } + + /* Testing get_node_from_intro_point() */ + { + const node_t *node = get_node_from_intro_point(ip); + tt_ptr_op(node, OP_EQ, &mock_node); + SMARTLIST_FOREACH_BEGIN(ip->base.link_specifiers, + hs_desc_link_specifier_t *, ls) { + if (ls->type == LS_LEGACY_ID) { + /* Change legacy id in link specifier which is not the mock node. */ + memset(ls->u.legacy_id, 'B', sizeof(ls->u.legacy_id)); + } + } SMARTLIST_FOREACH_END(ls); + node = get_node_from_intro_point(ip); + tt_ptr_op(node, OP_EQ, NULL); + } + + /* Testing can_service_launch_intro_circuit() */ + { + time_t now = time(NULL); + /* Put the start of the retry period back in time, we should be allowed. + * to launch intro circuit. */ + service->state.num_intro_circ_launched = 2; + service->state.intro_circ_retry_started_time = + (now - INTRO_CIRC_RETRY_PERIOD - 1); + ret = can_service_launch_intro_circuit(service, now); + tt_int_op(ret, OP_EQ, 1); + tt_u64_op(service->state.intro_circ_retry_started_time, OP_EQ, now); + tt_u64_op(service->state.num_intro_circ_launched, OP_EQ, 0); + /* Call it again, we should still be allowed because we are under + * MAX_INTRO_CIRCS_PER_PERIOD which been set to 0 previously. */ + ret = can_service_launch_intro_circuit(service, now); + tt_int_op(ret, OP_EQ, 1); + tt_u64_op(service->state.intro_circ_retry_started_time, OP_EQ, now); + tt_u64_op(service->state.num_intro_circ_launched, OP_EQ, 0); + /* Too many intro circuit launched means we are not allowed. */ + service->state.num_intro_circ_launched = 20; + ret = can_service_launch_intro_circuit(service, now); + tt_int_op(ret, OP_EQ, 0); + } + + /* Testing intro_point_should_expire(). */ + { + time_t now = time(NULL); + /* Just some basic test of the current state. */ + tt_u64_op(ip->introduce2_max, OP_GE, + INTRO_POINT_MIN_LIFETIME_INTRODUCTIONS); + tt_u64_op(ip->introduce2_max, OP_LE, + INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS); + tt_u64_op(ip->time_to_expire, OP_GE, + now + INTRO_POINT_LIFETIME_MIN_SECONDS); + tt_u64_op(ip->time_to_expire, OP_LE, + now + INTRO_POINT_LIFETIME_MAX_SECONDS); + + /* This newly created IP from above shouldn't expire now. */ + ret = intro_point_should_expire(ip, now); + tt_int_op(ret, OP_EQ, 0); + /* Maximum number of INTRODUCE2 cell reached, it should expire. */ + ip->introduce2_count = INTRO_POINT_MAX_LIFETIME_INTRODUCTIONS + 1; + ret = intro_point_should_expire(ip, now); + tt_int_op(ret, OP_EQ, 1); + ip->introduce2_count = 0; + /* It should expire if time to expire has been reached. */ + ip->time_to_expire = now - 1000; + ret = intro_point_should_expire(ip, now); + tt_int_op(ret, OP_EQ, 1); + } + done: + /* This will free the service and all objects associated to it. */ + hs_service_free_all(); + UNMOCK(node_get_by_id); +} + +/** Test that we do the right operations when an intro circuit opens */ +static void +test_intro_circuit_opened(void *arg) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + hs_service_t *service; + origin_circuit_t *circ = NULL; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + flags); + + /* No service associated with this circuit. */ setup_full_capture_of_logs(LOG_WARN); - /* Easiest way to make that function fail is to mock the - ed25519_sign_prefixed() function and make it fail. */ - cell = generate_establish_intro_cell(circuit_key_material, - sizeof(circuit_key_material)); - expect_log_msg_containing("Unable to gen signature for " - "ESTABLISH_INTRO cell."); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + + /* Set a service for this circuit. */ + { + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + + /* No intro point associated with this circuit. */ + setup_full_capture_of_logs(LOG_WARN); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Unknown introduction point auth key"); + teardown_capture_of_logs(); + } + + /* Set an IP object now for this circuit. */ + { + hs_service_intro_point_t *ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* Update ident to contain the intro point auth key. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + } + + /* This one should go all the way. */ + setup_full_capture_of_logs(LOG_INFO); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Introduction circuit 0 established for service"); teardown_capture_of_logs(); - tt_assert(!cell); done: - trn_cell_establish_intro_free(cell); - UNMOCK(ed25519_sign_prefixed); + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); + UNMOCK(relay_send_command_from_edge_); } -/** Test the HS ntor handshake. Simulate the sending of an encrypted INTRODUCE1 - * cell, and verify the proper derivation of decryption keys on the other end. - * Then simulate the sending of an authenticated RENDEZVOUS1 cell and verify - * the proper verification on the other end. */ +/** Test the operations we do on a circuit after we learn that we successfuly + * established an intro point on it */ static void -test_hs_ntor(void *arg) +test_intro_established(void *arg) { - int retval; + int ret; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + origin_circuit_t *circ = NULL; + hs_service_t *service; + hs_service_intro_point_t *ip = NULL; - uint8_t subcredential[DIGEST256_LEN]; + (void) arg; - ed25519_keypair_t service_intro_auth_keypair; - curve25519_keypair_t service_intro_enc_keypair; - curve25519_keypair_t service_ephemeral_rend_keypair; + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); - curve25519_keypair_t client_ephemeral_enc_keypair; + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, + flags); + tt_assert(circ); - hs_ntor_intro_cell_keys_t client_hs_ntor_intro_cell_keys; - hs_ntor_intro_cell_keys_t service_hs_ntor_intro_cell_keys; + /* Test a wrong purpose. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_INTRO; + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Received an INTRO_ESTABLISHED cell on a " + "non introduction circuit of purpose"); + teardown_capture_of_logs(); + + /* Back to normal. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO; + + /* No service associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + /* No introduction point associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Introduction circuit established without an " + "intro point object on circuit"); + teardown_capture_of_logs(); + + /* Set an IP object now for this circuit. */ + { + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* Update ident to contain the intro point auth key. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + } + + /* Send an empty payload. INTRO_ESTABLISHED cells are basically zeroes. */ + ret = hs_service_receive_intro_established(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, 0); + tt_u64_op(ip->circuit_established, OP_EQ, 1); + tt_int_op(TO_CIRCUIT(circ)->purpose, OP_EQ, CIRCUIT_PURPOSE_S_INTRO); + + done: + if (circ) + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); +} - hs_ntor_rend_cell_keys_t service_hs_ntor_rend_cell_keys; - hs_ntor_rend_cell_keys_t client_hs_ntor_rend_cell_keys; +/** Check the operations we do on a rendezvous circuit after we learn it's + * open */ +static void +test_rdv_circuit_opened(void *arg) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + origin_circuit_t *circ = NULL; + hs_service_t *service; (void) arg; - /* Generate fake data for this unittest */ + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_CONNECT_REND, flags); + crypto_rand((char *) circ->hs_ident->rendezvous_cookie, REND_COOKIE_LEN); + crypto_rand((char *) circ->hs_ident->rendezvous_handshake_info, + sizeof(circ->hs_ident->rendezvous_handshake_info)); + + /* No service associated with this circuit. */ + setup_full_capture_of_logs(LOG_WARN); + hs_service_circuit_has_opened(circ); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + /* This should be set to a non zero timestamp. */ + tt_u64_op(TO_CIRCUIT(circ)->timestamp_dirty, OP_NE, 0); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + /* Should be all good. */ + hs_service_circuit_has_opened(circ); + tt_int_op(TO_CIRCUIT(circ)->purpose, OP_EQ, CIRCUIT_PURPOSE_S_REND_JOINED); + + done: + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); + UNMOCK(relay_send_command_from_edge_); +} + +static void +mock_assert_circuit_ok(const circuit_t *c) +{ + (void) c; + return; +} + +/** Test for the general mechanism for closing intro circs. + * Also a way to identify that #23603 has been fixed. */ +static void +test_closing_intro_circs(void *arg) +{ + hs_service_t *service = NULL; + hs_service_intro_point_t *ip = NULL, *entry = NULL; + origin_circuit_t *intro_circ = NULL, *tmp_circ; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + + (void) arg; + + MOCK(assert_circuit_ok, mock_assert_circuit_ok); + + hs_init(); + + /* Initialize service */ + service = helper_create_service(); + /* Initialize intro point */ + ip = helper_create_service_ip(); + tt_assert(ip); + service_intro_point_add(service->desc_current->intro_points.map, ip); + + /* Initialize intro circuit */ + intro_circ = origin_circuit_init(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, flags); + intro_circ->hs_ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + /* Register circuit in the circuitmap . */ + hs_circuitmap_register_intro_circ_v3_service_side(intro_circ, + &ip->auth_key_kp.pubkey); + tmp_circ = + hs_circuitmap_get_intro_circ_v3_service_side(&ip->auth_key_kp.pubkey); + tt_ptr_op(tmp_circ, OP_EQ, intro_circ); + + /* Pretend that intro point has failed too much */ + ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES+1; + + /* Now pretend we are freeing this intro circuit. We want to see that our + * destructor is not gonna kill our intro point structure since that's the + * job of the cleanup routine. */ + circuit_free(TO_CIRCUIT(intro_circ)); + intro_circ = NULL; + entry = service_intro_point_find(service, &ip->auth_key_kp.pubkey); + tt_assert(entry); + /* The free should also remove the circuit from the circuitmap. */ + tmp_circ = + hs_circuitmap_get_intro_circ_v3_service_side(&ip->auth_key_kp.pubkey); + tt_assert(!tmp_circ); + + /* Now pretend that a new intro point circ was launched and opened. Check + * that the intro point will be established correctly. */ + intro_circ = origin_circuit_init(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, flags); + intro_circ->hs_ident = hs_ident_circuit_new(&service->keys.identity_pk, + HS_IDENT_CIRCUIT_INTRO); + ed25519_pubkey_copy(&intro_circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + /* Register circuit in the circuitmap . */ + hs_circuitmap_register_intro_circ_v3_service_side(intro_circ, + &ip->auth_key_kp.pubkey); + tmp_circ = + hs_circuitmap_get_intro_circ_v3_service_side(&ip->auth_key_kp.pubkey); + tt_ptr_op(tmp_circ, OP_EQ, intro_circ); + tt_int_op(TO_CIRCUIT(intro_circ)->marked_for_close, OP_EQ, 0); + circuit_mark_for_close(TO_CIRCUIT(intro_circ), END_CIRC_REASON_INTERNAL); + tt_int_op(TO_CIRCUIT(intro_circ)->marked_for_close, OP_NE, 0); + /* At this point, we should not be able to find it in the circuitmap. */ + tmp_circ = + hs_circuitmap_get_intro_circ_v3_service_side(&ip->auth_key_kp.pubkey); + tt_assert(!tmp_circ); + + done: + if (intro_circ) { + circuit_free(TO_CIRCUIT(intro_circ)); + } + /* Frees the service object. */ + hs_free_all(); + UNMOCK(assert_circuit_ok); +} + +/** Test sending and receiving introduce2 cells */ +static void +test_introduce2(void *arg) +{ + int ret; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + uint8_t payload[RELAY_PAYLOAD_SIZE] = {0}; + origin_circuit_t *circ = NULL; + hs_service_t *service; + hs_service_intro_point_t *ip = NULL; + + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(get_or_state, + get_or_state_replacement); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags); + tt_assert(circ); + + /* Test a wrong purpose. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_ESTABLISH_INTRO; + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Received an INTRODUCE2 cell on a " + "non introduction circuit of purpose"); + teardown_capture_of_logs(); + + /* Back to normal. */ + TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_S_INTRO; + + /* No service associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Unknown service identity key"); + teardown_capture_of_logs(); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + /* No introduction point associated to it. */ + setup_full_capture_of_logs(LOG_WARN); + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Unknown introduction auth key when handling " + "an INTRODUCE2 cell on circuit"); + teardown_capture_of_logs(); + + /* Set an IP object now for this circuit. */ { - /* Generate fake subcredential */ - memset(subcredential, 'Z', DIGEST256_LEN); + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* Update ident to contain the intro point auth key. */ + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + } + + /* This will fail because receiving an INTRODUCE2 cell implies a valid cell + * and then launching circuits so let's not do that and instead test that + * behaviour differently. */ + ret = hs_service_receive_introduce2(circ, payload, sizeof(payload)); + tt_int_op(ret, OP_EQ, -1); + tt_u64_op(ip->introduce2_count, OP_EQ, 0); + + done: + or_state_free(dummy_state); + dummy_state = NULL; + if (circ) + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); +} + +/** Test basic hidden service housekeeping operations (maintaining intro + * points, etc) */ +static void +test_service_event(void *arg) +{ + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + time_t now = time(NULL); + hs_service_t *service; + origin_circuit_t *circ = NULL; - /* service */ - curve25519_keypair_generate(&service_intro_enc_keypair, 0); - ed25519_keypair_generate(&service_intro_auth_keypair, 0); - curve25519_keypair_generate(&service_ephemeral_rend_keypair, 0); - /* client */ - curve25519_keypair_generate(&client_ephemeral_enc_keypair, 0); + (void) arg; + + hs_init(); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + + circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_INTRO, flags); + + /* Set a service for this circuit. */ + service = helper_create_service(); + ed25519_pubkey_copy(&circ->hs_ident->identity_pk, + &service->keys.identity_pk); + + /* Currently this consists of cleaning invalid intro points. So adding IPs + * here that should get cleaned up. */ + { + hs_service_intro_point_t *ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + /* This run will remove the IP because we have no circuits nor node_t + * associated with it. */ + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + /* We'll trigger a removal because we've reached our maximum amount of + * times we should retry a circuit. For this, we need to have a node_t + * that matches the identity of this IP. */ + routerinfo_t ri; + memset(&ri, 0, sizeof(ri)); + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); + /* This triggers a node_t creation. */ + tt_assert(nodelist_set_routerinfo(&ri, NULL)); + ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES + 1; + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + /* No removal but no circuit so this means the IP object will stay in the + * descriptor map so we can retry it. */ + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + ip->circuit_established = 1; /* We'll test that, it MUST be 0 after. */ + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + /* Remove the IP object at once for the next test. */ + ip->circuit_retries = MAX_INTRO_POINT_CIRCUIT_RETRIES + 1; + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + /* Now, we'll create an IP with a registered circuit. The IP object + * shouldn't go away. */ + ip = helper_create_service_ip(); + service_intro_point_add(service->desc_current->intro_points.map, ip); + ed25519_pubkey_copy(&circ->hs_ident->intro_auth_pk, + &ip->auth_key_kp.pubkey); + hs_circuitmap_register_intro_circ_v3_service_side( + circ, &ip->auth_key_kp.pubkey); + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + /* We'll mangle the IP object to expire. */ + ip->time_to_expire = now; + run_housekeeping_event(now); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); } - /* Client: Simulate the sending of an encrypted INTRODUCE1 cell */ - retval = - hs_ntor_client_get_introduce1_keys(&service_intro_auth_keypair.pubkey, - &service_intro_enc_keypair.pubkey, - &client_ephemeral_enc_keypair, - subcredential, - &client_hs_ntor_intro_cell_keys); - tt_int_op(retval, ==, 0); + done: + hs_circuitmap_remove_circuit(TO_CIRCUIT(circ)); + circuit_free(TO_CIRCUIT(circ)); + hs_free_all(); + UNMOCK(circuit_mark_for_close_); +} - /* Service: Simulate the decryption of the received INTRODUCE1 */ - retval = - hs_ntor_service_get_introduce1_keys(&service_intro_auth_keypair.pubkey, - &service_intro_enc_keypair, - &client_ephemeral_enc_keypair.pubkey, - subcredential, - &service_hs_ntor_intro_cell_keys); - tt_int_op(retval, ==, 0); +/** Test that we rotate descriptors correctly. */ +static void +test_rotate_descriptors(void *arg) +{ + int ret; + time_t next_rotation_time, now = time(NULL); + hs_service_t *service; + hs_service_descriptor_t *desc_next; - /* Test that the INTRODUCE1 encryption/mac keys match! */ - tt_mem_op(client_hs_ntor_intro_cell_keys.enc_key, OP_EQ, - service_hs_ntor_intro_cell_keys.enc_key, - CIPHER256_KEY_LEN); - tt_mem_op(client_hs_ntor_intro_cell_keys.mac_key, OP_EQ, - service_hs_ntor_intro_cell_keys.mac_key, - DIGEST256_LEN); + (void) arg; + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + + hs_init(); + MOCK(get_or_state, get_or_state_replacement); + MOCK(circuit_mark_for_close_, mock_circuit_mark_for_close); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + /* Descriptor rotation happens with a consensus with a new SRV. */ + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), mock_ns.valid_after); - /* Service: Simulate creation of RENDEZVOUS1 key material. */ - retval = - hs_ntor_service_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, - &service_intro_enc_keypair, - &service_ephemeral_rend_keypair, - &client_ephemeral_enc_keypair.pubkey, - &service_hs_ntor_rend_cell_keys); - tt_int_op(retval, ==, 0); + /* Create a service with a default descriptor and state. It's added to the + * global map. */ + service = helper_create_service(); + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + /* This triggers a build for both descriptors. The time now is only used in + * the descriptor certificate which is important to be now else the decoding + * will complain that the cert has expired if we use valid_after. */ + build_all_descriptors(now); + tt_assert(service->desc_current); + tt_assert(service->desc_next); - /* Client: Simulate the verification of a received RENDEZVOUS1 cell */ - retval = - hs_ntor_client_get_rendezvous1_keys(&service_intro_auth_keypair.pubkey, - &client_ephemeral_enc_keypair, - &service_intro_enc_keypair.pubkey, - &service_ephemeral_rend_keypair.pubkey, - &client_hs_ntor_rend_cell_keys); - tt_int_op(retval, ==, 0); + /* Tweak our service next rotation time so we can use a custom time. */ + service->state.next_rotation_time = next_rotation_time = + mock_ns.valid_after + (11 * 60 * 60); - /* Test that the RENDEZVOUS1 key material match! */ - tt_mem_op(client_hs_ntor_rend_cell_keys.rend_cell_auth_mac, OP_EQ, - service_hs_ntor_rend_cell_keys.rend_cell_auth_mac, - DIGEST256_LEN); - tt_mem_op(client_hs_ntor_rend_cell_keys.ntor_key_seed, OP_EQ, - service_hs_ntor_rend_cell_keys.ntor_key_seed, - DIGEST256_LEN); + /* Nothing should happen, we are not at a new SRV. Our next rotation time + * should be untouched. */ + rotate_all_descriptors(mock_ns.valid_after); + tt_u64_op(service->state.next_rotation_time, OP_EQ, next_rotation_time); + tt_assert(service->desc_current); + tt_assert(service->desc_next); + tt_u64_op(service->desc_current->time_period_num, OP_EQ, + hs_get_previous_time_period_num(0)); + tt_u64_op(service->desc_next->time_period_num, OP_EQ, + hs_get_time_period_num(0)); + /* Keep a reference so we can compare it after rotation to the current. */ + desc_next = service->desc_next; + + /* Going right after a new SRV. */ + ret = parse_rfc1123_time("Sat, 27 Oct 1985 01:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 27 Oct 1985 02:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), mock_ns.valid_after); + + /* Note down what to expect for the next rotation time which is 01:00 + 23h + * meaning 00:00:00. */ + next_rotation_time = mock_ns.valid_after + (23 * 60 * 60); + /* We should have our next rotation time modified, our current descriptor + * cleaned up and the next descriptor becoming the current. */ + rotate_all_descriptors(mock_ns.valid_after); + tt_u64_op(service->state.next_rotation_time, OP_EQ, next_rotation_time); + tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next)); + tt_assert(service->desc_next == NULL); + + /* A second time should do nothing. */ + rotate_all_descriptors(mock_ns.valid_after); + tt_u64_op(service->state.next_rotation_time, OP_EQ, next_rotation_time); + tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next)); + tt_assert(service->desc_next == NULL); + + build_all_descriptors(now); + tt_mem_op(service->desc_current, OP_EQ, desc_next, sizeof(*desc_next)); + tt_u64_op(service->desc_current->time_period_num, OP_EQ, + hs_get_time_period_num(0)); + tt_u64_op(service->desc_next->time_period_num, OP_EQ, + hs_get_next_time_period_num(0)); + tt_assert(service->desc_next); done: - ; + hs_free_all(); + UNMOCK(get_or_state); + UNMOCK(circuit_mark_for_close_); + UNMOCK(networkstatus_get_live_consensus); } -/** Test that our HS time period calculation functions work properly */ +/** Test building descriptors: picking intro points, setting up their link + * specifiers, etc. */ static void -test_time_period(void *arg) +test_build_update_descriptors(void *arg) { + int ret; + time_t now = time(NULL); + node_t *node; + hs_service_t *service; + hs_service_intro_point_t *ip_cur, *ip_next; + routerinfo_t ri; + (void) arg; - uint64_t tn; + + hs_init(); + + MOCK(get_or_state, + get_or_state_replacement); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 03:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 04:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), mock_ns.valid_after); + + /* Create a service without a current descriptor to trigger a build. */ + service = helper_create_service(); + tt_assert(service); + /* Unfortunately, the helper creates a dummy descriptor so get rid of it. */ + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + + /* We have a fresh service so this should trigger a build for both + * descriptors for specific time period that we'll test. */ + build_all_descriptors(now); + /* Check *current* descriptor. */ + tt_assert(service->desc_current); + tt_assert(service->desc_current->desc); + tt_assert(service->desc_current->intro_points.map); + /* The current time period is the one expected when starting at 03:00. */ + tt_u64_op(service->desc_current->time_period_num, OP_EQ, + hs_get_time_period_num(0)); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0); + + /* Check *next* descriptor. */ + tt_assert(service->desc_next); + tt_assert(service->desc_next->desc); + tt_assert(service->desc_next->intro_points.map); + tt_assert(service->desc_current != service->desc_next); + tt_u64_op(service->desc_next->time_period_num, OP_EQ, + hs_get_next_time_period_num(0)); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_next->next_upload_time, OP_EQ, 0); + + /* Time to test the update of those descriptors. At first, we have no node + * in the routerlist so this will find NO suitable node for the IPs. */ + setup_full_capture_of_logs(LOG_INFO); + update_all_descriptors(now); + expect_log_msg_containing("Unable to find a suitable node to be an " + "introduction point for service"); + teardown_capture_of_logs(); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 0); + tt_int_op(digest256map_size(service->desc_next->intro_points.map), + OP_EQ, 0); + + /* Now, we'll setup a node_t. */ + { + tor_addr_t ipv4_addr; + curve25519_secret_key_t curve25519_secret_key; + + memset(&ri, 0, sizeof(routerinfo_t)); + + tor_addr_parse(&ipv4_addr, "127.0.0.1"); + ri.addr = tor_addr_to_ipv4h(&ipv4_addr); + ri.or_port = 1337; + ri.purpose = ROUTER_PURPOSE_GENERAL; + /* Ugly yes but we never free the "ri" object so this just makes things + * easier. */ + ri.protocol_list = (char *) "HSDir=1-2 LinkAuth=3"; + ret = curve25519_secret_key_generate(&curve25519_secret_key, 0); + tt_int_op(ret, OP_EQ, 0); + ri.onion_curve25519_pkey = + tor_malloc_zero(sizeof(curve25519_public_key_t)); + ri.onion_pkey = crypto_pk_new(); + curve25519_public_key_generate(ri.onion_curve25519_pkey, + &curve25519_secret_key); + memset(ri.cache_info.identity_digest, 'A', DIGEST_LEN); + /* Setup ed25519 identity */ + ed25519_keypair_t kp1; + ed25519_keypair_generate(&kp1, 0); + ri.cache_info.signing_key_cert = tor_malloc_zero(sizeof(tor_cert_t)); + tt_assert(ri.cache_info.signing_key_cert); + ed25519_pubkey_copy(&ri.cache_info.signing_key_cert->signing_key, + &kp1.pubkey); + nodelist_set_routerinfo(&ri, NULL); + node = node_get_mutable_by_id(ri.cache_info.identity_digest); + tt_assert(node); + node->is_running = node->is_valid = node->is_fast = node->is_stable = 1; + } + + /* We expect to pick only one intro point from the node above. */ + setup_full_capture_of_logs(LOG_INFO); + update_all_descriptors(now); + tor_free(node->ri->onion_curve25519_pkey); /* Avoid memleak. */ + tor_free(node->ri->cache_info.signing_key_cert); + crypto_pk_free(node->ri->onion_pkey); + expect_log_msg_containing("just picked 1 intro points and wanted 3 for next " + "descriptor. It currently has 0 intro points. " + "Launching ESTABLISH_INTRO circuit shortly."); + teardown_capture_of_logs(); + tt_int_op(digest256map_size(service->desc_current->intro_points.map), + OP_EQ, 1); + tt_int_op(digest256map_size(service->desc_next->intro_points.map), + OP_EQ, 1); + /* Get the IP object. Because we don't have the auth key of the IP, we can't + * query it so get the first element in the map. */ + { + void *obj = NULL; + const uint8_t *key; + digest256map_iter_t *iter = + digest256map_iter_init(service->desc_current->intro_points.map); + digest256map_iter_get(iter, &key, &obj); + tt_assert(obj); + ip_cur = obj; + /* Get also the IP from the next descriptor. We'll make sure it's not the + * same object as in the current descriptor. */ + iter = digest256map_iter_init(service->desc_next->intro_points.map); + digest256map_iter_get(iter, &key, &obj); + tt_assert(obj); + ip_next = obj; + } + tt_mem_op(ip_cur, OP_NE, ip_next, sizeof(hs_desc_intro_point_t)); + + /* We won't test the service IP object because there is a specific test + * already for this but we'll make sure that the state is coherent.*/ + + /* Three link specifiers are mandatoy so make sure we do have them. */ + tt_int_op(smartlist_len(ip_cur->base.link_specifiers), OP_EQ, 3); + /* Make sure we have a valid encryption keypair generated when we pick an + * intro point in the update process. */ + tt_assert(!tor_mem_is_zero((char *) ip_cur->enc_key_kp.seckey.secret_key, + CURVE25519_SECKEY_LEN)); + tt_assert(!tor_mem_is_zero((char *) ip_cur->enc_key_kp.pubkey.public_key, + CURVE25519_PUBKEY_LEN)); + tt_u64_op(ip_cur->time_to_expire, OP_GE, now + + INTRO_POINT_LIFETIME_MIN_SECONDS); + tt_u64_op(ip_cur->time_to_expire, OP_LE, now + + INTRO_POINT_LIFETIME_MAX_SECONDS); + + /* Now, we will try to set up a service after a new time period has started + * and see if it behaves as expected. */ + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + + /* Create a service without a current descriptor to trigger a build. */ + service = helper_create_service(); + tt_assert(service); + /* Unfortunately, the helper creates a dummy descriptor so get rid of it. */ + service_descriptor_free(service->desc_current); + service->desc_current = NULL; + + /* We have a fresh service so this should trigger a build for both + * descriptors for specific time period that we'll test. */ + build_all_descriptors(now); + /* Check *current* descriptor. */ + tt_assert(service->desc_current); + tt_assert(service->desc_current->desc); + tt_assert(service->desc_current->intro_points.map); + /* This should be for the previous time period. */ + tt_u64_op(service->desc_current->time_period_num, OP_EQ, + hs_get_previous_time_period_num(0)); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0); + + /* Check *next* descriptor. */ + tt_assert(service->desc_next); + tt_assert(service->desc_next->desc); + tt_assert(service->desc_next->intro_points.map); + tt_assert(service->desc_current != service->desc_next); + tt_u64_op(service->desc_next->time_period_num, OP_EQ, + hs_get_time_period_num(0)); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_next->next_upload_time, OP_EQ, 0); + + /* Let's remove the next descriptor to simulate a rotation. */ + service_descriptor_free(service->desc_next); + service->desc_next = NULL; + + build_all_descriptors(now); + /* Check *next* descriptor. */ + tt_assert(service->desc_next); + tt_assert(service->desc_next->desc); + tt_assert(service->desc_next->intro_points.map); + tt_assert(service->desc_current != service->desc_next); + tt_u64_op(service->desc_next->time_period_num, OP_EQ, + hs_get_next_time_period_num(0)); + /* This should be untouched, the update descriptor process changes it. */ + tt_u64_op(service->desc_next->next_upload_time, OP_EQ, 0); + + done: + hs_free_all(); + nodelist_free_all(); +} + +static void +test_upload_descriptors(void *arg) +{ + int ret; + time_t now = time(NULL); + hs_service_t *service; + + (void) arg; + + hs_init(); + MOCK(get_or_state, + get_or_state_replacement); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + dummy_state = tor_malloc_zero(sizeof(or_state_t)); + + ret = parse_rfc1123_time("Sat, 26 Oct 1985 13:00:00 UTC", + &mock_ns.valid_after); + tt_int_op(ret, OP_EQ, 0); + ret = parse_rfc1123_time("Sat, 26 Oct 1985 14:00:00 UTC", + &mock_ns.fresh_until); + tt_int_op(ret, OP_EQ, 0); + + /* Create a service with no descriptor. It's added to the global map. */ + service = hs_service_new(get_options()); + tt_assert(service); + service->config.version = HS_VERSION_THREE; + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + /* Register service to global map. */ + ret = register_service(get_hs_service_map(), service); + tt_int_op(ret, OP_EQ, 0); + /* But first, build our descriptor. */ + build_all_descriptors(now); + + /* Nothing should happen because we have 0 introduction circuit established + * and we want (by default) 3 intro points. */ + run_upload_descriptor_event(now); + /* If no upload happened, this should be untouched. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, 0); + /* We'll simulate that we've opened our intro point circuit and that we only + * want one intro point. */ + service->config.num_intro_points = 1; + + /* Set our next upload time after now which will skip the upload. */ + service->desc_current->next_upload_time = now + 1000; + run_upload_descriptor_event(now); + /* If no upload happened, this should be untouched. */ + tt_u64_op(service->desc_current->next_upload_time, OP_EQ, now + 1000); + + done: + hs_free_all(); + UNMOCK(get_or_state); +} + +/** Test the functions that save and load HS revision counters to state. */ +static void +test_revision_counter_state(void *arg) +{ + char *state_line_one = NULL; + char *state_line_two = NULL; + + hs_service_descriptor_t *desc_one = service_descriptor_new(); + hs_service_descriptor_t *desc_two = service_descriptor_new(); + + (void) arg; + + /* Prepare both descriptors */ + desc_one->desc->plaintext_data.revision_counter = 42; + desc_two->desc->plaintext_data.revision_counter = 240; + memset(&desc_one->blinded_kp.pubkey.pubkey, 66, + sizeof(desc_one->blinded_kp.pubkey.pubkey)); + memset(&desc_two->blinded_kp.pubkey.pubkey, 240, + sizeof(desc_one->blinded_kp.pubkey.pubkey)); + + /* Turn the descriptor rev counters into state lines */ + state_line_one = encode_desc_rev_counter_for_state(desc_one); + tt_str_op(state_line_one, OP_EQ, + "QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI 42"); + + state_line_two = encode_desc_rev_counter_for_state(desc_two); + tt_str_op(state_line_two, OP_EQ, + "8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PDw8PA 240"); + + /* Now let's test our state parsing function: */ + int service_found; + uint64_t cached_rev_counter; + + /* First's try with wrong pubkey and check that no service was found */ + cached_rev_counter =check_state_line_for_service_rev_counter(state_line_one, + &desc_two->blinded_kp.pubkey, + &service_found); + tt_int_op(service_found, OP_EQ, 0); + tt_u64_op(cached_rev_counter, OP_EQ, 0); + + /* Now let's try with the right pubkeys */ + cached_rev_counter =check_state_line_for_service_rev_counter(state_line_one, + &desc_one->blinded_kp.pubkey, + &service_found); + tt_int_op(service_found, OP_EQ, 1); + tt_u64_op(cached_rev_counter, OP_EQ, 42); + + cached_rev_counter =check_state_line_for_service_rev_counter(state_line_two, + &desc_two->blinded_kp.pubkey, + &service_found); + tt_int_op(service_found, OP_EQ, 1); + tt_u64_op(cached_rev_counter, OP_EQ, 240); + + done: + tor_free(state_line_one); + tor_free(state_line_two); + service_descriptor_free(desc_one); + service_descriptor_free(desc_two); +} + +/** Global vars used by test_rendezvous1_parsing() */ +static char rend1_payload[RELAY_PAYLOAD_SIZE]; +static size_t rend1_payload_len = 0; + +/** Mock for relay_send_command_from_edge() to send a RENDEZVOUS1 cell. Instead + * of sending it to the network, instead save it to the global `rend1_payload` + * variable so that we can inspect it in the test_rendezvous1_parsing() + * test. */ +static int +mock_relay_send_rendezvous1(streamid_t stream_id, circuit_t *circ, + uint8_t relay_command, const char *payload, + size_t payload_len, + crypt_path_t *cpath_layer, + const char *filename, int lineno) +{ + (void) stream_id; + (void) circ; + (void) relay_command; + (void) cpath_layer; + (void) filename; + (void) lineno; + + memcpy(rend1_payload, payload, payload_len); + rend1_payload_len = payload_len; + + return 0; +} + +/** Send a RENDEZVOUS1 as a service, and parse it as a client. */ +static void +test_rendezvous1_parsing(void *arg) +{ int retval; - time_t fake_time; + static const char *test_addr = + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion"; + hs_service_t *service = NULL; + origin_circuit_t *service_circ = NULL; + origin_circuit_t *client_circ = NULL; + ed25519_keypair_t ip_auth_kp; + curve25519_keypair_t ephemeral_kp; + curve25519_keypair_t client_kp; + curve25519_keypair_t ip_enc_kp; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; - /* Let's do the example in prop224 section [TIME-PERIODS] */ - retval = parse_rfc1123_time("Wed, 13 Apr 2016 11:00:00 UTC", - &fake_time); - tt_int_op(retval, ==, 0); + (void) arg; - /* Check that the time period number is right */ - tn = get_time_period_num(fake_time); - tt_u64_op(tn, ==, 16903); + MOCK(relay_send_command_from_edge_, mock_relay_send_rendezvous1); - /* Increase current time to 11:59:59 UTC and check that the time period - number is still the same */ - fake_time += 3599; - tn = get_time_period_num(fake_time); - tt_u64_op(tn, ==, 16903); + { + /* Let's start by setting up the service that will start the rend */ + service = tor_malloc_zero(sizeof(hs_service_t)); + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + memcpy(service->onion_address, test_addr, sizeof(service->onion_address)); + tt_assert(service); + } + + { + /* Now let's set up the service rendezvous circuit and its keys. */ + service_circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_CONNECT_REND, + flags); + tor_free(service_circ->hs_ident); + hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys; + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + curve25519_keypair_generate(&ip_enc_kp, 0); + curve25519_keypair_generate(&ephemeral_kp, 0); + curve25519_keypair_generate(&client_kp, 0); + ed25519_keypair_generate(&ip_auth_kp, 0); + retval = hs_ntor_service_get_rendezvous1_keys(&ip_auth_kp.pubkey, + &ip_enc_kp, + &ephemeral_kp, + &client_kp.pubkey, + &hs_ntor_rend_cell_keys); + tt_int_op(retval, OP_EQ, 0); + + memset(rendezvous_cookie, 2, sizeof(rendezvous_cookie)); + service_circ->hs_ident = + create_rp_circuit_identifier(service, rendezvous_cookie, + &ephemeral_kp.pubkey, + &hs_ntor_rend_cell_keys); + } - /* Now take time to 12:00:00 UTC and check that the time period rotated */ - fake_time += 1; - tn = get_time_period_num(fake_time); - tt_u64_op(tn, ==, 16904); + /* Send out the RENDEZVOUS1 and make sure that our mock func worked */ + tt_assert(tor_mem_is_zero(rend1_payload, 32)); + hs_circ_service_rp_has_opened(service, service_circ); + tt_assert(!tor_mem_is_zero(rend1_payload, 32)); + tt_int_op(rend1_payload_len, OP_EQ, HS_LEGACY_RENDEZVOUS_CELL_SIZE); - /* Now also check our hs_get_next_time_period_num() function */ - tn = hs_get_next_time_period_num(fake_time); - tt_u64_op(tn, ==, 16905); + /******************************/ + + /** Now let's create the client rendezvous circuit */ + client_circ = + helper_create_origin_circuit(CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED, + flags); + /* fix up its circ ident */ + ed25519_pubkey_copy(&client_circ->hs_ident->intro_auth_pk, + &ip_auth_kp.pubkey); + memcpy(&client_circ->hs_ident->rendezvous_client_kp, + &client_kp, sizeof(client_circ->hs_ident->rendezvous_client_kp)); + memcpy(&client_circ->hs_ident->intro_enc_pk.public_key, + &ip_enc_kp.pubkey.public_key, + sizeof(client_circ->hs_ident->intro_enc_pk.public_key)); + + /* Now parse the rendezvous2 circuit and make sure it was fine. We are + * skipping 20 bytes off its payload, since that's the rendezvous cookie + * which is only present in REND1. */ + retval = handle_rendezvous2(client_circ, + (uint8_t*)rend1_payload+20, + rend1_payload_len-20); + tt_int_op(retval, OP_EQ, 0); + + /* TODO: We are only simulating client/service here. We could also simulate + * the rendezvous point by plugging in rend_mid_establish_rendezvous(). We + * would need an extra circuit and some more stuff but it's doable. */ done: - ; + circuit_free(TO_CIRCUIT(service_circ)); + circuit_free(TO_CIRCUIT(client_circ)); + hs_service_free(service); + hs_free_all(); + UNMOCK(relay_send_command_from_edge_); } struct testcase_t hs_service_tests[] = { - { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, + { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, + NULL, NULL }, + { "load_keys", test_load_keys, TT_FORK, + NULL, NULL }, + { "access_service", test_access_service, TT_FORK, + NULL, NULL }, + { "service_intro_point", test_service_intro_point, TT_FORK, + NULL, NULL }, + { "helper_functions", test_helper_functions, TT_FORK, + NULL, NULL }, + { "intro_circuit_opened", test_intro_circuit_opened, TT_FORK, + NULL, NULL }, + { "intro_established", test_intro_established, TT_FORK, + NULL, NULL }, + { "closing_intro_circs", test_closing_intro_circs, TT_FORK, + NULL, NULL }, + { "rdv_circuit_opened", test_rdv_circuit_opened, TT_FORK, + NULL, NULL }, + { "introduce2", test_introduce2, TT_FORK, + NULL, NULL }, + { "service_event", test_service_event, TT_FORK, + NULL, NULL }, + { "rotate_descriptors", test_rotate_descriptors, TT_FORK, + NULL, NULL }, + { "build_update_descriptors", test_build_update_descriptors, TT_FORK, NULL, NULL }, - { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK, + { "upload_descriptors", test_upload_descriptors, TT_FORK, NULL, NULL }, - { "hs_ntor", test_hs_ntor, TT_FORK, + { "revision_counter_state", test_revision_counter_state, TT_FORK, NULL, NULL }, - { "time_period", test_time_period, TT_FORK, + { "rendezvous1_parsing", test_rendezvous1_parsing, TT_FORK, NULL, NULL }, END_OF_TESTCASES diff --git a/src/test/test_introduce.c b/src/test/test_introduce.c index cfb8d83b1d..d502bdddb1 100644 --- a/src/test/test_introduce.c +++ b/src/test/test_introduce.c @@ -307,7 +307,7 @@ do_parse_test(uint8_t *plaintext, size_t plaintext_len, int phase) /* Do early parsing */ parsed_req = rend_service_begin_parse_intro(cell, cell_len, 2, &err_msg); tt_assert(parsed_req); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); tt_mem_op(parsed_req->pk,OP_EQ, digest, DIGEST_LEN); tt_assert(parsed_req->ciphertext); tt_assert(parsed_req->ciphertext_len > 0); @@ -318,7 +318,7 @@ do_parse_test(uint8_t *plaintext, size_t plaintext_len, int phase) /* Do decryption */ r = rend_service_decrypt_intro(parsed_req, k, &err_msg); tt_assert(!r); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); tt_assert(parsed_req->plaintext); tt_assert(parsed_req->plaintext_len > 0); @@ -328,7 +328,7 @@ do_parse_test(uint8_t *plaintext, size_t plaintext_len, int phase) /* Do late parsing */ r = rend_service_parse_intro_plaintext(parsed_req, &err_msg); tt_assert(!r); - tt_assert(!err_msg); + tt_ptr_op(err_msg, OP_EQ, NULL); tt_assert(parsed_req->parsed); done: @@ -355,7 +355,7 @@ make_intro_from_plaintext( /* * Figure out an upper bound on how big the ciphertext will be - * (see crypto_pk_public_hybrid_encrypt()) + * (see crypto_pk_obsolete_public_hybrid_encrypt()) */ ciphertext_size = PKCS1_OAEP_PADDING_OVERHEAD; ciphertext_size += crypto_pk_keysize(key); @@ -372,7 +372,7 @@ make_intro_from_plaintext( tt_assert(r >= 0); /* Do encryption */ - r = crypto_pk_public_hybrid_encrypt( + r = crypto_pk_obsolete_public_hybrid_encrypt( key, cell + DIGEST_LEN, ciphertext_size, buf, len, PK_PKCS1_OAEP_PADDING, 0); diff --git a/src/test/test_key_expiration.sh b/src/test/test_key_expiration.sh new file mode 100755 index 0000000000..5511dbf18c --- /dev/null +++ b/src/test/test_key_expiration.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +# Note: some of this code is lifted from zero_length_keys.sh and +# test_keygen.sh, and could be unified. + +umask 077 +set -e + +if [ $# -eq 0 ] || [ ! -f ${1} ] || [ ! -x ${1} ]; then + if [ "$TESTING_TOR_BINARY" = "" ] ; then + echo "Usage: ${0} PATH_TO_TOR [case-number]" + exit 1 + fi +fi + +if [ $# -ge 1 ]; then + TOR_BINARY="${1}" + shift +else + TOR_BINARY="${TESTING_TOR_BINARY}" +fi + +if [ $# -ge 1 ]; then + dflt=0 +else + dflt=1 +fi + +CASE1=$dflt +CASE2=$dflt +CASE3=$dflt + +if [ $# -ge 1 ]; then + eval "CASE${1}"=1 +fi + + +dump() { xxd -p "$1" | tr -d '\n '; } +die() { echo "$1" >&2 ; exit 5; } +check_dir() { [ -d "$1" ] || die "$1 did not exist"; } +check_file() { [ -e "$1" ] || die "$1 did not exist"; } +check_no_file() { [ -e "$1" ] && die "$1 was not supposed to exist" || true; } +check_files_eq() { cmp "$1" "$2" || die "$1 and $2 did not match: `dump $1` vs `dump $2`"; } +check_keys_eq() { check_files_eq "${SRC}/keys/${1}" "${ME}/keys/${1}"; } + +DATA_DIR=`mktemp -d -t tor_key_expiration_tests.XXXXXX` +if [ -z "$DATA_DIR" ]; then + echo "Failure: mktemp invocation returned empty string" >&2 + exit 3 +fi +if [ ! -d "$DATA_DIR" ]; then + echo "Failure: mktemp invocation result doesn't point to directory" >&2 + exit 3 +fi +trap "rm -rf '$DATA_DIR'" 0 + +# Use an absolute path for this or Tor will complain +DATA_DIR=`cd "${DATA_DIR}" && pwd` + +touch "${DATA_DIR}/empty_torrc" + +QUIETLY="--hush" +SILENTLY="--quiet" +TOR="${TOR_BINARY} --DisableNetwork 1 --ShutdownWaitLength 0 --ORPort 12345 --ExitRelay 0 -f ${DATA_DIR}/empty_torrc --DataDirectory ${DATA_DIR}" + +##### SETUP +# +# Here we create a set of keys. + +# Step 1: Start Tor with --list-fingerprint --quiet. Make sure everything is there. +echo "Setup step #1" +${TOR} --list-fingerprint ${SILENTLY} > /dev/null + +check_dir "${DATA_DIR}/keys" +check_file "${DATA_DIR}/keys/ed25519_master_id_public_key" +check_file "${DATA_DIR}/keys/ed25519_master_id_secret_key" +check_file "${DATA_DIR}/keys/ed25519_signing_cert" +check_file "${DATA_DIR}/keys/ed25519_signing_secret_key" +check_file "${DATA_DIR}/keys/secret_id_key" +check_file "${DATA_DIR}/keys/secret_onion_key" +check_file "${DATA_DIR}/keys/secret_onion_key_ntor" + +##### TEST CASES + +echo "=== Starting key expiration tests." + +FN="${DATA_DIR}/stderr" + +if [ "$CASE1" = 1 ]; then + echo "==== Case 1: Test --key-expiration without argument and ensure usage" + echo " instructions are printed." + + ${TOR} ${QUIETLY} --key-expiration 2>"$FN" || true + grep "No valid argument to --key-expiration found!" "$FN" >/dev/null || \ + die "Tor didn't mention supported --key-expiration argmuents" + + echo "==== Case 1: ok" +fi + +if [ "$CASE2" = 1 ]; then + echo "==== Case 2: Start Tor with --key-expiration 'sign' and make sure it prints an expiration." + + ${TOR} ${QUIETLY} --key-expiration sign 2>"$FN" + grep "signing-cert-expiry:" "$FN" >/dev/null || \ + die "Tor didn't print an expiration" + + echo "==== Case 2: ok" +fi + +if [ "$CASE3" = 1 ]; then + echo "==== Case 3: Start Tor with --key-expiration 'sign', when there is no" + echo " signing key, and make sure that Tor generates a new key" + echo " and prints its certificate's expiration." + + mv "${DATA_DIR}/keys/ed25519_signing_cert" \ + "${DATA_DIR}/keys/ed25519_signing_cert.bak" + + ${TOR} --key-expiration sign > "$FN" 2>&1 + grep "It looks like I need to generate and sign a new medium-term signing key" "$FN" >/dev/null || \ + die "Tor didn't create a new signing key" + check_file "${DATA_DIR}/keys/ed25519_signing_cert" + grep "signing-cert-expiry:" "$FN" >/dev/null || \ + die "Tor didn't print an expiration" + + mv "${DATA_DIR}/keys/ed25519_signing_cert.bak" \ + "${DATA_DIR}/keys/ed25519_signing_cert" + + echo "==== Case 3: ok" +fi diff --git a/src/test/test_keypin.c b/src/test/test_keypin.c index d2ec8e9ca7..79d7bac902 100644 --- a/src/test/test_keypin.c +++ b/src/test/test_keypin.c @@ -20,8 +20,8 @@ test_keypin_parse_line(void *arg) "aGVyZSBpcyBhIGdvb2Qgc2hhMSE " "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4"); tt_assert(ent); - tt_mem_op(ent->rsa_id, ==, "here is a good sha1!", 20); - tt_mem_op(ent->ed25519_key, ==, "This ed25519 scoffs at the sha1.", 32); + tt_mem_op(ent->rsa_id, OP_EQ, "here is a good sha1!", 20); + tt_mem_op(ent->ed25519_key, OP_EQ, "This ed25519 scoffs at the sha1.", 32); tor_free(ent); ent = NULL; /* Good line with extra stuff we will ignore. */ @@ -29,27 +29,27 @@ test_keypin_parse_line(void *arg) "aGVyZSBpcyBhIGdvb2Qgc2hhMSE " "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4helloworld"); tt_assert(ent); - tt_mem_op(ent->rsa_id, ==, "here is a good sha1!", 20); - tt_mem_op(ent->ed25519_key, ==, "This ed25519 scoffs at the sha1.", 32); + tt_mem_op(ent->rsa_id, OP_EQ, "here is a good sha1!", 20); + tt_mem_op(ent->ed25519_key, OP_EQ, "This ed25519 scoffs at the sha1.", 32); tor_free(ent); ent = NULL; /* Bad line: no space in the middle. */ ent = keypin_parse_journal_line( "aGVyZSBpcyBhIGdvb2Qgc2hhMSE?" "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4"); - tt_assert(! ent); + tt_ptr_op(ent, OP_EQ, NULL); /* Bad line: bad base64 in RSA ID */ ent = keypin_parse_journal_line( "aGVyZSBpcyBhIGdv!2Qgc2hhMSE " "VGhpcyBlZDI1NTE5IHNjb2ZmcyBhdCB0aGUgc2hhMS4"); - tt_assert(! ent); + tt_ptr_op(ent, OP_EQ, NULL); /* Bad line: bad base64 in Ed25519 */ ent = keypin_parse_journal_line( "aGVyZSBpcyBhIGdvb2Qgc2hhMSE " "VGhpcyBlZDI1NTE5IHNjb2ZmcyB!dCB0aGUgc2hhMS4"); - tt_assert(! ent); + tt_ptr_op(ent, OP_EQ, NULL); done: tor_free(ent); @@ -82,11 +82,11 @@ test_keypin_parse_file(void *arg) "Z2dsZSBpbiBzd29tZWVzd2FucyA aW4gdm9sdXB0YXRlIGF4ZS1oYWNrZXIgZXNzZSByaXA\n" "cHVsdXMgY3J1bW1paSBldSBtb28 ZiBudWxsYSBzbnV2di5QTFVHSFBMT1ZFUlhZWlpZLi4\n"; - tt_int_op(0, ==, keypin_load_journal_impl(data1, strlen(data1))); - tt_int_op(8, ==, smartlist_len(mock_addent_got)); + tt_int_op(0, OP_EQ, keypin_load_journal_impl(data1, strlen(data1))); + tt_int_op(8, OP_EQ, smartlist_len(mock_addent_got)); keypin_ent_t *ent = smartlist_get(mock_addent_got, 2); - tt_mem_op(ent->rsa_id, ==, "r lerkim, sed do bar", 20); - tt_mem_op(ent->ed25519_key, ==, "baloot tempor gluppitus ut labor", 32); + tt_mem_op(ent->rsa_id, OP_EQ, "r lerkim, sed do bar", 20); + tt_mem_op(ent->ed25519_key, OP_EQ, "baloot tempor gluppitus ut labor", 32); /* More complex example: weird lines, bogus lines, duplicate/conflicting lines */ @@ -107,24 +107,25 @@ test_keypin_parse_file(void *arg) "ZHMgc3BlYWsgdHJ1dGgsIGFuZCA aXQgd2FzIHRydaUgdGhhdCBhbGwgdGhlIG1hc3Rlcgo\n" ; - tt_int_op(0, ==, keypin_load_journal_impl(data2, strlen(data2))); - tt_int_op(13, ==, smartlist_len(mock_addent_got)); + tt_int_op(0, OP_EQ, keypin_load_journal_impl(data2, strlen(data2))); + tt_int_op(13, OP_EQ, smartlist_len(mock_addent_got)); ent = smartlist_get(mock_addent_got, 9); - tt_mem_op(ent->rsa_id, ==, "\"You have made a goo", 20); - tt_mem_op(ent->ed25519_key, ==, "d beginning.\" But no more. Wizar", 32); + tt_mem_op(ent->rsa_id, OP_EQ, "\"You have made a goo", 20); + tt_mem_op(ent->ed25519_key, OP_EQ, "d beginning.\" But no more. Wizar", 32); ent = smartlist_get(mock_addent_got, 12); - tt_mem_op(ent->rsa_id, ==, "ds speak truth, and ", 20); - tt_mem_op(ent->ed25519_key, ==, "it was tru\xa5 that all the master\n", 32); + tt_mem_op(ent->rsa_id, OP_EQ, "ds speak truth, and ", 20); + tt_mem_op(ent->ed25519_key, OP_EQ, + "it was tru\xa5 that all the master\n", 32); /* File truncated before NL */ const char data3[] = "Tm8gZHJhZ29uIGNhbiByZXNpc3Q IHRoZSBmYXNjaW5hdGlvbiBvZiByaWRkbGluZyB0YWw"; - tt_int_op(0, ==, keypin_load_journal_impl(data3, strlen(data3))); - tt_int_op(14, ==, smartlist_len(mock_addent_got)); + tt_int_op(0, OP_EQ, keypin_load_journal_impl(data3, strlen(data3))); + tt_int_op(14, OP_EQ, smartlist_len(mock_addent_got)); ent = smartlist_get(mock_addent_got, 13); - tt_mem_op(ent->rsa_id, ==, "No dragon can resist", 20); - tt_mem_op(ent->ed25519_key, ==, " the fascination of riddling tal", 32); + tt_mem_op(ent->rsa_id, OP_EQ, "No dragon can resist", 20); + tt_mem_op(ent->ed25519_key, OP_EQ, " the fascination of riddling tal", 32); done: keypin_clear(); @@ -141,32 +142,32 @@ test_keypin_add_entry(void *arg) (void)arg; keypin_clear(); - tt_int_op(KEYPIN_ADDED, ==, ADD("ambassadors-at-large", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("ambassadors-at-large", "bread-and-butter thing-in-itself")); - tt_int_op(KEYPIN_ADDED, ==, ADD("gentleman-adventurer", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("gentleman-adventurer", "cloak-and-dagger what's-his-face")); - tt_int_op(KEYPIN_FOUND, ==, ADD("ambassadors-at-large", + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("ambassadors-at-large", "bread-and-butter thing-in-itself")); - tt_int_op(KEYPIN_FOUND, ==, ADD("ambassadors-at-large", + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("ambassadors-at-large", "bread-and-butter thing-in-itself")); - tt_int_op(KEYPIN_FOUND, ==, ADD("gentleman-adventurer", + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("gentleman-adventurer", "cloak-and-dagger what's-his-face")); - tt_int_op(KEYPIN_ADDED, ==, ADD("Johnnies-come-lately", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("Johnnies-come-lately", "run-of-the-mill root-mean-square")); - tt_int_op(KEYPIN_MISMATCH, ==, ADD("gentleman-adventurer", + tt_int_op(KEYPIN_MISMATCH, OP_EQ, ADD("gentleman-adventurer", "hypersentimental closefistedness")); - tt_int_op(KEYPIN_MISMATCH, ==, ADD("disestablismentarian", + tt_int_op(KEYPIN_MISMATCH, OP_EQ, ADD("disestablismentarian", "cloak-and-dagger what's-his-face")); - tt_int_op(KEYPIN_FOUND, ==, ADD("gentleman-adventurer", + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("gentleman-adventurer", "cloak-and-dagger what's-his-face")); - tt_int_op(KEYPIN_NOT_FOUND, ==, LONE_RSA("Llanfairpwllgwyngyll")); - tt_int_op(KEYPIN_MISMATCH, ==, LONE_RSA("Johnnies-come-lately")); + tt_int_op(KEYPIN_NOT_FOUND, OP_EQ, LONE_RSA("Llanfairpwllgwyngyll")); + tt_int_op(KEYPIN_MISMATCH, OP_EQ, LONE_RSA("Johnnies-come-lately")); done: keypin_clear(); @@ -179,51 +180,51 @@ test_keypin_journal(void *arg) char *contents = NULL; const char *fname = get_fname("keypin-journal"); - tt_int_op(0, ==, keypin_load_journal(fname)); /* ENOENT is okay */ + tt_int_op(0, OP_EQ, keypin_load_journal(fname)); /* ENOENT is okay */ update_approx_time(1217709000); - tt_int_op(0, ==, keypin_open_journal(fname)); + tt_int_op(0, OP_EQ, keypin_open_journal(fname)); - tt_int_op(KEYPIN_ADDED, ==, ADD("king-of-the-herrings", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("king-of-the-herrings", "good-for-nothing attorney-at-law")); - tt_int_op(KEYPIN_ADDED, ==, ADD("yellowish-red-yellow", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("yellowish-red-yellow", "salt-and-pepper high-muck-a-muck")); - tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow", + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("yellowish-red-yellow", "salt-and-pepper high-muck-a-muck")); keypin_close_journal(); keypin_clear(); - tt_int_op(0, ==, keypin_load_journal(fname)); + tt_int_op(0, OP_EQ, keypin_load_journal(fname)); update_approx_time(1231041600); - tt_int_op(0, ==, keypin_open_journal(fname)); - tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow", + tt_int_op(0, OP_EQ, keypin_open_journal(fname)); + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("yellowish-red-yellow", "salt-and-pepper high-muck-a-muck")); - tt_int_op(KEYPIN_ADDED, ==, ADD("theatre-in-the-round", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("theatre-in-the-round", "holier-than-thou jack-in-the-box")); - tt_int_op(KEYPIN_ADDED, ==, ADD("no-deposit-no-return", + tt_int_op(KEYPIN_ADDED, OP_EQ, ADD("no-deposit-no-return", "across-the-board will-o-the-wisp")); - tt_int_op(KEYPIN_MISMATCH, ==, ADD("intellectualizations", + tt_int_op(KEYPIN_MISMATCH, OP_EQ, ADD("intellectualizations", "salt-and-pepper high-muck-a-muck")); keypin_close_journal(); keypin_clear(); - tt_int_op(0, ==, keypin_load_journal(fname)); + tt_int_op(0, OP_EQ, keypin_load_journal(fname)); update_approx_time(1412278354); - tt_int_op(0, ==, keypin_open_journal(fname)); - tt_int_op(KEYPIN_FOUND, ==, ADD("yellowish-red-yellow", + tt_int_op(0, OP_EQ, keypin_open_journal(fname)); + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("yellowish-red-yellow", "salt-and-pepper high-muck-a-muck")); - tt_int_op(KEYPIN_MISMATCH, ==, ADD("intellectualizations", + tt_int_op(KEYPIN_MISMATCH, OP_EQ, ADD("intellectualizations", "salt-and-pepper high-muck-a-muck")); - tt_int_op(KEYPIN_FOUND, ==, ADD("theatre-in-the-round", + tt_int_op(KEYPIN_FOUND, OP_EQ, ADD("theatre-in-the-round", "holier-than-thou jack-in-the-box")); - tt_int_op(KEYPIN_MISMATCH, ==, ADD("counterrevolutionary", + tt_int_op(KEYPIN_MISMATCH, OP_EQ, ADD("counterrevolutionary", "holier-than-thou jack-in-the-box")); - tt_int_op(KEYPIN_MISMATCH, ==, ADD("no-deposit-no-return", + tt_int_op(KEYPIN_MISMATCH, OP_EQ, ADD("no-deposit-no-return", "floccinaucinihilipilificationism")); keypin_close_journal(); contents = read_file_to_str(fname, RFTS_BIN, NULL); tt_assert(contents); - tt_str_op(contents,==, + tt_str_op(contents,OP_EQ, "\n" "@opened-at 2008-08-02 20:30:00\n" "a2luZy1vZi10aGUtaGVycmluZ3M Z29vZC1mb3Itbm90aGluZyBhdHRvcm5leS1hdC1sYXc\n" diff --git a/src/test/test_link_handshake.c b/src/test/test_link_handshake.c index c5508b0f04..422d419078 100644 --- a/src/test/test_link_handshake.c +++ b/src/test/test_link_handshake.c @@ -136,7 +136,7 @@ test_link_handshake_certs_ok(void *arg) * actually generate a CERTS cell. */ tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, - key1, key2, 86400), ==, 0); + key1, key2, 86400), OP_EQ, 0); if (with_ed) { /* If we're making a CERTS cell for an ed handshake, let's make sure we @@ -155,63 +155,63 @@ test_link_handshake_certs_ok(void *arg) c1->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; c1->link_proto = 3; - tt_int_op(connection_init_or_handshake_state(c1, 1), ==, 0); + tt_int_op(connection_init_or_handshake_state(c1, 1), OP_EQ, 0); /* c2 has started_here == 0 */ c2->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; c2->link_proto = 3; - tt_int_op(connection_init_or_handshake_state(c2, 0), ==, 0); + tt_int_op(connection_init_or_handshake_state(c2, 0), OP_EQ, 0); - tt_int_op(0, ==, connection_or_send_certs_cell(c1)); + tt_int_op(0, OP_EQ, connection_or_send_certs_cell(c1)); tt_assert(mock_got_var_cell); cell1 = mock_got_var_cell; - tt_int_op(0, ==, connection_or_send_certs_cell(c2)); + tt_int_op(0, OP_EQ, connection_or_send_certs_cell(c2)); tt_assert(mock_got_var_cell); cell2 = mock_got_var_cell; - tt_int_op(cell1->command, ==, CELL_CERTS); - tt_int_op(cell1->payload_len, >, 1); + tt_int_op(cell1->command, OP_EQ, CELL_CERTS); + tt_int_op(cell1->payload_len, OP_GT, 1); - tt_int_op(cell2->command, ==, CELL_CERTS); - tt_int_op(cell2->payload_len, >, 1); + tt_int_op(cell2->command, OP_EQ, CELL_CERTS); + tt_int_op(cell2->payload_len, OP_GT, 1); - tt_int_op(cell1->payload_len, ==, + tt_int_op(cell1->payload_len, OP_EQ, certs_cell_parse(&cc1, cell1->payload, cell1->payload_len)); - tt_int_op(cell2->payload_len, ==, + tt_int_op(cell2->payload_len, OP_EQ, certs_cell_parse(&cc2, cell2->payload, cell2->payload_len)); if (with_ed) { - tt_int_op(5, ==, cc1->n_certs); - tt_int_op(5, ==, cc2->n_certs); + tt_int_op(5, OP_EQ, cc1->n_certs); + tt_int_op(5, OP_EQ, cc2->n_certs); } else { - tt_int_op(2, ==, cc1->n_certs); - tt_int_op(2, ==, cc2->n_certs); + tt_int_op(2, OP_EQ, cc1->n_certs); + tt_int_op(2, OP_EQ, cc2->n_certs); } - tt_int_op(certs_cell_get_certs(cc1, 0)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc1, 0)->cert_type, OP_EQ, CERTTYPE_RSA1024_ID_AUTH); - tt_int_op(certs_cell_get_certs(cc1, 1)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc1, 1)->cert_type, OP_EQ, CERTTYPE_RSA1024_ID_ID); - tt_int_op(certs_cell_get_certs(cc2, 0)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc2, 0)->cert_type, OP_EQ, CERTTYPE_RSA1024_ID_LINK); - tt_int_op(certs_cell_get_certs(cc2, 1)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc2, 1)->cert_type, OP_EQ, CERTTYPE_RSA1024_ID_ID); if (with_ed) { - tt_int_op(certs_cell_get_certs(cc1, 2)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc1, 2)->cert_type, OP_EQ, CERTTYPE_ED_ID_SIGN); - tt_int_op(certs_cell_get_certs(cc1, 3)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc1, 3)->cert_type, OP_EQ, CERTTYPE_ED_SIGN_AUTH); - tt_int_op(certs_cell_get_certs(cc1, 4)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc1, 4)->cert_type, OP_EQ, CERTTYPE_RSA1024_ID_EDID); - tt_int_op(certs_cell_get_certs(cc2, 2)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc2, 2)->cert_type, OP_EQ, CERTTYPE_ED_ID_SIGN); - tt_int_op(certs_cell_get_certs(cc2, 3)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc2, 3)->cert_type, OP_EQ, CERTTYPE_ED_SIGN_LINK); - tt_int_op(certs_cell_get_certs(cc2, 4)->cert_type, ==, + tt_int_op(certs_cell_get_certs(cc2, 4)->cert_type, OP_EQ, CERTTYPE_RSA1024_ID_EDID); } @@ -240,8 +240,8 @@ test_link_handshake_certs_ok(void *arg) tor_assert(c1->handshake_state->authenticated); tt_assert(c1->handshake_state->received_certs_cell); - tt_assert(c1->handshake_state->certs->auth_cert == NULL); - tt_assert(c1->handshake_state->certs->ed_sign_auth == NULL); + tt_ptr_op(c1->handshake_state->certs->auth_cert, OP_EQ, NULL); + tt_ptr_op(c1->handshake_state->certs->ed_sign_auth, OP_EQ, NULL); tt_assert(c1->handshake_state->certs->id_cert); if (with_ed) { tt_assert(c1->handshake_state->certs->ed_sign_link); @@ -250,9 +250,9 @@ test_link_handshake_certs_ok(void *arg) tt_assert(c1->handshake_state->authenticated_rsa); tt_assert(c1->handshake_state->authenticated_ed25519); } else { - tt_assert(c1->handshake_state->certs->ed_sign_link == NULL); - tt_assert(c1->handshake_state->certs->ed_rsa_crosscert == NULL); - tt_assert(c1->handshake_state->certs->ed_id_sign == NULL); + tt_ptr_op(c1->handshake_state->certs->ed_sign_link, OP_EQ, NULL); + tt_ptr_op(c1->handshake_state->certs->ed_rsa_crosscert, OP_EQ, NULL); + tt_ptr_op(c1->handshake_state->certs->ed_id_sign, OP_EQ, NULL); tt_assert(c1->handshake_state->authenticated_rsa); tt_assert(! c1->handshake_state->authenticated_ed25519); } @@ -278,9 +278,9 @@ test_link_handshake_certs_ok(void *arg) tt_assert(c2->handshake_state->certs->ed_id_sign); } else { tt_assert(c2->handshake_state->certs->auth_cert); - tt_assert(c2->handshake_state->certs->ed_sign_auth == NULL); - tt_assert(c2->handshake_state->certs->ed_rsa_crosscert == NULL); - tt_assert(c2->handshake_state->certs->ed_id_sign == NULL); + tt_ptr_op(c2->handshake_state->certs->ed_sign_auth, OP_EQ, NULL); + tt_ptr_op(c2->handshake_state->certs->ed_rsa_crosscert, OP_EQ, NULL); + tt_ptr_op(c2->handshake_state->certs->ed_id_sign, OP_EQ, NULL); } tt_assert(c2->handshake_state->certs->id_cert); tt_assert(tor_mem_is_zero( @@ -376,14 +376,14 @@ recv_certs_setup(const struct testcase_t *test) tor_addr_from_ipv4h(&d->c->base_.addr, 0x801f0127); d->c->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; d->chan->conn = d->c; - tt_int_op(connection_init_or_handshake_state(d->c, 1), ==, 0); + tt_int_op(connection_init_or_handshake_state(d->c, 1), OP_EQ, 0); d->c->link_proto = 4; d->key1 = pk_generate(2); d->key2 = pk_generate(3); tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, - d->key1, d->key2, 86400), ==, 0); + d->key1, d->key2, 86400), OP_EQ, 0); if (is_ed) { init_mock_ed_keys(d->key2); } else { @@ -452,7 +452,7 @@ recv_certs_setup(const struct testcase_t *test) d->cell->command = CELL_CERTS; n = certs_cell_encode(d->cell->payload, 4096, d->ccell); - tt_int_op(n, >, 0); + tt_int_op(n, OP_GT, 0); d->cell->payload_len = n; MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key); @@ -465,9 +465,9 @@ recv_certs_setup(const struct testcase_t *test) mock_peer_cert = tor_x509_cert_dup(a); } - tt_int_op(0, ==, d->c->handshake_state->received_certs_cell); - tt_int_op(0, ==, mock_send_authenticate_called); - tt_int_op(0, ==, mock_send_netinfo_called); + tt_int_op(0, OP_EQ, d->c->handshake_state->received_certs_cell); + tt_int_op(0, OP_EQ, mock_send_authenticate_called); + tt_int_op(0, OP_EQ, mock_send_netinfo_called); return d; done: @@ -485,25 +485,25 @@ test_link_handshake_recv_certs_ok(void *arg) { certs_data_t *d = arg; channel_tls_process_certs_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(d->c->handshake_state->authenticated, ==, 1); - tt_int_op(d->c->handshake_state->authenticated_rsa, ==, 1); - tt_int_op(d->c->handshake_state->received_certs_cell, ==, 1); - tt_assert(d->c->handshake_state->certs->id_cert != NULL); - tt_assert(d->c->handshake_state->certs->auth_cert == NULL); + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(d->c->handshake_state->authenticated, OP_EQ, 1); + tt_int_op(d->c->handshake_state->authenticated_rsa, OP_EQ, 1); + tt_int_op(d->c->handshake_state->received_certs_cell, OP_EQ, 1); + tt_ptr_op(d->c->handshake_state->certs->id_cert, OP_NE, NULL); + tt_ptr_op(d->c->handshake_state->certs->auth_cert, OP_EQ, NULL); if (d->is_ed) { - tt_assert(d->c->handshake_state->certs->ed_id_sign != NULL); - tt_assert(d->c->handshake_state->certs->ed_sign_link != NULL); - tt_assert(d->c->handshake_state->certs->ed_sign_auth == NULL); - tt_assert(d->c->handshake_state->certs->ed_rsa_crosscert != NULL); - tt_int_op(d->c->handshake_state->authenticated_ed25519, ==, 1); + tt_ptr_op(d->c->handshake_state->certs->ed_id_sign, OP_NE, NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_sign_link, OP_NE, NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_sign_auth, OP_EQ, NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_rsa_crosscert, OP_NE, NULL); + tt_int_op(d->c->handshake_state->authenticated_ed25519, OP_EQ, 1); } else { - tt_assert(d->c->handshake_state->certs->ed_id_sign == NULL); - tt_assert(d->c->handshake_state->certs->ed_sign_link == NULL); - tt_assert(d->c->handshake_state->certs->ed_sign_auth == NULL); - tt_assert(d->c->handshake_state->certs->ed_rsa_crosscert == NULL); - tt_int_op(d->c->handshake_state->authenticated_ed25519, ==, 0); + tt_ptr_op(d->c->handshake_state->certs->ed_id_sign, OP_EQ, NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_sign_link, OP_EQ, NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_sign_auth, OP_EQ, NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_rsa_crosscert, OP_EQ, NULL); + tt_int_op(d->c->handshake_state->authenticated_ed25519, OP_EQ, 0); } done: @@ -517,17 +517,17 @@ test_link_handshake_recv_certs_ok_server(void *arg) d->c->handshake_state->started_here = 0; d->c->handshake_state->certs->started_here = 0; channel_tls_process_certs_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(d->c->handshake_state->authenticated, ==, 0); - tt_int_op(d->c->handshake_state->received_certs_cell, ==, 1); - tt_assert(d->c->handshake_state->certs->id_cert != NULL); - tt_assert(d->c->handshake_state->certs->link_cert == NULL); + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(d->c->handshake_state->authenticated, OP_EQ, 0); + tt_int_op(d->c->handshake_state->received_certs_cell, OP_EQ, 1); + tt_ptr_op(d->c->handshake_state->certs->id_cert, OP_NE, NULL); + tt_ptr_op(d->c->handshake_state->certs->link_cert, OP_EQ, NULL); if (d->is_ed) { - tt_assert(d->c->handshake_state->certs->ed_sign_auth != NULL); - tt_assert(d->c->handshake_state->certs->auth_cert == NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_sign_auth, OP_NE, NULL); + tt_ptr_op(d->c->handshake_state->certs->auth_cert, OP_EQ, NULL); } else { - tt_assert(d->c->handshake_state->certs->ed_sign_auth == NULL); - tt_assert(d->c->handshake_state->certs->auth_cert != NULL); + tt_ptr_op(d->c->handshake_state->certs->ed_sign_auth, OP_EQ, NULL); + tt_ptr_op(d->c->handshake_state->certs->auth_cert, OP_NE, NULL); } done: @@ -543,11 +543,11 @@ test_link_handshake_recv_certs_ok_server(void *arg) setup_capture_of_logs(LOG_INFO); \ { code ; } \ channel_tls_process_certs_cell(d->cell, d->chan); \ - tt_int_op(1, ==, mock_close_called); \ - tt_int_op(0, ==, mock_send_authenticate_called); \ - tt_int_op(0, ==, mock_send_netinfo_called); \ - tt_int_op(0, ==, d->c->handshake_state->authenticated_rsa); \ - tt_int_op(0, ==, d->c->handshake_state->authenticated_ed25519); \ + tt_int_op(1, OP_EQ, mock_close_called); \ + tt_int_op(0, OP_EQ, mock_send_authenticate_called); \ + tt_int_op(0, OP_EQ, mock_send_netinfo_called); \ + tt_int_op(0, OP_EQ, d->c->handshake_state->authenticated_rsa); \ + tt_int_op(0, OP_EQ, d->c->handshake_state->authenticated_ed25519); \ if (require_failure_message) { \ expect_log_msg_containing(require_failure_message); \ } \ @@ -603,7 +603,7 @@ CERTS_FAIL(truncated_5, /* ed25519 */ const char *msg = certs_cell_check(d->ccell); \ if (msg) puts(msg); \ ssize_t n = certs_cell_encode(d->cell->payload, 4096, d->ccell); \ - tt_int_op(n, >, 0); \ + tt_int_op(n, OP_GT, 0); \ d->cell->payload_len = n; \ } while (0) @@ -686,9 +686,9 @@ test_link_handshake_recv_certs_missing_id(void *arg) /* ed25519 */ /* This handshake succeeds, but since we have no ID cert, we will * just do the RSA handshake. */ channel_tls_process_certs_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(0, ==, d->c->handshake_state->authenticated_ed25519); - tt_int_op(1, ==, d->c->handshake_state->authenticated_rsa); + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(0, OP_EQ, d->c->handshake_state->authenticated_ed25519); + tt_int_op(1, OP_EQ, d->c->handshake_state->authenticated_rsa); done: ; } @@ -697,7 +697,7 @@ CERTS_FAIL(missing_signing_key, /* ed25519 */ require_failure_message = "No Ed25519 signing key"; tt_int_op(certs_cell_getlen_certs(d->ccell), OP_EQ, 5); certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 2); - tt_int_op(cert->cert_type, ==, CERTTYPE_ED_ID_SIGN); + tt_int_op(cert->cert_type, OP_EQ, CERTTYPE_ED_ID_SIGN); /* replace this with a valid master->signing cert, but with no * signing key. */ const ed25519_keypair_t *mk = get_master_identity_keypair(); @@ -905,28 +905,28 @@ test_link_handshake_send_authchallenge(void *arg) crypto_pk_t *rsa0 = pk_generate(0), *rsa1 = pk_generate(1); tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, - rsa0, rsa1, 86400), ==, 0); + rsa0, rsa1, 86400), OP_EQ, 0); init_mock_ed_keys(rsa0); MOCK(connection_or_write_var_cell_to_buf, mock_write_var_cell); - tt_int_op(connection_init_or_handshake_state(c1, 0), ==, 0); + tt_int_op(connection_init_or_handshake_state(c1, 0), OP_EQ, 0); c1->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; - tt_assert(! mock_got_var_cell); - tt_int_op(0, ==, connection_or_send_auth_challenge_cell(c1)); + tt_ptr_op(mock_got_var_cell, OP_EQ, NULL); + tt_int_op(0, OP_EQ, connection_or_send_auth_challenge_cell(c1)); cell1 = mock_got_var_cell; - tt_int_op(0, ==, connection_or_send_auth_challenge_cell(c1)); + tt_int_op(0, OP_EQ, connection_or_send_auth_challenge_cell(c1)); cell2 = mock_got_var_cell; - tt_int_op(38, ==, cell1->payload_len); - tt_int_op(38, ==, cell2->payload_len); - tt_int_op(0, ==, cell1->circ_id); - tt_int_op(0, ==, cell2->circ_id); - tt_int_op(CELL_AUTH_CHALLENGE, ==, cell1->command); - tt_int_op(CELL_AUTH_CHALLENGE, ==, cell2->command); + tt_int_op(38, OP_EQ, cell1->payload_len); + tt_int_op(38, OP_EQ, cell2->payload_len); + tt_int_op(0, OP_EQ, cell1->circ_id); + tt_int_op(0, OP_EQ, cell2->circ_id); + tt_int_op(CELL_AUTH_CHALLENGE, OP_EQ, cell1->command); + tt_int_op(CELL_AUTH_CHALLENGE, OP_EQ, cell2->command); - tt_mem_op("\x00\x02\x00\x01\x00\x03", ==, cell1->payload + 32, 6); - tt_mem_op("\x00\x02\x00\x01\x00\x03", ==, cell2->payload + 32, 6); - tt_mem_op(cell1->payload, !=, cell2->payload, 32); + tt_mem_op("\x00\x02\x00\x01\x00\x03", OP_EQ, cell1->payload + 32, 6); + tt_mem_op("\x00\x02\x00\x01\x00\x03", OP_EQ, cell2->payload + 32, 6); + tt_mem_op(cell1->payload, OP_NE, cell2->payload, 32); done: UNMOCK(connection_or_write_var_cell_to_buf); @@ -974,7 +974,7 @@ recv_authchallenge_setup(const struct testcase_t *test) d->c->base_.address = tor_strdup("HaveAnAddress"); d->c->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; d->chan->conn = d->c; - tt_int_op(connection_init_or_handshake_state(d->c, 1), ==, 0); + tt_int_op(connection_init_or_handshake_state(d->c, 1), OP_EQ, 0); d->c->link_proto = 4; d->c->handshake_state->received_certs_cell = 1; d->cell = var_cell_new(128); @@ -989,9 +989,9 @@ recv_authchallenge_setup(const struct testcase_t *test) MOCK(connection_or_close_for_error, mock_close_for_err); MOCK(connection_or_send_netinfo, mock_send_netinfo); MOCK(connection_or_send_authenticate_cell, mock_send_authenticate); - tt_int_op(0, ==, d->c->handshake_state->received_auth_challenge); - tt_int_op(0, ==, mock_send_authenticate_called); - tt_int_op(0, ==, mock_send_netinfo_called); + tt_int_op(0, OP_EQ, d->c->handshake_state->received_auth_challenge); + tt_int_op(0, OP_EQ, mock_send_authenticate_called); + tt_int_op(0, OP_EQ, mock_send_netinfo_called); return d; done: @@ -1010,11 +1010,11 @@ test_link_handshake_recv_authchallenge_ok(void *arg) authchallenge_data_t *d = arg; channel_tls_process_auth_challenge_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge); - tt_int_op(1, ==, mock_send_authenticate_called); - tt_int_op(1, ==, mock_send_netinfo_called); - tt_int_op(1, ==, mock_send_authenticate_called_with_type); /* RSA */ + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(1, OP_EQ, d->c->handshake_state->received_auth_challenge); + tt_int_op(1, OP_EQ, mock_send_authenticate_called); + tt_int_op(1, OP_EQ, mock_send_netinfo_called); + tt_int_op(1, OP_EQ, mock_send_authenticate_called_with_type); /* RSA */ done: ; } @@ -1029,11 +1029,11 @@ test_link_handshake_recv_authchallenge_ok_ed25519(void *arg) d->cell->payload[39] = 3; d->cell->payload_len += 2; channel_tls_process_auth_challenge_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge); - tt_int_op(1, ==, mock_send_authenticate_called); - tt_int_op(1, ==, mock_send_netinfo_called); - tt_int_op(3, ==, mock_send_authenticate_called_with_type); /* Ed25519 */ + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(1, OP_EQ, d->c->handshake_state->received_auth_challenge); + tt_int_op(1, OP_EQ, mock_send_authenticate_called); + tt_int_op(1, OP_EQ, mock_send_netinfo_called); + tt_int_op(3, OP_EQ, mock_send_authenticate_called_with_type); /* Ed25519 */ done: ; } @@ -1045,10 +1045,10 @@ test_link_handshake_recv_authchallenge_ok_noserver(void *arg) get_options_mutable()->ORPort_set = 0; channel_tls_process_auth_challenge_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge); - tt_int_op(0, ==, mock_send_authenticate_called); - tt_int_op(0, ==, mock_send_netinfo_called); + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(1, OP_EQ, d->c->handshake_state->received_auth_challenge); + tt_int_op(0, OP_EQ, mock_send_authenticate_called); + tt_int_op(0, OP_EQ, mock_send_netinfo_called); done: ; } @@ -1060,10 +1060,10 @@ test_link_handshake_recv_authchallenge_ok_unrecognized(void *arg) d->cell->payload[37] = 99; channel_tls_process_auth_challenge_cell(d->cell, d->chan); - tt_int_op(0, ==, mock_close_called); - tt_int_op(1, ==, d->c->handshake_state->received_auth_challenge); - tt_int_op(0, ==, mock_send_authenticate_called); - tt_int_op(1, ==, mock_send_netinfo_called); + tt_int_op(0, OP_EQ, mock_close_called); + tt_int_op(1, OP_EQ, d->c->handshake_state->received_auth_challenge); + tt_int_op(0, OP_EQ, mock_send_authenticate_called); + tt_int_op(1, OP_EQ, mock_send_netinfo_called); done: ; } @@ -1077,9 +1077,9 @@ test_link_handshake_recv_authchallenge_ok_unrecognized(void *arg) setup_capture_of_logs(LOG_INFO); \ { code ; } \ channel_tls_process_auth_challenge_cell(d->cell, d->chan); \ - tt_int_op(1, ==, mock_close_called); \ - tt_int_op(0, ==, mock_send_authenticate_called); \ - tt_int_op(0, ==, mock_send_netinfo_called); \ + tt_int_op(1, OP_EQ, mock_close_called); \ + tt_int_op(0, OP_EQ, mock_send_authenticate_called); \ + tt_int_op(0, OP_EQ, mock_send_netinfo_called); \ if (require_failure_message) { \ expect_log_msg_containing(require_failure_message); \ } \ @@ -1197,17 +1197,17 @@ authenticate_data_setup(const struct testcase_t *test) d->key1 = pk_generate(2); d->key2 = pk_generate(3); tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, - d->key1, d->key2, 86400), ==, 0); + d->key1, d->key2, 86400), OP_EQ, 0); init_mock_ed_keys(d->key2); d->c1->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; d->c1->link_proto = 3; - tt_int_op(connection_init_or_handshake_state(d->c1, 1), ==, 0); + tt_int_op(connection_init_or_handshake_state(d->c1, 1), OP_EQ, 0); d->c2->base_.state = OR_CONN_STATE_OR_HANDSHAKING_V3; d->c2->link_proto = 3; - tt_int_op(connection_init_or_handshake_state(d->c2, 0), ==, 0); + tt_int_op(connection_init_or_handshake_state(d->c2, 0), OP_EQ, 0); var_cell_t *cell = var_cell_new(16); cell->command = CELL_CERTS; or_handshake_state_record_var_cell(d->c1, d->c1->handshake_state, cell, 1); @@ -1260,7 +1260,7 @@ authenticate_data_setup(const struct testcase_t *test) authtype = AUTHTYPE_ED25519_SHA256_RFC5705; else authtype = AUTHTYPE_RSA_SHA256_TLSSECRET; - tt_int_op(0, ==, connection_or_send_authenticate_cell(d->c1, authtype)); + tt_int_op(0, OP_EQ, connection_or_send_authenticate_cell(d->c1, authtype)); tt_assert(mock_got_var_cell); d->cell = mock_got_var_cell; @@ -1285,65 +1285,65 @@ test_link_handshake_auth_cell(void *arg) crypto_pk_t *auth_pubkey = NULL; /* Is the cell well-formed on the outer layer? */ - tt_int_op(d->cell->command, ==, CELL_AUTHENTICATE); - tt_int_op(d->cell->payload[0], ==, 0); + tt_int_op(d->cell->command, OP_EQ, CELL_AUTHENTICATE); + tt_int_op(d->cell->payload[0], OP_EQ, 0); if (d->is_ed) - tt_int_op(d->cell->payload[1], ==, 3); + tt_int_op(d->cell->payload[1], OP_EQ, 3); else - tt_int_op(d->cell->payload[1], ==, 1); - tt_int_op(ntohs(get_uint16(d->cell->payload + 2)), ==, + tt_int_op(d->cell->payload[1], OP_EQ, 1); + tt_int_op(ntohs(get_uint16(d->cell->payload + 2)), OP_EQ, d->cell->payload_len - 4); /* Check it out for plausibility... */ auth_ctx_t ctx; ctx.is_ed = d->is_ed; - tt_int_op(d->cell->payload_len-4, ==, auth1_parse(&auth1, + tt_int_op(d->cell->payload_len-4, OP_EQ, auth1_parse(&auth1, d->cell->payload+4, d->cell->payload_len - 4, &ctx)); tt_assert(auth1); if (d->is_ed) { - tt_mem_op(auth1->type, ==, "AUTH0003", 8); + tt_mem_op(auth1->type, OP_EQ, "AUTH0003", 8); } else { - tt_mem_op(auth1->type, ==, "AUTH0001", 8); + tt_mem_op(auth1->type, OP_EQ, "AUTH0001", 8); } - tt_mem_op(auth1->tlssecrets, ==, "int getRandomNumber(){return 4;}", 32); + tt_mem_op(auth1->tlssecrets, OP_EQ, "int getRandomNumber(){return 4;}", 32); /* Is the signature okay? */ const uint8_t *start = d->cell->payload+4, *end = auth1->end_of_signed; if (d->is_ed) { ed25519_signature_t sig; - tt_int_op(auth1_getlen_sig(auth1), ==, ED25519_SIG_LEN); + tt_int_op(auth1_getlen_sig(auth1), OP_EQ, ED25519_SIG_LEN); memcpy(&sig.sig, auth1_getarray_sig(auth1), ED25519_SIG_LEN); tt_assert(!ed25519_checksig(&sig, start, end-start, &get_current_auth_keypair()->pubkey)); } else { uint8_t sig[128]; uint8_t digest[32]; - tt_int_op(auth1_getlen_sig(auth1), >, 120); + tt_int_op(auth1_getlen_sig(auth1), OP_GT, 120); auth_pubkey = tor_tls_cert_get_key( d->c2->handshake_state->certs->auth_cert); int n = crypto_pk_public_checksig( auth_pubkey, (char*)sig, sizeof(sig), (char*)auth1_getarray_sig(auth1), auth1_getlen_sig(auth1)); - tt_int_op(n, ==, 32); + tt_int_op(n, OP_EQ, 32); crypto_digest256((char*)digest, (const char*)start, end-start, DIGEST_SHA256); - tt_mem_op(sig, ==, digest, 32); + tt_mem_op(sig, OP_EQ, digest, 32); } /* Then feed it to c2. */ - tt_int_op(d->c2->handshake_state->authenticated, ==, 0); + tt_int_op(d->c2->handshake_state->authenticated, OP_EQ, 0); channel_tls_process_authenticate_cell(d->cell, d->chan2); - tt_int_op(mock_close_called, ==, 0); - tt_int_op(d->c2->handshake_state->authenticated, ==, 1); + tt_int_op(mock_close_called, OP_EQ, 0); + tt_int_op(d->c2->handshake_state->authenticated, OP_EQ, 1); if (d->is_ed) { - tt_int_op(d->c2->handshake_state->authenticated_ed25519, ==, 1); - tt_int_op(d->c2->handshake_state->authenticated_rsa, ==, 1); + tt_int_op(d->c2->handshake_state->authenticated_ed25519, OP_EQ, 1); + tt_int_op(d->c2->handshake_state->authenticated_rsa, OP_EQ, 1); } else { - tt_int_op(d->c2->handshake_state->authenticated_ed25519, ==, 0); - tt_int_op(d->c2->handshake_state->authenticated_rsa, ==, 1); + tt_int_op(d->c2->handshake_state->authenticated_ed25519, OP_EQ, 0); + tt_int_op(d->c2->handshake_state->authenticated_rsa, OP_EQ, 1); } done: @@ -1359,10 +1359,10 @@ test_link_handshake_auth_cell(void *arg) const char *require_failure_message = NULL; \ setup_capture_of_logs(LOG_INFO); \ { code ; } \ - tt_int_op(d->c2->handshake_state->authenticated, ==, 0); \ + tt_int_op(d->c2->handshake_state->authenticated, OP_EQ, 0); \ channel_tls_process_authenticate_cell(d->cell, d->chan2); \ - tt_int_op(mock_close_called, ==, 1); \ - tt_int_op(d->c2->handshake_state->authenticated, ==, 0); \ + tt_int_op(mock_close_called, OP_EQ, 1); \ + tt_int_op(d->c2->handshake_state->authenticated, OP_EQ, 0); \ if (require_failure_message) { \ expect_log_msg_containing(require_failure_message); \ } \ @@ -1390,8 +1390,8 @@ test_link_handshake_auth_already_authenticated(void *arg) setup_capture_of_logs(LOG_INFO); d->c2->handshake_state->authenticated = 1; channel_tls_process_authenticate_cell(d->cell, d->chan2); - tt_int_op(mock_close_called, ==, 1); - tt_int_op(d->c2->handshake_state->authenticated, ==, 1); + tt_int_op(mock_close_called, OP_EQ, 1); + tt_int_op(d->c2->handshake_state->authenticated, OP_EQ, 1); expect_log_msg_containing("The peer is already authenticated"); done: teardown_capture_of_logs(); @@ -1425,7 +1425,7 @@ AUTHENTICATE_FAIL(truncated_2, d->cell->payload[3]++) AUTHENTICATE_FAIL(tooshort_1, require_failure_message = "Authenticator was too short"; - tt_int_op(d->cell->payload_len, >=, 260); + tt_int_op(d->cell->payload_len, OP_GE, 260); d->cell->payload[2] -= 1; d->cell->payload_len -= 256;) AUTHENTICATE_FAIL(badcontent, diff --git a/src/test/test_logging.c b/src/test/test_logging.c index 94b3e4ea68..e373158e34 100644 --- a/src/test/test_logging.c +++ b/src/test/test_logging.c @@ -107,7 +107,7 @@ test_sigsafe_err(void *arg) close(STDERR_FILENO); content = read_file_to_str(fn, 0, NULL); - tt_assert(content != NULL); + tt_ptr_op(content, OP_NE, NULL); tor_split_lines(lines, content, (int)strlen(content)); tt_int_op(smartlist_len(lines), OP_GE, 5); @@ -140,7 +140,7 @@ test_ratelim(void *arg) char *msg = NULL; msg = rate_limit_log(&ten_min, now); - tt_assert(msg != NULL); + tt_ptr_op(msg, OP_NE, NULL); tt_str_op(msg, OP_EQ, ""); /* nothing was suppressed. */ tt_int_op(ten_min.last_allowed, OP_EQ, now); @@ -150,14 +150,14 @@ test_ratelim(void *arg) for (i = 0; i < 9; ++i) { now += 60; /* one minute has passed. */ msg = rate_limit_log(&ten_min, now); - tt_assert(msg == NULL); + tt_ptr_op(msg, OP_EQ, NULL); tt_int_op(ten_min.last_allowed, OP_EQ, start); tt_int_op(ten_min.n_calls_since_last_time, OP_EQ, i + 1); } now += 240; /* Okay, we can be done. */ msg = rate_limit_log(&ten_min, now); - tt_assert(msg != NULL); + tt_ptr_op(msg, OP_NE, NULL); tt_str_op(msg, OP_EQ, " [9 similar message(s) suppressed in last 600 seconds]"); done: diff --git a/src/test/test_microdesc.c b/src/test/test_microdesc.c index c78fda3b69..4f0ecd778b 100644 --- a/src/test/test_microdesc.c +++ b/src/test/test_microdesc.c @@ -19,7 +19,7 @@ #include <direct.h> #else #include <dirent.h> -#endif +#endif /* defined(_WIN32) */ static const char test_md1[] = "onion-key\n" @@ -464,7 +464,7 @@ test_md_generate(void *arg) microdesc_free(md); md = NULL; md = dirvote_create_microdescriptor(ri, 21); - tt_str_op(md->body, ==, test_md_18); + tt_str_op(md->body, OP_EQ, test_md_18); routerinfo_free(ri); ri = router_parse_entry_from_string(test_ri2, NULL, 0, 0, NULL, NULL); @@ -472,12 +472,12 @@ test_md_generate(void *arg) microdesc_free(md); md = NULL; md = dirvote_create_microdescriptor(ri, 18); - tt_str_op(md->body, ==, test_md2_18); + tt_str_op(md->body, OP_EQ, test_md2_18); microdesc_free(md); md = NULL; md = dirvote_create_microdescriptor(ri, 21); - tt_str_op(md->body, ==, test_md2_21); + tt_str_op(md->body, OP_EQ, test_md2_21); tt_assert(ed25519_pubkey_eq(md->ed25519_identity_pkey, &ri->cache_info.signing_key_cert->signing_key)); @@ -823,14 +823,14 @@ test_md_corrupt_desc(void *arg) "@last-listed 2015-06-22 10:00:00\n" "onion-k\n", NULL, SAVED_IN_JOURNAL, 0, time(NULL), NULL); - tt_int_op(smartlist_len(sl), ==, 0); + tt_int_op(smartlist_len(sl), OP_EQ, 0); smartlist_free(sl); sl = microdescs_add_to_cache(get_microdesc_cache(), "@last-listed 2015-06-22 10:00:00\n" "wiggly\n", NULL, SAVED_IN_JOURNAL, 0, time(NULL), NULL); - tt_int_op(smartlist_len(sl), ==, 0); + tt_int_op(smartlist_len(sl), OP_EQ, 0); smartlist_free(sl); tor_asprintf(&cp, "%s\n%s", test_md1, "@foobar\nonion-wobble\n"); @@ -838,7 +838,7 @@ test_md_corrupt_desc(void *arg) sl = microdescs_add_to_cache(get_microdesc_cache(), cp, cp+strlen(cp), SAVED_IN_JOURNAL, 0, time(NULL), NULL); - tt_int_op(smartlist_len(sl), ==, 0); + tt_int_op(smartlist_len(sl), OP_EQ, 0); done: tor_free(cp); diff --git a/src/test/test_nodelist.c b/src/test/test_nodelist.c index 256354415c..a873003d72 100644 --- a/src/test/test_nodelist.c +++ b/src/test/test_nodelist.c @@ -7,7 +7,9 @@ **/ #include "or.h" +#include "networkstatus.h" #include "nodelist.h" +#include "torcert.h" #include "test.h" /** Test the case when node_get_by_id() returns NULL, @@ -100,6 +102,107 @@ test_nodelist_node_is_dir(void *arg) return; } +static networkstatus_t *dummy_ns = NULL; +static networkstatus_t * +mock_networkstatus_get_latest_consensus(void) +{ + return dummy_ns; +} +static networkstatus_t * +mock_networkstatus_get_latest_consensus_by_flavor(consensus_flavor_t f) +{ + tor_assert(f == FLAV_MICRODESC); + return dummy_ns; +} + +static void +test_nodelist_ed_id(void *arg) +{ + routerstatus_t *rs[4]; + microdesc_t *md[4]; + routerinfo_t *ri[4]; + networkstatus_t *ns; + int i; + (void)arg; + + ns = tor_malloc_zero(sizeof(networkstatus_t)); + ns->flavor = FLAV_MICRODESC; + ns->routerstatus_list = smartlist_new(); + dummy_ns = ns; + MOCK(networkstatus_get_latest_consensus, + mock_networkstatus_get_latest_consensus); + MOCK(networkstatus_get_latest_consensus_by_flavor, + mock_networkstatus_get_latest_consensus_by_flavor); + + /* Make a bunch of dummy objects that we can play around with. Only set the + necessary fields */ + + for (i = 0; i < 4; ++i) { + rs[i] = tor_malloc_zero(sizeof(*rs[i])); + md[i] = tor_malloc_zero(sizeof(*md[i])); + ri[i] = tor_malloc_zero(sizeof(*ri[i])); + + crypto_rand(md[i]->digest, sizeof(md[i]->digest)); + md[i]->ed25519_identity_pkey = tor_malloc(sizeof(ed25519_public_key_t)); + crypto_rand((char*)md[i]->ed25519_identity_pkey, + sizeof(ed25519_public_key_t)); + crypto_rand(rs[i]->identity_digest, sizeof(rs[i]->identity_digest)); + memcpy(ri[i]->cache_info.identity_digest, rs[i]->identity_digest, + DIGEST_LEN); + memcpy(rs[i]->descriptor_digest, md[i]->digest, DIGEST256_LEN); + ri[i]->cache_info.signing_key_cert = tor_malloc_zero(sizeof(tor_cert_t)); + memcpy(&ri[i]->cache_info.signing_key_cert->signing_key, + md[i]->ed25519_identity_pkey, sizeof(ed25519_public_key_t)); + + if (i != 3) + smartlist_add(ns->routerstatus_list, rs[i]); + } + + tt_int_op(0, OP_EQ, smartlist_len(nodelist_get_list())); + + nodelist_set_consensus(ns); + + tt_int_op(3, OP_EQ, smartlist_len(nodelist_get_list())); + + /* No Ed25519 info yet, so nothing has an ED id. */ + tt_ptr_op(NULL, OP_EQ, node_get_by_ed25519_id(md[0]->ed25519_identity_pkey)); + + /* Register the first one by md, then look it up. */ + node_t *n = nodelist_add_microdesc(md[0]); + tt_ptr_op(n, OP_EQ, node_get_by_ed25519_id(md[0]->ed25519_identity_pkey)); + + /* Register the second by ri, then look it up. */ + routerinfo_t *ri_old = NULL; + n = nodelist_set_routerinfo(ri[1], &ri_old); + tt_ptr_op(n, OP_EQ, node_get_by_ed25519_id(md[1]->ed25519_identity_pkey)); + tt_ptr_op(ri_old, OP_EQ, NULL); + + /* Register it by md too. */ + node_t *n2 = nodelist_add_microdesc(md[1]); + tt_ptr_op(n2, OP_EQ, n); + tt_ptr_op(n, OP_EQ, node_get_by_ed25519_id(md[1]->ed25519_identity_pkey)); + + /* Register the 4th by ri only -- we never put it into the networkstatus, + * so it has to be independent */ + n = nodelist_set_routerinfo(ri[3], &ri_old); + tt_ptr_op(n, OP_EQ, node_get_by_ed25519_id(md[3]->ed25519_identity_pkey)); + tt_ptr_op(ri_old, OP_EQ, NULL); + tt_int_op(4, OP_EQ, smartlist_len(nodelist_get_list())); + + done: + for (i = 0; i < 4; ++i) { + tor_free(rs[i]); + tor_free(md[i]->ed25519_identity_pkey); + tor_free(md[i]); + tor_free(ri[i]->cache_info.signing_key_cert); + tor_free(ri[i]); + } + smartlist_clear(ns->routerstatus_list); + networkstatus_vote_free(ns); + UNMOCK(networkstatus_get_latest_consensus); + UNMOCK(networkstatus_get_latest_consensus_by_flavor); +} + #define NODE(name, flags) \ { #name, test_nodelist_##name, (flags), NULL, NULL } @@ -107,6 +210,7 @@ struct testcase_t nodelist_tests[] = { NODE(node_get_verbose_nickname_by_id_null_node, TT_FORK), NODE(node_get_verbose_nickname_not_named, TT_FORK), NODE(node_is_dir, TT_FORK), + NODE(ed_id, TT_FORK), END_OF_TESTCASES }; diff --git a/src/test/test_oom.c b/src/test/test_oom.c index f03a504d1d..cf28690a28 100644 --- a/src/test/test_oom.c +++ b/src/test/test_oom.c @@ -67,7 +67,7 @@ add_bytes_to_buf(buf_t *buf, size_t n_bytes) while (n_bytes) { size_t this_add = n_bytes > sizeof(b) ? sizeof(b) : n_bytes; crypto_rand(b, this_add); - write_to_buf(b, this_add, buf); + buf_add(buf, b, this_add); n_bytes -= this_add; } } diff --git a/src/test/test_oos.c b/src/test/test_oos.c index 9fd6bce5ae..e72fcf5de9 100644 --- a/src/test/test_oos.c +++ b/src/test/test_oos.c @@ -52,7 +52,7 @@ kill_conn_list_mock(smartlist_t *conns) { ++kill_conn_list_calls; - tt_assert(conns != NULL); + tt_ptr_op(conns, OP_NE, NULL); kill_conn_list_killed += smartlist_len(conns); @@ -248,7 +248,7 @@ close_for_error_mock(or_connection_t *orconn, int flush) { (void)flush; - tt_assert(orconn != NULL); + tt_ptr_op(orconn, OP_NE, NULL); ++cfe_calls; done: @@ -264,7 +264,7 @@ mark_for_close_oos_mock(connection_t *conn, (void)line; (void)file; - tt_assert(conn != NULL); + tt_ptr_op(conn, OP_NE, NULL); ++mark_calls; done: @@ -298,8 +298,8 @@ test_oos_kill_conn_list(void *arg) dir_c2->base_.purpose = DIR_PURPOSE_MIN_; c2 = TO_CONN(dir_c2); - tt_assert(c1 != NULL); - tt_assert(c2 != NULL); + tt_ptr_op(c1, OP_NE, NULL); + tt_ptr_op(c2, OP_NE, NULL); /* Make list */ l = smartlist_new(); @@ -345,7 +345,7 @@ get_num_circuits_mock(or_connection_t *conn) { int circs = 0; - tt_assert(conn != NULL); + tt_ptr_op(conn, OP_NE, NULL); if (conns_with_circs && smartlist_contains(conns_with_circs, TO_CONN(conn))) { @@ -397,7 +397,7 @@ test_oos_pick_oos_victims(void *arg) /* Try picking one */ picked = pick_oos_victims(1); /* It should be the one with circuits */ - tt_assert(picked != NULL); + tt_ptr_op(picked, OP_NE, NULL); tt_int_op(smartlist_len(picked), OP_EQ, 1); tt_assert(smartlist_contains(picked, smartlist_get(conns_for_mock, 0))); smartlist_free(picked); @@ -405,14 +405,14 @@ test_oos_pick_oos_victims(void *arg) /* Try picking none */ picked = pick_oos_victims(0); /* We should get an empty list */ - tt_assert(picked != NULL); + tt_ptr_op(picked, OP_NE, NULL); tt_int_op(smartlist_len(picked), OP_EQ, 0); smartlist_free(picked); /* Try picking two */ picked = pick_oos_victims(2); /* We should get both active orconns */ - tt_assert(picked != NULL); + tt_ptr_op(picked, OP_NE, NULL); tt_int_op(smartlist_len(picked), OP_EQ, 2); tt_assert(smartlist_contains(picked, smartlist_get(conns_for_mock, 0))); tt_assert(smartlist_contains(picked, smartlist_get(conns_for_mock, 1))); diff --git a/src/test/test_options.c b/src/test/test_options.c index ad735b72a6..62732cabf7 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -282,7 +282,7 @@ test_have_enough_mem_for_dircache(void *arg) or_options_t *opt=NULL; or_options_t *dflt=NULL; config_line_t *cl=NULL; - char *msg=NULL;; + char *msg=NULL; int r; const char *configuration = "ORPort 8080\nDirCache 1", *expect_errmsg; @@ -299,7 +299,7 @@ test_have_enough_mem_for_dircache(void *arg) /* 300 MB RAM available, DirCache enabled */ r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg); tt_int_op(r, OP_EQ, 0); - tt_assert(!msg); + tt_ptr_op(msg, OP_EQ, NULL); /* 200 MB RAM available, DirCache enabled */ r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg); @@ -322,7 +322,7 @@ test_have_enough_mem_for_dircache(void *arg) /* 300 MB RAM available, DirCache enabled, Bridge */ r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg); tt_int_op(r, OP_EQ, 0); - tt_assert(!msg); + tt_ptr_op(msg, OP_EQ, NULL); /* 200 MB RAM available, DirCache enabled, Bridge */ r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg); @@ -345,7 +345,7 @@ test_have_enough_mem_for_dircache(void *arg) /* 200 MB RAM available, DirCache disabled */ r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg); tt_int_op(r, OP_EQ, 0); - tt_assert(!msg); + tt_ptr_op(msg, OP_EQ, NULL); /* 300 MB RAM available, DirCache disabled */ r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg); @@ -398,12 +398,12 @@ fixed_get_uname(void) "V3AuthVoteDelay 20\n" \ "V3AuthDistDelay 20\n" \ "V3AuthNIntervalsValid 3\n" \ - "ClientUseIPv4 1\n" \ + "ClientUseIPv4 1\n" \ "VirtualAddrNetworkIPv4 127.192.0.0/10\n" \ "VirtualAddrNetworkIPv6 [FE80::]/10\n" \ - "SchedulerHighWaterMark__ 42\n" \ - "SchedulerLowWaterMark__ 10\n" \ - "UseEntryGuards 1\n" + "UseEntryGuards 1\n" \ + "Schedulers Vanilla\n" \ + "ClientDNSRejectInternalAddresses 1\n" typedef struct { or_options_t *old_opt; @@ -430,24 +430,24 @@ get_options_test_data(const char *conf) result->opt->ConnectionPadding = -1; // default must be "auto" rv = config_get_lines(conf, &cl, 1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); rv = config_assign(&options_format, result->opt, cl, 0, &msg); if (msg) { /* Display the parse error message by comparing it with an empty string */ tt_str_op(msg, OP_EQ, ""); } - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); config_free_lines(cl); result->opt->LogTimeGranularity = 1; result->opt->TokenBucketRefillInterval = 1; rv = config_get_lines(TEST_OPTIONS_OLD_VALUES, &cl, 1); - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); rv = config_assign(&options_format, result->def_opt, cl, 0, &msg); if (msg) { /* Display the parse error message by comparing it with an empty string */ tt_str_op(msg, OP_EQ, ""); } - tt_assert(rv == 0); + tt_int_op(rv, OP_EQ, 0); done: config_free_lines(cl); @@ -507,7 +507,7 @@ test_options_validate__uname_for_server(void *ignored) fixed_get_uname_result = "Windows 2000"; mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - expect_log_entry(); + expect_no_log_entry(); tor_free(msg); done: @@ -528,6 +528,8 @@ test_options_validate__outbound_addresses(void *ignored) ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Multiple outbound bind addresses configured: " + "xxyy!!!sdfaf"); done: free_options_test_data(tdata); @@ -591,13 +593,14 @@ test_options_validate__nickname(void *ignored) tdata = get_options_test_data("Nickname AMoreValidNick"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - tt_assert(!msg); + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); + tor_free(msg); free_options_test_data(tdata); tdata = get_options_test_data("DataDirectory /tmp/somewhere"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - tt_assert(!msg); + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); done: free_options_test_data(tdata); @@ -657,6 +660,7 @@ test_options_validate__logs(void *ignored) tt_str_op(tdata->opt->Logs->key, OP_EQ, "Log"); tt_str_op(tdata->opt->Logs->value, OP_EQ, "notice stdout"); tor_free(msg); + tt_int_op(ret, OP_EQ, -1); free_options_test_data(tdata); tdata = get_options_test_data(""); @@ -667,6 +671,7 @@ test_options_validate__logs(void *ignored) tt_str_op(tdata->opt->Logs->key, OP_EQ, "Log"); tt_str_op(tdata->opt->Logs->value, OP_EQ, "warn stdout"); tor_free(msg); + tt_int_op(ret, OP_EQ, -1); free_options_test_data(tdata); tdata = get_options_test_data(""); @@ -676,6 +681,7 @@ test_options_validate__logs(void *ignored) ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_assert(!tdata->opt->Logs); tor_free(msg); + tt_int_op(ret, OP_EQ, -1); free_options_test_data(tdata); tdata = get_options_test_data(""); @@ -684,6 +690,7 @@ test_options_validate__logs(void *ignored) ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 1, &msg); tt_assert(!tdata->opt->Logs); tor_free(msg); + tt_int_op(ret, OP_EQ, -1); free_options_test_data(tdata); tdata = get_options_test_data(""); @@ -692,6 +699,7 @@ test_options_validate__logs(void *ignored) ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_assert(!tdata->opt->Logs); tor_free(msg); + tt_int_op(ret, OP_EQ, -1); free_options_test_data(tdata); tdata = get_options_test_data(""); @@ -701,6 +709,7 @@ test_options_validate__logs(void *ignored) tdata->opt->Logs = cl; ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op((intptr_t)tdata->opt->Logs, OP_EQ, (intptr_t)cl); + tt_int_op(ret, OP_EQ, -1); done: quiet_level = orig_quiet_level; @@ -747,13 +756,13 @@ test_options_validate__authdir(void *ignored) mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - tt_assert(!msg); + tt_str_op(msg, OP_EQ, "Authoritative directory servers must set " + "ContactInfo"); + tor_free(msg); free_options_test_data(tdata); tdata = get_options_test_data("AuthoritativeDirectory 1\n" - "Address 100.200.10.1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "Address 100.200.10.1\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -764,9 +773,7 @@ test_options_validate__authdir(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" - "TestingTorNetwork 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "TestingTorNetwork 1\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -777,9 +784,7 @@ test_options_validate__authdir(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -791,9 +796,7 @@ test_options_validate__authdir(void *ignored) tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" "RecommendedVersions 1.2, 3.14\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(tdata->opt->RecommendedClientVersions->value, OP_EQ, "1.2, 3.14"); @@ -806,9 +809,7 @@ test_options_validate__authdir(void *ignored) "RecommendedVersions 1.2, 3.14\n" "RecommendedClientVersions 25\n" "RecommendedServerVersions 4.18\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(tdata->opt->RecommendedClientVersions->value, OP_EQ, "25"); @@ -822,9 +823,7 @@ test_options_validate__authdir(void *ignored) "RecommendedVersions 1.2, 3.14\n" "RecommendedClientVersions 25\n" "RecommendedServerVersions 4.18\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, "AuthoritativeDir is set, but none of (Bridge/V3)" @@ -836,9 +835,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "VersioningAuthoritativeDirectory 1\n" "RecommendedServerVersions 4.18\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, "Versioning authoritative dir servers must set " @@ -850,9 +847,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "VersioningAuthoritativeDirectory 1\n" "RecommendedClientVersions 4.18\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, "Versioning authoritative dir servers must set " @@ -863,9 +858,7 @@ test_options_validate__authdir(void *ignored) tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" "UseEntryGuards 1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); expect_log_msg("Authoritative directory servers " @@ -877,9 +870,7 @@ test_options_validate__authdir(void *ignored) tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" "V3AuthoritativeDir 1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); expect_log_msg("Authoritative directories always try" @@ -892,9 +883,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "DownloadExtraInfo 1\n" "V3AuthoritativeDir 1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); expect_no_log_msg("Authoritative directories always try" @@ -905,9 +894,7 @@ test_options_validate__authdir(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, "AuthoritativeDir is set, but none of (Bridge/V3)" @@ -919,9 +906,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "BridgeAuthoritativeDir 1\n" "ContactInfo hello@hello.com\n" - "V3BandwidthsFile non-existant-file\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "V3BandwidthsFile non-existant-file\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, @@ -933,9 +918,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "BridgeAuthoritativeDir 1\n" "ContactInfo hello@hello.com\n" - "V3BandwidthsFile non-existant-file\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "V3BandwidthsFile non-existant-file\n"); mock_clean_saved_logs(); options_validate(NULL, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, @@ -947,9 +930,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "BridgeAuthoritativeDir 1\n" "ContactInfo hello@hello.com\n" - "GuardfractionFile non-existant-file\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "GuardfractionFile non-existant-file\n"); mock_clean_saved_logs(); options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, @@ -961,9 +942,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "BridgeAuthoritativeDir 1\n" "ContactInfo hello@hello.com\n" - "GuardfractionFile non-existant-file\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "GuardfractionFile non-existant-file\n"); mock_clean_saved_logs(); options_validate(NULL, tdata->opt, tdata->def_opt, 0, &msg); tt_str_op(msg, OP_EQ, @@ -974,9 +953,7 @@ test_options_validate__authdir(void *ignored) tdata = get_options_test_data("AuthoritativeDirectory 1\n" "Address 100.200.10.1\n" "BridgeAuthoritativeDir 1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -989,9 +966,7 @@ test_options_validate__authdir(void *ignored) "Address 100.200.10.1\n" "DirPort 999\n" "BridgeAuthoritativeDir 1\n" - "ContactInfo hello@hello.com\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ContactInfo hello@hello.com\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1008,9 +983,7 @@ test_options_validate__authdir(void *ignored) /* "ORPort 888\n" */ /* "ClientOnly 1\n" */ /* "BridgeAuthoritativeDir 1\n" */ - /* "ContactInfo hello@hello.com\n" */ - /* "SchedulerHighWaterMark__ 42\n" */ - /* "SchedulerLowWaterMark__ 10\n"); */ + /* "ContactInfo hello@hello.com\n" ); */ /* mock_clean_saved_logs(); */ /* ret = options_validate(tdata->old_opt, tdata->opt, */ /* tdata->def_opt, 0, &msg); */ @@ -1103,7 +1076,7 @@ test_options_validate__transproxy(void *ignored) tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_PF_DIVERT); tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without " "any valid TransPort."); -#endif +#endif /* !defined(OpenBSD) && !defined( DARWIN ) */ tor_free(msg); // Test tproxy trans proxy @@ -1118,7 +1091,7 @@ test_options_validate__transproxy(void *ignored) tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_TPROXY); tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without any valid " "TransPort."); -#endif +#endif /* !defined(__linux__) */ tor_free(msg); // Test ipfw trans proxy @@ -1134,7 +1107,7 @@ test_options_validate__transproxy(void *ignored) tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_IPFW); tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without any valid " "TransPort."); -#endif +#endif /* !defined(KERNEL_MAY_SUPPORT_IPFW) */ tor_free(msg); // Test unknown trans proxy @@ -1154,46 +1127,41 @@ test_options_validate__transproxy(void *ignored) "TransPort 127.0.0.1:123\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - if (msg) { - TT_DIE(("Expected NULL but got '%s'", msg)); - } + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); #elif defined(KERNEL_MAY_SUPPORT_IPFW) tdata = get_options_test_data("TransProxyType ipfw\n" "TransPort 127.0.0.1:123\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - if (msg) { - TT_DIE(("Expected NULL but got '%s'", msg)); - } + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); + tor_free(msg); #elif defined(OpenBSD) tdata = get_options_test_data("TransProxyType pf-divert\n" "TransPort 127.0.0.1:123\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - if (msg) { - TT_DIE(("Expected NULL but got '%s'", msg)); - } + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); + tor_free(msg); #elif defined(__NetBSD__) tdata = get_options_test_data("TransProxyType default\n" "TransPort 127.0.0.1:123\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); - if (msg) { - TT_DIE(("Expected NULL but got '%s'", msg)); - } -#endif + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); + tor_free(msg); +#endif /* defined(__linux__) || ... */ // Assert that a test has run for some TransProxyType tt_assert(tdata); -#else +#else /* !(defined(USE_TRANSPARENT)) */ tdata = get_options_test_data("TransPort 127.0.0.1:555\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); tt_str_op(msg, OP_EQ, "TransPort is disabled in this build."); tor_free(msg); -#endif +#endif /* defined(USE_TRANSPARENT) */ done: free_options_test_data(tdata); @@ -1258,9 +1226,7 @@ test_options_validate__exclude_nodes(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("ExcludeNodes {cn}\n" - "StrictNodes 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "StrictNodes 1\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1271,9 +1237,7 @@ test_options_validate__exclude_nodes(void *ignored) tor_free(msg); free_options_test_data(tdata); - tdata = get_options_test_data("ExcludeNodes {cn}\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + tdata = get_options_test_data("ExcludeNodes {cn}\n"); mock_clean_saved_logs(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1291,49 +1255,6 @@ test_options_validate__exclude_nodes(void *ignored) } static void -test_options_validate__scheduler(void *ignored) -{ - (void)ignored; - int ret; - char *msg; - setup_capture_of_logs(LOG_DEBUG); - options_test_data_t *tdata = get_options_test_data( - "SchedulerLowWaterMark__ 0\n"); - - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - expect_log_msg("Bad SchedulerLowWaterMark__ option\n"); - tor_free(msg); - - // TODO: this test cannot run on platforms where UINT32_MAX == UINT64_MAX. - // I suspect it's unlikely this branch can actually happen - /* free_options_test_data(tdata); */ - /* tdata = get_options_test_data( */ - /* "SchedulerLowWaterMark 10000000000000000000\n"); */ - /* tdata->opt->SchedulerLowWaterMark__ = (uint64_t)UINT32_MAX; */ - /* tdata->opt->SchedulerLowWaterMark__++; */ - /* mock_clean_saved_logs(); */ - /* ret = options_validate(tdata->old_opt, tdata->opt, */ - /* tdata->def_opt, 0, &msg); */ - /* tt_int_op(ret, OP_EQ, -1); */ - /* expect_log_msg("Bad SchedulerLowWaterMark__ option\n"); */ - - free_options_test_data(tdata); - tdata = get_options_test_data("SchedulerLowWaterMark__ 42\n" - "SchedulerHighWaterMark__ 42\n"); - mock_clean_saved_logs(); - ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); - tt_int_op(ret, OP_EQ, -1); - expect_log_msg("Bad SchedulerHighWaterMark option\n"); - tor_free(msg); - - done: - teardown_capture_of_logs(); - free_options_test_data(tdata); - tor_free(msg); -} - -static void test_options_validate__node_families(void *ignored) { (void)ignored; @@ -1341,9 +1262,7 @@ test_options_validate__node_families(void *ignored) char *msg; options_test_data_t *tdata = get_options_test_data( "NodeFamily flux, flax\n" - "NodeFamily somewhere\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "NodeFamily somewhere\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1361,8 +1280,7 @@ test_options_validate__node_families(void *ignored) tor_free(msg); free_options_test_data(tdata); - tdata = get_options_test_data("SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + tdata = get_options_test_data(""); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1370,9 +1288,7 @@ test_options_validate__node_families(void *ignored) tor_free(msg); free_options_test_data(tdata); - tdata = get_options_test_data("NodeFamily !flux\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + tdata = get_options_test_data("NodeFamily !flux\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1421,9 +1337,7 @@ test_options_validate__recommended_packages(void *ignored) setup_capture_of_logs(LOG_WARN); options_test_data_t *tdata = get_options_test_data( "RecommendedPackages foo 1.2 http://foo.com sha1=123123123123\n" - "RecommendedPackages invalid-package-line\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "RecommendedPackages invalid-package-line\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1445,9 +1359,7 @@ test_options_validate__fetch_dir(void *ignored) char *msg; options_test_data_t *tdata = get_options_test_data( "FetchDirInfoExtraEarly 1\n" - "FetchDirInfoEarly 0\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "FetchDirInfoEarly 0\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1457,9 +1369,7 @@ test_options_validate__fetch_dir(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("FetchDirInfoExtraEarly 1\n" - "FetchDirInfoEarly 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "FetchDirInfoEarly 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1479,9 +1389,7 @@ test_options_validate__conn_limit(void *ignored) int ret; char *msg; options_test_data_t *tdata = get_options_test_data( - "ConnLimit 0\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 0\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1489,9 +1397,7 @@ test_options_validate__conn_limit(void *ignored) tor_free(msg); free_options_test_data(tdata); - tdata = get_options_test_data("ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + tdata = get_options_test_data("ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1513,9 +1419,7 @@ test_options_validate__paths_needed(void *ignored) setup_capture_of_logs(LOG_WARN); options_test_data_t *tdata = get_options_test_data( "PathsNeededToBuildCircuits 0.1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1528,9 +1432,7 @@ test_options_validate__paths_needed(void *ignored) free_options_test_data(tdata); mock_clean_saved_logs(); tdata = get_options_test_data("PathsNeededToBuildCircuits 0.99\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1543,9 +1445,7 @@ test_options_validate__paths_needed(void *ignored) free_options_test_data(tdata); mock_clean_saved_logs(); tdata = get_options_test_data("PathsNeededToBuildCircuits 0.91\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1568,9 +1468,7 @@ test_options_validate__max_client_circuits(void *ignored) char *msg; options_test_data_t *tdata = get_options_test_data( "MaxClientCircuitsPending 0\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1580,9 +1478,7 @@ test_options_validate__max_client_circuits(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("MaxClientCircuitsPending 1025\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1592,9 +1488,7 @@ test_options_validate__max_client_circuits(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1615,9 +1509,7 @@ test_options_validate__ports(void *ignored) options_test_data_t *tdata = get_options_test_data( "FirewallPorts 65537\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1628,9 +1520,7 @@ test_options_validate__ports(void *ignored) tdata = get_options_test_data("FirewallPorts 1\n" "LongLivedPorts 124444\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1642,9 +1532,7 @@ test_options_validate__ports(void *ignored) "LongLivedPorts 2\n" "RejectPlaintextPorts 112233\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1657,9 +1545,7 @@ test_options_validate__ports(void *ignored) "RejectPlaintextPorts 3\n" "WarnPlaintextPorts 65536\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1672,9 +1558,7 @@ test_options_validate__ports(void *ignored) "RejectPlaintextPorts 3\n" "WarnPlaintextPorts 4\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1696,9 +1580,7 @@ test_options_validate__reachable_addresses(void *ignored) options_test_data_t *tdata = get_options_test_data( "FascistFirewall 1\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1716,9 +1598,7 @@ test_options_validate__reachable_addresses(void *ignored) "ReachableDirAddresses *:81\n" "ReachableORAddresses *:444\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); tdata->opt->FirewallPorts = smartlist_new(); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1732,9 +1612,7 @@ test_options_validate__reachable_addresses(void *ignored) tdata = get_options_test_data("FascistFirewall 1\n" "FirewallPort 123\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1751,9 +1629,7 @@ test_options_validate__reachable_addresses(void *ignored) "ReachableAddresses *:83\n" "ReachableAddresses reject *:*\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1769,9 +1645,7 @@ test_options_validate__reachable_addresses(void *ignored) tdata = get_options_test_data("ReachableAddresses *:82\n" "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1782,9 +1656,7 @@ test_options_validate__reachable_addresses(void *ignored) tdata = get_options_test_data("ReachableORAddresses *:82\n" "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1795,9 +1667,7 @@ test_options_validate__reachable_addresses(void *ignored) tdata = get_options_test_data("ReachableDirAddresses *:82\n" "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1808,9 +1678,7 @@ test_options_validate__reachable_addresses(void *ignored) tdata = get_options_test_data("ClientUseIPv4 0\n" "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1908,9 +1776,7 @@ test_options_validate__use_bridges(void *ignored) "ClientUseIPv4 1\n" "ORPort 127.0.0.1:5555\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1921,9 +1787,7 @@ test_options_validate__use_bridges(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("UseBridges 1\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1936,9 +1800,7 @@ test_options_validate__use_bridges(void *ignored) tdata = get_options_test_data("UseBridges 1\n" "EntryNodes {cn}\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -1998,9 +1860,7 @@ test_options_validate__entry_nodes(void *ignored) "EntryNodes {cn}\n" "UseEntryGuards 0\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2012,9 +1872,7 @@ test_options_validate__entry_nodes(void *ignored) tdata = get_options_test_data("EntryNodes {cn}\n" "UseEntryGuards 1\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2035,9 +1893,7 @@ test_options_validate__safe_logging(void *ignored) char *msg; options_test_data_t *tdata = get_options_test_data( "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2047,9 +1903,7 @@ test_options_validate__safe_logging(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("SafeLogging 0\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2059,9 +1913,7 @@ test_options_validate__safe_logging(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("SafeLogging Relay\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2071,9 +1923,7 @@ test_options_validate__safe_logging(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("SafeLogging 1\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2083,9 +1933,7 @@ test_options_validate__safe_logging(void *ignored) free_options_test_data(tdata); tdata = get_options_test_data("SafeLogging stuffy\n" "MaxClientCircuitsPending 1\n" - "ConnLimit 1\n" - "SchedulerHighWaterMark__ 42\n" - "SchedulerLowWaterMark__ 10\n"); + "ConnLimit 1\n"); ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); tt_int_op(ret, OP_EQ, -1); @@ -2230,6 +2078,7 @@ test_options_validate__testing(void *ignored) ENSURE_DEFAULT(TestingServerConsensusDownloadSchedule, 3000); ENSURE_DEFAULT(TestingClientConsensusDownloadSchedule, 3000); ENSURE_DEFAULT(TestingBridgeDownloadSchedule, 3000); + ENSURE_DEFAULT(TestingBridgeBootstrapDownloadSchedule, 3000); ENSURE_DEFAULT(TestingClientMaxIntervalWithoutRequest, 3000); ENSURE_DEFAULT(TestingDirConnectionMaxStall, 3000); ENSURE_DEFAULT(TestingConsensusMaxDownloadTries, 3000); @@ -3501,7 +3350,7 @@ test_options_validate__control(void *ignored) "can reconfigure your Tor. That's bad! You should upgrade your " "Tor controller as soon as possible.\n"); tor_free(msg); -#endif +#endif /* defined(HAVE_SYS_UN_H) */ free_options_test_data(tdata); tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES @@ -4413,7 +4262,6 @@ struct testcase_t options_tests[] = { LOCAL_VALIDATE_TEST(relay_with_hidden_services), LOCAL_VALIDATE_TEST(transproxy), LOCAL_VALIDATE_TEST(exclude_nodes), - LOCAL_VALIDATE_TEST(scheduler), LOCAL_VALIDATE_TEST(node_families), LOCAL_VALIDATE_TEST(token_bucket), LOCAL_VALIDATE_TEST(recommended_packages), diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 1b2fac4325..83dca2d431 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -59,7 +59,7 @@ test_policy_summary_helper_family_flags(const char *policy_str, summary = policy_summarize(policy, family); - tt_assert(summary != NULL); + tt_ptr_op(summary, OP_NE, NULL); tt_str_op(summary,OP_EQ, expected_summary); short_policy = parse_short_policy(summary); @@ -147,7 +147,7 @@ test_policies_general(void *arg) p = router_parse_addr_policy_item_from_string("reject 192.168.0.0/16:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); tt_int_op(ADDR_POLICY_REJECT,OP_EQ, p->policy_type); tor_addr_from_ipv4h(&tar, 0xc0a80000u); tt_int_op(0,OP_EQ, tor_addr_compare(&p->addr, &tar, CMP_EXACT)); @@ -192,75 +192,75 @@ test_policies_general(void *arg) policy3 = smartlist_new(); p = router_parse_addr_policy_item_from_string("reject *:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy3, p); p = router_parse_addr_policy_item_from_string("accept *:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy3, p); policy4 = smartlist_new(); p = router_parse_addr_policy_item_from_string("accept *:443", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy4, p); p = router_parse_addr_policy_item_from_string("accept *:443", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy4, p); policy5 = smartlist_new(); p = router_parse_addr_policy_item_from_string("reject 0.0.0.0/8:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject 169.254.0.0/16:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject 127.0.0.0/8:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject 192.168.0.0/16:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject 10.0.0.0/8:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject 172.16.0.0/12:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject 80.190.250.90:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject *:1-65534", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("reject *:65535", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); p = router_parse_addr_policy_item_from_string("accept *:1-65535", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy5, p); policy6 = smartlist_new(); p = router_parse_addr_policy_item_from_string("accept 43.3.0.0/9:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy6, p); policy7 = smartlist_new(); p = router_parse_addr_policy_item_from_string("accept 0.0.0.0/8:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy7, p); tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy8, @@ -282,13 +282,13 @@ test_policies_general(void *arg) policy10 = smartlist_new(); p = router_parse_addr_policy_item_from_string("accept6 *:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy10, p); policy11 = smartlist_new(); p = router_parse_addr_policy_item_from_string("reject6 *:*", -1, &malformed_list); - tt_assert(p != NULL); + tt_ptr_op(p, OP_NE, NULL); smartlist_add(policy11, p); tt_assert(!exit_policy_is_general_exit(policy)); @@ -392,21 +392,21 @@ test_policies_general(void *arg) p = router_parse_addr_policy_item_from_string("acce::abcd", ADDR_POLICY_ACCEPT, &malformed_list); - tt_assert(!p); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); malformed_list = 0; p = router_parse_addr_policy_item_from_string("7:1234", ADDR_POLICY_ACCEPT, &malformed_list); - tt_assert(!p); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); malformed_list = 0; p = router_parse_addr_policy_item_from_string("::", ADDR_POLICY_ACCEPT, &malformed_list); - tt_assert(!p); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); malformed_list = 0; @@ -968,63 +968,63 @@ test_policies_general(void *arg) /* Make sure that IPv4 addresses are ignored in accept6/reject6 lines. */ p = router_parse_addr_policy_item_from_string("accept6 1.2.3.4:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(!malformed_list); p = router_parse_addr_policy_item_from_string("reject6 2.4.6.0/24:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(!malformed_list); p = router_parse_addr_policy_item_from_string("accept6 *4:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(!malformed_list); /* Make sure malformed policies are detected as such. */ p = router_parse_addr_policy_item_from_string("bad_token *4:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("accept6 **:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("accept */15:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("reject6 */:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("accept 127.0.0.1/33:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("accept6 [::1]/129:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("reject 8.8.8.8/-1:*", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("reject 8.8.4.4:10-5", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); p = router_parse_addr_policy_item_from_string("reject 1.2.3.4:-1", -1, &malformed_list); - tt_assert(p == NULL); + tt_ptr_op(p, OP_EQ, NULL); tt_assert(malformed_list); /* Test a too-long policy. */ @@ -1148,7 +1148,7 @@ test_policies_reject_exit_address(void *arg) /* test that IPv4 addresses are rejected on an IPv4-only exit */ policies_parse_exit_policy_reject_private(&policy, 0, ipv4_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 1); + tt_int_op(smartlist_len(policy), OP_EQ, 1); tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); addr_policy_list_free(policy); policy = NULL; @@ -1158,12 +1158,12 @@ test_policies_reject_exit_address(void *arg) * on IPv4-only exits, so policies_parse_exit_policy_reject_private doesn't * need to do anything) */ policies_parse_exit_policy_reject_private(&policy, 0, ipv6_list, 0, 0); - tt_assert(policy == NULL); + tt_ptr_op(policy, OP_EQ, NULL); /* test that only IPv4 addresses are rejected on an IPv4-only exit */ policies_parse_exit_policy_reject_private(&policy, 0, both_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 1); + tt_int_op(smartlist_len(policy), OP_EQ, 1); tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); addr_policy_list_free(policy); policy = NULL; @@ -1171,7 +1171,7 @@ test_policies_reject_exit_address(void *arg) /* Test that lists with duplicate entries produce the same results */ policies_parse_exit_policy_reject_private(&policy, 0, dupl_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 1); + tt_int_op(smartlist_len(policy), OP_EQ, 1); tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); addr_policy_list_free(policy); policy = NULL; @@ -1181,7 +1181,7 @@ test_policies_reject_exit_address(void *arg) /* test that IPv4 addresses are rejected on an IPv4/IPv6 exit */ policies_parse_exit_policy_reject_private(&policy, 1, ipv4_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 1); + tt_int_op(smartlist_len(policy), OP_EQ, 1); tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); addr_policy_list_free(policy); policy = NULL; @@ -1189,7 +1189,7 @@ test_policies_reject_exit_address(void *arg) /* test that IPv6 addresses are rejected on an IPv4/IPv6 exit */ policies_parse_exit_policy_reject_private(&policy, 1, ipv6_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 1); + tt_int_op(smartlist_len(policy), OP_EQ, 1); tt_assert(test_policy_has_address_helper(policy, &ipv6_addr)); addr_policy_list_free(policy); policy = NULL; @@ -1197,7 +1197,7 @@ test_policies_reject_exit_address(void *arg) /* test that IPv4 and IPv6 addresses are rejected on an IPv4/IPv6 exit */ policies_parse_exit_policy_reject_private(&policy, 1, both_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 2); + tt_int_op(smartlist_len(policy), OP_EQ, 2); tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); tt_assert(test_policy_has_address_helper(policy, &ipv6_addr)); addr_policy_list_free(policy); @@ -1206,7 +1206,7 @@ test_policies_reject_exit_address(void *arg) /* Test that lists with duplicate entries produce the same results */ policies_parse_exit_policy_reject_private(&policy, 1, dupl_list, 0, 0); tt_assert(policy); - tt_assert(smartlist_len(policy) == 2); + tt_int_op(smartlist_len(policy), OP_EQ, 2); tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); tt_assert(test_policy_has_address_helper(policy, &ipv6_addr)); addr_policy_list_free(policy); @@ -1258,7 +1258,7 @@ test_policies_reject_port_address(void *arg) * with IPv6 addresses on IPv4-only exits) */ policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 1); tt_assert(policy); - tt_assert(smartlist_len(policy) == 1); + tt_int_op(smartlist_len(policy), OP_EQ, 1); tt_assert(test_policy_has_address_helper(policy, &ipv4_port->addr)); addr_policy_list_free(policy); policy = NULL; @@ -1266,7 +1266,7 @@ test_policies_reject_port_address(void *arg) /* test that IPv4 and IPv6 ports are rejected on an IPv4/IPv6 exit */ policies_parse_exit_policy_reject_private(&policy, 1, NULL, 0, 1); tt_assert(policy); - tt_assert(smartlist_len(policy) == 2); + tt_int_op(smartlist_len(policy), OP_EQ, 2); tt_assert(test_policy_has_address_helper(policy, &ipv4_port->addr)); tt_assert(test_policy_has_address_helper(policy, &ipv6_port->addr)); addr_policy_list_free(policy); @@ -1337,7 +1337,7 @@ test_policies_reject_interface_address(void *arg) /* test that no addresses are rejected when none are supplied/requested */ policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 0); - tt_assert(policy == NULL); + tt_ptr_op(policy, OP_EQ, NULL); /* test that only IPv4 interface addresses are rejected on an IPv4-only exit * (and allow for duplicates) @@ -1372,7 +1372,7 @@ test_policies_reject_interface_address(void *arg) /* test that no addresses are rejected when none are supplied/requested */ policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 0); - tt_assert(policy == NULL); + tt_ptr_op(policy, OP_EQ, NULL); /* test that only IPv4 interface addresses are rejected on an IPv4-only exit */ @@ -1528,15 +1528,15 @@ test_policies_getinfo_helper_policies(void *arg) memset(&mock_my_routerinfo, 0, sizeof(mock_my_routerinfo)); rv = getinfo_helper_policies(NULL, "exit-policy/default", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) > 0); tor_free(answer); rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/default", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) > 0); tor_free(answer); @@ -1550,15 +1550,15 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) == 0); tor_free(answer); rv = getinfo_helper_policies(NULL, "exit-policy/ipv4", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); ipv4_len = strlen(answer); tt_assert(ipv4_len == 0 || ipv4_len == strlen(DEFAULT_POLICY_STRING)); tt_assert(ipv4_len == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING)); @@ -1566,8 +1566,8 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/ipv6", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); ipv6_len = strlen(answer); tt_assert(ipv6_len == 0 || ipv6_len == strlen(DEFAULT_POLICY_STRING)); tt_assert(ipv6_len == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING)); @@ -1575,8 +1575,8 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/full", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); /* It's either empty or it's the default */ tt_assert(strlen(answer) == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING)); tor_free(answer); @@ -1599,8 +1599,8 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) > 0); tor_free(answer); @@ -1609,8 +1609,8 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) > 0); tor_free(answer); @@ -1619,8 +1619,8 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) > 0); tor_free(answer); @@ -1629,31 +1629,31 @@ test_policies_getinfo_helper_policies(void *arg) rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) == 0); tor_free(answer); rv = getinfo_helper_policies(NULL, "exit-policy/ipv4", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); ipv4_len = strlen(answer); tt_assert(ipv4_len > 0); tor_free(answer); rv = getinfo_helper_policies(NULL, "exit-policy/ipv6", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); ipv6_len = strlen(answer); tt_assert(ipv6_len > 0); tor_free(answer); rv = getinfo_helper_policies(NULL, "exit-policy/full", &answer, &errmsg); - tt_assert(rv == 0); - tt_assert(answer != NULL); + tt_int_op(rv, OP_EQ, 0); + tt_ptr_op(answer, OP_NE, NULL); tt_assert(strlen(answer) > 0); tt_assert(strlen(answer) == ipv4_len + ipv6_len + 1); tor_free(answer); @@ -1746,34 +1746,34 @@ test_policies_fascist_firewall_allows_address(void *arg) mock_options.ClientUseIPv6 = 1; mock_options.UseBridges = 0; - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* Preferring IPv4 */ - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0), + OP_EQ, 0); /* Preferring IPv6 */ - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1) - == 0); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1), + OP_EQ, 0); /* Test the function's address matching with UseBridges on */ memset(&mock_options, 0, sizeof(or_options_t)); @@ -1781,46 +1781,46 @@ test_policies_fascist_firewall_allows_address(void *arg) mock_options.ClientUseIPv6 = 1; mock_options.UseBridges = 1; - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* Preferring IPv4 */ - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0), + OP_EQ, 0); /* Preferring IPv6 */ - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1) - == 0); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1), + OP_EQ, 0); /* bridge clients always use IPv6, regardless of ClientUseIPv6 */ mock_options.ClientUseIPv4 = 1; mock_options.ClientUseIPv6 = 0; - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* Test the function's address matching with IPv4 on */ memset(&mock_options, 0, sizeof(or_options_t)); @@ -1828,14 +1828,14 @@ test_policies_fascist_firewall_allows_address(void *arg) mock_options.ClientUseIPv6 = 0; mock_options.UseBridges = 0; - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* Test the function's address matching with IPv6 on */ memset(&mock_options, 0, sizeof(or_options_t)); @@ -1843,14 +1843,14 @@ test_policies_fascist_firewall_allows_address(void *arg) mock_options.ClientUseIPv6 = 1; mock_options.UseBridges = 0; - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* Test the function's address matching with ClientUseIPv4 0. * This means "use IPv6" regardless of the other settings. */ @@ -1859,14 +1859,14 @@ test_policies_fascist_firewall_allows_address(void *arg) mock_options.ClientUseIPv6 = 0; mock_options.UseBridges = 0; - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* Test the function's address matching for unusual inputs */ memset(&mock_options, 0, sizeof(or_options_t)); @@ -1875,27 +1875,28 @@ test_policies_fascist_firewall_allows_address(void *arg) mock_options.UseBridges = 1; /* NULL and tor_addr_is_null addresses are rejected */ - tt_assert(fascist_firewall_allows_address(NULL, port, policy, 0, 0) == 0); - tt_assert(fascist_firewall_allows_address(&n_ipv4_addr, port, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&n_ipv6_addr, port, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(NULL, port, policy, 0, 0), OP_EQ, + 0); + tt_int_op(fascist_firewall_allows_address(&n_ipv4_addr, port, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&n_ipv6_addr, port, policy, 0, 0), + OP_EQ, 0); /* zero ports are rejected */ - tt_assert(fascist_firewall_allows_address(&ipv4_addr, 0, policy, 0, 0) - == 0); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, 0, policy, 0, 0) - == 0); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, 0, policy, 0, 0), + OP_EQ, 0); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, 0, policy, 0, 0), + OP_EQ, 0); /* NULL and empty policies accept everything */ - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, NULL, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, NULL, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, e_policy, 0, 0) - == 1); - tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, e_policy, 0, 0) - == 1); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, NULL, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, NULL, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv4_addr, port, e_policy, 0, 0), + OP_EQ, 1); + tt_int_op(fascist_firewall_allows_address(&ipv6_addr, port, e_policy, 0, 0), + OP_EQ, 1); done: addr_policy_free(item); @@ -2032,12 +2033,12 @@ test_policies_fascist_firewall_choose_address(void *arg) == &ipv6_or_ap); /* null both OR addresses */ - tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0, - FIREWALL_OR_CONNECTION, 0, 1) - == NULL); - tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 1, - FIREWALL_OR_CONNECTION, 0, 0) - == NULL); + tt_ptr_op(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0, + FIREWALL_OR_CONNECTION, 0, 1), + OP_EQ, NULL); + tt_ptr_op(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 1, + FIREWALL_OR_CONNECTION, 0, 0), + OP_EQ, NULL); /* null preferred Dir addresses */ tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &n_ipv6_ap, 0, @@ -2048,12 +2049,12 @@ test_policies_fascist_firewall_choose_address(void *arg) == &ipv6_dir_ap); /* null both Dir addresses */ - tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0, - FIREWALL_DIR_CONNECTION, 0, 1) - == NULL); - tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 1, - FIREWALL_DIR_CONNECTION, 0, 0) - == NULL); + tt_ptr_op(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0, + FIREWALL_DIR_CONNECTION, 0, 1), + OP_EQ, NULL); + tt_ptr_op(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 1, + FIREWALL_DIR_CONNECTION, 0, 0), + OP_EQ, NULL); /* Prefer IPv4 but want IPv6 (contradictory) */ tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, diff --git a/src/test/test_proto_http.c b/src/test/test_proto_http.c new file mode 100644 index 0000000000..2f36fbccd7 --- /dev/null +++ b/src/test/test_proto_http.c @@ -0,0 +1,213 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_proto_http.c + * \brief Tests for our HTTP protocol parser code + */ + +#include "or.h" +#include "test.h" +#include "buffers.h" +#include "proto_http.h" +#include "log_test_helpers.h" + +#define S(str) str, sizeof(str)-1 + +static void +test_proto_http_peek(void *arg) +{ + (void) arg; + const struct { + int is_http; + const char *message; + size_t len; + } cases[] = { + { 1, S("GET /index HTTP/1.0\r\n") }, + { 1, S("GET /index HTTP/1.1\r\n") }, + { 1, S("GET ") }, + { 0, S("GIT ") }, + { 0, S("GET") }, + { 0, S("get ") }, + { 0, S("GETAWAY") }, + }; + unsigned i; + buf_t *buf = buf_new(); + for (i = 0; i < ARRAY_LENGTH(cases); ++i) { + TT_BLATHER(("Trying case %u", i)); + buf_add(buf, cases[i].message, cases[i].len); + tt_int_op(cases[i].is_http, OP_EQ, peek_buf_has_http_command(buf)); + buf_clear(buf); + } + done: + buf_free(buf); +} + +static void +test_proto_http_valid(void *arg) +{ + (void) arg; + const struct { + const char *message; + size_t len; + const char *headers; + const char *body; + size_t bodylen; + int should_detect_truncated; + int bytes_left_over; + } cases[] = { + { S("GET /index.html HTTP/1.0\r\n\r\n"), + "GET /index.html HTTP/1.0\r\n\r\n", + S(""), + 1, 0, + }, + { S("PUT /tor/foo HTTP/1.1\r\n" + "Content-Length: 51\r\n\r\n" + "this is a test of the http parsing system . test te"), + "PUT /tor/foo HTTP/1.1\r\n" "Content-Length: 51\r\n\r\n", + S("this is a test of the http parsing system . test te"), + 1, 0, + }, + { S("PUT /tor/foo HTTP/1.1\r\n" + "Content-Length: 5\r\n\r\n" + "there are more than 5 characters in this body."), + "PUT /tor/foo HTTP/1.1\r\n" "Content-Length: 5\r\n\r\n", + S("there"), + 0, 41, + }, + { S("PUT /tor/bar HTTP/1.1\r\n\r\n" + "this is another \x00test"), + "PUT /tor/bar HTTP/1.1\r\n\r\n", + S("this is another \x00test"), + 0, 0, + } + }; + unsigned i; + buf_t *buf = buf_new(); + char *h = NULL, *b = NULL; + + for (i = 0; i < ARRAY_LENGTH(cases); ++i) { + TT_BLATHER(("Trying case %u", i)); + size_t bl = 0; + // truncate by 2 chars + buf_add(buf, cases[i].message, cases[i].len - 2); + + if (cases[i].should_detect_truncated) { + tt_int_op(0, OP_EQ, fetch_from_buf_http(buf, &h, 1024*16, + &b, &bl, 1024*16, 0)); + tt_ptr_op(h, OP_EQ, NULL); + tt_ptr_op(b, OP_EQ, NULL); + tt_u64_op(bl, OP_EQ, 0); + tt_int_op(buf_datalen(buf), OP_EQ, cases[i].len - 2); + } + + // add the rest. + buf_add(buf, cases[i].message+cases[i].len-2, 2); + tt_int_op(1, OP_EQ, fetch_from_buf_http(buf, &h, 1024*16, + &b, &bl, 1024*16, 0)); + tt_str_op(h, OP_EQ, cases[i].headers); + tt_u64_op(bl, OP_EQ, cases[i].bodylen); + tt_mem_op(b, OP_EQ, cases[i].body, bl); + tt_int_op(buf_datalen(buf), OP_EQ, cases[i].bytes_left_over); + + buf_clear(buf); + tor_free(h); + tor_free(b); + } + done: + tor_free(h); + tor_free(b); + buf_free(buf); +} + +static void +test_proto_http_invalid(void *arg) +{ + (void) arg; + const struct { + const char *message; + size_t len; + const char *expect; + } cases[] = { + /* Overlong headers, headers not finished. */ + { S("GET /index.xhml HTTP/1.0\r\n" + "X-My-headers-are-too-long: yes indeed they are. They might be\r\n" + "X-My-headers-are-too-long: normal under other circumstances, but\r\n" + "X-My-headers-are-too-long: the 128-byte limit makes them bad\r\n"), + "headers too long." }, + /* Overlong finished headers. */ + { S("GET /index.xhml HTTP/1.0\r\n" + "X-My-headers-are-too-long: yes indeed they are. They might be\r\n" + "X-My-headers-are-too-long: normal under other circumstances, but\r\n" + "X-My-headers-are-too-long: the 128-byte limit makes them bad\r\n" + "\r\n"), + "headers too long." }, + /* Exactly too long finished headers. */ + { S("GET /index.xhml HTTP/1.0\r\n" + "X-My-headers-are-too-long: yes indeed they are. They might be\r\n" + "X-My-headers-are-too-long: normal un\r\n\r\n"), + "headerlen 129 larger than 127. Failing." }, + /* Body too long, with content-length */ + { S("GET /index.html HTTP/1.0\r\n" + "Content-Length: 129\r\n\r\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxx"), + "bodylen 129 larger than 127" }, + /* Body too long, with content-length lying */ + { S("GET /index.html HTTP/1.0\r\n" + "Content-Length: 99999\r\n\r\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"), + "bodylen 138 larger than 127" }, + /* Body too long, no content-length. */ + { S("GET /index.html HTTP/1.0\r\n\r\n" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxz"), + "bodylen 139 larger than 127" }, + /* Content-Length is junk. */ + { S("GET /index.html HTTP/1.0\r\n" + "Content-Length: Cheese\r\n\r\n" + "foo"), + "Content-Length is bogus; maybe someone is trying to crash us." }, + }; + unsigned i; + buf_t *buf = buf_new(); + char *h = NULL, *b = NULL; + setup_capture_of_logs(LOG_DEBUG); + + for (i = 0; i < ARRAY_LENGTH(cases); ++i) { + TT_BLATHER(("Trying case %u", i)); + size_t bl = 0; + buf_add(buf, cases[i].message, cases[i].len); + + /* Use low body limits here so we can force over-sized object warnings */ + tt_int_op(-1, OP_EQ, fetch_from_buf_http(buf, &h, 128, + &b, &bl, 128, 0)); + tt_ptr_op(h, OP_EQ, NULL); + tt_ptr_op(b, OP_EQ, NULL); + tt_u64_op(bl, OP_EQ, 0); + expect_log_msg_containing(cases[i].expect); + + buf_clear(buf); + tor_free(h); + tor_free(b); + mock_clean_saved_logs(); + } + done: + tor_free(h); + tor_free(b); + buf_free(buf); + teardown_capture_of_logs(); +} + +struct testcase_t proto_http_tests[] = { + { "peek", test_proto_http_peek, 0, NULL, NULL }, + { "valid", test_proto_http_valid, 0, NULL, NULL }, + { "invalid", test_proto_http_invalid, 0, NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_proto_misc.c b/src/test/test_proto_misc.c new file mode 100644 index 0000000000..263ca47447 --- /dev/null +++ b/src/test/test_proto_misc.c @@ -0,0 +1,263 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file test_proto_misc.c + * \brief Test our smaller buffer-based protocol functions + */ + +#include "or.h" +#include "test.h" +#include "buffers.h" +#include "connection_or.h" +#include "ext_orport.h" +#include "proto_cell.h" +#include "proto_control0.h" +#include "proto_ext_or.h" + +static void +test_proto_var_cell(void *arg) +{ + (void)arg; + char *mem_op_hex_tmp = NULL; + char tmp[1024]; + buf_t *buf = NULL; + var_cell_t *cell = NULL; + + buf = buf_new(); + memset(tmp, 0xf0, sizeof(tmp)); + + /* Short little commands will make us say "no cell yet." */ + tt_int_op(0, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 4)); + tt_ptr_op(cell, OP_EQ, NULL); + buf_add(buf, "\x01\x02\x02\0x2", 4); + tt_int_op(0, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 4)); + /* An incomplete fixed-length cell makes us say "no cell yet". */ + buf_add(buf, "\x03", 1); + tt_int_op(0, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 4)); + /* A complete fixed length-cell makes us say "not a variable-length cell" */ + buf_add(buf, tmp, 509); + tt_int_op(0, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 4)); + buf_clear(buf); + + /* An incomplete versions cell is a variable-length cell that isn't ready + * yet. */ + buf_add(buf, + "\x01\x02\x03\x04" /* circid */ + "\x07" /* VERSIONS */ + "\x00\x04" /* 4 bytes long */ + "\x00" /* incomplete */, 8); + tt_int_op(1, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 4)); + tt_ptr_op(cell, OP_EQ, NULL); + /* Complete it, and it's a variable-length cell. Leave a byte on the end for + * fun. */ + buf_add(buf, "\x09\x00\x25\ff", 4); + tt_int_op(1, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 4)); + tt_ptr_op(cell, OP_NE, NULL); + tt_int_op(cell->command, OP_EQ, CELL_VERSIONS); + tt_uint_op(cell->circ_id, OP_EQ, 0x01020304); + tt_int_op(cell->payload_len, OP_EQ, 4); + test_mem_op_hex(cell->payload, OP_EQ, "00090025"); + var_cell_free(cell); + cell = NULL; + tt_int_op(buf_datalen(buf), OP_EQ, 1); + buf_clear(buf); + + /* In link protocol 3 and earlier, circid fields were two bytes long. Let's + * ensure that gets handled correctly. */ + buf_add(buf, + "\x23\x45\x81\x00\x06" /* command 81; 6 bytes long */ + "coraje", 11); + tt_int_op(1, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 3)); + tt_ptr_op(cell, OP_NE, NULL); + tt_int_op(cell->command, OP_EQ, 129); + tt_uint_op(cell->circ_id, OP_EQ, 0x2345); + tt_int_op(cell->payload_len, OP_EQ, 6); + tt_mem_op(cell->payload, OP_EQ, "coraje", 6); + var_cell_free(cell); + cell = NULL; + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* In link protocol 2, only VERSIONS cells counted as variable-length */ + buf_add(buf, + "\x23\x45\x81\x00\x06" + "coraje", 11); /* As above */ + tt_int_op(0, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 2)); + buf_clear(buf); + buf_add(buf, + "\x23\x45\x07\x00\x06" + "futuro", 11); + tt_int_op(1, OP_EQ, fetch_var_cell_from_buf(buf, &cell, 2)); + tt_ptr_op(cell, OP_NE, NULL); + tt_int_op(cell->command, OP_EQ, 7); + tt_uint_op(cell->circ_id, OP_EQ, 0x2345); + tt_int_op(cell->payload_len, OP_EQ, 6); + tt_mem_op(cell->payload, OP_EQ, "futuro", 6); + var_cell_free(cell); + cell = NULL; + + done: + buf_free(buf); + var_cell_free(cell); + tor_free(mem_op_hex_tmp); +} + +static void +test_proto_control0(void *arg) +{ + (void)arg; + buf_t *buf = buf_new(); + + /* The only remaining function for the v0 control protocol is the function + that detects whether the user has stumbled across an old controller + that's using it. The format was: + u16 length; + u16 command; + u8 body[length]; + */ + + /* Empty buffer -- nothing to do. */ + tt_int_op(0, OP_EQ, peek_buf_has_control0_command(buf)); + /* 3 chars in buf -- can't tell */ + buf_add(buf, "AUT", 3); + tt_int_op(0, OP_EQ, peek_buf_has_control0_command(buf)); + /* command in buf -- easy to tell */ + buf_add(buf, "HENTICATE ", 10); + tt_int_op(0, OP_EQ, peek_buf_has_control0_command(buf)); + + /* Control0 command header in buf: make sure we detect it. */ + buf_clear(buf); + buf_add(buf, "\x09\x05" "\x00\x05" "blah", 8); + tt_int_op(1, OP_EQ, peek_buf_has_control0_command(buf)); + + done: + buf_free(buf); +} + +static void +test_proto_ext_or_cmd(void *arg) +{ + ext_or_cmd_t *cmd = NULL; + buf_t *buf = buf_new(); + char *tmp = NULL; + (void) arg; + + /* Empty -- should give "not there. */ + tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_EQ, cmd); + + /* Three bytes: shouldn't work. */ + buf_add(buf, "\x00\x20\x00", 3); + tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_EQ, cmd); + tt_int_op(3, OP_EQ, buf_datalen(buf)); + + /* 0020 0000: That's a nil command. It should work. */ + buf_add(buf, "\x00", 1); + tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_NE, cmd); + tt_int_op(0x20, OP_EQ, cmd->cmd); + tt_int_op(0, OP_EQ, cmd->len); + tt_int_op(0, OP_EQ, buf_datalen(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + /* Now try a length-6 command with one byte missing. */ + buf_add(buf, "\x10\x21\x00\x06""abcde", 9); + tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_EQ, cmd); + buf_add(buf, "f", 1); + tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_NE, cmd); + tt_int_op(0x1021, OP_EQ, cmd->cmd); + tt_int_op(6, OP_EQ, cmd->len); + tt_mem_op("abcdef", OP_EQ, cmd->body, 6); + tt_int_op(0, OP_EQ, buf_datalen(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + /* Now try a length-10 command with 4 extra bytes. */ + buf_add(buf, "\xff\xff\x00\x0aloremipsum\x10\x00\xff\xff", 18); + tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_NE, cmd); + tt_int_op(0xffff, OP_EQ, cmd->cmd); + tt_int_op(10, OP_EQ, cmd->len); + tt_mem_op("loremipsum", OP_EQ, cmd->body, 10); + tt_int_op(4, OP_EQ, buf_datalen(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + /* Finally, let's try a maximum-length command. We already have the header + * waiting. */ + tt_int_op(0, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tmp = tor_malloc_zero(65535); + buf_add(buf, tmp, 65535); + tt_int_op(1, OP_EQ, fetch_ext_or_command_from_buf(buf, &cmd)); + tt_ptr_op(NULL, OP_NE, cmd); + tt_int_op(0x1000, OP_EQ, cmd->cmd); + tt_int_op(0xffff, OP_EQ, cmd->len); + tt_mem_op(tmp, OP_EQ, cmd->body, 65535); + tt_int_op(0, OP_EQ, buf_datalen(buf)); + ext_or_cmd_free(cmd); + cmd = NULL; + + done: + ext_or_cmd_free(cmd); + buf_free(buf); + tor_free(tmp); +} + +static void +test_proto_line(void *arg) +{ + (void)arg; + char tmp[60]; + buf_t *buf = buf_new(); +#define S(str) str, sizeof(str)-1 + const struct { + const char *input; + size_t input_len; + size_t line_len; + const char *output; + int returnval; + } cases[] = { + { S("Hello world"), 0, NULL, 0 }, + { S("Hello world\n"), 12, "Hello world\n", 1 }, + { S("Hello world\nMore"), 12, "Hello world\n", 1 }, + { S("\n oh hello world\nMore"), 1, "\n", 1 }, + { S("Hello worpd\n\nMore"), 12, "Hello worpd\n", 1 }, + { S("------------------------------------------------------------\n"), 0, + NULL, -1 }, + }; + unsigned i; + for (i = 0; i < ARRAY_LENGTH(cases); ++i) { + buf_add(buf, cases[i].input, cases[i].input_len); + memset(tmp, 0xfe, sizeof(tmp)); + size_t sz = sizeof(tmp); + int rv = buf_get_line(buf, tmp, &sz); + tt_int_op(rv, OP_EQ, cases[i].returnval); + if (rv == 1) { + tt_int_op(sz, OP_LT, sizeof(tmp)); + tt_mem_op(cases[i].output, OP_EQ, tmp, sz+1); + tt_int_op(buf_datalen(buf), OP_EQ, cases[i].input_len - strlen(tmp)); + tt_int_op(sz, OP_EQ, cases[i].line_len); + } else { + tt_int_op(buf_datalen(buf), OP_EQ, cases[i].input_len); + // tt_int_op(sz, OP_EQ, sizeof(tmp)); + } + buf_clear(buf); + } + + done: + buf_free(buf); +} + +struct testcase_t proto_misc_tests[] = { + { "var_cell", test_proto_var_cell, 0, NULL, NULL }, + { "control0", test_proto_control0, 0, NULL, NULL }, + { "ext_or_cmd", test_proto_ext_or_cmd, TT_FORK, NULL, NULL }, + { "line", test_proto_line, 0, NULL, NULL }, + + END_OF_TESTCASES +}; + diff --git a/src/test/test_protover.c b/src/test/test_protover.c index 5626816024..6ce54890d6 100644 --- a/src/test/test_protover.c +++ b/src/test/test_protover.c @@ -88,27 +88,27 @@ test_protover_parse_fail(void *arg) /* random junk */ elts = parse_protocol_list("!!3@*"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); /* Missing equals sign in an entry */ elts = parse_protocol_list("Link=4 Haprauxymatyve Desc=9"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); /* Missing word. */ elts = parse_protocol_list("Link=4 =3 Desc=9"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); /* Broken numbers */ elts = parse_protocol_list("Link=fred"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); elts = parse_protocol_list("Link=1,fred"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); elts = parse_protocol_list("Link=1,fred,3"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); /* Broken range */ elts = parse_protocol_list("Link=1,9-8,3"); - tt_assert(elts == NULL); + tt_ptr_op(elts, OP_EQ, NULL); done: ; @@ -151,16 +151,16 @@ test_protover_all_supported(void *arg) char *msg = NULL; tt_assert(protover_all_supported(NULL, &msg)); - tt_assert(msg == NULL); + tt_ptr_op(msg, OP_EQ, NULL); tt_assert(protover_all_supported("", &msg)); - tt_assert(msg == NULL); + tt_ptr_op(msg, OP_EQ, NULL); // Some things that we do support tt_assert(protover_all_supported("Link=3-4", &msg)); - tt_assert(msg == NULL); + tt_ptr_op(msg, OP_EQ, NULL); tt_assert(protover_all_supported("Link=3-4 Desc=2", &msg)); - tt_assert(msg == NULL); + tt_ptr_op(msg, OP_EQ, NULL); // Some things we don't support tt_assert(! protover_all_supported("Wombat=9", &msg)); diff --git a/src/test/test_pt.c b/src/test/test_pt.c index 79b03171bc..07b6712ff9 100644 --- a/src/test/test_pt.c +++ b/src/test/test_pt.c @@ -40,34 +40,34 @@ test_pt_parsing(void *arg) /* incomplete cmethod */ strlcpy(line,"CMETHOD trebuchet",sizeof(line)); - tt_assert(parse_cmethod_line(line, mp) < 0); + tt_int_op(parse_cmethod_line(line, mp), OP_LT, 0); reset_mp(mp); /* wrong proxy type */ strlcpy(line,"CMETHOD trebuchet dog 127.0.0.1:1999",sizeof(line)); - tt_assert(parse_cmethod_line(line, mp) < 0); + tt_int_op(parse_cmethod_line(line, mp), OP_LT, 0); reset_mp(mp); /* wrong addrport */ strlcpy(line,"CMETHOD trebuchet socks4 abcd",sizeof(line)); - tt_assert(parse_cmethod_line(line, mp) < 0); + tt_int_op(parse_cmethod_line(line, mp), OP_LT, 0); reset_mp(mp); /* correct line */ strlcpy(line,"CMETHOD trebuchet socks5 127.0.0.1:1999",sizeof(line)); - tt_assert(parse_cmethod_line(line, mp) == 0); - tt_assert(smartlist_len(mp->transports) == 1); + tt_int_op(parse_cmethod_line(line, mp), OP_EQ, 0); + tt_int_op(smartlist_len(mp->transports), OP_EQ, 1); transport = smartlist_get(mp->transports, 0); /* test registered address of transport */ tor_addr_parse(&test_addr, "127.0.0.1"); tt_assert(tor_addr_eq(&test_addr, &transport->addr)); /* test registered port of transport */ - tt_assert(transport->port == 1999); + tt_uint_op(transport->port, OP_EQ, 1999); /* test registered SOCKS version of transport */ - tt_assert(transport->socks_version == PROXY_SOCKS5); + tt_int_op(transport->socks_version, OP_EQ, PROXY_SOCKS5); /* test registered name of transport */ tt_str_op(transport->name,OP_EQ, "trebuchet"); @@ -75,26 +75,26 @@ test_pt_parsing(void *arg) /* incomplete smethod */ strlcpy(line,"SMETHOD trebuchet",sizeof(line)); - tt_assert(parse_smethod_line(line, mp) < 0); + tt_int_op(parse_smethod_line(line, mp), OP_LT, 0); reset_mp(mp); /* wrong addr type */ strlcpy(line,"SMETHOD trebuchet abcd",sizeof(line)); - tt_assert(parse_smethod_line(line, mp) < 0); + tt_int_op(parse_smethod_line(line, mp), OP_LT, 0); reset_mp(mp); /* cowwect */ strlcpy(line,"SMETHOD trebuchy 127.0.0.2:2999",sizeof(line)); - tt_assert(parse_smethod_line(line, mp) == 0); - tt_assert(smartlist_len(mp->transports) == 1); + tt_int_op(parse_smethod_line(line, mp), OP_EQ, 0); + tt_int_op(smartlist_len(mp->transports), OP_EQ, 1); transport = smartlist_get(mp->transports, 0); /* test registered address of transport */ tor_addr_parse(&test_addr, "127.0.0.2"); tt_assert(tor_addr_eq(&test_addr, &transport->addr)); /* test registered port of transport */ - tt_assert(transport->port == 2999); + tt_uint_op(transport->port, OP_EQ, 2999); /* test registered name of transport */ tt_str_op(transport->name,OP_EQ, "trebuchy"); @@ -104,7 +104,7 @@ test_pt_parsing(void *arg) strlcpy(line,"SMETHOD trebuchet 127.0.0.1:9999 " "ARGS:counterweight=3,sling=snappy", sizeof(line)); - tt_assert(parse_smethod_line(line, mp) == 0); + tt_int_op(parse_smethod_line(line, mp), OP_EQ, 0); tt_int_op(1, OP_EQ, smartlist_len(mp->transports)); { const transport_t *transport_ = smartlist_get(mp->transports, 0); @@ -119,15 +119,15 @@ test_pt_parsing(void *arg) /* unsupported version */ strlcpy(line,"VERSION 666",sizeof(line)); - tt_assert(parse_version(line, mp) < 0); + tt_int_op(parse_version(line, mp), OP_LT, 0); /* incomplete VERSION */ strlcpy(line,"VERSION ",sizeof(line)); - tt_assert(parse_version(line, mp) < 0); + tt_int_op(parse_version(line, mp), OP_LT, 0); /* correct VERSION */ strlcpy(line,"VERSION 1",sizeof(line)); - tt_assert(parse_version(line, mp) == 0); + tt_int_op(parse_version(line, mp), OP_EQ, 0); done: reset_mp(mp); @@ -461,7 +461,7 @@ test_get_pt_proxy_uri(void *arg) /* Test with no proxy. */ uri = get_pt_proxy_uri(); - tt_assert(uri == NULL); + tt_ptr_op(uri, OP_EQ, NULL); /* Test with a SOCKS4 proxy. */ options->Socks4Proxy = tor_strdup("192.0.2.1:1080"); diff --git a/src/test/test_relay.c b/src/test/test_relay.c index 238d4c5baf..e3489627a0 100644 --- a/src/test/test_relay.c +++ b/src/test/test_relay.c @@ -91,14 +91,14 @@ test_relay_append_cell_to_circuit_queue(void *arg) append_cell_to_circuit_queue(TO_CIRCUIT(orcirc), nchan, cell, CELL_DIRECTION_OUT, 0); new_count = get_mock_scheduler_has_waiting_cells_count(); - tt_int_op(new_count, ==, old_count + 1); + tt_int_op(new_count, OP_EQ, old_count + 1); /* Now try the reverse direction */ old_count = get_mock_scheduler_has_waiting_cells_count(); append_cell_to_circuit_queue(TO_CIRCUIT(orcirc), pchan, cell, CELL_DIRECTION_IN, 0); new_count = get_mock_scheduler_has_waiting_cells_count(); - tt_int_op(new_count, ==, old_count + 1); + tt_int_op(new_count, OP_EQ, old_count + 1); UNMOCK(scheduler_channel_has_waiting_cells); diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c index feba8f664e..9354dd0480 100644 --- a/src/test/test_rendcache.c +++ b/src/test/test_rendcache.c @@ -21,22 +21,6 @@ static const int TIME_IN_THE_PAST = -(REND_CACHE_MAX_AGE + \ REND_CACHE_MAX_SKEW + 60); static const int TIME_IN_THE_FUTURE = REND_CACHE_MAX_SKEW + 60; -static rend_data_t * -mock_rend_data(const char *onion_address) -{ - rend_data_v2_t *v2_data = tor_malloc_zero(sizeof(*v2_data)); - rend_data_t *rend_query = &v2_data->base_; - rend_query->version = 2; - - strlcpy(v2_data->onion_address, onion_address, - sizeof(v2_data->onion_address)); - v2_data->auth_type = REND_NO_AUTH; - rend_query->hsdirs_fp = smartlist_new(); - smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa", - DIGEST_LEN)); - return rend_query; -} - static void test_rend_cache_lookup_entry(void *data) { @@ -74,6 +58,7 @@ test_rend_cache_lookup_entry(void *data) tt_int_op(ret, OP_EQ, 0); ret = rend_cache_lookup_entry(service_id, 2, &entry); + tt_int_op(ret, OP_EQ, 0); tt_assert(entry); tt_int_op(entry->len, OP_EQ, strlen(desc_holder->desc_str)); tt_str_op(entry->desc, OP_EQ, desc_holder->desc_str); @@ -962,9 +947,9 @@ test_rend_cache_free_all(void *data) rend_cache_free_all(); - tt_assert(!rend_cache); - tt_assert(!rend_cache_v2_dir); - tt_assert(!rend_cache_failure); + tt_ptr_op(rend_cache, OP_EQ, NULL); + tt_ptr_op(rend_cache_v2_dir, OP_EQ, NULL); + tt_ptr_op(rend_cache_failure, OP_EQ, NULL); tt_assert(!rend_cache_total_allocation); done: diff --git a/src/test/test_replay.c b/src/test/test_replay.c index 80e7203716..c379cafa7c 100644 --- a/src/test/test_replay.c +++ b/src/test/test_replay.c @@ -38,7 +38,7 @@ test_replaycache_alloc(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); done: if (r) replaycache_free(r); @@ -54,15 +54,15 @@ test_replaycache_badalloc(void *arg) /* Negative horizon should fail */ (void)arg; r = replaycache_new(-600, 300); - tt_assert(r == NULL); + tt_ptr_op(r, OP_EQ, NULL); /* Negative interval should get adjusted to zero */ r = replaycache_new(600, -300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); tt_int_op(r->scrub_interval,OP_EQ, 0); replaycache_free(r); /* Negative horizon and negative interval should still fail */ r = replaycache_new(-600, -300); - tt_assert(r == NULL); + tt_ptr_op(r, OP_EQ, NULL); done: if (r) replaycache_free(r); @@ -90,7 +90,7 @@ test_replaycache_miss(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); result = replaycache_add_and_test_internal(1200, r, test_buffer, @@ -123,7 +123,7 @@ test_replaycache_hit(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); result = replaycache_add_and_test_internal(1200, r, test_buffer, @@ -161,7 +161,7 @@ test_replaycache_age(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); result = replaycache_add_and_test_internal(1200, r, test_buffer, @@ -193,7 +193,7 @@ test_replaycache_elapsed(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); result = replaycache_add_and_test_internal(1200, r, test_buffer, @@ -220,7 +220,7 @@ test_replaycache_noexpire(void *arg) (void)arg; r = replaycache_new(0, 0); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); result = replaycache_add_and_test_internal(1200, r, test_buffer, @@ -251,7 +251,7 @@ test_replaycache_scrub(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); /* Set up like in test_replaycache_hit() */ result = @@ -294,7 +294,7 @@ test_replaycache_future(void *arg) (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); /* Set up like in test_replaycache_hit() */ result = @@ -343,7 +343,7 @@ test_replaycache_realtime(void *arg) /* Test the realtime as well as *_internal() entry points */ (void)arg; r = replaycache_new(600, 300); - tt_assert(r != NULL); + tt_ptr_op(r, OP_NE, NULL); /* This should miss */ result = diff --git a/src/test/test_router.c b/src/test/test_router.c new file mode 100644 index 0000000000..4e96e24534 --- /dev/null +++ b/src/test/test_router.c @@ -0,0 +1,112 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* Copyright (c) 2017, isis agora lovecruft */ +/* See LICENSE for licensing information */ + +/** + * \file test_router.c + * \brief Unittests for code in src/or/router.c + **/ + +#include "or.h" +#include "config.h" +#include "crypto_curve25519.h" +#include "crypto_ed25519.h" +#include "router.h" +#include "routerlist.h" + +/* Test suite stuff */ +#include "test.h" + +NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); + +static routerinfo_t* mock_routerinfo; + +static const routerinfo_t* +NS(router_get_my_routerinfo)(void) +{ + crypto_pk_t* ident_key; + crypto_pk_t* tap_key; + time_t now; + + if (!mock_routerinfo) { + /* Mock the published timestamp, otherwise router_dump_router_to_string() + * will poop its pants. */ + time(&now); + + /* We'll need keys, or router_dump_router_to_string() would return NULL. */ + ident_key = pk_generate(0); + tap_key = pk_generate(0); + + tor_assert(ident_key != NULL); + tor_assert(tap_key != NULL); + + mock_routerinfo = tor_malloc_zero(sizeof(routerinfo_t)); + mock_routerinfo->nickname = tor_strdup("ConlonNancarrow"); + mock_routerinfo->addr = 123456789; + mock_routerinfo->or_port = 443; + mock_routerinfo->platform = tor_strdup("unittest"); + mock_routerinfo->cache_info.published_on = now; + mock_routerinfo->identity_pkey = crypto_pk_dup_key(ident_key); + mock_routerinfo->onion_pkey = crypto_pk_dup_key(tap_key); + mock_routerinfo->bandwidthrate = 9001; + mock_routerinfo->bandwidthburst = 9002; + } + + return mock_routerinfo; +} + +/* If no distribution option was set, then check_bridge_distribution_setting() + * should have set it to "any". */ +static void +test_router_dump_router_to_string_no_bridge_distribution_method(void *arg) +{ + const char* needle = "bridge-distribution-request any"; + or_options_t* options = get_options_mutable(); + routerinfo_t* router = NULL; + curve25519_keypair_t ntor_keypair; + ed25519_keypair_t signing_keypair; + char* desc = NULL; + char* found = NULL; + (void)arg; + + NS_MOCK(router_get_my_routerinfo); + + options->ORPort_set = 1; + options->BridgeRelay = 1; + + /* Generate keys which router_dump_router_to_string() expects to exist. */ + tt_int_op(0, ==, curve25519_keypair_generate(&ntor_keypair, 0)); + tt_int_op(0, ==, ed25519_keypair_generate(&signing_keypair, 0)); + + /* Set up part of our routerinfo_t so that we don't trigger any other + * assertions in router_dump_router_to_string(). */ + router = (routerinfo_t*)router_get_my_routerinfo(); + tt_ptr_op(router, !=, NULL); + + router->onion_curve25519_pkey = &ntor_keypair.pubkey; + + /* Generate our server descriptor and ensure that the substring + * "bridge-distribution-request any" occurs somewhere within it. */ + desc = router_dump_router_to_string(router, + router->identity_pkey, + router->onion_pkey, + &ntor_keypair, + &signing_keypair); + tt_ptr_op(desc, !=, NULL); + found = strstr(desc, needle); + tt_ptr_op(found, !=, NULL); + + done: + NS_UNMOCK(router_get_my_routerinfo); + + tor_free(desc); +} + +#define ROUTER_TEST(name, flags) \ + { #name, test_router_ ## name, flags, NULL, NULL } + +struct testcase_t router_tests[] = { + ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK), + END_OF_TESTCASES +}; + diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c index db6b9b3872..d8b72651a0 100644 --- a/src/test/test_routerkeys.c +++ b/src/test/test_routerkeys.c @@ -92,8 +92,8 @@ test_routerkeys_ed_certs(void *args) uint8_t *junk = NULL; char *base64 = NULL; - tt_int_op(0,==,ed25519_keypair_generate(&kp1, 0)); - tt_int_op(0,==,ed25519_keypair_generate(&kp2, 0)); + tt_int_op(0,OP_EQ,ed25519_keypair_generate(&kp1, 0)); + tt_int_op(0,OP_EQ,ed25519_keypair_generate(&kp2, 0)); for (int i = 0; i <= 1; ++i) { uint32_t flags = i ? CERT_FLAG_INCLUDE_SIGNING_KEY : 0; @@ -101,45 +101,45 @@ test_routerkeys_ed_certs(void *args) cert[i] = tor_cert_create(&kp1, 5, &kp2.pubkey, now, 10000, flags); tt_assert(cert[i]); - tt_assert(cert[i]->sig_bad == 0); - tt_assert(cert[i]->sig_ok == 1); - tt_assert(cert[i]->cert_expired == 0); - tt_assert(cert[i]->cert_valid == 1); - tt_int_op(cert[i]->cert_type, ==, 5); - tt_mem_op(cert[i]->signed_key.pubkey, ==, &kp2.pubkey.pubkey, 32); - tt_mem_op(cert[i]->signing_key.pubkey, ==, &kp1.pubkey.pubkey, 32); - tt_int_op(cert[i]->signing_key_included, ==, i); + tt_uint_op(cert[i]->sig_bad, OP_EQ, 0); + tt_uint_op(cert[i]->sig_ok, OP_EQ, 1); + tt_uint_op(cert[i]->cert_expired, OP_EQ, 0); + tt_uint_op(cert[i]->cert_valid, OP_EQ, 1); + tt_int_op(cert[i]->cert_type, OP_EQ, 5); + tt_mem_op(cert[i]->signed_key.pubkey, OP_EQ, &kp2.pubkey.pubkey, 32); + tt_mem_op(cert[i]->signing_key.pubkey, OP_EQ, &kp1.pubkey.pubkey, 32); + tt_int_op(cert[i]->signing_key_included, OP_EQ, i); tt_assert(cert[i]->encoded); - tt_int_op(cert[i]->encoded_len, ==, 104 + 36 * i); - tt_int_op(cert[i]->encoded[0], ==, 1); - tt_int_op(cert[i]->encoded[1], ==, 5); + tt_int_op(cert[i]->encoded_len, OP_EQ, 104 + 36 * i); + tt_int_op(cert[i]->encoded[0], OP_EQ, 1); + tt_int_op(cert[i]->encoded[1], OP_EQ, 5); parsed_cert[i] = tor_cert_parse(cert[i]->encoded, cert[i]->encoded_len); tt_assert(parsed_cert[i]); - tt_int_op(cert[i]->encoded_len, ==, parsed_cert[i]->encoded_len); - tt_mem_op(cert[i]->encoded, ==, parsed_cert[i]->encoded, + tt_int_op(cert[i]->encoded_len, OP_EQ, parsed_cert[i]->encoded_len); + tt_mem_op(cert[i]->encoded, OP_EQ, parsed_cert[i]->encoded, cert[i]->encoded_len); - tt_assert(parsed_cert[i]->sig_bad == 0); - tt_assert(parsed_cert[i]->sig_ok == 0); - tt_assert(parsed_cert[i]->cert_expired == 0); - tt_assert(parsed_cert[i]->cert_valid == 0); + tt_uint_op(parsed_cert[i]->sig_bad, OP_EQ, 0); + tt_uint_op(parsed_cert[i]->sig_ok, OP_EQ, 0); + tt_uint_op(parsed_cert[i]->cert_expired, OP_EQ, 0); + tt_uint_op(parsed_cert[i]->cert_valid, OP_EQ, 0); /* Expired */ tt_int_op(tor_cert_checksig(parsed_cert[i], &kp1.pubkey, now + 30000), - <, 0); - tt_assert(parsed_cert[i]->cert_expired == 1); + OP_LT, 0); + tt_uint_op(parsed_cert[i]->cert_expired, OP_EQ, 1); parsed_cert[i]->cert_expired = 0; /* Wrong key */ - tt_int_op(tor_cert_checksig(parsed_cert[i], &kp2.pubkey, now), <, 0); - tt_assert(parsed_cert[i]->sig_bad== 1); + tt_int_op(tor_cert_checksig(parsed_cert[i], &kp2.pubkey, now), OP_LT, 0); + tt_uint_op(parsed_cert[i]->sig_bad, OP_EQ, 1); parsed_cert[i]->sig_bad = 0; /* Missing key */ int ok = tor_cert_checksig(parsed_cert[i], NULL, now); - tt_int_op(ok < 0, ==, i == 0); - tt_assert(parsed_cert[i]->sig_bad == 0); + tt_int_op(ok < 0, OP_EQ, i == 0); + tt_uint_op(parsed_cert[i]->sig_bad, OP_EQ, 0); tt_assert(parsed_cert[i]->sig_ok == (i != 0)); tt_assert(parsed_cert[i]->cert_valid == (i != 0)); parsed_cert[i]->sig_bad = 0; @@ -147,29 +147,29 @@ test_routerkeys_ed_certs(void *args) parsed_cert[i]->cert_valid = 0; /* Right key */ - tt_int_op(tor_cert_checksig(parsed_cert[i], &kp1.pubkey, now), ==, 0); - tt_assert(parsed_cert[i]->sig_bad == 0); - tt_assert(parsed_cert[i]->sig_ok == 1); - tt_assert(parsed_cert[i]->cert_expired == 0); - tt_assert(parsed_cert[i]->cert_valid == 1); + tt_int_op(tor_cert_checksig(parsed_cert[i], &kp1.pubkey, now), OP_EQ, 0); + tt_uint_op(parsed_cert[i]->sig_bad, OP_EQ, 0); + tt_uint_op(parsed_cert[i]->sig_ok, OP_EQ, 1); + tt_uint_op(parsed_cert[i]->cert_expired, OP_EQ, 0); + tt_uint_op(parsed_cert[i]->cert_valid, OP_EQ, 1); } /* Now try some junky certs. */ /* - Truncated */ nocert = tor_cert_parse(cert[0]->encoded, cert[0]->encoded_len-1); - tt_ptr_op(NULL, ==, nocert); + tt_ptr_op(NULL, OP_EQ, nocert); /* - First byte modified */ cert[0]->encoded[0] = 99; nocert = tor_cert_parse(cert[0]->encoded, cert[0]->encoded_len); - tt_ptr_op(NULL, ==, nocert); + tt_ptr_op(NULL, OP_EQ, nocert); cert[0]->encoded[0] = 1; /* - Extra byte at the end*/ junk = tor_malloc_zero(cert[0]->encoded_len + 1); memcpy(junk, cert[0]->encoded, cert[0]->encoded_len); nocert = tor_cert_parse(junk, cert[0]->encoded_len+1); - tt_ptr_op(NULL, ==, nocert); + tt_ptr_op(NULL, OP_EQ, nocert); /* - Multiple signing key instances */ tor_free(junk); @@ -183,7 +183,7 @@ test_routerkeys_ed_certs(void *args) junk[77] = 32; /* extlen */ junk[78] = 4; /* exttype */ nocert = tor_cert_parse(junk, 104 + 36 * 2); - tt_ptr_op(NULL, ==, nocert); + tt_ptr_op(NULL, OP_EQ, nocert); done: tor_cert_free(cert[0]); @@ -211,11 +211,12 @@ test_routerkeys_ed_key_create(void *arg) kp2 = ed_key_new(kp1, INIT_ED_KEY_NEEDCERT, now, 3600, 4, &cert); tt_assert(kp2); tt_assert(cert); - tt_mem_op(&cert->signed_key, ==, &kp2->pubkey, sizeof(ed25519_public_key_t)); + tt_mem_op(&cert->signed_key, OP_EQ, &kp2->pubkey, + sizeof(ed25519_public_key_t)); tt_assert(! cert->signing_key_included); - tt_int_op(cert->valid_until, >=, now); - tt_int_op(cert->valid_until, <=, now+7200); + tt_int_op(cert->valid_until, OP_GE, now); + tt_int_op(cert->valid_until, OP_LE, now+7200); /* Create a new key-including certificate signed by kp1 */ ed25519_keypair_free(kp2); @@ -227,8 +228,10 @@ test_routerkeys_ed_key_create(void *arg) tt_assert(kp2); tt_assert(cert); tt_assert(cert->signing_key_included); - tt_mem_op(&cert->signed_key, ==, &kp2->pubkey, sizeof(ed25519_public_key_t)); - tt_mem_op(&cert->signing_key, ==, &kp1->pubkey,sizeof(ed25519_public_key_t)); + tt_mem_op(&cert->signed_key, OP_EQ, &kp2->pubkey, + sizeof(ed25519_public_key_t)); + tt_mem_op(&cert->signing_key, OP_EQ, &kp1->pubkey, + sizeof(ed25519_public_key_t)); done: ed25519_keypair_free(kp1); @@ -261,8 +264,8 @@ test_routerkeys_ed_key_init_basic(void *arg) NULL, now, 0, 7, &cert); tt_assert(kp1 != NULL); tt_assert(cert == NULL); - tt_int_op(stat(get_fname("test_ed_key_1_cert"), &st), <, 0); - tt_int_op(stat(get_fname("test_ed_key_1_secret_key"), &st), ==, 0); + tt_int_op(stat(get_fname("test_ed_key_1_cert"), &st), OP_LT, 0); + tt_int_op(stat(get_fname("test_ed_key_1_secret_key"), &st), OP_EQ, 0); /* Fail to load if we say we need a cert */ kp2 = ed_key_init_from_file(fname1, INIT_ED_KEY_NEEDCERT, LOG_INFO, @@ -279,14 +282,14 @@ test_routerkeys_ed_key_init_basic(void *arg) NULL, now, 0, 7, &cert); tt_assert(kp2 != NULL); tt_assert(cert == NULL); - tt_mem_op(kp1, ==, kp2, sizeof(*kp1)); + tt_mem_op(kp1, OP_EQ, kp2, sizeof(*kp1)); ed25519_keypair_free(kp2); kp2 = NULL; kp2 = ed_key_init_from_file(fname1, 0, LOG_INFO, NULL, now, 0, 7, &cert); tt_assert(kp2 != NULL); tt_assert(cert == NULL); - tt_mem_op(kp1, ==, kp2, sizeof(*kp1)); + tt_mem_op(kp1, OP_EQ, kp2, sizeof(*kp1)); ed25519_keypair_free(kp2); kp2 = NULL; /* Now create a key with a cert. */ @@ -295,34 +298,34 @@ test_routerkeys_ed_key_init_basic(void *arg) LOG_INFO, kp1, now, 7200, 7, &cert); tt_assert(kp2 != NULL); tt_assert(cert != NULL); - tt_mem_op(kp1, !=, kp2, sizeof(*kp1)); - tt_int_op(stat(get_fname("test_ed_key_2_cert"), &st), ==, 0); - tt_int_op(stat(get_fname("test_ed_key_2_secret_key"), &st), ==, 0); + tt_mem_op(kp1, OP_NE, kp2, sizeof(*kp1)); + tt_int_op(stat(get_fname("test_ed_key_2_cert"), &st), OP_EQ, 0); + tt_int_op(stat(get_fname("test_ed_key_2_secret_key"), &st), OP_EQ, 0); tt_assert(cert->cert_valid == 1); - tt_mem_op(&cert->signed_key, ==, &kp2->pubkey, 32); + tt_mem_op(&cert->signed_key, OP_EQ, &kp2->pubkey, 32); /* Now verify we can load the cert... */ kp3 = ed_key_init_from_file(fname2, (INIT_ED_KEY_CREATE| INIT_ED_KEY_NEEDCERT), LOG_INFO, kp1, now, 7200, 7, &cert2); - tt_mem_op(kp2, ==, kp3, sizeof(*kp2)); - tt_mem_op(cert2->encoded, ==, cert->encoded, cert->encoded_len); + tt_mem_op(kp2, OP_EQ, kp3, sizeof(*kp2)); + tt_mem_op(cert2->encoded, OP_EQ, cert->encoded, cert->encoded_len); ed25519_keypair_free(kp3); kp3 = NULL; tor_cert_free(cert2); cert2 = NULL; /* ... even without create... */ kp3 = ed_key_init_from_file(fname2, INIT_ED_KEY_NEEDCERT, LOG_INFO, kp1, now, 7200, 7, &cert2); - tt_mem_op(kp2, ==, kp3, sizeof(*kp2)); - tt_mem_op(cert2->encoded, ==, cert->encoded, cert->encoded_len); + tt_mem_op(kp2, OP_EQ, kp3, sizeof(*kp2)); + tt_mem_op(cert2->encoded, OP_EQ, cert->encoded, cert->encoded_len); ed25519_keypair_free(kp3); kp3 = NULL; tor_cert_free(cert2); cert2 = NULL; /* ... but that we don't crash or anything if we say we don't want it. */ kp3 = ed_key_init_from_file(fname2, INIT_ED_KEY_NEEDCERT, LOG_INFO, kp1, now, 7200, 7, NULL); - tt_mem_op(kp2, ==, kp3, sizeof(*kp2)); + tt_mem_op(kp2, OP_EQ, kp3, sizeof(*kp2)); ed25519_keypair_free(kp3); kp3 = NULL; /* Fail if we're told the wrong signing key */ @@ -367,16 +370,16 @@ test_routerkeys_ed_key_init_split(void *arg) LOG_INFO, NULL, now, 0, 7, &cert); tt_assert(kp1 != NULL); tt_assert(cert == NULL); - tt_int_op(stat(get_fname("test_ed_key_3_cert"), &st), <, 0); - tt_int_op(stat(get_fname("test_ed_key_3_secret_key"), &st), ==, 0); - tt_int_op(stat(get_fname("test_ed_key_3_public_key"), &st), ==, 0); + tt_int_op(stat(get_fname("test_ed_key_3_cert"), &st), OP_LT, 0); + tt_int_op(stat(get_fname("test_ed_key_3_secret_key"), &st), OP_EQ, 0); + tt_int_op(stat(get_fname("test_ed_key_3_public_key"), &st), OP_EQ, 0); /* Load it. */ kp2 = ed_key_init_from_file(fname1, flags|INIT_ED_KEY_CREATE, LOG_INFO, NULL, now, 0, 7, &cert); tt_assert(kp2 != NULL); tt_assert(cert == NULL); - tt_mem_op(kp1, ==, kp2, sizeof(*kp2)); + tt_mem_op(kp1, OP_EQ, kp2, sizeof(*kp2)); ed25519_keypair_free(kp2); kp2 = NULL; /* Okay, try killing the secret key and loading it. */ @@ -385,7 +388,7 @@ test_routerkeys_ed_key_init_split(void *arg) LOG_INFO, NULL, now, 0, 7, &cert); tt_assert(kp2 != NULL); tt_assert(cert == NULL); - tt_mem_op(&kp1->pubkey, ==, &kp2->pubkey, sizeof(kp2->pubkey)); + tt_mem_op(&kp1->pubkey, OP_EQ, &kp2->pubkey, sizeof(kp2->pubkey)); tt_assert(tor_mem_is_zero((char*)kp2->seckey.seckey, sizeof(kp2->seckey.seckey))); ed25519_keypair_free(kp2); kp2 = NULL; @@ -395,7 +398,7 @@ test_routerkeys_ed_key_init_split(void *arg) LOG_INFO, NULL, now, 0, 7, &cert); tt_assert(kp2 != NULL); tt_assert(cert == NULL); - tt_mem_op(&kp1->pubkey, ==, &kp2->pubkey, sizeof(kp2->pubkey)); + tt_mem_op(&kp1->pubkey, OP_EQ, &kp2->pubkey, sizeof(kp2->pubkey)); tt_assert(tor_mem_is_zero((char*)kp2->seckey.seckey, sizeof(kp2->seckey.seckey))); ed25519_keypair_free(kp2); kp2 = NULL; @@ -446,12 +449,12 @@ test_routerkeys_ed_keys_init_all(void *arg) #else mkdir(dir, 0700); mkdir(get_fname("test_ed_keys_init_all/keys"), 0700); -#endif +#endif /* defined(_WIN32) */ options->DataDirectory = dir; - tt_int_op(1, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); + tt_int_op(1, OP_EQ, load_ed_keys(options, now)); + tt_int_op(0, OP_EQ, generate_ed_link_cert(options, now, 0)); tt_assert(get_master_identity_key()); tt_assert(get_master_identity_key()); tt_assert(get_master_signing_keypair()); @@ -465,21 +468,21 @@ test_routerkeys_ed_keys_init_all(void *arg) link_cert = tor_cert_dup(get_current_link_cert_cert()); /* Call load_ed_keys again, but nothing has changed. */ - tt_int_op(0, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); - tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); - tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); - tt_mem_op(&auth, ==, get_current_auth_keypair(), sizeof(auth)); + tt_int_op(0, OP_EQ, load_ed_keys(options, now)); + tt_int_op(0, OP_EQ, generate_ed_link_cert(options, now, 0)); + tt_mem_op(&id, OP_EQ, get_master_identity_key(), sizeof(id)); + tt_mem_op(&sign, OP_EQ, get_master_signing_keypair(), sizeof(sign)); + tt_mem_op(&auth, OP_EQ, get_current_auth_keypair(), sizeof(auth)); tt_assert(tor_cert_eq(link_cert, get_current_link_cert_cert())); /* Force a reload: we make new link/auth keys. */ routerkeys_free_all(); - tt_int_op(1, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); - tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); - tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); + tt_int_op(1, OP_EQ, load_ed_keys(options, now)); + tt_int_op(0, OP_EQ, generate_ed_link_cert(options, now, 0)); + tt_mem_op(&id, OP_EQ, get_master_identity_key(), sizeof(id)); + tt_mem_op(&sign, OP_EQ, get_master_signing_keypair(), sizeof(sign)); tt_assert(tor_cert_eq(link_cert, get_current_link_cert_cert())); - tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth)); + tt_mem_op(&auth, OP_NE, get_current_auth_keypair(), sizeof(auth)); tt_assert(get_master_signing_key_cert()); tt_assert(get_current_link_cert_cert()); tt_assert(get_current_auth_key_cert()); @@ -488,12 +491,12 @@ test_routerkeys_ed_keys_init_all(void *arg) memcpy(&auth, get_current_auth_keypair(), sizeof(auth)); /* Force a link/auth-key regeneration by advancing time. */ - tt_int_op(0, ==, load_ed_keys(options, now+3*86400)); - tt_int_op(0, ==, generate_ed_link_cert(options, now+3*86400, 0)); - tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); - tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); + tt_int_op(0, OP_EQ, load_ed_keys(options, now+3*86400)); + tt_int_op(0, OP_EQ, generate_ed_link_cert(options, now+3*86400, 0)); + tt_mem_op(&id, OP_EQ, get_master_identity_key(), sizeof(id)); + tt_mem_op(&sign, OP_EQ, get_master_signing_keypair(), sizeof(sign)); tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert())); - tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth)); + tt_mem_op(&auth, OP_NE, get_current_auth_keypair(), sizeof(auth)); tt_assert(get_master_signing_key_cert()); tt_assert(get_current_link_cert_cert()); tt_assert(get_current_auth_key_cert()); @@ -502,12 +505,12 @@ test_routerkeys_ed_keys_init_all(void *arg) memcpy(&auth, get_current_auth_keypair(), sizeof(auth)); /* Force a signing-key regeneration by advancing time. */ - tt_int_op(1, ==, load_ed_keys(options, now+100*86400)); - tt_int_op(0, ==, generate_ed_link_cert(options, now+100*86400, 0)); - tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); - tt_mem_op(&sign, !=, get_master_signing_keypair(), sizeof(sign)); + tt_int_op(1, OP_EQ, load_ed_keys(options, now+100*86400)); + tt_int_op(0, OP_EQ, generate_ed_link_cert(options, now+100*86400, 0)); + tt_mem_op(&id, OP_EQ, get_master_identity_key(), sizeof(id)); + tt_mem_op(&sign, OP_NE, get_master_signing_keypair(), sizeof(sign)); tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert())); - tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth)); + tt_mem_op(&auth, OP_NE, get_current_auth_keypair(), sizeof(auth)); tt_assert(get_master_signing_key_cert()); tt_assert(get_current_link_cert_cert()); tt_assert(get_current_auth_key_cert()); @@ -520,12 +523,12 @@ test_routerkeys_ed_keys_init_all(void *arg) routerkeys_free_all(); unlink(get_fname("test_ed_keys_init_all/keys/" "ed25519_master_id_secret_key")); - tt_int_op(1, ==, load_ed_keys(options, now)); - tt_int_op(0, ==, generate_ed_link_cert(options, now, 0)); - tt_mem_op(&id, ==, get_master_identity_key(), sizeof(id)); - tt_mem_op(&sign, ==, get_master_signing_keypair(), sizeof(sign)); + tt_int_op(1, OP_EQ, load_ed_keys(options, now)); + tt_int_op(0, OP_EQ, generate_ed_link_cert(options, now, 0)); + tt_mem_op(&id, OP_EQ, get_master_identity_key(), sizeof(id)); + tt_mem_op(&sign, OP_EQ, get_master_signing_keypair(), sizeof(sign)); tt_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert())); - tt_mem_op(&auth, !=, get_current_auth_keypair(), sizeof(auth)); + tt_mem_op(&auth, OP_NE, get_current_auth_keypair(), sizeof(auth)); tt_assert(get_master_signing_key_cert()); tt_assert(get_current_link_cert_cert()); tt_assert(get_current_auth_key_cert()); @@ -535,7 +538,7 @@ test_routerkeys_ed_keys_init_all(void *arg) log_global_min_severity_ = LOG_ERR; /* Suppress warnings. * XXX (better way to do this)? */ routerkeys_free_all(); - tt_int_op(-1, ==, load_ed_keys(options, now+200*86400)); + tt_int_op(-1, OP_EQ, load_ed_keys(options, now+200*86400)); done: tor_free(dir); @@ -556,20 +559,20 @@ test_routerkeys_cross_certify_ntor(void *args) time_t now = time(NULL); int sign; - tt_int_op(0, ==, ed25519_public_from_base64(&master_key, + tt_int_op(0, OP_EQ, ed25519_public_from_base64(&master_key, "IamwritingthesetestsOnARainyAfternoonin2014")); - tt_int_op(0, ==, curve25519_keypair_generate(&onion_keys, 0)); + tt_int_op(0, OP_EQ, curve25519_keypair_generate(&onion_keys, 0)); cert = make_ntor_onion_key_crosscert(&onion_keys, &master_key, now, 10000, &sign); tt_assert(cert); tt_assert(sign == 0 || sign == 1); - tt_int_op(cert->cert_type, ==, CERT_TYPE_ONION_ID); - tt_int_op(1, ==, ed25519_pubkey_eq(&cert->signed_key, &master_key)); - tt_int_op(0, ==, ed25519_public_key_from_curve25519_public_key( + tt_int_op(cert->cert_type, OP_EQ, CERT_TYPE_ONION_ID); + tt_int_op(1, OP_EQ, ed25519_pubkey_eq(&cert->signed_key, &master_key)); + tt_int_op(0, OP_EQ, ed25519_public_key_from_curve25519_public_key( &onion_check_key, &onion_keys.pubkey, sign)); - tt_int_op(0, ==, tor_cert_checksig(cert, &onion_check_key, now)); + tt_int_op(0, OP_EQ, tor_cert_checksig(cert, &onion_check_key, now)); done: tor_cert_free(cert); @@ -587,7 +590,7 @@ test_routerkeys_cross_certify_tap(void *args) char buf[128]; int n; - tt_int_op(0, ==, ed25519_public_from_base64(&master_key, + tt_int_op(0, OP_EQ, ed25519_public_from_base64(&master_key, "IAlreadyWroteTestsForRouterdescsUsingTheseX")); cc = make_tap_onion_key_crosscert(onion_key, @@ -598,14 +601,14 @@ test_routerkeys_cross_certify_tap(void *args) n = crypto_pk_public_checksig(onion_key, buf, sizeof(buf), (char*)cc, cc_len); - tt_int_op(n,>,0); - tt_int_op(n,==,52); + tt_int_op(n,OP_GT,0); + tt_int_op(n,OP_EQ,52); crypto_pk_get_digest(id_key, digest); - tt_mem_op(buf,==,digest,20); - tt_mem_op(buf+20,==,master_key.pubkey,32); + tt_mem_op(buf,OP_EQ,digest,20); + tt_mem_op(buf+20,OP_EQ,master_key.pubkey,32); - tt_int_op(0, ==, check_tap_onion_key_crosscert(cc, cc_len, + tt_int_op(0, OP_EQ, check_tap_onion_key_crosscert(cc, cc_len, onion_key, &master_key, (uint8_t*)digest)); done: diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 0b4b6c5c44..3b0e943ce5 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -117,7 +117,7 @@ test_routerlist_launch_descriptor_downloads(void *arg) MOCK(initiate_descriptor_downloads, mock_initiate_descriptor_downloads); launch_descriptor_downloads(DIR_PURPOSE_FETCH_MICRODESC, downloadable, NULL, now); - tt_int_op(3, ==, count); + tt_int_op(3, OP_EQ, count); UNMOCK(initiate_descriptor_downloads); done: @@ -148,24 +148,24 @@ construct_consensus(char **consensus_text_md) &v1, &n_vrs, now, 1); networkstatus_vote_free(vote); tt_assert(v1); - tt_int_op(n_vrs, ==, 4); - tt_int_op(smartlist_len(v1->routerstatus_list), ==, 4); + tt_int_op(n_vrs, OP_EQ, 4); + tt_int_op(smartlist_len(v1->routerstatus_list), OP_EQ, 4); dir_common_construct_vote_2(&vote, cert2, sign_skey_2, &dir_common_gen_routerstatus_for_v3ns, &v2, &n_vrs, now, 1); networkstatus_vote_free(vote); tt_assert(v2); - tt_int_op(n_vrs, ==, 4); - tt_int_op(smartlist_len(v2->routerstatus_list), ==, 4); + tt_int_op(n_vrs, OP_EQ, 4); + tt_int_op(smartlist_len(v2->routerstatus_list), OP_EQ, 4); dir_common_construct_vote_3(&vote, cert3, sign_skey_3, &dir_common_gen_routerstatus_for_v3ns, &v3, &n_vrs, now, 1); tt_assert(v3); - tt_int_op(n_vrs, ==, 4); - tt_int_op(smartlist_len(v3->routerstatus_list), ==, 4); + tt_int_op(n_vrs, OP_EQ, 4); + tt_int_op(smartlist_len(v3->routerstatus_list), OP_EQ, 4); networkstatus_vote_free(vote); votes = smartlist_new(); smartlist_add(votes, v1); @@ -247,16 +247,16 @@ test_router_pick_directory_server_impl(void *arg) /* No consensus available, fail early */ rs = router_pick_directory_server_impl(V3_DIRINFO, (const int) 0, NULL); - tt_assert(rs == NULL); + tt_ptr_op(rs, OP_EQ, NULL); construct_consensus(&consensus_text_md); tt_assert(consensus_text_md); con_md = networkstatus_parse_vote_from_string(consensus_text_md, NULL, NS_TYPE_CONSENSUS); tt_assert(con_md); - tt_int_op(con_md->flavor,==, FLAV_MICRODESC); + tt_int_op(con_md->flavor,OP_EQ, FLAV_MICRODESC); tt_assert(con_md->routerstatus_list); - tt_int_op(smartlist_len(con_md->routerstatus_list), ==, 3); + tt_int_op(smartlist_len(con_md->routerstatus_list), OP_EQ, 3); tt_assert(!networkstatus_set_current_consensus_from_ns(con_md, "microdesc")); @@ -287,7 +287,7 @@ test_router_pick_directory_server_impl(void *arg) rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); /* We should not fail now we have a consensus and routerstatus_list * and nodelist are populated. */ - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); /* Manipulate the nodes so we get the dir server we expect */ router1_id = tor_malloc(DIGEST_LEN); @@ -306,7 +306,7 @@ test_router_pick_directory_server_impl(void *arg) node_router1->is_running = 0; node_router3->is_running = 0; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN)); rs = NULL; node_router1->is_running = 1; @@ -319,7 +319,7 @@ test_router_pick_directory_server_impl(void *arg) node_router1->rs->dir_port = 0; node_router3->rs->dir_port = 0; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN)); rs = NULL; node_router1->rs->is_v2_dir = 1; @@ -330,7 +330,7 @@ test_router_pick_directory_server_impl(void *arg) node_router1->is_valid = 0; node_router3->is_valid = 0; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN)); rs = NULL; node_router1->is_valid = 1; @@ -341,7 +341,7 @@ test_router_pick_directory_server_impl(void *arg) node_router2->rs->last_dir_503_at = now; node_router3->rs->last_dir_503_at = now; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); node_router2->rs->last_dir_503_at = 0; node_router3->rs->last_dir_503_at = 0; @@ -358,13 +358,13 @@ test_router_pick_directory_server_impl(void *arg) node_router2->rs->or_port = 443; node_router3->rs->or_port = 442; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router3_id, DIGEST_LEN)); node_router1->rs->or_port = 442; node_router2->rs->or_port = 443; node_router3->rs->or_port = 444; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); /* Fascist firewall and overloaded */ @@ -373,7 +373,7 @@ test_router_pick_directory_server_impl(void *arg) node_router3->rs->or_port = 442; node_router3->rs->last_dir_503_at = now; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); node_router3->rs->last_dir_503_at = 0; @@ -391,7 +391,7 @@ test_router_pick_directory_server_impl(void *arg) node_router3->rs->dir_port = 81; node_router1->rs->last_dir_503_at = now; rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); - tt_assert(rs != NULL); + tt_ptr_op(rs, OP_NE, NULL); tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); node_router1->rs->last_dir_503_at = 0; @@ -449,27 +449,27 @@ test_routerlist_router_is_already_dir_fetching(void *arg) /* Test that we never get 1 from a NULL connection */ mocked_connection = NULL; - tt_assert(router_is_already_dir_fetching(&test_ap, 1, 1) == 0); - tt_assert(router_is_already_dir_fetching(&test_ap, 1, 0) == 0); - tt_assert(router_is_already_dir_fetching(&test_ap, 0, 1) == 0); + tt_int_op(router_is_already_dir_fetching(&test_ap, 1, 1), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(&test_ap, 1, 0), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(&test_ap, 0, 1), OP_EQ, 0); /* We always expect 0 in these cases */ - tt_assert(router_is_already_dir_fetching(&test_ap, 0, 0) == 0); - tt_assert(router_is_already_dir_fetching(NULL, 1, 1) == 0); - tt_assert(router_is_already_dir_fetching(&null_addr_ap, 1, 1) == 0); - tt_assert(router_is_already_dir_fetching(&zero_port_ap, 1, 1) == 0); + tt_int_op(router_is_already_dir_fetching(&test_ap, 0, 0), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(NULL, 1, 1), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(&null_addr_ap, 1, 1), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(&zero_port_ap, 1, 1), OP_EQ, 0); /* Test that we get 1 with a connection in the appropriate circumstances */ mocked_connection = connection_new(CONN_TYPE_DIR, AF_INET); - tt_assert(router_is_already_dir_fetching(&test_ap, 1, 1) == 1); - tt_assert(router_is_already_dir_fetching(&test_ap, 1, 0) == 1); - tt_assert(router_is_already_dir_fetching(&test_ap, 0, 1) == 1); + tt_int_op(router_is_already_dir_fetching(&test_ap, 1, 1), OP_EQ, 1); + tt_int_op(router_is_already_dir_fetching(&test_ap, 1, 0), OP_EQ, 1); + tt_int_op(router_is_already_dir_fetching(&test_ap, 0, 1), OP_EQ, 1); /* Test that we get 0 even with a connection in the appropriate * circumstances */ - tt_assert(router_is_already_dir_fetching(&test_ap, 0, 0) == 0); - tt_assert(router_is_already_dir_fetching(NULL, 1, 1) == 0); - tt_assert(router_is_already_dir_fetching(&null_addr_ap, 1, 1) == 0); - tt_assert(router_is_already_dir_fetching(&zero_port_ap, 1, 1) == 0); + tt_int_op(router_is_already_dir_fetching(&test_ap, 0, 0), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(NULL, 1, 1), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(&null_addr_ap, 1, 1), OP_EQ, 0); + tt_int_op(router_is_already_dir_fetching(&zero_port_ap, 1, 1), OP_EQ, 0); done: /* If a connection is never set up, connection_free chokes on it. */ diff --git a/src/test/test_routerset.c b/src/test/test_routerset.c index 7efd042ed5..c9c69911da 100644 --- a/src/test/test_routerset.c +++ b/src/test/test_routerset.c @@ -1602,7 +1602,7 @@ NS(test_main)(void *arg) */ NS_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unused)); + (const char *nickname, unsigned flags)); static const char *NS(mock_nickname); static void @@ -1632,11 +1632,11 @@ NS(test_main)(void *arg) } const node_t * -NS(node_get_by_nickname)(const char *nickname, int warn_if_unused) +NS(node_get_by_nickname)(const char *nickname, unsigned flags) { CALLED(node_get_by_nickname)++; tt_str_op(nickname, OP_EQ, NS(mock_nickname)); - tt_int_op(warn_if_unused, OP_EQ, 1); + tt_uint_op(flags, OP_EQ, 0); done: return NULL; @@ -1651,7 +1651,7 @@ NS(node_get_by_nickname)(const char *nickname, int warn_if_unused) */ NS_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unused)); + (const char *nickname, unsigned flags)); static const char *NS(mock_nickname); static node_t NS(mock_node); @@ -1683,11 +1683,11 @@ NS(test_main)(void *arg) } const node_t * -NS(node_get_by_nickname)(const char *nickname, int warn_if_unused) +NS(node_get_by_nickname)(const char *nickname, unsigned flags) { CALLED(node_get_by_nickname)++; tt_str_op(nickname, OP_EQ, NS(mock_nickname)); - tt_int_op(warn_if_unused, OP_EQ, 1); + tt_int_op(flags, OP_EQ, 0); done: return &NS(mock_node); @@ -1701,7 +1701,7 @@ NS(node_get_by_nickname)(const char *nickname, int warn_if_unused) */ NS_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unused)); + (const char *nickname, unsigned flags)); static char *NS(mock_nickname); static node_t NS(mock_node); @@ -1735,11 +1735,11 @@ NS(test_main)(void *arg) } const node_t * -NS(node_get_by_nickname)(const char *nickname, int warn_if_unused) +NS(node_get_by_nickname)(const char *nickname, unsigned flags) { CALLED(node_get_by_nickname)++; tt_str_op(nickname, OP_EQ, NS(mock_nickname)); - tt_int_op(warn_if_unused, OP_EQ, 1); + tt_int_op(flags, OP_EQ, 0); done: return &NS(mock_node); diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index 4c536b0905..63add2f382 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -6,11 +6,16 @@ #include <math.h> #include <event2/event.h> +#define SCHEDULER_KIST_PRIVATE #define TOR_CHANNEL_INTERNAL_ #define CHANNEL_PRIVATE_ #include "or.h" +#include "config.h" #include "compat_libevent.h" #include "channel.h" +#include "channeltls.h" +#include "connection.h" +#include "networkstatus.h" #define SCHEDULER_PRIVATE_ #include "scheduler.h" @@ -18,64 +23,100 @@ #include "test.h" #include "fakechans.h" -/* Event base for scheduelr tests */ -static struct event_base *mock_event_base = NULL; - -/* Statics controlling mocks */ -static circuitmux_t *mock_ccm_tgt_1 = NULL; -static circuitmux_t *mock_ccm_tgt_2 = NULL; +/* Shamelessly stolen from compat_libevent.c */ +#define V(major, minor, patch) \ + (((major) << 24) | ((minor) << 16) | ((patch) << 8)) -static circuitmux_t *mock_cgp_tgt_1 = NULL; -static circuitmux_policy_t *mock_cgp_val_1 = NULL; -static circuitmux_t *mock_cgp_tgt_2 = NULL; -static circuitmux_policy_t *mock_cgp_val_2 = NULL; +/****************************************************************************** + * Statistical info + *****************************************************************************/ static int scheduler_compare_channels_mock_ctr = 0; static int scheduler_run_mock_ctr = 0; -static void channel_flush_some_cells_mock_free_all(void); -static void channel_flush_some_cells_mock_set(channel_t *chan, - ssize_t num_cells); +/****************************************************************************** + * Utility functions and things we need to mock + *****************************************************************************/ +static or_options_t mocked_options; +static const or_options_t * +mock_get_options(void) +{ + return &mocked_options; +} -/* Setup for mock event stuff */ -static void mock_event_free_all(void); -static void mock_event_init(void); +static void +cleanup_scheduler_options(void) +{ + if (mocked_options.SchedulerTypes_) { + SMARTLIST_FOREACH(mocked_options.SchedulerTypes_, int *, i, tor_free(i)); + smartlist_free(mocked_options.SchedulerTypes_); + mocked_options.SchedulerTypes_ = NULL; + } +} + +static void +set_scheduler_options(int val) +{ + int *type; -/* Mocks used by scheduler tests */ -static ssize_t channel_flush_some_cells_mock(channel_t *chan, - ssize_t num_cells); -static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, - circuitmux_t *cmux_2); -static const circuitmux_policy_t * circuitmux_get_policy_mock( - circuitmux_t *cmux); -static int scheduler_compare_channels_mock(const void *c1_v, - const void *c2_v); -static void scheduler_run_noop_mock(void); -static struct event_base * tor_libevent_get_base_mock(void); - -/* Scheduler test cases */ -static void test_scheduler_channel_states(void *arg); -static void test_scheduler_compare_channels(void *arg); -static void test_scheduler_initfree(void *arg); -static void test_scheduler_loop(void *arg); -static void test_scheduler_queue_heuristic(void *arg); - -/* Mock event init/free */ + if (mocked_options.SchedulerTypes_ == NULL) { + mocked_options.SchedulerTypes_ = smartlist_new(); + } + type = tor_malloc_zero(sizeof(int)); + *type = val; + smartlist_add(mocked_options.SchedulerTypes_, type); +} -/* Shamelessly stolen from compat_libevent.c */ -#define V(major, minor, patch) \ - (((major) << 24) | ((minor) << 16) | ((patch) << 8)) +static void +clear_options(void) +{ + cleanup_scheduler_options(); + memset(&mocked_options, 0, sizeof(mocked_options)); +} + +static int32_t +mock_vanilla_networkstatus_get_param( + const networkstatus_t *ns, const char *param_name, int32_t default_val, + int32_t min_val, int32_t max_val) +{ + (void)ns; + (void)default_val; + (void)min_val; + (void)max_val; + // only support KISTSchedRunInterval right now + tor_assert(strcmp(param_name, "KISTSchedRunInterval")==0); + return 0; +} + +static int32_t +mock_kist_networkstatus_get_param( + const networkstatus_t *ns, const char *param_name, int32_t default_val, + int32_t min_val, int32_t max_val) +{ + (void)ns; + (void)default_val; + (void)min_val; + (void)max_val; + // only support KISTSchedRunInterval right now + tor_assert(strcmp(param_name, "KISTSchedRunInterval")==0); + return 12; +} +/* Event base for scheduelr tests */ +static struct event_base *mock_event_base = NULL; +/* Setup for mock event stuff */ +static void mock_event_free_all(void); +static void mock_event_init(void); static void mock_event_free_all(void) { - tt_assert(mock_event_base != NULL); + tt_ptr_op(mock_event_base, OP_NE, NULL); if (mock_event_base) { event_base_free(mock_event_base); mock_event_base = NULL; } - tt_ptr_op(mock_event_base, ==, NULL); + tt_ptr_op(mock_event_base, OP_EQ, NULL); done: return; @@ -86,7 +127,7 @@ mock_event_init(void) { struct event_config *cfg = NULL; - tt_ptr_op(mock_event_base, ==, NULL); + tt_ptr_op(mock_event_base, OP_EQ, NULL); /* * Really cut down from tor_libevent_initialize of @@ -104,13 +145,90 @@ mock_event_init(void) event_config_free(cfg); } - tt_assert(mock_event_base != NULL); + tt_ptr_op(mock_event_base, OP_NE, NULL); done: return; } -/* Mocks */ +static struct event_base * +tor_libevent_get_base_mock(void) +{ + return mock_event_base; +} + +static int +scheduler_compare_channels_mock(const void *c1_v, + const void *c2_v) +{ + uintptr_t p1, p2; + + p1 = (uintptr_t)(c1_v); + p2 = (uintptr_t)(c2_v); + + ++scheduler_compare_channels_mock_ctr; + + if (p1 == p2) return 0; + else if (p1 < p2) return 1; + else return -1; +} + +static void +scheduler_run_noop_mock(void) +{ + ++scheduler_run_mock_ctr; +} + +static circuitmux_t *mock_ccm_tgt_1 = NULL; +static circuitmux_t *mock_ccm_tgt_2 = NULL; +static circuitmux_t *mock_cgp_tgt_1 = NULL; +static circuitmux_policy_t *mock_cgp_val_1 = NULL; +static circuitmux_t *mock_cgp_tgt_2 = NULL; +static circuitmux_policy_t *mock_cgp_val_2 = NULL; + +static const circuitmux_policy_t * +circuitmux_get_policy_mock(circuitmux_t *cmux) +{ + const circuitmux_policy_t *result = NULL; + + tt_assert(cmux != NULL); + if (cmux) { + if (cmux == mock_cgp_tgt_1) result = mock_cgp_val_1; + else if (cmux == mock_cgp_tgt_2) result = mock_cgp_val_2; + else result = circuitmux_get_policy__real(cmux); + } + + done: + return result; +} + +static int +circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, + circuitmux_t *cmux_2) +{ + int result = 0; + + tt_assert(cmux_1 != NULL); + tt_assert(cmux_2 != NULL); + + if (cmux_1 != cmux_2) { + if (cmux_1 == mock_ccm_tgt_1 && cmux_2 == mock_ccm_tgt_2) result = -1; + else if (cmux_1 == mock_ccm_tgt_2 && cmux_2 == mock_ccm_tgt_1) { + result = 1; + } else { + if (cmux_1 == mock_ccm_tgt_1 || cmux_1 == mock_ccm_tgt_2) result = -1; + else if (cmux_2 == mock_ccm_tgt_1 || cmux_2 == mock_ccm_tgt_2) { + result = 1; + } else { + result = circuitmux_compare_muxes__real(cmux_1, cmux_2); + } + } + } + /* else result = 0 always */ + + done: + return result; +} typedef struct { const channel_t *chan; @@ -174,6 +292,67 @@ channel_flush_some_cells_mock_set(channel_t *chan, ssize_t num_cells) } } +static int +channel_more_to_flush_mock(channel_t *chan) +{ + tor_assert(chan); + + flush_mock_channel_t *found_mock_ch = NULL; + + /* Check if we have any queued */ + if (! TOR_SIMPLEQ_EMPTY(&chan->incoming_queue)) + return 1; + + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + if (flush_mock_ch != NULL && flush_mock_ch->chan != NULL) { + if (flush_mock_ch->chan == chan) { + /* Found it */ + found_mock_ch = flush_mock_ch; + break; + } + } else { + /* That shouldn't be there... */ + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } + } SMARTLIST_FOREACH_END(flush_mock_ch); + + tor_assert(found_mock_ch); + + /* Check if any circuits would like to queue some */ + /* special for the mock: return the number of cells (instead of 1), or zero + * if nothing to flush */ + return (found_mock_ch->cells > 0 ? (int)found_mock_ch->cells : 0 ); +} + +static void +channel_write_to_kernel_mock(channel_t *chan) +{ + (void)chan; + //log_debug(LD_SCHED, "chan=%d writing to kernel", + // (int)chan->global_identifier); +} + +static int +channel_should_write_to_kernel_mock(outbuf_table_t *ot, channel_t *chan) +{ + (void)ot; + (void)chan; + return 1; + /* We could make this more complicated if we wanted. But I don't think doing + * so tests much of anything */ + //static int called_counter = 0; + //if (++called_counter >= 3) { + // called_counter -= 3; + // log_debug(LD_SCHED, "chan=%d should write to kernel", + // (int)chan->global_identifier); + // return 1; + //} + //return 0; +} + static ssize_t channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) { @@ -181,7 +360,7 @@ channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) char unlimited = 0; flush_mock_channel_t *found = NULL; - tt_assert(chan != NULL); + tt_ptr_op(chan, OP_NE, NULL); if (chan) { if (num_cells < 0) { num_cells = 0; @@ -215,11 +394,6 @@ channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) flushed += max; found->cells -= max; - - if (found->cells <= 0) { - smartlist_remove(chans_for_flush_mock, found); - tor_free(found); - } } } } @@ -228,90 +402,26 @@ channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) return flushed; } -static int -circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, - circuitmux_t *cmux_2) -{ - int result = 0; - - tt_assert(cmux_1 != NULL); - tt_assert(cmux_2 != NULL); - - if (cmux_1 != cmux_2) { - if (cmux_1 == mock_ccm_tgt_1 && cmux_2 == mock_ccm_tgt_2) result = -1; - else if (cmux_1 == mock_ccm_tgt_2 && cmux_2 == mock_ccm_tgt_1) { - result = 1; - } else { - if (cmux_1 == mock_ccm_tgt_1 || cmux_1 == mock_ccm_tgt_2) result = -1; - else if (cmux_2 == mock_ccm_tgt_1 || cmux_2 == mock_ccm_tgt_2) { - result = 1; - } else { - result = circuitmux_compare_muxes__real(cmux_1, cmux_2); - } - } - } - /* else result = 0 always */ - - done: - return result; -} - -static const circuitmux_policy_t * -circuitmux_get_policy_mock(circuitmux_t *cmux) -{ - const circuitmux_policy_t *result = NULL; - - tt_assert(cmux != NULL); - if (cmux) { - if (cmux == mock_cgp_tgt_1) result = mock_cgp_val_1; - else if (cmux == mock_cgp_tgt_2) result = mock_cgp_val_2; - else result = circuitmux_get_policy__real(cmux); - } - - done: - return result; -} - -static int -scheduler_compare_channels_mock(const void *c1_v, - const void *c2_v) -{ - uintptr_t p1, p2; - - p1 = (uintptr_t)(c1_v); - p2 = (uintptr_t)(c2_v); - - ++scheduler_compare_channels_mock_ctr; - - if (p1 == p2) return 0; - else if (p1 < p2) return 1; - else return -1; -} - static void -scheduler_run_noop_mock(void) +update_socket_info_impl_mock(socket_table_ent_t *ent) { - ++scheduler_run_mock_ctr; + ent->cwnd = ent->unacked = ent->mss = ent->notsent = 0; + ent->limit = INT_MAX; } -static struct event_base * -tor_libevent_get_base_mock(void) -{ - return mock_event_base; -} - -/* Test cases */ - static void -test_scheduler_channel_states(void *arg) +perform_channel_state_tests(int KISTSchedRunInterval, int sched_type) { channel_t *ch1 = NULL, *ch2 = NULL; int old_count; - (void)arg; + /* setup options so we're sure about what sched we are running */ + MOCK(get_options, mock_get_options); + clear_options(); + mocked_options.KISTSchedRunInterval = KISTSchedRunInterval; + set_scheduler_options(sched_type); /* Set up libevent and scheduler */ - mock_event_init(); MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); scheduler_init(); @@ -324,9 +434,9 @@ test_scheduler_channel_states(void *arg) * Disable scheduler_run so we can just check the state transitions * without having to make everything it might call work too. */ - MOCK(scheduler_run, scheduler_run_noop_mock); + ((scheduler_t *) the_scheduler)->run = scheduler_run_noop_mock; - tt_int_op(smartlist_len(channels_pending), ==, 0); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 0); /* Set up a fake channel */ ch1 = new_fake_channel(); @@ -341,7 +451,7 @@ test_scheduler_channel_states(void *arg) tt_assert(ch1->registered); /* It should start off in SCHED_CHAN_IDLE */ - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_IDLE); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_IDLE); /* Now get another one */ ch2 = new_fake_channel(); @@ -351,41 +461,41 @@ test_scheduler_channel_states(void *arg) channel_register(ch2); tt_assert(ch2->registered); - /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ + /* Send ch1 to SCHED_CHAN_WAITING_TO_WRITE */ scheduler_channel_has_waiting_cells(ch1); - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_WAITING_TO_WRITE); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_TO_WRITE); /* This should send it to SCHED_CHAN_PENDING */ scheduler_channel_wants_writes(ch1); - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 1); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 1); /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS); /* Drop ch2 back to idle */ scheduler_channel_doesnt_want_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_IDLE); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_IDLE); /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS); /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ scheduler_channel_has_waiting_cells(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 2); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 2); /* This should send ch2 to SCHED_CHAN_WAITING_TO_WRITE */ scheduler_channel_doesnt_want_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_TO_WRITE); - tt_int_op(smartlist_len(channels_pending), ==, 1); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_TO_WRITE); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 1); /* ...and back to SCHED_CHAN_PENDING */ scheduler_channel_wants_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 2); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 2); /* Now we exercise scheduler_touch_channel */ old_count = scheduler_compare_channels_mock_ctr; @@ -394,14 +504,14 @@ test_scheduler_channel_states(void *arg) /* Close */ channel_mark_for_close(ch1); - tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_CLOSING); channel_mark_for_close(ch2); - tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_CLOSING); channel_closed(ch1); - tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_CLOSED); ch1 = NULL; channel_closed(ch2); - tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_CLOSED); ch2 = NULL; /* Shut things down */ @@ -415,8 +525,9 @@ test_scheduler_channel_states(void *arg) tor_free(ch2); UNMOCK(scheduler_compare_channels); - UNMOCK(scheduler_run); UNMOCK(tor_libevent_get_base); + UNMOCK(get_options); + cleanup_scheduler_options(); return; } @@ -464,21 +575,21 @@ test_scheduler_compare_channels(void *arg) /* Equal-channel case */ result = scheduler_compare_channels(&c1, &c1); - tt_int_op(result, ==, 0); + tt_int_op(result, OP_EQ, 0); /* Distinct channels, distinct policies */ result = scheduler_compare_channels(&c1, &c2); - tt_int_op(result, ==, -1); + tt_int_op(result, OP_EQ, -1); result = scheduler_compare_channels(&c2, &c1); - tt_int_op(result, ==, 1); + tt_int_op(result, OP_EQ, 1); /* Distinct channels, same policy */ tor_free(mock_cgp_val_2); mock_cgp_val_2 = mock_cgp_val_1; result = scheduler_compare_channels(&c1, &c2); - tt_int_op(result, ==, -1); + tt_int_op(result, OP_EQ, -1); result = scheduler_compare_channels(&c2, &c1); - tt_int_op(result, ==, 1); + tt_int_op(result, OP_EQ, 1); done: @@ -502,40 +613,22 @@ test_scheduler_compare_channels(void *arg) return; } -static void -test_scheduler_initfree(void *arg) -{ - (void)arg; - - tt_ptr_op(channels_pending, ==, NULL); - tt_ptr_op(run_sched_ev, ==, NULL); - - mock_event_init(); - MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); - - scheduler_init(); - - tt_assert(channels_pending != NULL); - tt_assert(run_sched_ev != NULL); - - scheduler_free_all(); - - UNMOCK(tor_libevent_get_base); - mock_event_free_all(); - - tt_ptr_op(channels_pending, ==, NULL); - tt_ptr_op(run_sched_ev, ==, NULL); - - done: - return; -} +/****************************************************************************** + * The actual tests! + *****************************************************************************/ static void -test_scheduler_loop(void *arg) +test_scheduler_loop_vanilla(void *arg) { + (void)arg; channel_t *ch1 = NULL, *ch2 = NULL; + void (*run_func_ptr)(void); - (void)arg; + /* setup options so we're sure about what sched we are running */ + MOCK(get_options, mock_get_options); + clear_options(); + set_scheduler_options(SCHEDULER_VANILLA); + mocked_options.KISTSchedRunInterval = 0; /* Set up libevent and scheduler */ @@ -551,12 +644,14 @@ test_scheduler_loop(void *arg) * Disable scheduler_run so we can just check the state transitions * without having to make everything it might call work too. */ - MOCK(scheduler_run, scheduler_run_noop_mock); + run_func_ptr = the_scheduler->run; + ((scheduler_t *) the_scheduler)->run = scheduler_run_noop_mock; - tt_int_op(smartlist_len(channels_pending), ==, 0); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 0); /* Set up a fake channel */ ch1 = new_fake_channel(); + ch1->magic = TLS_CHAN_MAGIC; tt_assert(ch1); /* Start it off in OPENING */ @@ -567,13 +662,14 @@ test_scheduler_loop(void *arg) channel_register(ch1); tt_assert(ch1->registered); /* Finish opening it */ - channel_change_state(ch1, CHANNEL_STATE_OPEN); + channel_change_state_open(ch1); /* It should start off in SCHED_CHAN_IDLE */ - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_IDLE); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_IDLE); /* Now get another one */ ch2 = new_fake_channel(); + ch2->magic = TLS_CHAN_MAGIC; tt_assert(ch2); ch2->state = CHANNEL_STATE_OPENING; ch2->cmux = circuitmux_alloc(); @@ -584,68 +680,62 @@ test_scheduler_loop(void *arg) * zero and we'll get coverage of that exception case in scheduler_run() */ - tt_int_op(ch1->state, ==, CHANNEL_STATE_OPEN); - tt_int_op(ch2->state, ==, CHANNEL_STATE_OPENING); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_OPEN); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_OPENING); /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ scheduler_channel_has_waiting_cells(ch1); - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_WAITING_TO_WRITE); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_TO_WRITE); /* This should send it to SCHED_CHAN_PENDING */ scheduler_channel_wants_writes(ch1); - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 1); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 1); /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS); /* Drop ch2 back to idle */ scheduler_channel_doesnt_want_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_IDLE); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_IDLE); /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS); /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ scheduler_channel_has_waiting_cells(ch2); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 2); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 2); /* * Now we've got two pending channels and need to fire off - * scheduler_run(); first, unmock it. + * the scheduler run() that we kept. */ - - UNMOCK(scheduler_run); - - scheduler_run(); - - /* Now re-mock it */ - MOCK(scheduler_run, scheduler_run_noop_mock); + run_func_ptr(); /* * Assert that they're still in the states we left and aren't still * pending */ - tt_int_op(ch1->state, ==, CHANNEL_STATE_OPEN); - tt_int_op(ch2->state, ==, CHANNEL_STATE_OPENING); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_OPEN); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_OPENING); tt_assert(ch1->scheduler_state != SCHED_CHAN_PENDING); tt_assert(ch2->scheduler_state != SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 0); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 0); /* Now, finish opening ch2, and get both back to pending */ - channel_change_state(ch2, CHANNEL_STATE_OPEN); + channel_change_state_open(ch2); scheduler_channel_wants_writes(ch1); scheduler_channel_wants_writes(ch2); scheduler_channel_has_waiting_cells(ch1); scheduler_channel_has_waiting_cells(ch2); - tt_int_op(ch1->state, ==, CHANNEL_STATE_OPEN); - tt_int_op(ch2->state, ==, CHANNEL_STATE_OPEN); - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); - tt_int_op(smartlist_len(channels_pending), ==, 2); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_OPEN); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_OPEN); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), OP_EQ, 2); /* Now, set up the channel_flush_some_cells() mock */ MOCK(channel_flush_some_cells, channel_flush_some_cells_mock); @@ -661,38 +751,33 @@ test_scheduler_loop(void *arg) channel_flush_some_cells_mock_set(ch2, 48); /* - * And re-run the scheduler_run() loop with non-zero returns from + * And re-run the scheduler run() loop with non-zero returns from * channel_flush_some_cells() this time. */ - UNMOCK(scheduler_run); - - scheduler_run(); - - /* Now re-mock it */ - MOCK(scheduler_run, scheduler_run_noop_mock); + run_func_ptr(); /* * ch1 should have gone to SCHED_CHAN_WAITING_FOR_CELLS, with 16 flushed * and 32 writeable. */ - tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch1->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS); /* * ...ch2 should also have gone to SCHED_CHAN_WAITING_FOR_CELLS, with * channel_more_to_flush() returning false and channel_num_cells_writeable() * > 0/ */ - tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, OP_EQ, SCHED_CHAN_WAITING_FOR_CELLS); /* Close */ channel_mark_for_close(ch1); - tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_CLOSING); channel_mark_for_close(ch2); - tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_CLOSING); channel_closed(ch1); - tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch1->state, OP_EQ, CHANNEL_STATE_CLOSED); ch1 = NULL; channel_closed(ch2); - tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSED); + tt_int_op(ch2->state, OP_EQ, CHANNEL_STATE_CLOSED); ch2 = NULL; /* Shut things down */ @@ -704,55 +789,296 @@ test_scheduler_loop(void *arg) done: tor_free(ch1); tor_free(ch2); + cleanup_scheduler_options(); UNMOCK(channel_flush_some_cells); UNMOCK(scheduler_compare_channels); - UNMOCK(scheduler_run); UNMOCK(tor_libevent_get_base); + UNMOCK(get_options); } static void -test_scheduler_queue_heuristic(void *arg) +test_scheduler_loop_kist(void *arg) { - time_t now = approx_time(); - uint64_t qh; + (void) arg; +#ifndef HAVE_KIST_SUPPORT + return; +#endif + + channel_t *ch1 = new_fake_channel(), *ch2 = new_fake_channel(); + channel_t *ch3 = new_fake_channel(); + + /* setup options so we're sure about what sched we are running */ + MOCK(get_options, mock_get_options); + MOCK(channel_flush_some_cells, channel_flush_some_cells_mock); + MOCK(channel_more_to_flush, channel_more_to_flush_mock); + MOCK(channel_write_to_kernel, channel_write_to_kernel_mock); + MOCK(channel_should_write_to_kernel, channel_should_write_to_kernel_mock); + MOCK(update_socket_info_impl, update_socket_info_impl_mock); + clear_options(); + mocked_options.KISTSchedRunInterval = 11; + set_scheduler_options(SCHEDULER_KIST); + scheduler_init(); + + tt_assert(ch1); + ch1->magic = TLS_CHAN_MAGIC; + ch1->state = CHANNEL_STATE_OPENING; + ch1->cmux = circuitmux_alloc(); + channel_register(ch1); + tt_assert(ch1->registered); + channel_change_state_open(ch1); + scheduler_channel_has_waiting_cells(ch1); + scheduler_channel_wants_writes(ch1); + channel_flush_some_cells_mock_set(ch1, 5); + + tt_assert(ch2); + ch2->magic = TLS_CHAN_MAGIC; + ch2->state = CHANNEL_STATE_OPENING; + ch2->cmux = circuitmux_alloc(); + channel_register(ch2); + tt_assert(ch2->registered); + channel_change_state_open(ch2); + scheduler_channel_has_waiting_cells(ch2); + scheduler_channel_wants_writes(ch2); + channel_flush_some_cells_mock_set(ch2, 5); + + the_scheduler->run(); + + scheduler_channel_has_waiting_cells(ch1); + channel_flush_some_cells_mock_set(ch1, 5); + + the_scheduler->run(); + + scheduler_channel_has_waiting_cells(ch1); + channel_flush_some_cells_mock_set(ch1, 5); + scheduler_channel_has_waiting_cells(ch2); + channel_flush_some_cells_mock_set(ch2, 5); + + the_scheduler->run(); + + channel_flush_some_cells_mock_free_all(); + + /* We'll try to run this closed channel threw the scheduler loop and make + * sure it ends up in the right state. */ + tt_assert(ch3); + ch3->magic = TLS_CHAN_MAGIC; + ch3->state = CHANNEL_STATE_OPEN; + ch3->cmux = circuitmux_alloc(); + channel_register(ch3); + tt_assert(ch3->registered); + + ch3->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + scheduler_channel_has_waiting_cells(ch3); + /* Should be in the pending list now waiting to be handled. */ + tt_int_op(ch3->scheduler_state, OP_EQ, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 1); + /* By running the scheduler on a closed channel, it should end up in the + * IDLE state and not in the pending channel list. */ + ch3->state = CHANNEL_STATE_CLOSED; + the_scheduler->run(); + tt_int_op(ch3->scheduler_state, OP_EQ, SCHED_CHAN_IDLE); + tt_int_op(smartlist_len(get_channels_pending()), OP_EQ, 0); + + done: + /* Prep the channel so the free() function doesn't explode. */ + ch1->state = ch2->state = ch3->state = CHANNEL_STATE_CLOSED; + ch1->registered = ch2->registered = ch3->registered = 0; + channel_free(ch1); + channel_free(ch2); + channel_free(ch3); + UNMOCK(update_socket_info_impl); + UNMOCK(channel_should_write_to_kernel); + UNMOCK(channel_write_to_kernel); + UNMOCK(channel_more_to_flush); + UNMOCK(channel_flush_some_cells); + UNMOCK(get_options); + scheduler_free_all(); + return; +} + +static void +test_scheduler_channel_states(void *arg) +{ + (void)arg; + perform_channel_state_tests(-1, SCHEDULER_VANILLA); + perform_channel_state_tests(11, SCHEDULER_KIST_LITE); +#ifdef HAVE_KIST_SUPPORT + perform_channel_state_tests(11, SCHEDULER_KIST); +#endif +} + +static void +test_scheduler_initfree(void *arg) +{ (void)arg; - queue_heuristic = 0; - queue_heuristic_timestamp = 0; + tt_ptr_op(channels_pending, ==, NULL); + tt_ptr_op(run_sched_ev, ==, NULL); - /* Not yet inited case */ - scheduler_update_queue_heuristic(now - 180); - tt_u64_op(queue_heuristic, ==, 0); - tt_int_op(queue_heuristic_timestamp, ==, now - 180); + mock_event_init(); + MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); + MOCK(get_options, mock_get_options); + set_scheduler_options(SCHEDULER_KIST); + set_scheduler_options(SCHEDULER_KIST_LITE); + set_scheduler_options(SCHEDULER_VANILLA); + + scheduler_init(); - queue_heuristic = 1000000000L; - queue_heuristic_timestamp = now - 120; + tt_ptr_op(channels_pending, !=, NULL); + tt_ptr_op(run_sched_ev, !=, NULL); + /* We have specified nothing in the torrc and there's no consensus so the + * KIST scheduler is what should be in use */ + tt_ptr_op(the_scheduler, ==, get_kist_scheduler()); + tt_int_op(sched_run_interval, ==, 10); - scheduler_update_queue_heuristic(now - 119); - tt_u64_op(queue_heuristic, ==, 500000000L); - tt_int_op(queue_heuristic_timestamp, ==, now - 119); + scheduler_free_all(); - scheduler_update_queue_heuristic(now - 116); - tt_u64_op(queue_heuristic, ==, 62500000L); - tt_int_op(queue_heuristic_timestamp, ==, now - 116); + UNMOCK(tor_libevent_get_base); + mock_event_free_all(); - qh = scheduler_get_queue_heuristic(); - tt_u64_op(qh, ==, 0); + tt_ptr_op(channels_pending, ==, NULL); + tt_ptr_op(run_sched_ev, ==, NULL); done: + UNMOCK(get_options); + cleanup_scheduler_options(); + return; +} + +static void +test_scheduler_can_use_kist(void *arg) +{ + (void)arg; + + int res_should, res_freq; + MOCK(get_options, mock_get_options); + + /* Test force enabling of KIST */ + clear_options(); + mocked_options.KISTSchedRunInterval = 1234; + res_should = scheduler_can_use_kist(); + res_freq = kist_scheduler_run_interval(NULL); +#ifdef HAVE_KIST_SUPPORT + tt_int_op(res_should, ==, 1); +#else /* HAVE_KIST_SUPPORT */ + tt_int_op(res_should, ==, 0); +#endif /* HAVE_KIST_SUPPORT */ + tt_int_op(res_freq, ==, 1234); + + /* Test defer to consensus, but no consensus available */ + clear_options(); + mocked_options.KISTSchedRunInterval = 0; + res_should = scheduler_can_use_kist(); + res_freq = kist_scheduler_run_interval(NULL); +#ifdef HAVE_KIST_SUPPORT + tt_int_op(res_should, ==, 1); +#else /* HAVE_KIST_SUPPORT */ + tt_int_op(res_should, ==, 0); +#endif /* HAVE_KIST_SUPPORT */ + tt_int_op(res_freq, ==, 10); + + /* Test defer to consensus, and kist consensus available */ + MOCK(networkstatus_get_param, mock_kist_networkstatus_get_param); + clear_options(); + mocked_options.KISTSchedRunInterval = 0; + res_should = scheduler_can_use_kist(); + res_freq = kist_scheduler_run_interval(NULL); +#ifdef HAVE_KIST_SUPPORT + tt_int_op(res_should, ==, 1); +#else /* HAVE_KIST_SUPPORT */ + tt_int_op(res_should, ==, 0); +#endif /* HAVE_KIST_SUPPORT */ + tt_int_op(res_freq, ==, 12); + UNMOCK(networkstatus_get_param); + + /* Test defer to consensus, and vanilla consensus available */ + MOCK(networkstatus_get_param, mock_vanilla_networkstatus_get_param); + clear_options(); + mocked_options.KISTSchedRunInterval = 0; + res_should = scheduler_can_use_kist(); + res_freq = kist_scheduler_run_interval(NULL); + tt_int_op(res_should, ==, 0); + tt_int_op(res_freq, ==, 0); + UNMOCK(networkstatus_get_param); + + done: + UNMOCK(get_options); + return; +} + +static void +test_scheduler_ns_changed(void *arg) +{ + (void) arg; + + /* + * Currently no scheduler implementations use the old/new consensuses passed + * in scheduler_notify_networkstatus_changed, so it is okay to pass NULL. + * + * "But then what does test actually exercise???" It tests that + * scheduler_notify_networkstatus_changed fetches the correct value from the + * consensus, and then switches the scheduler if necessasry. + */ + + MOCK(get_options, mock_get_options); + clear_options(); + set_scheduler_options(SCHEDULER_KIST); + set_scheduler_options(SCHEDULER_VANILLA); + + tt_ptr_op(the_scheduler, ==, NULL); + + /* Change from vanilla to kist via consensus */ + the_scheduler = get_vanilla_scheduler(); + MOCK(networkstatus_get_param, mock_kist_networkstatus_get_param); + scheduler_notify_networkstatus_changed(NULL, NULL); + UNMOCK(networkstatus_get_param); +#ifdef HAVE_KIST_SUPPORT + tt_ptr_op(the_scheduler, ==, get_kist_scheduler()); +#else + tt_ptr_op(the_scheduler, ==, get_vanilla_scheduler()); +#endif + + /* Change from kist to vanilla via consensus */ + the_scheduler = get_kist_scheduler(); + MOCK(networkstatus_get_param, mock_vanilla_networkstatus_get_param); + scheduler_notify_networkstatus_changed(NULL, NULL); + UNMOCK(networkstatus_get_param); + tt_ptr_op(the_scheduler, ==, get_vanilla_scheduler()); + + /* Doesn't change when using KIST */ + the_scheduler = get_kist_scheduler(); + MOCK(networkstatus_get_param, mock_kist_networkstatus_get_param); + scheduler_notify_networkstatus_changed(NULL, NULL); + UNMOCK(networkstatus_get_param); +#ifdef HAVE_KIST_SUPPORT + tt_ptr_op(the_scheduler, ==, get_kist_scheduler()); +#else + tt_ptr_op(the_scheduler, ==, get_vanilla_scheduler()); +#endif + + /* Doesn't change when using vanilla */ + the_scheduler = get_vanilla_scheduler(); + MOCK(networkstatus_get_param, mock_vanilla_networkstatus_get_param); + scheduler_notify_networkstatus_changed(NULL, NULL); + UNMOCK(networkstatus_get_param); + tt_ptr_op(the_scheduler, ==, get_vanilla_scheduler()); + + done: + UNMOCK(get_options); + cleanup_scheduler_options(); return; } struct testcase_t scheduler_tests[] = { - { "channel_states", test_scheduler_channel_states, TT_FORK, NULL, NULL }, { "compare_channels", test_scheduler_compare_channels, TT_FORK, NULL, NULL }, + { "channel_states", test_scheduler_channel_states, TT_FORK, NULL, NULL }, { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, - { "loop", test_scheduler_loop, TT_FORK, NULL, NULL }, - { "queue_heuristic", test_scheduler_queue_heuristic, - TT_FORK, NULL, NULL }, + { "loop_vanilla", test_scheduler_loop_vanilla, TT_FORK, NULL, NULL }, + { "loop_kist", test_scheduler_loop_kist, TT_FORK, NULL, NULL }, + { "ns_changed", test_scheduler_ns_changed, TT_FORK, NULL, NULL}, + { "should_use_kist", test_scheduler_can_use_kist, TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c index 026a0f3825..cac78baecf 100644 --- a/src/test/test_shared_random.c +++ b/src/test/test_shared_random.c @@ -73,65 +73,73 @@ test_get_sr_protocol_phase(void *arg) { retval = parse_rfc1123_time("Wed, 20 Apr 2015 23:59:00 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_REVEAL); + tt_int_op(phase, OP_EQ, SR_PHASE_REVEAL); } { retval = parse_rfc1123_time("Wed, 20 Apr 2015 00:00:00 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_COMMIT); + tt_int_op(phase, OP_EQ, SR_PHASE_COMMIT); } { retval = parse_rfc1123_time("Wed, 20 Apr 2015 00:00:01 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_COMMIT); + tt_int_op(phase, OP_EQ, SR_PHASE_COMMIT); } { retval = parse_rfc1123_time("Wed, 20 Apr 2015 11:59:00 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_COMMIT); + tt_int_op(phase, OP_EQ, SR_PHASE_COMMIT); } { retval = parse_rfc1123_time("Wed, 20 Apr 2015 12:00:00 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_REVEAL); + tt_int_op(phase, OP_EQ, SR_PHASE_REVEAL); } { retval = parse_rfc1123_time("Wed, 20 Apr 2015 12:00:01 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_REVEAL); + tt_int_op(phase, OP_EQ, SR_PHASE_REVEAL); } { retval = parse_rfc1123_time("Wed, 20 Apr 2015 13:00:00 UTC", &the_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); phase = get_sr_protocol_phase(the_time); - tt_int_op(phase, ==, SR_PHASE_REVEAL); + tt_int_op(phase, OP_EQ, SR_PHASE_REVEAL); } done: ; } -static networkstatus_t *mock_consensus = NULL; +static networkstatus_t mock_consensus; + +/* Mock function to immediately return our local 'mock_consensus'. */ +static networkstatus_t * +mock_networkstatus_get_live_consensus(time_t now) +{ + (void) now; + return &mock_consensus; +} static void test_get_state_valid_until_time(void *arg) @@ -143,11 +151,23 @@ test_get_state_valid_until_time(void *arg) (void) arg; + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + retval = parse_rfc1123_time("Mon, 20 Apr 2015 01:00:00 UTC", + &mock_consensus.fresh_until); + tt_int_op(retval, OP_EQ, 0); + + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + &mock_consensus.valid_after); + tt_int_op(retval, OP_EQ, 0); + { /* Get the valid until time if called at 00:00:01 */ retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC", ¤t_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); valid_until_time = get_state_valid_until_time(current_time); /* Compare it with the correct result */ @@ -158,7 +178,8 @@ test_get_state_valid_until_time(void *arg) { retval = parse_rfc1123_time("Mon, 20 Apr 2015 19:22:00 UTC", ¤t_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); valid_until_time = get_state_valid_until_time(current_time); format_iso_time(tbuf, valid_until_time); @@ -168,7 +189,8 @@ test_get_state_valid_until_time(void *arg) { retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:00 UTC", ¤t_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); valid_until_time = get_state_valid_until_time(current_time); format_iso_time(tbuf, valid_until_time); @@ -178,7 +200,8 @@ test_get_state_valid_until_time(void *arg) { retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", ¤t_time); - tt_int_op(retval, ==, 0); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); valid_until_time = get_state_valid_until_time(current_time); format_iso_time(tbuf, valid_until_time); @@ -186,82 +209,148 @@ test_get_state_valid_until_time(void *arg) } done: - ; -} - -/* Mock function to immediately return our local 'mock_consensus'. */ -static networkstatus_t * -mock_networkstatus_get_live_consensus(time_t now) -{ - (void) now; - return mock_consensus; + UNMOCK(networkstatus_get_live_consensus); } -/** Test the get_next_valid_after_time() function. */ +/** Test the function that calculates the start time of the current SRV + * protocol run. */ static void -test_get_next_valid_after_time(void *arg) +test_get_start_time_of_current_run(void *arg) { - time_t current_time; - time_t valid_after_time; - char tbuf[ISO_TIME_LEN + 1]; int retval; + char tbuf[ISO_TIME_LEN + 1]; + time_t current_time, run_start_time; (void) arg; - { - /* Setup a fake consensus just to get the times out of it, since - get_next_valid_after_time() needs them. */ - mock_consensus = tor_malloc_zero(sizeof(networkstatus_t)); + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); - retval = parse_rfc1123_time("Mon, 13 Jan 2016 16:00:00 UTC", - &mock_consensus->fresh_until); - tt_int_op(retval, ==, 0); + retval = parse_rfc1123_time("Mon, 20 Apr 2015 01:00:00 UTC", + &mock_consensus.fresh_until); + tt_int_op(retval, OP_EQ, 0); - retval = parse_rfc1123_time("Mon, 13 Jan 2016 15:00:00 UTC", - &mock_consensus->valid_after); - tt_int_op(retval, ==, 0); + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + &mock_consensus.valid_after); + tt_int_op(retval, OP_EQ, 0); + + { + /* Get start time if called at 00:00:01 */ + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC", + ¤t_time); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); - MOCK(networkstatus_get_live_consensus, - mock_networkstatus_get_live_consensus); + /* Compare it with the correct result */ + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); } { - /* Get the valid after time if called at 00:00:00 */ - retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:59:59 UTC", ¤t_time); - tt_int_op(retval, ==, 0); - valid_after_time = get_next_valid_after_time(current_time); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); /* Compare it with the correct result */ - format_iso_time(tbuf, valid_after_time); - tt_str_op("2015-04-20 01:00:00", OP_EQ, tbuf); + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); } { - /* Get the valid until time if called at 00:00:01 */ - retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:01 UTC", + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", ¤t_time); - tt_int_op(retval, ==, 0); - valid_after_time = get_next_valid_after_time(current_time); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); /* Compare it with the correct result */ - format_iso_time(tbuf, valid_after_time); - tt_str_op("2015-04-20 01:00:00", OP_EQ, tbuf); - } + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:00:00", OP_EQ, tbuf); + } + + /* Next test is testing it without a consensus to use the testing voting + * interval . */ + UNMOCK(networkstatus_get_live_consensus); + /* Now let's alter the voting schedule and check the correctness of the + * function. Voting interval of 10 seconds, means that an SRV protocol run + * takes 10 seconds * 24 rounds = 4 mins */ { - retval = parse_rfc1123_time("Mon, 20 Apr 2015 23:30:01 UTC", + or_options_t *options = get_options_mutable(); + options->V3AuthVotingInterval = 10; + options->TestingV3AuthInitialVotingInterval = 10; + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:15:32 UTC", ¤t_time); - tt_int_op(retval, ==, 0); - valid_after_time = get_next_valid_after_time(current_time); + tt_int_op(retval, OP_EQ, 0); + dirvote_recalculate_timing(get_options(), current_time); + run_start_time = + sr_state_get_start_time_of_current_protocol_run(current_time); /* Compare it with the correct result */ - format_iso_time(tbuf, valid_after_time); - tt_str_op("2015-04-21 00:00:00", OP_EQ, tbuf); - } + format_iso_time(tbuf, run_start_time); + tt_str_op("2015-04-20 00:12:00", OP_EQ, tbuf); + } + + done: + ; +} + +/** Do some rudimentary consistency checks between the functions that + * understand the shared random protocol schedule */ +static void +test_get_start_time_functions(void *arg) +{ + (void) arg; + int retval; + + MOCK(networkstatus_get_live_consensus, + mock_networkstatus_get_live_consensus); + + retval = parse_rfc1123_time("Mon, 20 Apr 2015 01:00:00 UTC", + &mock_consensus.fresh_until); + tt_int_op(retval, OP_EQ, 0); + + retval = parse_rfc1123_time("Mon, 20 Apr 2015 00:00:00 UTC", + &mock_consensus.valid_after); + tt_int_op(retval, OP_EQ, 0); + time_t now = mock_consensus.valid_after; + + dirvote_recalculate_timing(get_options(), now); + time_t start_time_of_protocol_run = + sr_state_get_start_time_of_current_protocol_run(now); + tt_assert(start_time_of_protocol_run); + + /* Check that the round start time of the beginning of the run, is itself */ + tt_int_op(get_start_time_of_current_round(), OP_EQ, + start_time_of_protocol_run); done: - networkstatus_vote_free(mock_consensus); + UNMOCK(networkstatus_get_live_consensus); +} + +static void +test_get_sr_protocol_duration(void *arg) +{ + (void) arg; + + /* Check that by default an SR phase is 12 hours */ + tt_int_op(sr_state_get_phase_duration(), OP_EQ, 12*60*60); + tt_int_op(sr_state_get_protocol_run_duration(), OP_EQ, 24*60*60); + + /* Now alter the voting interval and check that the SR phase is 2 mins long + * if voting happens every 10 seconds (10*12 seconds = 2 mins) */ + or_options_t *options = get_options_mutable(); + options->V3AuthVotingInterval = 10; + tt_int_op(sr_state_get_phase_duration(), OP_EQ, 2*60); + tt_int_op(sr_state_get_protocol_run_duration(), OP_EQ, 4*60); + + done: ; } /* In this test we are going to generate a sr_commit_t object and validate @@ -304,18 +393,18 @@ test_sr_commit(void *arg) tt_assert(!tor_mem_is_zero((char *) our_commit->random_number, sizeof(our_commit->random_number))); /* Commit and reveal timestamp should be the same. */ - tt_u64_op(our_commit->commit_ts, ==, our_commit->reveal_ts); + tt_u64_op(our_commit->commit_ts, OP_EQ, our_commit->reveal_ts); /* We should have a hashed reveal. */ tt_assert(!tor_mem_is_zero(our_commit->hashed_reveal, sizeof(our_commit->hashed_reveal))); /* Do we have a valid encoded commit and reveal. Note the following only * tests if the generated values are correct. Their could be a bug in * the decode function but we test them seperately. */ - tt_int_op(0, ==, reveal_decode(our_commit->encoded_reveal, + tt_int_op(0, OP_EQ, reveal_decode(our_commit->encoded_reveal, &test_commit)); - tt_int_op(0, ==, commit_decode(our_commit->encoded_commit, + tt_int_op(0, OP_EQ, commit_decode(our_commit->encoded_commit, &test_commit)); - tt_int_op(0, ==, verify_commit_and_reveal(our_commit)); + tt_int_op(0, OP_EQ, verify_commit_and_reveal(our_commit)); } /* Let's make sure our verify commit and reveal function works. We'll @@ -328,21 +417,21 @@ test_sr_commit(void *arg) /* Timestamp MUST match. */ test_commit.commit_ts = test_commit.reveal_ts - 42; setup_full_capture_of_logs(LOG_WARN); - tt_int_op(-1, ==, verify_commit_and_reveal(&test_commit)); + tt_int_op(-1, OP_EQ, verify_commit_and_reveal(&test_commit)); expect_log_msg_containing("doesn't match reveal timestamp"); teardown_capture_of_logs(); memcpy(&test_commit, our_commit, sizeof(test_commit)); - tt_int_op(0, ==, verify_commit_and_reveal(&test_commit)); + tt_int_op(0, OP_EQ, verify_commit_and_reveal(&test_commit)); /* Hashed reveal must match the H(encoded_reveal). */ memset(test_commit.hashed_reveal, 'X', sizeof(test_commit.hashed_reveal)); setup_full_capture_of_logs(LOG_WARN); - tt_int_op(-1, ==, verify_commit_and_reveal(&test_commit)); + tt_int_op(-1, OP_EQ, verify_commit_and_reveal(&test_commit)); expect_single_log_msg_containing("doesn't match the commit value"); teardown_capture_of_logs(); memcpy(&test_commit, our_commit, sizeof(test_commit)); - tt_int_op(0, ==, verify_commit_and_reveal(&test_commit)); + tt_int_op(0, OP_EQ, verify_commit_and_reveal(&test_commit)); } /* We'll build a list of values from our commit that our parsing function @@ -396,26 +485,26 @@ test_encoding(void *arg) /* Hash random number because we don't expose bytes of the RNG. */ ret = crypto_digest256(hashed_rand, raw_rand, sizeof(raw_rand), SR_DIGEST_ALG); - tt_int_op(0, ==, ret); + tt_int_op(0, OP_EQ, ret); /* Hash reveal value. */ - tt_int_op(SR_REVEAL_BASE64_LEN, ==, strlen(encoded_reveal)); + tt_int_op(SR_REVEAL_BASE64_LEN, OP_EQ, strlen(encoded_reveal)); ret = crypto_digest256(hashed_reveal, encoded_reveal, strlen(encoded_reveal), SR_DIGEST_ALG); - tt_int_op(0, ==, ret); - tt_int_op(SR_COMMIT_BASE64_LEN, ==, strlen(encoded_commit)); + tt_int_op(0, OP_EQ, ret); + tt_int_op(SR_COMMIT_BASE64_LEN, OP_EQ, strlen(encoded_commit)); /* Test our commit/reveal decode functions. */ { /* Test the reveal encoded value. */ - tt_int_op(0, ==, reveal_decode(encoded_reveal, &parsed_commit)); - tt_u64_op(ts, ==, parsed_commit.reveal_ts); + tt_int_op(0, OP_EQ, reveal_decode(encoded_reveal, &parsed_commit)); + tt_u64_op(ts, OP_EQ, parsed_commit.reveal_ts); tt_mem_op(hashed_rand, OP_EQ, parsed_commit.random_number, sizeof(hashed_rand)); /* Test the commit encoded value. */ memset(&parsed_commit, 0, sizeof(parsed_commit)); - tt_int_op(0, ==, commit_decode(encoded_commit, &parsed_commit)); - tt_u64_op(ts, ==, parsed_commit.commit_ts); + tt_int_op(0, OP_EQ, commit_decode(encoded_commit, &parsed_commit)); + tt_u64_op(ts, OP_EQ, parsed_commit.commit_ts); tt_mem_op(encoded_commit, OP_EQ, parsed_commit.encoded_commit, sizeof(parsed_commit.encoded_commit)); tt_mem_op(hashed_reveal, OP_EQ, parsed_commit.hashed_reveal, @@ -430,7 +519,7 @@ test_encoding(void *arg) memcpy(parsed_commit.random_number, hashed_rand, sizeof(parsed_commit.random_number)); ret = reveal_encode(&parsed_commit, encoded, sizeof(encoded)); - tt_int_op(SR_REVEAL_BASE64_LEN, ==, ret); + tt_int_op(SR_REVEAL_BASE64_LEN, OP_EQ, ret); tt_mem_op(encoded_reveal, OP_EQ, encoded, strlen(encoded_reveal)); } @@ -441,7 +530,7 @@ test_encoding(void *arg) memcpy(parsed_commit.hashed_reveal, hashed_reveal, sizeof(parsed_commit.hashed_reveal)); ret = commit_encode(&parsed_commit, encoded, sizeof(encoded)); - tt_int_op(SR_COMMIT_BASE64_LEN, ==, ret); + tt_int_op(SR_COMMIT_BASE64_LEN, OP_EQ, ret); tt_mem_op(encoded_commit, OP_EQ, encoded, strlen(encoded_commit)); } @@ -518,7 +607,7 @@ test_vote(void *arg) tt_assert(lines); /* Split the lines. We expect 2 here. */ ret = smartlist_split_string(chunks, lines, "\n", SPLIT_IGNORE_BLANK, 0); - tt_int_op(ret, ==, 4); + tt_int_op(ret, OP_EQ, 4); tt_str_op(smartlist_get(chunks, 0), OP_EQ, "shared-rand-participate"); /* Get our commitment line and will validate it agains our commit. The * format is as follow: @@ -528,7 +617,7 @@ test_vote(void *arg) char *commit_line = smartlist_get(chunks, 1); tt_assert(commit_line); ret = smartlist_split_string(tokens, commit_line, " ", 0, 0); - tt_int_op(ret, ==, 6); + tt_int_op(ret, OP_EQ, 6); tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-commit"); tt_str_op(smartlist_get(tokens, 1), OP_EQ, "1"); tt_str_op(smartlist_get(tokens, 2), OP_EQ, @@ -536,7 +625,7 @@ test_vote(void *arg) char digest[DIGEST_LEN]; base16_decode(digest, sizeof(digest), smartlist_get(tokens, 3), HEX_DIGEST_LEN); - tt_mem_op(digest, ==, our_commit->rsa_identity, sizeof(digest)); + tt_mem_op(digest, OP_EQ, our_commit->rsa_identity, sizeof(digest)); tt_str_op(smartlist_get(tokens, 4), OP_EQ, our_commit->encoded_commit); tt_str_op(smartlist_get(tokens, 5), OP_EQ, our_commit->encoded_reveal) ; @@ -552,7 +641,7 @@ test_vote(void *arg) /* Set valid flag explicitly here to compare since it's not set by * simply parsing the commit. */ parsed_commit->valid = 1; - tt_mem_op(parsed_commit, ==, our_commit, sizeof(*our_commit)); + tt_mem_op(parsed_commit, OP_EQ, our_commit, sizeof(*our_commit)); /* minor cleanup */ SMARTLIST_FOREACH(tokens, char *, s, tor_free(s)); @@ -562,7 +651,7 @@ test_vote(void *arg) char *prev_srv_line = smartlist_get(chunks, 2); tt_assert(prev_srv_line); ret = smartlist_split_string(tokens, prev_srv_line, " ", 0, 0); - tt_int_op(ret, ==, 3); + tt_int_op(ret, OP_EQ, 3); tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-previous-value"); tt_str_op(smartlist_get(tokens, 1), OP_EQ, "42"); tt_str_op(smartlist_get(tokens, 2), OP_EQ, @@ -576,7 +665,7 @@ test_vote(void *arg) char *current_srv_line = smartlist_get(chunks, 3); tt_assert(current_srv_line); ret = smartlist_split_string(tokens, current_srv_line, " ", 0, 0); - tt_int_op(ret, ==, 3); + tt_int_op(ret, OP_EQ, 3); tt_str_op(smartlist_get(tokens, 0), OP_EQ, "shared-rand-current-value"); tt_str_op(smartlist_get(tokens, 1), OP_EQ, "128"); tt_str_op(smartlist_get(tokens, 2), OP_EQ, @@ -629,7 +718,7 @@ test_state_load_from_disk(void *arg) /* First try with a nonexistent path. */ ret = disk_state_load_from_disk_impl("NONEXISTENTNONEXISTENT"); - tt_assert(ret == -ENOENT); + tt_int_op(ret, OP_EQ, -ENOENT); /* Now create a mock state directory and state file */ #ifdef _WIN32 @@ -637,9 +726,9 @@ test_state_load_from_disk(void *arg) #else ret = mkdir(dir, 0700); #endif - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); ret = write_str_to_file(sr_state_path, sr_state_str, 0); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Try to load the directory itself. Should fail. */ ret = disk_state_load_from_disk_impl(dir); @@ -647,11 +736,11 @@ test_state_load_from_disk(void *arg) /* State should be non-existent at this point. */ the_sr_state = get_sr_state(); - tt_assert(!the_sr_state); + tt_ptr_op(the_sr_state, OP_EQ, NULL); /* Now try to load the correct file! */ ret = disk_state_load_from_disk_impl(sr_state_path); - tt_assert(ret == 0); + tt_int_op(ret, OP_EQ, 0); /* Check the content of the state */ /* XXX check more deeply!!! */ @@ -689,7 +778,7 @@ test_sr_setup_commits(void) tt_assert(auth_cert); options->AuthoritativeDir = 1; - tt_int_op(0, ==, load_ed_keys(options, now)); + tt_int_op(0, OP_EQ, load_ed_keys(options, now)); } /* Generate three dummy commits according to sr_srv_calc_ref.py . Then @@ -775,7 +864,7 @@ test_sr_setup_commits(void) save_commit_to_state(commit_b); save_commit_to_state(commit_c); save_commit_to_state(commit_d); - tt_int_op(digestmap_size(get_sr_state()->commits), ==, 4); + tt_int_op(digestmap_size(get_sr_state()->commits), OP_EQ, 4); /* Now during REVEAL phase save commit D by restoring its reveal. */ set_sr_phase(SR_PHASE_REVEAL); @@ -820,9 +909,9 @@ test_sr_compute_srv(void *arg) /* Check the result against the test vector */ current_srv = sr_state_get_current_srv(); tt_assert(current_srv); - tt_u64_op(current_srv->num_reveals, ==, 3); + tt_u64_op(current_srv->num_reveals, OP_EQ, 3); tt_str_op(hex_str((char*)current_srv->value, 32), - ==, + OP_EQ, SRV_TEST_VECTOR); done: @@ -878,7 +967,7 @@ test_sr_get_majority_srv_from_votes(void *arg) /* Since it's only one vote with an SRV, it should not achieve majority and hence no SRV will be returned. */ chosen_srv = get_majority_srv_from_votes(votes, 1); - tt_assert(!chosen_srv); + tt_ptr_op(chosen_srv, OP_EQ, NULL); { /* Now put in 8 more votes. Let SRV_1 have majority. */ int i; @@ -897,21 +986,21 @@ test_sr_get_majority_srv_from_votes(void *arg) smartlist_add(votes, vote); } - tt_int_op(smartlist_len(votes), ==, 9); + tt_int_op(smartlist_len(votes), OP_EQ, 9); } /* Now we achieve majority for SRV_1, but not the AuthDirNumSRVAgreements requirement. So still not picking an SRV. */ set_num_srv_agreements(8); chosen_srv = get_majority_srv_from_votes(votes, 1); - tt_assert(!chosen_srv); + tt_ptr_op(chosen_srv, OP_EQ, NULL); /* We will now lower the AuthDirNumSRVAgreements requirement by tweaking the * consensus parameter and we will try again. This time it should work. */ set_num_srv_agreements(7); chosen_srv = get_majority_srv_from_votes(votes, 1); tt_assert(chosen_srv); - tt_u64_op(chosen_srv->num_reveals, ==, 42); + tt_u64_op(chosen_srv->num_reveals, OP_EQ, 42); tt_mem_op(chosen_srv->value, OP_EQ, SRV_1, sizeof(chosen_srv->value)); done: @@ -935,7 +1024,7 @@ test_utils(void *arg) memcpy(srv->value, srv_value, sizeof(srv->value)); dup_srv = srv_dup(srv); tt_assert(dup_srv); - tt_u64_op(dup_srv->num_reveals, ==, srv->num_reveals); + tt_u64_op(dup_srv->num_reveals, OP_EQ, srv->num_reveals); tt_mem_op(dup_srv->value, OP_EQ, srv->value, sizeof(srv->value)); tor_free(srv); tor_free(dup_srv); @@ -955,10 +1044,10 @@ test_utils(void *arg) sr_commit_t commit1, commit2; memcpy(commit1.encoded_commit, payload, sizeof(commit1.encoded_commit)); memcpy(commit2.encoded_commit, payload, sizeof(commit2.encoded_commit)); - tt_int_op(commitments_are_the_same(&commit1, &commit2), ==, 1); + tt_int_op(commitments_are_the_same(&commit1, &commit2), OP_EQ, 1); /* Let's corrupt one of them. */ memset(commit1.encoded_commit, 'A', sizeof(commit1.encoded_commit)); - tt_int_op(commitments_are_the_same(&commit1, &commit2), ==, 0); + tt_int_op(commitments_are_the_same(&commit1, &commit2), OP_EQ, 0); } /* Testing commit_is_authoritative(). */ @@ -969,32 +1058,32 @@ test_utils(void *arg) tt_assert(!crypto_pk_generate_key(k)); - tt_int_op(0, ==, crypto_pk_get_digest(k, digest)); + tt_int_op(0, OP_EQ, crypto_pk_get_digest(k, digest)); memcpy(commit.rsa_identity, digest, sizeof(commit.rsa_identity)); - tt_int_op(commit_is_authoritative(&commit, digest), ==, 1); + tt_int_op(commit_is_authoritative(&commit, digest), OP_EQ, 1); /* Change the pubkey. */ memset(commit.rsa_identity, 0, sizeof(commit.rsa_identity)); - tt_int_op(commit_is_authoritative(&commit, digest), ==, 0); + tt_int_op(commit_is_authoritative(&commit, digest), OP_EQ, 0); crypto_pk_free(k); } /* Testing get_phase_str(). */ { - tt_str_op(get_phase_str(SR_PHASE_REVEAL), ==, "reveal"); - tt_str_op(get_phase_str(SR_PHASE_COMMIT), ==, "commit"); + tt_str_op(get_phase_str(SR_PHASE_REVEAL), OP_EQ, "reveal"); + tt_str_op(get_phase_str(SR_PHASE_COMMIT), OP_EQ, "commit"); } /* Testing phase transition */ { init_authority_state(); set_sr_phase(SR_PHASE_COMMIT); - tt_int_op(is_phase_transition(SR_PHASE_REVEAL), ==, 1); - tt_int_op(is_phase_transition(SR_PHASE_COMMIT), ==, 0); + tt_int_op(is_phase_transition(SR_PHASE_REVEAL), OP_EQ, 1); + tt_int_op(is_phase_transition(SR_PHASE_COMMIT), OP_EQ, 0); set_sr_phase(SR_PHASE_REVEAL); - tt_int_op(is_phase_transition(SR_PHASE_REVEAL), ==, 0); - tt_int_op(is_phase_transition(SR_PHASE_COMMIT), ==, 1); + tt_int_op(is_phase_transition(SR_PHASE_REVEAL), OP_EQ, 0); + tt_int_op(is_phase_transition(SR_PHASE_COMMIT), OP_EQ, 1); /* Junk. */ - tt_int_op(is_phase_transition(42), ==, 1); + tt_int_op(is_phase_transition(42), OP_EQ, 1); } done: @@ -1022,24 +1111,24 @@ test_state_transition(void *arg) sr_commit_t *commit = sr_generate_our_commit(now, mock_cert); tt_assert(commit); sr_state_add_commit(commit); - tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); /* Let's test our delete feature. */ sr_state_delete_commits(); - tt_int_op(digestmap_size(state->commits), ==, 0); + tt_int_op(digestmap_size(state->commits), OP_EQ, 0); /* Add it back so we can continue the rest of the test because after * deletiong our commit will be freed so generate a new one. */ commit = sr_generate_our_commit(now, mock_cert); tt_assert(commit); sr_state_add_commit(commit); - tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); state->n_reveal_rounds = 42; state->n_commit_rounds = 43; state->n_protocol_runs = 44; reset_state_for_new_protocol_run(now); - tt_int_op(state->n_reveal_rounds, ==, 0); - tt_int_op(state->n_commit_rounds, ==, 0); - tt_u64_op(state->n_protocol_runs, ==, 45); - tt_int_op(digestmap_size(state->commits), ==, 0); + tt_int_op(state->n_reveal_rounds, OP_EQ, 0); + tt_int_op(state->n_commit_rounds, OP_EQ, 0); + tt_u64_op(state->n_protocol_runs, OP_EQ, 45); + tt_int_op(digestmap_size(state->commits), OP_EQ, 0); } /* Test SRV rotation in our state. */ @@ -1052,7 +1141,7 @@ test_state_transition(void *arg) state_rotate_srv(); prev = sr_state_get_previous_srv(); tt_assert(prev == cur); - tt_assert(!sr_state_get_current_srv()); + tt_ptr_op(sr_state_get_current_srv(), OP_EQ, NULL); sr_state_clean_srvs(); } @@ -1077,18 +1166,18 @@ test_state_transition(void *arg) /* Also, make sure we did change the current. */ tt_assert(sr_state_get_current_srv() != cur); /* We should have our commitment alone. */ - tt_int_op(digestmap_size(state->commits), ==, 1); - tt_int_op(state->n_reveal_rounds, ==, 0); - tt_int_op(state->n_commit_rounds, ==, 0); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); + tt_int_op(state->n_reveal_rounds, OP_EQ, 0); + tt_int_op(state->n_commit_rounds, OP_EQ, 0); /* 46 here since we were at 45 just before. */ - tt_u64_op(state->n_protocol_runs, ==, 46); + tt_u64_op(state->n_protocol_runs, OP_EQ, 46); } /* Cleanup of SRVs. */ { sr_state_clean_srvs(); - tt_assert(!sr_state_get_current_srv()); - tt_assert(!sr_state_get_previous_srv()); + tt_ptr_op(sr_state_get_current_srv(), OP_EQ, NULL); + tt_ptr_op(sr_state_get_previous_srv(), OP_EQ, NULL); } done: @@ -1117,6 +1206,8 @@ test_keep_commit(void *arg) state = get_sr_state(); } + crypto_rand((char*)fp, sizeof(fp)); + /* Test this very important function that tells us if we should keep a * commit or not in our state. Most of it depends on the phase and what's * in the commit so we'll change the commit as we go. */ @@ -1125,21 +1216,21 @@ test_keep_commit(void *arg) /* Set us in COMMIT phase for starter. */ set_sr_phase(SR_PHASE_COMMIT); /* We should never keep a commit from a non authoritative authority. */ - tt_int_op(should_keep_commit(commit, fp, SR_PHASE_COMMIT), ==, 0); + tt_int_op(should_keep_commit(commit, fp, SR_PHASE_COMMIT), OP_EQ, 0); /* This should NOT be kept because it has a reveal value in it. */ tt_assert(commit_has_reveal_value(commit)); tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_COMMIT), ==, 0); + SR_PHASE_COMMIT), OP_EQ, 0); /* Add it to the state which should return to not keep it. */ sr_state_add_commit(commit); tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_COMMIT), ==, 0); + SR_PHASE_COMMIT), OP_EQ, 0); /* Remove it from state so we can continue our testing. */ digestmap_remove(state->commits, commit->rsa_identity); /* Let's remove our reveal value which should make it OK to keep it. */ memset(commit->encoded_reveal, 0, sizeof(commit->encoded_reveal)); tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_COMMIT), ==, 1); + SR_PHASE_COMMIT), OP_EQ, 1); /* Let's reset our commit and go into REVEAL phase. */ sr_commit_free(commit); @@ -1151,17 +1242,17 @@ test_keep_commit(void *arg) memset(dup_commit->encoded_reveal, 0, sizeof(dup_commit->encoded_reveal)); set_sr_phase(SR_PHASE_REVEAL); /* We should never keep a commit from a non authoritative authority. */ - tt_int_op(should_keep_commit(commit, fp, SR_PHASE_REVEAL), ==, 0); + tt_int_op(should_keep_commit(commit, fp, SR_PHASE_REVEAL), OP_EQ, 0); /* We shouldn't accept a commit that is not in our state. */ tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_REVEAL), ==, 0); + SR_PHASE_REVEAL), OP_EQ, 0); /* Important to add the commit _without_ the reveal here. */ sr_state_add_commit(dup_commit); - tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); /* Our commit should be valid that is authoritative, contains a reveal, be * in the state and commitment and reveal values match. */ tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_REVEAL), ==, 1); + SR_PHASE_REVEAL), OP_EQ, 1); /* The commit shouldn't be kept if it's not verified that is no matchin * hashed reveal. */ { @@ -1172,7 +1263,7 @@ test_keep_commit(void *arg) memset(commit->hashed_reveal, 0, sizeof(commit->hashed_reveal)); setup_full_capture_of_logs(LOG_WARN); tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_REVEAL), ==, 0); + SR_PHASE_REVEAL), OP_EQ, 0); expect_log_msg_containing("doesn't match the commit value."); expect_log_msg_containing("has an invalid reveal value."); assert_log_predicate(mock_saved_log_n_entries() == 2, @@ -1183,11 +1274,11 @@ test_keep_commit(void *arg) } /* We shouldn't keep a commit that has no reveal. */ tt_int_op(should_keep_commit(dup_commit, dup_commit->rsa_identity, - SR_PHASE_REVEAL), ==, 0); + SR_PHASE_REVEAL), OP_EQ, 0); /* We must not keep a commit that is not the same from the commit phase. */ memset(commit->encoded_commit, 0, sizeof(commit->encoded_commit)); tt_int_op(should_keep_commit(commit, commit->rsa_identity, - SR_PHASE_REVEAL), ==, 0); + SR_PHASE_REVEAL), OP_EQ, 0); done: teardown_capture_of_logs(); @@ -1225,35 +1316,35 @@ test_state_update(void *arg) /* We are in COMMIT phase here and we'll trigger a state update but no * transition. */ sr_state_update(commit_phase_time); - tt_int_op(state->valid_after, ==, commit_phase_time); - tt_int_op(state->n_commit_rounds, ==, 1); - tt_int_op(state->phase, ==, SR_PHASE_COMMIT); - tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(state->valid_after, OP_EQ, commit_phase_time); + tt_int_op(state->n_commit_rounds, OP_EQ, 1); + tt_int_op(state->phase, OP_EQ, SR_PHASE_COMMIT); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); /* We are still in the COMMIT phase here but we'll trigger a state * transition to the REVEAL phase. */ sr_state_update(reveal_phase_time); - tt_int_op(state->phase, ==, SR_PHASE_REVEAL); - tt_int_op(state->valid_after, ==, reveal_phase_time); + tt_int_op(state->phase, OP_EQ, SR_PHASE_REVEAL); + tt_int_op(state->valid_after, OP_EQ, reveal_phase_time); /* Only our commit should be in there. */ - tt_int_op(digestmap_size(state->commits), ==, 1); - tt_int_op(state->n_reveal_rounds, ==, 1); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); + tt_int_op(state->n_reveal_rounds, OP_EQ, 1); /* We can't update a state with a valid after _lower_ than the creation * time so here it is. */ sr_state_update(commit_phase_time); - tt_int_op(state->valid_after, ==, reveal_phase_time); + tt_int_op(state->valid_after, OP_EQ, reveal_phase_time); /* Finally, let's go back in COMMIT phase so we can test the state update * of a new protocol run. */ state->valid_after = 0; sr_state_update(commit_phase_time); - tt_int_op(state->valid_after, ==, commit_phase_time); - tt_int_op(state->n_commit_rounds, ==, 1); - tt_int_op(state->n_reveal_rounds, ==, 0); - tt_u64_op(state->n_protocol_runs, ==, 1); - tt_int_op(state->phase, ==, SR_PHASE_COMMIT); - tt_int_op(digestmap_size(state->commits), ==, 1); + tt_int_op(state->valid_after, OP_EQ, commit_phase_time); + tt_int_op(state->n_commit_rounds, OP_EQ, 1); + tt_int_op(state->n_reveal_rounds, OP_EQ, 0); + tt_u64_op(state->n_protocol_runs, OP_EQ, 1); + tt_int_op(state->phase, OP_EQ, SR_PHASE_COMMIT); + tt_int_op(digestmap_size(state->commits), OP_EQ, 1); tt_assert(state->current_srv); done: @@ -1270,7 +1361,11 @@ struct testcase_t sr_tests[] = { NULL, NULL }, { "encoding", test_encoding, TT_FORK, NULL, NULL }, - { "get_next_valid_after_time", test_get_next_valid_after_time, TT_FORK, + { "get_start_time_of_current_run", test_get_start_time_of_current_run, + TT_FORK, NULL, NULL }, + { "get_start_time_functions", test_get_start_time_functions, + TT_FORK, NULL, NULL }, + { "get_sr_protocol_duration", test_get_sr_protocol_duration, TT_FORK, NULL, NULL }, { "get_state_valid_until_time", test_get_state_valid_until_time, TT_FORK, NULL, NULL }, diff --git a/src/test/test_socks.c b/src/test/test_socks.c index bb1be11f2b..9ae7530e22 100644 --- a/src/test/test_socks.c +++ b/src/test/test_socks.c @@ -6,7 +6,9 @@ #include "or.h" #include "buffers.h" #include "config.h" +#include "proto_socks.h" #include "test.h" +#include "log_test_helpers.h" typedef struct socks_test_data_t { socks_request_t *req; @@ -43,7 +45,7 @@ static const struct testcase_setup_t socks_setup = { buf_t *buf = testdata->buf; \ socks_request_t *socks = testdata->req; #define ADD_DATA(buf, s) \ - write_to_buf(s, sizeof(s)-1, buf) + buf_add(buf, s, sizeof(s)-1) static void socks_request_clear(socks_request_t *socks) @@ -61,8 +63,9 @@ test_socks_4_unsupported_commands(void *ptr) /* SOCKS 4 Send BIND [02] to IP address 2.2.2.2:4369 */ ADD_DATA(buf, "\x04\x02\x11\x11\x02\x02\x02\x02\x00"); - tt_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == -1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, -1); tt_int_op(4,OP_EQ, socks->socks_version); tt_int_op(0,OP_EQ, socks->replylen); /* XXX: shouldn't tor reply? */ @@ -80,8 +83,9 @@ test_socks_4_supported_commands(void *ptr) /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4370 */ ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x03\x00"); - tt_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, 1); tt_int_op(4,OP_EQ, socks->socks_version); tt_int_op(0,OP_EQ, socks->replylen); /* XXX: shouldn't tor reply? */ tt_int_op(SOCKS_COMMAND_CONNECT,OP_EQ, socks->command); @@ -95,8 +99,8 @@ test_socks_4_supported_commands(void *ptr) /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4369 with userid*/ ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x04me\x00"); - tt_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), + OP_EQ, 1); tt_int_op(4,OP_EQ, socks->socks_version); tt_int_op(0,OP_EQ, socks->replylen); /* XXX: shouldn't tor reply? */ tt_int_op(SOCKS_COMMAND_CONNECT,OP_EQ, socks->command); @@ -112,8 +116,9 @@ test_socks_4_supported_commands(void *ptr) /* SOCKS 4a Send RESOLVE [F0] request for torproject.org */ ADD_DATA(buf, "\x04\xF0\x01\x01\x00\x00\x00\x02me\x00torproject.org\x00"); - tt_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); + tt_int_op(fetch_from_buf_socks(buf, socks, 1, + get_options()->SafeSocks), + OP_EQ, 1); tt_int_op(4,OP_EQ, socks->socks_version); tt_int_op(0,OP_EQ, socks->replylen); /* XXX: shouldn't tor reply? */ tt_str_op("torproject.org",OP_EQ, socks->address); @@ -124,6 +129,83 @@ test_socks_4_supported_commands(void *ptr) ; } +static void +test_socks_4_bad_arguments(void *ptr) +{ + SOCKS_TEST_INIT(); + setup_capture_of_logs(LOG_DEBUG); + + /* Try with 0 IPv4 address */ + ADD_DATA(buf, "\x04\x01\x00\x50\x00\x00\x00\x00\x00"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Port or DestIP is zero."); + mock_clean_saved_logs(); + + /* Try with 0 port */ + ADD_DATA(buf, "\x04\x01\x00\x00\x01\x02\x03\x04\x00"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Port or DestIP is zero."); + mock_clean_saved_logs(); + + /* Try with 2000-byte username (!) */ + ADD_DATA(buf, "\x04\x01\x00\x50\x01\x02\x03\x04"); + int i; + for (i = 0; i < 200; ++i) { + ADD_DATA(buf, "1234567890"); + } + ADD_DATA(buf, "\x00"); + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), + OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("user name too long; rejecting."); + mock_clean_saved_logs(); + + /* Try with 2000-byte hostname */ + ADD_DATA(buf, "\x04\x01\x00\x50\x00\x00\x00\x01\x00"); + for (i = 0; i < 200; ++i) { + ADD_DATA(buf, "1234567890"); + } + ADD_DATA(buf, "\x00"); + { + const char *p; + size_t s; + buf_pullup(buf, 9999, &p, &s); + } + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), + OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Destaddr too long. Rejecting."); + mock_clean_saved_logs(); + + /* Try with 2000-byte hostname, not terminated. */ + ADD_DATA(buf, "\x04\x01\x00\x50\x00\x00\x00\x01\x00"); + for (i = 0; i < 200; ++i) { + ADD_DATA(buf, "1234567890"); + } + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), + OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Destaddr too long."); + mock_clean_saved_logs(); + + /* Socks4, bogus hostname */ + ADD_DATA(buf, "\x04\x01\x00\x50\x00\x00\x00\x01\x00" "---\x00" ); + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Your application (using socks4 to port 80) " + "gave Tor a malformed hostname: "); + mock_clean_saved_logs(); + + done: + teardown_capture_of_logs(); +} + /** Perform unsupported SOCKS 5 commands */ static void test_socks_5_unsupported_commands(void *ptr) @@ -199,10 +281,28 @@ test_socks_5_supported_commands(void *ptr) tt_int_op(0,OP_EQ, buf_datalen(buf)); socks_request_clear(socks); + /* SOCKS 5 Send CONNECT [01] to one of the ipv6 addresses for + torproject.org:80 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\x01\x00\x04" + "\x20\x02\x41\xb8\x02\x02\x0d\xeb\x02\x13\x21\xff\xfe\x20\x14\x26" + "\x00\x50"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks),OP_EQ, 1); + tt_int_op(5,OP_EQ, socks->socks_version); + tt_int_op(2,OP_EQ, socks->replylen); + tt_int_op(5,OP_EQ, socks->reply[0]); + tt_int_op(0,OP_EQ, socks->reply[1]); + tt_str_op("[2002:41b8:202:deb:213:21ff:fe20:1426]",OP_EQ, socks->address); + tt_int_op(80,OP_EQ, socks->port); + + tt_int_op(0,OP_EQ, buf_datalen(buf)); + socks_request_clear(socks); + /* SOCKS 5 Send CONNECT [01] to FQDN torproject.org:4369 */ ADD_DATA(buf, "\x05\x01\x00"); ADD_DATA(buf, "\x05\x01\x00\x03\x0Etorproject.org\x11\x11"); - tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + tt_int_op(fetch_from_buf_socks(buf, socks, 1, get_options()->SafeSocks),OP_EQ, 1); tt_int_op(5,OP_EQ, socks->socks_version); @@ -218,8 +318,9 @@ test_socks_5_supported_commands(void *ptr) /* SOCKS 5 Send RESOLVE [F0] request for torproject.org:4369 */ ADD_DATA(buf, "\x05\x01\x00"); ADD_DATA(buf, "\x05\xF0\x00\x03\x0Etorproject.org\x01\x02"); - tt_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, 1); tt_int_op(5,OP_EQ, socks->socks_version); tt_int_op(2,OP_EQ, socks->replylen); tt_int_op(5,OP_EQ, socks->reply[0]); @@ -229,47 +330,46 @@ test_socks_5_supported_commands(void *ptr) tt_int_op(0,OP_EQ, buf_datalen(buf)); socks_request_clear(socks); - /* SOCKS 5 Should reject RESOLVE [F0] request for IPv4 address + /* SOCKS 5 Should NOT reject RESOLVE [F0] request for IPv4 address * string if SafeSocks is enabled. */ ADD_DATA(buf, "\x05\x01\x00"); ADD_DATA(buf, "\x05\xF0\x00\x03\x07"); ADD_DATA(buf, "8.8.8.8"); - ADD_DATA(buf, "\x01\x02"); - tt_assert(fetch_from_buf_socks(buf,socks,get_options()->TestSocks,1) - == -1); + ADD_DATA(buf, "\x11\x11"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, 1), + OP_EQ, 1); - tt_int_op(5,OP_EQ,socks->socks_version); - tt_int_op(10,OP_EQ,socks->replylen); - tt_int_op(5,OP_EQ,socks->reply[0]); - tt_int_op(SOCKS5_NOT_ALLOWED,OP_EQ,socks->reply[1]); - tt_int_op(1,OP_EQ,socks->reply[3]); + tt_str_op("8.8.8.8", OP_EQ, socks->address); + tt_int_op(4369, OP_EQ, socks->port); + + tt_int_op(0, OP_EQ, buf_datalen(buf)); socks_request_clear(socks); - /* SOCKS 5 should reject RESOLVE [F0] reject for IPv6 address + /* SOCKS 5 should NOT reject RESOLVE [F0] reject for IPv6 address * string if SafeSocks is enabled. */ ADD_DATA(buf, "\x05\x01\x00"); ADD_DATA(buf, "\x05\xF0\x00\x03\x27"); ADD_DATA(buf, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); ADD_DATA(buf, "\x01\x02"); - tt_assert(fetch_from_buf_socks(buf,socks,get_options()->TestSocks,1) - == -1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, 1), + OP_EQ, -1); - tt_int_op(5,OP_EQ,socks->socks_version); - tt_int_op(10,OP_EQ,socks->replylen); - tt_int_op(5,OP_EQ,socks->reply[0]); - tt_int_op(SOCKS5_NOT_ALLOWED,OP_EQ,socks->reply[1]); - tt_int_op(1,OP_EQ,socks->reply[3]); + tt_str_op("2001:0db8:85a3:0000:0000:8a2e:0370:7334", OP_EQ, socks->address); + tt_int_op(258, OP_EQ, socks->port); + + tt_int_op(0, OP_EQ, buf_datalen(buf)); socks_request_clear(socks); /* SOCKS 5 Send RESOLVE_PTR [F1] for IP address 2.2.2.5 */ ADD_DATA(buf, "\x05\x01\x00"); ADD_DATA(buf, "\x05\xF1\x00\x01\x02\x02\x02\x05\x01\x03"); - tt_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, - get_options()->SafeSocks) == 1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, 1); tt_int_op(5,OP_EQ, socks->socks_version); tt_int_op(2,OP_EQ, socks->replylen); tt_int_op(5,OP_EQ, socks->reply[0]); @@ -380,9 +480,9 @@ test_socks_5_authenticate_with_data(void *ptr) /* SOCKS 5 Send username/password */ /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ ADD_DATA(buf, "\x01\x02me\x03you\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); - tt_assert(fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks) == 1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, 1); tt_int_op(5,OP_EQ, socks->socks_version); tt_int_op(2,OP_EQ, socks->replylen); tt_int_op(1,OP_EQ, socks->reply[0]); @@ -400,6 +500,48 @@ test_socks_5_authenticate_with_data(void *ptr) ; } +/** Try to negotiate an unsupported authentication type */ +static void +test_socks_5_auth_unsupported_type(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* None of these authentication types are recognized. */ + ADD_DATA(buf, "\x05\x03\x99\x21\x10"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, -1); + tt_int_op(0,OP_EQ, socks->socks_version); + tt_int_op(2,OP_EQ, socks->replylen); + tt_int_op(5,OP_EQ, socks->reply[0]); + tt_int_op(0xff,OP_EQ, socks->reply[1]); + + done: + ; +} + +/** Try to negotiate an unsupported version of username/password auth. */ +static void +test_socks_5_auth_unsupported_version(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* Negotiate username/password */ + ADD_DATA(buf, "\x05\x01\x02"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, 0); + tt_int_op(0,OP_EQ, buf_datalen(buf)); /* buf should be drained */ + /* Now, suggest an unrecognized username/password version */ + ADD_DATA(buf, "\x02\x05" "hello" "\x05" "world"); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, -1); + + done: + ; +} + /** Perform SOCKS 5 authentication before method negotiated */ static void test_socks_5_auth_before_negotiation(void *ptr) @@ -408,9 +550,9 @@ test_socks_5_auth_before_negotiation(void *ptr) /* SOCKS 5 Send username/password */ ADD_DATA(buf, "\x01\x02me\x02me"); - tt_assert(fetch_from_buf_socks(buf, socks, - get_options()->TestSocks, - get_options()->SafeSocks) == -1); + tt_int_op(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), + OP_EQ, -1); tt_int_op(0,OP_EQ, socks->socks_version); tt_int_op(0,OP_EQ, socks->replylen); tt_int_op(0,OP_EQ, socks->reply[0]); @@ -492,20 +634,397 @@ test_socks_5_malformed_commands(void *ptr) ; } +static void +test_socks_5_bad_arguments(void *ptr) +{ + SOCKS_TEST_INIT(); + setup_capture_of_logs(LOG_DEBUG); + + /* Socks5, bogus hostname */ + ADD_DATA(buf, "\x05\x01\x00" "\x05\x01\x00\x03\x03" "---" "\x00\x50" ); + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Your application (using socks5 to port 80) " + "gave Tor a malformed hostname: "); + mock_clean_saved_logs(); + socks_request_clear(socks); + + done: + teardown_capture_of_logs(); +} + +/** check for correct behavior when the socks command has not arrived. */ +static void +test_socks_truncated(void *ptr) +{ + const struct { + enum { NONE, AUTH, ALL } setup; + const char *body; + size_t len; + } commands[] = { + /* SOCKS4 */ + /* Connect, to an IP. */ + { NONE, "\x04\x01\x05\x05\x01\x02\x03\x04\x00", 9}, + /* Connect, to an IP, with authentication. */ + { NONE, "\x04\x01\x05\x05\x01\x02\x03\x04hello\x00", 14}, + /* SOCKS4A */ + /* Connect, to a hostname */ + { NONE, "\x04\x01\x09\x09\x00\x00\x00\x01\x00www.example.com\x00", 25}, + /* Connect, to a hostname, with authentication */ + { NONE, "\x04\x01\x09\x09\x00\x00\x00\x01hi\x00www.example.com\x00", 27}, + /* SOCKS5 */ + /* initial handshake */ + { NONE, "\x05\x00", 2 }, + /* no-auth handshake */ + { NONE, "\x05\x03\x99\x21\x10", 5 }, + /* SOCSK5, username-password, all empty. */ + { AUTH, "\x01\x00\x00", 3 }, + /* SOCSK5, username-password, 1 char each. */ + { AUTH, "\x01\x01x\x01y", 5 }, + /* SOCSK5, username-password, max length. */ + { AUTH, "\x01\xff" + "Ogni tempo ha il suo fascismo: se ne notano i segni premonitori " + "dovunque la concentrazione di potere nega al cittadino la " + "possibilit\xc3\xa0 e la capacit\xc3\xa0 di esprimere ed attuare la " + "sua volont\xc3\xa0. A questo si arriva in molti modi, non " + "necessariamente col terror" + "\xff" + "e dell'intimidazione poliziesca, ma anche negando o distorcendo " + "l'informazione, inquinando la giustizia, paralizzando la scuola, " + "diffondendo in molti modi sottili la nostalgia per un mondo in cui " + "regnava sovrano l'ordine, ed in cui la sicurezza dei pochi " + /* privilegiati riposava sul lavoro forzato e sul silenzio forzato dei + molti. -- Primo Levi */ , 513 }, + /* Socks5, IPv4 address */ + { ALL, "\x05\x01\x00\x01\x01\x02\x03\x04\x20\x20", 10 }, + /* Socks5, IPv6 address */ + { ALL, "\x05\x01\x00\x04" + "\x49\x20\x48\x41\x5a\x20\x45\x41\x53\x54\x45\x52\x20\x45\x47\x47" + "\x20\x20", 22 }, + /* Socks5, hostname, empty. */ + { ALL, "\x05\x01\x00\x03" "\x00" "\x00\x50", 7 }, + /* Socks5, hostname, moderate. */ + { ALL, "\x05\x01\x00\x03" "\x11" "onion.example.com" "\x00\x50", 24 }, + /* Socks5, hostname, maximum. */ + { ALL, "\x05\x01\x00\x03" "\xff" + "whatsoever.I.shall.see.or.hear.in.the.course.of.my.profession.as.well." + "as.outside.my.profession.in.my.intercourse.with.men.if.it.be.what." + "should.not.be.published.abroad.I.will.never.divulge.holding.such." + "things.to.be.holy.secrets.x.hippocratic.oath.wikipedia" + "\x00\x50", 262 }, + }; + unsigned i, j; + SOCKS_TEST_INIT(); + for (i = 0; i < ARRAY_LENGTH(commands); ++i) { + for (j = 0; j < commands[i].len; ++j) { + switch (commands[i].setup) { + default: /* Falls through */ + case NONE: + /* This test calls for no setup on the socks state. */ + break; + case AUTH: + /* This test calls for the socks state to be waiting for + * username/password authentication */ + ADD_DATA(buf, "\x05\x01\x02"); + tt_int_op(0, OP_EQ, fetch_from_buf_socks(buf, socks, 0, 0)); + tt_int_op(0, OP_EQ, buf_datalen(buf)); + break; + case ALL: + /* This test calls for the socks state to be waiting for + * the connection request */ + ADD_DATA(buf, "\x05\x01\x00"); + tt_int_op(0, OP_EQ, fetch_from_buf_socks(buf, socks, 0, 0)); + tt_int_op(0, OP_EQ, buf_datalen(buf)); + } + + TT_BLATHER(("Checking command %u, length %u, omitting char %u", i, j, + (unsigned)commands[i].body[j])); + buf_add(buf, commands[i].body, j); + /* This should return 0 meaning "not done yet" */ + tt_int_op(0, OP_EQ, fetch_from_buf_socks(buf, socks, 0, 0)); + tt_uint_op(j, OP_EQ, buf_datalen(buf)); /* Nothing was drained */ + buf_clear(buf); + socks_request_free(testdata->req); + socks = testdata->req = socks_request_new(); + } + } + done: + ; +} + +static void +test_socks_wrong_protocol(void *ptr) +{ + SOCKS_TEST_INIT(); + setup_capture_of_logs(LOG_DEBUG); + + /* HTTP request. */ + ADD_DATA(buf, "GET /index.html HTTP/1.0" ); + tt_int_op(fetch_from_buf_socks(buf, socks, 1, 0), OP_EQ, -1); + buf_clear(buf); + expect_log_msg_containing("Socks version 71 not recognized. " + "(This port is not an HTTP proxy;"); + mock_clean_saved_logs(); + socks_request_clear(socks); + + done: + teardown_capture_of_logs(); +} + +/* Check our client-side socks4 parsing (that is to say, our parsing of + * server responses). + */ +static void +test_socks_client_v4(void *arg) +{ + (void)arg; + buf_t *buf = buf_new(); + char *reason = NULL; + + /* Legit socks4 response, success */ + ADD_DATA(buf, "\x04\x5a\x20\x25\x01\x02\x03\x04"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, PROXY_SOCKS4_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* Legit socks4 response, failure. */ + ADD_DATA(buf, "\x04\x5b\x20\x25\x01\x02\x03\x04"); + tt_int_op(-1, OP_EQ, + fetch_from_buf_socks_client(buf, PROXY_SOCKS4_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_NE, NULL); + tt_str_op(reason, OP_EQ, "server rejected connection"); + + done: + buf_free(buf); + tor_free(reason); +} + +/* Check our client-side socks5 authentication-negotiation parsing (that is to + * say, our parsing of server responses). + */ +static void +test_socks_client_v5_auth(void *arg) +{ + (void)arg; + buf_t *buf = buf_new(); + char *reason = NULL; + + /* Legit socks5 responses, got a method we like. */ + ADD_DATA(buf, "\x05\x00"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_METHOD_NONE, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* Same, but we wanted something else. */ + ADD_DATA(buf, "\x05\x00"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* Same, and they offered a password. */ + ADD_DATA(buf, "\x05\x02"); + tt_int_op(2, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* They rejected our method, or selected something we don't know. */ + ADD_DATA(buf, "\x05\xff"); + tt_int_op(-1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_METHOD_NONE, + &reason)); + tt_str_op(reason, OP_EQ, "server doesn't support any of our available " + "authentication methods"); + buf_clear(buf); + tor_free(reason); + ADD_DATA(buf, "\x05\xff"); + tt_int_op(-1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929, + &reason)); + tt_str_op(reason, OP_EQ, "server doesn't support any of our available " + "authentication methods"); + tor_free(reason); + buf_clear(buf); + + /* Now check for authentication responses: check success and failure. */ + ADD_DATA(buf, "\x01\x00"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_RFC1929_OK, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + ADD_DATA(buf, "\x01\xf0"); + tt_int_op(-1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_AUTH_RFC1929_OK, + &reason)); + tt_ptr_op(reason, OP_NE, NULL); + tt_str_op(reason, OP_EQ, "authentication failed"); + + done: + buf_free(buf); + tor_free(reason); +} + +/* Check our client-side socks5 connect parsing (that is to say, our parsing + * of server responses). + */ +static void +test_socks_client_v5_connect(void *arg) +{ + (void)arg; + buf_t *buf = buf_new(); + char *reason = NULL; + + /* Legit socks5 responses, success, ipv4. */ + ADD_DATA(buf, "\x05\x00\x00\x01\x01\x02\x03\x04\x00\x05"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* Legit socks5 responses, success, ipv6. */ + ADD_DATA(buf, "\x05\x00\x00\x04" + "abcdefghijklmnop" + "\x00\x05"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* Legit socks5 responses, success, hostname. */ + ADD_DATA(buf, "\x05\x00\x00\x03\x12" + "gopher.example.com" + "\x00\x05"); + tt_int_op(1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_EQ, NULL); + tt_int_op(buf_datalen(buf), OP_EQ, 0); + + /* Legit socks5 responses, failure, hostname. */ + ADD_DATA(buf, "\x05\x03\x00\x03\x12" + "gopher.example.com" + "\x00\x05"); + tt_int_op(-1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_NE, NULL); + tt_str_op(reason, OP_EQ, "Network unreachable"); + tor_free(reason); + buf_clear(buf); + + /* Bogus socks5 responses: what is address type 0x17? */ + ADD_DATA(buf, "\x05\x03\x00\x17\x12 blah blah"); + tt_int_op(-1, OP_EQ, + fetch_from_buf_socks_client(buf, + PROXY_SOCKS5_WANT_CONNECT_OK, + &reason)); + tt_ptr_op(reason, OP_NE, NULL); + tt_str_op(reason, OP_EQ, "invalid response to connect request"); + buf_clear(buf); + + done: + buf_free(buf); + tor_free(reason); +} + +static void +test_socks_client_truncated(void *arg) +{ + (void)arg; + buf_t *buf = buf_new(); + char *reason = NULL; + +#define S(str) str, (sizeof(str)-1) + const struct { + int state; + const char *body; + size_t len; + } replies[] = { + { PROXY_SOCKS4_WANT_CONNECT_OK, S("\x04\x5a\x20\x25\x01\x02\x03\x04") }, + { PROXY_SOCKS4_WANT_CONNECT_OK, S("\x04\x5b\x20\x25\x01\x02\x03\x04") }, + { PROXY_SOCKS5_WANT_AUTH_METHOD_NONE, S("\x05\x00") }, + { PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929, S("\x05\x00") }, + { PROXY_SOCKS5_WANT_AUTH_RFC1929_OK, S("\x01\x00") }, + { PROXY_SOCKS5_WANT_CONNECT_OK, + S("\x05\x00\x00\x01\x01\x02\x03\x04\x00\x05") }, + { PROXY_SOCKS5_WANT_CONNECT_OK, + S("\x05\x00\x00\x04" "abcdefghijklmnop" "\x00\x05") }, + { PROXY_SOCKS5_WANT_CONNECT_OK, + S("\x05\x00\x00\x03\x12" "gopher.example.com" "\x00\x05") }, + { PROXY_SOCKS5_WANT_CONNECT_OK, + S("\x05\x03\x00\x03\x12" "gopher.example.com""\x00\x05") }, + { PROXY_SOCKS5_WANT_CONNECT_OK, + S("\x05\x03\x00\x17") }, + }; + unsigned i, j; + for (i = 0; i < ARRAY_LENGTH(replies); ++i) { + for (j = 0; j < replies[i].len; ++j) { + TT_BLATHER(("Checking command %u, length %u", i, j)); + buf_add(buf, replies[i].body, j); + /* This should return 0 meaning "not done yet" */ + tt_int_op(0, OP_EQ, + fetch_from_buf_socks_client(buf, replies[i].state, &reason)); + tt_uint_op(j, OP_EQ, buf_datalen(buf)); /* Nothing was drained */ + buf_clear(buf); + tt_ptr_op(reason, OP_EQ, NULL); + } + } + + done: + tor_free(reason); + buf_free(buf); +} + #define SOCKSENT(name) \ { #name, test_socks_##name, TT_FORK, &socks_setup, NULL } struct testcase_t socks_tests[] = { SOCKSENT(4_unsupported_commands), SOCKSENT(4_supported_commands), + SOCKSENT(4_bad_arguments), SOCKSENT(5_unsupported_commands), SOCKSENT(5_supported_commands), SOCKSENT(5_no_authenticate), + SOCKSENT(5_auth_unsupported_type), + SOCKSENT(5_auth_unsupported_version), SOCKSENT(5_auth_before_negotiation), SOCKSENT(5_authenticate), SOCKSENT(5_authenticate_with_data), SOCKSENT(5_malformed_commands), + SOCKSENT(5_bad_arguments), + + SOCKSENT(truncated), + + SOCKSENT(wrong_protocol), + + { "client/v4", test_socks_client_v4, TT_FORK, NULL, NULL }, + { "client/v5_auth", test_socks_client_v5_auth, TT_FORK, NULL, NULL }, + { "client/v5_connect", test_socks_client_v5_connect, TT_FORK, NULL, NULL }, + { "client/truncated", test_socks_client_truncated, TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_status.c b/src/test/test_status.c index a3b1a2af87..f86f8e3b9e 100644 --- a/src/test/test_status.c +++ b/src/test/test_status.c @@ -889,8 +889,8 @@ NS(logv)(int severity, log_domain_mask_t domain, const char *funcname, tt_str_op(format, OP_EQ, "Average packaged cell fullness: %2.3f%%. " "TLS write overhead: %.f%%"); - tt_double_op(fabs(va_arg(ap, double) - 50.0), <=, DBL_EPSILON); - tt_double_op(fabs(va_arg(ap, double) - 0.0), <=, DBL_EPSILON); + tt_double_op(fabs(va_arg(ap, double) - 50.0), OP_LE, DBL_EPSILON); + tt_double_op(fabs(va_arg(ap, double) - 0.0), OP_LE, DBL_EPSILON); break; default: tt_abort_msg("unexpected call to logv()"); // TODO: prettyprint args @@ -1039,7 +1039,7 @@ NS(logv)(int severity, log_domain_mask_t domain, "Average packaged cell fullness: %2.3f%%. " "TLS write overhead: %.f%%"); tt_int_op(fabs(va_arg(ap, double) - 100.0) <= DBL_EPSILON, OP_EQ, 1); - tt_double_op(fabs(va_arg(ap, double) - 100.0), <=, DBL_EPSILON); + tt_double_op(fabs(va_arg(ap, double) - 100.0), OP_LE, DBL_EPSILON); break; default: tt_abort_msg("unexpected call to logv()"); // TODO: prettyprint args diff --git a/src/test/test_storagedir.c b/src/test/test_storagedir.c index 19e5de4ea3..a27074c21f 100644 --- a/src/test/test_storagedir.c +++ b/src/test/test_storagedir.c @@ -338,7 +338,7 @@ test_storagedir_read_labeled(void *arg) tt_assert(labels->next->next); tt_str_op(labels->next->next->key, OP_EQ, "Yadda"); tt_str_op(labels->next->next->value, OP_EQ, "yadda."); - tt_assert(labels->next->next->next == NULL); + tt_ptr_op(labels->next->next->next, OP_EQ, NULL); /* Try reading this time. */ sz = 0; diff --git a/src/test/test_switch_id.c b/src/test/test_switch_id.c index 53de793fe8..fe36d8c6e6 100644 --- a/src/test/test_switch_id.c +++ b/src/test/test_switch_id.c @@ -71,7 +71,7 @@ check_can_bind_low_ports(void) return -1; } -#endif +#endif /* !defined(_WIN32) */ int main(int argc, char **argv) @@ -83,7 +83,7 @@ main(int argc, char **argv) fprintf(stderr, "This test is not supported on your OS.\n"); return 77; -#else +#else /* !(defined(_WIN32)) */ const char *username; const char *testname; if (argc != 3) { @@ -174,7 +174,7 @@ main(int argc, char **argv) } cap_free(caps); } -#endif +#endif /* defined(HAVE_LINUX_CAPABILITIES) */ break; default: fprintf(stderr, "Unsupported test '%s'\n", testname); @@ -187,6 +187,6 @@ main(int argc, char **argv) } return (okay ? 0 : 1); -#endif +#endif /* defined(_WIN32) */ } diff --git a/src/test/test_threads.c b/src/test/test_threads.c index 18a9407ff7..ed6d8f04aa 100644 --- a/src/test/test_threads.c +++ b/src/test/test_threads.c @@ -139,8 +139,8 @@ test_threads_basic(void *arg) !strcmp(strmap_get(thread_test_strmap_, "thread 2"), strmap_get(thread_test_strmap_, "last to run"))); - tt_int_op(thread_fns_failed, ==, 0); - tt_int_op(thread_fn_tid1, !=, thread_fn_tid2); + tt_int_op(thread_fns_failed, OP_EQ, 0); + tt_int_op(thread_fn_tid1, OP_NE, thread_fn_tid2); done: tor_free(s1); @@ -275,14 +275,14 @@ test_threads_conditionvar(void *arg) SPIN(); tor_mutex_release(ti->mutex); - tt_int_op(ti->value, ==, 1337); + tt_int_op(ti->value, OP_EQ, 1337); if (!timeout) { - tt_int_op(ti->n_shutdown, ==, 4); + tt_int_op(ti->n_shutdown, OP_EQ, 4); } else { tor_sleep_msec(200); tor_mutex_acquire(ti->mutex); - tt_int_op(ti->n_shutdown, ==, 2); - tt_int_op(ti->n_timeouts, ==, 2); + tt_int_op(ti->n_shutdown, OP_EQ, 2); + tt_int_op(ti->n_timeouts, OP_EQ, 2); tor_mutex_release(ti->mutex); } diff --git a/src/test/test_tortls.c b/src/test/test_tortls.c index 7aa3051464..29f7cc9c37 100644 --- a/src/test/test_tortls.c +++ b/src/test/test_tortls.c @@ -63,7 +63,7 @@ fake_num_ciphers(void) { return 0; } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_errno_to_tls_error(void *data) @@ -136,7 +136,7 @@ test_tortls_tor_tls_new(void *data) SSL_CTX_free(client_tls_context->ctx); client_tls_context->ctx = NULL; tls = tor_tls_new(-1, 0); - tt_assert(!tls); + tt_ptr_op(tls, OP_EQ, NULL); #ifndef OPENSSL_OPAQUE method = give_me_a_test_method(); @@ -144,8 +144,8 @@ test_tortls_tor_tls_new(void *data) method->num_ciphers = fake_num_ciphers; client_tls_context->ctx = ctx; tls = tor_tls_new(-1, 0); - tt_assert(!tls); -#endif + tt_ptr_op(tls, OP_EQ, NULL); +#endif /* !defined(OPENSSL_OPAQUE) */ done: UNMOCK(tor_tls_cert_matches_key); @@ -376,7 +376,7 @@ test_tortls_log_one_error(void *ignored) tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_RECORD_TOO_LARGE), LOG_WARN, 0, NULL); expect_log_severity(LOG_INFO); -#endif +#endif /* !defined(OPENSSL_1_1_API) */ mock_clean_saved_logs(); tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_UNKNOWN_PROTOCOL), @@ -490,7 +490,7 @@ test_tortls_get_error(void *ignored) tor_free(tls); SSL_CTX_free(ctx); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_always_accept_verify_cb(void *ignored) @@ -520,7 +520,7 @@ test_tortls_x509_cert_free(void *ignored) cert->encoded = tor_malloc_zero(1); tor_x509_cert_free(cert); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_x509_cert_get_id_digests(void *ignored) @@ -662,7 +662,7 @@ test_tortls_cert_get_key(void *ignored) tor_free(cert); crypto_pk_free(res); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_get_my_client_auth_key(void *ignored) @@ -744,7 +744,7 @@ get_cipher_by_name(const char *name) return NULL; } -#endif +#endif /* !defined(HAVE_SSL_GET_CLIENT_CIPHERS) */ #ifndef OPENSSL_OPAQUE static void @@ -879,7 +879,7 @@ test_tortls_classify_client_ciphers(void *ignored) tor_free(tls); SSL_CTX_free(ctx); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_client_is_using_v2_ciphers(void *ignored) @@ -921,7 +921,7 @@ test_tortls_client_is_using_v2_ciphers(void *ignored) done: SSL_free(ssl); SSL_CTX_free(ctx); -#endif +#endif /* defined(HAVE_SSL_GET_CLIENT_CIPHERS) */ } #ifndef OPENSSL_OPAQUE @@ -937,7 +937,7 @@ fixed_try_to_extract_certs_from_tls(int severity, tor_tls_t *tls, *cert_out = fixed_try_to_extract_certs_from_tls_cert_out_result; *id_cert_out = fixed_try_to_extract_certs_from_tls_id_cert_out_result; } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static const char* notCompletelyValidCertString = @@ -956,7 +956,7 @@ static const char* notCompletelyValidCertString = "jC9UeuErhaA/zzWi8ewMTFZW/WshOrm3fNvcMrMLKtH534JKvcdMg6qIdjTFINIr\n" "evnAhf0cwULaebn+lMs8Pdl7y37+sfluVok=\n" "-----END CERTIFICATE-----\n"; -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static const char* validCertString = "-----BEGIN CERTIFICATE-----\n" "MIIDpTCCAY0CAg3+MA0GCSqGSIb3DQEBBQUAMF4xCzAJBgNVBAYTAlVTMREwDwYD\n" @@ -1079,7 +1079,7 @@ test_tortls_verify(void *ignored) tor_free(tls); tor_free(k); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1118,7 +1118,7 @@ test_tortls_check_lifetime(void *ignored) tor_free(tls); X509_free(validCert); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static int fixed_ssl_pending_result = 0; @@ -1153,7 +1153,7 @@ test_tortls_get_pending_bytes(void *ignored) tor_free(tls->ssl); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_get_forced_write_size(void *ignored) @@ -1276,13 +1276,13 @@ test_tortls_SSL_SESSION_get_master_key(void *ignored) tt_int_op(out[0], OP_EQ, 43); done: -#endif +#endif /* !defined(HAVE_SSL_SESSION_GET_MASTER_KEY) */ tor_free(tls->ssl->session); tor_free(tls->ssl); tor_free(tls); tor_free(out); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1290,7 +1290,7 @@ test_tortls_get_tlssecrets(void *ignored) { (void)ignored; int ret; - uint8_t *secret_out = tor_malloc_zero(DIGEST256_LEN);; + uint8_t *secret_out = tor_malloc_zero(DIGEST256_LEN); tor_tls_t *tls; tls = tor_malloc_zero(sizeof(tor_tls_t)); tls->ssl = tor_malloc_zero(sizeof(SSL)); @@ -1308,7 +1308,7 @@ test_tortls_get_tlssecrets(void *ignored) tor_free(tls->ssl); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1350,7 +1350,7 @@ test_tortls_get_buffer_sizes(void *ignored) tt_int_op(rbuf_c, OP_EQ, 1); tt_int_op(wbuf_c, OP_EQ, 2); -#endif +#endif /* OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) */ done: tor_free(tls->ssl->s3->rbuf.buf); @@ -1359,7 +1359,7 @@ test_tortls_get_buffer_sizes(void *ignored) tor_free(tls->ssl); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_evaluate_ecgroup_for_tls(void *ignored) @@ -1378,6 +1378,7 @@ test_tortls_evaluate_ecgroup_for_tls(void *ignored) ret = evaluate_ecgroup_for_tls("P224"); // tt_int_op(ret, OP_EQ, 1); This varies between machines + tt_assert(ret == 0 || ret == 1); done: (void)0; @@ -1450,7 +1451,7 @@ test_tortls_try_to_extract_certs_from_tls(void *ignored) X509_free(c1); X509_free(c2); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1482,7 +1483,7 @@ test_tortls_get_peer_cert(void *ignored) tor_free(tls); X509_free(cert); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1512,7 +1513,7 @@ test_tortls_peer_has_cert(void *ignored) tor_free(tls); X509_free(cert); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_is_server(void *ignored) @@ -1572,7 +1573,7 @@ test_tortls_session_secret_cb(void *ignored) SSL_CTX_free(ctx); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE /* TODO: It seems block_renegotiation and unblock_renegotiation and @@ -1623,7 +1624,7 @@ test_tortls_unblock_renegotiation(void *ignored) tor_free(tls->ssl); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1642,7 +1643,7 @@ test_tortls_assert_renegotiation_unblocked(void *ignored) tor_free(tls->ssl); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static void test_tortls_set_logged_address(void *ignored) @@ -1697,7 +1698,7 @@ test_tortls_set_renegotiate_callback(void *ignored) tor_free(tls->ssl); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static SSL_CIPHER *fixed_cipher1 = NULL; @@ -1715,7 +1716,7 @@ fake_get_cipher(unsigned ncipher) return NULL; } } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1785,7 +1786,7 @@ test_tortls_find_cipher_by_id(void *ignored) SSL_CTX_free(ctx); tor_free(fixed_cipher1); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1813,7 +1814,7 @@ test_tortls_debug_state_callback(void *ignored) tor_free(buf); tor_free(ssl); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -1877,7 +1878,7 @@ test_tortls_server_info_callback(void *ignored) SSL_CTX_free(ctx); tor_free(tls); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static int fixed_ssl_read_result_index; @@ -1918,7 +1919,7 @@ setting_version_and_state_ssl_shutdown(SSL *s) s->version = SSL2_VERSION; return fixed_ssl_shutdown_result; } -#endif +#endif /* !defined(LIBRESSL_VERSION_NUMBER) */ static int dummy_handshake_func(SSL *s) @@ -2014,7 +2015,7 @@ test_tortls_shutdown(void *ignored) method->ssl_shutdown = setting_version_and_state_ssl_shutdown; ret = tor_tls_shutdown(tls); tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); -#endif +#endif /* !defined(LIBRESSL_VERSION_NUMBER) */ done: teardown_capture_of_logs(); @@ -2085,7 +2086,7 @@ test_tortls_read(void *ignored) ret = tor_tls_read(tls, buf, 10); tt_int_op(ret, OP_EQ, TOR_TLS_CLOSE); tt_int_op(tls->state, OP_EQ, TOR_TLS_ST_CLOSED); -#endif +#endif /* !defined(LIBRESSL_VERSION_NUMBER) */ // TODO: fill up done: @@ -2160,7 +2161,7 @@ test_tortls_write(void *ignored) tor_free(tls); tor_free(method); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static int fixed_ssl_accept_result; @@ -2240,7 +2241,7 @@ test_tortls_handshake(void *ignored) "(null):SSLv3 write client hello B)\n"); expect_log_msg("TLS error while handshaking: (null) (in system library:" "connect:SSLv3 write client hello B)\n"); -#endif +#endif /* 0 */ expect_log_severity(LOG_INFO); tls->isServer = 0; @@ -2258,7 +2259,7 @@ test_tortls_handshake(void *ignored) "(null) (in bignum routines:(null):SSLv3 write client hello B)\n"); expect_log_msg("TLS error while handshaking: " "(null) (in system library:connect:SSLv3 write client hello B)\n"); -#endif +#endif /* 0 */ expect_log_severity(LOG_WARN); done: @@ -2268,7 +2269,7 @@ test_tortls_handshake(void *ignored) tor_free(tls); tor_free(method); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #ifndef OPENSSL_OPAQUE static void @@ -2343,7 +2344,7 @@ test_tortls_finish_handshake(void *ignored) tor_free(method); teardown_capture_of_logs(); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static int fixed_crypto_pk_new_result_index; static crypto_pk_t *fixed_crypto_pk_new_result[5]; @@ -2568,7 +2569,7 @@ test_tortls_context_new(void *ignored) UNMOCK(crypto_pk_generate_key_with_bits); UNMOCK(crypto_pk_new); } -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ static int fixed_crypto_pk_get_evp_pkey_result_index = 0; static EVP_PKEY *fixed_crypto_pk_get_evp_pkey_result[5]; @@ -2637,7 +2638,7 @@ test_tortls_cert_new(void *ignored) X509_get_pubkey(cert)->type = EVP_PKEY_DSA; ret = tor_x509_cert_new(cert); tt_assert(ret); -#endif +#endif /* 0 */ #ifndef OPENSSL_OPAQUE cert = read_cert_from(validCertString); @@ -2645,7 +2646,7 @@ test_tortls_cert_new(void *ignored) cert->cert_info = NULL; ret = tor_x509_cert_new(cert); tt_assert(ret); -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ done: tor_x509_cert_free(ret); @@ -2692,7 +2693,7 @@ test_tortls_cert_is_valid(void *ignored) cert->cert->cert_info->key = NULL; ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 1); tt_int_op(ret, OP_EQ, 0); -#endif +#endif /* !defined(OPENSSL_OPAQUE) */ #if 0 tor_x509_cert_free(cert); @@ -2731,7 +2732,7 @@ test_tortls_cert_is_valid(void *ignored) X509_get_pubkey(cert->cert)->ameth = NULL; ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0); tt_int_op(ret, OP_EQ, 0); -#endif +#endif /* 0 */ done: tor_x509_cert_free(cert); @@ -2764,7 +2765,7 @@ test_tortls_context_init_one(void *ignored) { #name, NULL, TT_SKIP, NULL, NULL } #else #define INTRUSIVE_TEST_CASE(name, flags) LOCAL_TEST_CASE(name, flags) -#endif +#endif /* defined(OPENSSL_OPAQUE) */ struct testcase_t tortls_tests[] = { LOCAL_TEST_CASE(errno_to_tls_error, 0), diff --git a/src/test/test_util.c b/src/test/test_util.c index fa6ce1dc85..0519a4758f 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -70,7 +70,7 @@ test_util_read_until_eof_impl(const char *fname, size_t file_len, fd = open(fifo_name, O_RDONLY|O_BINARY); tt_int_op(fd, OP_GE, 0); str = read_file_to_str_until_eof(fd, read_limit, &sz); - tt_assert(str != NULL); + tt_ptr_op(str, OP_NE, NULL); if (read_limit < file_len) tt_int_op(sz, OP_EQ, read_limit); @@ -367,7 +367,7 @@ test_util_time(void *arg) * calculations internally, then catches the overflow. */ #define TV_SEC_MAX TIME_MAX #define TV_SEC_MIN TIME_MIN -#endif +#endif /* defined(_WIN32) */ /* Assume tv_usec is an unsigned integer until proven otherwise */ #define TV_USEC_MAX UINT_MAX @@ -635,13 +635,16 @@ test_util_time(void *arg) * time_t */ a_time.tm_year = 2039-1900; #if SIZEOF_TIME_T == 4 + setup_full_capture_of_logs(LOG_WARN); tt_int_op((time_t) -1,OP_EQ, tor_timegm(&a_time)); + expect_single_log_msg_containing("Result does not fit in tor_timegm"); + teardown_capture_of_logs(); #elif SIZEOF_TIME_T == 8 t_res = 2178252895UL; tt_int_op(t_res, OP_EQ, tor_timegm(&a_time)); tor_gmtime_r(&t_res, &b_time); TM_EQUAL(a_time, b_time); -#endif +#endif /* SIZEOF_TIME_T == 4 || ... */ /* Test tor_timegm out of range */ @@ -651,8 +654,7 @@ test_util_time(void *arg) setup_full_capture_of_logs(LOG_WARN); \ } while (0) #define CHECK_TIMEGM_WARNING(msg) do { \ - expect_log_msg_containing(msg); \ - tt_int_op(1, OP_EQ, smartlist_len(mock_saved_logs())); \ + expect_single_log_msg_containing(msg); \ teardown_capture_of_logs(); \ } while (0) @@ -689,7 +691,7 @@ test_util_time(void *arg) CAPTURE(); tt_int_op((time_t) -1,OP_EQ, tor_timegm(&a_time)); CHECK_TIMEGM_ARG_OUT_OF_RANGE(); -#endif +#endif /* SIZEOF_INT == 4 || SIZEOF_INT == 8 */ #if SIZEOF_INT == 8 a_time.tm_year = -1*(1 << 48); @@ -711,7 +713,7 @@ test_util_time(void *arg) CAPTURE(); tt_int_op((time_t) -1,OP_EQ, tor_timegm(&a_time)); CHECK_TIMEGM_ARG_OUT_OF_RANGE(); -#endif +#endif /* SIZEOF_INT == 8 */ /* Wrong year >= INT32_MAX - 1900 */ #if SIZEOF_INT == 4 || SIZEOF_INT == 8 @@ -724,7 +726,7 @@ test_util_time(void *arg) CAPTURE(); tt_int_op((time_t) -1,OP_EQ, tor_timegm(&a_time)); CHECK_TIMEGM_ARG_OUT_OF_RANGE(); -#endif +#endif /* SIZEOF_INT == 4 || SIZEOF_INT == 8 */ #if SIZEOF_INT == 8 /* one of the largest tm_year values my 64 bit system supports */ @@ -752,7 +754,7 @@ test_util_time(void *arg) CAPTURE(); tt_int_op((time_t) -1,OP_EQ, tor_timegm(&a_time)); CHECK_TIMEGM_ARG_OUT_OF_RANGE(); -#endif +#endif /* SIZEOF_INT == 8 */ /* month */ a_time.tm_year = 2007-1900; /* restore valid year */ @@ -888,7 +890,7 @@ test_util_time(void *arg) teardown_capture_of_logs(); } } -#endif +#endif /* SIZEOF_TIME_T == 8 */ /* time_t >= INT_MAX yields a year clamped to 2037 or 9999, * depending on whether the implementation of the system gmtime(_r) @@ -904,7 +906,7 @@ test_util_time(void *arg) tt_assert(b_time.tm_year == (2037-1900) || b_time.tm_year == (2038-1900)); } -#endif +#endif /* SIZEOF_TIME_T == 4 || SIZEOF_TIME_T == 8 */ #if SIZEOF_TIME_T == 8 { @@ -929,7 +931,7 @@ test_util_time(void *arg) tt_assert(b_time.tm_year == (2037-1900) || b_time.tm_year == (9999-1900)); } -#endif +#endif /* SIZEOF_TIME_T == 8 */ /* Test {format,parse}_rfc1123_time */ @@ -965,7 +967,9 @@ test_util_time(void *arg) strlcpy(timestr, "Wed, 17 Feb 2038 06:13:20 GMT", sizeof(timestr)); t_res = 0; + CAPTURE(); i = parse_rfc1123_time(timestr, &t_res); + CHECK_TIMEGM_WARNING("does not fit in tor_timegm"); tt_int_op(-1,OP_EQ, i); #elif SIZEOF_TIME_T == 8 tt_str_op("Wed, 17 Feb 2038 06:13:20 GMT",OP_EQ, timestr); @@ -974,7 +978,7 @@ test_util_time(void *arg) i = parse_rfc1123_time(timestr, &t_res); tt_int_op(0,OP_EQ, i); tt_int_op(t_res,OP_EQ, (time_t)2150000000UL); -#endif +#endif /* SIZEOF_TIME_T == 4 || ... */ /* The timezone doesn't matter */ t_res = 0; @@ -1040,13 +1044,16 @@ test_util_time(void *arg) /* This value is out of range with 32 bit time_t, but in range for 64 bit * time_t */ t_res = 0; - i = parse_iso_time("2038-02-17 06:13:20", &t_res); #if SIZEOF_TIME_T == 4 + CAPTURE(); + i = parse_iso_time("2038-02-17 06:13:20", &t_res); tt_int_op(-1,OP_EQ, i); + CHECK_TIMEGM_WARNING("does not fit in tor_timegm"); #elif SIZEOF_TIME_T == 8 + i = parse_iso_time("2038-02-17 06:13:20", &t_res); tt_int_op(0,OP_EQ, i); tt_int_op(t_res,OP_EQ, (time_t)2150000000UL); -#endif +#endif /* SIZEOF_TIME_T == 4 || ... */ tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-zz 99-99x99", &t_res)); tt_int_op(-1,OP_EQ, parse_iso_time("2011-03-32 00:00:00", &t_res)); @@ -1129,7 +1136,7 @@ test_util_time(void *arg) /* This SHOULD work on windows too; see bug #18665 */ tt_str_op("2038-02-17 06:13:20",OP_EQ, timestr); #endif -#endif +#endif /* SIZEOF_TIME_T == 4 || ... */ #undef CAPTURE #undef CHECK_TIMEGM_ARG_OUT_OF_RANGE @@ -1218,13 +1225,16 @@ test_util_parse_http_time(void *arg) #if SIZEOF_TIME_T == 4 /* parse_http_time should indicate failure on overflow, but it doesn't yet. * Hopefully #18480 will improve the failure semantics in this case. */ + setup_full_capture_of_logs(LOG_WARN); tt_int_op(0,OP_EQ,parse_http_time("Wed, 17 Feb 2038 06:13:20 GMT", &a_time)); tt_int_op((time_t)-1,OP_EQ, tor_timegm(&a_time)); + expect_single_log_msg_containing("does not fit in tor_timegm"); + teardown_capture_of_logs(); #elif SIZEOF_TIME_T == 8 tt_int_op(0,OP_EQ,parse_http_time("Wed, 17 Feb 2038 06:13:20 GMT", &a_time)); tt_int_op((time_t)2150000000UL,OP_EQ, tor_timegm(&a_time)); T("2038-02-17 06:13:20"); -#endif +#endif /* SIZEOF_TIME_T == 4 || ... */ tt_int_op(-1,OP_EQ, parse_http_time("2004-08-zz 99-99x99 GMT", &a_time)); tt_int_op(-1,OP_EQ, parse_http_time("2011-03-32 00:00:00 GMT", &a_time)); @@ -1237,7 +1247,7 @@ test_util_parse_http_time(void *arg) #undef T done: - ; + teardown_capture_of_logs(); } static void @@ -1471,7 +1481,7 @@ test_util_config_line_comment_character(void *arg) tor_free(k); tor_free(v); test_streq(str, ""); -#endif +#endif /* 0 */ done: tor_free(k); @@ -1602,7 +1612,7 @@ test_util_config_line_escaped_content(void *arg) str = parse_config_line_from_str_verbose(str, &k, &v, NULL); tt_ptr_op(str, OP_EQ, NULL); tor_free(k); tor_free(v); -#endif +#endif /* 0 */ str = buf6; @@ -1674,14 +1684,14 @@ test_util_config_line_crlf(void *arg) tt_assert(str); tt_str_op(k,OP_EQ,"Hello"); tt_str_op(v,OP_EQ,"world"); - tt_assert(!err); + tt_ptr_op(err, OP_EQ, NULL); tor_free(k); tor_free(v); str = parse_config_line_from_str_verbose(str, &k, &v, &err); tt_assert(str); tt_str_op(k,OP_EQ,"Hello"); tt_str_op(v,OP_EQ,"nice big world"); - tt_assert(!err); + tt_ptr_op(err, OP_EQ, NULL); tor_free(k); tor_free(v); tt_str_op(str,OP_EQ, ""); @@ -1786,7 +1796,7 @@ test_util_expand_filename(void *arg) done: tor_free(str); } -#endif +#endif /* !defined(_WIN32) */ /** Test tor_escape_str_for_pt_args(). */ static void @@ -1941,7 +1951,7 @@ test_util_strmisc(void *arg) tt_assert(!tor_mem_is_zero(buf, 10)); /* Test 'escaped' */ - tt_assert(NULL == escaped(NULL)); + tt_ptr_op(escaped(NULL), OP_EQ, NULL); tt_str_op("\"\"",OP_EQ, escaped("")); tt_str_op("\"abcd\"",OP_EQ, escaped("abcd")); tt_str_op("\"\\\\ \\n\\r\\t\\\"\\'\"",OP_EQ, escaped("\\ \n\r\t\"'")); @@ -1999,23 +2009,23 @@ test_util_strmisc(void *arg) /* Test memmem and memstr */ { const char *haystack = "abcde"; - tt_assert(!tor_memmem(haystack, 5, "ef", 2)); + tt_ptr_op(tor_memmem(haystack, 5, "ef", 2), OP_EQ, NULL); tt_ptr_op(tor_memmem(haystack, 5, "cd", 2),OP_EQ, haystack + 2); tt_ptr_op(tor_memmem(haystack, 5, "cde", 3),OP_EQ, haystack + 2); - tt_assert(!tor_memmem(haystack, 4, "cde", 3)); + tt_ptr_op(tor_memmem(haystack, 4, "cde", 3), OP_EQ, NULL); haystack = "ababcad"; tt_ptr_op(tor_memmem(haystack, 7, "abc", 3),OP_EQ, haystack + 2); tt_ptr_op(tor_memmem(haystack, 7, "ad", 2),OP_EQ, haystack + 5); tt_ptr_op(tor_memmem(haystack, 7, "cad", 3),OP_EQ, haystack + 4); - tt_assert(!tor_memmem(haystack, 7, "dadad", 5)); - tt_assert(!tor_memmem(haystack, 7, "abcdefghij", 10)); + tt_ptr_op(tor_memmem(haystack, 7, "dadad", 5), OP_EQ, NULL); + tt_ptr_op(tor_memmem(haystack, 7, "abcdefghij", 10), OP_EQ, NULL); /* memstr */ tt_ptr_op(tor_memstr(haystack, 7, "abc"),OP_EQ, haystack + 2); tt_ptr_op(tor_memstr(haystack, 7, "cad"),OP_EQ, haystack + 4); - tt_assert(!tor_memstr(haystack, 6, "cad")); - tt_assert(!tor_memstr(haystack, 7, "cadd")); - tt_assert(!tor_memstr(haystack, 7, "fe")); - tt_assert(!tor_memstr(haystack, 7, "ababcade")); + tt_ptr_op(tor_memstr(haystack, 6, "cad"), OP_EQ, NULL); + tt_ptr_op(tor_memstr(haystack, 7, "cadd"), OP_EQ, NULL); + tt_ptr_op(tor_memstr(haystack, 7, "fe"), OP_EQ, NULL); + tt_ptr_op(tor_memstr(haystack, 7, "ababcade"), OP_EQ, NULL); } /* Test hex_str */ @@ -2125,10 +2135,13 @@ test_util_parse_integer(void *arg) /* Base different than 10 */ tt_int_op(2L,OP_EQ, tor_parse_long("10",2,0,100,NULL,NULL)); tt_int_op(0L,OP_EQ, tor_parse_long("2",2,0,100,NULL,NULL)); - tt_int_op(0L,OP_EQ, tor_parse_long("10",-2,0,100,NULL,NULL)); tt_int_op(68284L,OP_EQ, tor_parse_long("10abc",16,0,70000,NULL,NULL)); tt_int_op(68284L,OP_EQ, tor_parse_long("10ABC",16,0,70000,NULL,NULL)); + tor_capture_bugs_(2); + tt_int_op(0L,OP_EQ, tor_parse_long("10",-2,0,100,NULL,NULL)); tt_int_op(0,OP_EQ, tor_parse_long("10ABC",-1,0,70000,&i,NULL)); + tt_int_op(2, OP_EQ, smartlist_len(tor_get_captured_bug_log_())); + tor_end_capture_bugs_(); tt_int_op(i,OP_EQ, 0); /* Test parse_ulong */ @@ -2141,7 +2154,10 @@ test_util_parse_integer(void *arg) tt_int_op(0UL,OP_EQ, tor_parse_ulong("8",8,0,100,NULL,NULL)); tt_int_op(50UL,OP_EQ, tor_parse_ulong("50",10,50,100,NULL,NULL)); tt_int_op(0UL,OP_EQ, tor_parse_ulong("-50",10,0,100,NULL,NULL)); + tor_capture_bugs_(1); tt_int_op(0UL,OP_EQ, tor_parse_ulong("50",-1,50,100,&i,NULL)); + tt_int_op(1, OP_EQ, smartlist_len(tor_get_captured_bug_log_())); + tor_end_capture_bugs_(); tt_int_op(0,OP_EQ, i); tt_int_op(0UL,OP_EQ, tor_parse_ulong("-50",10,0,100,&i,NULL)); tt_int_op(0,OP_EQ, i); @@ -2157,8 +2173,11 @@ test_util_parse_integer(void *arg) tt_assert(U64_LITERAL(0) == tor_parse_uint64("12345678901",10,500,INT32_MAX, &i, &cp)); tt_int_op(0,OP_EQ, i); + tor_capture_bugs_(1); tt_assert(U64_LITERAL(0) == tor_parse_uint64("123",-1,0,INT32_MAX, &i, &cp)); + tt_int_op(1, OP_EQ, smartlist_len(tor_get_captured_bug_log_())); + tor_end_capture_bugs_(); tt_int_op(0,OP_EQ, i); { @@ -2170,10 +2189,13 @@ test_util_parse_integer(void *arg) tt_int_op(1,OP_EQ, i); tt_assert(DBL_TO_U64(d) == 0); d = tor_parse_double(" ", 0, (double)UINT64_MAX,&i,NULL); + tt_double_op(fabs(d), OP_LT, 1e-10); tt_int_op(0,OP_EQ, i); d = tor_parse_double(".0a", 0, (double)UINT64_MAX,&i,NULL); + tt_double_op(fabs(d), OP_LT, 1e-10); tt_int_op(0,OP_EQ, i); d = tor_parse_double(".0a", 0, (double)UINT64_MAX,&i,&cp); + tt_double_op(fabs(d), OP_LT, 1e-10); tt_int_op(1,OP_EQ, i); d = tor_parse_double("-.0", 0, (double)UINT64_MAX,&i,NULL); tt_int_op(1,OP_EQ, i); @@ -2251,15 +2273,15 @@ test_util_compress_impl(compress_method_t method) tt_assert(tor_compress_supports_method(method)); if (method != NO_METHOD) { - tt_assert(tor_compress_version_str(method) != NULL); - tt_assert(tor_compress_header_version_str(method) != NULL); + tt_ptr_op(tor_compress_version_str(method), OP_NE, NULL); + tt_ptr_op(tor_compress_header_version_str(method), OP_NE, NULL); } buf1 = tor_strdup("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAZ"); tt_assert(detect_compression_method(buf1, strlen(buf1)) == UNKNOWN_METHOD); tt_assert(!tor_compress(&buf2, &len1, buf1, strlen(buf1)+1, method)); - tt_assert(buf2 != NULL); + tt_ptr_op(buf2, OP_NE, NULL); if (method == NO_METHOD) { // The identity transform doesn't actually compress, and it isn't // detectable as "the identity transform." @@ -2271,7 +2293,7 @@ test_util_compress_impl(compress_method_t method) } tt_assert(!tor_uncompress(&buf3, &len2, buf2, len1, method, 1, LOG_INFO)); - tt_assert(buf3 != NULL); + tt_ptr_op(buf3, OP_NE, NULL); tt_int_op(strlen(buf1) + 1, OP_EQ, len2); tt_str_op(buf1, OP_EQ, buf3); tt_int_op(buf3[len2], OP_EQ, 0); @@ -2318,7 +2340,7 @@ test_util_compress_impl(compress_method_t method) if (method != NO_METHOD) { tt_assert(tor_uncompress(&buf3, &len2, buf2, len1-16, method, 1, LOG_INFO)); - tt_assert(buf3 == NULL); + tt_ptr_op(buf3, OP_EQ, NULL); } done: @@ -2459,6 +2481,126 @@ test_util_decompress_concatenated(void *arg) } static void +test_util_decompress_junk_impl(compress_method_t method) +{ + char input[4096]; + char *result = NULL, *result2 = NULL; + size_t szr, szr2, sz; + int r; + + /* This shouldn't be a compressed string according to any method. */ + strlcpy(input, "This shouldn't be a compressed string by any means.", + sizeof(input)); + sz = strlen(input); + setup_capture_of_logs(LOG_WARN); + r = tor_uncompress(&result, &szr, input, sz, method, 0, LOG_WARN); + tt_int_op(r, OP_EQ, -1); + tt_ptr_op(result, OP_EQ, NULL); + expect_log_msg_containing("Error while uncompressing data: bad input?"); + mock_clean_saved_logs(); + + /* Now try again, with a compressed object that starts out good and turns to + junk. */ + crypto_rand(input, sizeof(input)); + r = tor_compress(&result, &szr, input, sizeof(input), method); + tt_int_op(r, OP_EQ, 0); + crypto_rand(result+szr/2, szr-(szr/2)); // trash the 2nd half of the result + r = tor_uncompress(&result2, &szr2, result, szr, method, 0, LOG_WARN); + tt_int_op(r, OP_EQ, -1); + expect_log_msg_containing("Error while uncompressing data: bad input?"); + + done: + teardown_capture_of_logs(); + tor_free(result); + tor_free(result2); +} + +static void +test_util_decompress_junk(void *arg) +{ + const char *methodname = arg; + tt_assert(methodname); + + compress_method_t method = compression_method_get_by_name(methodname); + tt_int_op(method, OP_NE, UNKNOWN_METHOD); + if (! tor_compress_supports_method(method)) { + tt_skip(); + } + + test_util_decompress_junk_impl(method); + done: + ; +} + +/* mock replacement for tor_compress_is_compression_bomb that doesn't + * believe in compression bombs. */ +static int +mock_is_never_compression_bomb(size_t in, size_t out) +{ + (void)in; + (void) out; + return 0; +} + +static void +test_util_decompress_dos_impl(compress_method_t method) +{ + char *input; + char *result = NULL, *result2 = NULL; + size_t szr, szr2; + int r; + + const size_t big = 1024*1024; + /* one megabyte of 0s. */ + input = tor_malloc_zero(big); + + /* Compress it into "result": it should fail. */ + setup_full_capture_of_logs(LOG_WARN); + r = tor_compress(&result, &szr, input, big, method); + tt_int_op(r, OP_EQ, -1); + expect_log_msg_containing( + "other Tors would think this was a compression bomb"); + teardown_capture_of_logs(); + + /* Try again, but this time suppress compression-bomb detection */ + MOCK(tor_compress_is_compression_bomb, mock_is_never_compression_bomb); + r = tor_compress(&result, &szr, input, big, method); + UNMOCK(tor_compress_is_compression_bomb); + tt_int_op(r, OP_EQ, 0); + tt_ptr_op(result, OP_NE, NULL); + + /* We should refuse to uncomrpess it again, since it looks like a + * compression bomb. */ + setup_capture_of_logs(LOG_WARN); + r = tor_uncompress(&result2, &szr2, result, szr, method, 0, LOG_WARN); + tt_int_op(r, OP_EQ, -1); + expect_log_msg_containing("bomb; abandoning stream"); + + done: + teardown_capture_of_logs(); + tor_free(input); + tor_free(result); + tor_free(result2); +} + +static void +test_util_decompress_dos(void *arg) +{ + const char *methodname = arg; + tt_assert(methodname); + + compress_method_t method = compression_method_get_by_name(methodname); + tt_int_op(method, OP_NE, UNKNOWN_METHOD); + if (! tor_compress_supports_method(method)) { + tt_skip(); + } + + test_util_decompress_dos_impl(method); + done: + ; +} + +static void test_util_gzip_compression_bomb(void *arg) { /* A 'compression bomb' is a very small object that uncompresses to a huge @@ -2525,7 +2667,7 @@ test_util_mmap(void *arg) crypto_rand(buf, buflen); mapping = tor_mmap_file(fname1); - tt_assert(! mapping); + tt_ptr_op(mapping, OP_EQ, NULL); write_str_to_file(fname1, "Short file.", 1); @@ -2543,7 +2685,7 @@ test_util_mmap(void *arg) tt_str_op(mapping->data,OP_EQ, "Short file."); tt_int_op(0, OP_EQ, tor_munmap_file(mapping)); mapping = NULL; -#endif +#endif /* defined(_WIN32) */ /* Now a zero-length file. */ write_str_to_file(fname1, "", 1); @@ -2554,7 +2696,7 @@ test_util_mmap(void *arg) /* Make sure that we fail to map a no-longer-existent file. */ mapping = tor_mmap_file(fname1); - tt_assert(! mapping); + tt_ptr_op(mapping, OP_EQ, NULL); /* Now try a big file that stretches across a few pages and isn't aligned */ write_bytes_to_file(fname2, buf, buflen, 1); @@ -2872,7 +3014,7 @@ test_util_sscanf(void *arg) r = tor_sscanf("9223372036854775808. -9223372036854775809.", "%d. %d.", &int1, &int2); tt_int_op(r,OP_EQ, 0); -#endif +#endif /* SIZEOF_INT == 4 || ... */ #if SIZEOF_LONG == 4 /* %lu */ @@ -2967,7 +3109,7 @@ test_util_sscanf(void *arg) r = tor_sscanf("9223372036854775808. -9223372036854775809.", "%ld. %ld.", &lng1, &lng2); tt_int_op(r,OP_EQ, 0); -#endif +#endif /* SIZEOF_LONG == 4 || ... */ r = tor_sscanf("123.456 .000007 -900123123.2000787 00003.2", "%lf %lf %lf %lf", &d1,&d2,&d3,&d4); @@ -2994,7 +3136,7 @@ strnlen(const char *s, size_t len) return len; return p - s; } -#endif +#endif /* !defined(HAVE_STRNLEN) */ static void test_util_format_time_interval(void *arg) @@ -3361,7 +3503,7 @@ test_util_format_time_interval(void *arg) tt_ci_char_op(label_m[0],OP_EQ, 'm'); /* and 7 or 8 seconds - ignored */ -#endif +#endif /* SIZEOF_LONG == 4 || SIZEOF_LONG == 8 */ #if SIZEOF_LONG == 8 @@ -3399,7 +3541,7 @@ test_util_format_time_interval(void *arg) tt_ci_char_op(label_m[0],OP_EQ, 'm'); /* and 7 or 8 seconds - ignored */ -#endif +#endif /* SIZEOF_LONG == 8 */ done: ; @@ -3442,7 +3584,7 @@ test_util_path_is_relative(void *arg) tt_int_op(0,OP_EQ, path_is_relative("\\dir")); tt_int_op(0,OP_EQ, path_is_relative("a:\\dir")); tt_int_op(0,OP_EQ, path_is_relative("z:\\dir")); -#endif +#endif /* defined(_WIN32) */ done: ; @@ -3462,7 +3604,7 @@ test_util_memarea(void *arg) malloc(), which is free to lay out memory most any way it wants. */ if (1) tt_skip(); -#endif +#endif /* defined(DISABLE_MEMORY_SENTINELS) */ (void)arg; tt_assert(area); @@ -3633,8 +3775,8 @@ test_util_strtok(void *arg) } tor_snprintf(buf, sizeof(buf), "%s", pad1); tor_snprintf(buf2, sizeof(buf2), "%s", pad2); - tt_assert(NULL == tor_strtok_r_impl(buf, " ", &cp1)); - tt_assert(NULL == tor_strtok_r_impl(buf2, ".!..;!", &cp2)); + tt_ptr_op(tor_strtok_r_impl(buf, " ", &cp1), OP_EQ, NULL); + tt_ptr_op(tor_strtok_r_impl(buf2, ".!..;!", &cp2), OP_EQ, NULL); tor_snprintf(buf, sizeof(buf), "%sGraved on the dark in gestures of descent%s", pad1, pad1); @@ -3984,7 +4126,7 @@ test_util_load_win_lib(void *ptr) if (h) FreeLibrary(h); } -#endif +#endif /* defined(_WIN32) */ #ifndef _WIN32 static void @@ -4036,7 +4178,7 @@ test_util_exit_status(void *ptr) tt_int_op(n,OP_EQ, strlen(hex_errno)); tt_int_op(n,OP_EQ, HEX_ERRNO_SIZE); -#endif +#endif /* SIZEOF_INT == 4 || ... */ clear_hex_errno(hex_errno); n = format_helper_exit_status(0x7F, 0, hex_errno); @@ -4054,7 +4196,7 @@ test_util_exit_status(void *ptr) done: ; } -#endif +#endif /* !defined(_WIN32) */ #ifndef _WIN32 static void @@ -4182,7 +4324,7 @@ test_util_string_from_pipe(void *ptr) close(test_pipe[1]); } -#endif // _WIN32 +#endif /* !defined(_WIN32) */ /** * Test for format_hex_number_sigsafe() @@ -4378,7 +4520,7 @@ test_util_split_lines(void *ptr) /* Check we have not got too many lines */ tt_int_op(MAX_SPLIT_LINE_COUNT, OP_GT, j); /* Check that there actually should be a line here */ - tt_assert(tests[i].split_line[j] != NULL); + tt_ptr_op(tests[i].split_line[j], OP_NE, NULL); log_info(LD_GENERAL, "Line %d of test %d, should be <%s>", j, i, tests[i].split_line[j]); /* Check that the line is as expected */ @@ -4494,11 +4636,11 @@ test_util_di_map(void *arg) char dflt_entry[] = "'You have made a good beginning', but no more"; - tt_int_op(32, ==, sizeof(key1)); - tt_int_op(32, ==, sizeof(key2)); - tt_int_op(32, ==, sizeof(key3)); + tt_int_op(32, OP_EQ, sizeof(key1)); + tt_int_op(32, OP_EQ, sizeof(key2)); + tt_int_op(32, OP_EQ, sizeof(key3)); - tt_ptr_op(dflt_entry, ==, dimap_search(dimap, key1, dflt_entry)); + tt_ptr_op(dflt_entry, OP_EQ, dimap_search(dimap, key1, dflt_entry)); char *str1 = tor_strdup("You are precisely as big as what you love" " and precisely as small as what you allow" @@ -4516,10 +4658,10 @@ test_util_di_map(void *arg) dimap_add_entry(&dimap, key2, str2); dimap_add_entry(&dimap, key3, str3); - tt_ptr_op(str1, ==, dimap_search(dimap, key1, dflt_entry)); - tt_ptr_op(str3, ==, dimap_search(dimap, key3, dflt_entry)); - tt_ptr_op(str2, ==, dimap_search(dimap, key2, dflt_entry)); - tt_ptr_op(dflt_entry, ==, dimap_search(dimap, key4, dflt_entry)); + tt_ptr_op(str1, OP_EQ, dimap_search(dimap, key1, dflt_entry)); + tt_ptr_op(str3, OP_EQ, dimap_search(dimap, key3, dflt_entry)); + tt_ptr_op(str2, OP_EQ, dimap_search(dimap, key2, dflt_entry)); + tt_ptr_op(dflt_entry, OP_EQ, dimap_search(dimap, key4, dflt_entry)); done: dimap_free(dimap, tor_free_); @@ -4986,34 +5128,34 @@ test_util_round_to_next_multiple_of(void *arg) { (void)arg; - tt_u64_op(round_uint64_to_next_multiple_of(0,1), ==, 0); - tt_u64_op(round_uint64_to_next_multiple_of(0,7), ==, 0); + tt_u64_op(round_uint64_to_next_multiple_of(0,1), OP_EQ, 0); + tt_u64_op(round_uint64_to_next_multiple_of(0,7), OP_EQ, 0); - tt_u64_op(round_uint64_to_next_multiple_of(99,1), ==, 99); - tt_u64_op(round_uint64_to_next_multiple_of(99,7), ==, 105); - tt_u64_op(round_uint64_to_next_multiple_of(99,9), ==, 99); + tt_u64_op(round_uint64_to_next_multiple_of(99,1), OP_EQ, 99); + tt_u64_op(round_uint64_to_next_multiple_of(99,7), OP_EQ, 105); + tt_u64_op(round_uint64_to_next_multiple_of(99,9), OP_EQ, 99); - tt_u64_op(round_uint64_to_next_multiple_of(UINT64_MAX,2), ==, + tt_u64_op(round_uint64_to_next_multiple_of(UINT64_MAX,2), OP_EQ, UINT64_MAX); - tt_int_op(round_uint32_to_next_multiple_of(0,1), ==, 0); - tt_int_op(round_uint32_to_next_multiple_of(0,7), ==, 0); + tt_int_op(round_uint32_to_next_multiple_of(0,1), OP_EQ, 0); + tt_int_op(round_uint32_to_next_multiple_of(0,7), OP_EQ, 0); - tt_int_op(round_uint32_to_next_multiple_of(99,1), ==, 99); - tt_int_op(round_uint32_to_next_multiple_of(99,7), ==, 105); - tt_int_op(round_uint32_to_next_multiple_of(99,9), ==, 99); + tt_int_op(round_uint32_to_next_multiple_of(99,1), OP_EQ, 99); + tt_int_op(round_uint32_to_next_multiple_of(99,7), OP_EQ, 105); + tt_int_op(round_uint32_to_next_multiple_of(99,9), OP_EQ, 99); - tt_int_op(round_uint32_to_next_multiple_of(UINT32_MAX,2), ==, + tt_int_op(round_uint32_to_next_multiple_of(UINT32_MAX,2), OP_EQ, UINT32_MAX); - tt_uint_op(round_to_next_multiple_of(0,1), ==, 0); - tt_uint_op(round_to_next_multiple_of(0,7), ==, 0); + tt_uint_op(round_to_next_multiple_of(0,1), OP_EQ, 0); + tt_uint_op(round_to_next_multiple_of(0,7), OP_EQ, 0); - tt_uint_op(round_to_next_multiple_of(99,1), ==, 99); - tt_uint_op(round_to_next_multiple_of(99,7), ==, 105); - tt_uint_op(round_to_next_multiple_of(99,9), ==, 99); + tt_uint_op(round_to_next_multiple_of(99,1), OP_EQ, 99); + tt_uint_op(round_to_next_multiple_of(99,7), OP_EQ, 105); + tt_uint_op(round_to_next_multiple_of(99,9), OP_EQ, 99); - tt_uint_op(round_to_next_multiple_of(UINT_MAX,2), ==, + tt_uint_op(round_to_next_multiple_of(UINT_MAX,2), OP_EQ, UINT_MAX); done: ; @@ -5034,26 +5176,26 @@ test_util_laplace(void *arg) const double delta_f = 15.0, epsilon = 0.3; /* b = 15.0 / 0.3 = 50.0 */ (void)arg; - tt_i64_op(INT64_MIN, ==, sample_laplace_distribution(mu, b, 0.0)); - tt_i64_op(-69, ==, sample_laplace_distribution(mu, b, 0.01)); - tt_i64_op(24, ==, sample_laplace_distribution(mu, b, 0.5)); - tt_i64_op(24, ==, sample_laplace_distribution(mu, b, 0.51)); - tt_i64_op(117, ==, sample_laplace_distribution(mu, b, 0.99)); + tt_i64_op(INT64_MIN, OP_EQ, sample_laplace_distribution(mu, b, 0.0)); + tt_i64_op(-69, OP_EQ, sample_laplace_distribution(mu, b, 0.01)); + tt_i64_op(24, OP_EQ, sample_laplace_distribution(mu, b, 0.5)); + tt_i64_op(24, OP_EQ, sample_laplace_distribution(mu, b, 0.51)); + tt_i64_op(117, OP_EQ, sample_laplace_distribution(mu, b, 0.99)); /* >>> laplace.ppf([0.0, 0.1, 0.25, 0.5, 0.75, 0.9, 0.99], * ... loc = 0, scale = 50) * array([ -inf, -80.47189562, -34.65735903, 0. , * 34.65735903, 80.47189562, 195.60115027]) */ - tt_i64_op(INT64_MIN + 20, ==, + tt_i64_op(INT64_MIN + 20, OP_EQ, add_laplace_noise(20, 0.0, delta_f, epsilon)); - tt_i64_op(-60, ==, add_laplace_noise(20, 0.1, delta_f, epsilon)); - tt_i64_op(-14, ==, add_laplace_noise(20, 0.25, delta_f, epsilon)); - tt_i64_op(20, ==, add_laplace_noise(20, 0.5, delta_f, epsilon)); - tt_i64_op(54, ==, add_laplace_noise(20, 0.75, delta_f, epsilon)); - tt_i64_op(100, ==, add_laplace_noise(20, 0.9, delta_f, epsilon)); - tt_i64_op(215, ==, add_laplace_noise(20, 0.99, delta_f, epsilon)); + tt_i64_op(-60, OP_EQ, add_laplace_noise(20, 0.1, delta_f, epsilon)); + tt_i64_op(-14, OP_EQ, add_laplace_noise(20, 0.25, delta_f, epsilon)); + tt_i64_op(20, OP_EQ, add_laplace_noise(20, 0.5, delta_f, epsilon)); + tt_i64_op(54, OP_EQ, add_laplace_noise(20, 0.75, delta_f, epsilon)); + tt_i64_op(100, OP_EQ, add_laplace_noise(20, 0.9, delta_f, epsilon)); + tt_i64_op(215, OP_EQ, add_laplace_noise(20, 0.99, delta_f, epsilon)); /* Test extreme values of signal with maximally negative values of noise * 1.0000000000000002 is the smallest number > 1 @@ -5066,54 +5208,54 @@ test_util_laplace(void *arg) */ const double noscale_df = 1.0, noscale_eps = 1.0; - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(0, 0.0, noscale_df, noscale_eps)); /* is it clipped to INT64_MIN? */ - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(-1, 0.0, noscale_df, noscale_eps)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(INT64_MIN, 0.0, noscale_df, noscale_eps)); /* ... even when scaled? */ - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(0, 0.0, delta_f, epsilon)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(0, 0.0, DBL_MAX, 1)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(INT64_MIN, 0.0, DBL_MAX, 1)); /* does it play nice with INT64_MAX? */ - tt_i64_op((INT64_MIN + INT64_MAX), ==, + tt_i64_op((INT64_MIN + INT64_MAX), OP_EQ, add_laplace_noise(INT64_MAX, 0.0, noscale_df, noscale_eps)); /* do near-zero fractional values work? */ const double min_dbl_error = 0.0000000000000002; - tt_i64_op(-35, ==, + tt_i64_op(-35, OP_EQ, add_laplace_noise(0, min_dbl_error, noscale_df, noscale_eps)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(INT64_MIN, min_dbl_error, noscale_df, noscale_eps)); - tt_i64_op((-35 + INT64_MAX), ==, + tt_i64_op((-35 + INT64_MAX), OP_EQ, add_laplace_noise(INT64_MAX, min_dbl_error, noscale_df, noscale_eps)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(0, min_dbl_error, DBL_MAX, 1)); - tt_i64_op((INT64_MAX + INT64_MIN), ==, + tt_i64_op((INT64_MAX + INT64_MIN), OP_EQ, add_laplace_noise(INT64_MAX, min_dbl_error, DBL_MAX, 1)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, add_laplace_noise(INT64_MIN, min_dbl_error, DBL_MAX, 1)); /* does it play nice with INT64_MAX? */ - tt_i64_op((INT64_MAX - 35), ==, + tt_i64_op((INT64_MAX - 35), OP_EQ, add_laplace_noise(INT64_MAX, min_dbl_error, noscale_df, noscale_eps)); @@ -5128,31 +5270,31 @@ test_util_laplace(void *arg) const double max_dbl_lt_one = 0.9999999999999998; /* do near-one fractional values work? */ - tt_i64_op(35, ==, + tt_i64_op(35, OP_EQ, add_laplace_noise(0, max_dbl_lt_one, noscale_df, noscale_eps)); /* is it clipped to INT64_MAX? */ - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, add_laplace_noise(INT64_MAX - 35, max_dbl_lt_one, noscale_df, noscale_eps)); - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, add_laplace_noise(INT64_MAX - 34, max_dbl_lt_one, noscale_df, noscale_eps)); - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, add_laplace_noise(INT64_MAX, max_dbl_lt_one, noscale_df, noscale_eps)); /* ... even when scaled? */ - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, add_laplace_noise(INT64_MAX, max_dbl_lt_one, delta_f, epsilon)); - tt_i64_op((INT64_MIN + INT64_MAX), ==, + tt_i64_op((INT64_MIN + INT64_MAX), OP_EQ, add_laplace_noise(INT64_MIN, max_dbl_lt_one, DBL_MAX, 1)); - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, add_laplace_noise(INT64_MAX, max_dbl_lt_one, DBL_MAX, 1)); /* does it play nice with INT64_MIN? */ - tt_i64_op((INT64_MIN + 35), ==, + tt_i64_op((INT64_MIN + 35), OP_EQ, add_laplace_noise(INT64_MIN, max_dbl_lt_one, noscale_df, noscale_eps)); @@ -5165,32 +5307,32 @@ test_util_clamp_double_to_int64(void *arg) { (void)arg; - tt_i64_op(INT64_MIN, ==, clamp_double_to_int64(-INFINITY_DBL)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, clamp_double_to_int64(-INFINITY_DBL)); + tt_i64_op(INT64_MIN, OP_EQ, clamp_double_to_int64(-1.0 * pow(2.0, 64.0) - 1.0)); - tt_i64_op(INT64_MIN, ==, + tt_i64_op(INT64_MIN, OP_EQ, clamp_double_to_int64(-1.0 * pow(2.0, 63.0) - 1.0)); - tt_i64_op(((uint64_t) -1) << 53, ==, + tt_i64_op(((uint64_t) -1) << 53, OP_EQ, clamp_double_to_int64(-1.0 * pow(2.0, 53.0))); - tt_i64_op((((uint64_t) -1) << 53) + 1, ==, + tt_i64_op((((uint64_t) -1) << 53) + 1, OP_EQ, clamp_double_to_int64(-1.0 * pow(2.0, 53.0) + 1.0)); - tt_i64_op(-1, ==, clamp_double_to_int64(-1.0)); - tt_i64_op(0, ==, clamp_double_to_int64(-0.9)); - tt_i64_op(0, ==, clamp_double_to_int64(-0.1)); - tt_i64_op(0, ==, clamp_double_to_int64(0.0)); - tt_i64_op(0, ==, clamp_double_to_int64(NAN_DBL)); - tt_i64_op(0, ==, clamp_double_to_int64(0.1)); - tt_i64_op(0, ==, clamp_double_to_int64(0.9)); - tt_i64_op(1, ==, clamp_double_to_int64(1.0)); - tt_i64_op((((int64_t) 1) << 53) - 1, ==, + tt_i64_op(-1, OP_EQ, clamp_double_to_int64(-1.0)); + tt_i64_op(0, OP_EQ, clamp_double_to_int64(-0.9)); + tt_i64_op(0, OP_EQ, clamp_double_to_int64(-0.1)); + tt_i64_op(0, OP_EQ, clamp_double_to_int64(0.0)); + tt_i64_op(0, OP_EQ, clamp_double_to_int64(NAN_DBL)); + tt_i64_op(0, OP_EQ, clamp_double_to_int64(0.1)); + tt_i64_op(0, OP_EQ, clamp_double_to_int64(0.9)); + tt_i64_op(1, OP_EQ, clamp_double_to_int64(1.0)); + tt_i64_op((((int64_t) 1) << 53) - 1, OP_EQ, clamp_double_to_int64(pow(2.0, 53.0) - 1.0)); - tt_i64_op(((int64_t) 1) << 53, ==, + tt_i64_op(((int64_t) 1) << 53, OP_EQ, clamp_double_to_int64(pow(2.0, 53.0))); - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, clamp_double_to_int64(pow(2.0, 63.0))); - tt_i64_op(INT64_MAX, ==, + tt_i64_op(INT64_MAX, OP_EQ, clamp_double_to_int64(pow(2.0, 64.0))); - tt_i64_op(INT64_MAX, ==, clamp_double_to_int64(INFINITY_DBL)); + tt_i64_op(INT64_MAX, OP_EQ, clamp_double_to_int64(INFINITY_DBL)); done: ; @@ -5204,7 +5346,7 @@ fd_is_cloexec(tor_socket_t fd) int flags = fcntl(fd, F_GETFD, 0); return (flags & FD_CLOEXEC) == FD_CLOEXEC; } -#endif +#endif /* defined(FD_CLOEXEC) */ #ifndef _WIN32 #define CAN_CHECK_NONBLOCK @@ -5214,7 +5356,7 @@ fd_is_nonblocking(tor_socket_t fd) int flags = fcntl(fd, F_GETFL, 0); return (flags & O_NONBLOCK) == O_NONBLOCK; } -#endif +#endif /* !defined(_WIN32) */ #define ERRNO_IS_EPROTO(e) (e == SOCK_ERRNO(EPROTONOSUPPORT)) #define SOCK_ERR_IS_EPROTO(s) ERRNO_IS_EPROTO(tor_socket_errno(s)) @@ -5258,13 +5400,13 @@ test_util_socket(void *arg) tt_int_op(fd_is_cloexec(fd2), OP_EQ, 0); tt_int_op(fd_is_cloexec(fd3), OP_EQ, 1); tt_int_op(fd_is_cloexec(fd4), OP_EQ, 1); -#endif +#endif /* defined(CAN_CHECK_CLOEXEC) */ #ifdef CAN_CHECK_NONBLOCK tt_int_op(fd_is_nonblocking(fd1), OP_EQ, 0); tt_int_op(fd_is_nonblocking(fd2), OP_EQ, 1); tt_int_op(fd_is_nonblocking(fd3), OP_EQ, 0); tt_int_op(fd_is_nonblocking(fd4), OP_EQ, 1); -#endif +#endif /* defined(CAN_CHECK_NONBLOCK) */ tor_assert(tor_close_socket == tor_close_socket__real); @@ -5320,7 +5462,7 @@ is_there_a_localhost(int family) return result; } -#endif +#endif /* 0 */ /* Test for socketpair and ersatz_socketpair(). We test them both, since * the latter is a tolerably good way to exersize tor_accept_socket(). */ @@ -5347,7 +5489,7 @@ test_util_socketpair(void *arg) * Assume we're on a machine without 127.0.0.1 or ::1 and give up now. */ tt_skip(); } -#endif +#endif /* defined(__FreeBSD__) */ tt_int_op(0, OP_EQ, socketpair_result); tt_assert(SOCKET_OK(fds[0])); @@ -5508,7 +5650,7 @@ test_util_get_avail_disk_space(void *arg) #else tt_i64_op(val, OP_GT, 0); /* You have some space. */ tt_i64_op(val, OP_LT, ((int64_t)1)<<56); /* You don't have a zebibyte */ -#endif +#endif /* !defined(HAVE_STATVFS) && !defined(_WIN32) */ done: ; @@ -5558,25 +5700,25 @@ test_util_pwdb(void *arg) /* Uncached case. */ /* Let's assume that we exist. */ me = tor_getpwuid(getuid()); - tt_assert(me != NULL); + tt_ptr_op(me, OP_NE, NULL); name = tor_strdup(me->pw_name); /* Uncached case */ me2 = tor_getpwnam(name); - tt_assert(me2 != NULL); + tt_ptr_op(me2, OP_NE, NULL); tt_int_op(me2->pw_uid, OP_EQ, getuid()); /* Cached case */ me3 = tor_getpwuid(getuid()); - tt_assert(me3 != NULL); + tt_ptr_op(me3, OP_NE, NULL); tt_str_op(me3->pw_name, OP_EQ, name); me3 = tor_getpwnam(name); - tt_assert(me3 != NULL); + tt_ptr_op(me3, OP_NE, NULL); tt_int_op(me3->pw_uid, OP_EQ, getuid()); dir = get_user_homedir(name); - tt_assert(dir != NULL); + tt_ptr_op(dir, OP_NE, NULL); /* Try failing cases. First find a user that doesn't exist by name */ char randbytes[4]; @@ -5596,7 +5738,7 @@ test_util_pwdb(void *arg) /* We should do a LOG_ERR */ setup_full_capture_of_logs(LOG_ERR); dir = get_user_homedir(badname); - tt_assert(dir == NULL); + tt_ptr_op(dir, OP_EQ, NULL); expect_log_msg_containing("not found"); tt_int_op(smartlist_len(mock_saved_logs()), OP_EQ, 1); teardown_capture_of_logs(); @@ -5618,7 +5760,7 @@ test_util_pwdb(void *arg) tor_free(dir); teardown_capture_of_logs(); } -#endif +#endif /* !defined(_WIN32) */ static void test_util_calloc_check(void *arg) @@ -5796,7 +5938,7 @@ test_util_htonll(void *arg) #else tt_u64_op(res_le, OP_EQ, tor_htonll(0x8877665544332211)); tt_u64_op(res_le, OP_EQ, tor_ntohll(0x8877665544332211)); -#endif +#endif /* defined(WORDS_BIGENDIAN) */ done: ; @@ -5895,6 +6037,16 @@ test_util_get_unquoted_path(void *arg) &passthrough_setup, \ (char*)(identifier) } +#define COMPRESS_JUNK(name, identifier) \ + { "compress_junk/" #name, test_util_decompress_junk, 0, \ + &passthrough_setup, \ + (char*)(identifier) } + +#define COMPRESS_DOS(name, identifier) \ + { "compress_dos/" #name, test_util_decompress_dos, 0, \ + &passthrough_setup, \ + (char*)(identifier) } + #ifdef _WIN32 #define UTIL_TEST_NO_WIN(n, f) { #n, NULL, TT_SKIP, NULL, NULL } #define UTIL_TEST_WIN_ONLY(n, f) UTIL_TEST(n, (f)) @@ -5903,7 +6055,7 @@ test_util_get_unquoted_path(void *arg) #define UTIL_TEST_NO_WIN(n, f) UTIL_TEST(n, (f)) #define UTIL_TEST_WIN_ONLY(n, f) { #n, NULL, TT_SKIP, NULL, NULL } #define UTIL_LEGACY_NO_WIN(n) UTIL_LEGACY(n) -#endif +#endif /* defined(_WIN32) */ struct testcase_t util_tests[] = { UTIL_LEGACY(time), @@ -5929,6 +6081,13 @@ struct testcase_t util_tests[] = { COMPRESS_CONCAT(lzma, "x-tor-lzma"), COMPRESS_CONCAT(zstd, "x-zstd"), COMPRESS_CONCAT(none, "identity"), + COMPRESS_JUNK(zlib, "deflate"), + COMPRESS_JUNK(gzip, "gzip"), + COMPRESS_JUNK(lzma, "x-tor-lzma"), + COMPRESS_DOS(zlib, "deflate"), + COMPRESS_DOS(gzip, "gzip"), + COMPRESS_DOS(lzma, "x-tor-lzma"), + COMPRESS_DOS(zstd, "x-zstd"), UTIL_TEST(gzip_compression_bomb, TT_FORK), UTIL_LEGACY(datadir), UTIL_LEGACY(memarea), diff --git a/src/test/test_util_format.c b/src/test/test_util_format.c index ea0a86499f..683d5fdac1 100644 --- a/src/test/test_util_format.c +++ b/src/test/test_util_format.c @@ -345,7 +345,7 @@ test_util_format_base32_decode(void *arg) const char *src = "mjwgc2dcnrswqmjs"; ret = base32_decode(dst, strlen(expected), src, strlen(src)); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); tt_str_op(expected, OP_EQ, dst); } @@ -356,7 +356,7 @@ test_util_format_base32_decode(void *arg) const char *src = "mjwgc2dcnrswq"; ret = base32_decode(dst, strlen(expected), src, strlen(src)); - tt_int_op(ret, ==, 0); + tt_int_op(ret, OP_EQ, 0); tt_mem_op(expected, OP_EQ, dst, strlen(expected)); } @@ -364,9 +364,9 @@ test_util_format_base32_decode(void *arg) { /* Invalid character '#'. */ ret = base32_decode(dst, real_dstlen, "#abcde", 6); - tt_int_op(ret, ==, -1); + tt_int_op(ret, OP_EQ, -1); /* Make sure the destination buffer has been zeroed even on error. */ - tt_int_op(tor_mem_is_zero(dst, real_dstlen), ==, 1); + tt_int_op(tor_mem_is_zero(dst, real_dstlen), OP_EQ, 1); } done: diff --git a/src/test/test_util_process.c b/src/test/test_util_process.c index 70292f2287..68ce6cfd40 100644 --- a/src/test/test_util_process.c +++ b/src/test/test_util_process.c @@ -67,7 +67,7 @@ test_util_process_clear_waitpid_callback(void *ignored) done: teardown_capture_of_logs(); } -#endif /* _WIN32 */ +#endif /* !defined(_WIN32) */ #ifndef _WIN32 #define TEST(name) { #name, test_util_process_##name, 0, NULL, NULL } diff --git a/src/test/test_util_slow.c b/src/test/test_util_slow.c index 3e5d78948d..2cd68cf118 100644 --- a/src/test/test_util_slow.c +++ b/src/test/test_util_slow.c @@ -22,14 +22,14 @@ #else #define TEST_CHILD (BUILDDIR "/src/test/test-child") #define EOL "\n" -#endif +#endif /* defined(_WIN32) */ #ifdef _WIN32 /* I've assumed Windows doesn't have the gap between fork and exec * that causes the race condition on unix-like platforms */ #define MATCH_PROCESS_STATUS(s1,s2) ((s1) == (s2)) -#else +#else /* !(defined(_WIN32)) */ /* work around a race condition of the timing of SIGCHLD handler updates * to the process_handle's fields, and checks of those fields * @@ -46,7 +46,7 @@ ||((s2) == PROCESS_STATUS_RUNNING_OR_NOTRUNNING \ && IS_RUNNING_OR_NOTRUNNING(s1))) -#endif // _WIN32 +#endif /* defined(_WIN32) */ /** Helper function for testing tor_spawn_background */ static void @@ -78,7 +78,7 @@ run_util_spawn_background(const char *argv[], const char *expected_out, return; } - tt_assert(process_handle != NULL); + tt_ptr_op(process_handle, OP_NE, NULL); /* When a spawned process forks, fails, then exits very quickly, * (this typically occurs when exec fails) @@ -102,7 +102,7 @@ run_util_spawn_background(const char *argv[], const char *expected_out, * that is, PROCESS_STATUS_RUNNING_OR_NOTRUNNING */ tt_assert(process_handle->waitpid_cb != NULL || expected_status == PROCESS_STATUS_RUNNING_OR_NOTRUNNING); -#endif +#endif /* !defined(_WIN32) */ #ifdef _WIN32 tt_assert(process_handle->stdout_pipe != INVALID_HANDLE_VALUE); @@ -112,7 +112,7 @@ run_util_spawn_background(const char *argv[], const char *expected_out, tt_assert(process_handle->stdout_pipe >= 0); tt_assert(process_handle->stderr_pipe >= 0); tt_assert(process_handle->stdin_pipe >= 0); -#endif +#endif /* defined(_WIN32) */ /* Check stdout */ pos = tor_read_all_from_process_stdout(process_handle, stdout_buf, @@ -178,7 +178,7 @@ test_util_spawn_background_fail(void *ptr) /* TODO: Once we can signal failure to exec, set this to be * PROCESS_STATUS_RUNNING_OR_ERROR */ const int expected_status = PROCESS_STATUS_RUNNING_OR_NOTRUNNING; -#endif +#endif /* defined(_WIN32) */ memset(expected_out, 0xf0, sizeof(expected_out)); memset(code, 0xf0, sizeof(code)); @@ -244,7 +244,7 @@ test_util_spawn_background_partial_read_impl(int exit_early) tt_assert(!eof); pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf, sizeof(stdout_buf) - 1, NULL, &eof); -#endif +#endif /* defined(_WIN32) */ log_info(LD_GENERAL, "tor_read_all_handle() returned %d", (int)pos); /* We would have blocked, keep on trying */ @@ -270,7 +270,7 @@ test_util_spawn_background_partial_read_impl(int exit_early) sizeof(stdout_buf) - 1, process_handle); tt_int_op(0,OP_EQ, pos); -#else +#else /* !(defined(_WIN32)) */ if (!eof) { /* We should have got all the data, but maybe not the EOF flag */ pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf, @@ -280,7 +280,7 @@ test_util_spawn_background_partial_read_impl(int exit_early) tt_assert(eof); } /* Otherwise, we got the EOF on the last read */ -#endif +#endif /* defined(_WIN32) */ /* Check it terminated correctly */ retval = tor_get_exit_code(process_handle, 1, &exit_code); @@ -351,7 +351,7 @@ test_util_spawn_background_waitpid_notify(void *arg) } tt_int_op(ms_timer, OP_GT, 0); tt_ptr_op(process_handle->waitpid_cb, OP_EQ, NULL); -#endif +#endif /* !defined(_WIN32) */ ms_timer = 30*1000; while (((retval = tor_get_exit_code(process_handle, 0, &exit_code)) diff --git a/src/test/test_workqueue.c b/src/test/test_workqueue.c index 6fa46f90d4..2b03173717 100644 --- a/src/test/test_workqueue.c +++ b/src/test/test_workqueue.c @@ -61,9 +61,9 @@ mark_handled(int serial) tor_assert(! bitarray_is_set(handled, serial)); bitarray_set(handled, serial); tor_mutex_release(&bitmap_mutex); -#else +#else /* !(defined(TRACK_RESPONSES)) */ (void)serial; -#endif +#endif /* defined(TRACK_RESPONSES) */ } static workqueue_reply_t @@ -288,7 +288,7 @@ replysock_readable_cb(tor_socket_t sock, short what, void *arg) } puts(""); tor_mutex_release(&bitmap_mutex); -#endif +#endif /* defined(TRACK_RESPONSES) */ if (n_sent - (n_received+n_successful_cancel) < opt_n_lowwater) { int n_to_send = n_received + opt_n_inflight - n_sent; @@ -422,7 +422,7 @@ main(int argc, char **argv) received = bitarray_init_zero(opt_n_items); tor_mutex_init(&bitmap_mutex); handled_len = opt_n_items; -#endif +#endif /* defined(TRACK_RESPONSES) */ for (i = 0; i < opt_n_inflight; ++i) { if (! add_work(tp)) { diff --git a/src/test/testing_common.c b/src/test/testing_common.c index d7e36edbc0..7e9c47b48d 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -33,7 +33,7 @@ const char tor_git_revision[] = ""; #include <direct.h> #else #include <dirent.h> -#endif +#endif /* defined(_WIN32) */ #include "or.h" @@ -84,7 +84,7 @@ setup_directory(void) (int)getpid(), rnd32); r = mkdir(temp_dir); } -#else +#else /* !(defined(_WIN32)) */ tor_snprintf(temp_dir, sizeof(temp_dir), "/tmp/tor_test_%d_%s", (int) getpid(), rnd32); r = mkdir(temp_dir, 0700); @@ -92,7 +92,7 @@ setup_directory(void) /* undo sticky bit so tests don't get confused. */ r = chown(temp_dir, getuid(), getgid()); } -#endif +#endif /* defined(_WIN32) */ if (r) { fprintf(stderr, "Can't create directory %s:", temp_dir); perror(""); @@ -241,7 +241,7 @@ main(int c, const char **v) int r = crypto_use_tor_alloc_functions(); tor_assert(r == 0); } -#endif +#endif /* defined(USE_DMALLOC) */ update_approx_time(time(NULL)); options = options_new(); diff --git a/src/test/testing_rsakeys.c b/src/test/testing_rsakeys.c index 5dff233a69..7a24c0ed14 100644 --- a/src/test/testing_rsakeys.c +++ b/src/test/testing_rsakeys.c @@ -436,7 +436,7 @@ static int next_key_idx_1024; #define N_PREGEN_KEYS_2048 ARRAY_LENGTH(PREGEN_KEYS_2048) static crypto_pk_t *pregen_keys_2048[N_PREGEN_KEYS_2048]; static int next_key_idx_2048; -#endif +#endif /* defined(USE_PREGENERATED_RSA_KEYS) */ /** Generate and return a new keypair for use in unit tests. If we're using * the key cache optimization, we might reuse keys. "idx" is ignored. @@ -466,14 +466,14 @@ pk_generate_internal(int bits) *idxp += crypto_rand_int_range(1,3); *idxp %= n_pregen; return crypto_pk_dup_key(pregen_array[*idxp]); -#else +#else /* !(defined(USE_PREGENERATED_RSA_KEYS)) */ crypto_pk_t *result; int res; result = crypto_pk_new(); res = crypto_pk_generate_key_with_bits__real(result, bits); tor_assert(!res); return result; -#endif +#endif /* defined(USE_PREGENERATED_RSA_KEYS) */ } crypto_pk_t * @@ -496,7 +496,7 @@ crypto_pk_generate_key_with_bits__get_cached(crypto_pk_t *env, int bits) } return 0; } -#endif +#endif /* defined(USE_PREGENERATED_RSA_KEYS) */ /** Free all storage used for the cached key optimization. */ void @@ -516,7 +516,7 @@ free_pregenerated_keys(void) pregen_keys_2048[idx] = NULL; } } -#endif +#endif /* defined(USE_PREGENERATED_RSA_KEYS) */ } void @@ -541,6 +541,6 @@ init_pregenerated_keys(void) MOCK(crypto_pk_generate_key_with_bits, crypto_pk_generate_key_with_bits__get_cached); -#endif +#endif /* defined(USE_PREGENERATED_RSA_KEYS) */ } diff --git a/src/tools/include.am b/src/tools/include.am index 717af9e2ae..37936c742f 100644 --- a/src/tools/include.am +++ b/src/tools/include.am @@ -8,8 +8,8 @@ src_tools_tor_resolve_SOURCES = src/tools/tor-resolve.c src_tools_tor_resolve_LDFLAGS = src_tools_tor_resolve_LDADD = src/common/libor.a \ src/common/libor-ctime.a \ - @TOR_LIB_MATH@ @TOR_LIB_WS32@ \ - $(rust_ldadd) + $(rust_ldadd) \ + @TOR_LIB_MATH@ @TOR_LIB_WS32@ @TOR_LIB_USERENV@ if COVERAGE_ENABLED src_tools_tor_cov_resolve_SOURCES = src/tools/tor-resolve.c @@ -26,9 +26,9 @@ src_tools_tor_gencert_LDADD = src/common/libor.a src/common/libor-crypto.a \ src/common/libor-ctime.a \ $(LIBKECCAK_TINY) \ $(LIBDONNA) \ + $(rust_ldadd) \ @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ - @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ - $(rust_ldadd) + @TOR_LIB_WS32@ @TOR_LIB_GDI@ @TOR_LIB_USERENV@ @CURVE25519_LIBS@ if COVERAGE_ENABLED src_tools_tor_cov_gencert_SOURCES = src/tools/tor-gencert.c diff --git a/src/tools/tor-gencert.c b/src/tools/tor-gencert.c index 395535697f..fb7465c0eb 100644 --- a/src/tools/tor-gencert.c +++ b/src/tools/tor-gencert.c @@ -430,7 +430,7 @@ key_to_string(EVP_PKEY *key) static int get_fingerprint(EVP_PKEY *pkey, char *out) { - int r = 1; + int r = -1; crypto_pk_t *pk = crypto_new_pk_from_rsa_(EVP_PKEY_get1_RSA(pkey)); if (pk) { r = crypto_pk_get_fingerprint(pk, out, 0); @@ -443,7 +443,7 @@ get_fingerprint(EVP_PKEY *pkey, char *out) static int get_digest(EVP_PKEY *pkey, char *out) { - int r = 1; + int r = -1; crypto_pk_t *pk = crypto_new_pk_from_rsa_(EVP_PKEY_get1_RSA(pkey)); if (pk) { r = crypto_pk_get_digest(pk, out); @@ -464,16 +464,20 @@ generate_certificate(void) char expires[ISO_TIME_LEN+1]; char id_digest[DIGEST_LEN]; char fingerprint[FINGERPRINT_LEN+1]; - char *ident = key_to_string(identity_key); - char *signing = key_to_string(signing_key); FILE *f; size_t signed_len; char digest[DIGEST_LEN]; char signature[1024]; /* handles up to 8192-bit keys. */ int r; - get_fingerprint(identity_key, fingerprint); - get_digest(identity_key, id_digest); + if (get_fingerprint(identity_key, fingerprint) < 0) { + return -1; + } + if (get_digest(identity_key, id_digest)) { + return -1; + } + char *ident = key_to_string(identity_key); + char *signing = key_to_string(signing_key); tor_localtime_r(&now, &tm); tm.tm_mon += months_lifetime; diff --git a/src/trunnel/channelpadding_negotiation.c b/src/trunnel/channelpadding_negotiation.c index 172d6f8a03..59e6b38384 100644 --- a/src/trunnel/channelpadding_negotiation.c +++ b/src/trunnel/channelpadding_negotiation.c @@ -1,4 +1,4 @@ -/* channelpadding_negotiation.c -- generated by Trunnel v1.4.3. +/* channelpadding_negotiation.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int channelpaddingnegotiation_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || channelpaddingnegotiation_deadcode_dummy__ @@ -57,7 +57,7 @@ channelpadding_negotiate_free(channelpadding_negotiate_t *obj) } uint8_t -channelpadding_negotiate_get_version(channelpadding_negotiate_t *inp) +channelpadding_negotiate_get_version(const channelpadding_negotiate_t *inp) { return inp->version; } @@ -72,7 +72,7 @@ channelpadding_negotiate_set_version(channelpadding_negotiate_t *inp, uint8_t va return 0; } uint8_t -channelpadding_negotiate_get_command(channelpadding_negotiate_t *inp) +channelpadding_negotiate_get_command(const channelpadding_negotiate_t *inp) { return inp->command; } @@ -87,7 +87,7 @@ channelpadding_negotiate_set_command(channelpadding_negotiate_t *inp, uint8_t va return 0; } uint16_t -channelpadding_negotiate_get_ito_low_ms(channelpadding_negotiate_t *inp) +channelpadding_negotiate_get_ito_low_ms(const channelpadding_negotiate_t *inp) { return inp->ito_low_ms; } @@ -98,7 +98,7 @@ channelpadding_negotiate_set_ito_low_ms(channelpadding_negotiate_t *inp, uint16_ return 0; } uint16_t -channelpadding_negotiate_get_ito_high_ms(channelpadding_negotiate_t *inp) +channelpadding_negotiate_get_ito_high_ms(const channelpadding_negotiate_t *inp) { return inp->ito_high_ms; } diff --git a/src/trunnel/channelpadding_negotiation.h b/src/trunnel/channelpadding_negotiation.h index e58bda3be1..fcfc232fea 100644 --- a/src/trunnel/channelpadding_negotiation.h +++ b/src/trunnel/channelpadding_negotiation.h @@ -1,4 +1,4 @@ -/* channelpadding_negotiation.h -- generated by by Trunnel v1.4.3. +/* channelpadding_negotiation.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -60,7 +60,7 @@ int channelpadding_negotiate_clear_errors(channelpadding_negotiate_t *obj); /** Return the value of the version field of the * channelpadding_negotiate_t in 'inp' */ -uint8_t channelpadding_negotiate_get_version(channelpadding_negotiate_t *inp); +uint8_t channelpadding_negotiate_get_version(const channelpadding_negotiate_t *inp); /** Set the value of the version field of the * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. @@ -69,7 +69,7 @@ int channelpadding_negotiate_set_version(channelpadding_negotiate_t *inp, uint8_ /** Return the value of the command field of the * channelpadding_negotiate_t in 'inp' */ -uint8_t channelpadding_negotiate_get_command(channelpadding_negotiate_t *inp); +uint8_t channelpadding_negotiate_get_command(const channelpadding_negotiate_t *inp); /** Set the value of the command field of the * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. @@ -78,7 +78,7 @@ int channelpadding_negotiate_set_command(channelpadding_negotiate_t *inp, uint8_ /** Return the value of the ito_low_ms field of the * channelpadding_negotiate_t in 'inp' */ -uint16_t channelpadding_negotiate_get_ito_low_ms(channelpadding_negotiate_t *inp); +uint16_t channelpadding_negotiate_get_ito_low_ms(const channelpadding_negotiate_t *inp); /** Set the value of the ito_low_ms field of the * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. @@ -87,7 +87,7 @@ int channelpadding_negotiate_set_ito_low_ms(channelpadding_negotiate_t *inp, uin /** Return the value of the ito_high_ms field of the * channelpadding_negotiate_t in 'inp' */ -uint16_t channelpadding_negotiate_get_ito_high_ms(channelpadding_negotiate_t *inp); +uint16_t channelpadding_negotiate_get_ito_high_ms(const channelpadding_negotiate_t *inp); /** Set the value of the ito_high_ms field of the * channelpadding_negotiate_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. diff --git a/src/trunnel/ed25519_cert.c b/src/trunnel/ed25519_cert.c index ee02fda646..1276c7a505 100644 --- a/src/trunnel/ed25519_cert.c +++ b/src/trunnel/ed25519_cert.c @@ -1,4 +1,4 @@ -/* ed25519_cert.c -- generated by Trunnel v1.5.1. +/* ed25519_cert.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int edcert_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || edcert_deadcode_dummy__ diff --git a/src/trunnel/ed25519_cert.h b/src/trunnel/ed25519_cert.h index 782bd59585..e086c6fced 100644 --- a/src/trunnel/ed25519_cert.h +++ b/src/trunnel/ed25519_cert.h @@ -1,4 +1,4 @@ -/* ed25519_cert.h -- generated by by Trunnel v1.5.1. +/* ed25519_cert.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/trunnel/ed25519_cert.trunnel b/src/trunnel/ed25519_cert.trunnel index e424ce5464..8d6483d558 100644 --- a/src/trunnel/ed25519_cert.trunnel +++ b/src/trunnel/ed25519_cert.trunnel @@ -28,6 +28,12 @@ const LS_IPV6 = 0x01; const LS_LEGACY_ID = 0x02; const LS_ED25519_ID = 0x03; +// XXX hs_link_specifier_dup() violates the opaqueness of link_specifier_t by +// taking its sizeof(). If we ever want to turn on TRUNNEL_OPAQUE, or +// if we ever make link_specifier contain other types, we will +// need to refactor that function to do the copy by encoding and decoding the +// object. + // amended from tor.trunnel struct link_specifier { u8 ls_type; diff --git a/src/trunnel/hs/cell_common.c b/src/trunnel/hs/cell_common.c index b7f19ffc60..af223560c1 100644 --- a/src/trunnel/hs/cell_common.c +++ b/src/trunnel/hs/cell_common.c @@ -1,4 +1,4 @@ -/* cell_common.c -- generated by Trunnel v1.5.1. +/* cell_common.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int cellcommon_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || cellcommon_deadcode_dummy__ diff --git a/src/trunnel/hs/cell_common.h b/src/trunnel/hs/cell_common.h index 4d98a54cf4..e08eedfdb3 100644 --- a/src/trunnel/hs/cell_common.h +++ b/src/trunnel/hs/cell_common.h @@ -1,4 +1,4 @@ -/* cell_common.h -- generated by by Trunnel v1.5.1. +/* cell_common.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/trunnel/hs/cell_establish_intro.c b/src/trunnel/hs/cell_establish_intro.c index 22e198c369..ae3b7b1bc8 100644 --- a/src/trunnel/hs/cell_establish_intro.c +++ b/src/trunnel/hs/cell_establish_intro.c @@ -1,4 +1,4 @@ -/* cell_establish_intro.c -- generated by Trunnel v1.5.1. +/* cell_establish_intro.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int cellestablishintro_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || cellestablishintro_deadcode_dummy__ diff --git a/src/trunnel/hs/cell_establish_intro.h b/src/trunnel/hs/cell_establish_intro.h index 2f0d893659..ccaef5488c 100644 --- a/src/trunnel/hs/cell_establish_intro.h +++ b/src/trunnel/hs/cell_establish_intro.h @@ -1,4 +1,4 @@ -/* cell_establish_intro.h -- generated by by Trunnel v1.5.1. +/* cell_establish_intro.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/trunnel/hs/cell_introduce1.c b/src/trunnel/hs/cell_introduce1.c index 7501f6f196..358b355cda 100644 --- a/src/trunnel/hs/cell_introduce1.c +++ b/src/trunnel/hs/cell_introduce1.c @@ -1,4 +1,4 @@ -/* cell_introduce1.c -- generated by Trunnel v1.5.1. +/* cell_introduce1.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int cellintroduce_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || cellintroduce_deadcode_dummy__ diff --git a/src/trunnel/hs/cell_introduce1.h b/src/trunnel/hs/cell_introduce1.h index cca825a431..fa218adc6d 100644 --- a/src/trunnel/hs/cell_introduce1.h +++ b/src/trunnel/hs/cell_introduce1.h @@ -1,4 +1,4 @@ -/* cell_introduce1.h -- generated by by Trunnel v1.5.1. +/* cell_introduce1.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/trunnel/hs/cell_rendezvous.c b/src/trunnel/hs/cell_rendezvous.c new file mode 100644 index 0000000000..53cb609138 --- /dev/null +++ b/src/trunnel/hs/cell_rendezvous.c @@ -0,0 +1,470 @@ +/* cell_rendezvous.c -- generated by Trunnel v1.5.2. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#include <stdlib.h> +#include "trunnel-impl.h" + +#include "cell_rendezvous.h" + +#define TRUNNEL_SET_ERROR_CODE(obj) \ + do { \ + (obj)->trunnel_error_code_ = 1; \ + } while (0) + +#if defined(__COVERITY__) || defined(__clang_analyzer__) +/* If we're running a static analysis tool, we don't want it to complain + * that some of our remaining-bytes checks are dead-code. */ +int cellrendezvous_deadcode_dummy__ = 0; +#define OR_DEADCODE_DUMMY || cellrendezvous_deadcode_dummy__ +#else +#define OR_DEADCODE_DUMMY +#endif + +#define CHECK_REMAINING(nbytes, label) \ + do { \ + if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \ + goto label; \ + } \ + } while (0) + +trn_cell_rendezvous1_t * +trn_cell_rendezvous1_new(void) +{ + trn_cell_rendezvous1_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous1_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_rendezvous1_clear(trn_cell_rendezvous1_t *obj) +{ + (void) obj; + TRUNNEL_DYNARRAY_WIPE(&obj->handshake_info); + TRUNNEL_DYNARRAY_CLEAR(&obj->handshake_info); +} + +void +trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *obj) +{ + if (obj == NULL) + return; + trn_cell_rendezvous1_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_rendezvous1_t)); + trunnel_free_(obj); +} + +size_t +trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp) +{ + (void)inp; return TRUNNEL_REND_COOKIE_LEN; +} + +uint8_t +trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx) +{ + trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN); + return inp->rendezvous_cookie[idx]; +} + +uint8_t +trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx) +{ + return trn_cell_rendezvous1_get_rendezvous_cookie((trn_cell_rendezvous1_t*)inp, idx); +} +int +trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN); + inp->rendezvous_cookie[idx] = elt; + return 0; +} + +uint8_t * +trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp) +{ + return inp->rendezvous_cookie; +} +const uint8_t * +trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp) +{ + return (const uint8_t *)trn_cell_rendezvous1_getarray_rendezvous_cookie((trn_cell_rendezvous1_t*)inp); +} +size_t +trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->handshake_info); +} + +uint8_t +trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->handshake_info, idx); +} + +uint8_t +trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx) +{ + return trn_cell_rendezvous1_get_handshake_info((trn_cell_rendezvous1_t*)inp, idx); +} +int +trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->handshake_info, idx, elt); + return 0; +} +int +trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt) +{ + TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->handshake_info, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +uint8_t * +trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp) +{ + return inp->handshake_info.elts_; +} +const uint8_t * +trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp) +{ + return (const uint8_t *)trn_cell_rendezvous1_getarray_handshake_info((trn_cell_rendezvous1_t*)inp); +} +int +trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen) +{ + uint8_t *newptr; + newptr = trunnel_dynarray_setlen(&inp->handshake_info.allocated_, + &inp->handshake_info.n_, inp->handshake_info.elts_, newlen, + sizeof(inp->handshake_info.elts_[0]), (trunnel_free_fn_t) NULL, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->handshake_info.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_rendezvous1_check(obj)) + return -1; + + + /* Length of u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */ + result += TRUNNEL_REND_COOKIE_LEN; + + /* Length of u8 handshake_info[] */ + result += TRUNNEL_DYNARRAY_LEN(&obj->handshake_info); + return result; +} +int +trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_rendezvous1_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous1_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = trn_cell_rendezvous1_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_rendezvous1_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */ + trunnel_assert(written <= avail); + if (avail - written < TRUNNEL_REND_COOKIE_LEN) + goto truncated; + memcpy(ptr, obj->rendezvous_cookie, TRUNNEL_REND_COOKIE_LEN); + written += TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN; + + /* Encode u8 handshake_info[] */ + { + size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->handshake_info); + trunnel_assert(written <= avail); + if (avail - written < elt_len) + goto truncated; + if (elt_len) + memcpy(ptr, obj->handshake_info.elts_, elt_len); + written += elt_len; ptr += elt_len; + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As trn_cell_rendezvous1_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_rendezvous1_parse_into(trn_cell_rendezvous1_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN] */ + CHECK_REMAINING(TRUNNEL_REND_COOKIE_LEN, truncated); + memcpy(obj->rendezvous_cookie, ptr, TRUNNEL_REND_COOKIE_LEN); + remaining -= TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN; + + /* Parse u8 handshake_info[] */ + TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->handshake_info, remaining, {}); + obj->handshake_info.n_ = remaining; + if (remaining) + memcpy(obj->handshake_info.elts_, ptr, remaining); + ptr += remaining; remaining -= remaining; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + trunnel_alloc_failed: + return -1; +} + +ssize_t +trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_rendezvous1_new(); + if (NULL == *output) + return -1; + result = trn_cell_rendezvous1_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_rendezvous1_free(*output); + *output = NULL; + } + return result; +} +trn_cell_rendezvous2_t * +trn_cell_rendezvous2_new(void) +{ + trn_cell_rendezvous2_t *val = trunnel_calloc(1, sizeof(trn_cell_rendezvous2_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_rendezvous2_clear(trn_cell_rendezvous2_t *obj) +{ + (void) obj; +} + +void +trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *obj) +{ + if (obj == NULL) + return; + trn_cell_rendezvous2_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_rendezvous2_t)); + trunnel_free_(obj); +} + +size_t +trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp) +{ + (void)inp; return TRUNNEL_HANDSHAKE_INFO_LEN; +} + +uint8_t +trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx) +{ + trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN); + return inp->handshake_info[idx]; +} + +uint8_t +trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx) +{ + return trn_cell_rendezvous2_get_handshake_info((trn_cell_rendezvous2_t*)inp, idx); +} +int +trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt) +{ + trunnel_assert(idx < TRUNNEL_HANDSHAKE_INFO_LEN); + inp->handshake_info[idx] = elt; + return 0; +} + +uint8_t * +trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp) +{ + return inp->handshake_info; +} +const uint8_t * +trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp) +{ + return (const uint8_t *)trn_cell_rendezvous2_getarray_handshake_info((trn_cell_rendezvous2_t*)inp); +} +const char * +trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_rendezvous2_check(obj)) + return -1; + + + /* Length of u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */ + result += TRUNNEL_HANDSHAKE_INFO_LEN; + return result; +} +int +trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_rendezvous2_encode(uint8_t *output, const size_t avail, const trn_cell_rendezvous2_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = trn_cell_rendezvous2_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_rendezvous2_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */ + trunnel_assert(written <= avail); + if (avail - written < TRUNNEL_HANDSHAKE_INFO_LEN) + goto truncated; + memcpy(ptr, obj->handshake_info, TRUNNEL_HANDSHAKE_INFO_LEN); + written += TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As trn_cell_rendezvous2_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_rendezvous2_parse_into(trn_cell_rendezvous2_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN] */ + CHECK_REMAINING(TRUNNEL_HANDSHAKE_INFO_LEN, truncated); + memcpy(obj->handshake_info, ptr, TRUNNEL_HANDSHAKE_INFO_LEN); + remaining -= TRUNNEL_HANDSHAKE_INFO_LEN; ptr += TRUNNEL_HANDSHAKE_INFO_LEN; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_rendezvous2_new(); + if (NULL == *output) + return -1; + result = trn_cell_rendezvous2_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_rendezvous2_free(*output); + *output = NULL; + } + return result; +} diff --git a/src/trunnel/hs/cell_rendezvous.h b/src/trunnel/hs/cell_rendezvous.h new file mode 100644 index 0000000000..39e14da25b --- /dev/null +++ b/src/trunnel/hs/cell_rendezvous.h @@ -0,0 +1,187 @@ +/* cell_rendezvous.h -- generated by Trunnel v1.5.2. + * https://gitweb.torproject.org/trunnel.git + * You probably shouldn't edit this file. + */ +#ifndef TRUNNEL_CELL_RENDEZVOUS_H +#define TRUNNEL_CELL_RENDEZVOUS_H + +#include <stdint.h> +#include "trunnel.h" + +#define TRUNNEL_REND_COOKIE_LEN 20 +#define TRUNNEL_HANDSHAKE_INFO_LEN 64 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS1) +struct trn_cell_rendezvous1_st { + uint8_t rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN]; + TRUNNEL_DYNARRAY_HEAD(, uint8_t) handshake_info; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_rendezvous1_st trn_cell_rendezvous1_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_RENDEZVOUS2) +struct trn_cell_rendezvous2_st { + uint8_t handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN]; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_rendezvous2_st trn_cell_rendezvous2_t; +/** Return a newly allocated trn_cell_rendezvous1 with all elements + * set to zero. + */ +trn_cell_rendezvous1_t *trn_cell_rendezvous1_new(void); +/** Release all storage held by the trn_cell_rendezvous1 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void trn_cell_rendezvous1_free(trn_cell_rendezvous1_t *victim); +/** Try to parse a trn_cell_rendezvous1 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated trn_cell_rendezvous1_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_rendezvous1_parse(trn_cell_rendezvous1_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_rendezvous1 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t trn_cell_rendezvous1_encoded_len(const trn_cell_rendezvous1_t *obj); +/** Try to encode the trn_cell_rendezvous1 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t trn_cell_rendezvous1_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous1_t *input); +/** Check whether the internal state of the trn_cell_rendezvous1 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_rendezvous1_check(const trn_cell_rendezvous1_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_rendezvous1_clear_errors(trn_cell_rendezvous1_t *obj); +/** Return the (constant) length of the array holding the + * rendezvous_cookie field of the trn_cell_rendezvous1_t in 'inp'. + */ +size_t trn_cell_rendezvous1_getlen_rendezvous_cookie(const trn_cell_rendezvous1_t *inp); +/** Return the element at position 'idx' of the fixed array field + * rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp'. + */ +uint8_t trn_cell_rendezvous1_get_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx); +/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return + * a const pointer + */ +uint8_t trn_cell_rendezvous1_getconst_rendezvous_cookie(const trn_cell_rendezvous1_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * rendezvous_cookie of the trn_cell_rendezvous1_t in 'inp', so that + * it will hold the value 'elt'. + */ +int trn_cell_rendezvous1_set_rendezvous_cookie(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the TRUNNEL_REND_COOKIE_LEN-element array + * field rendezvous_cookie of 'inp'. + */ +uint8_t * trn_cell_rendezvous1_getarray_rendezvous_cookie(trn_cell_rendezvous1_t *inp); +/** As trn_cell_rendezvous1_get_rendezvous_cookie, but take and return + * a const pointer + */ +const uint8_t * trn_cell_rendezvous1_getconstarray_rendezvous_cookie(const trn_cell_rendezvous1_t *inp); +/** Return the length of the dynamic array holding the handshake_info + * field of the trn_cell_rendezvous1_t in 'inp'. + */ +size_t trn_cell_rendezvous1_getlen_handshake_info(const trn_cell_rendezvous1_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * handshake_info of the trn_cell_rendezvous1_t in 'inp'. + */ +uint8_t trn_cell_rendezvous1_get_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx); +/** As trn_cell_rendezvous1_get_handshake_info, but take and return a + * const pointer + */ +uint8_t trn_cell_rendezvous1_getconst_handshake_info(const trn_cell_rendezvous1_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * handshake_info of the trn_cell_rendezvous1_t in 'inp', so that it + * will hold the value 'elt'. + */ +int trn_cell_rendezvous1_set_handshake_info(trn_cell_rendezvous1_t *inp, size_t idx, uint8_t elt); +/** Append a new element 'elt' to the dynamic array field + * handshake_info of the trn_cell_rendezvous1_t in 'inp'. + */ +int trn_cell_rendezvous1_add_handshake_info(trn_cell_rendezvous1_t *inp, uint8_t elt); +/** Return a pointer to the variable-length array field handshake_info + * of 'inp'. + */ +uint8_t * trn_cell_rendezvous1_getarray_handshake_info(trn_cell_rendezvous1_t *inp); +/** As trn_cell_rendezvous1_get_handshake_info, but take and return a + * const pointer + */ +const uint8_t * trn_cell_rendezvous1_getconstarray_handshake_info(const trn_cell_rendezvous1_t *inp); +/** Change the length of the variable-length array field + * handshake_info of 'inp' to 'newlen'.Fill extra elements with 0. + * Return 0 on success; return -1 and set the error code on 'inp' on + * failure. + */ +int trn_cell_rendezvous1_setlen_handshake_info(trn_cell_rendezvous1_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_rendezvous2 with all elements + * set to zero. + */ +trn_cell_rendezvous2_t *trn_cell_rendezvous2_new(void); +/** Release all storage held by the trn_cell_rendezvous2 in 'victim'. + * (Do nothing if 'victim' is NULL.) + */ +void trn_cell_rendezvous2_free(trn_cell_rendezvous2_t *victim); +/** Try to parse a trn_cell_rendezvous2 from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated trn_cell_rendezvous2_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_rendezvous2_parse(trn_cell_rendezvous2_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_rendezvous2 in 'obj'. On failure, return a negative value. + * Note that this value may be an overestimate, and can even be an + * underestimate for certain unencodeable objects. + */ +ssize_t trn_cell_rendezvous2_encoded_len(const trn_cell_rendezvous2_t *obj); +/** Try to encode the trn_cell_rendezvous2 from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t trn_cell_rendezvous2_encode(uint8_t *output, size_t avail, const trn_cell_rendezvous2_t *input); +/** Check whether the internal state of the trn_cell_rendezvous2 in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_rendezvous2_check(const trn_cell_rendezvous2_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_rendezvous2_clear_errors(trn_cell_rendezvous2_t *obj); +/** Return the (constant) length of the array holding the + * handshake_info field of the trn_cell_rendezvous2_t in 'inp'. + */ +size_t trn_cell_rendezvous2_getlen_handshake_info(const trn_cell_rendezvous2_t *inp); +/** Return the element at position 'idx' of the fixed array field + * handshake_info of the trn_cell_rendezvous2_t in 'inp'. + */ +uint8_t trn_cell_rendezvous2_get_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx); +/** As trn_cell_rendezvous2_get_handshake_info, but take and return a + * const pointer + */ +uint8_t trn_cell_rendezvous2_getconst_handshake_info(const trn_cell_rendezvous2_t *inp, size_t idx); +/** Change the element at position 'idx' of the fixed array field + * handshake_info of the trn_cell_rendezvous2_t in 'inp', so that it + * will hold the value 'elt'. + */ +int trn_cell_rendezvous2_set_handshake_info(trn_cell_rendezvous2_t *inp, size_t idx, uint8_t elt); +/** Return a pointer to the TRUNNEL_HANDSHAKE_INFO_LEN-element array + * field handshake_info of 'inp'. + */ +uint8_t * trn_cell_rendezvous2_getarray_handshake_info(trn_cell_rendezvous2_t *inp); +/** As trn_cell_rendezvous2_get_handshake_info, but take and return a + * const pointer + */ +const uint8_t * trn_cell_rendezvous2_getconstarray_handshake_info(const trn_cell_rendezvous2_t *inp); + + +#endif diff --git a/src/trunnel/hs/cell_rendezvous.trunnel b/src/trunnel/hs/cell_rendezvous.trunnel new file mode 100644 index 0000000000..2128b4d126 --- /dev/null +++ b/src/trunnel/hs/cell_rendezvous.trunnel @@ -0,0 +1,29 @@ +/* + * This contains the definition of the RENDEZVOUS1/2 cell for onion service + * version 3 and onward. The following format is specified in proposal 224 + * section 4.2. + */ + +/* Rendezvous cookie length. */ +const TRUNNEL_REND_COOKIE_LEN = 20; +/* The HANDSHAKE_INFO field layout is as follow: + * SERVER_PK [PK_PUBKEY_LEN bytes] + * AUTH [MAC_LEN bytes] + * This means, the size is 32 bytes + 32 bytes. */ +const TRUNNEL_HANDSHAKE_INFO_LEN = 64; + +/* RENDEZVOUS1 payload. See details in section 4.2. */ +struct trn_cell_rendezvous1 { + /* The RENDEZVOUS_COOKIE field. */ + u8 rendezvous_cookie[TRUNNEL_REND_COOKIE_LEN]; + + /* The HANDSHAKE_INFO field which has a variable length depending on the + * handshake type used. */ + u8 handshake_info[]; +}; + +/* RENDEZVOUS2 payload. See details in section 4.2. */ +struct trn_cell_rendezvous2 { + /* The HANDSHAKE_INFO field. */ + u8 handshake_info[TRUNNEL_HANDSHAKE_INFO_LEN]; +}; diff --git a/src/trunnel/include.am b/src/trunnel/include.am index de6cf4781f..ca79ff3a39 100644 --- a/src/trunnel/include.am +++ b/src/trunnel/include.am @@ -22,6 +22,7 @@ TRUNNELSOURCES = \ src/trunnel/hs/cell_common.c \ src/trunnel/hs/cell_establish_intro.c \ src/trunnel/hs/cell_introduce1.c \ + src/trunnel/hs/cell_rendezvous.c \ src/trunnel/channelpadding_negotiation.c TRUNNELHEADERS = \ @@ -34,6 +35,7 @@ TRUNNELHEADERS = \ src/trunnel/hs/cell_common.h \ src/trunnel/hs/cell_establish_intro.h \ src/trunnel/hs/cell_introduce1.h \ + src/trunnel/hs/cell_rendezvous.h \ src/trunnel/channelpadding_negotiation.h src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES) diff --git a/src/trunnel/link_handshake.c b/src/trunnel/link_handshake.c index 887f710d9c..03ead31c62 100644 --- a/src/trunnel/link_handshake.c +++ b/src/trunnel/link_handshake.c @@ -1,4 +1,4 @@ -/* link_handshake.c -- generated by Trunnel v1.5.1. +/* link_handshake.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int linkhandshake_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || linkhandshake_deadcode_dummy__ diff --git a/src/trunnel/link_handshake.h b/src/trunnel/link_handshake.h index 418662631a..6a23483adc 100644 --- a/src/trunnel/link_handshake.h +++ b/src/trunnel/link_handshake.h @@ -1,4 +1,4 @@ -/* link_handshake.h -- generated by by Trunnel v1.5.1. +/* link_handshake.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/trunnel/pwbox.c b/src/trunnel/pwbox.c index f4b910bdab..c356515d36 100644 --- a/src/trunnel/pwbox.c +++ b/src/trunnel/pwbox.c @@ -1,4 +1,4 @@ -/* pwbox.c -- generated by Trunnel v1.5.1. +/* pwbox.c -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ @@ -13,7 +13,7 @@ } while (0) #if defined(__COVERITY__) || defined(__clang_analyzer__) -/* If we're runnning a static analysis tool, we don't want it to complain +/* If we're running a static analysis tool, we don't want it to complain * that some of our remaining-bytes checks are dead-code. */ int pwbox_deadcode_dummy__ = 0; #define OR_DEADCODE_DUMMY || pwbox_deadcode_dummy__ diff --git a/src/trunnel/pwbox.h b/src/trunnel/pwbox.h index 939b3c41a7..a9a421408a 100644 --- a/src/trunnel/pwbox.h +++ b/src/trunnel/pwbox.h @@ -1,4 +1,4 @@ -/* pwbox.h -- generated by by Trunnel v1.5.1. +/* pwbox.h -- generated by Trunnel v1.5.2. * https://gitweb.torproject.org/trunnel.git * You probably shouldn't edit this file. */ diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index 3ecf024829..7def010cd6 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -218,7 +218,7 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.3.1.9-dev" +#define VERSION "0.3.2.7-rc-dev" |