aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore16
-rw-r--r--ChangeLog1022
-rw-r--r--Doxyfile.in26
-rw-r--r--Makefile.am18
-rw-r--r--ReleaseNotes787
-rw-r--r--acinclude.m423
-rwxr-xr-xautogen.sh4
-rw-r--r--changes/199745
-rw-r--r--changes/204604
-rw-r--r--changes/204924
-rw-r--r--changes/213598
-rw-r--r--changes/bug190254
-rw-r--r--changes/bug198694
-rw-r--r--changes/bug19926_029_info3
-rw-r--r--changes/bug199604
-rw-r--r--changes/bug1996811
-rw-r--r--changes/bug1996910
-rw-r--r--changes/bug200854
-rw-r--r--changes/bug202354
-rw-r--r--changes/bug202706
-rw-r--r--changes/bug20306_0294
-rw-r--r--changes/bug203077
-rw-r--r--changes/bug204014
-rw-r--r--changes/bug204236
-rw-r--r--changes/bug204725
-rw-r--r--changes/bug204845
-rw-r--r--changes/bug204874
-rw-r--r--changes/bug205294
-rw-r--r--changes/bug205337
-rw-r--r--changes/bug205348
-rw-r--r--changes/bug205366
-rw-r--r--changes/bug205513
-rw-r--r--changes/bug205533
-rw-r--r--changes/bug205604
-rw-r--r--changes/bug205875
-rw-r--r--changes/bug205883
-rw-r--r--changes/bug205913
-rw-r--r--changes/bug205936
-rw-r--r--changes/bug205975
-rw-r--r--changes/bug206136
-rw-r--r--changes/bug206343
-rw-r--r--changes/bug206385
-rw-r--r--changes/bug20710_0254
-rw-r--r--changes/bug207117
-rw-r--r--changes/bug207154
-rw-r--r--changes/bug207163
-rw-r--r--changes/bug208104
-rw-r--r--changes/bug208644
-rw-r--r--changes/bug208754
-rw-r--r--changes/bug208949
-rw-r--r--changes/bug209353
-rw-r--r--changes/bug21007_case24
-rw-r--r--changes/bug2101811
-rw-r--r--changes/bug210278
-rw-r--r--changes/bug210356
-rw-r--r--changes/bug210513
-rw-r--r--changes/bug21108_0296
-rw-r--r--changes/bug211163
-rw-r--r--changes/bug212805
-rw-r--r--changes/bug213577
-rw-r--r--changes/bug21369_check3
-rw-r--r--changes/bug214154
-rw-r--r--changes/bug214203
-rw-r--r--changes/bug214474
-rw-r--r--changes/bug214715
-rw-r--r--changes/bug214723
-rw-r--r--changes/bug214925
-rw-r--r--changes/bug215537
-rw-r--r--changes/bug215624
-rw-r--r--changes/bug215815
-rw-r--r--changes/bug215945
-rw-r--r--changes/bug215965
-rw-r--r--changes/bug216823
-rw-r--r--changes/bug217205
-rw-r--r--changes/bug217715
-rw-r--r--changes/bug217996
-rw-r--r--changes/bug218256
-rw-r--r--changes/bug21894_0295
-rw-r--r--changes/bug219693
-rw-r--r--changes/bug222466
-rw-r--r--changes/bug222523
-rw-r--r--changes/bug22400_014
-rw-r--r--changes/bug224473
-rw-r--r--changes/bug22460_case116
-rw-r--r--changes/bug22466_regenerate8
-rw-r--r--changes/bug227537
-rw-r--r--changes/bug228033
-rw-r--r--changes/bug230787
-rw-r--r--changes/bug236104
-rw-r--r--changes/bug238625
-rw-r--r--changes/feature215705
-rw-r--r--changes/geoip-december20164
-rw-r--r--changes/geoip-january20174
-rw-r--r--changes/geoip-november20164
-rw-r--r--changes/more_module_docs4
-rw-r--r--changes/ticket197697
-rw-r--r--changes/ticket20170-v35
-rw-r--r--changes/ticket206563
-rw-r--r--changes/trove-2017-0018
-rw-r--r--changes/trove-2017-0046
-rw-r--r--changes/trove-2017-012-part25
-rw-r--r--configure.ac111
-rw-r--r--contrib/win32build/tor-mingw.nsi.in2
-rw-r--r--doc/HACKING/CodingStandards.md4
-rw-r--r--doc/HACKING/Fuzzing.md123
-rw-r--r--doc/HACKING/HelpfulTools.md55
-rw-r--r--doc/HACKING/ReleasingTor.md18
-rw-r--r--doc/HACKING/WritingTests.md2
-rw-r--r--doc/include.am12
-rw-r--r--doc/tor.1.txt262
-rw-r--r--m4/ax_check_sign.m44
-rw-r--r--m4/pc_from_ucontext.m420
-rwxr-xr-xscripts/codegen/gen_server_ciphers.py62
-rwxr-xr-x[-rw-r--r--]scripts/codegen/get_mozilla_ciphers.py13
-rwxr-xr-xscripts/codegen/run_trunnel.sh10
-rwxr-xr-xscripts/maint/checkSpace.pl134
-rw-r--r--scripts/maint/fallback.blacklist37
-rw-r--r--scripts/maint/fallback.whitelist208
-rwxr-xr-xscripts/maint/lintChanges.py17
-rwxr-xr-xscripts/maint/updateFallbackDirs.py502
-rw-r--r--src/common/address.c17
-rw-r--r--src/common/address.h5
-rw-r--r--src/common/backtrace.c2
-rw-r--r--src/common/ciphers.inc80
-rw-r--r--src/common/compat.c16
-rw-r--r--src/common/container.c18
-rw-r--r--src/common/container.h1
-rw-r--r--src/common/crypto.c86
-rw-r--r--src/common/crypto.h20
-rw-r--r--src/common/crypto_curve25519.c2
-rw-r--r--src/common/crypto_ed25519.c52
-rw-r--r--src/common/crypto_ed25519.h31
-rw-r--r--src/common/crypto_format.c21
-rw-r--r--src/common/crypto_format.h1
-rw-r--r--src/common/log.c8
-rw-r--r--src/common/sandbox.c2
-rw-r--r--src/common/timers.c14
-rw-r--r--src/common/timers.h2
-rw-r--r--src/common/torgzip.c2
-rw-r--r--src/common/torlog.h4
-rw-r--r--src/common/tortls.c95
-rw-r--r--src/common/tortls.h11
-rw-r--r--src/common/util.c103
-rw-r--r--src/common/util.h9
-rw-r--r--src/common/util_bug.c2
-rw-r--r--src/common/util_format.c9
-rw-r--r--src/config/torrc.sample.in7
-rw-r--r--src/ext/csiphash.c4
-rw-r--r--src/ext/trunnel/trunnel-impl.h3
-rw-r--r--src/ext/trunnel/trunnel.c4
-rw-r--r--src/ext/trunnel/trunnel.h2
-rw-r--r--src/include.am2
-rw-r--r--src/or/addressmap.c18
-rw-r--r--src/or/bridges.c881
-rw-r--r--src/or/bridges.h70
-rw-r--r--src/or/buffers.c141
-rw-r--r--src/or/buffers.h8
-rw-r--r--src/or/channel.c478
-rw-r--r--src/or/channel.h44
-rw-r--r--src/or/channeltls.c394
-rw-r--r--src/or/channeltls.h3
-rw-r--r--src/or/circpathbias.c330
-rw-r--r--src/or/circuitbuild.c261
-rw-r--r--src/or/circuitbuild.h20
-rw-r--r--src/or/circuitlist.c438
-rw-r--r--src/or/circuitlist.h10
-rw-r--r--src/or/circuitmux_ewma.c2
-rw-r--r--src/or/circuitstats.c35
-rw-r--r--src/or/circuitstats.h5
-rw-r--r--src/or/circuituse.c368
-rw-r--r--src/or/circuituse.h21
-rw-r--r--src/or/config.c372
-rw-r--r--src/or/confparse.c39
-rw-r--r--src/or/connection.c85
-rw-r--r--src/or/connection_edge.c228
-rw-r--r--src/or/connection_edge.h2
-rw-r--r--src/or/connection_or.c700
-rw-r--r--src/or/connection_or.h31
-rw-r--r--src/or/control.c79
-rw-r--r--src/or/directory.c586
-rw-r--r--src/or/directory.h43
-rw-r--r--src/or/dirserv.c98
-rw-r--r--src/or/dirserv.h3
-rw-r--r--src/or/dirvote.c99
-rw-r--r--src/or/dirvote.h10
-rw-r--r--src/or/dns.c4
-rw-r--r--src/or/dnsserv.c22
-rw-r--r--src/or/entrynodes.c5248
-rw-r--r--src/or/entrynodes.h606
-rw-r--r--src/or/geoip.c2
-rw-r--r--src/or/hibernate.c6
-rw-r--r--src/or/hs_cache.c398
-rw-r--r--src/or/hs_cache.h63
-rw-r--r--src/or/hs_circuitmap.c328
-rw-r--r--src/or/hs_circuitmap.h70
-rw-r--r--src/or/hs_common.c270
-rw-r--r--src/or/hs_common.h43
-rw-r--r--src/or/hs_descriptor.c1896
-rw-r--r--src/or/hs_descriptor.h248
-rw-r--r--src/or/hs_intropoint.c596
-rw-r--r--src/or/hs_intropoint.h61
-rw-r--r--src/or/hs_service.c172
-rw-r--r--src/or/hs_service.h27
-rw-r--r--src/or/include.am16
-rw-r--r--src/or/main.c222
-rw-r--r--src/or/microdesc.c17
-rw-r--r--src/or/networkstatus.c116
-rw-r--r--src/or/networkstatus.h12
-rw-r--r--src/or/nodelist.c133
-rw-r--r--src/or/nodelist.h10
-rw-r--r--src/or/onion.c314
-rw-r--r--src/or/onion.h2
-rw-r--r--src/or/or.h220
-rw-r--r--src/or/parsecommon.c450
-rw-r--r--src/or/parsecommon.h315
-rw-r--r--src/or/policies.c106
-rw-r--r--src/or/protover.c10
-rw-r--r--src/or/relay.c58
-rw-r--r--src/or/relay.h7
-rw-r--r--src/or/rendcache.c79
-rw-r--r--src/or/rendcache.h13
-rw-r--r--src/or/rendclient.c150
-rw-r--r--src/or/rendclient.h2
-rw-r--r--src/or/rendcommon.c153
-rw-r--r--src/or/rendcommon.h24
-rw-r--r--src/or/rendmid.c79
-rw-r--r--src/or/rendmid.h8
-rw-r--r--src/or/rendservice.c761
-rw-r--r--src/or/rendservice.h7
-rw-r--r--src/or/router.c48
-rw-r--r--src/or/routerkeys.c204
-rw-r--r--src/or/routerkeys.h13
-rw-r--r--src/or/routerlist.c154
-rw-r--r--src/or/routerlist.h1
-rw-r--r--src/or/routerparse.c824
-rw-r--r--src/or/routerparse.h7
-rw-r--r--src/or/routerset.c21
-rw-r--r--src/or/routerset.h5
-rw-r--r--src/or/scheduler.c113
-rw-r--r--src/or/shared_random.c52
-rw-r--r--src/or/shared_random.h4
-rw-r--r--src/or/statefile.c2
-rw-r--r--src/or/torcert.c410
-rw-r--r--src/or/torcert.h42
-rw-r--r--src/or/transports.c7
-rw-r--r--src/test/bench.c3
-rw-r--r--src/test/fuzz/dict/consensus52
-rw-r--r--src/test/fuzz/dict/descriptor41
-rw-r--r--src/test/fuzz/dict/extrainfo32
-rw-r--r--src/test/fuzz/dict/hsdescv28
-rw-r--r--src/test/fuzz/dict/http24
-rw-r--r--src/test/fuzz/dict/iptsv26
-rw-r--r--src/test/fuzz/dict/microdesc7
-rwxr-xr-xsrc/test/fuzz/fixup_filenames.sh19
-rw-r--r--src/test/fuzz/fuzz_consensus.c78
-rw-r--r--src/test/fuzz/fuzz_descriptor.c79
-rw-r--r--src/test/fuzz/fuzz_extrainfo.c65
-rw-r--r--src/test/fuzz/fuzz_hsdescv2.c52
-rw-r--r--src/test/fuzz/fuzz_http.c133
-rw-r--r--src/test/fuzz/fuzz_iptsv2.c46
-rw-r--r--src/test/fuzz/fuzz_microdesc.c47
-rwxr-xr-xsrc/test/fuzz/fuzz_multi.sh34
-rw-r--r--src/test/fuzz/fuzz_vrs.c82
-rw-r--r--src/test/fuzz/fuzzing.h13
-rw-r--r--src/test/fuzz/fuzzing_common.c190
-rw-r--r--src/test/fuzz/include.am250
-rwxr-xr-xsrc/test/fuzz/minimize.sh14
-rwxr-xr-xsrc/test/fuzz_static_testcases.sh27
-rw-r--r--src/test/include.am12
-rw-r--r--src/test/test-memwipe.c2
-rwxr-xr-xsrc/test/test-network.sh60
-rw-r--r--src/test/test.c7
-rw-r--r--src/test/test.h7
-rw-r--r--src/test/test_bt_cl.c11
-rw-r--r--src/test/test_buffers.c44
-rw-r--r--src/test/test_cell_formats.c35
-rw-r--r--src/test/test_channel.c111
-rw-r--r--src/test/test_channeltls.c9
-rw-r--r--src/test/test_circuitlist.c63
-rw-r--r--src/test/test_circuituse.c304
-rw-r--r--src/test/test_config.c42
-rw-r--r--src/test/test_connection.c12
-rw-r--r--src/test/test_containers.c57
-rw-r--r--src/test/test_controller.c6
-rw-r--r--src/test/test_crypto.c49
-rw-r--r--src/test/test_dir.c476
-rw-r--r--src/test/test_dir_handle_get.c73
-rw-r--r--src/test/test_entryconn.c20
-rw-r--r--src/test/test_entrynodes.c3040
-rw-r--r--src/test/test_helpers.c51
-rw-r--r--src/test/test_helpers.h9
-rw-r--r--src/test/test_hs.c226
-rw-r--r--src/test/test_hs_cache.c528
-rw-r--r--src/test/test_hs_descriptor.c1039
-rw-r--r--src/test/test_hs_intropoint.c875
-rw-r--r--src/test/test_hs_service.c111
-rw-r--r--src/test/test_link_handshake.c723
-rw-r--r--src/test/test_oom.c19
-rw-r--r--src/test/test_options.c44
-rw-r--r--src/test/test_policy.c16
-rw-r--r--src/test/test_pt.c8
-rw-r--r--src/test/test_rendcache.c65
-rw-r--r--src/test/test_routerkeys.c81
-rw-r--r--src/test/test_routerlist.c26
-rw-r--r--src/test/test_routerset.c28
-rw-r--r--src/test/test_shared_random.c16
-rw-r--r--src/test/test_tortls.c26
-rw-r--r--src/test/test_util.c79
-rw-r--r--src/test/test_util_format.c20
-rw-r--r--src/test/testing_common.c69
-rw-r--r--src/test/testing_rsakeys.c546
-rw-r--r--src/trunnel/ed25519_cert.c1848
-rw-r--r--src/trunnel/ed25519_cert.h678
-rw-r--r--src/trunnel/ed25519_cert.trunnel58
-rw-r--r--src/trunnel/hs/cell_common.c594
-rw-r--r--src/trunnel/hs/cell_common.h198
-rw-r--r--src/trunnel/hs/cell_common.trunnel12
-rw-r--r--src/trunnel/hs/cell_establish_intro.c735
-rw-r--r--src/trunnel/hs/cell_establish_intro.h275
-rw-r--r--src/trunnel/hs/cell_establish_intro.trunnel41
-rw-r--r--src/trunnel/hs/cell_introduce1.c1357
-rw-r--r--src/trunnel/hs/cell_introduce1.h493
-rw-r--r--src/trunnel/hs/cell_introduce1.trunnel60
-rw-r--r--src/trunnel/include.am15
-rw-r--r--src/trunnel/link_handshake.c210
-rw-r--r--src/trunnel/link_handshake.h148
-rw-r--r--src/trunnel/pwbox.c52
-rw-r--r--src/trunnel/pwbox.h38
-rw-r--r--src/win32/orconfig.h2
329 files changed, 34962 insertions, 7958 deletions
diff --git a/.gitignore b/.gitignore
index dc6738c079..d33b79736d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ uptime-*.json
/Makefile
/Makefile.in
/aclocal.m4
+/ar-lib
/autom4te.cache
/build-stamp
/compile
@@ -94,11 +95,6 @@ uptime-*.json
/doc/tor.html
/doc/tor.html.in
/doc/tor.1.xml
-/doc/tor-fw-helper.1
-/doc/tor-fw-helper.1.in
-/doc/tor-fw-helper.html
-/doc/tor-fw-helper.html.in
-/doc/tor-fw-helper.1.xml
/doc/tor-gencert.1
/doc/tor-gencert.1.in
/doc/tor-gencert.html
@@ -196,6 +192,10 @@ uptime-*.json
/src/test/test-timers.exe
/src/test/test_workqueue.exe
+# /src/test/fuzz
+/src/test/fuzz/fuzz-*
+/src/test/fuzz/lf-fuzz-*
+
# /src/tools/
/src/tools/tor-checkkey
/src/tools/tor-resolve
@@ -214,12 +214,6 @@ uptime-*.json
/src/trunnel/libor-trunnel-testing.a
/src/trunnel/libor-trunnel.a
-# /src/tools/tor-fw-helper/
-/src/tools/tor-fw-helper/tor-fw-helper
-/src/tools/tor-fw-helper/tor-fw-helper.exe
-/src/tools/tor-fw-helper/Makefile
-/src/tools/tor-fw-helper/Makefile.in
-
# /src/win32/
/src/win32/Makefile
/src/win32/Makefile.in
diff --git a/ChangeLog b/ChangeLog
index 7a10a7ce96..7cc4f44881 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,967 @@
-Changes in version 0.2.9.5-rc - 2016-1?-??
+Changes in version 0.3.0.4-??? - 2017-02-??
+
+
+Changes in version 0.3.0.3-alpha - 2017-02-03
+ Tor 0.3.0.3-alpha fixes a few significant bugs introduced over the
+ 0.3.0.x development series, including some that could cause
+ authorities to behave badly. There is also a fix for a longstanding
+ bug that could prevent IPv6 exits from working. Tor 0.3.0.3-alpha also
+ includes some smaller features and bugfixes.
+
+ The Tor 0.3.0.x release series is now in patch-freeze: no additional
+ features will be considered for inclusion in 0.3.0.x. We suspect that
+ some bugs will probably remain, however, and we encourage people to
+ test this release.
+
+ o Major bugfixes (directory authority):
+ - During voting, when marking a relay as a probable sybil, do not
+ clear its BadExit flag: sybils can still be bad in other ways
+ too. (We still clear the other flags.) Fixes bug 21108; bugfix
+ on 0.2.0.13-alpha.
+ - When deciding whether we have just found a router to be reachable,
+ do not penalize it for not having performed an Ed25519 link
+ handshake if it does not claim to support an Ed25519 handshake.
+ Previously, we would treat such relays as non-running. Fixes bug
+ 21107; bugfix on 0.3.0.1-alpha.
+
+ o Major bugfixes (entry guards):
+ - Stop trying to build circuits through entry guards for which we
+ have no descriptor. Also, stop crashing in the case that we *do*
+ accidentally try to build a circuit in such a state. Fixes bug
+ 21242; bugfix on 0.3.0.1-alpha.
+
+ o Major bugfixes (IPv6 Exits):
+ - Stop rejecting all IPv6 traffic on Exits whose exit policy rejects
+ any IPv6 addresses. Instead, only reject a port over IPv6 if the
+ exit policy rejects that port on more than an IPv6 /16 of
+ addresses. This bug was made worse by 17027 in 0.2.8.1-alpha,
+ which rejected a relay's own IPv6 address by default. Fixes bug
+ 21357; bugfix on commit 004f3f4e53 in 0.2.4.7-alpha.
+
+ o Minor feature (client):
+ - Enable IPv6 traffic on the SocksPort by default. To disable this,
+ a user will have to specify "NoIPv6Traffic". Closes ticket 21269.
+
+ o Minor feature (fallback scripts):
+ - Add a check_existing mode to updateFallbackDirs.py, which checks
+ if fallbacks in the hard-coded list are working. Closes ticket
+ 20174. Patch by haxxpop.
+
+ o Minor features (ciphersuite selection):
+ - Clients now advertise a list of ciphersuites closer to the ones
+ preferred by Firefox. Closes part of ticket 15426.
+ - Allow relays to accept a wider range of ciphersuites, including
+ chacha20-poly1305 and AES-CCM. Closes the other part of 15426.
+
+ o Minor features (controller, configuration):
+ - Each of the *Port options, such as SocksPort, ORPort, ControlPort,
+ and so on, now comes with a __*Port variant that will not be saved
+ to the torrc file by the controller's SAVECONF command. This
+ change allows TorBrowser to set up a single-use domain socket for
+ each time it launches Tor. Closes ticket 20956.
+ - The GETCONF command can now query options that may only be
+ meaningful in context-sensitive lists. This allows the controller
+ to query the mixed SocksPort/__SocksPort style options introduced
+ in feature 20956. Implements ticket 21300.
+
+ o Minor features (portability, compilation):
+ - Autoconf now checks to determine if OpenSSL structures are opaque,
+ instead of explicitly checking for OpenSSL version numbers. Part
+ of ticket 21359.
+ - Support building with recent LibreSSL code that uses opaque
+ structures. Closes ticket 21359.
+
+ o Minor features (relay):
+ - We now allow separation of exit and relay traffic to different
+ source IP addresses, using the OutboundBindAddressExit and
+ OutboundBindAddressOR options respectively. Closes ticket 17975.
+ Written by Michael Sonntag.
+
+ o Minor bugfix (logging):
+ - Don't recommend the use of Tor2web in non-anonymous mode.
+ Recommending Tor2web is a bad idea because the client loses all
+ anonymity. Tor2web should only be used in specific cases by users
+ who *know* and understand the issues. Fixes bug 21294; bugfix
+ on 0.2.9.3-alpha.
+
+ o Minor bugfixes (client):
+ - Always recover from failures in extend_info_from_node(), in an
+ attempt to prevent any recurrence of bug 21242. Fixes bug 21372;
+ bugfix on 0.2.3.1-alpha.
+
+ o Minor bugfixes (client, entry guards):
+ - Fix a bug warning (with backtrace) when we fail a channel that
+ circuits to fallback directories on it. Fixes bug 21128; bugfix
+ on 0.3.0.1-alpha.
+ - Fix a spurious bug warning (with backtrace) when removing an
+ expired entry guard. Fixes bug 21129; bugfix on 0.3.0.1-alpha.
+ - Fix a bug of the new guard algorithm where tor could stall for up
+ to 10 minutes before retrying a guard after a long period of no
+ network. Fixes bug 21052; bugfix on 0.3.0.1-alpha.
+ - Do not try to build circuits until we have descriptors for our
+ primary entry guards. Related to fix for bug 21242.
+
+ o Minor bugfixes (configure, autoconf):
+ - Rename the configure option --enable-expensive-hardening to
+ --enable-fragile-hardening. Expensive hardening makes the tor
+ daemon abort when some kinds of issues are detected. Thus, it
+ makes tor more at risk of remote crashes but safer against RCE or
+ heartbleed bug category. We now try to explain this issue in a
+ message from the configure script. Fixes bug 21290; bugfix
+ on 0.2.5.4-alpha.
+
+ o Minor bugfixes (controller):
+ - Restore the (deprecated) DROPGUARDS controller command. Fixes bug
+ 20824; bugfix on 0.3.0.1-alpha.
+
+ o Minor bugfixes (hidden service):
+ - Clean up the code for expiring intro points with no associated
+ circuits. It was causing, rarely, a service with some expiring
+ introduction points to not open enough additional introduction
+ points. Fixes part of bug 21302; bugfix on 0.2.7.2-alpha.
+ - Stop setting the torrc option HiddenServiceStatistics to "0" just
+ because we're not a bridge or relay. Instead, we preserve whatever
+ value the user set (or didn't set). Fixes bug 21150; bugfix
+ on 0.2.6.2-alpha.
+ - Resolve two possible underflows which could lead to creating and
+ closing a lot of introduction point circuits in a non-stop loop.
+ Fixes bug 21302; bugfix on 0.2.7.2-alpha.
+
+ o Minor bugfixes (portability):
+ - Use "OpenBSD" compiler macro instead of "OPENBSD" or "__OpenBSD__".
+ It is supported by OpenBSD itself, and also by most OpenBSD
+ variants (such as Bitrig). Fixes bug 20980; bugfix
+ on 0.1.2.1-alpha.
+ - When mapping a file of length greater than SIZE_MAX, do not
+ silently truncate its contents. This issue could occur on 32 bit
+ systems with large file support and files which are larger than 4
+ GB. Fixes bug 21134; bugfix on 0.3.0.1-alpha.
+
+ o Minor bugfixes (tor-resolve):
+ - The tor-resolve command line tool now rejects hostnames over 255
+ characters in length. Previously, it would silently truncate them,
+ which could lead to bugs. Fixes bug 21280; bugfix on 0.0.9pre5.
+ Patch by "junglefowl".
+
+ o Minor bugfixes (Windows services):
+ - Be sure to initialize the monotonic time subsystem before using
+ it, even when running as an NT service. Fixes bug 21356; bugfix
+ on 0.2.9.1-alpha.
+
+
+Changes in version 0.3.0.2-alpha - 2017-01-23
+ Tor 0.3.0.2-alpha fixes a denial-of-service bug where an attacker could
+ cause relays and clients to crash, even if they were not built with
+ the --enable-expensive-hardening option. This bug affects all 0.2.9.x
+ versions, and also affects 0.3.0.1-alpha: all relays running an affected
+ version should upgrade.
+
+ Tor 0.3.0.2-alpha also improves how exit relays and clients handle DNS
+ time-to-live values, makes directory authorities enforce the 1-to-1
+ mapping of relay RSA identity keys to ED25519 identity keys, fixes a
+ client-side onion service reachability bug, does better at selecting
+ the set of fallback directories, and more.
+
+ o Major bugfixes (security, also in 0.2.9.9):
+ - Downgrade the "-ftrapv" option from "always on" to "only on when
+ --enable-expensive-hardening is provided." This hardening option, like
+ others, can turn survivable bugs into crashes--and having it on by
+ default made a (relatively harmless) integer overflow bug into a
+ denial-of-service bug. Fixes bug 21278 (TROVE-2017-001); bugfix on
+ 0.2.9.1-alpha.
+
+ o Major features (security):
+ - 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 features (directory authority, security):
+ - The default for AuthDirPinKeys is now 1: directory authorities
+ will reject relays where the RSA identity key matches a previously
+ seen value, but the Ed25519 key has changed. Closes ticket 18319.
+
+ o Major bugfixes (client, guard, crash):
+ - In circuit_get_global_origin_list(), return the actual list of
+ origin circuits. The previous version of this code returned the
+ list of all the circuits, and could have caused strange bugs,
+ including possible crashes. Fixes bug 21118; bugfix
+ on 0.3.0.1-alpha.
+
+ o Major bugfixes (client, onion service, also in 0.2.9.9):
+ - Fix a client-side onion service reachability bug, where multiple
+ socks requests to an onion service (or a single slow request)
+ could cause us to mistakenly mark some of the service's
+ introduction points as failed, and we cache that failure so
+ eventually we run out and can't reach the service. Also resolves a
+ mysterious "Remote server sent bogus reason code 65021" log
+ warning. The bug was introduced in ticket 17218, where we tried to
+ remember the circuit end reason as a uint16_t, which mangled
+ negative values. Partially fixes bug 21056 and fixes bug 20307;
+ bugfix on 0.2.8.1-alpha.
+
+ o Major bugfixes (DNS):
+ - 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 Minor features (controller):
+ - Add "GETINFO sr/current" and "GETINFO sr/previous" keys, to expose
+ shared-random values to the controller. Closes ticket 19925.
+
+ o Minor features (entry guards):
+ - Add UseEntryGuards to TEST_OPTIONS_DEFAULT_VALUES in order to not
+ break regression tests.
+ - Require UseEntryGuards when UseBridges is set, in order to make
+ sure bridges aren't bypassed. Resolves ticket 20502.
+
+ o Minor features (fallback directories):
+ - Select 200 fallback directories for each release. Closes
+ ticket 20881.
+ - Allow 3 fallback relays per operator, which is safe now that we
+ are choosing 200 fallback relays. Closes ticket 20912.
+ - Exclude relays affected by bug 20499 from the fallback list.
+ Exclude relays from the fallback list if they are running versions
+ known to be affected by bug 20499, or if in our tests they deliver
+ a stale consensus (i.e. one that expired more than 24 hours ago).
+ Closes ticket 20539.
+ - Reduce the minimum fallback bandwidth to 1 MByte/s. Part of
+ ticket 18828.
+ - Require fallback directories to have the same address and port for
+ 7 days (now that we have enough relays with this stability).
+ Relays whose OnionOO stability timer is reset on restart by bug
+ 18050 should upgrade to Tor 0.2.8.7 or later, which has a fix for
+ this issue. Closes ticket 20880; maintains short-term fix
+ in 0.2.8.2-alpha.
+ - Require fallbacks to have flags for 90% of the time (weighted
+ decaying average), rather than 95%. This allows at least 73% of
+ clients to bootstrap in the first 5 seconds without contacting an
+ authority. Part of ticket 18828.
+ - Annotate updateFallbackDirs.py with the bandwidth and consensus
+ weight for each candidate fallback. Closes ticket 20878.
+ - Make it easier to change the output sort order of fallbacks.
+ Closes ticket 20822.
+ - Display the relay fingerprint when downloading consensuses from
+ fallbacks. Closes ticket 20908.
+
+ o Minor features (geoip, also in 0.2.9.9):
+ - Update geoip and geoip6 to the January 4 2017 Maxmind GeoLite2
+ Country database.
+
+ o Minor features (next-gen onion service directories):
+ - Remove the "EnableOnionServicesV3" consensus parameter that we
+ introduced in 0.3.0.1-alpha: relays are now always willing to act
+ as v3 onion service directories. Resolves ticket 19899.
+
+ o Minor features (linting):
+ - Enhance the changes file linter to warn on Tor versions that are
+ prefixed with "tor-". Closes ticket 21096.
+
+ o Minor features (logging):
+ - In several places, describe unset ed25519 keys as "<unset>",
+ rather than the scary "AAAAAAAA...AAA". Closes ticket 21037.
+
+ o Minor bugfix (control protocol):
+ - The reply to a "GETINFO config/names" request via the control
+ protocol now spells the type "Dependent" correctly. This is a
+ breaking change in the control protocol. (The field seems to be
+ ignored by the most common known controllers.) Fixes bug 18146;
+ bugfix on 0.1.1.4-alpha.
+
+ o Minor bugfixes (bug resilience):
+ - Fix an unreachable size_t overflow in base64_decode(). Fixes bug
+ 19222; bugfix on 0.2.0.9-alpha. Found by Guido Vranken; fixed by
+ Hans Jerry Illikainen.
+
+ o Minor bugfixes (build):
+ - Replace obsolete Autoconf macros with their modern equivalent and
+ prevent similar issues in the future. Fixes bug 20990; bugfix
+ on 0.1.0.1-rc.
+
+ o Minor bugfixes (client, guards):
+ - Fix bug where Tor would think that there are circuits waiting for
+ better guards even though those circuits have been freed. Fixes
+ bug 21142; bugfix on 0.3.0.1-alpha.
+
+ o Minor bugfixes (config):
+ - Don't assert on startup when trying to get the options list and
+ LearnCircuitBuildTimeout is set to 0: we are currently parsing the
+ options so of course they aren't ready yet. Fixes bug 21062;
+ bugfix on 0.2.9.3-alpha.
+
+ o Minor bugfixes (controller):
+ - Make the GETINFO interface for inquiring about entry guards
+ support the new guards backend. Fixes bug 20823; bugfix
+ on 0.3.0.1-alpha.
+
+ o Minor bugfixes (dead code):
+ - Remove a redundant check for PidFile changes at runtime in
+ options_transition_allowed(): this check is already performed
+ regardless of whether the sandbox is active. Fixes bug 21123;
+ bugfix on 0.2.5.4-alpha.
+
+ o Minor bugfixes (documentation):
+ - Update the tor manual page to document every option that can not
+ be changed while tor is running. Fixes bug 21122.
+
+ o Minor bugfixes (fallback directories):
+ - Stop failing when a relay has no uptime data in
+ updateFallbackDirs.py. Fixes bug 20945; bugfix on 0.2.8.1-alpha.
+ - Avoid checking fallback candidates' DirPorts if they are down in
+ OnionOO. When a relay operator has multiple relays, this
+ prioritizes relays that are up over relays that are down. Fixes
+ bug 20926; bugfix on 0.2.8.3-alpha.
+ - Stop failing when OUTPUT_COMMENTS is True in updateFallbackDirs.py.
+ Fixes bug 20877; bugfix on 0.2.8.3-alpha.
+
+ o Minor bugfixes (guards, bootstrapping):
+ - When connecting to a directory guard during bootstrap, do not mark
+ the guard as successful until we receive a good-looking directory
+ response from it. Fixes bug 20974; bugfix on 0.3.0.1-alpha.
+
+ o Minor bugfixes (onion services):
+ - Fix the config reload pruning of old vs new services so it
+ actually works when both ephemeral and non-ephemeral services are
+ configured. Fixes bug 21054; bugfix on 0.3.0.1-alpha.
+ - Allow the number of introduction points to be as low as 0, rather
+ than as low as 3. Fixes bug 21033; bugfix on 0.2.7.2-alpha.
+
+ o Minor bugfixes (IPv6):
+ - Make IPv6-using clients try harder to find an IPv6 directory
+ server. Fixes bug 20999; bugfix on 0.2.8.2-alpha.
+ - When IPv6 addresses have not been downloaded yet (microdesc
+ consensus documents don't list relay IPv6 addresses), use hard-
+ coded addresses for authorities, fallbacks, and configured
+ bridges. Now IPv6-only clients can use microdescriptors. Fixes bug
+ 20996; bugfix on b167e82 from 19608 in 0.2.8.5-alpha.
+
+ o Minor bugfixes (memory leaks):
+ - Fix a memory leak when configuring hidden services. Fixes bug
+ 20987; bugfix on 0.3.0.1-alpha.
+
+ o Minor bugfixes (portability, also in 0.2.9.9):
+ - Avoid crashing when Tor is built using headers that contain
+ CLOCK_MONOTONIC_COARSE, but then tries to run on an older kernel
+ without CLOCK_MONOTONIC_COARSE. Fixes bug 21035; bugfix
+ on 0.2.9.1-alpha.
+ - Fix Libevent detection on platforms without Libevent 1 headers
+ installed. Fixes bug 21051; bugfix on 0.2.9.1-alpha.
+
+ o Minor bugfixes (relay):
+ - Honor DataDirectoryGroupReadable when tor is a relay. Previously,
+ initializing the keys would reset the DataDirectory to 0700
+ instead of 0750 even if DataDirectoryGroupReadable was set to 1.
+ Fixes bug 19953; bugfix on 0.0.2pre16. Patch by "redfish".
+
+ o Minor bugfixes (testing):
+ - Remove undefined behavior from the backtrace generator by removing
+ its signal handler. Fixes bug 21026; bugfix on 0.2.5.2-alpha.
+
+ o Minor bugfixes (unit tests):
+ - Allow the unit tests to pass even when DNS lookups of bogus
+ addresses do not fail as expected. Fixes bug 20862 and 20863;
+ bugfix on unit tests introduced in 0.2.8.1-alpha
+ through 0.2.9.4-alpha.
+
+ o Code simplification and refactoring:
+ - Refactor code to manipulate global_origin_circuit_list into
+ separate functions. Closes ticket 20921.
+
+ o Documentation (formatting):
+ - Clean up formatting of tor.1 man page and HTML doc, where <pre>
+ blocks were incorrectly appearing. Closes ticket 20885.
+
+ o Documentation (man page):
+ - Clarify many options in tor.1 and add some min/max values for
+ HiddenService options. Closes ticket 21058.
+
+
+Changes in version 0.2.9.9 - 2017-01-23
+ Tor 0.2.9.9 fixes a denial-of-service bug where an attacker could
+ cause relays and clients to crash, even if they were not built with
+ the --enable-expensive-hardening option. This bug affects all 0.2.9.x
+ versions, and also affects 0.3.0.1-alpha: all relays running an affected
+ version should upgrade.
+
+ This release also resolves a client-side onion service reachability
+ bug, and resolves a pair of small portability issues.
+
+ o Major bugfixes (security):
+ - Downgrade the "-ftrapv" option from "always on" to "only on when
+ --enable-expensive-hardening is provided." This hardening option,
+ like others, can turn survivable bugs into crashes -- and having
+ it on by default made a (relatively harmless) integer overflow bug
+ into a denial-of-service bug. Fixes bug 21278 (TROVE-2017-001);
+ bugfix on 0.2.9.1-alpha.
+
+ o Major bugfixes (client, onion service):
+ - Fix a client-side onion service reachability bug, where multiple
+ socks requests to an onion service (or a single slow request)
+ could cause us to mistakenly mark some of the service's
+ introduction points as failed, and we cache that failure so
+ eventually we run out and can't reach the service. Also resolves a
+ mysterious "Remote server sent bogus reason code 65021" log
+ warning. The bug was introduced in ticket 17218, where we tried to
+ remember the circuit end reason as a uint16_t, which mangled
+ negative values. Partially fixes bug 21056 and fixes bug 20307;
+ bugfix on 0.2.8.1-alpha.
+
+ o Minor features (geoip):
+ - Update geoip and geoip6 to the January 4 2017 Maxmind GeoLite2
+ Country database.
+
+ o Minor bugfixes (portability):
+ - Avoid crashing when Tor is built using headers that contain
+ CLOCK_MONOTONIC_COARSE, but then tries to run on an older kernel
+ without CLOCK_MONOTONIC_COARSE. Fixes bug 21035; bugfix
+ on 0.2.9.1-alpha.
+ - Fix Libevent detection on platforms without Libevent 1 headers
+ installed. Fixes bug 21051; bugfix on 0.2.9.1-alpha.
+
+
+Changes in version 0.3.0.1-alpha - 2016-12-19
+ Tor 0.3.0.1-alpha is the first alpha release in the 0.3.0 development
+ series. It strengthens Tor's link and circuit handshakes by
+ identifying relays by their Ed25519 keys, improves the algorithm that
+ clients use to choose and maintain their list of guards, and includes
+ additional backend support for the next-generation hidden service
+ design. It also contains numerous other small features and
+ improvements to security, correctness, and performance.
+
+ Below are the changes since 0.2.9.8.
+
+ o Major features (guard selection algorithm):
+ - Tor's guard selection algorithm has been redesigned from the
+ ground up, to better support unreliable networks and restrictive
+ sets of entry nodes, and to better resist guard-capture attacks by
+ hostile local networks. Implements proposal 271; closes
+ ticket 19877.
+
+ o Major features (next-generation hidden services):
+ - Relays can now handle v3 ESTABLISH_INTRO cells as specified by
+ prop224 aka "Next Generation Hidden Services". Service and clients
+ don't use this functionality yet. Closes ticket 19043. Based on
+ initial code by Alec Heifetz.
+ - Relays now support the HSDir version 3 protocol, so that they can
+ can store and serve v3 descriptors. This is part of the next-
+ generation onion service work detailled in proposal 224. Closes
+ ticket 17238.
+
+ o Major features (protocol, ed25519 identity keys):
+ - Relays now use Ed25519 to prove their Ed25519 identities and to
+ one another, and to clients. This algorithm is faster and more
+ secure than the RSA-based handshake we've been doing until now.
+ Implements the second big part of proposal 220; Closes
+ ticket 15055.
+ - Clients now support including Ed25519 identity keys in the EXTEND2
+ cells they generate. By default, this is controlled by a consensus
+ parameter, currently disabled. You can turn this feature on for
+ testing by setting ExtendByEd25519ID in your configuration. This
+ might make your traffic appear different than the traffic
+ generated by other users, however. Implements part of ticket
+ 15056; part of proposal 220.
+ - Relays now understand requests to extend to other relays by their
+ Ed25519 identity keys. When an Ed25519 identity key is included in
+ an EXTEND2 cell, the relay will only extend the circuit if the
+ other relay can prove ownership of that identity. Implements part
+ of ticket 15056; part of proposal 220.
+
+ o Major bugfixes (scheduler):
+ - Actually compare circuit policies in ewma_cmp_cmux(). This bug
+ caused the channel scheduler to behave more or less randomly,
+ rather than preferring channels with higher-priority circuits.
+ Fixes bug 20459; bugfix on 0.2.6.2-alpha.
+
+ o Minor features (controller):
+ - When HSFETCH arguments cannot be parsed, say "Invalid argument"
+ rather than "unrecognized." Closes ticket 20389; patch from
+ Ivan Markin.
+
+ o Minor features (diagnostic, directory client):
+ - Warn when we find an unexpected inconsistency in directory
+ download status objects. Prevents some negative consequences of
+ bug 20593.
+
+ o Minor features (directory authority):
+ - Add a new authority-only AuthDirTestEd25519LinkKeys option (on by
+ default) to control whether authorities should try to probe relays
+ by their Ed25519 link keys. This option will go away in a few
+ releases--unless we encounter major trouble in our ed25519 link
+ protocol rollout, in which case it will serve as a safety option.
+
+ o Minor features (directory cache):
+ - Relays and bridges will now refuse to serve the consensus they
+ have if they know it is too old for a client to use. Closes
+ ticket 20511.
+
+ o Minor features (ed25519 link handshake):
+ - Advertise support for the ed25519 link handshake using the
+ 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):
+ - 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.
+
+ o Minor features (infrastructure):
+ - Implement smartlist_add_strdup() function. Replaces the use of
+ smartlist_add(sl, tor_strdup(str)). Closes ticket 20048.
+
+ o Minor bugfixes (client):
+ - When clients that use bridges start up with a cached consensus on
+ disk, they were ignoring it and downloading a new one. Now they
+ use the cached one. Fixes bug 20269; bugfix on 0.2.3.12-alpha.
+
+ o Minor bugfixes (configuration):
+ - Accept non-space whitespace characters after the severity level in
+ the `Log` option. Fixes bug 19965; bugfix on 0.2.1.1-alpha.
+ - Support "TByte" and "TBytes" units in options given in bytes.
+ "TB", "terabyte(s)", "TBit(s)" and "terabit(s)" were already
+ supported. Fixes bug 20622; bugfix on 0.2.0.14-alpha.
+
+ o Minor bugfixes (consensus weight):
+ - Add new consensus method that initializes bw weights to 1 instead
+ of 0. This prevents a zero weight from making it all the way to
+ the end (happens in small testing networks) and causing an error.
+ Fixes bug 14881; bugfix on 0.2.2.17-alpha.
+
+ o Minor bugfixes (descriptors):
+ - Correctly recognise downloaded full descriptors as valid, even
+ when using microdescriptors as circuits. This affects clients with
+ FetchUselessDescriptors set, and may affect directory authorities.
+ Fixes bug 20839; bugfix on 0.2.3.2-alpha.
+
+ o Minor bugfixes (directory system):
+ - Download all consensus flavors, descriptors, and authority
+ certificates when FetchUselessDescriptors is set, regardless of
+ whether tor is a directory cache or not. Fixes bug 20667; bugfix
+ on all recent tor versions.
+ - Bridges and relays now use microdescriptors (like clients do)
+ rather than old-style router descriptors. Now bridges will blend
+ in with clients in terms of the circuits they build. Fixes bug
+ 6769; bugfix on 0.2.3.2-alpha.
+
+ o Minor bugfixes (ed25519 certificates):
+ - Correctly interpret ed25519 certificates that would expire some
+ time after 19 Jan 2038. Fixes bug 20027; bugfix on 0.2.7.2-alpha.
+
+ o Minor bugfixes (hidden services):
+ - Stop ignoring misconfigured hidden services. Instead, refuse to
+ start tor until the misconfigurations have been corrected. Fixes
+ bug 20559; bugfix on multiple commits in 0.2.7.1-alpha
+ and earlier.
+
+ o Minor bugfixes (memory leak at exit):
+ - Fix a small harmless memory leak at exit of the previously unused
+ RSA->Ed identity cross-certificate. Fixes bug 17779; bugfix
+ on 0.2.7.2-alpha.
+
+ o Minor bugfixes (util):
+ - When finishing writing a file to disk, if we were about to replace
+ the file with the temporary file created before and we fail to
+ replace it, remove the temporary file so it doesn't stay on disk.
+ Fixes bug 20646; bugfix on tor-0.2.0.7-alpha. Patch by fk.
+
+ o Minor bugfixes (Windows):
+ - Check for getpagesize before using it to mmap files. This fixes
+ compilation in some MinGW environments. Fixes bug 20530; bugfix on
+ 0.1.2.1-alpha. Reported by "ice".
+
+ o Code simplification and refactoring:
+ - Abolish all global guard context in entrynodes.c; replace with new
+ guard_selection_t structure as preparation for proposal 271.
+ Closes ticket 19858.
+ - Introduce rend_service_is_ephemeral() that tells if given onion
+ service is ephemeral. Replace unclear NULL-checkings for service
+ directory with this function. Closes ticket 20526.
+ - Extract magic numbers in circuituse.c into defined variables.
+ - Refactor circuit_is_available_for_use to remove unnecessary check.
+ - Refactor circuit_predict_and_launch_new for readability and
+ testability. Closes ticket 18873.
+ - Refactor large if statement in purpose_needs_anonymity to use
+ switch statement instead. Closes part of ticket 20077.
+ - Refactor the hashing API to return negative values for errors, as
+ is done as throughout the codebase. Closes ticket 20717.
+ - Remove data structures that were used to index or_connection
+ objects by their RSA identity digests. These structures are fully
+ redundant with the similar structures used in the
+ channel abstraction.
+ - Remove duplicate code in the channel_write_*cell() functions.
+ Closes ticket 13827; patch from Pingl.
+ - Remove redundant behavior of is_sensitive_dir_purpose, refactor to
+ use only purpose_needs_anonymity. Closes part of ticket 20077.
+ - The code to generate and parse EXTEND and EXTEND2 cells has been
+ replaced with code automatically generated by the
+ "trunnel" utility.
+
+ o Documentation:
+ - Include the "TBits" unit in Tor's man page. Fixes part of bug
+ 20622; bugfix on tor-0.2.5.1-alpha.
+ - Change '1' to 'weight_scale' in consensus bw weights calculation
+ comments, as that is reality. Closes ticket 20273. Patch
+ from pastly.
+ - Correct the value for AuthDirGuardBWGuarantee in the manpage, from
+ 250 KBytes to 2 MBytes. Fixes bug 20435; bugfix
+ on tor-0.2.5.6-alpha.
+ - Stop the man page from incorrectly stating that HiddenServiceDir
+ must already exist. Fixes 20486.
+ - Clarify that when ClientRejectInternalAddresses is enabled (which
+ is the default), multicast DNS hostnames for machines on the local
+ network (of the form *.local) are also rejected. Closes
+ ticket 17070.
+
+ o Removed features:
+ - The AuthDirMaxServersPerAuthAddr option no longer exists: The same
+ limit for relays running on a single IP applies to authority IP
+ addresses as well as to non-authority IP addresses. Closes
+ ticket 20960.
+ - The UseDirectoryGuards torrc option no longer exists: all users
+ that use entry guards will also use directory guards. Related to
+ proposal 271; implements part of ticket 20831.
+
+ o Testing:
+ - New unit tests for tor_htonll(). Closes ticket 19563. Patch
+ from "overcaffeinated".
+ - Perform the coding style checks when running the tests and fail
+ when coding style violations are found. Closes ticket 5500.
+ - Add tests for networkstatus_compute_bw_weights_v10.
+ - Add unit tests circuit_predict_and_launch_new.
+ - Extract dummy_origin_circuit_new so it can be used by other
+ test functions.
+
+
+Changes in version 0.2.8.12 - 2016-12-19
+ Tor 0.2.8.12 backports a fix for a medium-severity issue (bug 21018
+ below) where Tor clients could crash when attempting to visit a
+ hostile hidden service. Clients are recommended to upgrade as packages
+ become available for their systems.
+
+ It also includes an updated list of fallback directories, backported
+ from 0.2.9.
+
+ Now that the Tor 0.2.9 series is stable, only major bugfixes will be
+ backported to 0.2.8 in the future.
+
+ o Major bugfixes (parsing, security, backported from 0.2.9.8):
+ - Fix a bug in parsing that could cause clients to read a single
+ byte past the end of an allocated region. This bug could be used
+ to cause hardened clients (built with --enable-expensive-hardening)
+ to crash if they tried to visit a hostile hidden service. Non-
+ hardened clients are only affected depending on the details of
+ their platform's memory allocator. Fixes bug 21018; bugfix on
+ 0.2.0.8-alpha. Found by using libFuzzer. Also tracked as TROVE-
+ 2016-12-002 and as CVE-2016-1254.
+
+ o Minor features (fallback directory list, backported from 0.2.9.8):
+ - Replace the 81 remaining fallbacks of the 100 originally
+ introduced in Tor 0.2.8.3-alpha in March 2016, with a list of 177
+ fallbacks (123 new, 54 existing, 27 removed) generated in December
+ 2016. Resolves ticket 20170.
+
+ o Minor features (geoip, backported from 0.2.9.7-rc):
+ - Update geoip and geoip6 to the December 7 2016 Maxmind GeoLite2
+ Country database.
+
+
+Changes in version 0.2.9.8 - 2016-12-19
+ Tor 0.2.9.8 is the first stable release of the Tor 0.2.9 series.
+
+ The Tor 0.2.9 series makes mandatory a number of security features
+ that were formerly optional. It includes support for a new shared-
+ randomness protocol that will form the basis for next generation
+ hidden services, includes a single-hop hidden service mode for
+ optimizing .onion services that don't actually want to be hidden,
+ tries harder not to overload the directory authorities with excessive
+ downloads, and supports a better protocol versioning scheme for
+ improved compatibility with other implementations of the Tor protocol.
+
+ And of course, there are numerous other bugfixes and improvements.
+
+ This release also includes a fix for a medium-severity issue (bug
+ 21018 below) where Tor clients could crash when attempting to visit a
+ hostile hidden service. Clients are recommended to upgrade as packages
+ become available for their systems.
+
+ Below are the changes since 0.2.9.7-rc. For a list of all changes
+ since 0.2.8, see the ReleaseNotes file.
+
+ o Major bugfixes (parsing, security):
+ - Fix a bug in parsing that could cause clients to read a single
+ byte past the end of an allocated region. This bug could be used
+ to cause hardened clients (built with --enable-expensive-hardening)
+ to crash if they tried to visit a hostile hidden service. Non-
+ hardened clients are only affected depending on the details of
+ their platform's memory allocator. Fixes bug 21018; bugfix on
+ 0.2.0.8-alpha. Found by using libFuzzer. Also tracked as TROVE-
+ 2016-12-002 and as CVE-2016-1254.
+
+ o Minor features (fallback directory list):
+ - Replace the 81 remaining fallbacks of the 100 originally
+ introduced in Tor 0.2.8.3-alpha in March 2016, with a list of 177
+ fallbacks (123 new, 54 existing, 27 removed) generated in December
+ 2016. Resolves ticket 20170.
+
+
+Changes in version 0.2.9.7-rc - 2016-12-12
+ Tor 0.2.9.7-rc fixes a few small bugs remaining in Tor 0.2.9.6-rc,
+ including a few that had prevented tests from passing on
+ some platforms.
+
+ o Minor features (geoip):
+ - Update geoip and geoip6 to the December 7 2016 Maxmind GeoLite2
+ Country database.
+
+ o Minor bugfix (build):
+ - The current Git revision when building from a local repository is
+ now detected correctly when using git worktrees. Fixes bug 20492;
+ bugfix on 0.2.3.9-alpha.
+
+ o Minor bugfixes (directory authority):
+ - When computing old Tor protocol line version in protover, we were
+ looking at 0.2.7.5 twice instead of a specific case for
+ 0.2.9.1-alpha. Fixes bug 20810; bugfix on 0.2.9.4-alpha.
+
+ o Minor bugfixes (download scheduling):
+ - Resolve a "bug" warning when considering a download schedule whose
+ delay had approached INT_MAX. Fixes 20875; bugfix on 0.2.9.5-alpha.
+
+ o Minor bugfixes (logging):
+ - Downgrade a harmless log message about the
+ pending_entry_connections list from "warn" to "info". Mitigates
+ bug 19926.
+
+ o Minor bugfixes (memory leak):
+ - Fix a small memory leak when receiving AF_UNIX connections on a
+ SocksPort. Fixes bug 20716; bugfix on 0.2.6.3-alpha.
+ - When moving a signed descriptor object from a source to an
+ existing destination, free the allocated memory inside that
+ destination object. Fixes bug 20715; bugfix on 0.2.8.3-alpha.
+
+ o Minor bugfixes (memory leak, use-after-free, linux seccomp2 sandbox):
+ - Fix a memory leak and use-after-free error when removing entries
+ from the sandbox's getaddrinfo() cache. Fixes bug 20710; bugfix on
+ 0.2.5.5-alpha. Patch from "cypherpunks".
+
+ o Minor bugfixes (portability):
+ - Use the correct spelling of MAC_OS_X_VERSION_10_12 on configure.ac
+ Fixes bug 20935; bugfix on 0.2.9.6-rc.
+
+ o Minor bugfixes (unit tests):
+ - Stop expecting NetBSD unit tests to report success for ipfw. Part
+ of a fix for bug 19960; bugfix on 0.2.9.5-alpha.
+ - Fix tolerances in unit tests for monotonic time comparisons
+ between nanoseconds and microseconds. Previously, we accepted a 10
+ us difference only, which is not realistic on every platform's
+ clock_gettime(). Fixes bug 19974; bugfix on 0.2.9.1-alpha.
+ - Remove a double-free in the single onion service unit test. Stop
+ ignoring a return value. Make future changes less error-prone.
+ Fixes bug 20864; bugfix on 0.2.9.6-rc.
+
+
+Changes in version 0.2.8.11 - 2016-12-08
+ Tor 0.2.8.11 backports fixes for additional portability issues that
+ could prevent Tor from building correctly on OSX Sierra, or with
+ OpenSSL 1.1. Affected users should upgrade; others can safely stay
+ with 0.2.8.10.
+
+ o Minor bugfixes (portability):
+ - Avoid compilation errors when building on OSX Sierra. Sierra began
+ to support the getentropy() and clock_gettime() APIs, but created
+ a few problems in doing so. Tor 0.2.9 has a more thorough set of
+ workarounds; in 0.2.8, we are just using the /dev/urandom and mach
+ monotonic time interfaces. Fixes bug 20865. Bugfix
+ on 0.2.8.1-alpha.
+
+ o Minor bugfixes (portability, backport from 0.2.9.5-alpha):
+ - Fix compilation with OpenSSL 1.1 and less commonly-used CPU
+ architectures. Closes ticket 20588.
+
+
+Changes in version 0.2.8.10 - 2016-12-02
+ Tor 0.2.8.10 backports a fix for a bug that would sometimes make clients
+ unusable after they left standby mode. It also backports fixes for
+ a few portability issues and a small but problematic memory leak.
+
+ o Major bugfixes (client reliability, backport from 0.2.9.5-alpha):
+ - When Tor leaves standby because of a new application request, open
+ circuits as needed to serve that request. Previously, we would
+ potentially wait a very long time. Fixes part of bug 19969; bugfix
+ on 0.2.8.1-alpha.
+
+ o Major bugfixes (client performance, backport from 0.2.9.5-alpha):
+ - Clients now respond to new application stream requests immediately
+ when they arrive, rather than waiting up to one second before
+ starting to handle them. Fixes part of bug 19969; bugfix
+ on 0.2.8.1-alpha.
+
+ o Minor bugfixes (portability, backport from 0.2.9.6-rc):
+ - Work around a bug in the OSX 10.12 SDK that would prevent us from
+ successfully targeting earlier versions of OSX. Resolves
+ ticket 20235.
+
+ o Minor bugfixes (portability, backport from 0.2.9.5-alpha):
+ - Fix implicit conversion warnings under OpenSSL 1.1. Fixes bug
+ 20551; bugfix on 0.2.1.1-alpha.
+
+ o Minor bugfixes (relay, backport from 0.2.9.5-alpha):
+ - Work around a memory leak in OpenSSL 1.1 when encoding public
+ keys. Fixes bug 20553; bugfix on 0.0.2pre8.
+
+ o Minor features (geoip):
+ - Update geoip and geoip6 to the November 3 2016 Maxmind GeoLite2
+ Country database.
+
+Changes in version 0.2.9.6-rc - 2016-12-02
+ Tor 0.2.9.6-rc fixes a few remaining bugs found in the previous alpha
+ version. We hope that it will be ready to become stable soon, and we
+ encourage everyone to test this release. If no showstopper bugs are
+ found here, the next 0.2.9 release will be stable.
+
+ o Major bugfixes (relay, resolver, logging):
+ - For relays that don't know their own address, avoid attempting a
+ local hostname resolve for each descriptor we download. This
+ will cut down on the number of "Success: chose address 'x.x.x.x'"
+ log lines, and also avoid confusing clock jumps if the resolver
+ is slow. Fixes bugs 20423 and 20610; bugfix on 0.2.8.1-alpha.
+
+ o Minor bugfixes (client, fascistfirewall):
+ - Avoid spurious warnings when ReachableAddresses or FascistFirewall
+ is set. Fixes bug 20306; bugfix on 0.2.8.2-alpha.
+
+ o Minor bugfixes (hidden services):
+ - Stop ignoring the anonymity status of saved keys for hidden
+ services and single onion services when first starting tor.
+ Instead, refuse to start tor if any hidden service key has been
+ used in a different hidden service anonymity mode. Fixes bug
+ 20638; bugfix on 17178 in 0.2.9.3-alpha; reported by ahf.
+
+ o Minor bugfixes (portability):
+ - Work around a bug in the OSX 10.12 SDK that would prevent us from
+ successfully targeting earlier versions of OSX. Resolves
+ ticket 20235.
+ - Run correctly when built on Windows build environments that
+ require _vcsprintf(). Fixes bug 20560; bugfix on 0.2.2.11-alpha.
+
+ o Minor bugfixes (single onion services, Tor2web):
+ - Stop complaining about long-term one-hop circuits deliberately
+ created by single onion services and Tor2web. These log messages
+ are intended to diagnose issue 8387, which relates to circuits
+ hanging around forever for no reason. Fixes bug 20613; bugfix on
+ 0.2.9.1-alpha. Reported by "pastly".
+
+ o Minor bugfixes (unit tests):
+ - Stop spurious failures in the local interface address discovery
+ unit tests. Fixes bug 20634; bugfix on 0.2.8.1-alpha; patch by
+ Neel Chauhan.
+
+ o Documentation:
+ - Correct the minimum bandwidth value in torrc.sample, and queue a
+ corresponding change for torrc.minimal. Closes ticket 20085.
+
+
+Changes in version 0.2.9.5-alpha - 2016-11-08
+ Tor 0.2.9.5-alpha fixes numerous bugs discovered in the previous alpha
+ version. We believe one or two probably remain, and we encourage
+ everyone to test this release.
+
+ o Major bugfixes (client performance):
+ - Clients now respond to new application stream requests immediately
+ when they arrive, rather than waiting up to one second before
+ starting to handle them. Fixes part of bug 19969; bugfix
+ on 0.2.8.1-alpha.
+
+ o Major bugfixes (client reliability):
+ - When Tor leaves standby because of a new application request, open
+ circuits as needed to serve that request. Previously, we would
+ potentially wait a very long time. Fixes part of bug 19969; bugfix
+ on 0.2.8.1-alpha.
+
+ o Major bugfixes (download scheduling):
+ - When using an exponential backoff schedule, do not give up on
+ downloading just because we have failed a bunch of times. Since
+ each delay is longer than the last, retrying indefinitely won't
+ hurt. Fixes bug 20536; bugfix on 0.2.9.1-alpha.
+ - If a consensus expires while we are waiting for certificates to
+ download, stop waiting for certificates.
+ - If we stop waiting for certificates less than a minute after we
+ started downloading them, do not consider the certificate download
+ failure a separate failure. Fixes bug 20533; bugfix
+ on 0.2.0.9-alpha.
+ - Remove the maximum delay on exponential-backoff scheduling. Since
+ we now allow an infinite number of failures (see ticket 20536), we
+ must now allow the time to grow longer on each failure. Fixes part
+ of bug 20534; bugfix on 0.2.9.1-alpha.
+ - Make our initial download delays closer to those from 0.2.8. Fixes
+ another part of bug 20534; bugfix on 0.2.9.1-alpha.
+ - When determining when to download a directory object, handle times
+ after 2038 if the operating system supports them. (Someday this
+ will be important!) Fixes bug 20587; bugfix on 0.2.8.1-alpha.
+ - When using exponential backoff in test networks, use a lower
+ exponent, so the delays do not vary as much. This helps test
+ networks bootstrap consistently. Fixes bug 20597; bugfix on 20499.
+
+ o Minor features (geoip):
+ - Update geoip and geoip6 to the November 3 2016 Maxmind GeoLite2
+ Country database.
+
+ o Minor bugfixes (client directory scheduling):
+ - Treat "relay too busy to answer request" as a failed request and a
+ reason to back off on our retry frequency. This is safe now that
+ exponential backoffs retry indefinitely, and avoids a bug where we
+ would reset our download schedule erroneously. Fixes bug 20593;
+ bugfix on 0.2.9.1-alpha.
+
+ o Minor bugfixes (client, logging):
+ - Remove a BUG warning in circuit_pick_extend_handshake(). Instead,
+ assume all nodes support EXTEND2. Use ntor whenever a key is
+ available. Fixes bug 20472; bugfix on 0.2.9.3-alpha.
+ - On DNSPort, stop logging a BUG warning on a failed hostname
+ lookup. Fixes bug 19869; bugfix on 0.2.9.1-alpha.
+
+ o Minor bugfixes (hidden services):
+ - When configuring hidden services, check every hidden service
+ directory's permissions. Previously, we only checked the last
+ hidden service. Fixes bug 20529; bugfix the work to fix 13942
+ in 0.2.6.2-alpha.
+
+ o Minor bugfixes (portability):
+ - Fix compilation with OpenSSL 1.1 and less commonly-used CPU
+ architectures. Closes ticket 20588.
+ - Use ECDHE ciphers instead of ECDH in tortls tests. LibreSSL has
+ removed the ECDH ciphers which caused the tests to fail on
+ platforms which use it. Fixes bug 20460; bugfix on 0.2.8.1-alpha.
+ - Fix implicit conversion warnings under OpenSSL 1.1. Fixes bug
+ 20551; bugfix on 0.2.1.1-alpha.
+
+ o Minor bugfixes (relay bootstrap):
+ - Ensure relays don't make multiple connections during bootstrap.
+ Fixes bug 20591; bugfix on 0.2.8.1-alpha.
+
+ o Minor bugfixes (relay):
+ - Work around a memory leak in OpenSSL 1.1 when encoding public
+ keys. Fixes bug 20553; bugfix on 0.0.2pre8.
+ - Avoid a small memory leak when informing worker threads about
+ rotated onion keys. Fixes bug 20401; bugfix on 0.2.6.3-alpha.
+ - Do not try to parallelize workers more than 16x without the user
+ explicitly configuring us to do so, even if we do detect more than
+ 16 CPU cores. Fixes bug 19968; bugfix on 0.2.3.1-alpha.
+
+ o Minor bugfixes (single onion services):
+ - Start correctly when creating a single onion service in a
+ directory that did not previously exist. Fixes bug 20484; bugfix
+ on 0.2.9.3-alpha.
+
+ o Minor bugfixes (testing):
+ - Avoid a unit test failure on systems with over 16 detectable CPU
+ cores. Fixes bug 19968; bugfix on 0.2.3.1-alpha.
+
+ o Documentation:
+ - Clarify that setting HiddenServiceNonAnonymousMode requires you to
+ also set "SOCKSPort 0". Fixes bug 20487; bugfix on 0.2.9.3-alpha.
+ - Module-level documentation for several more modules. Closes
+ tickets 19287 and 19290.
Changes in version 0.2.8.9 - 2016-10-17
@@ -123,7 +1086,7 @@ Changes in version 0.2.9.4-alpha - 2016-10-17
o Minor bugfixes (getpass):
- Defensively fix a non-triggerable heap corruption at do_getpass()
to protect ourselves from mistakes in the future. Fixes bug
- #19223; bugfix on 0.2.7.3-rc. Bug found by Guido Vranken, patch
+ 19223; bugfix on 0.2.7.3-rc. Bug found by Guido Vranken, patch
by nherring.
o Minor bugfixes (hidden service):
@@ -194,7 +1157,7 @@ Changes in version 0.2.9.3-alpha - 2016-09-23
o Major bugfixes (circuit building):
- Hidden service client-to-intro-point and service-to-rendezvous-
- point cicruits use the TAP key supplied by the protocol, to avoid
+ point circuits use the TAP key supplied by the protocol, to avoid
epistemic attacks. Fixes bug 19163; bugfix on 0.2.4.18-rc.
o Major bugfixes (compilation, OpenBSD):
@@ -216,8 +1179,9 @@ Changes in version 0.2.9.3-alpha - 2016-09-23
OpenSSL 0.9.7 or later since 2009. Closes ticket 19998.
o Minor feature (fallback directories):
- - Remove broken entries from the hard-coded fallback directory list.
- Closes ticket 20190; patch by teor.
+ - Remove 8 fallbacks that are no longer suitable, leaving 81 of the
+ 100 fallbacks originally introduced in Tor 0.2.8.2-alpha in March
+ 2016. Closes ticket 20190; patch by teor.
o Minor features (geoip, also in 0.2.8.8):
- Update geoip and geoip6 to the September 6 2016 Maxmind GeoLite2
@@ -312,7 +1276,7 @@ Changes in version 0.2.9.3-alpha - 2016-09-23
- Document the default PathsNeededToBuildCircuits value that's used
by clients when the directory authorities don't set
min_paths_for_circs_pct. Fixes bug 20117; bugfix on 02c320916e02
- in tor-0.2.4.10-alpha. Patch by teor, reported by Jesse V.
+ in 0.2.4.10-alpha. Patch by teor, reported by Jesse V.
- Fix manual for the User option: it takes a username, not a UID.
Fixes bug 19122; bugfix on 0.0.2pre16 (the first version to have
a manpage!).
@@ -351,12 +1315,12 @@ Changes in version 0.2.9.3-alpha - 2016-09-23
o Minor bugfixes (options):
- Check the consistency of UseEntryGuards and EntryNodes more
- reliably. Fixes bug 20074; bugfix on tor- 0.2.4.12-alpha. Patch
+ reliably. Fixes bug 20074; bugfix on 0.2.4.12-alpha. Patch
by teor.
- Stop changing the configured value of UseEntryGuards on
authorities and Tor2web clients. Fixes bug 20074; bugfix on
- commits 51fc6799 in tor-0.1.1.16-rc and acda1735 in tor-0.2.4.3-
- alpha. Patch by teor.
+ commits 51fc6799 in 0.1.1.16-rc and acda1735 in 0.2.4.3-alpha.
+ Patch by teor.
o Minor bugfixes (Tor2web):
- Prevent Tor2web clients running hidden services, these services
@@ -395,8 +1359,9 @@ Changes in version 0.2.8.8 - 2016-09-23
this one.
o Minor feature (fallback directories):
- - Remove broken fallbacks from the hard-coded fallback directory
- list. Closes ticket 20190; patch by teor.
+ - Remove 8 fallbacks that are no longer suitable, leaving 81 of the
+ 100 fallbacks originally introduced in Tor 0.2.8.2-alpha in March
+ 2016. Closes ticket 20190; patch by teor.
o Minor features (geoip):
- Update geoip and geoip6 to the September 6 2016 Maxmind GeoLite2
@@ -850,8 +1815,9 @@ Changes in version 0.2.8.6 - 2016-08-02
is signed. Fixes bug 19682; bugfix on 0.2.8.1-alpha.
o Minor bugfixes (fallback directories):
- - Remove a fallback that was on the hardcoded list, then opted-out.
- Fixes bug 19782; update to fallback list from 0.2.8.2-alpha.
+ - Remove 1 fallback that was on the hardcoded list, then opted-out,
+ leaving 89 of the 100 fallbacks originally introduced in Tor
+ 0.2.8.2-alpha in March 2016. Closes ticket 19782; patch by teor.
o Minor bugfixes (Linux seccomp2 sandbox):
- Allow more syscalls when running with "Sandbox 1" enabled:
@@ -875,7 +1841,7 @@ Changes in version 0.2.8.5-rc - 2016-07-07
o Major bugfixes (heartbeat):
- Fix a regression that would crash Tor when the periodic
"heartbeat" log messages were disabled. Fixes bug 19454; bugfix on
- tor-0.2.8.1-alpha. Reported by "kubaku".
+ 0.2.8.1-alpha. Reported by "kubaku".
o Minor features (build):
- Tor now again builds with the recent OpenSSL 1.1 development
@@ -923,8 +1889,9 @@ Changes in version 0.2.8.5-rc - 2016-07-07
- Update fallback whitelist and blacklist based on relay operator
emails. Blacklist unsuitable (non-working, over-volatile)
fallbacks. Resolves ticket 19071. Patch by teor.
- - Update hard-coded fallback list to remove unsuitable fallbacks.
- Resolves ticket 19071. Patch by teor.
+ - Remove 10 unsuitable fallbacks, leaving 90 of the 100 fallbacks
+ originally introduced in Tor 0.2.8.2-alpha in March 2016. Closes
+ ticket 19071; patch by teor.
Changes in version 0.2.8.4-rc - 2016-06-15
@@ -1021,7 +1988,7 @@ Changes in version 0.2.8.3-alpha - 2016-05-26
o Major bugfixes (testing):
- Fix a bug that would block 'make test-network-all' on systems where
- IPv6 packets were lost. Fixes bug 19008; bugfix on tor-0.2.7.3-rc.
+ IPv6 packets were lost. Fixes bug 19008; bugfix on 0.2.7.3-rc.
- Avoid "WSANOTINITIALISED" warnings in the unit tests. Fixes bug 18668;
bugfix on 0.2.8.1-alpha.
@@ -1034,10 +2001,12 @@ Changes in version 0.2.8.3-alpha - 2016-05-26
- Give each fallback the same weight for client selection; restrict
fallbacks to one per operator; report fallback directory detail
changes when rebuilding list; add new fallback directory mirrors
- to the whitelist; update fallback directories based on the latest
- OnionOO data; and any other minor simplifications and fixes.
- Closes tasks 17158, 17905, 18749, bug 18689, and fixes part of bug
- 18812 on 0.2.8.1-alpha; patch by "teor".
+ to the whitelist; and many other minor simplifications and fixes.
+ Closes tasks 17905, 18749, bug 18689, and fixes part of bug 18812 on
+ 0.2.8.1-alpha; patch by "teor".
+ - Replace the 21 fallbacks generated in January 2016 and included in
+ Tor 0.2.8.1-alpha, with a list of 100 fallbacks generated in March
+ 2016. Closes task 17158; patch by "teor".
o Minor features (geoip):
- Update geoip and geoip6 to the May 4 2016 Maxmind GeoLite2
@@ -1307,7 +2276,7 @@ Changes in version 0.2.8.2-alpha - 2016-03-28
testing versions of the static libraries. Fixes bug 18490; bugfix
on 0.2.7.1-alpha.
- Avoid spurious failures from configure files related to calling
- exit(0) in TOR_SEARCH_LIBRARY. Fixes bug 18625; bugfix on
+ exit(0) in TOR_SEARCH_LIBRARY. Fixes bug 18626; bugfix on
0.2.0.1-alpha. Patch from "cypherpunks".
- Silence spurious clang-scan warnings in the ed25519_donna code by
explicitly initializing some objects. Fixes bug 18384; bugfix on
@@ -1481,10 +2450,11 @@ Changes in version 0.2.8.1-alpha - 2016-02-04
should reduces failures due to fallback churn. Implements ticket
4483. Patch by "teor". Implements IPv4 portions of proposal 210 by
"mikeperry" and "teor".
- - Include a trial list of default fallback directories, based on an
- opt-in survey of suitable relays. Doing this should make clients
- bootstrap more quickly and reliably, and reduce the load on the
- directory authorities. Closes ticket 15775. Patch by "teor".
+ - Include a trial list of 21 default fallback directories, generated
+ in January 2016, based on an opt-in survey of suitable relays.
+ Doing this should make clients bootstrap more quickly and reliably,
+ and reduce the load on the directory authorities. Closes ticket
+ 15775. Patch by "teor".
Candidates identified using an OnionOO script by "weasel", "teor",
"gsathya", and "karsten".
- Previously only relays that explicitly opened a directory port
diff --git a/Doxyfile.in b/Doxyfile.in
index a39348f2cb..4cf9c30ded 100644
--- a/Doxyfile.in
+++ b/Doxyfile.in
@@ -446,12 +446,6 @@ MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = NO
-
# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
# This will remove the Files entry from the Quick Index and from the
# Folder Tree View (if specified). The default is YES.
@@ -760,12 +754,6 @@ HTML_FOOTER =
HTML_STYLESHEET =
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
-
-HTML_ALIGN_MEMBERS = YES
-
# If the GENERATE_HTMLHELP tag is set to YES, additional index files
# will be generated that can be used as input for tools like the
# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
@@ -1047,18 +1035,6 @@ GENERATE_XML = NO
XML_OUTPUT = xml
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_DTD =
-
# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
# dump the program listings (including syntax highlighting
# and cross-referencing information) to the XML output. Note that
@@ -1264,7 +1240,7 @@ HAVE_DOT = NO
# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
# containing the font.
-DOT_FONTNAME = FreeSans
+DOT_FONTNAME =
# By default doxygen will tell dot to use the output directory to look for the
# FreeSans.ttf font (which doxygen will put there itself). If you specify a
diff --git a/Makefile.am b/Makefile.am
index f400728bac..3d50594bf4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,6 +9,7 @@ noinst_LIBRARIES=
EXTRA_DIST=
noinst_HEADERS=
bin_PROGRAMS=
+EXTRA_PROGRAMS=
CLEANFILES=
TESTS=
noinst_PROGRAMS=
@@ -34,7 +35,8 @@ EXTRA_DIST+= \
LICENSE \
Makefile.nmake \
README \
- ReleaseNotes
+ ReleaseNotes \
+ scripts/maint/checkSpace.pl
## This tells etags how to find mockable function definitions.
AM_ETAGSFLAGS=--regex='{c}/MOCK_IMPL([^,]+,\W*\([a-zA-Z0-9_]+\)\W*,/\1/s'
@@ -52,6 +54,12 @@ TEST_CFLAGS=
TEST_CPPFLAGS=-DTOR_UNIT_TESTS
TEST_NETWORK_FLAGS=--hs-multi-client 1
endif
+TEST_NETWORK_WARNING_FLAGS=--quiet --only-warnings
+
+if LIBFUZZER_ENABLED
+TEST_CFLAGS += -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div
+# not "edge"
+endif
TEST_NETWORK_ALL_LOG_DIR=$(top_builddir)/test_network_log
TEST_NETWORK_ALL_DRIVER_FLAGS=--color-tests yes
@@ -82,6 +90,8 @@ doxygen:
test: all
$(top_builddir)/src/test/test
+check-local: check-spaces
+
need-chutney-path:
@if test ! -d "$$CHUTNEY_PATH"; then \
echo '$$CHUTNEY_PATH was not set.'; \
@@ -126,6 +136,7 @@ test-network-all: need-chutney-path test-driver $(TESTING_TOR_BINARY) src/tools/
done; \
for f in $$flavors; do \
$(SHELL) $(top_srcdir)/test-driver --test-name $$f --log-file $(TEST_NETWORK_ALL_LOG_DIR)/$$f.log --trs-file $(TEST_NETWORK_ALL_LOG_DIR)/$$f.trs $(TEST_NETWORK_ALL_DRIVER_FLAGS) $(top_srcdir)/src/test/test-network.sh --flavor $$f $(TEST_NETWORK_FLAGS); \
+ $(top_srcdir)/src/test/test-network.sh $(TEST_NETWORK_WARNING_FLAGS); \
done; \
echo "Log and result files are available in $(TEST_NETWORK_ALL_LOG_DIR)."; \
! grep -q FAIL test_network_log/*.trs
@@ -181,11 +192,14 @@ coverage-html-full: all
# Avoid strlcpy.c, strlcat.c, aes.c, OpenBSD_malloc_Linux.c, sha256.c,
# tinytest*.[ch]
check-spaces:
- $(top_srcdir)/scripts/maint/checkSpace.pl -C \
+if USE_PERL
+ $(PERL) $(top_srcdir)/scripts/maint/checkSpace.pl -C \
$(top_srcdir)/src/common/*.[ch] \
$(top_srcdir)/src/or/*.[ch] \
$(top_srcdir)/src/test/*.[ch] \
+ $(top_srcdir)/src/test/*/*.[ch] \
$(top_srcdir)/src/tools/*.[ch]
+endif
check-docs: all
$(PERL) $(top_builddir)/scripts/maint/checkOptionDocs.pl
diff --git a/ReleaseNotes b/ReleaseNotes
index af61a4d739..d6adbe5f9b 100644
--- a/ReleaseNotes
+++ b/ReleaseNotes
@@ -2,6 +2,791 @@ 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.9.9 - 2017-01-23
+ Tor 0.2.9.9 fixes a denial-of-service bug where an attacker could
+ cause relays and clients to crash, even if they were not built with
+ the --enable-expensive-hardening option. This bug affects all 0.2.9.x
+ versions, and also affects 0.3.0.1-alpha: all relays running an affected
+ version should upgrade.
+
+ This release also resolves a client-side onion service reachability
+ bug, and resolves a pair of small portability issues.
+
+ o Major bugfixes (security):
+ - Downgrade the "-ftrapv" option from "always on" to "only on when
+ --enable-expensive-hardening is provided." This hardening option,
+ like others, can turn survivable bugs into crashes -- and having
+ it on by default made a (relatively harmless) integer overflow bug
+ into a denial-of-service bug. Fixes bug 21278 (TROVE-2017-001);
+ bugfix on 0.2.9.1-alpha.
+
+ o Major bugfixes (client, onion service):
+ - Fix a client-side onion service reachability bug, where multiple
+ socks requests to an onion service (or a single slow request)
+ could cause us to mistakenly mark some of the service's
+ introduction points as failed, and we cache that failure so
+ eventually we run out and can't reach the service. Also resolves a
+ mysterious "Remote server sent bogus reason code 65021" log
+ warning. The bug was introduced in ticket 17218, where we tried to
+ remember the circuit end reason as a uint16_t, which mangled
+ negative values. Partially fixes bug 21056 and fixes bug 20307;
+ bugfix on 0.2.8.1-alpha.
+
+ o Minor features (geoip):
+ - Update geoip and geoip6 to the January 4 2017 Maxmind GeoLite2
+ Country database.
+
+ o Minor bugfixes (portability):
+ - Avoid crashing when Tor is built using headers that contain
+ CLOCK_MONOTONIC_COARSE, but then tries to run on an older kernel
+ without CLOCK_MONOTONIC_COARSE. Fixes bug 21035; bugfix
+ on 0.2.9.1-alpha.
+ - Fix Libevent detection on platforms without Libevent 1 headers
+ installed. Fixes bug 21051; bugfix on 0.2.9.1-alpha.
+
+
+Changes in version 0.2.8.12 - 2016-12-19
+ Tor 0.2.8.12 backports a fix for a medium-severity issue (bug 21018
+ below) where Tor clients could crash when attempting to visit a
+ hostile hidden service. Clients are recommended to upgrade as packages
+ become available for their systems.
+
+ It also includes an updated list of fallback directories, backported
+ from 0.2.9.
+
+ Now that the Tor 0.2.9 series is stable, only major bugfixes will be
+ backported to 0.2.8 in the future.
+
+ o Major bugfixes (parsing, security, backported from 0.2.9.8):
+ - Fix a bug in parsing that could cause clients to read a single
+ byte past the end of an allocated region. This bug could be used
+ to cause hardened clients (built with --enable-expensive-hardening)
+ to crash if they tried to visit a hostile hidden service. Non-
+ hardened clients are only affected depending on the details of
+ their platform's memory allocator. Fixes bug 21018; bugfix on
+ 0.2.0.8-alpha. Found by using libFuzzer. Also tracked as TROVE-
+ 2016-12-002 and as CVE-2016-1254.
+
+ o Minor features (fallback directory list, backported from 0.2.9.8):
+ - Replace the 81 remaining fallbacks of the 100 originally
+ introduced in Tor 0.2.8.3-alpha in March 2016, with a list of 177
+ fallbacks (123 new, 54 existing, 27 removed) generated in December
+ 2016. Resolves ticket 20170.
+
+ o Minor features (geoip, backported from 0.2.9.7-rc):
+ - Update geoip and geoip6 to the December 7 2016 Maxmind GeoLite2
+ Country database.
+
+
+Changes in version 0.2.9.8 - 2016-12-19
+ Tor 0.2.9.8 is the first stable release of the Tor 0.2.9 series.
+
+ The Tor 0.2.9 series makes mandatory a number of security features
+ that were formerly optional. It includes support for a new shared-
+ randomness protocol that will form the basis for next generation
+ hidden services, includes a single-hop hidden service mode for
+ optimizing .onion services that don't actually want to be hidden,
+ tries harder not to overload the directory authorities with excessive
+ downloads, and supports a better protocol versioning scheme for
+ improved compatibility with other implementations of the Tor protocol.
+
+ And of course, there are numerous other bugfixes and improvements.
+
+ This release also includes a fix for a medium-severity issue (bug
+ 21018 below) where Tor clients could crash when attempting to visit a
+ hostile hidden service. Clients are recommended to upgrade as packages
+ become available for their systems.
+
+ Below are listed the changes since Tor 0.2.8.11. For a list of
+ changes since 0.2.9.7-rc, see the ChangeLog file.
+
+ o New system requirements:
+ - When building with OpenSSL, Tor now requires version 1.0.1 or
+ later. OpenSSL 1.0.0 and earlier are no longer supported by the
+ OpenSSL team, and should not be used. Closes ticket 20303.
+ - Tor now requires Libevent version 2.0.10-stable or later. Older
+ versions of Libevent have less efficient backends for several
+ platforms, and lack the DNS code that we use for our server-side
+ DNS support. This implements ticket 19554.
+ - Tor now requires zlib version 1.2 or later, for security,
+ efficiency, and (eventually) gzip support. (Back when we started,
+ zlib 1.1 and zlib 1.0 were still found in the wild. 1.2 was
+ released in 2003. We recommend the latest version.)
+
+ o Deprecated features:
+ - A number of DNS-cache-related sub-options for client ports are now
+ deprecated for security reasons, and may be removed in a future
+ version of Tor. (We believe that client-side DNS caching is a bad
+ idea for anonymity, and you should not turn it on.) The options
+ are: CacheDNS, CacheIPv4DNS, CacheIPv6DNS, UseDNSCache,
+ UseIPv4Cache, and UseIPv6Cache.
+ - A number of options are deprecated for security reasons, and may
+ be removed in a future version of Tor. The options are:
+ AllowDotExit, AllowInvalidNodes, AllowSingleHopCircuits,
+ AllowSingleHopExits, ClientDNSRejectInternalAddresses,
+ CloseHSClientCircuitsImmediatelyOnTimeout,
+ CloseHSServiceRendCircuitsImmediatelyOnTimeout,
+ ExcludeSingleHopRelays, FastFirstHopPK, TLSECGroup,
+ UseNTorHandshake, and WarnUnsafeSocks.
+ - The *ListenAddress options are now deprecated as unnecessary: the
+ corresponding *Port options should be used instead. These options
+ may someday be removed. The affected options are:
+ ControlListenAddress, DNSListenAddress, DirListenAddress,
+ NATDListenAddress, ORListenAddress, SocksListenAddress,
+ and TransListenAddress.
+
+ o Major bugfixes (parsing, security, new since 0.2.9.7-rc):
+ - Fix a bug in parsing that could cause clients to read a single
+ byte past the end of an allocated region. This bug could be used
+ to cause hardened clients (built with --enable-expensive-hardening)
+ to crash if they tried to visit a hostile hidden service. Non-
+ hardened clients are only affected depending on the details of
+ their platform's memory allocator. Fixes bug 21018; bugfix on
+ 0.2.0.8-alpha. Found by using libFuzzer. Also tracked as TROVE-
+ 2016-12-002 and as CVE-2016-1254.
+
+ o Major features (build, hardening):
+ - Tor now builds with -ftrapv by default on compilers that support
+ it. This option detects signed integer overflow (which C forbids),
+ and turns it into a hard-failure. We do not apply this option to
+ code that needs to run in constant time to avoid side-channels;
+ instead, we use -fwrapv in that code. Closes ticket 17983.
+ - When --enable-expensive-hardening is selected, stop applying the
+ clang/gcc sanitizers to code that needs to run in constant time.
+ Although we are aware of no introduced side-channels, we are not
+ able to prove that there are none. Related to ticket 17983.
+
+ o Major features (circuit building, security):
+ - Authorities, relays, and clients now require ntor keys in all
+ descriptors, for all hops (except for rare hidden service protocol
+ cases), for all circuits, and for all other roles. Part of
+ ticket 19163.
+ - Authorities, relays, and clients only use ntor, except for
+ rare cases in the hidden service protocol. Part of ticket 19163.
+
+ o Major features (compilation):
+ - Our big list of extra GCC warnings is now enabled by default when
+ building with GCC (or with anything like Clang that claims to be
+ GCC-compatible). To make all warnings into fatal compilation
+ errors, pass --enable-fatal-warnings to configure. Closes
+ ticket 19044.
+ - Use the Autoconf macro AC_USE_SYSTEM_EXTENSIONS to automatically
+ turn on C and POSIX extensions. (Previously, we attempted to do
+ this on an ad hoc basis.) Closes ticket 19139.
+
+ o Major features (directory authorities, hidden services):
+ - Directory authorities can now perform the shared randomness
+ protocol specified by proposal 250. Using this protocol, directory
+ authorities generate a global fresh random value every day. In the
+ future, this value will be used by hidden services to select
+ HSDirs. This release implements the directory authority feature;
+ the hidden service side will be implemented in the future as part
+ of proposal 224. Resolves ticket 16943; implements proposal 250.
+
+ o Major features (downloading, random exponential backoff):
+ - When we fail to download an object from a directory service, wait
+ for an (exponentially increasing) randomized amount of time before
+ retrying, rather than a fixed interval as we did before. This
+ prevents a group of Tor instances from becoming too synchronized,
+ or a single Tor instance from becoming too predictable, in its
+ download schedule. Closes ticket 15942.
+
+ o Major features (resource management):
+ - Tor can now notice it is about to run out of sockets, and
+ preemptively close connections of lower priority. (This feature is
+ off by default for now, since the current prioritizing method is
+ yet not mature enough. You can enable it by setting
+ "DisableOOSCheck 0", but watch out: it might close some sockets
+ you would rather have it keep.) Closes ticket 18640.
+
+ o Major features (single-hop "hidden" services):
+ - Add experimental HiddenServiceSingleHopMode and
+ HiddenServiceNonAnonymousMode options. When both are set to 1,
+ every hidden service on that Tor instance becomes a non-anonymous
+ Single Onion Service. Single Onions make one-hop (direct)
+ connections to their introduction and rendezvous points. One-hop
+ circuits make Single Onion servers easily locatable, but clients
+ remain location-anonymous. This is compatible with the existing
+ hidden service implementation, and works on the current Tor
+ network without any changes to older relays or clients. Implements
+ proposal 260, completes ticket 17178. Patch by teor and asn.
+
+ o Major features (subprotocol versions):
+ - Tor directory authorities now vote on a set of recommended
+ "subprotocol versions", and on a set of required subprotocol
+ versions. Clients and relays that lack support for a _required_
+ subprotocol version will not start; those that lack support for a
+ _recommended_ subprotocol version will warn the user to upgrade.
+ This change allows compatible implementations of the Tor protocol(s)
+ to exist without pretending to be 100% bug-compatible with
+ particular releases of Tor itself. Closes ticket 19958; implements
+ part of proposal 264.
+
+ o Major bugfixes (circuit building):
+ - Hidden service client-to-intro-point and service-to-rendezvous-
+ point circuits use the TAP key supplied by the protocol, to avoid
+ epistemic attacks. Fixes bug 19163; bugfix on 0.2.4.18-rc.
+
+ o Major bugfixes (download scheduling):
+ - Avoid resetting download status for consensuses hourly, since we
+ already have another, smarter retry mechanism. Fixes bug 8625;
+ bugfix on 0.2.0.9-alpha.
+ - If a consensus expires while we are waiting for certificates to
+ download, stop waiting for certificates.
+ - If we stop waiting for certificates less than a minute after we
+ started downloading them, do not consider the certificate download
+ failure a separate failure. Fixes bug 20533; bugfix
+ on 0.2.0.9-alpha.
+ - When using exponential backoff in test networks, use a lower
+ exponent, so the delays do not vary as much. This helps test
+ networks bootstrap consistently. Fixes bug 20597; bugfix on 20499.
+
+ o Major bugfixes (exit policies):
+ - Avoid disclosing exit outbound bind addresses, configured port
+ bind addresses, and local interface addresses in relay descriptors
+ by default under ExitPolicyRejectPrivate. Instead, only reject
+ these (otherwise unlisted) addresses if
+ ExitPolicyRejectLocalInterfaces is set. Fixes bug 18456; bugfix on
+ 0.2.7.2-alpha. Patch by teor.
+
+ o Major bugfixes (hidden services):
+ - Allow Tor clients with appropriate controllers to work with
+ FetchHidServDescriptors set to 0. Previously, this option also
+ disabled descriptor cache lookup, thus breaking hidden services
+ entirely. Fixes bug 18704; bugfix on 0.2.0.20-rc. Patch by "twim".
+ - Clients now require hidden services to include the TAP keys for
+ their intro points in the hidden service descriptor. This prevents
+ an inadvertent upgrade to ntor, which a malicious hidden service
+ could use to distinguish clients by consensus version. Fixes bug
+ 20012; bugfix on 0.2.4.8-alpha. Patch by teor.
+
+ o Major bugfixes (relay, resolver, logging):
+ - For relays that don't know their own address, avoid attempting a
+ local hostname resolve for each descriptor we download. This
+ will cut down on the number of "Success: chose address 'x.x.x.x'"
+ log lines, and also avoid confusing clock jumps if the resolver
+ is slow. Fixes bugs 20423 and 20610; bugfix on 0.2.8.1-alpha.
+
+ o Minor features (port flags):
+ - Add new flags to the *Port options to give finer control over which
+ requests are allowed. The flags are NoDNSRequest, NoOnionTraffic,
+ and the synthetic flag OnionTrafficOnly, which is equivalent to
+ NoDNSRequest, NoIPv4Traffic, and NoIPv6Traffic. Closes enhancement
+ 18693; patch by "teor".
+
+ o Minor features (build, hardening):
+ - Detect and work around a libclang_rt problem that would prevent
+ clang from finding __mulodi4() on some 32-bit platforms, and thus
+ keep -ftrapv from linking on those systems. Closes ticket 19079.
+ - When building on a system without runtime support for the runtime
+ hardening options, try to log a useful warning at configuration
+ time, rather than an incomprehensible warning at link time. If
+ expensive hardening was requested, this warning becomes an error.
+ Closes ticket 18895.
+
+ o Minor features (client, directory):
+ - Since authorities now omit all routers that lack the Running and
+ Valid flags, we assume that any relay listed in the consensus must
+ have those flags. Closes ticket 20001; implements part of
+ proposal 272.
+
+ o Minor features (code safety):
+ - In our integer-parsing functions, ensure that the maximum value we
+ allow is no smaller than the minimum value. Closes ticket 19063;
+ patch from "U+039b".
+
+ o Minor features (compilation, portability):
+ - Compile correctly on MacOS 10.12 (aka "Sierra"). Closes
+ ticket 20241.
+
+ o Minor features (config):
+ - Warn users when descriptor and port addresses are inconsistent.
+ Mitigates bug 13953; patch by teor.
+
+ o Minor features (controller):
+ - Allow controllers to configure basic client authorization on
+ hidden services when they create them with the ADD_ONION controller
+ command. Implements ticket 15588. Patch by "special".
+ - Fire a STATUS_SERVER controller event whenever the hibernation
+ status changes between "awake"/"soft"/"hard". Closes ticket 18685.
+ - Implement new GETINFO queries for all downloads that use
+ download_status_t to schedule retries. This allows controllers to
+ examine the schedule for pending downloads. Closes ticket 19323.
+
+ o Minor features (development tools, etags):
+ - Teach the "make tags" Makefile target how to correctly find
+ "MOCK_IMPL" function definitions. Patch from nherring; closes
+ ticket 16869.
+
+ o Minor features (directory authority):
+ - After voting, if the authorities decide that a relay is not
+ "Valid", they no longer include it in the consensus at all. Closes
+ ticket 20002; implements part of proposal 272.
+ - Directory authorities now only give the Guard flag to a relay if
+ they are also giving it the Stable flag. This change allows us to
+ simplify path selection for clients. It should have minimal effect
+ in practice, since >99% of Guards already have the Stable flag.
+ Implements ticket 18624.
+ - Directory authorities now write their v3-status-votes file out to
+ disk earlier in the consensus process, so we have a record of the
+ votes even if we abort the consensus process. Resolves
+ ticket 19036.
+
+ o Minor features (fallback directory list, new since 0.2.9.7-rc):
+ - Replace the 81 remaining fallbacks of the 100 originally
+ introduced in Tor 0.2.8.3-alpha in March 2016, with a list of 177
+ fallbacks (123 new, 54 existing, 27 removed) generated in December
+ 2016. Resolves ticket 20170.
+
+ o Minor features (hidden service):
+ - Stop being so strict about the payload length of "rendezvous1"
+ cells. We used to be locked in to the "TAP" handshake length, and
+ now we can handle better handshakes like "ntor". Resolves
+ ticket 18998.
+
+ o Minor features (infrastructure, time):
+ - Tor now includes an improved timer backend, so that we can
+ efficiently support tens or hundreds of thousands of concurrent
+ timers, as will be needed for some of our planned anti-traffic-
+ analysis work. This code is based on William Ahern's "timeout.c"
+ project, which implements a "tickless hierarchical timing wheel".
+ Closes ticket 18365.
+ - Tor now uses the operating system's monotonic timers (where
+ available) for internal fine-grained timing. Previously we would
+ look at the system clock, and then attempt to compensate for the
+ clock running backwards. Closes ticket 18908.
+
+ o Minor features (logging):
+ - Add a set of macros to check nonfatal assertions, for internal
+ use. Migrating more of our checks to these should help us avoid
+ needless crash bugs. Closes ticket 18613.
+ - Provide a more useful warning message when configured with an
+ invalid Nickname. Closes ticket 18300; patch from "icanhasaccount".
+ - When dumping unparseable router descriptors, optionally store them
+ in separate files, named by digest, up to a configurable size
+ limit. You can change the size limit by setting the
+ MaxUnparseableDescSizeToLog option, and disable this feature by
+ setting that option to 0. Closes ticket 18322.
+
+ o Minor features (performance):
+ - Change the "optimistic data" extension from "off by default" to
+ "on by default". The default was ordinarily overridden by a
+ consensus option, but when clients were bootstrapping for the
+ first time, they would not have a consensus to get the option
+ from. Changing this default saves a round-trip during startup.
+ Closes ticket 18815.
+
+ o Minor features (relay, usability):
+ - When the directory authorities refuse a bad relay's descriptor,
+ encourage the relay operator to contact us. Many relay operators
+ won't notice this line in their logs, but it's a win if even a few
+ learn why we don't like what their relay was doing. Resolves
+ ticket 18760.
+
+ o Minor features (security, TLS):
+ - Servers no longer support clients that lack AES ciphersuites.
+ (3DES is no longer considered an acceptable cipher.) We believe
+ that no such Tor clients currently exist, since Tor has required
+ OpenSSL 0.9.7 or later since 2009. Closes ticket 19998.
+
+ o Minor features (testing):
+ - Disable memory protections on OpenBSD when performing our unit
+ tests for memwipe(). The test deliberately invokes undefined
+ behavior, and the OpenBSD protections interfere with this. Patch
+ from "rubiate". Closes ticket 20066.
+ - Move the test-network.sh script to chutney, and modify tor's test-
+ network.sh to call the (newer) chutney version when available.
+ Resolves ticket 19116. Patch by teor.
+ - Use the lcov convention for marking lines as unreachable, so that
+ we don't count them when we're generating test coverage data.
+ Update our coverage tools to understand this convention. Closes
+ ticket 16792.
+ - Our link-handshake unit tests now check that when invalid
+ handshakes fail, they fail with the error messages we expected.
+ - Our unit testing code that captures log messages no longer
+ prevents them from being written out if the user asked for them
+ (by passing --debug or --info or --notice or --warn to the "test"
+ binary). This change prevents us from missing unexpected log
+ messages simply because we were looking for others. Related to
+ ticket 19999.
+ - The unit tests now log all warning messages with the "BUG" flag.
+ Previously, they only logged errors by default. This change will
+ help us make our testing code more correct, and make sure that we
+ only hit this code when we mean to. In the meantime, however,
+ there will be more warnings in the unit test logs than before.
+ This is preparatory work for ticket 19999.
+ - The unit tests now treat any failure of a "tor_assert_nonfatal()"
+ assertion as a test failure.
+ - We've done significant work to make the unit tests run faster.
+
+ o Minor features (testing, ipv6):
+ - Add the hs-ipv6 chutney target to make test-network-all's IPv6
+ tests. Remove bridges+hs, as it's somewhat redundant. This
+ requires a recent chutney version that supports IPv6 clients,
+ relays, and authorities. Closes ticket 20069; patch by teor.
+ - Add the single-onion and single-onion-ipv6 chutney targets to
+ "make test-network-all". This requires a recent chutney version
+ with the single onion network flavors (git c72a652 or later).
+ Closes ticket 20072; patch by teor.
+
+ o Minor features (Tor2web):
+ - Make Tor2web clients respect ReachableAddresses. This feature was
+ inadvertently enabled in 0.2.8.6, then removed by bugfix 19973 on
+ 0.2.8.7. Implements feature 20034. Patch by teor.
+
+ o Minor features (unix domain sockets):
+ - When configuring a unix domain socket for a SocksPort,
+ ControlPort, or Hidden service, you can now wrap the address in
+ quotes, using C-style escapes inside the quotes. This allows unix
+ domain socket paths to contain spaces. Resolves ticket 18753.
+
+ o Minor features (user interface):
+ - Tor now supports the ability to declare options deprecated, so
+ that we can recommend that people stop using them. Previously, this
+ was done in an ad-hoc way. There is a new --list-deprecated-options
+ command-line option to list all of the deprecated options. Closes
+ ticket 19820.
+
+ o Minor features (virtual addresses):
+ - Increase the maximum number of bits for the IPv6 virtual network
+ prefix from 16 to 104. In this way, the condition for address
+ allocation is less restrictive. Closes ticket 20151; feature
+ on 0.2.4.7-alpha.
+
+ o Minor bug fixes (circuits):
+ - Use the CircuitBuildTimeout option whenever
+ LearnCircuitBuildTimeout is disabled. Previously, we would respect
+ the option when a user disabled it, but not when it was disabled
+ because some other option was set. Fixes bug 20073; bugfix on
+ 0.2.4.12-alpha. Patch by teor.
+
+ o Minor bugfixes (build):
+ - The current Git revision when building from a local repository is
+ now detected correctly when using git worktrees. Fixes bug 20492;
+ bugfix on 0.2.3.9-alpha.
+
+ o Minor bugfixes (relay address discovery):
+ - Stop reordering IP addresses returned by the OS. This makes it
+ more likely that Tor will guess the same relay IP address every
+ time. Fixes issue 20163; bugfix on 0.2.7.1-alpha, ticket 17027.
+ Reported by René Mayrhofer, patch by "cypherpunks".
+
+ o Minor bugfixes (memory allocation):
+ - Change how we allocate memory for large chunks on buffers, to
+ avoid a (currently impossible) integer overflow, and to waste less
+ space when allocating unusually large chunks. Fixes bug 20081;
+ bugfix on 0.2.0.16-alpha. Issue identified by Guido Vranken.
+
+ o Minor bugfixes (bootstrap):
+ - Remember the directory server we fetched the consensus or previous
+ certificates from, and use it to fetch future authority
+ certificates. This change improves bootstrapping performance.
+ Fixes bug 18963; bugfix on 0.2.8.1-alpha.
+
+ o Minor bugfixes (circuits):
+ - Make sure extend_info_from_router() is only called on servers.
+ Fixes bug 19639; bugfix on 0.2.8.1-alpha.
+
+ o Minor bugfixes (client, fascistfirewall):
+ - Avoid spurious warnings when ReachableAddresses or FascistFirewall
+ is set. Fixes bug 20306; bugfix on 0.2.8.2-alpha.
+
+ o Minor bugfixes (client, unix domain sockets):
+ - Disable IsolateClientAddr when using AF_UNIX backed SocksPorts as
+ the client address is meaningless. Fixes bug 20261; bugfix
+ on 0.2.6.3-alpha.
+
+ o Minor bugfixes (code style):
+ - Fix an integer signedness conversion issue in the case conversion
+ tables. Fixes bug 19168; bugfix on 0.2.1.11-alpha.
+
+ o Minor bugfixes (compilation):
+ - Build correctly on versions of libevent2 without support for
+ evutil_secure_rng_add_bytes(). Fixes bug 19904; bugfix
+ on 0.2.5.4-alpha.
+ - When building with Clang, use a full set of GCC warnings.
+ (Previously, we included only a subset, because of the way we
+ detected them.) Fixes bug 19216; bugfix on 0.2.0.1-alpha.
+ - Detect Libevent2 functions correctly on systems that provide
+ libevent2, but where libevent1 is linked with -levent. Fixes bug
+ 19904; bugfix on 0.2.2.24-alpha. Patch from Rubiate.
+ - Run correctly when built on Windows build environments that
+ require _vcsprintf(). Fixes bug 20560; bugfix on 0.2.2.11-alpha.
+
+ o Minor bugfixes (configuration):
+ - When parsing quoted configuration values from the torrc file,
+ handle Windows line endings correctly. Fixes bug 19167; bugfix on
+ 0.2.0.16-alpha. Patch from "Pingl".
+
+ o Minor bugfixes (directory authority):
+ - Authorities now sort the "package" lines in their votes, for ease
+ of debugging. (They are already sorted in consensus documents.)
+ Fixes bug 18840; bugfix on 0.2.6.3-alpha.
+ - Die with a more useful error when the operator forgets to place
+ the authority_signing_key file into the keys directory. This
+ avoids an uninformative assert & traceback about having an invalid
+ key. Fixes bug 20065; bugfix on 0.2.0.1-alpha.
+ - When allowing private addresses, mark Exits that only exit to
+ private locations as such. Fixes bug 20064; bugfix
+ on 0.2.2.9-alpha.
+ - When parsing a detached signature, make sure we use the length of
+ the digest algorithm instead of a hardcoded DIGEST256_LEN in
+ order to avoid comparing bytes out-of-bounds with a smaller digest
+ length such as SHA1. Fixes bug 19066; bugfix on 0.2.2.6-alpha.
+
+ o Minor bugfixes (getpass):
+ - Defensively fix a non-triggerable heap corruption at do_getpass()
+ to protect ourselves from mistakes in the future. Fixes bug
+ 19223; bugfix on 0.2.7.3-rc. Bug found by Guido Vranken, patch
+ by nherring.
+
+ o Minor bugfixes (guard selection):
+ - Don't mark guards as unreachable if connection_connect() fails.
+ That function fails for local reasons, so it shouldn't reveal
+ anything about the status of the guard. Fixes bug 14334; bugfix
+ on 0.2.3.10-alpha.
+ - Use a single entry guard even if the NumEntryGuards consensus
+ parameter is not provided. Fixes bug 17688; bugfix
+ on 0.2.5.6-alpha.
+
+ o Minor bugfixes (hidden services):
+ - Increase the minimum number of internal circuits we preemptively
+ build from 2 to 3, so a circuit is available when a client
+ connects to another onion service. Fixes bug 13239; bugfix
+ on 0.1.0.1-rc.
+ - Allow hidden services to run on IPv6 addresses even when the
+ IPv6Exit option is not set. Fixes bug 18357; bugfix
+ on 0.2.4.7-alpha.
+ - Stop logging intro point details to the client log on certain
+ error conditions. Fixed as part of bug 20012; bugfix on
+ 0.2.4.8-alpha. Patch by teor.
+ - When deleting an ephemeral hidden service, close its intro points
+ even if they are not completely open. Fixes bug 18604; bugfix
+ on 0.2.7.1-alpha.
+ - When configuring hidden services, check every hidden service
+ directory's permissions. Previously, we only checked the last
+ hidden service. Fixes bug 20529; bugfix on 0.2.6.2-alpha.
+
+ o Minor bugfixes (IPv6, testing):
+ - Check for IPv6 correctly on Linux when running test networks.
+ Fixes bug 19905; bugfix on 0.2.7.3-rc; patch by teor.
+
+ o Minor bugfixes (Linux seccomp2 sandbox):
+ - Add permission to run the sched_yield() and sigaltstack() system
+ calls, in order to support versions of Tor compiled with asan or
+ ubsan code that use these calls. Now "sandbox 1" and
+ "--enable-expensive-hardening" should be compatible on more
+ systems. Fixes bug 20063; bugfix on 0.2.5.1-alpha.
+
+ o Minor bugfixes (logging):
+ - Downgrade a harmless log message about the
+ pending_entry_connections list from "warn" to "info". Mitigates
+ bug 19926.
+ - Log a more accurate message when we fail to dump a microdescriptor.
+ Fixes bug 17758; bugfix on 0.2.2.8-alpha. Patch from Daniel Pinto.
+ - When logging a directory ownership mismatch, log the owning
+ username correctly. Fixes bug 19578; bugfix on 0.2.2.29-beta.
+ - When we are unable to remove the bw_accounting file, do not warn
+ if the reason we couldn't remove it was that it didn't exist.
+ Fixes bug 19964; bugfix on 0.2.5.4-alpha. Patch from pastly.
+
+ o Minor bugfixes (memory leak):
+ - Fix a series of slow memory leaks related to parsing torrc files
+ and options. Fixes bug 19466; bugfix on 0.2.1.6-alpha.
+ - Avoid a small memory leak when informing worker threads about
+ rotated onion keys. Fixes bug 20401; bugfix on 0.2.6.3-alpha.
+ - Fix a small memory leak when receiving AF_UNIX connections on a
+ SocksPort. Fixes bug 20716; bugfix on 0.2.6.3-alpha.
+ - When moving a signed descriptor object from a source to an
+ existing destination, free the allocated memory inside that
+ destination object. Fixes bug 20715; bugfix on 0.2.8.3-alpha.
+ - Fix a memory leak and use-after-free error when removing entries
+ from the sandbox's getaddrinfo() cache. Fixes bug 20710; bugfix on
+ 0.2.5.5-alpha. Patch from "cypherpunks".
+ - Fix a small, uncommon memory leak that could occur when reading a
+ truncated ed25519 key file. Fixes bug 18956; bugfix
+ on 0.2.6.1-alpha.
+
+ o Minor bugfixes (option parsing):
+ - Count unix sockets when counting client listeners (SOCKS, Trans,
+ NATD, and DNS). This has no user-visible behavior changes: these
+ options are set once, and never read. Required for correct
+ behavior in ticket 17178. Fixes bug 19677; bugfix on
+ 0.2.6.3-alpha. Patch by teor.
+
+ o Minor bugfixes (options):
+ - Check the consistency of UseEntryGuards and EntryNodes more
+ reliably. Fixes bug 20074; bugfix on 0.2.4.12-alpha. Patch
+ by teor.
+ - Stop changing the configured value of UseEntryGuards on
+ authorities and Tor2web clients. Fixes bug 20074; bugfix on
+ commits 51fc6799 in 0.1.1.16-rc and acda1735 in 0.2.4.3-alpha.
+ Patch by teor.
+
+ o Minor bugfixes (relay):
+ - Ensure relays don't make multiple connections during bootstrap.
+ Fixes bug 20591; bugfix on 0.2.8.1-alpha.
+ - Do not try to parallelize workers more than 16x without the user
+ explicitly configuring us to do so, even if we do detect more than
+ 16 CPU cores. Fixes bug 19968; bugfix on 0.2.3.1-alpha.
+
+ o Minor bugfixes (testing):
+ - The test-stem and test-network makefile targets now depend only on
+ the tor binary that they are testing. Previously, they depended on
+ "make all". Fixes bug 18240; bugfix on 0.2.8.2-alpha. Based on a
+ patch from "cypherpunks".
+ - Allow clients to retry HSDirs much faster in test networks. Fixes
+ bug 19702; bugfix on 0.2.7.1-alpha. Patch by teor.
+ - Avoid a unit test failure on systems with over 16 detectable CPU
+ cores. Fixes bug 19968; bugfix on 0.2.3.1-alpha.
+ - Let backtrace tests work correctly under AddressSanitizer:
+ disable ASAN's detection of segmentation faults while running
+ test_bt.sh, so that we can make sure that our own backtrace
+ generation code works. Fixes bug 18934; bugfix
+ on 0.2.5.2-alpha. Patch from "cypherpunks".
+ - Fix the test-network-all target on out-of-tree builds by using the
+ correct path to the test driver script. Fixes bug 19421; bugfix
+ on 0.2.7.3-rc.
+ - Stop spurious failures in the local interface address discovery
+ unit tests. Fixes bug 20634; bugfix on 0.2.8.1-alpha; patch by
+ Neel Chauhan.
+ - Use ECDHE ciphers instead of ECDH in tortls tests. LibreSSL has
+ removed the ECDH ciphers which caused the tests to fail on
+ platforms which use it. Fixes bug 20460; bugfix on 0.2.8.1-alpha.
+ - The tor_tls_server_info_callback unit test no longer crashes when
+ debug-level logging is turned on. Fixes bug 20041; bugfix
+ on 0.2.8.1-alpha.
+
+ o Minor bugfixes (time):
+ - Improve overflow checks in tv_udiff and tv_mdiff. Fixes bug 19483;
+ bugfix on all released tor versions.
+ - When computing the difference between two times in milliseconds,
+ we now round to the nearest millisecond correctly. Previously, we
+ could sometimes round in the wrong direction. Fixes bug 19428;
+ bugfix on 0.2.2.2-alpha.
+
+ o Minor bugfixes (Tor2web):
+ - Prevent Tor2web clients from running hidden services: these services
+ are not anonymous due to the one-hop client paths. Fixes bug
+ 19678. Patch by teor.
+
+ o Minor bugfixes (user interface):
+ - Display a more accurate number of suppressed messages in the log
+ rate-limiter. Previously, there was a potential integer overflow
+ in the counter. Now, if the number of messages hits a maximum, the
+ rate-limiter doesn't count any further. Fixes bug 19435; bugfix
+ on 0.2.4.11-alpha.
+ - Fix a typo in the passphrase prompt for the ed25519 identity key.
+ Fixes bug 19503; bugfix on 0.2.7.2-alpha.
+
+ o Code simplification and refactoring:
+ - Remove redundant declarations of the MIN macro. Closes
+ ticket 18889.
+ - Rename tor_dup_addr() to tor_addr_to_str_dup() to avoid confusion.
+ Closes ticket 18462; patch from "icanhasaccount".
+ - Split the 600-line directory_handle_command_get function into
+ separate functions for different URL types. Closes ticket 16698.
+
+ o Documentation:
+ - Add module-level internal documentation for 36 C files that
+ previously didn't have a high-level overview. Closes ticket 20385.
+ - Correct the IPv6 syntax in our documentation for the
+ VirtualAddrNetworkIPv6 torrc option. Closes ticket 19743.
+ - Correct the minimum bandwidth value in torrc.sample, and queue a
+ corresponding change for torrc.minimal. Closes ticket 20085.
+ - Fix spelling of "--enable-tor2web-mode" in the manpage. Closes
+ ticket 19153. Patch from "U+039b".
+ - Module-level documentation for several more modules. Closes
+ tickets 19287 and 19290.
+ - Document the --passphrase-fd option in the tor manpage. Fixes bug
+ 19504; bugfix on 0.2.7.3-rc.
+ - Document the default PathsNeededToBuildCircuits value that's used
+ by clients when the directory authorities don't set
+ min_paths_for_circs_pct. Fixes bug 20117; bugfix on 0.2.4.10-alpha.
+ Patch by teor, reported by Jesse V.
+ - Fix manual for the User option: it takes a username, not a UID.
+ Fixes bug 19122; bugfix on 0.0.2pre16 (the first version to have
+ a manpage!).
+ - Fix the description of the --passphrase-fd option in the
+ tor-gencert manpage. The option is used to pass the number of a
+ file descriptor to read the passphrase from, not to read the file
+ descriptor from. Fixes bug 19505; bugfix on 0.2.0.20-alpha.
+
+ o Removed code:
+ - We no longer include the (dead, deprecated) bufferevent code in
+ Tor. Closes ticket 19450. Based on a patch from "U+039b".
+
+ o Removed features:
+ - Remove support for "GET /tor/bytes.txt" DirPort request, and
+ "GETINFO dir-usage" controller request, which were only available
+ via a compile-time option in Tor anyway. Feature was added in
+ 0.2.2.1-alpha. Resolves ticket 19035.
+ - There is no longer a compile-time option to disable support for
+ TransPort. (If you don't want TransPort, just don't use it.) Patch
+ from "U+039b". Closes ticket 19449.
+
+ o Testing:
+ - Run more workqueue tests as part of "make check". These had
+ previously been implemented, but you needed to know special
+ command-line options to enable them.
+ - We now have unit tests for our code to reject zlib "compression
+ bombs". (Fortunately, the code works fine.)
+
+
+Changes in version 0.2.8.11 - 2016-12-08
+ Tor 0.2.8.11 backports fixes for additional portability issues that
+ could prevent Tor from building correctly on OSX Sierra, or with
+ OpenSSL 1.1. Affected users should upgrade; others can safely stay
+ with 0.2.8.10.
+
+ o Minor bugfixes (portability):
+ - Avoid compilation errors when building on OSX Sierra. Sierra began
+ to support the getentropy() and clock_gettime() APIs, but created
+ a few problems in doing so. Tor 0.2.9 has a more thorough set of
+ workarounds; in 0.2.8, we are just using the /dev/urandom and mach
+ monotonic time interfaces. Fixes bug 20865. Bugfix
+ on 0.2.8.1-alpha.
+
+ o Minor bugfixes (portability, backport from 0.2.9.5-alpha):
+ - Fix compilation with OpenSSL 1.1 and less commonly-used CPU
+ architectures. Closes ticket 20588.
+
+
+Changes in version 0.2.8.10 - 2016-12-02
+ Tor 0.2.8.10 backports a fix for a bug that would sometimes make clients
+ unusable after they left standby mode. It also backports fixes for
+ a few portability issues and a small but problematic memory leak.
+
+ o Major bugfixes (client reliability, backport from 0.2.9.5-alpha):
+ - When Tor leaves standby because of a new application request, open
+ circuits as needed to serve that request. Previously, we would
+ potentially wait a very long time. Fixes part of bug 19969; bugfix
+ on 0.2.8.1-alpha.
+
+ o Major bugfixes (client performance, backport from 0.2.9.5-alpha):
+ - Clients now respond to new application stream requests immediately
+ when they arrive, rather than waiting up to one second before
+ starting to handle them. Fixes part of bug 19969; bugfix
+ on 0.2.8.1-alpha.
+
+ o Minor bugfixes (portability, backport from 0.2.9.6-rc):
+ - Work around a bug in the OSX 10.12 SDK that would prevent us from
+ successfully targeting earlier versions of OSX. Resolves
+ ticket 20235.
+
+ o Minor bugfixes (portability, backport from 0.2.9.5-alpha):
+ - Fix implicit conversion warnings under OpenSSL 1.1. Fixes bug
+ 20551; bugfix on 0.2.1.1-alpha.
+
+ o Minor bugfixes (relay, backport from 0.2.9.5-alpha):
+ - Work around a memory leak in OpenSSL 1.1 when encoding public
+ keys. Fixes bug 20553; bugfix on 0.0.2pre8.
+
+ o Minor features (geoip):
+ - Update geoip and geoip6 to the November 3 2016 Maxmind GeoLite2
+ Country database.
+
+
Changes in version 0.2.8.9 - 2016-10-17
Tor 0.2.8.9 backports a fix for a security hole in previous versions
of Tor that would allow a remote attacker to crash a Tor client,
@@ -449,7 +1234,7 @@ Changes in version 0.2.8.6 - 2016-08-02
o Minor bugfixes (build):
- Avoid spurious failures from configure files related to calling
- exit(0) in TOR_SEARCH_LIBRARY. Fixes bug 18625; bugfix on
+ exit(0) in TOR_SEARCH_LIBRARY. Fixes bug 18626; bugfix on
0.2.0.1-alpha. Patch from "cypherpunks".
- Do not link the unit tests against both the testing and non-
testing versions of the static libraries. Fixes bug 18490; bugfix
diff --git a/acinclude.m4 b/acinclude.m4
index ab12317139..193d3a7a08 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -51,12 +51,12 @@ AC_DEFUN([TOR_TRY_COMPILE_WITH_CFLAGS], [
AC_CACHE_CHECK([whether the compiler accepts $1], VAR, [
tor_saved_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -pedantic -Werror $1"
- AC_TRY_COMPILE([], [return 0;],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
[AS_VAR_SET(VAR,yes)],
[AS_VAR_SET(VAR,no)])
if test x$2 != x; then
AS_VAR_PUSHDEF([can_link],[tor_can_link_$1])
- AC_TRY_LINK([], [return 0;],
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
[AS_VAR_SET(can_link,yes)],
[AS_VAR_SET(can_link,no)])
AS_VAR_POPDEF([can_link])
@@ -93,7 +93,7 @@ AC_DEFUN([TOR_CHECK_LDFLAGS], [
AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [fputs("", stdout)])],
[AS_VAR_SET(VAR,yes)],
[AS_VAR_SET(VAR,no)],
- [AC_TRY_LINK([], [return 0;],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
[AS_VAR_SET(VAR,yes)],
[AS_VAR_SET(VAR,no)])])
CFLAGS="$tor_saved_CFLAGS"
@@ -113,21 +113,21 @@ if test x$2 = xdevpkg; then
h=" headers for"
fi
if test -f /etc/debian_version && test x"$tor_$1_$2_debian" != x; then
- AC_WARN([On Debian, you can install$h $1 using "apt-get install $tor_$1_$2_debian"])
+ AC_MSG_WARN([On Debian, you can install$h $1 using "apt-get install $tor_$1_$2_debian"])
if test x"$tor_$1_$2_debian" != x"$tor_$1_devpkg_debian"; then
- AC_WARN([ You will probably need $tor_$1_devpkg_debian too.])
+ AC_MSG_WARN([ You will probably need $tor_$1_devpkg_debian too.])
fi
fi
if test -f /etc/fedora-release && test x"$tor_$1_$2_redhat" != x; then
- AC_WARN([On Fedora, you can install$h $1 using "dnf install $tor_$1_$2_redhat"])
+ AC_MSG_WARN([On Fedora, you can install$h $1 using "dnf install $tor_$1_$2_redhat"])
if test x"$tor_$1_$2_redhat" != x"$tor_$1_devpkg_redhat"; then
- AC_WARN([ You will probably need to install $tor_$1_devpkg_redhat too.])
+ AC_MSG_WARN([ You will probably need to install $tor_$1_devpkg_redhat too.])
fi
else
if test -f /etc/redhat-release && test x"$tor_$1_$2_redhat" != x; then
- AC_WARN([On most Redhat-based systems, you can get$h $1 by installing the $tor_$1_$2_redhat RPM package])
+ AC_MSG_WARN([On most Redhat-based systems, you can get$h $1 by installing the $tor_$1_$2_redhat RPM package])
if test x"$tor_$1_$2_redhat" != x"$tor_$1_devpkg_redhat"; then
- AC_WARN([ You will probably need to install $tor_$1_devpkg_redhat too.])
+ AC_MSG_WARN([ You will probably need to install $tor_$1_devpkg_redhat too.])
fi
fi
fi
@@ -245,7 +245,10 @@ if test "$cross_compiling" != yes; then
LDFLAGS="$tor_tryextra $orig_LDFLAGS"
fi
AC_RUN_IFELSE([AC_LANG_PROGRAM([$5], [$6])],
- [runnable=yes], [runnable=no])
+ [runnable=yes], [runnable=no],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[]])],
+ [runnable=yes],
+ [runnable=no])])
if test "$runnable" = yes; then
tor_cv_library_$1_linker_option=$tor_tryextra
break
diff --git a/autogen.sh b/autogen.sh
index 8c43a9798a..276dd4047c 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,12 +1,12 @@
#!/bin/sh
if [ -x "`which autoreconf 2>/dev/null`" ] ; then
- opt="-if"
+ opt="-i -f -W all,error"
for i in $@; do
case "$i" in
-v)
- opt=$opt"v"
+ opt="${opt} -v"
;;
esac
done
diff --git a/changes/19974 b/changes/19974
deleted file mode 100644
index 5496143ddf..0000000000
--- a/changes/19974
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (unit tests):
- - Fix tolerances in unit tests for monotonic time comparisons between
- nanoseconds and microseconds. Previously, we accepted a 10 us
- difference only, which is not realistic on every platform's
- clock_gettime(). Fixes bug 19974; bugfix on 0.2.9.1-alpha.
diff --git a/changes/20460 b/changes/20460
deleted file mode 100644
index 9fbb4a7986..0000000000
--- a/changes/20460
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (testing):
- - Use ECDHE ciphers instead of ECDH in tortls tests. LibreSSL has
- removed the ECDH ciphers which caused the tests to fail on
- platforms which use it. Fixes bug 20460; bugfix on 0.2.8.1-alpha.
diff --git a/changes/20492 b/changes/20492
deleted file mode 100644
index fdcd4d0b4b..0000000000
--- a/changes/20492
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfix (build):
- - The current Git revision when building from a local repository is now
- detected correctly when using git worktrees. Fixes bug 20492; bugfix on
- 0.2.3.9-alpha.
diff --git a/changes/21359 b/changes/21359
deleted file mode 100644
index cc9b377d52..0000000000
--- a/changes/21359
+++ /dev/null
@@ -1,8 +0,0 @@
-
- o Minor features (portability, compilationc)
- - Support building with recent LibreSSL code that uses opaque
- structures. Closes ticket 21359.
- - Autoconf now check to determine if OpenSSL
- structures are opaque, instead of explicitly checking for
- OpenSSL version numbers.
- Part of ticket 21359.
diff --git a/changes/bug19025 b/changes/bug19025
deleted file mode 100644
index 0f365f52ba..0000000000
--- a/changes/bug19025
+++ /dev/null
@@ -1,4 +0,0 @@
- o Major bugfixes (DNS):
- - 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.
diff --git a/changes/bug19869 b/changes/bug19869
deleted file mode 100644
index 430048f161..0000000000
--- a/changes/bug19869
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (DNSPort):
- - On DNSPort, stop logging a BUG warning on a failed hostname lookup.
- Fixes bug 19869; bugfix on 0.2.9.1-alpha.
-
diff --git a/changes/bug19926_029_info b/changes/bug19926_029_info
deleted file mode 100644
index 93fd81b6cb..0000000000
--- a/changes/bug19926_029_info
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (logging):
- - Downgrade a harmless log message about the pending_entry_connections
- list from "warn" to "info". Mitigates bug 19926.
diff --git a/changes/bug19960 b/changes/bug19960
deleted file mode 100644
index 5d655859a6..0000000000
--- a/changes/bug19960
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (netbsd, unit tests):
- - Stop expecting NetBSD unit tests to report success for ipfw;
- on NetBSD, it's only pf that's supported.
- Part of a fix for bug 19960; bugfix on 0.2.9.5-alpha.
diff --git a/changes/bug19968 b/changes/bug19968
deleted file mode 100644
index b285706e70..0000000000
--- a/changes/bug19968
+++ /dev/null
@@ -1,11 +0,0 @@
- o Minor bugfixes (relay):
- - Do not try to parallelize workers more than 16x without the
- user explicitly configuring us to do so, even if we do detect more than
- 16 CPU cores. Fixes bug 19968; bugfix on
- 0.2.3.1-alpha.
-
-
- o Minor bugfixes (testing):
- - Avoid a unit test failure on systems with over 16 detectable
- CPU cores. Fixes bug 19968; bugfix on
- 0.2.3.1-alpha.
diff --git a/changes/bug19969 b/changes/bug19969
deleted file mode 100644
index c760c6de03..0000000000
--- a/changes/bug19969
+++ /dev/null
@@ -1,10 +0,0 @@
- o Major bugfixes (client performance):
- - Clients now respond to new application stream requests when
- they arrive, rather than waiting up to one second before starting
- to handle them. Fixes part of bug 19969; bugfix on 0.2.8.1-alpha.
-
- o Major bugfixes (clients on flaky network connections):
- - When Tor leaves standby because of a new application request, open
- circuits as needed to serve that request. Previously, we would
- potentially wait a very long time. Fixes part of bug 19969; bugfix
- on 0.2.8.1-alpha.
diff --git a/changes/bug20085 b/changes/bug20085
deleted file mode 100644
index fd10e7eeeb..0000000000
--- a/changes/bug20085
+++ /dev/null
@@ -1,4 +0,0 @@
- o Documentation:
- - Correct the minimum bandwidth value in torrc.sample, and queue a
- corresponding change for torrc.minimal. Closes ticket 20085.
-
diff --git a/changes/bug20235 b/changes/bug20235
deleted file mode 100644
index 54026a8943..0000000000
--- a/changes/bug20235
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor features (compatibility):
- - Work around a bug in the OSX 10.12 SDK that would prevent us
- from successfully targetting earlier versions of OSX.
- Resolves ticket 20235.
diff --git a/changes/bug20270 b/changes/bug20270
new file mode 100644
index 0000000000..d538a358dc
--- /dev/null
+++ b/changes/bug20270
@@ -0,0 +1,6 @@
+ o Minor bugfixes (directory authority):
+ - When rejecting a router descriptor because the relay is 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.
+
diff --git a/changes/bug20306_029 b/changes/bug20306_029
deleted file mode 100644
index ada2676b2b..0000000000
--- a/changes/bug20306_029
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (fascistfirewall):
- - Avoid spurious warnings when ReachableAddresses or FascistFirewall
- is set. Fixes bug 20306; bugfix on 0.2.8.2-alpha.
-
diff --git a/changes/bug20307 b/changes/bug20307
deleted file mode 100644
index 9112c9c78d..0000000000
--- a/changes/bug20307
+++ /dev/null
@@ -1,7 +0,0 @@
- o Minor bugfixes (circuit, hidden service)
- - When closing a circuit, the reason for doing so was assigned from an int
- value to a uint16_t which is quite a problem for negative values that are
- our internal reasons (ex: END_CIRC_REASON_IP_NOW_REDUNDANT). On the HS
- side, this was causing introduction points to be flagged as unusable
- because the reason wasn't the right one due to the bad conversion.
- Partially fixes bug 21056 and fixes bug 20307; Bugfix on 0.2.8.1-alpha.
diff --git a/changes/bug20401 b/changes/bug20401
deleted file mode 100644
index 85ab3c7322..0000000000
--- a/changes/bug20401
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (relay):
- - Avoid a small memory leak when informing worker threads about rotated
- onion keys. Fixes bug 20401; bugfix on 0.2.6.3-alpha.
-
diff --git a/changes/bug20423 b/changes/bug20423
deleted file mode 100644
index 32bdc3f081..0000000000
--- a/changes/bug20423
+++ /dev/null
@@ -1,6 +0,0 @@
- o Major bugfixes:
- - For relays that don't know their own address, avoid attempting
- a local hostname resolve for each descriptor we download. Also cut
- down on the number of "Success: chose address 'x.x.x.x'" log lines.
- Fixes bugs 20423 and 20610; bugfix on 0.2.8.1-alpha.
-
diff --git a/changes/bug20472 b/changes/bug20472
deleted file mode 100644
index 4d90c39f5b..0000000000
--- a/changes/bug20472
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (circuits):
- - Remove a BUG warning in circuit_pick_extend_handshake. Instead, assume
- all nodes support EXTEND2. Use ntor whenever a key is available.
- Fixes bug 20472; bugfix on 0.2.9.3-alpha.
-
diff --git a/changes/bug20484 b/changes/bug20484
deleted file mode 100644
index 9a0b95cb39..0000000000
--- a/changes/bug20484
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (single onion services):
- - Start correctly when creating a single onion service in a
- directory that did not previously exist. Fixes bug 20484; bugfix on
- 0.2.9.3-alpha.
-
diff --git a/changes/bug20487 b/changes/bug20487
deleted file mode 100644
index 4435f14a95..0000000000
--- a/changes/bug20487
+++ /dev/null
@@ -1,4 +0,0 @@
- o Documentation:
- - Clarify that setting HiddenServiceNonAnonymousMode requires
- you to also set "SOCKSPort 0". Fixes bug 20487; bugfix on
- 0.2.9.3-alpha.
diff --git a/changes/bug20529 b/changes/bug20529
deleted file mode 100644
index 276be5b2b6..0000000000
--- a/changes/bug20529
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (hidden services):
- - When configuring hidden services, check every hidden service directory's
- permissions. Previously, we only checked the last hidden service.
- Fixes bug 20529; bugfix on 13942 commit 85bfad1 in 0.2.6.2-alpha.
diff --git a/changes/bug20533 b/changes/bug20533
deleted file mode 100644
index 7d1a456328..0000000000
--- a/changes/bug20533
+++ /dev/null
@@ -1,7 +0,0 @@
- o Minor bugfixes (consensus downloads):
- - If a consensus expires while we are waiting for certificates to download,
- stop waiting for certificates.
- - If we stop waiting for certificates less than a minute after we started
- downloading them, do not consider the certificate download failure a
- separate failure.
- Fixes bug 20533; bugfix on commit e0204f21 in 0.2.0.9-alpha.
diff --git a/changes/bug20534 b/changes/bug20534
deleted file mode 100644
index 49db433a01..0000000000
--- a/changes/bug20534
+++ /dev/null
@@ -1,8 +0,0 @@
- o Minor bugfixes (directory download scheduling):
- - Remove the maximum delay on exponential-backoff scheduling.
- Since we now allow an infinite number of failures (see ticket
- 20536), we must now allow the time to grow longer on each failure.
- Fixes part of bug 20534; bugfix on 0.2.9.1-alpha.
- - Use initial delays and decrements in download scheduling closer to
- those from 0.2.8. Fixes another part of bug 20534; bugfix on
- 0.2.9.1-alpha.
diff --git a/changes/bug20536 b/changes/bug20536
deleted file mode 100644
index 9e0dd164bb..0000000000
--- a/changes/bug20536
+++ /dev/null
@@ -1,6 +0,0 @@
- o Major bugfixes (download scheduling):
- - When using an exponential backoff schedule, do not give up on
- dowloading just because we have failed a bunch of times. Since
- each delay is longer than the last, retrying indefinitely won't
- hurt. Fixes bug 20536; bugfix on 0.2.9.1-alpha.
-
diff --git a/changes/bug20551 b/changes/bug20551
deleted file mode 100644
index b7ec4ca7cc..0000000000
--- a/changes/bug20551
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (compilation):
- - Fix implicit conversion warnings under OpenSSL 1.1.
- Fixes bug 20551; bugfix on 0.2.1.1-alpha.
diff --git a/changes/bug20553 b/changes/bug20553
deleted file mode 100644
index 12a2780303..0000000000
--- a/changes/bug20553
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (memory leak):
- - Work around a memory leak in OpenSSL 1.1 when encoding public keys.
- Fixes bug 20553; bugfix on 0.0.2pre8.
diff --git a/changes/bug20560 b/changes/bug20560
deleted file mode 100644
index 43d605b296..0000000000
--- a/changes/bug20560
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (portability):
- - Run correctly when built on Windows build environments that require
- _vcsprintf(). Fixes bug 20560; bugfix on 0.2.2.11-alpha.
-
diff --git a/changes/bug20587 b/changes/bug20587
deleted file mode 100644
index 341b001363..0000000000
--- a/changes/bug20587
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (download timing):
- - When determining when to download a directory object, handle times
- after 2038 if the operating system supports that. (Someday this will be
- important!) Fixes bug 20587; bugfix on 0.2.8.1-alpha.
-
diff --git a/changes/bug20588 b/changes/bug20588
deleted file mode 100644
index 832ef81336..0000000000
--- a/changes/bug20588
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor features (portability):
- - Fix compilation with OpenSSL 1.1 and less commonly-used
- CPU architectures. Closes ticket 20588.
diff --git a/changes/bug20591 b/changes/bug20591
deleted file mode 100644
index deaa738f5e..0000000000
--- a/changes/bug20591
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (relay bootstrap):
- - Ensure relays don't make multiple connections during bootstrap.
- Fixes bug 20591; bugfix on 0.2.8.1-alpha.
diff --git a/changes/bug20593 b/changes/bug20593
deleted file mode 100644
index e9f54d317a..0000000000
--- a/changes/bug20593
+++ /dev/null
@@ -1,6 +0,0 @@
- o Minor bugfixes (client directory scheduling):
- - Treat "relay too busy to answer request" as a failed request and a
- reason to back off on our retry frequency. This is safe now that
- exponential backups retry indefinitely, and avoids a bug where we would
- reset our download schedule erroneously.
- Fixes bug 20593; bugfix on 0.2.9.1-alpha.
diff --git a/changes/bug20597 b/changes/bug20597
deleted file mode 100644
index f199b63933..0000000000
--- a/changes/bug20597
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (test networks, exponential backoff):
- - When using exponential backoff in test networks, use a lower exponent,
- so the delays do not vary as much. This helps test networks bootstrap
- consistently. Fixes bug 20597; bugfix on 20499; not in any released
- version of tor.
diff --git a/changes/bug20613 b/changes/bug20613
deleted file mode 100644
index 19bb61f4e0..0000000000
--- a/changes/bug20613
+++ /dev/null
@@ -1,6 +0,0 @@
- o Minor bugfixes (single onion services, Tor2web):
- - Stop logging long-term one-hop circuits deliberately created by single
- onion services and Tor2web. These log messages are intended to diagnose
- issue 8387, which relates to circuits hanging around forever for no
- reason.
- Fixes bug 20613; bugfix on 0.2.9.1-alpha. Reported by "pastly".
diff --git a/changes/bug20634 b/changes/bug20634
deleted file mode 100644
index 62fc9f4787..0000000000
--- a/changes/bug20634
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (unit tests):
- - Stop spurious failures in the local interface address discovery unit
- tests. Fixes bug 20634; bugfix on 0.2.8.1-alpha; patch by Neel Chauhan.
diff --git a/changes/bug20638 b/changes/bug20638
deleted file mode 100644
index 260d7d0a75..0000000000
--- a/changes/bug20638
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (hidden services):
- - Stop ignoring hidden service key anonymity when first starting tor.
- Instead, refuse to start tor if any hidden service key has been used in
- a different hidden service anonymity mode.
- Fixes bug 20638; bugfix on 17178 in 0.2.9.3-alpha; reported by ahf.
diff --git a/changes/bug20710_025 b/changes/bug20710_025
deleted file mode 100644
index 12bd07536c..0000000000
--- a/changes/bug20710_025
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (memory leak, use-after-free, linux seccomp2 sandbox):
- - Fix a memory leak and use-after-free error when removing entries
- from the sandbox's getaddrinfo() cache. Fixes bug 20710; bugfix on
- 0.2.5.5-alpha. Patch from "cypherpunks".
diff --git a/changes/bug20711 b/changes/bug20711
new file mode 100644
index 0000000000..0bc0d94fb1
--- /dev/null
+++ b/changes/bug20711
@@ -0,0 +1,7 @@
+ o Minor bugfixes (directory mirrors):
+ - Allow relays to use directory mirrors without a DirPort: these relays
+ need to be contacted over their ORPorts using a begindir connection.
+ Fixes bug 20711; bugfix on 0.2.8.2-alpha.
+ - Clarify the message logged when a remote relay is unexpectedly missing
+ an ORPort or DirPort: users were confusing this with a local port.
+ Fixes bug 20711; bugfix on 0.2.8.2-alpha.
diff --git a/changes/bug20715 b/changes/bug20715
deleted file mode 100644
index 737a560cec..0000000000
--- a/changes/bug20715
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (memory leak)
- - When moving a signed descriptor object from a source to an existing
- destination, free the allocated memory inside that destination object.
- Bugfix on tor-0.2.8.3-alpha; Closes #20715.
diff --git a/changes/bug20716 b/changes/bug20716
deleted file mode 100644
index 37fd6feecf..0000000000
--- a/changes/bug20716
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (client, memory leak):
- - Fix a small memory leak when receiving AF_UNIX connections on
- a SocksPort. Fixes bug 20716; bugfix on 0.2.6.3-alpha.
diff --git a/changes/bug20810 b/changes/bug20810
deleted file mode 100644
index 5420a73175..0000000000
--- a/changes/bug20810
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (relay)
- - When computing old Tor protocol line version in protover, we were
- looking at 0.2.7.5 twice instead of a specific case for 0.2.9.1-alpha.
- Bugfix on tor-0.2.9.4-alpha.
diff --git a/changes/bug20864 b/changes/bug20864
deleted file mode 100644
index 7b8c70fad6..0000000000
--- a/changes/bug20864
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (unit tests, hidden services):
- - Remove a double-free in the single onion service unit test. Stop
- ignoring a return value. Make future changes less error-prone.
- Fixes bug 20864; bugfix on 0.2.9.6-rc.
diff --git a/changes/bug20875 b/changes/bug20875
deleted file mode 100644
index 6bba2cbc12..0000000000
--- a/changes/bug20875
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor bugfixes (download scheduling)
- - Resolve a "bug" warning when considering a download schedule whose
- delay had approached INT_MAX. Fixes 20875; bugfix on 0.2.9.5-alpha.
-
diff --git a/changes/bug20894 b/changes/bug20894
new file mode 100644
index 0000000000..2dbf9b9aa9
--- /dev/null
+++ b/changes/bug20894
@@ -0,0 +1,9 @@
+ o Major bugfixes (HTTP, parsing):
+ - When parsing a malformed content-length field from an HTTP message,
+ do not read off the end of the buffer. This bug was a potential
+ remote denial-of-service attack against Tor clients and relays.
+ A workaround was released in October 2016, which prevents this
+ bug from crashing Tor. This is a fix for the underlying issue,
+ which should no longer matter (if you applied the earlier patch).
+ Fixes bug 20894; bugfix on 0.2.0.16-alpha. Bug found by fuzzing
+ using AFL (http://lcamtuf.coredump.cx/afl/).
diff --git a/changes/bug20935 b/changes/bug20935
deleted file mode 100644
index 78068c7c06..0000000000
--- a/changes/bug20935
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (portability):
- - Use the correct spelling of MAC_OS_X_VERSION_10_12 on configure.ac
- Fixes bug 20935; bugfix on 0.2.9.6-rc.
diff --git a/changes/bug21007_case2 b/changes/bug21007_case2
new file mode 100644
index 0000000000..43344449ec
--- /dev/null
+++ b/changes/bug21007_case2
@@ -0,0 +1,4 @@
+ o Minor bugfixes (guards):
+ - Don't warn about a missing guard state on timeout-measurement
+ circuits: they aren't supposed to be using guards. Fixes an
+ instance of bug 21007; bugfix on 0.3.0.1-alpha.
diff --git a/changes/bug21018 b/changes/bug21018
deleted file mode 100644
index 49a8b47a25..0000000000
--- a/changes/bug21018
+++ /dev/null
@@ -1,11 +0,0 @@
- o Major bugfixes (parsing, security):
-
- - Fix a bug in parsing that could cause clients to read a single
- byte past the end of an allocated region. This bug could be
- used to cause hardened clients (built with
- --enable-expensive-hardening) to crash if they tried to visit
- a hostile hidden service. Non-hardened clients are only
- affected depending on the details of their platform's memory
- allocator. Fixes bug 21018; bugfix on 0.2.0.8-alpha. Found by
- using libFuzzer. Also tracked as TROVE-2016-12-002 and as
- CVE-2016-1254.
diff --git a/changes/bug21027 b/changes/bug21027
new file mode 100644
index 0000000000..d20df876fa
--- /dev/null
+++ b/changes/bug21027
@@ -0,0 +1,8 @@
+ o Major bugfixes (bridges):
+
+ - When the same bridge is configured multiple times at different
+ address:port combinations (but with the same identity), treat
+ those bridge instances as separate guards. This allows clients to
+ configure the same bridge with multiple pluggable transports, once
+ again. Fixes bug 21027; bugfix on 0.3.0.1-alpha.
+
diff --git a/changes/bug21035 b/changes/bug21035
deleted file mode 100644
index bbf3340787..0000000000
--- a/changes/bug21035
+++ /dev/null
@@ -1,6 +0,0 @@
- o Minor bugfixes (portability):
- - Avoid crashing when Tor is built using headers that contain
- CLOCK_MONOTONIC_COARSE, but then tries to run on an older kernel
- without CLOCK_MONOTONIC_COARSE. Fixes bug 21035; bugfix on
- 0.2.9.1-alpha.
-
diff --git a/changes/bug21051 b/changes/bug21051
deleted file mode 100644
index 8bb4f80c8e..0000000000
--- a/changes/bug21051
+++ /dev/null
@@ -1,3 +0,0 @@
- o Minor bugfixes (compilation):
- - Fix Libevent detection on platforms without Libevent 1 headers
- installed. Fixes bug 21051; bugfix on 0.2.9.1-alpha.
diff --git a/changes/bug21108_029 b/changes/bug21108_029
deleted file mode 100644
index 3a3f004fc6..0000000000
--- a/changes/bug21108_029
+++ /dev/null
@@ -1,6 +0,0 @@
- o Major bugfixes (directory authority):
- - During voting, when marking a node as a probable sybil, do not
- clear its BadExit flag: sybils can still be bad in other ways
- too. (We still clear the other flags.) Fixes bug 21108; bugfix
- on 0.2.0.13-alpha.
-
diff --git a/changes/bug21116 b/changes/bug21116
new file mode 100644
index 0000000000..2304ab0fd6
--- /dev/null
+++ b/changes/bug21116
@@ -0,0 +1,3 @@
+ o Minor bugfixes (test):
+ - Fix Raspbian build missing socket errno in test util. Fixes bug 21116.;
+ bugfix on tor-0.2.8.2. Patch by "hein".
diff --git a/changes/bug21280 b/changes/bug21280
deleted file mode 100644
index e9f0bc174c..0000000000
--- a/changes/bug21280
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor bugfixes (tor-resolve):
- - The tor-resolve command line tool now rejects hostnames over 255
- characters in length. Previously, it would silently truncate
- them, which could lead to bugs. Fixes bug 21280; bugfix on 0.0.9pre5.
- Patch by "junglefowl".
diff --git a/changes/bug21357 b/changes/bug21357
deleted file mode 100644
index a1cb43a78a..0000000000
--- a/changes/bug21357
+++ /dev/null
@@ -1,7 +0,0 @@
- o Major bugfixes (IPv6 Exits):
- - Stop rejecting all IPv6 traffic on Exits whose exit policy rejects IPv6
- addresses. Instead, only reject a port over IPv6 if the exit policy
- rejects that port on more than an IPv6 /16 of addresses. This bug was
- made worse by 17027 in 0.2.8.1-alpha, which rejects a relay's own IPv6
- address by default.
- Fixes bug 21357; bugfix on commit 004f3f4e53 in 0.2.4.7-alpha.
diff --git a/changes/bug21369_check b/changes/bug21369_check
new file mode 100644
index 0000000000..2cd808c9b6
--- /dev/null
+++ b/changes/bug21369_check
@@ -0,0 +1,3 @@
+ o Minor features (reliability, crash):
+ - Try better to detect problems in buffers where they might grow (or
+ think they have grown) over 2 GB in size. Diagnostic for bug 21369.
diff --git a/changes/bug21415 b/changes/bug21415
new file mode 100644
index 0000000000..f0aa72f81f
--- /dev/null
+++ b/changes/bug21415
@@ -0,0 +1,4 @@
+ o Minor bugfix (entry guards):
+ - Silence a BUG() warning when attempting to use a guard whose descriptor
+ we don't know and make this scenario more unlikely to happen. Fixes bug
+ 21415; bugfix on 0.3.0.1-alpha.
diff --git a/changes/bug21420 b/changes/bug21420
new file mode 100644
index 0000000000..014404466a
--- /dev/null
+++ b/changes/bug21420
@@ -0,0 +1,3 @@
+ o Minor bugfixes (certificate expiration time):
+ - Avoid using link certificates that don't become valid till
+ some time in the future. Fixes bug 21420; bugfix on 0.2.4.11-alpha
diff --git a/changes/bug21447 b/changes/bug21447
new file mode 100644
index 0000000000..c025b92313
--- /dev/null
+++ b/changes/bug21447
@@ -0,0 +1,4 @@
+ o Minor bugfixes (testing):
+ - Rename "make fuzz" to "make test-fuzz-corpora", since it doesn't
+ actually fuzz anything. Fixes bug 21447; bugfix on 0.3.0.3-alpha.
+
diff --git a/changes/bug21471 b/changes/bug21471
new file mode 100644
index 0000000000..684035b19c
--- /dev/null
+++ b/changes/bug21471
@@ -0,0 +1,5 @@
+ o Major bugfixes (hidden service directory v3):
+ - When a descriptor lookup was done and it was not found in the directory
+ cache, it would crash on a NULL pointer instead of returning the 404
+ code back to the client like it was suppose to. Fixes bug 21471.;
+ bugfixes on tor-0.3.0.1-alpha.
diff --git a/changes/bug21472 b/changes/bug21472
new file mode 100644
index 0000000000..f31ec9157e
--- /dev/null
+++ b/changes/bug21472
@@ -0,0 +1,3 @@
+ o Documentation:
+ - Small fixes to the fuzzing documentation. Closes ticket
+ 21472.
diff --git a/changes/bug21492 b/changes/bug21492
new file mode 100644
index 0000000000..2ed7947771
--- /dev/null
+++ b/changes/bug21492
@@ -0,0 +1,5 @@
+ o Minor bugfixes (correctness):
+ - Remove a redundant check for the UseEntryGuards option from the
+ options_transition_affects_guards() function. Fixes bug 21492;
+ bugfix on 0.3.0.1-alpha.
+
diff --git a/changes/bug21553 b/changes/bug21553
new file mode 100644
index 0000000000..6ffa3e29a2
--- /dev/null
+++ b/changes/bug21553
@@ -0,0 +1,7 @@
+ o Minor bugfixes (hidden service):
+ - When encoding a legacy ESTABLISH_INTRO cell, we were using the sizeof()
+ on a pointer instead of real size of the destination buffer leading to
+ an overflow passing an enormous value to the signing digest function.
+ Fortunately, that value was only used to make sure the destination
+ buffer length was big enough for the key size and in this case it was.
+ Fixes bug 21553; bugfix on tor-0.3.0.1-alpha.
diff --git a/changes/bug21562 b/changes/bug21562
new file mode 100644
index 0000000000..48396a00e7
--- /dev/null
+++ b/changes/bug21562
@@ -0,0 +1,4 @@
+ o Minor bugfixes (testing):
+ - Use bash in src/test/test-network.sh. This ensures we reliably call
+ chutney's newer tools/test-network.sh when available.
+ Fixes bug 21562; bugfix on tor-0.2.9.1-alpha.
diff --git a/changes/bug21581 b/changes/bug21581
new file mode 100644
index 0000000000..1077719856
--- /dev/null
+++ b/changes/bug21581
@@ -0,0 +1,5 @@
+ o Minor bugfixes (testing):
+ - Restore support for test-network.sh on BSD and other systems without
+ bash. (But use bash if it's available.) This is a workaround until we
+ remove bash-specific code in 19699.
+ Fixes bug 21581; bugfix on 21562, not in any released version of tor.
diff --git a/changes/bug21594 b/changes/bug21594
new file mode 100644
index 0000000000..e624d1226d
--- /dev/null
+++ b/changes/bug21594
@@ -0,0 +1,5 @@
+ o Minor bugfixes (hidden services):
+ - Make hidden services with 8 to 10 introduction points check for failed
+ circuits immediately after startup. Previously, they would wait for 5
+ minutes before performing their first checks. Fixes bug 21594; bugfix on
+ commit 190aac0eab9 in Tor 0.2.3.9-alpha. Reported by alecmuffett.
diff --git a/changes/bug21596 b/changes/bug21596
new file mode 100644
index 0000000000..ec0a46bb81
--- /dev/null
+++ b/changes/bug21596
@@ -0,0 +1,5 @@
+ o Minor bugfixes (hidden services):
+ - Make hidden services check for failed intro point connections, even when
+ they have exceeded their intro point creation limit. Fixes bug 21596;
+ bugfix on commit d67bf8b2f23 in Tor 0.2.7.2-alpha. Reported by
+ alecmuffett.
diff --git a/changes/bug21682 b/changes/bug21682
new file mode 100644
index 0000000000..ab7126e4d6
--- /dev/null
+++ b/changes/bug21682
@@ -0,0 +1,3 @@
+ o Minor bugfixes (memory leaks):
+ - Fix a memory leak when using GETCONF on a port option.
+ Fixes bug 21682; bugfix on 0.3.0.3-alpha.
diff --git a/changes/bug21720 b/changes/bug21720
new file mode 100644
index 0000000000..6d2fbcf711
--- /dev/null
+++ b/changes/bug21720
@@ -0,0 +1,5 @@
+ o Documentation:
+ - Update the description of the directory server options in the manual
+ page, to clarify that DirPort is no longer necessary to be a directory
+ cache. Closes ticket 21720.
+
diff --git a/changes/bug21771 b/changes/bug21771
new file mode 100644
index 0000000000..f814c75c0b
--- /dev/null
+++ b/changes/bug21771
@@ -0,0 +1,5 @@
+ o Minor bugfixes (guard selection):
+ - Fix a guard selection bug where Tor would refuse to bootstrap in some
+ cases if the user swapped a bridge for another bridge in their
+ configuration file.
+ Fixes bug 21771; bugfix on 0.3.0.1-alpha. Reported by "torvlnt33r".
diff --git a/changes/bug21799 b/changes/bug21799
new file mode 100644
index 0000000000..ee2e904a35
--- /dev/null
+++ b/changes/bug21799
@@ -0,0 +1,6 @@
+ o Minor bugfixes (tests):
+ - Run the entry_guard_parse_from_state_full test with the time set
+ to a specific date. (The guard state that this test was parsing
+ contained guards that had expired since the test was first
+ written.) Fixes bug 21799; bugfix on 0.3.0.1-alpha.
+
diff --git a/changes/bug21825 b/changes/bug21825
new file mode 100644
index 0000000000..8f14b32f84
--- /dev/null
+++ b/changes/bug21825
@@ -0,0 +1,6 @@
+ o Minor bugfix (compilation):
+ - Functions in hs_service.c was only compiled for unit test making the
+ created object (.o) contain no symbols in src/or/libor.a resulting in a
+ compilation warning from clang. We now expose those functions for the
+ unit tests. This will be changed in 0.3.2 release. Fixes bug 21825.;
+ bugfix on tor-0.3.0.1-alpha.
diff --git a/changes/bug21894_029 b/changes/bug21894_029
new file mode 100644
index 0000000000..e3a84fa721
--- /dev/null
+++ b/changes/bug21894_029
@@ -0,0 +1,5 @@
+ o Minor bugfixes (crash prevention):
+ - Fix an (currently untriggerable, but potentially dangerous) crash
+ bug when base32-encoding inputs whose sizes are not a multiple of
+ 5. Fixes bug 21894; bugfix on 0.2.9.1-alpha.
+
diff --git a/changes/bug21969 b/changes/bug21969
new file mode 100644
index 0000000000..9b116fc4cc
--- /dev/null
+++ b/changes/bug21969
@@ -0,0 +1,3 @@
+ o Major bugfixes (entry guards):
+ - Don't block bootstrapping when a primary bridge is offline and we can't
+ get its descriptor. Fixes bug 21969; bugfix on 0.3.0.3-alpha.
diff --git a/changes/bug22246 b/changes/bug22246
new file mode 100644
index 0000000000..dbdf31a433
--- /dev/null
+++ b/changes/bug22246
@@ -0,0 +1,6 @@
+ o Major bugfixes (hidden service directory, security):
+ - Fix an assertion failure in the hidden service directory code, which
+ could be used by an attacker to remotely cause a Tor relay process to
+ exit. Relays running earlier versions of Tor 0.3.0.x should upgrade.
+ This security issue is tracked as tracked as
+ TROVE-2017-002. Fixes bug 22246; bugfix on 0.3.0.1-alpha.
diff --git a/changes/bug22252 b/changes/bug22252
new file mode 100644
index 0000000000..42b9d8e095
--- /dev/null
+++ b/changes/bug22252
@@ -0,0 +1,3 @@
+ o Minor bugfixes (configuration):
+ - Do not crash when starting with LearnCircuitBuildTimeout 0.
+ Fixes bug 22252; bugfix on 0.2.9.3-alpha.
diff --git a/changes/bug22400_01 b/changes/bug22400_01
new file mode 100644
index 0000000000..454c5f746f
--- /dev/null
+++ b/changes/bug22400_01
@@ -0,0 +1,4 @@
+ 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/bug22447 b/changes/bug22447
new file mode 100644
index 0000000000..f5649d633c
--- /dev/null
+++ b/changes/bug22447
@@ -0,0 +1,3 @@
+ o Major bugfixes (hidden service v3):
+ - HSDir failed to validate the encrypted size of a v3 descriptor and thus
+ rejecting it. Fixes bug 22447; bugfix on tor-0.3.0.1-alpha.
diff --git a/changes/bug22460_case1 b/changes/bug22460_case1
new file mode 100644
index 0000000000..cfe78ad791
--- /dev/null
+++ b/changes/bug22460_case1
@@ -0,0 +1,16 @@
+ 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.
+
+
diff --git a/changes/bug22466_regenerate b/changes/bug22466_regenerate
new file mode 100644
index 0000000000..8dbda89c8f
--- /dev/null
+++ b/changes/bug22466_regenerate
@@ -0,0 +1,8 @@
+ 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.
+
diff --git a/changes/bug22753 b/changes/bug22753
new file mode 100644
index 0000000000..32a6dfa56c
--- /dev/null
+++ b/changes/bug22753
@@ -0,0 +1,7 @@
+ 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/bug22803 b/changes/bug22803
new file mode 100644
index 0000000000..80b4b9f589
--- /dev/null
+++ b/changes/bug22803
@@ -0,0 +1,3 @@
+ 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/bug23078 b/changes/bug23078
new file mode 100644
index 0000000000..67624007cf
--- /dev/null
+++ b/changes/bug23078
@@ -0,0 +1,7 @@
+ 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/bug23610 b/changes/bug23610
new file mode 100644
index 0000000000..f2dc8bd1a6
--- /dev/null
+++ b/changes/bug23610
@@ -0,0 +1,4 @@
+ 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/bug23862 b/changes/bug23862
new file mode 100644
index 0000000000..301ce73672
--- /dev/null
+++ b/changes/bug23862
@@ -0,0 +1,5 @@
+ o Minor bugfixes (entry guards):
+ - Tor now updates its guard state when it reads a consensus regardless of
+ whether it's missing descriptors. That makes tor use its primary guards
+ to fetch descriptors in some edge cases where it would have used fallback
+ directories in the past. Fixes bug 23862; bugfix on 0.3.0.1-alpha. \ No newline at end of file
diff --git a/changes/feature21570 b/changes/feature21570
new file mode 100644
index 0000000000..40555eefa9
--- /dev/null
+++ b/changes/feature21570
@@ -0,0 +1,5 @@
+ o Minor features (testing):
+ - During 'make test-network-all', if tor logs any warnings, ask chutney
+ to output them. Requires a recent version of chutney with the 21572
+ patch.
+ Implements 21570.
diff --git a/changes/geoip-december2016 b/changes/geoip-december2016
deleted file mode 100644
index 60754ea21d..0000000000
--- a/changes/geoip-december2016
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor features:
- - Update geoip and geoip6 to the December 7 2016 Maxmind GeoLite2
- Country database.
-
diff --git a/changes/geoip-january2017 b/changes/geoip-january2017
deleted file mode 100644
index 77bc9a5991..0000000000
--- a/changes/geoip-january2017
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor features (geoip):
- - Update geoip and geoip6 to the January 4 2017 Maxmind GeoLite2
- Country database.
-
diff --git a/changes/geoip-november2016 b/changes/geoip-november2016
deleted file mode 100644
index b3f9913bb1..0000000000
--- a/changes/geoip-november2016
+++ /dev/null
@@ -1,4 +0,0 @@
- o Minor features (ge0oip):
- - Update geoip and geoip6 to the November 3 2016 Maxmind GeoLite2
- Country database.
-
diff --git a/changes/more_module_docs b/changes/more_module_docs
deleted file mode 100644
index 0066ddfcf0..0000000000
--- a/changes/more_module_docs
+++ /dev/null
@@ -1,4 +0,0 @@
- o Documentation:
- - Module-level documentation for several more modules. Closes tickets
- 19287 and
- 19290.
diff --git a/changes/ticket19769 b/changes/ticket19769
deleted file mode 100644
index 9fc05c3e9e..0000000000
--- a/changes/ticket19769
+++ /dev/null
@@ -1,7 +0,0 @@
- o Major features (security):
- - 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.
diff --git a/changes/ticket20170-v3 b/changes/ticket20170-v3
deleted file mode 100644
index d634e72053..0000000000
--- a/changes/ticket20170-v3
+++ /dev/null
@@ -1,5 +0,0 @@
- o Minor features (fallback directory list):
- - Replace the 81 remaining fallbacks of the 100 originally introduced
- in Tor 0.2.8.3-alpha in March 2016, with a list of 177 fallbacks
- (123 new, 54 existing, 27 removed) generated in December 2016.
- Resolves ticket 20170.
diff --git a/changes/ticket20656 b/changes/ticket20656
new file mode 100644
index 0000000000..28192e8978
--- /dev/null
+++ b/changes/ticket20656
@@ -0,0 +1,3 @@
+ o Minor feature (protover):
+ - Add new protocol version for proposal 224. HSIntro now advertises
+ version "3-4" and HSDir version "1-2". Fixes ticket 20656.
diff --git a/changes/trove-2017-001 b/changes/trove-2017-001
deleted file mode 100644
index 5187e6d5f1..0000000000
--- a/changes/trove-2017-001
+++ /dev/null
@@ -1,8 +0,0 @@
- o Major bugfixes (security):
- - Downgrade the "-ftrapv" option from "always on" to "only on when
- --enable-expensive-hardening is provided." This hardening option, like
- others, can turn survivable bugs into crashes--and having it on by
- default made a (relatively harmless) integer overflow bug into a
- denial-of-service bug. Fixes bug 21278 (TROVE-2017-001); bugfix on
- 0.2.9.1-alpha.
-
diff --git a/changes/trove-2017-004 b/changes/trove-2017-004
new file mode 100644
index 0000000000..af1567f220
--- /dev/null
+++ b/changes/trove-2017-004
@@ -0,0 +1,6 @@
+ o Major bugfixes (hidden service, relay, security):
+ - Fix an assertion failure when a hidden service handles a
+ malformed BEGIN cell. This bug resulted in the service crashing
+ triggered by a tor_assert(). Fixes bug 22493, tracked as
+ TROVE-2017-004 and as CVE-2017-0375; bugfix on 0.3.0.1-alpha.
+ Found by armadev.
diff --git a/changes/trove-2017-012-part2 b/changes/trove-2017-012-part2
new file mode 100644
index 0000000000..ed994c5b02
--- /dev/null
+++ b/changes/trove-2017-012-part2
@@ -0,0 +1,5 @@
+ o Major bugfixes (security, relay):
+ - When running as a relay, make sure that we never ever choose ourselves
+ as a guard. Previously, this was possible. Fixes part of bug 21534;
+ bugfix on 0.3.0.1-alpha. This issue is also tracked as TROVE-2017-012
+ and CVE-2017-8822.
diff --git a/configure.ac b/configure.ac
index 17e6fca0d6..88bc968848 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,14 +4,14 @@ dnl Copyright (c) 2007-2015, The Tor Project, Inc.
dnl See LICENSE for licensing information
AC_PREREQ([2.63])
-AC_INIT([tor],[0.2.9.14-dev])
+AC_INIT([tor],[0.3.0.13-dev])
AC_CONFIG_SRCDIR([src/or/main.c])
AC_CONFIG_MACRO_DIR([m4])
# "foreign" means we don't follow GNU package layout standards
# "1.11" means we require automake version 1.11 or newer
# "subdir-objects" means put .o files in the same directory as the .c files
-AM_INIT_AUTOMAKE([foreign 1.11 subdir-objects])
+AM_INIT_AUTOMAKE([foreign 1.11 subdir-objects -Wall -Werror])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_CONFIG_HEADERS([orconfig.h])
@@ -49,6 +49,10 @@ AC_ARG_ENABLE(asserts-in-tests,
AS_HELP_STRING(--disable-asserts-in-tests, [disable tor_assert() calls in the unit tests, for branch coverage]))
AC_ARG_ENABLE(system-torrc,
AS_HELP_STRING(--disable-system-torrc, [don't look for a system-wide torrc file]))
+AC_ARG_ENABLE(libfuzzer,
+ AS_HELP_STRING(--enable-libfuzzer, [build extra fuzzers based on 'libfuzzer']))
+AC_ARG_ENABLE(oss-fuzz,
+ AS_HELP_STRING(--enable-oss-fuzz, [build extra fuzzers based on 'oss-fuzz' environment]))
if test "x$enable_coverage" != "xyes" -a "x$enable_asserts_in_tests" = "xno" ; then
AC_MSG_ERROR([Can't disable assertions outside of coverage build])
@@ -57,6 +61,8 @@ fi
AM_CONDITIONAL(UNITTESTS_ENABLED, test "x$enable_unittests" != "xno")
AM_CONDITIONAL(COVERAGE_ENABLED, test "x$enable_coverage" = "xyes")
AM_CONDITIONAL(DISABLE_ASSERTS_IN_UNIT_TESTS, test "x$enable_asserts_in_tests" = "xno")
+AM_CONDITIONAL(LIBFUZZER_ENABLED, test "x$enable_libfuzzer" = "xyes")
+AM_CONDITIONAL(OSS_FUZZ_ENABLED, test "x$enable_oss_fuzz" = "xyes")
if test "$enable_static_tor" = "yes"; then
enable_static_libevent="yes";
@@ -139,8 +145,14 @@ dnl Others suggest '/gs /safeseh /nxcompat /dynamicbase' for non-gcc on Windows
AC_ARG_ENABLE(gcc-hardening,
AS_HELP_STRING(--disable-gcc-hardening, [disable compiler security checks]))
+dnl Deprecated --enable-expensive-hardening but keep it for now for backward compat.
AC_ARG_ENABLE(expensive-hardening,
- AS_HELP_STRING(--enable-expensive-hardening, [enable more expensive compiler hardening; makes Tor slower]))
+ AS_HELP_STRING(--enable-expensive-hardening, [enable more fragile and expensive compiler hardening; makes Tor slower]))
+AC_ARG_ENABLE(fragile-hardening,
+ AS_HELP_STRING(--enable-fragile-hardening, [enable more fragile and expensive compiler hardening; makes Tor slower]))
+if test "x$enable_expensive_hardening" = "xyes" || test "x$enable_fragile_hardening" = "xyes"; then
+ fragile_hardening="yes"
+fi
dnl Linker hardening options
dnl Currently these options are ELF specific - you can't use this with MacOSX
@@ -170,11 +182,15 @@ AC_ARG_ENABLE(seccomp,
AC_ARG_ENABLE(libscrypt,
AS_HELP_STRING(--disable-libscrypt, [do not attempt to use libscrypt]))
-dnl check for the correct "ar" when cross-compiling
-AN_MAKEVAR([AR], [AC_PROG_AR])
-AN_PROGRAM([ar], [AC_PROG_AR])
-AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL([AR], [ar], [ar])])
-AC_PROG_AR
+dnl check for the correct "ar" when cross-compiling.
+dnl (AM_PROG_AR was new in automake 1.11.2, which we do not yet require,
+dnl so kludge up a replacement for the case where it isn't there yet.)
+m4_ifdef([AM_PROG_AR],
+ [AM_PROG_AR],
+ [AN_MAKEVAR([AR], [AC_PROG_AR])
+ AN_PROGRAM([ar], [AC_PROG_AR])
+ AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL([AR], [ar], [:])])
+ AC_PROG_AR])
dnl Check whether the above macro has settled for a simply named tool even
dnl though we're cross compiling. We must do this before running AC_PROG_CC,
@@ -194,11 +210,11 @@ AC_PROG_CC
AC_PROG_CPP
AC_PROG_MAKE_SET
AC_PROG_RANLIB
+AC_PROG_SED
-AC_PATH_PROG([PERL], [perl])
-
-dnl autoconf 2.59 appears not to support AC_PROG_SED
-AC_CHECK_PROG([SED],[sed],[sed],[/bin/false])
+AC_ARG_VAR([PERL], [path to Perl binary])
+AC_CHECK_PROGS([PERL], [perl])
+AM_CONDITIONAL(USE_PERL, [test "x$ac_cv_prog_PERL" != "x"])
dnl check for asciidoc and a2x
AC_PATH_PROG([ASCIIDOC], [asciidoc], none)
@@ -206,9 +222,6 @@ AC_PATH_PROGS([A2X], [a2x a2x.py], none)
AM_CONDITIONAL(USE_ASCIIDOC, test "x$asciidoc" = "xtrue")
-AM_CONDITIONAL(USE_FW_HELPER, test "x$natpmp" = "xtrue" || test "x$upnp" = "xtrue")
-AM_CONDITIONAL(NAT_PMP, test "x$natpmp" = "xtrue")
-AM_CONDITIONAL(MINIUPNPC, test "x$upnp" = "xtrue")
AM_PROG_CC_C_O
AC_PROG_CC_C99
@@ -427,7 +440,7 @@ AC_CHECK_FUNCS(
# checks. So we should only probe for those functions if we are sure that we
# are not targetting OSX 10.11 or earlier.
AC_MSG_CHECKING([for a pre-Sierra OSX build target])
-AC_TRY_COMPILE([
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#ifdef __APPLE__
# include <AvailabilityMacros.h>
# ifndef MAC_OS_X_VERSION_10_12
@@ -439,7 +452,7 @@ AC_TRY_COMPILE([
# endif
# endif
#endif
-], [],
+]], [[]])],
[on_macos_pre_10_12=no ; AC_MSG_RESULT([no])],
[on_macos_pre_10_12=yes; AC_MSG_RESULT([yes])])
@@ -639,16 +652,16 @@ LIBS="$TOR_OPENSSL_LIBS $LIBS"
LDFLAGS="$TOR_LDFLAGS_openssl $LDFLAGS"
CPPFLAGS="$TOR_CPPFLAGS_openssl $CPPFLAGS"
-AC_TRY_COMPILE([
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <openssl/opensslv.h>
#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x1000100fL
#error "too old"
#endif
- ], [],
+ ]], [[]])],
[ : ],
- [ AC_ERROR([OpenSSL is too old. We require 1.0.1 or later. You can specify a path to a newer one with --with-openssl-dir.]) ])
+ [ AC_MSG_ERROR([OpenSSL is too old. We require 1.0.1 or later. You can specify a path to a newer one with --with-openssl-dir.]) ])
-AC_TRY_COMPILE([
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#if defined(OPENSSL_NO_EC) || defined(OPENSSL_NO_ECDH) || defined(OPENSSL_NO_ECDSA)
@@ -657,9 +670,9 @@ AC_TRY_COMPILE([
#if !defined(NID_X9_62_prime256v1) || !defined(NID_secp224r1)
#error "curves unavailable"
#endif
- ], [],
+ ]], [[]])],
[ : ],
- [ AC_ERROR([OpenSSL is built without full ECC support, including curves P256 and P224. You can specify a path to one with ECC support with --with-openssl-dir.]) ])
+ [ AC_MSG_ERROR([OpenSSL is built without full ECC support, including curves P256 and P224. You can specify a path to one with ECC support with --with-openssl-dir.]) ])
AC_CHECK_MEMBERS([struct ssl_method_st.get_cipher_by_char], , ,
[#include <openssl/ssl.h>
@@ -739,6 +752,7 @@ CFLAGS_FWRAPV=
CFLAGS_ASAN=
CFLAGS_UBSAN=
+
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [
#if !defined(__clang__)
#error
@@ -762,21 +776,21 @@ m4_ifdef([AS_VAR_IF],[
AS_VAR_POPDEF([can_compile])
TOR_CHECK_CFLAGS(-Wstack-protector)
TOR_CHECK_CFLAGS(--param ssp-buffer-size=1)
- if test "$bwin32" = "false"; then
+ if test "$bwin32" = "false" && test "$enable_libfuzzer" != "yes" && test "$enable_oss_fuzz" != "yes"; then
TOR_CHECK_CFLAGS(-fPIE)
TOR_CHECK_LDFLAGS(-pie, "$all_ldflags_for_check", "$all_libs_for_check")
fi
TOR_TRY_COMPILE_WITH_CFLAGS(-fwrapv, also_link, CFLAGS_FWRAPV="-fwrapv", true)
fi
-if test "x$enable_expensive_hardening" = "xyes"; then
+if test "$fragile_hardening" = "yes"; then
TOR_TRY_COMPILE_WITH_CFLAGS(-ftrapv, also_link, CFLAGS_FTRAPV="-ftrapv", true)
if test "$tor_cv_cflags__ftrapv" = "yes" && test "$tor_can_link__ftrapv" != "yes"; then
AC_MSG_WARN([The compiler supports -ftrapv, but for some reason I was not able to link with -ftrapv. Are you missing run-time support? Run-time hardening will not work as well as it should.])
fi
if test "$tor_cv_cflags__ftrapv" != "yes"; then
- AC_MSG_ERROR([You requested expensive hardening, but the compiler does not seem to support -ftrapv.])
+ AC_MSG_ERROR([You requested fragile hardening, but the compiler does not seem to support -ftrapv.])
fi
TOR_TRY_COMPILE_WITH_CFLAGS([-fsanitize=address], also_link, CFLAGS_ASAN="-fsanitize=address", true)
@@ -858,7 +872,7 @@ saved_CFLAGS="$CFLAGS"
TOR_CHECK_CFLAGS(-fomit-frame-pointer)
F_OMIT_FRAME_POINTER=''
if test "$saved_CFLAGS" != "$CFLAGS"; then
- if test "x$enable_expensive_hardening" != "xyes"; then
+ if test "$fragile_hardening" = "yes"; then
F_OMIT_FRAME_POINTER='-fomit-frame-pointer'
fi
fi
@@ -1136,10 +1150,6 @@ if test "x$linux_netfilter_ipv6_ip6_tables" = "x1"; then
fi
if test "x$transparent_ok" = "x1"; then
AC_DEFINE(USE_TRANSPARENT, 1, "Define to enable transparent proxy support")
- case "$host" in
- *-*-openbsd* | *-*-bitrig*)
- AC_DEFINE(OPENBSD, 1, "Define to handle pf on OpenBSD properly") ;;
- esac
else
AC_MSG_NOTICE([Transparent proxy support enabled, but missing headers.])
fi
@@ -1443,6 +1453,14 @@ AC_CHECK_DECLS([mlockall], , , [
#include <sys/mman.h>
#endif])
+# Some MinGW environments don't have getpagesize in unistd.h. We don't use
+# AC_CHECK_FUNCS(getpagesize), because other environments rename getpagesize
+# using macros
+AC_CHECK_DECLS([getpagesize], , , [
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif])
+
# Allow user to specify an alternate syslog facility
AC_ARG_WITH(syslog-facility,
AS_HELP_STRING(--with-syslog-facility=LOG, [syslog facility to use (default=LOG_DAEMON)]),
@@ -1462,9 +1480,9 @@ AC_CHECK_FUNC(gethostbyname_r, [
AC_MSG_CHECKING([how many arguments gethostbyname_r() wants])
OLD_CFLAGS=$CFLAGS
CFLAGS="$CFLAGS $MY_CPPFLAGS $MY_THREAD_CPPFLAGS $MY_CFLAGS"
- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <netdb.h>
- ], [[
+ ]], [[
char *cp1, *cp2;
struct hostent *h1, *h2;
int i1, i2;
@@ -1475,27 +1493,27 @@ AC_CHECK_FUNC(gethostbyname_r, [
[Define this if gethostbyname_r takes 6 arguments])
AC_MSG_RESULT(6)
], [
- AC_TRY_COMPILE([
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <netdb.h>
- ], [
+ ]], [[
char *cp1, *cp2;
struct hostent *h1;
int i1, i2;
(void)gethostbyname_r(cp1,h1,cp2,i1,&i2);
- ], [
+ ]])], [
AC_DEFINE(HAVE_GETHOSTBYNAME_R)
AC_DEFINE(HAVE_GETHOSTBYNAME_R_5_ARG, 1,
[Define this if gethostbyname_r takes 5 arguments])
AC_MSG_RESULT(5)
], [
- AC_TRY_COMPILE([
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#include <netdb.h>
- ], [
+ ]], [[
char *cp1;
struct hostent *h1;
struct hostent_data hd;
(void) gethostbyname_r(cp1,h1,&hd);
- ], [
+ ]])], [
AC_DEFINE(HAVE_GETHOSTBYNAME_R)
AC_DEFINE(HAVE_GETHOSTBYNAME_R_3_ARG, 1,
[Define this if gethostbyname_r takes 3 arguments])
@@ -1949,4 +1967,19 @@ if test "x$asciidoc" = "xtrue" && test "$ASCIIDOC" = "none"; then
done
fi
+if test "$fragile_hardening" = "yes"; then
+ AC_MSG_WARN([
+
+============
+Warning! Building Tor with --enable-fragile-hardening (also known as
+--enable-expensive-hardening) makes some kinds of attacks harder, but makes
+other kinds of attacks easier. A Tor instance build with this option will be
+somewhat less vulnerable to remote code execution, arithmetic overflow, or
+out-of-bounds read/writes... but at the cost of becoming more vulnerable to
+denial of service attacks. For more information, see
+https://trac.torproject.org/projects/tor/wiki/doc/TorFragileHardening
+============
+ ])
+fi
+
AC_OUTPUT
diff --git a/contrib/win32build/tor-mingw.nsi.in b/contrib/win32build/tor-mingw.nsi.in
index 83c6d5c5e9..8cb6fb1d35 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.2.9.14-dev"
+!define VERSION "0.3.0.13-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 f1c65850a4..01212a9919 100644
--- a/doc/HACKING/CodingStandards.md
+++ b/doc/HACKING/CodingStandards.md
@@ -93,6 +93,10 @@ What needs a changes file?
rewrites. Anything about which somebody might plausibly wonder "when
did that happen, and/or why did we do that" 6 months down the line.
+What does not need a changes file?
+
+ * Bugfixes for code that hasn't shipped in any released version of Tor
+
Why use changes files instead of Git commit messages?
* Git commit messages are written for developers, not users, and they
diff --git a/doc/HACKING/Fuzzing.md b/doc/HACKING/Fuzzing.md
new file mode 100644
index 0000000000..2039d6a4c0
--- /dev/null
+++ b/doc/HACKING/Fuzzing.md
@@ -0,0 +1,123 @@
+= Fuzzing Tor
+
+== The simple version (no fuzzing, only tests)
+
+Check out fuzzing-corpora, and set TOR_FUZZ_CORPORA to point to the place
+where you checked it out.
+
+To run the fuzzing test cases in a deterministic fashion, use:
+ make test-fuzz-corpora
+
+This won't actually fuzz Tor! It will just run all the fuzz binaries
+on our existing set of testcases for the fuzzer.
+
+
+== Different kinds of fuzzing
+
+Right now we support three different kinds of fuzzer.
+
+First, there's American Fuzzy Lop (AFL), a fuzzer that works by forking
+a target binary and passing it lots of different inputs on stdin. It's the
+trickiest one to set up, so I'll be describing it more below.
+
+Second, there's libFuzzer, a llvm-based fuzzer that you link in as a library,
+and it runs a target function over and over. To use this one, you'll need to
+have a reasonably recent clang and libfuzzer installed. At that point, you
+just build with --enable-expensive-hardening and --enable-libfuzzer. That
+will produce a set of binaries in src/test/fuzz/lf-fuzz-* . These programs
+take as input a series of directories full of fuzzing examples. For more
+information on libfuzzer, see http://llvm.org/docs/LibFuzzer.html
+
+Third, there's Google's OSS-Fuzz infrastructure, which expects to get all of
+its. For more on this, see https://github.com/google/oss-fuzz and the
+projects/tor subdirectory. You'll need to mess around with Docker a bit to
+test this one out; it's meant to run on Google's infrastructure.
+
+In all cases, you'll need some starting examples to give the fuzzer when it
+starts out. There's a set in the "fuzzing-corpora" git repository. Try
+setting TOR_FUZZ_CORPORA to point to a checkout of that repository
+
+== Writing Tor fuzzers
+
+A tor fuzzing harness should have:
+* a fuzz_init() function to set up any necessary global state.
+* a fuzz_main() function to receive input and pass it to a parser.
+* a fuzz_cleanup() function to clear global state.
+
+Most fuzzing frameworks will produce many invalid inputs - a tor fuzzing
+harness should rejecting invalid inputs without crashing or behaving badly.
+
+But the fuzzing harness should crash if tor fails an assertion, triggers a
+bug, or accesses memory it shouldn't. This helps fuzzing frameworks detect
+"interesting" cases.
+
+
+== Guided Fuzzing with AFL
+
+There is no HTTPS, hash, or signature for American Fuzzy Lop's source code, so
+its integrity can't be verified. That said, you really shouldn't fuzz on a
+machine you care about, anyway.
+
+To Build:
+ Get AFL from http://lcamtuf.coredump.cx/afl/ and unpack it
+ cd afl
+ make
+ cd ../tor
+ PATH=$PATH:../afl/ CC="../afl/afl-gcc" ./configure --enable-expensive-hardening
+ AFL_HARDEN=1 make clean fuzzers
+
+To Find The ASAN Memory Limit: (64-bit only)
+
+On 64-bit platforms, afl needs to know how much memory ASAN uses,
+because ASAN tends to allocate a ridiculous amount of virtual memory,
+and then not actually use it.
+
+Read afl/docs/notes_for_asan.txt for more details.
+
+ Download recidivm from http://jwilk.net/software/recidivm
+ Download the signature
+ Check the signature
+ tar xvzf recidivm*.tar.gz
+ cd recidivm*
+ make
+ /path/to/recidivm -v src/test/fuzz/fuzz-http
+ Use the final "ok" figure as the input to -m when calling afl-fuzz
+ (Normally, recidivm would output a figure automatically, but in some cases,
+ the fuzzing harness will hang when the memory limit is too small.)
+
+You could also just say "none" instead of the memory limit below, if you
+don't care about memory limits.
+
+
+To Run:
+ mkdir -p src/test/fuzz/fuzz_http_findings
+ ../afl/afl-fuzz -i ${TOR_FUZZ_CORPORA}/http -o src/test/fuzz/fuzz_http_findings -m <asan-memory-limit> -- src/test/fuzz/fuzz-http
+
+
+AFL has a multi-core mode, check the documentation for details.
+You might find the included fuzz-multi.sh script useful for this.
+
+macOS (OS X) requires slightly more preparation, including:
+* using afl-clang (or afl-clang-fast from the llvm directory)
+* disabling external crash reporting (AFL will guide you through this step)
+
+== Triaging Issues
+
+Crashes are usually interesting, particularly if using AFL_HARDEN=1 and --enable-expensive-hardening. Sometimes crashes are due to bugs in the harness code.
+
+Hangs might be interesting, but they might also be spurious machine slowdowns.
+Check if a hang is reproducible before reporting it. Sometimes, processing
+valid inputs may take a second or so, particularly with the fuzzer and
+sanitizers enabled.
+
+To see what fuzz-http is doing with a test case, call it like this:
+ src/test/fuzz/fuzz-http --debug < /path/to/test.case
+
+(Logging is disabled while fuzzing to increase fuzzing speed.)
+
+== Reporting Issues
+
+Please report any issues discovered using the process in Tor's security issue
+policy:
+
+https://trac.torproject.org/projects/tor/wiki/org/meetings/2016SummerDevMeeting/Notes/SecurityIssuePolicy
diff --git a/doc/HACKING/HelpfulTools.md b/doc/HACKING/HelpfulTools.md
index a7f36e6c7e..67481ace43 100644
--- a/doc/HACKING/HelpfulTools.md
+++ b/doc/HACKING/HelpfulTools.md
@@ -142,6 +142,12 @@ run `make test-network`.
We also have scripts to run integration tests using Stem. To try them, set
`STEM_SOURCE_DIR` to your Stem source directory, and run `test-stem`.
+Profiling Tor
+-------------
+
+Ongoing notes about Tor profiling can be found at
+https://pad.riseup.net/p/profiling-tor
+
Profiling Tor with oprofile
---------------------------
@@ -168,6 +174,55 @@ Here are some basic instructions
* `opreport -l that_dir/*`
- Profit
+Profiling Tor with perf
+-----------------------
+
+This works with a running Tor, and requires root.
+
+1. Decide how long you want to profile for. Start with (say) 30 seconds. If that
+ works, try again with longer times.
+
+2. Find the PID of your running tor process.
+
+3. Run `perf record --call-graph dwarf -p <PID> sleep <SECONDS>`
+
+ (You may need to do this as root.)
+
+ You might need to add `-e cpu-clock` as an option to the perf record line
+ above, if you are on an older CPU without access to hardware profiling
+ events, or in a VM, or something.
+
+4. Now you have a perf.data file. Have a look at it with `perf report
+ --no-children --sort symbol,dso` or `perf report --no-children --sort
+ symbol,dso --stdio --header`. How does it look?
+
+5a. Once you have a nice big perf.data file, you can compress it, encrypt it,
+ and send it to your favorite Tor developers.
+
+5b. Or maybe you'd rather not send a nice big perf.data file. Who knows what's
+ in that!? It's kinda scary. To generate a less scary file, you can use `perf
+ report -g > <FILENAME>.out`. Then you can compress that and put it somewhere
+ public.
+
+Profiling Tor with gperftools aka Google-performance-tools
+----------------------------------------------------------
+
+This should work on nearly any unixy system. It doesn't seem to be compatible
+with RunAsDaemon though.
+
+Beforehand, install google-perftools.
+
+1. You need to rebuild Tor, hack the linking steps to add `-lprofiler` to the
+ libs. You can do this by adding `LIBS=-lprofiler` when you call `./configure`.
+
+Now you can run Tor with profiling enabled, and use the pprof utility to look at
+performance! See the gperftools manual for more info, but basically:
+
+2. Run `env CPUPROFILE=/tmp/profile src/or/tor -f <path/torrc>`. The profile file
+ is not written to until Tor finishes execuction.
+
+3. Run `pprof src/or/tor /tm/profile` to start the REPL.
+
Generating and analyzing a callgraph
------------------------------------
diff --git a/doc/HACKING/ReleasingTor.md b/doc/HACKING/ReleasingTor.md
index 7595398241..4761ca9a37 100644
--- a/doc/HACKING/ReleasingTor.md
+++ b/doc/HACKING/ReleasingTor.md
@@ -28,7 +28,7 @@ new Tor release:
Is make check-spaces happy?
- Does 'make distcheck' compain?
+ Does 'make distcheck' complain?
How about 'make test-stem' and 'make test-network'?
@@ -98,7 +98,7 @@ new Tor release:
to a release-0.2.x branch, manually commit the changelogs to the later
git branches too.
-3. If you're doing the first stable release in a series, you need to
+3. If you're doing the first stable release in a series, you need to
create a ReleaseNotes for the series as a whole. To get started
there, copy all of the Changelog entries from the series into a new
file, and run `./scripts/maint/sortChanges.py` on it. That will
@@ -164,12 +164,15 @@ new Tor release:
0.2.2.23-alpha" (or whatever the version is), and we select the date as
the date in the ChangeLog.
-5. Wait up to a day or two (for a development release), or until most
- packages are up (for a stable release), and mail the release blurb and
- changelog to tor-talk or tor-announce.
+5. Mail the release blurb and ChangeLog to tor-talk (development release) or
+ tor-announce (stable).
- (We might be moving to faster announcements, but don't announce until
- the website is at least updated.)
+ Post the changelog on the the blog as well. You can generate a
+ blog-formatted version of the changelog with the -B option to
+ format-changelog.
+
+ When you post, include an estimate of when the next TorBrowser releases
+ will come out that include this Tor release.
=== V. Aftermath and cleanup
@@ -182,4 +185,5 @@ new Tor release:
2. Forward-port the ChangeLog (and ReleaseNotes if appropriate).
+3. Keep an eye on the blog post, to moderate comments and answer questions.
diff --git a/doc/HACKING/WritingTests.md b/doc/HACKING/WritingTests.md
index de80bbdef2..4dae41e922 100644
--- a/doc/HACKING/WritingTests.md
+++ b/doc/HACKING/WritingTests.md
@@ -48,7 +48,7 @@ isolation, you just run `./src/test/test-memwipe`.
To run tests within the unit test programs, you can specify the name
of the test. The string ".." can be used as a wildcard at the end of the
test name. For example, to run all the cell format tests, enter
-`./src/test/test cellfmt/..`. To run
+`./src/test/test cellfmt/..`.
Many tests that need to mess with global state run in forked subprocesses in
order to keep from contaminating one another. But when debugging a failing test,
diff --git a/doc/include.am b/doc/include.am
index 7164a4b2a0..0e8de231e1 100644
--- a/doc/include.am
+++ b/doc/include.am
@@ -12,17 +12,11 @@
# part of the source distribution, so that people without asciidoc can
# just use the .1 and .html files.
-base_mans = doc/tor doc/tor-gencert doc/tor-resolve doc/torify
-all_mans = $(base_mans)
-if USE_FW_HELPER
-install_mans = $(all_mans)
-else
-install_mans = $(base_mans)
-endif
+all_mans = doc/tor doc/tor-gencert doc/tor-resolve doc/torify
if USE_ASCIIDOC
-nodist_man1_MANS = $(install_mans:=.1)
-doc_DATA = $(install_mans:=.html)
+nodist_man1_MANS = $(all_mans:=.1)
+doc_DATA = $(all_mans:=.html)
html_in = $(all_mans:=.html.in)
man_in = $(all_mans:=.1.in)
txt_in = $(all_mans:=.1.txt)
diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index 4c5d5359af..7ef8920858 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -176,7 +176,7 @@ forward slash (/) in the configuration file and on the command line.
GENERAL OPTIONS
---------------
-[[BandwidthRate]] **BandwidthRate** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[BandwidthRate]] **BandwidthRate** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
A token bucket limits the average incoming bandwidth usage on this node
to the specified number of bytes per second, and the average outgoing
bandwidth usage to that same value. If you want to run a relay in the
@@ -195,35 +195,35 @@ GENERAL OPTIONS
To avoid confusion, we recommend writing "bytes" or "bits" explicitly,
since it's easy to forget that "B" means bytes, not bits.
-[[BandwidthBurst]] **BandwidthBurst** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[BandwidthBurst]] **BandwidthBurst** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
Limit the maximum token bucket size (also known as the burst) to the given
number of bytes in each direction. (Default: 1 GByte)
-[[MaxAdvertisedBandwidth]] **MaxAdvertisedBandwidth** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[MaxAdvertisedBandwidth]] **MaxAdvertisedBandwidth** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
If set, we will not advertise more than this amount of bandwidth for our
BandwidthRate. Server operators who want to reduce the number of clients
who ask to build circuits through them (since this is proportional to
advertised bandwidth rate) can thus reduce the CPU demands on their server
without impacting network performance.
-[[RelayBandwidthRate]] **RelayBandwidthRate** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[RelayBandwidthRate]] **RelayBandwidthRate** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
If not 0, a separate token bucket limits the average incoming bandwidth
usage for \_relayed traffic_ on this node to the specified number of bytes
per second, and the average outgoing bandwidth usage to that same value.
Relayed traffic currently is calculated to include answers to directory
requests, but that may change in future versions. (Default: 0)
-[[RelayBandwidthBurst]] **RelayBandwidthBurst** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[RelayBandwidthBurst]] **RelayBandwidthBurst** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
If not 0, limit the maximum token bucket size (also known as the burst) for
\_relayed traffic_ to the given number of bytes in each direction.
(Default: 0)
-[[PerConnBWRate]] **PerConnBWRate** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[PerConnBWRate]] **PerConnBWRate** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
If set, do separate rate limiting for each connection from a non-relay.
You should never need to change this value, since a network-wide value is
published in the consensus and your relay will use that value. (Default: 0)
-[[PerConnBWBurst]] **PerConnBWBurst** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[PerConnBWBurst]] **PerConnBWBurst** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
If set, do separate rate limiting for each connection from a non-relay.
You should never need to change this value, since a network-wide value is
published in the consensus and your relay will use that value. (Default: 0)
@@ -390,7 +390,8 @@ GENERAL OPTIONS
file readable by the default GID. (Default: 0)
[[DataDirectory]] **DataDirectory** __DIR__::
- Store working data in DIR (Default: @LOCALSTATEDIR@/lib/tor)
+ Store working data in DIR. Can not be changed while tor is running.
+ (Default: @LOCALSTATEDIR@/lib/tor)
[[DataDirectoryGroupReadable]] **DataDirectoryGroupReadable** **0**|**1**::
If this option is set to 0, don't allow the filesystem group to read the
@@ -465,7 +466,8 @@ GENERAL OPTIONS
not supported. We believe that this feature works on modern Gnu/Linux
distributions, and that it should work on *BSD systems (untested). This
option requires that you start your Tor as root, and you should use the
- **User** option to properly reduce Tor's privileges. (Default: 0)
+ **User** option to properly reduce Tor's privileges.
+ Can not be changed while tor is running. (Default: 0)
[[DisableDebuggerAttachment]] **DisableDebuggerAttachment** **0**|**1**::
If set to 1, Tor will attempt to prevent basic debugging attachment attempts
@@ -505,11 +507,13 @@ GENERAL OPTIONS
(Default: 1)
[[FetchUselessDescriptors]] **FetchUselessDescriptors** **0**|**1**::
- If set to 1, Tor will fetch every non-obsolete descriptor from the
- authorities that it hears about. Otherwise, it will avoid fetching useless
- descriptors, for example for routers that are not running. This option is
- useful if you're using the contributed "exitlist" script to enumerate Tor
- nodes that exit to certain addresses. (Default: 0)
+ If set to 1, Tor will fetch every consensus flavor, descriptor, and
+ certificate that it hears about. Otherwise, it will avoid fetching useless
+ descriptors: flavors that it is not using to build circuits, and authority
+ certificates it does not trust. This option is useful if you're using a
+ tor client with an external parser that uses a full consensus.
+ This option fetches all documents, **DirCache** fetches and serves
+ all documents. (Default: 0)
[[HTTPProxy]] **HTTPProxy** __host__[:__port__]::
Tor will make all its directory requests through this host:port (or host:80
@@ -538,7 +542,20 @@ 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. (Default: 0)
+ experimental feature. 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
+ ConnLimit
+ CookieAuthFile
+ DirPortFrontPage
+ ExtORPortCookieAuthFile
+ Logs
+ ServerDNSResolvConfFile
+ Tor must remain in client or server mode (some changes to ClientOnly and
+ ORPort are not allowed).
+ (Default: 0)
[[Socks4Proxy]] **Socks4Proxy** __host__[:__port__]::
Tor will make all OR connections through the SOCKS 4 proxy at host:port
@@ -609,7 +626,7 @@ GENERAL OPTIONS
message currently has at least one domain; most currently have exactly
one. This doesn't affect controller log messages. (Default: 0)
-[[MaxUnparseableDescSizeToLog]] **MaxUnparseableDescSizeToLog** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**::
+[[MaxUnparseableDescSizeToLog]] **MaxUnparseableDescSizeToLog** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**::
Unparseable descriptors (e.g. for votes, consensuses, routers) are logged
in separate files by hash, up to the specified size in total. Note that
only files logged during the lifetime of this Tor process count toward the
@@ -624,9 +641,23 @@ GENERAL OPTIONS
This setting will be ignored for connections to the loopback addresses
(127.0.0.0/8 and ::1).
+[[OutboundBindAddressOR]] **OutboundBindAddressOR** __IP__::
+ Make all outbound non-exit (=relay and other) 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 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 for connections to the loopback addresses (127.0.0.0/8
+ and ::1).
+
[[PidFile]] **PidFile** __FILE__::
On startup, write our PID to FILE. On clean shutdown, remove
- FILE.
+ FILE. Can not be changed while tor is running.
[[ProtocolWarnings]] **ProtocolWarnings** **0**|**1**::
If 1, Tor will log with severity \'warn' various cases of other parties not
@@ -642,6 +673,7 @@ GENERAL OPTIONS
[[RunAsDaemon]] **RunAsDaemon** **0**|**1**::
If 1, Tor forks and daemonizes to the background. This option has no effect
on Windows; instead you should use the --service command-line option.
+ Can not be changed while tor is running.
(Default: 0)
[[LogTimeGranularity]] **LogTimeGranularity** __NUM__::
@@ -658,7 +690,8 @@ GENERAL OPTIONS
[[SyslogIdentityTag]] **SyslogIdentityTag** __tag__::
When logging to syslog, adds a tag to the syslog identity such that
- log entries are marked with "Tor-__tag__". (Default: none)
+ log entries are marked with "Tor-__tag__". Can not be changed while tor is
+ running. (Default: none)
[[SafeLogging]] **SafeLogging** **0**|**1**|**relay**::
Tor can scrub potentially sensitive strings from log messages (e.g.
@@ -673,6 +706,7 @@ GENERAL OPTIONS
[[User]] **User** __Username__::
On startup, setuid to this user and setgid to their primary group.
+ Can not be changed while tor is running.
[[KeepBindCapabilities]] **KeepBindCapabilities** **0**|**1**|**auto**::
On Linux, when we are started as root and we switch our identity using
@@ -680,20 +714,23 @@ GENERAL OPTIONS
try to retain our ability to bind to low ports. If this value is 1, we
try to keep the capability; if it is 0 we do not; and if it is **auto**,
we keep the capability only if we are configured to listen on a low port.
+ Can not be changed while tor is running.
(Default: auto.)
[[HardwareAccel]] **HardwareAccel** **0**|**1**::
If non-zero, try to use built-in (static) crypto hardware acceleration when
- available. (Default: 0)
+ available. Can not be changed while tor is running. (Default: 0)
[[AccelName]] **AccelName** __NAME__::
When using OpenSSL hardware crypto acceleration attempt to load the dynamic
engine of this name. This must be used for any dynamic hardware engine.
- Names can be verified with the openssl engine command.
+ Names can be verified with the openssl engine command. Can not be changed
+ while tor is running.
[[AccelDir]] **AccelDir** __DIR__::
Specify this option if using dynamic hardware acceleration and the engine
implementation library resides somewhere other than the OpenSSL default.
+ Can not be changed while tor is running.
[[AvoidDiskWrites]] **AvoidDiskWrites** **0**|**1**::
If non-zero, try to write to disk less frequently than we would otherwise.
@@ -718,6 +755,13 @@ GENERAL OPTIONS
127.0.0.1 or 10.0.0.1. This is mostly useful for debugging
rate-limiting. (Default: 0)
+[[ExtendByEd25519ID]] **ExtendByEd25519ID** **0**|**1**|**auto**::
+ If this option is set to 1, we always try to include a relay's Ed25519 ID
+ when telling the proceeding relay in a circuit to extend to it.
+ If this option is set to 0, we never include Ed25519 IDs when extending
+ circuits. If the option is set to "default", we obey a
+ parameter in the consensus document. (Default: auto)
+
CLIENT OPTIONS
--------------
@@ -817,7 +861,7 @@ The following options are useful only for clients (that is, if
[[ExcludeExitNodes]] **ExcludeExitNodes** __node__,__node__,__...__::
A list of identity fingerprints, country codes, and address
patterns of nodes to never use when picking an exit node---that is, a
- node that delivers traffic for you outside the Tor network. Note that any
+ node that delivers traffic for you *outside* the Tor network. Note that any
node listed in ExcludeNodes is automatically considered to be part of this
list too. See
the **ExcludeNodes** option for more information on how to specify
@@ -834,7 +878,7 @@ The following options are useful only for clients (that is, if
[[ExitNodes]] **ExitNodes** __node__,__node__,__...__::
A list of identity fingerprints, country codes, and address
patterns of nodes to use as exit node---that is, a
- node that delivers traffic for you outside the Tor network. See
+ node that delivers traffic for you *outside* the Tor network. See
the **ExcludeNodes** option for more information on how to specify nodes. +
+
Note that if you list too few nodes here, or if you exclude too many exit
@@ -842,7 +886,7 @@ The following options are useful only for clients (that is, if
if none of the exits you list allows traffic on port 80 or 443, you won't
be able to browse the web. +
+
- Note also that not every circuit is used to deliver traffic outside of
+ Note also that not every circuit is used to deliver traffic *outside* of
the Tor network. It is normal to see non-exit circuits (such as those
used to connect to hidden services, those that do directory fetches,
those used for relay reachability self-tests, and so on) that end
@@ -868,16 +912,16 @@ The following options are useful only for clients (that is, if
the **ExcludeNodes** option for more information on how to specify nodes.
[[StrictNodes]] **StrictNodes** **0**|**1**::
- If StrictNodes is set to 1, Tor will treat the ExcludeNodes option as a
- requirement to follow for all the circuits you generate, even if doing so
- will break functionality for you. If StrictNodes is set to 0, Tor will
+ If StrictNodes is set to 1, Tor will treat solely the ExcludeNodes option
+ as a requirement to follow for all the circuits you generate, even if
+ doing so will break functionality for you (StrictNodes applies to neither
+ ExcludeExitNodes nor to ExitNodes). If StrictNodes is set to 0, Tor will
still try to avoid nodes in the ExcludeNodes list, but it will err on the
- side of avoiding unexpected errors. Specifically, StrictNodes 0 tells
- Tor that it is okay to use an excluded node when it is *necessary* to
- perform relay reachability self-tests, connect to
- a hidden service, provide a hidden service to a client, fulfill a .exit
- request, upload directory information, or download directory information.
- (Default: 0)
+ side of avoiding unexpected errors. Specifically, StrictNodes 0 tells Tor
+ that it is okay to use an excluded node when it is *necessary* to perform
+ relay reachability self-tests, connect to a hidden service, provide a
+ hidden service to a client, fulfill a .exit request, upload directory
+ information, or download directory information. (Default: 0)
[[FascistFirewall]] **FascistFirewall** **0**|**1**::
If 1, Tor will only create outgoing connections to ORs running on ports
@@ -1142,6 +1186,7 @@ The following options are useful only for clients (that is, if
authentication" when IsolateSOCKSAuth is disabled, or when this
option is set.
+[[SocksPortFlagsMisc]]::
Flags are processed left to right. If flags conflict, the last flag on the
line is used, and all earlier flags are ignored. No error is issued for
conflicting flags.
@@ -1172,7 +1217,8 @@ The following options are useful only for clients (that is, if
NUM must be between 1 and 1000, inclusive. Note that the configured
bandwidth limits are still expressed in bytes per second: this
option only affects the frequency with which Tor checks to see whether
- previously exhausted connections may read again. (Default: 100 msec)
+ previously exhausted connections may read again.
+ Can not be changed while tor is running. (Default: 100 msec)
[[TrackHostExits]] **TrackHostExits** __host__,__.domain__,__...__::
For each value in the comma separated list, Tor will track recent
@@ -1209,15 +1255,6 @@ The following options are useful only for clients (that is, if
Authorities, Single Onion Services, and Tor2web clients. In these cases,
the this option is ignored. (Default: 1)
-[[UseEntryGuardsAsDirGuards]] **UseEntryGuardsAsDirGuards** **0**|**1**::
- If this option is set to 1, and UseEntryGuards is also set to 1,
- we try to use our entry guards as directory
- guards, and failing that, pick more nodes to act as our directory guards.
- This helps prevent an adversary from enumerating clients. It's only
- available for clients (non-relay, non-bridge) that aren't configured to
- download any non-default directory material. It doesn't currently
- do anything when we lack a live consensus. (Default: 1)
-
[[GuardfractionFile]] **GuardfractionFile** __FILENAME__::
V3 authoritative directories only. Configures the location of the
guardfraction file which contains information about how long relays
@@ -1335,7 +1372,7 @@ The following options are useful only for clients (that is, if
[[TransProxyType]] **TransProxyType** **default**|**TPROXY**|**ipfw**|**pf-divert**::
TransProxyType may only be enabled when there is transparent proxy listener
- enabled.
+ enabled. +
+
Set this to "TPROXY" if you wish to be able to use the TPROXY Linux module
to transparently proxy connections that are configured using the TransPort
@@ -1343,19 +1380,19 @@ The following options are useful only for clients (that is, if
for all addresses, even when the TransListenAddress is configured for an
internal address. Detailed information on how to configure the TPROXY
feature can be found in the Linux kernel source tree in the file
- Documentation/networking/tproxy.txt.
+ Documentation/networking/tproxy.txt. +
+
- Set this option to "ipfw" to use the FreeBSD ipfw interface.
+ Set this option to "ipfw" to use the FreeBSD ipfw interface. +
+
On *BSD operating systems when using pf, set this to "pf-divert" to take
advantage of +divert-to+ rules, which do not modify the packets like
+rdr-to+ rules do. Detailed information on how to configure pf to use
+divert-to+ rules can be found in the pf.conf(5) manual page. On OpenBSD,
+divert-to+ is available to use on versions greater than or equal to
- OpenBSD 4.4.
+ OpenBSD 4.4. +
+
Set this to "default", or leave it unconfigured, to use regular IPTables
- on Linux, or to use pf +rdr-to+ rules on *BSD systems.
+ on Linux, or to use pf +rdr-to+ rules on *BSD systems. +
+
(Default: "default".)
@@ -1413,7 +1450,8 @@ The following options are useful only for clients (that is, if
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
specifically requested__ (for example, via a .exit hostname, or a
- controller request). (Default: 1)
+ controller request). If true, multicast DNS hostnames for machines on the
+ local network (of the form *.local) are also rejected. (Default: 1)
[[DownloadExtraInfo]] **DownloadExtraInfo** **0**|**1**::
If true, Tor downloads and caches "extra-info" documents. These documents
@@ -1462,11 +1500,11 @@ The following options are useful only for clients (that is, if
(Example:
Tor2webRendezvousPoints Fastyfasty, ABCD1234CDEF5678ABCD1234CDEF5678ABCD1234, \{cc}, 255.254.0.0/8) +
+
- This feature can only be used if Tor2webMode is also enabled.
+ This feature can only be used if Tor2webMode is also enabled. +
+
ExcludeNodes have higher priority than Tor2webRendezvousPoints,
which means that nodes specified in ExcludeNodes will not be
- picked as RPs.
+ picked as RPs. +
+
If no nodes in Tor2webRendezvousPoints are currently available for
use, Tor will choose a random node when building HS circuits.
@@ -1494,7 +1532,7 @@ The following options are useful only for clients (that is, if
These options override the default behavior of Tor's (**currently
experimental**) path bias detection algorithm. To try to find broken or
misbehaving guard nodes, Tor looks for nodes where more than a certain
- fraction of circuits through that guard fail to get built.
+ fraction of circuits through that guard fail to get built. +
+
The PathBiasCircThreshold option controls how many circuits we need to build
through a guard before we make these checks. The PathBiasNoticeRate,
@@ -1520,14 +1558,14 @@ The following options are useful only for clients (that is, if
[[PathBiasScaleUseThreshold]] **PathBiasScaleUseThreshold** __NUM__::
Similar to the above options, these options override the default behavior
- of Tor's (**currently experimental**) path use bias detection algorithm.
+ of Tor's (**currently experimental**) path use bias detection algorithm. +
+
Where as the path bias parameters govern thresholds for successfully
building circuits, these four path use bias parameters govern thresholds
only for circuit usage. Circuits which receive no stream usage
are not counted by this detection algorithm. A used circuit is considered
successful if it is capable of carrying streams or otherwise receiving
- well-formed responses to RELAY cells.
+ well-formed responses to RELAY cells. +
+
By default, or if a negative value is provided for one of these options,
Tor uses reasonable defaults from the networkstatus consensus document.
@@ -1661,7 +1699,7 @@ is non-zero):
Tells Tor whether to run as an exit relay. If Tor is running as a
non-bridge server, and ExitRelay is set to 1, then Tor allows traffic to
exit according to the ExitPolicy option (or the default ExitPolicy if
- none is specified).
+ none is specified). +
+
If ExitRelay is set to 0, no traffic is allowed to
exit, and the ExitPolicy option is ignored. +
@@ -1739,6 +1777,7 @@ is non-zero):
reject *:6881-6999
accept *:*
+[[ExitPolicyDefault]]::
Since the default exit policy uses accept/reject *, it applies to both
IPv4 and IPv6 addresses.
@@ -1775,7 +1814,7 @@ is non-zero):
that they are in the same \'family', Tor clients will not use them in the
same circuit. (Each server only needs to list the other servers in its
family; it doesn't need to list itself, but it won't hurt.) Do not list
- any bridge relay as it would compromise its concealment.
+ any bridge relay as it would compromise its concealment. +
+
When listing a node, it's better to list it by fingerprint than by
nickname: fingerprints are more reliable.
@@ -1793,26 +1832,27 @@ is non-zero):
Advertise this port to listen for connections from Tor clients and
servers. This option is required to be a Tor server.
Set it to "auto" to have Tor pick a port for you. Set it to 0 to not
- run an ORPort at all. This option can occur more than once. (Default: 0)
-+
+ run an ORPort at all. This option can occur more than once. (Default: 0) +
+ +
Tor recognizes these flags on each ORPort:
- **NoAdvertise**::
+ **NoAdvertise**;;
By default, we bind to a port and tell our users about it. If
NoAdvertise is specified, we don't advertise, but listen anyway. This
can be useful if the port everybody will be connecting to (for
example, one that's opened on our firewall) is somewhere else.
- **NoListen**::
+ **NoListen**;;
By default, we bind to a port and tell our users about it. If
NoListen is specified, we don't bind, but advertise anyway. This
can be useful if something else (for example, a firewall's port
forwarding configuration) is causing connections to reach us.
- **IPv4Only**::
+ **IPv4Only**;;
If the address is absent, or resolves to both an IPv4 and an IPv6
address, only listen to the IPv4 address.
- **IPv6Only**::
+ **IPv6Only**;;
If the address is absent, or resolves to both an IPv4 and an IPv6
address, only listen to the IPv6 address.
-+
+
+[[ORPortFlagsExclusive]]::
For obvious reasons, NoAdvertise and NoListen are mutually exclusive, and
IPv4Only and IPv6Only are mutually exclusive.
@@ -1820,8 +1860,8 @@ is non-zero):
Bind to this IP address to listen for connections from Tor clients and
servers. If you specify a port, bind to this port rather than the one
specified in ORPort. (Default: 0.0.0.0) This directive can be specified
- multiple times to bind to multiple addresses/ports.
-+
+ multiple times to bind to multiple addresses/ports. +
+ +
This option is deprecated; you can get the same behavior with ORPort now
that it supports NoAdvertise and explicit addresses.
@@ -1840,7 +1880,7 @@ is non-zero):
[[PublishServerDescriptor]] **PublishServerDescriptor** **0**|**1**|**v3**|**bridge**,**...**::
This option specifies which descriptors Tor will publish when acting as
a relay. You can
- choose multiple arguments, separated by commas.
+ choose multiple arguments, separated by commas. +
+
If this option is set to 0, Tor will not publish its
descriptors to any directories. (This is useful if you're testing
@@ -1868,7 +1908,7 @@ is non-zero):
to 0 will disable the heartbeat. Otherwise, it must be at least 30
minutes. (Default: 6 hours)
-[[AccountingMax]] **AccountingMax** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**|**TBytes**::
+[[AccountingMax]] **AccountingMax** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
Limits the max number of bytes sent and received within a set time period
using a given calculation rule (see: AccountingStart, AccountingRule).
Useful if you need to stay under a specific bandwidth. By default, the
@@ -2082,8 +2122,9 @@ is non-zero):
DIRECTORY SERVER OPTIONS
------------------------
-The following options are useful only for directory servers (that is,
-if DirPort is non-zero):
+The following options are useful only for directory servers. (Relays with
+enough bandwidth automatically become directory servers; see DirCache for
+details.)
[[DirPortFrontPage]] **DirPortFrontPage** __FILENAME__::
When this option is set, it takes an HTML file and publishes it as "/" on
@@ -2095,16 +2136,16 @@ if DirPort is non-zero):
If this option is nonzero, advertise the directory service on this port.
Set it to "auto" to have Tor pick a port for you. This option can occur
more than once, but only one advertised DirPort is supported: all
- but one DirPort must have the **NoAdvertise** flag set. (Default: 0)
-+
+ but one DirPort must have the **NoAdvertise** flag set. (Default: 0) +
+ +
The same flags are supported here as are supported by ORPort.
[[DirListenAddress]] **DirListenAddress** __IP__[:__PORT__]::
Bind the directory service to this address. If you specify a port, bind to
this port rather than the one specified in DirPort. (Default: 0.0.0.0)
This directive can be specified multiple times to bind to multiple
- addresses/ports.
-+
+ addresses/ports. +
+ +
This option is deprecated; you can get the same behavior with DirPort now
that it supports NoAdvertise and explicit addresses.
@@ -2199,7 +2240,7 @@ on the public Tor network.
[[AuthDirBadExit]] **AuthDirBadExit** __AddressPattern...__::
Authoritative directories only. A set of address patterns for servers that
will be listed as bad exits in any network status document this authority
- publishes, if **AuthDirListBadExits** is set.
+ publishes, if **AuthDirListBadExits** is set. +
+
(The address pattern syntax here and in the options below
is the same as for exit policies, except that you don't need to say
@@ -2237,26 +2278,22 @@ on the public Tor network.
list as acceptable on a single IP address. Set this to "0" for "no limit".
(Default: 2)
-[[AuthDirMaxServersPerAuthAddr]] **AuthDirMaxServersPerAuthAddr** __NUM__::
- Authoritative directories only. Like AuthDirMaxServersPerAddr, but applies
- to addresses shared with directory authorities. (Default: 5)
-
-[[AuthDirFastGuarantee]] **AuthDirFastGuarantee** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[AuthDirFastGuarantee]] **AuthDirFastGuarantee** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
Authoritative directories only. If non-zero, always vote the
Fast flag for any relay advertising this amount of capacity or
more. (Default: 100 KBytes)
-[[AuthDirGuardBWGuarantee]] **AuthDirGuardBWGuarantee** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[AuthDirGuardBWGuarantee]] **AuthDirGuardBWGuarantee** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
Authoritative directories only. If non-zero, this advertised capacity
or more is always sufficient to satisfy the bandwidth requirement
- for the Guard flag. (Default: 250 KBytes)
+ for the Guard flag. (Default: 2 MBytes)
[[AuthDirPinKeys]] **AuthDirPinKeys** **0**|**1**::
Authoritative directories only. If non-zero, do not allow any relay to
publish a descriptor if any other relay has reserved its <Ed25519,RSA>
identity keypair. In all cases, Tor records every keypair it accepts
in a journal if it is new, or if it differs from the most recently
- accepted pinning for one of the keys it contains. (Default: 0)
+ accepted pinning for one of the keys it contains. (Default: 1)
[[AuthDirSharedRandomness]] **AuthDirSharedRandomness** **0**|**1**::
Authoritative directories only. Switch for the shared random protocol.
@@ -2264,6 +2301,13 @@ on the public Tor network.
(default), the flag "shared-rand-participate" is added to the authority
vote indicating participation in the protocol. (Default: 1)
+[[AuthDirTestEd25519LinkKeys]] **AuthDirTestEd25519LinkKeys** **0**|**1**::
+ Authoritative directories only. If this option is set to 0, then we treat
+ relays as "Running" if their RSA key is correct when we probe them,
+ regardless of their Ed25519 key. We should only ever set this option to 0
+ if there is some major bug in Ed25519 link authentication that causes us
+ to label all the relays as not Running. (Default: 1)
+
[[BridgePassword]] **BridgePassword** __Password__::
If set, contains an HTTP authenticator that tells a bridge authority to
serve all requested bridge information. Used by the (only partially
@@ -2335,9 +2379,9 @@ The following options are used to configure a hidden service.
[[HiddenServiceDir]] **HiddenServiceDir** __DIRECTORY__::
Store data files for a hidden service in DIRECTORY. Every hidden service
must have a separate directory. You may use this option multiple times to
- specify multiple services. DIRECTORY must be an existing directory.
+ specify multiple services. If DIRECTORY does not exist, Tor will create it.
(Note: in current versions of Tor, if DIRECTORY is a relative path,
- it will be relative to current
+ it will be relative to the current
working directory of Tor instance, not to its DataDirectory. Do not
rely on this behavior; it is not guaranteed to remain the same in future
versions.)
@@ -2384,8 +2428,8 @@ The following options are used to configure a hidden service.
[[HiddenServiceMaxStreams]] **HiddenServiceMaxStreams** __N__::
The maximum number of simultaneous streams (connections) per rendezvous
- circuit. (Setting this to 0 will allow an unlimited number of simultanous
- streams.) (Default: 0)
+ circuit. The maximum value allowed is 65535. (Setting this to 0 will allow
+ an unlimited number of simultanous streams.) (Default: 0)
[[HiddenServiceMaxStreamsCloseCircuit]] **HiddenServiceMaxStreamsCloseCircuit** **0**|**1**::
If set to 1, then exceeding **HiddenServiceMaxStreams** will cause the
@@ -2394,8 +2438,9 @@ The following options are used to configure a hidden service.
[[RendPostPeriod]] **RendPostPeriod** __N__ **seconds**|**minutes**|**hours**|**days**|**weeks**::
Every time the specified period elapses, Tor uploads any rendezvous
- service descriptors to the directory servers. This information is also
- uploaded whenever it changes. (Default: 1 hour)
+ service descriptors to the directory servers. This information is also
+ uploaded whenever it changes. Minimum value allowed is 10 minutes and
+ maximum is 3.5 days. (Default: 1 hour)
[[HiddenServiceDirGroupReadable]] **HiddenServiceDirGroupReadable** **0**|**1**::
If this option is set to 1, allow the filesystem group to read the
@@ -2417,20 +2462,20 @@ The following options are used to configure a hidden service.
Single Onion Service. One-hop circuits make Single Onion servers easily
locatable, but clients remain location-anonymous. However, the fact that a
client is accessing a Single Onion rather than a Hidden Service may be
- statistically distinguishable.
-
+ statistically distinguishable. +
+ +
**WARNING:** Once a hidden service directory has been used by a tor
instance in HiddenServiceSingleHopMode, it can **NEVER** be used again for
a hidden service. It is best practice to create a new hidden service
directory, key, and address for each new Single Onion Service and Hidden
Service. It is not possible to run Single Onion Services and Hidden
Services from the same tor instance: they should be run on different
- servers with different IP addresses.
-
+ servers with different IP addresses. +
+ +
HiddenServiceSingleHopMode requires HiddenServiceNonAnonymousMode to be set
to 1. Since a Single Onion service is non-anonymous, you can not configure
a SOCKSPort on a tor instance that is running in
- **HiddenServiceSingleHopMode**.
+ **HiddenServiceSingleHopMode**. Can not be changed while tor is running.
(Default: 0)
[[HiddenServiceNonAnonymousMode]] **HiddenServiceNonAnonymousMode** **0**|**1**::
@@ -2438,8 +2483,8 @@ The following options are used to configure a hidden service.
non-anonymous HiddenServiceSingleHopMode. Enables direct connections in the
server-side hidden service protocol. If you are using this option,
you need to disable all client-side services on your Tor instance,
- including setting SOCKSPort to "0".
- (Default: 0)
+ including setting SOCKSPort to "0". Can not be changed while tor is
+ running. (Default: 0)
TESTING NETWORK OPTIONS
-----------------------
@@ -2525,7 +2570,7 @@ The following options are used for running a testing Tor network.
time. Changing this requires that **TestingTorNetwork** is set. (Default:
10 minutes)
-[[TestingMinFastFlagThreshold]] **TestingMinFastFlagThreshold** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[TestingMinFastFlagThreshold]] **TestingMinFastFlagThreshold** __N__ **bytes**|**KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
Minimum value for the Fast flag. Overrides the ordinary minimum taken
from the consensus when TestingTorNetwork is set. (Default: 0.)
@@ -2584,7 +2629,7 @@ The following options are used for running a testing Tor network.
A list of identity fingerprints, country codes, and
address patterns of nodes to vote Exit for regardless of their
uptime, bandwidth, or exit policy. See the **ExcludeNodes**
- option for more information on how to specify nodes.
+ option for more information on how to specify nodes. +
+
In order for this option to have any effect, **TestingTorNetwork**
has to be set. See the **ExcludeNodes** option for more
@@ -2593,7 +2638,7 @@ The following options are used for running a testing Tor network.
[[TestingDirAuthVoteExitIsStrict]] **TestingDirAuthVoteExitIsStrict** **0**|**1** ::
If True (1), a node will never receive the Exit flag unless it is specified
in the **TestingDirAuthVoteExit** list, regardless of its uptime, bandwidth,
- or exit policy.
+ or exit policy. +
+
In order for this option to have any effect, **TestingTorNetwork**
has to be set.
@@ -2602,14 +2647,14 @@ The following options are used for running a testing Tor network.
A list of identity fingerprints and country codes and
address patterns of nodes to vote Guard for regardless of their
uptime and bandwidth. See the **ExcludeNodes** option for more
- information on how to specify nodes.
+ information on how to specify nodes. +
+
In order for this option to have any effect, **TestingTorNetwork**
has to be set.
[[TestingDirAuthVoteGuardIsStrict]] **TestingDirAuthVoteGuardIsStrict** **0**|**1** ::
If True (1), a node will never receive the Guard flag unless it is specified
- in the **TestingDirAuthVoteGuard** list, regardless of its uptime and bandwidth.
+ in the **TestingDirAuthVoteGuard** list, regardless of its uptime and bandwidth. +
+
In order for this option to have any effect, **TestingTorNetwork**
has to be set.
@@ -2618,14 +2663,14 @@ The following options are used for running a testing Tor network.
A list of identity fingerprints and country codes and
address patterns of nodes to vote HSDir for regardless of their
uptime and DirPort. See the **ExcludeNodes** option for more
- information on how to specify nodes.
+ information on how to specify nodes. +
+
In order for this option to have any effect, **TestingTorNetwork**
must be set.
[[TestingDirAuthVoteHSDirIsStrict]] **TestingDirAuthVoteHSDirIsStrict** **0**|**1** ::
If True (1), a node will never receive the HSDir flag unless it is specified
- in the **TestingDirAuthVoteHSDir** list, regardless of its uptime and DirPort.
+ in the **TestingDirAuthVoteHSDir** list, regardless of its uptime and DirPort. +
+
In order for this option to have any effect, **TestingTorNetwork**
has to be set.
@@ -2645,7 +2690,7 @@ The following options are used for running a testing Tor network.
events. Changing this requires that **TestingTorNetwork** is set.
(Default: 0)
-[[TestingMinExitFlagThreshold]] **TestingMinExitFlagThreshold** __N__ **KBytes**|**MBytes**|**GBytes**|**KBits**|**MBits**|**GBits**::
+[[TestingMinExitFlagThreshold]] **TestingMinExitFlagThreshold** __N__ **KBytes**|**MBytes**|**GBytes**|**TBytes**|**KBits**|**MBits**|**GBits**|**TBits**::
Sets a lower-bound for assigning an exit flag when running as an
authority on a testing network. Overrides the usual default lower bound
of 4 KB. (Default: 0)
@@ -2669,6 +2714,19 @@ The following options are used for running a testing Tor network.
we replace it and issue a new key?
(Default: 3 hours for link and auth; 1 day for signing.)
+NON-PERSISTENT OPTIONS
+----------------------
+
+These options are not saved to the torrc file by the "SAVECONF" controller
+command. Other options of this type are documented in control-spec.txt,
+section 5.4. End-users should mostly ignore them.
+
+[[UnderscorePorts]] **\_\_ControlPort**, **\_\_DirPort**, **\_\_DNSPort**, **\_\_ExtORPort**, **\_\_NATDPort**, **\_\_ORPort**, **\_\_SocksPort**, **\_\_TransPort**::
+ These underscore-prefixed options are variants of the regular Port
+ options. They behave the same, except they are not saved to the
+ torrc file by the controller's SAVECONF command.
+
+
SIGNALS
-------
diff --git a/m4/ax_check_sign.m4 b/m4/ax_check_sign.m4
index 104b17014c..d67e114dba 100644
--- a/m4/ax_check_sign.m4
+++ b/m4/ax_check_sign.m4
@@ -41,8 +41,8 @@ AU_ALIAS([VL_CHECK_SIGN], [AX_CHECK_SIGN])
AC_DEFUN([AX_CHECK_SIGN], [
typename=`echo $1 | sed "s/@<:@^a-zA-Z0-9_@:>@/_/g"`
AC_CACHE_CHECK([whether $1 is signed], ax_cv_decl_${typename}_signed, [
- AC_TRY_COMPILE([$4],
- [ int foo @<:@ 1 - 2 * !((($1) -1) < 0) @:>@ ],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[$4]],
+ [[ int foo @<:@ 1 - 2 * !((($1) -1) < 0) @:>@ ]])],
[ eval "ax_cv_decl_${typename}_signed=\"yes\"" ],
[ eval "ax_cv_decl_${typename}_signed=\"no\"" ])])
symbolname=`echo $1 | sed "s/@<:@^a-zA-Z0-9_@:>@/_/g" | tr "a-z" "A-Z"`
diff --git a/m4/pc_from_ucontext.m4 b/m4/pc_from_ucontext.m4
index 8a9dc459e6..9b66bf752c 100644
--- a/m4/pc_from_ucontext.m4
+++ b/m4/pc_from_ucontext.m4
@@ -79,29 +79,29 @@ AC_DEFUN([AC_PC_FROM_UCONTEXT],
if ! $pc_field_found; then
# Prefer sys/ucontext.h to ucontext.h, for OS X's sake.
if test "x$ac_cv_header_cygwin_signal_h" = xyes; then
- AC_TRY_COMPILE([#include <cygwin/signal.h>],
- [ucontext_t u; return u.$pc_field == 0;],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <cygwin/signal.h>]],
+ [[ucontext_t u; return u.$pc_field == 0;]])],
AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT([$pc_field])
pc_field_found=true)
elif test "x$ac_cv_header_sys_ucontext_h" = xyes; then
- AC_TRY_COMPILE([#include <sys/ucontext.h>],
- [ucontext_t u; return u.$pc_field == 0;],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <sys/ucontext.h>]],
+ [[ucontext_t u; return u.$pc_field == 0;]])],
AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT([$pc_field])
pc_field_found=true)
elif test "x$ac_cv_header_ucontext_h" = xyes; then
- AC_TRY_COMPILE([#include <ucontext.h>],
- [ucontext_t u; return u.$pc_field == 0;],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <ucontext.h>]],
+ [[ucontext_t u; return u.$pc_field == 0;]])],
AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT([$pc_field])
pc_field_found=true)
else # hope some standard header gives it to us
- AC_TRY_COMPILE([],
- [ucontext_t u; return u.$pc_field == 0;],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]],
+ [[ucontext_t u; return u.$pc_field == 0;]])],
AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT([$pc_field])
@@ -114,8 +114,8 @@ AC_DEFUN([AC_PC_FROM_UCONTEXT],
pc_fields="$pc_fields sc_rip" # OpenBSD (x86_64)
for pc_field in $pc_fields; do
if ! $pc_field_found; then
- AC_TRY_COMPILE([#include <signal.h>],
- [ucontext_t u; return u.$pc_field == 0;],
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <signal.h>]],
+ [[ucontext_t u; return u.$pc_field == 0;]])],
AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT([$pc_field])
diff --git a/scripts/codegen/gen_server_ciphers.py b/scripts/codegen/gen_server_ciphers.py
index 0dca8a6734..7470f8a025 100755
--- a/scripts/codegen/gen_server_ciphers.py
+++ b/scripts/codegen/gen_server_ciphers.py
@@ -13,13 +13,13 @@ import sys
EPHEMERAL_INDICATORS = [ "_EDH_", "_DHE_", "_ECDHE_" ]
BAD_STUFF = [ "_DES_40_", "MD5", "_RC4_", "_DES_64_",
- "_SEED_", "_CAMELLIA_", "_NULL" ]
+ "_SEED_", "_CAMELLIA_", "_NULL",
+ "_CCM_8", "_DES_", ]
# these never get #ifdeffed.
MANDATORY = [
"TLS1_TXT_DHE_RSA_WITH_AES_256_SHA",
"TLS1_TXT_DHE_RSA_WITH_AES_128_SHA",
- "SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA",
]
def find_ciphers(filename):
@@ -48,15 +48,23 @@ def usable_cipher(ciph):
# All fields we sort on, in order of priority.
FIELDS = [ 'cipher', 'fwsec', 'mode', 'digest', 'bitlength' ]
# Map from sorted fields to recognized value in descending order of goodness
-FIELD_VALS = { 'cipher' : [ 'AES', 'DES'],
+FIELD_VALS = { 'cipher' : [ 'AES', 'CHACHA20' ],
'fwsec' : [ 'ECDHE', 'DHE' ],
- 'mode' : [ 'GCM', 'CBC' ],
- 'digest' : [ 'SHA384', 'SHA256', 'SHA' ],
+ 'mode' : [ 'POLY1305', 'GCM', 'CCM', 'CBC', ],
+ 'digest' : [ 'n/a', 'SHA384', 'SHA256', 'SHA', ],
'bitlength' : [ '256', '128', '192' ],
}
class Ciphersuite(object):
def __init__(self, name, fwsec, cipher, bitlength, mode, digest):
+ if fwsec == 'EDH':
+ fwsec = 'DHE'
+
+ if mode in [ '_CBC3', '_CBC', '' ]:
+ mode = 'CBC'
+ elif mode == '_GCM':
+ mode = 'GCM'
+
self.name = name
self.fwsec = fwsec
self.cipher = cipher
@@ -74,42 +82,50 @@ class Ciphersuite(object):
def parse_cipher(ciph):
m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_(AES|DES)_(256|128|192)(|_CBC|_CBC3|_GCM)_(SHA|SHA256|SHA384)$', ciph)
- if not m:
- print "/* Couldn't parse %s ! */"%ciph
- return None
+ if m:
+ fwsec, cipher, bits, mode, digest = m.groups()
+ return Ciphersuite(ciph, fwsec, cipher, bits, mode, digest)
- fwsec, cipher, bits, mode, digest = m.groups()
- if fwsec == 'EDH':
- fwsec = 'DHE'
+ m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_(AES|DES)_(256|128|192)_CCM', ciph)
+ if m:
+ fwsec, cipher, bits = m.groups()
+ return Ciphersuite(ciph, fwsec, cipher, bits, "CCM", "n/a")
- if mode in [ '_CBC3', '_CBC', '' ]:
- mode = 'CBC'
- elif mode == '_GCM':
- mode = 'GCM'
+ m = re.match('(?:TLS1|SSL3)_TXT_(EDH|DHE|ECDHE)_RSA(?:_WITH)?_CHACHA20_POLY1305', ciph)
+ if m:
+ fwsec, = m.groups()
+ return Ciphersuite(ciph, fwsec, "CHACHA20", "256", "POLY1305", "n/a")
+
+ print "/* Couldn't parse %s ! */"%ciph
+ return None
- return Ciphersuite(ciph, fwsec, cipher, bits, mode, digest)
ALL_CIPHERS = []
for fname in sys.argv[1:]:
- ALL_CIPHERS += (parse_cipher(c)
- for c in find_ciphers(fname)
- if usable_cipher(c) )
+ for c in find_ciphers(fname):
+ if usable_cipher(c):
+ parsed = parse_cipher(c)
+ if parsed != None:
+ ALL_CIPHERS.append(parsed)
ALL_CIPHERS.sort(key=Ciphersuite.sort_key)
+indent = " "*7
+
for c in ALL_CIPHERS:
if c is ALL_CIPHERS[-1]:
- colon = ';'
+ colon = ''
else:
colon = ' ":"'
if c.name in MANDATORY:
- print " /* Required */"
- print ' %s%s'%(c.name,colon)
+ print "%s/* Required */"%indent
+ print '%s%s%s'%(indent,c.name,colon)
else:
print "#ifdef %s"%c.name
- print ' %s%s'%(c.name,colon)
+ print '%s%s%s'%(indent,c.name,colon)
print "#endif"
+print '%s;'%indent
diff --git a/scripts/codegen/get_mozilla_ciphers.py b/scripts/codegen/get_mozilla_ciphers.py
index e673ec7dc6..b07746c2e2 100644..100755
--- a/scripts/codegen/get_mozilla_ciphers.py
+++ b/scripts/codegen/get_mozilla_ciphers.py
@@ -127,9 +127,9 @@ for k, v in enabled_ciphers.items():
#oSSLinclude = ('/usr/include/openssl/ssl3.h', '/usr/include/openssl/ssl.h',
# '/usr/include/openssl/ssl2.h', '/usr/include/openssl/ssl23.h',
# '/usr/include/openssl/tls1.h')
-oSSLinclude = ('ssl/ssl3.h', 'ssl/ssl.h',
- 'ssl/ssl2.h', 'ssl/ssl23.h',
- 'ssl/tls1.h')
+oSSLinclude = ['ssl3.h', 'ssl.h'
+ 'ssl2.h', 'ssl23.h',
+ 'tls1.h']
#####
# This reads the hex code for the ciphers that are used by firefox.
@@ -155,9 +155,12 @@ for x in used_ciphers:
openssl_macro_by_hex = {}
all_openssl_macros = {}
for fl in oSSLinclude:
- fp = open(ossl(fl), 'r')
+ fname = ossl("include/openssl/"+fl)
+ if not os.path.exists(fname):
+ continue
+ fp = open(fname, 'r')
for line in fp.readlines():
- m = re.match('#define\s+(\S+)\s+(\S+)', line)
+ m = re.match('# *define\s+(\S+)\s+(\S+)', line)
if m:
value,key = m.groups()
if key.startswith('0x') and "_CK_" in value:
diff --git a/scripts/codegen/run_trunnel.sh b/scripts/codegen/run_trunnel.sh
index d2669931e9..428804342a 100755
--- a/scripts/codegen/run_trunnel.sh
+++ b/scripts/codegen/run_trunnel.sh
@@ -5,7 +5,13 @@ if test "x$TRUNNEL_PATH" != "x"; then
export PYTHONPATH
fi
-python -m trunnel --require-version=1.4 ./src/trunnel/*.trunnel
+OPTIONS="--require-version=1.5.1"
-python -m trunnel --require-version=1.4 --write-c-files --target-dir=./src/ext/trunnel/
+# Get all .trunnel files recursively from that directory so we can support
+# multiple sub-directories.
+for file in `find ./src/trunnel/ -name '*.trunnel'`; do
+ python -m trunnel ${OPTIONS} $file
+done
+
+python -m trunnel ${OPTIONS} --write-c-files --target-dir=./src/ext/trunnel/
diff --git a/scripts/maint/checkSpace.pl b/scripts/maint/checkSpace.pl
index e90f5b330b..6d19d6ccde 100755
--- a/scripts/maint/checkSpace.pl
+++ b/scripts/maint/checkSpace.pl
@@ -1,48 +1,59 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my $found = 0;
+sub msg {
+ $found = 1;
+ print "$_[0]";
+}
+
+my $C = 0;
if ($ARGV[0] =~ /^-/) {
- $lang = shift @ARGV;
+ my $lang = shift @ARGV;
$C = ($lang eq '-C');
-# $TXT = ($lang eq '-txt');
}
-for $fn (@ARGV) {
+for my $fn (@ARGV) {
open(F, "$fn");
- $lastnil = 0;
- $lastline = "";
- $incomment = 0;
+ my $lastnil = 0;
+ my $lastline = "";
+ my $incomment = 0;
+ my $in_func_head = 0;
while (<F>) {
## Warn about windows-style newlines.
- # (We insist on lines that end with a single LF character, not
- # CR LF.)
+ # (We insist on lines that end with a single LF character, not
+ # CR LF.)
if (/\r/) {
- print " CR:$fn:$.\n";
+ msg " CR:$fn:$.\n";
}
## Warn about tabs.
- # (We only use spaces)
+ # (We only use spaces)
if (/\t/) {
- print " TAB:$fn:$.\n";
+ msg " TAB:$fn:$.\n";
}
## Warn about labels that don't have a space in front of them
- # (We indent every label at least one space)
+ # (We indent every label at least one space)
if (/^[a-zA-Z_][a-zA-Z_0-9]*:/) {
- print "nosplabel:$fn:$.\n";
+ msg "nosplabel:$fn:$.\n";
}
## Warn about trailing whitespace.
- # (We don't allow whitespace at the end of the line; make your
- # editor highlight it for you so you can stop adding it in.)
+ # (We don't allow whitespace at the end of the line; make your
+ # editor highlight it for you so you can stop adding it in.)
if (/ +$/) {
- print "Space\@EOL:$fn:$.\n";
+ msg "Space\@EOL:$fn:$.\n";
}
## Warn about control keywords without following space.
- # (We put a space after every 'if', 'while', 'for', 'switch', etc)
+ # (We put a space after every 'if', 'while', 'for', 'switch', etc)
if ($C && /\s(?:if|while|for|switch)\(/) {
- print " KW(:$fn:$.\n";
+ msg " KW(:$fn:$.\n";
}
## Warn about #else #if instead of #elif.
# (We only allow #elif)
if (($lastline =~ /^\# *else/) and ($_ =~ /^\# *if/)) {
- print " #else#if:$fn:$.\n";
+ msg " #else#if:$fn:$.\n";
}
## Warn about some K&R violations
# (We use K&R-style C, where open braces go on the same line as
@@ -53,23 +64,23 @@ for $fn (@ARGV) {
# other stuff;
# }
if (/^\s+\{/ and $lastline =~ /^\s*(if|while|for|else if)/ and
- $lastline !~ /\{$/) {
- print "non-K&R {:$fn:$.\n";
- }
+ $lastline !~ /\{$/) {
+ msg "non-K&R {:$fn:$.\n";
+ }
if (/^\s*else/ and $lastline =~ /\}$/) {
- print " }\\nelse:$fn:$.\n";
- }
+ msg " }\\nelse:$fn:$.\n";
+ }
$lastline = $_;
## Warn about unnecessary empty lines.
# (Don't put an empty line before a line that contains nothing
# but a closing brace.)
if ($lastnil && /^\s*}\n/) {
- print " UnnecNL:$fn:$.\n";
+ msg " UnnecNL:$fn:$.\n";
}
## Warn about multiple empty lines.
# (At most one blank line in a row.)
if ($lastnil && /^$/) {
- print " DoubleNL:$fn:$.\n";
+ msg " DoubleNL:$fn:$.\n";
} elsif (/^$/) {
$lastnil = 1;
} else {
@@ -79,7 +90,7 @@ for $fn (@ARGV) {
## accept double-line lines.
# (Don't make lines wider than 80 characters, including newline.)
if (/^.{80}/) {
- print " Wide:$fn:$.\n";
+ msg " Wide:$fn:$.\n";
}
### Juju to skip over comments and strings, since the tests
### we're about to do are okay there.
@@ -102,28 +113,28 @@ for $fn (@ARGV) {
s!"(?:[^\"]+|\\.)*"!"X"!g;
next if /^\#/;
## Warn about C++-style comments.
- # (Use C style comments only.)
+ # (Use C style comments only.)
if (m!//!) {
- # print " //:$fn:$.\n";
+ # msg " //:$fn:$.\n";
s!//.*!!;
}
## Warn about unquoted braces preceded by non-space.
- # (No character except a space should come before a {)
+ # (No character except a space should come before a {)
if (/([^\s'])\{/) {
- print " $1\{:$fn:$.\n";
+ msg " $1\{:$fn:$.\n";
}
## Warn about multiple internal spaces.
#if (/[^\s,:]\s{2,}[^\s\\=]/) {
- # print " X X:$fn:$.\n";
+ # msg " X X:$fn:$.\n";
#}
## Warn about { with stuff after.
#s/\s+$//;
#if (/\{[^\}\\]+$/) {
- # print " {X:$fn:$.\n";
+ # msg " {X:$fn:$.\n";
#}
## Warn about function calls with space before parens.
- # (Don't put a space between the name of a function and its
- # arguments.)
+ # (Don't put a space between the name of a function and its
+ # arguments.)
if (/(\w+)\s\(([A-Z]*)/) {
if ($1 ne "if" and $1 ne "while" and $1 ne "for" and
$1 ne "switch" and $1 ne "return" and $1 ne "int" and
@@ -131,19 +142,19 @@ for $fn (@ARGV) {
$1 ne "void" and $1 ne "__attribute__" and $1 ne "op" and
$1 ne "size_t" and $1 ne "double" and
$1 ne "workqueue_reply_t") {
- print " fn ():$fn:$.\n";
+ msg " fn ():$fn:$.\n";
}
}
## Warn about functions not declared at start of line.
- # (When you're declaring functions, put "static" and "const"
- # and the return type on one line, and the function name at
- # the start of a new line.)
+ # (When you're declaring functions, put "static" and "const"
+ # and the return type on one line, and the function name at
+ # the start of a new line.)
if ($in_func_head ||
($fn !~ /\.h$/ && /^[a-zA-Z0-9_]/ &&
! /^(?:const |static )*(?:typedef|struct|union)[^\(]*$/ &&
! /= *\{$/ && ! /;$/)) {
if (/.\{$/){
- print "fn() {:$fn:$.\n";
+ msg "fn() {:$fn:$.\n";
$in_func_head = 0;
} elsif (/^\S[^\(]* +\**[a-zA-Z0-9_]+\(/) {
$in_func_head = -1; # started with tp fn
@@ -151,37 +162,38 @@ for $fn (@ARGV) {
$in_func_head = 0;
} elsif (/\{/) {
if ($in_func_head == -1) {
- print "tp fn():$fn:$.\n";
+ msg "tp fn():$fn:$.\n";
}
$in_func_head = 0;
}
}
- ## Check for forbidden functions except when they are
- # explicitly permitted
- if (/\bassert\(/ && not /assert OK/) {
- print "assert :$fn:$. (use tor_assert)\n";
- }
- if (/\bmemcmp\(/ && not /memcmp OK/) {
- print "memcmp :$fn:$. (use {tor,fast}_mem{eq,neq,cmp}\n";
- }
- # always forbidden.
- if (not / OVERRIDE /) {
- if (/\bstrcat\(/ or /\bstrcpy\(/ or /\bsprintf\(/) {
- print "$& :$fn:$.\n";
- }
- if (/\bmalloc\(/ or /\bfree\(/ or /\brealloc\(/ or
- /\bstrdup\(/ or /\bstrndup\(/ or /\bcalloc\(/) {
- print "$& :$fn:$. (use tor_malloc, tor_free, etc)\n";
- }
- }
+ ## Check for forbidden functions except when they are
+ # explicitly permitted
+ if (/\bassert\(/ && not /assert OK/) {
+ msg "assert :$fn:$. (use tor_assert)\n";
+ }
+ if (/\bmemcmp\(/ && not /memcmp OK/) {
+ msg "memcmp :$fn:$. (use {tor,fast}_mem{eq,neq,cmp}\n";
+ }
+ # always forbidden.
+ if (not /\ OVERRIDE\ /) {
+ if (/\bstrcat\(/ or /\bstrcpy\(/ or /\bsprintf\(/) {
+ msg "$& :$fn:$.\n";
+ }
+ if (/\bmalloc\(/ or /\bfree\(/ or /\brealloc\(/ or
+ /\bstrdup\(/ or /\bstrndup\(/ or /\bcalloc\(/) {
+ msg "$& :$fn:$. (use tor_malloc, tor_free, etc)\n";
+ }
+ }
}
}
## Warn if the file doesn't end with a blank line.
# (End each file with a single blank line.)
if (! $lastnil) {
- print " EOL\@EOF:$fn:$.\n";
+ msg " EOL\@EOF:$fn:$.\n";
}
close(F);
}
+exit $found;
diff --git a/scripts/maint/fallback.blacklist b/scripts/maint/fallback.blacklist
index c9fd8a9236..974b304729 100644
--- a/scripts/maint/fallback.blacklist
+++ b/scripts/maint/fallback.blacklist
@@ -27,11 +27,6 @@
# https://lists.torproject.org/pipermail/tor-relays/2015-December/008384.html
80.82.215.199:80 orport=443 id=3BEFAB76461B6B99DCF34C285E933562F5712AE4 ipv6=[2001:4ba0:cafe:a18::1]:443
-# https://lists.torproject.org/pipermail/tor-relays/2016-January/008515.html
-# later opt-out in
-# https://lists.torproject.org/pipermail/tor-relays/2016-January/008521.html
-5.9.158.75:80 orport=443 id=F1BE15429B3CE696D6807F4D4A58B1BFEC45C822 ipv6=[2a01:4f8:190:514a::2]:443
-
# Email sent directly to teor, verified using relay contact info
5.34.183.168:80 orport=443 id=601C92108A568742A7A6D9473FE3A414F7149070
217.12.199.208:8080 orport=22 id=BCFB0933367D626715DA32A147F417194A5D48D6
@@ -132,7 +127,6 @@
85.114.135.20:9030 orport=9001 id=ED8A9291A3139E34BBD35037B082081EC6C26C80 ipv6=[2001:4ba0:fff5:2d::8]:9001
148.251.128.156:9030 orport=9001 id=E382042E06A0A68AFC533E5AD5FB6867A12DF9FF ipv6=[2a01:4f8:210:238a::8]:9001
62.210.115.147:9030 orport=9001 id=7F1D94E2C36F8CC595C2AB00022A5AE38171D50B ipv6=[2001:bc8:3182:101::8]:9001
-212.47.250.24:9030 orport=9001 id=33DA0CAB7C27812EFF2E22C9705630A54D101FEB
# Email sent directly to teor, verified using relay contact info
74.208.220.222:60000 orport=59999 id=4AA22235F0E9B3795A33930343CBB3EDAC60C5B0
@@ -227,3 +221,34 @@ id=9C8A123081EFBE022EF795630F447839DDFDDDEC
# Fallback was on 0.2.8.2-alpha list, but opted-out before 0.2.8.6
37.187.1.149:9030 orport=9001 id=08DC0F3C6E3D9C527C1FC8745D35DD1B0DE1875D ipv6=[2001:41d0:a:195::1]:9001
+
+# Email sent directly to teor, verified using relay contact info
+195.154.15.227:9030 orport=9001 id=6C3E3AB2F5F03CD71B637D433BAD924A1ECC5796
+
+# Fallback was on 0.2.8.6 list, but changed IPv4 before 0.2.9
+195.154.8.111:80 orport=443 id=FCB6695F8F2DC240E974510A4B3A0F2B12AB5B64
+# Same operator, not on 0.2.8.6 list, also changed IPv4
+51.255.235.246:80 orport=443 id=9B99C72B02AF8E3E5BE3596964F9CACD0090D132
+
+# Fallback was on 0.2.8.6 list, but changed IPv4 before 0.2.9
+5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33
+
+# Fallbacks were on 0.2.8.6 list, but went down before 0.2.9
+194.150.168.79:11112 orport=11111 id=29F1020B94BE25E6BE1AD13E93CE19D2131B487C
+94.126.23.174:9030 orport=9001 id=6FC6F08270D565BE89B7C819DD8E2D487397C073
+195.191.233.221:80 orport=443 id=DE134FC8E5CC4EC8A5DE66934E70AC9D70267197
+176.31.180.157:143 orport=22 id=E781F4EC69671B3F1864AE2753E0890351506329 ipv6=[2001:41d0:8:eb9d::1]:22
+
+# Fallback was on 0.2.8.6 list, but opted-out before 0.2.9
+144.76.73.140:9030 orport=9001 id=6A640018EABF3DA9BAD9321AA37C2C87BBE1F907
+
+# https://lists.torproject.org/pipermail/tor-relays/2016-December/011114.html
+# no dirport
+86.107.110.34:0 orport=9001 id=A0E3D30A660DB70CA0B6D081BA54D094DED6F28D
+94.242.59.147:80 orport=9001 id=674DCBB0D9C1C4C4DBFB4A9AE024AF59FE4E7F46 ipv6=[2a00:1838:35:42::b648]:9001
+
+# Email sent directly to teor, verified using relay contact info
+167.114.152.100:9030 orport=443 id=0EF5E5FFC5D1EABCBDA1AFF6F6D6325C5756B0B2 ipv6=[2607:5300:100:200::1608]:443
+
+# Email sent directly to teor, verified using relay contact info
+163.172.35.245:80 orport=443 id=B771AA877687F88E6F1CA5354756DF6C8A7B6B24
diff --git a/scripts/maint/fallback.whitelist b/scripts/maint/fallback.whitelist
index c801e46b15..c993be97d3 100644
--- a/scripts/maint/fallback.whitelist
+++ b/scripts/maint/fallback.whitelist
@@ -50,17 +50,14 @@
167.114.35.28:9030 orport=9001 id=E65D300F11E1DB12C534B0146BDAB6972F1A8A48
# https://lists.torproject.org/pipermail/tor-relays/2015-December/008374.html
-170.130.1.7:9030 orport=9001 id=FA3415659444AE006E7E9E5375E82F29700CFDFD
+104.243.35.196:9030 orport=9001 id=FA3415659444AE006E7E9E5375E82F29700CFDFD
# https://lists.torproject.org/pipermail/tor-relays/2015-December/008378.html
144.76.14.145:110 orport=143 id=14419131033443AE6E21DA82B0D307F7CAE42BDB ipv6=[2a01:4f8:190:9490::dead]:443
# https://lists.torproject.org/pipermail/tor-relays/2015-December/008379.html
# Email sent directly to teor, verified using relay contact info
-91.121.84.137:4951 orport=4051 id=6DE61A6F72C1E5418A66BFED80DFB63E4C77668F
-
-# https://lists.torproject.org/pipermail/tor-relays/2015-December/008380.html
-5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33
+91.121.84.137:4951 orport=4051 id=6DE61A6F72C1E5418A66BFED80DFB63E4C77668F ipv6=[2001:41d0:1:8989::1]:4051
# https://lists.torproject.org/pipermail/tor-relays/2015-December/008381.html
# Sent additional email to teor with more relays
@@ -99,17 +96,14 @@
178.62.199.226:80 orport=443 id=CBEFF7BA4A4062045133C053F2D70524D8BBE5BE ipv6=[2a03:b0c0:2:d0::b7:5001]:443
# Emails sent directly to teor, verified using relay contact info
-217.12.199.208:80 orport=443 id=DF3AED4322B1824BF5539AE54B2D1B38E080FF05
+217.12.199.208:80 orport=443 id=DF3AED4322B1824BF5539AE54B2D1B38E080FF05 ipv6=[2a02:27a8:0:2::7e]:443
# Email sent directly to teor, verified using relay contact info
94.23.204.175:9030 orport=9001 id=5665A3904C89E22E971305EE8C1997BCA4123C69
-# https://twitter.com/binarytenshi/status/717952514327453697
-94.126.23.174:9030 orport=9001 id=6FC6F08270D565BE89B7C819DD8E2D487397C073
-
# Email sent directly to teor, verified using relay contact info
-171.25.193.78:80 orport=443 id=A478E421F83194C114F41E94F95999672AED51FE ipv6=[2001:67c:289c:3::78]:443
171.25.193.77:80 orport=443 id=A10C4F666D27364036B562823E5830BC448E046A ipv6=[2001:67c:289c:3::77]:443
+171.25.193.78:80 orport=443 id=A478E421F83194C114F41E94F95999672AED51FE ipv6=[2001:67c:289c:3::78]:443
171.25.193.131:80 orport=443 id=79861CF8522FC637EF046F7688F5289E49D94576
171.25.193.20:80 orport=443 id=DD8BD7307017407FCC36F8D04A688F74A0774C02 ipv6=[2001:67c:289c::20]:443
# OK, but same machine as 79861CF8522FC637EF046F7688F5289E49D94576
@@ -118,9 +112,9 @@
#171.25.193.25:80 orport=443 id=185663B7C12777F052B2C2D23D7A239D8DA88A0F ipv6=[2001:67c:289c::25]:443
# Email sent directly to teor, verified using relay contact info
-212.47.229.2:9030 orport=9001 id=20462CBA5DA4C2D963567D17D0B7249718114A68
+212.47.229.2:9030 orport=9001 id=20462CBA5DA4C2D963567D17D0B7249718114A68 ipv6=[2001:bc8:4400:2100::f03]:9001
93.115.97.242:9030 orport=9001 id=B5212DB685A2A0FCFBAE425738E478D12361710D
-46.28.109.231:9030 orport=9001 id=F70B7C5CD72D74C7F9F2DC84FA9D20D51BA13610 ipv6=[2a02:2b88:2:1::4205:42]:9001
+46.28.109.231:9030 orport=9001 id=F70B7C5CD72D74C7F9F2DC84FA9D20D51BA13610 ipv6=[2a02:2b88:2:1::4205:1]:9001
# Email sent directly to teor, verified using relay contact info
85.235.250.88:80 orport=443 id=72B2B12A3F60408BDBC98C6DF53988D3A0B3F0EE
@@ -154,31 +148,19 @@
178.16.208.59:80 orport=443 id=136F9299A5009A4E0E96494E723BDB556FB0A26B ipv6=[2a00:1c20:4089:1234:bff6:e1bb:1ce3:8dc6]:443
# Email sent directly to teor, verified using relay contact info
-195.154.8.111:80 orport=443 id=FCB6695F8F2DC240E974510A4B3A0F2B12AB5B64
-51.255.235.246:80 orport=443 id=9B99C72B02AF8E3E5BE3596964F9CACD0090D132
5.39.76.158:80 orport=443 id=C41F60F8B00E7FEF5CCC5BC6BB514CA1B8AAB651
# Email sent directly to teor, verified using relay contact info
-109.163.234.5:80 orport=443 id=5C84C35936B7100B949AC75764EEF1352550550B
-109.163.234.7:80 orport=443 id=C46524E586E1B997329703D356C07EE12B28C722
-109.163.234.9:80 orport=443 id=5714542DCBEE1DD9864824723638FD44B2122CEA
-77.247.181.162:80 orport=443 id=7BB160A8F54BD74F3DA5F2CE701E8772B841859D
-109.163.234.4:80 orport=443 id=6B1E001929AF4DDBB747D02EC28340792B7724A6
-77.247.181.164:80 orport=443 id=10E13E340651D0EF66B4DEBF610B3C0981168107
-109.163.234.8:80 orport=443 id=20B0038D7A2FD73C696922551B8344CB0893D1F8
-77.247.181.166:80 orport=443 id=06E123865C590189B3181114F23F0F13A7BC0E69
-109.163.234.2:80 orport=443 id=B4F883DB3D478C7AE569C9F6CB766FD58650DC6A
+109.163.234.2:80 orport=443 id=14F92FF956105932E9DEC5B82A7778A0B1BD9A52
+109.163.234.4:80 orport=443 id=4888770464F0E900EFEF1BA181EA873D13F7713C
+109.163.234.5:80 orport=443 id=5EB8D862E70981B8690DEDEF546789E26AB2BD24
+109.163.234.7:80 orport=443 id=23038A7F2845EBA2234ECD6651BD4A7762F51B18
+109.163.234.8:80 orport=443 id=0818DAE0E2DDF795AEDEAC60B15E71901084F281
+109.163.234.9:80 orport=443 id=ABF7FBF389C9A747938B639B20E80620B460B2A9
62.102.148.67:80 orport=443 id=4A0C3E177AF684581EF780981AEAF51A98A6B5CF
-109.163.234.5:80 orport=443 id=5C84C35936B7100B949AC75764EEF1352550550B
-109.163.234.7:80 orport=443 id=C46524E586E1B997329703D356C07EE12B28C722
-109.163.234.9:80 orport=443 id=5714542DCBEE1DD9864824723638FD44B2122CEA
77.247.181.162:80 orport=443 id=7BB160A8F54BD74F3DA5F2CE701E8772B841859D
-109.163.234.4:80 orport=443 id=6B1E001929AF4DDBB747D02EC28340792B7724A6
77.247.181.164:80 orport=443 id=10E13E340651D0EF66B4DEBF610B3C0981168107
-109.163.234.8:80 orport=443 id=20B0038D7A2FD73C696922551B8344CB0893D1F8
77.247.181.166:80 orport=443 id=06E123865C590189B3181114F23F0F13A7BC0E69
-109.163.234.2:80 orport=443 id=B4F883DB3D478C7AE569C9F6CB766FD58650DC6A
-62.102.148.67:80 orport=443 id=4A0C3E177AF684581EF780981AEAF51A98A6B5CF
# https://twitter.com/biotimylated/status/718994247500718080
212.47.252.149:9030 orport=9001 id=2CAC39BAA996791CEFAADC9D4754D65AF5EB77C0
@@ -215,9 +197,7 @@
# Email sent directly to teor, verified using relay contact info
86.59.119.88:80 orport=443 id=ACD889D86E02EDDAB1AFD81F598C0936238DC6D0
-
-# Email sent directly to teor, verified using relay contact info
-144.76.73.140:9030 orport=9001 id=6A640018EABF3DA9BAD9321AA37C2C87BBE1F907
+86.59.119.83:80 orport=443 id=FC9AC8EA0160D88BCCFDE066940D7DD9FA45495B
# Email sent directly to teor, verified using relay contact info
193.11.164.243:9030 orport=9001 id=FFA72BD683BC2FCF988356E6BEC1E490F313FB07 ipv6=[2001:6b0:7:125::243]:9001
@@ -278,8 +258,8 @@
# Email sent directly to teor, verified using relay contact info
178.62.22.36:80 orport=443 id=A0766C0D3A667A3232C7D569DE94A28F9922FCB1 ipv6=[2a03:b0c0:1:d0::174:1]:9050
-188.166.23.127:80 orport=443 id=3771A8154DEA98D551607806C80A209CDAA74535 ipv6=[2a03:b0c0:2:d0::27b:7001]:9050
-198.199.64.217:80 orport=443 id=FAD306BAA59F6A02783F8606BDAA431F5FF7D1EA ipv6=[2604:a880:400:d0::1a9:b001]:9050
+188.166.23.127:80 orport=443 id=8672E8A01B4D3FA4C0BBE21C740D4506302EA487 ipv6=[2a03:b0c0:2:d0::27b:7001]:9050
+198.199.64.217:80 orport=443 id=B1D81825CFD7209BD1B4520B040EF5653C204A23 ipv6=[2604:a880:400:d0::1a9:b001]:9050
159.203.32.149:80 orport=443 id=55C7554AFCEC1062DCBAC93E67B2E03C6F330EFC ipv6=[2604:a880:cad:d0::105:f001]:9050
# Email sent directly to teor, verified using relay contact info
@@ -301,9 +281,6 @@
212.47.230.49:9030 orport=9001 id=3D6D0771E54056AEFC28BB1DE816951F11826E97
# Email sent directly to teor, verified using relay contact info
-176.31.180.157:143 orport=22 id=E781F4EC69671B3F1864AE2753E0890351506329 ipv6=[2001:41d0:8:eb9d::1]:22
-
-# Email sent directly to teor, verified using relay contact info
192.99.55.69:80 orport=443 id=0682DE15222A4A4A0D67DBA72A8132161992C023
192.99.59.140:80 orport=443 id=3C9148DA49F20654730FAC83FFF693A4D49D0244
51.254.215.13:80 orport=443 id=73C30C8ABDD6D9346C822966DE73B9F82CB6178A
@@ -318,7 +295,7 @@
151.80.42.103:9030 orport=9001 id=9007C1D8E4F03D506A4A011B907A9E8D04E3C605 ipv6=[2001:41d0:e:f67::114]:9001
# Email sent directly to teor, verified using relay contact info
-5.39.92.199:80 orport=443 id=0BEA4A88D069753218EAAAD6D22EA87B9A1319D6
+5.39.92.199:80 orport=443 id=0BEA4A88D069753218EAAAD6D22EA87B9A1319D6 ipv6=[2001:41d0:8:b1c7::1]:443
# Email sent directly to teor, verified using relay contact info
176.31.159.231:80 orport=443 id=D5DBCC0B4F029F80C7B8D33F20CF7D97F0423BB1
@@ -332,10 +309,7 @@
212.47.241.21:80 orport=443 id=892F941915F6A0C6E0958E52E0A9685C190CF45C
# Email sent directly to teor, verified using relay contact info
-195.191.233.221:80 orport=443 id=DE134FC8E5CC4EC8A5DE66934E70AC9D70267197
-
-# Email sent directly to teor, verified using relay contact info
-62.210.238.33:9030 orport=9001 id=FDF845FC159C0020E2BDDA120C30C5C5038F74B4
+212.129.38.254:9030 orport=9001 id=FDF845FC159C0020E2BDDA120C30C5C5038F74B4
# Email sent directly to teor, verified using relay contact info
37.157.195.87:8030 orport=443 id=12FD624EE73CEF37137C90D38B2406A66F68FAA2
@@ -405,12 +379,12 @@
91.219.237.229:80 orport=443 id=1ECD73B936CB6E6B3CD647CC204F108D9DF2C9F7
# Email sent directly to teor, verified using relay contact info
-# Suitable, check with operator before adding
-#212.47.240.10:82 orport=443 id=2A4C448784F5A83AFE6C78DA357D5E31F7989DEB
-212.47.240.10:81 orport=993 id=72527E3242CB15AADE28374AE0D35833FC083F60
+212.47.240.10:82 orport=443 id=2A4C448784F5A83AFE6C78DA357D5E31F7989DEB
+# Ok, but on the same machine as 2A4C448784F5A83AFE6C78DA357D5E31F7989DEB
+#212.47.240.10:81 orport=993 id=72527E3242CB15AADE28374AE0D35833FC083F60
163.172.131.88:80 orport=443 id=AD253B49E303C6AB1E048B014392AC569E8A7DAE ipv6=[2001:bc8:4400:2100::2:1009]:443
-# Suitable, check with operator before adding
-#163.172.131.88:81 orport=993 id=D5F3FB17504744FB7ECEF46F4B1D155258A6D942 ipv6=D5F3FB17504744FB7ECEF46F4B1D155258A6D942
+# Ok, but on the same machine as AD253B49E303C6AB1E048B014392AC569E8A7DAE
+#163.172.131.88:81 orport=993 id=D5F3FB17504744FB7ECEF46F4B1D155258A6D942 ipv6=[2001:bc8:4400:2100::2:1009]:993
# Email sent directly to teor, verified using relay contact info
46.101.151.222:80 orport=443 id=1DBAED235E3957DE1ABD25B4206BE71406FB61F8
@@ -444,9 +418,6 @@
188.166.133.133:9030 orport=9001 id=774555642FDC1E1D4FDF2E0C31B7CA9501C5C9C7 ipv6=[2a03:b0c0:2:d0::5:f001]:9001
# Email sent directly to teor, verified using relay contact info
-5.196.88.122:9030 orport=9001 id=0C2C599AFCB26F5CFC2C7592435924C1D63D9484
-
-# Email sent directly to teor, verified using relay contact info
46.8.249.10:80 orport=443 id=31670150090A7C3513CB7914B9610E786391A95D
# Email sent directly to teor, verified using relay contact info
@@ -485,11 +456,10 @@
5.9.146.203:80 orport=443 id=1F45542A24A61BF9408F1C05E0DCE4E29F2CBA11
# Email sent directly to teor, verified using relay contact info
-167.114.152.100:9030 orport=443 id=0EF5E5FFC5D1EABCBDA1AFF6F6D6325C5756B0B2 ipv6=[2607:5300:100:200::1608]:443
-
-# Email sent directly to teor, verified using relay contact info
-192.99.168.102:80 orport=443 id=230A8B2A8BA861210D9B4BA97745AEC217A94207
-167.114.153.21:80 orport=443 id=0B85617241252517E8ECF2CFC7F4C1A32DCD153F
+# Updated details from atlas based on ticket #20010
+163.172.176.167:80 orport=443 id=230A8B2A8BA861210D9B4BA97745AEC217A94207
+163.172.149.155:80 orport=443 id=0B85617241252517E8ECF2CFC7F4C1A32DCD153F
+163.172.149.122:80 orport=443 id=A9406A006D6E7B5DA30F2C6D4E42A338B5E340B2
# Email sent directly to teor, verified using relay contact info
204.11.50.131:9030 orport=9001 id=185F2A57B0C4620582602761097D17DB81654F70
@@ -498,9 +468,6 @@
151.236.222.217:44607 orport=9001 id=94D58704C2589C130C9C39ED148BD8EA468DBA54
# Email sent directly to teor, verified using relay contact info
-194.150.168.79:11112 orport=11111 id=29F1020B94BE25E6BE1AD13E93CE19D2131B487C
-
-# Email sent directly to teor, verified using relay contact info
185.35.202.221:9030 orport=9001 id=C13B91384CDD52A871E3ECECE4EF74A7AC7DCB08 ipv6=[2a02:ed06::221]:9001
# Email sent directly to teor, verified using relay contact info
@@ -513,7 +480,7 @@
92.222.20.130:80 orport=443 id=0639612FF149AA19DF3BCEA147E5B8FED6F3C87C
# Email sent directly to teor, verified using relay contact info
-80.112.155.100:9030 orport=9001 id=1163378F239C36CA1BDC730AC50BF4F2976141F5 ipv6=[2001:470:7b02::38]:9001
+80.112.155.100:9030 orport=9001 id=53B000310984CD86AF47E5F3CD0BFF184E34B383 ipv6=[2001:470:7b02::38]:9001
# Email sent directly to teor, verified using relay contact info
83.212.99.68:80 orport=443 id=DDBB2A38252ADDA53E4492DDF982CA6CC6E10EC0 ipv6=[2001:648:2ffc:1225:a800:bff:fe3d:67b5]:443
@@ -522,7 +489,7 @@
95.130.11.147:9030 orport=443 id=6B697F3FF04C26123466A5C0E5D1F8D91925967A
# Email sent directly to teor, verified using relay contact info
-176.31.191.26:9030 orport=9001 id=7350AB9ED7568F22745198359373C04AC783C37C
+176.31.191.26:80 orport=443 id=7350AB9ED7568F22745198359373C04AC783C37C
# Email sent directly to teor, verified using relay contact info
128.199.55.207:9030 orport=9001 id=BCEF908195805E03E92CCFE669C48738E556B9C5 ipv6=[2a03:b0c0:2:d0::158:3001]:9001
@@ -540,16 +507,17 @@
80.240.139.111:80 orport=443 id=DD3BE7382C221F31723C7B294310EF9282B9111B
# Email sent directly to teor, verified using relay contact info
-185.97.32.18:9030 orport=9001 id=3BAB316CAAEC47E71905EB6C65584636D5689A8A
+185.97.32.18:9030 orport=9001 id=04250C3835019B26AA6764E85D836088BE441088
# Email sent directly to teor, verified using relay contact info
149.56.45.200:9030 orport=9001 id=FE296180018833AF03A8EACD5894A614623D3F76
# Email sent directly to teor, verified using relay contact info
-81.2.209.10:443 orport=80 id=B6904ADD4C0D10CDA7179E051962350A69A63243
+81.2.209.10:443 orport=80 id=B6904ADD4C0D10CDA7179E051962350A69A63243 ipv6=[2001:15e8:201:1::d10a]:80
# Email sent directly to teor, verified using relay contact info
-195.154.164.243:80 orport=443 id=AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C ipv6=[2001:bc8:399f:f000::1]:993
+# IPv6 address unreliable
+195.154.164.243:80 orport=443 id=AC66FFA4AB35A59EBBF5BF4C70008BF24D8A7A5C #ipv6=[2001:bc8:399f:f000::1]:993
138.201.26.2:80 orport=443 id=6D3A3ED5671E4E3F58D4951438B10AE552A5FA0F
81.7.16.182:80 orport=443 id=51E1CF613FD6F9F11FE24743C91D6F9981807D82 ipv6=[2a02:180:1:1::517:10b6]:993
134.119.36.135:80 orport=443 id=763C9556602BD6207771A7A3D958091D44C43228 ipv6=[2a00:1158:3::2a8]:993
@@ -563,7 +531,7 @@
217.12.208.117:80 orport=443 id=E6E18151300F90C235D3809F90B31330737CEB43 ipv6=[2a00:1ca8:a7::1bb]:993
81.7.10.251:80 orport=443 id=8073670F8F852971298F8AF2C5B23AE012645901 ipv6=[2a02:180:1:1::517:afb]:993
46.36.39.50:80 orport=443 id=ED4B0DBA79AEF5521564FA0231455DCFDDE73BB6 ipv6=[2a02:25b0:aaaa:aaaa:8d49:b692:4852:0]:995
-91.194.90.103:80 orport=443 id=75C4495F4D80522CA6F6A3FB349F1B009563F4B7 ipv6=[2a02:c200:0:10:3:0:5449:1]:993
+91.194.90.103:80 orport=443 id=75C4495F4D80522CA6F6A3FB349F1B009563F4B7 ipv6=[2a02:c205:3000:5449::1]:993
163.172.25.118:80 orport=22 id=0CF8F3E6590F45D50B70F2F7DA6605ECA6CD408F
188.138.88.42:80 orport=443 id=70C55A114C0EF3DC5784A4FAEE64388434A3398F
81.7.13.84:80 orport=443 id=0C1E7DD9ED0676C788933F68A9985ED853CA5812 ipv6=[2a02:180:1:1::5b8f:538c]:993
@@ -587,11 +555,10 @@
91.229.20.27:9030 orport=9001 id=9A0D54D3A6D2E0767596BF1515E6162A75B3293F
# Email sent directly to teor, verified using relay contact info
-# Awaiting confirmation of new ORPort from relay operator
80.127.137.19:80 orport=443 id=6EF897645B79B6CB35E853B32506375014DE3621 ipv6=[2001:981:47c1:1::6]:443
# Email sent directly to teor, verified using relay contact info
-163.172.138.22:80 orport=443 id=8664DC892540F3C789DB37008236C096C871734D
+163.172.138.22:80 orport=443 id=8664DC892540F3C789DB37008236C096C871734D ipv6=[2001:bc8:4400:2100::1:3]:443
# Email sent directly to teor, verified using relay contact info
97.74.237.196:9030 orport=9001 id=2F0F32AB1E5B943CA7D062C03F18960C86E70D94
@@ -603,7 +570,7 @@
178.62.98.160:9030 orport=9001 id=8B92044763E880996A988831B15B2B0E5AD1544A
# Email sent directly to teor, verified using relay contact info
-195.154.15.227:9030 orport=9001 id=6C3E3AB2F5F03CD71B637D433BAD924A1ECC5796
+163.172.217.50:9030 orport=9001 id=02ECD99ECD596013A8134D46531560816ECC4BE6
# Email sent directly to teor, verified using relay contact info
185.100.86.100:80 orport=443 id=0E8C0C8315B66DB5F703804B3889A1DD66C67CE0
@@ -617,10 +584,11 @@
178.62.86.96:9030 orport=9001 id=439D0447772CB107B886F7782DBC201FA26B92D1 ipv6=[2a03:b0c0:1:d0::3cf:7001]:9050
# Email sent directly to teor, verified using relay contact info
-91.233.106.121:80 orport=443 id=896364B7996F5DFBA0E15D1A2E06D0B98B555DD6
+# Very low bandwidth, stale consensues, excluded to cut down on warnings
+#91.233.106.121:80 orport=443 id=896364B7996F5DFBA0E15D1A2E06D0B98B555DD6
# Email sent directly to teor, verified using relay contact info
-167.114.113.48:9030 orport=443 id=2EC0C66EA700C44670444280AABAB1EC78B722A0
+167.114.113.48:9030 orport=403 id=2EC0C66EA700C44670444280AABAB1EC78B722A0
# Email sent directly to teor, verified using relay contact info
79.120.16.42:9030 orport=9001 id=BD552C165E2ED2887D3F1CCE9CFF155DDA2D86E6
@@ -675,7 +643,7 @@
46.4.111.124:9030 orport=9001 id=D9065F9E57899B3D272AA212317AF61A9B14D204
# Email sent directly to teor, verified using relay contact info
-78.46.164.129:9030 orport=9001 id=52AEA31188331F421B2EDB494DB65CD181E5B257
+138.201.130.32:9030 orport=9001 id=52AEA31188331F421B2EDB494DB65CD181E5B257
# Email sent directly to teor, verified using relay contact info
185.100.85.61:80 orport=443 id=025B66CEBC070FCB0519D206CF0CF4965C20C96E
@@ -684,11 +652,12 @@
108.166.168.158:80 orport=443 id=CDAB3AE06A8C9C6BF817B3B0F1877A4B91465699
# Email sent directly to teor, verified using relay contact info
-91.219.236.222:80 orport=443 id=EC413181CEB1C8EDC17608BBB177CD5FD8535E99
+91.219.236.222:80 orport=443 id=20704E7DD51501DC303FA51B738D7B7E61397CF6
# Email sent directly to teor, verified using relay contact info
185.14.185.240:9030 orport=443 id=D62FB817B0288085FAC38A6DC8B36DCD85B70260
192.34.63.137:9030 orport=443 id=ABCB4965F1FEE193602B50A365425105C889D3F8
+128.199.197.16:9030 orport=443 id=DEE5298B3BA18CDE651421CD2DCB34A4A69F224D
# Email sent directly to teor, verified using relay contact info
185.13.38.75:9030 orport=9001 id=D2A1703758A0FBBA026988B92C2F88BAB59F9361
@@ -719,7 +688,7 @@
166.70.207.2:9030 orport=9001 id=E3DB2E354B883B59E8DC56B3E7A353DDFD457812
# Emails sent directly to teor, verified using relay contact info
-#69.162.139.9:9030 orport=9001 id=4791FC0692EAB60DF2BCCAFF940B95B74E7654F6 ipv6=[2607:f128:40:1212::45a2:8b09]:9001
+69.162.139.9:9030 orport=9001 id=4791FC0692EAB60DF2BCCAFF940B95B74E7654F6 ipv6=[2607:f128:40:1212::45a2:8b09]:9001
# Email sent directly to teor, verified using relay contact info
213.239.217.18:1338 orport=1337 id=C37BC191AC389179674578C3E6944E925FE186C2 ipv6=[2a01:4f8:a0:746a:101:1:1:1]:1337
@@ -749,7 +718,6 @@
# Email sent directly to teor, verified using relay contact info
163.172.35.249:80 orport=443 id=C08DE49658E5B3CFC6F2A952B453C4B608C9A16A
163.172.35.247:80 orport=443 id=71AB4726D830FAE776D74AEF790CF04D8E0151B4
-163.172.13.124:80 orport=443 id=B771AA877687F88E6F1CA5354756DF6C8A7B6B24
# Email sent directly to teor, verified using relay contact info
64.113.32.29:9030 orport=9001 id=30C19B81981F450C402306E2E7CFB6C3F79CB6B2
@@ -768,3 +736,95 @@
# Email sent directly to teor, verified using relay contact info
62.216.5.120:9030 orport=9001 id=D032D4D617140D6B828FC7C4334860E45E414FBE
+
+# Email sent directly to teor, verified using relay contact info
+51.254.136.195:80 orport=443 id=7BB70F8585DFC27E75D692970C0EEB0F22983A63
+
+# Email sent directly to teor, verified using relay contact info
+163.172.13.165:9030 orport=9001 id=33DA0CAB7C27812EFF2E22C9705630A54D101FEB ipv6=[2001:bc8:38cb:201::8]:9001
+
+# Email sent directly to teor, verified using relay contact info
+5.196.88.122:9030 orport=9001 id=0C2C599AFCB26F5CFC2C7592435924C1D63D9484 ipv6=[2001:41d0:a:fb7a::1]:9001
+
+# Email sent directly to teor, verified using relay contact info
+5.9.158.75:80 orport=443 id=1AF72E8906E6C49481A791A6F8F84F8DFEBBB2BA ipv6=[2a01:4f8:190:514a::2]:443
+
+# Email sent directly to teor, verified using relay contact info
+46.101.169.151:9030 orport=9001 id=D760C5B436E42F93D77EF2D969157EEA14F9B39C ipv6=[2a03:b0c0:3:d0::74f:a001]:9001
+
+# Email sent directly to teor, verified using relay contact info
+199.249.223.81:80 orport=443 id=F7447E99EB5CBD4D5EB913EE0E35AC642B5C1EF3
+199.249.223.79:80 orport=443 id=D33292FEDE24DD40F2385283E55C87F85C0943B6
+199.249.223.78:80 orport=443 id=EC15DB62D9101481F364DE52EB8313C838BDDC29
+199.249.223.77:80 orport=443 id=CC4A3AE960E3617F49BF9887B79186C14CBA6813
+199.249.223.76:80 orport=443 id=43209F6D50C657A56FE79AF01CA69F9EF19BD338
+199.249.223.75:80 orport=443 id=60D3667F56AEC5C69CF7E8F557DB21DDF6C36060
+199.249.223.74:80 orport=443 id=5F4CD12099AF20FAF9ADFDCEC65316A376D0201C
+199.249.223.73:80 orport=443 id=5649CB2158DA94FB747415F26628BEC07FA57616
+199.249.223.72:80 orport=443 id=B028707969D8ED84E6DEA597A884F78AAD471971
+199.249.223.71:80 orport=443 id=B6320E44A230302C7BF9319E67597A9B87882241
+199.249.223.60:80 orport=443 id=B7047FBDE9C53C39011CA84E5CB2A8E3543066D0
+199.249.223.61:80 orport=443 id=40E7D6CE5085E4CDDA31D51A29D1457EB53F12AD
+199.249.223.62:80 orport=443 id=0077BCBA7244DB3E6A5ED2746E86170066684887
+199.249.223.63:80 orport=443 id=1DB25DF59DAA01B5BE3D3CEB8AFED115940EBE8B
+199.249.223.64:80 orport=443 id=9F2856F6D2B89AD4EF6D5723FAB167DB5A53519A
+199.249.223.65:80 orport=443 id=9D21F034C3BFF4E7737D08CF775DC1745706801F
+199.249.223.66:80 orport=443 id=C5A53BCC174EF8FD0DCB223E4AA929FA557DEDB2
+199.249.223.67:80 orport=443 id=155D6F57425F16C0624D77777641E4EB1B47C6F0
+199.249.223.68:80 orport=443 id=DF20497E487A979995D851A5BCEC313DF7E5BC51
+199.249.223.69:80 orport=443 id=7FA8E7E44F1392A4E40FFC3B69DB3B00091B7FD3
+
+# https://lists.torproject.org/pipermail/tor-relays/2016-December/011114.html
+86.105.212.130:9030 orport=443 id=9C900A7F6F5DD034CFFD192DAEC9CCAA813DB022
+
+# Email sent directly to teor, verified using relay contact info
+178.33.183.251:80 orport=443 id=DD823AFB415380A802DCAEB9461AE637604107FB ipv6=[2001:41d0:2:a683::251]:443
+
+# Email sent directly to teor, verified using relay contact info
+#31.185.104.19:80 orport=443 id=9EAD5B2D3DBD96DBC80DCE423B0C345E920A758D
+# OK, but on same machine as 9EAD5B2D3DBD96DBC80DCE423B0C345E920A758D
+31.185.104.20:80 orport=443 id=ADB2C26629643DBB9F8FE0096E7D16F9414B4F8D
+#31.185.104.21:80 orport=443 id=C2AAB088555850FC434E68943F551072042B85F1
+#31.185.104.22:80 orport=443 id=5BA3A52760A0EABF7E7C3ED3048A77328FF0F148
+
+# Email sent directly to teor, verified using relay contact info
+185.34.60.114:80 orport=443 id=7F7A695DF6F2B8640A70B6ADD01105BC2EBC5135
+
+# Email sent directly to teor, verified using relay contact info
+94.142.242.84:80 orport=443 id=AA0D167E03E298F9A8CD50F448B81FBD7FA80D56 ipv6=[2a02:898:24:84::1]:443
+
+# Email sent directly to teor, verified using relay contact info
+185.129.62.62:9030 orport=9001 id=ACDD9E85A05B127BA010466C13C8C47212E8A38F ipv6=[2a06:d380:0:3700::62]:9001
+
+# Email sent directly to teor, verified using relay contact info
+# The e84 part of the IPv6 address does not have a leading 0 in the consensus
+81.30.158.213:9030 orport=9001 id=789EA6C9AE9ADDD8760903171CFA9AC5741B0C70 ipv6=[2001:4ba0:cafe:e84::1]:9001
+
+# https://lists.torproject.org/pipermail/tor-relays/2016-December/011209.html
+5.9.159.14:9030 orport=9001 id=0F100F60C7A63BED90216052324D29B08CFCF797
+
+# Email sent directly to teor, verified using relay contact info
+45.62.255.25:80 orport=443 id=3473ED788D9E63361D1572B7E82EC54338953D2A
+
+# Email sent directly to teor, verified using relay contact info
+217.79.179.177:9030 orport=9001 id=3E53D3979DB07EFD736661C934A1DED14127B684 ipv6=[2001:4ba0:fff9:131:6c4f::90d3]:9001
+
+# Email sent directly to teor, verified using relay contact info
+212.47.244.38:8080 orport=443 id=E81EF60A73B3809F8964F73766B01BAA0A171E20
+163.172.157.213:8080 orport=443 id=4623A9EC53BFD83155929E56D6F7B55B5E718C24
+163.172.139.104:8080 orport=443 id=68F175CCABE727AA2D2309BCD8789499CEE36ED7
+
+# Email sent directly to teor, verified using relay contact info
+163.172.223.200:80 orport=443 id=998BF3ED7F70E33D1C307247B9626D9E7573C438
+195.154.122.54:80 orport=443 id=64E99CB34C595A02A3165484BD1215E7389322C6
+
+# Email sent directly to teor, verified using relay contact info
+185.100.86.128:9030 orport=9001 id=9B31F1F1C1554F9FFB3455911F82E818EF7C7883
+185.100.85.101:9030 orport=9001 id=4061C553CA88021B8302F0814365070AAE617270
+31.171.155.108:9030 orport=9001 id=D3E5EDDBE5159388704D6785BE51930AAFACEC6F
+
+# Email sent directly to teor, verified using relay contact info
+89.163.247.43:9030 orport=9001 id=BC7ACFAC04854C77167C7D66B7E471314ED8C410 ipv6=[2001:4ba0:fff7:25::5]:9001
+
+# Email sent directly to teor, verified using relay contact info
+95.85.8.226:80 orport=443 id=1211AC1BBB8A1AF7CBA86BCE8689AA3146B86423
diff --git a/scripts/maint/lintChanges.py b/scripts/maint/lintChanges.py
index b63a4eb3a1..bf06064fa8 100755
--- a/scripts/maint/lintChanges.py
+++ b/scripts/maint/lintChanges.py
@@ -7,7 +7,7 @@ import re
import os
-KNOWN_GROUPS=set([
+KNOWN_GROUPS = set([
"Minor bugfix",
"Minor bugfixes",
"Major bugfix",
@@ -22,6 +22,7 @@ KNOWN_GROUPS=set([
"Code simplification and refactoring",
"Removed features"])
+
def lintfile(fname):
have_warned = []
@@ -43,17 +44,15 @@ def lintfile(fname):
if bugnum and bugnum not in contents:
warn("bug number {} does not appear".format(bugnum))
- lines = contents.split("\n")
-
m = re.match(r'^[ ]{2}o ([^\(:]*)([^:]*):', contents)
if not m:
warn("header not in format expected")
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("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))
if m:
isBug = ("bug" in m.group(1).lower() or "fix" in m.group(1).lower())
@@ -76,6 +75,8 @@ def lintfile(fname):
elif not re.search('[fF]ixes ([a-z ]*)bug (\d+); bugfix on ',
contents):
warn("bugfix incant is not semicoloned")
+ elif re.search('tor-([0-9]+)', contents):
+ warn("do not prefix versions with 'tor-'")
if __name__ == '__main__':
diff --git a/scripts/maint/updateFallbackDirs.py b/scripts/maint/updateFallbackDirs.py
index 110ecda64c..117ac5cccb 100755
--- a/scripts/maint/updateFallbackDirs.py
+++ b/scripts/maint/updateFallbackDirs.py
@@ -1,6 +1,8 @@
#!/usr/bin/python
-# Usage: scripts/maint/updateFallbackDirs.py > src/or/fallback_dirs.inc
+# Usage:
+# scripts/maint/updateFallbackDirs.py > src/or/fallback_dirs.inc
+# scripts/maint/updateFallbackDirs.py check_existing > src/or/fallback_dirs.inc
#
# This script should be run from a stable, reliable network connection,
# with no other network activity (and not over tor).
@@ -37,16 +39,13 @@ import dateutil.parser
# bson_lazy provides bson
#from bson import json_util
import copy
+import re
-from stem.descriptor.remote import DescriptorDownloader
+from stem.descriptor import DocumentHandler
+from stem.descriptor.remote import get_consensus
import logging
-# INFO tells you why each relay was included or excluded
-# WARN tells you about potential misconfigurations and relay detail changes
-logging.basicConfig(level=logging.WARNING)
logging.root.name = ''
-# INFO tells you about each consensus download attempt
-logging.getLogger('stem').setLevel(logging.WARNING)
HAVE_IPADDRESS = False
try:
@@ -80,7 +79,27 @@ PERFORM_IPV4_DIRPORT_CHECKS = False if OUTPUT_CANDIDATES else True
# Don't check ~1000 candidates when OUTPUT_CANDIDATES is True
PERFORM_IPV6_DIRPORT_CHECKS = False if OUTPUT_CANDIDATES else False
-# Output fallback name, flags, and ContactInfo in a C comment?
+# Must relays be running now?
+MUST_BE_RUNNING_NOW = (PERFORM_IPV4_DIRPORT_CHECKS
+ or PERFORM_IPV6_DIRPORT_CHECKS)
+
+# Clients have been using microdesc consensuses by default for a while now
+DOWNLOAD_MICRODESC_CONSENSUS = True
+
+# If a relay delivers an expired consensus, if it expired less than this many
+# seconds ago, we still allow the relay. This should never be less than -90,
+# as all directory mirrors should have downloaded a consensus 90 minutes
+# before it expires. It should never be more than 24 hours, because clients
+# reject consensuses that are older than REASONABLY_LIVE_TIME.
+# For the consensus expiry check to be accurate, the machine running this
+# script needs an accurate clock.
+# We use 24 hours to compensate for #20909, where relays on 0.2.9.5-alpha and
+# 0.3.0.0-alpha-dev and later deliver stale consensuses, but typically recover
+# after ~12 hours.
+# We should make this lower when #20909 is fixed, see #20942.
+CONSENSUS_EXPIRY_TOLERANCE = 24*60*60
+
+# Output fallback name, flags, bandwidth, and ContactInfo in a C comment?
OUTPUT_COMMENTS = True if OUTPUT_CANDIDATES else False
# Output matching ContactInfo in fallbacks list or the blacklist?
@@ -88,6 +107,12 @@ OUTPUT_COMMENTS = True if OUTPUT_CANDIDATES else False
CONTACT_COUNT = True if OUTPUT_CANDIDATES else False
CONTACT_BLACKLIST_COUNT = True if OUTPUT_CANDIDATES else False
+# How the list should be sorted:
+# fingerprint: is useful for stable diffs of fallback lists
+# measured_bandwidth: is useful when pruning the list based on bandwidth
+# contact: is useful for contacting operators once the list has been pruned
+OUTPUT_SORT_FIELD = 'contact' if OUTPUT_CANDIDATES else 'fingerprint'
+
## OnionOO Settings
ONIONOO = 'https://onionoo.torproject.org/'
@@ -121,22 +146,28 @@ BLACKLIST_EXCLUDES_WHITELIST_ENTRIES = True
WHITELIST_FILE_NAME = 'scripts/maint/fallback.whitelist'
BLACKLIST_FILE_NAME = 'scripts/maint/fallback.blacklist'
+FALLBACK_FILE_NAME = 'src/or/fallback_dirs.inc'
# The number of bytes we'll read from a filter file before giving up
MAX_LIST_FILE_SIZE = 1024 * 1024
## Eligibility Settings
-# Reduced due to a bug in tor where a relay submits a 0 DirPort when restarted
-# This causes OnionOO to (correctly) reset its stability timer
-# This issue will be fixed in 0.2.7.7 and 0.2.8.2
-# Until then, the CUTOFFs below ensure a decent level of stability.
+# Require fallbacks to have the same address and port for a set amount of time
+#
+# There was a bug in Tor 0.2.8.1-alpha and earlier where a relay temporarily
+# submits a 0 DirPort when restarted.
+# This causes OnionOO to (correctly) reset its stability timer.
+# Affected relays should upgrade to Tor 0.2.8.7 or later, which has a fix
+# for this issue.
ADDRESS_AND_PORT_STABLE_DAYS = 7
+# We ignore relays that have been down for more than this period
+MAX_DOWNTIME_DAYS = 0 if MUST_BE_RUNNING_NOW else 7
# What time-weighted-fraction of these flags must FallbackDirs
# Equal or Exceed?
-CUTOFF_RUNNING = .95
-CUTOFF_V2DIR = .95
-CUTOFF_GUARD = .95
+CUTOFF_RUNNING = .90
+CUTOFF_V2DIR = .90
+CUTOFF_GUARD = .90
# What time-weighted-fraction of these flags must FallbackDirs
# Equal or Fall Under?
# .00 means no bad exits
@@ -155,12 +186,19 @@ ONIONOO_SCALE_ONE = 999.
_FB_POG = 0.2
FALLBACK_PROPORTION_OF_GUARDS = None if OUTPUT_CANDIDATES else _FB_POG
-# We want exactly 100 fallbacks for the initial release
-# This gives us scope to add extra fallbacks to the list as needed
# Limit the number of fallbacks (eliminating lowest by advertised bandwidth)
-MAX_FALLBACK_COUNT = None if OUTPUT_CANDIDATES else 100
-# Emit a C #error if the number of fallbacks is below
-MIN_FALLBACK_COUNT = 100
+MAX_FALLBACK_COUNT = None if OUTPUT_CANDIDATES else 200
+# Emit a C #error if the number of fallbacks is less than expected
+MIN_FALLBACK_COUNT = 0 if OUTPUT_CANDIDATES else MAX_FALLBACK_COUNT*0.75
+
+# The maximum number of fallbacks on the same address, contact, or family
+# With 200 fallbacks, this means each operator can see 1% of client bootstraps
+# (The directory authorities used to see ~12% of client bootstraps each.)
+MAX_FALLBACKS_PER_IP = 1
+MAX_FALLBACKS_PER_IPV4 = MAX_FALLBACKS_PER_IP
+MAX_FALLBACKS_PER_IPV6 = MAX_FALLBACKS_PER_IP
+MAX_FALLBACKS_PER_CONTACT = 3
+MAX_FALLBACKS_PER_FAMILY = 3
## Fallback Bandwidth Requirements
@@ -171,12 +209,12 @@ MIN_FALLBACK_COUNT = 100
EXIT_BANDWIDTH_FRACTION = 1.0
# If a single fallback's bandwidth is too low, it's pointless adding it
-# We expect fallbacks to handle an extra 30 kilobytes per second of traffic
+# We expect fallbacks to handle an extra 10 kilobytes per second of traffic
# Make sure they can support a hundred times the expected extra load
-# (Use 102.4 to make it come out nicely in MB/s)
+# (Use 102.4 to make it come out nicely in MByte/s)
# We convert this to a consensus weight before applying the filter,
# because all the bandwidth amounts are specified by the relay
-MIN_BANDWIDTH = 102.4 * 30.0 * 1024.0
+MIN_BANDWIDTH = 102.4 * 10.0 * 1024.0
# Clients will time out after 30 seconds trying to download a consensus
# So allow fallback directories half that to deliver a consensus
@@ -329,6 +367,15 @@ def read_from_file(file_name, max_len):
)
return None
+def parse_fallback_file(file_name):
+ file_data = read_from_file(file_name, MAX_LIST_FILE_SIZE)
+ file_data = cleanse_unprintable(file_data)
+ file_data = remove_bad_chars(file_data, '\n"\0')
+ file_data = re.sub('/\*.*?\*/', '', file_data)
+ file_data = file_data.replace(',', '\n')
+ file_data = file_data.replace(' weight=10', '')
+ return file_data
+
def load_possibly_compressed_response_json(response):
if response.info().get('Content-Encoding') == 'gzip':
buf = StringIO.StringIO( response.read() )
@@ -367,8 +414,8 @@ def onionoo_fetch(what, **kwargs):
params = kwargs
params['type'] = 'relay'
#params['limit'] = 10
- params['first_seen_days'] = '%d-'%(ADDRESS_AND_PORT_STABLE_DAYS,)
- params['last_seen_days'] = '-7'
+ params['first_seen_days'] = '%d-'%(ADDRESS_AND_PORT_STABLE_DAYS)
+ params['last_seen_days'] = '-%d'%(MAX_DOWNTIME_DAYS)
params['flag'] = 'V2Dir'
url = ONIONOO + what + '?' + urllib.urlencode(params)
@@ -497,6 +544,8 @@ class Candidate(object):
if (not 'effective_family' in details
or details['effective_family'] is None):
details['effective_family'] = []
+ if not 'platform' in details:
+ details['platform'] = None
details['last_changed_address_or_port'] = parse_ts(
details['last_changed_address_or_port'])
self._data = details
@@ -511,6 +560,7 @@ class Candidate(object):
self._compute_ipv6addr()
if not self.has_ipv6():
logging.debug("Failed to get an ipv6 address for %s."%(self._fpr,))
+ self._compute_version()
def _stable_sort_or_addresses(self):
# replace self._data['or_addresses'] with a stable ordering,
@@ -623,6 +673,59 @@ class Candidate(object):
self.ipv6orport = int(port)
return
+ def _compute_version(self):
+ # parse the version out of the platform string
+ # The platform looks like: "Tor 0.2.7.6 on Linux"
+ self._data['version'] = None
+ if self._data['platform'] is None:
+ return
+ # be tolerant of weird whitespacing, use a whitespace split
+ tokens = self._data['platform'].split()
+ for token in tokens:
+ vnums = token.split('.')
+ # if it's at least a.b.c.d, with potentially an -alpha-dev, -alpha, -rc
+ if (len(vnums) >= 4 and vnums[0].isdigit() and vnums[1].isdigit() and
+ vnums[2].isdigit()):
+ self._data['version'] = token
+ return
+
+ # From #20509
+ # bug #20499 affects versions from 0.2.9.1-alpha-dev to 0.2.9.4-alpha-dev
+ # and version 0.3.0.0-alpha-dev
+ # Exhaustive lists are hard to get wrong
+ STALE_CONSENSUS_VERSIONS = ['0.2.9.1-alpha-dev',
+ '0.2.9.2-alpha',
+ '0.2.9.2-alpha-dev',
+ '0.2.9.3-alpha',
+ '0.2.9.3-alpha-dev',
+ '0.2.9.4-alpha',
+ '0.2.9.4-alpha-dev',
+ '0.3.0.0-alpha-dev'
+ ]
+
+ def is_valid_version(self):
+ # call _compute_version before calling this
+ # is the version of the relay a version we want as a fallback?
+ # checks both recommended versions and bug #20499 / #20509
+ #
+ # if the relay doesn't have a recommended version field, exclude the relay
+ if not self._data.has_key('recommended_version'):
+ log_excluded('%s not a candidate: no recommended_version field',
+ self._fpr)
+ return False
+ if not self._data['recommended_version']:
+ log_excluded('%s not a candidate: version not recommended', self._fpr)
+ return False
+ # if the relay doesn't have version field, exclude the relay
+ if not self._data.has_key('version'):
+ log_excluded('%s not a candidate: no version field', self._fpr)
+ return False
+ if self._data['version'] in Candidate.STALE_CONSENSUS_VERSIONS:
+ logging.warning('%s not a candidate: version delivers stale consensuses',
+ self._fpr)
+ return False
+ return True
+
@staticmethod
def _extract_generic_history(history, which='unknown'):
# given a tree like this:
@@ -767,41 +870,42 @@ class Candidate(object):
self._badexit = self._avg_generic_history(badexit) / ONIONOO_SCALE_ONE
def is_candidate(self):
- must_be_running_now = (PERFORM_IPV4_DIRPORT_CHECKS
- or PERFORM_IPV6_DIRPORT_CHECKS)
- if (must_be_running_now and not self.is_running()):
- logging.info('%s not a candidate: not running now, unable to check ' +
- 'DirPort consensus download', self._fpr)
- return False
- if (self._data['last_changed_address_or_port'] >
- self.CUTOFF_ADDRESS_AND_PORT_STABLE):
- logging.info('%s not a candidate: changed address/port recently (%s)',
- self._fpr, self._data['last_changed_address_or_port'])
- return False
- if self._running < CUTOFF_RUNNING:
- logging.info('%s not a candidate: running avg too low (%lf)',
- self._fpr, self._running)
- return False
- if self._v2dir < CUTOFF_V2DIR:
- logging.info('%s not a candidate: v2dir avg too low (%lf)',
- self._fpr, self._v2dir)
- return False
- if self._badexit is not None and self._badexit > PERMITTED_BADEXIT:
- logging.info('%s not a candidate: badexit avg too high (%lf)',
- self._fpr, self._badexit)
- return False
- # if the relay doesn't report a version, also exclude the relay
- if (not self._data.has_key('recommended_version')
- or not self._data['recommended_version']):
- logging.info('%s not a candidate: version not recommended', self._fpr)
- return False
- if self._guard < CUTOFF_GUARD:
- logging.info('%s not a candidate: guard avg too low (%lf)',
- self._fpr, self._guard)
- return False
- if (not self._data.has_key('consensus_weight')
- or self._data['consensus_weight'] < 1):
- logging.info('%s not a candidate: consensus weight invalid', self._fpr)
+ try:
+ if (MUST_BE_RUNNING_NOW and not self.is_running()):
+ log_excluded('%s not a candidate: not running now, unable to check ' +
+ 'DirPort consensus download', self._fpr)
+ return False
+ if (self._data['last_changed_address_or_port'] >
+ self.CUTOFF_ADDRESS_AND_PORT_STABLE):
+ log_excluded('%s not a candidate: changed address/port recently (%s)',
+ self._fpr, self._data['last_changed_address_or_port'])
+ return False
+ if self._running < CUTOFF_RUNNING:
+ log_excluded('%s not a candidate: running avg too low (%lf)',
+ self._fpr, self._running)
+ return False
+ if self._v2dir < CUTOFF_V2DIR:
+ log_excluded('%s not a candidate: v2dir avg too low (%lf)',
+ self._fpr, self._v2dir)
+ return False
+ if self._badexit is not None and self._badexit > PERMITTED_BADEXIT:
+ log_excluded('%s not a candidate: badexit avg too high (%lf)',
+ self._fpr, self._badexit)
+ return False
+ # this function logs a message depending on which check fails
+ if not self.is_valid_version():
+ return False
+ if self._guard < CUTOFF_GUARD:
+ log_excluded('%s not a candidate: guard avg too low (%lf)',
+ self._fpr, self._guard)
+ return False
+ if (not self._data.has_key('consensus_weight')
+ or self._data['consensus_weight'] < 1):
+ log_excluded('%s not a candidate: consensus weight invalid', self._fpr)
+ return False
+ except BaseException as e:
+ logging.warning("Exception %s when checking if fallback is a candidate",
+ str(e))
return False
return True
@@ -878,26 +982,26 @@ class Candidate(object):
for key in entry:
value = entry[key]
if key == 'id' and value == self._fpr:
- logging.info('%s is in the blacklist: fingerprint matches',
+ log_excluded('%s is in the blacklist: fingerprint matches',
self._fpr)
return True
if key == 'ipv4' and value == self.dirip:
# if the dirport is present, check it too
if entry.has_key('dirport'):
if int(entry['dirport']) == self.dirport:
- logging.info('%s is in the blacklist: IPv4 (%s) and ' +
+ log_excluded('%s is in the blacklist: IPv4 (%s) and ' +
'DirPort (%d) match', self._fpr, self.dirip,
self.dirport)
return True
# if the orport is present, check it too
elif entry.has_key('orport'):
if int(entry['orport']) == self.orport:
- logging.info('%s is in the blacklist: IPv4 (%s) and ' +
+ log_excluded('%s is in the blacklist: IPv4 (%s) and ' +
'ORPort (%d) match', self._fpr, self.dirip,
self.orport)
return True
else:
- logging.info('%s is in the blacklist: IPv4 (%s) matches, and ' +
+ log_excluded('%s is in the blacklist: IPv4 (%s) matches, and ' +
'entry has no DirPort or ORPort', self._fpr,
self.dirip)
return True
@@ -911,19 +1015,19 @@ class Candidate(object):
# if the dirport is present, check it too
if entry.has_key('dirport'):
if int(entry['dirport']) == self.dirport:
- logging.info('%s is in the blacklist: IPv6 (%s) and ' +
+ log_excluded('%s is in the blacklist: IPv6 (%s) and ' +
'DirPort (%d) match', self._fpr, ipv6,
self.dirport)
return True
# we've already checked the ORPort, it's part of entry['ipv6']
else:
- logging.info('%s is in the blacklist: IPv6 (%s) matches, and' +
+ log_excluded('%s is in the blacklist: IPv6 (%s) matches, and' +
'entry has no DirPort', self._fpr, ipv6)
return True
elif (key == 'ipv6' or self.has_ipv6()):
# only log if the fingerprint matches but the IPv6 doesn't
if entry.has_key('id') and entry['id'] == self._fpr:
- logging.info('%s skipping IPv6 blacklist comparison: relay ' +
+ log_excluded('%s skipping IPv6 blacklist comparison: relay ' +
'has%s IPv6%s, but entry has%s IPv6%s', self._fpr,
'' if self.has_ipv6() else ' no',
(' (' + ipv6 + ')') if self.has_ipv6() else '',
@@ -1062,42 +1166,63 @@ class Candidate(object):
return True
return False
- # report how long it takes to download a consensus from dirip:dirport
+ # log how long it takes to download a consensus from dirip:dirport
+ # returns True if the download failed, False if it succeeded within max_time
@staticmethod
- def fallback_consensus_download_speed(dirip, dirport, nickname, max_time):
+ def fallback_consensus_download_speed(dirip, dirport, nickname, fingerprint,
+ max_time):
download_failed = False
- downloader = DescriptorDownloader()
- start = datetime.datetime.utcnow()
# some directory mirrors respond to requests in ways that hang python
# sockets, which is why we log this line here
- logging.info('Initiating consensus download from %s (%s:%d).', nickname,
- dirip, dirport)
+ logging.info('Initiating %sconsensus download from %s (%s:%d) %s.',
+ 'microdesc ' if DOWNLOAD_MICRODESC_CONSENSUS else '',
+ nickname, dirip, dirport, fingerprint)
# there appears to be about 1 second of overhead when comparing stem's
# internal trace time and the elapsed time calculated here
TIMEOUT_SLOP = 1.0
+ start = datetime.datetime.utcnow()
try:
- downloader.get_consensus(endpoints = [(dirip, dirport)],
- timeout = (max_time + TIMEOUT_SLOP),
- validate = True,
- retries = 0,
- fall_back_to_authority = False).run()
+ consensus = get_consensus(
+ endpoints = [(dirip, dirport)],
+ timeout = (max_time + TIMEOUT_SLOP),
+ validate = True,
+ retries = 0,
+ fall_back_to_authority = False,
+ document_handler = DocumentHandler.BARE_DOCUMENT,
+ microdescriptor = DOWNLOAD_MICRODESC_CONSENSUS
+ ).run()[0]
+ end = datetime.datetime.utcnow()
+ time_since_expiry = (end - consensus.valid_until).total_seconds()
except Exception, stem_error:
- logging.info('Unable to retrieve a consensus from %s: %s', nickname,
+ end = datetime.datetime.utcnow()
+ log_excluded('Unable to retrieve a consensus from %s: %s', nickname,
stem_error)
status = 'error: "%s"' % (stem_error)
level = logging.WARNING
download_failed = True
- elapsed = (datetime.datetime.utcnow() - start).total_seconds()
- if elapsed > max_time:
+ elapsed = (end - start).total_seconds()
+ if download_failed:
+ # keep the error failure status, and avoid using the variables
+ pass
+ elif elapsed > max_time:
status = 'too slow'
level = logging.WARNING
download_failed = True
+ elif (time_since_expiry > 0):
+ status = 'outdated consensus, expired %ds ago'%(int(time_since_expiry))
+ if time_since_expiry <= CONSENSUS_EXPIRY_TOLERANCE:
+ status += ', tolerating up to %ds'%(CONSENSUS_EXPIRY_TOLERANCE)
+ level = logging.INFO
+ else:
+ status += ', invalid'
+ level = logging.WARNING
+ download_failed = True
else:
status = 'ok'
level = logging.DEBUG
- logging.log(level, 'Consensus download: %0.1fs %s from %s (%s:%d), ' +
+ logging.log(level, 'Consensus download: %0.1fs %s from %s (%s:%d) %s, ' +
'max download time %0.1fs.', elapsed, status, nickname,
- dirip, dirport, max_time)
+ dirip, dirport, fingerprint, max_time)
return download_failed
# does this fallback download the consensus fast enough?
@@ -1109,12 +1234,14 @@ class Candidate(object):
ipv4_failed = Candidate.fallback_consensus_download_speed(self.dirip,
self.dirport,
self._data['nickname'],
+ self._fpr,
CONSENSUS_DOWNLOAD_SPEED_MAX)
if self.has_ipv6() and PERFORM_IPV6_DIRPORT_CHECKS:
# Clients assume the IPv6 DirPort is the same as the IPv4 DirPort
ipv6_failed = Candidate.fallback_consensus_download_speed(self.ipv6addr,
self.dirport,
self._data['nickname'],
+ self._fpr,
CONSENSUS_DOWNLOAD_SPEED_MAX)
return ((not ipv4_failed) and (not ipv6_failed))
@@ -1151,6 +1278,7 @@ class Candidate(object):
# /*
# nickname
# flags
+ # adjusted bandwidth, consensus weight
# [contact]
# [identical contact counts]
# */
@@ -1162,6 +1290,13 @@ class Candidate(object):
s += 'Flags: '
s += cleanse_c_multiline_comment(' '.join(sorted(self._data['flags'])))
s += '\n'
+ # this is an adjusted bandwidth, see calculate_measured_bandwidth()
+ bandwidth = self._data['measured_bandwidth']
+ weight = self._data['consensus_weight']
+ s += 'Bandwidth: %.1f MByte/s, Consensus Weight: %d'%(
+ bandwidth/(1024.0*1024.0),
+ weight)
+ s += '\n'
if self._data['contact'] is not None:
s += cleanse_c_multiline_comment(self._data['contact'])
if CONTACT_COUNT or CONTACT_BLACKLIST_COUNT:
@@ -1183,6 +1318,7 @@ class Candidate(object):
s += '\n'
s += '*/'
s += '\n'
+ return s
# output the fallback info C string for this fallback
# this is the text that would go after FallbackDir in a torrc
@@ -1251,7 +1387,8 @@ class CandidateList(dict):
d = fetch('details',
fields=('fingerprint,nickname,contact,last_changed_address_or_port,' +
'consensus_weight,advertised_bandwidth,or_addresses,' +
- 'dir_address,recommended_version,flags,effective_family'))
+ 'dir_address,recommended_version,flags,effective_family,' +
+ 'platform'))
logging.debug('Loading details document done.')
if not 'relays' in d: raise Exception("No relays found in document.")
@@ -1297,13 +1434,12 @@ class CandidateList(dict):
self.fallbacks.sort(key=lambda f: f._data['measured_bandwidth'],
reverse=True)
- # sort fallbacks by their fingerprint, lowest to highest
- # this is useful for stable diffs of fallback lists
- def sort_fallbacks_by_fingerprint(self):
- self.fallbacks.sort(key=lambda f: f._fpr)
+ # sort fallbacks by the data field data_field, lowest to highest
+ def sort_fallbacks_by(self, data_field):
+ self.fallbacks.sort(key=lambda f: f._data[data_field])
@staticmethod
- def load_relaylist(file_name):
+ def load_relaylist(file_obj):
""" Read each line in the file, and parse it like a FallbackDir line:
an IPv4 address and optional port:
<IPv4 address>:<port>
@@ -1318,8 +1454,9 @@ class CandidateList(dict):
(of string -> string key/value pairs),
and these dictionaries are placed in an array.
comments start with # and are ignored """
+ file_data = file_obj['data']
+ file_name = file_obj['name']
relaylist = []
- file_data = read_from_file(file_name, MAX_LIST_FILE_SIZE)
if file_data is None:
return relaylist
for line in file_data.split('\n'):
@@ -1360,12 +1497,12 @@ class CandidateList(dict):
return relaylist
# apply the fallback whitelist and blacklist
- def apply_filter_lists(self):
+ def apply_filter_lists(self, whitelist_obj, blacklist_obj):
excluded_count = 0
logging.debug('Applying whitelist and blacklist.')
# parse the whitelist and blacklist
- whitelist = self.load_relaylist(WHITELIST_FILE_NAME)
- blacklist = self.load_relaylist(BLACKLIST_FILE_NAME)
+ whitelist = self.load_relaylist(whitelist_obj)
+ blacklist = self.load_relaylist(blacklist_obj)
filtered_fallbacks = []
for f in self.fallbacks:
in_whitelist = f.is_in_whitelist(whitelist)
@@ -1385,7 +1522,7 @@ class CandidateList(dict):
elif in_blacklist:
# exclude
excluded_count += 1
- logging.info('Excluding %s: in blacklist.', f._fpr)
+ log_excluded('Excluding %s: in blacklist.', f._fpr)
else:
if INCLUDE_UNLISTED_ENTRIES:
# include
@@ -1393,7 +1530,7 @@ class CandidateList(dict):
else:
# exclude
excluded_count += 1
- logging.info('Excluding %s: in neither blacklist nor whitelist.',
+ log_excluded('Excluding %s: in neither blacklist nor whitelist.',
f._fpr)
self.fallbacks = filtered_fallbacks
return excluded_count
@@ -1429,8 +1566,8 @@ class CandidateList(dict):
# the bandwidth we log here is limited by the relay's consensus weight
# as well as its adverttised bandwidth. See set_measured_bandwidth
# for details
- logging.info('%s not a candidate: bandwidth %.1fMB/s too low, must ' +
- 'be at least %.1fMB/s', f._fpr,
+ log_excluded('%s not a candidate: bandwidth %.1fMByte/s too low, ' +
+ 'must be at least %.1fMByte/s', f._fpr,
f._data['measured_bandwidth']/(1024.0*1024.0),
MIN_BANDWIDTH/(1024.0*1024.0))
self.fallbacks = above_min_bw_fallbacks
@@ -1470,49 +1607,85 @@ class CandidateList(dict):
else:
return None
- # does exclusion_list contain attribute?
+ # return a new bag suitable for storing attributes
+ @staticmethod
+ def attribute_new():
+ return dict()
+
+ # get the count of attribute in attribute_bag
+ # if attribute is None or the empty string, return 0
+ @staticmethod
+ def attribute_count(attribute, attribute_bag):
+ if attribute is None or attribute == '':
+ return 0
+ if attribute not in attribute_bag:
+ return 0
+ return attribute_bag[attribute]
+
+ # does attribute_bag contain more than max_count instances of attribute?
# if so, return False
# if not, return True
- # if attribute is None or the empty string, always return True
+ # if attribute is None or the empty string, or max_count is invalid,
+ # always return True
@staticmethod
- def allow(attribute, exclusion_list):
- if attribute is None or attribute == '':
+ def attribute_allow(attribute, attribute_bag, max_count=1):
+ if attribute is None or attribute == '' or max_count <= 0:
return True
- elif attribute in exclusion_list:
+ elif CandidateList.attribute_count(attribute, attribute_bag) >= max_count:
return False
else:
return True
- # make sure there is only one fallback per IPv4 address, and per IPv6 address
+ # add attribute to attribute_bag, incrementing the count if it is already
+ # present
+ # if attribute is None or the empty string, or count is invalid,
+ # do nothing
+ @staticmethod
+ def attribute_add(attribute, attribute_bag, count=1):
+ if attribute is None or attribute == '' or count <= 0:
+ pass
+ attribute_bag.setdefault(attribute, 0)
+ attribute_bag[attribute] += count
+
+ # make sure there are only MAX_FALLBACKS_PER_IP fallbacks per IPv4 address,
+ # and per IPv6 address
# there is only one IPv4 address on each fallback: the IPv4 DirPort address
# (we choose the IPv4 ORPort which is on the same IPv4 as the DirPort)
# there is at most one IPv6 address on each fallback: the IPv6 ORPort address
# we try to match the IPv4 ORPort, but will use any IPv6 address if needed
- # (clients assume the IPv6 DirPort is the same as the IPv4 DirPort, but
- # typically only use the IPv6 ORPort)
+ # (clients only use the IPv6 ORPort)
# if there is no IPv6 address, only the IPv4 address is checked
# return the number of candidates we excluded
def limit_fallbacks_same_ip(self):
ip_limit_fallbacks = []
- ip_list = []
+ ip_list = CandidateList.attribute_new()
for f in self.fallbacks:
- if (CandidateList.allow(f.dirip, ip_list)
- and CandidateList.allow(f.ipv6addr, ip_list)):
+ if (CandidateList.attribute_allow(f.dirip, ip_list,
+ MAX_FALLBACKS_PER_IPV4)
+ and CandidateList.attribute_allow(f.ipv6addr, ip_list,
+ MAX_FALLBACKS_PER_IPV6)):
ip_limit_fallbacks.append(f)
- ip_list.append(f.dirip)
+ CandidateList.attribute_add(f.dirip, ip_list)
if f.has_ipv6():
- ip_list.append(f.ipv6addr)
- elif not CandidateList.allow(f.dirip, ip_list):
- logging.info('Eliminated %s: already have fallback on IPv4 %s'%(
- f._fpr, f.dirip))
- elif f.has_ipv6() and not CandidateList.allow(f.ipv6addr, ip_list):
- logging.info('Eliminated %s: already have fallback on IPv6 %s'%(
- f._fpr, f.ipv6addr))
+ CandidateList.attribute_add(f.ipv6addr, ip_list)
+ elif not CandidateList.attribute_allow(f.dirip, ip_list,
+ MAX_FALLBACKS_PER_IPV4):
+ log_excluded('Eliminated %s: already have %d fallback(s) on IPv4 %s'
+ %(f._fpr, CandidateList.attribute_count(f.dirip, ip_list),
+ f.dirip))
+ elif (f.has_ipv6() and
+ not CandidateList.attribute_allow(f.ipv6addr, ip_list,
+ MAX_FALLBACKS_PER_IPV6)):
+ log_excluded('Eliminated %s: already have %d fallback(s) on IPv6 %s'
+ %(f._fpr, CandidateList.attribute_count(f.ipv6addr,
+ ip_list),
+ f.ipv6addr))
original_count = len(self.fallbacks)
self.fallbacks = ip_limit_fallbacks
return original_count - len(self.fallbacks)
- # make sure there is only one fallback per ContactInfo
+ # make sure there are only MAX_FALLBACKS_PER_CONTACT fallbacks for each
+ # ContactInfo
# if there is no ContactInfo, allow the fallback
# this check can be gamed by providing no ContactInfo, or by setting the
# ContactInfo to match another fallback
@@ -1520,37 +1693,45 @@ class CandidateList(dict):
# go down at similar times, its usefulness outweighs the risk
def limit_fallbacks_same_contact(self):
contact_limit_fallbacks = []
- contact_list = []
+ contact_list = CandidateList.attribute_new()
for f in self.fallbacks:
- if CandidateList.allow(f._data['contact'], contact_list):
+ if CandidateList.attribute_allow(f._data['contact'], contact_list,
+ MAX_FALLBACKS_PER_CONTACT):
contact_limit_fallbacks.append(f)
- contact_list.append(f._data['contact'])
+ CandidateList.attribute_add(f._data['contact'], contact_list)
else:
- logging.info(('Eliminated %s: already have fallback on ' +
- 'ContactInfo %s')%(f._fpr, f._data['contact']))
+ log_excluded(
+ 'Eliminated %s: already have %d fallback(s) on ContactInfo %s'
+ %(f._fpr, CandidateList.attribute_count(f._data['contact'],
+ contact_list),
+ f._data['contact']))
original_count = len(self.fallbacks)
self.fallbacks = contact_limit_fallbacks
return original_count - len(self.fallbacks)
- # make sure there is only one fallback per effective family
+ # make sure there are only MAX_FALLBACKS_PER_FAMILY fallbacks per effective
+ # family
# if there is no family, allow the fallback
- # this check can't be gamed, because we use effective family, which ensures
- # mutual family declarations
+ # we use effective family, which ensures mutual family declarations
+ # but the check can be gamed by not declaring a family at all
# if any indirect families exist, the result depends on the order in which
# fallbacks are sorted in the list
def limit_fallbacks_same_family(self):
family_limit_fallbacks = []
- fingerprint_list = []
+ fingerprint_list = CandidateList.attribute_new()
for f in self.fallbacks:
- if CandidateList.allow(f._fpr, fingerprint_list):
+ if CandidateList.attribute_allow(f._fpr, fingerprint_list,
+ MAX_FALLBACKS_PER_FAMILY):
family_limit_fallbacks.append(f)
- fingerprint_list.append(f._fpr)
- fingerprint_list.extend(f._data['effective_family'])
+ CandidateList.attribute_add(f._fpr, fingerprint_list)
+ for family_fingerprint in f._data['effective_family']:
+ CandidateList.attribute_add(family_fingerprint, fingerprint_list)
else:
- # technically, we already have a fallback with this fallback in its
- # effective family
- logging.info('Eliminated %s: already have fallback in effective ' +
- 'family'%(f._fpr))
+ # we already have a fallback with this fallback in its effective
+ # family
+ log_excluded(
+ 'Eliminated %s: already have %d fallback(s) in effective family'
+ %(f._fpr, CandidateList.attribute_count(f._fpr, fingerprint_list)))
original_count = len(self.fallbacks)
self.fallbacks = family_limit_fallbacks
return original_count - len(self.fallbacks)
@@ -1878,8 +2059,8 @@ class CandidateList(dict):
min_bw = min_fb._data['measured_bandwidth']
max_fb = self.fallback_max()
max_bw = max_fb._data['measured_bandwidth']
- s += 'Bandwidth Range: %.1f - %.1f MB/s'%(min_bw/(1024.0*1024.0),
- max_bw/(1024.0*1024.0))
+ s += 'Bandwidth Range: %.1f - %.1f MByte/s'%(min_bw/(1024.0*1024.0),
+ max_bw/(1024.0*1024.0))
s += '\n'
s += '*/'
if fallback_count < MIN_FALLBACK_COUNT:
@@ -1892,9 +2073,44 @@ class CandidateList(dict):
s += 'or setting INCLUDE_UNLISTED_ENTRIES = True.'
return s
+def process_existing():
+ logging.basicConfig(level=logging.INFO)
+ logging.getLogger('stem').setLevel(logging.INFO)
+ whitelist = {'data': parse_fallback_file(FALLBACK_FILE_NAME),
+ 'name': FALLBACK_FILE_NAME}
+ blacklist = {'data': read_from_file(BLACKLIST_FILE_NAME, MAX_LIST_FILE_SIZE),
+ 'name': BLACKLIST_FILE_NAME}
+ list_fallbacks(whitelist, blacklist)
+
+def process_default():
+ logging.basicConfig(level=logging.WARNING)
+ logging.getLogger('stem').setLevel(logging.WARNING)
+ whitelist = {'data': read_from_file(WHITELIST_FILE_NAME, MAX_LIST_FILE_SIZE),
+ 'name': WHITELIST_FILE_NAME}
+ blacklist = {'data': read_from_file(BLACKLIST_FILE_NAME, MAX_LIST_FILE_SIZE),
+ 'name': BLACKLIST_FILE_NAME}
+ list_fallbacks(whitelist, blacklist)
+
## Main Function
+def main():
+ if get_command() == 'check_existing':
+ process_existing()
+ else:
+ process_default()
+
+def get_command():
+ if len(sys.argv) == 2:
+ return sys.argv[1]
+ else:
+ return None
-def list_fallbacks():
+def log_excluded(msg, *args):
+ if get_command() == 'check_existing':
+ logging.warning(msg, *args)
+ else:
+ logging.info(msg, *args)
+
+def list_fallbacks(whitelist, blacklist):
""" Fetches required onionoo documents and evaluates the
fallback directory criteria for each of the relays """
@@ -1927,7 +2143,7 @@ def list_fallbacks():
# warning that the details have changed from those in the whitelist.
# instead, there will be an info-level log during the eligibility check.
initial_count = len(candidates.fallbacks)
- excluded_count = candidates.apply_filter_lists()
+ excluded_count = candidates.apply_filter_lists(whitelist, blacklist)
print candidates.summarise_filters(initial_count, excluded_count)
eligible_count = len(candidates.fallbacks)
@@ -1985,15 +2201,17 @@ def list_fallbacks():
for s in fetch_source_list():
print describe_fetch_source(s)
+ # sort the list differently depending on why we've created it:
# if we're outputting the final fallback list, sort by fingerprint
# this makes diffs much more stable
- # otherwise, leave sorted by bandwidth, which allows operators to be
- # contacted in priority order
- if not OUTPUT_CANDIDATES:
- candidates.sort_fallbacks_by_fingerprint()
+ # otherwise, if we're trying to find a bandwidth cutoff, or we want to
+ # contact operators in priority order, sort by bandwidth (not yet
+ # implemented)
+ # otherwise, if we're contacting operators, sort by contact
+ candidates.sort_fallbacks_by(OUTPUT_SORT_FIELD)
for x in candidates.fallbacks:
print x.fallbackdir_line(candidates.fallbacks, prefilter_fallbacks)
if __name__ == "__main__":
- list_fallbacks()
+ main()
diff --git a/src/common/address.c b/src/common/address.c
index 773e688554..2693239146 100644
--- a/src/common/address.c
+++ b/src/common/address.c
@@ -159,6 +159,8 @@ tor_addr_from_sockaddr(tor_addr_t *a, const struct sockaddr *sa,
tor_assert(a);
tor_assert(sa);
+ /* This memset is redundant; leaving it in to avoid any future accidents,
+ however. */
memset(a, 0, sizeof(*a));
if (sa->sa_family == AF_INET) {
@@ -235,8 +237,8 @@ tor_addr_make_null(tor_addr_t *a, sa_family_t family)
*
* Return 0 on success, -1 on failure; 1 on transient failure.
*/
-int
-tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr)
+MOCK_IMPL(int,
+tor_addr_lookup,(const char *name, uint16_t family, tor_addr_t *addr))
{
/* Perhaps eventually this should be replaced by a tor_getaddrinfo or
* something.
@@ -2100,7 +2102,8 @@ get_interface_address,(int severity, uint32_t *addr))
}
/** Return true if we can tell that <b>name</b> is a canonical name for the
- * loopback address. */
+ * loopback address. Return true also for *.local hostnames, which are
+ * multicast DNS names for hosts on the local network. */
int
tor_addr_hostname_is_local(const char *name)
{
@@ -2121,3 +2124,11 @@ tor_addr_port_new(const tor_addr_t *addr, uint16_t port)
return ap;
}
+/** Return true iff <a>a</b> and <b>b</b> are the same address and port */
+int
+tor_addr_port_eq(const tor_addr_port_t *a,
+ const tor_addr_port_t *b)
+{
+ return tor_addr_eq(&a->addr, &b->addr) && a->port == b->port;
+}
+
diff --git a/src/common/address.h b/src/common/address.h
index 51db42c315..0dc6edae37 100644
--- a/src/common/address.h
+++ b/src/common/address.h
@@ -190,7 +190,8 @@ tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u)
*/
#define TOR_ADDR_BUF_LEN 48
-int tor_addr_lookup(const char *name, uint16_t family, tor_addr_t *addr_out);
+MOCK_DECL(int, tor_addr_lookup,(const char *name, uint16_t family,
+ tor_addr_t *addr_out));
char *tor_addr_to_str_dup(const tor_addr_t *addr) ATTR_MALLOC;
/** Wrapper function of fmt_addr_impl(). It does not decorate IPv6
@@ -342,6 +343,8 @@ get_interface_address_list(int severity, int include_internal)
}
tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port);
+int tor_addr_port_eq(const tor_addr_port_t *a,
+ const tor_addr_port_t *b);
#ifdef ADDRESS_PRIVATE
MOCK_DECL(smartlist_t *,get_interface_addresses_raw,(int severity,
diff --git a/src/common/backtrace.c b/src/common/backtrace.c
index 81e04e94eb..61096952d8 100644
--- a/src/common/backtrace.c
+++ b/src/common/backtrace.c
@@ -76,7 +76,7 @@ clean_backtrace(void **stack, size_t depth, const ucontext_t *ctx)
#ifdef PC_FROM_UCONTEXT
#if defined(__linux__)
const size_t n = 1;
-#elif defined(__darwin__) || defined(__APPLE__) || defined(__OpenBSD__) \
+#elif defined(__darwin__) || defined(__APPLE__) || defined(OpenBSD) \
|| defined(__FreeBSD__)
const size_t n = 2;
#else
diff --git a/src/common/ciphers.inc b/src/common/ciphers.inc
index ab4ac40724..a336e2dfa0 100644
--- a/src/common/ciphers.inc
+++ b/src/common/ciphers.inc
@@ -14,6 +14,26 @@
#else
XCIPHER(0xc02f, TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
#endif
+#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
+ CIPHER(0xcca9, TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
+#else
+ XCIPHER(0xcca9, TLS1_TXT_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
+#endif
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305
+ CIPHER(0xcca8, TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305)
+#else
+ XCIPHER(0xcca8, TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305)
+#endif
+#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+ CIPHER(0xc02c, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
+#else
+ XCIPHER(0xc02c, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
+#endif
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+ CIPHER(0xc030, TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
+#else
+ XCIPHER(0xc030, TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
+#endif
#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
CIPHER(0xc00a, TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
#else
@@ -34,88 +54,28 @@
#else
XCIPHER(0xc014, TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA)
#endif
-#ifdef TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA
- CIPHER(0xc012, TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA)
-#else
- XCIPHER(0xc012, TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA)
-#endif
-#ifdef TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA
- CIPHER(0xc007, TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA)
-#else
- XCIPHER(0xc007, TLS1_TXT_ECDHE_ECDSA_WITH_RC4_128_SHA)
-#endif
-#ifdef TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA
- CIPHER(0xc011, TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA)
-#else
- XCIPHER(0xc011, TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA)
-#endif
#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_SHA
CIPHER(0x0033, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA)
#else
XCIPHER(0x0033, TLS1_TXT_DHE_RSA_WITH_AES_128_SHA)
#endif
-#ifdef TLS1_TXT_DHE_DSS_WITH_AES_128_SHA
- CIPHER(0x0032, TLS1_TXT_DHE_DSS_WITH_AES_128_SHA)
-#else
- XCIPHER(0x0032, TLS1_TXT_DHE_DSS_WITH_AES_128_SHA)
-#endif
-#ifdef TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
- CIPHER(0x0045, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA)
-#else
- XCIPHER(0x0045, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA)
-#endif
#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA
CIPHER(0x0039, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA)
#else
XCIPHER(0x0039, TLS1_TXT_DHE_RSA_WITH_AES_256_SHA)
#endif
-#ifdef TLS1_TXT_DHE_DSS_WITH_AES_256_SHA
- CIPHER(0x0038, TLS1_TXT_DHE_DSS_WITH_AES_256_SHA)
-#else
- XCIPHER(0x0038, TLS1_TXT_DHE_DSS_WITH_AES_256_SHA)
-#endif
-#ifdef TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
- CIPHER(0x0088, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA)
-#else
- XCIPHER(0x0088, TLS1_TXT_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA)
-#endif
-#ifdef SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA
- CIPHER(0x0016, SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA)
-#else
- XCIPHER(0x0016, SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA)
-#endif
#ifdef TLS1_TXT_RSA_WITH_AES_128_SHA
CIPHER(0x002f, TLS1_TXT_RSA_WITH_AES_128_SHA)
#else
XCIPHER(0x002f, TLS1_TXT_RSA_WITH_AES_128_SHA)
#endif
-#ifdef TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA
- CIPHER(0x0041, TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA)
-#else
- XCIPHER(0x0041, TLS1_TXT_RSA_WITH_CAMELLIA_128_CBC_SHA)
-#endif
#ifdef TLS1_TXT_RSA_WITH_AES_256_SHA
CIPHER(0x0035, TLS1_TXT_RSA_WITH_AES_256_SHA)
#else
XCIPHER(0x0035, TLS1_TXT_RSA_WITH_AES_256_SHA)
#endif
-#ifdef TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA
- CIPHER(0x0084, TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA)
-#else
- XCIPHER(0x0084, TLS1_TXT_RSA_WITH_CAMELLIA_256_CBC_SHA)
-#endif
#ifdef SSL3_TXT_RSA_DES_192_CBC3_SHA
CIPHER(0x000a, SSL3_TXT_RSA_DES_192_CBC3_SHA)
#else
XCIPHER(0x000a, SSL3_TXT_RSA_DES_192_CBC3_SHA)
#endif
-#ifdef SSL3_TXT_RSA_RC4_128_SHA
- CIPHER(0x0005, SSL3_TXT_RSA_RC4_128_SHA)
-#else
- XCIPHER(0x0005, SSL3_TXT_RSA_RC4_128_SHA)
-#endif
-#ifdef SSL3_TXT_RSA_RC4_128_MD5
- CIPHER(0x0004, SSL3_TXT_RSA_RC4_128_MD5)
-#else
- XCIPHER(0x0004, SSL3_TXT_RSA_RC4_128_MD5)
-#endif
diff --git a/src/common/compat.c b/src/common/compat.c
index e16dfb1d2b..7ec7030ecc 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -204,7 +204,15 @@ tor_rename(const char *path_old, const char *path_new)
sandbox_intern_string(path_new));
}
-#if defined(HAVE_SYS_MMAN_H) || defined(RUNNING_DOXYGEN)
+/* Some MinGW builds have sys/mman.h, but not the corresponding symbols.
+ * Other configs rename the symbols using macros (including getpagesize).
+ * So check for sys/mman.h and unistd.h, and a getpagesize declaration. */
+#if (defined(HAVE_SYS_MMAN_H) && defined(HAVE_UNISTD_H) && \
+ defined(HAVE_DECL_GETPAGESIZE))
+#define COMPAT_HAS_MMAN_AND_PAGESIZE
+#endif
+
+#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". */
@@ -250,6 +258,12 @@ tor_mmap_file(const char *filename)
page_size = getpagesize();
size += (size%page_size) ? page_size-(size%page_size) : 0;
+ if (st.st_size > SSIZE_T_CEILING || (off_t)size < st.st_size) {
+ log_warn(LD_FS, "File \"%s\" is too large. Ignoring.",filename);
+ errno = EFBIG;
+ close(fd);
+ return NULL;
+ }
if (!size) {
/* Zero-length file. If we call mmap on it, it will succeed but
* return NULL, and bad things will happen. So just fail. */
diff --git a/src/common/container.c b/src/common/container.c
index ec59dccf62..1448ab403c 100644
--- a/src/common/container.c
+++ b/src/common/container.c
@@ -132,6 +132,24 @@ smartlist_remove(smartlist_t *sl, const void *element)
}
}
+/** As <b>smartlist_remove</b>, but do not change the order of
+ * any elements not removed */
+void
+smartlist_remove_keeporder(smartlist_t *sl, const void *element)
+{
+ int i, j, num_used_orig = sl->num_used;
+ if (element == NULL)
+ return;
+
+ for (i=j=0; j < num_used_orig; ++j) {
+ if (sl->list[j] == element) {
+ --sl->num_used;
+ } else {
+ sl->list[i++] = sl->list[j];
+ }
+ }
+}
+
/** If <b>sl</b> is nonempty, remove and return the final element. Otherwise,
* return NULL. */
void *
diff --git a/src/common/container.h b/src/common/container.h
index 71495b660a..00c3ca81ad 100644
--- a/src/common/container.h
+++ b/src/common/container.h
@@ -33,6 +33,7 @@ void smartlist_clear(smartlist_t *sl);
void smartlist_add(smartlist_t *sl, void *element);
void smartlist_add_all(smartlist_t *sl, const smartlist_t *s2);
void smartlist_remove(smartlist_t *sl, const void *element);
+void smartlist_remove_keeporder(smartlist_t *sl, const void *element);
void *smartlist_pop_last(smartlist_t *sl);
void smartlist_reverse(smartlist_t *sl);
void smartlist_string_remove(smartlist_t *sl, const char *element);
diff --git a/src/common/crypto.c b/src/common/crypto.c
index 39c8cc2b0a..d1357182d2 100644
--- a/src/common/crypto.c
+++ b/src/common/crypto.c
@@ -1117,10 +1117,10 @@ crypto_pk_private_decrypt(crypto_pk_t *env, char *to,
* <b>tolen</b> is the number of writable bytes in <b>to</b>, and must be
* at least the length of the modulus of <b>env</b>.
*/
-int
-crypto_pk_public_checksig(const crypto_pk_t *env, char *to,
- size_t tolen,
- const char *from, size_t fromlen)
+MOCK_IMPL(int,
+crypto_pk_public_checksig,(const crypto_pk_t *env, char *to,
+ size_t tolen,
+ const char *from, size_t fromlen))
{
int r;
tor_assert(env);
@@ -1144,9 +1144,10 @@ crypto_pk_public_checksig(const crypto_pk_t *env, char *to,
* in <b>env</b>. Return 0 if <b>sig</b> is a correct signature for
* SHA1(data). Else return -1.
*/
-int
-crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
- size_t datalen, const char *sig, size_t siglen)
+MOCK_IMPL(int,
+crypto_pk_public_checksig_digest,(crypto_pk_t *env, const char *data,
+ size_t datalen, const char *sig,
+ size_t siglen))
{
char digest[DIGEST_LEN];
char *buf;
@@ -1516,7 +1517,7 @@ crypto_pk_get_hashed_fingerprint(crypto_pk_t *pk, char *fp_out)
if (crypto_pk_get_digest(pk, digest)) {
return -1;
}
- if (crypto_digest(hashed_digest, digest, DIGEST_LEN)) {
+ if (crypto_digest(hashed_digest, digest, DIGEST_LEN) < 0) {
return -1;
}
base16_encode(fp_out, FINGERPRINT_LEN + 1, hashed_digest, DIGEST_LEN);
@@ -1710,19 +1711,21 @@ crypto_cipher_decrypt_with_iv(const char *key,
/** Compute the SHA1 digest of the <b>len</b> bytes on data stored in
* <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>.
- * Return 0 on success, 1 on failure.
+ * Return 0 on success, -1 on failure.
*/
int
crypto_digest(char *digest, const char *m, size_t len)
{
tor_assert(m);
tor_assert(digest);
- return (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
+ if (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL)
+ return -1;
+ return 0;
}
/** Compute a 256-bit digest of <b>len</b> bytes in data stored in <b>m</b>,
* using the algorithm <b>algorithm</b>. Write the DIGEST_LEN256-byte result
- * into <b>digest</b>. Return 0 on success, 1 on failure. */
+ * into <b>digest</b>. Return 0 on success, -1 on failure. */
int
crypto_digest256(char *digest, const char *m, size_t len,
digest_algorithm_t algorithm)
@@ -1730,16 +1733,22 @@ crypto_digest256(char *digest, const char *m, size_t len,
tor_assert(m);
tor_assert(digest);
tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256);
+
+ int ret = 0;
if (algorithm == DIGEST_SHA256)
- return (SHA256((const uint8_t*)m,len,(uint8_t*)digest) == NULL);
+ ret = (SHA256((const uint8_t*)m,len,(uint8_t*)digest) != NULL);
else
- return (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len)
- == -1);
+ ret = (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len)
+ > -1);
+
+ if (!ret)
+ return -1;
+ return 0;
}
/** Compute a 512-bit digest of <b>len</b> bytes in data stored in <b>m</b>,
* using the algorithm <b>algorithm</b>. Write the DIGEST_LEN512-byte result
- * into <b>digest</b>. Return 0 on success, 1 on failure. */
+ * into <b>digest</b>. Return 0 on success, -1 on failure. */
int
crypto_digest512(char *digest, const char *m, size_t len,
digest_algorithm_t algorithm)
@@ -1747,12 +1756,18 @@ crypto_digest512(char *digest, const char *m, size_t len,
tor_assert(m);
tor_assert(digest);
tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512);
+
+ int ret = 0;
if (algorithm == DIGEST_SHA512)
- return (SHA512((const unsigned char*)m,len,(unsigned char*)digest)
- == NULL);
+ ret = (SHA512((const unsigned char*)m,len,(unsigned char*)digest)
+ != NULL);
else
- return (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len)
- == -1);
+ ret = (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len)
+ > -1);
+
+ if (!ret)
+ return -1;
+ return 0;
}
/** Set the common_digests_t in <b>ds_out</b> to contain every digest on the
@@ -2119,6 +2134,35 @@ crypto_hmac_sha256(char *hmac_out,
tor_assert(rv);
}
+/** Compute a MAC using SHA3-256 of <b>msg_len</b> bytes in <b>msg</b> using a
+ * <b>key</b> of length <b>key_len</b> and a <b>salt</b> of length
+ * <b>salt_len</b>. Store the result of <b>len_out</b> bytes in in
+ * <b>mac_out</b>. This function can't fail. */
+void
+crypto_mac_sha3_256(uint8_t *mac_out, size_t len_out,
+ const uint8_t *key, size_t key_len,
+ const uint8_t *msg, size_t msg_len)
+{
+ crypto_digest_t *digest;
+
+ const uint64_t key_len_netorder = tor_htonll(key_len);
+
+ tor_assert(mac_out);
+ tor_assert(key);
+ tor_assert(msg);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+
+ /* Order matters here that is any subsystem using this function should
+ * expect this very precise ordering in the MAC construction. */
+ crypto_digest_add_bytes(digest, (const char *) &key_len_netorder,
+ sizeof(key_len_netorder));
+ crypto_digest_add_bytes(digest, (const char *) key, key_len);
+ crypto_digest_add_bytes(digest, (const char *) msg, msg_len);
+ crypto_digest_get_digest(digest, (char *) mac_out, len_out);
+ crypto_digest_free(digest);
+}
+
/** Internal state for a eXtendable-Output Function (XOF). */
struct crypto_xof_t {
keccak_state s;
@@ -2638,7 +2682,7 @@ crypto_expand_key_material_TAP(const uint8_t *key_in, size_t key_in_len,
for (cp = key_out, i=0; cp < key_out+key_out_len;
++i, cp += DIGEST_LEN) {
tmp[key_in_len] = i;
- if (crypto_digest((char*)digest, (const char *)tmp, key_in_len+1))
+ if (crypto_digest((char*)digest, (const char *)tmp, key_in_len+1) < 0)
goto exit;
memcpy(cp, digest, MIN(DIGEST_LEN, key_out_len-(cp-key_out)));
}
@@ -2862,7 +2906,7 @@ crypto_strongest_rand_fallback(uint8_t *out, size_t out_len)
size_t n;
for (i = 0; filenames[i]; ++i) {
- log_debug(LD_FS, "Opening %s for entropy", filenames[i]);
+ log_debug(LD_FS, "Considering %s for entropy", filenames[i]);
fd = open(sandbox_intern_string(filenames[i]), O_RDONLY, 0);
if (fd<0) continue;
log_info(LD_CRYPTO, "Reading entropy from \"%s\"", filenames[i]);
diff --git a/src/common/crypto.h b/src/common/crypto.h
index 116e0a62fd..42345f80e8 100644
--- a/src/common/crypto.h
+++ b/src/common/crypto.h
@@ -59,10 +59,12 @@
#define DIGEST256_LEN 32
/** Length of the output of our 64-bit optimized message digests (SHA512). */
#define DIGEST512_LEN 64
-/** Length of our symmetric cipher's keys. */
+/** Length of our symmetric cipher's keys of 128-bit. */
#define CIPHER_KEY_LEN 16
-/** Length of our symmetric cipher's IV. */
+/** Length of our symmetric cipher's IV of 128-bit. */
#define CIPHER_IV_LEN 16
+/** Length of our symmetric cipher's keys of 256-bit. */
+#define CIPHER256_KEY_LEN 32
/** Length of our public keys. */
#define PK_BYTES (1024/8)
/** Length of our DH keys. */
@@ -178,10 +180,12 @@ int crypto_pk_public_encrypt(crypto_pk_t *env, char *to, size_t tolen,
int crypto_pk_private_decrypt(crypto_pk_t *env, char *to, size_t tolen,
const char *from, size_t fromlen,
int padding, int warnOnFailure);
-int crypto_pk_public_checksig(const crypto_pk_t *env, char *to, size_t tolen,
- const char *from, size_t fromlen);
-int crypto_pk_public_checksig_digest(crypto_pk_t *env, const char *data,
- size_t datalen, const char *sig, size_t siglen);
+MOCK_DECL(int, crypto_pk_public_checksig,(const crypto_pk_t *env,
+ char *to, size_t tolen,
+ const char *from, size_t fromlen));
+MOCK_DECL(int, crypto_pk_public_checksig_digest,(crypto_pk_t *env,
+ const char *data, size_t datalen,
+ const char *sig, size_t siglen));
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,
@@ -255,6 +259,10 @@ void crypto_digest_assign(crypto_digest_t *into,
void crypto_hmac_sha256(char *hmac_out,
const char *key, size_t key_len,
const char *msg, size_t msg_len);
+void crypto_mac_sha3_256(uint8_t *mac_out, size_t len_out,
+ const uint8_t *key, size_t key_len,
+ const uint8_t *msg, size_t msg_len);
+
crypto_xof_t *crypto_xof_new(void);
void crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len);
void crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len);
diff --git a/src/common/crypto_curve25519.c b/src/common/crypto_curve25519.c
index fcbee3aba2..5f328e124c 100644
--- a/src/common/crypto_curve25519.c
+++ b/src/common/crypto_curve25519.c
@@ -80,7 +80,7 @@ curve25519_impl(uint8_t *output, const uint8_t *secret,
/**
* Helper function: Multiply the scalar "secret" by the Curve25519
* basepoint (X=9), and store the result in "output". Return 0 on
- * success, -1 on false.
+ * success, -1 on failure.
*/
STATIC int
curve25519_basepoint_impl(uint8_t *output, const uint8_t *secret)
diff --git a/src/common/crypto_ed25519.c b/src/common/crypto_ed25519.c
index 30ed772274..525d25a3e0 100644
--- a/src/common/crypto_ed25519.c
+++ b/src/common/crypto_ed25519.c
@@ -15,6 +15,7 @@
* keys to and from the corresponding Curve25519 keys.
*/
+#define CRYPTO_ED25519_PRIVATE
#include "orconfig.h"
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
@@ -34,7 +35,6 @@
#include <openssl/sha.h>
static void pick_ed25519_impl(void);
-static int ed25519_impl_spot_check(void);
/** An Ed25519 implementation, as a set of function pointers. */
typedef struct {
@@ -211,6 +211,14 @@ ed25519_keypair_generate(ed25519_keypair_t *keypair_out, int extra_strong)
return 0;
}
+/** Return true iff 'pubkey' is set to zero (eg to indicate that it is not
+ * set). */
+int
+ed25519_public_key_is_zero(const ed25519_public_key_t *pubkey)
+{
+ return tor_mem_is_zero((char*)pubkey->pubkey, ED25519_PUBKEY_LEN);
+}
+
/* Return a heap-allocated array that contains <b>msg</b> prefixed by the
* string <b>prefix_str</b>. Set <b>final_msg_len_out</b> to the size of the
* final array. If an error occured, return NULL. It's the resonsibility of the
@@ -267,11 +275,11 @@ ed25519_sign(ed25519_signature_t *signature_out,
* Like ed25519_sign(), but also prefix <b>msg</b> with <b>prefix_str</b>
* before signing. <b>prefix_str</b> must be a NUL-terminated string.
*/
-int
-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)
+MOCK_IMPL(int,
+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))
{
int retval;
size_t prefixed_msg_len;
@@ -300,10 +308,10 @@ ed25519_sign_prefixed(ed25519_signature_t *signature_out,
*
* Return 0 if the signature is valid; -1 if it isn't.
*/
-int
-ed25519_checksig(const ed25519_signature_t *signature,
- const uint8_t *msg, size_t len,
- const ed25519_public_key_t *pubkey)
+MOCK_IMPL(int,
+ed25519_checksig,(const ed25519_signature_t *signature,
+ const uint8_t *msg, size_t len,
+ const ed25519_public_key_t *pubkey))
{
return
get_ed_impl()->open(signature->sig, msg, len, pubkey->pubkey) < 0 ? -1 : 0;
@@ -346,10 +354,10 @@ ed25519_checksig_prefixed(const ed25519_signature_t *signature,
* was valid. Otherwise return -N, where N is the number of invalid
* signatures.
*/
-int
-ed25519_checksig_batch(int *okay_out,
- const ed25519_checkable_t *checkable,
- int n_checkable)
+MOCK_IMPL(int,
+ed25519_checksig_batch,(int *okay_out,
+ const ed25519_checkable_t *checkable,
+ int n_checkable))
{
int i, res;
const ed25519_impl_t *impl = get_ed_impl();
@@ -620,10 +628,22 @@ ed25519_pubkey_eq(const ed25519_public_key_t *key1,
return tor_memeq(key1->pubkey, key2->pubkey, ED25519_PUBKEY_LEN);
}
+/**
+ * Set <b>dest</b> to contain the same key as <b>src</b>.
+ */
+void
+ed25519_pubkey_copy(ed25519_public_key_t *dest,
+ const ed25519_public_key_t *src)
+{
+ tor_assert(dest);
+ tor_assert(src);
+ memcpy(dest, src, sizeof(ed25519_public_key_t));
+}
+
/** Check whether the given Ed25519 implementation seems to be working.
* If so, return 0; otherwise return -1. */
-static int
-ed25519_impl_spot_check(void)
+MOCK_IMPL(STATIC int,
+ed25519_impl_spot_check,(void))
{
static const uint8_t alicesk[32] = {
0xc5,0xaa,0x8d,0xf4,0x3f,0x9f,0x83,0x7b,
diff --git a/src/common/crypto_ed25519.h b/src/common/crypto_ed25519.h
index 31afc49ccc..f4a4adad68 100644
--- a/src/common/crypto_ed25519.h
+++ b/src/common/crypto_ed25519.h
@@ -51,21 +51,24 @@ int ed25519_keypair_generate(ed25519_keypair_t *keypair_out, int extra_strong);
int ed25519_sign(ed25519_signature_t *signature_out,
const uint8_t *msg, size_t len,
const ed25519_keypair_t *key);
-int ed25519_checksig(const ed25519_signature_t *signature,
- const uint8_t *msg, size_t len,
- const ed25519_public_key_t *pubkey);
+MOCK_DECL(int,ed25519_checksig,(const ed25519_signature_t *signature,
+ const uint8_t *msg, size_t len,
+ const ed25519_public_key_t *pubkey));
+
+MOCK_DECL(int,
+ed25519_sign_prefixed,(ed25519_signature_t *signature_out,
+ const uint8_t *msg, size_t len,
+ const char *prefix_str,
+ const ed25519_keypair_t *keypair));
-int
-ed25519_sign_prefixed(ed25519_signature_t *signature_out,
- const uint8_t *msg, size_t len,
- const char *prefix_str,
- const ed25519_keypair_t *keypair);
int
ed25519_checksig_prefixed(const ed25519_signature_t *signature,
const uint8_t *msg, size_t len,
const char *prefix_str,
const ed25519_public_key_t *pubkey);
+int ed25519_public_key_is_zero(const ed25519_public_key_t *pubkey);
+
/**
* A collection of information necessary to check an Ed25519 signature. Used
* for batch verification.
@@ -81,9 +84,9 @@ typedef struct {
size_t len;
} ed25519_checkable_t;
-int ed25519_checksig_batch(int *okay_out,
- const ed25519_checkable_t *checkable,
- int n_checkable);
+MOCK_DECL(int, ed25519_checksig_batch,(int *okay_out,
+ const ed25519_checkable_t *checkable,
+ int n_checkable));
int ed25519_keypair_from_curve25519_keypair(ed25519_keypair_t *out,
int *signbit_out,
@@ -118,6 +121,8 @@ void ed25519_keypair_free(ed25519_keypair_t *kp);
int ed25519_pubkey_eq(const ed25519_public_key_t *key1,
const ed25519_public_key_t *key2);
+void ed25519_pubkey_copy(ed25519_public_key_t *dest,
+ const ed25519_public_key_t *src);
void ed25519_set_impl_params(int use_donna);
void ed25519_init(void);
@@ -127,5 +132,9 @@ void crypto_ed25519_testing_force_impl(const char *name);
void crypto_ed25519_testing_restore_impl(void);
#endif
+#ifdef CRYPTO_ED25519_PRIVATE
+MOCK_DECL(STATIC int, ed25519_impl_spot_check, (void));
+#endif
+
#endif
diff --git a/src/common/crypto_format.c b/src/common/crypto_format.c
index 2f6d847c83..aa2a9d1fb0 100644
--- a/src/common/crypto_format.c
+++ b/src/common/crypto_format.c
@@ -161,6 +161,27 @@ curve25519_public_from_base64(curve25519_public_key_t *pkey,
}
}
+/** For logging convenience: Convert <b>pkey</b> to a statically allocated
+ * base64 string and return it. Not threadsafe. Format not meant to be
+ * computer-readable; it may change in the future. Subsequent calls invalidate
+ * previous returns. */
+const char *
+ed25519_fmt(const ed25519_public_key_t *pkey)
+{
+ static char formatted[ED25519_BASE64_LEN+1];
+ if (pkey) {
+ if (ed25519_public_key_is_zero(pkey)) {
+ strlcpy(formatted, "<unset>", sizeof(formatted));
+ } else {
+ int r = ed25519_public_to_base64(formatted, pkey);
+ tor_assert(!r);
+ }
+ } else {
+ strlcpy(formatted, "<null>", sizeof(formatted));
+ }
+ return formatted;
+}
+
/** Try to decode the string <b>input</b> into an ed25519 public key. On
* success, store the value in <b>pkey</b> and return 0. Otherwise return
* -1. */
diff --git a/src/common/crypto_format.h b/src/common/crypto_format.h
index 012e228cc4..86c29d319c 100644
--- a/src/common/crypto_format.h
+++ b/src/common/crypto_format.h
@@ -28,6 +28,7 @@ int ed25519_public_from_base64(ed25519_public_key_t *pkey,
const char *input);
int ed25519_public_to_base64(char *output,
const ed25519_public_key_t *pkey);
+const char *ed25519_fmt(const ed25519_public_key_t *pkey);
/* XXXX move these to crypto_format.h */
#define ED25519_SIG_BASE64_LEN 86
diff --git a/src/common/log.c b/src/common/log.c
index 56adc77f84..5f7151bf0c 100644
--- a/src/common/log.c
+++ b/src/common/log.c
@@ -682,7 +682,7 @@ tor_log_get_logfile_names(smartlist_t *out)
continue;
if (lf->filename == NULL)
continue;
- smartlist_add(out, tor_strdup(lf->filename));
+ smartlist_add_strdup(out, lf->filename);
}
UNLOCK_LOGS();
@@ -1177,7 +1177,7 @@ static const char *domain_list[] = {
"GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM",
"HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV",
"OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL",
- "SCHED", NULL
+ "SCHED", "GUARD", NULL
};
/** Return a bitmask for the log domain for which <b>domain</b> is the name,
@@ -1319,10 +1319,8 @@ parse_log_severity_config(const char **cfg_ptr,
if (got_an_unqualified_range > 1)
return -1;
- space = strchr(cfg, ' ');
+ space = find_whitespace(cfg);
dash = strchr(cfg, '-');
- if (!space)
- space = strchr(cfg, '\0');
if (dash && dash < space) {
sev_lo = tor_strndup(cfg, dash-cfg);
sev_hi = tor_strndup(dash+1, space-(dash+1));
diff --git a/src/common/sandbox.c b/src/common/sandbox.c
index 7f4511db2a..2e1519e922 100644
--- a/src/common/sandbox.c
+++ b/src/common/sandbox.c
@@ -1554,7 +1554,7 @@ sandbox_getaddrinfo(const char *name, const char *servname,
return err;
}
- /* Otherwise, the sanbox is on. If we have an item, yield its cached
+ /* Otherwise, the sandbox is on. If we have an item, yield its cached
result. */
if (item) {
*res = item->res;
diff --git a/src/common/timers.c b/src/common/timers.c
index 41b2008ac4..e1ad47b15b 100644
--- a/src/common/timers.c
+++ b/src/common/timers.c
@@ -255,6 +255,20 @@ timer_set_cb(tor_timer_t *t, timer_cb_fn_t cb, void *arg)
}
/**
+ * Set *<b>cb_out</b> (if provided) to this timer's callback function,
+ * and *<b>arg_out</b> (if provided) to this timer's callback argument.
+ */
+void
+timer_get_cb(const tor_timer_t *t,
+ timer_cb_fn_t *cb_out, void **arg_out)
+{
+ if (cb_out)
+ *cb_out = t->callback.cb;
+ if (arg_out)
+ *arg_out = t->callback.arg;
+}
+
+/**
* Schedule the timer t to fire at the current time plus a delay of
* <b>delay</b> microseconds. All times are relative to monotime_get().
*/
diff --git a/src/common/timers.h b/src/common/timers.h
index 5f918f8e15..c5246a3335 100644
--- a/src/common/timers.h
+++ b/src/common/timers.h
@@ -13,6 +13,8 @@ typedef void (*timer_cb_fn_t)(tor_timer_t *, void *,
const struct monotime_t *);
tor_timer_t *timer_new(timer_cb_fn_t cb, void *arg);
void timer_set_cb(tor_timer_t *t, timer_cb_fn_t cb, void *arg);
+void timer_get_cb(const tor_timer_t *t,
+ timer_cb_fn_t *cb_out, void **arg_out);
void timer_schedule(tor_timer_t *t, const struct timeval *delay);
void timer_disable(tor_timer_t *t);
void timer_free(tor_timer_t *t);
diff --git a/src/common/torgzip.c b/src/common/torgzip.c
index c44399aa74..03786cc597 100644
--- a/src/common/torgzip.c
+++ b/src/common/torgzip.c
@@ -207,7 +207,7 @@ tor_gzip_compress(char **out, size_t *out_len,
}
done:
*out_len = stream->total_out;
-#ifdef OPENBSD
+#if defined(OpenBSD)
/* "Hey Rocky! Watch me change an unsigned field to a signed field in a
* third-party API!"
* "Oh, that trick will just make people do unsafe casts to the unsigned
diff --git a/src/common/torlog.h b/src/common/torlog.h
index 6732a42741..bc957858d9 100644
--- a/src/common/torlog.h
+++ b/src/common/torlog.h
@@ -99,8 +99,10 @@
#define LD_CHANNEL (1u<<21)
/** Scheduler */
#define LD_SCHED (1u<<22)
+/** Guard nodes */
+#define LD_GUARD (1u<<23)
/** Number of logging domains in the code. */
-#define N_LOGGING_DOMAINS 23
+#define N_LOGGING_DOMAINS 24
/** This log message is not safe to send to a callback-based logger
* immediately. Used as a flag, not a log domain. */
diff --git a/src/common/tortls.c b/src/common/tortls.c
index d61cc2e58a..e3e8830b3e 100644
--- a/src/common/tortls.c
+++ b/src/common/tortls.c
@@ -136,6 +136,7 @@ static void tor_tls_context_decref(tor_tls_context_t *ctx);
static void tor_tls_context_incref(tor_tls_context_t *ctx);
static int check_cert_lifetime_internal(int severity, const X509 *cert,
+ time_t now,
int past_tolerance, int future_tolerance);
/** Global TLS contexts. We keep them here because nobody else needs
@@ -482,8 +483,22 @@ MOCK_IMPL(STATIC X509 *,
* then we might pick a time where we're about to expire. Lastly, be
* sure to start on a day boundary. */
time_t now = time(NULL);
- start_time = crypto_rand_time_range(now - cert_lifetime, now) + 2*24*3600;
- start_time -= start_time % (24*3600);
+ /* Our certificate lifetime will be cert_lifetime no matter what, but if we
+ * start cert_lifetime in the past, we'll have 0 real lifetime. instead we
+ * start up to (cert_lifetime - min_real_lifetime - start_granularity) in
+ * 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;
+ /* Don't actually start in the future! */
+ if (earliest_start_time >= now)
+ earliest_start_time = now - 1;
+ 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;
+
+ end_time = start_time + cert_lifetime;
tor_assert(rsa);
tor_assert(cname);
@@ -517,12 +532,12 @@ MOCK_IMPL(STATIC X509 *,
if (!X509_time_adj(X509_get_notBefore(x509),0,&start_time))
goto error;
- end_time = start_time + cert_lifetime;
if (!X509_time_adj(X509_get_notAfter(x509),0,&end_time))
goto error;
if (!X509_set_pubkey(x509, pkey))
goto error;
- if (!X509_sign(x509, sign_pkey, EVP_sha1()))
+
+ if (!X509_sign(x509, sign_pkey, EVP_sha256()))
goto error;
goto done;
@@ -583,6 +598,12 @@ static const char UNRESTRICTED_SERVER_CIPHER_LIST[] =
#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256
TLS1_TXT_DHE_RSA_WITH_AES_128_GCM_SHA256 ":"
#endif
+#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_CCM
+ TLS1_TXT_DHE_RSA_WITH_AES_256_CCM ":"
+#endif
+#ifdef TLS1_TXT_DHE_RSA_WITH_AES_128_CCM
+ TLS1_TXT_DHE_RSA_WITH_AES_128_CCM ":"
+#endif
#ifdef TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256
TLS1_TXT_DHE_RSA_WITH_AES_256_SHA256 ":"
#endif
@@ -592,8 +613,14 @@ static const char UNRESTRICTED_SERVER_CIPHER_LIST[] =
/* Required */
TLS1_TXT_DHE_RSA_WITH_AES_256_SHA ":"
/* Required */
- TLS1_TXT_DHE_RSA_WITH_AES_128_SHA
- ;
+ TLS1_TXT_DHE_RSA_WITH_AES_128_SHA ":"
+#ifdef TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305
+ TLS1_TXT_ECDHE_RSA_WITH_CHACHA20_POLY1305 ":"
+#endif
+#ifdef TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305
+ TLS1_TXT_DHE_RSA_WITH_CHACHA20_POLY1305
+#endif
+ ;
/* Note: to set up your own private testing network with link crypto
* disabled, set your Tors' cipher list to
@@ -778,8 +805,8 @@ tor_tls_context_decref(tor_tls_context_t *ctx)
/** Set *<b>link_cert_out</b> and *<b>id_cert_out</b> to the link certificate
* and ID certificate that we're currently using for our V3 in-protocol
* handshake's certificate chain. If <b>server</b> is true, provide the certs
- * that we use in server mode; otherwise, provide the certs that we use in
- * client mode. */
+ * that we use in server mode (auth, ID); otherwise, provide the certs that we
+ * use in client mode. (link, ID) */
int
tor_tls_get_my_certs(int server,
const tor_x509_cert_t **link_cert_out,
@@ -809,7 +836,7 @@ tor_tls_get_my_client_auth_key(void)
/**
* Return a newly allocated copy of the public key that a certificate
- * certifies. Return NULL if the cert's key is not RSA.
+ * certifies. Watch out! This returns NULL if the cert's key is not RSA.
*/
crypto_pk_t *
tor_tls_cert_get_key(tor_x509_cert_t *cert)
@@ -864,6 +891,7 @@ int
tor_tls_cert_is_valid(int severity,
const tor_x509_cert_t *cert,
const tor_x509_cert_t *signing_cert,
+ time_t now,
int check_rsa_1024)
{
check_no_tls_errors();
@@ -883,7 +911,7 @@ tor_tls_cert_is_valid(int severity,
/* okay, the signature checked out right. Now let's check the check the
* lifetime. */
- if (check_cert_lifetime_internal(severity, cert->cert,
+ if (check_cert_lifetime_internal(severity, cert->cert, now,
48*60*60, 30*24*60*60) < 0)
goto bad;
@@ -1028,6 +1056,8 @@ tor_tls_context_init_one(tor_tls_context_t **ppcontext,
/** The group we should use for ecdhe when none was selected. */
#define NID_tor_default_ecdhe_group NID_X9_62_prime256v1
+#define RSA_LINK_KEY_BITS 2048
+
/** Create a new TLS context for use with Tor TLS handshakes.
* <b>identity</b> should be set to the identity key used to sign the
* certificate.
@@ -1053,7 +1083,7 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime,
/* Generate short-term RSA key for use with TLS. */
if (!(rsa = crypto_pk_new()))
goto error;
- if (crypto_pk_generate_key(rsa)<0)
+ if (crypto_pk_generate_key_with_bits(rsa, RSA_LINK_KEY_BITS)<0)
goto error;
if (!is_client) {
/* Generate short-term RSA key for use in the in-protocol ("v3")
@@ -2051,13 +2081,13 @@ tor_tls_get_own_cert,(tor_tls_t *tls))
/** Warn that a certificate lifetime extends through a certain range. */
static void
-log_cert_lifetime(int severity, const X509 *cert, const char *problem)
+log_cert_lifetime(int severity, const X509 *cert, const char *problem,
+ time_t now)
{
BIO *bio = NULL;
BUF_MEM *buf;
char *s1=NULL, *s2=NULL;
char mytime[33];
- time_t now = time(NULL);
struct tm tm;
size_t n;
@@ -2205,6 +2235,7 @@ tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity_key)
*/
int
tor_tls_check_lifetime(int severity, tor_tls_t *tls,
+ time_t now,
int past_tolerance, int future_tolerance)
{
X509 *cert;
@@ -2213,7 +2244,7 @@ tor_tls_check_lifetime(int severity, tor_tls_t *tls,
if (!(cert = SSL_get_peer_certificate(tls->ssl)))
goto done;
- if (check_cert_lifetime_internal(severity, cert,
+ if (check_cert_lifetime_internal(severity, cert, now,
past_tolerance, future_tolerance) < 0)
goto done;
@@ -2229,24 +2260,24 @@ tor_tls_check_lifetime(int severity, tor_tls_t *tls,
/** Helper: check whether <b>cert</b> is expired give or take
* <b>past_tolerance</b> seconds, or not-yet-valid give or take
- * <b>future_tolerance</b> seconds. If it is live, return 0. If it is not
- * live, log a message and return -1. */
+ * <b>future_tolerance</b> seconds. (Relative to the current time
+ * <b>now</b>.) If it is live, return 0. If it is not live, log a message
+ * and return -1. */
static int
check_cert_lifetime_internal(int severity, const X509 *cert,
+ time_t now,
int past_tolerance, int future_tolerance)
{
- time_t now, t;
-
- now = time(NULL);
+ time_t t;
t = now + future_tolerance;
if (X509_cmp_time(X509_get_notBefore_const(cert), &t) > 0) {
- log_cert_lifetime(severity, cert, "not yet valid");
+ log_cert_lifetime(severity, cert, "not yet valid", now);
return -1;
}
t = now - past_tolerance;
if (X509_cmp_time(X509_get_notAfter_const(cert), &t) < 0) {
- log_cert_lifetime(severity, cert, "already expired");
+ log_cert_lifetime(severity, cert, "already expired", now);
return -1;
}
@@ -2471,6 +2502,28 @@ tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out))
return 0;
}
+/** Using the RFC5705 key material exporting construction, and the
+ * provided <b>context</b> (<b>context_len</b> bytes long) and
+ * <b>label</b> (a NUL-terminated string), compute a 32-byte secret in
+ * <b>secrets_out</b> that only the parties to this TLS session can
+ * compute. Return 0 on success and -1 on failure.
+ */
+MOCK_IMPL(int,
+tor_tls_export_key_material,(tor_tls_t *tls, uint8_t *secrets_out,
+ const uint8_t *context,
+ size_t context_len,
+ const char *label))
+{
+ tor_assert(tls);
+ tor_assert(tls->ssl);
+
+ int r = SSL_export_keying_material(tls->ssl,
+ secrets_out, DIGEST256_LEN,
+ label, strlen(label),
+ context, context_len, 1);
+ return (r == 1) ? 0 : -1;
+}
+
/** Examine the amount of memory used and available for buffers in <b>tls</b>.
* Set *<b>rbuf_capacity</b> to the amount of storage allocated for the read
* buffer and *<b>rbuf_bytes</b> to the amount actually used.
diff --git a/src/common/tortls.h b/src/common/tortls.h
index f018c45c82..bb7701cc4b 100644
--- a/src/common/tortls.h
+++ b/src/common/tortls.h
@@ -176,6 +176,7 @@ extern uint64_t total_bytes_written_by_tls;
#endif /* endif 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);
void tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz);
@@ -197,12 +198,12 @@ void tor_tls_set_renegotiate_callback(tor_tls_t *tls,
int tor_tls_is_server(tor_tls_t *tls);
void tor_tls_free(tor_tls_t *tls);
int tor_tls_peer_has_cert(tor_tls_t *tls);
-tor_x509_cert_t *tor_x509_cert_dup(const tor_x509_cert_t *cert);
MOCK_DECL(tor_x509_cert_t *,tor_tls_get_peer_cert,(tor_tls_t *tls));
MOCK_DECL(tor_x509_cert_t *,tor_tls_get_own_cert,(tor_tls_t *tls));
int tor_tls_verify(int severity, tor_tls_t *tls, crypto_pk_t **identity);
int tor_tls_check_lifetime(int severity,
- tor_tls_t *tls, int past_tolerance,
+ tor_tls_t *tls, time_t now,
+ int past_tolerance,
int future_tolerance);
MOCK_DECL(int, tor_tls_read, (tor_tls_t *tls, char *cp, size_t len));
int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n);
@@ -228,6 +229,11 @@ int tor_tls_used_v1_handshake(tor_tls_t *tls);
int tor_tls_get_num_server_handshakes(tor_tls_t *tls);
int tor_tls_server_got_renegotiate(tor_tls_t *tls);
MOCK_DECL(int,tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out));
+MOCK_DECL(int,tor_tls_export_key_material,(
+ tor_tls_t *tls, uint8_t *secrets_out,
+ const uint8_t *context,
+ size_t context_len,
+ const char *label));
/* Log and abort if there are unhandled TLS errors in OpenSSL's error stack.
*/
@@ -256,6 +262,7 @@ MOCK_DECL(int,tor_tls_cert_matches_key,(const tor_tls_t *tls,
int tor_tls_cert_is_valid(int severity,
const tor_x509_cert_t *cert,
const tor_x509_cert_t *signing_cert,
+ time_t now,
int check_rsa_1024);
const char *tor_tls_get_ciphersuite_name(tor_tls_t *tls);
diff --git a/src/common/util.c b/src/common/util.c
index d2cbacde31..4942ee3a9e 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -79,7 +79,7 @@
#include <malloc/malloc.h>
#endif
#ifdef HAVE_MALLOC_H
-#if !defined(OPENBSD) && !defined(__FreeBSD__)
+#if !defined(OpenBSD) && !defined(__FreeBSD__)
/* OpenBSD has a malloc.h, but for our purposes, it only exists in order to
* 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. */
@@ -187,8 +187,9 @@ tor_malloc_zero_(size_t size DMALLOC_PARAMS)
* 0xfffe0001. */
#define SQRT_SIZE_MAX_P1 (((size_t)1) << (sizeof(size_t)*4))
-/** Return non-zero if and only if the product of the arguments is exact. */
-static inline int
+/** Return non-zero if and only if the product of the arguments is exact,
+ * and cannot overflow. */
+int
size_mul_check(const size_t x, const size_t y)
{
/* This first check is equivalent to
@@ -202,15 +203,6 @@ size_mul_check(const size_t x, const size_t y)
x <= SIZE_MAX / y);
}
-#ifdef TOR_UNIT_TESTS
-/** Exposed for unit tests only */
-int
-size_mul_check__(const size_t x, const size_t y)
-{
- return size_mul_check(x,y);
-}
-#endif
-
/** Allocate a chunk of <b>nmemb</b>*<b>size</b> bytes of memory, fill
* the memory with zero bytes, and return a pointer to the result.
* Log and terminate the process on error. (Same as
@@ -712,6 +704,19 @@ tor_strisnonupper(const char *s)
return 1;
}
+/** Return true iff every character in <b>s</b> is whitespace space; else
+ * return false. */
+int
+tor_strisspace(const char *s)
+{
+ while (*s) {
+ if (!TOR_ISSPACE(*s))
+ return 0;
+ s++;
+ }
+ return 1;
+}
+
/** As strcmp, except that either string may be NULL. The NULL string is
* considered to be before any non-NULL string. */
int
@@ -1803,17 +1808,26 @@ format_iso_time_nospace_usec(char *buf, const struct timeval *tv)
/** Given an ISO-formatted UTC time value (after the epoch) in <b>cp</b>,
* parse it and store its value in *<b>t</b>. Return 0 on success, -1 on
* failure. Ignore extraneous stuff in <b>cp</b> after the end of the time
- * string, unless <b>strict</b> is set. */
+ * string, unless <b>strict</b> is set. If <b>nospace</b> is set,
+ * expect the YYYY-MM-DDTHH:MM:SS format. */
int
-parse_iso_time_(const char *cp, time_t *t, int strict)
+parse_iso_time_(const char *cp, time_t *t, int strict, int nospace)
{
struct tm st_tm;
unsigned int year=0, month=0, day=0, hour=0, minute=0, second=0;
int n_fields;
- char extra_char;
- n_fields = tor_sscanf(cp, "%u-%2u-%2u %2u:%2u:%2u%c", &year, &month,
- &day, &hour, &minute, &second, &extra_char);
- if (strict ? (n_fields != 6) : (n_fields < 6)) {
+ char extra_char, separator_char;
+ n_fields = tor_sscanf(cp, "%u-%2u-%2u%c%2u:%2u:%2u%c",
+ &year, &month, &day,
+ &separator_char,
+ &hour, &minute, &second, &extra_char);
+ if (strict ? (n_fields != 7) : (n_fields < 7)) {
+ char *esc = esc_for_log(cp);
+ log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
+ tor_free(esc);
+ return -1;
+ }
+ if (separator_char != (nospace ? 'T' : ' ')) {
char *esc = esc_for_log(cp);
log_warn(LD_GENERAL, "ISO time %s was unparseable", esc);
tor_free(esc);
@@ -1855,7 +1869,16 @@ parse_iso_time_(const char *cp, time_t *t, int strict)
int
parse_iso_time(const char *cp, time_t *t)
{
- return parse_iso_time_(cp, t, 1);
+ return parse_iso_time_(cp, t, 1, 0);
+}
+
+/**
+ * As parse_iso_time, but parses a time encoded by format_iso_time_nospace().
+ */
+int
+parse_iso_time_nospace(const char *cp, time_t *t)
+{
+ return parse_iso_time_(cp, t, 1, 1);
}
/** Given a <b>date</b> in one of the three formats allowed by HTTP (ugh),
@@ -2270,10 +2293,14 @@ check_private_dir,(const char *dirname, cpd_check_t check,
* permissions on the directory will be checked again below.*/
fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
- if (fd == -1)
+ if (fd == -1) {
+ log_warn(LD_FS, "Could not reopen recently created directory %s: %s",
+ dirname,
+ strerror(errno));
return -1;
- else
+ } else {
close(fd);
+ }
} else if (!(check & CPD_CHECK)) {
log_warn(LD_FS, "Directory %s does not exist.", dirname);
@@ -2601,6 +2628,14 @@ finish_writing_to_file_impl(open_file_t *file_data, int abort_write)
if (file_data->rename_on_close) {
tor_assert(file_data->tempname && file_data->filename);
+ if (!abort_write) {
+ tor_assert(strcmp(file_data->filename, file_data->tempname));
+ if (replace_file(file_data->tempname, file_data->filename)) {
+ log_warn(LD_FS, "Error replacing \"%s\": %s", file_data->filename,
+ strerror(errno));
+ abort_write = r = -1;
+ }
+ }
if (abort_write) {
int res = unlink(file_data->tempname);
if (res != 0) {
@@ -2609,13 +2644,6 @@ finish_writing_to_file_impl(open_file_t *file_data, int abort_write)
file_data->tempname, strerror(errno));
r = -1;
}
- } else {
- tor_assert(strcmp(file_data->filename, file_data->tempname));
- if (replace_file(file_data->tempname, file_data->filename)) {
- log_warn(LD_FS, "Error replacing \"%s\": %s", file_data->filename,
- strerror(errno));
- r = -1;
- }
}
}
@@ -3534,6 +3562,17 @@ smartlist_add_vasprintf(struct smartlist_t *sl, const char *pattern,
smartlist_add(sl, str);
}
+/** Append a copy of string to sl */
+void
+smartlist_add_strdup(struct smartlist_t *sl, const char *string)
+{
+ char *copy;
+
+ copy = tor_strdup(string);
+
+ smartlist_add(sl, copy);
+}
+
/** Return a new list containing the filenames in the directory <b>dirname</b>.
* Return NULL on error or if <b>dirname</b> is not a directory.
*/
@@ -3567,7 +3606,7 @@ tor_listdir, (const char *dirname))
#endif
if (strcmp(name, ".") &&
strcmp(name, "..")) {
- smartlist_add(result, tor_strdup(name));
+ smartlist_add_strdup(result, name);
}
if (!FindNextFile(handle, &findData)) {
DWORD err;
@@ -3593,7 +3632,7 @@ tor_listdir, (const char *dirname))
if (!strcmp(de->d_name, ".") ||
!strcmp(de->d_name, ".."))
continue;
- smartlist_add(result, tor_strdup(de->d_name));
+ smartlist_add_strdup(result, de->d_name);
}
closedir(d);
#endif
@@ -4864,7 +4903,7 @@ get_current_process_environment_variables(void)
char **environ_tmp; /* Not const char ** ? Really? */
for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) {
- smartlist_add(sl, tor_strdup(*environ_tmp));
+ smartlist_add_strdup(sl, *environ_tmp);
}
return sl;
@@ -5247,7 +5286,7 @@ tor_get_lines_from_handle, (FILE *handle,
goto done;
if (!lines) lines = smartlist_new();
- smartlist_add(lines, tor_strdup(stdout_buf));
+ smartlist_add_strdup(lines, stdout_buf);
}
done:
diff --git a/src/common/util.h b/src/common/util.h
index 479fc8d610..13fcc5142d 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -186,6 +186,7 @@ void tor_strlower(char *s) ATTR_NONNULL((1));
void tor_strupper(char *s) ATTR_NONNULL((1));
int tor_strisprint(const char *s) ATTR_NONNULL((1));
int tor_strisnonupper(const char *s) ATTR_NONNULL((1));
+int tor_strisspace(const char *s);
int strcmp_opt(const char *s1, const char *s2);
int strcmpstart(const char *s1, const char *s2) ATTR_NONNULL((1,2));
int strcmp_len(const char *s1, const char *s2, size_t len) ATTR_NONNULL((1,2));
@@ -239,6 +240,7 @@ void smartlist_add_asprintf(struct smartlist_t *sl, const char *pattern, ...)
void smartlist_add_vasprintf(struct smartlist_t *sl, const char *pattern,
va_list args)
CHECK_PRINTF(2, 0);
+void smartlist_add_strdup(struct smartlist_t *sl, const char *string);
/* Time helpers */
long tv_udiff(const struct timeval *start, const struct timeval *end);
@@ -254,8 +256,9 @@ void format_local_iso_time(char *buf, time_t t);
void format_iso_time(char *buf, time_t t);
void format_iso_time_nospace(char *buf, time_t t);
void format_iso_time_nospace_usec(char *buf, const struct timeval *tv);
-int parse_iso_time_(const char *cp, time_t *t, int strict);
+int parse_iso_time_(const char *cp, time_t *t, int strict, int nospace);
int parse_iso_time(const char *buf, time_t *t);
+int parse_iso_time_nospace(const char *cp, time_t *t);
int parse_http_time(const char *buf, struct tm *tm);
int format_time_interval(char *out, size_t out_len, long interval);
@@ -551,9 +554,7 @@ STATIC int format_helper_exit_status(unsigned char child_state,
#endif
-#ifdef TOR_UNIT_TESTS
-int size_mul_check__(const size_t x, const size_t y);
-#endif
+int size_mul_check(const size_t x, const size_t y);
#define ARRAY_LENGTH(x) ((sizeof(x)) / sizeof(x[0]))
diff --git a/src/common/util_bug.c b/src/common/util_bug.c
index 08aba47974..c7bfdefe80 100644
--- a/src/common/util_bug.c
+++ b/src/common/util_bug.c
@@ -44,7 +44,7 @@ static void
add_captured_bug(const char *s)
{
--n_bugs_to_capture;
- smartlist_add(bug_messages, tor_strdup(s));
+ smartlist_add_strdup(bug_messages, s);
}
/** Set a callback to be invoked when we get any tor_bug_occurred_
* invocation. We use this in the unit tests so that a nonfatal
diff --git a/src/common/util_format.c b/src/common/util_format.c
index aef9db85c8..7e8ee1b868 100644
--- a/src/common/util_format.c
+++ b/src/common/util_format.c
@@ -51,9 +51,10 @@ base32_encode(char *dest, size_t destlen, const char *src, size_t srclen)
for (i=0,bit=0; bit < nbits; ++i, bit+=5) {
/* set v to the 16-bit value starting at src[bits/8], 0-padded. */
- v = ((uint8_t)src[bit/8]) << 8;
- if (bit+5<nbits)
- v += (uint8_t)src[(bit/8)+1];
+ size_t idx = bit / 8;
+ v = ((uint8_t)src[idx]) << 8;
+ if (idx+1 < srclen)
+ v += (uint8_t)src[idx+1];
/* set u to the 5-bit value at the bit'th bit of buf. */
u = (v >> (11-(bit%8))) & 0x1F;
dest[i] = BASE32_CHARS[u];
@@ -398,7 +399,7 @@ base64_decode(char *dest, size_t destlen, const char *src, size_t srclen)
* Number of bytes required to hold all bits == (srclen*6)/8.
* Yes, we want to round down: anything that hangs over the end of a
* byte is padding. */
- if (destlen < (srclen*3)/4)
+ if (!size_mul_check(srclen, 3) || destlen < (srclen*3)/4)
return -1;
if (destlen > SIZE_T_CEILING)
return -1;
diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in
index 5328206da9..37777443ac 100644
--- a/src/config/torrc.sample.in
+++ b/src/config/torrc.sample.in
@@ -95,7 +95,12 @@
## If you have multiple network interfaces, you can specify one for
## outgoing traffic to use.
-# OutboundBindAddress 10.0.0.5
+## OutboundBindAddressExit will be used for all exit traffic, while
+## OutboundBindAddressOR will be used for all other connections.
+## If you do not wish to differentiate, use OutboundBindAddress to
+## specify the same address for both in a single line.
+#OutboundBindAddressExit 10.0.0.4
+#OutboundBindAddressOR 10.0.0.5
## A handle for your relay, so people don't have to refer to it by key.
## Nicknames must be between 1 and 19 characters inclusive, and must
diff --git a/src/ext/csiphash.c b/src/ext/csiphash.c
index 49a6dc4778..a8f3d5b8b8 100644
--- a/src/ext/csiphash.c
+++ b/src/ext/csiphash.c
@@ -53,7 +53,7 @@
#else
/* See: http://sourceforge.net/p/predef/wiki/Endianness/ */
-# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(OpenBSD)
# include <sys/endian.h>
# else
# include <endian.h>
@@ -62,7 +62,7 @@
__BYTE_ORDER == __LITTLE_ENDIAN
# define _le64toh(x) ((uint64_t)(x))
# else
-# if defined(__OpenBSD__)
+# if defined(OpenBSD)
# define _le64toh(x) letoh64(x)
# else
# define _le64toh(x) le64toh(x)
diff --git a/src/ext/trunnel/trunnel-impl.h b/src/ext/trunnel/trunnel-impl.h
index 3ffde6e09b..bc805851b1 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.4.6
+/* trunnel-impl.h -- copied from Trunnel v1.5.1
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -17,6 +17,7 @@
#include "trunnel.h"
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
#if defined(_MSC_VER) && (_MSC_VER < 1600)
#define uint8_t unsigned char
diff --git a/src/ext/trunnel/trunnel.c b/src/ext/trunnel/trunnel.c
index 3994422643..8f72351277 100644
--- a/src/ext/trunnel/trunnel.c
+++ b/src/ext/trunnel/trunnel.c
@@ -1,4 +1,4 @@
-/* trunnel.c -- copied from Trunnel v1.4.6
+/* trunnel.c -- copied from Trunnel v1.5.1
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -31,7 +31,7 @@
# define IS_LITTLE_ENDIAN
# endif
#else
-# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# if defined(__FreeBSD__) || defined(__NetBSD__) || defined(OpenBSD)
# include <sys/endian.h>
# else
# include <endian.h>
diff --git a/src/ext/trunnel/trunnel.h b/src/ext/trunnel/trunnel.h
index 41068b8fb3..85bbcc5451 100644
--- a/src/ext/trunnel/trunnel.h
+++ b/src/ext/trunnel/trunnel.h
@@ -1,4 +1,4 @@
-/* trunnel.h -- copied from Trunnel v1.4.6
+/* trunnel.h -- copied from Trunnel v1.5.1
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
diff --git a/src/include.am b/src/include.am
index c468af3649..d12684e187 100644
--- a/src/include.am
+++ b/src/include.am
@@ -6,4 +6,4 @@ include src/test/include.am
include src/tools/include.am
include src/win32/include.am
include src/config/include.am
-
+include src/test/fuzz/include.am
diff --git a/src/or/addressmap.c b/src/or/addressmap.c
index 33fd7e0f4a..85a6434f4a 100644
--- a/src/or/addressmap.c
+++ b/src/or/addressmap.c
@@ -376,29 +376,38 @@ addressmap_rewrite(char *address, size_t maxlen,
char *addr_orig = tor_strdup(address);
char *log_addr_orig = NULL;
+ /* We use a loop here to limit the total number of rewrites we do,
+ * so that we can't hit an infinite loop. */
for (rewrites = 0; rewrites < 16; rewrites++) {
int exact_match = 0;
log_addr_orig = tor_strdup(escaped_safe_str_client(address));
+ /* First check to see if there's an exact match for this address */
ent = strmap_get(addressmap, address);
if (!ent || !ent->new_address) {
+ /* And if we don't have an exact match, try to check whether
+ * we have a pattern-based match.
+ */
ent = addressmap_match_superdomains(address);
} else {
if (ent->src_wildcard && !ent->dst_wildcard &&
!strcasecmp(address, ent->new_address)) {
- /* This is a rule like *.example.com example.com, and we just got
- * "example.com" */
+ /* This is a rule like "rewrite *.example.com to example.com", and we
+ * just got "example.com". Instead of calling it an infinite loop,
+ * call it complete. */
goto done;
}
-
exact_match = 1;
}
if (!ent || !ent->new_address) {
+ /* We still have no match at all. We're done! */
goto done;
}
+ /* Check wither the flags we were passed tell us not to use this
+ * mapping. */
switch (ent->source) {
case ADDRMAPSRC_DNS:
{
@@ -431,6 +440,8 @@ addressmap_rewrite(char *address, size_t maxlen,
goto done;
}
+ /* Now fill in the address with the new address. That might be via
+ * appending some new stuff to the end, or via just replacing it. */
if (ent->dst_wildcard && !exact_match) {
strlcat(address, ".", maxlen);
strlcat(address, ent->new_address, maxlen);
@@ -438,6 +449,7 @@ addressmap_rewrite(char *address, size_t maxlen,
strlcpy(address, ent->new_address, maxlen);
}
+ /* Is this now a .exit address? If so, remember where we got it.*/
if (!strcmpend(address, ".exit") &&
strcmpend(addr_orig, ".exit") &&
exit_source == ADDRMAPSRC_NONE) {
diff --git a/src/or/bridges.c b/src/or/bridges.c
new file mode 100644
index 0000000000..0b4588307c
--- /dev/null
+++ b/src/or/bridges.c
@@ -0,0 +1,881 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bridges.c
+ * \brief Code to manage bridges and bridge selection.
+ *
+ * Bridges are fixed entry nodes, used for censorship circumvention.
+ **/
+
+#include "or.h"
+#include "bridges.h"
+#include "circuitbuild.h"
+#include "config.h"
+#include "connection.h"
+#include "directory.h"
+#include "entrynodes.h"
+#include "nodelist.h"
+#include "policies.h"
+#include "router.h"
+#include "routerlist.h"
+#include "routerset.h"
+#include "transports.h"
+
+/** Information about a configured bridge. Currently this just matches the
+ * ones in the torrc file, but one day we may be able to learn about new
+ * bridges on our own, and remember them in the state file. */
+struct bridge_info_t {
+ /** Address and port of the bridge, as configured by the user.*/
+ tor_addr_port_t addrport_configured;
+ /** Address of the bridge. */
+ tor_addr_t addr;
+ /** TLS port for the bridge. */
+ uint16_t port;
+ /** Boolean: We are re-parsing our bridge list, and we are going to remove
+ * this one if we don't find it in the list of configured bridges. */
+ unsigned marked_for_removal : 1;
+ /** Expected identity digest, or all zero bytes if we don't know what the
+ * digest should be. */
+ char identity[DIGEST_LEN];
+
+ /** Name of pluggable transport protocol taken from its config line. */
+ char *transport_name;
+
+ /** When should we next try to fetch a descriptor for this bridge? */
+ download_status_t fetch_status;
+
+ /** A smartlist of k=v values to be passed to the SOCKS proxy, if
+ transports are used for this bridge. */
+ smartlist_t *socks_args;
+};
+
+static void bridge_free(bridge_info_t *bridge);
+
+/** 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
+ * in this list does not necessarily correspond to the order of bridges
+ * in the torrc. */
+static smartlist_t *bridge_list = NULL;
+
+/** Mark every entry of the bridge list to be removed on our next call to
+ * sweep_bridge_list unless it has first been un-marked. */
+void
+mark_bridge_list(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b,
+ b->marked_for_removal = 1);
+}
+
+/** Remove every entry of the bridge list that was marked with
+ * mark_bridge_list if it has not subsequently been un-marked. */
+void
+sweep_bridge_list(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
+ if (b->marked_for_removal) {
+ SMARTLIST_DEL_CURRENT(bridge_list, b);
+ bridge_free(b);
+ }
+ } SMARTLIST_FOREACH_END(b);
+}
+
+/** Initialize the bridge list to empty, creating it if needed. */
+static void
+clear_bridge_list(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b));
+ smartlist_clear(bridge_list);
+}
+
+/** Free the bridge <b>bridge</b>. */
+static void
+bridge_free(bridge_info_t *bridge)
+{
+ if (!bridge)
+ return;
+
+ tor_free(bridge->transport_name);
+ if (bridge->socks_args) {
+ SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s));
+ smartlist_free(bridge->socks_args);
+ }
+
+ tor_free(bridge);
+}
+
+/** Return a list of all the configured bridges, as bridge_info_t pointers. */
+const smartlist_t *
+bridge_list_get(void)
+{
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+ return bridge_list;
+}
+
+/**
+ * Given a <b>bridge</b>, return a pointer to its RSA identity digest, or
+ * NULL if we don't know one for it.
+ */
+const uint8_t *
+bridge_get_rsa_id_digest(const bridge_info_t *bridge)
+{
+ tor_assert(bridge);
+ if (tor_digest_is_zero(bridge->identity))
+ return NULL;
+ else
+ return (const uint8_t *) bridge->identity;
+}
+
+/**
+ * Given a <b>bridge</b>, return a pointer to its configured addr:port
+ * combination.
+ */
+const tor_addr_port_t *
+bridge_get_addr_port(const bridge_info_t *bridge)
+{
+ tor_assert(bridge);
+ return &bridge->addrport_configured;
+}
+
+/** If we have a bridge configured whose digest matches <b>digest</b>, or a
+ * bridge with no known digest whose address matches any of the
+ * tor_addr_port_t's in <b>orports</b>, return that bridge. Else return
+ * NULL. */
+static bridge_info_t *
+get_configured_bridge_by_orports_digest(const char *digest,
+ const smartlist_t *orports)
+{
+ if (!bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
+ {
+ if (tor_digest_is_zero(bridge->identity)) {
+ SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap)
+ {
+ if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 &&
+ bridge->port == ap->port)
+ return bridge;
+ }
+ SMARTLIST_FOREACH_END(ap);
+ }
+ if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ }
+ SMARTLIST_FOREACH_END(bridge);
+ return NULL;
+}
+
+/** If we have a bridge configured whose digest matches <b>digest</b>, or a
+ * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
+ * return that bridge. Else return NULL. If <b>digest</b> is NULL, check for
+ * address/port matches only. */
+bridge_info_t *
+get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest)
+{
+ if (!bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
+ {
+ if ((tor_digest_is_zero(bridge->identity) || digest == NULL) &&
+ !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
+ bridge->port == port)
+ return bridge;
+ if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ }
+ SMARTLIST_FOREACH_END(bridge);
+ return NULL;
+}
+
+/**
+ * As get_configured_bridge_by_addr_port, but require that the
+ * address match <b>addr</b>:<b>port</b>, and that the ID digest match
+ * <b>digest</b>. (The other function will ignore the address if the
+ * digest matches.)
+ */
+bridge_info_t *
+get_configured_bridge_by_exact_addr_port_digest(const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest)
+{
+ if (!bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
+ if (!tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
+ bridge->port == port) {
+
+ if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ else if (!digest || tor_digest_is_zero(bridge->identity))
+ return bridge;
+ }
+
+ } SMARTLIST_FOREACH_END(bridge);
+ return NULL;
+}
+
+/** If we have a bridge configured whose digest matches <b>digest</b>, or a
+ * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
+ * return 1. Else return 0. If <b>digest</b> is NULL, check for
+ * address/port matches only. */
+int
+addr_is_a_configured_bridge(const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest)
+{
+ tor_assert(addr);
+ return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0;
+}
+
+/** If we have a bridge configured whose digest matches
+ * <b>ei->identity_digest</b>, or a bridge with no known digest whose address
+ * matches <b>ei->addr</b>:<b>ei->port</b>, return 1. Else return 0.
+ * If <b>ei->onion_key</b> is NULL, check for address/port matches only. */
+int
+extend_info_is_a_configured_bridge(const extend_info_t *ei)
+{
+ const char *digest = ei->onion_key ? ei->identity_digest : NULL;
+ return addr_is_a_configured_bridge(&ei->addr, ei->port, digest);
+}
+
+/** Wrapper around get_configured_bridge_by_addr_port_digest() to look
+ * it up via router descriptor <b>ri</b>. */
+static bridge_info_t *
+get_configured_bridge_by_routerinfo(const routerinfo_t *ri)
+{
+ bridge_info_t *bi = NULL;
+ smartlist_t *orports = router_get_all_orports(ri);
+ bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest,
+ orports);
+ SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
+ smartlist_free(orports);
+ return bi;
+}
+
+/** Return 1 if <b>ri</b> is one of our known bridges, else 0. */
+int
+routerinfo_is_a_configured_bridge(const routerinfo_t *ri)
+{
+ return get_configured_bridge_by_routerinfo(ri) ? 1 : 0;
+}
+
+/** Return 1 if <b>node</b> is one of our configured bridges, else 0. */
+int
+node_is_a_configured_bridge(const node_t *node)
+{
+ int retval = 0;
+ smartlist_t *orports = node_get_all_orports(node);
+ retval = get_configured_bridge_by_orports_digest(node->identity,
+ orports) != NULL;
+ SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
+ smartlist_free(orports);
+ return retval;
+}
+
+/** We made a connection to a router at <b>addr</b>:<b>port</b>
+ * without knowing its digest. Its digest turned out to be <b>digest</b>.
+ * If it was a bridge, and we still don't know its digest, record it.
+ */
+void
+learned_router_identity(const tor_addr_t *addr, uint16_t port,
+ const char *digest,
+ const ed25519_public_key_t *ed_id)
+{
+ // XXXX prop220 use ed_id here, once there is some way to specify
+ (void)ed_id;
+ int learned = 0;
+ bridge_info_t *bridge =
+ get_configured_bridge_by_exact_addr_port_digest(addr, port, digest);
+ if (bridge && tor_digest_is_zero(bridge->identity)) {
+ memcpy(bridge->identity, digest, DIGEST_LEN);
+ learned = 1;
+ }
+ /* XXXX prop220 remember bridge ed25519 identities -- add a field */
+#if 0
+ if (bridge && ed_id &&
+ ed25519_public_key_is_zero(&bridge->ed25519_identity) &&
+ !ed25519_public_key_is_zero(ed_id)) {
+ memcpy(&bridge->ed25519_identity, ed_id, sizeof(*ed_id));
+ learned = 1;
+ }
+#endif
+ if (learned) {
+ char *transport_info = NULL;
+ const char *transport_name =
+ find_transport_name_by_bridge_addrport(addr, port);
+ if (transport_name)
+ tor_asprintf(&transport_info, " (with transport '%s')", transport_name);
+
+ // XXXX prop220 log both fingerprints.
+ log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.",
+ hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port),
+ transport_info ? transport_info : "");
+ tor_free(transport_info);
+ entry_guard_learned_bridge_identity(&bridge->addrport_configured,
+ (const uint8_t *)digest);
+ }
+}
+
+/** Return true if <b>bridge</b> has the same identity digest as
+ * <b>digest</b>. If <b>digest</b> is NULL, it matches
+ * bridges with unspecified identity digests. */
+static int
+bridge_has_digest(const bridge_info_t *bridge, const char *digest)
+{
+ if (digest)
+ return tor_memeq(digest, bridge->identity, DIGEST_LEN);
+ else
+ return tor_digest_is_zero(bridge->identity);
+}
+
+/** We are about to add a new bridge at <b>addr</b>:<b>port</b>, with optional
+ * <b>digest</b> and <b>transport_name</b>. Mark for removal any previously
+ * existing bridge with the same address and port, and warn the user as
+ * appropriate.
+ */
+static void
+bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port,
+ const char *digest, const char *transport_name)
+{
+ /* Iterate the already-registered bridge list:
+
+ If you find a bridge with the same adress and port, mark it for
+ removal. It doesn't make sense to have two active bridges with
+ the same IP:PORT. If the bridge in question has a different
+ digest or transport than <b>digest</b>/<b>transport_name</b>,
+ it's probably a misconfiguration and we should warn the user.
+ */
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
+ if (bridge->marked_for_removal)
+ continue;
+
+ if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) {
+
+ bridge->marked_for_removal = 1;
+
+ if (!bridge_has_digest(bridge, digest) ||
+ strcmp_opt(bridge->transport_name, transport_name)) {
+ /* warn the user */
+ char *bridge_description_new, *bridge_description_old;
+ tor_asprintf(&bridge_description_new, "%s:%s:%s",
+ fmt_addrport(addr, port),
+ digest ? hex_str(digest, DIGEST_LEN) : "",
+ transport_name ? transport_name : "");
+ tor_asprintf(&bridge_description_old, "%s:%s:%s",
+ fmt_addrport(&bridge->addr, bridge->port),
+ tor_digest_is_zero(bridge->identity) ?
+ "" : hex_str(bridge->identity,DIGEST_LEN),
+ bridge->transport_name ? bridge->transport_name : "");
+
+ log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict"
+ " with the already registered bridge '%s'. We will discard"
+ " the old bridge and keep '%s'. If this is not what you"
+ " wanted, please change your configuration file accordingly.",
+ bridge_description_new, bridge_description_old,
+ bridge_description_new);
+
+ tor_free(bridge_description_new);
+ tor_free(bridge_description_old);
+ }
+ }
+ } SMARTLIST_FOREACH_END(bridge);
+}
+
+/** Return True if we have a bridge that uses a transport with name
+ * <b>transport_name</b>. */
+MOCK_IMPL(int,
+transport_is_needed, (const char *transport_name))
+{
+ if (!bridge_list)
+ return 0;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
+ if (bridge->transport_name &&
+ !strcmp(bridge->transport_name, transport_name))
+ return 1;
+ } SMARTLIST_FOREACH_END(bridge);
+
+ return 0;
+}
+
+/** Register the bridge information in <b>bridge_line</b> to the
+ * bridge subsystem. Steals reference of <b>bridge_line</b>. */
+void
+bridge_add_from_config(bridge_line_t *bridge_line)
+{
+ bridge_info_t *b;
+
+ // XXXX prop220 add a way to specify ed25519 ID to bridge_line_t.
+
+ { /* Log the bridge we are about to register: */
+ log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)",
+ fmt_addrport(&bridge_line->addr, bridge_line->port),
+ bridge_line->transport_name ?
+ bridge_line->transport_name : "no transport",
+ tor_digest_is_zero(bridge_line->digest) ?
+ "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN));
+
+ if (bridge_line->socks_args) { /* print socks arguments */
+ int i = 0;
+
+ tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+
+ log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:",
+ smartlist_len(bridge_line->socks_args));
+ SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg,
+ log_debug(LD_CONFIG, "%d: %s", ++i, arg));
+ }
+ }
+
+ bridge_resolve_conflicts(&bridge_line->addr,
+ bridge_line->port,
+ bridge_line->digest,
+ bridge_line->transport_name);
+
+ b = tor_malloc_zero(sizeof(bridge_info_t));
+ tor_addr_copy(&b->addrport_configured.addr, &bridge_line->addr);
+ b->addrport_configured.port = bridge_line->port;
+ tor_addr_copy(&b->addr, &bridge_line->addr);
+ b->port = bridge_line->port;
+ memcpy(b->identity, bridge_line->digest, DIGEST_LEN);
+ if (bridge_line->transport_name)
+ b->transport_name = bridge_line->transport_name;
+ b->fetch_status.schedule = DL_SCHED_BRIDGE;
+ b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL;
+ b->socks_args = bridge_line->socks_args;
+ if (!bridge_list)
+ bridge_list = smartlist_new();
+
+ tor_free(bridge_line); /* Deallocate bridge_line now. */
+
+ smartlist_add(bridge_list, b);
+}
+
+/** If <b>digest</b> is one of our known bridges, return it. */
+bridge_info_t *
+find_bridge_by_digest(const char *digest)
+{
+ if (! bridge_list)
+ return NULL;
+ SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge,
+ {
+ if (tor_memeq(bridge->identity, digest, DIGEST_LEN))
+ return bridge;
+ });
+ return NULL;
+}
+
+/** Given the <b>addr</b> and <b>port</b> of a bridge, if that bridge
+ * supports a pluggable transport, return its name. Otherwise, return
+ * NULL. */
+const char *
+find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+{
+ if (!bridge_list)
+ return NULL;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
+ if (tor_addr_eq(&bridge->addr, addr) &&
+ (bridge->port == port))
+ return bridge->transport_name;
+ } SMARTLIST_FOREACH_END(bridge);
+
+ return NULL;
+}
+
+/** If <b>addr</b> and <b>port</b> match the address and port of a
+ * bridge of ours that uses pluggable transports, place its transport
+ * in <b>transport</b>.
+ *
+ * Return 0 on success (found a transport, or found a bridge with no
+ * transport, or found no bridge); return -1 if we should be using a
+ * transport, but the transport could not be found.
+ */
+int
+get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
+ const transport_t **transport)
+{
+ *transport = NULL;
+ if (!bridge_list)
+ return 0;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
+ if (tor_addr_eq(&bridge->addr, addr) &&
+ (bridge->port == port)) { /* bridge matched */
+ if (bridge->transport_name) { /* it also uses pluggable transports */
+ *transport = transport_get_by_name(bridge->transport_name);
+ if (*transport == NULL) { /* it uses pluggable transports, but
+ the transport could not be found! */
+ return -1;
+ }
+ return 0;
+ } else { /* bridge matched, but it doesn't use transports. */
+ break;
+ }
+ }
+ } SMARTLIST_FOREACH_END(bridge);
+
+ *transport = NULL;
+ return 0;
+}
+
+/** Return a smartlist containing all the SOCKS arguments that we
+ * should pass to the SOCKS proxy. */
+const smartlist_t *
+get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+{
+ bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr,
+ port,
+ NULL);
+ return bridge ? bridge->socks_args : NULL;
+}
+
+/** We need to ask <b>bridge</b> for its server descriptor. */
+static void
+launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)
+{
+ const or_options_t *options = get_options();
+ circuit_guard_state_t *guard_state = NULL;
+
+ if (connection_get_by_type_addr_port_purpose(
+ CONN_TYPE_DIR, &bridge->addr, bridge->port,
+ DIR_PURPOSE_FETCH_SERVERDESC))
+ return; /* it's already on the way */
+
+ if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
+ download_status_mark_impossible(&bridge->fetch_status);
+ log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
+ safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
+ return;
+ }
+
+ /* Until we get a descriptor for the bridge, we only know one address for
+ * it. */
+ if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
+ FIREWALL_OR_CONNECTION, 0, 0)) {
+ log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a "
+ "bridge, but that bridge is not reachable through our "
+ "firewall.");
+ return;
+ }
+
+ guard_state = get_guard_state_for_bridge_desc_fetch(bridge->identity);
+
+ directory_initiate_command(&bridge->addr, bridge->port,
+ NULL, 0, /*no dirport*/
+ bridge->identity,
+ DIR_PURPOSE_FETCH_SERVERDESC,
+ ROUTER_PURPOSE_BRIDGE,
+ DIRIND_ONEHOP, "authority.z", NULL, 0, 0,
+ guard_state);
+}
+
+/** Fetching the bridge descriptor from the bridge authority returned a
+ * "not found". Fall back to trying a direct fetch. */
+void
+retry_bridge_descriptor_fetch_directly(const char *digest)
+{
+ bridge_info_t *bridge = find_bridge_by_digest(digest);
+ if (!bridge)
+ return; /* not found? oh well. */
+
+ launch_direct_bridge_descriptor_fetch(bridge);
+}
+
+/** For each bridge in our list for which we don't currently have a
+ * descriptor, fetch a new copy of its descriptor -- either directly
+ * from the bridge or via a bridge authority. */
+void
+fetch_bridge_descriptors(const or_options_t *options, time_t now)
+{
+ int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO);
+ int ask_bridge_directly;
+ int can_use_bridge_authority;
+
+ if (!bridge_list)
+ return;
+
+ /* If we still have unconfigured managed proxies, don't go and
+ connect to a bridge. */
+ if (pt_proxies_configuration_pending())
+ return;
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
+ {
+ if (!download_status_is_ready(&bridge->fetch_status, now,
+ IMPOSSIBLE_TO_DOWNLOAD))
+ continue; /* don't bother, no need to retry yet */
+ if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
+ download_status_mark_impossible(&bridge->fetch_status);
+ log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
+ safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
+ continue;
+ }
+
+ /* schedule another fetch as if this one will fail, in case it does */
+ download_status_failed(&bridge->fetch_status, 0);
+
+ can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) &&
+ num_bridge_auths;
+ ask_bridge_directly = !can_use_bridge_authority ||
+ !options->UpdateBridgesFromAuthority;
+ log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)",
+ ask_bridge_directly, tor_digest_is_zero(bridge->identity),
+ !options->UpdateBridgesFromAuthority, !num_bridge_auths);
+
+ if (ask_bridge_directly &&
+ !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
+ FIREWALL_OR_CONNECTION, 0,
+ 0)) {
+ log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our "
+ "firewall policy. %s.",
+ fmt_addrport(&bridge->addr, bridge->port),
+ can_use_bridge_authority ?
+ "Asking bridge authority instead" : "Skipping");
+ if (can_use_bridge_authority)
+ ask_bridge_directly = 0;
+ else
+ continue;
+ }
+
+ if (ask_bridge_directly) {
+ /* we need to ask the bridge itself for its descriptor. */
+ launch_direct_bridge_descriptor_fetch(bridge);
+ } else {
+ /* We have a digest and we want to ask an authority. We could
+ * combine all the requests into one, but that may give more
+ * hints to the bridge authority than we want to give. */
+ char resource[10 + HEX_DIGEST_LEN];
+ memcpy(resource, "fp/", 3);
+ base16_encode(resource+3, HEX_DIGEST_LEN+1,
+ bridge->identity, DIGEST_LEN);
+ memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3);
+ log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.",
+ resource);
+ directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC,
+ ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY);
+ }
+ }
+ SMARTLIST_FOREACH_END(bridge);
+}
+
+/** If our <b>bridge</b> is configured to be a different address than
+ * the bridge gives in <b>node</b>, rewrite the routerinfo
+ * we received to use the address we meant to use. Now we handle
+ * multihomed bridges better.
+ */
+static void
+rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node)
+{
+ /* XXXX move this function. */
+ /* XXXX overridden addresses should really live in the node_t, so that the
+ * routerinfo_t and the microdesc_t can be immutable. But we can only
+ * do that safely if we know that no function that connects to an OR
+ * does so through an address from any source other than node_get_addr().
+ */
+ tor_addr_t addr;
+ const or_options_t *options = get_options();
+
+ if (node->ri) {
+ routerinfo_t *ri = node->ri;
+ tor_addr_from_ipv4h(&addr, ri->addr);
+
+ if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
+ bridge->port == ri->or_port) ||
+ (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) &&
+ bridge->port == ri->ipv6_orport)) {
+ /* they match, so no need to do anything */
+ } else {
+ if (tor_addr_family(&bridge->addr) == AF_INET) {
+ ri->addr = tor_addr_to_ipv4h(&bridge->addr);
+ ri->or_port = bridge->port;
+ log_info(LD_DIR,
+ "Adjusted bridge routerinfo for '%s' to match configured "
+ "address %s:%d.",
+ ri->nickname, fmt_addr32(ri->addr), ri->or_port);
+ } else if (tor_addr_family(&bridge->addr) == AF_INET6) {
+ tor_addr_copy(&ri->ipv6_addr, &bridge->addr);
+ ri->ipv6_orport = bridge->port;
+ log_info(LD_DIR,
+ "Adjusted bridge routerinfo for '%s' to match configured "
+ "address %s.",
+ ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport));
+ } else {
+ log_err(LD_BUG, "Address family not supported: %d.",
+ tor_addr_family(&bridge->addr));
+ return;
+ }
+ }
+
+ if (options->ClientPreferIPv6ORPort == -1) {
+ /* Mark which address to use based on which bridge_t we got. */
+ node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 &&
+ !tor_addr_is_null(&node->ri->ipv6_addr));
+ } else {
+ /* Mark which address to use based on user preference */
+ node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) &&
+ !tor_addr_is_null(&node->ri->ipv6_addr));
+ }
+
+ /* XXXipv6 we lack support for falling back to another address for
+ the same relay, warn the user */
+ if (!tor_addr_is_null(&ri->ipv6_addr)) {
+ tor_addr_port_t ap;
+ node_get_pref_orport(node, &ap);
+ log_notice(LD_CONFIG,
+ "Bridge '%s' has both an IPv4 and an IPv6 address. "
+ "Will prefer using its %s address (%s) based on %s.",
+ ri->nickname,
+ node->ipv6_preferred ? "IPv6" : "IPv4",
+ fmt_addrport(&ap.addr, ap.port),
+ options->ClientPreferIPv6ORPort == -1 ?
+ "the configured Bridge address" :
+ "ClientPreferIPv6ORPort");
+ }
+ }
+ if (node->rs) {
+ routerstatus_t *rs = node->rs;
+ tor_addr_from_ipv4h(&addr, rs->addr);
+
+ if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
+ bridge->port == rs->or_port) {
+ /* they match, so no need to do anything */
+ } else {
+ rs->addr = tor_addr_to_ipv4h(&bridge->addr);
+ rs->or_port = bridge->port;
+ log_info(LD_DIR,
+ "Adjusted bridge routerstatus for '%s' to match "
+ "configured address %s.",
+ rs->nickname, fmt_addrport(&bridge->addr, rs->or_port));
+ }
+ }
+}
+
+/** We just learned a descriptor for a bridge. See if that
+ * digest is in our entry guard list, and add it if not. */
+void
+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;
+ bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri);
+ time_t now = time(NULL);
+ router_set_status(ri->cache_info.identity_digest, 1);
+
+ 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)
+ download_status_reset(&bridge->fetch_status);
+
+ node = node_get_mutable_by_id(ri->cache_info.identity_digest);
+ tor_assert(node);
+ rewrite_node_address_for_bridge(bridge, node);
+ if (tor_digest_is_zero(bridge->identity)) {
+ memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN);
+ log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d",
+ hex_str(bridge->identity, DIGEST_LEN),
+ fmt_and_decorate_addr(&bridge->addr),
+ (int) bridge->port);
+ }
+ entry_guard_learned_bridge_identity(&bridge->addrport_configured,
+ (const uint8_t*)ri->cache_info.identity_digest);
+
+ 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 (first) {
+ routerlist_retry_directory_downloads(now);
+ }
+ }
+ }
+}
+
+/** 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))
+{
+ smartlist_t *result = NULL;
+ char *digest_tmp;
+
+ if (get_options()->UseBridges && bridge_list) {
+ result = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
+ digest_tmp = tor_malloc(DIGEST_LEN);
+ memcpy(digest_tmp, b->identity, DIGEST_LEN);
+ smartlist_add(result, digest_tmp);
+ } SMARTLIST_FOREACH_END(b);
+ }
+
+ return result;
+}
+
+/** Get the download status for a bridge descriptor given its identity */
+MOCK_IMPL(download_status_t *,
+get_bridge_dl_status_by_id, (const char *digest))
+{
+ download_status_t *dl = NULL;
+
+ if (digest && get_options()->UseBridges && bridge_list) {
+ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
+ if (tor_memeq(digest, b->identity, DIGEST_LEN)) {
+ dl = &(b->fetch_status);
+ break;
+ }
+ } SMARTLIST_FOREACH_END(b);
+ }
+
+ return dl;
+}
+
+/** Release all storage held in bridges.c */
+void
+bridges_free_all(void)
+{
+ clear_bridge_list();
+ smartlist_free(bridge_list);
+ bridge_list = NULL;
+}
+
diff --git a/src/or/bridges.h b/src/or/bridges.h
new file mode 100644
index 0000000000..27ea5e197c
--- /dev/null
+++ b/src/or/bridges.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file bridges.h
+ * \brief Header file for circuitbuild.c.
+ **/
+
+#ifndef TOR_BRIDGES_H
+#define TOR_BRIDGES_H
+
+struct bridge_line_t;
+
+/* Opaque handle to a configured bridge */
+typedef struct bridge_info_t bridge_info_t;
+
+void mark_bridge_list(void);
+void sweep_bridge_list(void);
+const smartlist_t *bridge_list_get(void);
+bridge_info_t *find_bridge_by_digest(const char *digest);
+const uint8_t *bridge_get_rsa_id_digest(const bridge_info_t *bridge);
+const tor_addr_port_t * bridge_get_addr_port(const bridge_info_t *bridge);
+bridge_info_t *get_configured_bridge_by_addr_port_digest(
+ const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest);
+bridge_info_t *get_configured_bridge_by_exact_addr_port_digest(
+ const tor_addr_t *addr,
+ uint16_t port,
+ const char *digest);
+
+int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port,
+ const char *digest);
+int extend_info_is_a_configured_bridge(const extend_info_t *ei);
+int routerinfo_is_a_configured_bridge(const routerinfo_t *ri);
+int node_is_a_configured_bridge(const node_t *node);
+void learned_router_identity(const tor_addr_t *addr, uint16_t port,
+ const char *digest,
+ const ed25519_public_key_t *ed_id);
+
+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);
+
+int any_bridges_dont_support_microdescriptors(void);
+
+const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr,
+ uint16_t port);
+struct transport_t;
+int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
+ const struct transport_t **transport);
+
+MOCK_DECL(int, transport_is_needed, (const char *transport_name));
+int validate_pluggable_transports_config(void);
+
+MOCK_DECL(smartlist_t *, list_bridge_identities, (void));
+MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id,
+ (const char *digest));
+
+void bridges_free_all(void);
+
+#endif
+
diff --git a/src/or/buffers.c b/src/or/buffers.c
index 89382d1d8e..201778e301 100644
--- a/src/or/buffers.c
+++ b/src/or/buffers.c
@@ -281,6 +281,7 @@ buf_pullup(buf_t *buf, size_t bytes)
}
#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)
{
@@ -292,6 +293,53 @@ buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz)
*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. */
@@ -562,6 +610,11 @@ read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof,
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;
@@ -619,6 +672,11 @@ read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf)
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;
@@ -813,6 +871,11 @@ write_to_buf(const char *string, size_t string_len, buf_t *buf)
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))
@@ -962,6 +1025,12 @@ 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;
@@ -1090,6 +1159,52 @@ buf_find_string_offset(const buf_t *buf, const char *s, size_t n)
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
@@ -1115,9 +1230,10 @@ fetch_from_buf_http(buf_t *buf,
char **body_out, size_t *body_used, size_t max_bodylen,
int force_complete)
{
- char *headers, *p;
- size_t headerlen, bodylen, contentlen;
+ char *headers;
+ size_t headerlen, bodylen, contentlen=0;
int crlf_offset;
+ int r;
check();
if (!buf->head)
@@ -1153,17 +1269,12 @@ fetch_from_buf_http(buf_t *buf,
return -1;
}
-#define CONTENT_LENGTH "\r\nContent-Length: "
- p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH);
- if (p) {
- int i;
- i = atoi(p+strlen(CONTENT_LENGTH));
- if (i < 0) {
- log_warn(LD_PROTOCOL, "Content-Length is less than zero; it looks like "
- "someone is trying to crash us.");
- return -1;
- }
- contentlen = i;
+ 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) {
@@ -1176,7 +1287,11 @@ fetch_from_buf_http(buf_t *buf,
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);
diff --git a/src/or/buffers.h b/src/or/buffers.h
index 52b21d5885..bb53b3bbff 100644
--- a/src/or/buffers.h
+++ b/src/or/buffers.h
@@ -64,7 +64,10 @@ void assert_buf_ok(buf_t *buf);
#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);
#define DEBUG_CHUNK_ALLOC
@@ -97,5 +100,10 @@ struct buf_t {
};
#endif
+#ifdef BUFFERS_PRIVATE
+STATIC int buf_http_find_content_length(const char *headers, size_t headerlen,
+ size_t *result_out);
+#endif
+
#endif
diff --git a/src/or/channel.c b/src/or/channel.c
index f547aea1b3..45f1602ab2 100644
--- a/src/or/channel.c
+++ b/src/or/channel.c
@@ -733,27 +733,62 @@ channel_find_by_global_id(uint64_t global_identifier)
return rv;
}
+/** Return true iff <b>chan</b> matches <b>rsa_id_digest</b> and <b>ed_id</b>.
+ * as its identity keys. If either is NULL, do not check for a match. */
+static int
+channel_remote_identity_matches(const channel_t *chan,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id)
+{
+ if (BUG(!chan))
+ return 0;
+ if (rsa_id_digest) {
+ if (tor_memneq(rsa_id_digest, chan->identity_digest, DIGEST_LEN))
+ return 0;
+ }
+ if (ed_id) {
+ if (tor_memneq(ed_id->pubkey, chan->ed25519_identity.pubkey,
+ ED25519_PUBKEY_LEN))
+ return 0;
+ }
+ return 1;
+}
+
/**
- * Find channel by digest of the remote endpoint
+ * Find channel by RSA/Ed25519 identity of of the remote endpoint
+ *
+ * This function looks up a channel by the digest of its remote endpoint's RSA
+ * identity key. If <b>ed_id</b> is provided and nonzero, only a channel
+ * matching the <b>ed_id</b> will be returned.
*
- * This function looks up a channel by the digest of its remote endpoint in
- * the channel digest map. It's possible that more than one channel to a
- * given endpoint exists. Use channel_next_with_digest() to walk the list.
+ * It's possible that more than one channel to a given endpoint exists. Use
+ * channel_next_with_rsa_identity() to walk the list of channels; make sure
+ * to test for Ed25519 identity match too (as appropriate)
*/
-
channel_t *
-channel_find_by_remote_digest(const char *identity_digest)
+channel_find_by_remote_identity(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id)
{
channel_t *rv = NULL;
channel_idmap_entry_t *ent, search;
- tor_assert(identity_digest);
+ tor_assert(rsa_id_digest); /* For now, we require that every channel have
+ * an RSA identity, and that every lookup
+ * contain an RSA identity */
+ if (ed_id && ed25519_public_key_is_zero(ed_id)) {
+ /* Treat zero as meaning "We don't care about the presence or absence of
+ * an Ed key", not "There must be no Ed key". */
+ ed_id = NULL;
+ }
- memcpy(search.digest, identity_digest, DIGEST_LEN);
+ memcpy(search.digest, rsa_id_digest, DIGEST_LEN);
ent = HT_FIND(channel_idmap, &channel_identity_map, &search);
if (ent) {
rv = TOR_LIST_FIRST(&ent->channel_list);
}
+ while (rv && ! channel_remote_identity_matches(rv, rsa_id_digest, ed_id)) {
+ rv = channel_next_with_rsa_identity(rv);
+ }
return rv;
}
@@ -766,7 +801,7 @@ channel_find_by_remote_digest(const char *identity_digest)
*/
channel_t *
-channel_next_with_digest(channel_t *chan)
+channel_next_with_rsa_identity(channel_t *chan)
{
tor_assert(chan);
@@ -1433,10 +1468,10 @@ channel_clear_identity_digest(channel_t *chan)
* This function sets the identity digest of the remote endpoint for a
* channel; this is intended for use by the lower layer.
*/
-
void
channel_set_identity_digest(channel_t *chan,
- const char *identity_digest)
+ const char *identity_digest,
+ const ed25519_public_key_t *ed_identity)
{
int was_in_digest_map, should_be_in_digest_map, state_not_in_map;
@@ -1475,6 +1510,11 @@ channel_set_identity_digest(channel_t *chan,
memset(chan->identity_digest, 0,
sizeof(chan->identity_digest));
}
+ if (ed_identity) {
+ memcpy(&chan->ed25519_identity, ed_identity, sizeof(*ed_identity));
+ } else {
+ memset(&chan->ed25519_identity, 0, sizeof(*ed_identity));
+ }
/* Put it in the digest map if we should */
if (should_be_in_digest_map)
@@ -1738,7 +1778,7 @@ channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q)
rv = get_cell_network_size(chan->wide_circ_ids);
break;
default:
- tor_assert(1);
+ tor_assert_nonfatal_unreached_once();
}
return rv;
@@ -1838,45 +1878,58 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q)
}
}
-/**
- * Write a cell to a channel
+/** Write a generic cell type to a channel
*
- * Write a fixed-length cell to a channel using the write_cell() method.
- * This is equivalent to the pre-channels connection_or_write_cell_to_buf();
- * it is called by the transport-independent code to deliver a cell to a
- * channel for transmission.
+ * Write a generic cell to a channel. It is called by channel_write_cell(),
+ * channel_write_var_cell() and channel_write_packed_cell() in order to reduce
+ * code duplication. Notice that it takes cell as pointer of type void,
+ * this can be dangerous because no type check is performed.
*/
void
-channel_write_cell(channel_t *chan, cell_t *cell)
+channel_write_cell_generic_(channel_t *chan, const char *cell_type,
+ void *cell, cell_queue_entry_t *q)
{
- cell_queue_entry_t q;
tor_assert(chan);
tor_assert(cell);
if (CHANNEL_IS_CLOSING(chan)) {
- log_debug(LD_CHANNEL, "Discarding cell_t %p on closing channel %p with "
- "global ID "U64_FORMAT, cell, chan,
+ log_debug(LD_CHANNEL, "Discarding %c %p on closing channel %p with "
+ "global ID "U64_FORMAT, *cell_type, cell, chan,
U64_PRINTF_ARG(chan->global_identifier));
tor_free(cell);
return;
}
-
log_debug(LD_CHANNEL,
- "Writing cell_t %p to channel %p with global ID "
- U64_FORMAT,
+ "Writing %c %p to channel %p with global ID "
+ U64_FORMAT, *cell_type,
cell, chan, U64_PRINTF_ARG(chan->global_identifier));
- q.type = CELL_QUEUE_FIXED;
- q.u.fixed.cell = cell;
- channel_write_cell_queue_entry(chan, &q);
-
+ channel_write_cell_queue_entry(chan, q);
/* Update the queue size estimate */
channel_update_xmit_queue_size(chan);
}
/**
+ * Write a cell to a channel
+ *
+ * Write a fixed-length cell to a channel using the write_cell() method.
+ * This is equivalent to the pre-channels connection_or_write_cell_to_buf();
+ * it is called by the transport-independent code to deliver a cell to a
+ * channel for transmission.
+ */
+
+void
+channel_write_cell(channel_t *chan, cell_t *cell)
+{
+ cell_queue_entry_t q;
+ q.type = CELL_QUEUE_FIXED;
+ q.u.fixed.cell = cell;
+ channel_write_cell_generic_(chan, "cell_t", cell, &q);
+}
+
+/**
* Write a packed cell to a channel
*
* Write a packed cell to a channel using the write_cell() method. This is
@@ -1888,30 +1941,9 @@ void
channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell)
{
cell_queue_entry_t q;
-
- tor_assert(chan);
- tor_assert(packed_cell);
-
- if (CHANNEL_IS_CLOSING(chan)) {
- log_debug(LD_CHANNEL, "Discarding packed_cell_t %p on closing channel %p "
- "with global ID "U64_FORMAT, packed_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
- packed_cell_free(packed_cell);
- return;
- }
-
- log_debug(LD_CHANNEL,
- "Writing packed_cell_t %p to channel %p with global ID "
- U64_FORMAT,
- packed_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
-
q.type = CELL_QUEUE_PACKED;
q.u.packed.packed_cell = packed_cell;
- channel_write_cell_queue_entry(chan, &q);
-
- /* Update the queue size estimate */
- channel_update_xmit_queue_size(chan);
+ channel_write_cell_generic_(chan, "packed_cell_t", packed_cell, &q);
}
/**
@@ -1927,30 +1959,9 @@ void
channel_write_var_cell(channel_t *chan, var_cell_t *var_cell)
{
cell_queue_entry_t q;
-
- tor_assert(chan);
- tor_assert(var_cell);
-
- if (CHANNEL_IS_CLOSING(chan)) {
- log_debug(LD_CHANNEL, "Discarding var_cell_t %p on closing channel %p "
- "with global ID "U64_FORMAT, var_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
- var_cell_free(var_cell);
- return;
- }
-
- log_debug(LD_CHANNEL,
- "Writing var_cell_t %p to channel %p with global ID "
- U64_FORMAT,
- var_cell, chan,
- U64_PRINTF_ARG(chan->global_identifier));
-
q.type = CELL_QUEUE_VAR;
q.u.var.var_cell = var_cell;
- channel_write_cell_queue_entry(chan, &q);
-
- /* Update the queue size estimate */
- channel_update_xmit_queue_size(chan);
+ channel_write_cell_generic_(chan, "var_cell_t", var_cell, &q);
}
/**
@@ -2307,121 +2318,120 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan,
free_q = 0;
handed_off = 0;
- if (1) {
- /* Figure out how big it is for statistical purposes */
- cell_size = channel_get_cell_queue_entry_size(chan, q);
- /*
- * Okay, we have a good queue entry, try to give it to the lower
- * layer.
- */
- switch (q->type) {
- case CELL_QUEUE_FIXED:
- if (q->u.fixed.cell) {
- if (chan->write_cell(chan,
- q->u.fixed.cell)) {
- ++flushed;
- channel_timestamp_xmit(chan);
- ++(chan->n_cells_xmitted);
- chan->n_bytes_xmitted += cell_size;
- free_q = 1;
- handed_off = 1;
- }
- /* Else couldn't write it; leave it on the queue */
- } else {
- /* This shouldn't happen */
- log_info(LD_CHANNEL,
- "Saw broken cell queue entry of type CELL_QUEUE_FIXED "
- "with no cell on channel %p "
- "(global ID " U64_FORMAT ").",
- chan, U64_PRINTF_ARG(chan->global_identifier));
- /* Throw it away */
- free_q = 1;
- handed_off = 0;
- }
- break;
- case CELL_QUEUE_PACKED:
- if (q->u.packed.packed_cell) {
- if (chan->write_packed_cell(chan,
- q->u.packed.packed_cell)) {
- ++flushed;
- channel_timestamp_xmit(chan);
- ++(chan->n_cells_xmitted);
- chan->n_bytes_xmitted += cell_size;
- free_q = 1;
- handed_off = 1;
- }
- /* Else couldn't write it; leave it on the queue */
- } else {
- /* This shouldn't happen */
- log_info(LD_CHANNEL,
- "Saw broken cell queue entry of type CELL_QUEUE_PACKED "
- "with no cell on channel %p "
- "(global ID " U64_FORMAT ").",
- chan, U64_PRINTF_ARG(chan->global_identifier));
- /* Throw it away */
- free_q = 1;
- handed_off = 0;
- }
- break;
- case CELL_QUEUE_VAR:
- if (q->u.var.var_cell) {
- if (chan->write_var_cell(chan,
- q->u.var.var_cell)) {
- ++flushed;
- channel_timestamp_xmit(chan);
- ++(chan->n_cells_xmitted);
- chan->n_bytes_xmitted += cell_size;
- free_q = 1;
- handed_off = 1;
- }
- /* Else couldn't write it; leave it on the queue */
- } else {
- /* This shouldn't happen */
- log_info(LD_CHANNEL,
- "Saw broken cell queue entry of type CELL_QUEUE_VAR "
- "with no cell on channel %p "
- "(global ID " U64_FORMAT ").",
- chan, U64_PRINTF_ARG(chan->global_identifier));
- /* Throw it away */
- free_q = 1;
- handed_off = 0;
- }
- break;
- default:
- /* Unknown type, log and free it */
- log_info(LD_CHANNEL,
- "Saw an unknown cell queue entry type %d on channel %p "
- "(global ID " U64_FORMAT "; ignoring it."
- " Someone should fix this.",
- q->type, chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Figure out how big it is for statistical purposes */
+ cell_size = channel_get_cell_queue_entry_size(chan, q);
+ /*
+ * Okay, we have a good queue entry, try to give it to the lower
+ * layer.
+ */
+ switch (q->type) {
+ case CELL_QUEUE_FIXED:
+ if (q->u.fixed.cell) {
+ if (chan->write_cell(chan,
+ q->u.fixed.cell)) {
+ ++flushed;
+ channel_timestamp_xmit(chan);
+ ++(chan->n_cells_xmitted);
+ chan->n_bytes_xmitted += cell_size;
free_q = 1;
- handed_off = 0;
+ handed_off = 1;
+ }
+ /* Else couldn't write it; leave it on the queue */
+ } else {
+ /* This shouldn't happen */
+ log_info(LD_CHANNEL,
+ "Saw broken cell queue entry of type CELL_QUEUE_FIXED "
+ "with no cell on channel %p "
+ "(global ID " U64_FORMAT ").",
+ chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Throw it away */
+ free_q = 1;
+ handed_off = 0;
}
+ break;
+ case CELL_QUEUE_PACKED:
+ if (q->u.packed.packed_cell) {
+ if (chan->write_packed_cell(chan,
+ q->u.packed.packed_cell)) {
+ ++flushed;
+ channel_timestamp_xmit(chan);
+ ++(chan->n_cells_xmitted);
+ chan->n_bytes_xmitted += cell_size;
+ free_q = 1;
+ handed_off = 1;
+ }
+ /* Else couldn't write it; leave it on the queue */
+ } else {
+ /* This shouldn't happen */
+ log_info(LD_CHANNEL,
+ "Saw broken cell queue entry of type CELL_QUEUE_PACKED "
+ "with no cell on channel %p "
+ "(global ID " U64_FORMAT ").",
+ chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Throw it away */
+ free_q = 1;
+ handed_off = 0;
+ }
+ break;
+ case CELL_QUEUE_VAR:
+ if (q->u.var.var_cell) {
+ if (chan->write_var_cell(chan,
+ q->u.var.var_cell)) {
+ ++flushed;
+ channel_timestamp_xmit(chan);
+ ++(chan->n_cells_xmitted);
+ chan->n_bytes_xmitted += cell_size;
+ free_q = 1;
+ handed_off = 1;
+ }
+ /* Else couldn't write it; leave it on the queue */
+ } else {
+ /* This shouldn't happen */
+ log_info(LD_CHANNEL,
+ "Saw broken cell queue entry of type CELL_QUEUE_VAR "
+ "with no cell on channel %p "
+ "(global ID " U64_FORMAT ").",
+ chan, U64_PRINTF_ARG(chan->global_identifier));
+ /* Throw it away */
+ free_q = 1;
+ handed_off = 0;
+ }
+ break;
+ default:
+ /* Unknown type, log and free it */
+ log_info(LD_CHANNEL,
+ "Saw an unknown cell queue entry type %d on channel %p "
+ "(global ID " U64_FORMAT "; ignoring it."
+ " Someone should fix this.",
+ q->type, chan, U64_PRINTF_ARG(chan->global_identifier));
+ free_q = 1;
+ handed_off = 0;
+ }
+ /*
+ * if free_q is set, we used it and should remove the queue entry;
+ * we have to do the free down here so TOR_SIMPLEQ_REMOVE_HEAD isn't
+ * accessing freed memory
+ */
+ if (free_q) {
+ TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next);
/*
- * if free_q is set, we used it and should remove the queue entry;
- * we have to do the free down here so TOR_SIMPLEQ_REMOVE_HEAD isn't
- * accessing freed memory
+ * ...and we handed a cell off to the lower layer, so we should
+ * update the counters.
*/
- if (free_q) {
- TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next);
- /*
- * ...and we handed a cell off to the lower layer, so we should
- * update the counters.
- */
- ++n_channel_cells_passed_to_lower_layer;
- --n_channel_cells_in_queues;
- n_channel_bytes_passed_to_lower_layer += cell_size;
- n_channel_bytes_in_queues -= cell_size;
- channel_assert_counter_consistency();
- /* Update the channel's queue size too */
- chan->bytes_in_queue -= cell_size;
- /* Finally, free q */
- cell_queue_entry_free(q, handed_off);
- q = NULL;
- }
+ ++n_channel_cells_passed_to_lower_layer;
+ --n_channel_cells_in_queues;
+ n_channel_bytes_passed_to_lower_layer += cell_size;
+ n_channel_bytes_in_queues -= cell_size;
+ channel_assert_counter_consistency();
+ /* Update the channel's queue size too */
+ chan->bytes_in_queue -= cell_size;
+ /* Finally, free q */
+ cell_queue_entry_free(q, handed_off);
+ q = NULL;
+ } else {
/* No cell removed from list, so we can't go on any further */
- else break;
+ break;
}
}
}
@@ -2567,16 +2577,6 @@ channel_do_open_actions(channel_t *chan)
if (started_here) {
circuit_build_times_network_is_live(get_circuit_build_times_mutable());
rep_hist_note_connect_succeeded(chan->identity_digest, now);
- if (entry_guard_register_connect_status(
- chan->identity_digest, 1, 0, now) < 0) {
- /* Close any circuits pending on this channel. We leave it in state
- * 'open' though, because it didn't actually *fail* -- we just
- * chose not to use it. */
- log_debug(LD_OR,
- "New entry guard was reachable, but closing this "
- "connection so we can retry the earlier entry guards.");
- close_origin_circuits = 1;
- }
router_set_status(chan->identity_digest, 1);
} else {
/* only report it to the geoip module if it's not a known router */
@@ -3249,9 +3249,10 @@ channel_free_all(void)
channel_t *
channel_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest)
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id)
{
- return channel_tls_connect(addr, port, id_digest);
+ return channel_tls_connect(addr, port, id_digest, ed_id);
}
/**
@@ -3324,7 +3325,8 @@ channel_is_better(time_t now, channel_t *a, channel_t *b,
*/
channel_t *
-channel_get_for_extend(const char *digest,
+channel_get_for_extend(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
const tor_addr_t *target_addr,
const char **msg_out,
int *launch_out)
@@ -3337,14 +3339,14 @@ channel_get_for_extend(const char *digest,
tor_assert(msg_out);
tor_assert(launch_out);
- chan = channel_find_by_remote_digest(digest);
+ chan = channel_find_by_remote_identity(rsa_id_digest, ed_id);
/* Walk the list, unrefing the old one and refing the new at each
* iteration.
*/
- for (; chan; chan = channel_next_with_digest(chan)) {
+ for (; chan; chan = channel_next_with_rsa_identity(chan)) {
tor_assert(tor_memeq(chan->identity_digest,
- digest, DIGEST_LEN));
+ rsa_id_digest, DIGEST_LEN));
if (CHANNEL_CONDEMNED(chan))
continue;
@@ -3355,6 +3357,11 @@ channel_get_for_extend(const char *digest,
continue;
}
+ /* The Ed25519 key has to match too */
+ if (!channel_remote_identity_matches(chan, rsa_id_digest, ed_id)) {
+ continue;
+ }
+
/* Never return a non-open connection. */
if (!CHANNEL_IS_OPEN(chan)) {
/* If the address matches, don't launch a new connection for this
@@ -4526,6 +4533,81 @@ channel_set_circid_type,(channel_t *chan,
}
}
+/** Helper for channel_update_bad_for_new_circs(): Perform the
+ * channel_update_bad_for_new_circs operation on all channels in <b>lst</b>,
+ * all of which MUST have the same RSA ID. (They MAY have different
+ * Ed25519 IDs.) */
+static void
+channel_rsa_id_group_set_badness(struct channel_list_s *lst, int force)
+{
+ /*XXXX This function should really be about channels. 15056 */
+ channel_t *chan;
+
+ /* First, get a minimal list of the ed25519 identites */
+ smartlist_t *ed_identities = smartlist_new();
+ TOR_LIST_FOREACH(chan, lst, next_with_same_id) {
+ uint8_t *id_copy =
+ tor_memdup(&chan->ed25519_identity.pubkey, DIGEST256_LEN);
+ smartlist_add(ed_identities, id_copy);
+ }
+ smartlist_sort_digests256(ed_identities);
+ smartlist_uniq_digests256(ed_identities);
+
+ /* Now, for each Ed identity, build a smartlist and find the best entry on
+ * it. */
+ smartlist_t *or_conns = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(ed_identities, const uint8_t *, ed_id) {
+ TOR_LIST_FOREACH(chan, lst, next_with_same_id) {
+ channel_tls_t *chantls = BASE_CHAN_TO_TLS(chan);
+ if (tor_memneq(ed_id, &chan->ed25519_identity.pubkey, DIGEST256_LEN))
+ continue;
+ or_connection_t *orconn = chantls->conn;
+ if (orconn) {
+ tor_assert(orconn->chan == chantls);
+ smartlist_add(or_conns, orconn);
+ }
+ }
+
+ connection_or_group_set_badness_(or_conns, force);
+ smartlist_clear(or_conns);
+ } SMARTLIST_FOREACH_END(ed_id);
+
+ /* XXXX 15056 we may want to do something special with connections that have
+ * no set Ed25519 identity! */
+
+ smartlist_free(or_conns);
+
+ SMARTLIST_FOREACH(ed_identities, uint8_t *, ed_id, tor_free(ed_id));
+ smartlist_free(ed_identities);
+}
+
+/** Go through all the channels (or if <b>digest</b> is non-NULL, just
+ * the OR connections with that digest), and set the is_bad_for_new_circs
+ * flag based on the rules in connection_or_group_set_badness() (or just
+ * always set it if <b>force</b> is true).
+ */
+void
+channel_update_bad_for_new_circs(const char *digest, int force)
+{
+ if (digest) {
+ channel_idmap_entry_t *ent;
+ channel_idmap_entry_t search;
+ memset(&search, 0, sizeof(search));
+ memcpy(search.digest, digest, DIGEST_LEN);
+ ent = HT_FIND(channel_idmap, &channel_identity_map, &search);
+ if (ent) {
+ channel_rsa_id_group_set_badness(&ent->channel_list, force);
+ }
+ return;
+ }
+
+ /* no digest; just look at everything. */
+ channel_idmap_entry_t **iter;
+ HT_FOREACH(iter, channel_idmap, &channel_identity_map) {
+ channel_rsa_id_group_set_badness(&(*iter)->channel_list, force);
+ }
+}
+
/**
* Update the estimated number of bytes queued to transmit for this channel,
* and notify the scheduler. The estimate includes both the channel queue and
diff --git a/src/or/channel.h b/src/or/channel.h
index a711b56d44..26aa93b5e2 100644
--- a/src/or/channel.h
+++ b/src/or/channel.h
@@ -153,16 +153,32 @@ struct channel_s {
int (*write_var_cell)(channel_t *, var_cell_t *);
/**
- * Hash of the public RSA key for the other side's identity key, or
- * zeroes if the other side hasn't shown us a valid identity key.
+ * Hash of the public RSA key for the other side's RSA identity key -- or
+ * zeroes if we don't have an RSA identity in mind for the other side, and
+ * it hasn't shown us one.
+ *
+ * Note that this is the RSA identity that we hope the other side has -- not
+ * necessarily its true identity. Don't believe this identity unless
+ * authentication has happened.
*/
char identity_digest[DIGEST_LEN];
+ /**
+ * Ed25519 key for the other side of this channel -- or zeroes if we don't
+ * have an Ed25519 identity in mind for the other side, and it hasn't shown
+ * us one.
+ *
+ * Note that this is the identity that we hope the other side has -- not
+ * necessarily its true identity. Don't believe this identity unless
+ * authentication has happened.
+ */
+ ed25519_public_key_t ed25519_identity;
+
/** Nickname of the OR on the other side, or NULL if none. */
char *nickname;
/**
- * Linked list of channels with the same identity digest, for the
- * digest->channel map
+ * Linked list of channels with the same RSA identity digest, for use with
+ * the digest->channel map
*/
TOR_LIST_ENTRY(channel_s) next_with_same_id;
@@ -382,6 +398,9 @@ struct cell_queue_entry_s {
STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue);
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
/* Channel operations for subclasses and internal use only */
@@ -424,7 +443,8 @@ void channel_mark_incoming(channel_t *chan);
void channel_mark_outgoing(channel_t *chan);
void channel_mark_remote(channel_t *chan);
void channel_set_identity_digest(channel_t *chan,
- const char *identity_digest);
+ const char *identity_digest,
+ const ed25519_public_key_t *ed_identity);
void channel_set_remote_end(channel_t *chan,
const char *identity_digest,
const char *nickname);
@@ -486,9 +506,11 @@ int channel_send_destroy(circid_t circ_id, channel_t *chan,
*/
channel_t * channel_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest);
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id);
-channel_t * channel_get_for_extend(const char *digest,
+channel_t * channel_get_for_extend(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
const tor_addr_t *target_addr,
const char **msg_out,
int *launch_out);
@@ -502,11 +524,13 @@ int channel_is_better(time_t now,
*/
channel_t * channel_find_by_global_id(uint64_t global_identifier);
-channel_t * channel_find_by_remote_digest(const char *identity_digest);
+channel_t * channel_find_by_remote_identity(const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id);
/** For things returned by channel_find_by_remote_digest(), walk the list.
+ * The RSA key will match for all returned elements; the Ed25519 key might not.
*/
-channel_t * channel_next_with_digest(channel_t *chan);
+channel_t * channel_next_with_rsa_identity(channel_t *chan);
/*
* Helper macros to lookup state of given channel.
@@ -578,6 +602,8 @@ void channel_listener_dump_statistics(channel_listener_t *chan_l,
void channel_listener_dump_transport_statistics(channel_listener_t *chan_l,
int severity);
+void channel_update_bad_for_new_circs(const char *digest, int force);
+
/* Flow control queries */
uint64_t channel_get_global_queue_estimate(void);
int channel_num_cells_writeable(channel_t *chan);
diff --git a/src/or/channeltls.c b/src/or/channeltls.c
index 09cca95b64..dbed95fb43 100644
--- a/src/or/channeltls.c
+++ b/src/or/channeltls.c
@@ -49,12 +49,14 @@
#include "connection.h"
#include "connection_or.h"
#include "control.h"
+#include "entrynodes.h"
#include "link_handshake.h"
#include "relay.h"
#include "rephist.h"
#include "router.h"
#include "routerlist.h"
#include "scheduler.h"
+#include "torcert.h"
/** How many CELL_PADDING cells have we received, ever? */
uint64_t stats_n_padding_cells_processed = 0;
@@ -170,7 +172,8 @@ channel_tls_common_init(channel_tls_t *tlschan)
channel_t *
channel_tls_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest)
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id)
{
channel_tls_t *tlschan = tor_malloc_zero(sizeof(*tlschan));
channel_t *chan = &(tlschan->base_);
@@ -198,7 +201,7 @@ channel_tls_connect(const tor_addr_t *addr, uint16_t port,
channel_mark_outgoing(chan);
/* Set up or_connection stuff */
- tlschan->conn = connection_or_connect(addr, port, id_digest, tlschan);
+ tlschan->conn = connection_or_connect(addr, port, id_digest, ed_id, tlschan);
/* connection_or_connect() will fill in tlschan->conn */
if (!(tlschan->conn)) {
chan->reason_for_closing = CHANNEL_CLOSE_FOR_ERROR;
@@ -598,7 +601,7 @@ channel_tls_get_remote_descr_method(channel_t *chan, int flags)
break;
default:
/* Something's broken in channel.c */
- tor_assert(1);
+ tor_assert_nonfatal_unreached_once();
}
} else {
strlcpy(buf, "(No connection)", sizeof(buf));
@@ -667,7 +670,7 @@ channel_tls_is_canonical_method(channel_t *chan, int req)
break;
default:
/* This shouldn't happen; channel.c is broken if it does */
- tor_assert(1);
+ tor_assert_nonfatal_unreached_once();
}
}
/* else return 0 for tlschan->conn == NULL */
@@ -1092,6 +1095,10 @@ channel_tls_handle_cell(cell_t *cell, or_connection_t *conn)
if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3)
or_handshake_state_record_cell(conn, conn->handshake_state, cell, 1);
+ /* We note that we're on the internet whenever we read a cell. This is
+ * a fast operation. */
+ entry_guards_note_internet_connectivity(get_guard_selection_info());
+
switch (cell->command) {
case CELL_PADDING:
++stats_n_padding_cells_processed;
@@ -1270,6 +1277,10 @@ channel_tls_handle_var_cell(var_cell_t *var_cell, or_connection_t *conn)
return;
}
+ /* We note that we're on the internet whenever we read a cell. This is
+ * a fast operation. */
+ entry_guards_note_internet_connectivity(get_guard_selection_info());
+
/* Now handle the cell */
switch (var_cell->command) {
@@ -1639,15 +1650,20 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
if (!(chan->conn->handshake_state->authenticated)) {
tor_assert(tor_digest_is_zero(
(const char*)(chan->conn->handshake_state->
- authenticated_peer_id)));
+ authenticated_rsa_peer_id)));
+ tor_assert(tor_mem_is_zero(
+ (const char*)(chan->conn->handshake_state->
+ authenticated_ed25519_peer_id.pubkey), 32));
channel_set_circid_type(TLS_CHAN_TO_BASE(chan), NULL,
chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS);
connection_or_init_conn_from_address(chan->conn,
&(chan->conn->base_.addr),
chan->conn->base_.port,
+ /* zero, checked above */
(const char*)(chan->conn->handshake_state->
- authenticated_peer_id),
+ authenticated_rsa_peer_id),
+ NULL, /* Ed25519 ID: Also checked as zero */
0);
}
}
@@ -1744,6 +1760,41 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
assert_connection_ok(TO_CONN(chan->conn),time(NULL));
}
+/** Types of certificates that we know how to parse from CERTS cells. Each
+ * type corresponds to a different encoding format. */
+typedef enum cert_encoding_t {
+ CERT_ENCODING_UNKNOWN, /**< We don't recognize this. */
+ CERT_ENCODING_X509, /**< It's an RSA key, signed with RSA, encoded in x509.
+ * (Actually, it might not be RSA. We test that later.) */
+ CERT_ENCODING_ED25519, /**< It's something signed with an Ed25519 key,
+ * encoded asa a tor_cert_t.*/
+ CERT_ENCODING_RSA_CROSSCERT, /**< It's an Ed key signed with an RSA key. */
+} cert_encoding_t;
+
+/**
+ * Given one of the certificate type codes used in a CERTS cell,
+ * return the corresponding cert_encoding_t that we should use to parse
+ * the certificate.
+ */
+static cert_encoding_t
+certs_cell_typenum_to_cert_type(int typenum)
+{
+ switch (typenum) {
+ case CERTTYPE_RSA1024_ID_LINK:
+ case CERTTYPE_RSA1024_ID_ID:
+ case CERTTYPE_RSA1024_ID_AUTH:
+ return CERT_ENCODING_X509;
+ case CERTTYPE_ED_ID_SIGN:
+ case CERTTYPE_ED_SIGN_LINK:
+ case CERTTYPE_ED_SIGN_AUTH:
+ return CERT_ENCODING_ED25519;
+ case CERTTYPE_RSA1024_ID_EDID:
+ return CERT_ENCODING_RSA_CROSSCERT;
+ default:
+ return CERT_ENCODING_UNKNOWN;
+ }
+}
+
/**
* Process a CERTS cell from a channel.
*
@@ -1763,14 +1814,21 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan)
STATIC void
channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
{
-#define MAX_CERT_TYPE_WANTED OR_CERT_TYPE_AUTH_1024
- tor_x509_cert_t *certs[MAX_CERT_TYPE_WANTED + 1];
+#define MAX_CERT_TYPE_WANTED CERTTYPE_RSA1024_ID_EDID
+ /* These arrays will be sparse, since a cert type can be at most one
+ * of ed/x509 */
+ tor_x509_cert_t *x509_certs[MAX_CERT_TYPE_WANTED + 1];
+ tor_cert_t *ed_certs[MAX_CERT_TYPE_WANTED + 1];
+ uint8_t *rsa_ed_cc_cert = NULL;
+ size_t rsa_ed_cc_cert_len = 0;
+
int n_certs, i;
certs_cell_t *cc = NULL;
int send_netinfo = 0;
- memset(certs, 0, sizeof(certs));
+ memset(x509_certs, 0, sizeof(x509_certs));
+ memset(ed_certs, 0, sizeof(ed_certs));
tor_assert(cell);
tor_assert(chan);
tor_assert(chan->conn);
@@ -1814,77 +1872,149 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
if (cert_type > MAX_CERT_TYPE_WANTED)
continue;
+ const cert_encoding_t ct = certs_cell_typenum_to_cert_type(cert_type);
+ switch (ct) {
+ default:
+ case CERT_ENCODING_UNKNOWN:
+ break;
+ case CERT_ENCODING_X509: {
+ tor_x509_cert_t *x509_cert = tor_x509_cert_decode(cert_body, cert_len);
+ if (!x509_cert) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Received undecodable certificate in CERTS cell from %s:%d",
+ safe_str(chan->conn->base_.address),
+ chan->conn->base_.port);
+ } else {
+ if (x509_certs[cert_type]) {
+ tor_x509_cert_free(x509_cert);
+ ERR("Duplicate x509 certificate");
+ } else {
+ x509_certs[cert_type] = x509_cert;
+ }
+ }
+ break;
+ }
+ case CERT_ENCODING_ED25519: {
+ tor_cert_t *ed_cert = tor_cert_parse(cert_body, cert_len);
+ if (!ed_cert) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Received undecodable Ed certificate "
+ "in CERTS cell from %s:%d",
+ safe_str(chan->conn->base_.address),
+ chan->conn->base_.port);
+ } else {
+ if (ed_certs[cert_type]) {
+ tor_cert_free(ed_cert);
+ ERR("Duplicate Ed25519 certificate");
+ } else {
+ ed_certs[cert_type] = ed_cert;
+ }
+ }
+ break;
+ }
- tor_x509_cert_t *cert = tor_x509_cert_decode(cert_body, cert_len);
- if (!cert) {
- log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
- "Received undecodable certificate in CERTS cell from %s:%d",
- safe_str(chan->conn->base_.address),
- chan->conn->base_.port);
- } else {
- if (certs[cert_type]) {
- tor_x509_cert_free(cert);
- ERR("Duplicate x509 certificate");
- } else {
- certs[cert_type] = cert;
+ case CERT_ENCODING_RSA_CROSSCERT: {
+ if (rsa_ed_cc_cert) {
+ ERR("Duplicate RSA->Ed25519 crosscert");
+ } else {
+ rsa_ed_cc_cert = tor_memdup(cert_body, cert_len);
+ rsa_ed_cc_cert_len = cert_len;
+ }
+ break;
}
}
}
- tor_x509_cert_t *id_cert = certs[OR_CERT_TYPE_ID_1024];
- tor_x509_cert_t *auth_cert = certs[OR_CERT_TYPE_AUTH_1024];
- tor_x509_cert_t *link_cert = certs[OR_CERT_TYPE_TLS_LINK];
+ /* Move the certificates we (might) want into the handshake_state->certs
+ * structure. */
+ tor_x509_cert_t *id_cert = x509_certs[CERTTYPE_RSA1024_ID_ID];
+ tor_x509_cert_t *auth_cert = x509_certs[CERTTYPE_RSA1024_ID_AUTH];
+ tor_x509_cert_t *link_cert = x509_certs[CERTTYPE_RSA1024_ID_LINK];
+ chan->conn->handshake_state->certs->auth_cert = auth_cert;
+ chan->conn->handshake_state->certs->link_cert = link_cert;
+ chan->conn->handshake_state->certs->id_cert = id_cert;
+ x509_certs[CERTTYPE_RSA1024_ID_ID] =
+ x509_certs[CERTTYPE_RSA1024_ID_AUTH] =
+ x509_certs[CERTTYPE_RSA1024_ID_LINK] = NULL;
+
+ tor_cert_t *ed_id_sign = ed_certs[CERTTYPE_ED_ID_SIGN];
+ tor_cert_t *ed_sign_link = ed_certs[CERTTYPE_ED_SIGN_LINK];
+ tor_cert_t *ed_sign_auth = ed_certs[CERTTYPE_ED_SIGN_AUTH];
+ chan->conn->handshake_state->certs->ed_id_sign = ed_id_sign;
+ chan->conn->handshake_state->certs->ed_sign_link = ed_sign_link;
+ chan->conn->handshake_state->certs->ed_sign_auth = ed_sign_auth;
+ ed_certs[CERTTYPE_ED_ID_SIGN] =
+ ed_certs[CERTTYPE_ED_SIGN_LINK] =
+ ed_certs[CERTTYPE_ED_SIGN_AUTH] = NULL;
+
+ chan->conn->handshake_state->certs->ed_rsa_crosscert = rsa_ed_cc_cert;
+ chan->conn->handshake_state->certs->ed_rsa_crosscert_len =
+ rsa_ed_cc_cert_len;
+ rsa_ed_cc_cert = NULL;
+
+ int severity;
+ /* 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 &&
+ router_digest_is_trusted_dir(TLS_CHAN_TO_BASE(chan)->identity_digest))
+ severity = LOG_WARN;
+ else
+ severity = LOG_PROTOCOL_WARN;
+
+ const ed25519_public_key_t *checked_ed_id = NULL;
+ const common_digests_t *checked_rsa_id = NULL;
+ or_handshake_certs_check_both(severity,
+ chan->conn->handshake_state->certs,
+ chan->conn->tls,
+ time(NULL),
+ &checked_ed_id,
+ &checked_rsa_id);
+
+ if (!checked_rsa_id)
+ ERR("Invalid certificate chain!");
if (chan->conn->handshake_state->started_here) {
- int severity;
- if (! (id_cert && link_cert))
- ERR("The certs we wanted were missing");
- /* Okay. We should be able to check the certificates now. */
- if (! tor_tls_cert_matches_key(chan->conn->tls, link_cert)) {
- ERR("The link certificate didn't match the TLS public key");
- }
- /* 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 (router_digest_is_trusted_dir(
- TLS_CHAN_TO_BASE(chan)->identity_digest))
- severity = LOG_WARN;
- else
- severity = LOG_PROTOCOL_WARN;
-
- if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, 0))
- ERR("The link certificate was not valid");
- if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, 1))
- ERR("The ID certificate was not valid");
+ /* No more information is needed. */
chan->conn->handshake_state->authenticated = 1;
+ chan->conn->handshake_state->authenticated_rsa = 1;
{
- const common_digests_t *id_digests =
- tor_x509_cert_get_id_digests(id_cert);
+ const common_digests_t *id_digests = checked_rsa_id;
crypto_pk_t *identity_rcvd;
if (!id_digests)
ERR("Couldn't compute digests for key in ID cert");
identity_rcvd = tor_tls_cert_get_key(id_cert);
- if (!identity_rcvd)
- ERR("Internal error: Couldn't get RSA key from ID cert.");
- memcpy(chan->conn->handshake_state->authenticated_peer_id,
+ if (!identity_rcvd) {
+ ERR("Couldn't get RSA key from ID cert.");
+ }
+ memcpy(chan->conn->handshake_state->authenticated_rsa_peer_id,
id_digests->d[DIGEST_SHA1], DIGEST_LEN);
channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd,
chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS);
crypto_pk_free(identity_rcvd);
}
+ if (checked_ed_id) {
+ chan->conn->handshake_state->authenticated_ed25519 = 1;
+ memcpy(&chan->conn->handshake_state->authenticated_ed25519_peer_id,
+ checked_ed_id, sizeof(ed25519_public_key_t));
+ }
+
+ log_debug(LD_HANDSHAKE, "calling client_learned_peer_id from "
+ "process_certs_cell");
+
if (connection_or_client_learned_peer_id(chan->conn,
- chan->conn->handshake_state->authenticated_peer_id) < 0)
+ chan->conn->handshake_state->authenticated_rsa_peer_id,
+ checked_ed_id) < 0)
ERR("Problem setting or checking peer id");
- log_info(LD_OR,
- "Got some good certificates from %s:%d: Authenticated it.",
- safe_str(chan->conn->base_.address), chan->conn->base_.port);
-
- chan->conn->handshake_state->id_cert = id_cert;
- certs[OR_CERT_TYPE_ID_1024] = NULL;
+ log_info(LD_HANDSHAKE,
+ "Got some good certificates from %s:%d: Authenticated it with "
+ "RSA%s",
+ safe_str(chan->conn->base_.address), chan->conn->base_.port,
+ checked_ed_id ? " and Ed25519" : "");
if (!public_server_mode(get_options())) {
/* If we initiated the connection and we are not a public server, we
@@ -1893,25 +2023,14 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
send_netinfo = 1;
}
} else {
- if (! (id_cert && auth_cert))
- ERR("The certs we wanted were missing");
-
- /* Remember these certificates so we can check an AUTHENTICATE cell */
- if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, 1))
- ERR("The authentication certificate was not valid");
- if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, 1))
- ERR("The ID certificate was not valid");
-
+ /* We can't call it authenticated till we see an AUTHENTICATE cell. */
log_info(LD_OR,
- "Got some good certificates from %s:%d: "
+ "Got some good RSA%s certificates from %s:%d. "
"Waiting for AUTHENTICATE.",
+ checked_ed_id ? " and Ed25519" : "",
safe_str(chan->conn->base_.address),
chan->conn->base_.port);
/* XXXX check more stuff? */
-
- chan->conn->handshake_state->id_cert = id_cert;
- chan->conn->handshake_state->auth_cert = auth_cert;
- certs[OR_CERT_TYPE_ID_1024] = certs[OR_CERT_TYPE_AUTH_1024] = NULL;
}
chan->conn->handshake_state->received_certs_cell = 1;
@@ -1925,9 +2044,13 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan)
}
err:
- for (unsigned u = 0; u < ARRAY_LENGTH(certs); ++u) {
- tor_x509_cert_free(certs[u]);
+ for (unsigned u = 0; u < ARRAY_LENGTH(x509_certs); ++u) {
+ tor_x509_cert_free(x509_certs[u]);
+ }
+ for (unsigned u = 0; u < ARRAY_LENGTH(ed_certs); ++u) {
+ tor_cert_free(ed_certs[u]);
}
+ tor_free(rsa_ed_cc_cert);
certs_cell_free(cc);
#undef ERR
}
@@ -1984,8 +2107,12 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
/* Now see if there is an authentication type we can use */
for (i = 0; i < n_types; ++i) {
uint16_t authtype = auth_challenge_cell_get_methods(ac, i);
- if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET)
- use_type = authtype;
+ if (authchallenge_type_is_supported(authtype)) {
+ if (use_type == -1 ||
+ authchallenge_type_is_better(authtype, use_type)) {
+ use_type = authtype;
+ }
+ }
}
chan->conn->handshake_state->received_auth_challenge = 1;
@@ -2000,9 +2127,10 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
if (use_type >= 0) {
log_info(LD_OR,
"Got an AUTH_CHALLENGE cell from %s:%d: Sending "
- "authentication",
+ "authentication type %d",
safe_str(chan->conn->base_.address),
- chan->conn->base_.port);
+ chan->conn->base_.port,
+ use_type);
if (connection_or_send_authenticate_cell(chan->conn, use_type) < 0) {
log_warn(LD_OR,
@@ -2043,9 +2171,11 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan)
STATIC void
channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
{
- uint8_t expected[V3_AUTH_FIXED_PART_LEN+256];
+ var_cell_t *expected_cell = NULL;
const uint8_t *auth;
int authlen;
+ int authtype;
+ int bodylen;
tor_assert(cell);
tor_assert(chan);
@@ -2058,6 +2188,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
safe_str(chan->conn->base_.address), \
chan->conn->base_.port, (s)); \
connection_or_close_for_error(chan->conn, 0); \
+ var_cell_free(expected_cell); \
return; \
} while (0)
@@ -2075,9 +2206,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
}
if (!(chan->conn->handshake_state->received_certs_cell))
ERR("We never got a certs cell");
- if (chan->conn->handshake_state->auth_cert == NULL)
- ERR("We never got an authentication certificate");
- if (chan->conn->handshake_state->id_cert == NULL)
+ if (chan->conn->handshake_state->certs->id_cert == NULL)
ERR("We never got an identity certificate");
if (cell->payload_len < 4)
ERR("Cell was way too short");
@@ -2089,8 +2218,9 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
if (4 + len > cell->payload_len)
ERR("Authenticator was truncated");
- if (type != AUTHTYPE_RSA_SHA256_TLSSECRET)
+ if (! authchallenge_type_is_supported(type))
ERR("Authenticator type was not recognized");
+ authtype = type;
auth += 4;
authlen = len;
@@ -2099,25 +2229,55 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
if (authlen < V3_AUTH_BODY_LEN + 1)
ERR("Authenticator was too short");
- ssize_t bodylen =
- connection_or_compute_authenticate_cell_body(
- chan->conn, expected, sizeof(expected), NULL, 1);
- if (bodylen < 0 || bodylen != V3_AUTH_FIXED_PART_LEN)
+ expected_cell = connection_or_compute_authenticate_cell_body(
+ chan->conn, authtype, NULL, NULL, 1);
+ if (! expected_cell)
ERR("Couldn't compute expected AUTHENTICATE cell body");
- if (tor_memneq(expected, auth, bodylen))
+ int sig_is_rsa;
+ if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET ||
+ authtype == AUTHTYPE_RSA_SHA256_RFC5705) {
+ bodylen = V3_AUTH_BODY_LEN;
+ sig_is_rsa = 1;
+ } else {
+ tor_assert(authtype == AUTHTYPE_ED25519_SHA256_RFC5705);
+ /* Our earlier check had better have made sure we had room
+ * for an ed25519 sig (inadvertently) */
+ tor_assert(V3_AUTH_BODY_LEN > ED25519_SIG_LEN);
+ bodylen = authlen - ED25519_SIG_LEN;
+ sig_is_rsa = 0;
+ }
+ if (expected_cell->payload_len != bodylen+4) {
+ ERR("Expected AUTHENTICATE cell body len not as expected.");
+ }
+
+ /* Length of random part. */
+ if (BUG(bodylen < 24)) {
+ // LCOV_EXCL_START
+ ERR("Bodylen is somehow less than 24, which should really be impossible");
+ // LCOV_EXCL_STOP
+ }
+
+ if (tor_memneq(expected_cell->payload+4, auth, bodylen-24))
ERR("Some field in the AUTHENTICATE cell body was not as expected");
- {
+ if (sig_is_rsa) {
+ if (chan->conn->handshake_state->certs->ed_id_sign != NULL)
+ ERR("RSA-signed AUTHENTICATE response provided with an ED25519 cert");
+
+ if (chan->conn->handshake_state->certs->auth_cert == NULL)
+ ERR("We never got an RSA authentication certificate");
+
crypto_pk_t *pk = tor_tls_cert_get_key(
- chan->conn->handshake_state->auth_cert);
+ chan->conn->handshake_state->certs->auth_cert);
char d[DIGEST256_LEN];
char *signed_data;
size_t keysize;
int signed_len;
- if (!pk)
- ERR("Internal error: couldn't get RSA key from AUTH cert.");
+ if (! pk) {
+ ERR("Couldn't get RSA key from AUTH cert.");
+ }
crypto_digest256(d, (char*)auth, V3_AUTH_BODY_LEN, DIGEST_SHA256);
keysize = crypto_pk_keysize(pk);
@@ -2128,7 +2288,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
crypto_pk_free(pk);
if (signed_len < 0) {
tor_free(signed_data);
- ERR("Signature wasn't valid");
+ ERR("RSA signature wasn't valid");
}
if (signed_len < DIGEST256_LEN) {
tor_free(signed_data);
@@ -2141,41 +2301,75 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan)
ERR("Signature did not match data to be signed.");
}
tor_free(signed_data);
+ } else {
+ if (chan->conn->handshake_state->certs->ed_id_sign == NULL)
+ ERR("We never got an Ed25519 identity certificate.");
+ if (chan->conn->handshake_state->certs->ed_sign_auth == NULL)
+ ERR("We never got an Ed25519 authentication certificate.");
+
+ const ed25519_public_key_t *authkey =
+ &chan->conn->handshake_state->certs->ed_sign_auth->signed_key;
+ ed25519_signature_t sig;
+ tor_assert(authlen > ED25519_SIG_LEN);
+ memcpy(&sig.sig, auth + authlen - ED25519_SIG_LEN, ED25519_SIG_LEN);
+ if (ed25519_checksig(&sig, auth, authlen - ED25519_SIG_LEN, authkey)<0) {
+ ERR("Ed25519 signature wasn't valid.");
+ }
}
/* Okay, we are authenticated. */
chan->conn->handshake_state->received_authenticate = 1;
chan->conn->handshake_state->authenticated = 1;
+ chan->conn->handshake_state->authenticated_rsa = 1;
chan->conn->handshake_state->digest_received_data = 0;
{
- crypto_pk_t *identity_rcvd =
- tor_tls_cert_get_key(chan->conn->handshake_state->id_cert);
- const common_digests_t *id_digests =
- tor_x509_cert_get_id_digests(chan->conn->handshake_state->id_cert);
+ tor_x509_cert_t *id_cert = chan->conn->handshake_state->certs->id_cert;
+ crypto_pk_t *identity_rcvd = tor_tls_cert_get_key(id_cert);
+ const common_digests_t *id_digests = tor_x509_cert_get_id_digests(id_cert);
+ const ed25519_public_key_t *ed_identity_received = NULL;
+
+ if (! sig_is_rsa) {
+ chan->conn->handshake_state->authenticated_ed25519 = 1;
+ ed_identity_received =
+ &chan->conn->handshake_state->certs->ed_id_sign->signing_key;
+ memcpy(&chan->conn->handshake_state->authenticated_ed25519_peer_id,
+ ed_identity_received, sizeof(ed25519_public_key_t));
+ }
/* This must exist; we checked key type when reading the cert. */
tor_assert(id_digests);
- memcpy(chan->conn->handshake_state->authenticated_peer_id,
+ memcpy(chan->conn->handshake_state->authenticated_rsa_peer_id,
id_digests->d[DIGEST_SHA1], DIGEST_LEN);
channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd,
chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS);
crypto_pk_free(identity_rcvd);
+ log_debug(LD_HANDSHAKE,
+ "Calling connection_or_init_conn_from_address for %s "
+ " from %s, with%s ed25519 id.",
+ safe_str(chan->conn->base_.address),
+ __func__,
+ ed_identity_received ? "" : "out");
+
connection_or_init_conn_from_address(chan->conn,
&(chan->conn->base_.addr),
chan->conn->base_.port,
(const char*)(chan->conn->handshake_state->
- authenticated_peer_id),
+ authenticated_rsa_peer_id),
+ ed_identity_received,
0);
- log_info(LD_OR,
- "Got an AUTHENTICATE cell from %s:%d: Looks good.",
+ log_debug(LD_HANDSHAKE,
+ "Got an AUTHENTICATE cell from %s:%d, type %d: Looks good.",
safe_str(chan->conn->base_.address),
- chan->conn->base_.port);
+ chan->conn->base_.port,
+ authtype);
}
+ var_cell_free(expected_cell);
+
#undef ERR
}
diff --git a/src/or/channeltls.h b/src/or/channeltls.h
index 8b5863a461..729e595615 100644
--- a/src/or/channeltls.h
+++ b/src/or/channeltls.h
@@ -29,7 +29,8 @@ struct channel_tls_s {
#endif /* TOR_CHANNEL_INTERNAL_ */
channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port,
- const char *id_digest);
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id);
channel_listener_t * channel_tls_get_listener(void);
channel_listener_t * channel_tls_start_listener(void);
channel_t * channel_tls_handle_incoming(or_connection_t *orconn);
diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c
index 9f93e737f7..cdcb6deae4 100644
--- a/src/or/circpathbias.c
+++ b/src/or/circpathbias.c
@@ -11,6 +11,14 @@
* different tor nodes, in an attempt to detect attacks where
* an attacker deliberately causes circuits to fail until the client
* choses a path they like.
+ *
+ * This code is currently configured in a warning-only mode, though false
+ * positives appear to be rare in practice. There is also support for
+ * disabling really bad guards, but it's quite experimental and may have bad
+ * anonymity effects.
+ *
+ * The information here is associated with the entry_guard_t object for
+ * each guard, and stored persistently in the state file.
*/
#include "or.h"
@@ -43,19 +51,21 @@ static int entry_guard_inc_circ_attempt_count(entry_guard_t *guard);
static int
entry_guard_inc_circ_attempt_count(entry_guard_t *guard)
{
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
entry_guards_changed();
pathbias_measure_close_rate(guard);
- if (guard->path_bias_disabled)
+ if (pb->path_bias_disabled)
return -1;
pathbias_scale_close_rates(guard);
- guard->circ_attempts++;
+ pb->circ_attempts++;
- log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ log_info(LD_CIRC, "Got success count %f/%f for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ entry_guard_describe(guard));
return 0;
}
@@ -505,14 +515,16 @@ pathbias_count_build_success(origin_circuit_t *circ)
}
if (guard) {
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
if (circ->path_state == PATH_STATE_BUILD_ATTEMPTED) {
circ->path_state = PATH_STATE_BUILD_SUCCEEDED;
- guard->circ_successes++;
+ pb->circ_successes++;
entry_guards_changed();
- log_info(LD_CIRC, "Got success count %f/%f for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ log_info(LD_CIRC, "Got success count %f/%f for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ entry_guard_describe(guard));
} else {
if ((rate_msg = rate_limit_log(&success_notice_limit,
approx_time()))) {
@@ -527,11 +539,11 @@ pathbias_count_build_success(origin_circuit_t *circ)
}
}
- if (guard->circ_attempts < guard->circ_successes) {
+ if (pb->circ_attempts < pb->circ_successes) {
log_notice(LD_BUG, "Unexpectedly high successes counts (%f/%f) "
- "for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ entry_guard_describe(guard));
}
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
* CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT and have no guards here.
@@ -574,8 +586,6 @@ pathbias_count_build_success(origin_circuit_t *circ)
void
pathbias_count_use_attempt(origin_circuit_t *circ)
{
- entry_guard_t *guard;
-
if (!pathbias_should_count(circ)) {
return;
}
@@ -588,19 +598,21 @@ pathbias_count_use_attempt(origin_circuit_t *circ)
circuit_purpose_to_string(circ->base_.purpose),
circuit_state_to_string(circ->base_.state));
} else if (circ->path_state < PATH_STATE_USE_ATTEMPTED) {
- guard = entry_guard_get_by_id_digest(
+ entry_guard_t *guard = entry_guard_get_by_id_digest(
circ->cpath->extend_info->identity_digest);
if (guard) {
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
pathbias_measure_use_rate(guard);
pathbias_scale_use_rates(guard);
- guard->use_attempts++;
+ pb->use_attempts++;
entry_guards_changed();
log_debug(LD_CIRC,
- "Marked circuit %d (%f/%f) as used for guard %s ($%s).",
+ "Marked circuit %d (%f/%f) as used for guard %s.",
circ->global_identifier,
- guard->use_successes, guard->use_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ pb->use_successes, pb->use_attempts,
+ entry_guard_describe(guard));
}
circ->path_state = PATH_STATE_USE_ATTEMPTED;
@@ -702,22 +714,23 @@ pathbias_count_use_success(origin_circuit_t *circ)
guard = entry_guard_get_by_id_digest(
circ->cpath->extend_info->identity_digest);
if (guard) {
- guard->use_successes++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->use_successes++;
entry_guards_changed();
- if (guard->use_attempts < guard->use_successes) {
+ if (pb->use_attempts < pb->use_successes) {
log_notice(LD_BUG, "Unexpectedly high use successes counts (%f/%f) "
- "for guard %s=%s",
- guard->use_successes, guard->use_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "for guard %s",
+ pb->use_successes, pb->use_attempts,
+ entry_guard_describe(guard));
}
log_debug(LD_CIRC,
- "Marked circuit %d (%f/%f) as used successfully for guard "
- "%s ($%s).",
- circ->global_identifier, guard->use_successes,
- guard->use_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ "Marked circuit %d (%f/%f) as used successfully for guard %s",
+ circ->global_identifier, pb->use_successes,
+ pb->use_attempts,
+ entry_guard_describe(guard));
}
}
@@ -1018,9 +1031,11 @@ pathbias_count_successful_close(origin_circuit_t *circ)
}
if (guard) {
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
/* In the long run: circuit_success ~= successful_circuit_close +
* circ_failure + stream_failure */
- guard->successful_circuits_closed++;
+ pb->successful_circuits_closed++;
entry_guards_changed();
} else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
@@ -1057,7 +1072,9 @@ pathbias_count_collapse(origin_circuit_t *circ)
}
if (guard) {
- guard->collapsed_circuits++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->collapsed_circuits++;
entry_guards_changed();
} else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
@@ -1090,7 +1107,9 @@ pathbias_count_use_failed(origin_circuit_t *circ)
}
if (guard) {
- guard->unusable_circuits++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->unusable_circuits++;
entry_guards_changed();
} else if (circ->base_.purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
/* In rare cases, CIRCUIT_PURPOSE_TESTING can get converted to
@@ -1133,7 +1152,9 @@ pathbias_count_timeout(origin_circuit_t *circ)
}
if (guard) {
- guard->timeouts++;
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ pb->timeouts++;
entry_guards_changed();
}
}
@@ -1165,7 +1186,7 @@ pathbias_count_circs_in_states(entry_guard_t *guard,
if (ocirc->path_state >= from &&
ocirc->path_state <= to &&
pathbias_should_count(ocirc) &&
- fast_memeq(guard->identity,
+ fast_memeq(entry_guard_get_rsa_id_digest(guard),
ocirc->cpath->extend_info->identity_digest,
DIGEST_LEN)) {
log_debug(LD_CIRC, "Found opened circuit %d in path_state %s",
@@ -1189,7 +1210,9 @@ pathbias_count_circs_in_states(entry_guard_t *guard,
double
pathbias_get_close_success_count(entry_guard_t *guard)
{
- return guard->successful_circuits_closed +
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ return pb->successful_circuits_closed +
pathbias_count_circs_in_states(guard,
PATH_STATE_BUILD_SUCCEEDED,
PATH_STATE_USE_SUCCEEDED);
@@ -1205,7 +1228,9 @@ pathbias_get_close_success_count(entry_guard_t *guard)
double
pathbias_get_use_success_count(entry_guard_t *guard)
{
- return guard->use_successes +
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
+
+ return pb->use_successes +
pathbias_count_circs_in_states(guard,
PATH_STATE_USE_ATTEMPTED,
PATH_STATE_USE_SUCCEEDED);
@@ -1223,18 +1248,19 @@ static void
pathbias_measure_use_rate(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
- if (guard->use_attempts > pathbias_get_min_use(options)) {
+ if (pb->use_attempts > pathbias_get_min_use(options)) {
/* Note: We rely on the < comparison here to allow us to set a 0
* rate and disable the feature entirely. If refactoring, don't
* change to <= */
- if (pathbias_get_use_success_count(guard)/guard->use_attempts
+ if (pathbias_get_use_success_count(guard)/pb->use_attempts
< pathbias_get_extreme_use_rate(options)) {
/* Dropping is currently disabled by default. */
if (pathbias_get_dropguards(options)) {
- if (!guard->path_bias_disabled) {
+ if (!pb->path_bias_disabled) {
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing to carry an extremely large "
+ "Your Guard %s is failing to carry an extremely large "
"amount of stream on its circuits. "
"To avoid potential route manipulation attacks, Tor has "
"disabled use of this guard. "
@@ -1242,25 +1268,23 @@ pathbias_measure_use_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
+ tor_lround(pb->use_attempts),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->circ_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
- guard->path_bias_disabled = 1;
- guard->bad_since = approx_time();
- entry_guards_changed();
+ pb->path_bias_disabled = 1;
return;
}
- } else if (!guard->path_bias_use_extreme) {
- guard->path_bias_use_extreme = 1;
+ } else if (!pb->path_bias_use_extreme) {
+ pb->path_bias_use_extreme = 1;
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing to carry an extremely large "
+ "Your Guard %s is failing to carry an extremely large "
"amount of streams on its circuits. "
"This could indicate a route manipulation attack, network "
"overload, bad local network connectivity, or a bug. "
@@ -1268,23 +1292,23 @@ pathbias_measure_use_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
+ tor_lround(pb->use_attempts),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->circ_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
- } else if (pathbias_get_use_success_count(guard)/guard->use_attempts
+ } else if (pathbias_get_use_success_count(guard)/pb->use_attempts
< pathbias_get_notice_use_rate(options)) {
- if (!guard->path_bias_use_noticed) {
- guard->path_bias_use_noticed = 1;
+ if (!pb->path_bias_use_noticed) {
+ pb->path_bias_use_noticed = 1;
log_notice(LD_CIRC,
- "Your Guard %s ($%s) is failing to carry more streams on its "
+ "Your Guard %s is failing to carry more streams on its "
"circuits than usual. "
"Most likely this means the Tor network is overloaded "
"or your network connection is poor. "
@@ -1292,15 +1316,15 @@ pathbias_measure_use_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
+ tor_lround(pb->use_attempts),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->circ_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
}
@@ -1329,18 +1353,19 @@ static void
pathbias_measure_close_rate(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
- if (guard->circ_attempts > pathbias_get_min_circs(options)) {
+ if (pb->circ_attempts > pathbias_get_min_circs(options)) {
/* Note: We rely on the < comparison here to allow us to set a 0
* rate and disable the feature entirely. If refactoring, don't
* change to <= */
- if (pathbias_get_close_success_count(guard)/guard->circ_attempts
+ if (pathbias_get_close_success_count(guard)/pb->circ_attempts
< pathbias_get_extreme_rate(options)) {
/* Dropping is currently disabled by default. */
if (pathbias_get_dropguards(options)) {
- if (!guard->path_bias_disabled) {
+ if (!pb->path_bias_disabled) {
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing an extremely large "
+ "Your Guard %s is failing an extremely large "
"amount of circuits. "
"To avoid potential route manipulation attacks, Tor has "
"disabled use of this guard. "
@@ -1348,25 +1373,23 @@ pathbias_measure_close_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
- guard->path_bias_disabled = 1;
- guard->bad_since = approx_time();
- entry_guards_changed();
+ pb->path_bias_disabled = 1;
return;
}
- } else if (!guard->path_bias_extreme) {
- guard->path_bias_extreme = 1;
+ } else if (!pb->path_bias_extreme) {
+ pb->path_bias_extreme = 1;
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing an extremely large "
+ "Your Guard %s is failing an extremely large "
"amount of circuits. "
"This could indicate a route manipulation attack, "
"extreme network overload, or a bug. "
@@ -1374,23 +1397,23 @@ pathbias_measure_close_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
- } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts
+ } else if (pathbias_get_close_success_count(guard)/pb->circ_attempts
< pathbias_get_warn_rate(options)) {
- if (!guard->path_bias_warned) {
- guard->path_bias_warned = 1;
+ if (!pb->path_bias_warned) {
+ pb->path_bias_warned = 1;
log_warn(LD_CIRC,
- "Your Guard %s ($%s) is failing a very large "
+ "Your Guard %s is failing a very large "
"amount of circuits. "
"Most likely this means the Tor network is "
"overloaded, but it could also mean an attack against "
@@ -1399,38 +1422,38 @@ pathbias_measure_close_rate(entry_guard_t *guard)
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
- } else if (pathbias_get_close_success_count(guard)/guard->circ_attempts
+ } else if (pathbias_get_close_success_count(guard)/pb->circ_attempts
< pathbias_get_notice_rate(options)) {
- if (!guard->path_bias_noticed) {
- guard->path_bias_noticed = 1;
+ if (!pb->path_bias_noticed) {
+ pb->path_bias_noticed = 1;
log_notice(LD_CIRC,
- "Your Guard %s ($%s) is failing more circuits than "
+ "Your Guard %s is failing more circuits than "
"usual. "
"Most likely this means the Tor network is overloaded. "
"Success counts are %ld/%ld. Use counts are %ld/%ld. "
"%ld circuits completed, %ld were unusable, %ld collapsed, "
"and %ld timed out. "
"For reference, your timeout cutoff is %ld seconds.",
- guard->nickname, hex_str(guard->identity, DIGEST_LEN),
+ entry_guard_describe(guard),
tor_lround(pathbias_get_close_success_count(guard)),
- tor_lround(guard->circ_attempts),
+ tor_lround(pb->circ_attempts),
tor_lround(pathbias_get_use_success_count(guard)),
- tor_lround(guard->use_attempts),
- tor_lround(guard->circ_successes),
- tor_lround(guard->unusable_circuits),
- tor_lround(guard->collapsed_circuits),
- tor_lround(guard->timeouts),
+ tor_lround(pb->use_attempts),
+ tor_lround(pb->circ_successes),
+ tor_lround(pb->unusable_circuits),
+ tor_lround(pb->collapsed_circuits),
+ tor_lround(pb->timeouts),
tor_lround(get_circuit_build_close_time_ms()/1000));
}
}
@@ -1450,9 +1473,10 @@ static void
pathbias_scale_close_rates(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
/* If we get a ton of circuits, just scale everything down */
- if (guard->circ_attempts > pathbias_get_scale_threshold(options)) {
+ if (pb->circ_attempts > pathbias_get_scale_threshold(options)) {
double scale_ratio = pathbias_get_scale_ratio(options);
int opened_attempts = pathbias_count_circs_in_states(guard,
PATH_STATE_BUILD_ATTEMPTED, PATH_STATE_BUILD_ATTEMPTED);
@@ -1460,38 +1484,38 @@ pathbias_scale_close_rates(entry_guard_t *guard)
PATH_STATE_BUILD_SUCCEEDED,
PATH_STATE_USE_FAILED);
/* Verify that the counts are sane before and after scaling */
- int counts_are_sane = (guard->circ_attempts >= guard->circ_successes);
+ int counts_are_sane = (pb->circ_attempts >= pb->circ_successes);
- guard->circ_attempts -= (opened_attempts+opened_built);
- guard->circ_successes -= opened_built;
+ pb->circ_attempts -= (opened_attempts+opened_built);
+ pb->circ_successes -= opened_built;
- guard->circ_attempts *= scale_ratio;
- guard->circ_successes *= scale_ratio;
- guard->timeouts *= scale_ratio;
- guard->successful_circuits_closed *= scale_ratio;
- guard->collapsed_circuits *= scale_ratio;
- guard->unusable_circuits *= scale_ratio;
+ pb->circ_attempts *= scale_ratio;
+ pb->circ_successes *= scale_ratio;
+ pb->timeouts *= scale_ratio;
+ pb->successful_circuits_closed *= scale_ratio;
+ pb->collapsed_circuits *= scale_ratio;
+ pb->unusable_circuits *= scale_ratio;
- guard->circ_attempts += (opened_attempts+opened_built);
- guard->circ_successes += opened_built;
+ pb->circ_attempts += (opened_attempts+opened_built);
+ pb->circ_successes += opened_built;
entry_guards_changed();
log_info(LD_CIRC,
"Scaled pathbias counts to (%f,%f)/%f (%d/%d open) for guard "
- "%s ($%s)",
- guard->circ_successes, guard->successful_circuits_closed,
- guard->circ_attempts, opened_built, opened_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "%s",
+ pb->circ_successes, pb->successful_circuits_closed,
+ pb->circ_attempts, opened_built, opened_attempts,
+ entry_guard_describe(guard));
/* Have the counts just become invalid by this scaling attempt? */
- if (counts_are_sane && guard->circ_attempts < guard->circ_successes) {
+ if (counts_are_sane && pb->circ_attempts < pb->circ_successes) {
log_notice(LD_BUG,
"Scaling has mangled pathbias counts to %f/%f (%d/%d open) "
- "for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts, opened_built,
- opened_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ "for guard %s",
+ pb->circ_successes, pb->circ_attempts, opened_built,
+ opened_attempts,
+ entry_guard_describe(guard));
}
}
}
@@ -1509,35 +1533,35 @@ void
pathbias_scale_use_rates(entry_guard_t *guard)
{
const or_options_t *options = get_options();
+ guard_pathbias_t *pb = entry_guard_get_pathbias_state(guard);
/* If we get a ton of circuits, just scale everything down */
- if (guard->use_attempts > pathbias_get_scale_use_threshold(options)) {
+ if (pb->use_attempts > pathbias_get_scale_use_threshold(options)) {
double scale_ratio = pathbias_get_scale_ratio(options);
int opened_attempts = pathbias_count_circs_in_states(guard,
PATH_STATE_USE_ATTEMPTED, PATH_STATE_USE_SUCCEEDED);
/* Verify that the counts are sane before and after scaling */
- int counts_are_sane = (guard->use_attempts >= guard->use_successes);
+ int counts_are_sane = (pb->use_attempts >= pb->use_successes);
- guard->use_attempts -= opened_attempts;
+ pb->use_attempts -= opened_attempts;
- guard->use_attempts *= scale_ratio;
- guard->use_successes *= scale_ratio;
+ pb->use_attempts *= scale_ratio;
+ pb->use_successes *= scale_ratio;
- guard->use_attempts += opened_attempts;
+ pb->use_attempts += opened_attempts;
log_info(LD_CIRC,
- "Scaled pathbias use counts to %f/%f (%d open) for guard %s ($%s)",
- guard->use_successes, guard->use_attempts, opened_attempts,
- guard->nickname, hex_str(guard->identity, DIGEST_LEN));
+ "Scaled pathbias use counts to %f/%f (%d open) for guard %s",
+ pb->use_successes, pb->use_attempts, opened_attempts,
+ entry_guard_describe(guard));
/* Have the counts just become invalid by this scaling attempt? */
- if (counts_are_sane && guard->use_attempts < guard->use_successes) {
+ if (counts_are_sane && pb->use_attempts < pb->use_successes) {
log_notice(LD_BUG,
"Scaling has mangled pathbias usage counts to %f/%f "
- "(%d open) for guard %s ($%s)",
- guard->circ_successes, guard->circ_attempts,
- opened_attempts, guard->nickname,
- hex_str(guard->identity, DIGEST_LEN));
+ "(%d open) for guard %s",
+ pb->circ_successes, pb->circ_attempts,
+ opened_attempts, entry_guard_describe(guard));
}
entry_guards_changed();
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index cb9c146fb7..79962e8dbb 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -9,11 +9,26 @@
*
* \brief Implements the details of building circuits (by chosing paths,
* constructing/sending create/extend cells, and so on).
+ *
+ * On the client side, this module handles launching circuits. Circuit
+ * launches are srtarted from circuit_establish_circuit(), called from
+ * circuit_launch_by_extend_info()). To choose the path the circuit will
+ * take, onion_extend_cpath() calls into a maze of node selection functions.
+ *
+ * Once the circuit is ready to be launched, the first hop is treated as a
+ * special case with circuit_handle_first_hop(), since it might need to open a
+ * channel. As the channel opens, and later as CREATED and RELAY_EXTENDED
+ * cells arrive, the client will invoke circuit_send_next_onion_skin() to send
+ * CREATE or RELAY_EXTEND cells.
+ *
+ * On the server side, this module also handles the logic of responding to
+ * RELAY_EXTEND requests, using circuit_extend().
**/
#define CIRCUITBUILD_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "channel.h"
#include "circpathbias.h"
#define CIRCUITBUILD_PRIVATE
@@ -49,8 +64,9 @@
#include "transports.h"
static channel_t * channel_connect_for_circuit(const tor_addr_t *addr,
- uint16_t port,
- const char *id_digest);
+ uint16_t port,
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id);
static int circuit_deliver_create_cell(circuit_t *circ,
const create_cell_t *create_cell,
int relayed);
@@ -66,11 +82,12 @@ static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice);
*/
static channel_t *
channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port,
- const char *id_digest)
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id)
{
channel_t *chan;
- chan = channel_connect(addr, port, id_digest);
+ chan = channel_connect(addr, port, id_digest, ed_id);
if (chan) command_setup_channel(chan);
return chan;
@@ -502,6 +519,13 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags)
return circ;
}
+/** Return the guard state associated with <b>circ</b>, which may be NULL. */
+circuit_guard_state_t *
+origin_circuit_get_guard_state(origin_circuit_t *circ)
+{
+ return circ->guard_state;
+}
+
/** Start establishing the first hop of our circuit. Figure out what
* OR we should connect to, and if necessary start the connection to
* it. If we're already connected, then send the 'create' cell.
@@ -540,6 +564,7 @@ circuit_handle_first_hop(origin_circuit_t *circ)
firsthop->extend_info->port));
n_chan = channel_get_for_extend(firsthop->extend_info->identity_digest,
+ &firsthop->extend_info->ed_identity,
&firsthop->extend_info->addr,
&msg,
&should_launch);
@@ -557,7 +582,8 @@ circuit_handle_first_hop(origin_circuit_t *circ)
n_chan = channel_connect_for_circuit(
&firsthop->extend_info->addr,
firsthop->extend_info->port,
- firsthop->extend_info->identity_digest);
+ firsthop->extend_info->identity_digest,
+ &firsthop->extend_info->ed_identity);
if (!n_chan) { /* connect failed, forget the whole thing */
log_info(LD_CIRC,"connect to firsthop failed. Closing.");
return -END_CIRC_REASON_CONNECTFAILED;
@@ -866,6 +892,27 @@ circuit_pick_extend_handshake(uint8_t *cell_type_out,
}
}
+/**
+ * Return true iff <b>purpose</b> is a purpose for a circuit which is
+ * allowed to have no guard configured, even if the circuit is multihop
+ * and guards are enabled.
+ */
+static int
+circuit_purpose_may_omit_guard(int purpose)
+{
+ switch (purpose) {
+ case CIRCUIT_PURPOSE_TESTING:
+ case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT:
+ /* Testing circuits may omit guards because they're measuring
+ * liveness or performance, and don't want guards to interfere. */
+ return 1;
+ default:
+ /* All other multihop circuits should use guards if guards are
+ * enabled. */
+ return 0;
+ }
+}
+
/** This is the backbone function for building circuits.
*
* If circ's first hop is closed, then we need to build a create
@@ -940,7 +987,37 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
memset(&ec, 0, sizeof(ec));
if (!hop) {
/* done building the circuit. whew. */
- circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
+ 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;
@@ -958,7 +1035,7 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
"Assuming clock jump. Purpose %d (%s)", timediff,
circ->base_.purpose,
circuit_purpose_to_string(circ->base_.purpose));
- } else if (!circuit_build_times_disabled()) {
+ } 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())) {
@@ -982,7 +1059,8 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
pathbias_count_build_success(circ);
circuit_rep_hist_note_result(circ);
- circuit_has_opened(circ); /* do other actions as necessary */
+ 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();
@@ -1025,6 +1103,9 @@ circuit_send_next_onion_skin(origin_circuit_t *circ)
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,
@@ -1143,7 +1224,7 @@ circuit_extend(cell_t *cell, circuit_t *circ)
/* Check if they asked us for 0000..0000. We support using
* an empty fingerprint for the first hop (e.g. for a bridge relay),
- * but we don't want to let people send us extend cells for empty
+ * but we don't want to let clients send us extend cells for empty
* fingerprints -- a) because it opens the user up to a mitm attack,
* and b) because it lets an attacker force the relay to hold open a
* new TLS connection for each extend request. */
@@ -1153,6 +1234,18 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return -1;
}
+ /* Fill in ed_pubkey if it was not provided and we can infer it from
+ * our networkstatus */
+ if (ed25519_public_key_is_zero(&ec.ed_pubkey)) {
+ const node_t *node = node_get_by_id((const char*)ec.node_id);
+ const ed25519_public_key_t *node_ed_id = NULL;
+ if (node &&
+ node_supports_ed25519_link_authentication(node) &&
+ (node_ed_id = node_get_ed25519_id(node))) {
+ ed25519_pubkey_copy(&ec.ed_pubkey, node_ed_id);
+ }
+ }
+
/* Next, check if we're being asked to connect to the hop that the
* extend cell came from. There isn't any reason for that, and it can
* assist circular-path attacks. */
@@ -1164,7 +1257,17 @@ circuit_extend(cell_t *cell, circuit_t *circ)
return -1;
}
+ /* Check the previous hop Ed25519 ID too */
+ if (! ed25519_public_key_is_zero(&ec.ed_pubkey) &&
+ ed25519_pubkey_eq(&ec.ed_pubkey,
+ &TO_OR_CIRCUIT(circ)->p_chan->ed25519_identity)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Client asked me to extend back to the previous hop "
+ "(by Ed25519 ID).");
+ }
+
n_chan = channel_get_for_extend((const char*)ec.node_id,
+ &ec.ed_pubkey,
&ec.orport_ipv4.addr,
&msg,
&should_launch);
@@ -1176,8 +1279,9 @@ circuit_extend(cell_t *cell, circuit_t *circ)
circ->n_hop = extend_info_new(NULL /*nickname*/,
(const char*)ec.node_id,
- NULL /*onion_key*/,
- NULL /*curve25519_key*/,
+ &ec.ed_pubkey,
+ NULL, /*onion_key*/
+ NULL, /*curve25519_key*/
&ec.orport_ipv4.addr,
ec.orport_ipv4.port);
@@ -1190,7 +1294,8 @@ circuit_extend(cell_t *cell, circuit_t *circ)
/* we should try to open a connection */
n_chan = channel_connect_for_circuit(&ec.orport_ipv4.addr,
ec.orport_ipv4.port,
- (const char*)ec.node_id);
+ (const char*)ec.node_id,
+ &ec.ed_pubkey);
if (!n_chan) {
log_info(LD_CIRC,"Launching n_chan failed. Closing circuit.");
circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED);
@@ -1492,9 +1597,9 @@ circuit_get_unhandled_ports(time_t now)
* If we're returning 0, set need_uptime and need_capacity to
* indicate any requirements that the unhandled ports have.
*/
-int
-circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
- int *need_capacity)
+MOCK_IMPL(int,
+circuit_all_predicted_ports_handled, (time_t now, int *need_uptime,
+ int *need_capacity))
{
int i, enough;
uint16_t *port;
@@ -2026,7 +2131,8 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei)
return -1;
}
exit_ei = extend_info_from_node(node, 0);
- tor_assert(exit_ei);
+ if (BUG(exit_ei == NULL))
+ return -1;
}
state->chosen_exit = exit_ei;
return 0;
@@ -2182,9 +2288,14 @@ choose_good_middle_server(uint8_t purpose,
*
* 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.
*/
const node_t *
-choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
+choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state,
+ circuit_guard_state_t **guard_state_out)
{
const node_t *choice;
smartlist_t *excluded;
@@ -2199,7 +2310,8 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
(purpose != CIRCUIT_PURPOSE_TESTING || options->BridgeRelay)) {
/* This request is for an entry server to use for a regular circuit,
* and we use entry guard nodes. Just return one of the guard nodes. */
- return choose_random_entry(state);
+ tor_assert(guard_state_out);
+ return guards_choose_guard(state, guard_state_out);
}
excluded = smartlist_new();
@@ -2209,25 +2321,6 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
* family. */
nodelist_add_node_and_family(excluded, node);
}
- /* and exclude current entry guards and their families,
- * unless we're in a test network, and excluding guards
- * would exclude all nodes (i.e. we're in an incredibly small tor network,
- * or we're using TestingAuthVoteGuard *).
- * This is an incomplete fix, but is no worse than the previous behaviour,
- * and only applies to minimal, testing tor networks
- * (so it's no less secure) */
- /*XXXX++ use the using_as_guard flag to accomplish this.*/
- if (options->UseEntryGuards
- && (!options->TestingTorNetwork ||
- smartlist_len(nodelist_get_list()) > smartlist_len(get_entry_guards())
- )) {
- SMARTLIST_FOREACH(get_entry_guards(), const entry_guard_t *, entry,
- {
- if ((node = node_get_by_id(entry->identity))) {
- nodelist_add_node_and_family(excluded, node);
- }
- });
- }
if (state) {
if (state->need_uptime)
@@ -2283,7 +2376,8 @@ onion_extend_cpath(origin_circuit_t *circ)
if (cur_len == state->desired_path_len - 1) { /* Picking last node */
info = extend_info_dup(state->chosen_exit);
} else if (cur_len == 0) { /* picking first node */
- const node_t *r = choose_good_entry_server(purpose, state);
+ const node_t *r = choose_good_entry_server(purpose, state,
+ &circ->guard_state);
if (r) {
/* If we're a client, use the preferred address rather than the
primary address, for potentially connecting to an IPv6 OR
@@ -2291,14 +2385,14 @@ onion_extend_cpath(origin_circuit_t *circ)
int client = (server_mode(get_options()) == 0);
info = extend_info_from_node(r, client);
/* Clients can fail to find an allowed address */
- tor_assert(info || client);
+ tor_assert_nonfatal(info || client);
}
} else {
const node_t *r =
choose_good_middle_server(purpose, state, circ->cpath, cur_len);
if (r) {
info = extend_info_from_node(r, 0);
- tor_assert(info);
+ tor_assert_nonfatal(info);
}
}
@@ -2341,19 +2435,23 @@ onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice)
/** Allocate a new extend_info object based on the various arguments. */
extend_info_t *
-extend_info_new(const char *nickname, const char *digest,
+extend_info_new(const char *nickname,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
crypto_pk_t *onion_key,
- const curve25519_public_key_t *curve25519_key,
+ const curve25519_public_key_t *ntor_key,
const tor_addr_t *addr, uint16_t port)
{
extend_info_t *info = tor_malloc_zero(sizeof(extend_info_t));
- memcpy(info->identity_digest, digest, DIGEST_LEN);
+ memcpy(info->identity_digest, rsa_id_digest, DIGEST_LEN);
+ if (ed_id && !ed25519_public_key_is_zero(ed_id))
+ memcpy(&info->ed_identity, ed_id, sizeof(ed25519_public_key_t));
if (nickname)
strlcpy(info->nickname, nickname, sizeof(info->nickname));
if (onion_key)
info->onion_key = crypto_pk_dup_key(onion_key);
- if (curve25519_key)
- memcpy(&info->curve25519_onion_key, curve25519_key,
+ if (ntor_key)
+ memcpy(&info->curve25519_onion_key, ntor_key,
sizeof(curve25519_public_key_t));
tor_addr_copy(&info->addr, addr);
info->port = port;
@@ -2403,20 +2501,35 @@ extend_info_from_node(const node_t *node, int for_direct_connect)
return NULL;
}
+ const ed25519_public_key_t *ed_pubkey = NULL;
+
+ /* Don't send the ed25519 pubkey unless the target node actually supports
+ * authenticating with it. */
+ if (node_supports_ed25519_link_authentication(node)) {
+ log_info(LD_CIRC, "Including Ed25519 ID for %s", node_describe(node));
+ 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.",
+ node_describe(node));
+ }
+
if (valid_addr && node->ri)
return extend_info_new(node->ri->nickname,
- node->identity,
- node->ri->onion_pkey,
- node->ri->onion_curve25519_pkey,
- &ap.addr,
- ap.port);
+ node->identity,
+ ed_pubkey,
+ node->ri->onion_pkey,
+ node->ri->onion_curve25519_pkey,
+ &ap.addr,
+ ap.port);
else if (valid_addr && node->rs && node->md)
return extend_info_new(node->rs->nickname,
- node->identity,
- node->md->onion_pkey,
- node->md->onion_curve25519_pkey,
- &ap.addr,
- ap.port);
+ node->identity,
+ ed_pubkey,
+ node->md->onion_pkey,
+ node->md->onion_curve25519_pkey,
+ &ap.addr,
+ ap.port);
else
return NULL;
}
@@ -2447,8 +2560,8 @@ extend_info_dup(extend_info_t *info)
return newinfo;
}
-/** Return the routerinfo_t for the chosen exit router in <b>state</b>.
- * If there is no chosen exit, or if we don't know the routerinfo_t for
+/** Return the node_t for the chosen exit router in <b>state</b>.
+ * If there is no chosen exit, or if we don't know the node_t for
* the chosen exit, return NULL.
*/
const node_t *
@@ -2459,6 +2572,17 @@ build_state_get_exit_node(cpath_build_state_t *state)
return node_get_by_id(state->chosen_exit->identity_digest);
}
+/** Return the RSA ID digest for the chosen exit router in <b>state</b>.
+ * If there is no chosen exit, return NULL.
+ */
+const uint8_t *
+build_state_get_exit_rsa_id(cpath_build_state_t *state)
+{
+ if (!state || !state->chosen_exit)
+ return NULL;
+ return (const uint8_t *) state->chosen_exit->identity_digest;
+}
+
/** Return the nickname for the chosen exit router in <b>state</b>. If
* there is no chosen exit, or if we don't know the routerinfo_t for the
* chosen exit, return NULL.
@@ -2551,3 +2675,26 @@ extend_info_has_preferred_onion_key(const extend_info_t* ei)
return extend_info_supports_ntor(ei);
}
+/** Find the circuits that are waiting to find out whether their guards are
+ * usable, and if any are ready to become usable, mark them open and try
+ * attaching streams as appropriate. */
+void
+circuit_upgrade_circuits_from_guard_wait(void)
+{
+ smartlist_t *to_upgrade =
+ circuit_find_circuits_to_upgrade_from_guard_wait();
+
+ if (to_upgrade == NULL)
+ return;
+
+ log_info(LD_GUARD, "Upgrading %d circuits from 'waiting for better guard' "
+ "to 'open'.", smartlist_len(to_upgrade));
+
+ SMARTLIST_FOREACH_BEGIN(to_upgrade, origin_circuit_t *, circ) {
+ circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_OPEN);
+ circuit_has_opened(circ);
+ } SMARTLIST_FOREACH_END(circ);
+
+ smartlist_free(to_upgrade);
+}
+
diff --git a/src/or/circuitbuild.h b/src/or/circuitbuild.h
index 1244601f71..ddb070b427 100644
--- a/src/or/circuitbuild.h
+++ b/src/or/circuitbuild.h
@@ -21,6 +21,8 @@ origin_circuit_t *origin_circuit_init(uint8_t purpose, int flags);
origin_circuit_t *circuit_establish_circuit(uint8_t purpose,
extend_info_t *exit,
int flags);
+struct circuit_guard_state_t *origin_circuit_get_guard_state(
+ origin_circuit_t *circ);
int circuit_handle_first_hop(origin_circuit_t *circ);
void circuit_n_chan_done(channel_t *chan, int status,
int close_origin_circuits);
@@ -40,15 +42,18 @@ int onionskin_answer(or_circuit_t *circ,
const struct created_cell_t *created_cell,
const char *keys,
const uint8_t *rend_circ_nonce);
-int circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
- int *need_capacity);
+MOCK_DECL(int, circuit_all_predicted_ports_handled, (time_t now,
+ int *need_uptime,
+ int *need_capacity));
int circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *info);
int circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *info);
void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop);
-extend_info_t *extend_info_new(const char *nickname, const char *digest,
+extend_info_t *extend_info_new(const char *nickname,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
crypto_pk_t *onion_key,
- const curve25519_public_key_t *curve25519_key,
+ const curve25519_public_key_t *ntor_key,
const tor_addr_t *addr, uint16_t port);
extend_info_t *extend_info_from_node(const node_t *r, int for_direct_connect);
extend_info_t *extend_info_dup(extend_info_t *info);
@@ -59,11 +64,16 @@ int extend_info_supports_ntor(const extend_info_t* ei);
int circuit_can_use_tap(const origin_circuit_t *circ);
int circuit_has_usable_onion_key(const origin_circuit_t *circ);
int extend_info_has_preferred_onion_key(const extend_info_t* ei);
+const uint8_t *build_state_get_exit_rsa_id(cpath_build_state_t *state);
const node_t *build_state_get_exit_node(cpath_build_state_t *state);
const char *build_state_get_exit_nickname(cpath_build_state_t *state);
+struct circuit_guard_state_t;
+
const node_t *choose_good_entry_server(uint8_t purpose,
- cpath_build_state_t *state);
+ cpath_build_state_t *state,
+ struct circuit_guard_state_t **guard_state_out);
+void circuit_upgrade_circuits_from_guard_wait(void);
#ifdef CIRCUITBUILD_PRIVATE
STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan);
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index 977afca18d..8d6a4a7cc2 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -7,7 +7,48 @@
/**
* \file circuitlist.c
*
- * \brief Manage the global circuit list, and looking up circuits within it.
+ * \brief Manage global structures that list and index circuits, and
+ * look up circuits within them.
+ *
+ * One of the most frequent operations in Tor occurs every time that
+ * a relay cell arrives on a channel. When that happens, we need to
+ * find which circuit it is associated with, based on the channel and the
+ * circuit ID in the relay cell.
+ *
+ * To handle that, we maintain a global list of circuits, and a hashtable
+ * mapping [channel,circID] pairs to circuits. Circuits are added to and
+ * removed from this mapping using circuit_set_p_circid_chan() and
+ * circuit_set_n_circid_chan(). To look up a circuit from this map, most
+ * callers should use circuit_get_by_circid_channel(), though
+ * circuit_get_by_circid_channel_even_if_marked() is appropriate under some
+ * circumstances.
+ *
+ * We also need to allow for the possibility that we have blocked use of a
+ * circuit ID (because we are waiting to send a DESTROY cell), but the
+ * circuit is not there any more. For that case, we allow placeholder
+ * entries in the table, using channel_mark_circid_unusable().
+ *
+ * To efficiently handle a channel that has just opened, we also maintain a
+ * list of the circuits waiting for channels, so we can attach them as
+ * needed without iterating through the whole list of circuits, using
+ * circuit_get_all_pending_on_channel().
+ *
+ * In this module, we also handle the list of circuits that have been
+ * marked for close elsewhere, and close them as needed. (We use this
+ * "mark now, close later" pattern here and elsewhere to avoid
+ * unpredictable recursion if we closed every circuit immediately upon
+ * realizing it needed to close.) See circuit_mark_for_close() for the
+ * mark function, and circuit_close_all_marked() for the close function.
+ *
+ * For hidden services, we need to be able to look up introduction point
+ * circuits and rendezvous circuits by cookie, key, etc. These are
+ * currently handled with linear searches in
+ * circuit_get_ready_rend_circuit_by_rend_data(),
+ * circuit_get_next_by_pk_and_purpose(), and with hash lookups in
+ * circuit_get_rendezvous() and circuit_get_intro_point().
+ *
+ * This module is also the entry point for our out-of-memory handler
+ * logic, which was originally circuit-focused.
**/
#define CIRCUITLIST_PRIVATE
#include "or.h"
@@ -22,7 +63,10 @@
#include "connection_edge.h"
#include "connection_or.h"
#include "control.h"
+#include "entrynodes.h"
#include "main.h"
+#include "hs_circuitmap.h"
+#include "hs_common.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "onion.h"
@@ -42,18 +86,23 @@
/** A global list of all circuits at this hop. */
static smartlist_t *global_circuitlist = NULL;
+/** A global list of all origin circuits. Every element of this is also
+ * an element of global_circuitlist. */
+static smartlist_t *global_origin_circuit_list = NULL;
+
/** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */
static smartlist_t *circuits_pending_chans = NULL;
+/** List of all the (origin) circuits whose state is
+ * CIRCUIT_STATE_GUARD_WAIT. */
+static smartlist_t *circuits_pending_other_guards = NULL;
+
/** A list of all the circuits that have been marked with
* circuit_mark_for_close and which are waiting for circuit_about_to_free. */
static smartlist_t *circuits_pending_close = NULL;
static void circuit_free_cpath_node(crypt_path_t *victim);
static void cpath_ref_decref(crypt_path_reference_t *cpath_ref);
-//static void circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ,
-// const uint8_t *token);
-static void circuit_clear_rend_token(or_circuit_t *circ);
static void circuit_about_to_free_atexit(circuit_t *circ);
static void circuit_about_to_free(circuit_t *circ);
@@ -386,8 +435,10 @@ circuit_set_state(circuit_t *circ, uint8_t state)
tor_assert(circ);
if (state == circ->state)
return;
- if (!circuits_pending_chans)
+ if (PREDICT_UNLIKELY(!circuits_pending_chans))
circuits_pending_chans = smartlist_new();
+ if (PREDICT_UNLIKELY(!circuits_pending_other_guards))
+ circuits_pending_other_guards = smartlist_new();
if (circ->state == CIRCUIT_STATE_CHAN_WAIT) {
/* remove from waiting-circuit list. */
smartlist_remove(circuits_pending_chans, circ);
@@ -396,7 +447,13 @@ circuit_set_state(circuit_t *circ, uint8_t state)
/* add to waiting-circuit list. */
smartlist_add(circuits_pending_chans, circ);
}
- if (state == CIRCUIT_STATE_OPEN)
+ if (circ->state == CIRCUIT_STATE_GUARD_WAIT) {
+ smartlist_remove(circuits_pending_other_guards, circ);
+ }
+ if (state == CIRCUIT_STATE_GUARD_WAIT) {
+ smartlist_add(circuits_pending_other_guards, circ);
+ }
+ if (state == CIRCUIT_STATE_GUARD_WAIT || state == CIRCUIT_STATE_OPEN)
tor_assert(!circ->n_chan_create_cell);
circ->state = state;
}
@@ -452,6 +509,39 @@ circuit_count_pending_on_channel(channel_t *chan)
return cnt;
}
+/** Remove <b>origin_circ</b> from the global list of origin circuits.
+ * Called when we are freeing a circuit.
+ */
+static void
+circuit_remove_from_origin_circuit_list(origin_circuit_t *origin_circ)
+{
+ int origin_idx = origin_circ->global_origin_circuit_list_idx;
+ if (origin_idx < 0)
+ return;
+ origin_circuit_t *c2;
+ tor_assert(origin_idx <= smartlist_len(global_origin_circuit_list));
+ c2 = smartlist_get(global_origin_circuit_list, origin_idx);
+ tor_assert(origin_circ == c2);
+ smartlist_del(global_origin_circuit_list, origin_idx);
+ if (origin_idx < smartlist_len(global_origin_circuit_list)) {
+ origin_circuit_t *replacement =
+ smartlist_get(global_origin_circuit_list, origin_idx);
+ replacement->global_origin_circuit_list_idx = origin_idx;
+ }
+ origin_circ->global_origin_circuit_list_idx = -1;
+}
+
+/** Add <b>origin_circ</b> to the global list of origin circuits. Called
+ * when creating the circuit. */
+static void
+circuit_add_to_origin_circuit_list(origin_circuit_t *origin_circ)
+{
+ tor_assert(origin_circ->global_origin_circuit_list_idx == -1);
+ smartlist_t *lst = circuit_get_global_origin_circuit_list();
+ smartlist_add(lst, origin_circ);
+ origin_circ->global_origin_circuit_list_idx = smartlist_len(lst) - 1;
+}
+
/** Detach from the global circuit list, and deallocate, all
* circuits that have been marked for close.
*/
@@ -474,6 +564,11 @@ circuit_close_all_marked(void)
}
circ->global_circuitlist_idx = -1;
+ /* Remove it from the origin circuit list, if appropriate. */
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ circuit_remove_from_origin_circuit_list(TO_ORIGIN_CIRCUIT(circ));
+ }
+
circuit_about_to_free(circ);
circuit_free(circ);
} SMARTLIST_FOREACH_END(circ);
@@ -481,7 +576,7 @@ circuit_close_all_marked(void)
smartlist_clear(circuits_pending_close);
}
-/** Return the head of the global linked list of circuits. */
+/** Return a pointer to the global list of circuits. */
MOCK_IMPL(smartlist_t *,
circuit_get_global_list,(void))
{
@@ -490,6 +585,15 @@ circuit_get_global_list,(void))
return global_circuitlist;
}
+/** Return a pointer to the global list of origin circuits. */
+smartlist_t *
+circuit_get_global_origin_circuit_list(void)
+{
+ if (NULL == global_origin_circuit_list)
+ global_origin_circuit_list = smartlist_new();
+ return global_origin_circuit_list;
+}
+
/** Function to make circ-\>state human-readable */
const char *
circuit_state_to_string(int state)
@@ -499,6 +603,8 @@ circuit_state_to_string(int state)
case CIRCUIT_STATE_BUILDING: return "doing handshakes";
case CIRCUIT_STATE_ONIONSKIN_PENDING: return "processing the onion";
case CIRCUIT_STATE_CHAN_WAIT: return "connecting to server";
+ case CIRCUIT_STATE_GUARD_WAIT: return "waiting to see how other "
+ "guards perform";
case CIRCUIT_STATE_OPEN: return "open";
default:
log_warn(LD_BUG, "Unknown circuit state %d", state);
@@ -729,6 +835,10 @@ origin_circuit_new(void)
init_circuit_base(TO_CIRCUIT(circ));
+ /* Add to origin-list. */
+ circ->global_origin_circuit_list_idx = -1;
+ circuit_add_to_origin_circuit_list(circ);
+
circuit_build_times_update_last_circ(get_circuit_build_times_mutable());
return circ;
@@ -786,6 +896,9 @@ circuit_free(circuit_t *circ)
mem = ocirc;
memlen = sizeof(origin_circuit_t);
tor_assert(circ->magic == ORIGIN_CIRCUIT_MAGIC);
+
+ circuit_remove_from_origin_circuit_list(ocirc);
+
if (ocirc->build_state) {
extend_info_free(ocirc->build_state->chosen_exit);
circuit_free_cpath_node(ocirc->build_state->pending_final_cpath);
@@ -793,6 +906,12 @@ circuit_free(circuit_t *circ)
}
tor_free(ocirc->build_state);
+ /* Cancel before freeing, if we haven't already succeeded or failed. */
+ if (ocirc->guard_state) {
+ entry_guard_cancel(&ocirc->guard_state);
+ }
+ circuit_guard_state_free(ocirc->guard_state);
+
circuit_clear_cpath(ocirc);
crypto_pk_free(ocirc->intro_key);
@@ -824,7 +943,9 @@ circuit_free(circuit_t *circ)
crypto_cipher_free(ocirc->n_crypto);
crypto_digest_free(ocirc->n_digest);
- circuit_clear_rend_token(ocirc);
+ if (ocirc->hs_token) {
+ hs_circuitmap_remove_circuit(ocirc);
+ }
if (ocirc->rend_splice) {
or_circuit_t *other = ocirc->rend_splice;
@@ -925,12 +1046,18 @@ circuit_free_all(void)
smartlist_free(lst);
global_circuitlist = NULL;
+ smartlist_free(global_origin_circuit_list);
+ global_origin_circuit_list = NULL;
+
smartlist_free(circuits_pending_chans);
circuits_pending_chans = NULL;
smartlist_free(circuits_pending_close);
circuits_pending_close = NULL;
+ smartlist_free(circuits_pending_other_guards);
+ circuits_pending_other_guards = NULL;
+
{
chan_circid_circuit_map_t **elt, **next, *c;
for (elt = HT_START(chan_circid_map, &chan_circid_map);
@@ -1311,9 +1438,11 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data)
if (!circ->marked_for_close &&
circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
- if (ocirc->rend_data &&
- !rend_cmp_service_ids(rend_data->onion_address,
- ocirc->rend_data->onion_address) &&
+ if (ocirc->rend_data == NULL) {
+ continue;
+ }
+ if (!rend_cmp_service_ids(rend_data_get_address(rend_data),
+ rend_data_get_address(ocirc->rend_data)) &&
tor_memeq(ocirc->rend_data->rend_cookie,
rend_data->rend_cookie,
REND_COOKIE_LEN))
@@ -1324,14 +1453,50 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data)
return NULL;
}
+/** Return the first service introduction 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 introduction point circuit has a purpose of either
+ * CIRCUIT_PURPOSE_S_ESTABLISH_INTRO or CIRCUIT_PURPOSE_S_INTRO. This does not
+ * return a circuit marked for close and its state must be open. */
+origin_circuit_t *
+circuit_get_next_service_intro_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_ESTABLISH_INTRO &&
+ circ->purpose != CIRCUIT_PURPOSE_S_INTRO)) {
+ 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 rend_pk_digest field. Return NULL if no
- * circuit is found. If <b>start</b> is NULL, begin at the start of the list.
+ * <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
+ * circuit. Return NULL if no circuit is found. If <b>start</b> is NULL,
+ * begin at the start of the list.
*/
origin_circuit_t *
circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
- const char *digest, uint8_t purpose)
+ const uint8_t *digest, uint8_t purpose)
{
int idx;
smartlist_t *lst = circuit_get_global_list();
@@ -1343,190 +1508,25 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
for ( ; idx < smartlist_len(lst); ++idx) {
circuit_t *circ = smartlist_get(lst, idx);
+ origin_circuit_t *ocirc;
if (circ->marked_for_close)
continue;
if (circ->purpose != purpose)
continue;
+ /* At this point we should be able to get a valid origin circuit because
+ * the origin purpose we are looking for matches this circuit. */
+ if (BUG(!CIRCUIT_PURPOSE_IS_ORIGIN(circ->purpose))) {
+ break;
+ }
+ ocirc = TO_ORIGIN_CIRCUIT(circ);
if (!digest)
- return TO_ORIGIN_CIRCUIT(circ);
- else if (TO_ORIGIN_CIRCUIT(circ)->rend_data &&
- tor_memeq(TO_ORIGIN_CIRCUIT(circ)->rend_data->rend_pk_digest,
- digest, DIGEST_LEN))
- return TO_ORIGIN_CIRCUIT(circ);
- }
- return NULL;
-}
-
-/** Map from rendezvous cookie to or_circuit_t */
-static digestmap_t *rend_cookie_map = NULL;
-
-/** Map from introduction point digest to or_circuit_t */
-static digestmap_t *intro_digest_map = NULL;
-
-/** Return the OR circuit whose purpose is <b>purpose</b>, and whose
- * rend_token is the REND_TOKEN_LEN-byte <b>token</b>. If <b>is_rend_circ</b>,
- * look for rendezvous point circuits; otherwise look for introduction point
- * circuits. */
-static or_circuit_t *
-circuit_get_by_rend_token_and_purpose(uint8_t purpose, int is_rend_circ,
- const char *token)
-{
- or_circuit_t *circ;
- digestmap_t *map = is_rend_circ ? rend_cookie_map : intro_digest_map;
-
- if (!map)
- return NULL;
-
- circ = digestmap_get(map, token);
- if (!circ ||
- circ->base_.purpose != purpose ||
- circ->base_.marked_for_close)
- return NULL;
-
- if (!circ->rendinfo) {
- char *t = tor_strdup(hex_str(token, REND_TOKEN_LEN));
- log_warn(LD_BUG, "Wanted a circuit with %s:%d, but lookup returned a "
- "circuit with no rendinfo set.",
- safe_str(t), is_rend_circ);
- tor_free(t);
- return NULL;
- }
-
- if (! bool_eq(circ->rendinfo->is_rend_circ, is_rend_circ) ||
- tor_memneq(circ->rendinfo->rend_token, token, REND_TOKEN_LEN)) {
- char *t = tor_strdup(hex_str(token, REND_TOKEN_LEN));
- log_warn(LD_BUG, "Wanted a circuit with %s:%d, but lookup returned %s:%d",
- safe_str(t), is_rend_circ,
- safe_str(hex_str(circ->rendinfo->rend_token, REND_TOKEN_LEN)),
- (int)circ->rendinfo->is_rend_circ);
- tor_free(t);
- return NULL;
- }
-
- return circ;
-}
-
-/** Clear the rendezvous cookie or introduction point key digest that's
- * configured on <b>circ</b>, if any, and remove it from any such maps. */
-static void
-circuit_clear_rend_token(or_circuit_t *circ)
-{
- or_circuit_t *found_circ;
- digestmap_t *map;
-
- if (!circ || !circ->rendinfo)
- return;
-
- map = circ->rendinfo->is_rend_circ ? rend_cookie_map : intro_digest_map;
-
- if (!map) {
- log_warn(LD_BUG, "Tried to clear rend token on circuit, but found no map");
- return;
- }
-
- found_circ = digestmap_get(map, circ->rendinfo->rend_token);
- if (found_circ == circ) {
- /* Great, this is the right one. */
- digestmap_remove(map, circ->rendinfo->rend_token);
- } else if (found_circ) {
- log_warn(LD_BUG, "Tried to clear rend token on circuit, but "
- "it was already replaced in the map.");
- } else {
- log_warn(LD_BUG, "Tried to clear rend token on circuit, but "
- "it not in the map at all.");
- }
-
- tor_free(circ->rendinfo); /* Sets it to NULL too */
-}
-
-/** Set the rendezvous cookie (if is_rend_circ), or the introduction point
- * digest (if ! is_rend_circ) of <b>circ</b> to the REND_TOKEN_LEN-byte value
- * in <b>token</b>, and add it to the appropriate map. If it previously had a
- * token, clear it. If another circuit previously had the same
- * cookie/intro-digest, mark that circuit and remove it from the map. */
-static void
-circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ,
- const uint8_t *token)
-{
- digestmap_t **map_p, *map;
- or_circuit_t *found_circ;
-
- /* Find the right map, creating it as needed */
- map_p = is_rend_circ ? &rend_cookie_map : &intro_digest_map;
-
- if (!*map_p)
- *map_p = digestmap_new();
-
- map = *map_p;
-
- /* If this circuit already has a token, we need to remove that. */
- if (circ->rendinfo)
- circuit_clear_rend_token(circ);
-
- if (token == NULL) {
- /* We were only trying to remove this token, not set a new one. */
- return;
- }
-
- found_circ = digestmap_get(map, (const char *)token);
- if (found_circ) {
- tor_assert(found_circ != circ);
- circuit_clear_rend_token(found_circ);
- if (! found_circ->base_.marked_for_close) {
- circuit_mark_for_close(TO_CIRCUIT(found_circ), END_CIRC_REASON_FINISHED);
- if (is_rend_circ) {
- log_fn(LOG_PROTOCOL_WARN, LD_REND,
- "Duplicate rendezvous cookie (%s...) used on two circuits",
- hex_str((const char*)token, 4)); /* only log first 4 chars */
- }
+ return ocirc;
+ if (rend_circuit_pk_digest_eq(ocirc, digest)) {
+ return ocirc;
}
}
-
- /* Now set up the rendinfo */
- circ->rendinfo = tor_malloc(sizeof(*circ->rendinfo));
- memcpy(circ->rendinfo->rend_token, token, REND_TOKEN_LEN);
- circ->rendinfo->is_rend_circ = is_rend_circ ? 1 : 0;
-
- digestmap_set(map, (const char *)token, circ);
-}
-
-/** Return the circuit waiting for a rendezvous with the provided cookie.
- * Return NULL if no such circuit is found.
- */
-or_circuit_t *
-circuit_get_rendezvous(const uint8_t *cookie)
-{
- return circuit_get_by_rend_token_and_purpose(
- CIRCUIT_PURPOSE_REND_POINT_WAITING,
- 1, (const char*)cookie);
-}
-
-/** Return the circuit waiting for intro cells of the given digest.
- * Return NULL if no such circuit is found.
- */
-or_circuit_t *
-circuit_get_intro_point(const uint8_t *digest)
-{
- return circuit_get_by_rend_token_and_purpose(
- CIRCUIT_PURPOSE_INTRO_POINT, 0,
- (const char *)digest);
-}
-
-/** Set the rendezvous cookie of <b>circ</b> to <b>cookie</b>. If another
- * circuit previously had that cookie, mark it. */
-void
-circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie)
-{
- circuit_set_rend_token(circ, 1, cookie);
-}
-
-/** Set the intro point key digest of <b>circ</b> to <b>cookie</b>. If another
- * circuit previously had that intro point digest, mark it. */
-void
-circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest)
-{
- circuit_set_rend_token(circ, 0, digest);
+ return NULL;
}
/** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL,
@@ -1539,6 +1539,14 @@ circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest)
* cannibalize.
*
* If !CIRCLAUNCH_NEED_UPTIME, prefer returning non-uptime circuits.
+ *
+ * To "cannibalize" a circuit means to extend it an extra hop, and use it
+ * for some other purpose than we had originally intended. We do this when
+ * we want to perform some low-bandwidth task at a specific relay, and we
+ * would like the circuit to complete as soon as possible. (If we were going
+ * to use a lot of bandwidth, we wouldn't want a circuit with an extra hop.
+ * If we didn't care about circuit completion latency, we would just build
+ * a new circuit.)
*/
origin_circuit_t *
circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
@@ -1613,6 +1621,37 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info,
return best;
}
+/**
+ * Check whether any of the origin circuits that are waiting to see if
+ * their guard is good enough to use can be upgraded to "ready". If so,
+ * return a new smartlist containing them. Otherwise return NULL.
+ */
+smartlist_t *
+circuit_find_circuits_to_upgrade_from_guard_wait(void)
+{
+ /* Only if some circuit is actually waiting on an upgrade should we
+ * run the algorithm. */
+ if (! circuits_pending_other_guards ||
+ smartlist_len(circuits_pending_other_guards)==0)
+ return NULL;
+ /* Only if we have some origin circuits should we run the algorithm. */
+ if (!global_origin_circuit_list)
+ return NULL;
+
+ /* Okay; we can pass our circuit list to entrynodes.c.*/
+ smartlist_t *result = smartlist_new();
+ int circuits_upgraded = entry_guards_upgrade_waiting_circuits(
+ get_guard_selection_info(),
+ global_origin_circuit_list,
+ result);
+ if (circuits_upgraded && smartlist_len(result)) {
+ return result;
+ } else {
+ smartlist_free(result);
+ return NULL;
+ }
+}
+
/** Return the number of hops in circuit's path. If circ has no entries,
* or is NULL, returns 0. */
int
@@ -1807,7 +1846,8 @@ circuit_about_to_free(circuit_t *circ)
* module then. If it isn't OPEN, we send it there now to remember which
* links worked and which didn't.
*/
- if (circ->state != CIRCUIT_STATE_OPEN) {
+ if (circ->state != CIRCUIT_STATE_OPEN &&
+ circ->state != CIRCUIT_STATE_GUARD_WAIT) {
if (CIRCUIT_IS_ORIGIN(circ)) {
origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
circuit_build_failed(ocirc); /* take actions if necessary */
@@ -1818,9 +1858,14 @@ circuit_about_to_free(circuit_t *circ)
if (circuits_pending_chans)
smartlist_remove(circuits_pending_chans, circ);
}
+ if (circuits_pending_other_guards) {
+ smartlist_remove(circuits_pending_other_guards, circ);
+ }
if (CIRCUIT_IS_ORIGIN(circ)) {
control_event_circuit_status(TO_ORIGIN_CIRCUIT(circ),
- (circ->state == CIRCUIT_STATE_OPEN)?CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED,
+ (circ->state == CIRCUIT_STATE_OPEN ||
+ circ->state == CIRCUIT_STATE_GUARD_WAIT) ?
+ CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED,
orig_reason);
}
@@ -1833,7 +1878,7 @@ circuit_about_to_free(circuit_t *circ)
if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) {
/* treat this like getting a nack from it */
log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s",
- safe_str_client(ocirc->rend_data->onion_address),
+ safe_str_client(rend_data_get_address(ocirc->rend_data)),
safe_str_client(build_state_get_exit_nickname(ocirc->build_state)),
timed_out ? "Recording timeout." : "Removing from descriptor.");
rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit,
@@ -1850,7 +1895,7 @@ circuit_about_to_free(circuit_t *circ)
log_info(LD_REND, "Failed intro circ %s to %s "
"(building circuit to intro point). "
"Marking intro point as possibly unreachable.",
- safe_str_client(ocirc->rend_data->onion_address),
+ safe_str_client(rend_data_get_address(ocirc->rend_data)),
safe_str_client(build_state_get_exit_nickname(
ocirc->build_state)));
rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit,
@@ -2343,7 +2388,8 @@ assert_circuit_ok(const circuit_t *c)
tor_assert(c->deliver_window >= 0);
tor_assert(c->package_window >= 0);
- if (c->state == CIRCUIT_STATE_OPEN) {
+ if (c->state == CIRCUIT_STATE_OPEN ||
+ c->state == CIRCUIT_STATE_GUARD_WAIT) {
tor_assert(!c->n_chan_create_cell);
if (or_circ) {
tor_assert(or_circ->n_crypto);
diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h
index 2707b426ab..6abee37dc4 100644
--- a/src/or/circuitlist.h
+++ b/src/or/circuitlist.h
@@ -15,6 +15,7 @@
#include "testsupport.h"
MOCK_DECL(smartlist_t *, circuit_get_global_list, (void));
+smartlist_t *circuit_get_global_origin_circuit_list(void);
const char *circuit_state_to_string(int state);
const char *circuit_purpose_to_controller_string(uint8_t purpose);
const char *circuit_purpose_to_controller_hs_state_string(uint8_t purpose);
@@ -45,11 +46,8 @@ origin_circuit_t *circuit_get_by_global_id(uint32_t id);
origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data(
const rend_data_t *rend_data);
origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start,
- const char *digest, uint8_t purpose);
-or_circuit_t *circuit_get_rendezvous(const uint8_t *cookie);
-or_circuit_t *circuit_get_intro_point(const uint8_t *digest);
-void circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie);
-void circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest);
+ const uint8_t *digest, uint8_t purpose);
+origin_circuit_t *circuit_get_next_service_intro_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);
@@ -77,6 +75,8 @@ void channel_note_destroy_pending(channel_t *chan, circid_t id);
MOCK_DECL(void, channel_note_destroy_not_pending,
(channel_t *chan, circid_t id));
+smartlist_t *circuit_find_circuits_to_upgrade_from_guard_wait(void);
+
#ifdef CIRCUITLIST_PRIVATE
STATIC void circuit_free(circuit_t *circ);
STATIC size_t n_cells_in_circ_queues(const circuit_t *c);
diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c
index 5c2ebde73b..0219459cdb 100644
--- a/src/or/circuitmux_ewma.c
+++ b/src/or/circuitmux_ewma.c
@@ -500,7 +500,7 @@ ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1,
tor_assert(pol_data_2);
p1 = TO_EWMA_POL_DATA(pol_data_1);
- p2 = TO_EWMA_POL_DATA(pol_data_1);
+ p2 = TO_EWMA_POL_DATA(pol_data_2);
if (p1 != p2) {
/* Get the head cell_ewma_t from each queue */
diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c
index 418acc0024..6e73372550 100644
--- a/src/or/circuitstats.c
+++ b/src/or/circuitstats.c
@@ -105,13 +105,21 @@ get_circuit_build_timeout_ms(void)
* 6. If we are configured in Single Onion mode
*/
int
-circuit_build_times_disabled(void)
+circuit_build_times_disabled(const or_options_t *options)
+{
+ return circuit_build_times_disabled_(options, 0);
+}
+
+/** As circuit_build_times_disabled, but take options as an argument. */
+int
+circuit_build_times_disabled_(const or_options_t *options,
+ int ignore_consensus)
{
if (unit_tests) {
return 0;
} else {
- const or_options_t *options = get_options();
- int consensus_disabled = networkstatus_get_param(NULL, "cbtdisabled",
+ int consensus_disabled =
+ ignore_consensus ? 0 : networkstatus_get_param(NULL, "cbtdisabled",
0, 0, 1);
int config_disabled = !options->LearnCircuitBuildTimeout;
int dirauth_disabled = options->AuthoritativeDir;
@@ -417,7 +425,7 @@ circuit_build_times_new_consensus_params(circuit_build_times_t *cbt,
* update if we aren't.
*/
- if (!circuit_build_times_disabled()) {
+ if (!circuit_build_times_disabled(get_options())) {
num = circuit_build_times_recent_circuit_count(ns);
if (num > 0) {
@@ -493,14 +501,15 @@ static double
circuit_build_times_get_initial_timeout(void)
{
double timeout;
+ const or_options_t *options = get_options();
/*
* Check if we have LearnCircuitBuildTimeout, and if we don't,
* always use CircuitBuildTimeout, no questions asked.
*/
- if (!unit_tests && get_options()->CircuitBuildTimeout) {
- timeout = get_options()->CircuitBuildTimeout*1000;
- if (!circuit_build_times_disabled() &&
+ if (!unit_tests && options->CircuitBuildTimeout) {
+ timeout = options->CircuitBuildTimeout*1000;
+ if (!circuit_build_times_disabled(options) &&
timeout < circuit_build_times_min_timeout()) {
log_warn(LD_CIRC, "Config CircuitBuildTimeout too low. Setting to %ds",
circuit_build_times_min_timeout()/1000);
@@ -542,7 +551,7 @@ circuit_build_times_init(circuit_build_times_t *cbt)
* Check if we really are using adaptive timeouts, and don't keep
* track of this stuff if not.
*/
- if (!circuit_build_times_disabled()) {
+ if (!circuit_build_times_disabled(get_options())) {
cbt->liveness.num_recent_circs =
circuit_build_times_recent_circuit_count(NULL);
cbt->liveness.timeouts_after_firsthop =
@@ -906,7 +915,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt,
int err = 0;
circuit_build_times_init(cbt);
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled(get_options())) {
return 0;
}
@@ -1431,7 +1440,7 @@ circuit_build_times_network_check_changed(circuit_build_times_t *cbt)
#define MAX_TIMEOUT ((int32_t) (INT32_MAX/2))
/* Check to see if this has happened before. If so, double the timeout
- * to give people on abysmally bad network connections a shot at access */
+ * to give clients on abysmally bad network connections a shot at access */
if (cbt->timeout_ms >= circuit_build_times_get_initial_timeout()) {
if (cbt->timeout_ms > MAX_TIMEOUT || cbt->close_ms > MAX_TIMEOUT) {
log_warn(LD_CIRC, "Insanely large circuit build timeout value. "
@@ -1507,7 +1516,7 @@ circuit_build_times_count_close(circuit_build_times_t *cbt,
int did_onehop,
time_t start_time)
{
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled(get_options())) {
cbt->close_ms = cbt->timeout_ms
= circuit_build_times_get_initial_timeout();
return 0;
@@ -1538,7 +1547,7 @@ void
circuit_build_times_count_timeout(circuit_build_times_t *cbt,
int did_onehop)
{
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled(get_options())) {
cbt->close_ms = cbt->timeout_ms
= circuit_build_times_get_initial_timeout();
return;
@@ -1612,7 +1621,7 @@ circuit_build_times_set_timeout(circuit_build_times_t *cbt)
/*
* Just return if we aren't using adaptive timeouts
*/
- if (circuit_build_times_disabled())
+ if (circuit_build_times_disabled(get_options()))
return;
if (!circuit_build_times_set_timeout_worker(cbt))
diff --git a/src/or/circuitstats.h b/src/or/circuitstats.h
index 72b160983f..c748f82d5e 100644
--- a/src/or/circuitstats.h
+++ b/src/or/circuitstats.h
@@ -17,7 +17,10 @@ circuit_build_times_t *get_circuit_build_times_mutable(void);
double get_circuit_build_close_time_ms(void);
double get_circuit_build_timeout_ms(void);
-int circuit_build_times_disabled(void);
+int circuit_build_times_disabled(const or_options_t *options);
+int circuit_build_times_disabled_(const or_options_t *options,
+ int ignore_consensus);
+
int circuit_build_times_enough_to_compute(const circuit_build_times_t *cbt);
void circuit_build_times_update_state(const circuit_build_times_t *cbt,
or_state_t *state);
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 84574cd5b9..c2b450606b 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -6,11 +6,30 @@
/**
* \file circuituse.c
- * \brief Launch the right sort of circuits and attach streams to them.
+ * \brief Launch the right sort of circuits and attach the right streams to
+ * them.
+ *
+ * As distinct from circuitlist.c, which manages lookups to find circuits, and
+ * circuitbuild.c, which handles the logistics of circuit construction, this
+ * module keeps track of which streams can be attached to which circuits (in
+ * circuit_get_best()), and attaches streams to circuits (with
+ * circuit_try_attaching_streams(), connection_ap_handshake_attach_circuit(),
+ * and connection_ap_handshake_attach_chosen_circuit() ).
+ *
+ * This module also makes sure that we are building circuits for all of the
+ * predicted ports, using circuit_remove_handled_ports(),
+ * circuit_stream_is_being_handled(), and circuit_build_needed_cirs(). It
+ * handles launching circuits for specific targets using
+ * circuit_launch_by_extend_info().
+ *
+ * This is also where we handle expiring circuits that have been around for
+ * too long without actually completing, along with the circuit_build_timeout
+ * logic in circuitstats.c.
**/
#include "or.h"
#include "addressmap.h"
+#include "bridges.h"
#include "channel.h"
#include "circpathbias.h"
#include "circuitbuild.h"
@@ -22,6 +41,7 @@
#include "connection_edge.h"
#include "control.h"
#include "entrynodes.h"
+#include "hs_common.h"
#include "nodelist.h"
#include "networkstatus.h"
#include "policies.h"
@@ -154,8 +174,8 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ,
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(edge_conn->rend_data->onion_address,
- origin_circ->rend_data->onion_address))) {
+ 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;
}
@@ -530,16 +550,14 @@ circuit_expire_building(void)
== CPATH_STATE_OPEN;
log_info(LD_CIRC,
"No circuits are opened. Relaxing timeout for circuit %d "
- "(a %s %d-hop circuit in state %s with channel state %s). "
- "%d guards are live.",
+ "(a %s %d-hop circuit in state %s with channel state %s).",
TO_ORIGIN_CIRCUIT(victim)->global_identifier,
circuit_purpose_to_string(victim->purpose),
TO_ORIGIN_CIRCUIT(victim)->build_state ?
TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len :
-1,
circuit_state_to_string(victim->state),
- channel_state_to_string(victim->n_chan->state),
- num_live_entry_guards(0));
+ channel_state_to_string(victim->n_chan->state));
/* We count the timeout here for CBT, because technically this
* was a timeout, and the timeout value needs to reset if we
@@ -557,7 +575,7 @@ circuit_expire_building(void)
"No circuits are opened. Relaxed timeout for circuit %d "
"(a %s %d-hop circuit in state %s with channel state %s) to "
"%ldms. However, it appears the circuit has timed out "
- "anyway. %d guards are live.",
+ "anyway.",
TO_ORIGIN_CIRCUIT(victim)->global_identifier,
circuit_purpose_to_string(victim->purpose),
TO_ORIGIN_CIRCUIT(victim)->build_state ?
@@ -565,8 +583,7 @@ circuit_expire_building(void)
-1,
circuit_state_to_string(victim->state),
channel_state_to_string(victim->n_chan->state),
- (long)build_close_ms,
- num_live_entry_guards(0));
+ (long)build_close_ms);
}
}
@@ -780,6 +797,25 @@ circuit_expire_building(void)
} SMARTLIST_FOREACH_END(victim);
}
+/**
+ * Mark for close all circuits that start here, that were built through a
+ * guard we weren't sure if we wanted to use, and that have been waiting
+ * around for way too long.
+ */
+void
+circuit_expire_waiting_for_better_guard(void)
+{
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_origin_circuit_list(),
+ origin_circuit_t *, circ) {
+ if (TO_CIRCUIT(circ)->marked_for_close)
+ continue;
+ if (circ->guard_state == NULL)
+ continue;
+ if (entry_guard_state_should_expire(circ->guard_state))
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NONE);
+ } SMARTLIST_FOREACH_END(circ);
+}
+
/** For debugging #8387: track when we last called
* circuit_expire_old_circuits_clientside. */
static time_t last_expired_clientside_circuits = 0;
@@ -1003,8 +1039,117 @@ circuit_stream_is_being_handled(entry_connection_t *conn,
/** Don't keep more than this many unused open circuits around. */
#define MAX_UNUSED_OPEN_CIRCUITS 14
-/** Figure out how many circuits we have open that are clean. Make
- * sure it's enough for all the upcoming behaviors we predict we'll have.
+/* Return true if a circuit is available for use, meaning that it is open,
+ * clean, usable for new multi-hop connections, and a general purpose origin
+ * circuit.
+ * Accept any kind of circuit, return false if the above conditions are not
+ * met. */
+STATIC int
+circuit_is_available_for_use(const circuit_t *circ)
+{
+ const origin_circuit_t *origin_circ;
+ cpath_build_state_t *build_state;
+
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ return 0; /* We first filter out only origin circuits before doing the
+ following checks. */
+ if (circ->marked_for_close)
+ return 0; /* Don't mess with marked circs */
+ if (circ->timestamp_dirty)
+ return 0; /* Only count clean circs */
+ if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL)
+ return 0; /* We only pay attention to general purpose circuits.
+ General purpose circuits are always origin circuits. */
+
+ origin_circ = CONST_TO_ORIGIN_CIRCUIT(circ);
+ if (origin_circ->unusable_for_new_conns)
+ return 0;
+
+ build_state = origin_circ->build_state;
+ if (build_state->onehop_tunnel)
+ return 0;
+
+ return 1;
+}
+
+/* Return true if we need any more exit circuits.
+ * needs_uptime and needs_capacity are set only if we need more exit circuits.
+ * Check if we know of a port that's been requested recently and no circuit
+ * is currently available that can handle it. */
+STATIC int
+needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity)
+{
+ return (!circuit_all_predicted_ports_handled(now, needs_uptime,
+ needs_capacity) &&
+ router_have_consensus_path() == CONSENSUS_PATH_EXIT);
+}
+
+/* Hidden services need at least this many internal circuits */
+#define SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS 3
+
+/* 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)
+{
+ return (num_rend_services() &&
+ num_uptime_internal < SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS &&
+ router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
+}
+
+/* We need at least this many internal circuits for hidden service clients */
+#define SUFFICIENT_INTERNAL_HS_CLIENTS 3
+
+/* We need at least this much uptime for internal circuits for hidden service
+ * clients */
+#define SUFFICIENT_UPTIME_INTERNAL_HS_CLIENTS 2
+
+/* Return true if we need any more hidden service client circuits.
+ * HS clients only need an internal circuit. */
+STATIC int
+needs_hs_client_circuits(time_t now, int *needs_uptime, int *needs_capacity,
+ int num_internal, int num_uptime_internal)
+{
+ int used_internal_recently = rep_hist_get_predicted_internal(now,
+ needs_uptime,
+ needs_capacity);
+ int requires_uptime = num_uptime_internal <
+ SUFFICIENT_UPTIME_INTERNAL_HS_CLIENTS &&
+ needs_uptime;
+
+ return (used_internal_recently &&
+ (requires_uptime || num_internal < SUFFICIENT_INTERNAL_HS_CLIENTS) &&
+ router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
+}
+
+/* The minimum number of open slots we should keep in order to preemptively
+ * build circuits. */
+#define CBT_MIN_REMAINING_PREEMPTIVE_CIRCUITS 2
+
+/* Check to see if we need more circuits to have a good build timeout. However,
+ * leave a couple slots open so that we can still build circuits preemptively
+ * as needed. */
+#define CBT_MAX_UNUSED_OPEN_CIRCUITS (MAX_UNUSED_OPEN_CIRCUITS - \
+ CBT_MIN_REMAINING_PREEMPTIVE_CIRCUITS)
+
+/* Return true if we need more circuits for a good build timeout.
+ * XXXX make the assumption that build timeout streams should be
+ * created whenever we can build internal circuits. */
+STATIC int
+needs_circuits_for_build(int num)
+{
+ if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (num < CBT_MAX_UNUSED_OPEN_CIRCUITS &&
+ !circuit_build_times_disabled(get_options()) &&
+ circuit_build_times_needs_circuits_now(get_circuit_build_times())) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/** Determine how many circuits we have open that are clean,
+ * Make sure it's enough for all the upcoming behaviors we predict we'll have.
* But put an upper bound on the total number of circuits.
*/
static void
@@ -1016,25 +1161,14 @@ circuit_predict_and_launch_new(void)
time_t now = time(NULL);
int flags = 0;
- /* First, count how many of each type of circuit we have already. */
+ /* Count how many of each type of circuit we currently have. */
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
- cpath_build_state_t *build_state;
- origin_circuit_t *origin_circ;
- if (!CIRCUIT_IS_ORIGIN(circ))
- continue;
- if (circ->marked_for_close)
- continue; /* don't mess with marked circs */
- if (circ->timestamp_dirty)
- continue; /* only count clean circs */
- if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL)
- continue; /* only pay attention to general-purpose circs */
- origin_circ = TO_ORIGIN_CIRCUIT(circ);
- if (origin_circ->unusable_for_new_conns)
- continue;
- build_state = origin_circ->build_state;
- if (build_state->onehop_tunnel)
+ if (!circuit_is_available_for_use(circ))
continue;
+
num++;
+
+ cpath_build_state_t *build_state = TO_ORIGIN_CIRCUIT(circ)->build_state;
if (build_state->is_internal)
num_internal++;
if (build_state->need_uptime && build_state->is_internal)
@@ -1044,19 +1178,14 @@ circuit_predict_and_launch_new(void)
/* If that's enough, then stop now. */
if (num >= MAX_UNUSED_OPEN_CIRCUITS)
- return; /* we already have many, making more probably will hurt */
-
- /* Second, see if we need any more exit circuits. */
- /* check if we know of a port that's been requested recently
- * and no circuit is currently available that can handle it.
- * Exits (obviously) require an exit circuit. */
- if (!circuit_all_predicted_ports_handled(now, &port_needs_uptime,
- &port_needs_capacity)
- && router_have_consensus_path() == CONSENSUS_PATH_EXIT) {
+ return;
+
+ if (needs_exit_circuits(now, &port_needs_uptime, &port_needs_capacity)) {
if (port_needs_uptime)
flags |= CIRCLAUNCH_NEED_UPTIME;
if (port_needs_capacity)
flags |= CIRCLAUNCH_NEED_CAPACITY;
+
log_info(LD_CIRC,
"Have %d clean circs (%d internal), need another exit circ.",
num, num_internal);
@@ -1064,12 +1193,10 @@ circuit_predict_and_launch_new(void)
return;
}
- /* Third, see if we need any more hidden service (server) circuits.
- * HS servers only need an internal circuit. */
- if (num_rend_services() && num_uptime_internal < 3
- && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (needs_hs_server_circuits(num_uptime_internal)) {
flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME |
CIRCLAUNCH_IS_INTERNAL);
+
log_info(LD_CIRC,
"Have %d clean circs (%d internal), need another internal "
"circ for my hidden service.",
@@ -1078,18 +1205,16 @@ circuit_predict_and_launch_new(void)
return;
}
- /* Fourth, see if we need any more hidden service (client) circuits.
- * HS clients only need an internal circuit. */
- if (rep_hist_get_predicted_internal(now, &hidserv_needs_uptime,
- &hidserv_needs_capacity) &&
- ((num_uptime_internal<2 && hidserv_needs_uptime) ||
- num_internal<3)
- && router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (needs_hs_client_circuits(now, &hidserv_needs_uptime,
+ &hidserv_needs_capacity,
+ num_internal, num_uptime_internal))
+ {
if (hidserv_needs_uptime)
flags |= CIRCLAUNCH_NEED_UPTIME;
if (hidserv_needs_capacity)
flags |= CIRCLAUNCH_NEED_CAPACITY;
flags |= CIRCLAUNCH_IS_INTERNAL;
+
log_info(LD_CIRC,
"Have %d clean circs (%d uptime-internal, %d internal), need"
" another hidden service circ.",
@@ -1098,26 +1223,17 @@ circuit_predict_and_launch_new(void)
return;
}
- /* Finally, check to see if we still need more circuits to learn
- * a good build timeout. But if we're close to our max number we
- * want, don't do another -- we want to leave a few slots open so
- * we can still build circuits preemptively as needed.
- * XXXX make the assumption that build timeout streams should be
- * created whenever we can build internal circuits. */
- if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
- if (num < MAX_UNUSED_OPEN_CIRCUITS-2 &&
- ! circuit_build_times_disabled() &&
- circuit_build_times_needs_circuits_now(get_circuit_build_times())) {
- flags = CIRCLAUNCH_NEED_CAPACITY;
- /* if there are no exits in the consensus, make timeout
- * circuits internal */
- if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL)
- flags |= CIRCLAUNCH_IS_INTERNAL;
+ if (needs_circuits_for_build(num)) {
+ flags = CIRCLAUNCH_NEED_CAPACITY;
+ /* if there are no exits in the consensus, make timeout
+ * circuits internal */
+ if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL)
+ flags |= CIRCLAUNCH_IS_INTERNAL;
+
log_info(LD_CIRC,
"Have %d clean circs need another buildtime test circ.", num);
circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags);
return;
- }
}
}
@@ -1284,7 +1400,7 @@ circuit_expire_old_circuits_clientside(void)
cutoff = now;
last_expired_clientside_circuits = now.tv_sec;
- if (! circuit_build_times_disabled() &&
+ if (! circuit_build_times_disabled(get_options()) &&
circuit_build_times_needs_circuits(get_circuit_build_times())) {
/* Circuits should be shorter lived if we need more of them
* for learning a good build timeout */
@@ -1613,7 +1729,9 @@ circuit_build_failed(origin_circuit_t *circ)
"Our circuit died before the first hop with no connection");
}
if (n_chan_id && !already_marked) {
- entry_guard_register_connect_status(n_chan_id, 0, 1, time(NULL));
+ /* New guard API: we failed. */
+ if (circ->guard_state)
+ entry_guard_failed(&circ->guard_state);
/* if there are any one-hop streams waiting on this circuit, fail
* them now so they can retry elsewhere. */
connection_ap_fail_onehop(n_chan_id, circ->build_state);
@@ -1874,16 +1992,22 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
c->state, conn_state_to_string(c->type, c->state));
}
tor_assert(ENTRY_TO_CONN(conn)->state == AP_CONN_STATE_CIRCUIT_WAIT);
+
+ /* Will the exit policy of the exit node apply to this stream? */
check_exit_policy =
conn->socks_request->command == SOCKS_COMMAND_CONNECT &&
!conn->use_begindir &&
!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn));
+
+ /* Does this connection want a one-hop circuit? */
want_onehop = conn->want_onehop;
+ /* Do we need a high-uptime circuit? */
need_uptime = !conn->want_onehop && !conn->use_begindir &&
smartlist_contains_int_as_string(options->LongLivedPorts,
conn->socks_request->port);
+ /* Do we need an "internal" circuit? */
if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL)
need_internal = 1;
else if (conn->use_begindir || conn->want_onehop)
@@ -1891,23 +2015,33 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
else
need_internal = 0;
- circ = circuit_get_best(conn, 1, desired_circuit_purpose,
+ /* We now know what kind of circuit we need. See if there is an
+ * open circuit that we can use for this stream */
+ circ = circuit_get_best(conn, 1 /* Insist on open circuits */,
+ desired_circuit_purpose,
need_uptime, need_internal);
if (circ) {
+ /* We got a circuit that will work for this stream! We can return it. */
*circp = circ;
return 1; /* we're happy */
}
+ /* Okay, there's no circuit open that will work for this stream. Let's
+ * see if there's an in-progress circuit or if we have to launch one */
+
+ /* Do we know enough directory info to build circuits at all? */
int have_path = have_enough_path_info(!need_internal);
if (!want_onehop && (!router_have_minimum_dir_info() || !have_path)) {
+ /* If we don't have enough directory information, we can't build
+ * multihop circuits.
+ */
if (!connection_get_by_type(CONN_TYPE_DIR)) {
int severity = LOG_NOTICE;
- /* FFFF if this is a tunneled directory fetch, don't yell
- * as loudly. the user doesn't even know it's happening. */
+ /* Retry some stuff that might help the connection work. */
if (entry_list_is_constrained(options) &&
- entries_known_but_down(options)) {
+ guards_retry_optimistic(options)) {
log_fn(severity, LD_APP|LD_DIR,
"Application request when we haven't %s. "
"Optimistically trying known %s again.",
@@ -1915,7 +2049,6 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
"used client functionality lately" :
"received a consensus with exits",
options->UseBridges ? "bridges" : "entrynodes");
- entries_retry_all(options);
} else if (!options->UseBridges || any_bridge_descriptors_known()) {
log_fn(severity, LD_APP|LD_DIR,
"Application request when we haven't %s. "
@@ -1926,14 +2059,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
routerlist_retry_directory_downloads(time(NULL));
}
}
- /* the stream will be dealt with when router_have_minimum_dir_info becomes
- * 1, or when all directory attempts fail and directory_all_unreachable()
+ /* Since we didn't have enough directory info, we can't attach now. The
+ * stream will be dealt with when router_have_minimum_dir_info becomes 1,
+ * or when all directory attempts fail and directory_all_unreachable()
* kills it.
*/
return 0;
}
- /* Do we need to check exit policy? */
+ /* Check whether the exit policy of the chosen exit, or the exit policies
+ * of _all_ nodes, would forbid this node. */
if (check_exit_policy) {
if (!conn->chosen_exit_name) {
struct in_addr in;
@@ -1974,16 +2109,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
}
- /* is one already on the way? */
- circ = circuit_get_best(conn, 0, desired_circuit_purpose,
+ /* Now, check whether there already a circuit on the way that could handle
+ * this stream. This check matches the one above, but this time we
+ * do not require that the circuit will work. */
+ circ = circuit_get_best(conn, 0 /* don't insist on open circuits */,
+ desired_circuit_purpose,
need_uptime, need_internal);
if (circ)
log_debug(LD_CIRC, "one on the way!");
+
if (!circ) {
+ /* No open or in-progress circuit could handle this stream! We
+ * will have to launch one!
+ */
+
+ /* The chosen exit node, if there is one. */
extend_info_t *extend_info=NULL;
- uint8_t new_circ_purpose;
const int n_pending = count_pending_general_client_circuits();
+ /* Do we have too many pending circuits? */
if (n_pending >= options->MaxClientCircuitsPending) {
static ratelim_t delay_limit = RATELIM_INIT(10*60);
char *m;
@@ -1997,6 +2141,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
return 0;
}
+ /* 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) {
/* need to pick an intro point */
rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
@@ -2005,7 +2151,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
if (!extend_info) {
log_info(LD_REND,
"No intro points for '%s': re-fetching service descriptor.",
- safe_str_client(rend_data->onion_address));
+ safe_str_client(rend_data_get_address(rend_data)));
rend_client_refetch_v2_renddesc(rend_data);
connection_ap_mark_as_non_pending_circuit(conn);
ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT;
@@ -2013,7 +2159,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
log_info(LD_REND,"Chose %s as intro point for '%s'.",
extend_info_describe(extend_info),
- safe_str_client(rend_data->onion_address));
+ safe_str_client(rend_data_get_address(rend_data)));
}
/* If we have specified a particular exit node for our
@@ -2034,12 +2180,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
"Discarding this circuit.", conn->chosen_exit_name);
return -1;
}
- } else {
+ } else { /* ! (r && node_has_descriptor(r)) */
log_debug(LD_DIR, "considering %d, %s",
want_onehop, conn->chosen_exit_name);
if (want_onehop && conn->chosen_exit_name[0] == '$') {
/* We're asking for a one-hop circuit to a router that
* we don't have a routerinfo about. Make up an extend_info. */
+ /* XXX prop220: we need to make chosen_exit_name able to
+ * encode both key formats. This is not absolutely critical
+ * since this is just for one-hop circuits, but we should
+ * still get it done */
char digest[DIGEST_LEN];
char *hexdigest = conn->chosen_exit_name+1;
tor_addr_t addr;
@@ -2054,10 +2204,13 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
escaped_safe_str_client(conn->socks_request->address));
return -1;
}
+ /* XXXX prop220 add a workaround for ed25519 ID below*/
extend_info = extend_info_new(conn->chosen_exit_name+1,
- digest, NULL, NULL, &addr,
- conn->socks_request->port);
- } else {
+ digest,
+ NULL, /* Ed25519 ID */
+ NULL, NULL, /* onion keys */
+ &addr, conn->socks_request->port);
+ } else { /* ! (want_onehop && conn->chosen_exit_name[0] == '$') */
/* We will need an onion key for the router, and we
* don't have one. Refuse or relax requirements. */
log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
@@ -2075,8 +2228,10 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
}
}
- }
+ } /* Done checking for general circutis with chosen exits. */
+ /* What purpose do we need to launch this circuit with? */
+ uint8_t new_circ_purpose;
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED)
new_circ_purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND;
else if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
@@ -2085,6 +2240,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
new_circ_purpose = desired_circuit_purpose;
#ifdef ENABLE_TOR2WEB_MODE
+ /* If tor2Web is on, then hidden service requests should be one-hop.
+ */
if (options->Tor2webMode &&
(new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND ||
new_circ_purpose == CIRCUIT_PURPOSE_C_INTRODUCING)) {
@@ -2092,6 +2249,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
#endif
+ /* Determine what kind of a circuit to launch, and actually launch it. */
{
int flags = CIRCLAUNCH_NEED_CAPACITY;
if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
@@ -2103,6 +2261,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
extend_info_free(extend_info);
+ /* Now trigger things that need to happen when we launch circuits */
+
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) {
/* We just caused a circuit to get built because of this stream.
* If this stream has caused a _lot_ of circuits to be built, that's
@@ -2126,6 +2286,10 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn,
}
}
} /* endif (!circ) */
+
+ /* We either found a good circuit, or launched a new circuit, or failed to
+ * do so. Report success, and delay. */
+
if (circ) {
/* Mark the circuit with the isolation fields for this connection.
* When the circuit arrives, we'll clear these flags: this is
@@ -2325,7 +2489,9 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn,
pathbias_count_use_attempt(circ);
+ /* Now, actually link the connection. */
link_apconn_to_circ(conn, circ, cpath);
+
tor_assert(conn->socks_request);
if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
if (!conn->use_begindir)
@@ -2340,12 +2506,11 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn,
return 1;
}
-/** Try to find a safe live circuit for CONN_TYPE_AP connection conn. If
- * we don't find one: if conn cannot be handled by any known nodes,
- * warn and return -1 (conn needs to die, and is maybe already marked);
- * else launch new circuit (if necessary) and return 0.
- * Otherwise, associate conn with a safe live circuit, do the
- * right next step, and return 1.
+/** Try to find a safe live circuit for stream <b>conn</b>. If we find one,
+ * attach the stream, send appropriate cells, and return 1. Otherwise,
+ * try to launch new circuit(s) for the stream. If we can launch
+ * circuits, return 0. Otherwise, if we simply can't proceed with
+ * this stream, return -1. (conn needs to die, and is maybe already marked).
*/
/* XXXX this function should mark for close whenever it returns -1;
* its callers shouldn't have to worry about that. */
@@ -2364,6 +2529,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
conn_age = (int)(time(NULL) - base_conn->timestamp_created);
+ /* Is this connection so old that we should give up on it? */
if (conn_age >= get_options()->SocksTimeout) {
int severity = (tor_addr_is_null(&base_conn->addr) && !base_conn->port) ?
LOG_INFO : LOG_NOTICE;
@@ -2374,12 +2540,14 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
return -1;
}
+ /* We handle "general" (non-onion) connections much more straightforwardly.
+ */
if (!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn))) {
/* we're a general conn */
origin_circuit_t *circ=NULL;
/* Are we linked to a dir conn that aims to fetch a consensus?
- * We check here because this conn might no longer be needed. */
+ * We check here because the conn might no longer be needed. */
if (base_conn->linked_conn &&
base_conn->linked_conn->type == CONN_TYPE_DIR &&
base_conn->linked_conn->purpose == DIR_PURPOSE_FETCH_CONSENSUS) {
@@ -2397,6 +2565,9 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
}
}
+ /* If we have a chosen exit, we need to use a circuit that's
+ * 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);
int opt = conn->chosen_exit_optional;
@@ -2410,6 +2581,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
"Requested exit point '%s' is not known. %s.",
conn->chosen_exit_name, opt ? "Trying others" : "Closing");
if (opt) {
+ /* If we are allowed to ignore the .exit request, do so */
conn->chosen_exit_optional = 0;
tor_free(conn->chosen_exit_name);
return 0;
@@ -2422,6 +2594,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
"would refuse request. %s.",
conn->chosen_exit_name, opt ? "Trying others" : "Closing");
if (opt) {
+ /* If we are allowed to ignore the .exit request, do so */
conn->chosen_exit_optional = 0;
tor_free(conn->chosen_exit_name);
return 0;
@@ -2430,20 +2603,25 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)
}
}
- /* find the circuit that we should use, if there is one. */
+ /* Find the circuit that we should use, if there is one. Otherwise
+ * launch it. */
retval = circuit_get_open_circ_or_launch(
conn, CIRCUIT_PURPOSE_C_GENERAL, &circ);
- if (retval < 1) // XXXX++ if we totally fail, this still returns 0 -RD
+ if (retval < 1) {
+ /* We were either told "-1" (complete failure) or 0 (circuit in
+ * progress); we can't attach this stream yet. */
return retval;
+ }
log_debug(LD_APP|LD_CIRC,
"Attaching apconn to circ %u (stream %d sec old).",
(unsigned)circ->base_.n_circ_id, conn_age);
- /* print the circ's path, so people can figure out which circs are
+ /* print the circ's path, so clients can figure out which circs are
* sucking. */
circuit_log_path(LOG_INFO,LD_APP|LD_CIRC,circ);
- /* We have found a suitable circuit for our conn. Hurray. */
+ /* We have found a suitable circuit for our conn. Hurray. Do
+ * the attachment. */
return connection_ap_handshake_attach_chosen_circuit(conn, circ, NULL);
} else { /* we're a rendezvous conn */
diff --git a/src/or/circuituse.h b/src/or/circuituse.h
index 5973978c45..e5f8700ea2 100644
--- a/src/or/circuituse.h
+++ b/src/or/circuituse.h
@@ -13,6 +13,7 @@
#define TOR_CIRCUITUSE_H
void circuit_expire_building(void);
+void circuit_expire_waiting_for_better_guard(void);
void circuit_remove_handled_ports(smartlist_t *needed_ports);
int circuit_stream_is_being_handled(entry_connection_t *conn, uint16_t port,
int min);
@@ -59,5 +60,25 @@ int hostname_in_track_host_exits(const or_options_t *options,
const char *address);
void mark_circuit_unusable_for_new_conns(origin_circuit_t *circ);
+#ifdef TOR_UNIT_TESTS
+/* Used only by circuituse.c and test_circuituse.c */
+
+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_client_circuits(time_t now,
+ int *needs_uptime,
+ int *needs_capacity,
+ int num_internal,
+ int num_uptime_internal);
+
+STATIC int needs_circuits_for_build(int num);
+
+#endif
+
#endif
diff --git a/src/or/config.c b/src/or/config.c
index 42ff25877e..ea98ddacdc 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -6,11 +6,61 @@
/**
* \file config.c
- * \brief Code to parse and interpret configuration files.
+ * \brief Code to interpret the user's configuration of Tor.
+ *
+ * This module handles torrc configuration file, including parsing it,
+ * combining it with torrc.defaults and the command line, allowing
+ * user changes to it (via editing and SIGHUP or via the control port),
+ * writing it back to disk (because of SAVECONF from the control port),
+ * and -- most importantly, acting on it.
+ *
+ * The module additionally has some tools for manipulating and
+ * inspecting values that are calculated as a result of the
+ * configured options.
+ *
+ * <h3>How to add new options</h3>
+ *
+ * To add new items to the torrc, there are a minimum of three places to edit:
+ * <ul>
+ * <li>The or_options_t structure in or.h, where the options are stored.
+ * <li>The option_vars_ array below in this module, which configures
+ * the names of the torrc options, their types, their multiplicities,
+ * and their mappings to fields in or_options_t.
+ * <li>The manual in doc/tor.1.txt, to document what the new option
+ * is, and how it works.
+ * </ul>
+ *
+ * Additionally, you might need to edit these places too:
+ * <ul>
+ * <li>options_validate() below, in case you want to reject some possible
+ * values of the new configuration option.
+ * <li>options_transition_allowed() below, in case you need to
+ * forbid some or all changes in the option while Tor is
+ * running.
+ * <li>options_transition_affects_workers(), in case changes in the option
+ * might require Tor to relaunch or reconfigure its worker threads.
+ * <li>options_transition_affects_descriptor(), in case changes in the
+ * option might require a Tor relay to build and publish a new server
+ * descriptor.
+ * <li>options_act() and/or options_act_reversible(), in case there's some
+ * action that needs to be taken immediately based on the option's
+ * value.
+ * </ul>
+ *
+ * <h3>Changing the value of an option</h3>
+ *
+ * Because of the SAVECONF command from the control port, it's a bad
+ * idea to change the value of any user-configured option in the
+ * or_options_t. If you want to sometimes do this anyway, we recommend
+ * that you create a secondary field in or_options_t; that you have the
+ * user option linked only to the secondary field; that you use the
+ * secondary field to initialize the one that Tor actually looks at; and that
+ * you use the one Tor looks as the one that you modify.
**/
#define CONFIG_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "compat.h"
#include "addressmap.h"
#include "channel.h"
@@ -133,8 +183,17 @@ static config_abbrev_t option_abbrevs_[] = {
/** An entry for config_vars: "The option <b>name</b> is obsolete." */
#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL }
-#define VPORT(member,conftype,initvalue) \
- VAR(#member, conftype, member ## _lines, initvalue)
+/**
+ * Macro to declare *Port options. Each one comes in three entries.
+ * For example, most users should use "SocksPort" to configure the
+ * socks port, but TorBrowser wants to use __SocksPort so that it
+ * isn't stored by SAVECONF. The SocksPortLines virtual option is
+ * used to query both options from the controller.
+ */
+#define VPORT(member) \
+ VAR(#member "Lines", LINELIST_V, member ## _lines, NULL), \
+ VAR(#member, LINELIST_S, member ## _lines, NULL), \
+ VAR("__" #member, LINELIST_S, member ## _lines, NULL)
/** Array of configuration options. Until we disallow nonstandard
* abbreviations, order is significant, since the first matching option will
@@ -162,14 +221,14 @@ static config_var_t option_vars_[] = {
V(AuthDirInvalidCCs, CSV, ""),
V(AuthDirFastGuarantee, MEMUNIT, "100 KB"),
V(AuthDirGuardBWGuarantee, MEMUNIT, "2 MB"),
- V(AuthDirPinKeys, BOOL, "0"),
+ V(AuthDirPinKeys, BOOL, "1"),
V(AuthDirReject, LINELIST, NULL),
V(AuthDirRejectCCs, CSV, ""),
OBSOLETE("AuthDirRejectUnlisted"),
OBSOLETE("AuthDirListBadDirs"),
V(AuthDirListBadExits, BOOL, "0"),
V(AuthDirMaxServersPerAddr, UINT, "2"),
- V(AuthDirMaxServersPerAuthAddr,UINT, "5"),
+ OBSOLETE("AuthDirMaxServersPerAuthAddr"),
V(AuthDirHasIPv6Connectivity, BOOL, "0"),
VAR("AuthoritativeDirectory", BOOL, AuthoritativeDir, "0"),
V(AutomapHostsOnResolve, BOOL, "0"),
@@ -203,7 +262,7 @@ static config_var_t option_vars_[] = {
V(ConstrainedSockSize, MEMUNIT, "8192"),
V(ContactInfo, STRING, NULL),
V(ControlListenAddress, LINELIST, NULL),
- VPORT(ControlPort, LINELIST, NULL),
+ VPORT(ControlPort),
V(ControlPortFileGroupReadable,BOOL, "0"),
V(ControlPortWriteToFile, FILENAME, NULL),
V(ControlSocket, LINELIST, NULL),
@@ -221,7 +280,7 @@ static config_var_t option_vars_[] = {
V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"),
V(DirListenAddress, LINELIST, NULL),
V(DirPolicy, LINELIST, NULL),
- VPORT(DirPort, LINELIST, NULL),
+ VPORT(DirPort),
V(DirPortFrontPage, FILENAME, NULL),
VAR("DirReqStatistics", BOOL, DirReqStatistics_option, "1"),
VAR("DirAuthority", LINELIST, DirAuthorities, NULL),
@@ -239,7 +298,7 @@ static config_var_t option_vars_[] = {
OBSOLETE("DisableIOCP"),
OBSOLETE("DisableV2DirectoryInfo_"),
OBSOLETE("DynamicDHGroups"),
- VPORT(DNSPort, LINELIST, NULL),
+ VPORT(DNSPort),
V(DNSListenAddress, LINELIST, NULL),
V(DownloadExtraInfo, BOOL, "0"),
V(TestingEnableConnBwEvent, BOOL, "0"),
@@ -259,11 +318,13 @@ static config_var_t option_vars_[] = {
V(ExitPortStatistics, BOOL, "0"),
V(ExtendAllowPrivateAddresses, BOOL, "0"),
V(ExitRelay, AUTOBOOL, "auto"),
- VPORT(ExtORPort, LINELIST, NULL),
+ VPORT(ExtORPort),
V(ExtORPortCookieAuthFile, STRING, NULL),
V(ExtORPortCookieAuthFileGroupReadable, BOOL, "0"),
V(ExtraInfoStatistics, BOOL, "1"),
+ V(ExtendByEd25519ID, AUTOBOOL, "auto"),
V(FallbackDir, LINELIST, NULL),
+
V(UseDefaultFallbackDirs, BOOL, "1"),
OBSOLETE("FallbackNetworkstatusFile"),
@@ -304,7 +365,7 @@ static config_var_t option_vars_[] = {
VAR("HiddenServiceMaxStreams",LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceMaxStreamsCloseCircuit",LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL),
- V(HiddenServiceStatistics, BOOL, "1"),
+ VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
V(HidServAuth, LINELIST, NULL),
V(CloseHSClientCircuitsImmediatelyOnTimeout, BOOL, "0"),
V(CloseHSServiceRendCircuitsImmediatelyOnTimeout, BOOL, "0"),
@@ -345,7 +406,7 @@ static config_var_t option_vars_[] = {
V(NewCircuitPeriod, INTERVAL, "30 seconds"),
OBSOLETE("NamingAuthoritativeDirectory"),
V(NATDListenAddress, LINELIST, NULL),
- VPORT(NATDPort, LINELIST, NULL),
+ VPORT(NATDPort),
V(Nickname, STRING, NULL),
V(PredictedPortsRelevanceTime, INTERVAL, "1 hour"),
V(WarnUnsafeSocks, BOOL, "1"),
@@ -355,8 +416,10 @@ static config_var_t option_vars_[] = {
V(NumEntryGuards, UINT, "0"),
V(OfflineMasterKey, BOOL, "0"),
V(ORListenAddress, LINELIST, NULL),
- VPORT(ORPort, LINELIST, NULL),
+ VPORT(ORPort),
V(OutboundBindAddress, LINELIST, NULL),
+ V(OutboundBindAddressOR, LINELIST, NULL),
+ V(OutboundBindAddressExit, LINELIST, NULL),
OBSOLETE("PathBiasDisableRate"),
V(PathBiasCircThreshold, INT, "-1"),
@@ -427,7 +490,7 @@ static config_var_t option_vars_[] = {
V(ShutdownWaitLength, INTERVAL, "30 seconds"),
V(SocksListenAddress, LINELIST, NULL),
V(SocksPolicy, LINELIST, NULL),
- VPORT(SocksPort, LINELIST, NULL),
+ VPORT(SocksPort),
V(SocksTimeout, INTERVAL, "2 minutes"),
V(SSLKeyLifetime, INTERVAL, "0"),
OBSOLETE("StrictEntryNodes"),
@@ -442,19 +505,20 @@ static config_var_t option_vars_[] = {
V(TrackHostExits, CSV, NULL),
V(TrackHostExitsExpire, INTERVAL, "30 minutes"),
V(TransListenAddress, LINELIST, NULL),
- VPORT(TransPort, LINELIST, NULL),
+ VPORT(TransPort),
V(TransProxyType, STRING, "default"),
OBSOLETE("TunnelDirConns"),
V(UpdateBridgesFromAuthority, BOOL, "0"),
V(UseBridges, BOOL, "0"),
VAR("UseEntryGuards", BOOL, UseEntryGuards_option, "1"),
- V(UseEntryGuardsAsDirGuards, BOOL, "1"),
+ OBSOLETE("UseEntryGuardsAsDirGuards"),
V(UseGuardFraction, AUTOBOOL, "auto"),
V(UseMicrodescriptors, AUTOBOOL, "auto"),
OBSOLETE("UseNTorHandshake"),
V(User, STRING, NULL),
OBSOLETE("UserspaceIOCPBuffers"),
V(AuthDirSharedRandomness, BOOL, "1"),
+ V(AuthDirTestEd25519LinkKeys, BOOL, "1"),
OBSOLETE("V1AuthoritativeDirectory"),
OBSOLETE("V2AuthoritativeDirectory"),
VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"),
@@ -550,7 +614,6 @@ static const config_var_t testing_tor_network_defaults[] = {
V(EnforceDistinctSubnets, BOOL, "0"),
V(AssumeReachable, BOOL, "1"),
V(AuthDirMaxServersPerAddr, UINT, "0"),
- V(AuthDirMaxServersPerAuthAddr,UINT, "0"),
V(ClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL,
"0, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"),
V(ClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL,
@@ -788,7 +851,7 @@ set_options(or_options_t *new_val, char **msg)
tor_free(line);
}
} else {
- smartlist_add(elements, tor_strdup(options_format.vars[i].name));
+ smartlist_add_strdup(elements, options_format.vars[i].name);
smartlist_add(elements, NULL);
}
}
@@ -1517,6 +1580,34 @@ options_transition_requires_fresh_tls_context(const or_options_t *old_options,
return 0;
}
+/**
+ * Return true if changing the configuration from <b>old</b> to <b>new</b>
+ * affects the guard susbsystem.
+ */
+static int
+options_transition_affects_guards(const or_options_t *old,
+ const or_options_t *new)
+{
+ /* NOTE: Make sure this function stays in sync with
+ * entry_guards_set_filtered_flags */
+
+ tor_assert(old);
+ tor_assert(new);
+
+ return
+ (old->UseEntryGuards != new->UseEntryGuards ||
+ old->UseBridges != new->UseBridges ||
+ old->ClientUseIPv4 != new->ClientUseIPv4 ||
+ old->ClientUseIPv6 != new->ClientUseIPv6 ||
+ old->FascistFirewall != new->FascistFirewall ||
+ !routerset_equal(old->ExcludeNodes, new->ExcludeNodes) ||
+ !routerset_equal(old->EntryNodes, new->EntryNodes) ||
+ !smartlist_strings_eq(old->FirewallPorts, new->FirewallPorts) ||
+ !config_lines_eq(old->Bridges, new->Bridges) ||
+ !config_lines_eq(old->ReachableORAddresses, new->ReachableORAddresses) ||
+ !config_lines_eq(old->ReachableDirAddresses, new->ReachableDirAddresses));
+}
+
/** Fetch the active option list, and take actions based on it. All of the
* things we do should survive being done repeatedly. If present,
* <b>old_options</b> contains the previous value of the options.
@@ -1536,6 +1627,8 @@ options_act(const or_options_t *old_options)
const int transition_affects_workers =
old_options && options_transition_affects_workers(old_options, options);
int old_ewma_enabled;
+ const int transition_affects_guards =
+ old_options && options_transition_affects_guards(old_options, options);
/* disable ptrace and later, other basic debugging techniques */
{
@@ -1812,6 +1905,7 @@ options_act(const or_options_t *old_options)
if (old_options) {
int revise_trackexithosts = 0;
int revise_automap_entries = 0;
+ int abandon_circuits = 0;
if ((options->UseEntryGuards && !old_options->UseEntryGuards) ||
options->UseBridges != old_options->UseBridges ||
(options->UseBridges &&
@@ -1828,6 +1922,16 @@ options_act(const or_options_t *old_options)
"Changed to using entry guards or bridges, or changed "
"preferred or excluded node lists. "
"Abandoning previous circuits.");
+ abandon_circuits = 1;
+ }
+
+ if (transition_affects_guards) {
+ if (guards_update_all()) {
+ abandon_circuits = 1;
+ }
+ }
+
+ if (abandon_circuits) {
circuit_mark_all_unused_circs();
circuit_mark_all_dirty_circs_as_unusable();
revise_trackexithosts = 1;
@@ -1858,7 +1962,7 @@ options_act(const or_options_t *old_options)
addressmap_clear_invalid_automaps(options);
/* How long should we delay counting bridge stats after becoming a bridge?
- * We use this so we don't count people who used our bridge thinking it is
+ * We use this so we don't count clients who used our bridge thinking it is
* a relay. If you change this, don't forget to change the log message
* below. It's 4 hours (the time it takes to stop being used by clients)
* plus some extra time for clock skew. */
@@ -1909,6 +2013,8 @@ options_act(const or_options_t *old_options)
/* Only collect directory-request statistics on relays and bridges. */
options->DirReqStatistics = options->DirReqStatistics_option &&
server_mode(options);
+ options->HiddenServiceStatistics =
+ options->HiddenServiceStatistics_option && server_mode(options);
if (options->CellStatistics || options->DirReqStatistics ||
options->EntryStatistics || options->ExitPortStatistics ||
@@ -1923,7 +2029,6 @@ options_act(const or_options_t *old_options)
options->CellStatistics = 0;
options->EntryStatistics = 0;
options->ConnDirectionStatistics = 0;
- options->HiddenServiceStatistics = 0;
options->ExitPortStatistics = 0;
}
@@ -2009,13 +2114,6 @@ options_act(const or_options_t *old_options)
!options->BridgeAuthoritativeDir)
rep_hist_desc_stats_term();
- /* Check if we need to parse and add the EntryNodes config option. */
- if (options->EntryNodes &&
- (!old_options ||
- !routerset_equal(old_options->EntryNodes,options->EntryNodes) ||
- !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes)))
- entry_nodes_should_be_added();
-
/* Since our options changed, we might need to regenerate and upload our
* server descriptor.
*/
@@ -2340,8 +2438,8 @@ using_default_dir_authorities(const or_options_t *options)
* Fail if one or more of the following is true:
* - DNS name in <b>options-\>Address</b> cannot be resolved.
* - <b>options-\>Address</b> is a local host address.
- * - Attempt to getting local hostname fails.
- * - Attempt to getting network interface address fails.
+ * - Attempt at getting local hostname fails.
+ * - Attempt at getting network interface address fails.
*
* Return 0 if all is well, or -1 if we can't find a suitable
* public IP address.
@@ -2721,7 +2819,7 @@ compute_publishserverdescriptor(or_options_t *options)
#define MIN_REND_POST_PERIOD (10*60)
#define MIN_REND_POST_PERIOD_TESTING (5)
-/** Higest allowable value for PredictedPortsRelevanceTime; if this is
+/** Highest allowable value for PredictedPortsRelevanceTime; if this is
* too high, our selection of exits will decrease for an extended
* period of time to an uncomfortable level .*/
#define MAX_PREDICTED_CIRCS_RELEVANCE (60*60)
@@ -2853,8 +2951,7 @@ options_validate_single_onion(or_options_t *options, char **msg)
!options->Tor2webMode) {
REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as "
"an anonymous client. Please set Socks/Trans/NATD/DNSPort to 0, or "
- "HiddenServiceNonAnonymousMode to 0, or use the non-anonymous "
- "Tor2webMode.");
+ "revert HiddenServiceNonAnonymousMode to 0.");
}
/* If you run a hidden service in non-anonymous mode, the hidden service
@@ -2864,12 +2961,12 @@ options_validate_single_onion(or_options_t *options, char **msg)
REJECT("Non-anonymous (Tor2web) mode is incompatible with using Tor as a "
"hidden service. Please remove all HiddenServiceDir lines, or use "
"a version of tor compiled without --enable-tor2web-mode, or use "
- " HiddenServiceNonAnonymousMode.");
+ "HiddenServiceNonAnonymousMode.");
}
if (rend_service_allow_non_anonymous_connection(options)
&& options->UseEntryGuards) {
- /* Single Onion services only use entry guards when uploading descriptors,
+ /* Single Onion services only use entry guards when uploading descriptors;
* all other connections are one-hop. Further, Single Onions causes the
* hidden service code to do things which break the path bias
* detector, and it's far easier to turn off entry guards (and
@@ -2912,7 +3009,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
*msg = NULL;
/* Set UseEntryGuards from the configured value, before we check it below.
- * We change UseEntryGuards whenn it's incompatible with other options,
+ * We change UseEntryGuards when it's incompatible with other options,
* but leave UseEntryGuards_option with the original value.
* Always use the value of UseEntryGuards, not UseEntryGuards_option. */
options->UseEntryGuards = options->UseEntryGuards_option;
@@ -2998,7 +3095,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (!strcasecmp(options->TransProxyType, "default")) {
options->TransProxyType_parsed = TPT_DEFAULT;
} else if (!strcasecmp(options->TransProxyType, "pf-divert")) {
-#if !defined(__OpenBSD__) && !defined( DARWIN )
+#if !defined(OpenBSD) && !defined( DARWIN )
/* Later versions of OS X have pf */
REJECT("pf-divert is a OpenBSD-specific "
"and OS X/Darwin-specific feature.");
@@ -3264,23 +3361,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
"of the Internet, so they must not set Reachable*Addresses "
"or FascistFirewall or FirewallPorts or ClientUseIPv4 0.");
- /* We check if Reachable*Addresses blocks all addresses in
- * parse_reachable_addresses(). */
-
-#define WARN_PLEASE_USE_IPV6_LOG_MSG \
- "ClientPreferIPv6%sPort 1 is ignored unless tor is using IPv6. " \
- "Please set ClientUseIPv6 1, ClientUseIPv4 0, or configure bridges."
-
- if (!fascist_firewall_use_ipv6(options)
- && options->ClientPreferIPv6ORPort == 1)
- log_warn(LD_CONFIG, WARN_PLEASE_USE_IPV6_LOG_MSG, "OR");
-
- if (!fascist_firewall_use_ipv6(options)
- && options->ClientPreferIPv6DirPort == 1)
- log_warn(LD_CONFIG, WARN_PLEASE_USE_IPV6_LOG_MSG, "Dir");
-
-#undef WARN_PLEASE_USE_IPV6_LOG_MSG
-
if (options->UseBridges &&
server_mode(options))
REJECT("Servers must be able to freely connect to the rest "
@@ -3292,6 +3372,11 @@ options_validate(or_options_t *old_options, or_options_t *options,
if (options->UseBridges && options->EntryNodes)
REJECT("You cannot set both UseBridges and EntryNodes.");
+ /* If we have UseBridges as 1 and UseEntryGuards as 0, we end up bypassing
+ * the use of bridges */
+ if (options->UseBridges && !options->UseEntryGuards)
+ REJECT("Setting UseBridges requires also setting UseEntryGuards.");
+
options->MaxMemInQueues =
compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
server_mode(options));
@@ -3460,7 +3545,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
int severity = LOG_NOTICE;
/* Be a little quieter if we've deliberately disabled
* LearnCircuitBuildTimeout. */
- if (circuit_build_times_disabled()) {
+ if (circuit_build_times_disabled_(options, 1)) {
severity = LOG_INFO;
}
log_fn(severity, LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but "
@@ -4265,8 +4350,8 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess)
}
/* If we have less than 300 MB suggest disabling dircache */
-#define DIRCACHE_MIN_MB_BANDWIDTH 300
-#define DIRCACHE_MIN_BANDWIDTH (DIRCACHE_MIN_MB_BANDWIDTH*ONE_MEGABYTE)
+#define DIRCACHE_MIN_MEM_MB 300
+#define DIRCACHE_MIN_MEM_BYTES (DIRCACHE_MIN_MEM_MB*ONE_MEGABYTE)
#define STRINGIFY(val) #val
/** Create a warning message for emitting if we are a dircache but may not have
@@ -4286,21 +4371,21 @@ have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem,
}
}
if (options->DirCache) {
- if (total_mem < DIRCACHE_MIN_BANDWIDTH) {
+ if (total_mem < DIRCACHE_MIN_MEM_BYTES) {
if (options->BridgeRelay) {
*msg = tor_strdup("Running a Bridge with less than "
- STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
- "not recommended.");
+ STRINGIFY(DIRCACHE_MIN_MEM_MB) " MB of memory is not "
+ "recommended.");
} else {
*msg = tor_strdup("Being a directory cache (default) with less than "
- STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is "
- "not recommended and may consume most of the available "
+ STRINGIFY(DIRCACHE_MIN_MEM_MB) " MB of memory is not "
+ "recommended and may consume most of the available "
"resources, consider disabling this functionality by "
"setting the DirCache option to 0.");
}
}
} else {
- if (total_mem >= DIRCACHE_MIN_BANDWIDTH) {
+ if (total_mem >= DIRCACHE_MIN_MEM_BYTES) {
*msg = tor_strdup("DirCache is disabled and we are configured as a "
"relay. This may disqualify us from becoming a guard in the "
"future.");
@@ -4425,7 +4510,6 @@ options_transition_allowed(const or_options_t *old,
} while (0)
SB_NOCHANGE_STR(Address);
- SB_NOCHANGE_STR(PidFile);
SB_NOCHANGE_STR(ServerDNSResolvConfFile);
SB_NOCHANGE_STR(DirPortFrontPage);
SB_NOCHANGE_STR(CookieAuthFile);
@@ -5217,35 +5301,35 @@ addressmap_register_auto(const char *from, const char *to,
int from_wildcard = 0, to_wildcard = 0;
*msg = "whoops, forgot the error message";
- if (1) {
- if (!strcmp(to, "*") || !strcmp(from, "*")) {
- *msg = "can't remap from or to *";
- return -1;
- }
- /* Detect asterisks in expressions of type: '*.example.com' */
- if (!strncmp(from,"*.",2)) {
- from += 2;
- from_wildcard = 1;
- }
- if (!strncmp(to,"*.",2)) {
- to += 2;
- to_wildcard = 1;
- }
- if (to_wildcard && !from_wildcard) {
- *msg = "can only use wildcard (i.e. '*.') if 'from' address "
- "uses wildcard also";
- return -1;
- }
+ if (!strcmp(to, "*") || !strcmp(from, "*")) {
+ *msg = "can't remap from or to *";
+ return -1;
+ }
+ /* Detect asterisks in expressions of type: '*.example.com' */
+ if (!strncmp(from,"*.",2)) {
+ from += 2;
+ from_wildcard = 1;
+ }
+ if (!strncmp(to,"*.",2)) {
+ to += 2;
+ to_wildcard = 1;
+ }
- if (address_is_invalid_destination(to, 1)) {
- *msg = "destination is invalid";
- return -1;
- }
+ if (to_wildcard && !from_wildcard) {
+ *msg = "can only use wildcard (i.e. '*.') if 'from' address "
+ "uses wildcard also";
+ return -1;
+ }
- addressmap_register(from, tor_strdup(to), expires, addrmap_source,
- from_wildcard, to_wildcard);
+ if (address_is_invalid_destination(to, 1)) {
+ *msg = "destination is invalid";
+ return -1;
}
+
+ addressmap_register(from, tor_strdup(to), expires, addrmap_source,
+ from_wildcard, to_wildcard);
+
return 0;
}
@@ -5312,7 +5396,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
if (smartlist_len(elts) == 0)
- smartlist_add(elts, tor_strdup("stdout"));
+ smartlist_add_strdup(elts, "stdout");
if (smartlist_len(elts) == 1 &&
(!strcasecmp(smartlist_get(elts,0), "stdout") ||
@@ -5847,7 +5931,7 @@ get_options_from_transport_options_line(const char *line,const char *transport)
}
/* add it to the options smartlist */
- smartlist_add(options, tor_strdup(option));
+ smartlist_add_strdup(options, option);
log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option));
} SMARTLIST_FOREACH_END(option);
@@ -6172,6 +6256,7 @@ port_cfg_new(size_t namelen)
tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1);
port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t) + namelen + 1);
cfg->entry_cfg.ipv4_traffic = 1;
+ 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;
@@ -6525,7 +6610,7 @@ parse_port_config(smartlist_t *out,
/* This must be kept in sync with port_cfg_new's defaults */
int no_listen = 0, no_advertise = 0, all_addrs = 0,
bind_ipv4_only = 0, bind_ipv6_only = 0,
- ipv4_traffic = 1, ipv6_traffic = 0, prefer_ipv6 = 0, dns_request = 1,
+ ipv4_traffic = 1, ipv6_traffic = 1, prefer_ipv6 = 0, dns_request = 1,
onion_traffic = 1,
cache_ipv4 = 1, use_cached_ipv4 = 0,
cache_ipv6 = 0, use_cached_ipv6 = 0,
@@ -7763,7 +7848,7 @@ getinfo_helper_config(control_connection_t *conn,
case CONFIG_TYPE_CSV: type = "CommaList"; break;
case CONFIG_TYPE_CSV_INTERVAL: type = "TimeIntervalCommaList"; break;
case CONFIG_TYPE_LINELIST: type = "LineList"; break;
- case CONFIG_TYPE_LINELIST_S: type = "Dependant"; break;
+ case CONFIG_TYPE_LINELIST_S: type = "Dependent"; break;
case CONFIG_TYPE_LINELIST_V: type = "Virtual"; break;
default:
case CONFIG_TYPE_OBSOLETE:
@@ -7845,60 +7930,83 @@ getinfo_helper_config(control_connection_t *conn,
return 0;
}
-/** Parse outbound bind address option lines. If <b>validate_only</b>
- * is not 0 update OutboundBindAddressIPv4_ and
- * OutboundBindAddressIPv6_ in <b>options</b>. On failure, set
- * <b>msg</b> (if provided) to a newly allocated string containing a
- * description of the problem and return -1. */
+/* Check whether an address has already been set against the options
+ * depending on address family and destination type. Any exsting
+ * value will lead to a fail, even if it is the same value. If not
+ * set and not only validating, copy it into this location too.
+ * Returns 0 on success or -1 if this address is already set.
+ */
static int
-parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
+verify_and_store_outbound_address(sa_family_t family, tor_addr_t *addr,
+ outbound_addr_t type, or_options_t *options, int validate_only)
{
- const config_line_t *lines = options->OutboundBindAddress;
- int found_v4 = 0, found_v6 = 0;
-
+ if (type>=OUTBOUND_ADDR_MAX || (family!=AF_INET && family!=AF_INET6)) {
+ return -1;
+ }
+ int fam_index=0;
+ if (family==AF_INET6) {
+ fam_index=1;
+ }
+ tor_addr_t *dest=&options->OutboundBindAddresses[type][fam_index];
+ if (!tor_addr_is_null(dest)) {
+ return -1;
+ }
if (!validate_only) {
- memset(&options->OutboundBindAddressIPv4_, 0,
- sizeof(options->OutboundBindAddressIPv4_));
- memset(&options->OutboundBindAddressIPv6_, 0,
- sizeof(options->OutboundBindAddressIPv6_));
+ tor_addr_copy(dest, addr);
}
+ return 0;
+}
+
+/* Parse a list of address lines for a specific destination type.
+ * Will store them into the options if not validate_only. If a
+ * problem occurs, a suitable error message is store in msg.
+ * Returns 0 on success or -1 if any address is already set.
+ */
+static int
+parse_outbound_address_lines(const config_line_t *lines, outbound_addr_t type,
+ or_options_t *options, int validate_only, char **msg)
+{
+ tor_addr_t addr;
+ sa_family_t family;
while (lines) {
- tor_addr_t addr, *dst_addr = NULL;
- int af = tor_addr_parse(&addr, lines->value);
- switch (af) {
- case AF_INET:
- if (found_v4) {
- if (msg)
- tor_asprintf(msg, "Multiple IPv4 outbound bind addresses "
- "configured: %s", lines->value);
- return -1;
- }
- found_v4 = 1;
- dst_addr = &options->OutboundBindAddressIPv4_;
- break;
- case AF_INET6:
- if (found_v6) {
- if (msg)
- tor_asprintf(msg, "Multiple IPv6 outbound bind addresses "
- "configured: %s", lines->value);
- return -1;
- }
- found_v6 = 1;
- dst_addr = &options->OutboundBindAddressIPv6_;
- break;
- default:
+ family = tor_addr_parse(&addr, lines->value);
+ if (verify_and_store_outbound_address(family, &addr, type,
+ options, validate_only)) {
if (msg)
- tor_asprintf(msg, "Outbound bind address '%s' didn't parse.",
- lines->value);
+ tor_asprintf(msg, "Multiple%s%s outbound bind addresses "
+ "configured: %s",
+ family==AF_INET?" IPv4":(family==AF_INET6?" IPv6":""),
+ type==OUTBOUND_ADDR_OR?" OR":
+ (type==OUTBOUND_ADDR_EXIT?" exit":""), lines->value);
return -1;
}
- if (!validate_only)
- tor_addr_copy(dst_addr, &addr);
lines = lines->next;
}
return 0;
}
+/** Parse outbound bind address option lines. If <b>validate_only</b>
+ * is not 0 update OutboundBindAddresses in <b>options</b>.
+ * Only one address can be set for any of these values.
+ * On failure, set <b>msg</b> (if provided) to a newly allocated string
+ * containing a description of the problem and return -1.
+ */
+static int
+parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
+{
+ if (!validate_only) {
+ 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);
+ return 0;
+}
+
/** Load one of the geoip files, <a>family</a> determining which
* one. <a>default_fname</a> is used if on Windows and
* <a>fname</a> equals "<default>". */
diff --git a/src/or/confparse.c b/src/or/confparse.c
index efcf4f981e..9b13a91856 100644
--- a/src/or/confparse.c
+++ b/src/or/confparse.c
@@ -1,3 +1,4 @@
+
/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
@@ -9,6 +10,16 @@
*
* \brief Back-end for parsing and generating key-value files, used to
* implement the torrc file format and the state file.
+ *
+ * This module is used by config.c to parse and encode torrc
+ * configuration files, and by statefile.c to parse and encode the
+ * $DATADIR/state file.
+ *
+ * To use this module, its callers provide an instance of
+ * config_format_t to describe the mappings from a set of configuration
+ * options to a number of fields in a C structure. With this mapping,
+ * the functions here can convert back and forth between the C structure
+ * specified, and a linked list of key-value pairs.
*/
#include "or.h"
@@ -20,6 +31,8 @@ static int config_parse_msec_interval(const char *s, int *ok);
static int config_parse_interval(const char *s, int *ok);
static void config_reset(const config_format_t *fmt, void *options,
const config_var_t *var, int use_defaults);
+static config_line_t *config_lines_dup_and_filter(const config_line_t *inp,
+ const char *key);
/** Allocate an empty configuration object of a given format type. */
void *
@@ -624,9 +637,22 @@ config_value_needs_escape(const char *value)
config_line_t *
config_lines_dup(const config_line_t *inp)
{
+ return config_lines_dup_and_filter(inp, NULL);
+}
+
+/** Return a newly allocated deep copy of the lines in <b>inp</b>,
+ * but only the ones that match <b>key</b>. */
+static config_line_t *
+config_lines_dup_and_filter(const config_line_t *inp,
+ const char *key)
+{
config_line_t *result = NULL;
config_line_t **next_out = &result;
while (inp) {
+ if (key && strcasecmpstart(inp->key, key)) {
+ inp = inp->next;
+ continue;
+ }
*next_out = tor_malloc_zero(sizeof(config_line_t));
(*next_out)->key = tor_strdup(inp->key);
(*next_out)->value = tor_strdup(inp->value);
@@ -753,11 +779,11 @@ config_get_assigned_option(const config_format_t *fmt, const void *options,
tor_free(result);
return NULL;
case CONFIG_TYPE_LINELIST_S:
- log_warn(LD_CONFIG,
- "Can't return context-sensitive '%s' on its own", key);
tor_free(result->key);
tor_free(result);
- return NULL;
+ result = config_lines_dup_and_filter(*(const config_line_t **)value,
+ key);
+ break;
case CONFIG_TYPE_LINELIST:
case CONFIG_TYPE_LINELIST_V:
tor_free(result->key);
@@ -1148,6 +1174,11 @@ config_dump(const config_format_t *fmt, const void *default_options,
config_get_assigned_option(fmt, options, fmt->vars[i].name, 1);
for (; line; line = line->next) {
+ if (!strcmpstart(line->key, "__")) {
+ /* This check detects "hidden" variables inside LINELIST_V structures.
+ */
+ continue;
+ }
smartlist_add_asprintf(elements, "%s%s %s\n",
comment_option ? "# " : "",
line->key, line->value);
@@ -1213,6 +1244,8 @@ static struct unit_table_t memory_units[] = {
{ "gbits", 1<<27 },
{ "gbit", 1<<27 },
{ "tb", U64_LITERAL(1)<<40 },
+ { "tbyte", U64_LITERAL(1)<<40 },
+ { "tbytes", U64_LITERAL(1)<<40 },
{ "terabyte", U64_LITERAL(1)<<40 },
{ "terabytes", U64_LITERAL(1)<<40 },
{ "terabits", U64_LITERAL(1)<<37 },
diff --git a/src/or/connection.c b/src/or/connection.c
index 8b00d637f6..5628c6a3f9 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -34,7 +34,7 @@
* they become able to read or write register the fact with the event main
* loop by calling connection_watch_events(), connection_start_reading(), or
* connection_start_writing(). When they no longer want to read or write,
- * they call connection_stop_reading() or connection_start_writing().
+ * 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
@@ -56,6 +56,7 @@
#define CONNECTION_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "buffers.h"
/*
* Define this so we get channel internal functions, since we're implementing
@@ -82,6 +83,7 @@
#include "ext_orport.h"
#include "geoip.h"
#include "main.h"
+#include "hs_common.h"
#include "nodelist.h"
#include "policies.h"
#include "reasons.h"
@@ -132,6 +134,8 @@ static int connection_read_https_proxy_response(connection_t *conn);
static void connection_send_socks5_connect(connection_t *conn);
static const char *proxy_type_to_string(int proxy_type);
static int get_proxy_type(void);
+const tor_addr_t *conn_get_outbound_address(sa_family_t family,
+ const or_options_t *options, unsigned int conn_type);
/** The last addresses that our network interface seemed to have been
* binding to. We use this as one way to detect when our IP changes.
@@ -632,6 +636,11 @@ connection_free_(connection_t *conn)
cached_dir_decref(dir_conn->cached_dir);
rend_data_free(dir_conn->rend_data);
+ if (dir_conn->guard_state) {
+ /* Cancel before freeing, if it's still there. */
+ entry_guard_cancel(&dir_conn->guard_state);
+ }
+ circuit_guard_state_free(dir_conn->guard_state);
}
if (SOCKET_OK(conn->s)) {
@@ -643,7 +652,7 @@ connection_free_(connection_t *conn)
if (conn->type == CONN_TYPE_OR &&
!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest)) {
log_warn(LD_BUG, "called on OR conn with non-zeroed identity_digest");
- connection_or_remove_from_identity_map(TO_OR_CONN(conn));
+ connection_or_clear_identity(TO_OR_CONN(conn));
}
if (conn->type == CONN_TYPE_OR || conn->type == CONN_TYPE_EXT_OR) {
connection_or_remove_from_ext_or_id_map(TO_OR_CONN(conn));
@@ -674,7 +683,7 @@ connection_free,(connection_t *conn))
}
if (connection_speaks_cells(conn)) {
if (!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest)) {
- connection_or_remove_from_identity_map(TO_OR_CONN(conn));
+ connection_or_clear_identity(TO_OR_CONN(conn));
}
}
if (conn->type == CONN_TYPE_CONTROL) {
@@ -1768,7 +1777,7 @@ connection_connect_sockaddr,(connection_t *conn,
/*
* We've got the socket open; give the OOS handler a chance to check
- * against configuured maximum socket number, but tell it no exhaustion
+ * against configured maximum socket number, but tell it no exhaustion
* failure.
*/
connection_check_oos(get_n_open_sockets(), 0);
@@ -1887,6 +1896,55 @@ connection_connect_log_client_use_ip_version(const connection_t *conn)
}
}
+/** Retrieve the outbound address depending on the protocol (IPv4 or IPv6)
+ * and the connection type (relay, exit, ...)
+ * Return a socket address or NULL in case nothing is configured.
+ **/
+const tor_addr_t *
+conn_get_outbound_address(sa_family_t family,
+ const or_options_t *options, unsigned int conn_type)
+{
+ const tor_addr_t *ext_addr = NULL;
+
+ int fam_index;
+ switch (family) {
+ case AF_INET:
+ fam_index = 0;
+ break;
+ case AF_INET6:
+ fam_index = 1;
+ break;
+ default:
+ return NULL;
+ }
+
+ // If an exit connection, use the exit address (if present)
+ if (conn_type == CONN_TYPE_EXIT) {
+ if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT][fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT]
+ [fam_index];
+ } else if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index];
+ }
+ } else { // All non-exit connections
+ if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_OR][fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_OR]
+ [fam_index];
+ } else if (!tor_addr_is_null(
+ &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index])) {
+ ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR]
+ [fam_index];
+ }
+ }
+ return ext_addr;
+}
+
/** Take conn, make a nonblocking socket; try to connect to
* addr:port (port arrives in *host order*). If fail, return -1 and if
* applicable put your best guess about errno into *<b>socket_error</b>.
@@ -1908,26 +1966,15 @@ connection_connect(connection_t *conn, const char *address,
struct sockaddr *bind_addr = NULL;
struct sockaddr *dest_addr;
int dest_addr_len, bind_addr_len = 0;
- const or_options_t *options = get_options();
- int protocol_family;
/* Log if we didn't stick to ClientUseIPv4/6 or ClientPreferIPv6OR/DirPort
*/
connection_connect_log_client_use_ip_version(conn);
- if (tor_addr_family(addr) == AF_INET6)
- protocol_family = PF_INET6;
- else
- protocol_family = PF_INET;
-
if (!tor_addr_is_loopback(addr)) {
const tor_addr_t *ext_addr = NULL;
- if (protocol_family == AF_INET &&
- !tor_addr_is_null(&options->OutboundBindAddressIPv4_))
- ext_addr = &options->OutboundBindAddressIPv4_;
- else if (protocol_family == AF_INET6 &&
- !tor_addr_is_null(&options->OutboundBindAddressIPv6_))
- ext_addr = &options->OutboundBindAddressIPv6_;
+ ext_addr = conn_get_outbound_address(tor_addr_family(addr), get_options(),
+ conn->type);
if (ext_addr) {
memset(&bind_addr_ss, 0, sizeof(bind_addr_ss));
bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0,
@@ -4133,12 +4180,12 @@ connection_get_by_type_state_rendquery(int type, int state,
(type == CONN_TYPE_DIR &&
TO_DIR_CONN(conn)->rend_data &&
!rend_cmp_service_ids(rendquery,
- TO_DIR_CONN(conn)->rend_data->onion_address))
+ rend_data_get_address(TO_DIR_CONN(conn)->rend_data)))
||
(CONN_IS_EDGE(conn) &&
TO_EDGE_CONN(conn)->rend_data &&
!rend_cmp_service_ids(rendquery,
- TO_EDGE_CONN(conn)->rend_data->onion_address))
+ rend_data_get_address(TO_EDGE_CONN(conn)->rend_data)))
));
}
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 12fe2f57c9..c8e32cfacb 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -75,6 +75,7 @@
#include "directory.h"
#include "dirserv.h"
#include "hibernate.h"
+#include "hs_common.h"
#include "main.h"
#include "nodelist.h"
#include "policies.h"
@@ -329,6 +330,33 @@ relay_send_end_cell_from_edge(streamid_t stream_id, circuit_t *circ,
payload, 1, cpath_layer);
}
+/* If the connection <b>conn</b> is attempting to connect to an external
+ * destination that is an hidden service and the reason is a connection
+ * refused or timeout, log it so the operator can take appropriate actions.
+ * The log statement is a rate limited warning. */
+static void
+warn_if_hs_unreachable(const edge_connection_t *conn, uint8_t reason)
+{
+ tor_assert(conn);
+
+ if (conn->base_.type == CONN_TYPE_EXIT &&
+ connection_edge_is_rendezvous_stream(conn) &&
+ (reason == END_STREAM_REASON_CONNECTREFUSED ||
+ reason == END_STREAM_REASON_TIMEOUT)) {
+#define WARN_FAILED_HS_CONNECTION 300
+ static ratelim_t warn_limit = RATELIM_INIT(WARN_FAILED_HS_CONNECTION);
+ char *m;
+ if ((m = rate_limit_log(&warn_limit, approx_time()))) {
+ log_warn(LD_EDGE, "Onion service connection to %s failed (%s)",
+ (conn->base_.socket_family == AF_UNIX) ?
+ safe_str(conn->base_.address) :
+ safe_str(fmt_addrport(&conn->base_.addr, conn->base_.port)),
+ stream_end_reason_to_string(reason));
+ tor_free(m);
+ }
+ }
+}
+
/** Send a relay end cell from stream <b>conn</b> down conn's circuit, and
* remember that we've done so. If this is not a client connection, set the
* relay end cell's reason for closing as <b>reason</b>.
@@ -386,6 +414,9 @@ connection_edge_end(edge_connection_t *conn, uint8_t reason)
conn->base_.s);
connection_edge_send_command(conn, RELAY_COMMAND_END,
payload, payload_len);
+ /* We'll log warn if the connection was an hidden service and couldn't be
+ * made because the service wasn't available. */
+ warn_if_hs_unreachable(conn, control_reason);
} else {
log_debug(LD_EDGE,"No circ to send end on conn "
"(fd "TOR_SOCKET_T_FORMAT").",
@@ -831,7 +862,8 @@ connection_ap_rescan_and_attach_pending(void)
#endif
/** Tell any AP streams that are listed as waiting for a new circuit to try
- * again, either attaching to an available circ or launching a new one.
+ * again. If there is an available circuit for a stream, attach it. Otherwise,
+ * launch a new circuit.
*
* If <b>retry</b> is false, only check the list if it contains at least one
* streams that we have not yet tried to attach to a circuit.
@@ -846,8 +878,9 @@ connection_ap_attach_pending(int retry)
if (untried_pending_connections == 0 && !retry)
return;
- /* Don't allow modifications to pending_entry_connections while we are
- * iterating over it. */
+ /* Don't allow any modifications to list while we are iterating over
+ * it. We'll put streams back on this list if we can't attach them
+ * immediately. */
smartlist_t *pending = pending_entry_connections;
pending_entry_connections = smartlist_new();
@@ -866,9 +899,7 @@ connection_ap_attach_pending(int retry)
continue;
}
if (conn->state != AP_CONN_STATE_CIRCUIT_WAIT) {
- // XXXX 030 -- this is downgraded in 0.2.9, since we apparently
- // XXXX are running into it in practice. It's harmless.
- log_info(LD_BUG, "%p is no longer in circuit_wait. Its current state "
+ log_warn(LD_BUG, "%p is no longer in circuit_wait. Its current state "
"is %s. Why is it on pending_entry_connections?",
entry_conn,
conn_state_to_string(conn->type, conn->state));
@@ -876,6 +907,7 @@ connection_ap_attach_pending(int retry)
continue;
}
+ /* Okay, we're through the sanity checks. Try to handle this stream. */
if (connection_ap_handshake_attach_circuit(entry_conn) < 0) {
if (!conn->marked_for_close)
connection_mark_unattached_ap(entry_conn,
@@ -885,12 +917,17 @@ connection_ap_attach_pending(int retry)
if (! conn->marked_for_close &&
conn->type == CONN_TYPE_AP &&
conn->state == AP_CONN_STATE_CIRCUIT_WAIT) {
+ /* Is it still waiting for a circuit? If so, we didn't attach it,
+ * so it's still pending. Put it back on the list.
+ */
if (!smartlist_contains(pending_entry_connections, entry_conn)) {
smartlist_add(pending_entry_connections, entry_conn);
continue;
}
}
+ /* If we got here, then we either closed the connection, or
+ * we attached it. */
UNMARK();
} SMARTLIST_FOREACH_END(entry_conn);
@@ -1198,6 +1235,8 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
/* Remember the original address so we can tell the user about what
* they actually said, not just what it turned into. */
+ /* XXX yes, this is the same as out->orig_address above. One is
+ * in the output, and one is in the connection. */
if (! conn->original_dest_address) {
/* Is the 'if' necessary here? XXXX */
conn->original_dest_address = tor_strdup(conn->socks_request->address);
@@ -1205,7 +1244,7 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
/* First, apply MapAddress and MAPADDRESS mappings. We need to do
* these only for non-reverse lookups, since they don't exist for those.
- * We need to do this before we consider automapping, since we might
+ * We also need to do this before we consider automapping, since we might
* e.g. resolve irc.oftc.net into irconionaddress.onion, at which point
* we'd need to automap it. */
if (socks->command != SOCKS_COMMAND_RESOLVE_PTR) {
@@ -1217,9 +1256,12 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
}
- /* Now, handle automapping. Automapping happens when we're asked to
- * resolve a hostname, and AutomapHostsOnResolve is set, and
- * the hostname has a suffix listed in AutomapHostsSuffixes.
+ /* Now see if we need to create or return an existing Hostname->IP
+ * automapping. Automapping happens when we're asked to resolve a
+ * hostname, and AutomapHostsOnResolve is set, and the hostname has a
+ * suffix listed in AutomapHostsSuffixes. It's a handy feature
+ * that lets you have Tor assign e.g. IPv6 addresses for .onion
+ * names, and return them safely from DNSPort.
*/
if (socks->command == SOCKS_COMMAND_RESOLVE &&
tor_addr_parse(&addr_tmp, socks->address)<0 &&
@@ -1259,7 +1301,8 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
/* Now handle reverse lookups, if they're in the cache. This doesn't
- * happen too often, since client-side DNS caching is off by default. */
+ * happen too often, since client-side DNS caching is off by default,
+ * and very deprecated. */
if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) {
unsigned rewrite_flags = 0;
if (conn->entry_cfg.use_cached_ipv4_answers)
@@ -1288,7 +1331,7 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
* an internal address? If so, we should reject it if we're configured to
* do so. */
if (options->ClientDNSRejectInternalAddresses) {
- /* Don't let people try to do a reverse lookup on 10.0.0.1. */
+ /* Don't let clients try to do a reverse lookup on 10.0.0.1. */
tor_addr_t addr;
int ok;
ok = tor_addr_parse_PTR_name(
@@ -1304,11 +1347,12 @@ connection_ap_handshake_rewrite(entry_connection_t *conn,
}
}
- /* If we didn't automap it before, then this is still the address
- * that came straight from the user, mapped according to any
- * MapAddress/MAPADDRESS commands. Now other mappings, including
- * previously registered Automap entries, TrackHostExits entries,
- * and client-side DNS cache entries (not recommended).
+ /* If we didn't automap it before, then this is still the address that
+ * came straight from the user, mapped according to any
+ * MapAddress/MAPADDRESS commands. Now apply other mappings,
+ * including previously registered Automap entries (IP back to
+ * hostname), TrackHostExits entries, and client-side DNS cache
+ * entries (if they're turned on).
*/
if (socks->command != SOCKS_COMMAND_RESOLVE_PTR &&
!out->automap) {
@@ -1373,11 +1417,14 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
time_t now = time(NULL);
rewrite_result_t rr;
+ /* First we'll do the rewrite part. Let's see if we get a reasonable
+ * answer.
+ */
memset(&rr, 0, sizeof(rr));
connection_ap_handshake_rewrite(conn,&rr);
if (rr.should_close) {
- /* connection_ap_handshake_rewrite told us to close the connection,
+ /* connection_ap_handshake_rewrite told us to close the connection:
* either because it sent back an answer, or because it sent back an
* error */
connection_mark_unattached_ap(conn, rr.end_reason);
@@ -1391,8 +1438,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
const int automap = rr.automap;
const addressmap_entry_source_t exit_source = rr.exit_source;
- /* Parse the address provided by SOCKS. Modify it in-place if it
- * specifies a hidden-service (.onion) or particular exit node (.exit).
+ /* Now, we parse the address to see if it's an .onion or .exit or
+ * other special address.
*/
const hostname_type_t addresstype = parse_extended_hostname(socks->address);
@@ -1406,8 +1453,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
/* If this is a .exit hostname, strip off the .name.exit part, and
- * see whether we're going to connect there, and otherwise handle it.
- * (The ".exit" part got stripped off by "parse_extended_hostname").
+ * see whether we're willing to connect there, and and otherwise handle the
+ * .exit address.
*
* We'll set chosen_exit_name and/or close the connection as appropriate.
*/
@@ -1419,7 +1466,7 @@ 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. */
+ * 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. */
@@ -1448,7 +1495,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
tor_assert(!automap);
- /* Now, find the character before the .(name) part. */
+
+ /* Now, find the character before the .(name) part.
+ * (The ".exit" part got stripped off by "parse_extended_hostname").
+ *
+ * We're going to put the exit name into conn->chosen_exit_name, and
+ * look up a node correspondingly. */
char *s = strrchr(socks->address,'.');
if (s) {
/* The address was of the form "(stuff).(name).exit */
@@ -1504,10 +1556,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
implies no. */
}
- /* Now, handle everything that isn't a .onion address. */
+ /* Now, we handle everything that isn't a .onion address. */
if (addresstype != ONION_HOSTNAME) {
/* Not a hidden-service request. It's either a hostname or an IP,
- * possibly with a .exit that we stripped off. */
+ * 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
+ * appropriate request. */
/* Check for funny characters in the address. */
if (address_is_invalid_destination(socks->address, 1)) {
@@ -1554,30 +1608,37 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
/* Then check if we have a hostname or IP address, and whether DNS or
- * the IP address family are permitted */
+ * the IP address family are permitted. Reject if not. */
tor_addr_t dummy_addr;
int socks_family = tor_addr_parse(&dummy_addr, socks->address);
/* family will be -1 for a non-onion hostname that's not an IP */
- if (socks_family == -1 && !conn->entry_cfg.dns_request) {
- log_warn(LD_APP, "Refusing to connect to hostname %s "
- "because Port has NoDNSRequest set.",
- safe_str_client(socks->address));
- connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
- return -1;
- } else if (socks_family == AF_INET && !conn->entry_cfg.ipv4_traffic) {
- log_warn(LD_APP, "Refusing to connect to IPv4 address %s because "
- "Port has NoIPv4Traffic set.",
- safe_str_client(socks->address));
- connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
- return -1;
- } else if (socks_family == AF_INET6 && !conn->entry_cfg.ipv6_traffic) {
- log_warn(LD_APP, "Refusing to connect to IPv6 address %s because "
- "Port has NoIPv6Traffic set.",
- safe_str_client(socks->address));
- connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
- return -1;
+ if (socks_family == -1) {
+ if (!conn->entry_cfg.dns_request) {
+ log_warn(LD_APP, "Refusing to connect to hostname %s "
+ "because Port has NoDNSRequest set.",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ }
+ } else if (socks_family == AF_INET) {
+ if (!conn->entry_cfg.ipv4_traffic) {
+ log_warn(LD_APP, "Refusing to connect to IPv4 address %s because "
+ "Port has NoIPv4Traffic set.",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ }
+ } else if (socks_family == AF_INET6) {
+ if (!conn->entry_cfg.ipv6_traffic) {
+ log_warn(LD_APP, "Refusing to connect to IPv6 address %s because "
+ "Port has NoIPv6Traffic set.",
+ safe_str_client(socks->address));
+ connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
+ return -1;
+ }
+ } else {
+ tor_assert_nonfatal_unreached_once();
}
- /* No else, we've covered all possible returned value. */
/* See if this is a hostname lookup that we can answer immediately.
* (For example, an attempt to look up the IP address for an IP address.)
@@ -1598,7 +1659,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
tor_assert(!automap);
rep_hist_note_used_resolve(now); /* help predict this next time */
} else if (socks->command == SOCKS_COMMAND_CONNECT) {
- /* Special handling for attempts to connect */
+ /* Now see if this is a connect request that we can reject immediately */
+
tor_assert(!automap);
/* Don't allow connections to port 0. */
if (socks->port == 0) {
@@ -1607,7 +1669,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
return -1;
}
/* You can't make connections to internal addresses, by default.
- * Exceptions are begindir requests (where the address is meaningless,
+ * Exceptions are begindir requests (where the address is meaningless),
* or cases where you've hand-configured a particular exit, thereby
* making the local address meaningful. */
if (options->ClientRejectInternalAddresses &&
@@ -1651,7 +1713,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
} /* end "if we should check for internal addresses" */
/* Okay. We're still doing a CONNECT, and it wasn't a private
- * address. Do special handling for literal IP addresses */
+ * address. Here we do special handling for literal IP addresses,
+ * to see if we should reject this preemptively, and to set up
+ * fields in conn->entry_cfg to tell the exit what AF we want. */
{
tor_addr_t addr;
/* XXX Duplicate call to tor_addr_parse. */
@@ -1694,11 +1758,15 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
}
+ /* we never allow IPv6 answers on socks4. (TODO: Is this smart?) */
if (socks->socks_version == 4)
conn->entry_cfg.ipv6_traffic = 0;
/* Still handling CONNECT. Now, check for exit enclaves. (Which we
- * don't do on BEGINDIR, or there is a chosen exit.)
+ * don't do on BEGINDIR, or when there is a chosen exit.)
+ *
+ * TODO: Should we remove this? Exit enclaves are nutty and don't
+ * work very well
*/
if (!conn->use_begindir && !conn->chosen_exit_name && !circ) {
/* see if we can find a suitable enclave exit */
@@ -1722,7 +1790,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
if (consider_plaintext_ports(conn, socks->port) < 0)
return -1;
- /* Remember the port so that we do predicted requests there. */
+ /* Remember the port so that we will predict that more requests
+ there will happen in the future. */
if (!conn->use_begindir) {
/* help predict this next time */
rep_hist_note_used_port(now, socks->port);
@@ -1731,7 +1800,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
rep_hist_note_used_resolve(now); /* help predict this next time */
/* no extra processing needed */
} else {
- /* We should only be doing CONNECT or RESOLVE! */
+ /* We should only be doing CONNECT, RESOLVE, or RESOLVE_PTR! */
tor_fragile_assert();
}
@@ -1747,6 +1816,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
if (circ) {
rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath);
} else {
+ /* 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);
rv = 0;
}
@@ -1820,24 +1891,26 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
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(rend_data->onion_address));
+ safe_str_client(onion_address));
- /* Lookup the given onion address. If invalid, stop right now else we
- * might have it in the cache or not, it will be tested later on. */
+ /* 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(rend_data->onion_address, -1, &entry);
+ 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(rend_data->onion_address));
+ 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:
@@ -1847,8 +1920,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
}
}
- /* Help predict this next time. We're not sure if it will need
- * a stable circuit yet, but we know we'll need *something*. */
+ /* 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.
@@ -1858,14 +1932,17 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
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(rend_data->onion_address));
+ 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. */
+ /* 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;
}
@@ -1883,7 +1960,7 @@ get_pf_socket(void)
if (pf_socket >= 0)
return pf_socket;
-#ifdef OPENBSD
+#if defined(OpenBSD)
/* only works on OpenBSD */
pf = tor_open_cloexec("/dev/pf", O_RDONLY, 0);
#else
@@ -2452,7 +2529,9 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn)
* Otherwise, directory connections are typically one-hop.
* This matches the earlier check for directory connection path anonymity
* in directory_initiate_command_rend(). */
- if (is_sensitive_dir_purpose(linked_dir_conn_base->purpose)) {
+ if (purpose_needs_anonymity(linked_dir_conn_base->purpose,
+ TO_DIR_CONN(linked_dir_conn_base)->router_purpose,
+ TO_DIR_CONN(linked_dir_conn_base)->requested_resource)) {
assert_circ_anonymity_ok(circ, options);
}
} else {
@@ -3013,14 +3092,21 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
char *address = NULL;
uint16_t port = 0;
or_circuit_t *or_circ = NULL;
+ origin_circuit_t *origin_circ = NULL;
+ crypt_path_t *layer_hint = NULL;
const or_options_t *options = get_options();
begin_cell_t bcell;
int rv;
uint8_t end_reason=0;
assert_circuit_ok(circ);
- if (!CIRCUIT_IS_ORIGIN(circ))
+ if (!CIRCUIT_IS_ORIGIN(circ)) {
or_circ = TO_OR_CIRCUIT(circ);
+ } else {
+ tor_assert(circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED);
+ origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ layer_hint = origin_circ->cpath->prev;
+ }
relay_header_unpack(&rh, cell->payload);
if (rh.length > RELAY_PAYLOAD_SIZE)
@@ -3045,7 +3131,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
return -END_CIRC_REASON_TORPROTOCOL;
} else if (rv == -1) {
tor_free(bcell.address);
- relay_send_end_cell_from_edge(rh.stream_id, circ, end_reason, NULL);
+ relay_send_end_cell_from_edge(rh.stream_id, circ, end_reason, layer_hint);
return 0;
}
@@ -3082,7 +3168,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
if (!directory_permits_begindir_requests(options) ||
circ->purpose != CIRCUIT_PURPOSE_OR) {
relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_NOTDIRECTORY, NULL);
+ END_STREAM_REASON_NOTDIRECTORY, layer_hint);
return 0;
}
/* Make sure to get the 'real' address of the previous hop: the
@@ -3099,7 +3185,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
} else {
log_warn(LD_BUG, "Got an unexpected command %d", (int)rh.command);
relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_INTERNAL, NULL);
+ END_STREAM_REASON_INTERNAL, layer_hint);
return 0;
}
@@ -3110,7 +3196,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
if (bcell.flags & BEGIN_FLAG_IPV4_NOT_OK) {
tor_free(address);
relay_send_end_cell_from_edge(rh.stream_id, circ,
- END_STREAM_REASON_EXITPOLICY, NULL);
+ END_STREAM_REASON_EXITPOLICY, layer_hint);
return 0;
}
}
@@ -3133,7 +3219,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
n_stream->deliver_window = STREAMWINDOW_START;
if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
- origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ 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;
@@ -3153,7 +3239,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
* the hidden service. */
relay_send_end_cell_from_edge(rh.stream_id, circ,
END_STREAM_REASON_DONE,
- origin_circ->cpath->prev);
+ layer_hint);
connection_free(TO_CONN(n_stream));
tor_free(address);
@@ -3479,7 +3565,7 @@ connection_exit_connect_dir(edge_connection_t *exitconn)
* it is a general stream.
*/
int
-connection_edge_is_rendezvous_stream(edge_connection_t *conn)
+connection_edge_is_rendezvous_stream(const edge_connection_t *conn)
{
tor_assert(conn);
if (conn->rend_data)
diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h
index 5dfc8af901..61b5752aed 100644
--- a/src/or/connection_edge.h
+++ b/src/or/connection_edge.h
@@ -60,7 +60,7 @@ void connection_ap_handshake_socks_resolved_addr(entry_connection_t *conn,
int connection_exit_begin_conn(cell_t *cell, circuit_t *circ);
int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ);
void connection_exit_connect(edge_connection_t *conn);
-int connection_edge_is_rendezvous_stream(edge_connection_t *conn);
+int connection_edge_is_rendezvous_stream(const edge_connection_t *conn);
int connection_ap_can_use_exit(const entry_connection_t *conn,
const node_t *exit);
void connection_ap_expire_beginning(void);
diff --git a/src/or/connection_or.c b/src/or/connection_or.c
index dadfdc4380..e7a55a80a6 100644
--- a/src/or/connection_or.c
+++ b/src/or/connection_or.c
@@ -21,6 +21,7 @@
* This module also implements the client side of the v3 Tor link handshake,
**/
#include "or.h"
+#include "bridges.h"
#include "buffers.h"
/*
* Define this so we get channel internal functions, since we're implementing
@@ -49,9 +50,11 @@
#include "relay.h"
#include "rephist.h"
#include "router.h"
+#include "routerkeys.h"
#include "routerlist.h"
#include "ext_orport.h"
#include "scheduler.h"
+#include "torcert.h"
static int connection_tls_finish_handshake(or_connection_t *conn);
static int connection_or_launch_v3_or_handshake(or_connection_t *conn);
@@ -73,56 +76,25 @@ static void connection_or_mark_bad_for_new_circs(or_connection_t *or_conn);
static void connection_or_change_state(or_connection_t *conn, uint8_t state);
-/**************************************************************/
+static void connection_or_check_canonicity(or_connection_t *conn,
+ int started_here);
-/** Map from identity digest of connected OR or desired OR to a connection_t
- * with that identity digest. If there is more than one such connection_t,
- * they form a linked list, with next_with_same_id as the next pointer. */
-static digestmap_t *orconn_identity_map = NULL;
+/**************************************************************/
/** Global map between Extended ORPort identifiers and OR
* connections. */
static digestmap_t *orconn_ext_or_id_map = NULL;
-/** If conn is listed in orconn_identity_map, remove it, and clear
- * conn->identity_digest. Otherwise do nothing. */
+/** Clear clear conn->identity_digest and update other data
+ * structures as appropriate.*/
void
-connection_or_remove_from_identity_map(or_connection_t *conn)
+connection_or_clear_identity(or_connection_t *conn)
{
- or_connection_t *tmp;
tor_assert(conn);
- if (!orconn_identity_map)
- return;
- tmp = digestmap_get(orconn_identity_map, conn->identity_digest);
- if (!tmp) {
- if (!tor_digest_is_zero(conn->identity_digest)) {
- log_warn(LD_BUG, "Didn't find connection '%s' on identity map when "
- "trying to remove it.",
- conn->nickname ? conn->nickname : "NULL");
- }
- return;
- }
- if (conn == tmp) {
- if (conn->next_with_same_id)
- digestmap_set(orconn_identity_map, conn->identity_digest,
- conn->next_with_same_id);
- else
- digestmap_remove(orconn_identity_map, conn->identity_digest);
- } else {
- while (tmp->next_with_same_id) {
- if (tmp->next_with_same_id == conn) {
- tmp->next_with_same_id = conn->next_with_same_id;
- break;
- }
- tmp = tmp->next_with_same_id;
- }
- }
memset(conn->identity_digest, 0, DIGEST_LEN);
- conn->next_with_same_id = NULL;
}
-/** Remove all entries from the identity-to-orconn map, and clear
- * all identities in OR conns.*/
+/** Clear all identities in OR conns.*/
void
connection_or_clear_identity_map(void)
{
@@ -130,57 +102,72 @@ connection_or_clear_identity_map(void)
SMARTLIST_FOREACH(conns, connection_t *, conn,
{
if (conn->type == CONN_TYPE_OR) {
- or_connection_t *or_conn = TO_OR_CONN(conn);
- memset(or_conn->identity_digest, 0, DIGEST_LEN);
- or_conn->next_with_same_id = NULL;
+ connection_or_clear_identity(TO_OR_CONN(conn));
}
});
-
- digestmap_free(orconn_identity_map, NULL);
- orconn_identity_map = NULL;
}
/** Change conn->identity_digest to digest, and add conn into
- * orconn_digest_map. */
+ * the appropriate digest maps.
+ *
+ * NOTE that this function only allows two kinds of transitions: from
+ * unset identity to set identity, and from idempotent re-settings
+ * of the same identity. It's not allowed to clear an identity or to
+ * change an identity. Return 0 on success, and -1 if the transition
+ * is not allowed.
+ **/
static void
-connection_or_set_identity_digest(or_connection_t *conn, const char *digest)
+connection_or_set_identity_digest(or_connection_t *conn,
+ const char *rsa_digest,
+ const ed25519_public_key_t *ed_id)
{
- or_connection_t *tmp;
+ channel_t *chan = NULL;
tor_assert(conn);
- tor_assert(digest);
+ tor_assert(rsa_digest);
+
+ if (conn->chan)
+ chan = TLS_CHAN_TO_BASE(conn->chan);
- if (!orconn_identity_map)
- orconn_identity_map = digestmap_new();
- if (tor_memeq(conn->identity_digest, digest, DIGEST_LEN))
+ log_info(LD_HANDSHAKE, "Set identity digest for %p (%s): %s %s.",
+ conn,
+ escaped_safe_str(conn->base_.address),
+ hex_str(rsa_digest, DIGEST_LEN),
+ ed25519_fmt(ed_id));
+ log_info(LD_HANDSHAKE, " (Previously: %s %s)",
+ hex_str(conn->identity_digest, DIGEST_LEN),
+ chan ? ed25519_fmt(&chan->ed25519_identity) : "<null>");
+
+ const int rsa_id_was_set = ! tor_digest_is_zero(conn->identity_digest);
+ const int ed_id_was_set =
+ chan && !ed25519_public_key_is_zero(&chan->ed25519_identity);
+ const int rsa_changed =
+ tor_memneq(conn->identity_digest, rsa_digest, DIGEST_LEN);
+ const int ed_changed = ed_id_was_set &&
+ (!ed_id || !ed25519_pubkey_eq(ed_id, &chan->ed25519_identity));
+
+ tor_assert(!rsa_changed || !rsa_id_was_set);
+ tor_assert(!ed_changed || !ed_id_was_set);
+
+ if (!rsa_changed && !ed_changed)
return;
/* If the identity was set previously, remove the old mapping. */
- if (! tor_digest_is_zero(conn->identity_digest)) {
- connection_or_remove_from_identity_map(conn);
- if (conn->chan)
- channel_clear_identity_digest(TLS_CHAN_TO_BASE(conn->chan));
+ if (rsa_id_was_set) {
+ connection_or_clear_identity(conn);
+ if (chan)
+ channel_clear_identity_digest(chan);
}
- memcpy(conn->identity_digest, digest, DIGEST_LEN);
+ memcpy(conn->identity_digest, rsa_digest, DIGEST_LEN);
- /* If we're setting the ID to zero, don't add a mapping. */
- if (tor_digest_is_zero(digest))
+ /* If we're initializing the IDs to zero, don't add a mapping yet. */
+ if (tor_digest_is_zero(rsa_digest) &&
+ (!ed_id || ed25519_public_key_is_zero(ed_id)))
return;
- tmp = digestmap_set(orconn_identity_map, digest, conn);
- conn->next_with_same_id = tmp;
-
/* Deal with channels */
- if (conn->chan)
- channel_set_identity_digest(TLS_CHAN_TO_BASE(conn->chan), digest);
-
-#if 1
- /* Testing code to check for bugs in representation. */
- for (; tmp; tmp = tmp->next_with_same_id) {
- tor_assert(tor_memeq(tmp->identity_digest, digest, DIGEST_LEN));
- tor_assert(tmp != conn);
- }
-#endif
+ if (chan)
+ channel_set_identity_digest(chan, rsa_digest, ed_id);
}
/** Remove the Extended ORPort identifier of <b>conn</b> from the
@@ -731,8 +718,8 @@ connection_or_about_to_close(or_connection_t *or_conn)
const or_options_t *options = get_options();
connection_or_note_state_when_broken(or_conn);
rep_hist_note_connect_failed(or_conn->identity_digest, now);
- entry_guard_register_connect_status(or_conn->identity_digest,0,
- !options->HTTPSProxy, now);
+ /* Tell the new guard API about the channel failure */
+ entry_guard_chan_failed(TLS_CHAN_TO_BASE(or_conn->chan));
if (conn->state >= OR_CONN_STATE_TLS_HANDSHAKING) {
int reason = tls_error_to_orconn_end_reason(or_conn->tls_error);
control_event_or_conn_status(or_conn, OR_CONN_EVENT_FAILED,
@@ -877,15 +864,47 @@ void
connection_or_init_conn_from_address(or_connection_t *conn,
const tor_addr_t *addr, uint16_t port,
const char *id_digest,
+ const ed25519_public_key_t *ed_id,
int started_here)
{
- const node_t *r = node_get_by_id(id_digest);
- connection_or_set_identity_digest(conn, id_digest);
+ log_debug(LD_HANDSHAKE, "init conn from address %s: %s, %s (%d)",
+ fmt_addr(addr),
+ hex_str((const char*)id_digest, DIGEST_LEN),
+ ed25519_fmt(ed_id),
+ started_here);
+
+ connection_or_set_identity_digest(conn, id_digest, ed_id);
connection_or_update_token_buckets_helper(conn, 1, get_options());
conn->base_.port = port;
tor_addr_copy(&conn->base_.addr, addr);
tor_addr_copy(&conn->real_addr, addr);
+
+ connection_or_check_canonicity(conn, started_here);
+}
+
+/** Check whether the identity of <b>conn</b> matches a known node. If it
+ * does, check whether the address of conn matches the expected address, and
+ * update the connection's is_canonical flag, nickname, and address fields as
+ * appropriate. */
+static void
+connection_or_check_canonicity(or_connection_t *conn, int started_here)
+{
+ const char *id_digest = conn->identity_digest;
+ const ed25519_public_key_t *ed_id = NULL;
+ const tor_addr_t *addr = &conn->real_addr;
+ if (conn->chan)
+ ed_id = & TLS_CHAN_TO_BASE(conn->chan)->ed25519_identity;
+
+ const node_t *r = node_get_by_id(id_digest);
+ if (r &&
+ node_supports_ed25519_link_authentication(r) &&
+ ! node_ed25519_id_matches(r, ed_id)) {
+ /* If this node is capable of proving an ed25519 ID,
+ * we can't call this a canonical connection unless both IDs match. */
+ r = NULL;
+ }
+
if (r) {
tor_addr_port_t node_ap;
node_get_pref_orport(r, &node_ap);
@@ -907,10 +926,12 @@ connection_or_init_conn_from_address(or_connection_t *conn,
tor_addr_copy(&conn->base_.addr, &node_ap.addr);
conn->base_.port = node_ap.port;
}
+ tor_free(conn->nickname);
conn->nickname = tor_strdup(node_get_nickname(r));
tor_free(conn->base_.address);
conn->base_.address = tor_addr_to_str_dup(&node_ap.addr);
} else {
+ tor_free(conn->nickname);
conn->nickname = tor_malloc(HEX_DIGEST_LEN+2);
conn->nickname[0] = '$';
base16_encode(conn->nickname+1, HEX_DIGEST_LEN+1,
@@ -956,7 +977,7 @@ connection_or_mark_bad_for_new_circs(or_connection_t *or_conn)
* too old for new circuits? */
#define TIME_BEFORE_OR_CONN_IS_TOO_OLD (60*60*24*7)
-/** Given the head of the linked list for all the or_connections with a given
+/** Given a list of all the or_connections with a given
* identity, set elements of that list as is_bad_for_new_circs as
* appropriate. Helper for connection_or_set_bad_connections().
*
@@ -973,16 +994,19 @@ connection_or_mark_bad_for_new_circs(or_connection_t *or_conn)
* See channel_is_better() in channel.c for our idea of what makes one OR
* connection better than another.
*/
-static void
-connection_or_group_set_badness(or_connection_t *head, int force)
+void
+connection_or_group_set_badness_(smartlist_t *group, int force)
{
- or_connection_t *or_conn = NULL, *best = NULL;
+ /* XXXX this function should be entirely about channels, not OR
+ * XXXX connections. */
+
+ or_connection_t *best = NULL;
int n_old = 0, n_inprogress = 0, n_canonical = 0, n_other = 0;
time_t now = time(NULL);
/* Pass 1: expire everything that's old, and see what the status of
* everything else is. */
- for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) {
+ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) {
if (or_conn->base_.marked_for_close ||
connection_or_is_bad_for_new_circs(or_conn))
continue;
@@ -1006,11 +1030,11 @@ connection_or_group_set_badness(or_connection_t *head, int force)
} else {
++n_other;
}
- }
+ } SMARTLIST_FOREACH_END(or_conn);
/* Pass 2: We know how about how good the best connection is.
* expire everything that's worse, and find the very best if we can. */
- for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) {
+ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) {
if (or_conn->base_.marked_for_close ||
connection_or_is_bad_for_new_circs(or_conn))
continue; /* This one doesn't need to be marked bad. */
@@ -1037,7 +1061,7 @@ connection_or_group_set_badness(or_connection_t *head, int force)
0)) {
best = or_conn;
}
- }
+ } SMARTLIST_FOREACH_END(or_conn);
if (!best)
return;
@@ -1056,7 +1080,7 @@ connection_or_group_set_badness(or_connection_t *head, int force)
* 0.1.2.x dies out, the first case will go away, and the second one is
* "mostly harmless", so a fix can wait until somebody is bored.
*/
- for (or_conn = head; or_conn; or_conn = or_conn->next_with_same_id) {
+ SMARTLIST_FOREACH_BEGIN(group, or_connection_t *, or_conn) {
if (or_conn->base_.marked_for_close ||
connection_or_is_bad_for_new_circs(or_conn) ||
or_conn->base_.state != OR_CONN_STATE_OPEN)
@@ -1090,24 +1114,7 @@ connection_or_group_set_badness(or_connection_t *head, int force)
connection_or_mark_bad_for_new_circs(or_conn);
}
}
- }
-}
-
-/** Go through all the OR connections (or if <b>digest</b> is non-NULL, just
- * the OR connections with that digest), and set the is_bad_for_new_circs
- * flag based on the rules in connection_or_group_set_badness() (or just
- * always set it if <b>force</b> is true).
- */
-void
-connection_or_set_bad_connections(const char *digest, int force)
-{
- if (!orconn_identity_map)
- return;
-
- DIGESTMAP_FOREACH(orconn_identity_map, identity, or_connection_t *, conn) {
- if (!digest || tor_memeq(digest, conn->identity_digest, DIGEST_LEN))
- connection_or_group_set_badness(conn, force);
- } DIGESTMAP_FOREACH_END;
+ } SMARTLIST_FOREACH_END(or_conn);
}
/** <b>conn</b> is in the 'connecting' state, and it failed to complete
@@ -1173,7 +1180,9 @@ connection_or_notify_error(or_connection_t *conn,
MOCK_IMPL(or_connection_t *,
connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
- const char *id_digest, channel_tls_t *chan))
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id,
+ channel_tls_t *chan))
{
or_connection_t *conn;
const or_options_t *options = get_options();
@@ -1193,6 +1202,11 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
log_info(LD_PROTOCOL,"Client asked me to connect to myself. Refusing.");
return NULL;
}
+ if (server_mode(options) && router_ed25519_id_is_me(ed_id)) {
+ log_info(LD_PROTOCOL,"Client asked me to connect to myself by Ed25519 "
+ "identity. Refusing.");
+ return NULL;
+ }
conn = or_connection_new(CONN_TYPE_OR, tor_addr_family(&addr));
@@ -1205,7 +1219,7 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port,
*/
conn->chan = chan;
chan->conn = conn;
- connection_or_init_conn_from_address(conn, &addr, port, id_digest, 1);
+ connection_or_init_conn_from_address(conn, &addr, port, id_digest, ed_id, 1);
connection_or_change_state(conn, OR_CONN_STATE_CONNECTING);
control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0);
@@ -1562,18 +1576,25 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn,
crypto_pk_free(identity_rcvd);
- if (started_here)
+ if (started_here) {
+ /* A TLS handshake can't teach us an Ed25519 ID, so we set it to NULL
+ * here. */
+ log_debug(LD_HANDSHAKE, "Calling client_learned_peer_id from "
+ "check_valid_tls_handshake");
return connection_or_client_learned_peer_id(conn,
- (const uint8_t*)digest_rcvd_out);
+ (const uint8_t*)digest_rcvd_out,
+ NULL);
+ }
return 0;
}
/** Called when we (as a connection initiator) have definitively,
* authenticatedly, learned that ID of the Tor instance on the other
- * side of <b>conn</b> is <b>peer_id</b>. For v1 and v2 handshakes,
+ * side of <b>conn</b> is <b>rsa_peer_id</b> and optionally <b>ed_peer_id</b>.
+ * For v1 and v2 handshakes,
* this is right after we get a certificate chain in a TLS handshake
- * or renegotiation. For v3 handshakes, this is right after we get a
+ * or renegotiation. For v3+ handshakes, this is right after we get a
* certificate chain in a CERTS cell.
*
* If we did not know the ID before, record the one we got.
@@ -1594,12 +1615,31 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn,
*/
int
connection_or_client_learned_peer_id(or_connection_t *conn,
- const uint8_t *peer_id)
+ const uint8_t *rsa_peer_id,
+ const ed25519_public_key_t *ed_peer_id)
{
const or_options_t *options = get_options();
-
- if (tor_digest_is_zero(conn->identity_digest)) {
- connection_or_set_identity_digest(conn, (const char*)peer_id);
+ channel_tls_t *chan_tls = conn->chan;
+ channel_t *chan = channel_tls_to_base(chan_tls);
+ int changed_identity = 0;
+ tor_assert(chan);
+
+ const int expected_rsa_key =
+ ! tor_digest_is_zero(conn->identity_digest);
+ const int expected_ed_key =
+ ! ed25519_public_key_is_zero(&chan->ed25519_identity);
+
+ log_info(LD_HANDSHAKE, "learned peer id for %p (%s): %s, %s",
+ conn,
+ safe_str_client(conn->base_.address),
+ hex_str((const char*)rsa_peer_id, DIGEST_LEN),
+ ed25519_fmt(ed_peer_id));
+
+ if (! expected_rsa_key && ! expected_ed_key) {
+ log_info(LD_HANDSHAKE, "(we had no ID in mind when we made this "
+ "connection.");
+ connection_or_set_identity_digest(conn,
+ (const char*)rsa_peer_id, ed_peer_id);
tor_free(conn->nickname);
conn->nickname = tor_malloc(HEX_DIGEST_LEN+2);
conn->nickname[0] = '$';
@@ -1611,16 +1651,39 @@ connection_or_client_learned_peer_id(or_connection_t *conn,
/* if it's a bridge and we didn't know its identity fingerprint, now
* we do -- remember it for future attempts. */
learned_router_identity(&conn->base_.addr, conn->base_.port,
- (const char*)peer_id);
+ (const char*)rsa_peer_id, ed_peer_id);
+ changed_identity = 1;
}
- if (tor_memneq(peer_id, conn->identity_digest, DIGEST_LEN)) {
+ const int rsa_mismatch = expected_rsa_key &&
+ tor_memneq(rsa_peer_id, conn->identity_digest, DIGEST_LEN);
+ /* It only counts as an ed25519 mismatch if we wanted an ed25519 identity
+ * and didn't get it. It's okay if we get one that we didn't ask for. */
+ const int ed25519_mismatch =
+ expected_ed_key &&
+ (ed_peer_id == NULL ||
+ ! ed25519_pubkey_eq(&chan->ed25519_identity, ed_peer_id));
+
+ if (rsa_mismatch || ed25519_mismatch) {
/* I was aiming for a particular digest. I didn't get it! */
- char seen[HEX_DIGEST_LEN+1];
- char expected[HEX_DIGEST_LEN+1];
- base16_encode(seen, sizeof(seen), (const char*)peer_id, DIGEST_LEN);
- base16_encode(expected, sizeof(expected), conn->identity_digest,
+ char seen_rsa[HEX_DIGEST_LEN+1];
+ char expected_rsa[HEX_DIGEST_LEN+1];
+ char seen_ed[ED25519_BASE64_LEN+1];
+ char expected_ed[ED25519_BASE64_LEN+1];
+ base16_encode(seen_rsa, sizeof(seen_rsa),
+ (const char*)rsa_peer_id, DIGEST_LEN);
+ base16_encode(expected_rsa, sizeof(expected_rsa), conn->identity_digest,
DIGEST_LEN);
+ if (ed_peer_id) {
+ ed25519_public_to_base64(seen_ed, ed_peer_id);
+ } else {
+ strlcpy(seen_ed, "no ed25519 key", sizeof(seen_ed));
+ }
+ if (! ed25519_public_key_is_zero(&chan->ed25519_identity)) {
+ ed25519_public_to_base64(expected_ed, &chan->ed25519_identity);
+ } else {
+ strlcpy(expected_ed, "no ed25519 key", sizeof(expected_ed));
+ }
const int using_hardcoded_fingerprints =
!networkstatus_get_reasonably_live_consensus(time(NULL),
usable_consensus_flavor());
@@ -1655,11 +1718,13 @@ connection_or_client_learned_peer_id(or_connection_t *conn,
}
log_fn(severity, LD_HANDSHAKE,
- "Tried connecting to router at %s:%d, but identity key was not "
- "as expected: wanted %s but got %s.%s",
- conn->base_.address, conn->base_.port, expected, seen, extra_log);
- entry_guard_register_connect_status(conn->identity_digest, 0, 1,
- time(NULL));
+ "Tried connecting to router at %s:%d, but RSA identity key was not "
+ "as expected: wanted %s + %s but got %s + %s.%s",
+ conn->base_.address, conn->base_.port,
+ expected_rsa, expected_ed, seen_rsa, seen_ed, extra_log);
+
+ /* Tell the new guard API about the channel failure */
+ entry_guard_chan_failed(TLS_CHAN_TO_BASE(conn->chan));
control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED,
END_OR_CONN_REASON_OR_IDENTITY);
if (!authdir_mode_tests_reachability(options))
@@ -1669,9 +1734,24 @@ connection_or_client_learned_peer_id(or_connection_t *conn,
conn);
return -1;
}
+
+ if (!expected_ed_key && ed_peer_id) {
+ log_info(LD_HANDSHAKE, "(we had no Ed25519 ID in mind when we made this "
+ "connection.");
+ connection_or_set_identity_digest(conn,
+ (const char*)rsa_peer_id, ed_peer_id);
+ changed_identity = 1;
+ }
+
+ if (changed_identity) {
+ /* If we learned an identity for this connection, then we might have
+ * just discovered it to be canonical. */
+ connection_or_check_canonicity(conn, conn->handshake_state->started_here);
+ }
+
if (authdir_mode_tests_reachability(options)) {
dirserv_orconn_tls_done(&conn->base_.addr, conn->base_.port,
- (const char*)peer_id);
+ (const char*)rsa_peer_id, ed_peer_id);
}
return 0;
@@ -1727,7 +1807,8 @@ connection_tls_finish_handshake(or_connection_t *conn)
if (tor_tls_used_v1_handshake(conn->tls)) {
conn->link_proto = 1;
connection_or_init_conn_from_address(conn, &conn->base_.addr,
- conn->base_.port, digest_rcvd, 0);
+ conn->base_.port, digest_rcvd,
+ NULL, 0);
tor_tls_block_renegotiation(conn->tls);
rep_hist_note_negotiated_link_proto(1, started_here);
return connection_or_set_state_open(conn);
@@ -1736,7 +1817,8 @@ connection_tls_finish_handshake(or_connection_t *conn)
if (connection_init_or_handshake_state(conn, started_here) < 0)
return -1;
connection_or_init_conn_from_address(conn, &conn->base_.addr,
- conn->base_.port, digest_rcvd, 0);
+ conn->base_.port, digest_rcvd,
+ NULL, 0);
return connection_or_send_versions(conn, 0);
}
}
@@ -1775,6 +1857,11 @@ connection_init_or_handshake_state(or_connection_t *conn, int started_here)
s->started_here = started_here ? 1 : 0;
s->digest_sent_data = 1;
s->digest_received_data = 1;
+ if (! started_here && get_current_link_cert_cert()) {
+ s->own_link_cert = tor_cert_dup(get_current_link_cert_cert());
+ }
+ s->certs = or_handshake_certs_new();
+ s->certs->started_here = s->started_here;
return 0;
}
@@ -1786,8 +1873,8 @@ or_handshake_state_free(or_handshake_state_t *state)
return;
crypto_digest_free(state->digest_sent);
crypto_digest_free(state->digest_received);
- tor_x509_cert_free(state->auth_cert);
- tor_x509_cert_free(state->id_cert);
+ or_handshake_certs_free(state->certs);
+ tor_cert_free(state->own_link_cert);
memwipe(state, 0xBE, sizeof(or_handshake_state_t));
tor_free(state);
}
@@ -2134,66 +2221,187 @@ connection_or_send_netinfo,(or_connection_t *conn))
return 0;
}
+/** Helper used to add an encoded certs to a cert cell */
+static void
+add_certs_cell_cert_helper(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const uint8_t *cert_encoded,
+ size_t cert_len)
+{
+ tor_assert(cert_len <= UINT16_MAX);
+ certs_cell_cert_t *ccc = certs_cell_cert_new();
+ ccc->cert_type = cert_type;
+ ccc->cert_len = cert_len;
+ certs_cell_cert_setlen_body(ccc, cert_len);
+ memcpy(certs_cell_cert_getarray_body(ccc), cert_encoded, cert_len);
+
+ certs_cell_add_certs(certs_cell, ccc);
+}
+
+/** Add an encoded X509 cert (stored as <b>cert_len</b> bytes at
+ * <b>cert_encoded</b>) to the trunnel certs_cell_t object that we are
+ * building in <b>certs_cell</b>. Set its type field to <b>cert_type</b>.
+ * (If <b>cert</b> is NULL, take no action.) */
+static void
+add_x509_cert(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const tor_x509_cert_t *cert)
+{
+ if (NULL == cert)
+ return;
+
+ const uint8_t *cert_encoded = NULL;
+ size_t cert_len;
+ tor_x509_cert_get_der(cert, &cert_encoded, &cert_len);
+
+ add_certs_cell_cert_helper(certs_cell, cert_type, cert_encoded, cert_len);
+}
+
+/** Add an Ed25519 cert from <b>cert</b> to the trunnel certs_cell_t object
+ * that we are building in <b>certs_cell</b>. Set its type field to
+ * <b>cert_type</b>. (If <b>cert</b> is NULL, take no action.) */
+static void
+add_ed25519_cert(certs_cell_t *certs_cell,
+ uint8_t cert_type,
+ const tor_cert_t *cert)
+{
+ if (NULL == cert)
+ return;
+
+ add_certs_cell_cert_helper(certs_cell, cert_type,
+ cert->encoded, cert->encoded_len);
+}
+
+#ifdef TOR_UNIT_TESTS
+int certs_cell_ed25519_disabled_for_testing = 0;
+#else
+#define certs_cell_ed25519_disabled_for_testing 0
+#endif
+
/** Send a CERTS cell on the connection <b>conn</b>. Return 0 on success, -1
* on failure. */
int
connection_or_send_certs_cell(or_connection_t *conn)
{
- const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL,
- *using_link_cert = NULL;
+ const tor_x509_cert_t *global_link_cert = NULL, *id_cert = NULL;
tor_x509_cert_t *own_link_cert = NULL;
- const uint8_t *link_encoded = NULL, *id_encoded = NULL;
- size_t link_len, id_len;
var_cell_t *cell;
- size_t cell_len;
- ssize_t pos;
+
+ certs_cell_t *certs_cell = NULL;
tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3);
if (! conn->handshake_state)
return -1;
+
const int conn_in_server_mode = ! conn->handshake_state->started_here;
+
+ /* Get the encoded values of the X509 certificates */
if (tor_tls_get_my_certs(conn_in_server_mode,
&global_link_cert, &id_cert) < 0)
return -1;
+
if (conn_in_server_mode) {
- using_link_cert = own_link_cert = tor_tls_get_own_cert(conn->tls);
+ own_link_cert = tor_tls_get_own_cert(conn->tls);
+ }
+ tor_assert(id_cert);
+
+ certs_cell = certs_cell_new();
+
+ /* Start adding certs. First the link cert or auth1024 cert. */
+ if (conn_in_server_mode) {
+ tor_assert_nonfatal(own_link_cert);
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_TLS_LINK, own_link_cert);
} else {
- using_link_cert = global_link_cert;
+ tor_assert(global_link_cert);
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_AUTH_1024, global_link_cert);
}
- tor_x509_cert_get_der(using_link_cert, &link_encoded, &link_len);
- tor_x509_cert_get_der(id_cert, &id_encoded, &id_len);
- cell_len = 1 /* 1 byte: num certs in cell */ +
- 2 * ( 1 + 2 ) /* For each cert: 1 byte for type, 2 for length */ +
- link_len + id_len;
- cell = var_cell_new(cell_len);
- cell->command = CELL_CERTS;
- cell->payload[0] = 2;
- pos = 1;
+ /* Next the RSA->RSA ID cert */
+ add_x509_cert(certs_cell,
+ OR_CERT_TYPE_ID_1024, id_cert);
- if (conn_in_server_mode)
- cell->payload[pos] = OR_CERT_TYPE_TLS_LINK; /* Link cert */
- else
- cell->payload[pos] = OR_CERT_TYPE_AUTH_1024; /* client authentication */
- set_uint16(&cell->payload[pos+1], htons(link_len));
- memcpy(&cell->payload[pos+3], link_encoded, link_len);
- pos += 3 + link_len;
+ /* Next the Ed25519 certs */
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_ID_SIGN,
+ get_master_signing_key_cert());
+ if (conn_in_server_mode) {
+ tor_assert_nonfatal(conn->handshake_state->own_link_cert ||
+ certs_cell_ed25519_disabled_for_testing);
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_SIGN_LINK,
+ conn->handshake_state->own_link_cert);
+ } else {
+ add_ed25519_cert(certs_cell,
+ CERTTYPE_ED_SIGN_AUTH,
+ get_current_auth_key_cert());
+ }
+
+ /* And finally the crosscert. */
+ {
+ const uint8_t *crosscert=NULL;
+ size_t crosscert_len;
+ get_master_rsa_crosscert(&crosscert, &crosscert_len);
+ if (crosscert) {
+ add_certs_cell_cert_helper(certs_cell,
+ CERTTYPE_RSA1024_ID_EDID,
+ crosscert, crosscert_len);
+ }
+ }
- cell->payload[pos] = OR_CERT_TYPE_ID_1024; /* ID cert */
- set_uint16(&cell->payload[pos+1], htons(id_len));
- memcpy(&cell->payload[pos+3], id_encoded, id_len);
- pos += 3 + id_len;
+ /* We've added all the certs; make the cell. */
+ certs_cell->n_certs = certs_cell_getlen_certs(certs_cell);
- tor_assert(pos == (int)cell_len); /* Otherwise we just smashed the heap */
+ ssize_t alloc_len = certs_cell_encoded_len(certs_cell);
+ tor_assert(alloc_len >= 0 && alloc_len <= UINT16_MAX);
+ cell = var_cell_new(alloc_len);
+ cell->command = CELL_CERTS;
+ ssize_t enc_len = certs_cell_encode(cell->payload, alloc_len, certs_cell);
+ tor_assert(enc_len > 0 && enc_len <= alloc_len);
+ cell->payload_len = enc_len;
connection_or_write_var_cell_to_buf(cell, conn);
var_cell_free(cell);
+ certs_cell_free(certs_cell);
tor_x509_cert_free(own_link_cert);
return 0;
}
+/** Return true iff <b>challenge_type</b> is an AUTHCHALLENGE type that
+ * we can send and receive. */
+int
+authchallenge_type_is_supported(uint16_t challenge_type)
+{
+ switch (challenge_type) {
+ case AUTHTYPE_RSA_SHA256_TLSSECRET:
+ case AUTHTYPE_ED25519_SHA256_RFC5705:
+ return 1;
+ case AUTHTYPE_RSA_SHA256_RFC5705:
+ default:
+ return 0;
+ }
+}
+
+/** Return true iff <b>challenge_type_a</b> is one that we would rather
+ * use than <b>challenge_type_b</b>. */
+int
+authchallenge_type_is_better(uint16_t challenge_type_a,
+ uint16_t challenge_type_b)
+{
+ /* Any supported type is better than an unsupported one;
+ * all unsupported types are equally bad. */
+ if (!authchallenge_type_is_supported(challenge_type_a))
+ return 0;
+ if (!authchallenge_type_is_supported(challenge_type_b))
+ return 1;
+ /* It happens that types are superior in numerically ascending order.
+ * If that ever changes, this must change too. */
+ return (challenge_type_a > challenge_type_b);
+}
+
/** Send an AUTH_CHALLENGE cell on the connection <b>conn</b>. Return 0
* on success, -1 on failure. */
int
@@ -2208,17 +2416,26 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn)
auth_challenge_cell_t *ac = auth_challenge_cell_new();
+ tor_assert(sizeof(ac->challenge) == 32);
crypto_rand((char*)ac->challenge, sizeof(ac->challenge));
auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET);
+ /* Disabled, because everything that supports this method also supports
+ * the much-superior ED25519_SHA256_RFC5705 */
+ /* auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_RFC5705); */
+ auth_challenge_cell_add_methods(ac, AUTHTYPE_ED25519_SHA256_RFC5705);
auth_challenge_cell_set_n_methods(ac,
auth_challenge_cell_getlen_methods(ac));
cell = var_cell_new(auth_challenge_cell_encoded_len(ac));
ssize_t len = auth_challenge_cell_encode(cell->payload, cell->payload_len,
ac);
- if (len != cell->payload_len)
+ if (len != cell->payload_len) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Encoded auth challenge cell length not as expected");
goto done;
+ /* LCOV_EXCL_STOP */
+ }
cell->command = CELL_AUTH_CHALLENGE;
connection_or_write_var_cell_to_buf(cell, conn);
@@ -2232,8 +2449,8 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn)
}
/** Compute the main body of an AUTHENTICATE cell that a client can use
- * to authenticate itself on a v3 handshake for <b>conn</b>. Write it to the
- * <b>outlen</b>-byte buffer at <b>out</b>.
+ * to authenticate itself on a v3 handshake for <b>conn</b>. Return it
+ * in a var_cell_t.
*
* If <b>server</b> is true, only calculate the first
* V3_AUTH_FIXED_PART_LEN bytes -- the part of the authenticator that's
@@ -2249,24 +2466,44 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn)
*
* Return the length of the cell body on success, and -1 on failure.
*/
-int
+var_cell_t *
connection_or_compute_authenticate_cell_body(or_connection_t *conn,
- uint8_t *out, size_t outlen,
+ const int authtype,
crypto_pk_t *signing_key,
- int server)
+ const ed25519_keypair_t *ed_signing_key,
+ int server)
{
auth1_t *auth = NULL;
auth_ctx_t *ctx = auth_ctx_new();
- int result;
+ var_cell_t *result = NULL;
+ int old_tlssecrets_algorithm = 0;
+ const char *authtype_str = NULL;
- /* assert state is reasonable XXXX */
+ int is_ed = 0;
- ctx->is_ed = 0;
+ /* assert state is reasonable XXXX */
+ switch (authtype) {
+ case AUTHTYPE_RSA_SHA256_TLSSECRET:
+ authtype_str = "AUTH0001";
+ old_tlssecrets_algorithm = 1;
+ break;
+ case AUTHTYPE_RSA_SHA256_RFC5705:
+ authtype_str = "AUTH0002";
+ break;
+ case AUTHTYPE_ED25519_SHA256_RFC5705:
+ authtype_str = "AUTH0003";
+ is_ed = 1;
+ break;
+ default:
+ tor_assert(0);
+ break;
+ }
auth = auth1_new();
+ ctx->is_ed = is_ed;
/* Type: 8 bytes. */
- memcpy(auth1_getarray_type(auth), "AUTH0001", 8);
+ memcpy(auth1_getarray_type(auth), authtype_str, 8);
{
const tor_x509_cert_t *id_cert=NULL;
@@ -2276,7 +2513,7 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
goto err;
my_digests = tor_x509_cert_get_id_digests(id_cert);
their_digests =
- tor_x509_cert_get_id_digests(conn->handshake_state->id_cert);
+ tor_x509_cert_get_id_digests(conn->handshake_state->certs->id_cert);
tor_assert(my_digests);
tor_assert(their_digests);
my_id = (uint8_t*)my_digests->d[DIGEST_SHA256];
@@ -2292,6 +2529,22 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
memcpy(auth->sid, server_id, 32);
}
+ if (is_ed) {
+ const ed25519_public_key_t *my_ed_id, *their_ed_id;
+ if (!conn->handshake_state->certs->ed_id_sign) {
+ log_warn(LD_OR, "Ed authenticate without Ed ID cert from peer.");
+ goto err;
+ }
+ my_ed_id = get_master_identity_key();
+ their_ed_id = &conn->handshake_state->certs->ed_id_sign->signing_key;
+
+ const uint8_t *cid_ed = (server ? their_ed_id : my_ed_id)->pubkey;
+ const uint8_t *sid_ed = (server ? my_ed_id : their_ed_id)->pubkey;
+
+ memcpy(auth->u1_cid_ed, cid_ed, ED25519_PUBKEY_LEN);
+ memcpy(auth->u1_sid_ed, sid_ed, ED25519_PUBKEY_LEN);
+ }
+
{
crypto_digest_t *server_d, *client_d;
if (server) {
@@ -2318,7 +2571,8 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
cert = tor_tls_get_peer_cert(conn->tls);
}
if (!cert) {
- log_warn(LD_OR, "Unable to find cert when making AUTH1 data.");
+ log_warn(LD_OR, "Unable to find cert when making %s data.",
+ authtype_str);
goto err;
}
@@ -2329,36 +2583,79 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
}
/* HMAC of clientrandom and serverrandom using master key : 32 octets */
- tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets);
+ if (old_tlssecrets_algorithm) {
+ tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets);
+ } else {
+ char label[128];
+ tor_snprintf(label, sizeof(label),
+ "EXPORTER FOR TOR TLS CLIENT BINDING %s", authtype_str);
+ tor_tls_export_key_material(conn->tls, auth->tlssecrets,
+ auth->cid, sizeof(auth->cid),
+ label);
+ }
/* 8 octets were reserved for the current time, but we're trying to get out
* of the habit of sending time around willynilly. Fortunately, nothing
* checks it. That's followed by 16 bytes of nonce. */
crypto_rand((char*)auth->rand, 24);
+ ssize_t maxlen = auth1_encoded_len(auth, ctx);
+ if (ed_signing_key && is_ed) {
+ maxlen += ED25519_SIG_LEN;
+ } else if (signing_key && !is_ed) {
+ maxlen += crypto_pk_keysize(signing_key);
+ }
+
+ const int AUTH_CELL_HEADER_LEN = 4; /* 2 bytes of type, 2 bytes of length */
+ result = var_cell_new(AUTH_CELL_HEADER_LEN + maxlen);
+ uint8_t *const out = result->payload + AUTH_CELL_HEADER_LEN;
+ const size_t outlen = maxlen;
ssize_t len;
+
+ result->command = CELL_AUTHENTICATE;
+ set_uint16(result->payload, htons(authtype));
+
if ((len = auth1_encode(out, outlen, auth, ctx)) < 0) {
- log_warn(LD_OR, "Unable to encode signed part of AUTH1 data.");
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to encode signed part of AUTH1 data.");
goto err;
+ /* LCOV_EXCL_STOP */
}
if (server) {
auth1_t *tmp = NULL;
ssize_t len2 = auth1_parse(&tmp, out, len, ctx);
if (!tmp) {
- log_warn(LD_OR, "Unable to parse signed part of AUTH1 data.");
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to parse signed part of AUTH1 data that "
+ "we just encoded");
goto err;
+ /* LCOV_EXCL_STOP */
}
- result = (int) (tmp->end_of_fixed_part - out);
+ result->payload_len = (tmp->end_of_signed - result->payload);
+
auth1_free(tmp);
if (len2 != len) {
- log_warn(LD_OR, "Mismatched length when re-parsing AUTH1 data.");
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Mismatched length when re-parsing AUTH1 data.");
goto err;
+ /* LCOV_EXCL_STOP */
}
goto done;
}
- if (signing_key) {
+ if (ed_signing_key && is_ed) {
+ ed25519_signature_t sig;
+ if (ed25519_sign(&sig, out, len, ed_signing_key) < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to sign ed25519 authentication data");
+ goto err;
+ /* LCOV_EXCL_STOP */
+ }
+ auth1_setlen_sig(auth, ED25519_SIG_LEN);
+ memcpy(auth1_getarray_sig(auth), sig.sig, ED25519_SIG_LEN);
+
+ } else if (signing_key && !is_ed) {
auth1_setlen_sig(auth, crypto_pk_keysize(signing_key));
char d[32];
@@ -2373,18 +2670,24 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn,
}
auth1_setlen_sig(auth, siglen);
+ }
- len = auth1_encode(out, outlen, auth, ctx);
- if (len < 0) {
- log_warn(LD_OR, "Unable to encode signed AUTH1 data.");
- goto err;
- }
+ len = auth1_encode(out, outlen, auth, ctx);
+ if (len < 0) {
+ /* LCOV_EXCL_START */
+ log_warn(LD_BUG, "Unable to encode signed AUTH1 data.");
+ goto err;
+ /* LCOV_EXCL_STOP */
}
- result = (int) len;
+ tor_assert(len + AUTH_CELL_HEADER_LEN <= result->payload_len);
+ result->payload_len = len + AUTH_CELL_HEADER_LEN;
+ set_uint16(result->payload+2, htons(len));
+
goto done;
err:
- result = -1;
+ var_cell_free(result);
+ result = NULL;
done:
auth1_free(auth);
auth_ctx_free(ctx);
@@ -2398,44 +2701,29 @@ connection_or_send_authenticate_cell,(or_connection_t *conn, int authtype))
{
var_cell_t *cell;
crypto_pk_t *pk = tor_tls_get_my_client_auth_key();
- int authlen;
- size_t cell_maxlen;
/* XXXX make sure we're actually supposed to send this! */
if (!pk) {
log_warn(LD_BUG, "Can't compute authenticate cell: no client auth key");
return -1;
}
- if (authtype != AUTHTYPE_RSA_SHA256_TLSSECRET) {
+ if (! authchallenge_type_is_supported(authtype)) {
log_warn(LD_BUG, "Tried to send authenticate cell with unknown "
"authentication type %d", authtype);
return -1;
}
- cell_maxlen = 4 + /* overhead */
- V3_AUTH_BODY_LEN + /* Authentication body */
- crypto_pk_keysize(pk) + /* Max signature length */
- 16 /* add a few extra bytes just in case. */;
-
- cell = var_cell_new(cell_maxlen);
- cell->command = CELL_AUTHENTICATE;
- set_uint16(cell->payload, htons(AUTHTYPE_RSA_SHA256_TLSSECRET));
- /* skip over length ; we don't know that yet. */
-
- authlen = connection_or_compute_authenticate_cell_body(conn,
- cell->payload+4,
- cell_maxlen-4,
- pk,
- 0 /* not server */);
- if (authlen < 0) {
+ cell = connection_or_compute_authenticate_cell_body(conn,
+ authtype,
+ pk,
+ get_current_auth_keypair(),
+ 0 /* not server */);
+ if (! cell) {
+ /* LCOV_EXCL_START */
log_warn(LD_BUG, "Unable to compute authenticate cell!");
- var_cell_free(cell);
return -1;
+ /* LCOV_EXCL_STOP */
}
- tor_assert(authlen + 4 <= cell->payload_len);
- set_uint16(cell->payload+2, htons(authlen));
- cell->payload_len = authlen + 4;
-
connection_or_write_var_cell_to_buf(cell, conn);
var_cell_free(cell);
diff --git a/src/or/connection_or.h b/src/or/connection_or.h
index 2e8c6066cc..514a0fd008 100644
--- a/src/or/connection_or.h
+++ b/src/or/connection_or.h
@@ -12,14 +12,13 @@
#ifndef TOR_CONNECTION_OR_H
#define TOR_CONNECTION_OR_H
-void connection_or_remove_from_identity_map(or_connection_t *conn);
+void connection_or_clear_identity(or_connection_t *conn);
void connection_or_clear_identity_map(void);
void clear_broken_connection_map(int disable);
or_connection_t *connection_or_get_for_extend(const char *digest,
const tor_addr_t *target_addr,
const char **msg_out,
int *launch_out);
-void connection_or_set_bad_connections(const char *digest, int force);
void connection_or_block_renegotiation(or_connection_t *conn);
int connection_or_reached_eof(or_connection_t *conn);
@@ -40,7 +39,9 @@ void connection_or_notify_error(or_connection_t *conn,
MOCK_DECL(or_connection_t *,
connection_or_connect,
(const tor_addr_t *addr, uint16_t port,
- const char *id_digest, channel_tls_t *chan));
+ const char *id_digest,
+ const ed25519_public_key_t *ed_id,
+ channel_tls_t *chan));
void connection_or_close_normally(or_connection_t *orconn, int flush);
MOCK_DECL(void,connection_or_close_for_error,
@@ -59,10 +60,12 @@ int connection_init_or_handshake_state(or_connection_t *conn,
void connection_or_init_conn_from_address(or_connection_t *conn,
const tor_addr_t *addr,
uint16_t port,
- const char *id_digest,
+ const char *rsa_id_digest,
+ const ed25519_public_key_t *ed_id,
int started_here);
int connection_or_client_learned_peer_id(or_connection_t *conn,
- const uint8_t *peer_id);
+ const uint8_t *rsa_peer_id,
+ const ed25519_public_key_t *ed_peer_id);
time_t connection_or_client_used(or_connection_t *conn);
MOCK_DECL(int, connection_or_get_num_circuits, (or_connection_t *conn));
void or_handshake_state_free(or_handshake_state_t *state);
@@ -84,10 +87,14 @@ int connection_or_send_versions(or_connection_t *conn, int v3_plus);
MOCK_DECL(int,connection_or_send_netinfo,(or_connection_t *conn));
int connection_or_send_certs_cell(or_connection_t *conn);
int connection_or_send_auth_challenge_cell(or_connection_t *conn);
-int connection_or_compute_authenticate_cell_body(or_connection_t *conn,
- uint8_t *out, size_t outlen,
- crypto_pk_t *signing_key,
- int server);
+int authchallenge_type_is_supported(uint16_t challenge_type);
+int authchallenge_type_is_better(uint16_t challenge_type_a,
+ uint16_t challenge_type_b);
+var_cell_t *connection_or_compute_authenticate_cell_body(or_connection_t *conn,
+ const int authtype,
+ crypto_pk_t *signing_key,
+ const ed25519_keypair_t *ed_signing_key,
+ int server);
MOCK_DECL(int,connection_or_send_authenticate_cell,
(or_connection_t *conn, int type));
@@ -103,5 +110,11 @@ void var_cell_free(var_cell_t *cell);
/* DOCDOC */
#define MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS 4
+void connection_or_group_set_badness_(smartlist_t *group, int force);
+
+#ifdef TOR_UNIT_TESTS
+extern int certs_cell_ed25519_disabled_for_testing;
+#endif
+
#endif
diff --git a/src/or/control.c b/src/or/control.c
index 03d9fcee2a..879d9bbed9 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -36,6 +36,7 @@
#include "or.h"
#include "addressmap.h"
+#include "bridges.h"
#include "buffers.h"
#include "channel.h"
#include "channeltls.h"
@@ -57,6 +58,7 @@
#include "entrynodes.h"
#include "geoip.h"
#include "hibernate.h"
+#include "hs_common.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -69,6 +71,7 @@
#include "router.h"
#include "routerlist.h"
#include "routerparse.h"
+#include "shared_random.h"
#ifndef _WIN32
#include <pwd.h>
@@ -942,7 +945,7 @@ control_setconf_helper(control_connection_t *conn, uint32_t len, char *body,
++body;
}
- smartlist_add(entries, tor_strdup(""));
+ smartlist_add_strdup(entries, "");
config = smartlist_join_strings(entries, "\n", 0, NULL);
SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp));
smartlist_free(entries);
@@ -2028,7 +2031,7 @@ getinfo_helper_dir(control_connection_t *control_conn,
} else if (!strcmpstart(question, "dir/status/")) {
*answer = tor_strdup("");
} else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */
- if (directory_caches_dir_info(get_options())) {
+ if (we_want_to_fetch_flavor(get_options(), FLAV_NS)) {
const cached_dir_t *consensus = dirserv_get_consensus("ns");
if (consensus)
*answer = tor_strdup(consensus->dir);
@@ -2539,7 +2542,7 @@ circuit_describe_status_for_controller(origin_circuit_t *circ)
if (circ->rend_data != NULL) {
smartlist_add_asprintf(descparts, "REND_QUERY=%s",
- circ->rend_data->onion_address);
+ rend_data_get_address(circ->rend_data));
}
{
@@ -2594,6 +2597,8 @@ getinfo_helper_events(control_connection_t *control_conn,
if (circ->base_.state == CIRCUIT_STATE_OPEN)
state = "BUILT";
+ else if (circ->base_.state == CIRCUIT_STATE_GUARD_WAIT)
+ state = "GUARD_WAIT";
else if (circ->cpath)
state = "EXTENDED";
else
@@ -2866,6 +2871,26 @@ getinfo_helper_liveness(control_connection_t *control_conn,
return 0;
}
+/** Implementation helper for GETINFO: answers queries about shared random
+ * value. */
+static int
+getinfo_helper_sr(control_connection_t *control_conn,
+ const char *question, char **answer,
+ const char **errmsg)
+{
+ (void) control_conn;
+ (void) errmsg;
+
+ if (!strcmp(question, "sr/current")) {
+ *answer = sr_get_current_for_control();
+ } else if (!strcmp(question, "sr/previous")) {
+ *answer = sr_get_previous_for_control();
+ }
+ /* Else statement here is unrecognized key so do nothing. */
+
+ return 0;
+}
+
/** Callback function for GETINFO: on a given control connection, try to
* answer the question <b>q</b> and store the newly-allocated answer in
* *<b>a</b>. If an internal error occurs, return -1 and optionally set
@@ -3058,6 +3083,8 @@ static const getinfo_item_t getinfo_items[] = {
"Onion services owned by the current control connection."),
ITEM("onions/detached", onions,
"Onion services detached from the control connection."),
+ ITEM("sr/current", sr, "Get current shared random value."),
+ ITEM("sr/previous", sr, "Get previous shared random value."),
{ NULL, NULL, NULL, 0 }
};
@@ -3139,7 +3166,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len,
if (!ans) {
smartlist_add(unrecognized, (char*)q);
} else {
- smartlist_add(answers, tor_strdup(q));
+ smartlist_add_strdup(answers, q);
smartlist_add(answers, ans);
}
} SMARTLIST_FOREACH_END(q);
@@ -3350,7 +3377,8 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len,
SMARTLIST_FOREACH(nodes, const node_t *, node,
{
extend_info_t *info = extend_info_from_node(node, first_node);
- if (first_node && !info) {
+ if (!info) {
+ tor_assert_nonfatal(first_node);
log_warn(LD_CONTROL,
"controller tried to connect to a node that doesn't have any "
"addresses that are allowed by the firewall configuration; "
@@ -3358,10 +3386,6 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len,
circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED);
connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn);
goto done;
- } else {
- /* True, since node_has_descriptor(node) == true and we are extending
- * to the node's primary address */
- tor_assert(info);
}
circuit_append_new_exit(circ, info);
extend_info_free(info);
@@ -3377,7 +3401,8 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len,
goto done;
}
} else {
- if (circ->base_.state == CIRCUIT_STATE_OPEN) {
+ if (circ->base_.state == CIRCUIT_STATE_OPEN ||
+ circ->base_.state == CIRCUIT_STATE_GUARD_WAIT) {
int err_reason = 0;
circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
if ((err_reason = circuit_send_next_onion_skin(circ)) < 0) {
@@ -4039,6 +4064,14 @@ handle_control_dropguards(control_connection_t *conn,
smartlist_split_string(args, body, " ",
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ static int have_warned = 0;
+ if (! have_warned) {
+ log_warn(LD_CONTROL, "DROPGUARDS is dangerous; make sure you understand "
+ "the risks before using it. It may be removed in a future "
+ "version of Tor.");
+ have_warned = 1;
+ }
+
if (smartlist_len(args)) {
connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n");
} else {
@@ -4084,7 +4117,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len,
* of the id. */
desc_id = digest;
} else {
- connection_printf_to_buf(conn, "513 Unrecognized \"%s\"\r\n",
+ connection_printf_to_buf(conn, "513 Invalid argument \"%s\"\r\n",
arg1);
goto done;
}
@@ -6053,9 +6086,9 @@ control_event_networkstatus_changed_helper(smartlist_t *statuses,
return 0;
strs = smartlist_new();
- smartlist_add(strs, tor_strdup("650+"));
- smartlist_add(strs, tor_strdup(event_string));
- smartlist_add(strs, tor_strdup("\r\n"));
+ smartlist_add_strdup(strs, "650+");
+ smartlist_add_strdup(strs, event_string);
+ smartlist_add_strdup(strs, "\r\n");
SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs,
{
s = networkstatus_getinfo_helper_single(rs);
@@ -6864,8 +6897,10 @@ control_event_hs_descriptor_requested(const rend_data_t *rend_query,
send_control_event(EVENT_HS_DESC,
"650 HS_DESC REQUESTED %s %s %s %s\r\n",
- rend_hsaddress_str_or_unknown(rend_query->onion_address),
- rend_auth_type_to_string(rend_query->auth_type),
+ rend_hsaddress_str_or_unknown(
+ rend_data_get_address(rend_query)),
+ rend_auth_type_to_string(
+ TO_REND_DATA_V2(rend_query)->auth_type),
node_describe_longname_by_id(id_digest),
desc_id_base32);
}
@@ -6881,11 +6916,12 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp)
{
int replica;
const char *desc_id = NULL;
+ const rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data);
/* Possible if the fetch was done using a descriptor ID. This means that
* the HSFETCH command was used. */
- if (!tor_digest_is_zero(rend_data->desc_id_fetch)) {
- desc_id = rend_data->desc_id_fetch;
+ if (!tor_digest_is_zero(rend_data_v2->desc_id_fetch)) {
+ desc_id = rend_data_v2->desc_id_fetch;
goto end;
}
@@ -6893,7 +6929,7 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp)
* is the one associated with the HSDir fingerprint. */
for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS;
replica++) {
- const char *digest = rend_data->descriptor_id[replica];
+ const char *digest = rend_data_get_desc_id(rend_data, replica, NULL);
SMARTLIST_FOREACH_BEGIN(rend_data->hsdirs_fp, char *, fingerprint) {
if (tor_memcmp(fingerprint, hsdir_fp, DIGEST_LEN) == 0) {
@@ -7002,7 +7038,8 @@ control_event_hs_descriptor_receive_end(const char *action,
"650 HS_DESC %s %s %s %s%s%s\r\n",
action,
rend_hsaddress_str_or_unknown(onion_address),
- rend_auth_type_to_string(rend_data->auth_type),
+ rend_auth_type_to_string(
+ TO_REND_DATA_V2(rend_data)->auth_type),
node_describe_longname_by_id(id_digest),
desc_id_field ? desc_id_field : "",
reason_field ? reason_field : "");
@@ -7099,7 +7136,7 @@ control_event_hs_descriptor_failed(const rend_data_t *rend_data,
return;
}
control_event_hs_descriptor_receive_end("FAILED",
- rend_data->onion_address,
+ rend_data_get_address(rend_data),
rend_data, id_digest, reason);
}
diff --git a/src/or/directory.c b/src/or/directory.c
index f285e4c6ed..48d912bd28 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -3,19 +3,25 @@
* Copyright (c) 2007-2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
+#define DIRECTORY_PRIVATE
+
#include "or.h"
#include "backtrace.h"
+#include "bridges.h"
#include "buffers.h"
#include "circuitbuild.h"
#include "config.h"
#include "connection.h"
#include "connection_edge.h"
#include "control.h"
+#define DIRECTORY_PRIVATE
#include "directory.h"
#include "dirserv.h"
#include "dirvote.h"
#include "entrynodes.h"
#include "geoip.h"
+#include "hs_cache.h"
+#include "hs_common.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -33,16 +39,45 @@
#include "shared_random.h"
#if defined(EXPORTMALLINFO) && defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO)
-#ifndef OPENBSD
+#if !defined(OpenBSD)
#include <malloc.h>
#endif
#endif
/**
* \file directory.c
- * \brief Code to send and fetch directories and router
- * descriptors via HTTP. Directories use dirserv.c to generate the
- * results; clients use routers.c to parse them.
+ * \brief Code to send and fetch information from directory authorities and
+ * caches via HTTP.
+ *
+ * Directory caches and authorities use dirserv.c to generate the results of a
+ * query and stream them to the connection; clients use routerparse.c to parse
+ * them.
+ *
+ * Every directory request has a dir_connection_t on the client side and on
+ * the server side. In most cases, the dir_connection_t object is a linked
+ * connection, tunneled through an edge_connection_t so that it can be a
+ * stream on the Tor network. The only non-tunneled connections are those
+ * that are used to upload material (descriptors and votes) to authorities.
+ * Among tunneled connections, some use one-hop circuits, and others use
+ * multi-hop circuits for anonymity.
+ *
+ * Directory requests are launched by calling
+ * directory_initiate_command_rend() or one of its numerous variants. This
+ * launch the connection, will construct an HTTP request with
+ * directory_send_command(), send the and wait for a response. The client
+ * later handles the response with connection_dir_client_reached_eof(),
+ * which passes the information received to another part of Tor.
+ *
+ * On the server side, requests are read in directory_handle_command(),
+ * which dispatches first on the request type (GET or POST), and then on
+ * the URL requested. GET requests are processed with a table-based
+ * dispatcher in url_table[]. The process of handling larger GET requests
+ * is complicated because we need to avoid allocating a copy of all the
+ * data to be sent to the client in one huge buffer. Instead, we spool the
+ * data into the buffer using logic in connection_dirserv_flushed_some() in
+ * dirserv.c. (TODO: If we extended buf.c to have a zero-copy
+ * reference-based buffer type, we could remove most of that code, at the
+ * cost of a bit more reference counting.)
**/
/* In-points to directory.c:
@@ -65,7 +100,6 @@ static void directory_send_command(dir_connection_t *conn,
int purpose, int direct, const char *resource,
const char *payload, size_t payload_len,
time_t if_modified_since);
-static int directory_handle_command(dir_connection_t *conn);
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);
@@ -94,7 +128,8 @@ static void directory_initiate_command_rend(
const char *payload,
size_t payload_len,
time_t if_modified_since,
- const rend_data_t *rend_query);
+ const rend_data_t *rend_query,
+ circuit_guard_state_t *guard_state);
static void connection_dir_close_consensus_fetches(
dir_connection_t *except_this_one, const char *resource);
@@ -120,29 +155,55 @@ static void connection_dir_close_consensus_fetches(
/********* END VARIABLES ************/
-/** Return true iff the directory purpose <b>dir_purpose</b> (and if it's
- * fetching descriptors, it's fetching them for <b>router_purpose</b>)
- * must use an anonymous connection to a directory. */
+/** Return false if the directory purpose <b>dir_purpose</b>
+ * does not require an anonymous (three-hop) connection.
+ *
+ * Return true 1) by default, 2) if all directory actions have
+ * specifically been configured to be over an anonymous connection,
+ * or 3) if the router is a bridge */
int
-purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose)
+purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
+ const char *resource)
{
if (get_options()->AllDirActionsPrivate)
return 1;
- if (router_purpose == ROUTER_PURPOSE_BRIDGE)
+
+ if (router_purpose == ROUTER_PURPOSE_BRIDGE) {
+ if (dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC
+ && resource && !strcmp(resource, "authority.z")) {
+ /* We are asking a bridge for its own descriptor. That doesn't need
+ anonymity. */
+ return 0;
+ }
+ /* Assume all other bridge stuff needs anonymity. */
return 1; /* if no circuits yet, this might break bootstrapping, but it's
* needed to be safe. */
- if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR ||
- dir_purpose == DIR_PURPOSE_UPLOAD_VOTE ||
- dir_purpose == DIR_PURPOSE_UPLOAD_SIGNATURES ||
- dir_purpose == DIR_PURPOSE_FETCH_STATUS_VOTE ||
- dir_purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES ||
- dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS ||
- dir_purpose == DIR_PURPOSE_FETCH_CERTIFICATE ||
- dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC ||
- dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO ||
- dir_purpose == DIR_PURPOSE_FETCH_MICRODESC)
- return 0;
- return 1;
+ }
+
+ switch (dir_purpose)
+ {
+ case DIR_PURPOSE_UPLOAD_DIR:
+ case DIR_PURPOSE_UPLOAD_VOTE:
+ case DIR_PURPOSE_UPLOAD_SIGNATURES:
+ case DIR_PURPOSE_FETCH_STATUS_VOTE:
+ case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES:
+ case DIR_PURPOSE_FETCH_CONSENSUS:
+ case DIR_PURPOSE_FETCH_CERTIFICATE:
+ case DIR_PURPOSE_FETCH_SERVERDESC:
+ case DIR_PURPOSE_FETCH_EXTRAINFO:
+ case DIR_PURPOSE_FETCH_MICRODESC:
+ return 0;
+ case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
+ case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
+ case DIR_PURPOSE_FETCH_RENDDESC_V2:
+ return 1;
+ case DIR_PURPOSE_SERVER:
+ default:
+ log_warn(LD_BUG, "Called with dir_purpose=%d, router_purpose=%d",
+ dir_purpose, router_purpose);
+ tor_assert_nonfatal_unreached();
+ return 1; /* Assume it needs anonymity; better safe than sorry. */
+ }
}
/** Return a newly allocated string describing <b>auth</b>. Only describes
@@ -347,7 +408,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
log_info(LD_DIR, "Uploading an extrainfo too (length %d)",
(int) extrainfo_len);
}
- if (purpose_needs_anonymity(dir_purpose, router_purpose)) {
+ if (purpose_needs_anonymity(dir_purpose, router_purpose, NULL)) {
indirection = DIRIND_ANONYMOUS;
} else if (!fascist_firewall_allows_dir_server(ds,
FIREWALL_DIR_CONNECTION,
@@ -362,7 +423,8 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
directory_initiate_command_routerstatus(rs, dir_purpose,
router_purpose,
indirection,
- NULL, payload, upload_len, 0);
+ NULL, payload, upload_len, 0,
+ NULL);
} SMARTLIST_FOREACH_END(ds);
if (!found) {
char *s = authdir_type_to_string(type);
@@ -380,10 +442,9 @@ should_use_directory_guards(const or_options_t *options)
/* Public (non-bridge) servers never use directory guards. */
if (public_server_mode(options))
return 0;
- /* If guards are disabled, or directory guards are disabled, we can't
- * use directory guards.
+ /* If guards are disabled, we can't use directory guards.
*/
- if (!options->UseEntryGuards || !options->UseEntryGuardsAsDirGuards)
+ if (!options->UseEntryGuards)
return 0;
/* If we're configured to fetch directory info aggressively or of a
* nonstandard type, don't use directory guards. */
@@ -398,7 +459,8 @@ should_use_directory_guards(const or_options_t *options)
* information of type <b>type</b>, and return its routerstatus. */
static const routerstatus_t *
directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags,
- uint8_t dir_purpose)
+ uint8_t dir_purpose,
+ circuit_guard_state_t **guard_state_out)
{
const routerstatus_t *rs = NULL;
const or_options_t *options = get_options();
@@ -407,7 +469,7 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags,
log_warn(LD_BUG, "Called when we have UseBridges set.");
if (should_use_directory_guards(options)) {
- const node_t *node = choose_random_dirguard(type);
+ const node_t *node = guards_choose_dirguard(guard_state_out);
if (node)
rs = node->rs;
} else {
@@ -441,7 +503,8 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
int prefer_authority = (directory_fetches_from_authorities(options)
|| want_authority == DL_WANT_AUTHORITY);
int require_authority = 0;
- int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose);
+ int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose,
+ resource);
dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource);
time_t if_modified_since = 0;
@@ -487,6 +550,7 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
if (!options->FetchServerDescriptors)
return;
+ circuit_guard_state_t *guard_state = NULL;
if (!get_via_tor) {
if (options->UseBridges && !(type & BRIDGE_DIRINFO)) {
/* We want to ask a running bridge for which we have a descriptor.
@@ -495,25 +559,34 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
* sort of dir fetch we'll be doing, so it won't return a bridge
* that can't answer our question.
*/
- const node_t *node = choose_random_dirguard(type);
+ const node_t *node = guards_choose_dirguard(&guard_state);
if (node && node->ri) {
/* every bridge has a routerinfo. */
routerinfo_t *ri = node->ri;
/* clients always make OR connections to bridges */
tor_addr_port_t or_ap;
+ tor_addr_port_t nil_dir_ap;
/* we are willing to use a non-preferred address if we need to */
fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0,
&or_ap);
- directory_initiate_command(&or_ap.addr, or_ap.port,
- NULL, 0, /*no dirport*/
- ri->cache_info.identity_digest,
- dir_purpose,
- router_purpose,
- DIRIND_ONEHOP,
- resource, NULL, 0, if_modified_since);
- } else
+ tor_addr_make_null(&nil_dir_ap.addr, AF_INET);
+ nil_dir_ap.port = 0;
+ directory_initiate_command_rend(&or_ap,
+ &nil_dir_ap,
+ ri->cache_info.identity_digest,
+ dir_purpose,
+ router_purpose,
+ DIRIND_ONEHOP,
+ resource, NULL, 0, if_modified_since,
+ NULL, guard_state);
+ } else {
+ if (guard_state) {
+ entry_guard_cancel(&guard_state);
+ }
log_notice(LD_DIR, "Ignoring directory request, since no bridge "
"nodes are available yet.");
+ }
+
return;
} else {
if (prefer_authority || (type & BRIDGE_DIRINFO)) {
@@ -544,9 +617,9 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
}
}
if (!rs && !(type & BRIDGE_DIRINFO)) {
- /* */
rs = directory_pick_generic_dirserver(type, pds_flags,
- dir_purpose);
+ dir_purpose,
+ &guard_state);
if (!rs)
get_via_tor = 1; /* last resort: try routing it via Tor */
}
@@ -569,13 +642,14 @@ MOCK_IMPL(void, directory_get_from_dirserver, (
router_purpose,
indirection,
resource, NULL, 0,
- if_modified_since);
+ if_modified_since,
+ guard_state);
} else {
log_notice(LD_DIR,
"While fetching directory info, "
"no running dirservers known. Will try again later. "
"(purpose %d)", dir_purpose);
- if (!purpose_needs_anonymity(dir_purpose, router_purpose)) {
+ if (!purpose_needs_anonymity(dir_purpose, router_purpose, resource)) {
/* remember we tried them all and failed. */
directory_all_unreachable(time(NULL));
}
@@ -603,7 +677,7 @@ directory_get_from_all_authorities(uint8_t dir_purpose,
rs = &ds->fake_status;
directory_initiate_command_routerstatus(rs, dir_purpose, router_purpose,
DIRIND_ONEHOP, resource, NULL,
- 0, 0);
+ 0, 0, NULL);
} SMARTLIST_FOREACH_END(ds);
}
@@ -714,7 +788,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
const char *payload,
size_t payload_len,
time_t if_modified_since,
- const rend_data_t *rend_query)
+ const rend_data_t *rend_query,
+ circuit_guard_state_t *guard_state)
{
const or_options_t *options = get_options();
const node_t *node;
@@ -769,7 +844,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
dir_purpose, router_purpose,
indirection, resource,
payload, payload_len, if_modified_since,
- rend_query);
+ rend_query,
+ guard_state);
}
/** Launch a new connection to the directory server <b>status</b> to
@@ -794,13 +870,15 @@ MOCK_IMPL(void, directory_initiate_command_routerstatus,
const char *resource,
const char *payload,
size_t payload_len,
- time_t if_modified_since))
+ time_t if_modified_since,
+ circuit_guard_state_t *guard_state))
{
directory_initiate_command_routerstatus_rend(status, dir_purpose,
router_purpose,
indirection, resource,
payload, payload_len,
- if_modified_since, NULL);
+ if_modified_since, NULL,
+ guard_state);
}
/** Return true iff <b>conn</b> is the client side of a directory connection
@@ -828,6 +906,11 @@ directory_conn_is_self_reachability_test(dir_connection_t *conn)
static void
connection_dir_request_failed(dir_connection_t *conn)
{
+ if (conn->guard_state) {
+ /* We haven't seen a success on this guard state, so consider it to have
+ * failed. */
+ entry_guard_failed(&conn->guard_state);
+ }
if (directory_conn_is_self_reachability_test(conn)) {
return; /* this was a test fetch. don't retry. */
}
@@ -983,6 +1066,7 @@ directory_must_use_begindir(const or_options_t *options)
/** 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.
* 1) If or_port is 0, or it's a direct conn and or_port is firewalled
* or we're a dir mirror, no.
* 2) If we prefer to avoid begindir conns, and we're not fetching or
@@ -993,15 +1077,22 @@ directory_must_use_begindir(const or_options_t *options)
*/
static int
directory_command_should_use_begindir(const or_options_t *options,
- const tor_addr_t *addr,
- int or_port, uint8_t router_purpose,
+ const tor_addr_t *or_addr, int or_port,
+ const tor_addr_t *dir_addr, int dir_port,
+ uint8_t router_purpose,
dir_indirection_t indirection,
const char **reason)
{
(void) router_purpose;
+ (void) dir_addr;
tor_assert(reason);
*reason = NULL;
+ /* Reasons why we must use begindir */
+ if (!dir_port) {
+ *reason = "(using begindir - directory with no DirPort)";
+ return 1; /* We don't know a DirPort -- must begindir. */
+ }
/* Reasons why we can't possibly use begindir */
if (!or_port) {
*reason = "directory with unknown ORPort";
@@ -1014,7 +1105,7 @@ directory_command_should_use_begindir(const or_options_t *options,
}
if (indirection == DIRIND_ONEHOP) {
/* We're firewalled and want a direct OR connection */
- if (!fascist_firewall_allows_address_addr(addr, or_port,
+ if (!fascist_firewall_allows_address_addr(or_addr, or_port,
FIREWALL_OR_CONNECTION, 0, 0)) {
*reason = "ORPort not reachable";
return 0;
@@ -1040,6 +1131,7 @@ directory_command_should_use_begindir(const or_options_t *options,
* <b>router_purpose</b>, making an (in)direct connection as specified in
* <b>indirection</b>, with command <b>resource</b>, <b>payload</b> of
* <b>payload_len</b>, and asking for a result only <b>if_modified_since</b>.
+ * If <b>guard_state</b> is set, assign it to the directory circuit.
*/
void
directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
@@ -1048,7 +1140,8 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
uint8_t dir_purpose, uint8_t router_purpose,
dir_indirection_t indirection, const char *resource,
const char *payload, size_t payload_len,
- time_t if_modified_since)
+ time_t if_modified_since,
+ circuit_guard_state_t *guard_state)
{
tor_addr_port_t or_ap, dir_ap;
@@ -1075,19 +1168,7 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
digest, dir_purpose,
router_purpose, indirection,
resource, payload, payload_len,
- if_modified_since, NULL);
-}
-
-/** Return non-zero iff a directory connection with purpose
- * <b>dir_purpose</b> reveals sensitive information about a Tor
- * instance's client activities. (Such connections must be performed
- * through normal three-hop Tor circuits.) */
-int
-is_sensitive_dir_purpose(uint8_t dir_purpose)
-{
- return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) ||
- (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) ||
- (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2));
+ if_modified_since, NULL, guard_state);
}
/** Same as directory_initiate_command(), but accepts rendezvous data to
@@ -1102,7 +1183,8 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
const char *resource,
const char *payload, size_t payload_len,
time_t if_modified_since,
- const rend_data_t *rend_query)
+ const rend_data_t *rend_query,
+ circuit_guard_state_t *guard_state)
{
tor_assert(or_addr_port);
tor_assert(dir_addr_port);
@@ -1117,6 +1199,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
* send our directory request)? */
const int use_begindir = directory_command_should_use_begindir(options,
&or_addr_port->addr, or_addr_port->port,
+ &dir_addr_port->addr, dir_addr_port->port,
router_purpose, indirection,
&begindir_reason);
/* Will the connection go via a three-hop Tor circuit? Note that this
@@ -1137,7 +1220,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose));
- if (is_sensitive_dir_purpose(dir_purpose)) {
+ if (purpose_needs_anonymity(dir_purpose, router_purpose, resource)) {
tor_assert(anonymized_connection ||
rend_non_anonymous_mode_enabled(options));
}
@@ -1163,9 +1246,9 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
if (!port || tor_addr_is_null(&addr)) {
static int logged_backtrace = 0;
log_warn(LD_DIR,
- "Cannot make an outgoing %sconnection without %sPort.",
+ "Cannot make an outgoing %sconnection without a remote %sPort.",
use_begindir ? "begindir " : "",
- use_begindir ? "an OR" : "a Dir");
+ use_begindir ? "OR" : "Dir");
if (!logged_backtrace) {
log_backtrace(LOG_INFO, LD_BUG, "Address came from");
logged_backtrace = 1;
@@ -1203,6 +1286,11 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
port = options->HTTPProxyPort;
}
+ // In this case we should not have picked a directory guard.
+ if (BUG(guard_state)) {
+ entry_guard_cancel(&guard_state);
+ }
+
switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr,
port, &socket_error)) {
case -1:
@@ -1239,6 +1327,14 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port,
else if (anonymized_connection && !use_begindir)
rep_hist_note_used_port(time(NULL), conn->base_.port);
+ // In this case we should not have a directory guard; we'll
+ // get a regular guard later when we build the circuit.
+ if (BUG(anonymized_connection && guard_state)) {
+ entry_guard_cancel(&guard_state);
+ }
+
+ conn->guard_state = guard_state;
+
/* make an AP connection
* populate it and add it at the right state
* hook up both sides
@@ -1770,15 +1866,15 @@ body_is_plausible(const char *body, size_t len, int purpose)
if (purpose == DIR_PURPOSE_FETCH_MICRODESC) {
return (!strcmpstart(body,"onion-key"));
}
- if (1) {
- if (!strcmpstart(body,"router") ||
- !strcmpstart(body,"network-status"))
- return 1;
- for (i=0;i<32;++i) {
- if (!TOR_ISPRINT(body[i]) && !TOR_ISSPACE(body[i]))
- return 0;
- }
+
+ if (!strcmpstart(body,"router") ||
+ !strcmpstart(body,"network-status"))
+ return 1;
+ for (i=0;i<32;++i) {
+ if (!TOR_ISPRINT(body[i]) && !TOR_ISSPACE(body[i]))
+ return 0;
}
+
return 1;
}
@@ -1880,6 +1976,19 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
escaped(reason),
conn->base_.purpose);
+ if (conn->guard_state) {
+ /* we count the connection as successful once we can read from it. We do
+ * not, however, delay use of the circuit here, since it's just for a
+ * one-hop directory request. */
+ /* XXXXprop271 note that this will not do the right thing for other
+ * waiting circuits that would be triggered by this circuit becoming
+ * complete/usable. But that's ok, I think.
+ */
+ entry_guard_succeeded(&conn->guard_state);
+ circuit_guard_state_free(conn->guard_state);
+ conn->guard_state = NULL;
+ }
+
/* now check if it's got any hints for us about our IP address. */
if (conn->dirconn_direct) {
char *guess = http_get_header(headers, X_ADDRESS_HEADER);
@@ -2013,10 +2122,9 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
/* If we launched other fetches for this consensus, cancel them. */
connection_dir_close_consensus_fetches(conn, flavname);
- /* launches router downloads as needed */
+ /* update the list of routers and directory guards */
routers_update_all_from_networkstatus(now, 3);
update_microdescs_from_networkstatus(now);
- update_microdesc_downloads(now);
directory_info_has_arrived(now, 0, 0);
if (authdir_mode_v3(get_options())) {
sr_act_post_consensus(
@@ -2341,10 +2449,10 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
conn->identity_digest, \
reason) )
#define SEND_HS_DESC_FAILED_CONTENT() ( \
- control_event_hs_descriptor_content(conn->rend_data->onion_address, \
- conn->requested_resource, \
- conn->identity_digest, \
- NULL) )
+ control_event_hs_descriptor_content(rend_data_get_address(conn->rend_data), \
+ conn->requested_resource, \
+ conn->identity_digest, \
+ NULL) )
tor_assert(conn->rend_data);
log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d "
"(%s))",
@@ -2417,7 +2525,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
#define SEND_HS_DESC_UPLOAD_FAILED_EVENT(reason) ( \
control_event_hs_descriptor_upload_failed( \
conn->identity_digest, \
- conn->rend_data->onion_address, \
+ rend_data_get_address(conn->rend_data), \
reason) )
log_info(LD_REND,"Uploaded rendezvous descriptor (status %d "
"(%s))",
@@ -2431,7 +2539,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
"Uploading rendezvous descriptor: finished with status "
"200 (%s)", escaped(reason));
control_event_hs_descriptor_uploaded(conn->identity_digest,
- conn->rend_data->onion_address);
+ rend_data_get_address(conn->rend_data));
rend_service_desc_has_uploaded(conn->rend_data);
break;
case 400:
@@ -2542,7 +2650,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn)
* refetching is unnecessary.) */
if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 &&
dir_conn->rend_data &&
- strlen(dir_conn->rend_data->onion_address) == REND_SERVICE_ID_LEN_BASE32)
+ strlen(rend_data_get_address(dir_conn->rend_data)) ==
+ REND_SERVICE_ID_LEN_BASE32)
rend_client_refetch_v2_renddesc(dir_conn->rend_data);
}
@@ -2762,8 +2871,8 @@ static int handle_get_descriptor(dir_connection_t *conn,
const get_handler_args_t *args);
static int handle_get_keys(dir_connection_t *conn,
const get_handler_args_t *args);
-static int handle_get_rendezvous2(dir_connection_t *conn,
- const get_handler_args_t *args);
+static int handle_get_hs_descriptor_v2(dir_connection_t *conn,
+ const get_handler_args_t *args);
static int handle_get_robots(dir_connection_t *conn,
const get_handler_args_t *args);
static int handle_get_networkstatus_bridges(dir_connection_t *conn,
@@ -2779,7 +2888,8 @@ static const url_table_ent_t url_table[] = {
{ "/tor/server/", 1, handle_get_descriptor },
{ "/tor/extra/", 1, handle_get_descriptor },
{ "/tor/keys/", 1, handle_get_keys },
- { "/tor/rendezvous2/", 1, handle_get_rendezvous2 },
+ { "/tor/rendezvous2/", 1, handle_get_hs_descriptor_v2 },
+ { "/tor/hs/3/", 1, handle_get_hs_descriptor_v3 },
{ "/tor/robots.txt", 0, handle_get_robots },
{ "/tor/networkstatus-bridges", 0, handle_get_networkstatus_bridges },
{ NULL, 0, NULL },
@@ -2791,9 +2901,9 @@ static const url_table_ent_t url_table[] = {
* conn-\>outbuf. If the request is unrecognized, send a 404.
* Return 0 if we handled this successfully, or -1 if we need to close
* the connection. */
-STATIC int
-directory_handle_command_get(dir_connection_t *conn, const char *headers,
- const char *req_body, size_t req_body_len)
+MOCK_IMPL(STATIC int,
+directory_handle_command_get,(dir_connection_t *conn, const char *headers,
+ const char *req_body, size_t req_body_len))
{
char *url, *url_mem, *header;
time_t if_modified_since = 0;
@@ -2889,6 +2999,28 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args)
return 0;
}
+/** Warn that the consensus <b>v</b> of type <b>flavor</b> is too old and will
+ * not be served to clients. Rate-limit the warning to avoid logging an entry
+ * on every request.
+ */
+static void
+warn_consensus_is_too_old(networkstatus_t *v, const char *flavor, time_t now)
+{
+#define TOO_OLD_WARNING_INTERVAL (60*60)
+ static ratelim_t warned = RATELIM_INIT(TOO_OLD_WARNING_INTERVAL);
+ char timestamp[ISO_TIME_LEN+1];
+ char *dupes;
+
+ if ((dupes = rate_limit_log(&warned, now))) {
+ format_local_iso_time(timestamp, v->valid_until);
+ log_warn(LD_DIRSERV, "Our %s%sconsensus is too old, so we will not "
+ "serve it to clients. It was valid until %s local time and we "
+ "continued to serve it for up to 24 hours after it expired.%s",
+ flavor ? flavor : "", flavor ? " " : "", timestamp, dupes);
+ tor_free(dupes);
+ }
+}
+
/** Helper function for GET /tor/status-vote/current/consensus
*/
static int
@@ -2904,54 +3036,61 @@ handle_get_current_consensus(dir_connection_t *conn,
smartlist_t *dir_fps = smartlist_new();
long lifetime = NETWORKSTATUS_CACHE_LIFETIME;
- if (1) {
- networkstatus_t *v;
- time_t now = time(NULL);
- const char *want_fps = NULL;
- char *flavor = NULL;
- int flav = FLAV_NS;
- #define CONSENSUS_URL_PREFIX "/tor/status-vote/current/consensus/"
- #define CONSENSUS_FLAVORED_PREFIX "/tor/status-vote/current/consensus-"
- /* figure out the flavor if any, and who we wanted to sign the thing */
- if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) {
- const char *f, *cp;
- f = url + strlen(CONSENSUS_FLAVORED_PREFIX);
- cp = strchr(f, '/');
- if (cp) {
- want_fps = cp+1;
- flavor = tor_strndup(f, cp-f);
- } else {
- flavor = tor_strdup(f);
- }
- flav = networkstatus_parse_flavor_name(flavor);
- if (flav < 0)
- flav = FLAV_NS;
+ networkstatus_t *v;
+ time_t now = time(NULL);
+ const char *want_fps = NULL;
+ char *flavor = NULL;
+ int flav = FLAV_NS;
+#define CONSENSUS_URL_PREFIX "/tor/status-vote/current/consensus/"
+#define CONSENSUS_FLAVORED_PREFIX "/tor/status-vote/current/consensus-"
+ /* figure out the flavor if any, and who we wanted to sign the thing */
+ if (!strcmpstart(url, CONSENSUS_FLAVORED_PREFIX)) {
+ const char *f, *cp;
+ f = url + strlen(CONSENSUS_FLAVORED_PREFIX);
+ cp = strchr(f, '/');
+ if (cp) {
+ want_fps = cp+1;
+ flavor = tor_strndup(f, cp-f);
} else {
- if (!strcmpstart(url, CONSENSUS_URL_PREFIX))
- want_fps = url+strlen(CONSENSUS_URL_PREFIX);
+ flavor = tor_strdup(f);
}
+ flav = networkstatus_parse_flavor_name(flavor);
+ if (flav < 0)
+ flav = FLAV_NS;
+ } else {
+ if (!strcmpstart(url, CONSENSUS_URL_PREFIX))
+ want_fps = url+strlen(CONSENSUS_URL_PREFIX);
+ }
- v = networkstatus_get_latest_consensus_by_flavor(flav);
+ v = networkstatus_get_latest_consensus_by_flavor(flav);
- if (v && want_fps &&
- !client_likes_consensus(v, want_fps)) {
- write_http_status_line(conn, 404, "Consensus not signed by sufficient "
- "number of requested authorities");
- smartlist_free(dir_fps);
- geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS);
- tor_free(flavor);
- goto done;
- }
+ if (v && !networkstatus_consensus_reasonably_live(v, now)) {
+ write_http_status_line(conn, 404, "Consensus is too old");
+ warn_consensus_is_too_old(v, flavor, now);
+ smartlist_free(dir_fps);
+ geoip_note_ns_response(GEOIP_REJECT_NOT_FOUND);
+ tor_free(flavor);
+ goto done;
+ }
- {
- char *fp = tor_malloc_zero(DIGEST_LEN);
- if (flavor)
- strlcpy(fp, flavor, DIGEST_LEN);
- tor_free(flavor);
- smartlist_add(dir_fps, fp);
- }
- lifetime = (v && v->fresh_until > now) ? v->fresh_until - now : 0;
+ if (v && want_fps &&
+ !client_likes_consensus(v, want_fps)) {
+ write_http_status_line(conn, 404, "Consensus not signed by sufficient "
+ "number of requested authorities");
+ smartlist_free(dir_fps);
+ geoip_note_ns_response(GEOIP_REJECT_NOT_ENOUGH_SIGS);
+ tor_free(flavor);
+ goto done;
+ }
+
+ {
+ char *fp = tor_malloc_zero(DIGEST_LEN);
+ if (flavor)
+ strlcpy(fp, flavor, DIGEST_LEN);
+ tor_free(flavor);
+ smartlist_add(dir_fps, fp);
}
+ lifetime = (v && v->fresh_until > now) ? v->fresh_until - now : 0;
if (!smartlist_len(dir_fps)) { /* we failed to create/cache cp */
write_http_status_line(conn, 503, "Network status object unavailable");
@@ -2987,21 +3126,19 @@ handle_get_current_consensus(dir_connection_t *conn,
goto done;
}
- if (1) {
- tor_addr_t addr;
- if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
- geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS,
- &addr, NULL,
- time(NULL));
- geoip_note_ns_response(GEOIP_SUCCESS);
- /* Note that a request for a network status has started, so that we
- * can measure the download time later on. */
- if (conn->dirreq_id)
- geoip_start_dirreq(conn->dirreq_id, dlen, DIRREQ_TUNNELED);
- else
- geoip_start_dirreq(TO_CONN(conn)->global_identifier, dlen,
- DIRREQ_DIRECT);
- }
+ tor_addr_t addr;
+ if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
+ geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS,
+ &addr, NULL,
+ time(NULL));
+ geoip_note_ns_response(GEOIP_SUCCESS);
+ /* Note that a request for a network status has started, so that we
+ * can measure the download time later on. */
+ if (conn->dirreq_id)
+ geoip_start_dirreq(conn->dirreq_id, dlen, DIRREQ_TUNNELED);
+ else
+ geoip_start_dirreq(TO_CONN(conn)->global_identifier, dlen,
+ DIRREQ_DIRECT);
}
write_http_response_header(conn, -1, compressed,
@@ -3347,7 +3484,8 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args)
/** Helper function for GET /tor/rendezvous2/
*/
static int
-handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args)
+handle_get_hs_descriptor_v2(dir_connection_t *conn,
+ const get_handler_args_t *args)
{
const char *url = args->url;
if (connection_dir_is_encrypted(conn)) {
@@ -3381,6 +3519,43 @@ handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args)
return 0;
}
+/** Helper function for GET /tor/hs/3/<z>. Only for version 3.
+ */
+STATIC int
+handle_get_hs_descriptor_v3(dir_connection_t *conn,
+ const get_handler_args_t *args)
+{
+ int retval;
+ const char *desc_str = NULL;
+ const char *pubkey_str = NULL;
+ const char *url = args->url;
+
+ /* Reject unencrypted dir connections */
+ if (!connection_dir_is_encrypted(conn)) {
+ write_http_status_line(conn, 404, "Not found");
+ goto done;
+ }
+
+ /* After the path prefix follows the base64 encoded blinded pubkey which we
+ * use to get the descriptor from the cache. Skip the prefix and get the
+ * pubkey. */
+ tor_assert(!strcmpstart(url, "/tor/hs/3/"));
+ pubkey_str = url + strlen("/tor/hs/3/");
+ 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");
+ goto done;
+ }
+
+ /* Found requested descriptor! Pass it to this nice client. */
+ write_http_response_header(conn, strlen(desc_str), 0, 0);
+ connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn));
+
+ done:
+ return 0;
+}
+
/** Helper function for GET /tor/networkstatus-bridges
*/
static int
@@ -3436,14 +3611,98 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args)
return 0;
}
+/* Given the <b>url</b> from a POST request, try to extract the version number
+ * using the provided <b>prefix</b>. The version should be after the prefix and
+ * ending with the seperator "/". For instance:
+ * /tor/hs/3/publish
+ *
+ * On success, <b>end_pos</b> points to the position right after the version
+ * was found. On error, it is set to NULL.
+ *
+ * Return version on success else negative value. */
+STATIC int
+parse_hs_version_from_post(const char *url, const char *prefix,
+ const char **end_pos)
+{
+ int ok;
+ unsigned long version;
+ const char *start;
+ char *end = NULL;
+
+ tor_assert(url);
+ tor_assert(prefix);
+ tor_assert(end_pos);
+
+ /* Check if the prefix does start the url. */
+ if (strcmpstart(url, prefix)) {
+ goto err;
+ }
+ /* Move pointer to the end of the prefix string. */
+ start = url + strlen(prefix);
+ /* Try this to be the HS version and if we are still at the separator, next
+ * will be move to the right value. */
+ version = tor_parse_long(start, 10, 0, INT_MAX, &ok, &end);
+ if (!ok) {
+ goto err;
+ }
+
+ *end_pos = end;
+ return (int) version;
+ err:
+ *end_pos = NULL;
+ return -1;
+}
+
+/* Handle the POST request for a hidden service descripror. The request is in
+ * <b>url</b>, the body of the request is in <b>body</b>. Return 200 on success
+ * else return 400 indicating a bad request. */
+STATIC int
+handle_post_hs_descriptor(const char *url, const char *body)
+{
+ int version;
+ const char *end_pos;
+
+ tor_assert(url);
+ tor_assert(body);
+
+ version = parse_hs_version_from_post(url, "/tor/hs/", &end_pos);
+ if (version < 0) {
+ goto err;
+ }
+
+ /* We have a valid version number, now make sure it's a publish request. Use
+ * the end position just after the version and check for the command. */
+ if (strcmpstart(end_pos, "/publish")) {
+ goto err;
+ }
+
+ switch (version) {
+ case HS_VERSION_THREE:
+ if (hs_cache_store_as_dir(body) < 0) {
+ goto err;
+ }
+ log_info(LD_REND, "Publish request for HS descriptor handled "
+ "successfully.");
+ break;
+ default:
+ /* Unsupported version, return a bad request. */
+ goto err;
+ }
+
+ return 200;
+ err:
+ /* Bad request. */
+ return 400;
+}
+
/** Helper function: called when a dirserver gets a complete HTTP POST
* request. Look for an uploaded server descriptor or rendezvous
* service descriptor. On finding one, process it and write a
* response into conn-\>outbuf. If the request is unrecognized, send a
* 400. Always return 0. */
-static int
-directory_handle_command_post(dir_connection_t *conn, const char *headers,
- const char *body, size_t body_len)
+MOCK_IMPL(STATIC int,
+directory_handle_command_post,(dir_connection_t *conn, const char *headers,
+ const char *body, size_t body_len))
{
char *url = NULL;
const or_options_t *options = get_options();
@@ -3480,6 +3739,21 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
goto done;
}
+ /* Handle HS descriptor publish request. */
+ /* XXX: This should be disabled with a consensus param until we want to
+ * the prop224 be deployed and thus use. */
+ if (connection_dir_is_encrypted(conn) && !strcmpstart(url, "/tor/hs/")) {
+ const char *msg = "HS descriptor stored successfully.";
+
+ /* We most probably have a publish request for an HS descriptor. */
+ int code = handle_post_hs_descriptor(url, body);
+ if (code != 200) {
+ msg = "Invalid HS descriptor. Rejected.";
+ }
+ write_http_status_line(conn, code, msg);
+ goto done;
+ }
+
if (!authdir_mode(options)) {
/* we just provide cached directories; we don't want to
* receive anything. */
@@ -3560,7 +3834,7 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers,
* from the inbuf, try to process it; otherwise, leave it on the
* buffer. Return a 0 on success, or -1 on error.
*/
-static int
+STATIC int
directory_handle_command(dir_connection_t *conn)
{
char *headers=NULL, *body=NULL;
@@ -3861,7 +4135,7 @@ download_status_schedule_get_delay(download_status_t *dls,
delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1);
} else if (dls->backoff == DL_SCHED_RANDOM_EXPONENTIAL) {
/* Check if we missed a reset somehow */
- if (dls->last_backoff_position > dls_schedule_position) {
+ IF_BUG_ONCE(dls->last_backoff_position > dls_schedule_position) {
dls->last_backoff_position = 0;
dls->last_delay_used = 0;
}
diff --git a/src/or/directory.h b/src/or/directory.h
index 629b3ead90..1459c3bbdb 100644
--- a/src/or/directory.h
+++ b/src/or/directory.h
@@ -49,7 +49,8 @@ MOCK_DECL(void, directory_initiate_command_routerstatus,
const char *resource,
const char *payload,
size_t payload_len,
- time_t if_modified_since));
+ time_t if_modified_since,
+ struct circuit_guard_state_t *guard_state));
void directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
uint8_t dir_purpose,
@@ -59,7 +60,8 @@ void directory_initiate_command_routerstatus_rend(const routerstatus_t *status,
const char *payload,
size_t payload_len,
time_t if_modified_since,
- const rend_data_t *rend_query);
+ const rend_data_t *rend_query,
+ struct circuit_guard_state_t *guard_state);
int parse_http_response(const char *headers, int *code, time_t *date,
compress_method_t *compression, char **response);
@@ -77,7 +79,8 @@ void directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port,
dir_indirection_t indirection,
const char *resource,
const char *payload, size_t payload_len,
- time_t if_modified_since);
+ time_t if_modified_since,
+ struct circuit_guard_state_t *guard_state);
#define DSR_HEX (1<<0)
#define DSR_BASE64 (1<<1)
@@ -138,26 +141,39 @@ 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);
-/* Yes, these two functions are confusingly similar.
- * Let's sort that out in #20077. */
-int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose);
-int is_sensitive_dir_purpose(uint8_t dir_purpose);
+int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
+ const char *resource);
+
+#ifdef DIRECTORY_PRIVATE
+
+struct get_handler_args_t;
+STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn,
+ const struct get_handler_args_t *args);
+STATIC int directory_handle_command(dir_connection_t *conn);
+
+#endif
#ifdef TOR_UNIT_TESTS
-/* Used only by directory.c and test_dir.c */
+/* Used only by test_dir.c */
STATIC int parse_http_url(const char *headers, char **url);
STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose,
const char *resource);
-STATIC int directory_handle_command_get(dir_connection_t *conn,
- const char *headers,
- const char *req_body,
- size_t req_body_len);
+MOCK_DECL(STATIC int, directory_handle_command_get,(dir_connection_t *conn,
+ const char *headers,
+ const char *req_body,
+ size_t req_body_len));
+MOCK_DECL(STATIC int, directory_handle_command_post,(dir_connection_t *conn,
+ const char *headers,
+ const char *body,
+ size_t body_len));
STATIC int download_status_schedule_get_delay(download_status_t *dls,
const smartlist_t *schedule,
int min_delay, int max_delay,
time_t now);
+STATIC int handle_post_hs_descriptor(const char *url, const char *body);
+
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);
@@ -169,6 +185,9 @@ STATIC void find_dl_min_and_max_delay(download_status_t *dls,
int *min, int *max);
STATIC int next_random_exponential_delay(int delay, int max_delay);
+STATIC int parse_hs_version_from_post(const char *url, const char *prefix,
+ const char **end_pos);
+
#endif
#endif
diff --git a/src/or/dirserv.c b/src/or/dirserv.c
index da34c196f4..0e8a534eaf 100644
--- a/src/or/dirserv.c
+++ b/src/or/dirserv.c
@@ -274,6 +274,13 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg,
return FP_REJECT;
}
+ /* Check for the more usual versions to reject a router first. */
+ const uint32_t r = dirserv_get_status_impl(d, router->nickname,
+ router->addr, router->or_port,
+ router->platform, msg, severity);
+ if (r)
+ return r;
+
/* dirserv_get_status_impl already rejects versions older than 0.2.4.18-rc,
* and onion_curve25519_pkey was introduced in 0.2.4.8-alpha.
* But just in case a relay doesn't provide or lies about its version, or
@@ -324,9 +331,7 @@ dirserv_router_get_status(const routerinfo_t *router, const char **msg,
}
}
- return dirserv_get_status_impl(d, router->nickname,
- router->addr, router->or_port,
- router->platform, msg, severity);
+ return 0;
}
/** Return true if there is no point in downloading the router described by
@@ -1006,7 +1011,7 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out,
if (!node->is_running)
*cp++ = '!';
router_get_verbose_nickname(cp, ri);
- smartlist_add(rs_entries, tor_strdup(name_buf));
+ smartlist_add_strdup(rs_entries, name_buf);
} else if (ri->cache_info.published_on >= cutoff) {
smartlist_add(rs_entries, list_single_server_status(ri,
node->is_running));
@@ -1127,8 +1132,10 @@ directory_fetches_dir_info_later(const or_options_t *options)
return options->UseBridges != 0;
}
-/** Return true iff we want to fetch and keep certificates for authorities
+/** Return true iff we want to serve certificates for authorities
* that we don't acknowledge as authorities ourself.
+ * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch
+ * and keep these certificates.
*/
int
directory_caches_unknown_auth_certs(const or_options_t *options)
@@ -1136,11 +1143,14 @@ directory_caches_unknown_auth_certs(const or_options_t *options)
return dir_server_mode(options) || options->BridgeRelay;
}
-/** Return 1 if we want to keep descriptors, networkstatuses, etc around.
+/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc
* Else return 0.
* Check options->DirPort_set and directory_permits_begindir_requests()
* to see if we are willing to serve these directory documents to others via
* the DirPort and begindir-over-ORPort, respectively.
+ *
+ * To check if we should fetch documents, use we_want_to_fetch_flavor and
+ * we_want_to_fetch_unknown_auth_certs instead of this function.
*/
int
directory_caches_dir_info(const or_options_t *options)
@@ -1155,7 +1165,7 @@ directory_caches_dir_info(const or_options_t *options)
should_refuse_unknown_exits(options);
}
-/** Return 1 if we want to allow remote people to ask us directory
+/** Return 1 if we want to allow remote clients to ask us directory
* requests via the "begin_dir" interface, which doesn't require
* having any separate port open. */
int
@@ -2007,7 +2017,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
vrs->status.guardfraction_percentage);
}
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\n");
if (desc) {
summary = policy_summarize(desc->exit_policy, AF_INET);
@@ -2017,7 +2027,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version,
if (format == NS_V3_VOTE && vrs) {
if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) {
- smartlist_add(chunks, tor_strdup("id ed25519 none\n"));
+ smartlist_add_strdup(chunks, "id ed25519 none\n");
} else {
char ed_b64[BASE64_DIGEST256_LEN+1];
digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id);
@@ -2109,12 +2119,8 @@ get_possible_sybil_list(const smartlist_t *routers)
int addr_count;
/* Allow at most this number of Tor servers on a single IP address, ... */
int max_with_same_addr = options->AuthDirMaxServersPerAddr;
- /* ... unless it's a directory authority, in which case allow more. */
- int max_with_same_addr_on_authority = options->AuthDirMaxServersPerAuthAddr;
if (max_with_same_addr <= 0)
max_with_same_addr = INT_MAX;
- if (max_with_same_addr_on_authority <= 0)
- max_with_same_addr_on_authority = INT_MAX;
smartlist_add_all(routers_by_ip, routers);
smartlist_sort(routers_by_ip, compare_routerinfo_by_ip_and_bw_);
@@ -2127,9 +2133,7 @@ get_possible_sybil_list(const smartlist_t *routers)
last_addr = ri->addr;
addr_count = 1;
} else if (++addr_count > max_with_same_addr) {
- if (!router_addr_is_trusted_dir(ri->addr) ||
- addr_count > max_with_same_addr_on_authority)
- digestmap_set(omit_as_sybil, ri->cache_info.identity_digest, ri);
+ digestmap_set(omit_as_sybil, ri->cache_info.identity_digest, ri);
}
} SMARTLIST_FOREACH_END(ri);
@@ -2289,8 +2293,8 @@ dirserv_set_routerstatus_testing(routerstatus_t *rs)
}
/** Routerstatus <b>rs</b> is part of a group of routers that are on
- * too narrow an IP-space. Clear out its flags: we don't want people
- * using it.
+ * too narrow an IP-space. Clear out its flags since we don't want it be used
+ * because of its Sybil-like appearance.
*
* Leave its BadExit flag alone though, since if we think it's a bad exit,
* we want to vote that way in case all the other authorities are voting
@@ -3030,7 +3034,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
config_line_t *cl;
for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) {
if (validate_recommended_package_line(cl->value))
- smartlist_add(v3_out->package_lines, tor_strdup(cl->value));
+ smartlist_add_strdup(v3_out->package_lines, cl->value);
}
}
@@ -3039,9 +3043,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
"Authority Exit Fast Guard Stable V2Dir Valid HSDir",
0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
if (vote_on_reachability)
- smartlist_add(v3_out->known_flags, tor_strdup("Running"));
+ smartlist_add_strdup(v3_out->known_flags, "Running");
if (listbadexits)
- smartlist_add(v3_out->known_flags, tor_strdup("BadExit"));
+ smartlist_add_strdup(v3_out->known_flags, "BadExit");
smartlist_sort_strings(v3_out->known_flags);
if (options->ConsensusParams) {
@@ -3233,7 +3237,8 @@ dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
void
dirserv_orconn_tls_done(const tor_addr_t *addr,
uint16_t or_port,
- const char *digest_rcvd)
+ const char *digest_rcvd,
+ const ed25519_public_key_t *ed_id_rcvd)
{
node_t *node = NULL;
tor_addr_port_t orport;
@@ -3245,8 +3250,26 @@ dirserv_orconn_tls_done(const tor_addr_t *addr,
node = node_get_mutable_by_id(digest_rcvd);
if (node == NULL || node->ri == NULL)
return;
+
ri = node->ri;
+ if (get_options()->AuthDirTestEd25519LinkKeys &&
+ node_supports_ed25519_link_authentication(node) &&
+ ri->cache_info.signing_key_cert) {
+ /* We allow the node to have an ed25519 key if we haven't been told one in
+ * the routerinfo, but if we *HAVE* been told one in the routerinfo, it
+ * needs to match. */
+ const ed25519_public_key_t *expected_id =
+ &ri->cache_info.signing_key_cert->signing_key;
+ tor_assert(!ed25519_public_key_is_zero(expected_id));
+ if (! ed_id_rcvd || ! ed25519_pubkey_eq(ed_id_rcvd, expected_id)) {
+ log_info(LD_DIRSERV, "Router at %s:%d with RSA ID %s "
+ "did not present expected Ed25519 ID.",
+ fmt_addr(addr), or_port, hex_str(digest_rcvd, DIGEST_LEN));
+ return; /* Don't mark it as reachable. */
+ }
+ }
+
tor_addr_copy(&orport.addr, addr);
orport.port = or_port;
if (router_has_orport(ri, &orport)) {
@@ -3254,7 +3277,7 @@ dirserv_orconn_tls_done(const tor_addr_t *addr,
if (!authdir_mode_bridge(get_options()) ||
ri->purpose == ROUTER_PURPOSE_BRIDGE) {
char addrstr[TOR_ADDR_BUF_LEN];
- /* This is a bridge or we're not a bridge authorititative --
+ /* This is a bridge or we're not a bridge authority --
mark it as reachable. */
log_info(LD_DIRSERV, "Found router %s to be reachable at %s:%d. Yay.",
router_describe(ri),
@@ -3302,21 +3325,31 @@ dirserv_should_launch_reachability_test(const routerinfo_t *ri,
void
dirserv_single_reachability_test(time_t now, routerinfo_t *router)
{
+ const or_options_t *options = get_options();
channel_t *chan = NULL;
- node_t *node = NULL;
+ const node_t *node = NULL;
tor_addr_t router_addr;
+ const ed25519_public_key_t *ed_id_key;
(void) now;
tor_assert(router);
- node = node_get_mutable_by_id(router->cache_info.identity_digest);
+ node = node_get_by_id(router->cache_info.identity_digest);
tor_assert(node);
+ if (options->AuthDirTestEd25519LinkKeys &&
+ node_supports_ed25519_link_authentication(node)) {
+ ed_id_key = &router->cache_info.signing_key_cert->signing_key;
+ } else {
+ ed_id_key = NULL;
+ }
+
/* IPv4. */
log_debug(LD_OR,"Testing reachability of %s at %s:%u.",
router->nickname, fmt_addr32(router->addr), router->or_port);
tor_addr_from_ipv4h(&router_addr, router->addr);
chan = channel_tls_connect(&router_addr, router->or_port,
- router->cache_info.identity_digest);
+ router->cache_info.identity_digest,
+ ed_id_key);
if (chan) command_setup_channel(chan);
/* Possible IPv6. */
@@ -3328,7 +3361,8 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router)
tor_addr_to_str(addrstr, &router->ipv6_addr, sizeof(addrstr), 1),
router->ipv6_orport);
chan = channel_tls_connect(&router->ipv6_addr, router->ipv6_orport,
- router->cache_info.identity_digest);
+ router->cache_info.identity_digest,
+ ed_id_key);
if (chan) command_setup_channel(chan);
}
}
@@ -3677,8 +3711,14 @@ connection_dirserv_add_dir_bytes_to_outbuf(dir_connection_t *conn)
if (bytes < 8192)
bytes = 8192;
remaining = conn->cached_dir->dir_z_len - conn->cached_dir_offset;
- if (bytes > remaining)
+ if (BUG(remaining < 0)) {
+ remaining = 0;
+ }
+ if (bytes > remaining) {
bytes = (ssize_t) remaining;
+ if (BUG(bytes < 0))
+ return -1;
+ }
if (conn->zlib_state) {
connection_write_to_buf_zlib(
@@ -3689,7 +3729,7 @@ connection_dirserv_add_dir_bytes_to_outbuf(dir_connection_t *conn)
bytes, TO_CONN(conn));
}
conn->cached_dir_offset += bytes;
- if (conn->cached_dir_offset == (int)conn->cached_dir->dir_z_len) {
+ if (conn->cached_dir_offset >= (off_t)conn->cached_dir->dir_z_len) {
/* We just wrote the last one; finish up. */
connection_dirserv_finish_spooling(conn);
cached_dir_decref(conn->cached_dir);
diff --git a/src/or/dirserv.h b/src/or/dirserv.h
index 1e4f27e3d7..e83da5e5ac 100644
--- a/src/or/dirserv.h
+++ b/src/or/dirserv.h
@@ -73,7 +73,8 @@ int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
const char **msg);
void dirserv_orconn_tls_done(const tor_addr_t *addr,
uint16_t or_port,
- const char *digest_rcvd);
+ const char *digest_rcvd,
+ const ed25519_public_key_t *ed_id_rcvd);
int dirserv_should_launch_reachability_test(const routerinfo_t *ri,
const routerinfo_t *ri_old);
void dirserv_single_reachability_test(time_t now, routerinfo_t *router);
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index 738ab35bc1..e92d3b49dc 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -26,6 +26,39 @@
/**
* \file dirvote.c
* \brief Functions to compute directory consensus, and schedule voting.
+ *
+ * This module is the center of the consensus-voting based directory
+ * authority system. With this system, a set of authorities first
+ * publish vote based on their opinions of the network, and then compute
+ * a consensus from those votes. Each authority signs the consensus,
+ * and clients trust the consensus if enough known authorities have
+ * signed it.
+ *
+ * The code in this module is only invoked on directory authorities. It's
+ * responsible for:
+ *
+ * <ul>
+ * <li>Generating this authority's vote networkstatus, based on the
+ * authority's view of the network as represented in dirserv.c
+ * <li>Formatting the vote networkstatus objects.
+ * <li>Generating the microdescriptors that correspond to our own
+ * vote.
+ * <li>Sending votes to all the other authorities.
+ * <li>Trying to fetch missing votes from other authorities.
+ * <li>Computing the consensus from a set of votes, as well as
+ * a "detached signature" object for other authorities to fetch.
+ * <li>Collecting other authorities' signatures on the same consensus,
+ * until there are enough.
+ * <li>Publishing the consensus to the reset of the directory system.
+ * <li>Scheduling all of the above operations.
+ * </ul>
+ *
+ * The main entry points are in dirvote_act(), which handles scheduled
+ * actions; and dirvote_add_vote() and dirvote_add_signatures(), which
+ * handle uploaded and downloaded votes and signatures.
+ *
+ * (See dir-spec.txt from torspec.git for a complete specification of
+ * the directory protocol and voting algorithms.)
**/
/** A consensus that we have built and are appending signatures to. Once it's
@@ -250,11 +283,11 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key,
smartlist_add(chunks, rsf);
for (h = vrs->microdesc; h; h = h->next) {
- smartlist_add(chunks, tor_strdup(h->microdesc_hash_line));
+ smartlist_add_strdup(chunks, h->microdesc_hash_line);
}
} SMARTLIST_FOREACH_END(vrs);
- smartlist_add(chunks, tor_strdup("directory-footer\n"));
+ smartlist_add_strdup(chunks, "directory-footer\n");
/* The digest includes everything up through the space after
* directory-signature. (Yuck.) */
@@ -894,7 +927,7 @@ networkstatus_check_weights(int64_t Wgg, int64_t Wgd, int64_t Wmg,
*
* It returns true if weights could be computed, false otherwise.
*/
-static int
+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)
@@ -976,7 +1009,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
Wgd = weight_scale;
}
} else { // Subcase b: R+D >= S
- casename = "Case 2b1 (Wgg=1, Wmd=Wgd)";
+ casename = "Case 2b1 (Wgg=weight_scale, Wmd=Wgd)";
Wee = (weight_scale*(E - G + M))/E;
Wed = (weight_scale*(D - 2*E + 4*G - 2*M))/(3*D);
Wme = (weight_scale*(G-M))/E;
@@ -989,7 +1022,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
weight_scale, G, M, E, D, T, 10, 1);
if (berr) {
- casename = "Case 2b2 (Wgg=1, Wee=1)";
+ casename = "Case 2b2 (Wgg=weight_scale, Wee=weight_scale)";
Wgg = weight_scale;
Wee = weight_scale;
Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
@@ -1058,7 +1091,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
} else { // Subcase b: S+D >= T/3
// D != 0 because S+D >= T/3
if (G < E) {
- casename = "Case 3bg (G scarce, Wgg=1, Wmd == Wed)";
+ casename = "Case 3bg (G scarce, Wgg=weight_scale, Wmd == Wed)";
Wgg = weight_scale;
Wgd = (weight_scale*(D - 2*G + E + M))/(3*D);
Wmg = 0;
@@ -1070,7 +1103,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
Wed, weight_scale, G, M, E, D, T, 10, 1);
} else { // G >= E
- casename = "Case 3be (E scarce, Wee=1, Wmd == Wgd)";
+ casename = "Case 3be (E scarce, Wee=weight_scale, Wmd == Wgd)";
Wee = weight_scale;
Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
Wme = 0;
@@ -1104,7 +1137,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
tor_assert(0 < weight_scale && weight_scale <= INT32_MAX);
/*
- * Provide Wgm=Wgg, Wmm=1, Wem=Wee, Weg=Wed. May later determine
+ * Provide Wgm=Wgg, Wmm=weight_scale, Wem=Wee, Weg=Wed. May later determine
* that middle nodes need different bandwidth weights for dirport traffic,
* or that weird exit policies need special weight, or that bridges
* need special weight.
@@ -1287,7 +1320,17 @@ compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes)
* value in a newly allocated string.
*
* Note: this function DOES NOT check whether the votes are from
- * recognized authorities. (dirvote_add_vote does that.) */
+ * recognized authorities. (dirvote_add_vote does that.)
+ *
+ * <strong>WATCH OUT</strong>: You need to think before you change the
+ * behavior of this function, or of the functions it calls! If some
+ * authorities compute the consensus with a different algorithm than
+ * others, they will not reach the same result, and they will not all
+ * sign the same thing! If you really need to change the algorithm
+ * here, you should allocate a new "consensus_method" for the new
+ * behavior, and make the new behavior conditional on a new-enough
+ * consensus_method.
+ **/
char *
networkstatus_compute_consensus(smartlist_t *votes,
int total_authorities,
@@ -1306,7 +1349,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_t *flags;
const char *flavor_name;
uint32_t max_unmeasured_bw_kb = DEFAULT_MAX_UNMEASURED_BW_KB;
- int64_t G=0, M=0, E=0, D=0, T=0; /* For bandwidth weights */
+ int64_t G, M, E, D, T; /* For bandwidth weights */
const routerstatus_format_type_t rs_format =
flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC;
char *params = NULL;
@@ -1338,6 +1381,16 @@ networkstatus_compute_consensus(smartlist_t *votes,
consensus_method = MAX_SUPPORTED_CONSENSUS_METHOD;
}
+ if (consensus_method >= MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE) {
+ /* It's smarter to initialize these weights to 1, so that later on,
+ * we can't accidentally divide by zero. */
+ G = M = E = D = 1;
+ T = 4;
+ } else {
+ /* ...but originally, they were set to zero. */
+ G = M = E = D = T = 0;
+ }
+
/* Compute medians of time-related things, and figure out how many
* routers we might need to talk about. */
{
@@ -1377,7 +1430,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_free(sv); /* elements get freed later. */
}
SMARTLIST_FOREACH(v->known_flags, const char *, cp,
- smartlist_add(flags, tor_strdup(cp)));
+ smartlist_add_strdup(flags, cp));
} SMARTLIST_FOREACH_END(v);
valid_after = median_time(va_times, n_votes);
fresh_until = median_time(fu_times, n_votes);
@@ -1410,7 +1463,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_free(combined_client_versions);
if (consensus_method >= MIN_METHOD_FOR_ED25519_ID_VOTING)
- smartlist_add(flags, tor_strdup("NoEdConsensus"));
+ smartlist_add_strdup(flags, "NoEdConsensus");
smartlist_sort_strings(flags);
smartlist_uniq_strings(flags);
@@ -1474,9 +1527,9 @@ networkstatus_compute_consensus(smartlist_t *votes,
total_authorities);
if (smartlist_len(param_list)) {
params = smartlist_join_strings(param_list, " ", 0, NULL);
- smartlist_add(chunks, tor_strdup("params "));
+ smartlist_add_strdup(chunks, "params ");
smartlist_add(chunks, params);
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\n");
}
if (consensus_method >= MIN_METHOD_FOR_SHARED_RANDOM) {
@@ -2063,10 +2116,10 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_join_strings(chosen_flags, " ", 0, NULL));
/* Now the version line. */
if (chosen_version) {
- smartlist_add(chunks, tor_strdup("\nv "));
- smartlist_add(chunks, tor_strdup(chosen_version));
+ smartlist_add_strdup(chunks, "\nv ");
+ smartlist_add_strdup(chunks, chosen_version);
}
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\n");
if (chosen_protocol_list &&
consensus_method >= MIN_METHOD_FOR_RS_PROTOCOLS) {
smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list);
@@ -2119,7 +2172,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
}
/* Mark the directory footer region */
- smartlist_add(chunks, tor_strdup("directory-footer\n"));
+ smartlist_add_strdup(chunks, "directory-footer\n");
{
int64_t weight_scale = BW_WEIGHT_SCALE;
@@ -2170,7 +2223,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
const char *algname = crypto_digest_algorithm_get_name(digest_alg);
char *signature;
- smartlist_add(chunks, tor_strdup("directory-signature "));
+ smartlist_add_strdup(chunks, "directory-signature ");
/* Compute the hash of the chunks. */
crypto_digest_smartlist(digest, digest_len, chunks, "", digest_alg);
@@ -2197,7 +2250,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
smartlist_add(chunks, signature);
if (legacy_id_key_digest && legacy_signing_key) {
- smartlist_add(chunks, tor_strdup("directory-signature "));
+ smartlist_add_strdup(chunks, "directory-signature ");
base16_encode(fingerprint, sizeof(fingerprint),
legacy_id_key_digest, DIGEST_LEN);
crypto_pk_get_fingerprint(legacy_signing_key,
@@ -2510,7 +2563,7 @@ networkstatus_format_signatures(networkstatus_t *consensus,
base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len,
BASE64_ENCODE_MULTILINE);
strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
- smartlist_add(elements, tor_strdup(buf));
+ smartlist_add_strdup(elements, buf);
} SMARTLIST_FOREACH_END(sig);
} SMARTLIST_FOREACH_END(v);
@@ -3620,8 +3673,8 @@ dirvote_add_signatures(const char *detached_signatures_body,
"Queuing it for the next consensus.", source);
if (!pending_consensus_signature_list)
pending_consensus_signature_list = smartlist_new();
- smartlist_add(pending_consensus_signature_list,
- tor_strdup(detached_signatures_body));
+ smartlist_add_strdup(pending_consensus_signature_list,
+ detached_signatures_body);
*msg = "Signature queued";
return 0;
}
diff --git a/src/or/dirvote.h b/src/or/dirvote.h
index efd233ef5f..ac7db69db2 100644
--- a/src/or/dirvote.h
+++ b/src/or/dirvote.h
@@ -55,7 +55,7 @@
#define MIN_SUPPORTED_CONSENSUS_METHOD 13
/** The highest consensus method that we currently support. */
-#define MAX_SUPPORTED_CONSENSUS_METHOD 25
+#define MAX_SUPPORTED_CONSENSUS_METHOD 26
/** Lowest consensus method where microdesc consensuses omit any entry
* with no microdesc. */
@@ -111,6 +111,10 @@
* entries. */
#define MIN_METHOD_FOR_RS_PROTOCOLS 25
+/** Lowest consensus method where authorities initialize bandwidth weights to 1
+ * instead of 0. See #14881 */
+#define MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE 26
+
/** Default bandwidth to clip unmeasured bandwidths to using method >=
* MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not
* get confused with the above macros.) */
@@ -234,6 +238,10 @@ STATIC smartlist_t *dirvote_compute_params(smartlist_t *votes, int method,
int total_authorities);
STATIC char *compute_consensus_package_lines(smartlist_t *votes);
STATIC char *make_consensus_method_list(int low, int high, const char *sep);
+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
diff --git a/src/or/dns.c b/src/or/dns.c
index c1e3c3256e..ed20836aed 100644
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@ -1759,7 +1759,7 @@ wildcard_increment_answer(const char *id)
"invalid addresses. Apparently they are hijacking DNS failures. "
"I'll try to correct for this by treating future occurrences of "
"\"%s\" as 'not found'.", id, *ip, id);
- smartlist_add(dns_wildcard_list, tor_strdup(id));
+ smartlist_add_strdup(dns_wildcard_list, id);
}
if (!dns_wildcard_notice_given)
control_event_server_status(LOG_NOTICE, "DNS_HIJACKED");
@@ -1783,7 +1783,7 @@ add_wildcarded_test_address(const char *address)
n_test_addrs = get_options()->ServerDNSTestAddresses ?
smartlist_len(get_options()->ServerDNSTestAddresses) : 0;
- smartlist_add(dns_wildcarded_test_address_list, tor_strdup(address));
+ smartlist_add_strdup(dns_wildcarded_test_address_list, address);
n = smartlist_len(dns_wildcarded_test_address_list);
if (n > n_test_addrs/2) {
tor_log(dns_wildcarded_test_address_notice_given ? LOG_INFO : LOG_NOTICE,
diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c
index f5a4f2ac0f..8768b2a1d1 100644
--- a/src/or/dnsserv.c
+++ b/src/or/dnsserv.c
@@ -3,10 +3,22 @@
/**
* \file dnsserv.c
- * \brief Implements client-side DNS proxy server code. Note:
- * this is the DNS Server code, not the Server DNS code. Confused? This code
- * runs on client-side, and acts as a DNS server. The code in dns.c, on the
- * other hand, runs on Tor servers, and acts as a DNS client.
+ * \brief Implements client-side DNS proxy server code.
+ *
+ * When a user enables the DNSPort configuration option to have their local
+ * Tor client handle DNS requests, this module handles it. It functions as a
+ * "DNS Server" on the client side, which client applications use.
+ *
+ * Inbound DNS requests are represented as entry_connection_t here (since
+ * that's how Tor represents client-side streams), which are kept associated
+ * with an evdns_server_request structure as exposed by Libevent's
+ * evdns code.
+ *
+ * Upon receiving a DNS request, libevent calls our evdns_server_callback()
+ * function here, which causes this module to create an entry_connection_t
+ * request as appropriate. Later, when that request is answered,
+ * connection_edge.c calls dnsserv_resolved() so we can finish up and tell the
+ * DNS client.
**/
#include "or.h"
@@ -272,7 +284,7 @@ dnsserv_reject_request(entry_connection_t *conn)
}
/** Look up the original name that corresponds to 'addr' in req. We use this
- * to preserve case in order to facilitate people using 0x20-hacks to avoid
+ * to preserve case in order to facilitate clients using 0x20-hacks to avoid
* DNS poisoning. */
static const char *
evdns_get_orig_address(const struct evdns_server_request *req,
diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c
index 265b6dcda1..0109da8e01 100644
--- a/src/or/entrynodes.c
+++ b/src/or/entrynodes.c
@@ -10,18 +10,118 @@
*
* Entry nodes can be guards (for general use) or bridges (for censorship
* circumvention).
+ *
+ * In general, we use entry guards to prevent traffic-sampling attacks:
+ * if we chose every circuit independently, an adversary controlling
+ * some fraction of paths on the network would observe a sample of every
+ * user's traffic. Using guards gives users a chance of not being
+ * profiled.
+ *
+ * The current entry guard selection code is designed to try to avoid
+ * _ever_ trying every guard on the network, to try to stick to guards
+ * that we've used before, to handle hostile/broken networks, and
+ * to behave sanely when the network goes up and down.
+ *
+ * Our algorithm works as follows: First, we maintain a SAMPLE of guards
+ * we've seen in the networkstatus consensus. We maintain this sample
+ * over time, and store it persistently; it is chosen without reference
+ * to our configuration or firewall rules. Guards remain in the sample
+ * as they enter and leave the consensus. We expand this sample as
+ * needed, up to a maximum size.
+ *
+ * As a subset of the sample, we maintain a FILTERED SET of the guards
+ * that we would be willing to use if we could connect to them. The
+ * filter removes all the guards that we're excluding because they're
+ * bridges (or not bridges), because we have restrictive firewall rules,
+ * because of ExcludeNodes, because we of path bias restrictions,
+ * because they're absent from the network at present, and so on.
+ *
+ * As a subset of the filtered set, we keep a REACHABLE FILTERED SET
+ * (also called a "usable filtered set") of those guards that we call
+ * "reachable" or "maybe reachable". A guard is reachable if we've
+ * connected to it more recently than we've failed. A guard is "maybe
+ * reachable" if we have never tried to connect to it, or if we
+ * failed to connect to it so long ago that we no longer think our
+ * failure means it's down.
+ *
+ * As a persistent ordered list whose elements are taken from the
+ * sampled set, we track a CONFIRMED GUARDS LIST. A guard becomes
+ * confirmed when we successfully build a circuit through it, and decide
+ * to use that circuit. We order the guards on this list by the order
+ * in which they became confirmed.
+ *
+ * And as a final group, we have an ordered list of PRIMARY GUARDS,
+ * whose elements are taken from the filtered set. We prefer
+ * confirmed guards to non-confirmed guards for this list, and place
+ * other restrictions on it. The primary guards are the ones that we
+ * connect to "when nothing is wrong" -- circuits through them can be used
+ * immediately.
+ *
+ * To build circuits, we take a primary guard if possible -- or a
+ * reachable filtered confirmed guard if no primary guard is possible --
+ * or a random reachable filtered guard otherwise. If the guard is
+ * primary, we can use the circuit immediately on success. Otherwise,
+ * the guard is now "pending" -- we won't use its circuit unless all
+ * of the circuits we're trying to build through better guards have
+ * definitely failed.
+ *
+ * While we're building circuits, we track a little "guard state" for
+ * each circuit. We use this to keep track of whether the circuit is
+ * one that we can use as soon as its done, or whether it's one that
+ * we should keep around to see if we can do better. In the latter case,
+ * a periodic call to entry_guards_upgrade_waiting_circuits() will
+ * eventually upgrade it.
**/
+/* DOCDOC -- expand this.
+ *
+ * Information invariants:
+ *
+ * [x] whenever a guard becomes unreachable, clear its usable_filtered flag.
+ *
+ * [x] Whenever a guard becomes reachable or maybe-reachable, if its filtered
+ * flag is set, set its usable_filtered flag.
+ *
+ * [x] Whenever we get a new consensus, call update_from_consensus(). (LATER.)
+ *
+ * [x] Whenever the configuration changes in a relevant way, update the
+ * filtered/usable flags. (LATER.)
+ *
+ * [x] Whenever we add a guard to the sample, make sure its filtered/usable
+ * flags are set as possible.
+ *
+ * [x] Whenever we remove a guard from the sample, remove it from the primary
+ * and confirmed lists.
+ *
+ * [x] When we make a guard confirmed, update the primary list.
+ *
+ * [x] When we make a guard filtered or unfiltered, update the primary list.
+ *
+ * [x] When we are about to pick a guard, make sure that the primary list is
+ * full.
+ *
+ * [x] Before calling sample_reachable_filtered_entry_guards(), make sure
+ * that the filtered, primary, and confirmed flags are up-to-date.
+ *
+ * [x] Call entry_guard_consider_retry every time we are about to check
+ * is_usable_filtered or is_reachable, and every time we set
+ * is_filtered to 1.
+ *
+ * [x] Call entry_guards_changed_for_guard_selection() whenever we update
+ * a persistent field.
+ */
#define ENTRYNODES_PRIVATE
#include "or.h"
+#include "channel.h"
+#include "bridges.h"
#include "circpathbias.h"
#include "circuitbuild.h"
+#include "circuitlist.h"
#include "circuitstats.h"
#include "config.h"
#include "confparse.h"
#include "connection.h"
-#include "connection_or.h"
#include "control.h"
#include "directory.h"
#include "entrynodes.h"
@@ -37,2509 +137,3360 @@
#include "transports.h"
#include "statefile.h"
-/** Information about a configured bridge. Currently this just matches the
- * ones in the torrc file, but one day we may be able to learn about new
- * bridges on our own, and remember them in the state file. */
-typedef struct {
- /** Address of the bridge. */
- tor_addr_t addr;
- /** TLS port for the bridge. */
- uint16_t port;
- /** Boolean: We are re-parsing our bridge list, and we are going to remove
- * this one if we don't find it in the list of configured bridges. */
- unsigned marked_for_removal : 1;
- /** Expected identity digest, or all zero bytes if we don't know what the
- * digest should be. */
- char identity[DIGEST_LEN];
-
- /** Name of pluggable transport protocol taken from its config line. */
- char *transport_name;
-
- /** When should we next try to fetch a descriptor for this bridge? */
- download_status_t fetch_status;
-
- /** A smartlist of k=v values to be passed to the SOCKS proxy, if
- transports are used for this bridge. */
- smartlist_t *socks_args;
-} bridge_info_t;
-
-/** A list of our chosen entry guards. */
-static smartlist_t *entry_guards = NULL;
-/** A value of 1 means that the entry_guards list has changed
+/** A list of existing guard selection contexts. */
+static smartlist_t *guard_contexts = NULL;
+/** The currently enabled guard selection context. */
+static guard_selection_t *curr_guard_context = NULL;
+
+/** A value of 1 means that at least one context has changed,
* and those changes need to be flushed to disk. */
static int entry_guards_dirty = 0;
-static void bridge_free(bridge_info_t *bridge);
-static const node_t *choose_random_entry_impl(cpath_build_state_t *state,
- int for_directory,
- dirinfo_type_t dirtype,
- int *n_options_out);
-static int num_bridges_usable(void);
-
-/* Default number of entry guards in the case where the NumEntryGuards
- * consensus parameter is not set */
-#define DEFAULT_N_GUARDS 1
-/* Minimum and maximum number of entry guards (in case the NumEntryGuards
- * consensus parameter is set). */
-#define MIN_N_GUARDS 1
-#define MAX_N_GUARDS 10
-
-/** Return the list of entry guards, creating it if necessary. */
-const smartlist_t *
-get_entry_guards(void)
-{
- if (! entry_guards)
- entry_guards = smartlist_new();
- return entry_guards;
-}
-
-/** Check whether the entry guard <b>e</b> is usable, given the directory
- * authorities' opinion about the router (stored in <b>ri</b>) and the user's
- * configuration (in <b>options</b>). Set <b>e</b>->bad_since
- * accordingly. Return true iff the entry guard's status changes.
- *
- * If it's not usable, set *<b>reason</b> to a static string explaining why.
- */
-static int
-entry_guard_set_status(entry_guard_t *e, const node_t *node,
- time_t now, const or_options_t *options,
- const char **reason)
+static void entry_guard_set_filtered_flags(const or_options_t *options,
+ guard_selection_t *gs,
+ entry_guard_t *guard);
+static void pathbias_check_use_success_count(entry_guard_t *guard);
+static void pathbias_check_close_success_count(entry_guard_t *guard);
+static int node_is_possible_guard(const node_t *node);
+static int node_passes_guard_filter(const or_options_t *options,
+ const node_t *node);
+static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs,
+ const uint8_t *rsa_id_digest,
+ const char *nickname,
+ const tor_addr_port_t *bridge_addrport);
+static entry_guard_t *get_sampled_guard_by_bridge_addr(guard_selection_t *gs,
+ const tor_addr_port_t *addrport);
+static int entry_guard_obeys_restriction(const entry_guard_t *guard,
+ const entry_guard_restriction_t *rst);
+
+/** Return 0 if we should apply guardfraction information found in the
+ * consensus. A specific consensus can be specified with the
+ * <b>ns</b> argument, if NULL the most recent one will be picked.*/
+int
+should_apply_guardfraction(const networkstatus_t *ns)
{
- char buf[HEX_DIGEST_LEN+1];
- int changed = 0;
+ /* We need to check the corresponding torrc option and the consensus
+ * parameter if we need to. */
+ const or_options_t *options = get_options();
+
+ /* If UseGuardFraction is 'auto' then check the same-named consensus
+ * parameter. If the consensus parameter is not present, default to
+ * "off". */
+ if (options->UseGuardFraction == -1) {
+ return networkstatus_get_param(ns, "UseGuardFraction",
+ 0, /* default to "off" */
+ 0, 1);
+ }
- *reason = NULL;
+ return options->UseGuardFraction;
+}
- /* Do we want to mark this guard as bad? */
+/** Return true iff we know a descriptor for <b>guard</b> */
+static int
+guard_has_descriptor(const entry_guard_t *guard)
+{
+ const node_t *node = node_get_by_id(guard->identity);
if (!node)
- *reason = "unlisted";
- else if (!node->is_running)
- *reason = "down";
- else if (options->UseBridges && (!node->ri ||
- node->ri->purpose != ROUTER_PURPOSE_BRIDGE))
- *reason = "not a bridge";
- else if (options->UseBridges && !node_is_a_configured_bridge(node))
- *reason = "not a configured bridge";
- else if (!options->UseBridges && !node->is_possible_guard &&
- !routerset_contains_node(options->EntryNodes,node))
- *reason = "not recommended as a guard";
- else if (routerset_contains_node(options->ExcludeNodes, node))
- *reason = "excluded";
- /* We only care about OR connection connectivity for entry guards. */
- else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0))
- *reason = "unreachable by config";
- else if (e->path_bias_disabled)
- *reason = "path-biased";
-
- if (*reason && ! e->bad_since) {
- /* Router is newly bad. */
- base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN);
- log_info(LD_CIRC, "Entry guard %s (%s) is %s: marking as unusable.",
- e->nickname, buf, *reason);
-
- e->bad_since = now;
- control_event_guard(e->nickname, e->identity, "BAD");
- changed = 1;
- } else if (!*reason && e->bad_since) {
- /* There's nothing wrong with the router any more. */
- base16_encode(buf, sizeof(buf), e->identity, DIGEST_LEN);
- log_info(LD_CIRC, "Entry guard %s (%s) is no longer unusable: "
- "marking as ok.", e->nickname, buf);
-
- e->bad_since = 0;
- control_event_guard(e->nickname, e->identity, "GOOD");
- changed = 1;
+ return 0;
+ return node_has_descriptor(node);
+}
+
+/**
+ * Try to determine the correct type for a selection named "name",
+ * if <b>type</b> is GS_TYPE_INFER.
+ */
+STATIC guard_selection_type_t
+guard_selection_infer_type(guard_selection_type_t type,
+ const char *name)
+{
+ if (type == GS_TYPE_INFER) {
+ if (!strcmp(name, "bridges"))
+ type = GS_TYPE_BRIDGE;
+ else if (!strcmp(name, "restricted"))
+ type = GS_TYPE_RESTRICTED;
+ else
+ type = GS_TYPE_NORMAL;
}
+ return type;
+}
- if (node) {
- int is_dir = node_is_dir(node);
- if (options->UseBridges && node_is_a_configured_bridge(node))
- is_dir = 1;
- if (e->is_dir_cache != is_dir) {
- e->is_dir_cache = is_dir;
- changed = 1;
- }
+/**
+ * Allocate and return a new guard_selection_t, with the name <b>name</b>.
+ */
+STATIC guard_selection_t *
+guard_selection_new(const char *name,
+ guard_selection_type_t type)
+{
+ guard_selection_t *gs;
+
+ type = guard_selection_infer_type(type, name);
+
+ gs = tor_malloc_zero(sizeof(*gs));
+ gs->name = tor_strdup(name);
+ gs->type = type;
+ gs->sampled_entry_guards = smartlist_new();
+ gs->confirmed_entry_guards = smartlist_new();
+ gs->primary_entry_guards = smartlist_new();
+
+ return gs;
+}
+
+/**
+ * Return the guard selection called <b>name</b>. If there is none, and
+ * <b>create_if_absent</b> is true, then create and return it. If there
+ * is none, and <b>create_if_absent</b> is false, then return NULL.
+ */
+STATIC guard_selection_t *
+get_guard_selection_by_name(const char *name,
+ guard_selection_type_t type,
+ int create_if_absent)
+{
+ if (!guard_contexts) {
+ guard_contexts = smartlist_new();
}
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ if (!strcmp(gs->name, name))
+ return gs;
+ } SMARTLIST_FOREACH_END(gs);
- return changed;
+ if (! create_if_absent)
+ return NULL;
+
+ log_debug(LD_GUARD, "Creating a guard selection called %s", name);
+ guard_selection_t *new_selection = guard_selection_new(name, type);
+ smartlist_add(guard_contexts, new_selection);
+
+ return new_selection;
}
-/** Return true iff enough time has passed since we last tried to connect
- * to the unreachable guard <b>e</b> that we're willing to try again. */
-STATIC int
-entry_is_time_to_retry(const entry_guard_t *e, time_t now)
+/**
+ * Allocate the first guard context that we're planning to use,
+ * and make it the current context.
+ */
+static void
+create_initial_guard_context(void)
{
- struct guard_retry_period_s {
- time_t period_duration;
- time_t interval_during_period;
- };
+ tor_assert(! curr_guard_context);
+ if (!guard_contexts) {
+ guard_contexts = smartlist_new();
+ }
+ guard_selection_type_t type = GS_TYPE_INFER;
+ const char *name = choose_guard_selection(
+ get_options(),
+ networkstatus_get_live_consensus(approx_time()),
+ NULL,
+ &type);
+ tor_assert(name); // "name" can only be NULL if we had an old name.
+ tor_assert(type != GS_TYPE_INFER);
+ log_notice(LD_GUARD, "Starting with guard context \"%s\"", name);
+ curr_guard_context = get_guard_selection_by_name(name, type, 1);
+}
+
+/** Get current default guard_selection_t, creating it if necessary */
+guard_selection_t *
+get_guard_selection_info(void)
+{
+ if (!curr_guard_context) {
+ create_initial_guard_context();
+ }
- struct guard_retry_period_s periods[] = {
- { 6*60*60, 60*60 }, /* For first 6 hrs., retry hourly; */
- { 3*24*60*60, 4*60*60 }, /* Then retry every 4 hrs. until the
- 3-day mark; */
- { 7*24*60*60, 18*60*60 }, /* After 3 days, retry every 18 hours until
- 1 week mark. */
- { TIME_MAX, 36*60*60 } /* After 1 week, retry every 36 hours. */
- };
+ return curr_guard_context;
+}
- time_t ith_deadline_for_retry;
- time_t unreachable_for;
- unsigned i;
+/** Return a statically allocated human-readable description of <b>guard</b>
+ */
+const char *
+entry_guard_describe(const entry_guard_t *guard)
+{
+ static char buf[256];
+ tor_snprintf(buf, sizeof(buf),
+ "%s ($%s)",
+ strlen(guard->nickname) ? guard->nickname : "[bridge]",
+ hex_str(guard->identity, DIGEST_LEN));
+ return buf;
+}
- if (e->last_attempted < e->unreachable_since)
- return 1;
+/** Return <b>guard</b>'s 20-byte RSA identity digest */
+const char *
+entry_guard_get_rsa_id_digest(const entry_guard_t *guard)
+{
+ return guard->identity;
+}
- unreachable_for = now - e->unreachable_since;
+/** Return the pathbias state associated with <b>guard</b>. */
+guard_pathbias_t *
+entry_guard_get_pathbias_state(entry_guard_t *guard)
+{
+ return &guard->pb;
+}
- for (i = 0; i < ARRAY_LENGTH(periods); i++) {
- if (unreachable_for <= periods[i].period_duration) {
- ith_deadline_for_retry = e->last_attempted +
- periods[i].interval_during_period;
+HANDLE_IMPL(entry_guard, entry_guard_t, ATTR_UNUSED STATIC)
- return (now > ith_deadline_for_retry);
- }
- }
- return 0;
+/** Return an interval betweeen 'now' and 'max_backdate' seconds in the past,
+ * chosen uniformly at random. We use this before recording persistent
+ * dates, so that we aren't leaking exactly when we recorded it.
+ */
+MOCK_IMPL(STATIC time_t,
+randomize_time,(time_t now, time_t max_backdate))
+{
+ tor_assert(max_backdate > 0);
+
+ time_t earliest = now - max_backdate;
+ time_t latest = now;
+ if (earliest <= 0)
+ earliest = 1;
+ if (latest <= earliest)
+ latest = earliest + 1;
+
+ return crypto_rand_time_range(earliest, latest);
}
-/** Return the node corresponding to <b>e</b>, if <b>e</b> is
- * working well enough that we are willing to use it as an entry
- * right now. (Else return NULL.) In particular, it must be
- * - Listed as either up or never yet contacted;
- * - Present in the routerlist;
- * - Listed as 'stable' or 'fast' by the current dirserver consensus,
- * if demanded by <b>need_uptime</b> or <b>need_capacity</b>
- * (unless it's a configured EntryNode);
- * - Allowed by our current ReachableORAddresses config option; and
- * - Currently thought to be reachable by us (unless <b>assume_reachable</b>
- * is true).
- *
- * If the answer is no, set *<b>msg</b> to an explanation of why.
+/**
+ * @name parameters for networkstatus algorithm
*
- * If need_descriptor is true, only return the node if we currently have
- * a descriptor (routerinfo or microdesc) for it.
+ * These parameters are taken from the consensus; some are overrideable in
+ * the torrc.
+ */
+/**@{*/
+/**
+ * We never let our sampled guard set grow larger than this fraction
+ * of the guards on the network.
*/
-STATIC const node_t *
-entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags,
- const char **msg)
+STATIC double
+get_max_sample_threshold(void)
{
- const node_t *node;
- const or_options_t *options = get_options();
- int need_uptime = (flags & ENTRY_NEED_UPTIME) != 0;
- int need_capacity = (flags & ENTRY_NEED_CAPACITY) != 0;
- const int assume_reachable = (flags & ENTRY_ASSUME_REACHABLE) != 0;
- const int need_descriptor = (flags & ENTRY_NEED_DESCRIPTOR) != 0;
-
- tor_assert(msg);
+ int32_t pct =
+ networkstatus_get_param(NULL, "guard-max-sample-threshold-percent",
+ DFLT_MAX_SAMPLE_THRESHOLD_PERCENT,
+ 1, 100);
+ return pct / 100.0;
+}
+/**
+ * We never let our sampled guard set grow larger than this number.
+ */
+STATIC int
+get_max_sample_size_absolute(void)
+{
+ return (int) networkstatus_get_param(NULL, "guard-max-sample-size",
+ DFLT_MAX_SAMPLE_SIZE,
+ 1, INT32_MAX);
+}
+/**
+ * We always try to make our sample contain at least this many guards.
+ */
+STATIC int
+get_min_filtered_sample_size(void)
+{
+ return networkstatus_get_param(NULL, "guard-min-filtered-sample-size",
+ DFLT_MIN_FILTERED_SAMPLE_SIZE,
+ 1, INT32_MAX);
+}
+/**
+ * If a guard is unlisted for this many days in a row, we remove it.
+ */
+STATIC int
+get_remove_unlisted_guards_after_days(void)
+{
+ return networkstatus_get_param(NULL,
+ "guard-remove-unlisted-guards-after-days",
+ DFLT_REMOVE_UNLISTED_GUARDS_AFTER_DAYS,
+ 1, 365*10);
+}
+/**
+ * We remove unconfirmed guards from the sample after this many days,
+ * regardless of whether they are listed or unlisted.
+ */
+STATIC int
+get_guard_lifetime(void)
+{
+ if (get_options()->GuardLifetime >= 86400)
+ return get_options()->GuardLifetime;
+ int32_t days;
+ days = networkstatus_get_param(NULL,
+ "guard-lifetime-days",
+ DFLT_GUARD_LIFETIME_DAYS, 1, 365*10);
+ return days * 86400;
+}
+/**
+ * We remove confirmed guards from the sample if they were sampled
+ * GUARD_LIFETIME_DAYS ago and confirmed this many days ago.
+ */
+STATIC int
+get_guard_confirmed_min_lifetime(void)
+{
+ if (get_options()->GuardLifetime >= 86400)
+ return get_options()->GuardLifetime;
+ int32_t days;
+ days = networkstatus_get_param(NULL, "guard-confirmed-min-lifetime-days",
+ DFLT_GUARD_CONFIRMED_MIN_LIFETIME_DAYS,
+ 1, 365*10);
+ return days * 86400;
+}
+/**
+ * How many guards do we try to keep on our primary guard list?
+ */
+STATIC int
+get_n_primary_guards(void)
+{
+ const int n = get_options()->NumEntryGuards;
+ const int n_dir = get_options()->NumDirectoryGuards;
+ if (n > 5) {
+ return MAX(n_dir, n + n / 2);
+ } else if (n >= 1) {
+ return MAX(n_dir, n * 2);
+ }
- if (e->path_bias_disabled) {
- *msg = "path-biased";
- return NULL;
+ return networkstatus_get_param(NULL,
+ "guard-n-primary-guards",
+ DFLT_N_PRIMARY_GUARDS, 1, INT32_MAX);
+}
+/**
+ * Return the number of the live primary guards we should look at when
+ * making a circuit.
+ */
+STATIC int
+get_n_primary_guards_to_use(guard_usage_t usage)
+{
+ int configured;
+ const char *param_name;
+ int param_default;
+ if (usage == GUARD_USAGE_DIRGUARD) {
+ configured = get_options()->NumDirectoryGuards;
+ param_name = "guard-n-primary-dir-guards-to-use";
+ param_default = DFLT_N_PRIMARY_DIR_GUARDS_TO_USE;
+ } else {
+ configured = get_options()->NumEntryGuards;
+ param_name = "guard-n-primary-guards-to-use";
+ param_default = DFLT_N_PRIMARY_GUARDS_TO_USE;
}
- if (e->bad_since) {
- *msg = "bad";
- return NULL;
+ if (configured >= 1) {
+ return configured;
}
- /* no good if it's unreachable, unless assume_unreachable or can_retry. */
- if (!assume_reachable && !e->can_retry &&
- e->unreachable_since && !entry_is_time_to_retry(e, time(NULL))) {
- *msg = "unreachable";
- return NULL;
+ return networkstatus_get_param(NULL,
+ param_name, param_default, 1, INT32_MAX);
+}
+/**
+ * If we haven't successfully built or used a circuit in this long, then
+ * consider that the internet is probably down.
+ */
+STATIC int
+get_internet_likely_down_interval(void)
+{
+ return networkstatus_get_param(NULL, "guard-internet-likely-down-interval",
+ DFLT_INTERNET_LIKELY_DOWN_INTERVAL,
+ 1, INT32_MAX);
+}
+/**
+ * If we're trying to connect to a nonprimary guard for at least this
+ * many seconds, and we haven't gotten the connection to work, we will treat
+ * lower-priority guards as usable.
+ */
+STATIC int
+get_nonprimary_guard_connect_timeout(void)
+{
+ return networkstatus_get_param(NULL,
+ "guard-nonprimary-guard-connect-timeout",
+ DFLT_NONPRIMARY_GUARD_CONNECT_TIMEOUT,
+ 1, INT32_MAX);
+}
+/**
+ * If a circuit has been sitting around in 'waiting for better guard' state
+ * for at least this long, we'll expire it.
+ */
+STATIC int
+get_nonprimary_guard_idle_timeout(void)
+{
+ return networkstatus_get_param(NULL,
+ "guard-nonprimary-guard-idle-timeout",
+ DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT,
+ 1, INT32_MAX);
+}
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in a restricted setting.
+ */
+STATIC double
+get_meaningful_restriction_threshold(void)
+{
+ int32_t pct = networkstatus_get_param(NULL,
+ "guard-meaningful-restriction-percent",
+ DFLT_MEANINGFUL_RESTRICTION_PERCENT,
+ 1, INT32_MAX);
+ return pct / 100.0;
+}
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in an extremely restricted setting, and should warn.
+ */
+STATIC double
+get_extreme_restriction_threshold(void)
+{
+ int32_t pct = networkstatus_get_param(NULL,
+ "guard-extreme-restriction-percent",
+ DFLT_EXTREME_RESTRICTION_PERCENT,
+ 1, INT32_MAX);
+ return pct / 100.0;
+}
+
+/* Mark <b>guard</b> as maybe reachable again. */
+static void
+mark_guard_maybe_reachable(entry_guard_t *guard)
+{
+ if (guard->is_reachable != GUARD_REACHABLE_NO) {
+ return;
}
- node = node_get_by_id(e->identity);
- if (!node) {
- *msg = "no node info";
- return NULL;
+
+ /* Note that we do not clear failing_since: this guard is now only
+ * _maybe-reachable_. */
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+ if (guard->is_filtered_guard)
+ guard->is_usable_filtered_guard = 1;
+}
+
+/**
+ * Called when the network comes up after having seemed to be down for
+ * a while: Mark the primary guards as maybe-reachable so that we'll
+ * try them again.
+ */
+STATIC void
+mark_primary_guards_maybe_reachable(guard_selection_t *gs)
+{
+ tor_assert(gs);
+
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ mark_guard_maybe_reachable(guard);
+ } SMARTLIST_FOREACH_END(guard);
+}
+
+/* Called when we exhaust all guards in our sampled set: Marks all guards as
+ maybe-reachable so that we 'll try them again. */
+static void
+mark_all_guards_maybe_reachable(guard_selection_t *gs)
+{
+ tor_assert(gs);
+
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ mark_guard_maybe_reachable(guard);
+ } SMARTLIST_FOREACH_END(guard);
+}
+
+/**@}*/
+
+/**
+ * Given our options and our list of nodes, return the name of the
+ * guard selection that we should use. Return NULL for "use the
+ * same selection you were using before.
+ */
+STATIC const char *
+choose_guard_selection(const or_options_t *options,
+ const networkstatus_t *live_ns,
+ const guard_selection_t *old_selection,
+ guard_selection_type_t *type_out)
+{
+ tor_assert(options);
+ tor_assert(type_out);
+
+ if (options->UseBridges) {
+ *type_out = GS_TYPE_BRIDGE;
+ return "bridges";
}
- if (need_descriptor && !node_has_descriptor(node)) {
- *msg = "no descriptor";
- return NULL;
+
+ if (! live_ns) {
+ /* without a networkstatus, we can't tell any more than that. */
+ *type_out = GS_TYPE_NORMAL;
+ return "default";
}
- if (get_options()->UseBridges) {
- if (node_get_purpose(node) != ROUTER_PURPOSE_BRIDGE) {
- *msg = "not a bridge";
- return NULL;
- }
- if (!node_is_a_configured_bridge(node)) {
- *msg = "not a configured bridge";
- return NULL;
- }
- } else { /* !get_options()->UseBridges */
- if (node_get_purpose(node) != ROUTER_PURPOSE_GENERAL) {
- *msg = "not general-purpose";
- return NULL;
+
+ const smartlist_t *nodes = nodelist_get_list();
+ int n_guards = 0, n_passing_filter = 0;
+ SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) {
+ if (node_is_possible_guard(node)) {
+ ++n_guards;
+ if (node_passes_guard_filter(options, node)) {
+ ++n_passing_filter;
+ }
}
+ } SMARTLIST_FOREACH_END(node);
+
+ /* We use separate 'high' and 'low' thresholds here to prevent flapping
+ * back and forth */
+ const int meaningful_threshold_high =
+ (int)(n_guards * get_meaningful_restriction_threshold() * 1.05);
+ const int meaningful_threshold_mid =
+ (int)(n_guards * get_meaningful_restriction_threshold());
+ const int meaningful_threshold_low =
+ (int)(n_guards * get_meaningful_restriction_threshold() * .95);
+ const int extreme_threshold =
+ (int)(n_guards * get_extreme_restriction_threshold());
+
+ /*
+ If we have no previous selection, then we're "restricted" iff we are
+ below the meaningful restriction threshold. That's easy enough.
+
+ But if we _do_ have a previous selection, we make it a little
+ "sticky": we only move from "restricted" to "default" when we find
+ that we're above the threshold plus 5%, and we only move from
+ "default" to "restricted" when we're below the threshold minus 5%.
+ That should prevent us from flapping back and forth if we happen to
+ be hovering very close to the default.
+
+ The extreme threshold is for warning only.
+ */
+
+ static int have_warned_extreme_threshold = 0;
+ if (n_guards &&
+ n_passing_filter < extreme_threshold &&
+ ! have_warned_extreme_threshold) {
+ have_warned_extreme_threshold = 1;
+ const double exclude_frac =
+ (n_guards - n_passing_filter) / (double)n_guards;
+ log_warn(LD_GUARD, "Your configuration excludes %d%% of all possible "
+ "guards. That's likely to make you stand out from the "
+ "rest of the world.", (int)(exclude_frac * 100));
}
- if (routerset_contains_node(options->EntryNodes, node)) {
- /* they asked for it, they get it */
- need_uptime = need_capacity = 0;
- }
- if (node_is_unreliable(node, need_uptime, need_capacity, 0)) {
- *msg = "not fast/stable";
- return NULL;
+
+ /* Easy case: no previous selection. Just check if we are in restricted or
+ normal guard selection. */
+ if (old_selection == NULL) {
+ if (n_passing_filter >= meaningful_threshold_mid) {
+ *type_out = GS_TYPE_NORMAL;
+ return "default";
+ } else {
+ *type_out = GS_TYPE_RESTRICTED;
+ return "restricted";
+ }
}
- if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) {
- *msg = "unreachable by config";
- return NULL;
+
+ /* Trickier case: we do have a previous guard selection context. */
+ tor_assert(old_selection);
+
+ /* Use high and low thresholds to decide guard selection, and if we fall in
+ the middle then keep the current guard selection context. */
+ if (n_passing_filter >= meaningful_threshold_high) {
+ *type_out = GS_TYPE_NORMAL;
+ return "default";
+ } else if (n_passing_filter < meaningful_threshold_low) {
+ *type_out = GS_TYPE_RESTRICTED;
+ return "restricted";
+ } else {
+ /* we are in the middle: maintain previous guard selection */
+ *type_out = old_selection->type;
+ return old_selection->name;
}
- return node;
}
-/** Return the number of entry guards that we think are usable. */
+/**
+ * Check whether we should switch from our current guard selection to a
+ * different one. If so, switch and return 1. Return 0 otherwise.
+ *
+ * On a 1 return, the caller should mark all currently live circuits unusable
+ * for new streams, by calling circuit_mark_all_unused_circs() and
+ * circuit_mark_all_dirty_circs_as_unusable().
+ */
int
-num_live_entry_guards(int for_directory)
+update_guard_selection_choice(const or_options_t *options)
{
- int n = 0;
- const char *msg;
- /* Set the entry node attributes we are interested in. */
- entry_is_live_flags_t entry_flags = ENTRY_NEED_CAPACITY;
- if (!for_directory) {
- entry_flags |= ENTRY_NEED_DESCRIPTOR;
+ if (!curr_guard_context) {
+ create_initial_guard_context();
+ return 1;
}
- if (! entry_guards)
- return 0;
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
- if (for_directory && !entry->is_dir_cache)
- continue;
- if (entry_is_live(entry, entry_flags, &msg))
- ++n;
- } SMARTLIST_FOREACH_END(entry);
- return n;
+ guard_selection_type_t type = GS_TYPE_INFER;
+ const char *new_name = choose_guard_selection(
+ options,
+ networkstatus_get_live_consensus(approx_time()),
+ curr_guard_context,
+ &type);
+ tor_assert(new_name);
+ tor_assert(type != GS_TYPE_INFER);
+
+ const char *cur_name = curr_guard_context->name;
+ if (! strcmp(cur_name, new_name)) {
+ log_debug(LD_GUARD,
+ "Staying with guard context \"%s\" (no change)", new_name);
+ return 0; // No change
+ }
+
+ log_notice(LD_GUARD, "Switching to guard context \"%s\" (was using \"%s\")",
+ new_name, cur_name);
+ guard_selection_t *new_guard_context;
+ new_guard_context = get_guard_selection_by_name(new_name, type, 1);
+ tor_assert(new_guard_context);
+ tor_assert(new_guard_context != curr_guard_context);
+ curr_guard_context = new_guard_context;
+
+ return 1;
}
-/** If <b>digest</b> matches the identity of any node in the
- * entry_guards list, return that node. Else return NULL. */
-entry_guard_t *
-entry_guard_get_by_id_digest(const char *digest)
+/**
+ * Return true iff <b>node</b> has all the flags needed for us to consider it
+ * a possible guard when sampling guards.
+ */
+static int
+node_is_possible_guard(const node_t *node)
{
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
- if (tor_memeq(digest, entry->identity, DIGEST_LEN))
- return entry;
- );
+ /* The "GUARDS" set is all nodes in the nodelist for which this predicate
+ * holds. */
+
+ tor_assert(node);
+ return (node->is_possible_guard &&
+ node->is_stable &&
+ node->is_fast &&
+ node->is_valid &&
+ node_is_dir(node) &&
+ !router_digest_is_me(node->identity));
+}
+
+/**
+ * Return the sampled guard with the RSA identity digest <b>rsa_id</b>, or
+ * NULL if we don't have one. */
+STATIC entry_guard_t *
+get_sampled_guard_with_id(guard_selection_t *gs,
+ const uint8_t *rsa_id)
+{
+ tor_assert(gs);
+ tor_assert(rsa_id);
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (tor_memeq(guard->identity, rsa_id, DIGEST_LEN))
+ return guard;
+ } SMARTLIST_FOREACH_END(guard);
return NULL;
}
-/** Dump a description of our list of entry guards to the log at level
- * <b>severity</b>. */
-static void
-log_entry_guards(int severity)
-{
- smartlist_t *elements = smartlist_new();
- char *s;
-
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e)
- {
- const char *msg = NULL;
- if (entry_is_live(e, ENTRY_NEED_CAPACITY, &msg))
- smartlist_add_asprintf(elements, "%s [%s] (up %s)",
- e->nickname,
- hex_str(e->identity, DIGEST_LEN),
- e->made_contact ? "made-contact" : "never-contacted");
- else
- smartlist_add_asprintf(elements, "%s [%s] (%s, %s)",
- e->nickname,
- hex_str(e->identity, DIGEST_LEN),
- msg,
- e->made_contact ? "made-contact" : "never-contacted");
- }
- SMARTLIST_FOREACH_END(e);
+/** If <b>gs</b> contains a sampled entry guard matching <b>bridge</b>,
+ * return that guard. Otherwise return NULL. */
+static entry_guard_t *
+get_sampled_guard_for_bridge(guard_selection_t *gs,
+ const bridge_info_t *bridge)
+{
+ const uint8_t *id = bridge_get_rsa_id_digest(bridge);
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
+ entry_guard_t *guard;
+ if (BUG(!addrport))
+ return NULL; // LCOV_EXCL_LINE
+ guard = get_sampled_guard_by_bridge_addr(gs, addrport);
+ if (! guard || (id && tor_memneq(id, guard->identity, DIGEST_LEN)))
+ return NULL;
+ else
+ return guard;
+}
- s = smartlist_join_strings(elements, ",", 0, NULL);
- SMARTLIST_FOREACH(elements, char*, cp, tor_free(cp));
- smartlist_free(elements);
- log_fn(severity,LD_CIRC,"%s",s);
- tor_free(s);
+/** If we know a bridge_info_t matching <b>guard</b>, return that
+ * bridge. Otherwise return NULL. */
+static bridge_info_t *
+get_bridge_info_for_guard(const entry_guard_t *guard)
+{
+ const uint8_t *identity = NULL;
+ if (! tor_digest_is_zero(guard->identity)) {
+ identity = (const uint8_t *)guard->identity;
+ }
+ if (BUG(guard->bridge_addr == NULL))
+ return NULL;
+
+ return get_configured_bridge_by_exact_addr_port_digest(
+ &guard->bridge_addr->addr,
+ guard->bridge_addr->port,
+ (const char*)identity);
}
-/** Called when one or more guards that we would previously have used for some
- * purpose are no longer in use because a higher-priority guard has become
- * usable again. */
-static void
-control_event_guard_deferred(void)
-{
- /* XXXX We don't actually have a good way to figure out _how many_ entries
- * are live for some purpose. We need an entry_is_even_slightly_live()
- * function for this to work right. NumEntryGuards isn't reliable: if we
- * need guards with weird properties, we can have more than that number
- * live.
- **/
-#if 0
- int n = 0;
- const char *msg;
- const or_options_t *options = get_options();
- if (!entry_guards)
- return;
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry,
- {
- if (entry_is_live(entry, 0, 1, 0, &msg)) {
- if (n++ == options->NumEntryGuards) {
- control_event_guard(entry->nickname, entry->identity, "DEFERRED");
- return;
- }
- }
- });
-#endif
+/**
+ * Return true iff we have a sampled guard with the RSA identity digest
+ * <b>rsa_id</b>. */
+static inline int
+have_sampled_guard_with_id(guard_selection_t *gs, const uint8_t *rsa_id)
+{
+ return get_sampled_guard_with_id(gs, rsa_id) != NULL;
}
-/** Largest amount that we'll backdate chosen_on_date */
-#define CHOSEN_ON_DATE_SLOP (30*86400)
+/**
+ * Allocate a new entry_guard_t object for <b>node</b>, add it to the
+ * sampled entry guards in <b>gs</b>, and return it. <b>node</b> must
+ * not currently be a sampled guard in <b>gs</b>.
+ */
+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.",
+ node_describe(node));
-/** Add a new (preferably stable and fast) router to our
- * entry_guards list. Return a pointer to the router if we succeed,
- * or NULL if we can't find any more suitable entries.
- *
- * If <b>chosen</b> is defined, use that one, and if it's not
- * already in our entry_guards list, put it at the *beginning*.
- * Else, put the one we pick at the end of the list. */
-STATIC const node_t *
-add_an_entry_guard(const node_t *chosen, int reset_status, int prepend,
- int for_discovery, int for_directory)
+ /* make sure that the guard is not already sampled. */
+ if (BUG(have_sampled_guard_with_id(gs, (const uint8_t*)node->identity)))
+ return NULL; // LCOV_EXCL_LINE
+
+ return entry_guard_add_to_sample_impl(gs,
+ (const uint8_t*)node->identity,
+ node_get_nickname(node),
+ NULL);
+}
+
+/**
+ * Backend: adds a new sampled guard to <b>gs</b>, with given identity,
+ * nickname, and ORPort. rsa_id_digest and bridge_addrport are optional, but
+ * we need one of them. nickname is optional. The caller is responsible for
+ * maintaining the size limit of the SAMPLED_GUARDS set.
+ */
+static entry_guard_t *
+entry_guard_add_to_sample_impl(guard_selection_t *gs,
+ const uint8_t *rsa_id_digest,
+ const char *nickname,
+ const tor_addr_port_t *bridge_addrport)
+{
+ const int GUARD_LIFETIME = get_guard_lifetime();
+ tor_assert(gs);
+
+ // XXXX #20827 take ed25519 identity here too.
+
+ /* Make sure we can actually identify the guard. */
+ if (BUG(!rsa_id_digest && !bridge_addrport))
+ return NULL; // LCOV_EXCL_LINE
+
+ entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t));
+
+ /* persistent fields */
+ guard->is_persistent = (rsa_id_digest != NULL);
+ guard->selection_name = tor_strdup(gs->name);
+ if (rsa_id_digest)
+ memcpy(guard->identity, rsa_id_digest, DIGEST_LEN);
+ if (nickname)
+ strlcpy(guard->nickname, nickname, sizeof(guard->nickname));
+ guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10);
+ tor_free(guard->sampled_by_version);
+ guard->sampled_by_version = tor_strdup(VERSION);
+ guard->currently_listed = 1;
+ guard->confirmed_idx = -1;
+
+ /* non-persistent fields */
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+ if (bridge_addrport)
+ guard->bridge_addr = tor_memdup(bridge_addrport, sizeof(*bridge_addrport));
+
+ smartlist_add(gs->sampled_entry_guards, guard);
+ guard->in_selection = gs;
+ entry_guard_set_filtered_flags(get_options(), gs, guard);
+ entry_guards_changed_for_guard_selection(gs);
+ return guard;
+}
+
+/**
+ * Add an entry guard to the "bridges" guard selection sample, with
+ * information taken from <b>bridge</b>. Return that entry guard.
+ */
+static entry_guard_t *
+entry_guard_add_bridge_to_sample(guard_selection_t *gs,
+ const bridge_info_t *bridge)
{
- const node_t *node;
- entry_guard_t *entry;
-
- if (chosen) {
- node = chosen;
- entry = entry_guard_get_by_id_digest(node->identity);
- if (entry) {
- if (reset_status) {
- entry->bad_since = 0;
- entry->can_retry = 1;
- }
- entry->is_dir_cache = node_is_dir(node);
- if (get_options()->UseBridges && node_is_a_configured_bridge(node))
- entry->is_dir_cache = 1;
+ const uint8_t *id_digest = bridge_get_rsa_id_digest(bridge);
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
- return NULL;
- }
- } else if (!for_directory) {
- node = choose_good_entry_server(CIRCUIT_PURPOSE_C_GENERAL, NULL);
- if (!node)
- return NULL;
- } else {
- const routerstatus_t *rs;
- rs = router_pick_directory_server(MICRODESC_DIRINFO|V3_DIRINFO,
- PDS_FOR_GUARD);
- if (!rs)
- return NULL;
- node = node_get_by_id(rs->identity_digest);
- if (!node)
- return NULL;
- }
- if (node->using_as_guard)
+ tor_assert(addrport);
+
+ /* make sure that the guard is not already sampled. */
+ if (BUG(get_sampled_guard_for_bridge(gs, bridge)))
+ return NULL; // LCOV_EXCL_LINE
+
+ return entry_guard_add_to_sample_impl(gs, id_digest, NULL, addrport);
+}
+
+/**
+ * Return the entry_guard_t in <b>gs</b> whose address is <b>addrport</b>,
+ * or NULL if none exists.
+*/
+static entry_guard_t *
+get_sampled_guard_by_bridge_addr(guard_selection_t *gs,
+ const tor_addr_port_t *addrport)
+{
+ if (! gs)
return NULL;
- if (entry_guard_get_by_id_digest(node->identity) != NULL) {
- log_info(LD_CIRC, "I was about to add a duplicate entry guard.");
- /* This can happen if we choose a guard, then the node goes away, then
- * comes back. */
- ((node_t*) node)->using_as_guard = 1;
+ if (BUG(!addrport))
return NULL;
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, g) {
+ if (g->bridge_addr && tor_addr_port_eq(addrport, g->bridge_addr))
+ return g;
+ } SMARTLIST_FOREACH_END(g);
+ return NULL;
+}
+
+/** Update the guard subsystem's knowledge of the identity of the bridge
+ * at <b>addrport</b>. Idempotent.
+ */
+void
+entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport,
+ const uint8_t *rsa_id_digest)
+{
+ guard_selection_t *gs = get_guard_selection_by_name("bridges",
+ GS_TYPE_BRIDGE,
+ 0);
+ if (!gs)
+ return;
+
+ entry_guard_t *g = get_sampled_guard_by_bridge_addr(gs, addrport);
+ if (!g)
+ return;
+
+ int make_persistent = 0;
+
+ if (tor_digest_is_zero(g->identity)) {
+ memcpy(g->identity, rsa_id_digest, DIGEST_LEN);
+ make_persistent = 1;
+ } else if (tor_memeq(g->identity, rsa_id_digest, DIGEST_LEN)) {
+ /* Nothing to see here; we learned something we already knew. */
+ if (BUG(! g->is_persistent))
+ make_persistent = 1;
+ } else {
+ char old_id[HEX_DIGEST_LEN+1];
+ base16_encode(old_id, sizeof(old_id), g->identity, sizeof(g->identity));
+ log_warn(LD_BUG, "We 'learned' an identity %s for a bridge at %s:%d, but "
+ "we already knew a different one (%s). Ignoring the new info as "
+ "possibly bogus.",
+ hex_str((const char *)rsa_id_digest, DIGEST_LEN),
+ fmt_and_decorate_addr(&addrport->addr), addrport->port,
+ old_id);
+ return; // redundant, but let's be clear: we're not making this persistent.
}
- entry = tor_malloc_zero(sizeof(entry_guard_t));
- log_info(LD_CIRC, "Chose %s as new entry guard.",
- node_describe(node));
- strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname));
- memcpy(entry->identity, node->identity, DIGEST_LEN);
- entry->is_dir_cache = node_is_dir(node);
- if (get_options()->UseBridges && node_is_a_configured_bridge(node))
- entry->is_dir_cache = 1;
-
- /* Choose expiry time smudged over the past month. The goal here
- * is to a) spread out when Tor clients rotate their guards, so they
- * don't all select them on the same day, and b) avoid leaving a
- * precise timestamp in the state file about when we first picked
- * this guard. For details, see the Jan 2010 or-dev thread. */
- time_t now = time(NULL);
- entry->chosen_on_date = crypto_rand_time_range(now - 3600*24*30, now);
- entry->chosen_by_version = tor_strdup(VERSION);
-
- /* Are we picking this guard because all of our current guards are
- * down so we need another one (for_discovery is 1), or because we
- * decided we need more variety in our guard list (for_discovery is 0)?
- *
- * Currently we hack this behavior into place by setting "made_contact"
- * for guards of the latter variety, so we'll be willing to use any of
- * them right off the bat.
- */
- if (!for_discovery)
- entry->made_contact = 1;
- ((node_t*)node)->using_as_guard = 1;
- if (prepend)
- smartlist_insert(entry_guards, 0, entry);
- else
- smartlist_add(entry_guards, entry);
- control_event_guard(entry->nickname, entry->identity, "NEW");
- control_event_guard_deferred();
- log_entry_guards(LOG_INFO);
- return node;
+ if (make_persistent) {
+ g->is_persistent = 1;
+ entry_guards_changed_for_guard_selection(gs);
+ }
}
-/** Choose how many entry guards or directory guards we'll use. If
- * <b>for_directory</b> is true, we return how many directory guards to
- * use; else we return how many entry guards to use. */
+/**
+ * Return the number of sampled guards in <b>gs</b> that are "filtered"
+ * (that is, we're willing to connect to them) and that are "usable"
+ * (that is, either "reachable" or "maybe reachable").
+ *
+ * If a restriction is provided in <b>rst</b>, do not count any guards that
+ * violate it.
+ */
STATIC int
-decide_num_guards(const or_options_t *options, int for_directory)
+num_reachable_filtered_guards(guard_selection_t *gs,
+ const entry_guard_restriction_t *rst)
{
- if (for_directory) {
- int answer;
- if (options->NumDirectoryGuards != 0)
- return options->NumDirectoryGuards;
- answer = networkstatus_get_param(NULL, "NumDirectoryGuards", 0, 0, 10);
- if (answer) /* non-zero means use the consensus value */
- return answer;
- }
-
- if (options->NumEntryGuards)
- return options->NumEntryGuards;
+ int n_reachable_filtered_guards = 0;
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (! entry_guard_obeys_restriction(guard, rst))
+ continue;
+ if (guard->is_usable_filtered_guard)
+ ++n_reachable_filtered_guards;
+ } SMARTLIST_FOREACH_END(guard);
+ return n_reachable_filtered_guards;
+}
- /* Use the value from the consensus, or 3 if no guidance. */
- return networkstatus_get_param(NULL, "NumEntryGuards", DEFAULT_N_GUARDS,
- MIN_N_GUARDS, MAX_N_GUARDS);
+/** Return the actual maximum size for the sample in <b>gs</b>,
+ * given that we know about <b>n_guards</b> total. */
+static int
+get_max_sample_size(guard_selection_t *gs,
+ int n_guards)
+{
+ const int using_bridges = (gs->type == GS_TYPE_BRIDGE);
+ const int min_sample = get_min_filtered_sample_size();
+
+ /* If we are in bridge mode, expand our sample set as needed without worrying
+ * about max size. We should respect the user's wishes to use many bridges if
+ * that's what they have specified in their configuration file. */
+ if (using_bridges)
+ return INT_MAX;
+
+ const int max_sample_by_pct = (int)(n_guards * get_max_sample_threshold());
+ const int max_sample_absolute = get_max_sample_size_absolute();
+ const int max_sample = MIN(max_sample_by_pct, max_sample_absolute);
+ if (max_sample < min_sample)
+ return min_sample;
+ else
+ return max_sample;
}
-/** If the use of entry guards is configured, choose more entry guards
- * until we have enough in the list. */
-static void
-pick_entry_guards(const or_options_t *options, int for_directory)
-{
- int changed = 0;
- const int num_needed = decide_num_guards(options, for_directory);
+/**
+ * Return a smartlist of the all the guards that are not currently
+ * members of the sample (GUARDS - SAMPLED_GUARDS). The elements of
+ * this list are node_t pointers in the non-bridge case, and
+ * bridge_info_t pointers in the bridge case. Set *<b>n_guards_out/b>
+ * to the number of guards that we found in GUARDS, including those
+ * that were already sampled.
+ */
+static smartlist_t *
+get_eligible_guards(const or_options_t *options,
+ guard_selection_t *gs,
+ int *n_guards_out)
+{
+ /* Construct eligible_guards as GUARDS - SAMPLED_GUARDS */
+ smartlist_t *eligible_guards = smartlist_new();
+ int n_guards = 0; // total size of "GUARDS"
+
+ if (gs->type == GS_TYPE_BRIDGE) {
+ const smartlist_t *bridges = bridge_list_get();
+ SMARTLIST_FOREACH_BEGIN(bridges, bridge_info_t *, bridge) {
+ ++n_guards;
+ if (NULL != get_sampled_guard_for_bridge(gs, bridge)) {
+ continue;
+ }
+ smartlist_add(eligible_guards, bridge);
+ } SMARTLIST_FOREACH_END(bridge);
+ } else {
+ const smartlist_t *nodes = nodelist_get_list();
+ const int n_sampled = smartlist_len(gs->sampled_entry_guards);
+
+ /* Build a bloom filter of our current guards: let's keep this O(N). */
+ digestset_t *sampled_guard_ids = digestset_new(n_sampled);
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, const entry_guard_t *,
+ guard) {
+ digestset_add(sampled_guard_ids, guard->identity);
+ } SMARTLIST_FOREACH_END(guard);
+
+ SMARTLIST_FOREACH_BEGIN(nodes, const node_t *, node) {
+ if (! node_is_possible_guard(node))
+ continue;
+ if (gs->type == GS_TYPE_RESTRICTED) {
+ /* In restricted mode, we apply the filter BEFORE sampling, so
+ * that we are sampling from the nodes that we might actually
+ * select. If we sampled first, we might wind up with a sample
+ * that didn't include any EntryNodes at all. */
+ if (! node_passes_guard_filter(options, node))
+ continue;
+ }
+ ++n_guards;
+ if (digestset_contains(sampled_guard_ids, node->identity))
+ continue;
+ smartlist_add(eligible_guards, (node_t*)node);
+ } SMARTLIST_FOREACH_END(node);
- tor_assert(entry_guards);
+ /* Now we can free that bloom filter. */
+ digestset_free(sampled_guard_ids);
+ }
- while (num_live_entry_guards(for_directory) < num_needed) {
- if (!add_an_entry_guard(NULL, 0, 0, 0, for_directory))
- break;
- changed = 1;
+ *n_guards_out = n_guards;
+ return eligible_guards;
+}
+
+/** Helper: given a smartlist of either bridge_info_t (if gs->type is
+ * GS_TYPE_BRIDGE) or node_t (otherwise), pick one that can be a guard,
+ * add it as a guard, remove it from the list, and return a new
+ * entry_guard_t. Return NULL on failure. */
+static entry_guard_t *
+select_and_add_guard_item_for_sample(guard_selection_t *gs,
+ smartlist_t *eligible_guards)
+{
+ entry_guard_t *added_guard;
+ if (gs->type == GS_TYPE_BRIDGE) {
+ const bridge_info_t *bridge = smartlist_choose(eligible_guards);
+ if (BUG(!bridge))
+ return NULL; // LCOV_EXCL_LINE
+ smartlist_remove(eligible_guards, bridge);
+ added_guard = entry_guard_add_bridge_to_sample(gs, bridge);
+ } else {
+ const node_t *node =
+ node_sl_choose_by_bandwidth(eligible_guards, WEIGHT_FOR_GUARD);
+ if (BUG(!node))
+ return NULL; // LCOV_EXCL_LINE
+ smartlist_remove(eligible_guards, node);
+ added_guard = entry_guard_add_to_sample(gs, node);
}
- if (changed)
- entry_guards_changed();
-}
-/** How long (in seconds) do we allow an entry guard to be nonfunctional,
- * unlisted, excluded, or otherwise nonusable before we give up on it? */
-#define ENTRY_GUARD_REMOVE_AFTER (30*24*60*60)
+ return added_guard;
+}
-/** Release all storage held by <b>e</b>. */
-static void
-entry_guard_free(entry_guard_t *e)
+/** Return true iff we need a consensus to maintain our */
+static int
+live_consensus_is_missing(const guard_selection_t *gs)
{
- if (!e)
- return;
- tor_free(e->chosen_by_version);
- tor_free(e);
+ tor_assert(gs);
+ if (gs->type == GS_TYPE_BRIDGE) {
+ /* We don't update bridges from the consensus; they aren't there. */
+ return 0;
+ }
+ return networkstatus_get_live_consensus(approx_time()) == NULL;
}
/**
- * Return the minimum lifetime of working entry guard, in seconds,
- * as given in the consensus networkstatus. (Plus CHOSEN_ON_DATE_SLOP,
- * so that we can do the chosen_on_date randomization while achieving the
- * desired minimum lifetime.)
+ * Add new guards to the sampled guards in <b>gs</b> until there are
+ * enough usable filtered guards, but never grow the sample beyond its
+ * maximum size. Return the last guard added, or NULL if none were
+ * added.
*/
-static int32_t
-guards_get_lifetime(void)
+STATIC entry_guard_t *
+entry_guards_expand_sample(guard_selection_t *gs)
{
+ tor_assert(gs);
const or_options_t *options = get_options();
-#define DFLT_GUARD_LIFETIME (86400 * 60) /* Two months. */
-#define MIN_GUARD_LIFETIME (86400 * 30) /* One months. */
-#define MAX_GUARD_LIFETIME (86400 * 1826) /* Five years. */
- if (options->GuardLifetime >= 1) {
- return CLAMP(MIN_GUARD_LIFETIME,
- options->GuardLifetime,
- MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP;
+ if (live_consensus_is_missing(gs)) {
+ log_info(LD_GUARD, "Not expanding the sample guard set; we have "
+ "no live consensus.");
+ return NULL;
}
- return networkstatus_get_param(NULL, "GuardLifetime",
- DFLT_GUARD_LIFETIME,
- MIN_GUARD_LIFETIME,
- MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP;
-}
-
-/** Remove any entry guard which was selected by an unknown version of Tor,
- * or which was selected by a version of Tor that's known to select
- * entry guards badly, or which was selected more 2 months ago. */
-/* XXXX The "obsolete guards" and "chosen long ago guards" things should
- * probably be different functions. */
-static int
-remove_obsolete_entry_guards(time_t now)
-{
- int changed = 0, i;
- int32_t guard_lifetime = guards_get_lifetime();
-
- for (i = 0; i < smartlist_len(entry_guards); ++i) {
- entry_guard_t *entry = smartlist_get(entry_guards, i);
- const char *ver = entry->chosen_by_version;
- const char *msg = NULL;
- tor_version_t v;
- int version_is_bad = 0, date_is_bad = 0;
- if (!ver) {
- msg = "does not say what version of Tor it was selected by";
- version_is_bad = 1;
- } else if (tor_version_parse(ver, &v)) {
- msg = "does not seem to be from any recognized version of Tor";
- version_is_bad = 1;
- }
- if (!version_is_bad && entry->chosen_on_date + guard_lifetime < now) {
- /* It's been too long since the date listed in our state file. */
- msg = "was selected several months ago";
- date_is_bad = 1;
+ int n_sampled = smartlist_len(gs->sampled_entry_guards);
+ entry_guard_t *added_guard = NULL;
+ int n_usable_filtered_guards = num_reachable_filtered_guards(gs, NULL);
+ int n_guards = 0;
+ smartlist_t *eligible_guards = get_eligible_guards(options, gs, &n_guards);
+
+ const int max_sample = get_max_sample_size(gs, n_guards);
+ const int min_filtered_sample = get_min_filtered_sample_size();
+
+ log_info(LD_GUARD, "Expanding the sample guard set. We have %d guards "
+ "in the sample, and %d eligible guards to extend it with.",
+ n_sampled, smartlist_len(eligible_guards));
+
+ while (n_usable_filtered_guards < min_filtered_sample) {
+ /* Has our sample grown too large to expand? */
+ if (n_sampled >= max_sample) {
+ log_info(LD_GUARD, "Not expanding the guard sample any further; "
+ "just hit the maximum sample threshold of %d",
+ max_sample);
+ goto done;
}
- if (version_is_bad || date_is_bad) { /* we need to drop it */
- char dbuf[HEX_DIGEST_LEN+1];
- tor_assert(msg);
- base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN);
- log_fn(version_is_bad ? LOG_NOTICE : LOG_INFO, LD_CIRC,
- "Entry guard '%s' (%s) %s. (Version=%s.) Replacing it.",
- entry->nickname, dbuf, msg, ver?escaped(ver):"none");
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del_keeporder(entry_guards, i--);
- log_entry_guards(LOG_INFO);
- changed = 1;
+ /* Did we run out of guards? */
+ if (smartlist_len(eligible_guards) == 0) {
+ /* LCOV_EXCL_START
+ As long as MAX_SAMPLE_THRESHOLD makes can't be adjusted to
+ allow all guards to be sampled, this can't be reached.
+ */
+ log_info(LD_GUARD, "Not expanding the guard sample any further; "
+ "just ran out of eligible guards");
+ goto done;
+ /* LCOV_EXCL_STOP */
}
+
+ /* Otherwise we can add at least one new guard. */
+ added_guard = select_and_add_guard_item_for_sample(gs, eligible_guards);
+ if (!added_guard)
+ goto done; // LCOV_EXCL_LINE -- only fails on BUG.
+
+ ++n_sampled;
+
+ if (added_guard->is_usable_filtered_guard)
+ ++n_usable_filtered_guards;
}
- return changed ? 1 : 0;
+ done:
+ smartlist_free(eligible_guards);
+ return added_guard;
}
-/** Remove all entry guards that have been down or unlisted for so
- * long that we don't think they'll come up again. Return 1 if we
- * removed any, or 0 if we did nothing. */
-static int
-remove_dead_entry_guards(time_t now)
-{
- char dbuf[HEX_DIGEST_LEN+1];
- char tbuf[ISO_TIME_LEN+1];
- int i;
- int changed = 0;
-
- for (i = 0; i < smartlist_len(entry_guards); ) {
- entry_guard_t *entry = smartlist_get(entry_guards, i);
- if (entry->bad_since &&
- ! entry->path_bias_disabled &&
- entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) {
-
- base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN);
- format_local_iso_time(tbuf, entry->bad_since);
- log_info(LD_CIRC, "Entry guard '%s' (%s) has been down or unlisted "
- "since %s local time; removing.",
- entry->nickname, dbuf, tbuf);
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del_keeporder(entry_guards, i);
- log_entry_guards(LOG_INFO);
- changed = 1;
- } else
- ++i;
- }
- return changed ? 1 : 0;
-}
-
-/** Remove all currently listed entry guards. So new ones will be chosen. */
-void
-remove_all_entry_guards(void)
+/**
+ * Helper: <b>guard</b> has just been removed from the sampled guards:
+ * also remove it from primary and confirmed. */
+static void
+remove_guard_from_confirmed_and_primary_lists(guard_selection_t *gs,
+ entry_guard_t *guard)
{
- char dbuf[HEX_DIGEST_LEN+1];
+ if (guard->is_primary) {
+ guard->is_primary = 0;
+ smartlist_remove_keeporder(gs->primary_entry_guards, guard);
+ } else {
+ if (BUG(smartlist_contains(gs->primary_entry_guards, guard))) {
+ smartlist_remove_keeporder(gs->primary_entry_guards, guard);
+ }
+ }
- while (smartlist_len(entry_guards)) {
- entry_guard_t *entry = smartlist_get(entry_guards, 0);
- base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN);
- log_info(LD_CIRC, "Entry guard '%s' (%s) has been dropped.",
- entry->nickname, dbuf);
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del(entry_guards, 0);
+ if (guard->confirmed_idx >= 0) {
+ smartlist_remove_keeporder(gs->confirmed_entry_guards, guard);
+ guard->confirmed_idx = -1;
+ guard->confirmed_on_date = 0;
+ } else {
+ if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard))) {
+ // LCOV_EXCL_START
+ smartlist_remove_keeporder(gs->confirmed_entry_guards, guard);
+ // LCOV_EXCL_STOP
+ }
}
- log_entry_guards(LOG_INFO);
- entry_guards_changed();
}
-/** A new directory or router-status has arrived; update the down/listed
- * status of the entry guards.
- *
- * An entry is 'down' if the directory lists it as nonrunning.
- * An entry is 'unlisted' if the directory doesn't include it.
- *
- * Don't call this on startup; only on a fresh download. Otherwise we'll
- * think that things are unlisted.
- */
-void
-entry_guards_compute_status(const or_options_t *options, time_t now)
+/** Return true iff <b>guard</b> is currently "listed" -- that is, it
+ * appears in the consensus, or as a configured bridge (as
+ * appropriate) */
+MOCK_IMPL(STATIC int,
+entry_guard_is_listed,(guard_selection_t *gs, const entry_guard_t *guard))
{
- int changed = 0;
- digestmap_t *reasons;
+ if (gs->type == GS_TYPE_BRIDGE) {
+ return NULL != get_bridge_info_for_guard(guard);
+ } else {
+ const node_t *node = node_get_by_id(guard->identity);
+
+ return node && node_is_possible_guard(node);
+ }
+}
- if (! entry_guards)
+/**
+ * Update the status of all sampled guards based on the arrival of a
+ * new consensus networkstatus document. This will include marking
+ * some guards as listed or unlisted, and removing expired guards. */
+STATIC void
+sampled_guards_update_from_consensus(guard_selection_t *gs)
+{
+ tor_assert(gs);
+ const int REMOVE_UNLISTED_GUARDS_AFTER =
+ (get_remove_unlisted_guards_after_days() * 86400);
+ const int unlisted_since_slop = REMOVE_UNLISTED_GUARDS_AFTER / 5;
+
+ // It's important to use only a live consensus here; we don't want to
+ // make changes based on anything expired or old.
+ if (live_consensus_is_missing(gs)) {
+ log_info(LD_GUARD, "Not updating the sample guard set; we have "
+ "no live consensus.");
return;
+ }
+ log_info(LD_GUARD, "Updating sampled guard status based on received "
+ "consensus.");
+
+ int n_changes = 0;
+
+ /* First: Update listed/unlisted. */
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ /* XXXX #20827 check ed ID too */
+ const int is_listed = entry_guard_is_listed(gs, guard);
+
+ if (is_listed && ! guard->currently_listed) {
+ ++n_changes;
+ guard->currently_listed = 1;
+ guard->unlisted_since_date = 0;
+ log_info(LD_GUARD, "Sampled guard %s is now listed again.",
+ entry_guard_describe(guard));
+ } else if (!is_listed && guard->currently_listed) {
+ ++n_changes;
+ guard->currently_listed = 0;
+ guard->unlisted_since_date = randomize_time(approx_time(),
+ unlisted_since_slop);
+ log_info(LD_GUARD, "Sampled guard %s is now unlisted.",
+ entry_guard_describe(guard));
+ } else if (is_listed && guard->currently_listed) {
+ log_debug(LD_GUARD, "Sampled guard %s is still listed.",
+ entry_guard_describe(guard));
+ } else {
+ tor_assert(! is_listed && ! guard->currently_listed);
+ log_debug(LD_GUARD, "Sampled guard %s is still unlisted.",
+ entry_guard_describe(guard));
+ }
- if (options->EntryNodes) /* reshuffle the entry guard list if needed */
- entry_nodes_should_be_added();
-
- reasons = digestmap_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry)
- {
- const node_t *r = node_get_by_id(entry->identity);
- const char *reason = NULL;
- if (entry_guard_set_status(entry, r, now, options, &reason))
- changed = 1;
-
- if (entry->bad_since)
- tor_assert(reason);
- if (reason)
- digestmap_set(reasons, entry->identity, (char*)reason);
+ /* Clean up unlisted_since_date, just in case. */
+ if (guard->currently_listed && guard->unlisted_since_date) {
+ ++n_changes;
+ guard->unlisted_since_date = 0;
+ log_warn(LD_BUG, "Sampled guard %s was listed, but with "
+ "unlisted_since_date set. Fixing.",
+ entry_guard_describe(guard));
+ } else if (!guard->currently_listed && ! guard->unlisted_since_date) {
+ ++n_changes;
+ guard->unlisted_since_date = randomize_time(approx_time(),
+ unlisted_since_slop);
+ log_warn(LD_BUG, "Sampled guard %s was unlisted, but with "
+ "unlisted_since_date unset. Fixing.",
+ entry_guard_describe(guard));
}
- SMARTLIST_FOREACH_END(entry);
-
- if (remove_dead_entry_guards(now))
- changed = 1;
- if (remove_obsolete_entry_guards(now))
- changed = 1;
-
- if (changed) {
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
- const char *reason = digestmap_get(reasons, entry->identity);
- const char *live_msg = "";
- const node_t *r = entry_is_live(entry, ENTRY_NEED_CAPACITY, &live_msg);
- log_info(LD_CIRC, "Summary: Entry %s [%s] is %s, %s%s%s, and %s%s.",
- entry->nickname,
- hex_str(entry->identity, DIGEST_LEN),
- entry->unreachable_since ? "unreachable" : "reachable",
- entry->bad_since ? "unusable" : "usable",
- reason ? ", ": "",
- reason ? reason : "",
- r ? "live" : "not live / ",
- r ? "" : live_msg);
- } SMARTLIST_FOREACH_END(entry);
- log_info(LD_CIRC, " (%d/%d entry guards are usable/new)",
- num_live_entry_guards(0), smartlist_len(entry_guards));
- log_entry_guards(LOG_INFO);
- entry_guards_changed();
+ } SMARTLIST_FOREACH_END(guard);
+
+ const time_t remove_if_unlisted_since =
+ approx_time() - REMOVE_UNLISTED_GUARDS_AFTER;
+ const time_t maybe_remove_if_sampled_before =
+ approx_time() - get_guard_lifetime();
+ const time_t remove_if_confirmed_before =
+ approx_time() - get_guard_confirmed_min_lifetime();
+
+ /* Then: remove the ones that have been junk for too long */
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ int rmv = 0;
+
+ if (guard->currently_listed == 0 &&
+ guard->unlisted_since_date < remove_if_unlisted_since) {
+ /*
+ "We have a live consensus, and {IS_LISTED} is false, and
+ {FIRST_UNLISTED_AT} is over {REMOVE_UNLISTED_GUARDS_AFTER}
+ days in the past."
+ */
+ log_info(LD_GUARD, "Removing sampled guard %s: it has been unlisted "
+ "for over %d days", entry_guard_describe(guard),
+ get_remove_unlisted_guards_after_days());
+ rmv = 1;
+ } else if (guard->sampled_on_date < maybe_remove_if_sampled_before) {
+ /* We have a live consensus, and {ADDED_ON_DATE} is over
+ {GUARD_LIFETIME} ago, *and* {CONFIRMED_ON_DATE} is either
+ "never", or over {GUARD_CONFIRMED_MIN_LIFETIME} ago.
+ */
+ if (guard->confirmed_on_date == 0) {
+ rmv = 1;
+ log_info(LD_GUARD, "Removing sampled guard %s: it was sampled "
+ "over %d days ago, but never confirmed.",
+ entry_guard_describe(guard),
+ get_guard_lifetime() / 86400);
+ } else if (guard->confirmed_on_date < remove_if_confirmed_before) {
+ rmv = 1;
+ log_info(LD_GUARD, "Removing sampled guard %s: it was sampled "
+ "over %d days ago, and confirmed over %d days ago.",
+ entry_guard_describe(guard),
+ get_guard_lifetime() / 86400,
+ get_guard_confirmed_min_lifetime() / 86400);
+ }
+ }
+
+ if (rmv) {
+ ++n_changes;
+ SMARTLIST_DEL_CURRENT(gs->sampled_entry_guards, guard);
+ remove_guard_from_confirmed_and_primary_lists(gs, guard);
+ entry_guard_free(guard);
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ if (n_changes) {
+ gs->primary_guards_up_to_date = 0;
+ entry_guards_update_filtered_sets(gs);
+ /* We don't need to rebuild the confirmed list right here -- we may have
+ * removed confirmed guards above, but we can't have added any new
+ * confirmed guards.
+ */
+ entry_guards_changed_for_guard_selection(gs);
}
+}
+
+/**
+ * Return true iff <b>node</b> is a Tor relay that we are configured to
+ * be able to connect to. */
+static int
+node_passes_guard_filter(const or_options_t *options,
+ const node_t *node)
+{
+ /* NOTE: Make sure that this function stays in sync with
+ * options_transition_affects_entry_guards */
+ if (routerset_contains_node(options->ExcludeNodes, node))
+ return 0;
- digestmap_free(reasons, NULL);
+ if (options->EntryNodes &&
+ !routerset_contains_node(options->EntryNodes, node))
+ return 0;
+
+ if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0))
+ return 0;
+
+ if (node_is_a_configured_bridge(node))
+ return 0;
+
+ return 1;
}
-/** Called when a connection to an OR with the identity digest <b>digest</b>
- * is established (<b>succeeded</b>==1) or has failed (<b>succeeded</b>==0).
- * If the OR is an entry, change that entry's up/down status.
- * Return 0 normally, or -1 if we want to tear down the new connection.
- *
- * If <b>mark_relay_status</b>, also call router_set_status() on this
- * relay.
- */
-/* XXX We could change succeeded and mark_relay_status into 'int flags'.
- * Too many boolean arguments is a recipe for confusion.
- */
-int
-entry_guard_register_connect_status(const char *digest, int succeeded,
- int mark_relay_status, time_t now)
+/** Helper: Return true iff <b>bridge</b> passes our configuration
+ * filter-- if it is a relay that we are configured to be able to
+ * connect to. */
+static int
+bridge_passes_guard_filter(const or_options_t *options,
+ const bridge_info_t *bridge)
{
- int changed = 0;
- int refuse_conn = 0;
- int first_contact = 0;
- entry_guard_t *entry = NULL;
- int idx = -1;
- char buf[HEX_DIGEST_LEN+1];
+ tor_assert(bridge);
+ if (!bridge)
+ return 0;
- if (! entry_guards)
+ if (routerset_contains_bridge(options->ExcludeNodes, bridge))
return 0;
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- tor_assert(e);
- if (tor_memeq(e->identity, digest, DIGEST_LEN)) {
- entry = e;
- idx = e_sl_idx;
- break;
- }
- } SMARTLIST_FOREACH_END(e);
+ /* Ignore entrynodes */
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
- if (!entry)
+ if (!fascist_firewall_allows_address_addr(&addrport->addr,
+ addrport->port,
+ FIREWALL_OR_CONNECTION,
+ 0, 0))
return 0;
- base16_encode(buf, sizeof(buf), entry->identity, DIGEST_LEN);
-
- if (succeeded) {
- if (entry->unreachable_since) {
- log_info(LD_CIRC, "Entry guard '%s' (%s) is now reachable again. Good.",
- entry->nickname, buf);
- entry->can_retry = 0;
- entry->unreachable_since = 0;
- entry->last_attempted = now;
- control_event_guard(entry->nickname, entry->identity, "UP");
- changed = 1;
- }
- if (!entry->made_contact) {
- entry->made_contact = 1;
- first_contact = changed = 1;
- }
- } else { /* ! succeeded */
- if (!entry->made_contact) {
- /* We've never connected to this one. */
- log_info(LD_CIRC,
- "Connection to never-contacted entry guard '%s' (%s) failed. "
- "Removing from the list. %d/%d entry guards usable/new.",
- entry->nickname, buf,
- num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1);
- control_event_guard(entry->nickname, entry->identity, "DROPPED");
- entry_guard_free(entry);
- smartlist_del_keeporder(entry_guards, idx);
- log_entry_guards(LOG_INFO);
- changed = 1;
- } else if (!entry->unreachable_since) {
- log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). "
- "Marking as unreachable.", entry->nickname, buf);
- entry->unreachable_since = entry->last_attempted = now;
- control_event_guard(entry->nickname, entry->identity, "DOWN");
- changed = 1;
- entry->can_retry = 0; /* We gave it an early chance; no good. */
- } else {
- char tbuf[ISO_TIME_LEN+1];
- format_iso_time(tbuf, entry->unreachable_since);
- log_debug(LD_CIRC, "Failed to connect to unreachable entry guard "
- "'%s' (%s). It has been unreachable since %s.",
- entry->nickname, buf, tbuf);
- entry->last_attempted = now;
- entry->can_retry = 0; /* We gave it an early chance; no good. */
- }
- }
+ return 1;
+}
- /* if the caller asked us to, also update the is_running flags for this
- * relay */
- if (mark_relay_status)
- router_set_status(digest, succeeded);
-
- if (first_contact) {
- /* We've just added a new long-term entry guard. Perhaps the network just
- * came back? We should give our earlier entries another try too,
- * and close this connection so we don't use it before we've given
- * the others a shot. */
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- if (e == entry)
- break;
- if (e->made_contact) {
- const char *msg;
- const node_t *r = entry_is_live(e,
- ENTRY_NEED_CAPACITY | ENTRY_ASSUME_REACHABLE,
- &msg);
- if (r && e->unreachable_since) {
- refuse_conn = 1;
- e->can_retry = 1;
- }
- }
- } SMARTLIST_FOREACH_END(e);
- if (refuse_conn) {
- log_info(LD_CIRC,
- "Connected to new entry guard '%s' (%s). Marking earlier "
- "entry guards up. %d/%d entry guards usable/new.",
- entry->nickname, buf,
- num_live_entry_guards(0), smartlist_len(entry_guards));
- log_entry_guards(LOG_INFO);
- changed = 1;
+/**
+ * Return true iff <b>guard</b> is a Tor relay that we are configured to
+ * be able to connect to, and we haven't disabled it for omission from
+ * the consensus or path bias issues. */
+static int
+entry_guard_passes_filter(const or_options_t *options, guard_selection_t *gs,
+ entry_guard_t *guard)
+{
+ if (guard->currently_listed == 0)
+ return 0;
+ if (guard->pb.path_bias_disabled)
+ return 0;
+
+ if (gs->type == GS_TYPE_BRIDGE) {
+ const bridge_info_t *bridge = get_bridge_info_for_guard(guard);
+ if (bridge == NULL)
+ return 0;
+ return bridge_passes_guard_filter(options, bridge);
+ } else {
+ const node_t *node = node_get_by_id(guard->identity);
+ if (node == NULL) {
+ // This can happen when currently_listed is true, and we're not updating
+ // it because we don't have a live consensus.
+ return 0;
}
- }
- if (changed)
- entry_guards_changed();
- return refuse_conn ? -1 : 0;
+ return node_passes_guard_filter(options, node);
+ }
}
-/** When we try to choose an entry guard, should we parse and add
- * config's EntryNodes first? */
-static int should_add_entry_nodes = 0;
-
-/** Called when the value of EntryNodes changes in our configuration. */
-void
-entry_nodes_should_be_added(void)
+/** Return true iff <b>guard</b> is in the same family as <b>node</b>.
+ */
+static int
+guard_in_node_family(const entry_guard_t *guard, const node_t *node)
{
- log_info(LD_CIRC, "EntryNodes config option set. Putting configured "
- "relays at the front of the entry guard list.");
- should_add_entry_nodes = 1;
+ const node_t *guard_node = node_get_by_id(guard->identity);
+ if (guard_node) {
+ return nodes_in_same_family(guard_node, node);
+ } else {
+ /* If we don't have a node_t for the guard node, we might have
+ * a bridge_info_t for it. So let's check to see whether the bridge
+ * address matches has any family issues.
+ *
+ * (Strictly speaking, I believe this check is unnecessary, since we only
+ * use it to avoid the exit's family when building circuits, and we don't
+ * build multihop circuits until we have a routerinfo_t for the
+ * bridge... at which point, we'll also have a node_t for the
+ * bridge. Nonetheless, it seems wise to include it, in case our
+ * assumptions change down the road. -nickm.)
+ */
+ if (get_options()->EnforceDistinctSubnets && guard->bridge_addr) {
+ tor_addr_t node_addr;
+ node_get_addr(node, &node_addr);
+ if (addrs_in_same_network_family(&node_addr,
+ &guard->bridge_addr->addr)) {
+ return 1;
+ }
+ }
+ return 0;
+ }
}
-/** Update the using_as_guard fields of all the nodes. We do this after we
- * remove entry guards from the list: This is the only function that clears
- * the using_as_guard field. */
-static void
-update_node_guard_status(void)
+/**
+ * Return true iff <b>guard</b> obeys the restrictions defined in <b>rst</b>.
+ * (If <b>rst</b> is NULL, there are no restrictions.)
+ */
+static int
+entry_guard_obeys_restriction(const entry_guard_t *guard,
+ const entry_guard_restriction_t *rst)
{
- smartlist_t *nodes = nodelist_get_list();
- SMARTLIST_FOREACH(nodes, node_t *, node, node->using_as_guard = 0);
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
- node_t *node = node_get_mutable_by_id(entry->identity);
- if (node)
- node->using_as_guard = 1;
- } SMARTLIST_FOREACH_END(entry);
+ tor_assert(guard);
+ if (! rst)
+ return 1; // No restriction? No problem.
+
+ // Only one kind of restriction exists right now: excluding an exit
+ // ID and all of its family.
+ const node_t *node = node_get_by_id((const char*)rst->exclude_id);
+ if (node && guard_in_node_family(guard, node))
+ return 0;
+
+ return tor_memneq(guard->identity, rst->exclude_id, DIGEST_LEN);
}
-/** Adjust the entry guards list so that it only contains entries from
- * EntryNodes, adding new entries from EntryNodes to the list as needed. */
-STATIC void
-entry_guards_set_from_config(const or_options_t *options)
+/**
+ * Update the <b>is_filtered_guard</b> and <b>is_usable_filtered_guard</b>
+ * flags on <b>guard</b>. */
+void
+entry_guard_set_filtered_flags(const or_options_t *options,
+ guard_selection_t *gs,
+ entry_guard_t *guard)
{
- smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps;
- smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list;
- const int numentryguards = decide_num_guards(options, 0);
- tor_assert(entry_guards);
+ unsigned was_filtered = guard->is_filtered_guard;
+ guard->is_filtered_guard = 0;
+ guard->is_usable_filtered_guard = 0;
- should_add_entry_nodes = 0;
+ if (entry_guard_passes_filter(options, gs, guard)) {
+ guard->is_filtered_guard = 1;
- if (!options->EntryNodes) {
- /* It's possible that a controller set EntryNodes, thus making
- * should_add_entry_nodes set, then cleared it again, all before the
- * call to choose_random_entry() that triggered us. If so, just return.
- */
- return;
+ if (guard->is_reachable != GUARD_REACHABLE_NO)
+ guard->is_usable_filtered_guard = 1;
+
+ entry_guard_consider_retry(guard);
}
+ log_debug(LD_GUARD, "Updated sampled guard %s: filtered=%d; "
+ "reachable_filtered=%d.", entry_guard_describe(guard),
+ guard->is_filtered_guard, guard->is_usable_filtered_guard);
- {
- char *string = routerset_to_string(options->EntryNodes);
- log_info(LD_CIRC,"Adding configured EntryNodes '%s'.", string);
- tor_free(string);
+ if (!bool_eq(was_filtered, guard->is_filtered_guard)) {
+ /* This guard might now be primary or nonprimary. */
+ gs->primary_guards_up_to_date = 0;
}
+}
- entry_nodes = smartlist_new();
- worse_entry_nodes = smartlist_new();
- entry_fps = smartlist_new();
- old_entry_guards_on_list = smartlist_new();
- old_entry_guards_not_on_list = smartlist_new();
+/**
+ * Update the <b>is_filtered_guard</b> and <b>is_usable_filtered_guard</b>
+ * flag on every guard in <b>gs</b>. */
+STATIC void
+entry_guards_update_filtered_sets(guard_selection_t *gs)
+{
+ const or_options_t *options = get_options();
- /* Split entry guards into those on the list and those not. */
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_set_filtered_flags(options, gs, guard);
+ } SMARTLIST_FOREACH_END(guard);
+}
- routerset_get_all_nodes(entry_nodes, options->EntryNodes,
- options->ExcludeNodes, 0);
- SMARTLIST_FOREACH(entry_nodes, const node_t *,node,
- smartlist_add(entry_fps, (void*)node->identity));
+/**
+ * Return a random guard from the reachable filtered sample guards
+ * in <b>gs</b>, subject to the exclusion rules listed in <b>flags</b>.
+ * Return NULL if no such guard can be found.
+ *
+ * Make sure that the sample is big enough, and that all the filter flags
+ * are set correctly, before calling this function.
+ *
+ * If a restriction is provided in <b>rst</b>, do not return any guards that
+ * violate it.
+ **/
+STATIC entry_guard_t *
+sample_reachable_filtered_entry_guards(guard_selection_t *gs,
+ const entry_guard_restriction_t *rst,
+ unsigned flags)
+{
+ tor_assert(gs);
+ entry_guard_t *result = NULL;
+ const unsigned exclude_confirmed = flags & SAMPLE_EXCLUDE_CONFIRMED;
+ const unsigned exclude_primary = flags & SAMPLE_EXCLUDE_PRIMARY;
+ const unsigned exclude_pending = flags & SAMPLE_EXCLUDE_PENDING;
+ const unsigned no_update_primary = flags & SAMPLE_NO_UPDATE_PRIMARY;
+ const unsigned need_descriptor = flags & SAMPLE_EXCLUDE_NO_DESCRIPTOR;
+
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ const int n_reachable_filtered = num_reachable_filtered_guards(gs, rst);
+
+ log_info(LD_GUARD, "Trying to sample a reachable guard: We know of %d "
+ "in the USABLE_FILTERED set.", n_reachable_filtered);
+
+ const int min_filtered_sample = get_min_filtered_sample_size();
+ if (n_reachable_filtered < min_filtered_sample) {
+ log_info(LD_GUARD, " (That isn't enough. Trying to expand the sample.)");
+ entry_guards_expand_sample(gs);
+ }
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, {
- if (smartlist_contains_digest(entry_fps, e->identity))
- smartlist_add(old_entry_guards_on_list, e);
- else
- smartlist_add(old_entry_guards_not_on_list, e);
- });
+ if (exclude_primary && !gs->primary_guards_up_to_date && !no_update_primary)
+ entry_guards_update_primary(gs);
- /* Remove all currently configured guard nodes, excluded nodes, unreachable
- * nodes, or non-Guard nodes from entry_nodes. */
- SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) {
- if (entry_guard_get_by_id_digest(node->identity)) {
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
+ /* Build the set of reachable filtered guards. */
+ smartlist_t *reachable_filtered_sample = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);// redundant, but cheap.
+ if (! entry_guard_obeys_restriction(guard, rst))
continue;
- } else if (routerset_contains_node(options->ExcludeNodes, node)) {
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
+ if (! guard->is_usable_filtered_guard)
continue;
- } else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION,
- 0)) {
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
+ if (exclude_confirmed && guard->confirmed_idx >= 0)
continue;
- } else if (! node->is_possible_guard) {
- smartlist_add(worse_entry_nodes, (node_t*)node);
- SMARTLIST_DEL_CURRENT(entry_nodes, node);
- }
- } SMARTLIST_FOREACH_END(node);
+ if (exclude_primary && guard->is_primary)
+ continue;
+ if (exclude_pending && guard->is_pending)
+ continue;
+ if (need_descriptor && !guard_has_descriptor(guard))
+ continue;
+ smartlist_add(reachable_filtered_sample, guard);
+ } SMARTLIST_FOREACH_END(guard);
- /* Now build the new entry_guards list. */
- smartlist_clear(entry_guards);
- /* First, the previously configured guards that are in EntryNodes. */
- smartlist_add_all(entry_guards, old_entry_guards_on_list);
- /* Next, scramble the rest of EntryNodes, putting the guards first. */
- smartlist_shuffle(entry_nodes);
- smartlist_shuffle(worse_entry_nodes);
- smartlist_add_all(entry_nodes, worse_entry_nodes);
-
- /* Next, the rest of EntryNodes */
- SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) {
- add_an_entry_guard(node, 0, 0, 1, 0);
- if (smartlist_len(entry_guards) > numentryguards * 10)
- break;
- } SMARTLIST_FOREACH_END(node);
- log_notice(LD_GENERAL, "%d entries in guards", smartlist_len(entry_guards));
- /* Finally, free the remaining previously configured guards that are not in
- * EntryNodes. */
- SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e,
- entry_guard_free(e));
+ log_info(LD_GUARD, " (After filters [%x], we have %d guards to consider.)",
+ flags, smartlist_len(reachable_filtered_sample));
- update_node_guard_status();
+ if (smartlist_len(reachable_filtered_sample)) {
+ result = smartlist_choose(reachable_filtered_sample);
+ log_info(LD_GUARD, " (Selected %s.)",
+ result ? entry_guard_describe(result) : "<null>");
+ }
+ smartlist_free(reachable_filtered_sample);
- smartlist_free(entry_nodes);
- smartlist_free(worse_entry_nodes);
- smartlist_free(entry_fps);
- smartlist_free(old_entry_guards_on_list);
- smartlist_free(old_entry_guards_not_on_list);
- entry_guards_changed();
+ return result;
}
-/** Return 0 if we're fine adding arbitrary routers out of the
- * directory to our entry guard list, or return 1 if we have a
- * list already and we must stick to it.
+/**
+ * Helper: compare two entry_guard_t by their confirmed_idx values.
+ * Used to sort the confirmed list.
*/
-int
-entry_list_is_constrained(const or_options_t *options)
+static int
+compare_guards_by_confirmed_idx(const void **a_, const void **b_)
{
- if (options->EntryNodes)
+ const entry_guard_t *a = *a_, *b = *b_;
+ if (a->confirmed_idx < b->confirmed_idx)
+ return -1;
+ else if (a->confirmed_idx > b->confirmed_idx)
return 1;
- if (options->UseBridges)
- return 1;
- return 0;
+ else
+ return 0;
}
-/** Pick a live (up and listed) entry guard from entry_guards. If
- * <b>state</b> is non-NULL, this is for a specific circuit --
- * make sure not to pick this circuit's exit or any node in the
- * exit's family. If <b>state</b> is NULL, we're looking for a random
- * guard (likely a bridge). If <b>dirinfo</b> is not NO_DIRINFO (zero),
- * then only select from nodes that know how to answer directory questions
- * of that type. */
-const node_t *
-choose_random_entry(cpath_build_state_t *state)
+/**
+ * Find the confirmed guards from among the sampled guards in <b>gs</b>,
+ * and put them in confirmed_entry_guards in the correct
+ * order. Recalculate their indices.
+ */
+STATIC void
+entry_guards_update_confirmed(guard_selection_t *gs)
{
- return choose_random_entry_impl(state, 0, NO_DIRINFO, NULL);
+ smartlist_clear(gs->confirmed_entry_guards);
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (guard->confirmed_idx >= 0)
+ smartlist_add(gs->confirmed_entry_guards, guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ smartlist_sort(gs->confirmed_entry_guards, compare_guards_by_confirmed_idx);
+
+ int any_changed = 0;
+ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) {
+ if (guard->confirmed_idx != guard_sl_idx) {
+ any_changed = 1;
+ guard->confirmed_idx = guard_sl_idx;
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ gs->next_confirmed_idx = smartlist_len(gs->confirmed_entry_guards);
+
+ if (any_changed) {
+ entry_guards_changed_for_guard_selection(gs);
+ }
}
-/** Pick a live (up and listed) directory guard from entry_guards for
- * downloading information of type <b>type</b>. */
-const node_t *
-choose_random_dirguard(dirinfo_type_t type)
+/**
+ * Mark <b>guard</b> as a confirmed guard -- that is, one that we have
+ * connected to, and intend to use again.
+ */
+STATIC void
+make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard)
{
- return choose_random_entry_impl(NULL, 1, type, NULL);
+ if (BUG(guard->confirmed_on_date && guard->confirmed_idx >= 0))
+ return; // LCOV_EXCL_LINE
+
+ if (BUG(smartlist_contains(gs->confirmed_entry_guards, guard)))
+ return; // LCOV_EXCL_LINE
+
+ const int GUARD_LIFETIME = get_guard_lifetime();
+ guard->confirmed_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10);
+
+ log_info(LD_GUARD, "Marking %s as a confirmed guard (index %d)",
+ entry_guard_describe(guard),
+ gs->next_confirmed_idx);
+
+ guard->confirmed_idx = gs->next_confirmed_idx++;
+ smartlist_add(gs->confirmed_entry_guards, guard);
+
+ // This confirmed guard might kick something else out of the primary
+ // guards.
+ gs->primary_guards_up_to_date = 0;
+
+ entry_guards_changed_for_guard_selection(gs);
}
-/** Filter <b>all_entry_guards</b> for usable entry guards and put them
- * in <b>live_entry_guards</b>. We filter based on whether the node is
- * currently alive, and on whether it satisfies the restrictions
- * imposed by the other arguments of this function.
- *
- * We don't place more guards than NumEntryGuards in <b>live_entry_guards</b>.
- *
- * If <b>chosen_exit</b> is set, it contains the exit node of this
- * circuit. Make sure to not use it or its family as an entry guard.
- *
- * If <b>need_uptime</b> is set, we are looking for a stable entry guard.
- * if <b>need_capacity</b> is set, we are looking for a fast entry guard.
- *
- * The rest of the arguments are the same as in choose_random_entry_impl().
- *
- * Return 1 if we should choose a guard right away. Return 0 if we
- * should try to add more nodes to our list before deciding on a
- * guard.
+/**
+ * Recalculate the list of primary guards (the ones we'd prefer to use) from
+ * the filtered sample and the confirmed list.
*/
-STATIC int
-populate_live_entry_guards(smartlist_t *live_entry_guards,
- const smartlist_t *all_entry_guards,
- const node_t *chosen_exit,
- dirinfo_type_t dirinfo_type,
- int for_directory,
- int need_uptime, int need_capacity)
+STATIC void
+entry_guards_update_primary(guard_selection_t *gs)
{
- const or_options_t *options = get_options();
- const node_t *node = NULL;
- const int num_needed = decide_num_guards(options, for_directory);
- smartlist_t *exit_family = smartlist_new();
- int retval = 0;
- entry_is_live_flags_t entry_flags = 0;
+ tor_assert(gs);
- (void) dirinfo_type;
+ // prevent recursion. Recursion is potentially very bad here.
+ static int running = 0;
+ tor_assert(!running);
+ running = 1;
- { /* Set the flags we want our entry node to have */
- if (need_uptime) {
- entry_flags |= ENTRY_NEED_UPTIME;
- }
- if (need_capacity) {
- entry_flags |= ENTRY_NEED_CAPACITY;
- }
- if (!for_directory) {
- entry_flags |= ENTRY_NEED_DESCRIPTOR;
- }
- }
+ const int N_PRIMARY_GUARDS = get_n_primary_guards();
- tor_assert(all_entry_guards);
+ smartlist_t *new_primary_guards = smartlist_new();
+ smartlist_t *old_primary_guards = smartlist_new();
+ smartlist_add_all(old_primary_guards, gs->primary_entry_guards);
- if (chosen_exit) {
- nodelist_add_node_and_family(exit_family, chosen_exit);
+ /* Set this flag now, to prevent the calls below from recursing. */
+ gs->primary_guards_up_to_date = 1;
+
+ /* First, can we fill it up with confirmed guards? */
+ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) {
+ if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS)
+ break;
+ if (! guard->is_filtered_guard)
+ continue;
+ guard->is_primary = 1;
+ smartlist_add(new_primary_guards, guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Can we keep any older primary guards? First remove all the ones
+ * that we already kept. */
+ SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) {
+ if (smartlist_contains(new_primary_guards, guard)) {
+ SMARTLIST_DEL_CURRENT_KEEPORDER(old_primary_guards, guard);
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Now add any that are still good. */
+ SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) {
+ if (smartlist_len(new_primary_guards) >= N_PRIMARY_GUARDS)
+ break;
+ if (! guard->is_filtered_guard)
+ continue;
+ guard->is_primary = 1;
+ smartlist_add(new_primary_guards, guard);
+ SMARTLIST_DEL_CURRENT_KEEPORDER(old_primary_guards, guard);
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Mark the remaining previous primary guards as non-primary */
+ SMARTLIST_FOREACH_BEGIN(old_primary_guards, entry_guard_t *, guard) {
+ guard->is_primary = 0;
+ } SMARTLIST_FOREACH_END(guard);
+
+ /* Finally, fill out the list with sampled guards. */
+ while (smartlist_len(new_primary_guards) < N_PRIMARY_GUARDS) {
+ entry_guard_t *guard = sample_reachable_filtered_entry_guards(gs, NULL,
+ SAMPLE_EXCLUDE_CONFIRMED|
+ SAMPLE_EXCLUDE_PRIMARY|
+ SAMPLE_NO_UPDATE_PRIMARY);
+ if (!guard)
+ break;
+ guard->is_primary = 1;
+ smartlist_add(new_primary_guards, guard);
}
- SMARTLIST_FOREACH_BEGIN(all_entry_guards, const entry_guard_t *, entry) {
- const char *msg;
- node = entry_is_live(entry, entry_flags, &msg);
- if (!node)
- continue; /* down, no point */
- if (for_directory) {
- if (!entry->is_dir_cache)
- continue; /* We need a directory and didn't get one. */
- }
- if (node == chosen_exit)
- continue; /* don't pick the same node for entry and exit */
- if (smartlist_contains(exit_family, node))
- continue; /* avoid relays that are family members of our exit */
- smartlist_add(live_entry_guards, (void*)node);
- if (!entry->made_contact) {
- /* Always start with the first not-yet-contacted entry
- * guard. Otherwise we might add several new ones, pick
- * the second new one, and now we've expanded our entry
- * guard list without needing to. */
- retval = 1;
- goto done;
- }
- if (smartlist_len(live_entry_guards) >= num_needed) {
- retval = 1;
- goto done; /* We picked enough entry guards. Done! */
+#if 1
+ /* Debugging. */
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, guard, {
+ tor_assert_nonfatal(
+ bool_eq(guard->is_primary,
+ smartlist_contains(new_primary_guards, guard)));
+ });
+#endif
+
+ int any_change = 0;
+ if (smartlist_len(gs->primary_entry_guards) !=
+ smartlist_len(new_primary_guards)) {
+ any_change = 1;
+ } else {
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, g) {
+ if (g != smartlist_get(new_primary_guards, g_sl_idx)) {
+ any_change = 1;
}
- } SMARTLIST_FOREACH_END(entry);
+ } SMARTLIST_FOREACH_END(g);
+ }
- done:
- smartlist_free(exit_family);
+ if (any_change) {
+ log_info(LD_GUARD, "Primary entry guards have changed. "
+ "New primary guard list is: ");
+ int n = smartlist_len(new_primary_guards);
+ SMARTLIST_FOREACH_BEGIN(new_primary_guards, entry_guard_t *, g) {
+ log_info(LD_GUARD, " %d/%d: %s%s%s",
+ g_sl_idx+1, n, entry_guard_describe(g),
+ g->confirmed_idx >= 0 ? " (confirmed)" : "",
+ g->is_filtered_guard ? "" : " (excluded by filter)");
+ } SMARTLIST_FOREACH_END(g);
+ }
- return retval;
+ smartlist_free(old_primary_guards);
+ smartlist_free(gs->primary_entry_guards);
+ gs->primary_entry_guards = new_primary_guards;
+ gs->primary_guards_up_to_date = 1;
+ running = 0;
}
-/** Pick a node to be used as the entry guard of a circuit.
- *
- * If <b>state</b> is set, it contains the information we know about
- * the upcoming circuit.
- *
- * If <b>for_directory</b> is set, we are looking for a directory guard.
- *
- * <b>dirinfo_type</b> contains the kind of directory information we
- * are looking for in our node, or NO_DIRINFO (zero) if we are not
- * looking for any particular directory information (when set to
- * NO_DIRINFO, the <b>dirinfo_type</b> filter is ignored).
- *
- * If <b>n_options_out</b> is set, we set it to the number of
- * candidate guard nodes we had before picking a specific guard node.
- *
- * On success, return the node that should be used as the entry guard
- * of the circuit. Return NULL if no such node could be found.
- *
- * Helper for choose_random{entry,dirguard}.
-*/
-static const node_t *
-choose_random_entry_impl(cpath_build_state_t *state, int for_directory,
- dirinfo_type_t dirinfo_type, int *n_options_out)
+/**
+ * Return the number of seconds after the last attempt at which we should
+ * retry a guard that has been failing since <b>failing_since</b>.
+ */
+static int
+get_retry_schedule(time_t failing_since, time_t now,
+ int is_primary)
{
- const or_options_t *options = get_options();
- smartlist_t *live_entry_guards = smartlist_new();
- const node_t *chosen_exit =
- state?build_state_get_exit_node(state) : NULL;
- const node_t *node = NULL;
- int need_uptime = state ? state->need_uptime : 0;
- int need_capacity = state ? state->need_capacity : 0;
- int preferred_min = 0;
- const int num_needed = decide_num_guards(options, for_directory);
- int retval = 0;
-
- if (n_options_out)
- *n_options_out = 0;
-
- if (!entry_guards)
- entry_guards = smartlist_new();
-
- if (should_add_entry_nodes)
- entry_guards_set_from_config(options);
-
- if (!entry_list_is_constrained(options) &&
- smartlist_len(entry_guards) < num_needed)
- pick_entry_guards(options, for_directory);
-
- retry:
- smartlist_clear(live_entry_guards);
-
- /* Populate the list of live entry guards so that we pick one of
- them. */
- retval = populate_live_entry_guards(live_entry_guards,
- entry_guards,
- chosen_exit,
- dirinfo_type,
- for_directory,
- need_uptime, need_capacity);
-
- if (retval == 1) { /* We should choose a guard right now. */
- goto choose_and_finish;
- }
-
- if (entry_list_is_constrained(options)) {
- /* If we prefer the entry nodes we've got, and we have at least
- * one choice, that's great. Use it. */
- preferred_min = 1;
- } else {
- /* Try to have at least 2 choices available. This way we don't
- * get stuck with a single live-but-crummy entry and just keep
- * using it.
- * (We might get 2 live-but-crummy entry guards, but so be it.) */
- preferred_min = 2;
- }
-
- if (smartlist_len(live_entry_guards) < preferred_min) {
- if (!entry_list_is_constrained(options)) {
- /* still no? try adding a new entry then */
- /* XXX if guard doesn't imply fast and stable, then we need
- * to tell add_an_entry_guard below what we want, or it might
- * be a long time til we get it. -RD */
- node = add_an_entry_guard(NULL, 0, 0, 1, for_directory);
- if (node) {
- entry_guards_changed();
- /* XXX we start over here in case the new node we added shares
- * a family with our exit node. There's a chance that we'll just
- * load up on entry guards here, if the network we're using is
- * one big family. Perhaps we should teach add_an_entry_guard()
- * to understand nodes-to-avoid-if-possible? -RD */
- goto retry;
- }
- }
- if (!node && need_uptime) {
- need_uptime = 0; /* try without that requirement */
- goto retry;
- }
- if (!node && need_capacity) {
- /* still no? last attempt, try without requiring capacity */
- need_capacity = 0;
- goto retry;
- }
+ const unsigned SIX_HOURS = 6 * 3600;
+ const unsigned FOUR_DAYS = 4 * 86400;
+ const unsigned SEVEN_DAYS = 7 * 86400;
- /* live_entry_guards may be empty below. Oh well, we tried. */
+ time_t tdiff;
+ if (now > failing_since) {
+ tdiff = now - failing_since;
+ } else {
+ tdiff = 0;
}
- choose_and_finish:
- if (entry_list_is_constrained(options)) {
- /* We need to weight by bandwidth, because our bridges or entryguards
- * were not already selected proportional to their bandwidth. */
- node = node_sl_choose_by_bandwidth(live_entry_guards, WEIGHT_FOR_GUARD);
- } else {
- /* We choose uniformly at random here, because choose_good_entry_server()
- * already weights its choices by bandwidth, so we don't want to
- * *double*-weight our guard selection. */
- node = smartlist_choose(live_entry_guards);
+ const struct {
+ time_t maximum; int primary_delay; int nonprimary_delay;
+ } delays[] = {
+ { SIX_HOURS, 10*60, 1*60*60 },
+ { FOUR_DAYS, 90*60, 4*60*60 },
+ { SEVEN_DAYS, 4*60*60, 18*60*60 },
+ { TIME_MAX, 9*60*60, 36*60*60 }
+ };
+
+ unsigned i;
+ for (i = 0; i < ARRAY_LENGTH(delays); ++i) {
+ if (tdiff <= delays[i].maximum) {
+ return is_primary ? delays[i].primary_delay : delays[i].nonprimary_delay;
+ }
}
- if (n_options_out)
- *n_options_out = smartlist_len(live_entry_guards);
- smartlist_free(live_entry_guards);
- return node;
+ /* LCOV_EXCL_START -- can't reach, since delays ends with TIME_MAX. */
+ tor_assert_nonfatal_unreached();
+ return 36*60*60;
+ /* LCOV_EXCL_STOP */
}
-/** Parse <b>state</b> and learn about the entry guards it describes.
- * If <b>set</b> is true, and there are no errors, replace the global
- * entry_list with what we find.
- * On success, return 0. On failure, alloc into *<b>msg</b> a string
- * describing the error, and return -1.
+/**
+ * If <b>guard</b> is unreachable, consider whether enough time has passed
+ * to consider it maybe-reachable again.
*/
-int
-entry_guards_parse_state(or_state_t *state, int set, char **msg)
+STATIC void
+entry_guard_consider_retry(entry_guard_t *guard)
{
- entry_guard_t *node = NULL;
- smartlist_t *new_entry_guards = smartlist_new();
- config_line_t *line;
- time_t now = time(NULL);
- const char *state_version = state->TorVersion;
- digestmap_t *added_by = digestmap_new();
-
- *msg = NULL;
- for (line = state->EntryGuards; line; line = line->next) {
- if (!strcasecmp(line->key, "EntryGuard")) {
- smartlist_t *args = smartlist_new();
- node = tor_malloc_zero(sizeof(entry_guard_t));
- /* all entry guards on disk have been contacted */
- node->made_contact = 1;
- smartlist_add(new_entry_guards, node);
- smartlist_split_string(args, line->value, " ",
- SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
- if (smartlist_len(args)<2) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Too few arguments to EntryGuard");
- } else if (!is_legal_nickname(smartlist_get(args,0))) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Bad nickname for EntryGuard");
- } else {
- strlcpy(node->nickname, smartlist_get(args,0), MAX_NICKNAME_LEN+1);
- if (base16_decode(node->identity, DIGEST_LEN, smartlist_get(args,1),
- strlen(smartlist_get(args,1))) != DIGEST_LEN) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Bad hex digest for EntryGuard");
- }
- }
- if (smartlist_len(args) >= 3) {
- const char *is_cache = smartlist_get(args, 2);
- if (!strcasecmp(is_cache, "DirCache")) {
- node->is_dir_cache = 1;
- } else if (!strcasecmp(is_cache, "NoDirCache")) {
- node->is_dir_cache = 0;
- } else {
- log_warn(LD_CONFIG, "Bogus third argument to EntryGuard line: %s",
- escaped(is_cache));
- }
- }
- SMARTLIST_FOREACH(args, char*, cp, tor_free(cp));
- smartlist_free(args);
- if (*msg)
- break;
- } else if (!strcasecmp(line->key, "EntryGuardDownSince") ||
- !strcasecmp(line->key, "EntryGuardUnlistedSince")) {
- time_t when;
- time_t last_try = 0;
- if (!node) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "EntryGuardDownSince/UnlistedSince without EntryGuard");
- break;
- }
- if (parse_iso_time_(line->value, &when, 0)<0) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "Bad time in EntryGuardDownSince/UnlistedSince");
- break;
- }
- if (when > now) {
- /* It's a bad idea to believe info in the future: you can wind
- * up with timeouts that aren't allowed to happen for years. */
- continue;
- }
- if (strlen(line->value) >= ISO_TIME_LEN+ISO_TIME_LEN+1) {
- /* ignore failure */
- (void) parse_iso_time(line->value+ISO_TIME_LEN+1, &last_try);
- }
- if (!strcasecmp(line->key, "EntryGuardDownSince")) {
- node->unreachable_since = when;
- node->last_attempted = last_try;
- } else {
- node->bad_since = when;
- }
- } else if (!strcasecmp(line->key, "EntryGuardAddedBy")) {
- char d[DIGEST_LEN];
- /* format is digest version date */
- if (strlen(line->value) < HEX_DIGEST_LEN+1+1+1+ISO_TIME_LEN) {
- log_warn(LD_BUG, "EntryGuardAddedBy line is not long enough.");
- continue;
- }
- if (base16_decode(d, sizeof(d),
- line->value, HEX_DIGEST_LEN) != sizeof(d) ||
- line->value[HEX_DIGEST_LEN] != ' ') {
- log_warn(LD_BUG, "EntryGuardAddedBy line %s does not begin with "
- "hex digest", escaped(line->value));
+ if (guard->is_reachable != GUARD_REACHABLE_NO)
+ return; /* No retry needed. */
+
+ const time_t now = approx_time();
+ const int delay =
+ get_retry_schedule(guard->failing_since, now, guard->is_primary);
+ const time_t last_attempt = guard->last_tried_to_connect;
+
+ if (BUG(last_attempt == 0) ||
+ now >= last_attempt + delay) {
+ /* We should mark this retriable. */
+ char tbuf[ISO_TIME_LEN+1];
+ format_local_iso_time(tbuf, last_attempt);
+ log_info(LD_GUARD, "Marked %s%sguard %s for possible retry, since we "
+ "haven't tried to use it since %s.",
+ guard->is_primary?"primary ":"",
+ guard->confirmed_idx>=0?"confirmed ":"",
+ entry_guard_describe(guard),
+ tbuf);
+
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+ if (guard->is_filtered_guard)
+ guard->is_usable_filtered_guard = 1;
+ }
+}
+
+/** Tell the entry guards subsystem that we have confirmed that as of
+ * just now, we're on the internet. */
+void
+entry_guards_note_internet_connectivity(guard_selection_t *gs)
+{
+ gs->last_time_on_internet = approx_time();
+}
+
+/**
+ * Get a guard for use with a circuit. Prefer to pick a running primary
+ * guard; then a non-pending running filtered confirmed guard; then a
+ * non-pending runnable filtered guard. Update the
+ * <b>last_tried_to_connect</b> time and the <b>is_pending</b> fields of the
+ * guard as appropriate. Set <b>state_out</b> to the new guard-state
+ * of the circuit.
+ */
+STATIC entry_guard_t *
+select_entry_guard_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ const entry_guard_restriction_t *rst,
+ unsigned *state_out)
+{
+ const int need_descriptor = (usage == GUARD_USAGE_TRAFFIC);
+ tor_assert(gs);
+ tor_assert(state_out);
+
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+
+ int num_entry_guards = get_n_primary_guards_to_use(usage);
+ smartlist_t *usable_primary_guards = smartlist_new();
+
+ /* "If any entry in PRIMARY_GUARDS has {is_reachable} status of
+ <maybe> or <yes>, return the first such guard." */
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (! entry_guard_obeys_restriction(guard, rst))
+ continue;
+ if (guard->is_reachable != GUARD_REACHABLE_NO) {
+ if (need_descriptor && !guard_has_descriptor(guard)) {
continue;
}
- digestmap_set(added_by, d, tor_strdup(line->value+HEX_DIGEST_LEN+1));
- } else if (!strcasecmp(line->key, "EntryGuardPathUseBias")) {
- const or_options_t *options = get_options();
- double use_cnt, success_cnt;
-
- if (!node) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "EntryGuardPathUseBias without EntryGuard");
+ *state_out = GUARD_CIRC_STATE_USABLE_ON_COMPLETION;
+ guard->last_tried_to_connect = approx_time();
+ smartlist_add(usable_primary_guards, guard);
+ if (smartlist_len(usable_primary_guards) >= num_entry_guards)
break;
- }
+ }
+ } SMARTLIST_FOREACH_END(guard);
+
+ if (smartlist_len(usable_primary_guards)) {
+ entry_guard_t *guard = smartlist_choose(usable_primary_guards);
+ smartlist_free(usable_primary_guards);
+ log_info(LD_GUARD, "Selected primary guard %s for circuit.",
+ entry_guard_describe(guard));
+ return guard;
+ }
+ smartlist_free(usable_primary_guards);
+
+ /* "Otherwise, if the ordered intersection of {CONFIRMED_GUARDS}
+ and {USABLE_FILTERED_GUARDS} is nonempty, return the first
+ entry in that intersection that has {is_pending} set to
+ false." */
+ SMARTLIST_FOREACH_BEGIN(gs->confirmed_entry_guards, entry_guard_t *, guard) {
+ if (guard->is_primary)
+ continue; /* we already considered this one. */
+ if (! entry_guard_obeys_restriction(guard, rst))
+ continue;
+ entry_guard_consider_retry(guard);
+ if (guard->is_usable_filtered_guard && ! guard->is_pending) {
+ if (need_descriptor && !guard_has_descriptor(guard))
+ continue; /* not a bug */
+ guard->is_pending = 1;
+ guard->last_tried_to_connect = approx_time();
+ *state_out = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD;
+ log_info(LD_GUARD, "No primary guards available. Selected confirmed "
+ "guard %s for circuit. Will try other guards before using "
+ "this circuit.",
+ entry_guard_describe(guard));
+ return guard;
+ }
+ } SMARTLIST_FOREACH_END(guard);
- if (tor_sscanf(line->value, "%lf %lf",
- &use_cnt, &success_cnt) != 2) {
- log_info(LD_GENERAL, "Malformed path use bias line for node %s",
- node->nickname);
- continue;
- }
+ /* "Otherwise, if there is no such entry, select a member at
+ random from {USABLE_FILTERED_GUARDS}." */
+ {
+ entry_guard_t *guard;
+ unsigned flags = 0;
+ if (need_descriptor)
+ flags |= SAMPLE_EXCLUDE_NO_DESCRIPTOR;
+ guard = sample_reachable_filtered_entry_guards(gs,
+ rst,
+ SAMPLE_EXCLUDE_CONFIRMED |
+ SAMPLE_EXCLUDE_PRIMARY |
+ SAMPLE_EXCLUDE_PENDING |
+ flags);
+ if (guard == NULL) {
+ log_info(LD_GUARD, "Absolutely no sampled guards were available. "
+ "Marking all guards for retry and starting from top again.");
+ mark_all_guards_maybe_reachable(gs);
+ return NULL;
+ }
+ guard->is_pending = 1;
+ guard->last_tried_to_connect = approx_time();
+ *state_out = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD;
+ log_info(LD_GUARD, "No primary or confirmed guards available. Selected "
+ "random guard %s for circuit. Will try other guards before "
+ "using this circuit.",
+ entry_guard_describe(guard));
+ return guard;
+ }
+}
- if (use_cnt < success_cnt) {
- int severity = LOG_INFO;
- /* If this state file was written by a Tor that would have
- * already fixed it, then the overcounting bug is still there.. */
- if (tor_version_as_new_as(state_version, "0.2.4.13-alpha")) {
- severity = LOG_NOTICE;
- }
- log_fn(severity, LD_BUG,
- "State file contains unexpectedly high usage success "
- "counts %lf/%lf for Guard %s ($%s)",
- success_cnt, use_cnt,
- node->nickname, hex_str(node->identity, DIGEST_LEN));
- success_cnt = use_cnt;
- }
+/**
+ * Note that we failed to connect to or build circuits through <b>guard</b>.
+ * Use with a guard returned by select_entry_guard_for_circuit().
+ */
+STATIC void
+entry_guards_note_guard_failure(guard_selection_t *gs,
+ entry_guard_t *guard)
+{
+ tor_assert(gs);
- node->use_attempts = use_cnt;
- node->use_successes = success_cnt;
-
- log_info(LD_GENERAL, "Read %f/%f path use bias for node %s",
- node->use_successes, node->use_attempts, node->nickname);
-
- /* Note: We rely on the < comparison here to allow us to set a 0
- * rate and disable the feature entirely. If refactoring, don't
- * change to <= */
- if (pathbias_get_use_success_count(node)/node->use_attempts
- < pathbias_get_extreme_use_rate(options) &&
- pathbias_get_dropguards(options)) {
- node->path_bias_disabled = 1;
- log_info(LD_GENERAL,
- "Path use bias is too high (%f/%f); disabling node %s",
- node->circ_successes, node->circ_attempts, node->nickname);
- }
- } else if (!strcasecmp(line->key, "EntryGuardPathBias")) {
- const or_options_t *options = get_options();
- double hop_cnt, success_cnt, timeouts, collapsed, successful_closed,
- unusable;
-
- if (!node) {
- *msg = tor_strdup("Unable to parse entry nodes: "
- "EntryGuardPathBias without EntryGuard");
- break;
- }
+ guard->is_reachable = GUARD_REACHABLE_NO;
+ guard->is_usable_filtered_guard = 0;
- /* First try 3 params, then 2. */
- /* In the long run: circuit_success ~= successful_circuit_close +
- * collapsed_circuits +
- * unusable_circuits */
- if (tor_sscanf(line->value, "%lf %lf %lf %lf %lf %lf",
- &hop_cnt, &success_cnt, &successful_closed,
- &collapsed, &unusable, &timeouts) != 6) {
- int old_success, old_hops;
- if (tor_sscanf(line->value, "%u %u", &old_success, &old_hops) != 2) {
- continue;
- }
- log_info(LD_GENERAL, "Reading old-style EntryGuardPathBias %s",
- escaped(line->value));
-
- success_cnt = old_success;
- successful_closed = old_success;
- hop_cnt = old_hops;
- timeouts = 0;
- collapsed = 0;
- unusable = 0;
- }
+ guard->is_pending = 0;
+ if (guard->failing_since == 0)
+ guard->failing_since = approx_time();
- if (hop_cnt < success_cnt) {
- int severity = LOG_INFO;
- /* If this state file was written by a Tor that would have
- * already fixed it, then the overcounting bug is still there.. */
- if (tor_version_as_new_as(state_version, "0.2.4.13-alpha")) {
- severity = LOG_NOTICE;
- }
- log_fn(severity, LD_BUG,
- "State file contains unexpectedly high success counts "
- "%lf/%lf for Guard %s ($%s)",
- success_cnt, hop_cnt,
- node->nickname, hex_str(node->identity, DIGEST_LEN));
- success_cnt = hop_cnt;
- }
+ log_info(LD_GUARD, "Recorded failure for %s%sguard %s",
+ guard->is_primary?"primary ":"",
+ guard->confirmed_idx>=0?"confirmed ":"",
+ entry_guard_describe(guard));
+}
- node->circ_attempts = hop_cnt;
- node->circ_successes = success_cnt;
-
- node->successful_circuits_closed = successful_closed;
- node->timeouts = timeouts;
- node->collapsed_circuits = collapsed;
- node->unusable_circuits = unusable;
-
- log_info(LD_GENERAL, "Read %f/%f path bias for node %s",
- node->circ_successes, node->circ_attempts, node->nickname);
- /* Note: We rely on the < comparison here to allow us to set a 0
- * rate and disable the feature entirely. If refactoring, don't
- * change to <= */
- if (pathbias_get_close_success_count(node)/node->circ_attempts
- < pathbias_get_extreme_rate(options) &&
- pathbias_get_dropguards(options)) {
- node->path_bias_disabled = 1;
- log_info(LD_GENERAL,
- "Path bias is too high (%f/%f); disabling node %s",
- node->circ_successes, node->circ_attempts, node->nickname);
- }
+/**
+ * Note that we successfully connected to, and built a circuit through
+ * <b>guard</b>. Given the old guard-state of the circuit in <b>old_state</b>,
+ * return the new guard-state of the circuit.
+ *
+ * Be aware: the circuit is only usable when its guard-state becomes
+ * GUARD_CIRC_STATE_COMPLETE.
+ **/
+STATIC unsigned
+entry_guards_note_guard_success(guard_selection_t *gs,
+ entry_guard_t *guard,
+ unsigned old_state)
+{
+ tor_assert(gs);
+
+ /* Save this, since we're about to overwrite it. */
+ const time_t last_time_on_internet = gs->last_time_on_internet;
+ gs->last_time_on_internet = approx_time();
+
+ guard->is_reachable = GUARD_REACHABLE_YES;
+ guard->failing_since = 0;
+ guard->is_pending = 0;
+ if (guard->is_filtered_guard)
+ guard->is_usable_filtered_guard = 1;
+
+ if (guard->confirmed_idx < 0) {
+ make_guard_confirmed(gs, guard);
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+ }
- } else {
- log_warn(LD_BUG, "Unexpected key %s", line->key);
- }
+ unsigned new_state;
+ switch (old_state) {
+ case GUARD_CIRC_STATE_COMPLETE:
+ case GUARD_CIRC_STATE_USABLE_ON_COMPLETION:
+ new_state = GUARD_CIRC_STATE_COMPLETE;
+ break;
+ default:
+ tor_assert_nonfatal_unreached();
+ /* Fall through. */
+ case GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD:
+ if (guard->is_primary) {
+ /* XXXX #20832 -- I don't actually like this logic. It seems to make
+ * us a little more susceptible to evil-ISP attacks. The mitigations
+ * I'm thinking of, however, aren't local to this point, so I'll leave
+ * it alone. */
+ /* This guard may have become primary by virtue of being confirmed.
+ * If so, the circuit for it is now complete.
+ */
+ new_state = GUARD_CIRC_STATE_COMPLETE;
+ } else {
+ new_state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD;
+ }
+ break;
}
- SMARTLIST_FOREACH_BEGIN(new_entry_guards, entry_guard_t *, e) {
- char *sp;
- char *val = digestmap_get(added_by, e->identity);
- if (val && (sp = strchr(val, ' '))) {
- time_t when;
- *sp++ = '\0';
- if (parse_iso_time(sp, &when)<0) {
- log_warn(LD_BUG, "Can't read time %s in EntryGuardAddedBy", sp);
- } else {
- e->chosen_by_version = tor_strdup(val);
- e->chosen_on_date = when;
- }
- } else {
- if (state_version) {
- e->chosen_on_date = crypto_rand_time_range(now - 3600*24*30, now);
- e->chosen_by_version = tor_strdup(state_version);
- }
- }
- if (e->path_bias_disabled && !e->bad_since)
- e->bad_since = time(NULL);
+ if (! guard->is_primary) {
+ if (last_time_on_internet + get_internet_likely_down_interval()
+ < approx_time()) {
+ mark_primary_guards_maybe_reachable(gs);
}
- SMARTLIST_FOREACH_END(e);
+ }
- if (*msg || !set) {
- SMARTLIST_FOREACH(new_entry_guards, entry_guard_t *, e,
- entry_guard_free(e));
- smartlist_free(new_entry_guards);
- } else { /* !err && set */
- if (entry_guards) {
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
- entry_guard_free(e));
- smartlist_free(entry_guards);
- }
- entry_guards = new_entry_guards;
- entry_guards_dirty = 0;
- /* XXX hand new_entry_guards to this func, and move it up a
- * few lines, so we don't have to re-dirty it */
- if (remove_obsolete_entry_guards(now))
- entry_guards_dirty = 1;
+ log_info(LD_GUARD, "Recorded success for %s%sguard %s",
+ guard->is_primary?"primary ":"",
+ guard->confirmed_idx>=0?"confirmed ":"",
+ entry_guard_describe(guard));
- update_node_guard_status();
- }
- digestmap_free(added_by, tor_free_);
- return *msg ? -1 : 0;
+ return new_state;
}
-/** How long will we let a change in our guard nodes stay un-saved
- * when we are trying to avoid disk writes? */
-#define SLOW_GUARD_STATE_FLUSH_TIME 600
-/** How long will we let a change in our guard nodes stay un-saved
- * when we are not trying to avoid disk writes? */
-#define FAST_GUARD_STATE_FLUSH_TIME 30
-
-/** Our list of entry guards has changed, or some element of one
- * of our entry guards has changed. Write the changes to disk within
- * the next few minutes.
+/**
+ * Helper: Return true iff <b>a</b> has higher priority than <b>b</b>.
*/
-void
-entry_guards_changed(void)
+STATIC int
+entry_guard_has_higher_priority(entry_guard_t *a, entry_guard_t *b)
{
- time_t when;
- entry_guards_dirty = 1;
+ tor_assert(a && b);
+ if (a == b)
+ return 0;
- if (get_options()->AvoidDiskWrites)
- when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME;
- else
- when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME;
+ /* Confirmed is always better than unconfirmed; lower index better
+ than higher */
+ if (a->confirmed_idx < 0) {
+ if (b->confirmed_idx >= 0)
+ return 0;
+ } else {
+ if (b->confirmed_idx < 0)
+ return 1;
- /* or_state_save() will call entry_guards_update_state(). */
- or_state_mark_dirty(get_or_state(), when);
+ /* Lower confirmed_idx is better than higher. */
+ return (a->confirmed_idx < b->confirmed_idx);
+ }
+
+ /* If we reach this point, both are unconfirmed. If one is pending, it
+ * has higher priority. */
+ if (a->is_pending) {
+ if (! b->is_pending)
+ return 1;
+
+ /* Both are pending: earlier last_tried_connect wins. */
+ return a->last_tried_to_connect < b->last_tried_to_connect;
+ } else {
+ if (b->is_pending)
+ return 0;
+
+ /* Neither is pending: priorities are equal. */
+ return 0;
+ }
}
-/** If the entry guard info has not changed, do nothing and return.
- * Otherwise, free the EntryGuards piece of <b>state</b> and create
- * a new one out of the global entry_guards list, and then mark
- * <b>state</b> dirty so it will get saved to disk.
+/** Release all storage held in <b>restriction</b> */
+static void
+entry_guard_restriction_free(entry_guard_restriction_t *rst)
+{
+ tor_free(rst);
+}
+
+/**
+ * Release all storage held in <b>state</b>.
*/
void
-entry_guards_update_state(or_state_t *state)
+circuit_guard_state_free(circuit_guard_state_t *state)
{
- config_line_t **next, *line;
- if (! entry_guards_dirty)
+ if (!state)
return;
-
- config_free_lines(state->EntryGuards);
- next = &state->EntryGuards;
- *next = NULL;
- if (!entry_guards)
- entry_guards = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- char dbuf[HEX_DIGEST_LEN+1];
- if (!e->made_contact)
- continue; /* don't write this one to disk */
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuard");
- base16_encode(dbuf, sizeof(dbuf), e->identity, DIGEST_LEN);
- tor_asprintf(&line->value, "%s %s %sDirCache", e->nickname, dbuf,
- e->is_dir_cache ? "" : "No");
- next = &(line->next);
- if (e->unreachable_since) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardDownSince");
- line->value = tor_malloc(ISO_TIME_LEN+1+ISO_TIME_LEN+1);
- format_iso_time(line->value, e->unreachable_since);
- if (e->last_attempted) {
- line->value[ISO_TIME_LEN] = ' ';
- format_iso_time(line->value+ISO_TIME_LEN+1, e->last_attempted);
- }
- next = &(line->next);
- }
- if (e->bad_since) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardUnlistedSince");
- line->value = tor_malloc(ISO_TIME_LEN+1);
- format_iso_time(line->value, e->bad_since);
- next = &(line->next);
- }
- if (e->chosen_on_date && e->chosen_by_version &&
- !strchr(e->chosen_by_version, ' ')) {
- char d[HEX_DIGEST_LEN+1];
- char t[ISO_TIME_LEN+1];
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardAddedBy");
- base16_encode(d, sizeof(d), e->identity, DIGEST_LEN);
- format_iso_time(t, e->chosen_on_date);
- tor_asprintf(&line->value, "%s %s %s",
- d, e->chosen_by_version, t);
- next = &(line->next);
- }
- if (e->circ_attempts > 0) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardPathBias");
- /* In the long run: circuit_success ~= successful_circuit_close +
- * collapsed_circuits +
- * unusable_circuits */
- tor_asprintf(&line->value, "%f %f %f %f %f %f",
- e->circ_attempts, e->circ_successes,
- pathbias_get_close_success_count(e),
- e->collapsed_circuits,
- e->unusable_circuits, e->timeouts);
- next = &(line->next);
- }
- if (e->use_attempts > 0) {
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup("EntryGuardPathUseBias");
-
- tor_asprintf(&line->value, "%f %f",
- e->use_attempts,
- pathbias_get_use_success_count(e));
- next = &(line->next);
- }
-
- } SMARTLIST_FOREACH_END(e);
- if (!get_options()->AvoidDiskWrites)
- or_state_mark_dirty(get_or_state(), 0);
- entry_guards_dirty = 0;
+ entry_guard_restriction_free(state->restrictions);
+ entry_guard_handle_free(state->guard);
+ tor_free(state);
}
-/** If <b>question</b> is the string "entry-guards", then dump
- * to *<b>answer</b> a newly allocated string describing all of
- * the nodes in the global entry_guards list. See control-spec.txt
- * for details.
- * For backward compatibility, we also handle the string "helper-nodes".
- * */
+/**
+ * Pick a suitable entry guard for a circuit in, and place that guard
+ * in *<b>chosen_node_out</b>. Set *<b>guard_state_out</b> to an opaque
+ * state object that will record whether the circuit is ready to be used
+ * or not. Return 0 on success; on failure, return -1.
+ *
+ * If a restriction is provided in <b>rst</b>, do not return any guards that
+ * violate it, and remember that restriction in <b>guard_state_out</b> for
+ * later use. (Takes ownership of the <b>rst</b> object.)
+ */
int
-getinfo_helper_entry_guards(control_connection_t *conn,
- const char *question, char **answer,
- const char **errmsg)
-{
- (void) conn;
- (void) errmsg;
+entry_guard_pick_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ entry_guard_restriction_t *rst,
+ const node_t **chosen_node_out,
+ circuit_guard_state_t **guard_state_out)
+{
+ tor_assert(gs);
+ tor_assert(chosen_node_out);
+ tor_assert(guard_state_out);
+ *chosen_node_out = NULL;
+ *guard_state_out = NULL;
+
+ unsigned state = 0;
+ entry_guard_t *guard =
+ select_entry_guard_for_circuit(gs, usage, rst, &state);
+ if (! guard)
+ goto fail;
+ if (BUG(state == 0))
+ goto fail;
+ const node_t *node = node_get_by_id(guard->identity);
+ // XXXX #20827 check Ed ID.
+ if (! node)
+ goto fail;
+ if (BUG(usage != GUARD_USAGE_DIRGUARD && !node_has_descriptor(node)))
+ goto fail;
+
+ *chosen_node_out = node;
+ *guard_state_out = tor_malloc_zero(sizeof(circuit_guard_state_t));
+ (*guard_state_out)->guard = entry_guard_handle_new(guard);
+ (*guard_state_out)->state = state;
+ (*guard_state_out)->state_set_at = approx_time();
+ (*guard_state_out)->restrictions = rst;
- if (!strcmp(question,"entry-guards") ||
- !strcmp(question,"helper-nodes")) {
- smartlist_t *sl = smartlist_new();
- char tbuf[ISO_TIME_LEN+1];
- char nbuf[MAX_VERBOSE_NICKNAME_LEN+1];
- if (!entry_guards)
- entry_guards = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- const char *status = NULL;
- time_t when = 0;
- const node_t *node;
-
- if (!e->made_contact) {
- status = "never-connected";
- } else if (e->bad_since) {
- when = e->bad_since;
- status = "unusable";
- } else if (e->unreachable_since) {
- when = e->unreachable_since;
- status = "down";
- } else {
- status = "up";
- }
-
- node = node_get_by_id(e->identity);
- if (node) {
- node_get_verbose_nickname(node, nbuf);
- } else {
- nbuf[0] = '$';
- base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN);
- /* e->nickname field is not very reliable if we don't know about
- * this router any longer; don't include it. */
- }
-
- if (when) {
- format_iso_time(tbuf, when);
- smartlist_add_asprintf(sl, "%s %s %s\n", nbuf, status, tbuf);
- } else {
- smartlist_add_asprintf(sl, "%s %s\n", nbuf, status);
- }
- } SMARTLIST_FOREACH_END(e);
- *answer = smartlist_join_strings(sl, "", 0, NULL);
- SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
- smartlist_free(sl);
- }
return 0;
+ fail:
+ entry_guard_restriction_free(rst);
+ return -1;
}
-/** Return 0 if we should apply guardfraction information found in the
- * consensus. A specific consensus can be specified with the
- * <b>ns</b> argument, if NULL the most recent one will be picked.*/
-int
-should_apply_guardfraction(const networkstatus_t *ns)
+/**
+ * Called by the circuit building module when a circuit has succeeded: informs
+ * the guards code that the guard in *<b>guard_state_p</b> is working, and
+ * advances the state of the guard module. On a GUARD_USABLE_NEVER return
+ * value, the circuit is broken and should not be used. On a GUARD_USABLE_NOW
+ * return value, the circuit is ready to use. On a GUARD_MAYBE_USABLE_LATER
+ * return value, the circuit should not be used until we find out whether
+ * preferred guards will work for us.
+ */
+guard_usable_t
+entry_guard_succeeded(circuit_guard_state_t **guard_state_p)
{
- /* We need to check the corresponding torrc option and the consensus
- * parameter if we need to. */
- const or_options_t *options = get_options();
+ if (BUG(*guard_state_p == NULL))
+ return GUARD_USABLE_NEVER;
- /* If UseGuardFraction is 'auto' then check the same-named consensus
- * parameter. If the consensus parameter is not present, default to
- * "off". */
- if (options->UseGuardFraction == -1) {
- return networkstatus_get_param(ns, "UseGuardFraction",
- 0, /* default to "off" */
- 0, 1);
+ entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard);
+ if (! guard || BUG(guard->in_selection == NULL))
+ return GUARD_USABLE_NEVER;
+
+ unsigned newstate =
+ entry_guards_note_guard_success(guard->in_selection, guard,
+ (*guard_state_p)->state);
+
+ (*guard_state_p)->state = newstate;
+ (*guard_state_p)->state_set_at = approx_time();
+
+ if (newstate == GUARD_CIRC_STATE_COMPLETE) {
+ return GUARD_USABLE_NOW;
+ } else {
+ return GUARD_MAYBE_USABLE_LATER;
}
+}
- return options->UseGuardFraction;
+/** Cancel the selection of *<b>guard_state_p</b> without declaring
+ * success or failure. It is safe to call this function if success or
+ * failure _has_ already been declared. */
+void
+entry_guard_cancel(circuit_guard_state_t **guard_state_p)
+{
+ if (BUG(*guard_state_p == NULL))
+ return;
+ entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard);
+ if (! guard)
+ return;
+
+ /* XXXX prop271 -- last_tried_to_connect_at will be erroneous here, but this
+ * function will only get called in "bug" cases anyway. */
+ guard->is_pending = 0;
+ circuit_guard_state_free(*guard_state_p);
+ *guard_state_p = NULL;
}
-/* Given the original bandwidth of a guard and its guardfraction,
- * calculate how much bandwidth the guard should have as a guard and
- * as a non-guard.
- *
- * Quoting from proposal236:
- *
- * Let Wpf denote the weight from the 'bandwidth-weights' line a
- * client would apply to N for position p if it had the guard
- * flag, Wpn the weight if it did not have the guard flag, and B the
- * measured bandwidth of N in the consensus. Then instead of choosing
- * N for position p proportionally to Wpf*B or Wpn*B, clients should
- * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B.
- *
- * This function fills the <b>guardfraction_bw</b> structure. It sets
- * <b>guard_bw</b> to F*B and <b>non_guard_bw</b> to (1-F)*B.
+/**
+ * Called by the circuit building module when a circuit has succeeded:
+ * informs the guards code that the guard in *<b>guard_state_p</b> is
+ * not working, and advances the state of the guard module.
*/
void
-guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
- int orig_bandwidth,
- uint32_t guardfraction_percentage)
+entry_guard_failed(circuit_guard_state_t **guard_state_p)
{
- double guardfraction_fraction;
-
- /* Turn the percentage into a fraction. */
- tor_assert(guardfraction_percentage <= 100);
- guardfraction_fraction = guardfraction_percentage / 100.0;
+ if (BUG(*guard_state_p == NULL))
+ return;
- long guard_bw = tor_lround(guardfraction_fraction * orig_bandwidth);
- tor_assert(guard_bw <= INT_MAX);
+ entry_guard_t *guard = entry_guard_handle_get((*guard_state_p)->guard);
+ if (! guard || BUG(guard->in_selection == NULL))
+ return;
- guardfraction_bw->guard_bw = (int) guard_bw;
+ entry_guards_note_guard_failure(guard->in_selection, guard);
- guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw;
+ (*guard_state_p)->state = GUARD_CIRC_STATE_DEAD;
+ (*guard_state_p)->state_set_at = approx_time();
}
-/** 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
- * in this list does not necessarily correspond to the order of bridges
- * in the torrc. */
-static smartlist_t *bridge_list = NULL;
-
-/** Mark every entry of the bridge list to be removed on our next call to
- * sweep_bridge_list unless it has first been un-marked. */
+/**
+ * Run the entry_guard_failed() function on every circuit that is
+ * pending on <b>chan</b>.
+ */
void
-mark_bridge_list(void)
+entry_guard_chan_failed(channel_t *chan)
{
- if (!bridge_list)
- bridge_list = smartlist_new();
- SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b,
- b->marked_for_removal = 1);
-}
+ if (!chan)
+ return;
-/** Remove every entry of the bridge list that was marked with
- * mark_bridge_list if it has not subsequently been un-marked. */
-void
-sweep_bridge_list(void)
-{
- if (!bridge_list)
- bridge_list = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
- if (b->marked_for_removal) {
- SMARTLIST_DEL_CURRENT(bridge_list, b);
- bridge_free(b);
+ smartlist_t *pending = smartlist_new();
+ circuit_get_all_pending_on_channel(pending, chan);
+ SMARTLIST_FOREACH_BEGIN(pending, circuit_t *, circ) {
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ continue;
+
+ origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ if (origin_circ->guard_state) {
+ /* We might have no guard state if we didn't use a guard on this
+ * circuit (eg it's for a fallback directory). */
+ entry_guard_failed(&origin_circ->guard_state);
}
- } SMARTLIST_FOREACH_END(b);
+ } SMARTLIST_FOREACH_END(circ);
+ smartlist_free(pending);
}
-/** Initialize the bridge list to empty, creating it if needed. */
-static void
-clear_bridge_list(void)
+/**
+ * Return true iff every primary guard in <b>gs</b> is believed to
+ * be unreachable.
+ */
+STATIC int
+entry_guards_all_primary_guards_are_down(guard_selection_t *gs)
+{
+ tor_assert(gs);
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (guard->is_reachable != GUARD_REACHABLE_NO)
+ return 0;
+ } SMARTLIST_FOREACH_END(guard);
+ return 1;
+}
+
+/** Wrapper for entry_guard_has_higher_priority that compares the
+ * guard-priorities of a pair of circuits. Return 1 if <b>a</b> has higher
+ * priority than <b>b</b>.
+ *
+ * If a restriction is provided in <b>rst</b>, then do not consider
+ * <b>a</b> to have higher priority if it violates the restriction.
+ */
+static int
+circ_state_has_higher_priority(origin_circuit_t *a,
+ const entry_guard_restriction_t *rst,
+ origin_circuit_t *b)
{
- if (!bridge_list)
- bridge_list = smartlist_new();
- SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b));
- smartlist_clear(bridge_list);
-}
+ circuit_guard_state_t *state_a = origin_circuit_get_guard_state(a);
+ circuit_guard_state_t *state_b = origin_circuit_get_guard_state(b);
-/** Free the bridge <b>bridge</b>. */
-static void
-bridge_free(bridge_info_t *bridge)
-{
- if (!bridge)
- return;
+ tor_assert(state_a);
+ tor_assert(state_b);
- tor_free(bridge->transport_name);
- if (bridge->socks_args) {
- SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s));
- smartlist_free(bridge->socks_args);
- }
+ entry_guard_t *guard_a = entry_guard_handle_get(state_a->guard);
+ entry_guard_t *guard_b = entry_guard_handle_get(state_b->guard);
- tor_free(bridge);
+ if (! guard_a) {
+ /* Unknown guard -- never higher priority. */
+ return 0;
+ } else if (! guard_b) {
+ /* Known guard -- higher priority than any unknown guard. */
+ return 1;
+ } else if (! entry_guard_obeys_restriction(guard_a, rst)) {
+ /* Restriction violated; guard_a cannot have higher priority. */
+ return 0;
+ } else {
+ /* Both known -- compare.*/
+ return entry_guard_has_higher_priority(guard_a, guard_b);
+ }
}
-/** If we have a bridge configured whose digest matches <b>digest</b>, or a
- * bridge with no known digest whose address matches any of the
- * tor_addr_port_t's in <b>orports</b>, return that bridge. Else return
- * NULL. */
-static bridge_info_t *
-get_configured_bridge_by_orports_digest(const char *digest,
- const smartlist_t *orports)
-{
- if (!bridge_list)
- return NULL;
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
- {
- if (tor_digest_is_zero(bridge->identity)) {
- SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap)
- {
- if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 &&
- bridge->port == ap->port)
- return bridge;
- }
- SMARTLIST_FOREACH_END(ap);
+/**
+ * Look at all of the origin_circuit_t * objects in <b>all_circuits_in</b>,
+ * and see if any of them that were previously not ready to use for
+ * guard-related reasons are now ready to use. Place those circuits
+ * in <b>newly_complete_out</b>, and mark them COMPLETE.
+ *
+ * Return 1 if we upgraded any circuits, and 0 otherwise.
+ */
+int
+entry_guards_upgrade_waiting_circuits(guard_selection_t *gs,
+ const smartlist_t *all_circuits_in,
+ smartlist_t *newly_complete_out)
+{
+ tor_assert(gs);
+ tor_assert(all_circuits_in);
+ tor_assert(newly_complete_out);
+
+ if (! entry_guards_all_primary_guards_are_down(gs)) {
+ /* We only upgrade a waiting circuit if the primary guards are all
+ * down. */
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, "
+ "but not all primary guards were definitely down.");
+ return 0;
+ }
+
+ int n_waiting = 0;
+ int n_complete = 0;
+ int n_complete_blocking = 0;
+ origin_circuit_t *best_waiting_circuit = NULL;
+ smartlist_t *all_circuits = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(all_circuits_in, origin_circuit_t *, circ) {
+ // We filter out circuits that aren't ours, or which we can't
+ // reason about.
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (state == NULL)
+ continue;
+ entry_guard_t *guard = entry_guard_handle_get(state->guard);
+ if (!guard || guard->in_selection != gs)
+ continue;
+
+ smartlist_add(all_circuits, circ);
+ } SMARTLIST_FOREACH_END(circ);
+
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (BUG(state == NULL))
+ continue;
+
+ if (state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD) {
+ ++n_waiting;
+ if (! best_waiting_circuit ||
+ circ_state_has_higher_priority(circ, NULL, best_waiting_circuit)) {
+ best_waiting_circuit = circ;
}
- if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
- return bridge;
}
- SMARTLIST_FOREACH_END(bridge);
- return NULL;
-}
+ } SMARTLIST_FOREACH_END(circ);
-/** If we have a bridge configured whose digest matches <b>digest</b>, or a
- * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
- * return that bridge. Else return NULL. If <b>digest</b> is NULL, check for
- * address/port matches only. */
-static bridge_info_t *
-get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr,
- uint16_t port,
- const char *digest)
-{
- if (!bridge_list)
- return NULL;
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
- {
- if ((tor_digest_is_zero(bridge->identity) || digest == NULL) &&
- !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) &&
- bridge->port == port)
- return bridge;
- if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN))
- return bridge;
+ if (! best_waiting_circuit) {
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits, "
+ "but didn't find any.");
+ goto no_change;
+ }
+
+ /* We'll need to keep track of what restrictions were used when picking this
+ * circuit, so that we don't allow any circuit without those restrictions to
+ * block it. */
+ const entry_guard_restriction_t *rst_on_best_waiting =
+ origin_circuit_get_guard_state(best_waiting_circuit)->restrictions;
+
+ /* First look at the complete circuits: Do any block this circuit? */
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ /* "C2 "blocks" C1 if:
+ * C2 obeys all the restrictions that C1 had to obey, AND
+ * C2 has higher priority than C1, AND
+ * Either C2 is <complete>, or C2 is <waiting_for_better_guard>,
+ or C2 has been <usable_if_no_better_guard> for no more than
+ {NONPRIMARY_GUARD_CONNECT_TIMEOUT} seconds."
+ */
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if BUG((state == NULL))
+ continue;
+ if (state->state != GUARD_CIRC_STATE_COMPLETE)
+ continue;
+ ++n_complete;
+ if (circ_state_has_higher_priority(circ, rst_on_best_waiting,
+ best_waiting_circuit))
+ ++n_complete_blocking;
+ } SMARTLIST_FOREACH_END(circ);
+
+ if (n_complete_blocking) {
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found "
+ "%d complete and %d guard-stalled. At least one complete "
+ "circuit had higher priority, so not upgrading.",
+ n_complete, n_waiting);
+ goto no_change;
+ }
+
+ /* " * If any circuit C1 is <waiting_for_better_guard>, AND:
+ * All primary guards have reachable status of <no>.
+ * There is no circuit C2 that "blocks" C1.
+ Then, upgrade C1 to <complete>.""
+ */
+ int n_blockers_found = 0;
+ const time_t state_set_at_cutoff =
+ approx_time() - get_nonprimary_guard_connect_timeout();
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (BUG(state == NULL))
+ continue;
+ if (state->state != GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD)
+ continue;
+ if (state->state_set_at <= state_set_at_cutoff)
+ continue;
+ if (circ_state_has_higher_priority(circ, rst_on_best_waiting,
+ best_waiting_circuit))
+ ++n_blockers_found;
+ } SMARTLIST_FOREACH_END(circ);
+
+ if (n_blockers_found) {
+ log_debug(LD_GUARD, "Considered upgrading guard-stalled circuits: found "
+ "%d guard-stalled, but %d pending circuit(s) had higher "
+ "guard priority, so not upgrading.",
+ n_waiting, n_blockers_found);
+ goto no_change;
+ }
+
+ /* Okay. We have a best waiting circuit, and we aren't waiting for
+ anything better. Add all circuits with that priority to the
+ list, and call them COMPLETE. */
+ int n_succeeded = 0;
+ SMARTLIST_FOREACH_BEGIN(all_circuits, origin_circuit_t *, circ) {
+ circuit_guard_state_t *state = origin_circuit_get_guard_state(circ);
+ if (BUG(state == NULL))
+ continue;
+ if (circ != best_waiting_circuit && rst_on_best_waiting) {
+ /* Can't upgrade other circ with same priority as best; might
+ be blocked. */
+ continue;
}
- SMARTLIST_FOREACH_END(bridge);
- return NULL;
-}
+ if (state->state != GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD)
+ continue;
+ if (circ_state_has_higher_priority(best_waiting_circuit, NULL, circ))
+ continue;
-/** If we have a bridge configured whose digest matches <b>digest</b>, or a
- * bridge with no known digest whose address matches <b>addr</b>:<b>port</b>,
- * return 1. Else return 0. If <b>digest</b> is NULL, check for
- * address/port matches only. */
-int
-addr_is_a_configured_bridge(const tor_addr_t *addr,
- uint16_t port,
- const char *digest)
-{
- tor_assert(addr);
- return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0;
-}
+ state->state = GUARD_CIRC_STATE_COMPLETE;
+ state->state_set_at = approx_time();
+ smartlist_add(newly_complete_out, circ);
+ ++n_succeeded;
+ } SMARTLIST_FOREACH_END(circ);
-/** If we have a bridge configured whose digest matches
- * <b>ei->identity_digest</b>, or a bridge with no known digest whose address
- * matches <b>ei->addr</b>:<b>ei->port</b>, return 1. Else return 0.
- * If <b>ei->onion_key</b> is NULL, check for address/port matches only. */
-int
-extend_info_is_a_configured_bridge(const extend_info_t *ei)
-{
- const char *digest = ei->onion_key ? ei->identity_digest : NULL;
- return addr_is_a_configured_bridge(&ei->addr, ei->port, digest);
-}
+ log_info(LD_GUARD, "Considered upgrading guard-stalled circuits: found "
+ "%d guard-stalled, %d complete. %d of the guard-stalled "
+ "circuit(s) had high enough priority to upgrade.",
+ n_waiting, n_complete, n_succeeded);
-/** Wrapper around get_configured_bridge_by_addr_port_digest() to look
- * it up via router descriptor <b>ri</b>. */
-static bridge_info_t *
-get_configured_bridge_by_routerinfo(const routerinfo_t *ri)
-{
- bridge_info_t *bi = NULL;
- smartlist_t *orports = router_get_all_orports(ri);
- bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest,
- orports);
- SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
- smartlist_free(orports);
- return bi;
+ tor_assert_nonfatal(n_succeeded >= 1);
+ smartlist_free(all_circuits);
+ return 1;
+
+ no_change:
+ smartlist_free(all_circuits);
+ return 0;
}
-/** Return 1 if <b>ri</b> is one of our known bridges, else 0. */
+/**
+ * Return true iff the circuit whose state is <b>guard_state</b> should
+ * expire.
+ */
int
-routerinfo_is_a_configured_bridge(const routerinfo_t *ri)
+entry_guard_state_should_expire(circuit_guard_state_t *guard_state)
{
- return get_configured_bridge_by_routerinfo(ri) ? 1 : 0;
+ if (guard_state == NULL)
+ return 0;
+ const time_t expire_if_waiting_since =
+ approx_time() - get_nonprimary_guard_idle_timeout();
+ return (guard_state->state == GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD
+ && guard_state->state_set_at < expire_if_waiting_since);
}
-/** Return 1 if <b>node</b> is one of our configured bridges, else 0. */
+/**
+ * Update all derived pieces of the guard selection state in <b>gs</b>.
+ * Return true iff we should stop using all previously generated circuits.
+ */
int
-node_is_a_configured_bridge(const node_t *node)
+entry_guards_update_all(guard_selection_t *gs)
{
- int retval = 0;
- smartlist_t *orports = node_get_all_orports(node);
- retval = get_configured_bridge_by_orports_digest(node->identity,
- orports) != NULL;
- SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p));
- smartlist_free(orports);
- return retval;
+ sampled_guards_update_from_consensus(gs);
+ entry_guards_update_filtered_sets(gs);
+ entry_guards_update_confirmed(gs);
+ entry_guards_update_primary(gs);
+ return 0;
}
-/** We made a connection to a router at <b>addr</b>:<b>port</b>
- * without knowing its digest. Its digest turned out to be <b>digest</b>.
- * If it was a bridge, and we still don't know its digest, record it.
+/**
+ * Return a newly allocated string for encoding the persistent parts of
+ * <b>guard</b> to the state file.
*/
-void
-learned_router_identity(const tor_addr_t *addr, uint16_t port,
- const char *digest)
-{
- bridge_info_t *bridge =
- get_configured_bridge_by_addr_port_digest(addr, port, digest);
- if (bridge && tor_digest_is_zero(bridge->identity)) {
- char *transport_info = NULL;
- const char *transport_name =
- find_transport_name_by_bridge_addrport(addr, port);
- if (transport_name)
- tor_asprintf(&transport_info, " (with transport '%s')", transport_name);
+STATIC char *
+entry_guard_encode_for_state(entry_guard_t *guard)
+{
+ /*
+ * The meta-format we use is K=V K=V K=V... where K can be any
+ * characters excepts space and =, and V can be any characters except
+ * space. The order of entries is not allowed to matter.
+ * Unrecognized K=V entries are persisted; recognized but erroneous
+ * entries are corrected.
+ */
- memcpy(bridge->identity, digest, DIGEST_LEN);
- log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.",
- hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port),
- transport_info ? transport_info : "");
- tor_free(transport_info);
+ smartlist_t *result = smartlist_new();
+ char tbuf[ISO_TIME_LEN+1];
+
+ tor_assert(guard);
+
+ smartlist_add_asprintf(result, "in=%s", guard->selection_name);
+ smartlist_add_asprintf(result, "rsa_id=%s",
+ hex_str(guard->identity, DIGEST_LEN));
+ if (guard->bridge_addr) {
+ smartlist_add_asprintf(result, "bridge_addr=%s:%d",
+ fmt_and_decorate_addr(&guard->bridge_addr->addr),
+ guard->bridge_addr->port);
+ }
+ if (strlen(guard->nickname) && is_legal_nickname(guard->nickname)) {
+ smartlist_add_asprintf(result, "nickname=%s", guard->nickname);
}
-}
-/** Return true if <b>bridge</b> has the same identity digest as
- * <b>digest</b>. If <b>digest</b> is NULL, it matches
- * bridges with unspecified identity digests. */
-static int
-bridge_has_digest(const bridge_info_t *bridge, const char *digest)
-{
- if (digest)
- return tor_memeq(digest, bridge->identity, DIGEST_LEN);
- else
- return tor_digest_is_zero(bridge->identity);
+ format_iso_time_nospace(tbuf, guard->sampled_on_date);
+ smartlist_add_asprintf(result, "sampled_on=%s", tbuf);
+
+ if (guard->sampled_by_version) {
+ smartlist_add_asprintf(result, "sampled_by=%s",
+ guard->sampled_by_version);
+ }
+
+ if (guard->unlisted_since_date > 0) {
+ format_iso_time_nospace(tbuf, guard->unlisted_since_date);
+ smartlist_add_asprintf(result, "unlisted_since=%s", tbuf);
+ }
+
+ smartlist_add_asprintf(result, "listed=%d",
+ (int)guard->currently_listed);
+
+ if (guard->confirmed_idx >= 0) {
+ format_iso_time_nospace(tbuf, guard->confirmed_on_date);
+ smartlist_add_asprintf(result, "confirmed_on=%s", tbuf);
+
+ smartlist_add_asprintf(result, "confirmed_idx=%d", guard->confirmed_idx);
+ }
+
+ const double EPSILON = 1.0e-6;
+
+ /* Make a copy of the pathbias object, since we will want to update
+ some of them */
+ guard_pathbias_t *pb = tor_memdup(&guard->pb, sizeof(*pb));
+ pb->use_successes = pathbias_get_use_success_count(guard);
+ pb->successful_circuits_closed = pathbias_get_close_success_count(guard);
+
+ #define PB_FIELD(field) do { \
+ if (pb->field >= EPSILON) { \
+ smartlist_add_asprintf(result, "pb_" #field "=%f", pb->field); \
+ } \
+ } while (0)
+ PB_FIELD(use_attempts);
+ PB_FIELD(use_successes);
+ PB_FIELD(circ_attempts);
+ PB_FIELD(circ_successes);
+ PB_FIELD(successful_circuits_closed);
+ PB_FIELD(collapsed_circuits);
+ PB_FIELD(unusable_circuits);
+ PB_FIELD(timeouts);
+ tor_free(pb);
+#undef PB_FIELD
+
+ if (guard->extra_state_fields)
+ smartlist_add_strdup(result, guard->extra_state_fields);
+
+ char *joined = smartlist_join_strings(result, " ", 0, NULL);
+ SMARTLIST_FOREACH(result, char *, cp, tor_free(cp));
+ smartlist_free(result);
+
+ return joined;
}
-/** We are about to add a new bridge at <b>addr</b>:<b>port</b>, with optional
- * <b>digest</b> and <b>transport_name</b>. Mark for removal any previously
- * existing bridge with the same address and port, and warn the user as
- * appropriate.
+/**
+ * Given a string generated by entry_guard_encode_for_state(), parse it
+ * (if possible) and return an entry_guard_t object for it. Return NULL
+ * on complete failure.
*/
-static void
-bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port,
- const char *digest, const char *transport_name)
-{
- /* Iterate the already-registered bridge list:
+STATIC entry_guard_t *
+entry_guard_parse_from_state(const char *s)
+{
+ /* Unrecognized entries get put in here. */
+ smartlist_t *extra = smartlist_new();
+
+ /* These fields get parsed from the string. */
+ char *in = NULL;
+ char *rsa_id = NULL;
+ char *nickname = NULL;
+ char *sampled_on = NULL;
+ char *sampled_by = NULL;
+ char *unlisted_since = NULL;
+ char *listed = NULL;
+ char *confirmed_on = NULL;
+ char *confirmed_idx = NULL;
+ char *bridge_addr = NULL;
+
+ // pathbias
+ char *pb_use_attempts = NULL;
+ char *pb_use_successes = NULL;
+ char *pb_circ_attempts = NULL;
+ char *pb_circ_successes = NULL;
+ char *pb_successful_circuits_closed = NULL;
+ char *pb_collapsed_circuits = NULL;
+ char *pb_unusable_circuits = NULL;
+ char *pb_timeouts = NULL;
+
+ /* Split up the entries. Put the ones we know about in strings and the
+ * rest in "extra". */
+ {
+ smartlist_t *entries = smartlist_new();
+
+ strmap_t *vals = strmap_new(); // Maps keyword to location
+#define FIELD(f) \
+ strmap_set(vals, #f, &f);
+ FIELD(in);
+ FIELD(rsa_id);
+ FIELD(nickname);
+ FIELD(sampled_on);
+ FIELD(sampled_by);
+ FIELD(unlisted_since);
+ FIELD(listed);
+ FIELD(confirmed_on);
+ FIELD(confirmed_idx);
+ FIELD(bridge_addr);
+ FIELD(pb_use_attempts);
+ FIELD(pb_use_successes);
+ FIELD(pb_circ_attempts);
+ FIELD(pb_circ_successes);
+ FIELD(pb_successful_circuits_closed);
+ FIELD(pb_collapsed_circuits);
+ FIELD(pb_unusable_circuits);
+ FIELD(pb_timeouts);
+#undef FIELD
+
+ smartlist_split_string(entries, s, " ",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+
+ SMARTLIST_FOREACH_BEGIN(entries, char *, entry) {
+ const char *eq = strchr(entry, '=');
+ if (!eq) {
+ smartlist_add(extra, entry);
+ continue;
+ }
+ char *key = tor_strndup(entry, eq-entry);
+ char **target = strmap_get(vals, key);
+ if (target == NULL || *target != NULL) {
+ /* unrecognized or already set */
+ smartlist_add(extra, entry);
+ tor_free(key);
+ continue;
+ }
- If you find a bridge with the same adress and port, mark it for
- removal. It doesn't make sense to have two active bridges with
- the same IP:PORT. If the bridge in question has a different
- digest or transport than <b>digest</b>/<b>transport_name</b>,
- it's probably a misconfiguration and we should warn the user.
- */
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) {
- if (bridge->marked_for_removal)
- continue;
+ *target = tor_strdup(eq+1);
+ tor_free(key);
+ tor_free(entry);
+ } SMARTLIST_FOREACH_END(entry);
- if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) {
-
- bridge->marked_for_removal = 1;
-
- if (!bridge_has_digest(bridge, digest) ||
- strcmp_opt(bridge->transport_name, transport_name)) {
- /* warn the user */
- char *bridge_description_new, *bridge_description_old;
- tor_asprintf(&bridge_description_new, "%s:%s:%s",
- fmt_addrport(addr, port),
- digest ? hex_str(digest, DIGEST_LEN) : "",
- transport_name ? transport_name : "");
- tor_asprintf(&bridge_description_old, "%s:%s:%s",
- fmt_addrport(&bridge->addr, bridge->port),
- tor_digest_is_zero(bridge->identity) ?
- "" : hex_str(bridge->identity,DIGEST_LEN),
- bridge->transport_name ? bridge->transport_name : "");
-
- log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict"
- " with the already registered bridge '%s'. We will discard"
- " the old bridge and keep '%s'. If this is not what you"
- " wanted, please change your configuration file accordingly.",
- bridge_description_new, bridge_description_old,
- bridge_description_new);
-
- tor_free(bridge_description_new);
- tor_free(bridge_description_old);
- }
- }
- } SMARTLIST_FOREACH_END(bridge);
-}
+ smartlist_free(entries);
+ strmap_free(vals, NULL);
+ }
-/** Return True if we have a bridge that uses a transport with name
- * <b>transport_name</b>. */
-MOCK_IMPL(int,
-transport_is_needed, (const char *transport_name))
-{
- if (!bridge_list)
- return 0;
+ entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t));
+ guard->is_persistent = 1;
- SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
- if (bridge->transport_name &&
- !strcmp(bridge->transport_name, transport_name))
- return 1;
- } SMARTLIST_FOREACH_END(bridge);
+ if (in == NULL) {
+ log_warn(LD_CIRC, "Guard missing 'in' field");
+ goto err;
+ }
- return 0;
-}
+ guard->selection_name = in;
+ in = NULL;
-/** Register the bridge information in <b>bridge_line</b> to the
- * bridge subsystem. Steals reference of <b>bridge_line</b>. */
-void
-bridge_add_from_config(bridge_line_t *bridge_line)
-{
- bridge_info_t *b;
+ if (rsa_id == NULL) {
+ log_warn(LD_CIRC, "Guard missing RSA ID field");
+ goto err;
+ }
- { /* Log the bridge we are about to register: */
- log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)",
- fmt_addrport(&bridge_line->addr, bridge_line->port),
- bridge_line->transport_name ?
- bridge_line->transport_name : "no transport",
- tor_digest_is_zero(bridge_line->digest) ?
- "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN));
+ /* Process the identity and nickname. */
+ if (base16_decode(guard->identity, sizeof(guard->identity),
+ rsa_id, strlen(rsa_id)) != DIGEST_LEN) {
+ log_warn(LD_CIRC, "Unable to decode guard identity %s", escaped(rsa_id));
+ goto err;
+ }
- if (bridge_line->socks_args) { /* print socks arguments */
- int i = 0;
+ if (nickname) {
+ strlcpy(guard->nickname, nickname, sizeof(guard->nickname));
+ } else {
+ guard->nickname[0]='$';
+ base16_encode(guard->nickname+1, sizeof(guard->nickname)-1,
+ guard->identity, DIGEST_LEN);
+ }
- tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+ if (bridge_addr) {
+ tor_addr_port_t res;
+ memset(&res, 0, sizeof(res));
+ int r = tor_addr_port_parse(LOG_WARN, bridge_addr,
+ &res.addr, &res.port, -1);
+ if (r == 0)
+ guard->bridge_addr = tor_memdup(&res, sizeof(res));
+ /* On error, we already warned. */
+ }
- log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:",
- smartlist_len(bridge_line->socks_args));
- SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg,
- log_debug(LD_CONFIG, "%d: %s", ++i, arg));
+ /* Process the various time fields. */
+
+#define HANDLE_TIME(field) do { \
+ if (field) { \
+ int r = parse_iso_time_nospace(field, &field ## _time); \
+ if (r < 0) { \
+ log_warn(LD_CIRC, "Unable to parse %s %s from guard", \
+ #field, escaped(field)); \
+ field##_time = -1; \
+ } \
+ } \
+ } while (0)
+
+ time_t sampled_on_time = 0;
+ time_t unlisted_since_time = 0;
+ time_t confirmed_on_time = 0;
+
+ HANDLE_TIME(sampled_on);
+ HANDLE_TIME(unlisted_since);
+ HANDLE_TIME(confirmed_on);
+
+ if (sampled_on_time <= 0)
+ sampled_on_time = approx_time();
+ if (unlisted_since_time < 0)
+ unlisted_since_time = 0;
+ if (confirmed_on_time < 0)
+ confirmed_on_time = 0;
+
+ #undef HANDLE_TIME
+
+ guard->sampled_on_date = sampled_on_time;
+ guard->unlisted_since_date = unlisted_since_time;
+ guard->confirmed_on_date = confirmed_on_time;
+
+ /* Take sampled_by_version verbatim. */
+ guard->sampled_by_version = sampled_by;
+ sampled_by = NULL; /* prevent free */
+
+ /* Listed is a boolean */
+ if (listed && strcmp(listed, "0"))
+ guard->currently_listed = 1;
+
+ /* The index is a nonnegative integer. */
+ guard->confirmed_idx = -1;
+ if (confirmed_idx) {
+ int ok=1;
+ long idx = tor_parse_long(confirmed_idx, 10, 0, INT_MAX, &ok, NULL);
+ if (! ok) {
+ log_warn(LD_GUARD, "Guard has invalid confirmed_idx %s",
+ escaped(confirmed_idx));
+ } else {
+ guard->confirmed_idx = (int)idx;
}
}
- bridge_resolve_conflicts(&bridge_line->addr,
- bridge_line->port,
- bridge_line->digest,
- bridge_line->transport_name);
+ /* Anything we didn't recognize gets crammed together */
+ if (smartlist_len(extra) > 0) {
+ guard->extra_state_fields = smartlist_join_strings(extra, " ", 0, NULL);
+ }
- b = tor_malloc_zero(sizeof(bridge_info_t));
- tor_addr_copy(&b->addr, &bridge_line->addr);
- b->port = bridge_line->port;
- memcpy(b->identity, bridge_line->digest, DIGEST_LEN);
- if (bridge_line->transport_name)
- b->transport_name = bridge_line->transport_name;
- b->fetch_status.schedule = DL_SCHED_BRIDGE;
- b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL;
- b->socks_args = bridge_line->socks_args;
- if (!bridge_list)
- bridge_list = smartlist_new();
+ /* initialize non-persistent fields */
+ guard->is_reachable = GUARD_REACHABLE_MAYBE;
+
+#define PB_FIELD(field) \
+ do { \
+ if (pb_ ## field) { \
+ int ok = 1; \
+ double r = tor_parse_double(pb_ ## field, 0.0, 1e9, &ok, NULL); \
+ if (! ok) { \
+ log_warn(LD_CIRC, "Guard has invalid pb_%s %s", \
+ #field, pb_ ## field); \
+ } else { \
+ guard->pb.field = r; \
+ } \
+ } \
+ } while (0)
+ PB_FIELD(use_attempts);
+ PB_FIELD(use_successes);
+ PB_FIELD(circ_attempts);
+ PB_FIELD(circ_successes);
+ PB_FIELD(successful_circuits_closed);
+ PB_FIELD(collapsed_circuits);
+ PB_FIELD(unusable_circuits);
+ PB_FIELD(timeouts);
+#undef PB_FIELD
+
+ pathbias_check_use_success_count(guard);
+ pathbias_check_close_success_count(guard);
+
+ /* We update everything on this guard later, after we've parsed
+ * everything. */
+
+ goto done;
+
+ err:
+ // only consider it an error if the guard state was totally unparseable.
+ entry_guard_free(guard);
+ guard = NULL;
- tor_free(bridge_line); /* Deallocate bridge_line now. */
+ done:
+ tor_free(in);
+ tor_free(rsa_id);
+ tor_free(nickname);
+ tor_free(sampled_on);
+ tor_free(sampled_by);
+ tor_free(unlisted_since);
+ tor_free(listed);
+ tor_free(confirmed_on);
+ tor_free(confirmed_idx);
+ tor_free(bridge_addr);
+ tor_free(pb_use_attempts);
+ tor_free(pb_use_successes);
+ tor_free(pb_circ_attempts);
+ tor_free(pb_circ_successes);
+ tor_free(pb_successful_circuits_closed);
+ tor_free(pb_collapsed_circuits);
+ tor_free(pb_unusable_circuits);
+ tor_free(pb_timeouts);
+
+ SMARTLIST_FOREACH(extra, char *, cp, tor_free(cp));
+ smartlist_free(extra);
+
+ return guard;
+}
- smartlist_add(bridge_list, b);
+/**
+ * Replace the Guards entries in <b>state</b> with a list of all our sampled
+ * guards.
+ */
+static void
+entry_guards_update_guards_in_state(or_state_t *state)
+{
+ if (!guard_contexts)
+ return;
+ config_line_t *lines = NULL;
+ config_line_t **nextline = &lines;
+
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (guard->is_persistent == 0)
+ continue;
+ *nextline = tor_malloc_zero(sizeof(config_line_t));
+ (*nextline)->key = tor_strdup("Guard");
+ (*nextline)->value = entry_guard_encode_for_state(guard);
+ nextline = &(*nextline)->next;
+ } SMARTLIST_FOREACH_END(guard);
+ } SMARTLIST_FOREACH_END(gs);
+
+ config_free_lines(state->Guard);
+ state->Guard = lines;
}
-/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */
+/**
+ * Replace our sampled guards from the Guards entries in <b>state</b>. Return 0
+ * on success, -1 on failure. (If <b>set</b> is true, replace nothing -- only
+ * check whether replacing would work.)
+ */
static int
-routerset_contains_bridge(const routerset_t *routerset,
- const bridge_info_t *bridge)
+entry_guards_load_guards_from_state(or_state_t *state, int set)
+{
+ const config_line_t *line = state->Guard;
+ int n_errors = 0;
+
+ if (!guard_contexts)
+ guard_contexts = smartlist_new();
+
+ /* Wipe all our existing guard info. (we shouldn't have any, but
+ * let's be safe.) */
+ if (set) {
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ guard_selection_free(gs);
+ if (curr_guard_context == gs)
+ curr_guard_context = NULL;
+ SMARTLIST_DEL_CURRENT(guard_contexts, gs);
+ } SMARTLIST_FOREACH_END(gs);
+ }
+
+ for ( ; line != NULL; line = line->next) {
+ entry_guard_t *guard = entry_guard_parse_from_state(line->value);
+ if (guard == NULL) {
+ ++n_errors;
+ continue;
+ }
+ tor_assert(guard->selection_name);
+ if (!strcmp(guard->selection_name, "legacy")) {
+ ++n_errors;
+ entry_guard_free(guard);
+ continue;
+ }
+
+ if (set) {
+ guard_selection_t *gs;
+ gs = get_guard_selection_by_name(guard->selection_name,
+ GS_TYPE_INFER, 1);
+ tor_assert(gs);
+ smartlist_add(gs->sampled_entry_guards, guard);
+ guard->in_selection = gs;
+ } else {
+ entry_guard_free(guard);
+ }
+ }
+
+ if (set) {
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ entry_guards_update_all(gs);
+ } SMARTLIST_FOREACH_END(gs);
+ }
+ return n_errors ? -1 : 0;
+}
+
+/** If <b>digest</b> matches the identity of any node in the
+ * entry_guards list for the provided guard selection state,
+ return that node. Else return NULL. */
+entry_guard_t *
+entry_guard_get_by_id_digest_for_guard_selection(guard_selection_t *gs,
+ const char *digest)
{
- int result;
- extend_info_t *extinfo;
- tor_assert(bridge);
- if (!routerset)
- return 0;
+ return get_sampled_guard_with_id(gs, (const uint8_t*)digest);
+}
- extinfo = extend_info_new(
- NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port);
- result = routerset_contains_extendinfo(routerset, extinfo);
- extend_info_free(extinfo);
- return result;
+/** Return the node_t associated with a single entry_guard_t. May
+ * return NULL if the guard is not currently in the consensus. */
+const node_t *
+entry_guard_find_node(const entry_guard_t *guard)
+{
+ tor_assert(guard);
+ return node_get_by_id(guard->identity);
}
-/** If <b>digest</b> is one of our known bridges, return it. */
-static bridge_info_t *
-find_bridge_by_digest(const char *digest)
+/** If <b>digest</b> matches the identity of any node in the
+ * entry_guards list for the default guard selection state,
+ return that node. Else return NULL. */
+entry_guard_t *
+entry_guard_get_by_id_digest(const char *digest)
{
- SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge,
- {
- if (tor_memeq(bridge->identity, digest, DIGEST_LEN))
- return bridge;
- });
- return NULL;
+ return entry_guard_get_by_id_digest_for_guard_selection(
+ get_guard_selection_info(), digest);
}
-/** Given the <b>addr</b> and <b>port</b> of a bridge, if that bridge
- * supports a pluggable transport, return its name. Otherwise, return
- * NULL. */
-const char *
-find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+/** We are about to connect to bridge with identity <b>digest</b> to fetch its
+ * descriptor. Create a new guard state for this connection and return it. */
+circuit_guard_state_t *
+get_guard_state_for_bridge_desc_fetch(const char *digest)
{
- if (!bridge_list)
+ circuit_guard_state_t *guard_state = NULL;
+ entry_guard_t *guard = NULL;
+
+ guard = entry_guard_get_by_id_digest_for_guard_selection(
+ get_guard_selection_info(), digest);
+ if (!guard) {
return NULL;
+ }
- SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
- if (tor_addr_eq(&bridge->addr, addr) &&
- (bridge->port == port))
- return bridge->transport_name;
- } SMARTLIST_FOREACH_END(bridge);
+ /* Update the guard last_tried_to_connect time since it's checked by the
+ * guard susbsystem. */
+ guard->last_tried_to_connect = approx_time();
- return NULL;
+ /* Create the guard state */
+ guard_state = tor_malloc_zero(sizeof(circuit_guard_state_t));
+ guard_state->guard = entry_guard_handle_new(guard);
+ guard_state->state = GUARD_CIRC_STATE_USABLE_ON_COMPLETION;
+ guard_state->state_set_at = approx_time();
+ guard_state->restrictions = NULL;
+
+ return guard_state;
}
-/** If <b>addr</b> and <b>port</b> match the address and port of a
- * bridge of ours that uses pluggable transports, place its transport
- * in <b>transport</b>.
- *
- * Return 0 on success (found a transport, or found a bridge with no
- * transport, or found no bridge); return -1 if we should be using a
- * transport, but the transport could not be found.
+/** Release all storage held by <b>e</b>. */
+STATIC void
+entry_guard_free(entry_guard_t *e)
+{
+ if (!e)
+ return;
+ entry_guard_handles_clear(e);
+ tor_free(e->sampled_by_version);
+ tor_free(e->extra_state_fields);
+ tor_free(e->selection_name);
+ tor_free(e->bridge_addr);
+ tor_free(e);
+}
+
+/** Return 0 if we're fine adding arbitrary routers out of the
+ * directory to our entry guard list, or return 1 if we have a
+ * list already and we must stick to it.
*/
int
-get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
- const transport_t **transport)
+entry_list_is_constrained(const or_options_t *options)
{
- *transport = NULL;
- if (!bridge_list)
- return 0;
-
- SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) {
- if (tor_addr_eq(&bridge->addr, addr) &&
- (bridge->port == port)) { /* bridge matched */
- if (bridge->transport_name) { /* it also uses pluggable transports */
- *transport = transport_get_by_name(bridge->transport_name);
- if (*transport == NULL) { /* it uses pluggable transports, but
- the transport could not be found! */
- return -1;
- }
- return 0;
- } else { /* bridge matched, but it doesn't use transports. */
- break;
- }
- }
- } SMARTLIST_FOREACH_END(bridge);
-
- *transport = NULL;
+ // XXXX #21425 look at the current selection.
+ if (options->EntryNodes)
+ return 1;
+ if (options->UseBridges)
+ return 1;
return 0;
}
-/** Return a smartlist containing all the SOCKS arguments that we
- * should pass to the SOCKS proxy. */
-const smartlist_t *
-get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port)
+/** Return the number of bridges that have descriptors that are marked with
+ * purpose 'bridge' and are running.
+ */
+int
+num_bridges_usable(void)
{
- bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr,
- port,
- NULL);
- return bridge ? bridge->socks_args : NULL;
+ int n_options = 0;
+
+ tor_assert(get_options()->UseBridges);
+ guard_selection_t *gs = get_guard_selection_info();
+ tor_assert(gs->type == GS_TYPE_BRIDGE);
+
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) {
+ if (guard->is_reachable == GUARD_REACHABLE_NO)
+ continue;
+ if (tor_digest_is_zero(guard->identity))
+ continue;
+ const node_t *node = node_get_by_id(guard->identity);
+ if (node && node->ri)
+ ++n_options;
+ } SMARTLIST_FOREACH_END(guard);
+
+ return n_options;
}
-/** We need to ask <b>bridge</b> for its server descriptor. */
+/** Check the pathbias use success count of <b>node</b> and disable it if it
+ * goes over our thresholds. */
static void
-launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge)
+pathbias_check_use_success_count(entry_guard_t *node)
{
const or_options_t *options = get_options();
-
- if (connection_get_by_type_addr_port_purpose(
- CONN_TYPE_DIR, &bridge->addr, bridge->port,
- DIR_PURPOSE_FETCH_SERVERDESC))
- return; /* it's already on the way */
-
- if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
- download_status_mark_impossible(&bridge->fetch_status);
- log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
- safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
- return;
+ const double EPSILON = 1.0e-9;
+
+ /* Note: We rely on the < comparison here to allow us to set a 0
+ * rate and disable the feature entirely. If refactoring, don't
+ * change to <= */
+ if (node->pb.use_attempts > EPSILON &&
+ pathbias_get_use_success_count(node)/node->pb.use_attempts
+ < pathbias_get_extreme_use_rate(options) &&
+ pathbias_get_dropguards(options)) {
+ node->pb.path_bias_disabled = 1;
+ log_info(LD_GENERAL,
+ "Path use bias is too high (%f/%f); disabling node %s",
+ node->pb.circ_successes, node->pb.circ_attempts,
+ node->nickname);
}
+}
- /* Until we get a descriptor for the bridge, we only know one address for
- * it. */
- if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
- FIREWALL_OR_CONNECTION, 0, 0)) {
- log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a "
- "bridge, but that bridge is not reachable through our "
- "firewall.");
- return;
+/** Check the pathbias close count of <b>node</b> and disable it if it goes
+ * over our thresholds. */
+static void
+pathbias_check_close_success_count(entry_guard_t *node)
+{
+ const or_options_t *options = get_options();
+ const double EPSILON = 1.0e-9;
+
+ /* Note: We rely on the < comparison here to allow us to set a 0
+ * rate and disable the feature entirely. If refactoring, don't
+ * change to <= */
+ if (node->pb.circ_attempts > EPSILON &&
+ pathbias_get_close_success_count(node)/node->pb.circ_attempts
+ < pathbias_get_extreme_rate(options) &&
+ pathbias_get_dropguards(options)) {
+ node->pb.path_bias_disabled = 1;
+ log_info(LD_GENERAL,
+ "Path bias is too high (%f/%f); disabling node %s",
+ node->pb.circ_successes, node->pb.circ_attempts,
+ node->nickname);
}
-
- directory_initiate_command(&bridge->addr, bridge->port,
- NULL, 0, /*no dirport*/
- bridge->identity,
- DIR_PURPOSE_FETCH_SERVERDESC,
- ROUTER_PURPOSE_BRIDGE,
- DIRIND_ONEHOP, "authority.z", NULL, 0, 0);
}
-/** Fetching the bridge descriptor from the bridge authority returned a
- * "not found". Fall back to trying a direct fetch. */
-void
-retry_bridge_descriptor_fetch_directly(const char *digest)
+/** Parse <b>state</b> and learn about the entry guards it describes.
+ * If <b>set</b> is true, and there are no errors, replace the guard
+ * list in the default guard selection context with what we find.
+ * On success, return 0. On failure, alloc into *<b>msg</b> a string
+ * describing the error, and return -1.
+ */
+int
+entry_guards_parse_state(or_state_t *state, int set, char **msg)
{
- bridge_info_t *bridge = find_bridge_by_digest(digest);
- if (!bridge)
- return; /* not found? oh well. */
+ entry_guards_dirty = 0;
+ int r1 = entry_guards_load_guards_from_state(state, set);
+ entry_guards_dirty = 0;
- launch_direct_bridge_descriptor_fetch(bridge);
+ if (r1 < 0) {
+ if (msg && *msg == NULL) {
+ *msg = tor_strdup("parsing error");
+ }
+ return -1;
+ }
+ return 0;
}
-/** For each bridge in our list for which we don't currently have a
- * descriptor, fetch a new copy of its descriptor -- either directly
- * from the bridge or via a bridge authority. */
+/** How long will we let a change in our guard nodes stay un-saved
+ * when we are trying to avoid disk writes? */
+#define SLOW_GUARD_STATE_FLUSH_TIME 600
+/** How long will we let a change in our guard nodes stay un-saved
+ * when we are not trying to avoid disk writes? */
+#define FAST_GUARD_STATE_FLUSH_TIME 30
+
+/** Our list of entry guards has changed for a particular guard selection
+ * context, or some element of one of our entry guards has changed for one.
+ * Write the changes to disk within the next few minutes.
+ */
void
-fetch_bridge_descriptors(const or_options_t *options, time_t now)
+entry_guards_changed_for_guard_selection(guard_selection_t *gs)
{
- int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO);
- int ask_bridge_directly;
- int can_use_bridge_authority;
+ time_t when;
- if (!bridge_list)
- return;
+ tor_assert(gs != NULL);
- /* If we still have unconfigured managed proxies, don't go and
- connect to a bridge. */
- if (pt_proxies_configuration_pending())
- return;
+ entry_guards_dirty = 1;
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge)
- {
- if (!download_status_is_ready(&bridge->fetch_status, now,
- IMPOSSIBLE_TO_DOWNLOAD))
- continue; /* don't bother, no need to retry yet */
- if (routerset_contains_bridge(options->ExcludeNodes, bridge)) {
- download_status_mark_impossible(&bridge->fetch_status);
- log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.",
- safe_str_client(fmt_and_decorate_addr(&bridge->addr)));
- continue;
- }
+ if (get_options()->AvoidDiskWrites)
+ when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME;
+ else
+ when = time(NULL) + FAST_GUARD_STATE_FLUSH_TIME;
- /* schedule another fetch as if this one will fail, in case it does */
- download_status_failed(&bridge->fetch_status, 0);
-
- can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) &&
- num_bridge_auths;
- ask_bridge_directly = !can_use_bridge_authority ||
- !options->UpdateBridgesFromAuthority;
- log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)",
- ask_bridge_directly, tor_digest_is_zero(bridge->identity),
- !options->UpdateBridgesFromAuthority, !num_bridge_auths);
-
- if (ask_bridge_directly &&
- !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
- FIREWALL_OR_CONNECTION, 0,
- 0)) {
- log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our "
- "firewall policy. %s.",
- fmt_addrport(&bridge->addr, bridge->port),
- can_use_bridge_authority ?
- "Asking bridge authority instead" : "Skipping");
- if (can_use_bridge_authority)
- ask_bridge_directly = 0;
- else
- continue;
- }
+ /* or_state_save() will call entry_guards_update_state() and
+ entry_guards_update_guards_in_state()
+ */
+ or_state_mark_dirty(get_or_state(), when);
+}
- if (ask_bridge_directly) {
- /* we need to ask the bridge itself for its descriptor. */
- launch_direct_bridge_descriptor_fetch(bridge);
- } else {
- /* We have a digest and we want to ask an authority. We could
- * combine all the requests into one, but that may give more
- * hints to the bridge authority than we want to give. */
- char resource[10 + HEX_DIGEST_LEN];
- memcpy(resource, "fp/", 3);
- base16_encode(resource+3, HEX_DIGEST_LEN+1,
- bridge->identity, DIGEST_LEN);
- memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3);
- log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.",
- resource);
- directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC,
- ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY);
- }
- }
- SMARTLIST_FOREACH_END(bridge);
+/** Our list of entry guards has changed for the default guard selection
+ * context, or some element of one of our entry guards has changed. Write
+ * the changes to disk within the next few minutes.
+ */
+void
+entry_guards_changed(void)
+{
+ entry_guards_changed_for_guard_selection(get_guard_selection_info());
}
-/** If our <b>bridge</b> is configured to be a different address than
- * the bridge gives in <b>node</b>, rewrite the routerinfo
- * we received to use the address we meant to use. Now we handle
- * multihomed bridges better.
+/** If the entry guard info has not changed, do nothing and return.
+ * Otherwise, free the EntryGuards piece of <b>state</b> and create
+ * a new one out of the global entry_guards list, and then mark
+ * <b>state</b> dirty so it will get saved to disk.
*/
-static void
-rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node)
+void
+entry_guards_update_state(or_state_t *state)
{
- /* XXXX move this function. */
- /* XXXX overridden addresses should really live in the node_t, so that the
- * routerinfo_t and the microdesc_t can be immutable. But we can only
- * do that safely if we know that no function that connects to an OR
- * does so through an address from any source other than node_get_addr().
- */
- tor_addr_t addr;
- const or_options_t *options = get_options();
+ entry_guards_dirty = 0;
- if (node->ri) {
- routerinfo_t *ri = node->ri;
- tor_addr_from_ipv4h(&addr, ri->addr);
+ // Handles all guard info.
+ entry_guards_update_guards_in_state(state);
- if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
- bridge->port == ri->or_port) ||
- (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) &&
- bridge->port == ri->ipv6_orport)) {
- /* they match, so no need to do anything */
- } else {
- if (tor_addr_family(&bridge->addr) == AF_INET) {
- ri->addr = tor_addr_to_ipv4h(&bridge->addr);
- ri->or_port = bridge->port;
- log_info(LD_DIR,
- "Adjusted bridge routerinfo for '%s' to match configured "
- "address %s:%d.",
- ri->nickname, fmt_addr32(ri->addr), ri->or_port);
- } else if (tor_addr_family(&bridge->addr) == AF_INET6) {
- tor_addr_copy(&ri->ipv6_addr, &bridge->addr);
- ri->ipv6_orport = bridge->port;
- log_info(LD_DIR,
- "Adjusted bridge routerinfo for '%s' to match configured "
- "address %s.",
- ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport));
- } else {
- log_err(LD_BUG, "Address family not supported: %d.",
- tor_addr_family(&bridge->addr));
- return;
- }
- }
+ entry_guards_dirty = 0;
- if (options->ClientPreferIPv6ORPort == -1) {
- /* Mark which address to use based on which bridge_t we got. */
- node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 &&
- !tor_addr_is_null(&node->ri->ipv6_addr));
- } else {
- /* Mark which address to use based on user preference */
- node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) &&
- !tor_addr_is_null(&node->ri->ipv6_addr));
- }
+ if (!get_options()->AvoidDiskWrites)
+ or_state_mark_dirty(get_or_state(), 0);
+ entry_guards_dirty = 0;
+}
- /* XXXipv6 we lack support for falling back to another address for
- the same relay, warn the user */
- if (!tor_addr_is_null(&ri->ipv6_addr)) {
- tor_addr_port_t ap;
- node_get_pref_orport(node, &ap);
- log_notice(LD_CONFIG,
- "Bridge '%s' has both an IPv4 and an IPv6 address. "
- "Will prefer using its %s address (%s) based on %s.",
- ri->nickname,
- node->ipv6_preferred ? "IPv6" : "IPv4",
- fmt_addrport(&ap.addr, ap.port),
- options->ClientPreferIPv6ORPort == -1 ?
- "the configured Bridge address" :
- "ClientPreferIPv6ORPort");
- }
+/**
+ * Format a single entry guard in the format expected by the controller.
+ * Return a newly allocated string.
+ */
+STATIC char *
+getinfo_helper_format_single_entry_guard(const entry_guard_t *e)
+{
+ const char *status = NULL;
+ time_t when = 0;
+ const node_t *node;
+ char tbuf[ISO_TIME_LEN+1];
+ char nbuf[MAX_VERBOSE_NICKNAME_LEN+1];
+
+ /* This is going to be a bit tricky, since the status
+ * codes weren't really intended for prop271 guards.
+ *
+ * XXXX use a more appropriate format for exporting this information
+ */
+ if (e->confirmed_idx < 0) {
+ status = "never-connected";
+ } else if (! e->currently_listed) {
+ when = e->unlisted_since_date;
+ status = "unusable";
+ } else if (! e->is_filtered_guard) {
+ status = "unusable";
+ } else if (e->is_reachable == GUARD_REACHABLE_NO) {
+ when = e->failing_since;
+ status = "down";
+ } else {
+ status = "up";
}
- if (node->rs) {
- routerstatus_t *rs = node->rs;
- tor_addr_from_ipv4h(&addr, rs->addr);
- if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) &&
- bridge->port == rs->or_port) {
- /* they match, so no need to do anything */
- } else {
- rs->addr = tor_addr_to_ipv4h(&bridge->addr);
- rs->or_port = bridge->port;
- log_info(LD_DIR,
- "Adjusted bridge routerstatus for '%s' to match "
- "configured address %s.",
- rs->nickname, fmt_addrport(&bridge->addr, rs->or_port));
- }
+ node = entry_guard_find_node(e);
+ if (node) {
+ node_get_verbose_nickname(node, nbuf);
+ } else {
+ nbuf[0] = '$';
+ base16_encode(nbuf+1, sizeof(nbuf)-1, e->identity, DIGEST_LEN);
+ /* e->nickname field is not very reliable if we don't know about
+ * this router any longer; don't include it. */
}
-}
-/** We just learned a descriptor for a bridge. See if that
- * digest is in our entry guard list, and add it if not. */
-void
-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;
- bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri);
- time_t now = time(NULL);
- router_set_status(ri->cache_info.identity_digest, 1);
-
- 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)
- download_status_reset(&bridge->fetch_status);
-
- node = node_get_mutable_by_id(ri->cache_info.identity_digest);
- tor_assert(node);
- rewrite_node_address_for_bridge(bridge, node);
- if (tor_digest_is_zero(bridge->identity)) {
- memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN);
- log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d",
- hex_str(bridge->identity, DIGEST_LEN),
- fmt_and_decorate_addr(&bridge->addr),
- (int) bridge->port);
- }
- add_an_entry_guard(node, 1, 1, 0, 0);
-
- 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 */
- entry_guard_register_connect_status(ri->cache_info.identity_digest,
- 1, 0, now);
- if (first) {
- routerlist_retry_directory_downloads(now);
- }
- }
+ char *result = NULL;
+ if (when) {
+ format_iso_time(tbuf, when);
+ tor_asprintf(&result, "%s %s %s\n", nbuf, status, tbuf);
+ } else {
+ tor_asprintf(&result, "%s %s\n", nbuf, status);
}
+ return result;
}
-/** Return the number of bridges that have descriptors that
- * are marked with purpose 'bridge' and are running.
+/** If <b>question</b> is the string "entry-guards", then dump
+ * to *<b>answer</b> a newly allocated string describing all of
+ * the nodes in the global entry_guards list. See control-spec.txt
+ * for details.
+ * For backward compatibility, we also handle the string "helper-nodes".
*
- * 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. */
+ * XXX this should be totally redesigned after prop 271 too, and that's
+ * going to take some control spec work.
+ * */
int
-any_bridge_descriptors_known(void)
+getinfo_helper_entry_guards(control_connection_t *conn,
+ const char *question, char **answer,
+ const char **errmsg)
{
- tor_assert(get_options()->UseBridges);
- return choose_random_entry(NULL) != NULL;
+ guard_selection_t *gs = get_guard_selection_info();
+
+ tor_assert(gs != NULL);
+
+ (void) conn;
+ (void) errmsg;
+
+ if (!strcmp(question,"entry-guards") ||
+ !strcmp(question,"helper-nodes")) {
+ const smartlist_t *guards;
+ guards = gs->sampled_entry_guards;
+
+ smartlist_t *sl = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(guards, const entry_guard_t *, e) {
+ char *cp = getinfo_helper_format_single_entry_guard(e);
+ smartlist_add(sl, cp);
+ } SMARTLIST_FOREACH_END(e);
+ *answer = smartlist_join_strings(sl, "", 0, NULL);
+ SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
+ smartlist_free(sl);
+ }
+ return 0;
}
-/** Return the number of bridges that have descriptors that are marked with
- * purpose 'bridge' and are running.
+/* Given the original bandwidth of a guard and its guardfraction,
+ * calculate how much bandwidth the guard should have as a guard and
+ * as a non-guard.
+ *
+ * Quoting from proposal236:
+ *
+ * Let Wpf denote the weight from the 'bandwidth-weights' line a
+ * client would apply to N for position p if it had the guard
+ * flag, Wpn the weight if it did not have the guard flag, and B the
+ * measured bandwidth of N in the consensus. Then instead of choosing
+ * N for position p proportionally to Wpf*B or Wpn*B, clients should
+ * choose N proportionally to F*Wpf*B + (1-F)*Wpn*B.
+ *
+ * This function fills the <b>guardfraction_bw</b> structure. It sets
+ * <b>guard_bw</b> to F*B and <b>non_guard_bw</b> to (1-F)*B.
*/
-static int
-num_bridges_usable(void)
+void
+guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
+ int orig_bandwidth,
+ uint32_t guardfraction_percentage)
{
- int n_options = 0;
- tor_assert(get_options()->UseBridges);
- (void) choose_random_entry_impl(NULL, 0, 0, &n_options);
- return n_options;
+ double guardfraction_fraction;
+
+ /* Turn the percentage into a fraction. */
+ tor_assert(guardfraction_percentage <= 100);
+ guardfraction_fraction = guardfraction_percentage / 100.0;
+
+ long guard_bw = tor_lround(guardfraction_fraction * orig_bandwidth);
+ tor_assert(guard_bw <= INT_MAX);
+
+ guardfraction_bw->guard_bw = (int) guard_bw;
+
+ guardfraction_bw->non_guard_bw = orig_bandwidth - (int) guard_bw;
}
-/** Return a smartlist containing all bridge identity digests */
-MOCK_IMPL(smartlist_t *,
-list_bridge_identities, (void))
+/** Helper: Update the status of all entry guards, in whatever algorithm
+ * is used. Return true if we should stop using all previously generated
+ * circuits, by calling circuit_mark_all_unused_circs() and
+ * circuit_mark_all_dirty_circs_as_unusable().
+ */
+int
+guards_update_all(void)
{
- smartlist_t *result = NULL;
- char *digest_tmp;
+ int mark_circuits = 0;
+ if (update_guard_selection_choice(get_options()))
+ mark_circuits = 1;
- if (get_options()->UseBridges && bridge_list) {
- result = smartlist_new();
+ tor_assert(curr_guard_context);
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
- digest_tmp = tor_malloc(DIGEST_LEN);
- memcpy(digest_tmp, b->identity, DIGEST_LEN);
- smartlist_add(result, digest_tmp);
- } SMARTLIST_FOREACH_END(b);
- }
+ if (entry_guards_update_all(curr_guard_context))
+ mark_circuits = 1;
- return result;
+ return mark_circuits;
}
-/** Get the download status for a bridge descriptor given its identity */
-MOCK_IMPL(download_status_t *,
-get_bridge_dl_status_by_id, (const char *digest))
+/** Helper: pick a guard for a circuit, with whatever algorithm is
+ used. */
+const node_t *
+guards_choose_guard(cpath_build_state_t *state,
+ circuit_guard_state_t **guard_state_out)
+{
+ const node_t *r = NULL;
+ const uint8_t *exit_id = NULL;
+ entry_guard_restriction_t *rst = NULL;
+ if (state && (exit_id = build_state_get_exit_rsa_id(state))) {
+ /* We're building to a targeted exit node, so that node can't be
+ * chosen as our guard for this circuit. Remember that fact in a
+ * restriction. */
+ rst = tor_malloc_zero(sizeof(entry_guard_restriction_t));
+ memcpy(rst->exclude_id, exit_id, DIGEST_LEN);
+ }
+ if (entry_guard_pick_for_circuit(get_guard_selection_info(),
+ GUARD_USAGE_TRAFFIC,
+ rst,
+ &r,
+ guard_state_out) < 0) {
+ tor_assert(r == NULL);
+ }
+ return r;
+}
+
+/** Remove all currently listed entry guards for a given guard selection
+ * context. This frees and replaces <b>gs</b>, so don't use <b>gs</b>
+ * after calling this function. */
+void
+remove_all_entry_guards_for_guard_selection(guard_selection_t *gs)
{
- download_status_t *dl = NULL;
+ // This function shouldn't exist. XXXX
+ tor_assert(gs != NULL);
+ char *old_name = tor_strdup(gs->name);
+ guard_selection_type_t old_type = gs->type;
- if (digest && get_options()->UseBridges && bridge_list) {
- SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) {
- if (tor_memeq(digest, b->identity, DIGEST_LEN)) {
- dl = &(b->fetch_status);
- break;
- }
- } SMARTLIST_FOREACH_END(b);
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, entry, {
+ control_event_guard(entry->nickname, entry->identity, "DROPPED");
+ });
+
+ if (gs == curr_guard_context) {
+ curr_guard_context = NULL;
}
- return dl;
+ smartlist_remove(guard_contexts, gs);
+ guard_selection_free(gs);
+
+ gs = get_guard_selection_by_name(old_name, old_type, 1);
+ entry_guards_changed_for_guard_selection(gs);
+ tor_free(old_name);
}
-/** Return 1 if we have at least one descriptor for an entry guard
- * (bridge or member of EntryNodes) and all descriptors we know are
- * down. Else return 0. If <b>act</b> is 1, then mark the down guards
- * up; else just observe and report. */
-static int
-entries_retry_helper(const or_options_t *options, int act)
+/** Remove all currently listed entry guards, so new ones will be chosen.
+ *
+ * XXXX This function shouldn't exist -- it's meant to support the DROPGUARDS
+ * command, which is deprecated.
+ */
+void
+remove_all_entry_guards(void)
{
- const node_t *node;
- int any_known = 0;
- int any_running = 0;
- int need_bridges = options->UseBridges != 0;
- if (!entry_guards)
- entry_guards = smartlist_new();
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- node = node_get_by_id(e->identity);
- if (node && node_has_descriptor(node) &&
- node_is_bridge(node) == need_bridges &&
- (!need_bridges || (!e->bad_since &&
- node_is_a_configured_bridge(node)))) {
- any_known = 1;
- if (node->is_running)
- any_running = 1; /* some entry is both known and running */
- else if (act) {
- /* Mark all current connections to this OR as unhealthy, since
- * otherwise there could be one that started 30 seconds
- * ago, and in 30 seconds it will time out, causing us to mark
- * the node down and undermine the retry attempt. We mark even
- * the established conns, since if the network just came back
- * we'll want to attach circuits to fresh conns. */
- connection_or_set_bad_connections(node->identity, 1);
-
- /* mark this entry node for retry */
- router_set_status(node->identity, 1);
- e->can_retry = 1;
- e->bad_since = 0;
- }
- }
- } SMARTLIST_FOREACH_END(e);
- log_debug(LD_DIR, "%d: any_known %d, any_running %d",
- act, any_known, any_running);
- return any_known && !any_running;
+ remove_all_entry_guards_for_guard_selection(get_guard_selection_info());
}
-/** Do we know any descriptors for our bridges / entrynodes, and are
- * all the ones we have descriptors for down? */
+/** Helper: pick a directory guard, with whatever algorithm is used. */
+const node_t *
+guards_choose_dirguard(circuit_guard_state_t **guard_state_out)
+{
+ const node_t *r = NULL;
+ if (entry_guard_pick_for_circuit(get_guard_selection_info(),
+ GUARD_USAGE_DIRGUARD,
+ NULL,
+ &r,
+ guard_state_out) < 0) {
+ tor_assert(r == NULL);
+ }
+ return r;
+}
+
+/**
+ * If we're running with a constrained guard set, then maybe mark our guards
+ * usable. Return 1 if we do; 0 if we don't.
+ */
int
-entries_known_but_down(const or_options_t *options)
+guards_retry_optimistic(const or_options_t *options)
{
- tor_assert(entry_list_is_constrained(options));
- return entries_retry_helper(options, 0);
+ if (! entry_list_is_constrained(options))
+ return 0;
+
+ mark_primary_guards_maybe_reachable(get_guard_selection_info());
+
+ return 1;
}
-/** Mark all down known bridges / entrynodes up. */
-void
-entries_retry_all(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)
{
- tor_assert(entry_list_is_constrained(options));
- entries_retry_helper(options, 1);
+ if (!gs->primary_guards_up_to_date)
+ entry_guards_update_primary(gs);
+
+ int n_missing_descriptors = 0;
+ int n_considered = 0;
+ int num_primary_to_check;
+
+ /* We want to check for the descriptor of at least the first two primary
+ * guards in our list, since these are the guards that we typically use for
+ * circuits. */
+ num_primary_to_check = get_n_primary_guards_to_use(GUARD_USAGE_TRAFFIC);
+ num_primary_to_check++;
+
+ SMARTLIST_FOREACH_BEGIN(gs->primary_entry_guards, entry_guard_t *, guard) {
+ entry_guard_consider_retry(guard);
+ if (guard->is_reachable == GUARD_REACHABLE_NO)
+ continue;
+ n_considered++;
+ if (!guard_has_descriptor(guard))
+ n_missing_descriptors++;
+ if (n_considered >= num_primary_to_check)
+ break;
+ } SMARTLIST_FOREACH_END(guard);
+
+ return n_missing_descriptors == 0;
}
-/** Return true if at least one of our bridges runs a Tor version that can
- * provide microdescriptors to us. If not, we'll fall back to asking for
- * full descriptors. */
+/** As guard_selection_have_enough_dir_info_to_build_circuits, but uses
+ * the default guard selection. */
int
-any_bridge_supports_microdescriptors(void)
+entry_guards_have_enough_dir_info_to_build_circuits(void)
{
- const node_t *node;
- if (!get_options()->UseBridges || !entry_guards)
- return 0;
- SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) {
- node = node_get_by_id(e->identity);
- if (node && node->is_running &&
- node_is_bridge(node) && node_is_a_configured_bridge(node)) {
- /* This is one of our current bridges, and we know enough about
- * it to know that it will be able to answer our questions. */
- return 1;
- }
- } SMARTLIST_FOREACH_END(e);
- return 0;
+ return guard_selection_have_enough_dir_info_to_build_circuits(
+ get_guard_selection_info());
+}
+
+/** Free one guard selection context */
+STATIC void
+guard_selection_free(guard_selection_t *gs)
+{
+ if (!gs) return;
+
+ tor_free(gs->name);
+
+ if (gs->sampled_entry_guards) {
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, e,
+ entry_guard_free(e));
+ smartlist_free(gs->sampled_entry_guards);
+ gs->sampled_entry_guards = NULL;
+ }
+
+ smartlist_free(gs->confirmed_entry_guards);
+ smartlist_free(gs->primary_entry_guards);
+
+ tor_free(gs);
}
/** Release all storage held by the list of entry guards and related
@@ -2547,15 +3498,16 @@ any_bridge_supports_microdescriptors(void)
void
entry_guards_free_all(void)
{
- if (entry_guards) {
- SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
- entry_guard_free(e));
- smartlist_free(entry_guards);
- entry_guards = NULL;
+ /* Null out the default */
+ curr_guard_context = NULL;
+ /* Free all the guard contexts */
+ if (guard_contexts != NULL) {
+ SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) {
+ guard_selection_free(gs);
+ } SMARTLIST_FOREACH_END(gs);
+ smartlist_free(guard_contexts);
+ guard_contexts = NULL;
}
- clear_bridge_list();
- smartlist_free(bridge_list);
- bridge_list = NULL;
circuit_build_times_free_timeouts(get_circuit_build_times_mutable());
}
diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h
index 1021e67d43..32cfff73be 100644
--- a/src/or/entrynodes.h
+++ b/src/or/entrynodes.h
@@ -12,25 +12,27 @@
#ifndef TOR_ENTRYNODES_H
#define TOR_ENTRYNODES_H
-#if 1
-/* XXXX NM I would prefer that all of this stuff be private to
- * entrynodes.c. */
+#include "handles.h"
-/** An entry_guard_t represents our information about a chosen long-term
- * first hop, known as a "helper" node in the literature. We can't just
- * use a node_t, since we want to remember these even when we
- * don't have any directory info. */
-typedef struct entry_guard_t {
- char nickname[MAX_NICKNAME_LEN+1];
- char identity[DIGEST_LEN];
- time_t chosen_on_date; /**< Approximately when was this guard added?
- * "0" if we don't know. */
- char *chosen_by_version; /**< What tor version added this guard? NULL
- * if we don't know. */
- unsigned int made_contact : 1; /**< 0 if we have never connected to this
- * router, 1 if we have. */
- unsigned int can_retry : 1; /**< Should we retry connecting to this entry,
- * in spite of having it marked as unreachable?*/
+/* Forward declare for guard_selection_t; entrynodes.c has the real struct */
+typedef struct guard_selection_s guard_selection_t;
+
+/* Forward declare for entry_guard_t; the real declaration is private. */
+typedef struct entry_guard_t entry_guard_t;
+
+/* Forward declaration for circuit_guard_state_t; the real declaration is
+ private. */
+typedef struct circuit_guard_state_t circuit_guard_state_t;
+
+/* Forward declaration for entry_guard_restriction_t; the real declaration is
+ private. */
+typedef struct entry_guard_restriction_t entry_guard_restriction_t;
+
+/* Information about a guard's pathbias status.
+ * These fields are used in circpathbias.c to try to detect entry
+ * nodes that are failing circuits at a suspicious frequency.
+ */
+typedef struct guard_pathbias_t {
unsigned int path_bias_noticed : 1; /**< Did we alert the user about path
* bias for this node already? */
unsigned int path_bias_warned : 1; /**< Did we alert the user about path bias
@@ -43,15 +45,6 @@ typedef struct entry_guard_t {
* use bias for this node already? */
unsigned int path_bias_use_extreme : 1; /**< Did we alert the user about path
* use bias for this node already? */
- unsigned int is_dir_cache : 1; /**< Is this node a directory cache? */
- time_t bad_since; /**< 0 if this guard is currently usable, or the time at
- * which it was observed to become (according to the
- * directory or the user configuration) unusable. */
- time_t unreachable_since; /**< 0 if we can connect to this guard, or the
- * time at which we first noticed we couldn't
- * connect to it. */
- time_t last_attempted; /**< 0 if we can connect to this guard, or the time
- * at which we last failed to connect to it. */
double circ_attempts; /**< Number of circuits this guard has "attempted" */
double circ_successes; /**< Number of successfully built circuits using
@@ -68,98 +61,523 @@ typedef struct entry_guard_t {
double use_attempts; /**< Number of circuits we tried to use with streams */
double use_successes; /**< Number of successfully used circuits using
* this guard as first hop. */
-} entry_guard_t;
+} guard_pathbias_t;
+
+#if defined(ENTRYNODES_PRIVATE)
+/**
+ * @name values for entry_guard_t.is_reachable.
+ *
+ * See entry_guard_t.is_reachable for more information.
+ */
+/**@{*/
+#define GUARD_REACHABLE_NO 0
+#define GUARD_REACHABLE_YES 1
+#define GUARD_REACHABLE_MAYBE 2
+/**@}*/
+
+/** An entry_guard_t represents our information about a chosen long-term
+ * first hop, known as a "helper" node in the literature. We can't just
+ * use a node_t, since we want to remember these even when we
+ * don't have any directory info. */
+struct entry_guard_t {
+ HANDLE_ENTRY(entry_guard, entry_guard_t);
+
+ char nickname[MAX_HEX_NICKNAME_LEN+1];
+ char identity[DIGEST_LEN];
+ ed25519_public_key_t ed_id;
+
+ /**
+ * @name new guard selection algorithm fields.
+ *
+ * Only the new (prop271) algorithm uses these. For a more full
+ * description of the algorithm, see the module documentation for
+ * entrynodes.c
+ */
+ /**@{*/
+
+ /* == Persistent fields, present for all sampled guards. */
+ /** When was this guard added to the sample? */
+ time_t sampled_on_date;
+ /** Since what date has this guard been "unlisted"? A guard counts as
+ * unlisted if we have a live consensus that does not include it, or
+ * if we have a live consensus that does not include it as a usable
+ * guard. This field is zero when the guard is listed. */
+ time_t unlisted_since_date; // can be zero
+ /** What version of Tor added this guard to the sample? */
+ char *sampled_by_version;
+ /** Is this guard listed right now? If this is set, then
+ * unlisted_since_date should be set too. */
+ unsigned currently_listed : 1;
+
+ /* == Persistent fields, for confirmed guards only */
+ /** When was this guard confirmed? (That is, when did we first use it
+ * successfully and decide to keep it?) This field is zero if this is not a
+ * confirmed guard. */
+ time_t confirmed_on_date; /* 0 if not confirmed */
+ /**
+ * In what order was this guard confirmed? Guards with lower indices
+ * appear earlier on the confirmed list. If the confirmed list is compacted,
+ * this field corresponds to the index of this guard on the confirmed list.
+ *
+ * This field is set to -1 if this guard is not confirmed.
+ */
+ int confirmed_idx; /* -1 if not confirmed; otherwise the order that this
+ * item should occur in the CONFIRMED_GUARDS ordered
+ * list */
+
+ /**
+ * Which selection does this guard belong to?
+ */
+ char *selection_name;
+
+ /** Bridges only: address of the bridge. */
+ tor_addr_port_t *bridge_addr;
+
+ /* ==== Non-persistent fields. */
+ /* == These are used by sampled guards */
+ /** When did we last decide to try using this guard for a circuit? 0 for
+ * "not since we started up." */
+ time_t last_tried_to_connect;
+ /** How reachable do we consider this guard to be? One of
+ * GUARD_REACHABLE_NO, GUARD_REACHABLE_YES, or GUARD_REACHABLE_MAYBE. */
+ unsigned is_reachable : 2;
+ /** Boolean: true iff this guard is pending. A pending guard is one
+ * that we have an in-progress circuit through, and which we do not plan
+ * to try again until it either succeeds or fails. Primary guards can
+ * never be pending. */
+ unsigned is_pending : 1;
+ /** If true, don't write this guard to disk. (Used for bridges with unknown
+ * identities) */
+ unsigned is_persistent : 1;
+ /** When did we get the earliest connection failure for this guard?
+ * We clear this field on a successful connect. We do _not_ clear it
+ * when we mark the guard as "MAYBE" reachable.
+ */
+ time_t failing_since;
+
+ /* == Set inclusion flags. */
+ /** If true, this guard is in the filtered set. The filtered set includes
+ * all sampled guards that our configuration allows us to use. */
+ unsigned is_filtered_guard : 1;
+ /** If true, this guard is in the usable filtered set. The usable filtered
+ * set includes all filtered guards that are not believed to be
+ * unreachable. (That is, those for which is_reachable is not
+ * GUARD_REACHABLE_NO) */
+ unsigned is_usable_filtered_guard : 1;
+ unsigned is_primary:1;
+
+ /** This string holds any fields that we are maintaining because
+ * we saw them in the state, even if we don't understand them. */
+ char *extra_state_fields;
+
+ /** Backpointer to the guard selection that this guard belongs to.
+ * The entry_guard_t must never outlive its guard_selection. */
+ guard_selection_t *in_selection;
+ /**@}*/
+
+ /** Path bias information for this guard. */
+ guard_pathbias_t pb;
+};
+
+/**
+ * Possible rules for a guard selection to follow
+ */
+typedef enum guard_selection_type_t {
+ /** Infer the type of this selection from its name. */
+ GS_TYPE_INFER=0,
+ /** Use the normal guard selection algorithm, taking our sample from the
+ * complete list of guards in the consensus. */
+ GS_TYPE_NORMAL=1,
+ /** Use the normal guard selection algorithm, taking our sample from the
+ * configured bridges, and allowing it to grow as large as all the configured
+ * bridges */
+ GS_TYPE_BRIDGE,
+ /** Use the normal guard selection algorithm, taking our sample from the
+ * set of filtered nodes. */
+ GS_TYPE_RESTRICTED,
+} guard_selection_type_t;
+
+/**
+ * All of the the context for guard selection on a particular client.
+ *
+ * We maintain multiple guard selection contexts for a client, depending
+ * aspects on its current configuration -- whether an extremely
+ * restrictive EntryNodes is used, whether UseBridges is enabled, and so
+ * on.)
+ *
+ * See the module documentation for entrynodes.c for more information
+ * about guard selection algorithms.
+ */
+struct guard_selection_s {
+ /**
+ * The name for this guard-selection object. (Must not contain spaces).
+ */
+ char *name;
+
+ /**
+ * What rules does this guard-selection object follow?
+ */
+ guard_selection_type_t type;
+
+ /**
+ * A value of 1 means that primary_entry_guards is up-to-date; 0
+ * means we need to recalculate it before using primary_entry_guards
+ * or the is_primary flag on any guard.
+ */
+ int primary_guards_up_to_date;
+
+ /**
+ * A list of the sampled entry guards, as entry_guard_t structures.
+ * Not in any particular order. When we 'sample' a guard, we are
+ * noting it as a possible guard to pick in the future. The use of
+ * sampling here prevents us from being forced by an attacker to try
+ * every guard on the network. This list is persistent.
+ */
+ smartlist_t *sampled_entry_guards;
+
+ /**
+ * Ordered list (from highest to lowest priority) of guards that we
+ * have successfully contacted and decided to use. Every member of
+ * this list is a member of sampled_entry_guards. Every member should
+ * have confirmed_on_date set, and have confirmed_idx greater than
+ * any earlier member of the list.
+ *
+ * This list is persistent. It is a subset of the elements in
+ * sampled_entry_guards, and its pointers point to elements of
+ * sampled_entry_guards.
+ */
+ smartlist_t *confirmed_entry_guards;
+
+ /**
+ * Ordered list (from highest to lowest priority) of guards that we
+ * are willing to use the most happily. These guards may or may not
+ * yet be confirmed yet. If we can use one of these guards, we are
+ * probably not on a network that is trying to restrict our guard
+ * choices.
+ *
+ * This list is a subset of the elements in
+ * sampled_entry_guards, and its pointers point to elements of
+ * sampled_entry_guards.
+ */
+ smartlist_t *primary_entry_guards;
+
+ /** When did we last successfully build a circuit or use a circuit? */
+ time_t last_time_on_internet;
+
+ /** What confirmed_idx value should the next-added member of
+ * confirmed_entry_guards receive? */
+ int next_confirmed_idx;
+
+};
+
+struct entry_guard_handle_t;
+
+/**
+ * A restriction to remember which entry guards are off-limits for a given
+ * circuit.
+ *
+ * Right now, we only use restrictions to block a single guard and its family
+ * from being selected; this mechanism is designed to be more extensible in
+ * the future, however.
+ *
+ * Note: This mechanism is NOT for recording which guards are never to be
+ * used: only which guards cannot be used on <em>one particular circuit</em>.
+ */
+struct entry_guard_restriction_t {
+ /**
+ * The guard's RSA identity digest must not equal this; and it must not
+ * be in the same family as any node with this digest.
+ */
+ uint8_t exclude_id[DIGEST_LEN];
+};
+
+/**
+ * Per-circuit state to track whether we'll be able to use the circuit.
+ */
+struct circuit_guard_state_t {
+ /** Handle to the entry guard object for this circuit. */
+ struct entry_guard_handle_t *guard;
+ /** The time at which <b>state</b> last changed. */
+ time_t state_set_at;
+ /** One of GUARD_CIRC_STATE_* */
+ uint8_t state;
+
+ /**
+ * A set of restrictions that were placed on this guard when we selected it
+ * for this particular circuit. We need to remember the restrictions here,
+ * since any guard that breaks these restrictions will not block this
+ * circuit from becoming COMPLETE.
+ */
+ entry_guard_restriction_t *restrictions;
+};
+#endif
+
+/* Common entry points for old and new guard code */
+int guards_update_all(void);
+const node_t *guards_choose_guard(cpath_build_state_t *state,
+ circuit_guard_state_t **guard_state_out);
+const node_t *guards_choose_dirguard(circuit_guard_state_t **guard_state_out);
+#if 1
+/* XXXX NM I would prefer that all of this stuff be private to
+ * entrynodes.c. */
+entry_guard_t *entry_guard_get_by_id_digest_for_guard_selection(
+ guard_selection_t *gs, const char *digest);
entry_guard_t *entry_guard_get_by_id_digest(const char *digest);
+
+circuit_guard_state_t *
+get_guard_state_for_bridge_desc_fetch(const char *digest);
+
+void entry_guards_changed_for_guard_selection(guard_selection_t *gs);
void entry_guards_changed(void);
-const smartlist_t *get_entry_guards(void);
+guard_selection_t * get_guard_selection_info(void);
+int num_live_entry_guards_for_guard_selection(
+ guard_selection_t *gs,
+ int for_directory);
int num_live_entry_guards(int for_directory);
-
#endif
-#ifdef ENTRYNODES_PRIVATE
-STATIC const node_t *add_an_entry_guard(const node_t *chosen,
- int reset_status, int prepend,
- int for_discovery, int for_directory);
-
-STATIC int populate_live_entry_guards(smartlist_t *live_entry_guards,
- const smartlist_t *all_entry_guards,
- const node_t *chosen_exit,
- dirinfo_type_t dirinfo_type,
- int for_directory,
- int need_uptime, int need_capacity);
-STATIC int decide_num_guards(const or_options_t *options, int for_directory);
-
-STATIC void entry_guards_set_from_config(const or_options_t *options);
-
-/** Flags to be passed to entry_is_live() to indicate what kind of
- * entry nodes we are looking for. */
+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);
+const char *entry_guard_describe(const entry_guard_t *guard);
+guard_pathbias_t *entry_guard_get_pathbias_state(entry_guard_t *guard);
+
+/** Enum to specify how we're going to use a given guard, when we're picking
+ * one for immediate use. */
+typedef enum {
+ GUARD_USAGE_TRAFFIC = 0,
+ GUARD_USAGE_DIRGUARD = 1
+} guard_usage_t;
+
+void circuit_guard_state_free(circuit_guard_state_t *state);
+int entry_guard_pick_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ entry_guard_restriction_t *rst,
+ const node_t **chosen_node_out,
+ circuit_guard_state_t **guard_state_out);
+
+/* We just connected to an entry guard. What should we do with the circuit? */
typedef enum {
- ENTRY_NEED_UPTIME = 1<<0,
- ENTRY_NEED_CAPACITY = 1<<1,
- ENTRY_ASSUME_REACHABLE = 1<<2,
- ENTRY_NEED_DESCRIPTOR = 1<<3,
-} entry_is_live_flags_t;
+ GUARD_USABLE_NEVER = -1, /* Never use the circuit */
+ GUARD_MAYBE_USABLE_LATER = 0, /* Keep it. We might use it in the future */
+ GUARD_USABLE_NOW = 1, /* Use it right now */
+} guard_usable_t;
+
+guard_usable_t entry_guard_succeeded(circuit_guard_state_t **guard_state_p);
+void entry_guard_failed(circuit_guard_state_t **guard_state_p);
+void entry_guard_cancel(circuit_guard_state_t **guard_state_p);
+void entry_guard_chan_failed(channel_t *chan);
+int entry_guards_update_all(guard_selection_t *gs);
+int entry_guards_upgrade_waiting_circuits(guard_selection_t *gs,
+ const smartlist_t *all_circuits,
+ smartlist_t *newly_complete_out);
+int entry_guard_state_should_expire(circuit_guard_state_t *guard_state);
+void entry_guards_note_internet_connectivity(guard_selection_t *gs);
-STATIC const node_t *entry_is_live(const entry_guard_t *e,
- entry_is_live_flags_t flags,
- const char **msg);
+int update_guard_selection_choice(const or_options_t *options);
-STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now);
+/* Used by bridges.c only. */
+int num_bridges_usable(void);
+#ifdef ENTRYNODES_PRIVATE
+/**
+ * @name Default values for the parameters for the new (prop271) entry guard
+ * algorithm.
+ */
+/**@{*/
+/**
+ * We never let our sampled guard set grow larger than this percentage
+ * of the guards on the network.
+ */
+#define DFLT_MAX_SAMPLE_THRESHOLD_PERCENT 20
+/**
+ * We never let our sampled guard set grow larger than this number of
+ * guards.
+ */
+#define DFLT_MAX_SAMPLE_SIZE 60
+/**
+ * We always try to make our sample contain at least this many guards.
+ */
+#define DFLT_MIN_FILTERED_SAMPLE_SIZE 20
+/**
+ * If a guard is unlisted for this many days in a row, we remove it.
+ */
+#define DFLT_REMOVE_UNLISTED_GUARDS_AFTER_DAYS 20
+/**
+ * We remove unconfirmed guards from the sample after this many days,
+ * regardless of whether they are listed or unlisted.
+ */
+#define DFLT_GUARD_LIFETIME_DAYS 120
+/**
+ * We remove confirmed guards from the sample if they were sampled
+ * GUARD_LIFETIME_DAYS ago and confirmed this many days ago.
+ */
+#define DFLT_GUARD_CONFIRMED_MIN_LIFETIME_DAYS 60
+/**
+ * How many guards do we try to keep on our primary guard list?
+ */
+#define DFLT_N_PRIMARY_GUARDS 3
+/**
+ * Of the live guards on the primary guard list, how many do we consider when
+ * choosing a guard to use?
+ */
+#define DFLT_N_PRIMARY_GUARDS_TO_USE 1
+/**
+ * As DFLT_N_PRIMARY_GUARDS, but for choosing which directory guard to use.
+ */
+#define DFLT_N_PRIMARY_DIR_GUARDS_TO_USE 3
+/**
+ * If we haven't successfully built or used a circuit in this long, then
+ * consider that the internet is probably down.
+ */
+#define DFLT_INTERNET_LIKELY_DOWN_INTERVAL (10*60)
+/**
+ * If we're trying to connect to a nonprimary guard for at least this
+ * many seconds, and we haven't gotten the connection to work, we will treat
+ * lower-priority guards as usable.
+ */
+#define DFLT_NONPRIMARY_GUARD_CONNECT_TIMEOUT 15
+/**
+ * If a circuit has been sitting around in 'waiting for better guard' state
+ * for at least this long, we'll expire it.
+ */
+#define DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT (10*60)
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in a restricted setting.
+ */
+#define DFLT_MEANINGFUL_RESTRICTION_PERCENT 20
+/**
+ * If our configuration retains fewer than this fraction of guards from the
+ * torrc, we are in an extremely restricted setting, and should warn.
+ */
+#define DFLT_EXTREME_RESTRICTION_PERCENT 1
+/**@}*/
+
+STATIC double get_max_sample_threshold(void);
+STATIC int get_max_sample_size_absolute(void);
+STATIC int get_min_filtered_sample_size(void);
+STATIC int get_remove_unlisted_guards_after_days(void);
+STATIC int get_guard_lifetime(void);
+STATIC int get_guard_confirmed_min_lifetime(void);
+STATIC int get_n_primary_guards(void);
+STATIC int get_n_primary_guards_to_use(guard_usage_t usage);
+STATIC int get_internet_likely_down_interval(void);
+STATIC int get_nonprimary_guard_connect_timeout(void);
+STATIC int get_nonprimary_guard_idle_timeout(void);
+STATIC double get_meaningful_restriction_threshold(void);
+STATIC double get_extreme_restriction_threshold(void);
+
+HANDLE_DECL(entry_guard, entry_guard_t, STATIC)
+STATIC guard_selection_type_t guard_selection_infer_type(
+ guard_selection_type_t type_in,
+ const char *name);
+STATIC guard_selection_t *guard_selection_new(const char *name,
+ guard_selection_type_t type);
+STATIC guard_selection_t *get_guard_selection_by_name(
+ const char *name, guard_selection_type_t type, int create_if_absent);
+STATIC void guard_selection_free(guard_selection_t *gs);
+MOCK_DECL(STATIC int, entry_guard_is_listed,
+ (guard_selection_t *gs, const entry_guard_t *guard));
+STATIC const char *choose_guard_selection(const or_options_t *options,
+ const networkstatus_t *ns,
+ const guard_selection_t *old_selection,
+ guard_selection_type_t *type_out);
+STATIC entry_guard_t *get_sampled_guard_with_id(guard_selection_t *gs,
+ const uint8_t *rsa_id);
+
+MOCK_DECL(STATIC time_t, randomize_time, (time_t now, time_t max_backdate));
+
+STATIC entry_guard_t *entry_guard_add_to_sample(guard_selection_t *gs,
+ const node_t *node);
+STATIC entry_guard_t *entry_guards_expand_sample(guard_selection_t *gs);
+STATIC char *entry_guard_encode_for_state(entry_guard_t *guard);
+STATIC entry_guard_t *entry_guard_parse_from_state(const char *s);
+STATIC void entry_guard_free(entry_guard_t *e);
+STATIC void entry_guards_update_filtered_sets(guard_selection_t *gs);
+STATIC int entry_guards_all_primary_guards_are_down(guard_selection_t *gs);
+/**
+ * @name Flags for sample_reachable_filtered_entry_guards()
+ */
+/**@{*/
+#define SAMPLE_EXCLUDE_CONFIRMED (1u<<0)
+#define SAMPLE_EXCLUDE_PRIMARY (1u<<1)
+#define SAMPLE_EXCLUDE_PENDING (1u<<2)
+#define SAMPLE_NO_UPDATE_PRIMARY (1u<<3)
+#define SAMPLE_EXCLUDE_NO_DESCRIPTOR (1u<<4)
+/**@}*/
+STATIC entry_guard_t *sample_reachable_filtered_entry_guards(
+ guard_selection_t *gs,
+ const entry_guard_restriction_t *rst,
+ unsigned flags);
+STATIC void entry_guard_consider_retry(entry_guard_t *guard);
+STATIC void make_guard_confirmed(guard_selection_t *gs, entry_guard_t *guard);
+STATIC void entry_guards_update_confirmed(guard_selection_t *gs);
+STATIC void entry_guards_update_primary(guard_selection_t *gs);
+STATIC int num_reachable_filtered_guards(guard_selection_t *gs,
+ const entry_guard_restriction_t *rst);
+STATIC void sampled_guards_update_from_consensus(guard_selection_t *gs);
+/**
+ * @name Possible guard-states for a circuit.
+ */
+/**@{*/
+/** State for a circuit that can (so far as the guard subsystem is
+ * concerned) be used for actual traffic as soon as it is successfully
+ * opened. */
+#define GUARD_CIRC_STATE_USABLE_ON_COMPLETION 1
+/** State for an non-open circuit that we shouldn't use for actual
+ * traffic, when it completes, unless other circuits to preferable
+ * guards fail. */
+#define GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD 2
+/** State for an open circuit that we shouldn't use for actual traffic
+ * unless other circuits to preferable guards fail. */
+#define GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD 3
+/** State for a circuit that can (so far as the guard subsystem is
+ * concerned) be used for actual traffic. */
+#define GUARD_CIRC_STATE_COMPLETE 4
+/** State for a circuit that is unusable, and will not become usable. */
+#define GUARD_CIRC_STATE_DEAD 5
+/**@}*/
+STATIC void entry_guards_note_guard_failure(guard_selection_t *gs,
+ entry_guard_t *guard);
+STATIC entry_guard_t *select_entry_guard_for_circuit(guard_selection_t *gs,
+ guard_usage_t usage,
+ const entry_guard_restriction_t *rst,
+ unsigned *state_out);
+STATIC void mark_primary_guards_maybe_reachable(guard_selection_t *gs);
+STATIC unsigned entry_guards_note_guard_success(guard_selection_t *gs,
+ entry_guard_t *guard,
+ unsigned old_state);
+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);
#endif
+void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs);
void remove_all_entry_guards(void);
-void entry_guards_compute_status(const or_options_t *options, time_t now);
-int entry_guard_register_connect_status(const char *digest, int succeeded,
- int mark_relay_status, time_t now);
-void entry_nodes_should_be_added(void);
+struct bridge_info_t;
+void entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport,
+ const uint8_t *rsa_id_digest);
+
int entry_list_is_constrained(const or_options_t *options);
-const node_t *choose_random_entry(cpath_build_state_t *state);
-const node_t *choose_random_dirguard(dirinfo_type_t t);
+int guards_retry_optimistic(const or_options_t *options);
+int entry_guards_parse_state_for_guard_selection(
+ guard_selection_t *gs, or_state_t *state, int set, char **msg);
int entry_guards_parse_state(or_state_t *state, int set, char **msg);
void entry_guards_update_state(or_state_t *state);
int getinfo_helper_entry_guards(control_connection_t *conn,
const char *question, char **answer,
const char **errmsg);
-void mark_bridge_list(void);
-void sweep_bridge_list(void);
-
-int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port,
- const char *digest);
-int extend_info_is_a_configured_bridge(const extend_info_t *ei);
-int routerinfo_is_a_configured_bridge(const routerinfo_t *ri);
-int node_is_a_configured_bridge(const node_t *node);
-void learned_router_identity(const tor_addr_t *addr, uint16_t port,
- const char *digest);
-struct bridge_line_t;
-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);
int entries_known_but_down(const or_options_t *options);
void entries_retry_all(const or_options_t *options);
-int any_bridge_supports_microdescriptors(void);
-const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr,
- uint16_t port);
-
-int any_bridges_dont_support_microdescriptors(void);
+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);
void entry_guards_free_all(void);
-const char *find_transport_name_by_bridge_addrport(const tor_addr_t *addr,
- uint16_t port);
-struct transport_t;
-int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port,
- const struct transport_t **transport);
-
-MOCK_DECL(int, transport_is_needed, (const char *transport_name));
-int validate_pluggable_transports_config(void);
-
double pathbias_get_close_success_count(entry_guard_t *guard);
double pathbias_get_use_success_count(entry_guard_t *guard);
@@ -179,9 +597,5 @@ guard_get_guardfraction_bandwidth(guardfraction_bandwidth_t *guardfraction_bw,
int orig_bandwidth,
uint32_t guardfraction_percentage);
-MOCK_DECL(smartlist_t *, list_bridge_identities, (void));
-MOCK_DECL(download_status_t *, get_bridge_dl_status_by_id,
- (const char *digest));
-
#endif
diff --git a/src/or/geoip.c b/src/or/geoip.c
index 00c055bbe7..2f0047fa06 100644
--- a/src/or/geoip.c
+++ b/src/or/geoip.c
@@ -880,7 +880,7 @@ geoip_get_transport_history(void)
/* If it's the first time we see this transport, note it. */
if (val == 1)
- smartlist_add(transports_used, tor_strdup(transport_name));
+ smartlist_add_strdup(transports_used, transport_name);
log_debug(LD_GENERAL, "Client from '%s' with transport '%s'. "
"I've now seen %d clients.",
diff --git a/src/or/hibernate.c b/src/or/hibernate.c
index e3c80b5f14..8f28abb980 100644
--- a/src/or/hibernate.c
+++ b/src/or/hibernate.c
@@ -424,8 +424,8 @@ configure_accounting(time_t now)
if (-0.50 <= delta && delta <= 0.50) {
/* The start of the period is now a little later or earlier than we
* remembered. That's fine; we might lose some bytes we could otherwise
- * have written, but better to err on the side of obeying people's
- * accounting settings. */
+ * have written, but better to err on the side of obeying accounting
+ * settings. */
log_info(LD_ACCT, "Accounting interval moved by %.02f%%; "
"that's fine.", delta*100);
interval_end_time = start_of_accounting_period_after(now);
@@ -896,7 +896,7 @@ hibernate_go_dormant(time_t now)
log_notice(LD_ACCT,"Going dormant. Blowing away remaining connections.");
/* Close all OR/AP/exit conns. Leave dir conns because we still want
- * to be able to upload server descriptors so people know we're still
+ * to be able to upload server descriptors so clients know we're still
* running, and download directories so we can detect if we're obsolete.
* Leave control conns because we still want to be controllable.
*/
diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c
new file mode 100644
index 0000000000..43cd8c3258
--- /dev/null
+++ b/src/or/hs_cache.c
@@ -0,0 +1,398 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cache.c
+ * \brief Handle hidden service descriptor caches.
+ **/
+
+/* For unit tests.*/
+#define HS_CACHE_PRIVATE
+
+#include "hs_cache.h"
+
+#include "or.h"
+#include "config.h"
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "networkstatus.h"
+#include "rendcache.h"
+
+/* Directory descriptor cache. Map indexed by blinded key. */
+static digest256map_t *hs_cache_v3_dir;
+
+/* Remove a given descriptor from our cache. */
+static void
+remove_v3_desc_as_dir(const hs_cache_dir_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_remove(hs_cache_v3_dir, desc->key);
+}
+
+/* Store a given descriptor in our cache. */
+static void
+store_v3_desc_as_dir(hs_cache_dir_descriptor_t *desc)
+{
+ tor_assert(desc);
+ digest256map_set(hs_cache_v3_dir, desc->key, desc);
+}
+
+/* Query our cache and return the entry or NULL if not found. */
+static hs_cache_dir_descriptor_t *
+lookup_v3_desc_as_dir(const uint8_t *key)
+{
+ tor_assert(key);
+ return digest256map_get(hs_cache_v3_dir, key);
+}
+
+/* Free a directory descriptor object. */
+static void
+cache_dir_desc_free(hs_cache_dir_descriptor_t *desc)
+{
+ if (desc == NULL) {
+ return;
+ }
+ hs_desc_plaintext_data_free(desc->plaintext_data);
+ tor_free(desc->encoded_desc);
+ tor_free(desc);
+}
+
+/* Helper function: Use by the free all function using the digest256map
+ * interface to cache entries. */
+static void
+cache_dir_desc_free_(void *ptr)
+{
+ hs_cache_dir_descriptor_t *desc = ptr;
+ cache_dir_desc_free(desc);
+}
+
+/* Create a new directory cache descriptor object from a encoded descriptor.
+ * On success, return the heap-allocated cache object, otherwise return NULL if
+ * we can't decode the descriptor. */
+static hs_cache_dir_descriptor_t *
+cache_dir_desc_new(const char *desc)
+{
+ hs_cache_dir_descriptor_t *dir_desc;
+
+ tor_assert(desc);
+
+ dir_desc = tor_malloc_zero(sizeof(hs_cache_dir_descriptor_t));
+ dir_desc->plaintext_data =
+ tor_malloc_zero(sizeof(hs_desc_plaintext_data_t));
+ dir_desc->encoded_desc = tor_strdup(desc);
+
+ if (hs_desc_decode_plaintext(desc, dir_desc->plaintext_data) < 0) {
+ log_debug(LD_DIR, "Unable to decode descriptor. Rejecting.");
+ goto err;
+ }
+
+ /* The blinded pubkey is the indexed key. */
+ dir_desc->key = dir_desc->plaintext_data->blinded_pubkey.pubkey;
+ dir_desc->created_ts = time(NULL);
+ return dir_desc;
+
+ err:
+ cache_dir_desc_free(dir_desc);
+ return NULL;
+}
+
+/* Return the size of a cache entry in bytes. */
+static size_t
+cache_get_entry_size(const hs_cache_dir_descriptor_t *entry)
+{
+ return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data)
+ + strlen(entry->encoded_desc));
+}
+
+/* Try to store a valid version 3 descriptor in the directory cache. Return 0
+ * on success else a negative value is returned indicating that we have a
+ * newer version in our cache. On error, caller is responsible to free the
+ * given descriptor desc. */
+static int
+cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc)
+{
+ hs_cache_dir_descriptor_t *cache_entry;
+
+ tor_assert(desc);
+
+ /* Verify if we have an entry in the cache for that key and if yes, check
+ * if we should replace it? */
+ cache_entry = lookup_v3_desc_as_dir(desc->key);
+ if (cache_entry != NULL) {
+ /* Only replace descriptor if revision-counter is greater than the one
+ * in our cache */
+ 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!");
+ 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));
+ cache_dir_desc_free(cache_entry);
+ }
+ /* Store the descriptor we just got. We are sure here that either we
+ * don't have the entry or we have a newer descriptor and the old one
+ * has been removed from the cache. */
+ store_v3_desc_as_dir(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));
+
+ /* XXX: Update HS statistics. We should have specific stats for v3. */
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Using the query which is the base64 encoded blinded key of a version 3
+ * descriptor, lookup in our directory cache the entry. If found, 1 is
+ * returned and desc_out is populated with a newly allocated string being the
+ * encoded descriptor. If not found, 0 is returned and desc_out is untouched.
+ * On error, a negative value is returned and desc_out is untouched. */
+static int
+cache_lookup_v3_as_dir(const char *query, const char **desc_out)
+{
+ int found = 0;
+ ed25519_public_key_t blinded_key;
+ const hs_cache_dir_descriptor_t *entry;
+
+ tor_assert(query);
+
+ /* Decode blinded key using the given query value. */
+ if (ed25519_public_from_base64(&blinded_key, query) < 0) {
+ log_info(LD_REND, "Unable to decode the v3 HSDir query %s.",
+ safe_str_client(query));
+ goto err;
+ }
+
+ entry = lookup_v3_desc_as_dir(blinded_key.pubkey);
+ if (entry != NULL) {
+ found = 1;
+ if (desc_out) {
+ *desc_out = entry->encoded_desc;
+ }
+ }
+
+ return found;
+
+ err:
+ return -1;
+}
+
+/* Clean the v3 cache by removing any entry that has expired using the
+ * <b>global_cutoff</b> value. If <b>global_cutoff</b> is 0, the cleaning
+ * process will use the lifetime found in the plaintext data section. Return
+ * the number of bytes cleaned. */
+STATIC size_t
+cache_clean_v3_as_dir(time_t now, time_t global_cutoff)
+{
+ size_t bytes_removed = 0;
+
+ /* Code flow error if this ever happens. */
+ tor_assert(global_cutoff >= 0);
+
+ if (!hs_cache_v3_dir) { /* No cache to clean. Just return. */
+ return 0;
+ }
+
+ DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_dir, key,
+ hs_cache_dir_descriptor_t *, entry) {
+ size_t entry_size;
+ time_t cutoff = global_cutoff;
+ if (!cutoff) {
+ /* Cutoff is the lifetime of the entry found in the descriptor. */
+ cutoff = now - entry->plaintext_data->lifetime_sec;
+ }
+
+ /* If the entry has been created _after_ the cutoff, not expired so
+ * continue to the next entry in our v3 cache. */
+ if (entry->created_ts > cutoff) {
+ continue;
+ }
+ /* Here, our entry has expired, remove and free. */
+ MAP_DEL_CURRENT(key);
+ entry_size = cache_get_entry_size(entry);
+ bytes_removed += entry_size;
+ /* Entry is not in the cache anymore, destroy it. */
+ cache_dir_desc_free(entry);
+ /* Update our cache entry allocation size for the OOM. */
+ rend_cache_decrement_allocation(entry_size);
+ /* Logging. */
+ {
+ char key_b64[BASE64_DIGEST256_LEN + 1];
+ base64_encode(key_b64, sizeof(key_b64), (const char *) key,
+ DIGEST256_LEN, 0);
+ log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache",
+ safe_str_client(key_b64));
+ }
+ } DIGEST256MAP_FOREACH_END;
+
+ return bytes_removed;
+}
+
+/* Given an encoded descriptor, store it in the directory cache depending on
+ * which version it is. Return a negative value on error. On success, 0 is
+ * returned. */
+int
+hs_cache_store_as_dir(const char *desc)
+{
+ hs_cache_dir_descriptor_t *dir_desc = NULL;
+
+ tor_assert(desc);
+
+ /* Create a new cache object. This can fail if the descriptor plaintext data
+ * is unparseable which in this case a log message will be triggered. */
+ dir_desc = cache_dir_desc_new(desc);
+ if (dir_desc == NULL) {
+ goto err;
+ }
+
+ /* Call the right function against the descriptor version. At this point,
+ * we are sure that the descriptor's version is supported else the
+ * decoding would have failed. */
+ switch (dir_desc->plaintext_data->version) {
+ case HS_VERSION_THREE:
+ default:
+ if (cache_store_v3_as_dir(dir_desc) < 0) {
+ goto err;
+ }
+ break;
+ }
+ return 0;
+
+ err:
+ cache_dir_desc_free(dir_desc);
+ return -1;
+}
+
+/* Using the query, lookup in our directory cache the entry. If found, 1 is
+ * returned and desc_out is populated with a newly allocated string being
+ * the encoded descriptor. If not found, 0 is returned and desc_out is
+ * untouched. On error, a negative value is returned and desc_out is
+ * untouched. */
+int
+hs_cache_lookup_as_dir(uint32_t version, const char *query,
+ const char **desc_out)
+{
+ int found;
+
+ tor_assert(query);
+ /* This should never be called with an unsupported version. */
+ tor_assert(hs_desc_is_supported_version(version));
+
+ switch (version) {
+ case HS_VERSION_THREE:
+ default:
+ found = cache_lookup_v3_as_dir(query, desc_out);
+ break;
+ }
+
+ return found;
+}
+
+/* Clean all directory caches using the current time now. */
+void
+hs_cache_clean_as_dir(time_t now)
+{
+ time_t cutoff;
+
+ /* Start with v2 cache cleaning. */
+ cutoff = now - rend_cache_max_entry_lifetime();
+ rend_cache_clean_v2_descs_as_dir(cutoff);
+
+ /* 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_dir(now, 0);
+}
+
+/* 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
+ * aware of this. */
+size_t
+hs_cache_handle_oom(time_t now, size_t min_remove_bytes)
+{
+ time_t k;
+ size_t bytes_removed = 0;
+
+ /* Our OOM handler called with 0 bytes to remove is a code flow error. */
+ tor_assert(min_remove_bytes != 0);
+
+ /* The algorithm is as follow. K is the oldest expected descriptor age.
+ *
+ * 1) Deallocate all entries from v2 cache that are older than K hours.
+ * 1.1) If the amount of remove bytes has been reached, stop.
+ * 2) Deallocate all entries from v3 cache that are older than K hours
+ * 2.1) If the amount of remove bytes has been reached, stop.
+ * 3) Set K = K - RendPostPeriod and repeat process until K is < 0.
+ *
+ * This ends up being O(Kn).
+ */
+
+ /* Set K to the oldest expected age in seconds which is the maximum
+ * lifetime of a cache entry. We'll use the v2 lifetime because it's much
+ * bigger than the v3 thus leading to cleaning older descriptors. */
+ k = rend_cache_max_entry_lifetime();
+
+ do {
+ time_t cutoff;
+
+ /* If K becomes negative, it means we've empty the caches so stop and
+ * return what we were able to cleanup. */
+ if (k < 0) {
+ break;
+ }
+ /* Compute a cutoff value with K and the current time. */
+ cutoff = now - k;
+
+ /* Start by cleaning the v2 cache with that cutoff. */
+ bytes_removed += rend_cache_clean_v2_descs_as_dir(cutoff);
+
+ if (bytes_removed < min_remove_bytes) {
+ /* We haven't remove enough bytes so clean v3 cache. */
+ bytes_removed += cache_clean_v3_as_dir(now, cutoff);
+ /* Decrement K by a post period to shorten the cutoff. */
+ k -= get_options()->RendPostPeriod;
+ }
+ } while (bytes_removed < min_remove_bytes);
+
+ return bytes_removed;
+}
+
+/**
+ * Return the maximum size of an HS descriptor we are willing to accept as an
+ * HSDir.
+ */
+unsigned int
+hs_cache_get_max_descriptor_size(void)
+{
+ return (unsigned) networkstatus_get_param(NULL,
+ "HSV3MaxDescriptorSize",
+ HS_DESC_MAX_LEN, 1, INT32_MAX);
+}
+
+/* Initialize the hidden service cache subsystem. */
+void
+hs_cache_init(void)
+{
+ /* Calling this twice is very wrong code flow. */
+ tor_assert(!hs_cache_v3_dir);
+ hs_cache_v3_dir = digest256map_new();
+}
+
+/* Cleanup the hidden service cache subsystem. */
+void
+hs_cache_free_all(void)
+{
+ digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_);
+ hs_cache_v3_dir = NULL;
+}
+
diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h
new file mode 100644
index 0000000000..ba95e73338
--- /dev/null
+++ b/src/or/hs_cache.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_cache.h
+ * \brief Header file for hs_cache.c
+ **/
+
+#ifndef TOR_HS_CACHE_H
+#define TOR_HS_CACHE_H
+
+#include <stdint.h>
+
+#include "crypto.h"
+#include "crypto_ed25519.h"
+#include "hs_common.h"
+#include "hs_descriptor.h"
+#include "torcert.h"
+
+/* 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 {
+ /* This object is indexed using the blinded pubkey located in the plaintext
+ * data which is populated only once the descriptor has been successfully
+ * decoded and validated. This simply points to that pubkey. */
+ const uint8_t *key;
+
+ /* When does this entry has been created. Used to expire entries. */
+ time_t created_ts;
+
+ /* Descriptor plaintext information. Obviously, we can't decrypt the
+ * encrypted part of the descriptor. */
+ hs_desc_plaintext_data_t *plaintext_data;
+
+ /* Encoded descriptor which is basically in text form. It's a NUL terminated
+ * string thus safe to strlen(). */
+ char *encoded_desc;
+} hs_cache_dir_descriptor_t;
+
+/* Public API */
+
+void hs_cache_init(void);
+void hs_cache_free_all(void);
+void hs_cache_clean_as_dir(time_t now);
+size_t hs_cache_handle_oom(time_t now, size_t min_remove_bytes);
+
+unsigned int hs_cache_get_max_descriptor_size(void);
+
+/* Store and Lookup function. They are version agnostic that is depending on
+ * the requested version of the descriptor, it will be re-routed to the
+ * right function. */
+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);
+
+#ifdef HS_CACHE_PRIVATE
+
+STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff);
+
+#endif /* HS_CACHE_PRIVATE */
+
+#endif /* TOR_HS_CACHE_H */
+
diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c
new file mode 100644
index 0000000000..5003b4b593
--- /dev/null
+++ b/src/or/hs_circuitmap.c
@@ -0,0 +1,328 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuitmap.c
+ *
+ * \brief Manage the hidden service circuitmap: A hash table that maps binary
+ * tokens to introduction and rendezvous circuits.
+ **/
+
+#define HS_CIRCUITMAP_PRIVATE
+
+#include "or.h"
+#include "config.h"
+#include "circuitlist.h"
+#include "hs_circuitmap.h"
+
+/************************** HS circuitmap code *******************************/
+
+/* This is the hidden service circuitmap. It's a hash table that maps
+ introduction and rendezvous tokens to specific circuits such that given a
+ token it's easy to find the corresponding circuit. */
+static struct hs_circuitmap_ht *the_hs_circuitmap = NULL;
+
+/* This is a helper function used by the hash table code (HT_). It returns 1 if
+ * two circuits have the same HS token. */
+static int
+hs_circuits_have_same_token(const or_circuit_t *first_circuit,
+ const or_circuit_t *second_circuit)
+{
+ const hs_token_t *first_token;
+ const hs_token_t *second_token;
+
+ tor_assert(first_circuit);
+ tor_assert(second_circuit);
+
+ first_token = first_circuit->hs_token;
+ second_token = second_circuit->hs_token;
+
+ /* Both circs must have a token */
+ if (BUG(!first_token) || BUG(!second_token)) {
+ return 0;
+ }
+
+ if (first_token->type != second_token->type) {
+ return 0;
+ }
+
+ if (first_token->token_len != second_token->token_len)
+ return 0;
+
+ return tor_memeq(first_token->token,
+ second_token->token,
+ first_token->token_len);
+}
+
+/* This is a helper function for the hash table code (HT_). It hashes a circuit
+ * HS token into an unsigned int for use as a key by the hash table routines.*/
+static inline unsigned int
+hs_circuit_hash_token(const or_circuit_t *circuit)
+{
+ tor_assert(circuit->hs_token);
+
+ return (unsigned) siphash24g(circuit->hs_token->token,
+ circuit->hs_token->token_len);
+}
+
+/* Register the circuitmap hash table */
+HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct
+ or_circuit_t, // The name of the element struct,
+ hs_circuitmap_node, // The name of HT_ENTRY member
+ hs_circuit_hash_token, hs_circuits_have_same_token)
+
+HT_GENERATE2(hs_circuitmap_ht, or_circuit_t, hs_circuitmap_node,
+ hs_circuit_hash_token, hs_circuits_have_same_token,
+ 0.6, tor_reallocarray, tor_free_)
+
+#ifdef TOR_UNIT_TESTS
+
+/* Return the global HS circuitmap. Used by unittests. */
+hs_circuitmap_ht *
+get_hs_circuitmap(void)
+{
+ return the_hs_circuitmap;
+}
+
+#endif
+
+/****************** HS circuitmap utility functions **************************/
+
+/** Return a new HS token of type <b>type</b> containing <b>token</b>. */
+static hs_token_t *
+hs_token_new(hs_token_type_t type, size_t token_len,
+ const uint8_t *token)
+{
+ tor_assert(token);
+
+ hs_token_t *hs_token = tor_malloc_zero(sizeof(hs_token_t));
+ hs_token->type = type;
+ hs_token->token_len = token_len;
+ hs_token->token = tor_memdup(token, token_len);
+
+ return hs_token;
+}
+
+/** Free memory allocated by this <b>hs_token</b>. */
+static void
+hs_token_free(hs_token_t *hs_token)
+{
+ if (!hs_token) {
+ return;
+ }
+
+ tor_free(hs_token->token);
+ tor_free(hs_token);
+}
+
+/** Return the circuit from the circuitmap with token <b>search_token</b>. */
+static or_circuit_t *
+get_circuit_with_token(hs_token_t *search_token)
+{
+ tor_assert(the_hs_circuitmap);
+
+ /* We use a dummy circuit object for the hash table search routine. */
+ or_circuit_t search_circ;
+ search_circ.hs_token = search_token;
+ return HT_FIND(hs_circuitmap_ht, the_hs_circuitmap, &search_circ);
+}
+
+/* Helper function that registers <b>circ</b> with <b>token</b> on the HS
+ circuitmap. This function steals reference of <b>token</b>. */
+static void
+hs_circuitmap_register_impl(or_circuit_t *circ, hs_token_t *token)
+{
+ tor_assert(circ);
+ tor_assert(token);
+ tor_assert(the_hs_circuitmap);
+
+ /* If this circuit already has a token, clear it. */
+ if (circ->hs_token) {
+ hs_circuitmap_remove_circuit(circ);
+ }
+
+ /* Kill old circuits with the same token. We want new intro/rend circuits to
+ take precedence over old ones, so that HSes and clients and reestablish
+ killed circuits without changing the HS token. */
+ {
+ or_circuit_t *found_circ;
+ found_circ = get_circuit_with_token(token);
+ if (found_circ) {
+ hs_circuitmap_remove_circuit(found_circ);
+ if (!found_circ->base_.marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(found_circ),
+ END_CIRC_REASON_FINISHED);
+ }
+ }
+ }
+
+ /* Register circuit and token to circuitmap. */
+ circ->hs_token = token;
+ HT_INSERT(hs_circuitmap_ht, the_hs_circuitmap, circ);
+}
+
+/** Helper function: Register <b>circ</b> of <b>type</b> on the HS
+ * circuitmap. Use the HS <b>token</b> as the key to the hash table. If
+ * <b>token</b> is not set, clear the circuit of any HS tokens. */
+static void
+hs_circuitmap_register_circuit(or_circuit_t *circ,
+ hs_token_type_t type, size_t token_len,
+ const uint8_t *token)
+{
+ hs_token_t *hs_token = NULL;
+
+ /* Create a new token and register it to the circuitmap */
+ tor_assert(token);
+ hs_token = hs_token_new(type, token_len, token);
+ tor_assert(hs_token);
+ hs_circuitmap_register_impl(circ, hs_token);
+}
+
+/* Query circuitmap for circuit with <b>token</b> of size <b>token_len</b>.
+ * Only returns a circuit with purpose equal to the <b>wanted_circ_purpose</b>
+ * parameter and if it is NOT marked for close. Return NULL if no such circuit
+ * is found. */
+static or_circuit_t *
+hs_circuitmap_get_circuit(hs_token_type_t type,
+ size_t token_len,
+ const uint8_t *token,
+ uint8_t wanted_circ_purpose)
+{
+ or_circuit_t *found_circ = NULL;
+
+ tor_assert(the_hs_circuitmap);
+
+ /* Check the circuitmap if we have a circuit with this token */
+ {
+ hs_token_t *search_hs_token = hs_token_new(type, token_len, token);
+ tor_assert(search_hs_token);
+ found_circ = get_circuit_with_token(search_hs_token);
+ hs_token_free(search_hs_token);
+ }
+
+ /* Check that the circuit is useful to us */
+ if (!found_circ ||
+ found_circ->base_.purpose != wanted_circ_purpose ||
+ found_circ->base_.marked_for_close) {
+ return NULL;
+ }
+
+ return found_circ;
+}
+
+/************** Public circuitmap API ****************************************/
+
+/* Public function: Return v3 introduction circuit with <b>auth_key</b>. Return
+ * NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_intro_circ_v3(const ed25519_public_key_t *auth_key)
+{
+ tor_assert(auth_key);
+
+ return hs_circuitmap_get_circuit(HS_TOKEN_INTRO_V3,
+ ED25519_PUBKEY_LEN, auth_key->pubkey,
+ CIRCUIT_PURPOSE_INTRO_POINT);
+}
+
+/* Public function: Return v2 introduction circuit with <b>digest</b>. Return
+ * NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_intro_circ_v2(const uint8_t *digest)
+{
+ tor_assert(digest);
+
+ return hs_circuitmap_get_circuit(HS_TOKEN_INTRO_V2,
+ REND_TOKEN_LEN, digest,
+ CIRCUIT_PURPOSE_INTRO_POINT);
+}
+
+/* Public function: Return rendezvous circuit with rendezvous
+ * <b>cookie</b>. Return NULL if no such circuit is found in the circuitmap. */
+or_circuit_t *
+hs_circuitmap_get_rend_circ(const uint8_t *cookie)
+{
+ tor_assert(cookie);
+
+ return hs_circuitmap_get_circuit(HS_TOKEN_REND,
+ REND_TOKEN_LEN, cookie,
+ CIRCUIT_PURPOSE_REND_POINT_WAITING);
+}
+
+/* Public function: Register rendezvous circuit with key <b>cookie</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_rend_circ(or_circuit_t *circ, const uint8_t *cookie)
+{
+ hs_circuitmap_register_circuit(circ,
+ HS_TOKEN_REND,
+ REND_TOKEN_LEN, cookie);
+}
+
+/* Public function: Register v2 intro circuit with key <b>digest</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v2(or_circuit_t *circ, const uint8_t *digest)
+{
+ hs_circuitmap_register_circuit(circ,
+ HS_TOKEN_INTRO_V2,
+ REND_TOKEN_LEN, digest);
+}
+
+/* Public function: Register v3 intro circuit with key <b>auth_key</b> to the
+ * circuitmap. */
+void
+hs_circuitmap_register_intro_circ_v3(or_circuit_t *circ,
+ const ed25519_public_key_t *auth_key)
+{
+ hs_circuitmap_register_circuit(circ,
+ HS_TOKEN_INTRO_V3,
+ ED25519_PUBKEY_LEN, auth_key->pubkey);
+}
+
+/** Remove this circuit from the HS circuitmap. Clear its HS token, and remove
+ * it from the hashtable. */
+void
+hs_circuitmap_remove_circuit(or_circuit_t *circ)
+{
+ tor_assert(the_hs_circuitmap);
+
+ if (!circ || !circ->hs_token) {
+ return;
+ }
+
+ /* Remove circ from circuitmap */
+ or_circuit_t *tmp;
+ tmp = HT_REMOVE(hs_circuitmap_ht, the_hs_circuitmap, circ);
+ /* ... and ensure the removal was successful. */
+ if (tmp) {
+ tor_assert(tmp == circ);
+ } else {
+ log_warn(LD_BUG, "Could not find circuit (%u) in circuitmap.",
+ circ->p_circ_id);
+ }
+
+ /* Clear token from circ */
+ hs_token_free(circ->hs_token);
+ circ->hs_token = NULL;
+}
+
+/* Initialize the global HS circuitmap. */
+void
+hs_circuitmap_init(void)
+{
+ tor_assert(!the_hs_circuitmap);
+
+ the_hs_circuitmap = tor_malloc_zero(sizeof(struct hs_circuitmap_ht));
+ HT_INIT(hs_circuitmap_ht, the_hs_circuitmap);
+}
+
+/* Free all memory allocated by the global HS circuitmap. */
+void
+hs_circuitmap_free_all(void)
+{
+ if (the_hs_circuitmap) {
+ HT_CLEAR(hs_circuitmap_ht, the_hs_circuitmap);
+ tor_free(the_hs_circuitmap);
+ }
+}
+
diff --git a/src/or/hs_circuitmap.h b/src/or/hs_circuitmap.h
new file mode 100644
index 0000000000..b587039310
--- /dev/null
+++ b/src/or/hs_circuitmap.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_circuitmap.h
+ * \brief Header file for hs_circuitmap.c.
+ **/
+
+#ifndef TOR_HS_CIRCUITMAP_H
+#define TOR_HS_CIRCUITMAP_H
+
+typedef HT_HEAD(hs_circuitmap_ht, or_circuit_t) hs_circuitmap_ht;
+
+typedef struct hs_token_s hs_token_t;
+struct or_circuit_t;
+
+/** Public HS circuitmap API: */
+
+struct or_circuit_t *hs_circuitmap_get_rend_circ(const uint8_t *cookie);
+struct or_circuit_t *hs_circuitmap_get_intro_circ_v3(
+ const ed25519_public_key_t *auth_key);
+struct or_circuit_t *hs_circuitmap_get_intro_circ_v2(const uint8_t *digest);
+
+void hs_circuitmap_register_rend_circ(struct or_circuit_t *circ,
+ const uint8_t *cookie);
+void hs_circuitmap_register_intro_circ_v2(struct or_circuit_t *circ,
+ const uint8_t *digest);
+void hs_circuitmap_register_intro_circ_v3(struct or_circuit_t *circ,
+ const ed25519_public_key_t *auth_key);
+
+void hs_circuitmap_remove_circuit(struct or_circuit_t *circ);
+
+void hs_circuitmap_init(void);
+void hs_circuitmap_free_all(void);
+
+#ifdef HS_CIRCUITMAP_PRIVATE
+
+/** Represents the type of HS token. */
+typedef enum {
+ /** A rendezvous cookie (128bit)*/
+ HS_TOKEN_REND,
+ /** A v2 introduction point pubkey (160bit) */
+ HS_TOKEN_INTRO_V2,
+ /** A v3 introduction point pubkey (256bit) */
+ HS_TOKEN_INTRO_V3,
+} hs_token_type_t;
+
+/** Represents a token used in the HS protocol. Each such token maps to a
+ * specific introduction or rendezvous circuit. */
+struct hs_token_s {
+ /* Type of HS token. */
+ hs_token_type_t type;
+
+ /* The size of the token (depends on the type). */
+ size_t token_len;
+
+ /* The token itself. Memory allocated at runtime. */
+ uint8_t *token;
+};
+
+#endif /* HS_CIRCUITMAP_PRIVATE */
+
+#ifdef TOR_UNIT_TESTS
+
+hs_circuitmap_ht *get_hs_circuitmap(void);
+
+#endif /* TOR_UNIT_TESTS */
+
+#endif /* TOR_HS_CIRCUITMAP_H */
+
diff --git a/src/or/hs_common.c b/src/or/hs_common.c
new file mode 100644
index 0000000000..de96946ab5
--- /dev/null
+++ b/src/or/hs_common.c
@@ -0,0 +1,270 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_common.c
+ * \brief Contains code shared between different HS protocol version as well
+ * as useful data structures and accessors used by other subsystems.
+ * The rendcommon.c should only contains code relating to the v2
+ * protocol.
+ **/
+
+#include "or.h"
+
+#include "config.h"
+#include "networkstatus.h"
+#include "hs_common.h"
+#include "rendcommon.h"
+
+/* Create a new rend_data_t for a specific given <b>version</b>.
+ * Return a pointer to the newly allocated data structure. */
+static rend_data_t *
+rend_data_alloc(uint32_t version)
+{
+ rend_data_t *rend_data = NULL;
+
+ switch (version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2 = tor_malloc_zero(sizeof(*v2));
+ v2->base_.version = HS_VERSION_TWO;
+ v2->base_.hsdirs_fp = smartlist_new();
+ rend_data = &v2->base_;
+ break;
+ }
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ return rend_data;
+}
+
+/** Free all storage associated with <b>data</b> */
+void
+rend_data_free(rend_data_t *data)
+{
+ if (!data) {
+ return;
+ }
+ /* By using our allocation function, this should always be set. */
+ tor_assert(data->hsdirs_fp);
+ /* Cleanup the HSDir identity digest. */
+ SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d));
+ smartlist_free(data->hsdirs_fp);
+ /* Depending on the version, cleanup. */
+ switch (data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = TO_REND_DATA_V2(data);
+ tor_free(v2_data);
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+}
+
+/* Allocate and return a deep copy of <b>data</b>. */
+rend_data_t *
+rend_data_dup(const rend_data_t *data)
+{
+ rend_data_t *data_dup = NULL;
+ smartlist_t *hsdirs_fp = smartlist_new();
+
+ tor_assert(data);
+ tor_assert(data->hsdirs_fp);
+
+ SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp,
+ smartlist_add(hsdirs_fp, tor_memdup(fp, DIGEST_LEN)));
+
+ switch (data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = tor_memdup(TO_REND_DATA_V2(data),
+ sizeof(*v2_data));
+ data_dup = &v2_data->base_;
+ data_dup->hsdirs_fp = hsdirs_fp;
+ break;
+ }
+ default:
+ tor_assert(0);
+ break;
+ }
+
+ return data_dup;
+}
+
+/* Compute the descriptor ID for each HS descriptor replica and save them. A
+ * valid onion address must be present in the <b>rend_data</b>.
+ *
+ * Return 0 on success else -1. */
+static int
+compute_desc_id(rend_data_t *rend_data)
+{
+ int ret = 0;
+ unsigned replica;
+ time_t now = time(NULL);
+
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ {
+ rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data);
+ /* Compute descriptor ID for each replicas. */
+ for (replica = 0; replica < ARRAY_LENGTH(v2_data->descriptor_id);
+ replica++) {
+ ret = rend_compute_v2_desc_id(v2_data->descriptor_id[replica],
+ v2_data->onion_address,
+ v2_data->descriptor_cookie,
+ now, replica);
+ if (ret < 0) {
+ goto end;
+ }
+ }
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ end:
+ return ret;
+}
+
+/* Allocate and initialize a rend_data_t object for a service using the
+ * provided arguments. All arguments are optional (can be NULL), except from
+ * <b>onion_address</b> which MUST be set. The <b>pk_digest</b> is the hash of
+ * the service private key. The <b>cookie</b> is the rendezvous cookie and
+ * <b>auth_type</b> is which authentiation this service is configured with.
+ *
+ * Return a valid rend_data_t pointer. This only returns a version 2 object of
+ * rend_data_t. */
+rend_data_t *
+rend_data_service_create(const char *onion_address, const char *pk_digest,
+ const uint8_t *cookie, rend_auth_type_t auth_type)
+{
+ /* Create a rend_data_t object for version 2. */
+ rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO);
+ rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data);
+
+ /* We need at least one else the call is wrong. */
+ tor_assert(onion_address != NULL);
+
+ if (pk_digest) {
+ memcpy(v2->rend_pk_digest, pk_digest, sizeof(v2->rend_pk_digest));
+ }
+ if (cookie) {
+ memcpy(rend_data->rend_cookie, cookie, sizeof(rend_data->rend_cookie));
+ }
+
+ strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address));
+ v2->auth_type = auth_type;
+
+ return rend_data;
+}
+
+/* Allocate and initialize a rend_data_t object for a client request using the
+ * given arguments. Either an onion address or a descriptor ID is needed. Both
+ * can be given but in this case only the onion address will be used to make
+ * the descriptor fetch. The <b>cookie</b> is the rendezvous cookie and
+ * <b>auth_type</b> is which authentiation the service is configured with.
+ *
+ * Return a valid rend_data_t pointer or NULL on error meaning the
+ * descriptor IDs couldn't be computed from the given data. */
+rend_data_t *
+rend_data_client_create(const char *onion_address, const char *desc_id,
+ const char *cookie, rend_auth_type_t auth_type)
+{
+ /* Create a rend_data_t object for version 2. */
+ rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO);
+ rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data);
+
+ /* We need at least one else the call is wrong. */
+ tor_assert(onion_address != NULL || desc_id != NULL);
+
+ if (cookie) {
+ memcpy(v2->descriptor_cookie, cookie, sizeof(v2->descriptor_cookie));
+ }
+ if (desc_id) {
+ memcpy(v2->desc_id_fetch, desc_id, sizeof(v2->desc_id_fetch));
+ }
+ if (onion_address) {
+ strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address));
+ if (compute_desc_id(rend_data) < 0) {
+ goto error;
+ }
+ }
+
+ v2->auth_type = auth_type;
+
+ return rend_data;
+
+ error:
+ rend_data_free(rend_data);
+ return NULL;
+}
+
+/* Return the onion address from the rend data. Depending on the version,
+ * the size of the address can vary but it's always NUL terminated. */
+const char *
+rend_data_get_address(const rend_data_t *rend_data)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ return TO_REND_DATA_V2(rend_data)->onion_address;
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Return the descriptor ID for a specific replica number from the rend
+ * data. The returned data is a binary digest and depending on the version its
+ * size can vary. The size of the descriptor ID is put in <b>len_out</b> if
+ * non NULL. */
+const char *
+rend_data_get_desc_id(const rend_data_t *rend_data, uint8_t replica,
+ size_t *len_out)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ tor_assert(replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS);
+ if (len_out) {
+ *len_out = DIGEST_LEN;
+ }
+ return TO_REND_DATA_V2(rend_data)->descriptor_id[replica];
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
+/* Return the public key digest using the given <b>rend_data</b>. The size of
+ * the digest is put in <b>len_out</b> (if set) which can differ depending on
+ * the version. */
+const uint8_t *
+rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out)
+{
+ tor_assert(rend_data);
+
+ switch (rend_data->version) {
+ case HS_VERSION_TWO:
+ {
+ const rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data);
+ if (len_out) {
+ *len_out = sizeof(v2_data->rend_pk_digest);
+ }
+ return (const uint8_t *) v2_data->rend_pk_digest;
+ }
+ default:
+ /* We should always have a supported version. */
+ tor_assert(0);
+ }
+}
+
diff --git a/src/or/hs_common.h b/src/or/hs_common.h
new file mode 100644
index 0000000000..e0ab510ea4
--- /dev/null
+++ b/src/or/hs_common.h
@@ -0,0 +1,43 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_common.h
+ * \brief Header file containing common data for the whole HS subsytem.
+ **/
+
+#ifndef TOR_HS_COMMON_H
+#define TOR_HS_COMMON_H
+
+#include "or.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
+
+/* Denotes ed25519 authentication key on ESTABLISH_INTRO cell. */
+#define AUTH_KEY_ED25519 0x02
+
+/* String prefix for the signature of ESTABLISH_INTRO */
+#define ESTABLISH_INTRO_SIG_PREFIX "Tor establish-intro cell v1"
+
+void rend_data_free(rend_data_t *data);
+rend_data_t *rend_data_dup(const rend_data_t *data);
+rend_data_t *rend_data_client_create(const char *onion_address,
+ const char *desc_id,
+ const char *cookie,
+ rend_auth_type_t auth_type);
+rend_data_t *rend_data_service_create(const char *onion_address,
+ const char *pk_digest,
+ const uint8_t *cookie,
+ rend_auth_type_t auth_type);
+const char *rend_data_get_address(const rend_data_t *rend_data);
+const char *rend_data_get_desc_id(const rend_data_t *rend_data,
+ uint8_t replica, size_t *len_out);
+const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data,
+ size_t *len_out);
+
+#endif /* TOR_HS_COMMON_H */
+
diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c
new file mode 100644
index 0000000000..938b7a77df
--- /dev/null
+++ b/src/or/hs_descriptor.c
@@ -0,0 +1,1896 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.c
+ * \brief Handle hidden service descriptor encoding/decoding.
+ **/
+
+/* For unit tests.*/
+#define HS_DESCRIPTOR_PRIVATE
+
+#include "hs_descriptor.h"
+
+#include "or.h"
+#include "ed25519_cert.h" /* Trunnel interface. */
+#include "parsecommon.h"
+#include "rendcache.h"
+#include "hs_cache.h"
+#include "torcert.h" /* tor_cert_encode_ed22519() */
+
+/* Constant string value used for the descriptor format. */
+#define str_hs_desc "hs-descriptor"
+#define str_desc_cert "descriptor-signing-key-cert"
+#define str_rev_counter "revision-counter"
+#define str_superencrypted "superencrypted"
+#define str_signature "signature"
+#define str_lifetime "descriptor-lifetime"
+/* Constant string value for the encrypted part of the descriptor. */
+#define str_create2_formats "create2-formats"
+#define str_auth_required "authentication-required"
+#define str_single_onion "single-onion-service"
+#define str_intro_point "introduction-point"
+#define str_ip_auth_key "auth-key"
+#define str_ip_enc_key "enc-key"
+#define str_ip_enc_key_cert "enc-key-certification"
+#define str_intro_point_start "\n" str_intro_point " "
+/* Constant string value for the construction to encrypt the encrypted data
+ * section. */
+#define str_enc_hsdir_data "hsdir-superencrypted-data"
+/* Prefix required to compute/verify HS desc signatures */
+#define str_desc_sig_prefix "Tor onion service descriptor sig v3"
+
+/* Authentication supported types. */
+static const struct {
+ hs_desc_auth_type_t type;
+ const char *identifier;
+} auth_types[] = {
+ { HS_DESC_AUTH_PASSWORD, "password" },
+ { HS_DESC_AUTH_ED25519, "ed25519" },
+ /* Indicate end of array. */
+ { 0, NULL }
+};
+
+/* Descriptor ruleset. */
+static token_rule_t hs_desc_v3_token_table[] = {
+ T1_START(str_hs_desc, R_HS_DESCRIPTOR, EQ(1), NO_OBJ),
+ T1(str_lifetime, R3_DESC_LIFETIME, EQ(1), NO_OBJ),
+ T1(str_desc_cert, R3_DESC_SIGNING_CERT, NO_ARGS, NEED_OBJ),
+ T1(str_rev_counter, R3_REVISION_COUNTER, EQ(1), NO_OBJ),
+ T1(str_superencrypted, R3_SUPERENCRYPTED, NO_ARGS, NEED_OBJ),
+ T1_END(str_signature, R3_SIGNATURE, EQ(1), NO_OBJ),
+ END_OF_TABLE
+};
+
+/* Descriptor ruleset for the encrypted section. */
+static token_rule_t hs_desc_encrypted_v3_token_table[] = {
+ T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ),
+ T01(str_auth_required, R3_AUTHENTICATION_REQUIRED, ARGS, NO_OBJ),
+ T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ),
+ END_OF_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),
+ T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ),
+ T1(str_ip_enc_key, R3_INTRO_ENC_KEY, ARGS, OBJ_OK),
+ T1_END(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERTIFICATION,
+ NO_ARGS, NEED_OBJ),
+ 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);
+ if (ip->enc_key_type == HS_DESC_KEY_TYPE_LEGACY) {
+ crypto_pk_free(ip->enc_key.legacy);
+ }
+ 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)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->encrypted_blob) {
+ tor_free(desc->encrypted_blob);
+ }
+ tor_cert_free(desc->signing_key_cert);
+
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* Free the content of the encrypted section of a descriptor. */
+static void
+desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ if (desc->auth_types) {
+ SMARTLIST_FOREACH(desc->auth_types, char *, a, tor_free(a));
+ smartlist_free(desc->auth_types);
+ }
+ if (desc->intro_points) {
+ SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip,
+ desc_intro_point_free(ip));
+ smartlist_free(desc->intro_points);
+ }
+ memwipe(desc, 0, sizeof(*desc));
+}
+
+/* === ENCODING === */
+
+/* Encode the given link specifier objects into a newly allocated string.
+ * This can't fail so caller can always assume a valid string being
+ * returned. */
+STATIC char *
+encode_link_specifiers(const smartlist_t *specs)
+{
+ char *encoded_b64 = NULL;
+ link_specifier_list_t *lslist = link_specifier_list_new();
+
+ tor_assert(specs);
+ /* No link specifiers is a code flow error, can't happen. */
+ tor_assert(smartlist_len(specs) > 0);
+ tor_assert(smartlist_len(specs) <= UINT8_MAX);
+
+ link_specifier_list_set_n_spec(lslist, smartlist_len(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;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ link_specifier_list_add_spec(lslist, ls);
+ } SMARTLIST_FOREACH_END(spec);
+
+ {
+ uint8_t *encoded;
+ ssize_t encoded_len, encoded_b64_len, ret;
+
+ encoded_len = link_specifier_list_encoded_len(lslist);
+ tor_assert(encoded_len > 0);
+ encoded = tor_malloc_zero(encoded_len);
+ ret = link_specifier_list_encode(encoded, encoded_len, lslist);
+ tor_assert(ret == encoded_len);
+
+ /* Base64 encode our binary format. Add extra NUL byte for the base64
+ * encoded value. */
+ encoded_b64_len = base64_encode_size(encoded_len, 0) + 1;
+ encoded_b64 = tor_malloc_zero(encoded_b64_len);
+ ret = base64_encode(encoded_b64, encoded_b64_len, (const char *) encoded,
+ encoded_len, 0);
+ tor_assert(ret == (encoded_b64_len - 1));
+ tor_free(encoded);
+ }
+
+ link_specifier_list_free(lslist);
+ return encoded_b64;
+}
+
+/* Encode an introduction point encryption key and return a newly allocated
+ * string with it. On failure, return NULL. */
+static char *
+encode_enc_key(const ed25519_public_key_t *sig_key,
+ const hs_desc_intro_point_t *ip)
+{
+ char *encoded = NULL;
+ time_t now = time(NULL);
+
+ tor_assert(sig_key);
+ tor_assert(ip);
+
+ switch (ip->enc_key_type) {
+ case HS_DESC_KEY_TYPE_LEGACY:
+ {
+ char *key_str, b64_cert[256];
+ ssize_t cert_len;
+ size_t key_str_len;
+ uint8_t *cert_data = NULL;
+
+ /* Create cross certification cert. */
+ cert_len = tor_make_rsa_ed25519_crosscert(sig_key, ip->enc_key.legacy,
+ now + HS_DESC_CERT_LIFETIME,
+ &cert_data);
+ if (cert_len < 0) {
+ log_warn(LD_REND, "Unable to create legacy crosscert.");
+ goto err;
+ }
+ /* Encode cross cert. */
+ if (base64_encode(b64_cert, sizeof(b64_cert), (const char *) cert_data,
+ cert_len, BASE64_ENCODE_MULTILINE) < 0) {
+ tor_free(cert_data);
+ log_warn(LD_REND, "Unable to encode legacy crosscert.");
+ goto err;
+ }
+ tor_free(cert_data);
+ /* Convert the encryption key to a string. */
+ if (crypto_pk_write_public_key_to_string(ip->enc_key.legacy, &key_str,
+ &key_str_len) < 0) {
+ log_warn(LD_REND, "Unable to encode legacy encryption key.");
+ goto err;
+ }
+ tor_asprintf(&encoded,
+ "%s legacy\n%s" /* Newline is added by the call above. */
+ "%s\n"
+ "-----BEGIN CROSSCERT-----\n"
+ "%s"
+ "-----END CROSSCERT-----",
+ str_ip_enc_key, key_str,
+ str_ip_enc_key_cert, b64_cert);
+ tor_free(key_str);
+ break;
+ }
+ case HS_DESC_KEY_TYPE_CURVE25519:
+ {
+ int signbit, ret;
+ char *encoded_cert, key_fp_b64[CURVE25519_BASE64_PADDED_LEN + 1];
+ ed25519_keypair_t curve_kp;
+
+ if (ed25519_keypair_from_curve25519_keypair(&curve_kp, &signbit,
+ &ip->enc_key.curve25519)) {
+ goto err;
+ }
+ tor_cert_t *cross_cert = tor_cert_create(&curve_kp,
+ CERT_TYPE_CROSS_HS_IP_KEYS,
+ sig_key, now,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ memwipe(&curve_kp, 0, sizeof(curve_kp));
+ if (!cross_cert) {
+ goto err;
+ }
+ ret = tor_cert_encode_ed22519(cross_cert, &encoded_cert);
+ tor_cert_free(cross_cert);
+ if (ret) {
+ goto err;
+ }
+ if (curve25519_public_to_base64(key_fp_b64,
+ &ip->enc_key.curve25519.pubkey) < 0) {
+ tor_free(encoded_cert);
+ goto err;
+ }
+ tor_asprintf(&encoded,
+ "%s ntor %s\n"
+ "%s\n%s",
+ str_ip_enc_key, key_fp_b64,
+ str_ip_enc_key_cert, encoded_cert);
+ tor_free(encoded_cert);
+ break;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ err:
+ return encoded;
+}
+
+/* Encode an introduction point object and return a newly allocated string
+ * with it. On failure, return NULL. */
+static char *
+encode_intro_point(const ed25519_public_key_t *sig_key,
+ const hs_desc_intro_point_t *ip)
+{
+ char *encoded_ip = NULL;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(ip);
+ tor_assert(sig_key);
+
+ /* Encode link specifier. */
+ {
+ char *ls_str = encode_link_specifiers(ip->link_specifiers);
+ smartlist_add_asprintf(lines, "%s %s", str_intro_point, ls_str);
+ tor_free(ls_str);
+ }
+
+ /* Authentication key encoding. */
+ {
+ char *encoded_cert;
+ if (tor_cert_encode_ed22519(ip->auth_key_cert, &encoded_cert) < 0) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s\n%s", str_ip_auth_key, encoded_cert);
+ tor_free(encoded_cert);
+ }
+
+ /* Encryption key encoding. */
+ {
+ char *encoded_enc_key = encode_enc_key(sig_key, ip);
+ if (encoded_enc_key == NULL) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s", encoded_enc_key);
+ tor_free(encoded_enc_key);
+ }
+
+ /* Join them all in one blob of text. */
+ encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL);
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return encoded_ip;
+}
+
+/* Using a given decriptor object, build the secret input needed for the
+ * KDF and put it in the dst pointer which is an already allocated buffer
+ * of size dstlen. */
+static void
+build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen)
+{
+ size_t offset = 0;
+
+ tor_assert(desc);
+ tor_assert(dst);
+ tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen);
+
+ /* XXX use the destination length as the memcpy length */
+ /* Copy blinded public key. */
+ memcpy(dst, desc->plaintext_data.blinded_pubkey.pubkey,
+ sizeof(desc->plaintext_data.blinded_pubkey.pubkey));
+ offset += sizeof(desc->plaintext_data.blinded_pubkey.pubkey);
+ /* Copy subcredential. */
+ 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));
+ offset += sizeof(uint64_t);
+ tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset);
+}
+
+/* Do the KDF construction and put the resulting data in key_out which is of
+ * key_out_len length. It uses SHAKE-256 as specified in the spec. */
+static void
+build_kdf_key(const hs_descriptor_t *desc,
+ const uint8_t *salt, size_t salt_len,
+ uint8_t *key_out, size_t key_out_len)
+{
+ uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN];
+ crypto_xof_t *xof;
+
+ tor_assert(desc);
+ tor_assert(salt);
+ tor_assert(key_out);
+
+ /* Build the secret input for the KDF computation. */
+ build_secret_input(desc, secret_input, sizeof(secret_input));
+
+ xof = crypto_xof_new();
+ /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */
+ crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input));
+ crypto_xof_add_bytes(xof, salt, salt_len);
+ crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_hsdir_data,
+ strlen(str_enc_hsdir_data));
+ /* Eat from our KDF. */
+ crypto_xof_squeeze_bytes(xof, key_out, key_out_len);
+ crypto_xof_free(xof);
+ memwipe(secret_input, 0, sizeof(secret_input));
+}
+
+/* Using the given descriptor and salt, run it through our KDF function and
+ * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out.
+ * This function can't fail. */
+static void
+build_secret_key_iv_mac(const hs_descriptor_t *desc,
+ const uint8_t *salt, size_t salt_len,
+ uint8_t *key_out, size_t key_len,
+ uint8_t *iv_out, size_t iv_len,
+ uint8_t *mac_out, size_t mac_len)
+{
+ size_t offset = 0;
+ uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN];
+
+ tor_assert(desc);
+ tor_assert(salt);
+ tor_assert(key_out);
+ tor_assert(iv_out);
+ tor_assert(mac_out);
+
+ build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key));
+ /* Copy the bytes we need for both the secret key and IV. */
+ memcpy(key_out, kdf_key, key_len);
+ offset += key_len;
+ memcpy(iv_out, kdf_key + offset, iv_len);
+ offset += iv_len;
+ memcpy(mac_out, kdf_key + offset, mac_len);
+ /* Extra precaution to make sure we are not out of bound. */
+ tor_assert((offset + mac_len) == sizeof(kdf_key));
+ memwipe(kdf_key, 0, sizeof(kdf_key));
+}
+
+/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out.
+ * We use SHA3-256 for the MAC computation.
+ * This function can't fail. */
+static void
+build_mac(const uint8_t *mac_key, size_t mac_key_len,
+ const uint8_t *salt, size_t salt_len,
+ const uint8_t *encrypted, size_t encrypted_len,
+ uint8_t *mac_out, size_t mac_len)
+{
+ crypto_digest_t *digest;
+
+ const uint64_t mac_len_netorder = tor_htonll(mac_key_len);
+ const uint64_t salt_len_netorder = tor_htonll(salt_len);
+
+ tor_assert(mac_key);
+ tor_assert(salt);
+ tor_assert(encrypted);
+ tor_assert(mac_out);
+
+ digest = crypto_digest256_new(DIGEST_SHA3_256);
+ /* As specified in section 2.5 of proposal 224, first add the mac key
+ * then add the salt first and then the encrypted section. */
+
+ crypto_digest_add_bytes(digest, (const char *) &mac_len_netorder, 8);
+ crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len);
+ crypto_digest_add_bytes(digest, (const char *) &salt_len_netorder, 8);
+ crypto_digest_add_bytes(digest, (const char *) salt, salt_len);
+ crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len);
+ crypto_digest_get_digest(digest, (char *) mac_out, mac_len);
+ crypto_digest_free(digest);
+}
+
+/* Given a source length, return the new size including padding for the
+ * plaintext encryption. */
+static size_t
+compute_padded_plaintext_length(size_t plaintext_len)
+{
+ size_t plaintext_padded_len;
+
+ /* Make sure we won't overflow. */
+ tor_assert(plaintext_len <=
+ (SIZE_T_CEILING - HS_DESC_PLAINTEXT_PADDING_MULTIPLE));
+
+ /* Get the extra length we need to add. For example, if srclen is 234 bytes,
+ * this will expand to (2 * 128) == 256 thus an extra 22 bytes. */
+ plaintext_padded_len = CEIL_DIV(plaintext_len,
+ HS_DESC_PLAINTEXT_PADDING_MULTIPLE) *
+ HS_DESC_PLAINTEXT_PADDING_MULTIPLE;
+ /* Can never be extra careful. Make sure we are _really_ padded. */
+ tor_assert(!(plaintext_padded_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE));
+ return plaintext_padded_len;
+}
+
+/* Given a buffer, pad it up to the encrypted section padding requirement. Set
+ * the newly allocated string in padded_out and return the length of the
+ * padded buffer. */
+STATIC size_t
+build_plaintext_padding(const char *plaintext, size_t plaintext_len,
+ uint8_t **padded_out)
+{
+ size_t padded_len;
+ uint8_t *padded;
+
+ tor_assert(plaintext);
+ tor_assert(padded_out);
+
+ /* Allocate the final length including padding. */
+ padded_len = compute_padded_plaintext_length(plaintext_len);
+ tor_assert(padded_len >= plaintext_len);
+ padded = tor_malloc_zero(padded_len);
+
+ memcpy(padded, plaintext, plaintext_len);
+ *padded_out = padded;
+ return padded_len;
+}
+
+/* Using a key, IV and plaintext data of length plaintext_len, create the
+ * encrypted section by encrypting it and setting encrypted_out with the
+ * data. Return size of the encrypted data buffer. */
+static size_t
+build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext,
+ size_t plaintext_len, uint8_t **encrypted_out)
+{
+ size_t encrypted_len;
+ uint8_t *padded_plaintext, *encrypted;
+ crypto_cipher_t *cipher;
+
+ tor_assert(key);
+ tor_assert(iv);
+ tor_assert(plaintext);
+ tor_assert(encrypted_out);
+
+ /* This creates a cipher for AES. It can't fail. */
+ cipher = crypto_cipher_new_with_iv_and_bits(key, iv,
+ HS_DESC_ENCRYPTED_BIT_SIZE);
+ /* This can't fail. */
+ encrypted_len = build_plaintext_padding(plaintext, plaintext_len,
+ &padded_plaintext);
+ /* Extra precautions that we have a valie padding length. */
+ tor_assert(encrypted_len <= HS_DESC_PADDED_PLAINTEXT_MAX_LEN);
+ tor_assert(!(encrypted_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE));
+ /* We use a stream cipher so the encrypted length will be the same as the
+ * plaintext padded length. */
+ encrypted = tor_malloc_zero(encrypted_len);
+ /* This can't fail. */
+ crypto_cipher_encrypt(cipher, (char *) encrypted,
+ (const char *) padded_plaintext, encrypted_len);
+ *encrypted_out = encrypted;
+ /* Cleanup. */
+ crypto_cipher_free(cipher);
+ tor_free(padded_plaintext);
+ return encrypted_len;
+}
+
+/* Encrypt the given plaintext buffer and using the descriptor to get the
+ * keys. Set encrypted_out with the encrypted data and return the length of
+ * it. */
+static size_t
+encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext,
+ char **encrypted_out)
+{
+ char *final_blob;
+ size_t encrypted_len, final_blob_len, offset = 0;
+ uint8_t *encrypted;
+ uint8_t salt[HS_DESC_ENCRYPTED_SALT_LEN];
+ uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN];
+
+ tor_assert(desc);
+ tor_assert(plaintext);
+ tor_assert(encrypted_out);
+
+ /* Get our salt. The returned bytes are already hashed. */
+ crypto_strongest_rand(salt, sizeof(salt));
+
+ /* KDF construction resulting in a key from which the secret key, IV and MAC
+ * key are extracted which is what we need for the encryption. */
+ build_secret_key_iv_mac(desc, salt, sizeof(salt),
+ secret_key, sizeof(secret_key),
+ secret_iv, sizeof(secret_iv),
+ mac_key, sizeof(mac_key));
+
+ /* Build the encrypted part that is do the actual encryption. */
+ encrypted_len = build_encrypted(secret_key, secret_iv, plaintext,
+ strlen(plaintext), &encrypted);
+ memwipe(secret_key, 0, sizeof(secret_key));
+ memwipe(secret_iv, 0, sizeof(secret_iv));
+ /* This construction is specified in section 2.5 of proposal 224. */
+ final_blob_len = sizeof(salt) + encrypted_len + DIGEST256_LEN;
+ final_blob = tor_malloc_zero(final_blob_len);
+
+ /* Build the MAC. */
+ build_mac(mac_key, sizeof(mac_key), salt, sizeof(salt),
+ encrypted, encrypted_len, mac, sizeof(mac));
+ memwipe(mac_key, 0, sizeof(mac_key));
+
+ /* The salt is the first value. */
+ memcpy(final_blob, salt, sizeof(salt));
+ offset = sizeof(salt);
+ /* Second value is the encrypted data. */
+ memcpy(final_blob + offset, encrypted, encrypted_len);
+ offset += encrypted_len;
+ /* Third value is the MAC. */
+ memcpy(final_blob + offset, mac, sizeof(mac));
+ offset += sizeof(mac);
+ /* Cleanup the buffers. */
+ memwipe(salt, 0, sizeof(salt));
+ memwipe(encrypted, 0, encrypted_len);
+ tor_free(encrypted);
+ /* Extra precaution. */
+ tor_assert(offset == final_blob_len);
+
+ *encrypted_out = final_blob;
+ return final_blob_len;
+}
+
+/* Take care of encoding the encrypted data section and then encrypting it
+ * with the descriptor's key. A newly allocated NUL terminated string pointer
+ * containing the encrypted encoded blob is put in encrypted_blob_out. Return
+ * 0 on success else a negative value. */
+static int
+encode_encrypted_data(const hs_descriptor_t *desc,
+ char **encrypted_blob_out)
+{
+ int ret = -1;
+ char *encoded_str, *encrypted_blob;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(desc);
+ tor_assert(encrypted_blob_out);
+
+ /* Build the start of the section prior to the introduction points. */
+ {
+ if (!desc->encrypted_data.create2_ntor) {
+ log_err(LD_BUG, "HS desc doesn't have recognized handshake type.");
+ goto err;
+ }
+ smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats,
+ ONION_HANDSHAKE_TYPE_NTOR);
+
+ if (desc->encrypted_data.auth_types &&
+ smartlist_len(desc->encrypted_data.auth_types)) {
+ /* Put the authentication-required line. */
+ char *buf = smartlist_join_strings(desc->encrypted_data.auth_types, " ",
+ 0, NULL);
+ smartlist_add_asprintf(lines, "%s %s\n", str_auth_required, buf);
+ tor_free(buf);
+ }
+
+ if (desc->encrypted_data.single_onion_service) {
+ smartlist_add_asprintf(lines, "%s\n", str_single_onion);
+ }
+ }
+
+ /* Build the introduction point(s) section. */
+ SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points,
+ const hs_desc_intro_point_t *, ip) {
+ char *encoded_ip = encode_intro_point(&desc->plaintext_data.signing_pubkey,
+ ip);
+ if (encoded_ip == NULL) {
+ log_err(LD_BUG, "HS desc intro point is malformed.");
+ goto err;
+ }
+ smartlist_add(lines, encoded_ip);
+ } SMARTLIST_FOREACH_END(ip);
+
+ /* Build the entire encrypted data section into one encoded plaintext and
+ * then encrypt it. */
+ encoded_str = smartlist_join_strings(lines, "", 0, NULL);
+
+ /* Encrypt the section into an encrypted blob that we'll base64 encode
+ * before returning it. */
+ {
+ char *enc_b64;
+ ssize_t enc_b64_len, ret_len, enc_len;
+
+ enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob);
+ tor_free(encoded_str);
+ /* Get the encoded size plus a NUL terminating byte. */
+ enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1;
+ enc_b64 = tor_malloc_zero(enc_b64_len);
+ /* Base64 the encrypted blob before returning it. */
+ ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len,
+ BASE64_ENCODE_MULTILINE);
+ /* Return length doesn't count the NUL byte. */
+ tor_assert(ret_len == (enc_b64_len - 1));
+ tor_free(encrypted_blob);
+ *encrypted_blob_out = enc_b64;
+ }
+ /* Success! */
+ ret = 0;
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return ret;
+}
+
+/* Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the
+ * newly allocated string of the encoded descriptor. On error, -1 is returned
+ * and encoded_out is untouched. */
+static int
+desc_encode_v3(const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp, char **encoded_out)
+{
+ int ret = -1;
+ char *encoded_str = NULL;
+ size_t encoded_len;
+ smartlist_t *lines = smartlist_new();
+
+ tor_assert(desc);
+ tor_assert(signing_kp);
+ tor_assert(encoded_out);
+ tor_assert(desc->plaintext_data.version == 3);
+
+ /* Build the non-encrypted values. */
+ {
+ char *encoded_cert;
+ /* Encode certificate then create the first line of the descriptor. */
+ if (desc->plaintext_data.signing_key_cert->cert_type
+ != CERT_TYPE_SIGNING_HS_DESC) {
+ log_err(LD_BUG, "HS descriptor signing key has an unexpected cert type "
+ "(%d)", (int) desc->plaintext_data.signing_key_cert->cert_type);
+ goto err;
+ }
+ if (tor_cert_encode_ed22519(desc->plaintext_data.signing_key_cert,
+ &encoded_cert) < 0) {
+ /* The function will print error logs. */
+ goto err;
+ }
+ /* Create the hs descriptor line. */
+ smartlist_add_asprintf(lines, "%s %" PRIu32, str_hs_desc,
+ desc->plaintext_data.version);
+ /* Add the descriptor lifetime line (in minutes). */
+ smartlist_add_asprintf(lines, "%s %" PRIu32, str_lifetime,
+ desc->plaintext_data.lifetime_sec / 60);
+ /* Create the descriptor certificate line. */
+ smartlist_add_asprintf(lines, "%s\n%s", str_desc_cert, encoded_cert);
+ tor_free(encoded_cert);
+ /* Create the revision counter line. */
+ smartlist_add_asprintf(lines, "%s %" PRIu64, str_rev_counter,
+ desc->plaintext_data.revision_counter);
+ }
+
+ /* Build the superencrypted data section. */
+ {
+ char *enc_b64_blob=NULL;
+ if (encode_encrypted_data(desc, &enc_b64_blob) < 0) {
+ goto err;
+ }
+ smartlist_add_asprintf(lines,
+ "%s\n"
+ "-----BEGIN MESSAGE-----\n"
+ "%s"
+ "-----END MESSAGE-----",
+ str_superencrypted, enc_b64_blob);
+ tor_free(enc_b64_blob);
+ }
+
+ /* Join all lines in one string so we can generate a signature and append
+ * it to the descriptor. */
+ encoded_str = smartlist_join_strings(lines, "\n", 1, &encoded_len);
+
+ /* Sign all fields of the descriptor with our short term signing key. */
+ {
+ ed25519_signature_t sig;
+ char ed_sig_b64[ED25519_SIG_BASE64_LEN + 1];
+ if (ed25519_sign_prefixed(&sig,
+ (const uint8_t *) encoded_str, encoded_len,
+ str_desc_sig_prefix, signing_kp) < 0) {
+ log_warn(LD_BUG, "Can't sign encoded HS descriptor!");
+ tor_free(encoded_str);
+ goto err;
+ }
+ if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) {
+ log_warn(LD_BUG, "Can't base64 encode descriptor signature!");
+ tor_free(encoded_str);
+ goto err;
+ }
+ /* Create the signature line. */
+ smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64);
+ }
+ /* Free previous string that we used so compute the signature. */
+ tor_free(encoded_str);
+ encoded_str = smartlist_join_strings(lines, "\n", 1, NULL);
+ *encoded_out = encoded_str;
+
+ /* XXX: Trigger a control port event. */
+
+ /* Success! */
+ ret = 0;
+
+ err:
+ SMARTLIST_FOREACH(lines, char *, l, tor_free(l));
+ smartlist_free(lines);
+ return ret;
+}
+
+/* === DECODING === */
+
+/* Given an encoded string of the link specifiers, return a newly allocated
+ * list of decoded link specifiers. Return NULL on error. */
+STATIC smartlist_t *
+decode_link_specifiers(const char *encoded)
+{
+ int decoded_len;
+ size_t encoded_len, i;
+ uint8_t *decoded;
+ smartlist_t *results = NULL;
+ link_specifier_list_t *specs = NULL;
+
+ tor_assert(encoded);
+
+ encoded_len = strlen(encoded);
+ decoded = tor_malloc(encoded_len);
+ decoded_len = base64_decode((char *) decoded, encoded_len, encoded,
+ encoded_len);
+ if (decoded_len < 0) {
+ goto err;
+ }
+
+ if (link_specifier_list_parse(&specs, decoded,
+ (size_t) decoded_len) < decoded_len) {
+ goto err;
+ }
+ tor_assert(specs);
+ results = smartlist_new();
+
+ for (i = 0; i < link_specifier_list_getlen_spec(specs); i++) {
+ hs_desc_link_specifier_t *hs_spec;
+ link_specifier_t *ls = link_specifier_list_get_spec(specs, i);
+ tor_assert(ls);
+
+ hs_spec = tor_malloc_zero(sizeof(*hs_spec));
+ hs_spec->type = link_specifier_get_ls_type(ls);
+ switch (hs_spec->type) {
+ case LS_IPV4:
+ tor_addr_from_ipv4h(&hs_spec->u.ap.addr,
+ link_specifier_get_un_ipv4_addr(ls));
+ hs_spec->u.ap.port = link_specifier_get_un_ipv4_port(ls);
+ break;
+ case LS_IPV6:
+ tor_addr_from_ipv6_bytes(&hs_spec->u.ap.addr, (const char *)
+ link_specifier_getarray_un_ipv6_addr(ls));
+ hs_spec->u.ap.port = link_specifier_get_un_ipv6_port(ls);
+ break;
+ case LS_LEGACY_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_legacy_id(ls) ==
+ sizeof(hs_spec->u.legacy_id));
+ memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls),
+ sizeof(hs_spec->u.legacy_id));
+ break;
+ default:
+ goto err;
+ }
+
+ smartlist_add(results, hs_spec);
+ }
+
+ goto done;
+ err:
+ if (results) {
+ SMARTLIST_FOREACH(results, hs_desc_link_specifier_t *, s, tor_free(s));
+ smartlist_free(results);
+ results = NULL;
+ }
+ done:
+ link_specifier_list_free(specs);
+ tor_free(decoded);
+ return results;
+}
+
+/* Given a list of authentication types, decode it and put it in the encrypted
+ * data section. Return 1 if we at least know one of the type or 0 if we know
+ * none of them. */
+static int
+decode_auth_type(hs_desc_encrypted_data_t *desc, const char *list)
+{
+ int match = 0;
+
+ tor_assert(desc);
+ tor_assert(list);
+
+ desc->auth_types = smartlist_new();
+ smartlist_split_string(desc->auth_types, list, " ", 0, 0);
+
+ /* Validate the types that we at least know about one. */
+ SMARTLIST_FOREACH_BEGIN(desc->auth_types, const char *, auth) {
+ for (int idx = 0; auth_types[idx].identifier; idx++) {
+ if (!strncmp(auth, auth_types[idx].identifier,
+ strlen(auth_types[idx].identifier))) {
+ match = 1;
+ break;
+ }
+ }
+ } SMARTLIST_FOREACH_END(auth);
+
+ return match;
+}
+
+/* Parse a space-delimited list of integers representing CREATE2 formats into
+ * the bitfield in hs_desc_encrypted_data_t. Ignore unrecognized values. */
+static void
+decode_create2_list(hs_desc_encrypted_data_t *desc, const char *list)
+{
+ smartlist_t *tokens;
+
+ tor_assert(desc);
+ tor_assert(list);
+
+ tokens = smartlist_new();
+ smartlist_split_string(tokens, list, " ", 0, 0);
+
+ SMARTLIST_FOREACH_BEGIN(tokens, char *, s) {
+ int ok;
+ unsigned long type = tor_parse_ulong(s, 10, 1, UINT16_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Unparseable value %s in create2 list", escaped(s));
+ continue;
+ }
+ switch (type) {
+ case ONION_HANDSHAKE_TYPE_NTOR:
+ desc->create2_ntor = 1;
+ break;
+ default:
+ /* We deliberately ignore unsupported handshake types */
+ continue;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ SMARTLIST_FOREACH(tokens, char *, s, tor_free(s));
+ smartlist_free(tokens);
+}
+
+/* Given a certificate, validate the certificate for certain conditions which
+ * are if the given type matches the cert's one, if the signing key is
+ * included and if the that key was actually used to sign the certificate.
+ *
+ * Return 1 iff if all conditions pass or 0 if one of them fails. */
+STATIC int
+cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type)
+{
+ tor_assert(log_obj_type);
+
+ if (cert == NULL) {
+ log_warn(LD_REND, "Certificate for %s couldn't be parsed.", log_obj_type);
+ goto err;
+ }
+ if (cert->cert_type != type) {
+ log_warn(LD_REND, "Invalid cert type %02x for %s.", cert->cert_type,
+ log_obj_type);
+ goto err;
+ }
+ /* All certificate must have its signing key included. */
+ if (!cert->signing_key_included) {
+ log_warn(LD_REND, "Signing key is NOT included for %s.", log_obj_type);
+ goto err;
+ }
+ /* The following will not only check if the signature matches but also the
+ * expiration date and overall validity. */
+ if (tor_cert_checksig(cert, &cert->signing_key, time(NULL)) < 0) {
+ log_warn(LD_REND, "Invalid signature for %s.", log_obj_type);
+ goto err;
+ }
+
+ return 1;
+ err:
+ return 0;
+}
+
+/* Given some binary data, try to parse it to get a certificate object. If we
+ * have a valid cert, validate it using the given wanted type. On error, print
+ * a log using the err_msg has the certificate identifier adding semantic to
+ * the log and cert_out is set to NULL. On success, 0 is returned and cert_out
+ * points to a newly allocated certificate object. */
+static int
+cert_parse_and_validate(tor_cert_t **cert_out, const char *data,
+ size_t data_len, unsigned int cert_type_wanted,
+ const char *err_msg)
+{
+ tor_cert_t *cert;
+
+ tor_assert(cert_out);
+ tor_assert(data);
+ tor_assert(err_msg);
+
+ /* Parse certificate. */
+ cert = tor_cert_parse((const uint8_t *) data, data_len);
+ if (!cert) {
+ log_warn(LD_REND, "Certificate for %s couldn't be parsed.", err_msg);
+ goto err;
+ }
+
+ /* Validate certificate. */
+ if (!cert_is_valid(cert, cert_type_wanted, err_msg)) {
+ goto err;
+ }
+
+ *cert_out = cert;
+ return 0;
+
+ err:
+ tor_cert_free(cert);
+ *cert_out = NULL;
+ return -1;
+}
+
+/* Return true iff the given length of the encrypted data of a descriptor
+ * passes validation. */
+STATIC int
+encrypted_data_length_is_valid(size_t len)
+{
+ /* Make sure there is enough data for the salt and the mac. The equality is
+ * there to ensure that there is at least one byte of encrypted data. */
+ if (len <= HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN) {
+ log_warn(LD_REND, "Length of descriptor's encrypted data is too small. "
+ "Got %lu but minimum value is %d",
+ (unsigned long)len, HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN);
+ goto err;
+ }
+
+ return 1;
+ err:
+ return 0;
+}
+
+/* Decrypt the encrypted section of the descriptor using the given descriptor
+ * object desc. A newly allocated NUL terminated string is put in
+ * decrypted_out. Return the length of decrypted_out on success else 0 is
+ * returned and decrypted_out is set to NULL. */
+static size_t
+desc_decrypt_data_v3(const hs_descriptor_t *desc, char **decrypted_out)
+{
+ uint8_t *decrypted = NULL;
+ uint8_t secret_key[HS_DESC_ENCRYPTED_KEY_LEN], secret_iv[CIPHER_IV_LEN];
+ uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN];
+ const uint8_t *salt, *encrypted, *desc_mac;
+ size_t encrypted_len, result_len = 0;
+
+ tor_assert(decrypted_out);
+ tor_assert(desc);
+ tor_assert(desc->plaintext_data.encrypted_blob);
+
+ /* Construction is as follow: SALT | ENCRYPTED_DATA | MAC */
+ if (!encrypted_data_length_is_valid(
+ desc->plaintext_data.encrypted_blob_size)) {
+ goto err;
+ }
+
+ /* Start of the blob thus the salt. */
+ salt = desc->plaintext_data.encrypted_blob;
+ /* Next is the encrypted data. */
+ encrypted = desc->plaintext_data.encrypted_blob +
+ HS_DESC_ENCRYPTED_SALT_LEN;
+ encrypted_len = desc->plaintext_data.encrypted_blob_size -
+ (HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN);
+
+ /* At the very end is the MAC. Make sure it's of the right size. */
+ {
+ desc_mac = encrypted + encrypted_len;
+ size_t desc_mac_size = desc->plaintext_data.encrypted_blob_size -
+ (desc_mac - desc->plaintext_data.encrypted_blob);
+ if (desc_mac_size != DIGEST256_LEN) {
+ log_warn(LD_REND, "Service descriptor MAC length of encrypted data "
+ "is invalid (%lu, expected %u)",
+ (unsigned long) desc_mac_size, DIGEST256_LEN);
+ goto err;
+ }
+ }
+
+ /* KDF construction resulting in a key from which the secret key, IV and MAC
+ * key are extracted which is what we need for the decryption. */
+ build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ secret_key, sizeof(secret_key),
+ secret_iv, sizeof(secret_iv),
+ mac_key, sizeof(mac_key));
+
+ /* Build MAC. */
+ build_mac(mac_key, sizeof(mac_key), salt, HS_DESC_ENCRYPTED_SALT_LEN,
+ encrypted, encrypted_len, our_mac, sizeof(our_mac));
+ memwipe(mac_key, 0, sizeof(mac_key));
+ /* Verify MAC; MAC is H(mac_key || salt || encrypted)
+ *
+ * This is a critical check that is making sure the computed MAC matches the
+ * one in the descriptor. */
+ if (!tor_memeq(our_mac, desc_mac, sizeof(our_mac))) {
+ log_warn(LD_REND, "Encrypted service descriptor MAC check failed");
+ goto err;
+ }
+
+ {
+ /* Decrypt. Here we are assured that the encrypted length is valid for
+ * decryption. */
+ crypto_cipher_t *cipher;
+
+ cipher = crypto_cipher_new_with_iv_and_bits(secret_key, secret_iv,
+ HS_DESC_ENCRYPTED_BIT_SIZE);
+ /* Extra byte for the NUL terminated byte. */
+ decrypted = tor_malloc_zero(encrypted_len + 1);
+ crypto_cipher_decrypt(cipher, (char *) decrypted,
+ (const char *) encrypted, encrypted_len);
+ crypto_cipher_free(cipher);
+ }
+
+ {
+ /* Adjust length to remove NULL padding bytes */
+ uint8_t *end = memchr(decrypted, 0, encrypted_len);
+ result_len = encrypted_len;
+ if (end) {
+ result_len = end - decrypted;
+ }
+ }
+
+ /* Make sure to NUL terminate the string. */
+ decrypted[encrypted_len] = '\0';
+ *decrypted_out = (char *) decrypted;
+ goto done;
+
+ err:
+ if (decrypted) {
+ tor_free(decrypted);
+ }
+ *decrypted_out = NULL;
+ result_len = 0;
+
+ done:
+ memwipe(secret_key, 0, sizeof(secret_key));
+ memwipe(secret_iv, 0, sizeof(secret_iv));
+ return result_len;
+}
+
+/* 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
+ * be decoded. */
+STATIC hs_desc_intro_point_t *
+decode_introduction_point(const hs_descriptor_t *desc, const char *start)
+{
+ hs_desc_intro_point_t *ip = NULL;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+ tor_cert_t *cross_cert = NULL;
+ const directory_token_t *tok;
+
+ tor_assert(desc);
+ tor_assert(start);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, start, start + strlen(start),
+ tokens, hs_desc_intro_point_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Introduction point is not parseable");
+ goto err;
+ }
+
+ /* 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));
+
+ /* "introduction-point" SP link-specifiers NL */
+ tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT);
+ tor_assert(tok->n_args == 1);
+ 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;
+ }
+
+ /* "auth-key" NL certificate NL */
+ tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "ED25519 CERT")) {
+ log_warn(LD_REND, "Unexpected object type for introduction auth key");
+ goto err;
+ }
+
+ /* Parse cert and do some validation. */
+ if (cert_parse_and_validate(&ip->auth_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_AUTH_HS_IP_KEY,
+ "introduction point auth-key") < 0) {
+ goto err;
+ }
+
+ /* Exactly one "enc-key" ... */
+ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY);
+ if (!strcmp(tok->args[0], "ntor")) {
+ /* "enc-key" SP "ntor" SP key NL */
+ if (tok->n_args != 2 || tok->object_body) {
+ log_warn(LD_REND, "Introduction point ntor encryption key is invalid");
+ goto err;
+ }
+
+ if (curve25519_public_from_base64(&ip->enc_key.curve25519.pubkey,
+ tok->args[1]) < 0) {
+ log_warn(LD_REND, "Introduction point ntor encryption key is invalid");
+ goto err;
+ }
+ ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519;
+ } else if (!strcmp(tok->args[0], "legacy")) {
+ /* "enc-key" SP "legacy" NL key NL */
+ if (!tok->key) {
+ log_warn(LD_REND, "Introduction point legacy encryption key is "
+ "invalid");
+ goto err;
+ }
+ ip->enc_key.legacy = crypto_pk_dup_key(tok->key);
+ ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY;
+ } else {
+ /* Unknown key type so we can't use that introduction point. */
+ log_warn(LD_REND, "Introduction point encryption key is unrecognized.");
+ goto err;
+ }
+
+ /* "enc-key-certification" NL certificate NL */
+ tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERTIFICATION);
+ tor_assert(tok->object_body);
+ /* Do the cross certification. */
+ switch (ip->enc_key_type) {
+ case HS_DESC_KEY_TYPE_CURVE25519:
+ {
+ if (strcmp(tok->object_type, "ED25519 CERT")) {
+ log_warn(LD_REND, "Introduction point ntor encryption key "
+ "cross-certification has an unknown format.");
+ goto err;
+ }
+ if (cert_parse_and_validate(&cross_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS,
+ "introduction point enc-key-certification") < 0) {
+ goto err;
+ }
+ break;
+ }
+ case HS_DESC_KEY_TYPE_LEGACY:
+ if (strcmp(tok->object_type, "CROSSCERT")) {
+ log_warn(LD_REND, "Introduction point legacy encryption key "
+ "cross-certification has an unknown format.");
+ goto err;
+ }
+ if (rsa_ed25519_crosscert_check((const uint8_t *) tok->object_body,
+ tok->object_size, ip->enc_key.legacy,
+ &desc->plaintext_data.signing_key_cert->signed_key,
+ approx_time()-86400)) {
+ log_warn(LD_REND, "Unable to check cross-certification on the "
+ "introduction point legacy encryption key.");
+ goto err;
+ }
+ break;
+ default:
+ tor_assert(0);
+ break;
+ }
+ /* It is successfully cross certified. Flag the object. */
+ ip->cross_certified = 1;
+ goto done;
+
+ err:
+ desc_intro_point_free(ip);
+ ip = NULL;
+
+ done:
+ tor_cert_free(cross_cert);
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ memarea_drop_all(area);
+
+ return ip;
+}
+
+/* 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
+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();
+
+ tor_assert(desc);
+ tor_assert(desc_enc);
+ tor_assert(data);
+ tor_assert(desc_enc->intro_points);
+
+ /* Take the desc string, and extract the intro point substrings out of it */
+ {
+ /* Split the descriptor string using the intro point header as delimiter */
+ smartlist_split_string(chunked_desc, data, str_intro_point_start, 0, 0);
+
+ /* Check if there are actually any intro points included. The first chunk
+ * should be other descriptor fields (e.g. create2-formats), so it's not an
+ * intro point. */
+ if (smartlist_len(chunked_desc) < 2) {
+ goto done;
+ }
+ }
+
+ /* Take the intro point substrings, and prepare them for parsing */
+ {
+ int i = 0;
+ /* Prepend the introduction-point header to all the chunks, since
+ smartlist_split_string() devoured it. */
+ SMARTLIST_FOREACH_BEGIN(chunked_desc, char *, chunk) {
+ /* Ignore first chunk. It's other descriptor fields. */
+ if (i++ == 0) {
+ continue;
+ }
+
+ smartlist_add_asprintf(intro_points, "%s %s", str_intro_point, chunk);
+ } SMARTLIST_FOREACH_END(chunk);
+ }
+
+ /* Parse the intro points! */
+ 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;
+ }
+ 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. */
+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)
+{
+ int ret = 0;
+ ed25519_signature_t sig;
+ const char *sig_start;
+
+ tor_assert(b64_sig);
+ tor_assert(signing_pubkey);
+ tor_assert(encoded_desc);
+ /* Verifying nothing won't end well :). */
+ tor_assert(encoded_len > 0);
+
+ /* Signature length check. */
+ if (strlen(b64_sig) != ED25519_SIG_BASE64_LEN) {
+ log_warn(LD_REND, "Service descriptor has an invalid signature length."
+ "Exptected %d but got %lu",
+ ED25519_SIG_BASE64_LEN, (unsigned long) strlen(b64_sig));
+ goto err;
+ }
+
+ /* First, convert base64 blob to an ed25519 signature. */
+ if (ed25519_signature_from_base64(&sig, b64_sig) != 0) {
+ log_warn(LD_REND, "Service descriptor does not contain a valid "
+ "signature");
+ goto err;
+ }
+
+ /* Find the start of signature. */
+ sig_start = tor_memstr(encoded_desc, encoded_len, "\n" str_signature);
+ /* Getting here means the token parsing worked for the signature so if we
+ * can't find the start of the signature, we have a code flow issue. */
+ if (BUG(!sig_start)) {
+ goto err;
+ }
+ /* Skip newline, it has to go in the signature check. */
+ sig_start++;
+
+ /* Validate signature with the full body of the descriptor. */
+ if (ed25519_checksig_prefixed(&sig,
+ (const uint8_t *) encoded_desc,
+ sig_start - encoded_desc,
+ str_desc_sig_prefix,
+ signing_pubkey) != 0) {
+ log_warn(LD_REND, "Invalid signature on service descriptor");
+ goto err;
+ }
+ /* Valid signature! All is good. */
+ ret = 1;
+
+ err:
+ return ret;
+}
+
+/* Decode descriptor plaintext data for version 3. Given a list of tokens, an
+ * allocated plaintext object that will be populated and the encoded
+ * descriptor with its length. The last one is needed for signature
+ * verification. Unknown tokens are simply ignored so this won't error on
+ * unknowns but requires that all v3 token be present and valid.
+ *
+ * Return 0 on success else a negative value. */
+static int
+desc_decode_plaintext_v3(smartlist_t *tokens,
+ hs_desc_plaintext_data_t *desc,
+ const char *encoded_desc, size_t encoded_len)
+{
+ int ok;
+ directory_token_t *tok;
+
+ tor_assert(tokens);
+ tor_assert(desc);
+ /* Version higher could still use this function to decode most of the
+ * descriptor and then they decode the extra part. */
+ tor_assert(desc->version >= 3);
+
+ /* Descriptor lifetime parsing. */
+ tok = find_by_keyword(tokens, R3_DESC_LIFETIME);
+ tor_assert(tok->n_args == 1);
+ desc->lifetime_sec = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0,
+ UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor lifetime value is invalid");
+ goto err;
+ }
+ /* Put it from minute to second. */
+ desc->lifetime_sec *= 60;
+ if (desc->lifetime_sec > HS_DESC_MAX_LIFETIME) {
+ log_warn(LD_REND, "Service descriptor lifetime is too big. "
+ "Got %" PRIu32 " but max is %d",
+ desc->lifetime_sec, HS_DESC_MAX_LIFETIME);
+ goto err;
+ }
+
+ /* Descriptor signing certificate. */
+ tok = find_by_keyword(tokens, R3_DESC_SIGNING_CERT);
+ tor_assert(tok->object_body);
+ /* Expecting a prop220 cert with the signing key extension, which contains
+ * the blinded public key. */
+ if (strcmp(tok->object_type, "ED25519 CERT") != 0) {
+ log_warn(LD_REND, "Service descriptor signing cert wrong type (%s)",
+ escaped(tok->object_type));
+ goto err;
+ }
+ if (cert_parse_and_validate(&desc->signing_key_cert, tok->object_body,
+ tok->object_size, CERT_TYPE_SIGNING_HS_DESC,
+ "service descriptor signing key") < 0) {
+ goto err;
+ }
+
+ /* Copy the public keys into signing_pubkey and blinded_pubkey */
+ memcpy(&desc->signing_pubkey, &desc->signing_key_cert->signed_key,
+ sizeof(ed25519_public_key_t));
+ memcpy(&desc->blinded_pubkey, &desc->signing_key_cert->signing_key,
+ sizeof(ed25519_public_key_t));
+
+ /* Extract revision counter value. */
+ tok = find_by_keyword(tokens, R3_REVISION_COUNTER);
+ tor_assert(tok->n_args == 1);
+ desc->revision_counter = tor_parse_uint64(tok->args[0], 10, 0,
+ UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor revision-counter is invalid");
+ goto err;
+ }
+
+ /* Extract the encrypted data section. */
+ tok = find_by_keyword(tokens, R3_SUPERENCRYPTED);
+ tor_assert(tok->object_body);
+ if (strcmp(tok->object_type, "MESSAGE") != 0) {
+ log_warn(LD_REND, "Service descriptor encrypted data section is invalid");
+ goto err;
+ }
+ /* Make sure the length of the encrypted blob is valid. */
+ if (!encrypted_data_length_is_valid(tok->object_size)) {
+ goto err;
+ }
+
+ /* Copy the encrypted blob to the descriptor object so we can handle it
+ * latter if needed. */
+ desc->encrypted_blob = tor_memdup(tok->object_body, tok->object_size);
+ desc->encrypted_blob_size = tok->object_size;
+
+ /* Extract signature and verify it. */
+ tok = find_by_keyword(tokens, R3_SIGNATURE);
+ tor_assert(tok->n_args == 1);
+ /* First arg here is the actual encoded signature. */
+ if (!desc_sig_is_valid(tok->args[0], &desc->signing_pubkey,
+ encoded_desc, encoded_len)) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ return -1;
+}
+
+/* Decode the version 3 encrypted section of the given descriptor desc. The
+ * desc_encrypted_out will be populated with the decoded data. Return 0 on
+ * success else -1. */
+static int
+desc_decode_encrypted_v3(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted_out)
+{
+ int result = -1;
+ char *message = NULL;
+ size_t message_len;
+ memarea_t *area = NULL;
+ directory_token_t *tok;
+ smartlist_t *tokens = NULL;
+
+ tor_assert(desc);
+ tor_assert(desc_encrypted_out);
+
+ /* Decrypt the encrypted data that is located in the plaintext section in
+ * the descriptor as a blob of bytes. The following functions will use the
+ * keys found in the same section. */
+ message_len = desc_decrypt_data_v3(desc, &message);
+ if (!message_len) {
+ log_warn(LD_REND, "Service descriptor decryption failed.");
+ goto err;
+ }
+ tor_assert(message);
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ if (tokenize_string(area, message, message + message_len,
+ tokens, hs_desc_encrypted_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Encrypted service descriptor is not parseable.");
+ goto err;
+ }
+
+ /* CREATE2 supported cell format. It's mandatory. */
+ tok = find_by_keyword(tokens, R3_CREATE2_FORMATS);
+ tor_assert(tok);
+ decode_create2_list(desc_encrypted_out, tok->args[0]);
+ /* Must support ntor according to the specification */
+ if (!desc_encrypted_out->create2_ntor) {
+ log_warn(LD_REND, "Service create2-formats does not include ntor.");
+ goto err;
+ }
+
+ /* Authentication type. It's optional but only once. */
+ tok = find_opt_by_keyword(tokens, R3_AUTHENTICATION_REQUIRED);
+ if (tok) {
+ if (!decode_auth_type(desc_encrypted_out, tok->args[0])) {
+ log_warn(LD_REND, "Service descriptor authentication type has "
+ "invalid entry(ies).");
+ goto err;
+ }
+ }
+
+ /* Is this service a single onion service? */
+ tok = find_opt_by_keyword(tokens, R3_SINGLE_ONION_SERVICE);
+ if (tok) {
+ desc_encrypted_out->single_onion_service = 1;
+ }
+
+ /* 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;
+ }
+ /* Validation of maximum introduction points allowed. */
+ if (smartlist_len(desc_encrypted_out->intro_points) > 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,
+ smartlist_len(desc_encrypted_out->intro_points));
+ goto err;
+ }
+
+ /* NOTE: Unknown fields are allowed because this function could be used to
+ * decode other descriptor version. */
+
+ result = 0;
+ goto done;
+
+ err:
+ tor_assert(result < 0);
+ desc_encrypted_data_free_contents(desc_encrypted_out);
+
+ done:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ if (message) {
+ tor_free(message);
+ }
+ return result;
+}
+
+/* Table of encrypted decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_encrypted_handlers[])(
+ const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_encrypted_v3,
+};
+
+/* Decode the encrypted data section of the given descriptor and store the
+ * data in the given encrypted data object. Return 0 on success else a
+ * negative value on error. */
+int
+hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_encrypted)
+{
+ int ret;
+ uint32_t version;
+
+ tor_assert(desc);
+ /* Ease our life a bit. */
+ version = desc->plaintext_data.version;
+ tor_assert(desc_encrypted);
+ /* Calling this function without an encrypted blob to parse is a code flow
+ * error. The plaintext parsing should never succeed in the first place
+ * without an encrypted section. */
+ tor_assert(desc->plaintext_data.encrypted_blob);
+ /* Let's make sure we have a supported version as well. By correctly parsing
+ * the plaintext, this should not fail. */
+ if (BUG(!hs_desc_is_supported_version(version))) {
+ ret = -1;
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_encrypted_handlers) >= version);
+ tor_assert(decode_encrypted_handlers[version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_encrypted_handlers[version](desc, desc_encrypted);
+ if (ret < 0) {
+ goto err;
+ }
+
+ err:
+ return ret;
+}
+
+/* Table of plaintext decode function version specific. The function are
+ * indexed by the version number so v3 callback is at index 3 in the array. */
+static int
+ (*decode_plaintext_handlers[])(
+ smartlist_t *tokens,
+ hs_desc_plaintext_data_t *desc,
+ const char *encoded_desc,
+ size_t encoded_len) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_decode_plaintext_v3,
+};
+
+/* Fully decode the given descriptor plaintext and store the data in the
+ * plaintext data object. Returns 0 on success else a negative value. */
+int
+hs_desc_decode_plaintext(const char *encoded,
+ hs_desc_plaintext_data_t *plaintext)
+{
+ int ok = 0, ret = -1;
+ memarea_t *area = NULL;
+ smartlist_t *tokens = NULL;
+ size_t encoded_len;
+ directory_token_t *tok;
+
+ tor_assert(encoded);
+ tor_assert(plaintext);
+
+ /* Check that descriptor is within size limits. */
+ encoded_len = strlen(encoded);
+ if (encoded_len >= hs_cache_get_max_descriptor_size()) {
+ log_warn(LD_REND, "Service descriptor is too big (%lu bytes)",
+ (unsigned long) encoded_len);
+ goto err;
+ }
+
+ area = memarea_new();
+ tokens = smartlist_new();
+ /* Tokenize the descriptor so we can start to parse it. */
+ if (tokenize_string(area, encoded, encoded + encoded_len, tokens,
+ hs_desc_v3_token_table, 0) < 0) {
+ log_warn(LD_REND, "Service descriptor is not parseable");
+ goto err;
+ }
+
+ /* Get the version of the descriptor which is the first mandatory field of
+ * the descriptor. From there, we'll decode the right descriptor version. */
+ tok = find_by_keyword(tokens, R_HS_DESCRIPTOR);
+ tor_assert(tok->n_args == 1);
+ plaintext->version = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0,
+ UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_REND, "Service descriptor has unparseable version %s",
+ escaped(tok->args[0]));
+ goto err;
+ }
+ if (!hs_desc_is_supported_version(plaintext->version)) {
+ log_warn(LD_REND, "Service descriptor has unsupported version %" PRIu32,
+ plaintext->version);
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(decode_plaintext_handlers) >= plaintext->version);
+ tor_assert(decode_plaintext_handlers[plaintext->version]);
+
+ /* Run the version specific plaintext decoder. */
+ ret = decode_plaintext_handlers[plaintext->version](tokens, plaintext,
+ encoded, encoded_len);
+ if (ret < 0) {
+ goto err;
+ }
+ /* Success. Descriptor has been populated with the data. */
+ ret = 0;
+
+ err:
+ if (tokens) {
+ SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t));
+ smartlist_free(tokens);
+ }
+ if (area) {
+ memarea_drop_all(area);
+ }
+ return ret;
+}
+
+/* Fully decode an encoded descriptor and set a newly allocated descriptor
+ * object in desc_out. Subcredentials are used if not NULL else it's ignored.
+ *
+ * Return 0 on success. A negative value is returned on error and desc_out is
+ * set to NULL. */
+int
+hs_desc_decode_descriptor(const char *encoded,
+ const uint8_t *subcredential,
+ hs_descriptor_t **desc_out)
+{
+ int ret;
+ hs_descriptor_t *desc;
+
+ tor_assert(encoded);
+
+ desc = tor_malloc_zero(sizeof(hs_descriptor_t));
+
+ /* Subcredentials are optional. */
+ if (subcredential) {
+ memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential));
+ }
+
+ ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ ret = hs_desc_decode_encrypted(desc, &desc->encrypted_data);
+ if (ret < 0) {
+ goto err;
+ }
+
+ if (desc_out) {
+ *desc_out = desc;
+ } else {
+ hs_descriptor_free(desc);
+ }
+ return ret;
+
+ err:
+ hs_descriptor_free(desc);
+ if (desc_out) {
+ *desc_out = NULL;
+ }
+
+ tor_assert(ret < 0);
+ return ret;
+}
+
+/* Table of encode function version specific. The functions are indexed by the
+ * version number so v3 callback is at index 3 in the array. */
+static int
+ (*encode_handlers[])(
+ const hs_descriptor_t *desc,
+ const ed25519_keypair_t *signing_kp,
+ char **encoded_out) =
+{
+ /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL,
+ desc_encode_v3,
+};
+
+/* Encode the given descriptor desc including signing with the given key pair
+ * signing_kp. On success, encoded_out points to a newly allocated NUL
+ * terminated string that contains the encoded descriptor as a string.
+ *
+ * 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)
+{
+ int ret = -1;
+ uint32_t version;
+
+ tor_assert(desc);
+ tor_assert(encoded_out);
+
+ /* Make sure we support the version of the descriptor format. */
+ version = desc->plaintext_data.version;
+ if (!hs_desc_is_supported_version(version)) {
+ goto err;
+ }
+ /* Extra precaution. Having no handler for the supported version should
+ * never happened else we forgot to add it but we bumped the version. */
+ tor_assert(ARRAY_LENGTH(encode_handlers) >= version);
+ tor_assert(encode_handlers[version]);
+
+ ret = encode_handlers[version](desc, signing_kp, encoded_out);
+ if (ret < 0) {
+ goto err;
+ }
+
+ /* Try to decode what we just encoded. Symmetry is nice! */
+ ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL);
+ if (BUG(ret < 0)) {
+ goto err;
+ }
+
+ return 0;
+
+ err:
+ *encoded_out = NULL;
+ return ret;
+}
+
+/* Free the descriptor plaintext data object. */
+void
+hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc)
+{
+ desc_plaintext_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the descriptor encrypted data object. */
+void
+hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc)
+{
+ desc_encrypted_data_free_contents(desc);
+ tor_free(desc);
+}
+
+/* Free the given descriptor object. */
+void
+hs_descriptor_free(hs_descriptor_t *desc)
+{
+ if (!desc) {
+ return;
+ }
+
+ desc_plaintext_data_free_contents(&desc->plaintext_data);
+ desc_encrypted_data_free_contents(&desc->encrypted_data);
+ tor_free(desc);
+}
+
+/* Return the size in bytes of the given plaintext data object. A sizeof() is
+ * not enough because the object contains pointers and the encrypted blob.
+ * This is particularly useful for our OOM subsystem that tracks the HSDir
+ * cache size for instance. */
+size_t
+hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data)
+{
+ tor_assert(data);
+ return (sizeof(*data) + sizeof(*data->signing_key_cert) +
+ data->encrypted_blob_size);
+}
+
diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h
new file mode 100644
index 0000000000..b520d24471
--- /dev/null
+++ b/src/or/hs_descriptor.h
@@ -0,0 +1,248 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_descriptor.h
+ * \brief Header file for hs_descriptor.c
+ **/
+
+#ifndef TOR_HS_DESCRIPTOR_H
+#define TOR_HS_DESCRIPTOR_H
+
+#include <stdint.h>
+
+#include "or.h"
+#include "address.h"
+#include "container.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+#include "torcert.h"
+
+/* 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
+
+/* 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)
+/* 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
+ * the encryption key for the encrypted data section of the descriptor. This
+ * adds up to 68 bytes being the blinded key, hashed subcredential and
+ * revision counter. */
+#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \
+ ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t)
+/* Length of the KDF output value which is the length of the secret key,
+ * the secret IV and MAC key length which is the length of H() output. */
+#define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \
+ CIPHER256_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN
+/* We need to pad the plaintext version of the encrypted data section before
+ * encryption and it has to be a multiple of this value. */
+#define HS_DESC_PLAINTEXT_PADDING_MULTIPLE 128
+/* XXX: Let's make sure this makes sense as an upper limit for the padded
+ * plaintext section. Then we should enforce it as now only an assert will be
+ * triggered if we are above it. */
+/* Once padded, this is the maximum length in bytes for the plaintext. */
+#define HS_DESC_PADDED_PLAINTEXT_MAX_LEN 8192
+/* Minimum length in bytes of the encrypted portion of the descriptor. */
+#define HS_DESC_ENCRYPTED_MIN_LEN \
+ HS_DESC_ENCRYPTED_SALT_LEN + \
+ HS_DESC_PLAINTEXT_PADDING_MULTIPLE + DIGEST256_LEN
+/* Maximum length in bytes of a full hidden service descriptor. */
+#define HS_DESC_MAX_LEN 50000 /* 50kb max size */
+/* The minimum amount of fields a descriptor should contain. The parsing of
+ * the fields are version specific so the only required field, as a generic
+ * view of a descriptor, is 1 that is the version field. */
+#define HS_DESC_PLAINTEXT_MIN_FIELDS 1
+
+/* Key length for the descriptor symmetric encryption. As specified in the
+ * protocol, we use AES-256 for the encrypted section of the descriptor. The
+ * following is the length in bytes and the bit size. */
+#define HS_DESC_ENCRYPTED_KEY_LEN CIPHER256_KEY_LEN
+#define HS_DESC_ENCRYPTED_BIT_SIZE (HS_DESC_ENCRYPTED_KEY_LEN * 8)
+
+/* Type of authentication in the descriptor. */
+typedef enum {
+ HS_DESC_AUTH_PASSWORD = 1,
+ HS_DESC_AUTH_ED25519 = 2,
+} hs_desc_auth_type_t;
+
+/* Type of encryption key in the descriptor. */
+typedef enum {
+ HS_DESC_KEY_TYPE_LEGACY = 1,
+ HS_DESC_KEY_TYPE_CURVE25519 = 2,
+} hs_desc_key_type_t;
+
+/* Link specifier object that contains information on how to extend to the
+ * relay that is the address, port and handshake type. */
+typedef struct hs_desc_link_specifier_t {
+ /* Indicate the type of link specifier. See trunnel ed25519_cert
+ * specification. */
+ uint8_t type;
+
+ /* It's either an address/port or a legacy identity fingerprint. */
+ 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];
+ } u;
+} hs_desc_link_specifier_t;
+
+/* Introduction point information located in a descriptor. */
+typedef struct hs_desc_intro_point_t {
+ /* Link specifier(s) which details how to extend to the relay. This list
+ * contains hs_desc_link_specifier_t object. It MUST have at least one. */
+ smartlist_t *link_specifiers;
+
+ /* 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. */
+ tor_cert_t *auth_key_cert;
+
+ /* Encryption key type so we know which one to use in the union below. */
+ hs_desc_key_type_t enc_key_type;
+
+ /* Keys are mutually exclusive thus the union. */
+ union {
+ /* Encryption key used to encrypt request to hidden service. */
+ curve25519_keypair_t curve25519;
+
+ /* Backward compat: RSA 1024 encryption key for legacy purposes.
+ * Mutually exclusive with enc_key. */
+ crypto_pk_t *legacy;
+ } enc_key;
+
+ /* True iff the introduction point has passed the cross certification. Upon
+ * decoding an intro point, this must be true. */
+ unsigned int cross_certified : 1;
+} hs_desc_intro_point_t;
+
+/* The encrypted data section of a descriptor. Obviously the data in this is
+ * in plaintext but encrypted once encoded. */
+typedef struct hs_desc_encrypted_data_t {
+ /* Bitfield of CREATE2 cell supported formats. The only currently supported
+ * format is ntor. */
+ unsigned int create2_ntor : 1;
+
+ /* A list of authentication types that a client must at least support one
+ * in order to contact the service. Contains NULL terminated strings. */
+ smartlist_t *auth_types;
+
+ /* Is this descriptor a single onion service? */
+ unsigned int single_onion_service : 1;
+
+ /* A list of intro points. Contains hs_desc_intro_point_t objects. */
+ smartlist_t *intro_points;
+} hs_desc_encrypted_data_t;
+
+/* Plaintext data that is unencrypted information of the descriptor. */
+typedef struct hs_desc_plaintext_data_t {
+ /* Version of the descriptor format. Spec specifies this field as a
+ * positive integer. */
+ uint32_t version;
+
+ /* The lifetime of the descriptor in seconds. */
+ uint32_t lifetime_sec;
+
+ /* Certificate with the short-term ed22519 descriptor signing key for the
+ * replica which is signed by the blinded public key for that replica. */
+ tor_cert_t *signing_key_cert;
+
+ /* Signing public key which is used to sign the descriptor. Same public key
+ * as in the signing key certificate. */
+ ed25519_public_key_t signing_pubkey;
+
+ /* Blinded public key used for this descriptor derived from the master
+ * identity key and generated for a specific replica number. */
+ ed25519_public_key_t blinded_pubkey;
+
+ /* Revision counter is incremented at each upload, regardless of whether
+ * the descriptor has changed. This avoids leaking whether the descriptor
+ * has changed. Spec specifies this as a 8 bytes positive integer. */
+ uint64_t revision_counter;
+
+ /* Decoding only: The base64-decoded encrypted blob from the descriptor */
+ uint8_t *encrypted_blob;
+
+ /* Decoding only: Size of the encrypted_blob */
+ size_t encrypted_blob_size;
+} hs_desc_plaintext_data_t;
+
+/* Service descriptor in its decoded form. */
+typedef struct hs_descriptor_t {
+ /* Contains the plaintext part of the descriptor. */
+ hs_desc_plaintext_data_t plaintext_data;
+
+ /* The following contains what's in the encrypted part of the descriptor.
+ * It's only encrypted in the encoded version of the descriptor thus the
+ * data contained in that object is in plaintext. */
+ hs_desc_encrypted_data_t encrypted_data;
+
+ /* Subcredentials of a service, used by the client and service to decrypt
+ * the encrypted data. */
+ uint8_t subcredential[DIGEST256_LEN];
+} hs_descriptor_t;
+
+/* Return true iff the given descriptor format version is supported. */
+static inline int
+hs_desc_is_supported_version(uint32_t version)
+{
+ if (version < HS_DESC_SUPPORTED_FORMAT_VERSION_MIN ||
+ version > HS_DESC_SUPPORTED_FORMAT_VERSION_MAX) {
+ return 0;
+ }
+ return 1;
+}
+
+/* Public API. */
+
+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);
+
+int hs_desc_decode_descriptor(const char *encoded,
+ const uint8_t *subcredential,
+ hs_descriptor_t **desc_out);
+int hs_desc_decode_plaintext(const char *encoded,
+ hs_desc_plaintext_data_t *plaintext);
+int hs_desc_decode_encrypted(const hs_descriptor_t *desc,
+ hs_desc_encrypted_data_t *desc_out);
+
+size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data);
+
+#ifdef HS_DESCRIPTOR_PRIVATE
+
+/* Encoding. */
+STATIC char *encode_link_specifiers(const smartlist_t *specs);
+STATIC size_t build_plaintext_padding(const char *plaintext,
+ size_t plaintext_len,
+ uint8_t **padded_out);
+/* Decoding. */
+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);
+#endif /* HS_DESCRIPTOR_PRIVATE */
+
+#endif /* TOR_HS_DESCRIPTOR_H */
+
diff --git a/src/or/hs_intropoint.c b/src/or/hs_intropoint.c
new file mode 100644
index 0000000000..bc493e297e
--- /dev/null
+++ b/src/or/hs_intropoint.c
@@ -0,0 +1,596 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_intropoint.c
+ * \brief Implement next generation introductions point functionality
+ **/
+
+#define HS_INTROPOINT_PRIVATE
+
+#include "or.h"
+#include "config.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "config.h"
+#include "relay.h"
+#include "rendmid.h"
+#include "rephist.h"
+
+/* Trunnel */
+#include "ed25519_cert.h"
+#include "hs/cell_common.h"
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+
+#include "hs_circuitmap.h"
+#include "hs_intropoint.h"
+#include "hs_common.h"
+
+/** Extract the authentication key from an ESTABLISH_INTRO or INTRODUCE1 using
+ * the given <b>cell_type</b> from <b>cell</b> and place it in
+ * <b>auth_key_out</b>. */
+STATIC void
+get_auth_key_from_cell(ed25519_public_key_t *auth_key_out,
+ unsigned int cell_type, const void *cell)
+{
+ size_t auth_key_len;
+ const uint8_t *key_array;
+
+ tor_assert(auth_key_out);
+ tor_assert(cell);
+
+ switch (cell_type) {
+ case RELAY_COMMAND_ESTABLISH_INTRO:
+ {
+ const hs_cell_establish_intro_t *c_cell = cell;
+ key_array = hs_cell_establish_intro_getconstarray_auth_key(c_cell);
+ auth_key_len = hs_cell_establish_intro_getlen_auth_key(c_cell);
+ break;
+ }
+ case RELAY_COMMAND_INTRODUCE1:
+ {
+ const hs_cell_introduce1_t *c_cell = cell;
+ key_array = hs_cell_introduce1_getconstarray_auth_key(cell);
+ auth_key_len = hs_cell_introduce1_getlen_auth_key(c_cell);
+ break;
+ }
+ default:
+ /* Getting here is really bad as it means we got a unknown cell type from
+ * this file where every call has an hardcoded value. */
+ tor_assert(0); /* LCOV_EXCL_LINE */
+ }
+ tor_assert(key_array);
+ tor_assert(auth_key_len == sizeof(auth_key_out->pubkey));
+ memcpy(auth_key_out->pubkey, key_array, auth_key_len);
+}
+
+/** We received an ESTABLISH_INTRO <b>cell</b>. Verify its signature and MAC,
+ * given <b>circuit_key_material</b>. Return 0 on success else -1 on error. */
+STATIC int
+verify_establish_intro_cell(const hs_cell_establish_intro_t *cell,
+ const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len)
+{
+ /* We only reach this function if the first byte of the cell is 0x02 which
+ * means that auth_key_type is AUTH_KEY_ED25519, hence this check should
+ * always pass. See hs_intro_received_establish_intro(). */
+ if (BUG(cell->auth_key_type != AUTH_KEY_ED25519)) {
+ return -1;
+ }
+
+ /* Make sure the auth key length is of the right size for this type. For
+ * EXTRA safety, we check both the size of the array and the length which
+ * must be the same. Safety first!*/
+ if (hs_cell_establish_intro_getlen_auth_key(cell) != ED25519_PUBKEY_LEN ||
+ hs_cell_establish_intro_get_auth_key_len(cell) != ED25519_PUBKEY_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO auth key length is invalid");
+ return -1;
+ }
+
+ const uint8_t *msg = cell->start_cell;
+
+ /* Verify the sig */
+ {
+ ed25519_signature_t sig_struct;
+ const uint8_t *sig_array = hs_cell_establish_intro_getconstarray_sig(cell);
+
+ /* Make sure the signature length is of the right size. For EXTRA safety,
+ * we check both the size of the array and the length which must be the
+ * same. Safety first!*/
+ if (hs_cell_establish_intro_getlen_sig(cell) != sizeof(sig_struct.sig) ||
+ hs_cell_establish_intro_get_sig_len(cell) != sizeof(sig_struct.sig)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO sig len is invalid");
+ return -1;
+ }
+ /* We are now sure that sig_len is of the right size. */
+ memcpy(sig_struct.sig, sig_array, cell->sig_len);
+
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, cell);
+
+ const size_t sig_msg_len = cell->end_sig_fields - msg;
+ int sig_mismatch = ed25519_checksig_prefixed(&sig_struct,
+ msg, sig_msg_len,
+ ESTABLISH_INTRO_SIG_PREFIX,
+ &auth_key);
+ if (sig_mismatch) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO signature not as expected");
+ return -1;
+ }
+ }
+
+ /* Verify the MAC */
+ {
+ const size_t auth_msg_len = cell->end_mac_fields - msg;
+ uint8_t mac[DIGEST256_LEN];
+ crypto_mac_sha3_256(mac, sizeof(mac),
+ circuit_key_material, circuit_key_material_len,
+ msg, auth_msg_len);
+ if (tor_memneq(mac, cell->handshake_mac, sizeof(mac))) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "ESTABLISH_INTRO handshake_auth not as expected");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/* Send an INTRO_ESTABLISHED cell to <b>circ</b>. */
+MOCK_IMPL(int,
+hs_intro_send_intro_established_cell,(or_circuit_t *circ))
+{
+ int ret;
+ uint8_t *encoded_cell = NULL;
+ ssize_t encoded_len, result_len;
+ hs_cell_intro_established_t *cell;
+ cell_extension_t *ext;
+
+ tor_assert(circ);
+
+ /* Build the cell payload. */
+ cell = hs_cell_intro_established_new();
+ ext = cell_extension_new();
+ cell_extension_set_num(ext, 0);
+ hs_cell_intro_established_set_extensions(cell, ext);
+ /* Encode the cell to binary format. */
+ encoded_len = hs_cell_intro_established_encoded_len(cell);
+ tor_assert(encoded_len > 0);
+ encoded_cell = tor_malloc_zero(encoded_len);
+ result_len = hs_cell_intro_established_encode(encoded_cell, encoded_len,
+ cell);
+ tor_assert(encoded_len == result_len);
+
+ ret = relay_send_command_from_edge(0, TO_CIRCUIT(circ),
+ RELAY_COMMAND_INTRO_ESTABLISHED,
+ (char *) encoded_cell, encoded_len,
+ NULL);
+ /* On failure, the above function will close the circuit. */
+ hs_cell_intro_established_free(cell);
+ tor_free(encoded_cell);
+ return ret;
+}
+
+/** We received an ESTABLISH_INTRO <b>parsed_cell</b> on <b>circ</b>. It's
+ * well-formed and passed our verifications. Perform appropriate actions to
+ * establish an intro point. */
+static int
+handle_verified_establish_intro_cell(or_circuit_t *circ,
+ const hs_cell_establish_intro_t *parsed_cell)
+{
+ /* Get the auth key of this intro point */
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO,
+ parsed_cell);
+
+ /* Then notify the hidden service that the intro point is established by
+ sending an INTRO_ESTABLISHED cell */
+ if (hs_intro_send_intro_established_cell(circ)) {
+ log_warn(LD_PROTOCOL, "Couldn't send INTRO_ESTABLISHED cell.");
+ return -1;
+ }
+
+ /* Associate intro point auth key with this circuit. */
+ hs_circuitmap_register_intro_circ_v3(circ, &auth_key);
+ /* Repurpose this circuit into an intro circuit. */
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
+
+ return 0;
+}
+
+/** We just received an ESTABLISH_INTRO cell in <b>circ</b> with payload in
+ * <b>request</b>. Handle it by making <b>circ</b> an intro circuit. Return 0
+ * if everything went well, or -1 if there were errors. */
+static int
+handle_establish_intro(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ int cell_ok, retval = -1;
+ hs_cell_establish_intro_t *parsed_cell = NULL;
+
+ tor_assert(circ);
+ tor_assert(request);
+
+ log_info(LD_REND, "Received an ESTABLISH_INTRO request on circuit %" PRIu32,
+ circ->p_circ_id);
+
+ /* Check that the circuit is in shape to become an intro point */
+ if (!hs_intro_circuit_is_suitable_for_establish_intro(circ)) {
+ goto err;
+ }
+
+ /* Parse the cell */
+ ssize_t parsing_result = hs_cell_establish_intro_parse(&parsed_cell,
+ request, request_len);
+ if (parsing_result < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s ESTABLISH_INTRO cell.",
+ parsing_result == -1 ? "invalid" : "truncated");
+ goto err;
+ }
+
+ cell_ok = verify_establish_intro_cell(parsed_cell,
+ (uint8_t *) circ->rend_circ_nonce,
+ sizeof(circ->rend_circ_nonce));
+ if (cell_ok < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Failed to verify ESTABLISH_INTRO cell.");
+ goto err;
+ }
+
+ /* This cell is legit. Take the appropriate actions. */
+ cell_ok = handle_verified_establish_intro_cell(circ, parsed_cell);
+ if (cell_ok < 0) {
+ goto err;
+ }
+
+ /* We are done! */
+ retval = 0;
+ goto done;
+
+ err:
+ /* When sending the intro establish ack, on error the circuit can be marked
+ * as closed so avoid a double close. */
+ if (!TO_CIRCUIT(circ)->marked_for_close) {
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ }
+
+ done:
+ hs_cell_establish_intro_free(parsed_cell);
+ return retval;
+}
+
+/* Return True if circuit is suitable for being an intro circuit. */
+static int
+circuit_is_suitable_intro_point(const or_circuit_t *circ,
+ const char *log_cell_type_str)
+{
+ tor_assert(circ);
+ tor_assert(log_cell_type_str);
+
+ /* Basic circuit state sanity checks. */
+ if (circ->base_.purpose != CIRCUIT_PURPOSE_OR) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s on non-OR circuit.", log_cell_type_str);
+ return 0;
+ }
+
+ if (circ->base_.n_chan) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s on non-edge circuit.", log_cell_type_str);
+ return 0;
+ }
+
+ /* Suitable. */
+ return 1;
+}
+
+/* Return True if circuit is suitable for being service-side intro circuit. */
+int
+hs_intro_circuit_is_suitable_for_establish_intro(const or_circuit_t *circ)
+{
+ return circuit_is_suitable_intro_point(circ, "ESTABLISH_INTRO");
+}
+
+/* We just received an ESTABLISH_INTRO cell in <b>circ</b>. Figure out of it's
+ * a legacy or a next gen cell, and pass it to the appropriate handler. */
+int
+hs_intro_received_establish_intro(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ tor_assert(circ);
+ tor_assert(request);
+
+ if (request_len == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Empty ESTABLISH_INTRO cell.");
+ goto err;
+ }
+
+ /* Using the first byte of the cell, figure out the version of
+ * ESTABLISH_INTRO and pass it to the appropriate cell handler */
+ const uint8_t first_byte = request[0];
+ switch (first_byte) {
+ case HS_INTRO_AUTH_KEY_TYPE_LEGACY0:
+ case HS_INTRO_AUTH_KEY_TYPE_LEGACY1:
+ return rend_mid_establish_intro_legacy(circ, request, request_len);
+ case HS_INTRO_AUTH_KEY_TYPE_ED25519:
+ return handle_establish_intro(circ, request, request_len);
+ default:
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Unrecognized AUTH_KEY_TYPE %u.", first_byte);
+ goto err;
+ }
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
+/* Send an INTRODUCE_ACK cell onto the circuit <b>circ</b> with the status
+ * value in <b>status</b>. Depending on the status, it can be ACK or a NACK.
+ * Return 0 on success else a negative value on error which will close the
+ * circuit. */
+static int
+send_introduce_ack_cell(or_circuit_t *circ, hs_intro_ack_status_t status)
+{
+ int ret = -1;
+ uint8_t *encoded_cell = NULL;
+ ssize_t encoded_len, result_len;
+ hs_cell_introduce_ack_t *cell;
+ cell_extension_t *ext;
+
+ tor_assert(circ);
+
+ /* Setup the INTRODUCE_ACK cell. We have no extensions so the N_EXTENSIONS
+ * field is set to 0 by default with a new object. */
+ cell = hs_cell_introduce_ack_new();
+ ret = hs_cell_introduce_ack_set_status(cell, status);
+ /* We have no cell extensions in an INTRODUCE_ACK cell. */
+ ext = cell_extension_new();
+ cell_extension_set_num(ext, 0);
+ hs_cell_introduce_ack_set_extensions(cell, ext);
+ /* A wrong status is a very bad code flow error as this value is controlled
+ * by the code in this file and not an external input. This means we use a
+ * code that is not known by the trunnel ABI. */
+ tor_assert(ret == 0);
+ /* Encode the payload. We should never fail to get the encoded length. */
+ encoded_len = hs_cell_introduce_ack_encoded_len(cell);
+ tor_assert(encoded_len > 0);
+ encoded_cell = tor_malloc_zero(encoded_len);
+ result_len = hs_cell_introduce_ack_encode(encoded_cell, encoded_len, cell);
+ tor_assert(encoded_len == result_len);
+
+ ret = relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ),
+ RELAY_COMMAND_INTRODUCE_ACK,
+ (char *) encoded_cell, encoded_len,
+ NULL);
+ /* On failure, the above function will close the circuit. */
+ hs_cell_introduce_ack_free(cell);
+ tor_free(encoded_cell);
+ return ret;
+}
+
+/* Validate a parsed INTRODUCE1 <b>cell</b>. Return 0 if valid or else a
+ * negative value for an invalid cell that should be NACKed. */
+STATIC int
+validate_introduce1_parsed_cell(const hs_cell_introduce1_t *cell)
+{
+ size_t legacy_key_id_len;
+ const uint8_t *legacy_key_id;
+
+ tor_assert(cell);
+
+ /* This code path SHOULD NEVER be reached if the cell is a legacy type so
+ * safety net here. The legacy ID must be zeroes in this case. */
+ legacy_key_id_len = hs_cell_introduce1_getlen_legacy_key_id(cell);
+ legacy_key_id = hs_cell_introduce1_getconstarray_legacy_key_id(cell);
+ if (BUG(!tor_mem_is_zero((char *) legacy_key_id, legacy_key_id_len))) {
+ goto invalid;
+ }
+
+ /* The auth key of an INTRODUCE1 should be of type ed25519 thus leading to a
+ * known fixed length as well. */
+ if (hs_cell_introduce1_get_auth_key_type(cell) !=
+ HS_INTRO_AUTH_KEY_TYPE_ED25519) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell auth key type. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+ if (hs_cell_introduce1_get_auth_key_len(cell) != ED25519_PUBKEY_LEN ||
+ hs_cell_introduce1_getlen_auth_key(cell) != ED25519_PUBKEY_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell auth key length. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+ if (hs_cell_introduce1_getlen_encrypted(cell) == 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting invalid INTRODUCE1 cell encrypted length. "
+ "Responding with NACK.");
+ goto invalid;
+ }
+
+ return 0;
+ invalid:
+ return -1;
+}
+
+/* We just received a non legacy INTRODUCE1 cell on <b>client_circ</b> with
+ * the payload in <b>request</b> of size <b>request_len</b>. Return 0 if
+ * everything went well, or -1 if an error occured. This function is in charge
+ * of sending back an INTRODUCE_ACK cell and will close client_circ on error.
+ */
+STATIC int
+handle_introduce1(or_circuit_t *client_circ, const uint8_t *request,
+ size_t request_len)
+{
+ int ret = -1;
+ or_circuit_t *service_circ;
+ hs_cell_introduce1_t *parsed_cell;
+ hs_intro_ack_status_t status = HS_INTRO_ACK_STATUS_SUCCESS;
+
+ tor_assert(client_circ);
+ tor_assert(request);
+
+ /* Parse cell. Note that we can only parse the non encrypted section for
+ * which we'll use the authentication key to find the service introduction
+ * circuit and relay the cell on it. */
+ ssize_t cell_size = hs_cell_introduce1_parse(&parsed_cell, request,
+ request_len);
+ if (cell_size < 0) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
+ "Rejecting %s INTRODUCE1 cell. Responding with NACK.",
+ cell_size == -1 ? "invalid" : "truncated");
+ /* Inform client that the INTRODUCE1 has a bad format. */
+ status = HS_INTRO_ACK_STATUS_BAD_FORMAT;
+ goto send_ack;
+ }
+
+ /* Once parsed validate the cell format. */
+ if (validate_introduce1_parsed_cell(parsed_cell) < 0) {
+ /* Inform client that the INTRODUCE1 has bad format. */
+ status = HS_INTRO_ACK_STATUS_BAD_FORMAT;
+ goto send_ack;
+ }
+
+ /* Find introduction circuit through our circuit map. */
+ {
+ ed25519_public_key_t auth_key;
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_INTRODUCE1, parsed_cell);
+ service_circ = hs_circuitmap_get_intro_circ_v3(&auth_key);
+ if (service_circ == NULL) {
+ char b64_key[ED25519_BASE64_LEN + 1];
+ ed25519_public_to_base64(b64_key, &auth_key);
+ log_info(LD_REND, "No intro circuit found for INTRODUCE1 cell "
+ "with auth key %s from circuit %" PRIu32 ". "
+ "Responding with NACK.",
+ safe_str(b64_key), client_circ->p_circ_id);
+ /* Inform the client that we don't know the requested service ID. */
+ status = HS_INTRO_ACK_STATUS_UNKNOWN_ID;
+ goto send_ack;
+ }
+ }
+
+ /* Relay the cell to the service on its intro circuit with an INTRODUCE2
+ * cell which is the same exact payload. */
+ if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(service_circ),
+ RELAY_COMMAND_INTRODUCE2,
+ (char *) request, request_len, NULL)) {
+ log_warn(LD_PROTOCOL, "Unable to send INTRODUCE2 cell to the service.");
+ /* Inform the client that we can't relay the cell. */
+ status = HS_INTRO_ACK_STATUS_CANT_RELAY;
+ goto send_ack;
+ }
+
+ /* Success! Send an INTRODUCE_ACK success status onto the client circuit. */
+ status = HS_INTRO_ACK_STATUS_SUCCESS;
+ ret = 0;
+
+ send_ack:
+ /* Send INTRODUCE_ACK or INTRODUCE_NACK to client */
+ if (send_introduce_ack_cell(client_circ, status) < 0) {
+ log_warn(LD_PROTOCOL, "Unable to send an INTRODUCE ACK status %d "
+ "to client.", status);
+ /* Circuit has been closed on failure of transmission. */
+ goto done;
+ }
+ if (status != HS_INTRO_ACK_STATUS_SUCCESS) {
+ /* We just sent a NACK that is a non success status code so close the
+ * circuit because it's not useful to keep it open. Remember, a client can
+ * only send one INTRODUCE1 cell on a circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(client_circ), END_CIRC_REASON_INTERNAL);
+ }
+ done:
+ hs_cell_introduce1_free(parsed_cell);
+ return ret;
+}
+
+/* Identify if the encoded cell we just received is a legacy one or not. The
+ * <b>request</b> should be at least DIGEST_LEN bytes long. */
+STATIC int
+introduce1_cell_is_legacy(const uint8_t *request)
+{
+ tor_assert(request);
+
+ /* If the first 20 bytes of the cell (DIGEST_LEN) are NOT zeroes, it
+ * indicates a legacy cell (v2). */
+ if (!tor_mem_is_zero((const char *) request, DIGEST_LEN)) {
+ /* Legacy cell. */
+ return 1;
+ }
+ /* Not a legacy cell. */
+ return 0;
+}
+
+/* Return true iff the circuit <b>circ</b> is suitable for receiving an
+ * INTRODUCE1 cell. */
+STATIC int
+circuit_is_suitable_for_introduce1(const or_circuit_t *circ)
+{
+ tor_assert(circ);
+
+ /* Is this circuit an intro point circuit? */
+ if (!circuit_is_suitable_intro_point(circ, "INTRODUCE1")) {
+ return 0;
+ }
+
+ if (circ->already_received_introduce1) {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Blocking multiple introductions on the same circuit. "
+ "Someone might be trying to attack a hidden service through "
+ "this relay.");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* We just received an INTRODUCE1 cell on <b>circ</b>. Figure out which type
+ * it is and pass it to the appropriate handler. Return 0 on success else a
+ * negative value and the circuit is closed. */
+int
+hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
+{
+ int ret;
+
+ tor_assert(circ);
+ tor_assert(request);
+
+ /* A cell that can't hold a DIGEST_LEN is invalid as we need to check if
+ * it's a legacy cell or not using the first DIGEST_LEN bytes. */
+ if (request_len < DIGEST_LEN) {
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Invalid INTRODUCE1 cell length.");
+ goto err;
+ }
+
+ /* Make sure we have a circuit that can have an INTRODUCE1 cell on it. */
+ if (!circuit_is_suitable_for_introduce1(circ)) {
+ /* We do not send a NACK because the circuit is not suitable for any kind
+ * of response or transmission as it's a violation of the protocol. */
+ goto err;
+ }
+ /* Mark the circuit that we got this cell. None are allowed after this as a
+ * DoS mitigation since one circuit with one client can hammer a service. */
+ circ->already_received_introduce1 = 1;
+
+ /* We are sure here to have at least DIGEST_LEN bytes. */
+ if (introduce1_cell_is_legacy(request)) {
+ /* Handle a legacy cell. */
+ ret = rend_mid_introduce_legacy(circ, request, request_len);
+ } else {
+ /* Handle a non legacy cell. */
+ ret = handle_introduce1(circ, request, request_len);
+ }
+ return ret;
+
+ err:
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
+ return -1;
+}
+
diff --git a/src/or/hs_intropoint.h b/src/or/hs_intropoint.h
new file mode 100644
index 0000000000..e6024a858f
--- /dev/null
+++ b/src/or/hs_intropoint.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_intropoint.h
+ * \brief Header file for hs_intropoint.c.
+ **/
+
+#ifndef TOR_HS_INTRO_H
+#define TOR_HS_INTRO_H
+
+/* Authentication key type in an ESTABLISH_INTRO cell. */
+enum hs_intro_auth_key_type {
+ HS_INTRO_AUTH_KEY_TYPE_LEGACY0 = 0x00,
+ HS_INTRO_AUTH_KEY_TYPE_LEGACY1 = 0x01,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519 = 0x02,
+};
+
+/* INTRODUCE_ACK status code. */
+typedef enum {
+ HS_INTRO_ACK_STATUS_SUCCESS = 0x0000,
+ HS_INTRO_ACK_STATUS_UNKNOWN_ID = 0x0001,
+ HS_INTRO_ACK_STATUS_BAD_FORMAT = 0x0002,
+ HS_INTRO_ACK_STATUS_CANT_RELAY = 0x0003,
+} hs_intro_ack_status_t;
+
+int hs_intro_received_establish_intro(or_circuit_t *circ,
+ const uint8_t *request,
+ size_t request_len);
+int hs_intro_received_introduce1(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
+
+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);
+
+#ifdef HS_INTROPOINT_PRIVATE
+
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_introduce1.h"
+
+STATIC int
+verify_establish_intro_cell(const hs_cell_establish_intro_t *out,
+ const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len);
+
+STATIC void
+get_auth_key_from_cell(ed25519_public_key_t *auth_key_out,
+ unsigned int cell_type, const void *cell);
+
+STATIC int introduce1_cell_is_legacy(const uint8_t *request);
+STATIC int handle_introduce1(or_circuit_t *client_circ,
+ const uint8_t *request, size_t request_len);
+STATIC int validate_introduce1_parsed_cell(const hs_cell_introduce1_t *cell);
+STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ);
+
+#endif /* HS_INTROPOINT_PRIVATE */
+
+#endif /* TOR_HS_INTRO_H */
+
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
new file mode 100644
index 0000000000..8687403b86
--- /dev/null
+++ b/src/or/hs_service.c
@@ -0,0 +1,172 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.c
+ * \brief Implement next generation hidden service functionality
+ **/
+
+#include "or.h"
+#include "relay.h"
+#include "rendservice.h"
+#include "circuitlist.h"
+#include "circpathbias.h"
+
+#include "hs_service.h"
+#include "hs_common.h"
+
+#include "hs/cell_establish_intro.h"
+#include "hs/cell_common.h"
+
+/* 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. */
+
+/** 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 hs_cell_establish_intro_t *cell)
+{
+ ssize_t bytes_used = 0;
+
+ if (buf_out_len < RELAY_PAYLOAD_SIZE) {
+ return -1;
+ }
+
+ bytes_used = hs_cell_establish_intro_encode(buf_out, buf_out_len,
+ cell);
+ return bytes_used;
+}
+
+/* Set the cell extensions of <b>cell</b>. */
+static void
+set_cell_extensions(hs_cell_establish_intro_t *cell)
+{
+ cell_extension_t *cell_extensions = cell_extension_new();
+
+ /* For now, we don't use extensions at all. */
+ cell_extensions->num = 0; /* It's already zeroed, but be explicit. */
+ hs_cell_establish_intro_set_extensions(cell, cell_extensions);
+}
+
+/** 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. */
+hs_cell_establish_intro_t *
+generate_establish_intro_cell(const uint8_t *circuit_key_material,
+ size_t circuit_key_material_len)
+{
+ hs_cell_establish_intro_t *cell = NULL;
+ ssize_t encoded_len;
+
+ log_warn(LD_GENERAL,
+ "Generating ESTABLISH_INTRO cell (key_material_len: %u)",
+ (unsigned) circuit_key_material_len);
+
+ /* Generate short-term keypair for use in ESTABLISH_INTRO */
+ ed25519_keypair_t key_struct;
+ if (ed25519_keypair_generate(&key_struct, 0) < 0) {
+ goto err;
+ }
+
+ cell = hs_cell_establish_intro_new();
+
+ /* Set AUTH_KEY_TYPE: 2 means ed25519 */
+ hs_cell_establish_intro_set_auth_key_type(cell, AUTH_KEY_ED25519);
+
+ /* Set AUTH_KEY_LEN field */
+ /* Must also set byte-length of AUTH_KEY to match */
+ int auth_key_len = ED25519_PUBKEY_LEN;
+ hs_cell_establish_intro_set_auth_key_len(cell, auth_key_len);
+ hs_cell_establish_intro_setlen_auth_key(cell, auth_key_len);
+
+ /* Set AUTH_KEY field */
+ uint8_t *auth_key_ptr = hs_cell_establish_intro_getarray_auth_key(cell);
+ memcpy(auth_key_ptr, key_struct.pubkey.pubkey, auth_key_len);
+
+ /* No cell extensions needed */
+ set_cell_extensions(cell);
+
+ /* 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;
+ hs_cell_establish_intro_set_sig_len(cell, sig_len);
+ hs_cell_establish_intro_setlen_sig(cell, sig_len);
+
+ /* XXX How to make this process easier and nicer? */
+
+ /* 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 = hs_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.");
+ goto err;
+ }
+
+ /* sanity check */
+ tor_assert(encoded_len > ED25519_SIG_LEN + 2 + TRUNNEL_SHA3_256_LEN);
+
+ /* 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 =
+ hs_cell_establish_intro_getarray_handshake_mac(cell);
+ memcpy(handshake_ptr, mac, sizeof(mac));
+ }
+
+ /* Calculate the cell signature */
+ {
+ /* 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 = hs_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;
+ }
+
+ tor_assert(encoded_len > ED25519_SIG_LEN);
+
+ 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;
+ }
+
+ /* And write the signature to the cell */
+ uint8_t *sig_ptr = hs_cell_establish_intro_getarray_sig(cell);
+ memcpy(sig_ptr, sig.sig, sig_len);
+ }
+
+ /* We are done! Return the cell! */
+ return cell;
+
+ err:
+ hs_cell_establish_intro_free(cell);
+ return NULL;
+}
+
diff --git a/src/or/hs_service.h b/src/or/hs_service.h
new file mode 100644
index 0000000000..5d2d8dc4bb
--- /dev/null
+++ b/src/or/hs_service.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file hs_service.h
+ * \brief Header file for hs_service.c.
+ **/
+
+#ifndef TOR_HS_SERVICE_H
+#define TOR_HS_SERVICE_H
+
+#include "or.h"
+#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. */
+
+hs_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 hs_cell_establish_intro_t *cell);
+
+#endif /* TOR_HS_SERVICE_H */
+
diff --git a/src/or/include.am b/src/or/include.am
index ae493b7225..4e54deca55 100644
--- a/src/or/include.am
+++ b/src/or/include.am
@@ -19,6 +19,7 @@ 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/channeltls.c \
@@ -45,9 +46,15 @@ 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_service.c \
src/or/entrynodes.c \
src/or/ext_orport.c \
src/or/hibernate.c \
+ src/or/hs_cache.c \
+ src/or/hs_common.c \
+ src/or/hs_descriptor.c \
src/or/keypin.c \
src/or/main.c \
src/or/microdesc.c \
@@ -59,6 +66,7 @@ LIBTOR_A_SOURCES = \
src/or/shared_random.c \
src/or/shared_random_state.c \
src/or/transports.c \
+ src/or/parsecommon.c \
src/or/periodic.c \
src/or/protover.c \
src/or/policies.c \
@@ -126,6 +134,7 @@ endif
ORHEADERS = \
src/or/addressmap.h \
+ src/or/bridges.h \
src/or/buffers.h \
src/or/channel.h \
src/or/channeltls.h \
@@ -157,6 +166,12 @@ ORHEADERS = \
src/or/geoip.h \
src/or/entrynodes.h \
src/or/hibernate.h \
+ src/or/hs_cache.h \
+ src/or/hs_common.h \
+ src/or/hs_descriptor.h \
+ src/or/hs_intropoint.h \
+ src/or/hs_circuitmap.h \
+ src/or/hs_service.h \
src/or/keypin.h \
src/or/main.h \
src/or/microdesc.h \
@@ -171,6 +186,7 @@ ORHEADERS = \
src/or/shared_random.h \
src/or/shared_random_state.h \
src/or/transports.h \
+ src/or/parsecommon.h \
src/or/periodic.h \
src/or/policies.h \
src/or/protover.h \
diff --git a/src/or/main.c b/src/or/main.c
index 187b255bfb..1d899349ef 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -8,12 +8,49 @@
* \file main.c
* \brief Toplevel module. Handles signals, multiplexes between
* connections, implements main loop, and drives scheduled events.
+ *
+ * For the main loop itself; see run_main_loop_once(). It invokes the rest of
+ * Tor mostly through Libevent callbacks. Libevent callbacks can happen when
+ * a timer elapses, a signal is received, a socket is ready to read or write,
+ * or an event is manually activated.
+ *
+ * Most events in Tor are driven from these callbacks:
+ * <ul>
+ * <li>conn_read_callback() and conn_write_callback() here, which are
+ * invoked when a socket is ready to read or write respectively.
+ * <li>signal_callback(), which handles incoming signals.
+ * </ul>
+ * Other events are used for specific purposes, or for building more complex
+ * control structures. If you search for usage of tor_libevent_new(), you
+ * will find all the events that we construct in Tor.
+ *
+ * Tor has numerous housekeeping operations that need to happen
+ * regularly. They are handled in different ways:
+ * <ul>
+ * <li>The most frequent operations are handled after every read or write
+ * event, at the end of connection_handle_read() and
+ * connection_handle_write().
+ *
+ * <li>The next most frequent operations happen after each invocation of the
+ * main loop, in run_main_loop_once().
+ *
+ * <li>Once per second, we run all of the operations listed in
+ * second_elapsed_callback(), and in its child, run_scheduled_events().
+ *
+ * <li>Once-a-second operations are handled in second_elapsed_callback().
+ *
+ * <li>More infrequent operations take place based on the periodic event
+ * driver in periodic.c . These are stored in the periodic_events[]
+ * table.
+ * </ul>
+ *
**/
#define MAIN_PRIVATE
#include "or.h"
#include "addressmap.h"
#include "backtrace.h"
+#include "bridges.h"
#include "buffers.h"
#include "channel.h"
#include "channeltls.h"
@@ -37,6 +74,8 @@
#include "entrynodes.h"
#include "geoip.h"
#include "hibernate.h"
+#include "hs_cache.h"
+#include "hs_circuitmap.h"
#include "keypin.h"
#include "main.h"
#include "microdesc.h"
@@ -325,7 +364,7 @@ connection_unlink(connection_t *conn)
}
if (conn->type == CONN_TYPE_OR) {
if (!tor_digest_is_zero(TO_OR_CONN(conn)->identity_digest))
- connection_or_remove_from_identity_map(TO_OR_CONN(conn));
+ connection_or_clear_identity(TO_OR_CONN(conn));
/* connection_unlink() can only get called if the connection
* was already on the closeable list, and it got there by
* connection_mark_for_close(), which was called from
@@ -447,7 +486,7 @@ connection_check_event(connection_t *conn, struct event *ev)
*/
bad = ev != NULL;
} else {
- /* Everytyhing else should have an underlying socket, or a linked
+ /* Everything else should have an underlying socket, or a linked
* connection (which is also tracked with a read_event/write_event pair).
*/
bad = ev == NULL;
@@ -926,6 +965,15 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs)
{
const or_options_t *options = get_options();
+ /* if we have enough dir info, then update our guard status with
+ * whatever we just learned. */
+ int invalidate_circs = guards_update_all();
+
+ if (invalidate_circs) {
+ circuit_mark_all_unused_circs();
+ circuit_mark_all_dirty_circs_as_unusable();
+ }
+
if (!router_have_minimum_dir_info()) {
int quiet = suppress_logs || from_cache ||
directory_too_idle_to_fetch_descriptors(options, now);
@@ -939,9 +987,6 @@ directory_info_has_arrived(time_t now, int from_cache, int suppress_logs)
update_all_descriptor_downloads(now);
}
- /* if we have enough dir info, then update our guard status with
- * whatever we just learned. */
- entry_guards_compute_status(options, now);
/* Don't even bother trying to get extrainfo until the rest of our
* directory info is up-to-date */
if (options->DownloadExtraInfo)
@@ -1338,6 +1383,9 @@ run_scheduled_events(time_t now)
/* 0c. If we've deferred log messages for the controller, handle them now */
flush_pending_log_callbacks();
+ /* Maybe enough time elapsed for us to reconsider a circuit. */
+ circuit_upgrade_circuits_from_guard_wait();
+
if (options->UseBridges && !options->DisableNetwork) {
fetch_bridge_descriptors(options, now);
}
@@ -1358,6 +1406,7 @@ run_scheduled_events(time_t now)
/* (If our circuit build timeout can ever become lower than a second (which
* it can't, currently), we should do this more often.) */
circuit_expire_building();
+ circuit_expire_waiting_for_better_guard();
/* 3b. Also look at pending streams and prune the ones that 'began'
* a long time ago but haven't gotten a 'connected' yet.
@@ -1389,7 +1438,7 @@ run_scheduled_events(time_t now)
}
/* 5. We do housekeeping for each connection... */
- connection_or_set_bad_connections(NULL, 0);
+ channel_update_bad_for_new_circs(NULL, 0);
int i;
for (i=0;i<smartlist_len(connection_array);i++) {
run_connection_housekeeping(i, now);
@@ -1423,13 +1472,13 @@ run_scheduled_events(time_t now)
pt_configure_remaining_proxies();
}
+/* Periodic callback: Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion
+ * keys, shut down and restart all cpuworkers, and update our descriptor if
+ * necessary.
+ */
static int
rotate_onion_key_callback(time_t now, const or_options_t *options)
{
- /* 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys,
- * shut down and restart all cpuworkers, and update the directory if
- * necessary.
- */
if (server_mode(options)) {
time_t rotation_time = get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME;
if (rotation_time > now) {
@@ -1449,13 +1498,17 @@ rotate_onion_key_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/* Periodic callback: Every 30 seconds, check whether it's time to make new
+ * Ed25519 subkeys.
+ */
static int
check_ed_keys_callback(time_t now, const or_options_t *options)
{
if (server_mode(options)) {
if (should_make_new_ed_keys(options, now)) {
- if (load_ed_keys(options, now) < 0 ||
- generate_ed_link_cert(options, now)) {
+ int new_signing_key = load_ed_keys(options, now);
+ if (new_signing_key < 0 ||
+ generate_ed_link_cert(options, now, new_signing_key > 0)) {
log_err(LD_OR, "Unable to update Ed25519 keys! Exiting.");
tor_cleanup();
exit(0);
@@ -1466,6 +1519,11 @@ check_ed_keys_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: Every {LAZY,GREEDY}_DESCRIPTOR_RETRY_INTERVAL,
+ * see about fetching descriptors, microdescriptors, and extrainfo
+ * documents.
+ */
static int
launch_descriptor_fetches_callback(time_t now, const or_options_t *options)
{
@@ -1480,6 +1538,10 @@ launch_descriptor_fetches_callback(time_t now, const or_options_t *options)
return GREEDY_DESCRIPTOR_RETRY_INTERVAL;
}
+/**
+ * Periodic event: Rotate our X.509 certificates and TLS keys once every
+ * MAX_SSL_KEY_LIFETIME_INTERNAL.
+ */
static int
rotate_x509_certificate_callback(time_t now, const or_options_t *options)
{
@@ -1498,6 +1560,11 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options)
log_err(LD_BUG, "Error reinitializing TLS context");
tor_assert_unreached();
}
+ if (generate_ed_link_cert(options, now, 1)) {
+ log_err(LD_OR, "Unable to update Ed25519->TLS link certificate for "
+ "new TLS context.");
+ tor_assert_unreached();
+ }
/* We also make sure to rotate the TLS connections themselves if they've
* been up for too long -- but that's done via is_bad_for_new_circs in
@@ -1505,6 +1572,10 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options)
return MAX_SSL_KEY_LIFETIME_INTERNAL;
}
+/**
+ * Periodic callback: once an hour, grab some more entropy from the
+ * kernel and feed it to our CSPRNG.
+ **/
static int
add_entropy_callback(time_t now, const or_options_t *options)
{
@@ -1521,6 +1592,10 @@ add_entropy_callback(time_t now, const or_options_t *options)
return ENTROPY_INTERVAL;
}
+/**
+ * Periodic callback: if we're an authority, make sure we test
+ * the routers on the network for reachability.
+ */
static int
launch_reachability_tests_callback(time_t now, const or_options_t *options)
{
@@ -1532,6 +1607,10 @@ launch_reachability_tests_callback(time_t now, const or_options_t *options)
return REACHABILITY_TEST_INTERVAL;
}
+/**
+ * Periodic callback: if we're an authority, discount the stability
+ * information (and other rephist information) that's older.
+ */
static int
downrate_stability_callback(time_t now, const or_options_t *options)
{
@@ -1543,6 +1622,10 @@ downrate_stability_callback(time_t now, const or_options_t *options)
return safe_timer_diff(now, next);
}
+/**
+ * Periodic callback: if we're an authority, record our measured stability
+ * information from rephist in an mtbf file.
+ */
static int
save_stability_callback(time_t now, const or_options_t *options)
{
@@ -1555,6 +1638,10 @@ save_stability_callback(time_t now, const or_options_t *options)
return SAVE_STABILITY_INTERVAL;
}
+/**
+ * Periodic callback: if we're an authority, check on our authority
+ * certificate (the one that authenticates our authority signing key).
+ */
static int
check_authority_cert_callback(time_t now, const or_options_t *options)
{
@@ -1567,12 +1654,15 @@ check_authority_cert_callback(time_t now, const or_options_t *options)
return CHECK_V3_CERTIFICATE_INTERVAL;
}
+/**
+ * Periodic callback: If our consensus is too old, recalculate whether
+ * we can actually use it.
+ */
static int
check_expired_networkstatus_callback(time_t now, const or_options_t *options)
{
(void)options;
- /* 1f. Check whether our networkstatus has expired.
- */
+ /* Check whether our networkstatus has expired. */
networkstatus_t *ns = networkstatus_get_latest_consensus();
/*XXXX RD: This value needs to be the same as REASONABLY_LIVE_TIME in
* networkstatus_get_reasonably_live_consensus(), but that value is way
@@ -1586,6 +1676,9 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options)
return CHECK_EXPIRED_NS_INTERVAL;
}
+/**
+ * Periodic callback: Write statistics to disk if appropriate.
+ */
static int
write_stats_file_callback(time_t now, const or_options_t *options)
{
@@ -1633,6 +1726,9 @@ write_stats_file_callback(time_t now, const or_options_t *options)
return safe_timer_diff(now, next_time_to_write_stats_files);
}
+/**
+ * Periodic callback: Write bridge statistics to disk if appropriate.
+ */
static int
record_bridge_stats_callback(time_t now, const or_options_t *options)
{
@@ -1660,6 +1756,9 @@ record_bridge_stats_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: Clean in-memory caches every once in a while
+ */
static int
clean_caches_callback(time_t now, const or_options_t *options)
{
@@ -1667,12 +1766,16 @@ clean_caches_callback(time_t now, const or_options_t *options)
rep_history_clean(now - options->RephistTrackTime);
rend_cache_clean(now, REND_CACHE_TYPE_CLIENT);
rend_cache_clean(now, REND_CACHE_TYPE_SERVICE);
- rend_cache_clean_v2_descs_as_dir(now, 0);
+ hs_cache_clean_as_dir(now);
microdesc_cache_rebuild(NULL, 0);
#define CLEAN_CACHES_INTERVAL (30*60)
return CLEAN_CACHES_INTERVAL;
}
+/**
+ * Periodic callback: Clean the cache of failed hidden service lookups
+ * frequently.
+ */
static int
rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
{
@@ -1684,20 +1787,21 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options)
return 30;
}
+/**
+ * Periodic callback: If we're a server and initializing dns failed, retry.
+ */
static int
retry_dns_callback(time_t now, const or_options_t *options)
{
(void)now;
#define RETRY_DNS_INTERVAL (10*60)
- /* If we're a server and initializing dns failed, retry periodically. */
if (server_mode(options) && has_dns_init_failed())
dns_init();
return RETRY_DNS_INTERVAL;
}
- /* 2. Periodically, we consider force-uploading our descriptor
- * (if we've passed our internal checks). */
-
+/** Periodic callback: consider rebuilding or and re-uploading our descriptor
+ * (if we've passed our internal checks). */
static int
check_descriptor_callback(time_t now, const or_options_t *options)
{
@@ -1724,6 +1828,11 @@ check_descriptor_callback(time_t now, const or_options_t *options)
return CHECK_DESCRIPTOR_INTERVAL;
}
+/**
+ * Periodic callback: check whether we're reachable (as a relay), and
+ * whether our bandwidth has changed enough that we need to
+ * publish a new descriptor.
+ */
static int
check_for_reachability_bw_callback(time_t now, const or_options_t *options)
{
@@ -1760,13 +1869,13 @@ check_for_reachability_bw_callback(time_t now, const or_options_t *options)
return CHECK_DESCRIPTOR_INTERVAL;
}
+/**
+ * Periodic event: once a minute, (or every second if TestingTorNetwork, or
+ * during client bootstrap), check whether we want to download any
+ * networkstatus documents. */
static int
fetch_networkstatus_callback(time_t now, const or_options_t *options)
{
- /* 2c. Every minute (or every second if TestingTorNetwork, or during
- * client bootstrap), check whether we want to download any networkstatus
- * documents. */
-
/* How often do we check whether we should download network status
* documents? */
const int we_are_bootstrapping = networkstatus_consensus_is_bootstrapping(
@@ -1788,12 +1897,13 @@ fetch_networkstatus_callback(time_t now, const or_options_t *options)
return networkstatus_dl_check_interval;
}
+/**
+ * Periodic callback: Every 60 seconds, we relaunch listeners if any died. */
static int
retry_listeners_callback(time_t now, const or_options_t *options)
{
(void)now;
(void)options;
- /* 3d. And every 60 seconds, we relaunch listeners if any died. */
if (!net_is_disabled()) {
retry_all_listeners(NULL, NULL, 0);
return 60;
@@ -1801,6 +1911,9 @@ retry_listeners_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: as a server, see if we have any old unused circuits
+ * that should be expired */
static int
expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options)
{
@@ -1810,6 +1923,10 @@ expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options)
return 11;
}
+/**
+ * Periodic event: if we're an exit, see if our DNS server is telling us
+ * obvious lies.
+ */
static int
check_dns_honesty_callback(time_t now, const or_options_t *options)
{
@@ -1832,6 +1949,10 @@ check_dns_honesty_callback(time_t now, const or_options_t *options)
return 12*3600 + crypto_rand_int(12*3600);
}
+/**
+ * Periodic callback: if we're the bridge authority, write a networkstatus
+ * file to disk.
+ */
static int
write_bridge_ns_callback(time_t now, const or_options_t *options)
{
@@ -1844,6 +1965,9 @@ write_bridge_ns_callback(time_t now, const or_options_t *options)
return PERIODIC_EVENT_NO_UPDATE;
}
+/**
+ * Periodic callback: poke the tor-fw-helper app if we're using one.
+ */
static int
check_fw_helper_app_callback(time_t now, const or_options_t *options)
{
@@ -1867,7 +1991,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options)
return PORT_FORWARDING_CHECK_INTERVAL;
}
-/** Callback to write heartbeat message in the logs. */
+/**
+ * Periodic callback: write the heartbeat message in the logs.
+ */
static int
heartbeat_callback(time_t now, const or_options_t *options)
{
@@ -2178,8 +2304,9 @@ do_hup(void)
/* Maybe we've been given a new ed25519 key or certificate?
*/
time_t now = approx_time();
- if (load_ed_keys(options, now) < 0 ||
- generate_ed_link_cert(options, now)) {
+ int new_signing_key = load_ed_keys(options, now);
+ if (new_signing_key < 0 ||
+ generate_ed_link_cert(options, now, new_signing_key > 0)) {
log_warn(LD_OR, "Problem reloading Ed25519 keys; still using old keys.");
}
@@ -2291,6 +2418,9 @@ 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;
@@ -2373,19 +2503,26 @@ run_main_loop_once(void)
/* Make it easier to tell whether libevent failure is our fault or not. */
errno = 0;
#endif
- /* All active linked conns should get their read events activated. */
+
+ /* All active linked conns should get their read events activated,
+ * so that libevent knows to run their callbacks. */
SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn,
event_active(conn->read_event, EV_READ, 1));
called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0;
+ /* Make sure we know (about) what time it is. */
update_approx_time(time(NULL));
- /* poll until we have an event, or the second ends, or until we have
- * some active linked connections to trigger events for. */
+ /* Here it is: the main loop. Here we tell Libevent to poll until we have
+ * an event, or the second ends, or until we have some active linked
+ * connections to trigger events for. Libevent will wait till one
+ * of these happens, then run all the appropriate callbacks. */
loop_result = event_base_loop(tor_libevent_get_base(),
called_loop_once ? EVLOOP_ONCE : 0);
- /* let catch() handle things like ^c, and otherwise don't worry about it */
+ /* Oh, the loop failed. That might be an error that we need to
+ * catch, but more likely, it's just an interrupted poll() call or something,
+ * and we should try again. */
if (loop_result < 0) {
int e = tor_socket_errno(-1);
/* let the program survive things like ^z */
@@ -2408,9 +2545,17 @@ run_main_loop_once(void)
}
}
- /* This will be pretty fast if nothing new is pending. Note that this gets
- * called once per libevent loop, which will make it happen once per group
- * of events that fire, or once per second. */
+ /* And here is where we put callbacks that happen "every time the event loop
+ * runs." They must be very fast, or else the whole Tor process will get
+ * slowed down.
+ *
+ * Note that this gets called once per libevent loop, which will make it
+ * happen once per group of events that fire, or once per second. */
+
+ /* If there are any pending client connections, try attaching them to
+ * circuits (if we can.) This will be pretty fast if nothing new is
+ * pending.
+ */
connection_ap_attach_pending(0);
return 1;
@@ -2788,6 +2933,7 @@ 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. */
@@ -2971,6 +3117,7 @@ tor_free_all(int postfork)
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();
@@ -2983,12 +3130,14 @@ 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();
control_free_all();
sandbox_free_getaddrinfo_cache();
protover_free_all();
+ bridges_free_all();
if (!postfork) {
config_free_all();
or_state_free_all();
@@ -3451,6 +3600,7 @@ tor_main(int argc, char *argv[])
update_approx_time(time(NULL));
tor_threads_init();
init_logging(0);
+ monotime_init();
#ifdef USE_DMALLOC
{
/* Instruct OpenSSL to use our internal wrappers for malloc,
@@ -3484,8 +3634,6 @@ tor_main(int argc, char *argv[])
#endif
}
- monotime_init();
-
switch (get_options()->command) {
case CMD_RUN_TOR:
#ifdef NT_SERVICE
@@ -3494,7 +3642,7 @@ tor_main(int argc, char *argv[])
result = do_main_loop();
break;
case CMD_KEYGEN:
- result = load_ed_keys(get_options(), time(NULL));
+ result = load_ed_keys(get_options(), time(NULL)) < 0;
break;
case CMD_LIST_FINGERPRINT:
result = do_list_fingerprint();
diff --git a/src/or/microdesc.c b/src/or/microdesc.c
index a81dc54628..140117f683 100644
--- a/src/or/microdesc.c
+++ b/src/or/microdesc.c
@@ -917,20 +917,9 @@ update_microdescs_from_networkstatus(time_t now)
int
we_use_microdescriptors_for_circuits(const or_options_t *options)
{
- int ret = options->UseMicrodescriptors;
- if (ret == -1) {
- /* UseMicrodescriptors is "auto"; we need to decide: */
- /* If we are configured to use bridges and none of our bridges
- * know what a microdescriptor is, the answer is no. */
- if (options->UseBridges && !any_bridge_supports_microdescriptors())
- return 0;
- /* Otherwise, we decide that we'll use microdescriptors iff we are
- * not a server, and we're not autofetching everything. */
- /* XXXX++ what does not being a server have to do with it? also there's
- * a partitioning issue here where bridges differ from clients. */
- ret = !server_mode(options) && !options->FetchUselessDescriptors;
- }
- return ret;
+ if (options->UseMicrodescriptors == 0)
+ return 0; /* the user explicitly picked no */
+ return 1; /* yes and auto both mean yes */
}
/** Return true iff we should try to download microdescriptors at all. */
diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c
index 991cf80121..1fd0772f3e 100644
--- a/src/or/networkstatus.c
+++ b/src/or/networkstatus.c
@@ -6,12 +6,39 @@
/**
* \file networkstatus.c
- * \brief Functions and structures for handling network status documents as a
- * client or cache.
+ * \brief Functions and structures for handling networkstatus documents as a
+ * client or as a directory cache.
+ *
+ * A consensus networkstatus object is created by the directory
+ * authorities. It authenticates a set of network parameters--most
+ * importantly, the list of all the relays in the network. This list
+ * of relays is represented as an array of routerstatus_t objects.
+ *
+ * There are currently two flavors of consensus. With the older "NS"
+ * flavor, each relay is associated with a digest of its router
+ * descriptor. Tor instances that use this consensus keep the list of
+ * router descriptors as routerinfo_t objects stored and managed in
+ * routerlist.c. With the newer "microdesc" flavor, each relay is
+ * associated with a digest of the microdescriptor that the authorities
+ * made for it. These are stored and managed in microdesc.c. Information
+ * about the router is divided between the the networkstatus and the
+ * microdescriptor according to the general rule that microdescriptors
+ * should hold information that changes much less frequently than the
+ * information in the networkstatus.
+ *
+ * Modern clients use microdescriptor networkstatuses. Directory caches
+ * need to keep both kinds of networkstatus document, so they can serve them.
+ *
+ * This module manages fetching, holding, storing, updating, and
+ * validating networkstatus objects. The download-and-validate process
+ * is slightly complicated by the fact that the keys you need to
+ * validate a consensus are stored in the authority certificates, which
+ * you might not have yet when you download the consensus.
*/
#define NETWORKSTATUS_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "channel.h"
#include "circuitmux.h"
#include "circuitmux_ewma.h"
@@ -216,7 +243,7 @@ router_reload_consensus_networkstatus(void)
}
/** Free all storage held by the vote_routerstatus object <b>rs</b>. */
-STATIC void
+void
vote_routerstatus_free(vote_routerstatus_t *rs)
{
vote_microdesc_hash_t *h, *next;
@@ -788,8 +815,11 @@ networkstatus_nickname_is_unnamed(const char *nickname)
#define NONAUTHORITY_NS_CACHE_INTERVAL (60*60)
/** Return true iff, given the options listed in <b>options</b>, <b>flavor</b>
- * is the flavor of a consensus networkstatus that we would like to fetch. */
-static int
+ * is the flavor of a consensus networkstatus that we would like to fetch.
+ *
+ * For certificate fetches, use we_want_to_fetch_unknown_auth_certs, and
+ * for serving fetched documents, use directory_caches_dir_info. */
+int
we_want_to_fetch_flavor(const or_options_t *options, int flavor)
{
if (flavor < 0 || flavor > N_CONSENSUS_FLAVORS) {
@@ -811,6 +841,29 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor)
return flavor == usable_consensus_flavor();
}
+/** Return true iff, given the options listed in <b>options</b>, we would like
+ * to fetch and store unknown authority certificates.
+ *
+ * For consensus and descriptor fetches, use we_want_to_fetch_flavor, and
+ * for serving fetched certificates, use directory_caches_unknown_auth_certs.
+ */
+int
+we_want_to_fetch_unknown_auth_certs(const or_options_t *options)
+{
+ if (authdir_mode_v3(options) ||
+ directory_caches_unknown_auth_certs((options))) {
+ /* We want to serve all certs to others, regardless if we would use
+ * them ourselves. */
+ return 1;
+ }
+ if (options->FetchUselessDescriptors) {
+ /* Unknown certificates are definitely useless. */
+ return 1;
+ }
+ /* Otherwise, don't fetch unknown certificates. */
+ return 0;
+}
+
/** How long will we hang onto a possibly live consensus for which we're
* fetching certs before we check whether there is a better one? */
#define DELAY_WHILE_FETCHING_CERTS (20*60)
@@ -1325,6 +1378,24 @@ networkstatus_get_live_consensus,(time_t now))
return NULL;
}
+/** Determine if <b>consensus</b> is valid or expired recently enough that
+ * we can still use it.
+ *
+ * Return 1 if the consensus is reasonably live, or 0 if it is too old.
+ */
+int
+networkstatus_consensus_reasonably_live(networkstatus_t *consensus, time_t now)
+{
+#define REASONABLY_LIVE_TIME (24*60*60)
+ if (BUG(!consensus))
+ return 0;
+
+ if (now <= consensus->valid_until + REASONABLY_LIVE_TIME)
+ return 1;
+
+ return 0;
+}
+
/* XXXX remove this in favor of get_live_consensus. But actually,
* leave something like it for bridge users, who need to not totally
* lose if they spend a while fetching a new consensus. */
@@ -1333,12 +1404,11 @@ networkstatus_get_live_consensus,(time_t now))
networkstatus_t *
networkstatus_get_reasonably_live_consensus(time_t now, int flavor)
{
-#define REASONABLY_LIVE_TIME (24*60*60)
networkstatus_t *consensus =
networkstatus_get_latest_consensus_by_flavor(flavor);
if (consensus &&
consensus->valid_after <= now &&
- now <= consensus->valid_until+REASONABLY_LIVE_TIME)
+ networkstatus_consensus_reasonably_live(consensus, now))
return consensus;
else
return NULL;
@@ -1702,9 +1772,9 @@ networkstatus_set_current_consensus(const char *consensus,
}
if (flav != usable_consensus_flavor() &&
- !directory_caches_dir_info(options)) {
- /* This consensus is totally boring to us: we won't use it, and we won't
- * serve it. Drop it. */
+ !we_want_to_fetch_flavor(options, flav)) {
+ /* This consensus is totally boring to us: we won't use it, we didn't want
+ * it, and we won't serve it. Drop it. */
goto done;
}
@@ -1906,7 +1976,7 @@ networkstatus_set_current_consensus(const char *consensus,
download_status_failed(&consensus_dl_status[flav], 0);
}
- if (directory_caches_dir_info(options)) {
+ if (we_want_to_fetch_flavor(options, flav)) {
dirserv_set_cached_consensus_networkstatus(consensus,
flavor,
&c->digests,
@@ -2277,6 +2347,25 @@ networkstatus_get_param(const networkstatus_t *ns, const char *param_name,
}
/**
+ * As networkstatus_get_param(), but check torrc_value before checking the
+ * consensus. If torrc_value is in-range, then return it instead of the
+ * value from the consensus.
+ */
+int32_t
+networkstatus_get_overridable_param(const networkstatus_t *ns,
+ int32_t torrc_value,
+ const char *param_name,
+ int32_t default_val,
+ int32_t min_val, int32_t max_val)
+{
+ if (torrc_value >= min_val && torrc_value <= max_val)
+ return torrc_value;
+ else
+ return networkstatus_get_param(
+ ns, param_name, default_val, min_val, max_val);
+}
+
+/**
* Retrieve the consensus parameter that governs the
* fixed-point precision of our network balancing 'bandwidth-weights'
* (which are themselves integer consensus values). We divide them
@@ -2355,9 +2444,10 @@ int
client_would_use_router(const routerstatus_t *rs, time_t now,
const or_options_t *options)
{
- if (!rs->is_flagged_running && !options->FetchUselessDescriptors) {
+ (void) options; /* unused */
+ if (!rs->is_flagged_running) {
/* If we had this router descriptor, we wouldn't even bother using it.
- * But, if we want to have a complete list, fetch it anyway. */
+ * (Fetching and storing depends on by we_want_to_fetch_flavor().) */
return 0;
}
if (rs->published_on + OLD_ROUTER_DESC_MAX_AGE < now) {
diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h
index 71f36b69ed..66cd84c88e 100644
--- a/src/or/networkstatus.h
+++ b/src/or/networkstatus.h
@@ -66,6 +66,8 @@ const routerstatus_t *router_get_consensus_status_by_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,
const char *flavname);
void update_consensus_networkstatus_fetch_time(time_t now);
@@ -79,6 +81,8 @@ 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_consensus_reasonably_live(networkstatus_t *consensus,
+ time_t now);
networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now,
int flavor);
MOCK_DECL(int, networkstatus_consensus_is_bootstrapping,(time_t now));
@@ -111,6 +115,11 @@ 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,
+ int32_t default_val,
+ int32_t min_val, int32_t max_val);
int getinfo_helper_networkstatus(control_connection_t *conn,
const char *question, char **answer,
const char **errmsg);
@@ -123,8 +132,9 @@ document_signature_t *document_signature_dup(const document_signature_t *sig);
void networkstatus_free_all(void);
int networkstatus_get_weight_scale_param(networkstatus_t *ns);
+void vote_routerstatus_free(vote_routerstatus_t *rs);
+
#ifdef NETWORKSTATUS_PRIVATE
-STATIC void vote_routerstatus_free(vote_routerstatus_t *rs);
#ifdef TOR_UNIT_TESTS
STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c,
const char *flavor);
diff --git a/src/or/nodelist.c b/src/or/nodelist.c
index 0e9a651818..938b791102 100644
--- a/src/or/nodelist.c
+++ b/src/or/nodelist.c
@@ -10,6 +10,32 @@
* \brief Structures and functions for tracking what we know about the routers
* on the Tor network, and correlating information from networkstatus,
* routerinfo, and microdescs.
+ *
+ * The key structure here is node_t: that's the canonical way to refer
+ * to a Tor relay that we might want to build a circuit through. Every
+ * node_t has either a routerinfo_t, or a routerstatus_t from the current
+ * networkstatus consensus. If it has a routerstatus_t, it will also
+ * need to have a microdesc_t before you can use it for circuits.
+ *
+ * The nodelist_t is a global singleton that maps identities to node_t
+ * objects. Access them with the node_get_*() functions. The nodelist_t
+ * is maintained by calls throughout the codebase
+ *
+ * Generally, other code should not have to reach inside a node_t to
+ * see what information it has. Instead, you should call one of the
+ * many accessor functions that works on a generic node_t. If there
+ * isn't one that does what you need, it's better to make such a function,
+ * and then use it.
+ *
+ * For historical reasons, some of the functions that select a node_t
+ * from the list of all usable node_t objects are in the routerlist.c
+ * module, since they originally selected a routerinfo_t. (TODO: They
+ * should move!)
+ *
+ * (TODO: Perhaps someday we should abstract the remaining ways of
+ * talking about a relay to also be node_t instances. Those would be
+ * routerstatus_t as used for directory requests, and dir_server_t as
+ * used for authorities and fallback directories.)
*/
#include "or.h"
@@ -17,16 +43,19 @@
#include "config.h"
#include "control.h"
#include "dirserv.h"
+#include "entrynodes.h"
#include "geoip.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
+#include "protover.h"
#include "rendservice.h"
#include "router.h"
#include "routerlist.h"
#include "routerset.h"
+#include "torcert.h"
#include <string.h>
@@ -620,6 +649,73 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed))
}
}
+/** Return the Ed25519 identity key for the provided node, or NULL if it
+ * doesn't have one. */
+const ed25519_public_key_t *
+node_get_ed25519_id(const node_t *node)
+{
+ 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;
+ }
+ }
+ try_the_md:
+ if (node->md) {
+ if (node->md->ed25519_identity_pkey) {
+ return node->md->ed25519_identity_pkey;
+ }
+ }
+ return NULL;
+}
+
+/** Return true iff this node's Ed25519 identity matches <b>id</b>.
+ * (An absent Ed25519 identity matches NULL or zero.) */
+int
+node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id)
+{
+ const ed25519_public_key_t *node_id = node_get_ed25519_id(node);
+ if (node_id == NULL || ed25519_public_key_is_zero(node_id)) {
+ return id == NULL || ed25519_public_key_is_zero(id);
+ } else {
+ return id && ed25519_pubkey_eq(node_id, id);
+ }
+}
+
+/** Return true iff <b>node</b> supports authenticating itself
+ * by ed25519 ID during the link handshake in a way that we can understand
+ * when we probe it. */
+int
+node_supports_ed25519_link_authentication(const node_t *node)
+{
+ /* XXXX Oh hm. What if some day in the future there are link handshake
+ * versions that aren't 3 but which are ed25519 */
+ if (! node_get_ed25519_id(node))
+ return 0;
+ if (node->ri) {
+ const char *protos = node->ri->protocol_list;
+ if (protos == NULL)
+ return 0;
+ return protocol_list_supports_protocol(protos, PRT_LINKAUTH, 3);
+ }
+ if (node->rs) {
+ return node->rs->supports_ed25519_link_handshake;
+ }
+ 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)
+{
+ tor_assert(node);
+ return (const uint8_t*)node->identity;
+}
+
/** Return the nickname of <b>node</b>, or NULL if we can't find one. */
const char *
node_get_nickname(const node_t *node)
@@ -1069,9 +1165,11 @@ node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
- /* Prefer routerstatus over microdesc for consistency with the
- * fascist_firewall_* functions. Also check if the address or port are valid,
- * and try another alternative if they are not. */
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address.
+ * Prefer rs over md for consistency with the fascist_firewall_* functions.
+ * Check if the address or port are valid, and try another alternative
+ * if they are not. */
if (node->ri && tor_addr_port_is_valid(&node->ri->ipv6_addr,
node->ri->ipv6_orport, 0)) {
@@ -1131,6 +1229,9 @@ node_get_prim_dirport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address. */
+
RETURN_IPV4_AP(node->ri, dir_port, ap_out);
RETURN_IPV4_AP(node->rs, dir_port, ap_out);
/* Microdescriptors only have an IPv6 address */
@@ -1163,8 +1264,11 @@ node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out)
node_assert_ok(node);
tor_assert(ap_out);
- /* Check if the address or port are valid, and try another alternative if
- * they are not. Note that microdescriptors have no dir_port. */
+ /* Check ri first, because rewrite_node_address_for_bridge() updates
+ * node->ri with the configured bridge address.
+ * Prefer rs over md for consistency with the fascist_firewall_* functions.
+ * Check if the address or port are valid, and try another alternative
+ * if they are not. */
/* Assume IPv4 and IPv6 dirports are the same */
if (node->ri && tor_addr_port_is_valid(&node->ri->ipv6_addr,
@@ -1244,7 +1348,7 @@ nodelist_refresh_countries(void)
/** Return true iff router1 and router2 have similar enough network addresses
* that we should treat them as being in the same family */
-static inline int
+int
addrs_in_same_network_family(const tor_addr_t *a1,
const tor_addr_t *a2)
{
@@ -1551,8 +1655,8 @@ router_have_minimum_dir_info(void)
* this can cause router_have_consensus_path() to be set to
* CONSENSUS_PATH_EXIT, even if there are no nodes with accept exit policies.
*/
-consensus_path_type_t
-router_have_consensus_path(void)
+MOCK_IMPL(consensus_path_type_t,
+router_have_consensus_path, (void))
{
return have_consensus_path;
}
@@ -1641,9 +1745,9 @@ count_usable_descriptors(int *num_present, int *num_usable,
* If **<b>status_out</b> is present, allocate a new string and print the
* available percentages of guard, middle, and exit nodes to it, noting
* whether there are exits in the consensus.
- * If there are no guards in the consensus,
- * we treat the exit fraction as 100%.
- */
+ * If there are no exits in the consensus, we treat the exit fraction as 100%,
+ * but set router_have_consensus_path() so that we can only build internal
+ * paths. */
static double
compute_frac_paths_available(const networkstatus_t *consensus,
const or_options_t *options, time_t now,
@@ -1893,6 +1997,13 @@ 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;
diff --git a/src/or/nodelist.h b/src/or/nodelist.h
index 71a91e107f..4e5301df6b 100644
--- a/src/or/nodelist.h
+++ b/src/or/nodelist.h
@@ -55,6 +55,11 @@ void node_get_address_string(const node_t *node, char *cp, size_t len);
long node_get_declared_uptime(const node_t *node);
time_t node_get_published_on(const node_t *node);
const smartlist_t *node_get_declared_family(const node_t *node);
+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);
+const uint8_t *node_get_rsa_id_digest(const node_t *node);
int node_has_ipv6_addr(const node_t *node);
int node_has_ipv6_orport(const node_t *node);
@@ -89,6 +94,8 @@ int node_is_unreliable(const node_t *router, int need_uptime,
int router_exit_policy_all_nodes_reject(const tor_addr_t *addr, uint16_t port,
int need_uptime);
void router_set_status(const char *digest, int up);
+int addrs_in_same_network_family(const tor_addr_t *a1,
+ const tor_addr_t *a2);
/** router_have_minimum_dir_info tests to see if we have enough
* descriptor information to create circuits.
@@ -118,7 +125,8 @@ typedef enum {
* create exit and internal paths, circuits, streams, ... */
CONSENSUS_PATH_EXIT = 1
} consensus_path_type_t;
-consensus_path_type_t router_have_consensus_path(void);
+
+MOCK_DECL(consensus_path_type_t, router_have_consensus_path, (void));
void router_dir_info_changed(void);
const char *get_dir_info_status_string(void);
diff --git a/src/or/onion.c b/src/or/onion.c
index 4b803a785c..b3898d4085 100644
--- a/src/or/onion.c
+++ b/src/or/onion.c
@@ -76,6 +76,9 @@
#include "rephist.h"
#include "router.h"
+// trunnel
+#include "ed25519_cert.h"
+
/** Type for a linked list of circuits that are waiting for a free CPU worker
* to process a waiting onion handshake. */
typedef struct onion_queue_t {
@@ -873,13 +876,114 @@ check_extend_cell(const extend_cell_t *cell)
return check_create_cell(&cell->create_cell, 1);
}
-/** Protocol constants for specifier types in EXTEND2
- * @{
- */
-#define SPECTYPE_IPV4 0
-#define SPECTYPE_IPV6 1
-#define SPECTYPE_LEGACY_ID 2
-/** @} */
+static int
+extend_cell_from_extend1_cell_body(extend_cell_t *cell_out,
+ const extend1_cell_body_t *cell)
+{
+ tor_assert(cell_out);
+ tor_assert(cell);
+ memset(cell_out, 0, sizeof(*cell_out));
+ tor_addr_make_unspec(&cell_out->orport_ipv4.addr);
+ tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
+
+ cell_out->cell_type = RELAY_COMMAND_EXTEND;
+ tor_addr_from_ipv4h(&cell_out->orport_ipv4.addr, cell->ipv4addr);
+ cell_out->orport_ipv4.port = cell->port;
+ if (tor_memeq(cell->onionskin, NTOR_CREATE_MAGIC, 16)) {
+ cell_out->create_cell.cell_type = CELL_CREATE2;
+ cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_NTOR;
+ cell_out->create_cell.handshake_len = NTOR_ONIONSKIN_LEN;
+ memcpy(cell_out->create_cell.onionskin, cell->onionskin + 16,
+ NTOR_ONIONSKIN_LEN);
+ } else {
+ cell_out->create_cell.cell_type = CELL_CREATE;
+ cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_TAP;
+ cell_out->create_cell.handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN;
+ memcpy(cell_out->create_cell.onionskin, cell->onionskin,
+ TAP_ONIONSKIN_CHALLENGE_LEN);
+ }
+ memcpy(cell_out->node_id, cell->identity, DIGEST_LEN);
+ return 0;
+}
+
+static int
+create_cell_from_create2_cell_body(create_cell_t *cell_out,
+ const create2_cell_body_t *cell)
+{
+ tor_assert(cell_out);
+ tor_assert(cell);
+ memset(cell_out, 0, sizeof(create_cell_t));
+ if (BUG(cell->handshake_len > sizeof(cell_out->onionskin))) {
+ /* This should be impossible because there just isn't enough room in the
+ * input cell to make the handshake_len this large and provide a
+ * handshake_data to match. */
+ return -1;
+ }
+
+ cell_out->cell_type = CELL_CREATE2;
+ cell_out->handshake_type = cell->handshake_type;
+ cell_out->handshake_len = cell->handshake_len;
+ memcpy(cell_out->onionskin,
+ create2_cell_body_getconstarray_handshake_data(cell),
+ cell->handshake_len);
+ return 0;
+}
+
+static int
+extend_cell_from_extend2_cell_body(extend_cell_t *cell_out,
+ const extend2_cell_body_t *cell)
+{
+ tor_assert(cell_out);
+ tor_assert(cell);
+ int found_ipv4 = 0, found_ipv6 = 0, found_rsa_id = 0, found_ed_id = 0;
+ memset(cell_out, 0, sizeof(*cell_out));
+ tor_addr_make_unspec(&cell_out->orport_ipv4.addr);
+ tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
+ cell_out->cell_type = RELAY_COMMAND_EXTEND2;
+
+ unsigned i;
+ for (i = 0; i < cell->n_spec; ++i) {
+ const link_specifier_t *ls = extend2_cell_body_getconst_ls(cell, i);
+ switch (ls->ls_type) {
+ case LS_IPV4:
+ if (found_ipv4)
+ continue;
+ found_ipv4 = 1;
+ tor_addr_from_ipv4h(&cell_out->orport_ipv4.addr, ls->un_ipv4_addr);
+ cell_out->orport_ipv4.port = ls->un_ipv4_port;
+ break;
+ case LS_IPV6:
+ if (found_ipv6)
+ continue;
+ found_ipv6 = 1;
+ tor_addr_from_ipv6_bytes(&cell_out->orport_ipv6.addr,
+ (const char *)ls->un_ipv6_addr);
+ cell_out->orport_ipv6.port = ls->un_ipv6_port;
+ break;
+ case LS_LEGACY_ID:
+ if (found_rsa_id)
+ return -1;
+ found_rsa_id = 1;
+ memcpy(cell_out->node_id, ls->un_legacy_id, 20);
+ break;
+ case LS_ED25519_ID:
+ if (found_ed_id)
+ return -1;
+ found_ed_id = 1;
+ memcpy(cell_out->ed_pubkey.pubkey, ls->un_ed25519_id, 32);
+ break;
+ default:
+ /* Ignore this, whatever it is. */
+ break;
+ }
+ }
+
+ if (!found_rsa_id || !found_ipv4) /* These are mandatory */
+ return -1;
+
+ return create_cell_from_create2_cell_body(&cell_out->create_cell,
+ cell->create2);
+}
/** Parse an EXTEND or EXTEND2 cell (according to <b>command</b>) from the
* <b>payload_length</b> bytes of <b>payload</b> into <b>cell_out</b>. Return
@@ -888,101 +992,44 @@ int
extend_cell_parse(extend_cell_t *cell_out, const uint8_t command,
const uint8_t *payload, size_t payload_length)
{
- const uint8_t *eop;
- memset(cell_out, 0, sizeof(*cell_out));
+ tor_assert(cell_out);
+ tor_assert(payload);
+
if (payload_length > RELAY_PAYLOAD_SIZE)
return -1;
- eop = payload + payload_length;
switch (command) {
case RELAY_COMMAND_EXTEND:
{
- if (payload_length != 6 + TAP_ONIONSKIN_CHALLENGE_LEN + DIGEST_LEN)
+ extend1_cell_body_t *cell = NULL;
+ if (extend1_cell_body_parse(&cell, payload, payload_length)<0 ||
+ cell == NULL) {
+ if (cell)
+ extend1_cell_body_free(cell);
return -1;
-
- cell_out->cell_type = RELAY_COMMAND_EXTEND;
- tor_addr_from_ipv4n(&cell_out->orport_ipv4.addr, get_uint32(payload));
- cell_out->orport_ipv4.port = ntohs(get_uint16(payload+4));
- tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
- if (tor_memeq(payload + 6, NTOR_CREATE_MAGIC, 16)) {
- cell_out->create_cell.cell_type = CELL_CREATE2;
- cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_NTOR;
- cell_out->create_cell.handshake_len = NTOR_ONIONSKIN_LEN;
- memcpy(cell_out->create_cell.onionskin, payload + 22,
- NTOR_ONIONSKIN_LEN);
- } else {
- cell_out->create_cell.cell_type = CELL_CREATE;
- cell_out->create_cell.handshake_type = ONION_HANDSHAKE_TYPE_TAP;
- cell_out->create_cell.handshake_len = TAP_ONIONSKIN_CHALLENGE_LEN;
- memcpy(cell_out->create_cell.onionskin, payload + 6,
- TAP_ONIONSKIN_CHALLENGE_LEN);
}
- memcpy(cell_out->node_id, payload + 6 + TAP_ONIONSKIN_CHALLENGE_LEN,
- DIGEST_LEN);
- break;
+ int r = extend_cell_from_extend1_cell_body(cell_out, cell);
+ extend1_cell_body_free(cell);
+ if (r < 0)
+ return r;
}
+ break;
case RELAY_COMMAND_EXTEND2:
{
- uint8_t n_specs, spectype, speclen;
- int i;
- int found_ipv4 = 0, found_ipv6 = 0, found_id = 0;
- tor_addr_make_unspec(&cell_out->orport_ipv4.addr);
- tor_addr_make_unspec(&cell_out->orport_ipv6.addr);
-
- if (payload_length == 0)
+ extend2_cell_body_t *cell = NULL;
+ if (extend2_cell_body_parse(&cell, payload, payload_length) < 0 ||
+ cell == NULL) {
+ if (cell)
+ extend2_cell_body_free(cell);
return -1;
-
- cell_out->cell_type = RELAY_COMMAND_EXTEND2;
- n_specs = *payload++;
- /* Parse the specifiers. We'll only take the first IPv4 and first IPv6
- * address, and the node ID, and ignore everything else */
- for (i = 0; i < n_specs; ++i) {
- if (eop - payload < 2)
- return -1;
- spectype = payload[0];
- speclen = payload[1];
- payload += 2;
- if (eop - payload < speclen)
- return -1;
- switch (spectype) {
- case SPECTYPE_IPV4:
- if (speclen != 6)
- return -1;
- if (!found_ipv4) {
- tor_addr_from_ipv4n(&cell_out->orport_ipv4.addr,
- get_uint32(payload));
- cell_out->orport_ipv4.port = ntohs(get_uint16(payload+4));
- found_ipv4 = 1;
- }
- break;
- case SPECTYPE_IPV6:
- if (speclen != 18)
- return -1;
- if (!found_ipv6) {
- tor_addr_from_ipv6_bytes(&cell_out->orport_ipv6.addr,
- (const char*)payload);
- cell_out->orport_ipv6.port = ntohs(get_uint16(payload+16));
- found_ipv6 = 1;
- }
- break;
- case SPECTYPE_LEGACY_ID:
- if (speclen != 20)
- return -1;
- if (found_id)
- return -1;
- memcpy(cell_out->node_id, payload, 20);
- found_id = 1;
- break;
- }
- payload += speclen;
}
- if (!found_id || !found_ipv4)
- return -1;
- if (parse_create2_payload(&cell_out->create_cell,payload,eop-payload)<0)
- return -1;
- break;
+ int r = extend_cell_from_extend2_cell_body(cell_out, cell);
+ extend2_cell_body_free(cell);
+ if (r < 0)
+ return r;
}
+ break;
default:
return -1;
}
@@ -994,6 +1041,7 @@ extend_cell_parse(extend_cell_t *cell_out, const uint8_t command,
static int
check_extended_cell(const extended_cell_t *cell)
{
+ tor_assert(cell);
if (cell->created_cell.cell_type == CELL_CREATED) {
if (cell->cell_type != RELAY_COMMAND_EXTENDED)
return -1;
@@ -1015,6 +1063,9 @@ extended_cell_parse(extended_cell_t *cell_out,
const uint8_t command, const uint8_t *payload,
size_t payload_len)
{
+ tor_assert(cell_out);
+ tor_assert(payload);
+
memset(cell_out, 0, sizeof(*cell_out));
if (payload_len > RELAY_PAYLOAD_SIZE)
return -1;
@@ -1131,6 +1182,21 @@ created_cell_format(cell_t *cell_out, const created_cell_t *cell_in)
return 0;
}
+/** Return true iff we are configured (by torrc or by the networkstatus
+ * parameters) to use Ed25519 identities in our Extend2 cells. */
+static int
+should_include_ed25519_id_extend_cells(const networkstatus_t *ns,
+ const or_options_t *options)
+{
+ if (options->ExtendByEd25519ID != -1)
+ return options->ExtendByEd25519ID; /* The user has an opinion. */
+
+ return (int) networkstatus_get_param(ns, "ExtendByEd25519ID",
+ 0 /* default */,
+ 0 /* min */,
+ 1 /*max*/);
+}
+
/** Format the EXTEND{,2} cell in <b>cell_in</b>, storing its relay payload in
* <b>payload_out</b>, the number of bytes used in *<b>len_out</b>, and the
* relay command in *<b>command_out</b>. The <b>payload_out</b> must have
@@ -1139,12 +1205,11 @@ int
extend_cell_format(uint8_t *command_out, uint16_t *len_out,
uint8_t *payload_out, const extend_cell_t *cell_in)
{
- uint8_t *p, *eop;
+ uint8_t *p;
if (check_extend_cell(cell_in) < 0)
return -1;
p = payload_out;
- eop = payload_out + RELAY_PAYLOAD_SIZE;
memset(p, 0, RELAY_PAYLOAD_SIZE);
@@ -1167,33 +1232,56 @@ extend_cell_format(uint8_t *command_out, uint16_t *len_out,
break;
case RELAY_COMMAND_EXTEND2:
{
- uint8_t n = 2;
+ uint8_t n_specifiers = 2;
*command_out = RELAY_COMMAND_EXTEND2;
-
- *p++ = n; /* 2 identifiers */
- *p++ = SPECTYPE_IPV4; /* First is IPV4. */
- *p++ = 6; /* It's 6 bytes long. */
- set_uint32(p, tor_addr_to_ipv4n(&cell_in->orport_ipv4.addr));
- set_uint16(p+4, htons(cell_in->orport_ipv4.port));
- p += 6;
- *p++ = SPECTYPE_LEGACY_ID; /* Next is an identity digest. */
- *p++ = 20; /* It's 20 bytes long */
- memcpy(p, cell_in->node_id, DIGEST_LEN);
- p += 20;
-
- /* Now we can send the handshake */
- set_uint16(p, htons(cell_in->create_cell.handshake_type));
- set_uint16(p+2, htons(cell_in->create_cell.handshake_len));
- p += 4;
-
- if (cell_in->create_cell.handshake_len > eop - p)
- return -1;
-
- memcpy(p, cell_in->create_cell.onionskin,
+ extend2_cell_body_t *cell = extend2_cell_body_new();
+ link_specifier_t *ls;
+ {
+ /* IPv4 specifier first. */
+ ls = link_specifier_new();
+ extend2_cell_body_add_ls(cell, ls);
+ ls->ls_type = LS_IPV4;
+ ls->ls_len = 6;
+ ls->un_ipv4_addr = tor_addr_to_ipv4h(&cell_in->orport_ipv4.addr);
+ ls->un_ipv4_port = cell_in->orport_ipv4.port;
+ }
+ {
+ /* Then RSA id */
+ ls = link_specifier_new();
+ extend2_cell_body_add_ls(cell, ls);
+ ls->ls_type = LS_LEGACY_ID;
+ ls->ls_len = DIGEST_LEN;
+ memcpy(ls->un_legacy_id, cell_in->node_id, DIGEST_LEN);
+ }
+ if (should_include_ed25519_id_extend_cells(NULL, get_options()) &&
+ !ed25519_public_key_is_zero(&cell_in->ed_pubkey)) {
+ /* Then, maybe, the ed25519 id! */
+ ++n_specifiers;
+ ls = link_specifier_new();
+ extend2_cell_body_add_ls(cell, ls);
+ ls->ls_type = LS_ED25519_ID;
+ ls->ls_len = 32;
+ memcpy(ls->un_ed25519_id, cell_in->ed_pubkey.pubkey, 32);
+ }
+ cell->n_spec = n_specifiers;
+
+ /* Now, the handshake */
+ cell->create2 = create2_cell_body_new();
+ cell->create2->handshake_type = cell_in->create_cell.handshake_type;
+ cell->create2->handshake_len = cell_in->create_cell.handshake_len;
+ create2_cell_body_setlen_handshake_data(cell->create2,
+ cell_in->create_cell.handshake_len);
+ memcpy(create2_cell_body_getarray_handshake_data(cell->create2),
+ cell_in->create_cell.onionskin,
cell_in->create_cell.handshake_len);
- p += cell_in->create_cell.handshake_len;
- *len_out = p - payload_out;
+ ssize_t len_encoded = extend2_cell_body_encode(
+ payload_out, RELAY_PAYLOAD_SIZE,
+ cell);
+ extend2_cell_body_free(cell);
+ if (len_encoded < 0 || len_encoded > UINT16_MAX)
+ return -1;
+ *len_out = (uint16_t) len_encoded;
}
break;
default:
diff --git a/src/or/onion.h b/src/or/onion.h
index 0275fa00d2..19e4a7c381 100644
--- a/src/or/onion.h
+++ b/src/or/onion.h
@@ -85,6 +85,8 @@ typedef struct extend_cell_t {
tor_addr_port_t orport_ipv6;
/** Identity fingerprint of the node we're conecting to.*/
uint8_t node_id[DIGEST_LEN];
+ /** Ed25519 public identity key. Zero if not set. */
+ ed25519_public_key_t ed_pubkey;
/** The "create cell" embedded in this extend cell. Note that unlike the
* create cells we generate ourself, this once can have a handshake type we
* don't recognize. */
diff --git a/src/or/or.h b/src/or/or.h
index 75a02a531e..2b56a486d8 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -80,6 +80,7 @@
#include "crypto_ed25519.h"
#include "tor_queue.h"
#include "util_format.h"
+#include "hs_circuitmap.h"
/* These signals are defined to help handle_control_signal work.
*/
@@ -114,6 +115,9 @@
#define NON_ANONYMOUS_MODE_ENABLED 1
#endif
+/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */
+#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_))
+
/** Length of longest allowable configured nickname. */
#define MAX_NICKNAME_LEN 19
/** Length of a router identity encoded as a hexadecimal digest, plus
@@ -423,8 +427,12 @@ typedef enum {
/** Circuit state: I'd like to deliver a create, but my n_chan is still
* connecting. */
#define CIRCUIT_STATE_CHAN_WAIT 2
+/** Circuit state: the circuit is open but we don't want to actually use it
+ * until we find out if a better guard will be available.
+ */
+#define CIRCUIT_STATE_GUARD_WAIT 3
/** Circuit state: onionskin(s) processed, ready to send/receive cells. */
-#define CIRCUIT_STATE_OPEN 3
+#define CIRCUIT_STATE_OPEN 4
#define CIRCUIT_PURPOSE_MIN_ 1
@@ -767,6 +775,24 @@ typedef struct rend_service_authorization_t {
* establishment. Not all fields contain data depending on where this struct
* is used. */
typedef struct rend_data_t {
+ /* Hidden service protocol version of this base object. */
+ uint32_t version;
+
+ /** List of HSDir fingerprints on which this request has been sent to. This
+ * contains binary identity digest of the directory of size DIGEST_LEN. */
+ smartlist_t *hsdirs_fp;
+
+ /** Rendezvous cookie used by both, client and service. */
+ char rend_cookie[REND_COOKIE_LEN];
+
+ /** Number of streams associated with this rendezvous circuit. */
+ int nr_streams;
+} rend_data_t;
+
+typedef struct rend_data_v2_t {
+ /* Rendezvous base data. */
+ rend_data_t base_;
+
/** Onion address (without the .onion part) that a client requests. */
char onion_address[REND_SERVICE_ID_LEN_BASE32+1];
@@ -788,17 +814,16 @@ typedef struct rend_data_t {
/** Hash of the hidden service's PK used by a service. */
char rend_pk_digest[DIGEST_LEN];
+} rend_data_v2_t;
- /** Rendezvous cookie used by both, client and service. */
- char rend_cookie[REND_COOKIE_LEN];
-
- /** List of HSDir fingerprints on which this request has been sent to.
- * This contains binary identity digest of the directory. */
- smartlist_t *hsdirs_fp;
-
- /** Number of streams associated with this rendezvous circuit. */
- int nr_streams;
-} rend_data_t;
+/* From a base rend_data_t object <b>d</d>, return the v2 object. */
+static inline
+rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d)
+{
+ tor_assert(d);
+ tor_assert(d->version == 2);
+ return DOWNCAST(rend_data_v2_t, d);
+}
/** Time interval for tracking replays of DH public keys received in
* INTRODUCE2 cells. Used only to avoid launching multiple
@@ -1351,13 +1376,30 @@ typedef struct listener_connection_t {
#define OR_CERT_TYPE_RSA_ED_CROSSCERT 7
/**@}*/
-/** The one currently supported type of AUTHENTICATE cell. It contains
+/** The first supported type of AUTHENTICATE cell. It contains
* a bunch of structures signed with an RSA1024 key. The signed
* structures include a HMAC using negotiated TLS secrets, and a digest
* of all cells sent or received before the AUTHENTICATE cell (including
* the random server-generated AUTH_CHALLENGE cell).
*/
#define AUTHTYPE_RSA_SHA256_TLSSECRET 1
+/** As AUTHTYPE_RSA_SHA256_TLSSECRET, but instead of using the
+ * negotiated TLS secrets, uses exported keying material from the TLS
+ * 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.
+ **/
+#define AUTHTYPE_RSA_SHA256_RFC5705 2
+/** As AUTHTYPE_RSA_SHA256_RFC5705, but uses an Ed25519 identity key to
+ * authenticate. */
+#define AUTHTYPE_ED25519_SHA256_RFC5705 3
+/*
+ * NOTE: authchallenge_type_is_better() relies on these AUTHTYPE codes
+ * being sorted in order of preference. If we someday add one with
+ * a higher numerical value that we don't like as much, we should revise
+ * authchallenge_type_is_better().
+ */
/** The length of the part of the AUTHENTICATE cell body that the client and
* server can generate independently (when using RSA_SHA256_TLSSECRET). It
@@ -1368,6 +1410,34 @@ typedef struct listener_connection_t {
* signs. */
#define V3_AUTH_BODY_LEN (V3_AUTH_FIXED_PART_LEN + 8 + 16)
+/** Structure to hold all the certificates we've received on an OR connection
+ */
+typedef struct or_handshake_certs_t {
+ /** True iff we originated this connection. */
+ int started_here;
+ /** The cert for the 'auth' RSA key that's supposed to sign the AUTHENTICATE
+ * cell. Signed with the RSA identity key. */
+ tor_x509_cert_t *auth_cert;
+ /** The cert for the 'link' RSA key that was used to negotiate the TLS
+ * connection. Signed with the RSA identity key. */
+ tor_x509_cert_t *link_cert;
+ /** A self-signed identity certificate: the RSA identity key signed
+ * with itself. */
+ tor_x509_cert_t *id_cert;
+ /** The Ed25519 signing key, signed with the Ed25519 identity key. */
+ struct tor_cert_st *ed_id_sign;
+ /** A digest of the X509 link certificate for the TLS connection, signed
+ * with the Ed25519 siging key. */
+ struct tor_cert_st *ed_sign_link;
+ /** The Ed25519 authentication key (that's supposed to sign an AUTHENTICATE
+ * cell) , signed with the Ed25519 siging key. */
+ struct tor_cert_st *ed_sign_auth;
+ /** The Ed25519 identity key, crosssigned with the RSA identity key. */
+ uint8_t *ed_rsa_crosscert;
+ /** The length of <b>ed_rsa_crosscert</b> in bytes */
+ size_t ed_rsa_crosscert_len;
+} or_handshake_certs_t;
+
/** Stores flags and information related to the portion of a v2/v3 Tor OR
* connection handshake that happens after the TLS handshake is finished.
*/
@@ -1388,10 +1458,18 @@ typedef struct or_handshake_state_t {
/* True iff we've received valid authentication to some identity. */
unsigned int authenticated : 1;
+ unsigned int authenticated_rsa : 1;
+ unsigned int authenticated_ed25519 : 1;
/* True iff we have sent a netinfo cell */
unsigned int sent_netinfo : 1;
+ /** The signing->ed25519 link certificate corresponding to the x509
+ * certificate we used on the TLS connection (if this is a server-side
+ * connection). We make a copy of this here to prevent a race condition
+ * caused by TLS context rotation. */
+ struct tor_cert_st *own_link_cert;
+
/** True iff we should feed outgoing cells into digest_sent and
* digest_received respectively.
*
@@ -1405,9 +1483,12 @@ typedef struct or_handshake_state_t {
unsigned int digest_received_data : 1;
/**@}*/
- /** Identity digest that we have received and authenticated for our peer
+ /** Identity RSA digest that we have received and authenticated for our peer
* on this connection. */
- uint8_t authenticated_peer_id[DIGEST_LEN];
+ uint8_t authenticated_rsa_peer_id[DIGEST_LEN];
+ /** Identity Ed25519 public key that we have received and authenticated for
+ * our peer on this connection. */
+ ed25519_public_key_t authenticated_ed25519_peer_id;
/** Digests of the cells that we have sent or received as part of a V3
* handshake. Used for making and checking AUTHENTICATE cells.
@@ -1420,14 +1501,8 @@ typedef struct or_handshake_state_t {
/** Certificates that a connection initiator sent us in a CERTS cell; we're
* holding on to them until we get an AUTHENTICATE cell.
- *
- * @{
*/
- /** The cert for the key that's supposed to sign the AUTHENTICATE cell */
- tor_x509_cert_t *auth_cert;
- /** A self-signed identity certificate */
- tor_x509_cert_t *id_cert;
- /**@}*/
+ or_handshake_certs_t *certs;
} or_handshake_state_t;
/** Length of Extended ORPort connection identifier. */
@@ -1520,8 +1595,6 @@ typedef struct or_connection_t {
* bandwidthburst. (OPEN ORs only) */
int write_bucket; /**< When this hits 0, stop writing. Like read_bucket. */
- struct or_connection_t *next_with_same_id; /**< Next connection with same
- * identity digest as this one. */
/** Last emptied read token bucket in msec since midnight; only used if
* TB_EMPTY events are enabled. */
uint32_t read_emptied_time;
@@ -1599,6 +1672,8 @@ typedef struct entry_connection_t {
edge_connection_t edge_;
/** Nickname of planned exit node -- used with .exit support. */
+ /* XXX prop220: we need to make chosen_exit_name able to encode Ed IDs too.
+ * That's logically part of the UI parts for prop220 though. */
char *chosen_exit_name;
socks_request_t *socks_request; /**< SOCKS structure describing request (AP
@@ -1694,7 +1769,7 @@ typedef struct dir_connection_t {
/** Which 'resource' did we ask the directory for? This is typically the part
* of the URL string that defines, relative to the directory conn purpose,
* what thing we want. For example, in router descriptor downloads by
- * descriptor digest, it contains "d/", then one ore more +-separated
+ * descriptor digest, it contains "d/", then one or more +-separated
* fingerprints.
**/
char *requested_resource;
@@ -1721,6 +1796,10 @@ typedef struct dir_connection_t {
/** What rendezvous service are we querying for? */
rend_data_t *rend_data;
+ /** 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;
+
char identity_digest[DIGEST_LEN]; /**< Hash of the public RSA key for
* the directory server's signing key. */
@@ -1764,8 +1843,6 @@ typedef struct control_connection_t {
/** Cast a connection_t subtype pointer to a connection_t **/
#define TO_CONN(c) (&(((c)->base_)))
-/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */
-#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_))
/** Cast a entry_connection_t subtype pointer to a edge_connection_t **/
#define ENTRY_TO_EDGE_CONN(c) (&(((c))->edge_))
@@ -2206,6 +2283,10 @@ typedef struct routerstatus_t {
* accept EXTEND2 cells */
unsigned int supports_extend2_cells:1;
+ /** True iff this router has a protocol list that allows it to negotiate
+ * ed25519 identity keys on a link handshake. */
+ unsigned int supports_ed25519_link_handshake: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
@@ -2368,9 +2449,6 @@ typedef struct node_t {
/** Local info: we treat this node as if it rejects everything */
unsigned int rejects_all:1;
- /** Local info: this node is in our list of guards */
- unsigned int using_as_guard:1;
-
/* Local info: derived. */
/** True if the IPv6 OR port is preferred over the IPv4 OR port.
@@ -2650,7 +2728,10 @@ typedef struct {
typedef struct extend_info_t {
char nickname[MAX_HEX_NICKNAME_LEN+1]; /**< This router's nickname for
* display. */
- char identity_digest[DIGEST_LEN]; /**< Hash of this router's identity key. */
+ /** Hash of this router's RSA identity key. */
+ char identity_digest[DIGEST_LEN];
+ /** Ed25519 identity for this router, if any. */
+ ed25519_public_key_t ed_identity;
uint16_t port; /**< OR port. */
tor_addr_t addr; /**< IP address. */
crypto_pk_t *onion_key; /**< Current onionskin key. */
@@ -3088,6 +3169,15 @@ typedef struct origin_circuit_t {
/** Holds all rendezvous data on either client or service side. */
rend_data_t *rend_data;
+ /** 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. */
+ struct circuit_guard_state_t *guard_state;
+
+ /** Index into global_origin_circuit_list for this circuit. -1 if not
+ * present. */
+ int global_origin_circuit_list_idx;
+
/** How many more relay_early cells can we send on this circuit, according
* to the specification? */
unsigned int remaining_relay_early_cells : 4;
@@ -3292,7 +3382,12 @@ typedef struct or_circuit_t {
* is not marked for close. */
struct or_circuit_t *rend_splice;
- struct or_circuit_rendinfo_s *rendinfo;
+ /** If set, points to an HS token that this circuit might be carrying.
+ * Used by the HS circuitmap. */
+ hs_token_t *hs_token;
+ /** Hashtable node: used to look up the circuit by its HS token using the HS
+ circuitmap. */
+ HT_ENTRY(or_circuit_t) hs_circuitmap_node;
/** Stores KH for the handshake. */
char rend_circ_nonce[DIGEST_LEN];/* KH in tor-spec.txt */
@@ -3327,25 +3422,11 @@ typedef struct or_circuit_t {
uint32_t max_middle_cells;
} or_circuit_t;
-typedef struct or_circuit_rendinfo_s {
-
#if REND_COOKIE_LEN != DIGEST_LEN
#error "The REND_TOKEN_LEN macro assumes REND_COOKIE_LEN == DIGEST_LEN"
#endif
#define REND_TOKEN_LEN DIGEST_LEN
- /** A hash of location-hidden service's PK if purpose is INTRO_POINT, or a
- * rendezvous cookie if purpose is REND_POINT_WAITING. Filled with zeroes
- * otherwise.
- */
- char rend_token[REND_TOKEN_LEN];
-
- /** True if this is a rendezvous point circuit; false if this is an
- * introduction point. */
- unsigned is_rend_circ;
-
-} or_circuit_rendinfo_t;
-
/** Convert a circuit subtype to a circuit_t. */
#define TO_CIRCUIT(x) (&((x)->base_))
@@ -3485,6 +3566,12 @@ typedef struct routerset_t routerset_t;
* to pick its own port. */
#define CFG_AUTO_PORT 0xc4005e
+/** Enumeration of outbound address configuration types:
+ * Exit-only, OR-only, or both */
+typedef enum {OUTBOUND_ADDR_EXIT, OUTBOUND_ADDR_OR,
+ OUTBOUND_ADDR_EXIT_AND_OR,
+ OUTBOUND_ADDR_MAX} outbound_addr_t;
+
/** Configuration options for a Tor process. */
typedef struct {
uint32_t magic_;
@@ -3568,10 +3655,14 @@ typedef struct {
config_line_t *ControlListenAddress;
/** Local address to bind outbound sockets */
config_line_t *OutboundBindAddress;
- /** IPv4 address derived from OutboundBindAddress. */
- tor_addr_t OutboundBindAddressIPv4_;
- /** IPv6 address derived from OutboundBindAddress. */
- tor_addr_t OutboundBindAddressIPv6_;
+ /** Local address to bind outbound relay sockets */
+ config_line_t *OutboundBindAddressOR;
+ /** Local address to bind outbound exit sockets */
+ config_line_t *OutboundBindAddressExit;
+ /** Addresses derived from the various OutboundBindAddress lines.
+ * [][0] is IPv4, [][1] is IPv6
+ */
+ tor_addr_t OutboundBindAddresses[OUTBOUND_ADDR_MAX][2];
/** Directory server only: which versions of
* Tor should we tell users to run? */
config_line_t *RecommendedVersions;
@@ -3820,7 +3911,7 @@ typedef struct {
uint64_t BandwidthBurst; /**< How much bandwidth, at maximum, are we willing
* to use in a second? */
uint64_t MaxAdvertisedBandwidth; /**< How much bandwidth are we willing to
- * tell people we have? */
+ * tell other nodes we have? */
uint64_t RelayBandwidthRate; /**< How much bandwidth, on average, are we
* willing to use for all relayed conns? */
uint64_t RelayBandwidthBurst; /**< How much bandwidth, at maximum, will we
@@ -3906,9 +3997,6 @@ typedef struct {
* and vote for all other exits as good. */
int AuthDirMaxServersPerAddr; /**< Do not permit more than this
* number of servers per IP address. */
- int AuthDirMaxServersPerAuthAddr; /**< Do not permit more than this
- * number of servers per IP address shared
- * with an authority. */
int AuthDirHasIPv6Connectivity; /**< Boolean: are we on IPv6? */
int AuthDirPinKeys; /**< Boolean: Do we enforce key-pinning? */
@@ -3993,8 +4081,6 @@ typedef struct {
int UseEntryGuards;
int NumEntryGuards; /**< How many entry guards do we try to establish? */
- int UseEntryGuardsAsDirGuards; /** Boolean: Do we try to get directory info
- * from a smallish number of fixed nodes? */
/** If 1, we use any guardfraction information we see in the
* consensus. If 0, we don't. If -1, let the consensus parameter
@@ -4103,6 +4189,10 @@ typedef struct {
/** If true, the user wants us to collect statistics as hidden service
* directory, introduction point, or rendezvous point. */
+ int HiddenServiceStatistics_option;
+ /** Internal variable to remember whether we're actually acting on
+ * HiddenServiceStatistics_option -- yes if it's set and we're a server,
+ * else no. */
int HiddenServiceStatistics;
/** If true, include statistics file contents in extra-info documents. */
@@ -4466,7 +4556,7 @@ typedef struct {
* XXXX Eventually, the default will be 0. */
int ExitRelay;
- /** For how long (seconds) do we declare our singning keys to be valid? */
+ /** For how long (seconds) do we declare our signing keys to be valid? */
int SigningKeyLifetime;
/** For how long (seconds) do we declare our link keys to be valid? */
int TestingLinkCertLifetime;
@@ -4510,6 +4600,15 @@ typedef struct {
/** If 1, we skip all OOS checks. */
int DisableOOSCheck;
+
+ /** Autobool: Should we include Ed25519 identities in extend2 cells?
+ * If -1, we should do whatever the consensus parameter says. */
+ int ExtendByEd25519ID;
+
+ /** Bool (default: 1): When testing routerinfos as a directory authority,
+ * do we enforce Ed25519 identity match? */
+ /* NOTE: remove this option someday. */
+ int AuthDirTestEd25519LinkKeys;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */
@@ -4533,9 +4632,12 @@ typedef struct {
uint64_t AccountingBytesAtSoftLimit;
uint64_t AccountingExpectedUsage;
- /** A list of Entry Guard-related configuration lines. */
+ /** A list of Entry Guard-related configuration lines. (pre-prop271) */
config_line_t *EntryGuards;
+ /** A list of guard-related configuration lines. (post-prop271) */
+ config_line_t *Guard;
+
config_line_t *TransportProxies;
/** These fields hold information on the history of bandwidth usage for
@@ -5261,10 +5363,6 @@ typedef struct dir_server_t {
*/
#define PDS_NO_EXISTING_MICRODESC_FETCH (1<<4)
-/** This node is to be chosen as a directory guard, so don't choose any
- * node that's currently a guard. */
-#define PDS_FOR_GUARD (1<<5)
-
/** Possible ways to weight routers when choosing one randomly. See
* routerlist_sl_choose_by_bandwidth() for more information.*/
typedef enum bandwidth_weight_rule_t {
diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c
new file mode 100644
index 0000000000..ec2cec69f7
--- /dev/null
+++ b/src/or/parsecommon.c
@@ -0,0 +1,450 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file parsecommon.c
+ * \brief Common code to parse and validate various type of descriptors.
+ **/
+
+#include "parsecommon.h"
+#include "torlog.h"
+#include "util_format.h"
+
+#define MIN_ANNOTATION A_PURPOSE
+#define MAX_ANNOTATION A_UNKNOWN_
+
+#define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz)
+#define ALLOC(sz) memarea_alloc(area,sz)
+#define STRDUP(str) memarea_strdup(area,str)
+#define STRNDUP(str,n) memarea_strndup(area,(str),(n))
+
+#define RET_ERR(msg) \
+ STMT_BEGIN \
+ if (tok) token_clear(tok); \
+ tok = ALLOC_ZERO(sizeof(directory_token_t)); \
+ tok->tp = ERR_; \
+ tok->error = STRDUP(msg); \
+ goto done_tokenizing; \
+ STMT_END
+
+/** Free all resources allocated for <b>tok</b> */
+void
+token_clear(directory_token_t *tok)
+{
+ if (tok->key)
+ crypto_pk_free(tok->key);
+}
+
+/** Read all tokens from a string between <b>start</b> and <b>end</b>, and add
+ * them to <b>out</b>. Parse according to the token rules in <b>table</b>.
+ * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the
+ * entire string.
+ */
+int
+tokenize_string(memarea_t *area,
+ const char *start, const char *end, smartlist_t *out,
+ token_rule_t *table, int flags)
+{
+ const char **s;
+ directory_token_t *tok = NULL;
+ int counts[NIL_];
+ int i;
+ int first_nonannotation;
+ int prev_len = smartlist_len(out);
+ tor_assert(area);
+
+ s = &start;
+ if (!end) {
+ end = start+strlen(start);
+ } else {
+ /* it's only meaningful to check for nuls if we got an end-of-string ptr */
+ if (memchr(start, '\0', end-start)) {
+ log_warn(LD_DIR, "parse error: internal NUL character.");
+ return -1;
+ }
+ }
+ for (i = 0; i < NIL_; ++i)
+ counts[i] = 0;
+
+ SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]);
+
+ while (*s < end && (!tok || tok->tp != EOF_)) {
+ tok = get_next_token(area, s, end, table);
+ if (tok->tp == ERR_) {
+ log_warn(LD_DIR, "parse error: %s", tok->error);
+ token_clear(tok);
+ return -1;
+ }
+ ++counts[tok->tp];
+ smartlist_add(out, tok);
+ *s = eat_whitespace_eos(*s, end);
+ }
+
+ if (flags & TS_NOCHECK)
+ return 0;
+
+ if ((flags & TS_ANNOTATIONS_OK)) {
+ first_nonannotation = -1;
+ for (i = 0; i < smartlist_len(out); ++i) {
+ tok = smartlist_get(out, i);
+ if (tok->tp < MIN_ANNOTATION || tok->tp > MAX_ANNOTATION) {
+ first_nonannotation = i;
+ break;
+ }
+ }
+ if (first_nonannotation < 0) {
+ log_warn(LD_DIR, "parse error: item contains only annotations");
+ return -1;
+ }
+ for (i=first_nonannotation; i < smartlist_len(out); ++i) {
+ tok = smartlist_get(out, i);
+ if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
+ log_warn(LD_DIR, "parse error: Annotations mixed with keywords");
+ return -1;
+ }
+ }
+ if ((flags & TS_NO_NEW_ANNOTATIONS)) {
+ if (first_nonannotation != prev_len) {
+ log_warn(LD_DIR, "parse error: Unexpected annotations.");
+ return -1;
+ }
+ }
+ } else {
+ for (i=0; i < smartlist_len(out); ++i) {
+ tok = smartlist_get(out, i);
+ if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
+ log_warn(LD_DIR, "parse error: no annotations allowed.");
+ return -1;
+ }
+ }
+ first_nonannotation = 0;
+ }
+ for (i = 0; table[i].t; ++i) {
+ if (counts[table[i].v] < table[i].min_cnt) {
+ log_warn(LD_DIR, "Parse error: missing %s element.", table[i].t);
+ return -1;
+ }
+ if (counts[table[i].v] > table[i].max_cnt) {
+ log_warn(LD_DIR, "Parse error: too many %s elements.", table[i].t);
+ return -1;
+ }
+ if (table[i].pos & AT_START) {
+ if (smartlist_len(out) < 1 ||
+ (tok = smartlist_get(out, first_nonannotation))->tp != table[i].v) {
+ log_warn(LD_DIR, "Parse error: first item is not %s.", table[i].t);
+ return -1;
+ }
+ }
+ if (table[i].pos & AT_END) {
+ if (smartlist_len(out) < 1 ||
+ (tok = smartlist_get(out, smartlist_len(out)-1))->tp != table[i].v) {
+ log_warn(LD_DIR, "Parse error: last item is not %s.", table[i].t);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+/** Helper: parse space-separated arguments from the string <b>s</b> ending at
+ * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the
+ * number of parsed elements into the n_args field of <b>tok</b>. Allocate
+ * all storage in <b>area</b>. Return the number of arguments parsed, or
+ * return -1 if there was an insanely high number of arguments. */
+static inline int
+get_token_arguments(memarea_t *area, directory_token_t *tok,
+ const char *s, const char *eol)
+{
+/** Largest number of arguments we'll accept to any token, ever. */
+#define MAX_ARGS 512
+ char *mem = memarea_strndup(area, s, eol-s);
+ char *cp = mem;
+ int j = 0;
+ char *args[MAX_ARGS];
+ while (*cp) {
+ if (j == MAX_ARGS)
+ return -1;
+ args[j++] = cp;
+ cp = (char*)find_whitespace(cp);
+ if (!cp || !*cp)
+ break; /* End of the line. */
+ *cp++ = '\0';
+ cp = (char*)eat_whitespace(cp);
+ }
+ tok->n_args = j;
+ tok->args = memarea_memdup(area, args, j*sizeof(char*));
+ return j;
+#undef MAX_ARGS
+}
+
+/** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys
+ * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>.
+ * Return <b>tok</b> on success, or a new ERR_ token if the token didn't
+ * conform to the syntax we wanted.
+ **/
+static inline directory_token_t *
+token_check_object(memarea_t *area, const char *kwd,
+ directory_token_t *tok, obj_syntax o_syn)
+{
+ char ebuf[128];
+ switch (o_syn) {
+ case NO_OBJ:
+ /* No object is allowed for this token. */
+ if (tok->object_body) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Unexpected object for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ if (tok->key) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Unexpected public key for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ break;
+ case NEED_OBJ:
+ /* There must be a (non-key) object. */
+ if (!tok->object_body) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Missing object for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ break;
+ case NEED_KEY_1024: /* There must be a 1024-bit public key. */
+ case NEED_SKEY_1024: /* There must be a 1024-bit private key. */
+ if (tok->key && crypto_pk_num_bits(tok->key) != PK_BYTES*8) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits",
+ kwd, crypto_pk_num_bits(tok->key));
+ RET_ERR(ebuf);
+ }
+ /* fall through */
+ case NEED_KEY: /* There must be some kind of key. */
+ if (!tok->key) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd);
+ RET_ERR(ebuf);
+ }
+ if (o_syn != NEED_SKEY_1024) {
+ if (crypto_pk_key_is_private(tok->key)) {
+ tor_snprintf(ebuf, sizeof(ebuf),
+ "Private key given for %s, which wants a public key", kwd);
+ RET_ERR(ebuf);
+ }
+ } else { /* o_syn == NEED_SKEY_1024 */
+ if (!crypto_pk_key_is_private(tok->key)) {
+ tor_snprintf(ebuf, sizeof(ebuf),
+ "Public key given for %s, which wants a private key", kwd);
+ RET_ERR(ebuf);
+ }
+ }
+ break;
+ case OBJ_OK:
+ /* Anything goes with this token. */
+ break;
+ }
+
+ done_tokenizing:
+ return tok;
+}
+
+/** Helper function: read the next token from *s, advance *s to the end of the
+ * token, and return the parsed token. Parse *<b>s</b> according to the list
+ * of tokens in <b>table</b>.
+ */
+directory_token_t *
+get_next_token(memarea_t *area,
+ const char **s, const char *eos, token_rule_t *table)
+{
+ /** Reject any object at least this big; it is probably an overflow, an
+ * attack, a bug, or some other nonsense. */
+#define MAX_UNPARSED_OBJECT_SIZE (128*1024)
+ /** Reject any line at least this big; it is probably an overflow, an
+ * attack, a bug, or some other nonsense. */
+#define MAX_LINE_LENGTH (128*1024)
+
+ const char *next, *eol, *obstart;
+ size_t obname_len;
+ int i;
+ directory_token_t *tok;
+ obj_syntax o_syn = NO_OBJ;
+ char ebuf[128];
+ const char *kwd = "";
+
+ tor_assert(area);
+ tok = ALLOC_ZERO(sizeof(directory_token_t));
+ tok->tp = ERR_;
+
+ /* Set *s to first token, eol to end-of-line, next to after first token */
+ *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */
+ tor_assert(eos >= *s);
+ eol = memchr(*s, '\n', eos-*s);
+ if (!eol)
+ eol = eos;
+ if (eol - *s > MAX_LINE_LENGTH) {
+ RET_ERR("Line far too long");
+ }
+
+ next = find_whitespace_eos(*s, eol);
+
+ if (!strcmp_len(*s, "opt", next-*s)) {
+ /* Skip past an "opt" at the start of the line. */
+ *s = eat_whitespace_eos_no_nl(next, eol);
+ next = find_whitespace_eos(*s, eol);
+ } else if (*s == eos) { /* If no "opt", and end-of-line, line is invalid */
+ RET_ERR("Unexpected EOF");
+ }
+
+ /* Search the table for the appropriate entry. (I tried a binary search
+ * instead, but it wasn't any faster.) */
+ for (i = 0; table[i].t ; ++i) {
+ if (!strcmp_len(*s, table[i].t, next-*s)) {
+ /* We've found the keyword. */
+ kwd = table[i].t;
+ tok->tp = table[i].v;
+ o_syn = table[i].os;
+ *s = eat_whitespace_eos_no_nl(next, eol);
+ /* We go ahead whether there are arguments or not, so that tok->args is
+ * always set if we want arguments. */
+ if (table[i].concat_args) {
+ /* The keyword takes the line as a single argument */
+ tok->args = ALLOC(sizeof(char*));
+ tok->args[0] = STRNDUP(*s,eol-*s); /* Grab everything on line */
+ tok->n_args = 1;
+ } else {
+ /* This keyword takes multiple arguments. */
+ if (get_token_arguments(area, tok, *s, eol)<0) {
+ tor_snprintf(ebuf, sizeof(ebuf),"Far too many arguments to %s", kwd);
+ RET_ERR(ebuf);
+ }
+ *s = eol;
+ }
+ if (tok->n_args < table[i].min_args) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Too few arguments to %s", kwd);
+ RET_ERR(ebuf);
+ } else if (tok->n_args > table[i].max_args) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Too many arguments to %s", kwd);
+ RET_ERR(ebuf);
+ }
+ break;
+ }
+ }
+
+ if (tok->tp == ERR_) {
+ /* No keyword matched; call it an "K_opt" or "A_unrecognized" */
+ if (*s < eol && **s == '@')
+ tok->tp = A_UNKNOWN_;
+ else
+ tok->tp = K_OPT;
+ tok->args = ALLOC(sizeof(char*));
+ tok->args[0] = STRNDUP(*s, eol-*s);
+ tok->n_args = 1;
+ o_syn = OBJ_OK;
+ }
+
+ /* Check whether there's an object present */
+ *s = eat_whitespace_eos(eol, eos); /* Scan from end of first line */
+ tor_assert(eos >= *s);
+ eol = memchr(*s, '\n', eos-*s);
+ if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */
+ goto check_object;
+
+ obstart = *s; /* Set obstart to start of object spec */
+ if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */
+ strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */
+ (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */
+ RET_ERR("Malformed object: bad begin line");
+ }
+ tok->object_type = STRNDUP(*s+11, eol-*s-16);
+ obname_len = eol-*s-16; /* store objname length here to avoid a strlen() */
+ *s = eol+1; /* Set *s to possible start of object data (could be eos) */
+
+ /* Go to the end of the object */
+ next = tor_memstr(*s, eos-*s, "-----END ");
+ if (!next) {
+ RET_ERR("Malformed object: missing object end line");
+ }
+ tor_assert(eos >= next);
+ eol = memchr(next, '\n', eos-next);
+ if (!eol) /* end-of-line marker, or eos if there's no '\n' */
+ eol = eos;
+ /* Validate the ending tag, which should be 9 + NAME + 5 + eol */
+ if ((size_t)(eol-next) != 9+obname_len+5 ||
+ strcmp_len(next+9, tok->object_type, obname_len) ||
+ strcmp_len(eol-5, "-----", 5)) {
+ tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s",
+ tok->object_type);
+ ebuf[sizeof(ebuf)-1] = '\0';
+ RET_ERR(ebuf);
+ }
+ if (next - *s > MAX_UNPARSED_OBJECT_SIZE)
+ RET_ERR("Couldn't parse object: missing footer or object much too big.");
+
+ if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */
+ tok->key = crypto_pk_new();
+ if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart))
+ RET_ERR("Couldn't parse public key.");
+ } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */
+ tok->key = crypto_pk_new();
+ if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart))
+ RET_ERR("Couldn't parse private key.");
+ } else { /* If it's something else, try to base64-decode it */
+ int r;
+ tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */
+ r = base64_decode(tok->object_body, next-*s, *s, next-*s);
+ if (r<0)
+ RET_ERR("Malformed object: bad base64-encoded data");
+ tok->object_size = r;
+ }
+ *s = eol;
+
+ check_object:
+ tok = token_check_object(area, kwd, tok, o_syn);
+
+ done_tokenizing:
+ return tok;
+
+#undef RET_ERR
+#undef ALLOC
+#undef ALLOC_ZERO
+#undef STRDUP
+#undef STRNDUP
+}
+
+/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail
+ * with an assert if no such keyword is found.
+ */
+directory_token_t *
+find_by_keyword_(smartlist_t *s, directory_keyword keyword,
+ const char *keyword_as_string)
+{
+ directory_token_t *tok = find_opt_by_keyword(s, keyword);
+ if (PREDICT_UNLIKELY(!tok)) {
+ log_err(LD_BUG, "Missing %s [%d] in directory object that should have "
+ "been validated. Internal error.", keyword_as_string, (int)keyword);
+ tor_assert(tok);
+ }
+ return tok;
+}
+
+/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return
+ * NULL if no such keyword is found.
+ */
+directory_token_t *
+find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
+{
+ SMARTLIST_FOREACH(s, directory_token_t *, t, if (t->tp == keyword) return t);
+ return NULL;
+}
+
+/** If there are any directory_token_t entries in <b>s</b> whose keyword is
+ * <b>k</b>, return a newly allocated smartlist_t containing all such entries,
+ * 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)
+{
+ smartlist_t *out = NULL;
+ SMARTLIST_FOREACH(s, directory_token_t *, t,
+ if (t->tp == k) {
+ if (!out)
+ out = smartlist_new();
+ smartlist_add(out, t);
+ });
+ return out;
+}
+
diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h
new file mode 100644
index 0000000000..15e9f7ae85
--- /dev/null
+++ b/src/or/parsecommon.h
@@ -0,0 +1,315 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file parsecommon.h
+ * \brief Header file for parsecommon.c
+ **/
+
+#ifndef TOR_PARSECOMMON_H
+#define TOR_PARSECOMMON_H
+
+#include "container.h"
+#include "crypto.h"
+#include "memarea.h"
+
+/** Enumeration of possible token types. The ones starting with K_ correspond
+* to directory 'keywords'. A_ is for an annotation, R or C is related to
+* hidden services, ERR_ is an error in the tokenizing process, EOF_ is an
+* end-of-file marker, and NIL_ is used to encode not-a-token.
+*/
+typedef enum {
+ K_ACCEPT = 0,
+ K_ACCEPT6,
+ K_DIRECTORY_SIGNATURE,
+ K_RECOMMENDED_SOFTWARE,
+ K_REJECT,
+ K_REJECT6,
+ K_ROUTER,
+ K_SIGNED_DIRECTORY,
+ K_SIGNING_KEY,
+ K_ONION_KEY,
+ K_ONION_KEY_NTOR,
+ K_ROUTER_SIGNATURE,
+ K_PUBLISHED,
+ K_RUNNING_ROUTERS,
+ K_ROUTER_STATUS,
+ K_PLATFORM,
+ K_PROTO,
+ K_OPT,
+ K_BANDWIDTH,
+ K_CONTACT,
+ K_NETWORK_STATUS,
+ K_UPTIME,
+ K_DIR_SIGNING_KEY,
+ K_FAMILY,
+ K_FINGERPRINT,
+ K_HIBERNATING,
+ K_READ_HISTORY,
+ K_WRITE_HISTORY,
+ K_NETWORK_STATUS_VERSION,
+ K_DIR_SOURCE,
+ K_DIR_OPTIONS,
+ K_CLIENT_VERSIONS,
+ K_SERVER_VERSIONS,
+ K_RECOMMENDED_CLIENT_PROTOCOLS,
+ K_RECOMMENDED_RELAY_PROTOCOLS,
+ K_REQUIRED_CLIENT_PROTOCOLS,
+ K_REQUIRED_RELAY_PROTOCOLS,
+ K_OR_ADDRESS,
+ K_ID,
+ K_P,
+ K_P6,
+ K_R,
+ K_A,
+ K_S,
+ K_V,
+ K_W,
+ K_M,
+ K_EXTRA_INFO,
+ K_EXTRA_INFO_DIGEST,
+ K_CACHES_EXTRA_INFO,
+ K_HIDDEN_SERVICE_DIR,
+ K_ALLOW_SINGLE_HOP_EXITS,
+ K_IPV6_POLICY,
+ K_ROUTER_SIG_ED25519,
+ K_IDENTITY_ED25519,
+ K_MASTER_KEY_ED25519,
+ K_ONION_KEY_CROSSCERT,
+ K_NTOR_ONION_KEY_CROSSCERT,
+
+ K_DIRREQ_END,
+ K_DIRREQ_V2_IPS,
+ K_DIRREQ_V3_IPS,
+ K_DIRREQ_V2_REQS,
+ K_DIRREQ_V3_REQS,
+ K_DIRREQ_V2_SHARE,
+ K_DIRREQ_V3_SHARE,
+ K_DIRREQ_V2_RESP,
+ K_DIRREQ_V3_RESP,
+ K_DIRREQ_V2_DIR,
+ K_DIRREQ_V3_DIR,
+ K_DIRREQ_V2_TUN,
+ K_DIRREQ_V3_TUN,
+ K_ENTRY_END,
+ K_ENTRY_IPS,
+ K_CELL_END,
+ K_CELL_PROCESSED,
+ K_CELL_QUEUED,
+ K_CELL_TIME,
+ K_CELL_CIRCS,
+ K_EXIT_END,
+ K_EXIT_WRITTEN,
+ K_EXIT_READ,
+ K_EXIT_OPENED,
+
+ K_DIR_KEY_CERTIFICATE_VERSION,
+ K_DIR_IDENTITY_KEY,
+ K_DIR_KEY_PUBLISHED,
+ K_DIR_KEY_EXPIRES,
+ K_DIR_KEY_CERTIFICATION,
+ K_DIR_KEY_CROSSCERT,
+ K_DIR_ADDRESS,
+ K_DIR_TUNNELLED,
+
+ K_VOTE_STATUS,
+ K_VALID_AFTER,
+ K_FRESH_UNTIL,
+ K_VALID_UNTIL,
+ K_VOTING_DELAY,
+
+ K_KNOWN_FLAGS,
+ K_PARAMS,
+ K_BW_WEIGHTS,
+ K_VOTE_DIGEST,
+ K_CONSENSUS_DIGEST,
+ K_ADDITIONAL_DIGEST,
+ K_ADDITIONAL_SIGNATURE,
+ K_CONSENSUS_METHODS,
+ K_CONSENSUS_METHOD,
+ K_LEGACY_DIR_KEY,
+ K_DIRECTORY_FOOTER,
+ K_SIGNING_CERT_ED,
+ K_SR_FLAG,
+ K_COMMIT,
+ K_PREVIOUS_SRV,
+ K_CURRENT_SRV,
+ K_PACKAGE,
+
+ A_PURPOSE,
+ A_LAST_LISTED,
+ A_UNKNOWN_,
+
+ R_RENDEZVOUS_SERVICE_DESCRIPTOR,
+ R_VERSION,
+ R_PERMANENT_KEY,
+ R_SECRET_ID_PART,
+ R_PUBLICATION_TIME,
+ R_PROTOCOL_VERSIONS,
+ R_INTRODUCTION_POINTS,
+ R_SIGNATURE,
+
+ R_HS_DESCRIPTOR, /* From version 3, this MUST be generic to all future
+ descriptor versions thus making it R_. */
+ R3_DESC_LIFETIME,
+ R3_DESC_SIGNING_CERT,
+ R3_REVISION_COUNTER,
+ R3_SUPERENCRYPTED,
+ R3_SIGNATURE,
+ R3_CREATE2_FORMATS,
+ R3_AUTHENTICATION_REQUIRED,
+ R3_SINGLE_ONION_SERVICE,
+ R3_INTRODUCTION_POINT,
+ R3_INTRO_AUTH_KEY,
+ R3_INTRO_ENC_KEY,
+ R3_INTRO_ENC_KEY_CERTIFICATION,
+
+ R_IPO_IDENTIFIER,
+ R_IPO_IP_ADDRESS,
+ R_IPO_ONION_PORT,
+ R_IPO_ONION_KEY,
+ R_IPO_SERVICE_KEY,
+
+ C_CLIENT_NAME,
+ C_DESCRIPTOR_COOKIE,
+ C_CLIENT_KEY,
+
+ ERR_,
+ EOF_,
+ NIL_
+} directory_keyword;
+
+/** Structure to hold a single directory token.
+ *
+ * We parse a directory by breaking it into "tokens", each consisting
+ * of a keyword, a line full of arguments, and a binary object. The
+ * arguments and object are both optional, depending on the keyword
+ * type.
+ *
+ * This structure is only allocated in memareas; do not allocate it on
+ * the heap, or token_clear() won't work.
+ */
+typedef struct directory_token_t {
+ directory_keyword tp; /**< Type of the token. */
+ int n_args:30; /**< Number of elements in args */
+ char **args; /**< Array of arguments from keyword line. */
+
+ char *object_type; /**< -----BEGIN [object_type]-----*/
+ size_t object_size; /**< Bytes in object_body */
+ char *object_body; /**< Contents of object, base64-decoded. */
+
+ crypto_pk_t *key; /**< For public keys only. Heap-allocated. */
+
+ char *error; /**< For ERR_ tokens only. */
+} directory_token_t;
+
+/** We use a table of rules to decide how to parse each token type. */
+
+/** Rules for whether the keyword needs an object. */
+typedef enum {
+ NO_OBJ, /**< No object, ever. */
+ NEED_OBJ, /**< Object is required. */
+ NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */
+ NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */
+ NEED_KEY, /**< Object is required, and must be a public key. */
+ OBJ_OK, /**< Object is optional. */
+} obj_syntax;
+
+#define AT_START 1
+#define AT_END 2
+
+#define TS_ANNOTATIONS_OK 1
+#define TS_NOCHECK 2
+#define TS_NO_NEW_ANNOTATIONS 4
+
+/**
+ * @name macros for defining token rules
+ *
+ * Helper macros to define token tables. 's' is a string, 't' is a
+ * directory_keyword, 'a' is a trio of argument multiplicities, and 'o' is an
+ * object syntax.
+ */
+/**@{*/
+
+/** Appears to indicate the end of a table. */
+#define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 }
+/** An item with no restrictions: used for obsolete document types */
+#define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
+/** An item with no restrictions on multiplicity or location. */
+#define T0N(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
+/** An item that must appear exactly once */
+#define T1(s,t,a,o) { s, t, a, o, 1, 1, 0, 0 }
+/** An item that must appear exactly once, at the start of the document */
+#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START, 0 }
+/** An item that must appear exactly once, at the end of the document */
+#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END, 0 }
+/** An item that must appear one or more times */
+#define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0, 0 }
+/** An item that must appear no more than once */
+#define T01(s,t,a,o) { s, t, a, o, 0, 1, 0, 0 }
+/** An annotation that must appear no more than once */
+#define A01(s,t,a,o) { s, t, a, o, 0, 1, 0, 1 }
+
+/** Argument multiplicity: any number of arguments. */
+#define ARGS 0,INT_MAX,0
+/** Argument multiplicity: no arguments. */
+#define NO_ARGS 0,0,0
+/** Argument multiplicity: concatenate all arguments. */
+#define CONCAT_ARGS 1,1,1
+/** Argument multiplicity: at least <b>n</b> arguments. */
+#define GE(n) n,INT_MAX,0
+/** Argument multiplicity: exactly <b>n</b> arguments. */
+#define EQ(n) n,n,0
+/**@}*/
+
+/** Determines the parsing rules for a single token type. */
+typedef struct token_rule_t {
+ /** The string value of the keyword identifying the type of item. */
+ const char *t;
+ /** The corresponding directory_keyword enum. */
+ directory_keyword v;
+ /** Minimum number of arguments for this item */
+ int min_args;
+ /** Maximum number of arguments for this item */
+ int max_args;
+ /** If true, we concatenate all arguments for this item into a single
+ * string. */
+ int concat_args;
+ /** Requirements on object syntax for this item. */
+ obj_syntax os;
+ /** Lowest number of times this item may appear in a document. */
+ int min_cnt;
+ /** Highest number of times this item may appear in a document. */
+ int max_cnt;
+ /** One or more of AT_START/AT_END to limit where the item may appear in a
+ * document. */
+ int pos;
+ /** True iff this token is an annotation. */
+ int is_annotation;
+} token_rule_t;
+
+void token_clear(directory_token_t *tok);
+
+int tokenize_string(memarea_t *area,
+ const char *start, const char *end,
+ smartlist_t *out,
+ token_rule_t *table,
+ int flags);
+directory_token_t *get_next_token(memarea_t *area,
+ const char **s,
+ const char *eos,
+ token_rule_t *table);
+
+directory_token_t *find_by_keyword_(smartlist_t *s,
+ directory_keyword keyword,
+ const char *keyword_str);
+
+#define find_by_keyword(s, keyword) \
+ find_by_keyword_((s), (keyword), #keyword)
+
+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);
+
+#endif /* TOR_PARSECOMMON_H */
+
diff --git a/src/or/policies.c b/src/or/policies.c
index f58bf329ad..da5c4af6dc 100644
--- a/src/or/policies.c
+++ b/src/or/policies.c
@@ -6,6 +6,13 @@
/**
* \file policies.c
* \brief Code to parse and use address policies and exit policies.
+ *
+ * We have two key kinds of address policy: full and compressed. A full
+ * policy is an array of accept/reject patterns, to be applied in order.
+ * A short policy is simply a list of ports. This module handles both
+ * kinds, including generic functions to apply them to addresses, and
+ * also including code to manage the global policies that we apply to
+ * incoming and outgoing connections.
**/
#define POLICIES_PRIVATE
@@ -13,6 +20,7 @@
#include "or.h"
#include "config.h"
#include "dirserv.h"
+#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
#include "policies.h"
@@ -290,8 +298,8 @@ parse_reachable_addresses(void)
} else if (fascist_firewall_use_ipv6(options)
&& (policy_is_reject_star(reachable_or_addr_policy, AF_INET6, 0)
|| policy_is_reject_star(reachable_dir_addr_policy, AF_INET6, 0))) {
- log_warn(LD_CONFIG, "You have configured tor to use IPv6 "
- "(ClientUseIPv6 1 or UseBridges 1), but "
+ log_warn(LD_CONFIG, "You have configured tor to use or prefer IPv6 "
+ "(or UseBridges 1), but "
"ReachableAddresses, ReachableORAddresses, or "
"ReachableDirAddresses reject all IPv6 addresses. "
"Tor will not connect using IPv6.");
@@ -309,10 +317,8 @@ firewall_is_fascist_impl(void)
const or_options_t *options = get_options();
/* Assume every non-bridge relay has an IPv4 address.
* Clients which use bridges may only know the IPv6 address of their
- * bridge. */
- return (options->ClientUseIPv4 == 0
- || (!fascist_firewall_use_ipv6(options)
- && options->UseBridges == 1));
+ * bridge, but they will connect regardless of the ClientUseIPv6 setting. */
+ return options->ClientUseIPv4 == 0;
}
/** Return true iff the firewall options, including ClientUseIPv4 0 and
@@ -419,6 +425,9 @@ fascist_firewall_allows_address(const tor_addr_t *addr,
}
/** Is this client configured to use IPv6?
+ * Returns true if the client might use IPv6 for some of its connections
+ * (including dual-stack and IPv6-only clients), and false if it will never
+ * use IPv6 for any connections.
* Use node_ipv6_or/dir_preferred() when checking a specific node and OR/Dir
* port: it supports bridge client per-node IPv6 preferences.
*/
@@ -426,9 +435,11 @@ int
fascist_firewall_use_ipv6(const or_options_t *options)
{
/* Clients use IPv6 if it's set, or they use bridges, or they don't use
- * IPv4 */
- return (options->ClientUseIPv6 == 1 || options->UseBridges == 1
- || options->ClientUseIPv4 == 0);
+ * IPv4, or they prefer it.
+ * ClientPreferIPv6DirPort is deprecated, but check it anyway. */
+ return (options->ClientUseIPv6 == 1 || options->ClientUseIPv4 == 0 ||
+ options->ClientPreferIPv6ORPort == 1 ||
+ options->ClientPreferIPv6DirPort == 1 || options->UseBridges == 1);
}
/** Do we prefer to connect to IPv6, ignoring ClientPreferIPv6ORPort and
@@ -883,6 +894,33 @@ fascist_firewall_choose_address_ipv4h(uint32_t ipv4h_addr,
pref_ipv6, ap);
}
+/* The microdescriptor consensus has no IPv6 addresses in rs: they are in
+ * the microdescriptors. This means we can't rely on the node's IPv6 address
+ * until its microdescriptor is available (when using microdescs).
+ * But for bridges, rewrite_node_address_for_bridge() updates node->ri with
+ * the configured address, so we can trust bridge addresses.
+ * (Bridges could gain an IPv6 address if their microdescriptor arrives, but
+ * this will never be their preferred address: that is in the config.)
+ * Returns true if the node needs a microdescriptor for its IPv6 address, and
+ * false if the addresses in the node are already up-to-date.
+ */
+static int
+node_awaiting_ipv6(const or_options_t* options, const node_t *node)
+{
+ tor_assert(node);
+
+ /* There's no point waiting for an IPv6 address if we'd never use it */
+ if (!fascist_firewall_use_ipv6(options)) {
+ return 0;
+ }
+
+ /* We are waiting if we_use_microdescriptors_for_circuits() and we have no
+ * md. Bridges have a ri based on their config. They would never use the
+ * address from their md, so there's no need to wait for it. */
+ return (!node->md && we_use_microdescriptors_for_circuits(options) &&
+ !node->ri);
+}
+
/** Like fascist_firewall_choose_address_base(), but takes <b>rs</b>.
* Consults the corresponding node, then falls back to rs if node is NULL.
* This should only happen when there's no valid consensus, and rs doesn't
@@ -899,15 +937,15 @@ fascist_firewall_choose_address_rs(const routerstatus_t *rs,
tor_assert(ap);
+ const or_options_t *options = get_options();
const node_t *node = node_get_by_id(rs->identity_digest);
- if (node) {
+ if (node && !node_awaiting_ipv6(options, node)) {
return fascist_firewall_choose_address_node(node, fw_connection, pref_only,
ap);
} else {
/* There's no node-specific IPv6 preference, so use the generic IPv6
* preference instead. */
- const or_options_t *options = get_options();
int pref_ipv6 = (fw_connection == FIREWALL_OR_CONNECTION
? fascist_firewall_prefer_ipv6_orport(options)
: fascist_firewall_prefer_ipv6_dirport(options));
@@ -941,6 +979,18 @@ fascist_firewall_choose_address_node(const node_t *node,
node_assert_ok(node);
+ /* Calling fascist_firewall_choose_address_node() when the node is missing
+ * IPv6 information breaks IPv6-only clients.
+ * If the node is a hard-coded fallback directory or authority, call
+ * fascist_firewall_choose_address_rs() on the fake (hard-coded) routerstatus
+ * for the node.
+ * If it is not hard-coded, check that the node has a microdescriptor, full
+ * descriptor (routerinfo), or is one of our configured bridges before
+ * calling this function. */
+ if (BUG(node_awaiting_ipv6(get_options(), node))) {
+ return 0;
+ }
+
const int pref_ipv6_node = (fw_connection == FIREWALL_OR_CONNECTION
? node_ipv6_or_preferred(node)
: node_ipv6_dir_preferred(node));
@@ -1971,10 +2021,10 @@ policies_copy_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr)
}
}
-/** Helper function that adds copies of
- * or_options->OutboundBindAddressIPv[4|6]_ to a smartlist as tor_addr_t *, as
- * long as or_options is non-NULL, and the addresses are not
- * tor_addr_is_null(), by passing them to policies_add_addr_to_smartlist.
+/** Helper function that adds copies of or_options->OutboundBindAddresses
+ * to a smartlist as tor_addr_t *, as long as or_options is non-NULL, and
+ * the addresses are not tor_addr_is_null(), by passing them to
+ * policies_add_addr_to_smartlist.
*
* The caller is responsible for freeing all the tor_addr_t* in the smartlist.
*/
@@ -1983,10 +2033,14 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list,
const or_options_t *or_options)
{
if (or_options) {
- policies_copy_addr_to_smartlist(addr_list,
- &or_options->OutboundBindAddressIPv4_);
- policies_copy_addr_to_smartlist(addr_list,
- &or_options->OutboundBindAddressIPv6_);
+ for (int i=0;i<OUTBOUND_ADDR_MAX;i++) {
+ for (int j=0;j<2;j++) {
+ if (!tor_addr_is_null(&or_options->OutboundBindAddresses[i][j])) {
+ policies_copy_addr_to_smartlist(addr_list,
+ &or_options->OutboundBindAddresses[i][j]);
+ }
+ }
+ }
}
}
@@ -2003,10 +2057,10 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list,
* - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it
* to the list of configured addresses.
* If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true:
- * - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add
- * it to the list of configured addresses.
- * - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add
- * it to the list of configured addresses.
+ * - if or_options->OutboundBindAddresses[][0] (=IPv4) is not the null
+ * tor_addr_t, add it to the list of configured addresses.
+ * - if or_options->OutboundBindAddresses[][1] (=IPv6) is not the null
+ * tor_addr_t, add it to the list of configured addresses.
*
* If <b>or_options->BridgeRelay</b> is false, append entries of default
* Tor exit policy into <b>result</b> smartlist.
@@ -2528,9 +2582,9 @@ policy_summarize(smartlist_t *policy, sa_family_t family)
tor_snprintf(buf, sizeof(buf), "%d-%d", start_prt, AT(i)->prt_max);
if (AT(i)->accepted)
- smartlist_add(accepts, tor_strdup(buf));
+ smartlist_add_strdup(accepts, buf);
else
- smartlist_add(rejects, tor_strdup(buf));
+ smartlist_add_strdup(rejects, buf);
if (last)
break;
@@ -2710,7 +2764,7 @@ write_short_policy(const short_policy_t *policy)
smartlist_add_asprintf(sl, "%d-%d", e->min_port, e->max_port);
}
if (i < policy->n_entries-1)
- smartlist_add(sl, tor_strdup(","));
+ smartlist_add_strdup(sl, ",");
}
answer = smartlist_join_strings(sl, "", 0, NULL);
SMARTLIST_FOREACH(sl, char *, a, tor_free(a));
diff --git a/src/or/protover.c b/src/or/protover.c
index 98957cabdf..c76f028699 100644
--- a/src/or/protover.c
+++ b/src/or/protover.c
@@ -289,11 +289,11 @@ protover_get_supported_protocols(void)
"Cons=1-2 "
"Desc=1-2 "
"DirCache=1 "
- "HSDir=1 "
- "HSIntro=3 "
+ "HSDir=1-2 "
+ "HSIntro=3-4 "
"HSRend=1-2 "
"Link=1-4 "
- "LinkAuth=1 "
+ "LinkAuth=1,3 "
"Microdesc=1-2 "
"Relay=1-2";
}
@@ -348,7 +348,7 @@ encode_protocol_list(const smartlist_t *sl)
const char *separator = "";
smartlist_t *chunks = smartlist_new();
SMARTLIST_FOREACH_BEGIN(sl, const proto_entry_t *, ent) {
- smartlist_add(chunks, tor_strdup(separator));
+ smartlist_add_strdup(chunks, separator);
proto_entry_encode_into(chunks, ent);
@@ -477,7 +477,7 @@ contract_protocol_list(const smartlist_t *proto_strings)
smartlist_sort(lst, cmp_single_ent_by_version);
if (! first_entry)
- smartlist_add(chunks, tor_strdup(" "));
+ smartlist_add_strdup(chunks, " ");
/* We're going to construct this entry from the ranges. */
proto_entry_t *entry = tor_malloc_zero(sizeof(proto_entry_t));
diff --git a/src/or/relay.c b/src/or/relay.c
index 29f34ca033..e504643f35 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -8,6 +8,41 @@
* \file relay.c
* \brief Handle relay cell encryption/decryption, plus packaging and
* receiving from circuits, plus queuing on circuits.
+ *
+ * This is a core modules that makes Tor work. It's responsible for
+ * dealing with RELAY cells (the ones that travel more than one hop along a
+ * circuit), by:
+ * <ul>
+ * <li>constructing relays cells,
+ * <li>encrypting relay cells,
+ * <li>decrypting relay cells,
+ * <li>demultiplexing relay cells as they arrive on a connection,
+ * <li>queueing relay cells for retransmission,
+ * <li>or handling relay cells that are for us to receive (as an exit or a
+ * client).
+ * </ul>
+ *
+ * RELAY cells are generated throughout the code at the client or relay side,
+ * using relay_send_command_from_edge() or one of the functions like
+ * connection_edge_send_command() that calls it. Of particular interest is
+ * connection_edge_package_raw_inbuf(), which takes information that has
+ * arrived on an edge connection socket, and packages it as a RELAY_DATA cell
+ * -- this is how information is actually sent across the Tor network. The
+ * cryptography for these functions is handled deep in
+ * circuit_package_relay_cell(), which either adds a single layer of
+ * encryption (if we're an exit), or multiple layers (if we're the origin of
+ * the circuit). After construction and encryption, the RELAY cells are
+ * passed to append_cell_to_circuit_queue(), which queues them for
+ * transmission and tells the circuitmux (see circuitmux.c) that the circuit
+ * is waiting to send something.
+ *
+ * Incoming RELAY cells arrive at circuit_receive_relay_cell(), called from
+ * command.c. There they are decrypted and, if they are for us, are passed to
+ * connection_edge_process_relay_cell(). If they're not for us, they're
+ * re-queued for retransmission again with append_cell_to_circuit_queue().
+ *
+ * The connection_edge_process_relay_cell() function handles all the different
+ * types of relay cells, launching requests or transmitting data as needed.
**/
#define RELAY_PRIVATE
@@ -25,6 +60,7 @@
#include "connection_or.h"
#include "control.h"
#include "geoip.h"
+#include "hs_cache.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -564,11 +600,11 @@ relay_command_to_string(uint8_t command)
* If you can't send the cell, mark the circuit for close and return -1. Else
* return 0.
*/
-int
-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)
+MOCK_IMPL(int,
+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))
{
cell_t cell;
relay_header_t rh;
@@ -580,14 +616,14 @@ relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
memset(&cell, 0, sizeof(cell_t));
cell.command = CELL_RELAY;
- if (cpath_layer) {
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ tor_assert(cpath_layer);
cell.circ_id = circ->n_circ_id;
cell_direction = CELL_DIRECTION_OUT;
- } else if (! CIRCUIT_IS_ORIGIN(circ)) {
+ } else {
+ tor_assert(! cpath_layer);
cell.circ_id = TO_OR_CIRCUIT(circ)->p_circ_id;
cell_direction = CELL_DIRECTION_IN;
- } else {
- return -1;
}
memset(&rh, 0, sizeof(rh));
@@ -2483,9 +2519,7 @@ cell_queues_check_size(void)
if (rend_cache_total > get_options()->MaxMemInQueues / 5) {
const size_t bytes_to_remove =
rend_cache_total - (size_t)(get_options()->MaxMemInQueues / 10);
- rend_cache_clean_v2_descs_as_dir(time(NULL), bytes_to_remove);
- alloc -= rend_cache_total;
- alloc += rend_cache_get_total_allocation();
+ alloc -= hs_cache_handle_oom(time(NULL), bytes_to_remove);
}
circuits_handle_oom(alloc);
return 1;
diff --git a/src/or/relay.h b/src/or/relay.h
index c4f98d92ff..f01cbf1298 100644
--- a/src/or/relay.h
+++ b/src/or/relay.h
@@ -20,10 +20,13 @@ int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
void relay_header_pack(uint8_t *dest, const relay_header_t *src);
void relay_header_unpack(relay_header_t *dest, const uint8_t *src);
-int relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ,
+MOCK_DECL(int,
+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);
+ const char *filename, int lineno));
+/* Indicates to relay_send_command_from_edge() that it is a control cell. */
+#define CONTROL_CELL_ID 0
#define relay_send_command_from_edge(stream_id, circ, relay_command, payload, \
payload_len, cpath_layer) \
relay_send_command_from_edge_((stream_id), (circ), (relay_command), \
diff --git a/src/or/rendcache.c b/src/or/rendcache.c
index aa69d735fe..12c23ea87c 100644
--- a/src/or/rendcache.c
+++ b/src/or/rendcache.c
@@ -86,7 +86,7 @@ rend_cache_get_total_allocation(void)
}
/** Decrement the total bytes attributed to the rendezvous cache by n. */
-STATIC void
+void
rend_cache_decrement_allocation(size_t n)
{
static int have_underflowed = 0;
@@ -103,7 +103,7 @@ rend_cache_decrement_allocation(size_t n)
}
/** Increase the total bytes attributed to the rendezvous cache by n. */
-STATIC void
+void
rend_cache_increment_allocation(size_t n)
{
static int have_overflowed = 0;
@@ -462,45 +462,36 @@ rend_cache_intro_failure_note(rend_intro_point_failure_t failure,
}
/** Remove all old v2 descriptors and those for which this hidden service
- * directory is not responsible for any more.
- *
- * If at all possible, remove at least <b>force_remove</b> bytes of data.
- */
-void
-rend_cache_clean_v2_descs_as_dir(time_t now, size_t force_remove)
+ * directory is not responsible for any more. The cutoff is the time limit for
+ * which we want to keep the cache entry. In other words, any entry created
+ * before will be removed. */
+size_t
+rend_cache_clean_v2_descs_as_dir(time_t cutoff)
{
digestmap_iter_t *iter;
- time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW;
- const int LAST_SERVED_CUTOFF_STEP = 1800;
- time_t last_served_cutoff = cutoff;
size_t bytes_removed = 0;
- do {
- for (iter = digestmap_iter_init(rend_cache_v2_dir);
- !digestmap_iter_done(iter); ) {
- const char *key;
- void *val;
- rend_cache_entry_t *ent;
- digestmap_iter_get(iter, &key, &val);
- ent = val;
- if (ent->parsed->timestamp < cutoff ||
- ent->last_served < last_served_cutoff) {
- char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
- base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN);
- log_info(LD_REND, "Removing descriptor with ID '%s' from cache",
- safe_str_client(key_base32));
- bytes_removed += rend_cache_entry_allocation(ent);
- iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter);
- rend_cache_entry_free(ent);
- } else {
- iter = digestmap_iter_next(rend_cache_v2_dir, iter);
- }
+
+ for (iter = digestmap_iter_init(rend_cache_v2_dir);
+ !digestmap_iter_done(iter); ) {
+ const char *key;
+ void *val;
+ rend_cache_entry_t *ent;
+ digestmap_iter_get(iter, &key, &val);
+ ent = val;
+ if (ent->parsed->timestamp < cutoff) {
+ char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
+ base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN);
+ log_info(LD_REND, "Removing descriptor with ID '%s' from cache",
+ safe_str_client(key_base32));
+ bytes_removed += rend_cache_entry_allocation(ent);
+ iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter);
+ rend_cache_entry_free(ent);
+ } else {
+ iter = digestmap_iter_next(rend_cache_v2_dir, iter);
}
+ }
- /* In case we didn't remove enough bytes, advance the cutoff a little. */
- last_served_cutoff += LAST_SERVED_CUTOFF_STEP;
- if (last_served_cutoff > now)
- break;
- } while (bytes_removed < force_remove);
+ return bytes_removed;
}
/** Lookup in the client cache the given service ID <b>query</b> for
@@ -849,6 +840,8 @@ rend_cache_store_v2_desc_as_client(const char *desc,
char want_desc_id[DIGEST_LEN];
rend_cache_entry_t *e;
int retval = -1;
+ rend_data_v2_t *rend_data = TO_REND_DATA_V2(rend_query);
+
tor_assert(rend_cache);
tor_assert(desc);
tor_assert(desc_id_base32);
@@ -874,11 +867,11 @@ rend_cache_store_v2_desc_as_client(const char *desc,
log_warn(LD_REND, "Couldn't compute service ID.");
goto err;
}
- if (rend_query->onion_address[0] != '\0' &&
- strcmp(rend_query->onion_address, service_id)) {
+ if (rend_data->onion_address[0] != '\0' &&
+ strcmp(rend_data->onion_address, service_id)) {
log_warn(LD_REND, "Received service descriptor for service ID %s; "
"expected descriptor for service ID %s.",
- service_id, safe_str(rend_query->onion_address));
+ service_id, safe_str(rend_data->onion_address));
goto err;
}
if (tor_memneq(desc_id, want_desc_id, DIGEST_LEN)) {
@@ -890,14 +883,14 @@ rend_cache_store_v2_desc_as_client(const char *desc,
/* Decode/decrypt introduction points. */
if (intro_content && intro_size > 0) {
int n_intro_points;
- if (rend_query->auth_type != REND_NO_AUTH &&
- !tor_mem_is_zero(rend_query->descriptor_cookie,
- sizeof(rend_query->descriptor_cookie))) {
+ if (rend_data->auth_type != REND_NO_AUTH &&
+ !tor_mem_is_zero(rend_data->descriptor_cookie,
+ sizeof(rend_data->descriptor_cookie))) {
char *ipos_decrypted = NULL;
size_t ipos_decrypted_size;
if (rend_decrypt_introduction_points(&ipos_decrypted,
&ipos_decrypted_size,
- rend_query->descriptor_cookie,
+ rend_data->descriptor_cookie,
intro_content,
intro_size) < 0) {
log_warn(LD_REND, "Failed to decrypt introduction points. We are "
diff --git a/src/or/rendcache.h b/src/or/rendcache.h
index 270b614c38..746f142fcc 100644
--- a/src/or/rendcache.h
+++ b/src/or/rendcache.h
@@ -53,10 +53,17 @@ typedef enum {
REND_CACHE_TYPE_SERVICE = 2,
} rend_cache_type_t;
+/* Return maximum lifetime in seconds of a cache entry. */
+static inline time_t
+rend_cache_max_entry_lifetime(void)
+{
+ return REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW;
+}
+
void rend_cache_init(void);
void rend_cache_clean(time_t now, rend_cache_type_t cache_type);
void rend_cache_failure_clean(time_t now);
-void rend_cache_clean_v2_descs_as_dir(time_t now, size_t min_to_remove);
+size_t rend_cache_clean_v2_descs_as_dir(time_t cutoff);
void rend_cache_purge(void);
void rend_cache_free_all(void);
int rend_cache_lookup_entry(const char *query, int version,
@@ -77,6 +84,8 @@ void rend_cache_intro_failure_note(rend_intro_point_failure_t failure,
const uint8_t *identity,
const char *service_id);
void rend_cache_failure_purge(void);
+void rend_cache_decrement_allocation(size_t n);
+void rend_cache_increment_allocation(size_t n);
#ifdef RENDCACHE_PRIVATE
@@ -89,8 +98,6 @@ STATIC int cache_failure_intro_lookup(const uint8_t *identity,
const char *service_id,
rend_cache_failure_intro_t
**intro_entry);
-STATIC void rend_cache_decrement_allocation(size_t n);
-STATIC void rend_cache_increment_allocation(size_t n);
STATIC rend_cache_failure_intro_t *rend_cache_failure_intro_entry_new(
rend_intro_point_failure_t failure);
STATIC rend_cache_failure_t *rend_cache_failure_entry_new(void);
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index a93bc94a9c..06744ad795 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -16,6 +16,7 @@
#include "connection.h"
#include "connection_edge.h"
#include "directory.h"
+#include "hs_common.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -104,7 +105,7 @@ rend_client_reextend_intro_circuit(origin_circuit_t *circ)
if (!extend_info) {
log_warn(LD_REND,
"No usable introduction points left for %s. Closing.",
- safe_str_client(circ->rend_data->onion_address));
+ safe_str_client(rend_data_get_address(circ->rend_data)));
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
return -1;
}
@@ -144,18 +145,19 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
off_t dh_offset;
crypto_pk_t *intro_key = NULL;
int status = 0;
+ const char *onion_address;
tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
tor_assert(rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY);
tor_assert(introcirc->rend_data);
tor_assert(rendcirc->rend_data);
- tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
- rendcirc->rend_data->onion_address));
+ tor_assert(!rend_cmp_service_ids(rend_data_get_address(introcirc->rend_data),
+ rend_data_get_address(rendcirc->rend_data)));
assert_circ_anonymity_ok(introcirc, options);
assert_circ_anonymity_ok(rendcirc, options);
+ onion_address = rend_data_get_address(introcirc->rend_data);
- r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
- &entry);
+ r = rend_cache_lookup_entry(onion_address, -1, &entry);
/* An invalid onion address is not possible else we have a big issue. */
tor_assert(r != -EINVAL);
if (r < 0 || !rend_client_any_intro_points_usable(entry)) {
@@ -164,14 +166,13 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
log_info(LD_REND,
"query %s didn't have valid rend desc in cache. "
"Refetching descriptor.",
- safe_str_client(introcirc->rend_data->onion_address));
+ safe_str_client(onion_address));
rend_client_refetch_v2_renddesc(introcirc->rend_data);
{
connection_t *conn;
while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
- AP_CONN_STATE_CIRCUIT_WAIT,
- introcirc->rend_data->onion_address))) {
+ AP_CONN_STATE_CIRCUIT_WAIT, onion_address))) {
connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
conn->state = AP_CONN_STATE_RENDDESC_WAIT;
}
@@ -195,7 +196,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
log_info(LD_REND, "Could not find intro key for %s at %s; we "
"have a v2 rend desc with %d intro points. "
"Trying a different intro point...",
- safe_str_client(introcirc->rend_data->onion_address),
+ safe_str_client(onion_address),
safe_str_client(extend_info_describe(
introcirc->build_state->chosen_exit)),
smartlist_len(entry->parsed->intro_nodes));
@@ -235,11 +236,12 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
/* If version is 3, write (optional) auth data and timestamp. */
if (entry->parsed->protocols & (1<<3)) {
tmp[0] = 3; /* version 3 of the cell format */
- tmp[1] = (uint8_t)introcirc->rend_data->auth_type; /* auth type, if any */
+ /* auth type, if any */
+ tmp[1] = (uint8_t) TO_REND_DATA_V2(introcirc->rend_data)->auth_type;
v3_shift = 1;
- if (introcirc->rend_data->auth_type != REND_NO_AUTH) {
+ if (tmp[1] != REND_NO_AUTH) {
set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN));
- memcpy(tmp+4, introcirc->rend_data->descriptor_cookie,
+ memcpy(tmp+4, TO_REND_DATA_V2(introcirc->rend_data)->descriptor_cookie,
REND_DESC_COOKIE_LEN);
v3_shift += 2+REND_DESC_COOKIE_LEN;
}
@@ -359,7 +361,7 @@ rend_client_rendcirc_has_opened(origin_circuit_t *circ)
* Called to close other intro circuits we launched in parallel.
*/
static void
-rend_client_close_other_intros(const char *onion_address)
+rend_client_close_other_intros(const uint8_t *rend_pk_digest)
{
/* abort parallel intro circs, if any */
SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, c) {
@@ -368,8 +370,7 @@ rend_client_close_other_intros(const char *onion_address)
!c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) {
origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c);
if (oc->rend_data &&
- !rend_cmp_service_ids(onion_address,
- oc->rend_data->onion_address)) {
+ rend_circuit_pk_digest_eq(oc, rend_pk_digest)) {
log_info(LD_REND|LD_CIRC, "Closing introduction circuit %d that we "
"built in parallel (Purpose %d).", oc->global_identifier,
c->purpose);
@@ -431,7 +432,8 @@ rend_client_introduction_acked(origin_circuit_t *circ,
circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED);
/* close any other intros launched in parallel */
- rend_client_close_other_intros(circ->rend_data->onion_address);
+ rend_client_close_other_intros(rend_data_get_pk_digest(circ->rend_data,
+ NULL));
} else {
/* It's a NAK; the introduction point didn't relay our request. */
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING);
@@ -440,7 +442,7 @@ rend_client_introduction_acked(origin_circuit_t *circ,
* If none remain, refetch the service descriptor.
*/
log_info(LD_REND, "Got nack for %s from %s...",
- safe_str_client(circ->rend_data->onion_address),
+ safe_str_client(rend_data_get_address(circ->rend_data)),
safe_str_client(extend_info_describe(circ->build_state->chosen_exit)));
if (rend_client_report_intro_point_failure(circ->build_state->chosen_exit,
circ->rend_data,
@@ -694,13 +696,15 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32)
* in the case that no hidden service directory is left to ask for the
* descriptor, return 0, and in case of a failure -1. */
static int
-directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
+directory_get_from_hs_dir(const char *desc_id,
+ const rend_data_t *rend_query,
routerstatus_t *rs_hsdir)
{
routerstatus_t *hs_dir = rs_hsdir;
char *hsdir_fp;
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64];
+ const rend_data_v2_t *rend_data;
#ifdef ENABLE_TOR2WEB_MODE
const int tor2web_mode = get_options()->Tor2webMode;
const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS;
@@ -709,6 +713,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
#endif
tor_assert(desc_id);
+ tor_assert(rend_query);
+ rend_data = TO_REND_DATA_V2(rend_query);
base32_encode(desc_id_base32, sizeof(desc_id_base32),
desc_id, DIGEST_LEN);
@@ -731,10 +737,11 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
/* Encode descriptor cookie for logging purposes. Also, if the cookie is
* malformed, no fetch is triggered thus this needs to be done before the
* fetch request. */
- if (rend_query->auth_type != REND_NO_AUTH) {
+ if (rend_data->auth_type != REND_NO_AUTH) {
if (base64_encode(descriptor_cookie_base64,
sizeof(descriptor_cookie_base64),
- rend_query->descriptor_cookie, REND_DESC_COOKIE_LEN,
+ rend_data->descriptor_cookie,
+ REND_DESC_COOKIE_LEN,
0)<0) {
log_warn(LD_BUG, "Could not base64-encode descriptor cookie.");
return 0;
@@ -755,14 +762,14 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
how_to_fetch,
desc_id_base32,
NULL, 0, 0,
- rend_query);
+ rend_query, NULL);
log_info(LD_REND, "Sending fetch request for v2 descriptor for "
"service '%s' with descriptor ID '%s', auth type %d, "
"and descriptor cookie '%s' to hidden service "
"directory %s",
- rend_query->onion_address, desc_id_base32,
- rend_query->auth_type,
- (rend_query->auth_type == REND_NO_AUTH ? "[none]" :
+ rend_data->onion_address, desc_id_base32,
+ rend_data->auth_type,
+ (rend_data->auth_type == REND_NO_AUTH ? "[none]" :
escaped_safe_str_client(descriptor_cookie_base64)),
routerstatus_describe(hs_dir));
control_event_hs_descriptor_requested(rend_query,
@@ -777,8 +784,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query,
* On success, 1 is returned. If no hidden service is left to ask, return 0.
* On error, -1 is returned. */
static int
-fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query,
- smartlist_t *hsdirs)
+fetch_v2_desc_by_descid(const char *desc_id,
+ const rend_data_t *rend_query, smartlist_t *hsdirs)
{
int ret;
@@ -811,13 +818,12 @@ fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query,
* On success, 1 is returned. If no hidden service is left to ask, return 0.
* On error, -1 is returned. */
static int
-fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs)
+fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs)
{
char descriptor_id[DIGEST_LEN];
int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS];
int i, tries_left, ret;
-
- tor_assert(query);
+ rend_data_v2_t *rend_data = TO_REND_DATA_V2(rend_query);
/* Randomly iterate over the replicas until a descriptor can be fetched
* from one of the consecutive nodes, or no options are left. */
@@ -831,9 +837,10 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs)
int chosen_replica = replicas_left_to_try[rand_val];
replicas_left_to_try[rand_val] = replicas_left_to_try[--tries_left];
- ret = rend_compute_v2_desc_id(descriptor_id, query->onion_address,
- query->auth_type == REND_STEALTH_AUTH ?
- query->descriptor_cookie : NULL,
+ ret = rend_compute_v2_desc_id(descriptor_id,
+ rend_data->onion_address,
+ rend_data->auth_type == REND_STEALTH_AUTH ?
+ rend_data->descriptor_cookie : NULL,
time(NULL), chosen_replica);
if (ret < 0) {
/* Normally, on failure the descriptor_id is untouched but let's be
@@ -841,18 +848,18 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs)
goto end;
}
- if (tor_memcmp(descriptor_id, query->descriptor_id[chosen_replica],
+ if (tor_memcmp(descriptor_id, rend_data->descriptor_id[chosen_replica],
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(
- query->descriptor_id[chosen_replica]);
- memcpy(query->descriptor_id[chosen_replica], descriptor_id,
- sizeof(query->descriptor_id[chosen_replica]));
+ rend_data->descriptor_id[chosen_replica]);
+ memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id,
+ sizeof(rend_data->descriptor_id[chosen_replica]));
}
/* Trigger the fetch with the computed descriptor ID. */
- ret = fetch_v2_desc_by_descid(descriptor_id, query, hsdirs);
+ ret = fetch_v2_desc_by_descid(descriptor_id, rend_query, hsdirs);
if (ret != 0) {
/* Either on success or failure, as long as we tried a fetch we are
* done here. */
@@ -880,16 +887,23 @@ int
rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs)
{
int ret;
+ rend_data_v2_t *rend_data;
+ const char *onion_address;
tor_assert(query);
+ /* Get the version 2 data structure of the query. */
+ rend_data = TO_REND_DATA_V2(query);
+ onion_address = rend_data_get_address(query);
+
/* Depending on what's available in the rend data query object, we will
* trigger a fetch by HS address or using a descriptor ID. */
- if (query->onion_address[0] != '\0') {
+ if (onion_address[0] != '\0') {
ret = fetch_v2_desc_by_addr(query, hsdirs);
- } else if (!tor_digest_is_zero(query->desc_id_fetch)) {
- ret = fetch_v2_desc_by_descid(query->desc_id_fetch, query, hsdirs);
+ } else if (!tor_digest_is_zero(rend_data->desc_id_fetch)) {
+ ret = fetch_v2_desc_by_descid(rend_data->desc_id_fetch, query,
+ hsdirs);
} else {
/* Query data is invalid. */
ret = -1;
@@ -907,10 +921,11 @@ void
rend_client_refetch_v2_renddesc(rend_data_t *rend_query)
{
rend_cache_entry_t *e = NULL;
+ const char *onion_address = rend_data_get_address(rend_query);
tor_assert(rend_query);
/* Before fetching, check if we already have a usable descriptor here. */
- if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) == 0 &&
+ if (rend_cache_lookup_entry(onion_address, -1, &e) == 0 &&
rend_client_any_intro_points_usable(e)) {
log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we "
"already have a usable descriptor here. Not fetching.");
@@ -923,7 +938,7 @@ rend_client_refetch_v2_renddesc(rend_data_t *rend_query)
return;
}
log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s",
- safe_str_client(rend_query->onion_address));
+ safe_str_client(onion_address));
rend_client_fetch_v2_desc(rend_query, NULL);
/* We don't need to look the error code because either on failure or
@@ -959,7 +974,7 @@ rend_client_cancel_descriptor_fetches(void)
} else {
log_debug(LD_REND, "Marking for close dir conn fetching "
"rendezvous descriptor for service %s",
- safe_str(rd->onion_address));
+ safe_str(rend_data_get_address(rd)));
}
connection_mark_for_close(conn);
}
@@ -989,25 +1004,26 @@ rend_client_cancel_descriptor_fetches(void)
*/
int
rend_client_report_intro_point_failure(extend_info_t *failed_intro,
- rend_data_t *rend_query,
+ rend_data_t *rend_data,
unsigned int failure_type)
{
int i, r;
rend_cache_entry_t *ent;
connection_t *conn;
+ const char *onion_address = rend_data_get_address(rend_data);
- r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent);
+ r = rend_cache_lookup_entry(onion_address, -1, &ent);
if (r < 0) {
/* Either invalid onion address or cache entry not found. */
switch (-r) {
case EINVAL:
log_warn(LD_BUG, "Malformed service ID %s.",
- escaped_safe_str_client(rend_query->onion_address));
+ escaped_safe_str_client(onion_address));
return -1;
case ENOENT:
log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.",
- escaped_safe_str_client(rend_query->onion_address));
- rend_client_refetch_v2_renddesc(rend_query);
+ escaped_safe_str_client(onion_address));
+ rend_client_refetch_v2_renddesc(rend_data);
return 0;
default:
log_warn(LD_BUG, "Unknown cache lookup returned code: %d", r);
@@ -1031,7 +1047,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
case INTRO_POINT_FAILURE_GENERIC:
rend_cache_intro_failure_note(failure_type,
(uint8_t *)failed_intro->identity_digest,
- rend_query->onion_address);
+ onion_address);
rend_intro_point_free(intro);
smartlist_del(ent->parsed->intro_nodes, i);
break;
@@ -1049,8 +1065,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
if (zap_intro_point) {
rend_cache_intro_failure_note(
failure_type,
- (uint8_t *) failed_intro->identity_digest,
- rend_query->onion_address);
+ (uint8_t *) failed_intro->identity_digest, onion_address);
rend_intro_point_free(intro);
smartlist_del(ent->parsed->intro_nodes, i);
}
@@ -1064,14 +1079,14 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
if (! rend_client_any_intro_points_usable(ent)) {
log_info(LD_REND,
"No more intro points remain for %s. Re-fetching descriptor.",
- escaped_safe_str_client(rend_query->onion_address));
- rend_client_refetch_v2_renddesc(rend_query);
+ escaped_safe_str_client(onion_address));
+ rend_client_refetch_v2_renddesc(rend_data);
/* move all pending streams back to renddesc_wait */
/* NOTE: We can now do this faster, if we use pending_entry_connections */
while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP,
AP_CONN_STATE_CIRCUIT_WAIT,
- rend_query->onion_address))) {
+ onion_address))) {
connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn));
conn->state = AP_CONN_STATE_RENDDESC_WAIT;
}
@@ -1080,7 +1095,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro,
}
log_info(LD_REND,"%d options left for %s.",
smartlist_len(ent->parsed->intro_nodes),
- escaped_safe_str_client(rend_query->onion_address));
+ escaped_safe_str_client(onion_address));
return 1;
}
@@ -1221,10 +1236,11 @@ rend_client_desc_trynow(const char *query)
rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data;
if (!rend_data)
continue;
- if (rend_cmp_service_ids(query, rend_data->onion_address))
+ const char *onion_address = rend_data_get_address(rend_data);
+ if (rend_cmp_service_ids(query, onion_address))
continue;
assert_connection_ok(base_conn, now);
- if (rend_cache_lookup_entry(rend_data->onion_address, -1,
+ if (rend_cache_lookup_entry(onion_address, -1,
&entry) == 0 &&
rend_client_any_intro_points_usable(entry)) {
/* either this fetch worked, or it failed but there was a
@@ -1259,11 +1275,12 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
{
unsigned int have_onion = 0;
rend_cache_entry_t *cache_entry = NULL;
+ const char *onion_address = rend_data_get_address(rend_data);
+ rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data);
- if (*rend_data->onion_address != '\0') {
+ if (onion_address[0] != '\0') {
/* Ignore return value; we find an entry, or we don't. */
- (void) rend_cache_lookup_entry(rend_data->onion_address, -1,
- &cache_entry);
+ (void) rend_cache_lookup_entry(onion_address, -1, &cache_entry);
have_onion = 1;
}
@@ -1277,17 +1294,17 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data)
/* Remove the HS's entries in last_hid_serv_requests. */
if (have_onion) {
unsigned int replica;
- for (replica = 0; replica < ARRAY_LENGTH(rend_data->descriptor_id);
+ for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id);
replica++) {
- const char *desc_id = rend_data->descriptor_id[replica];
+ const char *desc_id = rend_data_v2->descriptor_id[replica];
purge_hid_serv_from_last_hid_serv_requests(desc_id);
}
log_info(LD_REND, "Connection attempt for %s has ended; "
"cleaning up temporary state.",
- safe_str_client(rend_data->onion_address));
+ 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->desc_id_fetch);
+ purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch);
}
}
@@ -1301,12 +1318,13 @@ rend_client_get_random_intro(const rend_data_t *rend_query)
int ret;
extend_info_t *result;
rend_cache_entry_t *entry;
+ const char *onion_address = rend_data_get_address(rend_query);
- ret = rend_cache_lookup_entry(rend_query->onion_address, -1, &entry);
+ ret = rend_cache_lookup_entry(onion_address, -1, &entry);
if (ret < 0 || !rend_client_any_intro_points_usable(entry)) {
log_warn(LD_REND,
"Query '%s' didn't have valid rend desc in cache. Failing.",
- safe_str_client(rend_query->onion_address));
+ safe_str_client(onion_address));
/* XXX: Should we refetch the descriptor here if the IPs are not usable
* anymore ?. */
return NULL;
diff --git a/src/or/rendclient.h b/src/or/rendclient.h
index b8f8c2f871..164305a773 100644
--- a/src/or/rendclient.h
+++ b/src/or/rendclient.h
@@ -27,7 +27,7 @@ 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_query,
+ rend_data_t *rend_data,
unsigned int failure_type);
int rend_client_rendezvous_acked(origin_circuit_t *circ,
diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c
index d9d39b1f19..bc53762fb6 100644
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@ -12,9 +12,11 @@
#include "circuitbuild.h"
#include "config.h"
#include "control.h"
+#include "hs_common.h"
#include "rendclient.h"
#include "rendcommon.h"
#include "rendmid.h"
+#include "hs_intropoint.h"
#include "rendservice.h"
#include "rephist.h"
#include "router.h"
@@ -761,7 +763,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
switch (command) {
case RELAY_COMMAND_ESTABLISH_INTRO:
if (or_circ)
- r = rend_mid_establish_intro(or_circ,payload,length);
+ r = hs_intro_received_establish_intro(or_circ,payload,length);
break;
case RELAY_COMMAND_ESTABLISH_RENDEZVOUS:
if (or_circ)
@@ -769,7 +771,7 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
break;
case RELAY_COMMAND_INTRODUCE1:
if (or_circ)
- r = rend_mid_introduce(or_circ,payload,length);
+ r = hs_intro_received_introduce1(or_circ,payload,length);
break;
case RELAY_COMMAND_INTRODUCE2:
if (origin_circ)
@@ -804,124 +806,6 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
command);
}
-/** Allocate and return a new rend_data_t with the same
- * contents as <b>query</b>. */
-rend_data_t *
-rend_data_dup(const rend_data_t *data)
-{
- rend_data_t *data_dup;
- tor_assert(data);
- data_dup = tor_memdup(data, sizeof(rend_data_t));
- data_dup->hsdirs_fp = smartlist_new();
- SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp,
- smartlist_add(data_dup->hsdirs_fp,
- tor_memdup(fp, DIGEST_LEN)));
- return data_dup;
-}
-
-/** Compute descriptor ID for each replicas and save them. A valid onion
- * address must be present in the <b>rend_data</b>.
- *
- * Return 0 on success else -1. */
-static int
-compute_desc_id(rend_data_t *rend_data)
-{
- int ret = 0;
- unsigned replica;
- time_t now = time(NULL);
-
- tor_assert(rend_data);
-
- /* Compute descriptor ID for each replicas. */
- for (replica = 0; replica < ARRAY_LENGTH(rend_data->descriptor_id);
- replica++) {
- ret = rend_compute_v2_desc_id(rend_data->descriptor_id[replica],
- rend_data->onion_address,
- rend_data->descriptor_cookie,
- now, replica);
- if (ret < 0) {
- goto end;
- }
- }
-
- end:
- return ret;
-}
-
-/** Allocate and initialize a rend_data_t object for a service using the
- * given arguments. Only the <b>onion_address</b> is not optional.
- *
- * Return a valid rend_data_t pointer. */
-rend_data_t *
-rend_data_service_create(const char *onion_address, const char *pk_digest,
- const uint8_t *cookie, rend_auth_type_t auth_type)
-{
- rend_data_t *rend_data = tor_malloc_zero(sizeof(*rend_data));
-
- /* We need at least one else the call is wrong. */
- tor_assert(onion_address != NULL);
-
- if (pk_digest) {
- memcpy(rend_data->rend_pk_digest, pk_digest,
- sizeof(rend_data->rend_pk_digest));
- }
- if (cookie) {
- memcpy(rend_data->rend_cookie, cookie,
- sizeof(rend_data->rend_cookie));
- }
-
- strlcpy(rend_data->onion_address, onion_address,
- sizeof(rend_data->onion_address));
- rend_data->auth_type = auth_type;
- /* Won't be used but still need to initialize it for rend_data dup and
- * free. */
- rend_data->hsdirs_fp = smartlist_new();
-
- return rend_data;
-}
-
-/** Allocate and initialize a rend_data_t object for a client request using
- * the given arguments. Either an onion address or a descriptor ID is
- * needed. Both can be given but only the onion address will be used to make
- * the descriptor fetch.
- *
- * Return a valid rend_data_t pointer or NULL on error meaning the
- * descriptor IDs couldn't be computed from the given data. */
-rend_data_t *
-rend_data_client_create(const char *onion_address, const char *desc_id,
- const char *cookie, rend_auth_type_t auth_type)
-{
- rend_data_t *rend_data = tor_malloc_zero(sizeof(*rend_data));
-
- /* We need at least one else the call is wrong. */
- tor_assert(onion_address != NULL || desc_id != NULL);
-
- if (cookie) {
- memcpy(rend_data->descriptor_cookie, cookie,
- sizeof(rend_data->descriptor_cookie));
- }
- if (desc_id) {
- memcpy(rend_data->desc_id_fetch, desc_id,
- sizeof(rend_data->desc_id_fetch));
- }
- if (onion_address) {
- strlcpy(rend_data->onion_address, onion_address,
- sizeof(rend_data->onion_address));
- if (compute_desc_id(rend_data) < 0) {
- goto error;
- }
- }
-
- rend_data->auth_type = auth_type;
- rend_data->hsdirs_fp = smartlist_new();
-
- return rend_data;
-
- error:
- rend_data_free(rend_data);
- return NULL;
-}
-
/** Determine the routers that are responsible for <b>id</b> (binary) and
* add pointers to those routers' routerstatus_t to <b>responsible_dirs</b>.
* Return -1 if we're returning an empty smartlist, else return 0.
@@ -1116,3 +1000,32 @@ assert_circ_anonymity_ok(origin_circuit_t *circ,
}
}
+/* Return 1 iff the given <b>digest</b> of a permenanent hidden service key is
+ * equal to the digest in the origin circuit <b>ocirc</b> of its rend data .
+ * If the rend data doesn't exist, 0 is returned. This function is agnostic to
+ * the rend data version. */
+int
+rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc,
+ const uint8_t *digest)
+{
+ size_t rend_pk_digest_len;
+ const uint8_t *rend_pk_digest;
+
+ tor_assert(ocirc);
+ tor_assert(digest);
+
+ if (ocirc->rend_data == NULL) {
+ goto no_match;
+ }
+
+ rend_pk_digest = rend_data_get_pk_digest(ocirc->rend_data,
+ &rend_pk_digest_len);
+ if (tor_memeq(rend_pk_digest, digest, rend_pk_digest_len)) {
+ goto match;
+ }
+ no_match:
+ return 0;
+ match:
+ return 1;
+}
+
diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h
index 090e6f25e0..942ace5761 100644
--- a/src/or/rendcommon.h
+++ b/src/or/rendcommon.h
@@ -18,19 +18,6 @@ typedef enum rend_intro_point_failure_t {
INTRO_POINT_FAILURE_UNREACHABLE = 2,
} rend_intro_point_failure_t;
-/** Free all storage associated with <b>data</b> */
-static inline void
-rend_data_free(rend_data_t *data)
-{
- if (!data) {
- return;
- }
- /* Cleanup the HSDir identity digest. */
- SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d));
- smartlist_free(data->hsdirs_fp);
- tor_free(data);
-}
-
int rend_cmp_service_ids(const char *one, const char *two);
void rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint,
@@ -60,15 +47,8 @@ void rend_get_descriptor_id_bytes(char *descriptor_id_out,
int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs,
const char *id);
-rend_data_t *rend_data_dup(const rend_data_t *data);
-rend_data_t *rend_data_client_create(const char *onion_address,
- const char *desc_id,
- const char *cookie,
- rend_auth_type_t auth_type);
-rend_data_t *rend_data_service_create(const char *onion_address,
- const char *pk_digest,
- const uint8_t *cookie,
- rend_auth_type_t auth_type);
+int rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc,
+ const uint8_t *digest);
char *rend_auth_encode_cookie(const uint8_t *cookie_in,
rend_auth_type_t auth_type);
diff --git a/src/or/rendmid.c b/src/or/rendmid.c
index ca0ad7b0d4..57c8cfac92 100644
--- a/src/or/rendmid.c
+++ b/src/or/rendmid.c
@@ -11,16 +11,19 @@
#include "circuitlist.h"
#include "circuituse.h"
#include "config.h"
+#include "crypto.h"
#include "relay.h"
#include "rendmid.h"
#include "rephist.h"
+#include "hs_circuitmap.h"
+#include "hs_intropoint.h"
/** Respond to an ESTABLISH_INTRO cell by checking the signed data and
* setting the circuit's purpose and service pk digest.
*/
int
-rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
- size_t request_len)
+rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
{
crypto_pk_t *pk = NULL;
char buf[DIGEST_LEN+9];
@@ -32,15 +35,14 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
int reason = END_CIRC_REASON_INTERNAL;
log_info(LD_REND,
- "Received an ESTABLISH_INTRO request on circuit %u",
+ "Received a legacy ESTABLISH_INTRO request on circuit %u",
(unsigned) circ->p_circ_id);
- if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) {
- log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
- "Rejecting ESTABLISH_INTRO on non-OR or non-edge circuit.");
+ if (!hs_intro_circuit_is_suitable_for_establish_intro(circ)) {
reason = END_CIRC_REASON_TORPROTOCOL;
goto err;
}
+
if (request_len < 2+DIGEST_LEN)
goto truncated;
/* First 2 bytes: length of asn1-encoded key. */
@@ -94,7 +96,7 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
/* Close any other intro circuits with the same pk. */
c = NULL;
- while ((c = circuit_get_intro_point((const uint8_t *)pk_digest))) {
+ while ((c = hs_circuitmap_get_intro_circ_v2((const uint8_t *)pk_digest))) {
log_info(LD_REND, "Replacing old circuit for service %s",
safe_str(serviceid));
circuit_mark_for_close(TO_CIRCUIT(c), END_CIRC_REASON_FINISHED);
@@ -102,16 +104,14 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
}
/* Acknowledge the request. */
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
- RELAY_COMMAND_INTRO_ESTABLISHED,
- "", 0, NULL)<0) {
+ if (hs_intro_send_intro_established_cell(circ) < 0) {
log_info(LD_GENERAL, "Couldn't send INTRO_ESTABLISHED cell.");
- goto err;
+ goto err_no_close;
}
/* Now, set up this circuit. */
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
- circuit_set_intro_point_digest(circ, (uint8_t *)pk_digest);
+ hs_circuitmap_register_intro_circ_v2(circ, (uint8_t *)pk_digest);
log_info(LD_REND,
"Established introduction point on circuit %u for service %s",
@@ -122,8 +122,9 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
log_warn(LD_PROTOCOL, "Rejecting truncated ESTABLISH_INTRO cell.");
reason = END_CIRC_REASON_TORPROTOCOL;
err:
- if (pk) crypto_pk_free(pk);
circuit_mark_for_close(TO_CIRCUIT(circ), reason);
+ err_no_close:
+ if (pk) crypto_pk_free(pk);
return -1;
}
@@ -132,8 +133,8 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
* INTRODUCE2 cell.
*/
int
-rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
- size_t request_len)
+rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len)
{
or_circuit_t *intro_circ;
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
@@ -142,26 +143,10 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
log_info(LD_REND, "Received an INTRODUCE1 request on circuit %u",
(unsigned)circ->p_circ_id);
- if (circ->base_.purpose != CIRCUIT_PURPOSE_OR || circ->base_.n_chan) {
- log_warn(LD_PROTOCOL,
- "Rejecting INTRODUCE1 on non-OR or non-edge circuit %u.",
- (unsigned)circ->p_circ_id);
- goto err;
- }
-
- /* We have already done an introduction on this circuit but we just
- received a request for another one. We block it since this might
- be an attempt to DoS a hidden service (#15515). */
- if (circ->already_received_introduce1) {
- log_fn(LOG_PROTOCOL_WARN, LD_REND,
- "Blocking multiple introductions on the same circuit. "
- "Someone might be trying to attack a hidden service through "
- "this relay.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_TORPROTOCOL);
- return -1;
- }
-
- circ->already_received_introduce1 = 1;
+ /* At this point, we know that the circuit is valid for an INTRODUCE1
+ * because the validation has been made before calling this function. */
+ tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_OR);
+ tor_assert(!circ->base_.n_chan);
/* We could change this to MAX_HEX_NICKNAME_LEN now that 0.0.9.x is
* obsolete; however, there isn't much reason to do so, and we're going
@@ -180,7 +165,7 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
/* The first 20 bytes are all we look at: they have a hash of the service's
* PK. */
- intro_circ = circuit_get_intro_point((const uint8_t*)request);
+ intro_circ = hs_circuitmap_get_intro_circ_v2((const uint8_t*)request);
if (!intro_circ) {
log_info(LD_REND,
"No intro circ found for INTRODUCE1 cell (%s) from circuit %u; "
@@ -201,14 +186,15 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
(char*)request, request_len, NULL)) {
log_warn(LD_GENERAL,
"Unable to send INTRODUCE2 cell to Tor client.");
- goto err;
+ /* Stop right now, the circuit has been closed. */
+ return -1;
}
/* And send an ack down the client's circuit. Empty body means succeeded. */
if (relay_send_command_from_edge(0,TO_CIRCUIT(circ),
RELAY_COMMAND_INTRODUCE_ACK,
NULL,0,NULL)) {
log_warn(LD_GENERAL, "Unable to send INTRODUCE_ACK cell to Tor client.");
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
+ /* Stop right now, the circuit has been closed. */
return -1;
}
@@ -220,8 +206,6 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
RELAY_COMMAND_INTRODUCE_ACK,
nak_body, 1, NULL)) {
log_warn(LD_GENERAL, "Unable to send NAK to Tor client.");
- /* Is this right? */
- circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL);
}
return -1;
}
@@ -258,7 +242,7 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
goto err;
}
- if (circuit_get_rendezvous(request)) {
+ if (hs_circuitmap_get_rend_circ(request)) {
log_warn(LD_PROTOCOL,
"Duplicate rendezvous cookie in ESTABLISH_RENDEZVOUS.");
goto err;
@@ -269,12 +253,12 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
RELAY_COMMAND_RENDEZVOUS_ESTABLISHED,
"", 0, NULL)<0) {
log_warn(LD_PROTOCOL, "Couldn't send RENDEZVOUS_ESTABLISHED cell.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
+ /* Stop right now, the circuit has been closed. */
+ return -1;
}
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_POINT_WAITING);
- circuit_set_rendezvous_cookie(circ, request);
+ hs_circuitmap_register_rend_circ(circ, request);
base16_encode(hexid,9,(char*)request,4);
@@ -323,7 +307,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
"Got request for rendezvous from circuit %u to cookie %s.",
(unsigned)circ->p_circ_id, hexid);
- rend_circ = circuit_get_rendezvous(request);
+ rend_circ = hs_circuitmap_get_rend_circ(request);
if (!rend_circ) {
log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
"Rejecting RENDEZVOUS1 cell with unrecognized rendezvous cookie %s.",
@@ -346,7 +330,8 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
log_warn(LD_GENERAL,
"Unable to send RENDEZVOUS2 cell to client on circuit %u.",
(unsigned)rend_circ->p_circ_id);
- goto err;
+ /* Stop right now, the circuit has been closed. */
+ return -1;
}
/* Join the circuits. */
@@ -357,7 +342,7 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_ESTABLISHED);
circuit_change_purpose(TO_CIRCUIT(rend_circ),
CIRCUIT_PURPOSE_REND_ESTABLISHED);
- circuit_set_rendezvous_cookie(circ, NULL);
+ hs_circuitmap_remove_circuit(circ);
rend_circ->rend_splice = circ;
circ->rend_splice = rend_circ;
diff --git a/src/or/rendmid.h b/src/or/rendmid.h
index 10d1287085..347d745853 100644
--- a/src/or/rendmid.h
+++ b/src/or/rendmid.h
@@ -12,10 +12,10 @@
#ifndef TOR_RENDMID_H
#define TOR_RENDMID_H
-int rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request,
- size_t request_len);
-int rend_mid_introduce(or_circuit_t *circ, const uint8_t *request,
- size_t request_len);
+int rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
+int rend_mid_introduce_legacy(or_circuit_t *circ, const uint8_t *request,
+ size_t request_len);
int rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request,
size_t request_len);
int rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request,
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index da200d1381..09abc205dc 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -17,6 +17,7 @@
#include "config.h"
#include "control.h"
#include "directory.h"
+#include "hs_common.h"
#include "main.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -78,6 +79,10 @@ static int rend_service_check_private_dir(const or_options_t *options,
static int rend_service_check_private_dir_impl(const or_options_t *options,
const rend_service_t *s,
int create);
+static const smartlist_t* rend_get_service_list(
+ const smartlist_t* substitute_service_list);
+static smartlist_t* rend_get_service_list_mutable(
+ smartlist_t* substitute_service_list);
/** Represents the mapping from a virtual port of a rendezvous service to
* a real port on some IP.
@@ -105,9 +110,9 @@ struct rend_service_port_config_s {
/** If we can't build our intro circuits, don't retry for this long. */
#define INTRO_CIRC_RETRY_PERIOD (60*5)
-/** Don't try to build more than this many circuits before giving up
- * for a while.*/
-#define MAX_INTRO_CIRCS_PER_PERIOD 10
+/** How many times will a hidden service operator attempt to connect to
+ * a requested rendezvous point before giving up? */
+#define MAX_REND_FAILURES 1
/** How many seconds should we spend trying to connect to a requested
* rendezvous point before giving up? */
#define MAX_REND_TIMEOUT 30
@@ -136,18 +141,58 @@ static const char *hostname_fname = "hostname";
static const char *client_keys_fname = "client_keys";
static const char *sos_poison_fname = "onion_service_non_anonymous";
+/** A list of rend_service_t's for services run on this OP.
+ */
+static smartlist_t *rend_service_list = NULL;
+
+/* Like rend_get_service_list_mutable, but returns a read-only list. */
+static const smartlist_t*
+rend_get_service_list(const smartlist_t* substitute_service_list)
+{
+ /* It is safe to cast away the const here, because
+ * rend_get_service_list_mutable does not actually modify the list */
+ return rend_get_service_list_mutable((smartlist_t*)substitute_service_list);
+}
+
+/* Return a mutable list of hidden services.
+ * If substitute_service_list is not NULL, return it.
+ * Otherwise, check if the global rend_service_list is non-NULL, and if so,
+ * return it.
+ * Otherwise, log a BUG message and return NULL.
+ * */
+static smartlist_t*
+rend_get_service_list_mutable(smartlist_t* substitute_service_list)
+{
+ if (substitute_service_list) {
+ return substitute_service_list;
+ }
+
+ /* If no special service list is provided, then just use the global one. */
+
+ if (BUG(!rend_service_list)) {
+ /* No global HS list, which is a programmer error. */
+ return NULL;
+ }
+
+ return rend_service_list;
+}
+
+/** Tells if onion service <b>s</b> is ephemeral.
+ */
+static unsigned int
+rend_service_is_ephemeral(const struct rend_service_t *s)
+{
+ return (s->directory == NULL);
+}
+
/** Returns a escaped string representation of the service, <b>s</b>.
*/
static const char *
rend_service_escaped_dir(const struct rend_service_t *s)
{
- return (s->directory) ? escaped(s->directory) : "[EPHEMERAL]";
+ return rend_service_is_ephemeral(s) ? "[EPHEMERAL]" : escaped(s->directory);
}
-/** A list of rend_service_t's for services run on this OP.
- */
-static smartlist_t *rend_service_list = NULL;
-
/** Return the number of rendezvous services we have configured. */
int
num_rend_services(void)
@@ -243,17 +288,13 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
int i;
rend_service_port_config_t *p;
- smartlist_t *s_list;
- /* If no special service list is provided, then just use the global one. */
- if (!service_list) {
- if (BUG(!rend_service_list)) {
- /* No global HS list, which is a failure. */
- return -1;
- }
+ tor_assert(service);
- s_list = rend_service_list;
- } else {
- s_list = service_list;
+ 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;
}
service->intro_nodes = smartlist_new();
@@ -261,7 +302,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
if (service->max_streams_per_circuit < 0) {
log_warn(LD_CONFIG, "Hidden service (%s) configured with negative max "
- "streams per circuit; ignoring.",
+ "streams per circuit.",
rend_service_escaped_dir(service));
rend_service_free(service);
return -1;
@@ -270,7 +311,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
if (service->max_streams_close_circuit < 0 ||
service->max_streams_close_circuit > 1) {
log_warn(LD_CONFIG, "Hidden service (%s) configured with invalid "
- "max streams handling; ignoring.",
+ "max streams handling.",
rend_service_escaped_dir(service));
rend_service_free(service);
return -1;
@@ -280,15 +321,14 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
(!service->clients ||
smartlist_len(service->clients) == 0)) {
log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but no "
- "clients; ignoring.",
+ "clients.",
rend_service_escaped_dir(service));
rend_service_free(service);
return -1;
}
if (!service->ports || !smartlist_len(service->ports)) {
- log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured; "
- "ignoring.",
+ log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
rend_service_escaped_dir(service));
rend_service_free(service);
return -1;
@@ -309,22 +349,22 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
* lock file. But this is enough to detect a simple mistake that
* at least one person has actually made.
*/
- if (service->directory != NULL) {
+ tor_assert(s_list);
+ if (!rend_service_is_ephemeral(service)) {
/* Skip dupe for ephemeral services. */
SMARTLIST_FOREACH(s_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, ignoring.",
+ "directory %s.",
rend_service_escaped_dir(service));
rend_service_free(service);
return -1;
}
}
- smartlist_add(s_list, service);
- log_debug(LD_REND,"Configuring service with directory \"%s\"",
- service->directory);
+ log_debug(LD_REND,"Configuring service with directory %s",
+ rend_service_escaped_dir(service));
for (i = 0; i < smartlist_len(service->ports); ++i) {
p = smartlist_get(service->ports, i);
if (!(p->is_unix_addr)) {
@@ -338,14 +378,19 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service)
"Service maps port %d to socket at \"%s\"",
p->virtual_port, p->unix_addr);
#else
- log_debug(LD_REND,
- "Service maps port %d to an AF_UNIX socket, but we "
- "have no AF_UNIX support on this platform. This is "
- "probably a bug.",
- p->virtual_port);
+ log_warn(LD_BUG,
+ "Service maps port %d to an AF_UNIX socket, but we "
+ "have no AF_UNIX support on this platform. This is "
+ "probably a bug.",
+ p->virtual_port);
+ rend_service_free(service);
+ return -1;
#endif /* defined(HAVE_SYS_UN_H) */
}
}
+ /* The service passed all the checks */
+ tor_assert(s_list);
+ smartlist_add(s_list, service);
return 0;
}
/* NOTREACHED */
@@ -367,9 +412,9 @@ rend_service_port_config_new(const char *socket_path)
return conf;
}
-/** Parses a real-port to virtual-port mapping separated by the provided
- * separator and returns a new rend_service_port_config_t, or NULL and an
- * optional error string on failure.
+/** Parses a virtual-port to real-port/socket mapping separated by
+ * the provided separator and returns a new rend_service_port_config_t,
+ * or NULL and an optional error string on failure.
*
* The format is: VirtualPort SEP (IP|RealPort|IP:RealPort|'socket':path)?
*
@@ -394,14 +439,12 @@ rend_service_parse_port_config(const char *string, const char *sep,
smartlist_split_string(sl, string, sep,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
if (smartlist_len(sl) < 1 || BUG(smartlist_len(sl) > 2)) {
- if (err_msg_out)
- err_msg = tor_strdup("Bad syntax in hidden service port configuration.");
+ err_msg = tor_strdup("Bad syntax in hidden service port configuration.");
goto err;
}
virtport = (int)tor_parse_long(smartlist_get(sl,0), 10, 1, 65535, NULL,NULL);
if (!virtport) {
- if (err_msg_out)
- tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service "
+ tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service "
"port configuration", escaped(smartlist_get(sl,0)));
goto err;
@@ -429,10 +472,8 @@ rend_service_parse_port_config(const char *string, const char *sep,
} else if (strchr(addrport, ':') || strchr(addrport, '.')) {
/* else try it as an IP:port pair if it has a : or . in it */
if (tor_addr_port_lookup(addrport, &addr, &p)<0) {
- if (err_msg_out)
- err_msg = tor_strdup("Unparseable address in hidden service port "
- "configuration.");
-
+ err_msg = tor_strdup("Unparseable address in hidden service port "
+ "configuration.");
goto err;
}
realport = p?p:virtport;
@@ -440,11 +481,9 @@ rend_service_parse_port_config(const char *string, const char *sep,
/* No addr:port, no addr -- must be port. */
realport = (int)tor_parse_long(addrport, 10, 1, 65535, NULL, NULL);
if (!realport) {
- if (err_msg_out)
- tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in "
- "hidden service port configuration.",
- escaped(addrport));
-
+ tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in "
+ "hidden service port configuration.",
+ escaped(addrport));
goto err;
}
tor_addr_from_ipv4h(&addr, 0x7F000001u); /* Default to 127.0.0.1 */
@@ -463,7 +502,11 @@ rend_service_parse_port_config(const char *string, const char *sep,
err:
tor_free(addrport);
- if (err_msg_out) *err_msg_out = err_msg;
+ if (err_msg_out != NULL) {
+ *err_msg_out = err_msg;
+ } else {
+ tor_free(err_msg);
+ }
SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
smartlist_free(sl);
@@ -503,33 +546,102 @@ rend_service_check_dir_and_add(smartlist_t *service_list,
return -1;
}
- if (validate_only) {
- rend_service_free(service);
- return 0;
- } else {
- /* Use service_list for unit tests */
- smartlist_t *s_list = NULL;
- /* If no special service list is provided, then just use the global one. */
- if (!service_list) {
- if (BUG(!rend_service_list)) {
- /* No global HS list, which is a failure, because we plan on adding to
- * it */
- return -1;
- }
- s_list = rend_service_list;
- } else {
- s_list = service_list;
+ 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);
+}
+
+/* If this is a reload and there were hidden services configured before,
+ * keep the introduction points that are still needed and close the
+ * other ones. */
+STATIC void
+prune_services_on_reload(smartlist_t *old_service_list,
+ smartlist_t *new_service_list)
+{
+ origin_circuit_t *ocirc = NULL;
+ smartlist_t *surviving_services = NULL;
+
+ tor_assert(old_service_list);
+ tor_assert(new_service_list);
+
+ /* This contains all _existing_ services that survives the relaod that is
+ * that haven't been removed from the configuration. The difference between
+ * this list and the new service list is that the new list can possibly
+ * contain newly configured service that have no introduction points opened
+ * yet nor key material loaded or generated. */
+ surviving_services = smartlist_new();
+
+ /* Preserve the existing ephemeral services.
+ *
+ * This is the ephemeral service equivalent of the "Copy introduction
+ * points to new services" block, except there's no copy required since
+ * the service structure isn't regenerated.
+ *
+ * After this is done, all ephemeral services will be:
+ * * Removed from old_service_list, so the equivalent non-ephemeral code
+ * will not attempt to preserve them.
+ * * Added to the new_service_list (that previously only had the
+ * services listed in the configuration).
+ * * Added to surviving_services, which is the list of services that
+ * will NOT have their intro point closed.
+ */
+ SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) {
+ if (rend_service_is_ephemeral(old)) {
+ SMARTLIST_DEL_CURRENT(old_service_list, old);
+ smartlist_add(surviving_services, old);
+ smartlist_add(new_service_list, old);
}
- /* s_list can not be NULL here - if both service_list and rend_service_list
- * are NULL, and validate_only is false, we exit earlier in the function
- */
- if (BUG(!s_list)) {
- return -1;
+ } SMARTLIST_FOREACH_END(old);
+
+ /* Copy introduction points to new services. This is O(n^2), but it's only
+ * called on reconfigure, so it's ok performance wise. */
+ SMARTLIST_FOREACH_BEGIN(new_service_list, rend_service_t *, new) {
+ SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) {
+ /* Skip ephemeral services as we only want to copy introduction points
+ * from current services to newly configured one that already exists.
+ * The same directory means it's the same service. */
+ if (rend_service_is_ephemeral(new) || rend_service_is_ephemeral(old) ||
+ strcmp(old->directory, new->directory)) {
+ continue;
+ }
+ smartlist_add_all(new->intro_nodes, old->intro_nodes);
+ smartlist_clear(old->intro_nodes);
+ smartlist_add_all(new->expiring_nodes, old->expiring_nodes);
+ smartlist_clear(old->expiring_nodes);
+ /* This regular service will survive the closing IPs step after. */
+ smartlist_add(surviving_services, old);
+ break;
+ } SMARTLIST_FOREACH_END(old);
+ } SMARTLIST_FOREACH_END(new);
+
+ /* For every service introduction circuit we can find, see if we have a
+ * 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);
+ 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. */
+ keep_it = 1;
+ break;
+ }
+ } SMARTLIST_FOREACH_END(s);
+ if (keep_it) {
+ continue;
}
- /* Ignore service failures until 030 */
- rend_add_service(s_list, service);
- return 0;
- }
+ log_info(LD_REND, "Closing intro point %s for service %s.",
+ safe_str_client(extend_info_describe(
+ ocirc->build_state->chosen_exit)),
+ safe_str_client(rend_data_get_address(ocirc->rend_data)));
+ /* Reason is FINISHED because service has been removed and thus the
+ * circuit is considered old/uneeded. */
+ circuit_mark_for_close(TO_CIRCUIT(ocirc), END_CIRC_REASON_FINISHED);
+ }
+ smartlist_free(surviving_services);
}
/** Set up rend_service_list, based on the values of HiddenServiceDir and
@@ -544,21 +656,23 @@ rend_config_services(const or_options_t *options, int validate_only)
rend_service_t *service = NULL;
rend_service_port_config_t *portcfg;
smartlist_t *old_service_list = NULL;
+ smartlist_t *temp_service_list = NULL;
int ok = 0;
+ int rv = -1;
- if (!validate_only) {
- old_service_list = rend_service_list;
- rend_service_list = smartlist_new();
- }
+ /* Use a temporary service list, so that we can check the new services'
+ * consistency with each other */
+ temp_service_list = smartlist_new();
for (line = options->RendConfigLines; line; line = line->next) {
if (!strcasecmp(line->key, "HiddenServiceDir")) {
/* register the service we just finished parsing
* this code registers every service except the last one parsed,
* which is registered below the loop */
- if (rend_service_check_dir_and_add(NULL, options, service,
+ if (rend_service_check_dir_and_add(temp_service_list, options, service,
validate_only) < 0) {
- return -1;
+ service = NULL;
+ goto free_and_return;
}
service = tor_malloc_zero(sizeof(rend_service_t));
service->directory = tor_strdup(line->value);
@@ -570,8 +684,7 @@ rend_config_services(const or_options_t *options, int validate_only)
if (!service) {
log_warn(LD_CONFIG, "%s with no preceding HiddenServiceDir directive",
line->key);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
if (!strcasecmp(line->key, "HiddenServicePort")) {
char *err_msg = NULL;
@@ -580,8 +693,7 @@ rend_config_services(const or_options_t *options, int validate_only)
if (err_msg)
log_warn(LD_CONFIG, "%s", err_msg);
tor_free(err_msg);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
tor_assert(!err_msg);
smartlist_add(service->ports, portcfg);
@@ -592,12 +704,12 @@ rend_config_services(const or_options_t *options, int validate_only)
log_warn(LD_CONFIG,
"HiddenServiceAllowUnknownPorts should be 0 or 1, not %s",
line->value);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
log_info(LD_CONFIG,
"HiddenServiceAllowUnknownPorts=%d for %s",
- (int)service->allow_unknown_ports, service->directory);
+ (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,
@@ -606,12 +718,12 @@ rend_config_services(const or_options_t *options, int validate_only)
log_warn(LD_CONFIG,
"HiddenServiceDirGroupReadable should be 0 or 1, not %s",
line->value);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
log_info(LD_CONFIG,
"HiddenServiceDirGroupReadable=%d for %s",
- service->dir_group_readable, service->directory);
+ 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);
@@ -619,12 +731,12 @@ rend_config_services(const or_options_t *options, int validate_only)
log_warn(LD_CONFIG,
"HiddenServiceMaxStreams should be between 0 and %d, not %s",
65535, line->value);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
log_info(LD_CONFIG,
"HiddenServiceMaxStreams=%d for %s",
- service->max_streams_per_circuit, service->directory);
+ 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);
@@ -633,28 +745,26 @@ rend_config_services(const or_options_t *options, int validate_only)
"HiddenServiceMaxStreamsCloseCircuit should be 0 or 1, "
"not %s",
line->value);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
log_info(LD_CONFIG,
"HiddenServiceMaxStreamsCloseCircuit=%d for %s",
- (int)service->max_streams_close_circuit, service->directory);
+ (int)service->max_streams_close_circuit,
+ rend_service_escaped_dir(service));
} else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) {
service->n_intro_points_wanted =
(unsigned int) tor_parse_long(line->value, 10,
- NUM_INTRO_POINTS_DEFAULT,
- NUM_INTRO_POINTS_MAX, &ok, NULL);
+ 0, NUM_INTRO_POINTS_MAX, &ok, NULL);
if (!ok) {
log_warn(LD_CONFIG,
"HiddenServiceNumIntroductionPoints "
"should be between %d and %d, not %s",
- NUM_INTRO_POINTS_DEFAULT, NUM_INTRO_POINTS_MAX,
- line->value);
- rend_service_free(service);
- return -1;
+ 0, NUM_INTRO_POINTS_MAX, line->value);
+ goto free_and_return;
}
log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s",
- service->n_intro_points_wanted, service->directory);
+ service->n_intro_points_wanted,
+ rend_service_escaped_dir(service));
} else 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
@@ -665,8 +775,7 @@ rend_config_services(const or_options_t *options, int validate_only)
if (service->auth_type != REND_NO_AUTH) {
log_warn(LD_CONFIG, "Got multiple HiddenServiceAuthorizeClient "
"lines for a single service.");
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
type_names_split = smartlist_new();
smartlist_split_string(type_names_split, line->value, " ", 0, 2);
@@ -674,9 +783,7 @@ 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.");
- smartlist_free(type_names_split);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
authname = smartlist_get(type_names_split, 0);
if (!strcasecmp(authname, "basic")) {
@@ -690,8 +797,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);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
service->clients = smartlist_new();
if (smartlist_len(type_names_split) < 2) {
@@ -728,8 +834,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);
- rend_service_free(service);
- return -1;
+ goto free_and_return;
}
client = tor_malloc_zero(sizeof(rend_authorized_client_t));
client->client_name = tor_strdup(client_name);
@@ -751,109 +856,59 @@ 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");
- rend_service_free(service);
- return -1;
+ 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.");
- rend_service_free(service);
- return -1;
+ 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(NULL, options, service,
+ if (rend_service_check_dir_and_add(temp_service_list, options, service,
validate_only) < 0) {
- return -1;
+ service = NULL;
+ goto free_and_return;
}
+ service = NULL;
+
+ /* Free the newly added services if validating */
+ if (validate_only) {
+ rv = 0;
+ goto free_and_return;
+ }
+
+ /* Otherwise, use the newly added services as the new service list
+ * Since we have now replaced the global service list, from this point on we
+ * must succeed, or die trying. */
+ old_service_list = rend_service_list;
+ rend_service_list = temp_service_list;
+ temp_service_list = NULL;
/* If this is a reload and there were hidden services configured before,
* keep the introduction points that are still needed and close the
* other ones. */
if (old_service_list && !validate_only) {
- smartlist_t *surviving_services = smartlist_new();
-
- /* Preserve the existing ephemeral services.
- *
- * This is the ephemeral service equivalent of the "Copy introduction
- * points to new services" block, except there's no copy required since
- * the service structure isn't regenerated.
- *
- * After this is done, all ephemeral services will be:
- * * Removed from old_service_list, so the equivalent non-ephemeral code
- * will not attempt to preserve them.
- * * Added to the new rend_service_list (that previously only had the
- * services listed in the configuration).
- * * Added to surviving_services, which is the list of services that
- * will NOT have their intro point closed.
- */
- SMARTLIST_FOREACH(old_service_list, rend_service_t *, old, {
- if (!old->directory) {
- SMARTLIST_DEL_CURRENT(old_service_list, old);
- smartlist_add(surviving_services, old);
- smartlist_add(rend_service_list, old);
- }
- });
-
- /* Copy introduction points to new services. */
- /* XXXX This is O(n^2), but it's only called on reconfigure, so it's
- * probably ok? */
- SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, new) {
- SMARTLIST_FOREACH_BEGIN(old_service_list, rend_service_t *, old) {
- if (new->directory && old->directory &&
- !strcmp(old->directory, new->directory)) {
- smartlist_add_all(new->intro_nodes, old->intro_nodes);
- smartlist_clear(old->intro_nodes);
- smartlist_add_all(new->expiring_nodes, old->expiring_nodes);
- smartlist_clear(old->expiring_nodes);
- smartlist_add(surviving_services, old);
- break;
- }
- } SMARTLIST_FOREACH_END(old);
- } SMARTLIST_FOREACH_END(new);
-
- /* Close introduction circuits of services we don't serve anymore. */
- /* XXXX it would be nicer if we had a nicer abstraction to use here,
- * so we could just iterate over the list of services to close, but
- * once again, this isn't critical-path code. */
- SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
- if (!circ->marked_for_close &&
- circ->state == CIRCUIT_STATE_OPEN &&
- (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
- circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
- origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
- int keep_it = 0;
- tor_assert(oc->rend_data);
- SMARTLIST_FOREACH(surviving_services, rend_service_t *, ptr, {
- if (tor_memeq(ptr->pk_digest, oc->rend_data->rend_pk_digest,
- DIGEST_LEN)) {
- keep_it = 1;
- break;
- }
- });
- if (keep_it)
- continue;
- log_info(LD_REND, "Closing intro point %s for service %s.",
- safe_str_client(extend_info_describe(
- oc->build_state->chosen_exit)),
- oc->rend_data->onion_address);
- circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
- /* XXXX Is there another reason we should use here? */
- }
- }
- SMARTLIST_FOREACH_END(circ);
- smartlist_free(surviving_services);
- SMARTLIST_FOREACH(old_service_list, rend_service_t *, ptr,
- rend_service_free(ptr));
+ prune_services_on_reload(old_service_list, rend_service_list);
+ /* Every remaining service in the old list have been removed from the
+ * configuration so clean them up safely. */
+ SMARTLIST_FOREACH(old_service_list, rend_service_t *, s,
+ rend_service_free(s));
smartlist_free(old_service_list);
}
return 0;
+ free_and_return:
+ rend_service_free(service);
+ SMARTLIST_FOREACH(temp_service_list, rend_service_t *, ptr,
+ rend_service_free(ptr));
+ smartlist_free(temp_service_list);
+ return rv;
}
/** Add the ephemeral service <b>pk</b>/<b>ports</b> if possible, using
@@ -951,7 +1006,7 @@ rend_service_del_ephemeral(const char *service_id)
"removal.");
return -1;
}
- if (s->directory) {
+ if (!rend_service_is_ephemeral(s)) {
log_warn(LD_CONFIG, "Requested non-ephemeral Onion Service for removal.");
return -1;
}
@@ -968,12 +1023,13 @@ rend_service_del_ephemeral(const char *service_id)
circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
tor_assert(oc->rend_data);
- if (!tor_memeq(s->pk_digest, oc->rend_data->rend_pk_digest, DIGEST_LEN))
+ if (!rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) {
continue;
+ }
log_debug(LD_REND, "Closing intro point %s for service %s.",
safe_str_client(extend_info_describe(
oc->build_state->chosen_exit)),
- oc->rend_data->onion_address);
+ rend_data_get_address(oc->rend_data));
circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
}
} SMARTLIST_FOREACH_END(circ);
@@ -1067,7 +1123,7 @@ rend_service_sos_poison_path(const rend_service_t *service)
return rend_service_path(service, sos_poison_fname);
}
-/** Return True if hidden services <b>service> has been poisoned by single
+/** Return True if hidden services <b>service</b> has been poisoned by single
* onion mode. */
static int
service_is_single_onion_poisoned(const rend_service_t *service)
@@ -1080,7 +1136,7 @@ service_is_single_onion_poisoned(const rend_service_t *service)
return 0;
}
- if (!service->directory) {
+ if (rend_service_is_ephemeral(service)) {
return 0;
}
@@ -1132,8 +1188,13 @@ rend_service_verify_single_onion_poison(const rend_service_t* s,
}
/* Ephemeral services are checked at ADD_ONION time */
- if (!s->directory) {
- return 0;
+ if (BUG(rend_service_is_ephemeral(s))) {
+ return -1;
+ }
+
+ /* Service is expected to have a directory */
+ if (BUG(!s->directory)) {
+ return -1;
}
/* Services without keys are always ok - their keys will only ever be used
@@ -1176,7 +1237,7 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service,
int retval = -1;
char *poison_fname = NULL;
- if (!service->directory) {
+ if (rend_service_is_ephemeral(service)) {
log_info(LD_REND, "Ephemeral HS started in non-anonymous mode.");
return 0;
}
@@ -1226,7 +1287,7 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service,
return retval;
}
-/** We just got launched in Single Onion Mode. That's a non-anoymous mode for
+/** We just got launched in Single Onion Mode. That's a non-anonymous mode for
* hidden services. If s is new, we should mark its hidden service
* directory appropriately so that it is never launched as a location-private
* hidden service. (New directories don't have private key files.)
@@ -1243,6 +1304,16 @@ rend_service_poison_new_single_onion_dir(const rend_service_t *s,
/* We must only poison directories if we're in Single Onion mode */
tor_assert(rend_service_non_anonymous_mode_enabled(options));
+ /* Ephemeral services aren't allowed in non-anonymous mode */
+ if (BUG(rend_service_is_ephemeral(s))) {
+ return -1;
+ }
+
+ /* Service is expected to have a directory */
+ if (BUG(!s->directory)) {
+ return -1;
+ }
+
if (!rend_service_private_key_exists(s)) {
if (poison_new_single_onion_hidden_service_dir_impl(s, options)
< 0) {
@@ -1262,22 +1333,17 @@ rend_service_poison_new_single_onion_dir(const rend_service_t *s,
int
rend_service_load_all_keys(const smartlist_t *service_list)
{
- const smartlist_t *s_list = NULL;
- /* If no special service list is provided, then just use the global one. */
- if (!service_list) {
- if (BUG(!rend_service_list)) {
- return -1;
- }
- s_list = rend_service_list;
- } else {
- s_list = service_list;
+ /* Use service_list for unit tests */
+ const smartlist_t *s_list = rend_get_service_list(service_list);
+ if (BUG(!s_list)) {
+ return -1;
}
SMARTLIST_FOREACH_BEGIN(s_list, rend_service_t *, s) {
if (s->private_key)
continue;
- log_info(LD_REND, "Loading hidden-service keys from \"%s\"",
- s->directory);
+ log_info(LD_REND, "Loading hidden-service keys from %s",
+ rend_service_escaped_dir(s));
if (rend_service_load_keys(s) < 0)
return -1;
@@ -1309,9 +1375,9 @@ rend_services_add_filenames_to_lists(smartlist_t *open_lst,
if (!rend_service_list)
return;
SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) {
- if (s->directory) {
+ if (!rend_service_is_ephemeral(s)) {
rend_service_add_filenames_to_list(open_lst, s);
- smartlist_add(stat_lst, tor_strdup(s->directory));
+ smartlist_add_strdup(stat_lst, s->directory);
}
} SMARTLIST_FOREACH_END(s);
}
@@ -1806,7 +1872,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
const or_options_t *options = get_options();
char *err_msg = NULL;
int err_msg_severity = LOG_WARN;
- const char *stage_descr = NULL;
+ const char *stage_descr = NULL, *rend_pk_digest;
int reason = END_CIRC_REASON_TORPROTOCOL;
/* Service/circuit/key stuff we can learn before parsing */
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
@@ -1841,14 +1907,15 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
assert_circ_anonymity_ok(circuit, options);
tor_assert(circuit->rend_data);
+ /* XXX: This is version 2 specific (only one supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
/* We'll use this in a bazillion log messages */
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
/* look up service depending on circuit. */
- service =
- rend_service_get_by_pk_digest(circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_BUG,
"Internal error: Got an INTRODUCE2 cell on an intro "
@@ -2073,8 +2140,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit,
/* Fill in the circuit's state. */
launched->rend_data =
- rend_data_service_create(service->service_id,
- circuit->rend_data->rend_pk_digest,
+ rend_data_service_create(service->service_id, rend_pk_digest,
parsed_req->rc, service->auth_type);
launched->build_state->service_pending_final_cpath_ref =
@@ -3113,15 +3179,67 @@ count_intro_point_circuits(const rend_service_t *service)
circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) {
origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
if (oc->rend_data &&
- !rend_cmp_service_ids(service->service_id,
- oc->rend_data->onion_address))
+ rend_circuit_pk_digest_eq(oc, (uint8_t *) service->pk_digest)) {
num_ipos++;
+ }
}
}
SMARTLIST_FOREACH_END(circ);
return num_ipos;
}
+/* Given a buffer of at least RELAY_PAYLOAD_SIZE bytes in <b>cell_body_out</b>,
+ write the body of a legacy ESTABLISH_INTRO cell in it. Use <b>intro_key</b>
+ as the intro point auth key, and <b>rend_circ_nonce</b> as the circuit
+ 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)
+{
+ int retval = -1;
+ int r;
+ int len = 0;
+ char auth[DIGEST_LEN + 9];
+
+ tor_assert(intro_key);
+ tor_assert(rend_circ_nonce);
+
+ /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
+ r = crypto_pk_asn1_encode(intro_key, cell_body_out+2,
+ RELAY_PAYLOAD_SIZE-2);
+ if (r < 0) {
+ log_warn(LD_BUG, "Internal error; failed to establish intro point.");
+ goto err;
+ }
+ len = r;
+ set_uint16(cell_body_out, htons((uint16_t)len));
+ len += 2;
+ memcpy(auth, rend_circ_nonce, DIGEST_LEN);
+ memcpy(auth+DIGEST_LEN, "INTRODUCE", 9);
+ 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);
+ if (r<0) {
+ log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
+ goto err;
+ }
+ len += r;
+
+ retval = len;
+
+ err:
+ memwipe(auth, 0, sizeof(auth));
+
+ return retval;
+}
+
/** Called when we're done building a circuit to an introduction point:
* sends a RELAY_ESTABLISH_INTRO cell.
*/
@@ -3129,23 +3247,23 @@ void
rend_service_intro_has_opened(origin_circuit_t *circuit)
{
rend_service_t *service;
- size_t len;
- int r;
char buf[RELAY_PAYLOAD_SIZE];
- char auth[DIGEST_LEN + 9];
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
+ unsigned int expiring_nodes_len, num_ip_circuits, valid_ip_circuits = 0;
int reason = END_CIRC_REASON_TORPROTOCOL;
+ const char *rend_pk_digest;
tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
assert_circ_anonymity_ok(circuit, get_options());
tor_assert(circuit->cpath);
tor_assert(circuit->rend_data);
+ /* XXX: This is version 2 specific (only on supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
- service = rend_service_get_by_pk_digest(
- circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %u.",
safe_str_client(serviceid), (unsigned)circuit->base_.n_circ_id);
@@ -3153,13 +3271,22 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
goto err;
}
+ /* Take the current amount of expiring nodes and the current amount of IP
+ * circuits and compute how many valid IP circuits we have. */
+ expiring_nodes_len = (unsigned int) smartlist_len(service->expiring_nodes);
+ num_ip_circuits = count_intro_point_circuits(service);
+ /* Let's avoid an underflow. The valid_ip_circuits is initialized to 0 in
+ * case this condition turns out false because it means that all circuits
+ * are expiring so we need to keep this circuit. */
+ if (num_ip_circuits > expiring_nodes_len) {
+ valid_ip_circuits = num_ip_circuits - expiring_nodes_len;
+ }
+
/* If we already have enough introduction circuits for this service,
* redefine this one as a general circuit or close it, depending.
- * Substract the amount of expiring nodes here since the circuits are
+ * Substract the amount of expiring nodes here because the circuits are
* still opened. */
- if ((count_intro_point_circuits(service) -
- smartlist_len(service->expiring_nodes)) >
- service->n_intro_points_wanted) {
+ if (valid_ip_circuits > service->n_intro_points_wanted) {
const or_options_t *options = get_options();
/* Remove the intro point associated with this circuit, it's being
* repurposed or closed thus cleanup memory. */
@@ -3186,9 +3313,8 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_C_GENERAL);
{
- rend_data_t *rend_data = circuit->rend_data;
+ rend_data_free(circuit->rend_data);
circuit->rend_data = NULL;
- rend_data_free(rend_data);
}
{
crypto_pk_t *intro_key = circuit->intro_key;
@@ -3206,42 +3332,25 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
(unsigned)circuit->base_.n_circ_id, serviceid);
circuit_log_path(LOG_INFO, LD_REND, circuit);
- /* Use the intro key instead of the service key in ESTABLISH_INTRO. */
- crypto_pk_t *intro_key = circuit->intro_key;
- /* Build the payload for a RELAY_ESTABLISH_INTRO cell. */
- r = crypto_pk_asn1_encode(intro_key, buf+2,
- RELAY_PAYLOAD_SIZE-2);
- if (r < 0) {
- log_warn(LD_BUG, "Internal error; failed to establish intro point.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
- }
- len = r;
- set_uint16(buf, htons((uint16_t)len));
- len += 2;
- memcpy(auth, circuit->cpath->prev->rend_circ_nonce, DIGEST_LEN);
- memcpy(auth+DIGEST_LEN, "INTRODUCE", 9);
- if (crypto_digest(buf+len, auth, DIGEST_LEN+9))
- goto err;
- len += 20;
- note_crypto_pk_op(REND_SERVER);
- r = crypto_pk_private_sign_digest(intro_key, buf+len, sizeof(buf)-len,
- buf, len);
- if (r<0) {
- log_warn(LD_BUG, "Internal error: couldn't sign introduction request.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
- }
- len += r;
+ /* Send the ESTABLISH_INTRO cell */
+ {
+ ssize_t len;
+ len = encode_establish_intro_cell_legacy(buf, sizeof(buf),
+ circuit->intro_key,
+ circuit->cpath->prev->rend_circ_nonce);
+ if (len < 0) {
+ reason = END_CIRC_REASON_INTERNAL;
+ goto err;
+ }
- if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit),
- RELAY_COMMAND_ESTABLISH_INTRO,
- buf, len, circuit->cpath->prev)<0) {
- log_info(LD_GENERAL,
+ if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit),
+ RELAY_COMMAND_ESTABLISH_INTRO,
+ buf, len, circuit->cpath->prev)<0) {
+ log_info(LD_GENERAL,
"Couldn't send introduction request for service %s on circuit %u",
serviceid, (unsigned)circuit->base_.n_circ_id);
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
+ goto done;
+ }
}
/* We've attempted to use this circuit */
@@ -3253,7 +3362,6 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
circuit_mark_for_close(TO_CIRCUIT(circuit), reason);
done:
memwipe(buf, 0, sizeof(buf));
- memwipe(auth, 0, sizeof(auth));
memwipe(serviceid, 0, sizeof(serviceid));
return;
@@ -3272,22 +3380,24 @@ rend_service_intro_established(origin_circuit_t *circuit,
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
(void) request;
(void) request_len;
+ tor_assert(circuit->rend_data);
+ /* XXX: This is version 2 specific (only supported one for now). */
+ const char *rend_pk_digest =
+ (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
if (circuit->base_.purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
log_warn(LD_PROTOCOL,
"received INTRO_ESTABLISHED cell on non-intro circuit.");
goto err;
}
- tor_assert(circuit->rend_data);
- service = rend_service_get_by_pk_digest(
- circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_REND, "Unknown service on introduction circuit %u.",
(unsigned)circuit->base_.n_circ_id);
goto err;
}
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
/* We've just successfully established a intro circuit to one of our
* introduction point, account for it. */
intro = find_intro_point(circuit);
@@ -3330,6 +3440,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
char hexcookie[9];
int reason;
+ const char *rend_cookie, *rend_pk_digest;
tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
tor_assert(circuit->cpath);
@@ -3337,6 +3448,11 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
assert_circ_anonymity_ok(circuit, get_options());
tor_assert(circuit->rend_data);
+ /* XXX: This is version 2 specific (only one supported). */
+ rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data,
+ 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);
@@ -3346,9 +3462,9 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
hop = circuit->build_state->service_pending_final_cpath_ref->cpath;
- base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4);
+ base16_encode(hexcookie,9, rend_cookie,4);
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
- circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
log_info(LD_REND,
"Done building circuit %u to rendezvous with "
@@ -3377,8 +3493,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
circuit->build_state->pending_final_cpath = hop;
circuit->build_state->service_pending_final_cpath_ref->cpath = NULL;
- service = rend_service_get_by_pk_digest(
- circuit->rend_data->rend_pk_digest);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_GENERAL, "Internal error: unrecognized service ID on "
"rendezvous circuit.");
@@ -3387,7 +3502,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
}
/* All we need to do is send a RELAY_RENDEZVOUS1 cell... */
- memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN);
+ memcpy(buf, rend_cookie, REND_COOKIE_LEN);
if (crypto_dh_get_public(hop->rend_dh_handshake_state,
buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) {
log_warn(LD_GENERAL,"Couldn't get DH public key.");
@@ -3403,8 +3518,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit)
buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN,
circuit->cpath->prev)<0) {
log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell.");
- reason = END_CIRC_REASON_INTERNAL;
- goto err;
+ goto done;
}
crypto_dh_free(hop->rend_dh_handshake_state);
@@ -3451,8 +3565,8 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
origin_circuit_t *circ = NULL;
tor_assert(intro);
- while ((circ = circuit_get_next_by_pk_and_purpose(circ,pk_digest,
- CIRCUIT_PURPOSE_S_INTRO))) {
+ while ((circ = circuit_get_next_by_pk_and_purpose(circ,
+ (uint8_t *) pk_digest, CIRCUIT_PURPOSE_S_INTRO))) {
if (tor_memeq(circ->build_state->chosen_exit->identity_digest,
intro->extend_info->identity_digest, DIGEST_LEN) &&
circ->rend_data) {
@@ -3461,8 +3575,9 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
}
circ = NULL;
- while ((circ = circuit_get_next_by_pk_and_purpose(circ,pk_digest,
- CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
+ while ((circ = circuit_get_next_by_pk_and_purpose(circ,
+ (uint8_t *) pk_digest,
+ CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) {
if (tor_memeq(circ->build_state->chosen_exit->identity_digest,
intro->extend_info->identity_digest, DIGEST_LEN) &&
circ->rend_data) {
@@ -3501,7 +3616,7 @@ find_intro_point(origin_circuit_t *circ)
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO);
tor_assert(circ->rend_data);
- serviceid = circ->rend_data->onion_address;
+ serviceid = rend_data_get_address(circ->rend_data);
SMARTLIST_FOREACH(rend_service_list, rend_service_t *, s,
if (tor_memeq(s->service_id, serviceid, REND_SERVICE_ID_LEN_BASE32)) {
@@ -3583,7 +3698,7 @@ directory_post_to_hs_dir(rend_service_descriptor_t *renddesc,
DIRIND_ANONYMOUS, NULL,
desc->desc_str,
strlen(desc->desc_str),
- 0, rend_data);
+ 0, rend_data, NULL);
rend_data_free(rend_data);
base32_encode(desc_id_base32, sizeof(desc_id_base32),
desc->desc_id, DIGEST_LEN);
@@ -3815,6 +3930,19 @@ remove_invalid_intro_points(rend_service_t *service,
{
tor_assert(service);
+ /* Remove any expired nodes that doesn't have a circuit. */
+ SMARTLIST_FOREACH_BEGIN(service->expiring_nodes, rend_intro_point_t *,
+ intro) {
+ origin_circuit_t *intro_circ =
+ find_intro_circuit(intro, service->pk_digest);
+ if (intro_circ) {
+ continue;
+ }
+ /* No more circuit, cleanup the into point object. */
+ SMARTLIST_DEL_CURRENT(service->expiring_nodes, intro);
+ rend_intro_point_free(intro);
+ } SMARTLIST_FOREACH_END(intro);
+
SMARTLIST_FOREACH_BEGIN(service->intro_nodes, rend_intro_point_t *,
intro) {
/* Find the introduction point node object. */
@@ -3890,10 +4018,13 @@ void
rend_service_desc_has_uploaded(const rend_data_t *rend_data)
{
rend_service_t *service;
+ const char *onion_address;
tor_assert(rend_data);
- service = rend_service_get_by_service_id(rend_data->onion_address);
+ onion_address = rend_data_get_address(rend_data);
+
+ service = rend_service_get_by_service_id(onion_address);
if (service == NULL) {
return;
}
@@ -3911,6 +4042,18 @@ rend_service_desc_has_uploaded(const rend_data_t *rend_data)
} SMARTLIST_FOREACH_END(intro);
}
+/** Don't try to build more than this many circuits before giving up
+ * for a while. Dynamically calculated based on the configured number of
+ * introduction points for the service, n_intro_points_wanted. */
+static int
+rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted)
+{
+ /* Allow all but one of the initial connections to fail and be
+ * retried. (If all fail, we *want* to wait, because something is broken.) */
+ tor_assert(n_intro_points_wanted <= NUM_INTRO_POINTS_MAX);
+ return (int)(2*n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA);
+}
+
/** For every service, check how many intro points it currently has, and:
* - Invalidate introdution points based on specific criteria, see
* remove_invalid_intro_points comments.
@@ -3955,6 +4098,10 @@ rend_consider_services_intro_points(void)
smartlist_clear(exclude_nodes);
smartlist_clear(retry_nodes);
+ /* Cleanup the invalid intro points and save the node objects, if any,
+ * in the exclude_nodes and retry_nodes lists. */
+ remove_invalid_intro_points(service, exclude_nodes, retry_nodes, now);
+
/* This retry period is important here so we don't stress circuit
* creation. */
if (now > service->intro_period_started + INTRO_CIRC_RETRY_PERIOD) {
@@ -3962,16 +4109,13 @@ rend_consider_services_intro_points(void)
service->intro_period_started = now;
service->n_intro_circuits_launched = 0;
} else if (service->n_intro_circuits_launched >=
- MAX_INTRO_CIRCS_PER_PERIOD) {
+ rend_max_intro_circs_per_period(
+ service->n_intro_points_wanted)) {
/* We have failed too many times in this period; wait for the next
- * one before we try again. */
+ * one before we try to initiate any more connections. */
continue;
}
- /* Cleanup the invalid intro points and save the node objects, if apply,
- * in the exclude_nodes and retry_nodes list. */
- remove_invalid_intro_points(service, exclude_nodes, retry_nodes, now);
-
/* Let's try to rebuild circuit on the nodes we want to retry on. */
SMARTLIST_FOREACH_BEGIN(retry_nodes, rend_intro_point_t *, intro) {
r = rend_service_launch_establish_intro(service, intro);
@@ -3991,17 +4135,17 @@ rend_consider_services_intro_points(void)
/* Avoid mismatched signed comparaison below. */
intro_nodes_len = (unsigned int) smartlist_len(service->intro_nodes);
- /* Quiescent state, no node expiring and we have more or the amount of
- * wanted node for this service. Proceed to the next service. Could be
- * more because we launch two preemptive circuits if our intro nodes
- * list is empty. */
- if (smartlist_len(service->expiring_nodes) == 0 &&
- intro_nodes_len >= service->n_intro_points_wanted) {
+ /* Quiescent state, we have more or the equal amount of wanted node for
+ * this service. Proceed to the next service. We can have more nodes
+ * because we launch extra preemptive circuits if our intro nodes list was
+ * originally empty for performance reasons. */
+ if (intro_nodes_len >= service->n_intro_points_wanted) {
continue;
}
- /* Number of intro points we want to open which is the wanted amount
- * minus the current amount of valid nodes. */
+ /* Number of intro points we want to open which is the wanted amount minus
+ * the current amount of valid nodes. We know that this won't underflow
+ * because of the check above. */
n_intro_points_to_open = service->n_intro_points_wanted - intro_nodes_len;
if (intro_nodes_len == 0) {
/* We want to end up with n_intro_points_wanted intro points, but if
@@ -4058,6 +4202,9 @@ rend_consider_services_intro_points(void)
* even if we are a single onion service and intend to connect to it
* directly ourselves. */
intro->extend_info = extend_info_from_node(node, 0);
+ if (BUG(intro->extend_info == NULL)) {
+ break;
+ }
intro->intro_key = crypto_pk_new();
const int fail = crypto_pk_generate_key(intro->intro_key);
tor_assert(!fail);
@@ -4193,8 +4340,8 @@ rend_service_dump_stats(int severity)
for (i=0; i < smartlist_len(rend_service_list); ++i) {
service = smartlist_get(rend_service_list, i);
- tor_log(severity, LD_GENERAL, "Service configured in \"%s\":",
- service->directory);
+ tor_log(severity, LD_GENERAL, "Service configured in %s:",
+ rend_service_escaped_dir(service));
for (j=0; j < smartlist_len(service->intro_nodes); ++j) {
intro = smartlist_get(service->intro_nodes, j);
safe_name = safe_str_client(intro->extend_info->nickname);
@@ -4280,14 +4427,16 @@ rend_service_set_connection_addr_port(edge_connection_t *conn,
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,
- circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
- service = rend_service_get_by_pk_digest(
- circ->rend_data->rend_pk_digest);
+ rend_pk_digest, REND_SERVICE_ID_LEN);
+ service = rend_service_get_by_pk_digest(rend_pk_digest);
if (!service) {
log_warn(LD_REND, "Couldn't find any service associated with pk %s on "
"rendezvous circuit %u; closing.",
diff --git a/src/or/rendservice.h b/src/or/rendservice.h
index 3b185672f6..85daaae4e2 100644
--- a/src/or/rendservice.h
+++ b/src/or/rendservice.h
@@ -129,6 +129,13 @@ STATIC int rend_service_verify_single_onion_poison(
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);
+STATIC void prune_services_on_reload(smartlist_t *old_service_list,
+ smartlist_t *new_service_list);
+
#endif
int num_rend_services(void);
diff --git a/src/or/router.c b/src/or/router.c
index 31f2ff00d2..53f213210e 100644
--- a/src/or/router.c
+++ b/src/or/router.c
@@ -849,7 +849,12 @@ init_keys(void)
if (init_keys_common() < 0)
return -1;
/* Make sure DataDirectory exists, and is private. */
- if (check_private_dir(options->DataDirectory, CPD_CREATE, options->User)) {
+ cpd_check_t cpd_opts = CPD_CREATE;
+ if (options->DataDirectoryGroupReadable)
+ cpd_opts |= CPD_GROUP_READ;
+ if (check_private_dir(options->DataDirectory, cpd_opts, options->User)) {
+ log_err(LD_OR, "Can't create/check datadirectory %s",
+ options->DataDirectory);
return -1;
}
/* Check the key directory. */
@@ -901,7 +906,8 @@ init_keys(void)
}
/* 1d. Load all ed25519 keys */
- if (load_ed_keys(options,now) < 0)
+ const int new_signing_key = load_ed_keys(options,now);
+ if (new_signing_key < 0)
return -1;
/* 2. Read onion key. Make it if none is found. */
@@ -971,7 +977,7 @@ init_keys(void)
/* 3b. Get an ed25519 link certificate. Note that we need to do this
* after we set up the TLS context */
- if (generate_ed_link_cert(options, now) < 0) {
+ if (generate_ed_link_cert(options, now, new_signing_key > 0) < 0) {
log_err(LD_GENERAL,"Couldn't make link cert");
return -1;
}
@@ -1178,9 +1184,9 @@ router_should_be_directory_server(const or_options_t *options, int dir_port)
if (accounting_is_enabled(options) &&
get_options()->AccountingRule != ACCT_IN) {
/* Don't spend bytes for directory traffic if we could end up hibernating,
- * but allow DirPort otherwise. Some people set AccountingMax because
- * they're confused or to get statistics. Directory traffic has a much
- * larger effect on output than input so there is no reason to turn it
+ * but allow DirPort otherwise. Some relay operators set AccountingMax
+ * because they're confused or to get statistics. Directory traffic has a
+ * much larger effect on output than input so there is no reason to turn it
* off if using AccountingRule in. */
int interval_length = accounting_get_interval_length();
uint32_t effective_bw = get_effective_bwrate(options);
@@ -1312,8 +1318,15 @@ extend_info_from_router(const routerinfo_t *r)
/* Make sure we don't need to check address reachability */
tor_assert_nonfatal(router_skip_or_reachability(get_options(), 0));
+ const ed25519_public_key_t *ed_id_key;
+ if (r->cache_info.signing_key_cert)
+ ed_id_key = &r->cache_info.signing_key_cert->signing_key;
+ else
+ ed_id_key = NULL;
+
router_get_prim_orport(r, &ap);
return extend_info_new(r->nickname, r->cache_info.identity_digest,
+ ed_id_key,
r->onion_pkey, r->onion_curve25519_pkey,
&ap.addr, ap.port);
}
@@ -1378,7 +1391,8 @@ consider_testing_reachability(int test_or, int test_dir)
me->cache_info.identity_digest,
DIR_PURPOSE_FETCH_SERVERDESC,
ROUTER_PURPOSE_GENERAL,
- DIRIND_ANON_DIRPORT, "authority.z", NULL, 0, 0);
+ DIRIND_ANON_DIRPORT, "authority.z",
+ NULL, 0, 0, NULL);
}
}
@@ -2206,7 +2220,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
log_warn(LD_CONFIG, "There is a router named \"%s\" in my "
"declared family, but that isn't a legal nickname. "
"Skipping it.", escaped(name));
- smartlist_add(warned_nonexistent_family, tor_strdup(name));
+ smartlist_add_strdup(warned_nonexistent_family, name);
}
if (is_legal) {
smartlist_add(ri->declared_family, name);
@@ -2890,7 +2904,7 @@ router_dump_router_to_string(routerinfo_t *router,
/* Write the exit policy to the end of 's'. */
if (!router->exit_policy || !smartlist_len(router->exit_policy)) {
- smartlist_add(chunks, tor_strdup("reject *:*\n"));
+ smartlist_add_strdup(chunks, "reject *:*\n");
} else if (router->exit_policy) {
char *exit_policy = router_dump_exit_policy_to_string(router,1,0);
@@ -2912,12 +2926,12 @@ router_dump_router_to_string(routerinfo_t *router,
if (decide_to_advertise_begindir(options,
router->supports_tunnelled_dir_requests)) {
- smartlist_add(chunks, tor_strdup("tunnelled-dir-server\n"));
+ smartlist_add_strdup(chunks, "tunnelled-dir-server\n");
}
/* Sign the descriptor with Ed25519 */
if (emit_ed_sigs) {
- smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
+ smartlist_add_strdup(chunks, "router-sig-ed25519 ");
crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
ED_DESC_SIGNATURE_PREFIX,
chunks, "", DIGEST_SHA256);
@@ -2933,7 +2947,7 @@ router_dump_router_to_string(routerinfo_t *router,
}
/* Sign the descriptor with RSA */
- smartlist_add(chunks, tor_strdup("router-signature\n"));
+ smartlist_add_strdup(chunks, "router-signature\n");
crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1);
@@ -2948,7 +2962,7 @@ router_dump_router_to_string(routerinfo_t *router,
}
/* include a last '\n' */
- smartlist_add(chunks, tor_strdup("\n"));
+ smartlist_add_strdup(chunks, "\n");
output = smartlist_join_strings(chunks, "", 0, NULL);
@@ -3206,13 +3220,13 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
if (should_record_bridge_info(options) && write_stats_to_extrainfo) {
const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now);
if (bridge_stats) {
- smartlist_add(chunks, tor_strdup(bridge_stats));
+ smartlist_add_strdup(chunks, bridge_stats);
}
}
if (emit_ed_sigs) {
char sha256_digest[DIGEST256_LEN];
- smartlist_add(chunks, tor_strdup("router-sig-ed25519 "));
+ smartlist_add_strdup(chunks, "router-sig-ed25519 ");
crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN,
ED_DESC_SIGNATURE_PREFIX,
chunks, "", DIGEST_SHA256);
@@ -3227,7 +3241,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
smartlist_add_asprintf(chunks, "%s\n", buf);
}
- smartlist_add(chunks, tor_strdup("router-signature\n"));
+ smartlist_add_strdup(chunks, "router-signature\n");
s = smartlist_join_strings(chunks, "", 0, NULL);
while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) {
@@ -3262,7 +3276,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
"descriptor.");
goto err;
}
- smartlist_add(chunks, tor_strdup(sig));
+ smartlist_add_strdup(chunks, sig);
tor_free(s);
s = smartlist_join_strings(chunks, "", 0, NULL);
diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c
index ca32228fc7..aa7aee4b02 100644
--- a/src/or/routerkeys.c
+++ b/src/or/routerkeys.c
@@ -5,8 +5,13 @@
* \file routerkeys.c
*
* \brief Functions and structures to handle generating and maintaining the
- * set of keypairs necessary to be an OR. (Some of the code in router.c
- * belongs here.)
+ * set of keypairs necessary to be an OR.
+ *
+ * The keys handled here now are the Ed25519 keys that Tor relays use to sign
+ * descriptors, authenticate themselves on links, and identify one another
+ * uniquely. Other keys are maintained in router.c and rendservice.c.
+ *
+ * (TODO: The keys in router.c should go here too.)
*/
#include "or.h"
@@ -19,6 +24,7 @@
#define ENC_KEY_HEADER "Boxed Ed25519 key"
#define ENC_KEY_TAG "master"
+/* DOCDOC */
static ssize_t
do_getpass(const char *prompt, char *buf, size_t buflen,
int twice, const or_options_t *options)
@@ -85,6 +91,7 @@ do_getpass(const char *prompt, char *buf, size_t buflen,
return length;
}
+/* DOCDOC */
int
read_encrypted_secret_key(ed25519_secret_key_t *out,
const char *fname)
@@ -157,6 +164,7 @@ read_encrypted_secret_key(ed25519_secret_key_t *out,
return r;
}
+/* DOCDOC */
int
write_encrypted_secret_key(const ed25519_secret_key_t *key,
const char *fname)
@@ -200,6 +208,7 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key,
return r;
}
+/* DOCDOC */
static int
write_secret_key(const ed25519_secret_key_t *key, int encrypted,
const char *fname,
@@ -659,10 +668,14 @@ static tor_cert_t *auth_key_cert = NULL;
static uint8_t *rsa_ed_crosscert = NULL;
static size_t rsa_ed_crosscert_len = 0;
+static time_t rsa_ed_crosscert_expiration = 0;
/**
* Running as a server: load, reload, or refresh our ed25519 keys and
* certificates, creating and saving new ones as needed.
+ *
+ * Return -1 on failure; 0 on success if the signing key was not replaced;
+ * and 1 on success if the signing key was replaced.
*/
int
load_ed_keys(const or_options_t *options, time_t now)
@@ -675,6 +688,7 @@ load_ed_keys(const or_options_t *options, time_t now)
const tor_cert_t *check_signing_cert = NULL;
tor_cert_t *sign_cert = NULL;
tor_cert_t *auth_cert = NULL;
+ int signing_key_changed = 0;
#define FAIL(msg) do { \
log_warn(LD_OR, (msg)); \
@@ -690,8 +704,10 @@ load_ed_keys(const or_options_t *options, time_t now)
tor_cert_free(cert); \
cert = (newval); \
} while (0)
+#define HAPPENS_SOON(when, interval) \
+ ((when) < now + (interval))
#define EXPIRES_SOON(cert, interval) \
- (!(cert) || (cert)->valid_until < now + (interval))
+ (!(cert) || HAPPENS_SOON((cert)->valid_until, (interval)))
/* XXXX support encrypted identity keys fully */
@@ -710,7 +726,23 @@ load_ed_keys(const or_options_t *options, time_t now)
use_signing = sign;
}
+ if (use_signing) {
+ /* We loaded a signing key with its certificate. */
+ if (! master_signing_key) {
+ /* We didn't know one before! */
+ signing_key_changed = 1;
+ } else if (! ed25519_pubkey_eq(&use_signing->pubkey,
+ &master_signing_key->pubkey) ||
+ ! tor_memeq(use_signing->seckey.seckey,
+ master_signing_key->seckey.seckey,
+ ED25519_SECKEY_LEN)) {
+ /* We loaded a different signing key than the one we knew before. */
+ signing_key_changed = 1;
+ }
+ }
+
if (!use_signing && master_signing_key) {
+ /* We couldn't load a signing key, but we already had one loaded */
check_signing_cert = signing_key_cert;
use_signing = master_signing_key;
}
@@ -733,8 +765,12 @@ load_ed_keys(const or_options_t *options, time_t now)
if (need_new_signing_key) {
log_notice(LD_OR, "It looks like I need to generate and sign a new "
- "medium-term signing key, because %s. To do that, I need to "
- "load%s the permanent master identity key.",
+ "medium-term signing key, because %s. To do that, I "
+ "need to load%s the permanent master identity key. "
+ "If the master identity key was not moved or encrypted "
+ "with a passphrase, this will be done automatically and "
+ "no further action is required. Otherwise, provide the "
+ "necessary data using 'tor --keygen' to do it manually.",
(NULL == use_signing) ? "I don't have one" :
EXPIRES_SOON(check_signing_cert, 0) ? "the one I have is expired" :
"you asked me to make one with --keygen",
@@ -742,15 +778,19 @@ load_ed_keys(const or_options_t *options, time_t now)
} else if (want_new_signing_key && !offline_master) {
log_notice(LD_OR, "It looks like I should try to generate and sign a "
"new medium-term signing key, because the one I have is "
- "going to expire soon. To do that, I'm going to have to try to "
- "load the permanent master identity key.");
+ "going to expire soon. To do that, I'm going to have to "
+ "try to load the permanent master identity key. "
+ "If the master identity key was not moved or encrypted "
+ "with a passphrase, this will be done automatically and "
+ "no further action is required. Otherwise, provide the "
+ "necessary data using 'tor --keygen' to do it manually.");
} else if (want_new_signing_key) {
log_notice(LD_OR, "It looks like I should try to generate and sign a "
"new medium-term signing key, because the one I have is "
"going to expire soon. But OfflineMasterKey is set, so I "
- "won't try to load a permanent master identity key is set. "
- "You will need to use 'tor --keygen' make a new signing key "
- "and certificate.");
+ "won't try to load a permanent master identity key. You "
+ "will need to use 'tor --keygen' to make a new signing "
+ "key and certificate.");
}
{
@@ -768,8 +808,11 @@ load_ed_keys(const or_options_t *options, time_t now)
if (options->command == CMD_KEYGEN)
flags |= INIT_ED_KEY_TRY_ENCRYPTED;
- /* Check the key directory */
- if (check_private_dir(options->DataDirectory, CPD_CREATE, options->User)) {
+ /* Check/Create the key directory */
+ cpd_check_t cpd_opts = CPD_CREATE;
+ if (options->DataDirectoryGroupReadable)
+ cpd_opts |= CPD_GROUP_READ;
+ if (check_private_dir(options->DataDirectory, cpd_opts, options->User)) {
log_err(LD_OR, "Can't create/check datadirectory %s",
options->DataDirectory);
goto err;
@@ -859,6 +902,7 @@ load_ed_keys(const or_options_t *options, time_t now)
if (!sign)
FAIL("Missing signing key");
use_signing = sign;
+ signing_key_changed = 1;
tor_assert(sign_cert->signing_key_included);
tor_assert(ed25519_pubkey_eq(&sign_cert->signing_key, &id->pubkey));
@@ -879,17 +923,23 @@ load_ed_keys(const or_options_t *options, time_t now)
if (options->command == CMD_KEYGEN)
goto end;
- if (!rsa_ed_crosscert && server_mode(options)) {
+ if (server_mode(options) &&
+ (!rsa_ed_crosscert ||
+ HAPPENS_SOON(rsa_ed_crosscert_expiration, 30*86400))) {
uint8_t *crosscert;
+ time_t expiration = now+6*30*86400; /* 6 months in the future. */
ssize_t crosscert_len = tor_make_rsa_ed25519_crosscert(&id->pubkey,
get_server_identity_key(),
- now+10*365*86400,/*XXXX*/
+ expiration,
&crosscert);
+ tor_free(rsa_ed_crosscert);
rsa_ed_crosscert_len = crosscert_len;
rsa_ed_crosscert = crosscert;
+ rsa_ed_crosscert_expiration = expiration;
}
if (!current_auth_key ||
+ signing_key_changed ||
EXPIRES_SOON(auth_key_cert, options->TestingAuthKeySlop)) {
auth = ed_key_new(use_signing, INIT_ED_KEY_NEEDCERT,
now,
@@ -917,7 +967,7 @@ load_ed_keys(const or_options_t *options, time_t now)
SET_CERT(auth_key_cert, auth_cert);
}
- return 0;
+ return signing_key_changed;
err:
ed25519_keypair_free(id);
ed25519_keypair_free(sign);
@@ -927,21 +977,39 @@ load_ed_keys(const or_options_t *options, time_t now)
return -1;
}
-/* DOCDOC */
+/**
+ * Retrieve our currently-in-use Ed25519 link certificate and id certificate,
+ * and, if they would expire soon (based on the time <b>now</b>, generate new
+ * certificates (without embedding the public part of the signing key inside).
+ * If <b>force</b> is true, always generate a new certificate.
+ *
+ * The signed_key from the current id->signing certificate will be used to
+ * sign the new key within newly generated X509 certificate.
+ *
+ * Returns -1 upon error. Otherwise, returns 0 upon success (either when the
+ * current certificate is still valid, or when a new certificate was
+ * successfully generated, or no certificate was needed).
+ */
int
-generate_ed_link_cert(const or_options_t *options, time_t now)
+generate_ed_link_cert(const or_options_t *options, time_t now,
+ int force)
{
const tor_x509_cert_t *link_ = NULL, *id = NULL;
tor_cert_t *link_cert = NULL;
if (tor_tls_get_my_certs(1, &link_, &id) < 0 || link_ == NULL) {
+ if (!server_mode(options)) {
+ /* No need to make an Ed25519->Link cert: we are a client */
+ return 0;
+ }
log_warn(LD_OR, "Can't get my x509 link cert.");
return -1;
}
const common_digests_t *digests = tor_x509_cert_get_cert_digests(link_);
- if (link_cert_cert &&
+ if (force == 0 &&
+ link_cert_cert &&
! EXPIRES_SOON(link_cert_cert, options->TestingLinkKeySlop) &&
fast_memeq(digests->d[DIGEST_SHA256], link_cert_cert->signed_key.pubkey,
DIGEST256_LEN)) {
@@ -967,6 +1035,17 @@ generate_ed_link_cert(const or_options_t *options, time_t now)
#undef SET_KEY
#undef SET_CERT
+/**
+ * Return 1 if any of the following are true:
+ *
+ * - if one of our Ed25519 signing, auth, or link certificates would expire
+ * soon w.r.t. the time <b>now</b>,
+ * - if we do not currently have a link certificate, or
+ * - if our cached Ed25519 link certificate is not same as the one we're
+ * currently using.
+ *
+ * Otherwise, returns 0.
+ */
int
should_make_new_ed_keys(const or_options_t *options, const time_t now)
{
@@ -996,6 +1075,62 @@ should_make_new_ed_keys(const or_options_t *options, const time_t now)
}
#undef EXPIRES_SOON
+#undef HAPPENS_SOON
+
+#ifdef TOR_UNIT_TESTS
+/* Helper for unit tests: populate the ed25519 keys without saving or
+ * loading */
+void
+init_mock_ed_keys(const crypto_pk_t *rsa_identity_key)
+{
+ routerkeys_free_all();
+
+#define MAKEKEY(k) \
+ k = tor_malloc_zero(sizeof(*k)); \
+ if (ed25519_keypair_generate(k, 0) < 0) { \
+ log_warn(LD_BUG, "Couldn't make a keypair"); \
+ goto err; \
+ }
+ MAKEKEY(master_identity_key);
+ MAKEKEY(master_signing_key);
+ MAKEKEY(current_auth_key);
+#define MAKECERT(cert, signing, signed_, type, flags) \
+ cert = tor_cert_create(signing, \
+ type, \
+ &signed_->pubkey, \
+ time(NULL), 86400, \
+ flags); \
+ if (!cert) { \
+ log_warn(LD_BUG, "Couldn't make a %s certificate!", #cert); \
+ goto err; \
+ }
+
+ MAKECERT(signing_key_cert,
+ master_identity_key, master_signing_key, CERT_TYPE_ID_SIGNING,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ MAKECERT(auth_key_cert,
+ master_signing_key, current_auth_key, CERT_TYPE_SIGNING_AUTH, 0);
+
+ if (generate_ed_link_cert(get_options(), time(NULL), 0) < 0) {
+ log_warn(LD_BUG, "Couldn't make link certificate");
+ goto err;
+ }
+
+ rsa_ed_crosscert_len = tor_make_rsa_ed25519_crosscert(
+ &master_identity_key->pubkey,
+ rsa_identity_key,
+ time(NULL)+86400,
+ &rsa_ed_crosscert);
+
+ return;
+
+ err:
+ routerkeys_free_all();
+ tor_assert_nonfatal_unreached();
+}
+#undef MAKEKEY
+#undef MAKECERT
+#endif
const ed25519_public_key_t *
get_master_identity_key(void)
@@ -1005,6 +1140,24 @@ get_master_identity_key(void)
return &master_identity_key->pubkey;
}
+/** Return true iff <b>id</b> is our Ed25519 master identity key. */
+int
+router_ed25519_id_is_me(const ed25519_public_key_t *id)
+{
+ return id && master_identity_key &&
+ ed25519_pubkey_eq(id, &master_identity_key->pubkey);
+}
+
+#ifdef TOR_UNIT_TESTS
+/* only exists for the unit tests, since otherwise the identity key
+ * should be used to sign nothing but the signing key. */
+const ed25519_keypair_t *
+get_master_identity_keypair(void)
+{
+ return master_identity_key;
+}
+#endif
+
const ed25519_keypair_t *
get_master_signing_keypair(void)
{
@@ -1095,12 +1248,12 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
/** Check whether an RSA-TAP cross-certification is correct. Return 0 if it
* is, -1 if it isn't. */
-int
-check_tap_onion_key_crosscert(const uint8_t *crosscert,
- int crosscert_len,
- const crypto_pk_t *onion_pkey,
- const ed25519_public_key_t *master_id_pkey,
- const uint8_t *rsa_id_digest)
+MOCK_IMPL(int,
+check_tap_onion_key_crosscert,(const uint8_t *crosscert,
+ int crosscert_len,
+ const crypto_pk_t *onion_pkey,
+ const ed25519_public_key_t *master_id_pkey,
+ const uint8_t *rsa_id_digest))
{
uint8_t *cc = tor_malloc(crypto_pk_keysize(onion_pkey));
int cc_len =
@@ -1139,9 +1292,12 @@ routerkeys_free_all(void)
tor_cert_free(signing_key_cert);
tor_cert_free(link_cert_cert);
tor_cert_free(auth_key_cert);
+ tor_free(rsa_ed_crosscert);
master_identity_key = master_signing_key = NULL;
current_auth_key = NULL;
signing_key_cert = link_cert_cert = auth_key_cert = NULL;
+ rsa_ed_crosscert = NULL; // redundant
+ rsa_ed_crosscert_len = 0;
}
diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h
index be9b19aea8..845abb4c70 100644
--- a/src/or/routerkeys.h
+++ b/src/or/routerkeys.h
@@ -45,6 +45,8 @@ const struct tor_cert_st *get_current_auth_key_cert(void);
void get_master_rsa_crosscert(const uint8_t **cert_out,
size_t *size_out);
+int router_ed25519_id_is_me(const ed25519_public_key_t *id);
+
struct tor_cert_st *make_ntor_onion_key_crosscert(
const curve25519_keypair_t *onion_key,
const ed25519_public_key_t *master_id_key,
@@ -55,16 +57,16 @@ uint8_t *make_tap_onion_key_crosscert(const crypto_pk_t *onion_key,
const crypto_pk_t *rsa_id_key,
int *len_out);
-int check_tap_onion_key_crosscert(const uint8_t *crosscert,
+MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert,
int crosscert_len,
const crypto_pk_t *onion_pkey,
const ed25519_public_key_t *master_id_pkey,
- const uint8_t *rsa_id_digest);
+ const uint8_t *rsa_id_digest));
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);
-int generate_ed_link_cert(const or_options_t *options, time_t now);
+int generate_ed_link_cert(const or_options_t *options, time_t now, int force);
int read_encrypted_secret_key(ed25519_secret_key_t *out,
const char *fname);
@@ -73,5 +75,10 @@ int write_encrypted_secret_key(const ed25519_secret_key_t *out,
void routerkeys_free_all(void);
+#ifdef TOR_UNIT_TESTS
+const ed25519_keypair_t *get_master_identity_keypair(void);
+void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key);
+#endif
+
#endif
diff --git a/src/or/routerlist.c b/src/or/routerlist.c
index 1ad03b6cda..f2ab6051c7 100644
--- a/src/or/routerlist.c
+++ b/src/or/routerlist.c
@@ -93,6 +93,7 @@
#define ROUTERLIST_PRIVATE
#include "or.h"
#include "backtrace.h"
+#include "bridges.h"
#include "crypto_ed25519.h"
#include "circuitstats.h"
#include "config.h"
@@ -586,7 +587,7 @@ trusted_dirs_load_certs_from_string(const char *contents, int source,
"signing key %s", from_store ? "cached" : "downloaded",
ds->nickname, hex_str(cert->signing_key_digest,DIGEST_LEN));
} else {
- int adding = directory_caches_unknown_auth_certs(get_options());
+ int adding = we_want_to_fetch_unknown_auth_certs(get_options());
log_info(LD_DIR, "%s %s certificate for unrecognized directory "
"authority with signing key %s",
adding ? "Adding" : "Not adding",
@@ -929,7 +930,8 @@ authority_certs_fetch_resource_impl(const char *resource,
const routerstatus_t *rs)
{
const or_options_t *options = get_options();
- int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0);
+ int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0,
+ resource);
/* Make sure bridge clients never connect to anything but a bridge */
if (options->UseBridges) {
@@ -947,7 +949,7 @@ authority_certs_fetch_resource_impl(const char *resource,
/* If we've just downloaded a consensus from a bridge, re-use that
* bridge */
- if (options->UseBridges && node && !get_via_tor) {
+ if (options->UseBridges && node && node->ri && !get_via_tor) {
/* clients always make OR connections to bridges */
tor_addr_port_t or_ap;
/* we are willing to use a non-preferred address if we need to */
@@ -959,7 +961,7 @@ authority_certs_fetch_resource_impl(const char *resource,
DIR_PURPOSE_FETCH_CERTIFICATE,
0,
indirection,
- resource, NULL, 0, 0);
+ resource, NULL, 0, 0, NULL);
return;
}
@@ -969,7 +971,7 @@ authority_certs_fetch_resource_impl(const char *resource,
directory_initiate_command_routerstatus(rs,
DIR_PURPOSE_FETCH_CERTIFICATE,
0, indirection, resource, NULL,
- 0, 0);
+ 0, 0, NULL);
return;
}
@@ -1011,7 +1013,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now,
char *resource = NULL;
cert_list_t *cl;
const or_options_t *options = get_options();
- const int cache = directory_caches_unknown_auth_certs(options);
+ const int keep_unknown = we_want_to_fetch_unknown_auth_certs(options);
fp_pair_t *fp_tmp = NULL;
char id_digest_str[2*DIGEST_LEN+1];
char sk_digest_str[2*DIGEST_LEN+1];
@@ -1083,9 +1085,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now,
if (!smartlist_len(voter->sigs))
continue; /* This authority never signed this consensus, so don't
* go looking for a cert with key digest 0000000000. */
- if (!cache &&
+ if (!keep_unknown &&
!trusteddirserver_get_by_v3_auth_digest(voter->identity_digest))
- continue; /* We are not a cache, and we don't know this authority.*/
+ continue; /* We don't want unknown certs, and we don't know this
+ * authority.*/
/*
* If we don't know *any* cert for this authority, and a download by ID
@@ -1202,7 +1205,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now,
int need_plus = 0;
smartlist_t *fps = smartlist_new();
- smartlist_add(fps, tor_strdup("fp/"));
+ smartlist_add_strdup(fps, "fp/");
SMARTLIST_FOREACH_BEGIN(missing_id_digests, const char *, d) {
char *fp = NULL;
@@ -1242,7 +1245,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now,
int need_plus = 0;
smartlist_t *fp_pairs = smartlist_new();
- smartlist_add(fp_pairs, tor_strdup("fp-sk/"));
+ smartlist_add_strdup(fp_pairs, "fp-sk/");
SMARTLIST_FOREACH_BEGIN(missing_cert_digests, const fp_pair_t *, d) {
char *fp_pair = NULL;
@@ -1823,43 +1826,24 @@ router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc,
return 0;
}
-/* Check if we already have a directory fetch from ds, for serverdesc
- * (including extrainfo) or microdesc documents.
+/* Check if we already have a directory fetch from the ipv4 or ipv6
+ * router, for serverdesc (including extrainfo) or microdesc documents.
* If so, return 1, if not, return 0.
*/
static int
-router_is_already_dir_fetching_ds(const dir_server_t *ds,
- int serverdesc,
- int microdesc)
+router_is_already_dir_fetching_(uint32_t ipv4_addr,
+ const tor_addr_t *ipv6_addr,
+ uint16_t dir_port,
+ int serverdesc,
+ int microdesc)
{
tor_addr_port_t ipv4_dir_ap, ipv6_dir_ap;
/* Assume IPv6 DirPort is the same as IPv4 DirPort */
- tor_addr_from_ipv4h(&ipv4_dir_ap.addr, ds->addr);
- ipv4_dir_ap.port = ds->dir_port;
- tor_addr_copy(&ipv6_dir_ap.addr, &ds->ipv6_addr);
- ipv6_dir_ap.port = ds->dir_port;
-
- return (router_is_already_dir_fetching(&ipv4_dir_ap, serverdesc, microdesc)
- || router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc));
-}
-
-/* Check if we already have a directory fetch from rs, for serverdesc
- * (including extrainfo) or microdesc documents.
- * If so, return 1, if not, return 0.
- */
-static int
-router_is_already_dir_fetching_rs(const routerstatus_t *rs,
- int serverdesc,
- int microdesc)
-{
- tor_addr_port_t ipv4_dir_ap, ipv6_dir_ap;
-
- /* Assume IPv6 DirPort is the same as IPv4 DirPort */
- tor_addr_from_ipv4h(&ipv4_dir_ap.addr, rs->addr);
- ipv4_dir_ap.port = rs->dir_port;
- tor_addr_copy(&ipv6_dir_ap.addr, &rs->ipv6_addr);
- ipv6_dir_ap.port = rs->dir_port;
+ tor_addr_from_ipv4h(&ipv4_dir_ap.addr, ipv4_addr);
+ ipv4_dir_ap.port = dir_port;
+ tor_addr_copy(&ipv6_dir_ap.addr, ipv6_addr);
+ ipv6_dir_ap.port = dir_port;
return (router_is_already_dir_fetching(&ipv4_dir_ap, serverdesc, microdesc)
|| router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc));
@@ -1951,6 +1935,21 @@ router_picked_poor_directory_log(const routerstatus_t *rs)
} \
STMT_END
+/* Common code used in the loop within router_pick_directory_server_impl and
+ * router_pick_trusteddirserver_impl.
+ *
+ * Check if the given <b>identity</b> supports extrainfo. If not, skip further
+ * checks.
+ */
+#define SKIP_MISSING_TRUSTED_EXTRAINFO(type, identity) \
+ STMT_BEGIN \
+ int is_trusted_extrainfo = router_digest_is_trusted_dir_type( \
+ (identity), EXTRAINFO_DIRINFO); \
+ if (((type) & EXTRAINFO_DIRINFO) && \
+ !router_supports_extrainfo((identity), is_trusted_extrainfo)) \
+ continue; \
+ STMT_END
+
/* When iterating through the routerlist, can OR address/port preference
* and reachability checks be skipped?
*/
@@ -1997,7 +1996,6 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
const int fascistfirewall = ! (flags & PDS_IGNORE_FASCISTFIREWALL);
const int no_serverdesc_fetching =(flags & PDS_NO_EXISTING_SERVERDESC_FETCH);
const int no_microdesc_fetching = (flags & PDS_NO_EXISTING_MICRODESC_FETCH);
- const int for_guard = (flags & PDS_FOR_GUARD);
int try_excluding = 1, n_excluded = 0, n_busy = 0;
int try_ip_pref = 1;
@@ -2019,7 +2017,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
/* Find all the running dirservers we know about. */
SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) {
- int is_trusted, is_trusted_extrainfo;
+ int is_trusted;
int is_overloaded;
const routerstatus_t *status = node->rs;
const country_t country = node->country;
@@ -2030,20 +2028,9 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
continue;
if (requireother && router_digest_is_me(node->identity))
continue;
- is_trusted = router_digest_is_trusted_dir(node->identity);
- is_trusted_extrainfo = router_digest_is_trusted_dir_type(
- node->identity, EXTRAINFO_DIRINFO);
- if ((type & EXTRAINFO_DIRINFO) &&
- !router_supports_extrainfo(node->identity, is_trusted_extrainfo))
- continue;
- /* Don't make the same node a guard twice */
- if (for_guard && node->using_as_guard) {
- continue;
- }
- /* Ensure that a directory guard is actually a guard node. */
- if (for_guard && !node->is_possible_guard) {
- continue;
- }
+
+ SKIP_MISSING_TRUSTED_EXTRAINFO(type, node->identity);
+
if (try_excluding &&
routerset_contains_routerstatus(options->ExcludeNodes, status,
country)) {
@@ -2051,14 +2038,17 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags,
continue;
}
- if (router_is_already_dir_fetching_rs(status,
- no_serverdesc_fetching,
- no_microdesc_fetching)) {
+ if (router_is_already_dir_fetching_(status->addr,
+ &status->ipv6_addr,
+ status->dir_port,
+ no_serverdesc_fetching,
+ no_microdesc_fetching)) {
++n_busy;
continue;
}
is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now;
+ is_trusted = router_digest_is_trusted_dir(node->identity);
/* Clients use IPv6 addresses if the server has one and the client
* prefers IPv6.
@@ -2190,11 +2180,9 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
if (!d->is_running) continue;
if ((type & d->type) == 0)
continue;
- int is_trusted_extrainfo = router_digest_is_trusted_dir_type(
- d->digest, EXTRAINFO_DIRINFO);
- if ((type & EXTRAINFO_DIRINFO) &&
- !router_supports_extrainfo(d->digest, is_trusted_extrainfo))
- continue;
+
+ SKIP_MISSING_TRUSTED_EXTRAINFO(type, d->digest);
+
if (requireother && me && router_digest_is_me(d->digest))
continue;
if (try_excluding &&
@@ -2204,8 +2192,11 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist,
continue;
}
- if (router_is_already_dir_fetching_ds(d, no_serverdesc_fetching,
- no_microdesc_fetching)) {
+ if (router_is_already_dir_fetching_(d->addr,
+ &d->ipv6_addr,
+ d->dir_port,
+ no_serverdesc_fetching,
+ no_microdesc_fetching)) {
++n_busy;
continue;
}
@@ -2992,20 +2983,6 @@ router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type)
return 0;
}
-/** Return true iff <b>addr</b> is the address of one of our trusted
- * directory authorities. */
-int
-router_addr_is_trusted_dir(uint32_t addr)
-{
- if (!trusted_dir_servers)
- return 0;
- SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ent,
- if (ent->addr == addr)
- return 1;
- );
- return 0;
-}
-
/** If hexdigest is correctly formed, base16_decode it into
* digest, which must have DIGEST_LEN space in it.
* Return 0 on success, -1 on failure.
@@ -3910,7 +3887,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
router_describe(router));
*msg = "Router descriptor is not referenced by any network-status.";
- /* Only journal this desc if we'll be serving it. */
+ /* Only journal this desc if we want to keep old descriptors */
if (!from_cache && should_cache_old_descriptors())
signed_desc_append_to_journal(&router->cache_info,
&routerlist->desc_store);
@@ -4497,7 +4474,7 @@ router_load_extrainfo_from_string(const char *s, const char *eos,
ei->cache_info.identity_digest,
DIGEST_LEN);
smartlist_string_remove(requested_fingerprints, fp);
- /* We silently let people stuff us with extrainfos we didn't ask for,
+ /* We silently let relays stuff us with extrainfos we didn't ask for,
* so long as we would have wanted them anyway. Since we always fetch
* all the extrainfos we want, and we never actually act on them
* inside Tor, this should be harmless. */
@@ -4540,13 +4517,14 @@ router_load_extrainfo_from_string(const char *s, const char *eos,
smartlist_free(extrainfo_list);
}
-/** Return true iff any networkstatus includes a descriptor whose digest
- * is that of <b>desc</b>. */
+/** Return true iff the latest ns-flavored consensus includes a descriptor
+ * whose digest is that of <b>desc</b>. */
static int
signed_desc_digest_is_recognized(signed_descriptor_t *desc)
{
const routerstatus_t *rs;
- networkstatus_t *consensus = networkstatus_get_latest_consensus();
+ networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor(
+ FLAV_NS);
if (consensus) {
rs = networkstatus_vote_find_entry(consensus, desc->identity_digest);
@@ -4592,7 +4570,7 @@ router_exit_policy_rejects_all(const routerinfo_t *router)
return router->policy_is_reject_star;
}
-/** Create an directory server at <b>address</b>:<b>port</b>, with OR identity
+/** Create a directory server at <b>address</b>:<b>port</b>, with OR identity
* key <b>digest</b> which has DIGEST_LEN bytes. If <b>address</b> is NULL,
* add ourself. If <b>is_authority</b>, this is a directory authority. Return
* the new directory server entry on success or NULL on failure. */
@@ -4960,7 +4938,7 @@ MOCK_IMPL(STATIC void, initiate_descriptor_downloads,
directory_initiate_command_routerstatus(source, purpose,
ROUTER_PURPOSE_GENERAL,
DIRIND_ONEHOP,
- resource, NULL, 0, 0);
+ resource, NULL, 0, 0, NULL);
} else {
directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource,
pds_flags, DL_WANT_ANY_DIRSERVER);
@@ -5174,7 +5152,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote,
++n_would_reject;
continue; /* We would throw it out immediately. */
}
- if (!directory_caches_dir_info(options) &&
+ if (!we_want_to_fetch_flavor(options, consensus->flavor) &&
!client_would_use_router(rs, now, options)) {
++n_wouldnt_use;
continue; /* We would never use it ourself. */
diff --git a/src/or/routerlist.h b/src/or/routerlist.h
index 606e9085ce..8b68d69f28 100644
--- a/src/or/routerlist.h
+++ b/src/or/routerlist.h
@@ -86,7 +86,6 @@ int router_digest_is_trusted_dir_type(const char *digest,
#define router_digest_is_trusted_dir(d) \
router_digest_is_trusted_dir_type((d), NO_DIRINFO)
-int router_addr_is_trusted_dir(uint32_t addr);
int hexdigest_to_digest(const char *hexdigest, char *digest);
const routerinfo_t *router_get_by_id_digest(const char *digest);
routerinfo_t *router_get_mutable_by_digest(const char *digest);
diff --git a/src/or/routerparse.c b/src/or/routerparse.c
index 521e237be2..0336c035b4 100644
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@ -60,6 +60,7 @@
#include "circuitstats.h"
#include "dirserv.h"
#include "dirvote.h"
+#include "parsecommon.h"
#include "policies.h"
#include "protover.h"
#include "rendcommon.h"
@@ -81,267 +82,6 @@
/****************************************************************************/
-/** Enumeration of possible token types. The ones starting with K_ correspond
- * to directory 'keywords'. A_ is for an annotation, R or C is related to
- * hidden services, ERR_ is an error in the tokenizing process, EOF_ is an
- * end-of-file marker, and NIL_ is used to encode not-a-token.
- */
-typedef enum {
- K_ACCEPT = 0,
- K_ACCEPT6,
- K_DIRECTORY_SIGNATURE,
- K_RECOMMENDED_SOFTWARE,
- K_REJECT,
- K_REJECT6,
- K_ROUTER,
- K_SIGNED_DIRECTORY,
- K_SIGNING_KEY,
- K_ONION_KEY,
- K_ONION_KEY_NTOR,
- K_ROUTER_SIGNATURE,
- K_PUBLISHED,
- K_RUNNING_ROUTERS,
- K_ROUTER_STATUS,
- K_PLATFORM,
- K_PROTO,
- K_OPT,
- K_BANDWIDTH,
- K_CONTACT,
- K_NETWORK_STATUS,
- K_UPTIME,
- K_DIR_SIGNING_KEY,
- K_FAMILY,
- K_FINGERPRINT,
- K_HIBERNATING,
- K_READ_HISTORY,
- K_WRITE_HISTORY,
- K_NETWORK_STATUS_VERSION,
- K_DIR_SOURCE,
- K_DIR_OPTIONS,
- K_CLIENT_VERSIONS,
- K_SERVER_VERSIONS,
- K_RECOMMENDED_CLIENT_PROTOCOLS,
- K_RECOMMENDED_RELAY_PROTOCOLS,
- K_REQUIRED_CLIENT_PROTOCOLS,
- K_REQUIRED_RELAY_PROTOCOLS,
- K_OR_ADDRESS,
- K_ID,
- K_P,
- K_P6,
- K_R,
- K_A,
- K_S,
- K_V,
- K_W,
- K_M,
- K_EXTRA_INFO,
- K_EXTRA_INFO_DIGEST,
- K_CACHES_EXTRA_INFO,
- K_HIDDEN_SERVICE_DIR,
- K_ALLOW_SINGLE_HOP_EXITS,
- K_IPV6_POLICY,
- K_ROUTER_SIG_ED25519,
- K_IDENTITY_ED25519,
- K_MASTER_KEY_ED25519,
- K_ONION_KEY_CROSSCERT,
- K_NTOR_ONION_KEY_CROSSCERT,
-
- K_DIRREQ_END,
- K_DIRREQ_V2_IPS,
- K_DIRREQ_V3_IPS,
- K_DIRREQ_V2_REQS,
- K_DIRREQ_V3_REQS,
- K_DIRREQ_V2_SHARE,
- K_DIRREQ_V3_SHARE,
- K_DIRREQ_V2_RESP,
- K_DIRREQ_V3_RESP,
- K_DIRREQ_V2_DIR,
- K_DIRREQ_V3_DIR,
- K_DIRREQ_V2_TUN,
- K_DIRREQ_V3_TUN,
- K_ENTRY_END,
- K_ENTRY_IPS,
- K_CELL_END,
- K_CELL_PROCESSED,
- K_CELL_QUEUED,
- K_CELL_TIME,
- K_CELL_CIRCS,
- K_EXIT_END,
- K_EXIT_WRITTEN,
- K_EXIT_READ,
- K_EXIT_OPENED,
-
- K_DIR_KEY_CERTIFICATE_VERSION,
- K_DIR_IDENTITY_KEY,
- K_DIR_KEY_PUBLISHED,
- K_DIR_KEY_EXPIRES,
- K_DIR_KEY_CERTIFICATION,
- K_DIR_KEY_CROSSCERT,
- K_DIR_ADDRESS,
- K_DIR_TUNNELLED,
-
- K_VOTE_STATUS,
- K_VALID_AFTER,
- K_FRESH_UNTIL,
- K_VALID_UNTIL,
- K_VOTING_DELAY,
-
- K_KNOWN_FLAGS,
- K_PARAMS,
- K_BW_WEIGHTS,
- K_VOTE_DIGEST,
- K_CONSENSUS_DIGEST,
- K_ADDITIONAL_DIGEST,
- K_ADDITIONAL_SIGNATURE,
- K_CONSENSUS_METHODS,
- K_CONSENSUS_METHOD,
- K_LEGACY_DIR_KEY,
- K_DIRECTORY_FOOTER,
- K_SIGNING_CERT_ED,
- K_SR_FLAG,
- K_COMMIT,
- K_PREVIOUS_SRV,
- K_CURRENT_SRV,
- K_PACKAGE,
-
- A_PURPOSE,
- A_LAST_LISTED,
- A_UNKNOWN_,
-
- R_RENDEZVOUS_SERVICE_DESCRIPTOR,
- R_VERSION,
- R_PERMANENT_KEY,
- R_SECRET_ID_PART,
- R_PUBLICATION_TIME,
- R_PROTOCOL_VERSIONS,
- R_INTRODUCTION_POINTS,
- R_SIGNATURE,
-
- R_IPO_IDENTIFIER,
- R_IPO_IP_ADDRESS,
- R_IPO_ONION_PORT,
- R_IPO_ONION_KEY,
- R_IPO_SERVICE_KEY,
-
- C_CLIENT_NAME,
- C_DESCRIPTOR_COOKIE,
- C_CLIENT_KEY,
-
- ERR_,
- EOF_,
- NIL_
-} directory_keyword;
-
-#define MIN_ANNOTATION A_PURPOSE
-#define MAX_ANNOTATION A_UNKNOWN_
-
-/** Structure to hold a single directory token.
- *
- * We parse a directory by breaking it into "tokens", each consisting
- * of a keyword, a line full of arguments, and a binary object. The
- * arguments and object are both optional, depending on the keyword
- * type.
- *
- * This structure is only allocated in memareas; do not allocate it on
- * the heap, or token_clear() won't work.
- */
-typedef struct directory_token_t {
- directory_keyword tp; /**< Type of the token. */
- int n_args:30; /**< Number of elements in args */
- char **args; /**< Array of arguments from keyword line. */
-
- char *object_type; /**< -----BEGIN [object_type]-----*/
- size_t object_size; /**< Bytes in object_body */
- char *object_body; /**< Contents of object, base64-decoded. */
-
- crypto_pk_t *key; /**< For public keys only. Heap-allocated. */
-
- char *error; /**< For ERR_ tokens only. */
-} directory_token_t;
-
-/* ********************************************************************** */
-
-/** We use a table of rules to decide how to parse each token type. */
-
-/** Rules for whether the keyword needs an object. */
-typedef enum {
- NO_OBJ, /**< No object, ever. */
- NEED_OBJ, /**< Object is required. */
- NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */
- NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */
- NEED_KEY, /**< Object is required, and must be a public key. */
- OBJ_OK, /**< Object is optional. */
-} obj_syntax;
-
-#define AT_START 1
-#define AT_END 2
-
-/** Determines the parsing rules for a single token type. */
-typedef struct token_rule_t {
- /** The string value of the keyword identifying the type of item. */
- const char *t;
- /** The corresponding directory_keyword enum. */
- directory_keyword v;
- /** Minimum number of arguments for this item */
- int min_args;
- /** Maximum number of arguments for this item */
- int max_args;
- /** If true, we concatenate all arguments for this item into a single
- * string. */
- int concat_args;
- /** Requirements on object syntax for this item. */
- obj_syntax os;
- /** Lowest number of times this item may appear in a document. */
- int min_cnt;
- /** Highest number of times this item may appear in a document. */
- int max_cnt;
- /** One or more of AT_START/AT_END to limit where the item may appear in a
- * document. */
- int pos;
- /** True iff this token is an annotation. */
- int is_annotation;
-} token_rule_t;
-
-/**
- * @name macros for defining token rules
- *
- * Helper macros to define token tables. 's' is a string, 't' is a
- * directory_keyword, 'a' is a trio of argument multiplicities, and 'o' is an
- * object syntax.
- */
-/**@{*/
-
-/** Appears to indicate the end of a table. */
-#define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 }
-/** An item with no restrictions: used for obsolete document types */
-#define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
-/** An item with no restrictions on multiplicity or location. */
-#define T0N(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 }
-/** An item that must appear exactly once */
-#define T1(s,t,a,o) { s, t, a, o, 1, 1, 0, 0 }
-/** An item that must appear exactly once, at the start of the document */
-#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START, 0 }
-/** An item that must appear exactly once, at the end of the document */
-#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END, 0 }
-/** An item that must appear one or more times */
-#define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0, 0 }
-/** An item that must appear no more than once */
-#define T01(s,t,a,o) { s, t, a, o, 0, 1, 0, 0 }
-/** An annotation that must appear no more than once */
-#define A01(s,t,a,o) { s, t, a, o, 0, 1, 0, 1 }
-
-/** Argument multiplicity: any number of arguments. */
-#define ARGS 0,INT_MAX,0
-/** Argument multiplicity: no arguments. */
-#define NO_ARGS 0,0,0
-/** Argument multiplicity: concatenate all arguments. */
-#define CONCAT_ARGS 1,1,1
-/** Argument multiplicity: at least <b>n</b> arguments. */
-#define GE(n) n,INT_MAX,0
-/** Argument multiplicity: exactly <b>n</b> arguments. */
-#define EQ(n) n,n,0
-/**@}*/
-
/** List of tokens recognized in router descriptors */
static token_rule_t routerdesc_token_table[] = {
T0N("reject", K_REJECT, ARGS, NO_OBJ ),
@@ -628,30 +368,9 @@ static int router_get_hashes_impl(const char *s, size_t s_len,
common_digests_t *digests,
const char *start_str, const char *end_str,
char end_char);
-static void token_clear(directory_token_t *tok);
-static smartlist_t *find_all_by_keyword(smartlist_t *s, directory_keyword k);
static smartlist_t *find_all_exitpolicy(smartlist_t *s);
-static directory_token_t *find_by_keyword_(smartlist_t *s,
- directory_keyword keyword,
- const char *keyword_str);
-#define find_by_keyword(s, keyword) find_by_keyword_((s), (keyword), #keyword)
-static directory_token_t *find_opt_by_keyword(smartlist_t *s,
- directory_keyword keyword);
-
-#define TS_ANNOTATIONS_OK 1
-#define TS_NOCHECK 2
-#define TS_NO_NEW_ANNOTATIONS 4
-static int tokenize_string(memarea_t *area,
- const char *start, const char *end,
- smartlist_t *out,
- token_rule_t *table,
- int flags);
-static directory_token_t *get_next_token(memarea_t *area,
- const char **s,
- const char *eos,
- token_rule_t *table);
-#define CST_CHECK_AUTHORITY (1<<0)
-#define CST_NO_CHECK_OBJTYPE (1<<1)
+
+#define CST_NO_CHECK_OBJTYPE (1<<0)
static int check_signature_token(const char *digest,
ssize_t digest_len,
directory_token_t *tok,
@@ -995,7 +714,7 @@ dump_desc_populate_one_file, (const char *dirname, const char *f))
* filename.
*/
if (crypto_digest256((char *)content_digest, desc, (size_t) st.st_size,
- DIGEST_SHA256) != 0) {
+ DIGEST_SHA256) < 0) {
/* Weird, but okay */
log_info(LD_DIR,
"Unable to hash content of %s from unparseable descriptors "
@@ -1144,8 +863,8 @@ dump_desc_populate_fifo_from_directory(const char *dirname)
* type *<b>type</b> to file $DATADIR/unparseable-desc. Do not write more
* than one descriptor to disk per minute. If there is already such a
* file in the data directory, overwrite it. */
-STATIC void
-dump_desc(const char *desc, const char *type)
+MOCK_IMPL(STATIC void,
+dump_desc,(const char *desc, const char *type))
{
tor_assert(desc);
tor_assert(type);
@@ -1159,7 +878,7 @@ dump_desc(const char *desc, const char *type)
/* Get the hash for logging purposes anyway */
len = strlen(desc);
if (crypto_digest256((char *)digest_sha256, desc, len,
- DIGEST_SHA256) != 0) {
+ DIGEST_SHA256) < 0) {
log_info(LD_DIR,
"Unable to parse descriptor of type %s, and unable to even hash"
" it!", type);
@@ -1453,28 +1172,15 @@ tor_version_is_obsolete(const char *myversion, const char *versionlist)
return ret;
}
-/** Return true iff <b>key</b> is allowed to sign directories.
- */
-static int
-dir_signing_key_is_trusted(crypto_pk_t *key)
+MOCK_IMPL(STATIC int,
+signed_digest_equals, (const uint8_t *d1, const uint8_t *d2, size_t len))
{
- char digest[DIGEST_LEN];
- if (!key) return 0;
- if (crypto_pk_get_digest(key, digest) < 0) {
- log_warn(LD_DIR, "Error computing dir-signing-key digest");
- return 0;
- }
- if (!router_digest_is_trusted_dir(digest)) {
- log_warn(LD_DIR, "Listed dir-signing-key is not trusted");
- return 0;
- }
- return 1;
+ return tor_memeq(d1, d2, len);
}
/** Check whether the object body of the token in <b>tok</b> has a good
- * signature for <b>digest</b> using key <b>pkey</b>. If
- * <b>CST_CHECK_AUTHORITY</b> is set, make sure that <b>pkey</b> is the key of
- * a directory authority. If <b>CST_NO_CHECK_OBJTYPE</b> is set, do not check
+ * signature for <b>digest</b> using key <b>pkey</b>.
+ * If <b>CST_NO_CHECK_OBJTYPE</b> is set, do not check
* the object type of the signature object. Use <b>doctype</b> as the type of
* the document when generating log messages. Return 0 on success, negative
* on failure.
@@ -1489,7 +1195,6 @@ check_signature_token(const char *digest,
{
char *signed_digest;
size_t keysize;
- const int check_authority = (flags & CST_CHECK_AUTHORITY);
const int check_objtype = ! (flags & CST_NO_CHECK_OBJTYPE);
tor_assert(pkey);
@@ -1497,12 +1202,6 @@ check_signature_token(const char *digest,
tor_assert(digest);
tor_assert(doctype);
- if (check_authority && !dir_signing_key_is_trusted(pkey)) {
- log_warn(LD_DIR, "Key on %s did not come from an authority; rejecting",
- doctype);
- return -1;
- }
-
if (check_objtype) {
if (strcmp(tok->object_type, "SIGNATURE")) {
log_warn(LD_DIR, "Bad object type on %s signature", doctype);
@@ -1521,7 +1220,8 @@ check_signature_token(const char *digest,
}
// log_debug(LD_DIR,"Signed %s hash starts %s", doctype,
// hex_str(signed_digest,4));
- if (tor_memneq(digest, signed_digest, digest_len)) {
+ if (! signed_digest_equals((const uint8_t *)digest,
+ (const uint8_t *)signed_digest, digest_len)) {
log_warn(LD_DIR, "Error reading %s: signature does not match.", doctype);
tor_free(signed_digest);
return -1;
@@ -2100,12 +1800,13 @@ router_parse_entry_from_string(const char *s, const char *end,
ed25519_checkable_t check[3];
int check_ok[3];
- if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
+ time_t expires = TIME_MAX;
+ if (tor_cert_get_checkable_sig(&check[0], cert, NULL, &expires) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
goto err;
}
if (tor_cert_get_checkable_sig(&check[1],
- ntor_cc_cert, &ntor_cc_pk) < 0) {
+ ntor_cc_cert, &ntor_cc_pk, &expires) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert.");
goto err;
}
@@ -2135,10 +1836,7 @@ router_parse_entry_from_string(const char *s, const char *end,
}
/* We check this before adding it to the routerlist. */
- if (cert->valid_until < ntor_cc_cert->valid_until)
- router->cert_expiration_time = cert->valid_until;
- else
- router->cert_expiration_time = ntor_cc_cert->valid_until;
+ router->cert_expiration_time = expires;
}
}
@@ -2220,7 +1918,7 @@ router_parse_entry_from_string(const char *s, const char *end,
escaped(tok->args[i]));
goto err;
}
- smartlist_add(router->declared_family, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(router->declared_family, tok->args[i]);
}
}
@@ -2452,7 +2150,7 @@ extrainfo_parse_entry_from_string(const char *s, const char *end,
ed25519_checkable_t check[2];
int check_ok[2];
- if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) {
+ if (tor_cert_get_checkable_sig(&check[0], cert, NULL, NULL) < 0) {
log_err(LD_BUG, "Couldn't create 'checkable' for cert.");
goto err;
}
@@ -2964,6 +2662,8 @@ routerstatus_parse_entry_from_string(memarea_t *area,
rs->protocols_known = 1;
rs->supports_extend2_cells =
protocol_list_supports_protocol(tok->args[0], PRT_RELAY, 2);
+ rs->supports_ed25519_link_handshake =
+ protocol_list_supports_protocol(tok->args[0], PRT_LINKAUTH, 3);
}
if ((tok = find_opt_by_keyword(tokens, K_V))) {
tor_assert(tok->n_args == 1);
@@ -3723,9 +3423,9 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS);
if (tok) {
for (i=0; i < tok->n_args; ++i)
- smartlist_add(ns->supported_methods, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->supported_methods, tok->args[i]);
} else {
- smartlist_add(ns->supported_methods, tor_strdup("1"));
+ smartlist_add_strdup(ns->supported_methods, "1");
}
} else {
tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD);
@@ -3807,7 +3507,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
ns->package_lines = smartlist_new();
if (package_lst) {
SMARTLIST_FOREACH(package_lst, directory_token_t *, t,
- smartlist_add(ns->package_lines, tor_strdup(t->args[0])));
+ smartlist_add_strdup(ns->package_lines, t->args[0]));
}
smartlist_free(package_lst);
}
@@ -3816,7 +3516,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
ns->known_flags = smartlist_new();
inorder = 1;
for (i = 0; i < tok->n_args; ++i) {
- smartlist_add(ns->known_flags, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->known_flags, tok->args[i]);
if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) {
log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]);
inorder = 0;
@@ -3868,7 +3568,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
}
tor_free(last_kwd);
last_kwd = tor_strndup(tok->args[i], eq_pos);
- smartlist_add(ns->net_params, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->net_params, tok->args[i]);
}
if (!inorder) {
log_warn(LD_DIR, "params not in order");
@@ -4011,11 +3711,10 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
if (ns->type != NS_TYPE_CONSENSUS) {
vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t));
if (routerstatus_parse_entry_from_string(rs_area, &s, rs_tokens, ns,
- rs, 0, 0))
+ rs, 0, 0)) {
smartlist_add(ns->routerstatus_list, rs);
- else {
- tor_free(rs->version);
- tor_free(rs);
+ } else {
+ vote_routerstatus_free(rs);
}
} else {
routerstatus_t *rs;
@@ -4111,7 +3810,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out,
log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i]));
goto err;
}
- smartlist_add(ns->weight_params, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(ns->weight_params, tok->args[i]);
}
}
@@ -4740,445 +4439,6 @@ assert_addr_policy_ok(smartlist_t *lst)
});
}
-/*
- * Low-level tokenizer for router descriptors and directories.
- */
-
-/** Free all resources allocated for <b>tok</b> */
-static void
-token_clear(directory_token_t *tok)
-{
- if (tok->key)
- crypto_pk_free(tok->key);
-}
-
-#define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz)
-#define ALLOC(sz) memarea_alloc(area,sz)
-#define STRDUP(str) memarea_strdup(area,str)
-#define STRNDUP(str,n) memarea_strndup(area,(str),(n))
-
-#define RET_ERR(msg) \
- STMT_BEGIN \
- if (tok) token_clear(tok); \
- tok = ALLOC_ZERO(sizeof(directory_token_t)); \
- tok->tp = ERR_; \
- tok->error = STRDUP(msg); \
- goto done_tokenizing; \
- STMT_END
-
-/** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys
- * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>.
- * Return <b>tok</b> on success, or a new ERR_ token if the token didn't
- * conform to the syntax we wanted.
- **/
-static inline directory_token_t *
-token_check_object(memarea_t *area, const char *kwd,
- directory_token_t *tok, obj_syntax o_syn)
-{
- char ebuf[128];
- switch (o_syn) {
- case NO_OBJ:
- /* No object is allowed for this token. */
- if (tok->object_body) {
- tor_snprintf(ebuf, sizeof(ebuf), "Unexpected object for %s", kwd);
- RET_ERR(ebuf);
- }
- if (tok->key) {
- tor_snprintf(ebuf, sizeof(ebuf), "Unexpected public key for %s", kwd);
- RET_ERR(ebuf);
- }
- break;
- case NEED_OBJ:
- /* There must be a (non-key) object. */
- if (!tok->object_body) {
- tor_snprintf(ebuf, sizeof(ebuf), "Missing object for %s", kwd);
- RET_ERR(ebuf);
- }
- break;
- case NEED_KEY_1024: /* There must be a 1024-bit public key. */
- case NEED_SKEY_1024: /* There must be a 1024-bit private key. */
- if (tok->key && crypto_pk_num_bits(tok->key) != PK_BYTES*8) {
- tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits",
- kwd, crypto_pk_num_bits(tok->key));
- RET_ERR(ebuf);
- }
- /* fall through */
- case NEED_KEY: /* There must be some kind of key. */
- if (!tok->key) {
- tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd);
- RET_ERR(ebuf);
- }
- if (o_syn != NEED_SKEY_1024) {
- if (crypto_pk_key_is_private(tok->key)) {
- tor_snprintf(ebuf, sizeof(ebuf),
- "Private key given for %s, which wants a public key", kwd);
- RET_ERR(ebuf);
- }
- } else { /* o_syn == NEED_SKEY_1024 */
- if (!crypto_pk_key_is_private(tok->key)) {
- tor_snprintf(ebuf, sizeof(ebuf),
- "Public key given for %s, which wants a private key", kwd);
- RET_ERR(ebuf);
- }
- }
- break;
- case OBJ_OK:
- /* Anything goes with this token. */
- break;
- }
-
- done_tokenizing:
- return tok;
-}
-
-/** Helper: parse space-separated arguments from the string <b>s</b> ending at
- * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the
- * number of parsed elements into the n_args field of <b>tok</b>. Allocate
- * all storage in <b>area</b>. Return the number of arguments parsed, or
- * return -1 if there was an insanely high number of arguments. */
-static inline int
-get_token_arguments(memarea_t *area, directory_token_t *tok,
- const char *s, const char *eol)
-{
-/** Largest number of arguments we'll accept to any token, ever. */
-#define MAX_ARGS 512
- char *mem = memarea_strndup(area, s, eol-s);
- char *cp = mem;
- int j = 0;
- char *args[MAX_ARGS];
- while (*cp) {
- if (j == MAX_ARGS)
- return -1;
- args[j++] = cp;
- cp = (char*)find_whitespace(cp);
- if (!cp || !*cp)
- break; /* End of the line. */
- *cp++ = '\0';
- cp = (char*)eat_whitespace(cp);
- }
- tok->n_args = j;
- tok->args = memarea_memdup(area, args, j*sizeof(char*));
- return j;
-#undef MAX_ARGS
-}
-
-/** Helper function: read the next token from *s, advance *s to the end of the
- * token, and return the parsed token. Parse *<b>s</b> according to the list
- * of tokens in <b>table</b>.
- */
-static directory_token_t *
-get_next_token(memarea_t *area,
- const char **s, const char *eos, token_rule_t *table)
-{
- /** Reject any object at least this big; it is probably an overflow, an
- * attack, a bug, or some other nonsense. */
-#define MAX_UNPARSED_OBJECT_SIZE (128*1024)
- /** Reject any line at least this big; it is probably an overflow, an
- * attack, a bug, or some other nonsense. */
-#define MAX_LINE_LENGTH (128*1024)
-
- const char *next, *eol, *obstart;
- size_t obname_len;
- int i;
- directory_token_t *tok;
- obj_syntax o_syn = NO_OBJ;
- char ebuf[128];
- const char *kwd = "";
-
- tor_assert(area);
- tok = ALLOC_ZERO(sizeof(directory_token_t));
- tok->tp = ERR_;
-
- /* Set *s to first token, eol to end-of-line, next to after first token */
- *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */
- tor_assert(eos >= *s);
- eol = memchr(*s, '\n', eos-*s);
- if (!eol)
- eol = eos;
- if (eol - *s > MAX_LINE_LENGTH) {
- RET_ERR("Line far too long");
- }
-
- next = find_whitespace_eos(*s, eol);
-
- if (!strcmp_len(*s, "opt", next-*s)) {
- /* Skip past an "opt" at the start of the line. */
- *s = eat_whitespace_eos_no_nl(next, eol);
- next = find_whitespace_eos(*s, eol);
- } else if (*s == eos) { /* If no "opt", and end-of-line, line is invalid */
- RET_ERR("Unexpected EOF");
- }
-
- /* Search the table for the appropriate entry. (I tried a binary search
- * instead, but it wasn't any faster.) */
- for (i = 0; table[i].t ; ++i) {
- if (!strcmp_len(*s, table[i].t, next-*s)) {
- /* We've found the keyword. */
- kwd = table[i].t;
- tok->tp = table[i].v;
- o_syn = table[i].os;
- *s = eat_whitespace_eos_no_nl(next, eol);
- /* We go ahead whether there are arguments or not, so that tok->args is
- * always set if we want arguments. */
- if (table[i].concat_args) {
- /* The keyword takes the line as a single argument */
- tok->args = ALLOC(sizeof(char*));
- tok->args[0] = STRNDUP(*s,eol-*s); /* Grab everything on line */
- tok->n_args = 1;
- } else {
- /* This keyword takes multiple arguments. */
- if (get_token_arguments(area, tok, *s, eol)<0) {
- tor_snprintf(ebuf, sizeof(ebuf),"Far too many arguments to %s", kwd);
- RET_ERR(ebuf);
- }
- *s = eol;
- }
- if (tok->n_args < table[i].min_args) {
- tor_snprintf(ebuf, sizeof(ebuf), "Too few arguments to %s", kwd);
- RET_ERR(ebuf);
- } else if (tok->n_args > table[i].max_args) {
- tor_snprintf(ebuf, sizeof(ebuf), "Too many arguments to %s", kwd);
- RET_ERR(ebuf);
- }
- break;
- }
- }
-
- if (tok->tp == ERR_) {
- /* No keyword matched; call it an "K_opt" or "A_unrecognized" */
- if (*s < eol && **s == '@')
- tok->tp = A_UNKNOWN_;
- else
- tok->tp = K_OPT;
- tok->args = ALLOC(sizeof(char*));
- tok->args[0] = STRNDUP(*s, eol-*s);
- tok->n_args = 1;
- o_syn = OBJ_OK;
- }
-
- /* Check whether there's an object present */
- *s = eat_whitespace_eos(eol, eos); /* Scan from end of first line */
- tor_assert(eos >= *s);
- eol = memchr(*s, '\n', eos-*s);
- if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */
- goto check_object;
-
- obstart = *s; /* Set obstart to start of object spec */
- if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */
- strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */
- (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */
- RET_ERR("Malformed object: bad begin line");
- }
- tok->object_type = STRNDUP(*s+11, eol-*s-16);
- obname_len = eol-*s-16; /* store objname length here to avoid a strlen() */
- *s = eol+1; /* Set *s to possible start of object data (could be eos) */
-
- /* Go to the end of the object */
- next = tor_memstr(*s, eos-*s, "-----END ");
- if (!next) {
- RET_ERR("Malformed object: missing object end line");
- }
- tor_assert(eos >= next);
- eol = memchr(next, '\n', eos-next);
- if (!eol) /* end-of-line marker, or eos if there's no '\n' */
- eol = eos;
- /* Validate the ending tag, which should be 9 + NAME + 5 + eol */
- if ((size_t)(eol-next) != 9+obname_len+5 ||
- strcmp_len(next+9, tok->object_type, obname_len) ||
- strcmp_len(eol-5, "-----", 5)) {
- tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s",
- tok->object_type);
- ebuf[sizeof(ebuf)-1] = '\0';
- RET_ERR(ebuf);
- }
- if (next - *s > MAX_UNPARSED_OBJECT_SIZE)
- RET_ERR("Couldn't parse object: missing footer or object much too big.");
-
- if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */
- tok->key = crypto_pk_new();
- if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart))
- RET_ERR("Couldn't parse public key.");
- } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */
- tok->key = crypto_pk_new();
- if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart))
- RET_ERR("Couldn't parse private key.");
- } else { /* If it's something else, try to base64-decode it */
- int r;
- tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */
- r = base64_decode(tok->object_body, next-*s, *s, next-*s);
- if (r<0)
- RET_ERR("Malformed object: bad base64-encoded data");
- tok->object_size = r;
- }
- *s = eol;
-
- check_object:
- tok = token_check_object(area, kwd, tok, o_syn);
-
- done_tokenizing:
- return tok;
-
-#undef RET_ERR
-#undef ALLOC
-#undef ALLOC_ZERO
-#undef STRDUP
-#undef STRNDUP
-}
-
-/** Read all tokens from a string between <b>start</b> and <b>end</b>, and add
- * them to <b>out</b>. Parse according to the token rules in <b>table</b>.
- * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the
- * entire string.
- */
-static int
-tokenize_string(memarea_t *area,
- const char *start, const char *end, smartlist_t *out,
- token_rule_t *table, int flags)
-{
- const char **s;
- directory_token_t *tok = NULL;
- int counts[NIL_];
- int i;
- int first_nonannotation;
- int prev_len = smartlist_len(out);
- tor_assert(area);
-
- s = &start;
- if (!end) {
- end = start+strlen(start);
- } else {
- /* it's only meaningful to check for nuls if we got an end-of-string ptr */
- if (memchr(start, '\0', end-start)) {
- log_warn(LD_DIR, "parse error: internal NUL character.");
- return -1;
- }
- }
- for (i = 0; i < NIL_; ++i)
- counts[i] = 0;
-
- SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]);
-
- while (*s < end && (!tok || tok->tp != EOF_)) {
- tok = get_next_token(area, s, end, table);
- if (tok->tp == ERR_) {
- log_warn(LD_DIR, "parse error: %s", tok->error);
- token_clear(tok);
- return -1;
- }
- ++counts[tok->tp];
- smartlist_add(out, tok);
- *s = eat_whitespace_eos(*s, end);
- }
-
- if (flags & TS_NOCHECK)
- return 0;
-
- if ((flags & TS_ANNOTATIONS_OK)) {
- first_nonannotation = -1;
- for (i = 0; i < smartlist_len(out); ++i) {
- tok = smartlist_get(out, i);
- if (tok->tp < MIN_ANNOTATION || tok->tp > MAX_ANNOTATION) {
- first_nonannotation = i;
- break;
- }
- }
- if (first_nonannotation < 0) {
- log_warn(LD_DIR, "parse error: item contains only annotations");
- return -1;
- }
- for (i=first_nonannotation; i < smartlist_len(out); ++i) {
- tok = smartlist_get(out, i);
- if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
- log_warn(LD_DIR, "parse error: Annotations mixed with keywords");
- return -1;
- }
- }
- if ((flags & TS_NO_NEW_ANNOTATIONS)) {
- if (first_nonannotation != prev_len) {
- log_warn(LD_DIR, "parse error: Unexpected annotations.");
- return -1;
- }
- }
- } else {
- for (i=0; i < smartlist_len(out); ++i) {
- tok = smartlist_get(out, i);
- if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) {
- log_warn(LD_DIR, "parse error: no annotations allowed.");
- return -1;
- }
- }
- first_nonannotation = 0;
- }
- for (i = 0; table[i].t; ++i) {
- if (counts[table[i].v] < table[i].min_cnt) {
- log_warn(LD_DIR, "Parse error: missing %s element.", table[i].t);
- return -1;
- }
- if (counts[table[i].v] > table[i].max_cnt) {
- log_warn(LD_DIR, "Parse error: too many %s elements.", table[i].t);
- return -1;
- }
- if (table[i].pos & AT_START) {
- if (smartlist_len(out) < 1 ||
- (tok = smartlist_get(out, first_nonannotation))->tp != table[i].v) {
- log_warn(LD_DIR, "Parse error: first item is not %s.", table[i].t);
- return -1;
- }
- }
- if (table[i].pos & AT_END) {
- if (smartlist_len(out) < 1 ||
- (tok = smartlist_get(out, smartlist_len(out)-1))->tp != table[i].v) {
- log_warn(LD_DIR, "Parse error: last item is not %s.", table[i].t);
- return -1;
- }
- }
- }
- return 0;
-}
-
-/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return
- * NULL if no such keyword is found.
- */
-static directory_token_t *
-find_opt_by_keyword(smartlist_t *s, directory_keyword keyword)
-{
- SMARTLIST_FOREACH(s, directory_token_t *, t, if (t->tp == keyword) return t);
- return NULL;
-}
-
-/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail
- * with an assert if no such keyword is found.
- */
-static directory_token_t *
-find_by_keyword_(smartlist_t *s, directory_keyword keyword,
- const char *keyword_as_string)
-{
- directory_token_t *tok = find_opt_by_keyword(s, keyword);
- if (PREDICT_UNLIKELY(!tok)) {
- log_err(LD_BUG, "Missing %s [%d] in directory object that should have "
- "been validated. Internal error.", keyword_as_string, (int)keyword);
- tor_assert(tok);
- }
- return tok;
-}
-
-/** If there are any directory_token_t entries in <b>s</b> whose keyword is
- * <b>k</b>, return a newly allocated smartlist_t containing all such entries,
- * in the same order in which they occur in <b>s</b>. Otherwise return
- * NULL. */
-static smartlist_t *
-find_all_by_keyword(smartlist_t *s, directory_keyword k)
-{
- smartlist_t *out = NULL;
- SMARTLIST_FOREACH(s, directory_token_t *, t,
- if (t->tp == k) {
- if (!out)
- out = smartlist_new();
- smartlist_add(out, t);
- });
- return out;
-}
-
/** Return a newly allocated smartlist of all accept or reject tokens in
* <b>s</b>.
*/
@@ -5254,13 +4514,24 @@ router_get_hash_impl(const char *s, size_t s_len, char *digest,
&start,&end)<0)
return -1;
+ return router_compute_hash_final(digest, start, end-start, alg);
+}
+
+/** Compute the digest of the <b>len</b>-byte directory object at
+ * <b>start</b>, using <b>alg</b>. Store the result in <b>digest</b>, which
+ * must be long enough to hold it. */
+MOCK_IMPL(STATIC int,
+router_compute_hash_final,(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg))
+{
if (alg == DIGEST_SHA1) {
- if (crypto_digest(digest, start, end-start)) {
+ if (crypto_digest(digest, start, len) < 0) {
log_warn(LD_BUG,"couldn't compute digest");
return -1;
}
} else {
- if (crypto_digest256(digest, start, end-start, alg)) {
+ if (crypto_digest256(digest, start, len, alg) < 0) {
log_warn(LD_BUG,"couldn't compute digest");
return -1;
}
@@ -5446,11 +4717,13 @@ microdescs_parse_from_string(const char *s, const char *eos,
if (!strcmp(t->args[0], "ed25519")) {
if (md->ed25519_identity_pkey) {
log_warn(LD_DIR, "Extra ed25519 key in microdesc");
+ smartlist_free(id_lines);
goto next;
}
ed25519_public_key_t k;
if (ed25519_public_from_base64(&k, t->args[1])<0) {
log_warn(LD_DIR, "Bogus ed25519 key in microdesc");
+ smartlist_free(id_lines);
goto next;
}
md->ed25519_identity_pkey = tor_memdup(&k, sizeof(k));
@@ -5476,7 +4749,7 @@ microdescs_parse_from_string(const char *s, const char *eos,
escaped(tok->args[i]));
goto next;
}
- smartlist_add(md->family, tor_strdup(tok->args[i]));
+ smartlist_add_strdup(md->family, tok->args[i]);
}
}
@@ -5923,7 +5196,8 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out,
* descriptor. */
tok = find_by_keyword(tokens, R_PUBLICATION_TIME);
tor_assert(tok->n_args == 1);
- if (parse_iso_time_(tok->args[0], &result->timestamp, strict_time_fmt) < 0) {
+ if (parse_iso_time_(tok->args[0], &result->timestamp,
+ strict_time_fmt, 0) < 0) {
log_warn(LD_REND, "Invalid publication time: '%s'", tok->args[0]);
goto err;
}
diff --git a/src/or/routerparse.h b/src/or/routerparse.h
index 01a5de88e8..648f29b0d3 100644
--- a/src/or/routerparse.h
+++ b/src/or/routerparse.h
@@ -113,7 +113,6 @@ STATIC int routerstatus_parse_guardfraction(const char *guardfraction_str,
MOCK_DECL(STATIC dumped_desc_t *, dump_desc_populate_one_file,
(const char *dirname, const char *f));
STATIC void dump_desc_populate_fifo_from_directory(const char *dirname);
-STATIC void dump_desc(const char *desc, const char *type);
STATIC void dump_desc_fifo_cleanup(void);
struct memarea_t;
STATIC routerstatus_t *routerstatus_parse_entry_from_string(
@@ -123,6 +122,12 @@ STATIC routerstatus_t *routerstatus_parse_entry_from_string(
vote_routerstatus_t *vote_rs,
int consensus_method,
consensus_flavor_t flav);
+MOCK_DECL(STATIC void,dump_desc,(const char *desc, const char *type));
+MOCK_DECL(STATIC int, router_compute_hash_final,(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg));
+MOCK_DECL(STATIC int, signed_digest_equals,
+ (const uint8_t *d1, const uint8_t *d2, size_t len));
#endif
#define ED_DESC_SIGNATURE_PREFIX "Tor router descriptor signature v1"
diff --git a/src/or/routerset.c b/src/or/routerset.c
index 58b66ea777..d0df0a74e6 100644
--- a/src/or/routerset.c
+++ b/src/or/routerset.c
@@ -28,6 +28,7 @@
#define ROUTERSET_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "geoip.h"
#include "nodelist.h"
#include "policies.h"
@@ -262,12 +263,12 @@ routerset_add_unknown_ccs(routerset_t **setp, int only_if_some_cc_set)
geoip_get_country("A1") >= 0;
if (add_unknown) {
- smartlist_add(set->country_names, tor_strdup("??"));
- smartlist_add(set->list, tor_strdup("{??}"));
+ smartlist_add_strdup(set->country_names, "??");
+ smartlist_add_strdup(set->list, "{??}");
}
if (add_a1) {
- smartlist_add(set->country_names, tor_strdup("a1"));
- smartlist_add(set->list, tor_strdup("{a1}"));
+ smartlist_add_strdup(set->country_names, "a1");
+ smartlist_add_strdup(set->list, "{a1}");
}
if (add_unknown || add_a1) {
@@ -334,6 +335,18 @@ routerset_contains_node(const routerset_t *set, const node_t *node)
return 0;
}
+/** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */
+int
+routerset_contains_bridge(const routerset_t *set, const bridge_info_t *bridge)
+{
+ const char *id = (const char*)bridge_get_rsa_id_digest(bridge);
+ const tor_addr_port_t *addrport = bridge_get_addr_port(bridge);
+
+ tor_assert(addrport);
+ return routerset_contains(set, &addrport->addr, addrport->port,
+ NULL, id, -1);
+}
+
/** Add every known node_t that is a member of <b>routerset</b> to
* <b>out</b>, but never add any that are part of <b>excludeset</b>.
* If <b>running_only</b>, only add the running ones. */
diff --git a/src/or/routerset.h b/src/or/routerset.h
index c2f7205c3e..2e3b4b0fe0 100644
--- a/src/or/routerset.h
+++ b/src/or/routerset.h
@@ -26,8 +26,11 @@ int routerset_contains_routerstatus(const routerset_t *set,
country_t country);
int routerset_contains_extendinfo(const routerset_t *set,
const extend_info_t *ei);
-
+struct bridge_info_t;
+int routerset_contains_bridge(const routerset_t *set,
+ const struct bridge_info_t *bridge);
int routerset_contains_node(const routerset_t *set, const node_t *node);
+
void routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset,
const routerset_t *excludeset,
int running_only);
diff --git a/src/or/scheduler.c b/src/or/scheduler.c
index 49ac1b939a..033e6d119c 100644
--- a/src/or/scheduler.c
+++ b/src/or/scheduler.c
@@ -1,11 +1,6 @@
/* * Copyright (c) 2013-2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
-/**
- * \file scheduler.c
- * \brief Relay scheduling system
- **/
-
#include "or.h"
#define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */
@@ -32,66 +27,102 @@ static uint32_t sched_q_high_water = 32768;
static uint32_t sched_max_flush_cells = 16;
-/*
- * Write scheduling works by keeping track of which channels can
+/**
+ * \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.
+ *
+ * The earliest versions of Tor approximated a kind of round-robin system
+ * among active connections, but only approximated it.
+ *
+ * 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:
*
- * 1.) Not open for writes, no cells to send
- * - Not much to do here, and the channel will have scheduler_state ==
- * SCHED_CHAN_IDLE
- * - Transitions from:
- * - Open for writes/has cells by simultaneously draining all circuit
+ * <ol>
+ * <li>
+ * Not open for writes, no cells to send.
+ * <ul><li> Not much to do here, and the channel will have scheduler_state
+ * == SCHED_CHAN_IDLE
+ * <li> Transitions from:
+ * <ul>
+ * <li>Open for writes/has cells by simultaneously draining all circuit
* queues and filling the output buffer.
- * - Transitions to:
- * - Not open for writes/has cells by arrival of cells on an attached
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li> Not open for writes/has cells by arrival of cells on an attached
* circuit (this would be driven from append_cell_to_circuit_queue())
- * - Open for writes/no cells by a channel type specific path;
+ * <li> Open for writes/no cells by a channel type specific path;
* driven from connection_or_flushed_some() for channel_tls_t.
+ * </ul>
+ * </ul>
*
- * 2.) Open for writes, no cells to send
- * - Not much here either; this will be the state an idle but open channel
- * can be expected to settle in. It will have scheduler_state ==
- * SCHED_CHAN_WAITING_FOR_CELLS
- * - Transitions from:
- * - Not open for writes/no cells by flushing some of the output
+ * <li> Open for writes, no cells to send
+ * <ul>
+ * <li>Not much here either; this will be the state an idle but open
+ * channel can be expected to settle in. It will have scheduler_state
+ * == SCHED_CHAN_WAITING_FOR_CELLS
+ * <li> Transitions from:
+ * <ul>
+ * <li>Not open for writes/no cells by flushing some of the output
* buffer.
- * - Open for writes/has cells by the scheduler moving cells from
+ * <li>Open for writes/has cells by the scheduler moving cells from
* circuit queues to channel output queue, but not having enough
* to fill the output queue.
- * - Transitions to:
- * - Open for writes/has cells by arrival of new cells on an attached
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li>Open for writes/has cells by arrival of new cells on an attached
* circuit, in append_cell_to_circuit_queue()
+ * </ul>
+ * </ul>
*
- * 3.) Not open for writes, cells to send
- * - This is the state of a busy circuit limited by output bandwidth;
+ * <li>Not open for writes, cells to send
+ * <ul>
+ * <li>This is the state of a busy circuit limited by output bandwidth;
* cells have piled up in the circuit queues waiting to be relayed.
* The channel will have scheduler_state == SCHED_CHAN_WAITING_TO_WRITE.
- * - Transitions from:
- * - Not open for writes/no cells by arrival of cells on an attached
+ * <li> Transitions from:
+ * <ul>
+ * <li>Not open for writes/no cells by arrival of cells on an attached
* circuit
- * - 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
- * - Transitions to:
- * - Opens for writes/has cells by draining some of the output buffer
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li>Opens for writes/has cells by draining some of the output buffer
* via the connection_or_flushed_some() path (for channel_tls_t).
+ * </ul>
+ * </ul>
*
- * 4.) Open for writes, cells to send
- * - This connection is ready to relay some cells and waiting for
+ * <li>Open for writes, cells to send
+ * <ul>
+ * <li>This connection is ready to relay some cells and waiting for
* the scheduler to choose it. The channel will have scheduler_state ==
* SCHED_CHAN_PENDING.
- * - Transitions from:
- * - Not open for writes/has cells by the connection_or_flushed_some()
+ * <li>Transitions from:
+ * <ul>
+ * <li> Not open for writes/has cells by the connection_or_flushed_some()
* path
- * - 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
- * - Transitions to:
- * - Not open for writes/no cells by draining all circuit queues and
- * simultaneously filling the output buffer.
- * - Not open for writes/has cells by writing enough cells to fill the
+ * </ul>
+ * <li> Transitions to:
+ * <ul>
+ * <li>Not open for writes/no cells by draining all circuit queues and
+ * simultaneously filling the output buffer.
+ * <li>Not open for writes/has cells by writing enough cells to fill the
* output buffer
- * - Open for writes/no cells by draining all attached circuit queues
+ * <li>Open for writes/no cells by draining all attached circuit queues
* without also filling the output buffer
+ * </ul>
+ * </ul>
+ * </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-
diff --git a/src/or/shared_random.c b/src/or/shared_random.c
index 5f6b03f1ba..f798a51a9f 100644
--- a/src/or/shared_random.c
+++ b/src/or/shared_random.c
@@ -192,7 +192,7 @@ verify_commit_and_reveal(const sr_commit_t *commit)
/* Use the invariant length since the encoded reveal variable has an
* extra byte for the NUL terminated byte. */
if (crypto_digest256(received_hashed_reveal, commit->encoded_reveal,
- SR_REVEAL_BASE64_LEN, commit->alg)) {
+ SR_REVEAL_BASE64_LEN, commit->alg) < 0) {
/* Unable to digest the reveal blob, this is unlikely. */
goto invalid;
}
@@ -502,6 +502,20 @@ get_vote_line_from_commit(const sr_commit_t *commit, sr_phase_t phase)
return vote_line;
}
+/* Convert a given srv object to a string for the control port. This doesn't
+ * fail and the srv object MUST be valid. */
+static char *
+srv_to_control_string(const sr_srv_t *srv)
+{
+ char *srv_str;
+ char srv_hash_encoded[SR_SRV_VALUE_BASE64_LEN + 1];
+ tor_assert(srv);
+
+ sr_srv_encode(srv_hash_encoded, sizeof(srv_hash_encoded), srv);
+ tor_asprintf(&srv_str, "%s", srv_hash_encoded);
+ return srv_str;
+}
+
/* Return a heap allocated string that contains the given <b>srv</b> string
* representation formatted for a networkstatus document using the
* <b>key</b> as the start of the line. This doesn't return NULL. */
@@ -932,7 +946,7 @@ sr_generate_our_commit(time_t timestamp, const authority_cert_t *my_rsa_cert)
/* The invariant length is used here since the encoded reveal variable
* has an extra byte added for the NULL terminated byte. */
if (crypto_digest256(commit->hashed_reveal, commit->encoded_reveal,
- SR_REVEAL_BASE64_LEN, commit->alg)) {
+ SR_REVEAL_BASE64_LEN, commit->alg) < 0) {
goto error;
}
@@ -1012,7 +1026,7 @@ sr_compute_srv(void)
SMARTLIST_FOREACH(chunks, char *, s, tor_free(s));
smartlist_free(chunks);
if (crypto_digest256(hashed_reveals, reveals, strlen(reveals),
- SR_DIGEST_ALG)) {
+ SR_DIGEST_ALG) < 0) {
goto end;
}
current_srv = generate_srv(hashed_reveals, reveal_num,
@@ -1348,6 +1362,38 @@ sr_save_and_cleanup(void)
sr_cleanup();
}
+/* Return the current SRV string representation for the control port. Return a
+ * newly allocated string on success containing the value else "" if not found
+ * or if we don't have a valid consensus yet. */
+char *
+sr_get_current_for_control(void)
+{
+ char *srv_str;
+ const networkstatus_t *c = networkstatus_get_latest_consensus();
+ if (c && c->sr_info.current_srv) {
+ srv_str = srv_to_control_string(c->sr_info.current_srv);
+ } else {
+ srv_str = tor_strdup("");
+ }
+ return srv_str;
+}
+
+/* Return the previous SRV string representation for the control port. Return
+ * a newly allocated string on success containing the value else "" if not
+ * found or if we don't have a valid consensus yet. */
+char *
+sr_get_previous_for_control(void)
+{
+ char *srv_str;
+ const networkstatus_t *c = networkstatus_get_latest_consensus();
+ if (c && c->sr_info.previous_srv) {
+ srv_str = srv_to_control_string(c->sr_info.previous_srv);
+ } else {
+ srv_str = tor_strdup("");
+ }
+ return srv_str;
+}
+
#ifdef TOR_UNIT_TESTS
/* Set the global value of number of SRV agreements so the test can play
diff --git a/src/or/shared_random.h b/src/or/shared_random.h
index 9885934cc7..dbb8effeaa 100644
--- a/src/or/shared_random.h
+++ b/src/or/shared_random.h
@@ -129,6 +129,10 @@ const char *sr_commit_get_rsa_fpr(const sr_commit_t *commit)
void sr_compute_srv(void);
sr_commit_t *sr_generate_our_commit(time_t timestamp,
const authority_cert_t *my_rsa_cert);
+
+char *sr_get_current_for_control(void);
+char *sr_get_previous_for_control(void);
+
#ifdef SHARED_RANDOM_PRIVATE
/* Encode */
diff --git a/src/or/statefile.c b/src/or/statefile.c
index 8fa4324b25..a95ba8533c 100644
--- a/src/or/statefile.c
+++ b/src/or/statefile.c
@@ -102,6 +102,8 @@ static config_var_t state_vars_[] = {
V(BWHistoryDirWriteValues, CSV, ""),
V(BWHistoryDirWriteMaxima, CSV, ""),
+ V(Guard, LINELIST, NULL),
+
V(TorVersion, STRING, NULL),
V(LastRotatedOnionKey, ISOTIME, NULL),
diff --git a/src/or/torcert.c b/src/or/torcert.c
index a6a33c675a..c58f3da2d3 100644
--- a/src/or/torcert.c
+++ b/src/or/torcert.c
@@ -6,8 +6,27 @@
*
* \brief Implementation for ed25519-signed certificates as used in the Tor
* protocol.
+ *
+ * This certificate format is designed to be simple and compact; it's
+ * documented in tor-spec.txt in the torspec.git repository. All of the
+ * certificates in this format are signed with an Ed25519 key; the
+ * contents themselves may be another Ed25519 key, a digest of a
+ * RSA key, or some other material.
+ *
+ * In this module there is also support for a crooss-certification of
+ * Ed25519 identities using (older) RSA1024 identities.
+ *
+ * Tor uses other types of certificate too, beyond those described in this
+ * module. Notably, our use of TLS requires us to touch X.509 certificates,
+ * even though sensible people would stay away from those. Our X.509
+ * certificates are represented with tor_x509_cert_t, and implemented in
+ * tortls.c. We also have a separate certificate type that authorities
+ * use to authenticate their RSA signing keys with their RSA identity keys:
+ * that one is authority_cert_t, and it's mostly handled in routerlist.c.
*/
+#include "or.h"
+#include "config.h"
#include "crypto.h"
#include "torcert.h"
#include "ed25519_cert.h"
@@ -137,7 +156,12 @@ tor_cert_parse(const uint8_t *encoded, const size_t len)
cert->encoded_len = len;
memcpy(cert->signed_key.pubkey, parsed->certified_key, 32);
- cert->valid_until = parsed->exp_field * 3600;
+ int64_t valid_until_64 = ((int64_t)parsed->exp_field) * 3600;
+#if SIZEOF_TIME_T < SIZEOF_INT64_T
+ if (valid_until_64 > TIME_MAX)
+ valid_until_64 = TIME_MAX - 1;
+#endif
+ cert->valid_until = (time_t) valid_until_64;
cert->cert_type = parsed->cert_type;
for (unsigned i = 0; i < ed25519_cert_getlen_ext(parsed); ++i) {
@@ -164,11 +188,17 @@ tor_cert_parse(const uint8_t *encoded, const size_t len)
}
/** Fill in <b>checkable_out</b> with the information needed to check
- * the signature on <b>cert</b> with <b>pubkey</b>. */
+ * the signature on <b>cert</b> with <b>pubkey</b>.
+ *
+ * On success, if <b>expiration_out</b> is provided, and it is some time
+ * _after_ the expiration time of this certificate, set it to the
+ * expiration time of this certificate.
+ */
int
tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
const tor_cert_t *cert,
- const ed25519_public_key_t *pubkey)
+ const ed25519_public_key_t *pubkey,
+ time_t *expiration_out)
{
if (! pubkey) {
if (cert->signing_key_included)
@@ -185,6 +215,10 @@ tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
memcpy(checkable_out->signature.sig,
cert->encoded + signed_len, ED25519_SIG_LEN);
+ if (expiration_out) {
+ *expiration_out = MIN(*expiration_out, cert->valid_until);
+ }
+
return 0;
}
@@ -199,14 +233,15 @@ tor_cert_checksig(tor_cert_t *cert,
{
ed25519_checkable_t checkable;
int okay;
+ time_t expires = TIME_MAX;
- if (now && now > cert->valid_until) {
- cert->cert_expired = 1;
+ if (tor_cert_get_checkable_sig(&checkable, cert, pubkey, &expires) < 0)
return -1;
- }
- if (tor_cert_get_checkable_sig(&checkable, cert, pubkey) < 0)
+ if (now && now > expires) {
+ cert->cert_expired = 1;
return -1;
+ }
if (ed25519_checksig_batch(&okay, &checkable, 1) < 0) {
cert->sig_bad = 1;
@@ -255,6 +290,8 @@ tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2)
return tor_cert_eq(cert1, cert2);
}
+#define RSA_ED_CROSSCERT_PREFIX "Tor TLS RSA/Ed25519 cross-certificate"
+
/** Create new cross-certification object to certify <b>ed_key</b> as the
* master ed25519 identity key for the RSA identity key <b>rsa_key</b>.
* Allocates and stores the encoded certificate in *<b>cert</b>, and returns
@@ -279,11 +316,21 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
ssize_t sz = rsa_ed_crosscert_encode(res, alloc_sz, cc);
tor_assert(sz > 0 && sz <= alloc_sz);
+ crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
+ crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX,
+ strlen(RSA_ED_CROSSCERT_PREFIX));
+
const int signed_part_len = 32 + 4;
+ crypto_digest_add_bytes(d, (char*)res, signed_part_len);
+
+ uint8_t digest[DIGEST256_LEN];
+ crypto_digest_get_digest(d, (char*)digest, sizeof(digest));
+ crypto_digest_free(d);
+
int siglen = crypto_pk_private_sign(rsa_key,
(char*)rsa_ed_crosscert_getarray_sig(cc),
rsa_ed_crosscert_getlen_sig(cc),
- (char*)res, signed_part_len);
+ (char*)digest, sizeof(digest));
tor_assert(siglen > 0 && siglen <= (int)crypto_pk_keysize(rsa_key));
tor_assert(siglen <= UINT8_MAX);
cc->sig_len = siglen;
@@ -295,3 +342,350 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key,
return sz;
}
+/**
+ * Check whether the <b>crosscert_len</b> byte certificate in <b>crosscert</b>
+ * is in fact a correct cross-certification of <b>master_key</b> using
+ * the RSA key <b>rsa_id_key</b>.
+ *
+ * Also reject the certificate if it expired before
+ * <b>reject_if_expired_before</b>.
+ *
+ * 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)
+{
+ rsa_ed_crosscert_t *cc = NULL;
+ int rv;
+
+#define ERR(code, s) \
+ do { \
+ log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \
+ "Received a bad RSA->Ed25519 crosscert: %s", \
+ (s)); \
+ rv = (code); \
+ goto err; \
+ } while (0)
+
+ if (BUG(crypto_pk_keysize(rsa_id_key) > PK_BYTES))
+ return -1;
+
+ if (BUG(!crosscert))
+ return -1;
+
+ ssize_t parsed_len = rsa_ed_crosscert_parse(&cc, crosscert, crosscert_len);
+ if (parsed_len < 0 || crosscert_len != (size_t)parsed_len) {
+ ERR(-2, "Unparseable or overlong crosscert");
+ }
+
+ if (tor_memneq(rsa_ed_crosscert_getarray_ed_key(cc),
+ master_key->pubkey,
+ ED25519_PUBKEY_LEN)) {
+ ERR(-3, "Crosscert did not match Ed25519 key");
+ }
+
+ const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc);
+ const uint64_t expiration_time = expiration_date * 3600;
+
+ if (reject_if_expired_before < 0 ||
+ expiration_time < (uint64_t)reject_if_expired_before) {
+ ERR(-4, "Crosscert is expired");
+ }
+
+ const uint8_t *eos = rsa_ed_crosscert_get_end_of_signed(cc);
+ const uint8_t *sig = rsa_ed_crosscert_getarray_sig(cc);
+ const uint8_t siglen = rsa_ed_crosscert_get_sig_len(cc);
+ tor_assert(eos >= crosscert);
+ tor_assert((size_t)(eos - crosscert) <= crosscert_len);
+ tor_assert(siglen == rsa_ed_crosscert_getlen_sig(cc));
+
+ /* Compute the digest */
+ uint8_t digest[DIGEST256_LEN];
+ crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256);
+ crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX,
+ strlen(RSA_ED_CROSSCERT_PREFIX));
+ crypto_digest_add_bytes(d, (char*)crosscert, eos-crosscert);
+ crypto_digest_get_digest(d, (char*)digest, sizeof(digest));
+ crypto_digest_free(d);
+
+ /* Now check the signature */
+ uint8_t signed_[PK_BYTES];
+ int signed_len = crypto_pk_public_checksig(rsa_id_key,
+ (char*)signed_, sizeof(signed_),
+ (char*)sig, siglen);
+ if (signed_len < DIGEST256_LEN) {
+ ERR(-5, "Bad signature, or length of signed data not as expected");
+ }
+
+ if (tor_memneq(digest, signed_, DIGEST256_LEN)) {
+ ERR(-6, "The signature was good, but it didn't match the data");
+ }
+
+ rv = 0;
+ err:
+ rsa_ed_crosscert_free(cc);
+ return rv;
+}
+
+/** Construct and return a new empty or_handshake_certs object */
+or_handshake_certs_t *
+or_handshake_certs_new(void)
+{
+ return tor_malloc_zero(sizeof(or_handshake_certs_t));
+}
+
+/** Release all storage held in <b>certs</b> */
+void
+or_handshake_certs_free(or_handshake_certs_t *certs)
+{
+ if (!certs)
+ return;
+
+ tor_x509_cert_free(certs->auth_cert);
+ tor_x509_cert_free(certs->link_cert);
+ tor_x509_cert_free(certs->id_cert);
+
+ tor_cert_free(certs->ed_id_sign);
+ tor_cert_free(certs->ed_sign_link);
+ tor_cert_free(certs->ed_sign_auth);
+ tor_free(certs->ed_rsa_crosscert);
+
+ memwipe(certs, 0xBD, sizeof(*certs));
+ tor_free(certs);
+}
+
+#undef ERR
+#define ERR(s) \
+ do { \
+ log_fn(severity, LD_PROTOCOL, \
+ "Received a bad CERTS cell: %s", \
+ (s)); \
+ return 0; \
+ } while (0)
+
+int
+or_handshake_certs_rsa_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now)
+{
+ tor_x509_cert_t *link_cert = certs->link_cert;
+ tor_x509_cert_t *auth_cert = certs->auth_cert;
+ tor_x509_cert_t *id_cert = certs->id_cert;
+
+ if (certs->started_here) {
+ if (! (id_cert && link_cert))
+ ERR("The certs we wanted (ID, Link) were missing");
+ if (! tor_tls_cert_matches_key(tls, link_cert))
+ ERR("The link certificate didn't match the TLS public key");
+ if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, now, 0))
+ ERR("The link certificate was not valid");
+ if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, now, 1))
+ ERR("The ID certificate was not valid");
+ } else {
+ if (! (id_cert && auth_cert))
+ ERR("The certs we wanted (ID, Auth) were missing");
+ if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, now, 1))
+ ERR("The authentication certificate was not valid");
+ if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, now, 1))
+ ERR("The ID certificate was not valid");
+ }
+
+ return 1;
+}
+
+/** Check all the ed25519 certificates in <b>certs</b> against each other, and
+ * against the peer certificate in <b>tls</b> if appropriate. On success,
+ * return 0; on failure, return a negative value and warn at level
+ * <b>severity</b> */
+int
+or_handshake_certs_ed25519_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now)
+{
+ ed25519_checkable_t check[10];
+ unsigned n_checkable = 0;
+ time_t expiration = TIME_MAX;
+
+#define ADDCERT(cert, pk) \
+ do { \
+ tor_assert(n_checkable < ARRAY_LENGTH(check)); \
+ if (tor_cert_get_checkable_sig(&check[n_checkable++], cert, pk, \
+ &expiration) < 0) \
+ ERR("Could not get checkable cert."); \
+ } while (0)
+
+ if (! certs->ed_id_sign || !certs->ed_id_sign->signing_key_included) {
+ ERR("No Ed25519 signing key");
+ }
+ ADDCERT(certs->ed_id_sign, NULL);
+
+ if (certs->started_here) {
+ if (! certs->ed_sign_link)
+ ERR("No Ed25519 link key");
+ {
+ /* check for a match with the TLS cert. */
+ tor_x509_cert_t *peer_cert = tor_tls_get_peer_cert(tls);
+ if (BUG(!peer_cert)) {
+ /* This is a bug, because if we got to this point, we are a connection
+ * that was initiated here, and we completed a TLS handshake. The
+ * other side *must* have given us a certificate! */
+ ERR("No x509 peer cert"); // LCOV_EXCL_LINE
+ }
+ const common_digests_t *peer_cert_digests =
+ tor_x509_cert_get_cert_digests(peer_cert);
+ int okay = tor_memeq(peer_cert_digests->d[DIGEST_SHA256],
+ certs->ed_sign_link->signed_key.pubkey,
+ DIGEST256_LEN);
+ tor_x509_cert_free(peer_cert);
+ if (!okay)
+ ERR("Link certificate does not match TLS certificate");
+ }
+
+ ADDCERT(certs->ed_sign_link, &certs->ed_id_sign->signed_key);
+
+ } else {
+ if (! certs->ed_sign_auth)
+ ERR("No Ed25519 link authentication key");
+ ADDCERT(certs->ed_sign_auth, &certs->ed_id_sign->signed_key);
+ }
+
+ if (expiration < now) {
+ ERR("At least one certificate expired.");
+ }
+
+ /* Okay, we've gotten ready to check all the Ed25519 certificates.
+ * Now, we are going to check the RSA certificate's cross-certification
+ * with the ED certificates.
+ *
+ * FFFF In the future, we might want to make this optional.
+ */
+
+ tor_x509_cert_t *rsa_id_cert = certs->id_cert;
+ if (!rsa_id_cert) {
+ ERR("Missing legacy RSA ID certificate");
+ }
+ if (! tor_tls_cert_is_valid(severity, rsa_id_cert, rsa_id_cert, now, 1)) {
+ ERR("The legacy RSA ID certificate was not valid");
+ }
+ if (! certs->ed_rsa_crosscert) {
+ ERR("Missing RSA->Ed25519 crosscert");
+ }
+ crypto_pk_t *rsa_id_key = tor_tls_cert_get_key(rsa_id_cert);
+ if (!rsa_id_key) {
+ ERR("RSA ID cert had no RSA key");
+ }
+
+ if (rsa_ed25519_crosscert_check(certs->ed_rsa_crosscert,
+ certs->ed_rsa_crosscert_len,
+ rsa_id_key,
+ &certs->ed_id_sign->signing_key,
+ now) < 0) {
+ crypto_pk_free(rsa_id_key);
+ ERR("Invalid RSA->Ed25519 crosscert");
+ }
+ crypto_pk_free(rsa_id_key);
+ rsa_id_key = NULL;
+
+ /* FFFF We could save a little time in the client case by queueing
+ * this batch to check it later, along with the signature from the
+ * AUTHENTICATE cell. That will change our data flow a bit, though,
+ * so I say "postpone". */
+
+ if (ed25519_checksig_batch(NULL, check, n_checkable) < 0) {
+ ERR("At least one Ed25519 certificate was badly signed");
+ }
+
+ return 1;
+}
+
+/**
+ * Check the Ed certificates and/or the RSA certificates, as appropriate. If
+ * we obtained an Ed25519 identity, set *ed_id_out. If we obtained an RSA
+ * identity, set *rs_id_out. Otherwise, set them both to NULL.
+ */
+void
+or_handshake_certs_check_both(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now,
+ const ed25519_public_key_t **ed_id_out,
+ const common_digests_t **rsa_id_out)
+{
+ tor_assert(ed_id_out);
+ tor_assert(rsa_id_out);
+
+ *ed_id_out = NULL;
+ *rsa_id_out = NULL;
+
+ if (certs->ed_id_sign) {
+ if (or_handshake_certs_ed25519_ok(severity, certs, tls, now)) {
+ tor_assert(certs->ed_id_sign);
+ tor_assert(certs->id_cert);
+
+ *ed_id_out = &certs->ed_id_sign->signing_key;
+ *rsa_id_out = tor_x509_cert_get_id_digests(certs->id_cert);
+
+ /* If we reached this point, we did not look at any of the
+ * subsidiary RSA certificates, so we'd better just remove them.
+ */
+ tor_x509_cert_free(certs->link_cert);
+ tor_x509_cert_free(certs->auth_cert);
+ certs->link_cert = certs->auth_cert = NULL;
+ }
+ /* We do _not_ fall through here. If you provided us Ed25519
+ * certificates, we expect to verify them! */
+ } else {
+ /* No ed25519 keys given in the CERTS cell */
+ if (or_handshake_certs_rsa_ok(severity, certs, tls, now)) {
+ *rsa_id_out = tor_x509_cert_get_id_digests(certs->id_cert);
+ }
+ }
+}
+
+/* === ENCODING === */
+
+/* Encode the ed25519 certificate <b>cert</b> and put the newly allocated
+ * string in <b>cert_str_out</b>. Return 0 on success else a negative value. */
+int
+tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out)
+{
+ int ret = -1;
+ char *ed_cert_b64 = NULL;
+ size_t ed_cert_b64_len;
+
+ tor_assert(cert);
+ tor_assert(cert_str_out);
+
+ /* Get the encoded size and add the NUL byte. */
+ ed_cert_b64_len = base64_encode_size(cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) + 1;
+ ed_cert_b64 = tor_malloc_zero(ed_cert_b64_len);
+
+ /* Base64 encode the encoded certificate. */
+ if (base64_encode(ed_cert_b64, ed_cert_b64_len,
+ (const char *) cert->encoded, cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
+ log_err(LD_BUG, "Couldn't base64-encode ed22519 cert!");
+ goto err;
+ }
+
+ /* Put everything together in a NUL terminated string. */
+ tor_asprintf(cert_str_out,
+ "-----BEGIN ED25519 CERT-----\n"
+ "%s"
+ "-----END ED25519 CERT-----",
+ ed_cert_b64);
+ /* Success! */
+ ret = 0;
+
+ err:
+ tor_free(ed_cert_b64);
+ return ret;
+}
+
diff --git a/src/or/torcert.h b/src/or/torcert.h
index 9c819c0abb..090f6b5811 100644
--- a/src/or/torcert.h
+++ b/src/or/torcert.h
@@ -6,12 +6,15 @@
#include "crypto_ed25519.h"
-#define SIGNED_KEY_TYPE_ED25519 0x01
+#define SIGNED_KEY_TYPE_ED25519 0x01
-#define CERT_TYPE_ID_SIGNING 0x04
-#define CERT_TYPE_SIGNING_LINK 0x05
-#define CERT_TYPE_SIGNING_AUTH 0x06
-#define CERT_TYPE_ONION_ID 0x0A
+#define CERT_TYPE_ID_SIGNING 0x04
+#define CERT_TYPE_SIGNING_LINK 0x05
+#define CERT_TYPE_SIGNING_AUTH 0x06
+#define CERT_TYPE_SIGNING_HS_DESC 0x08
+#define CERT_TYPE_AUTH_HS_IP_KEY 0x09
+#define CERT_TYPE_ONION_ID 0x0A
+#define CERT_TYPE_CROSS_HS_IP_KEYS 0x0B
#define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1
@@ -57,8 +60,9 @@ tor_cert_t *tor_cert_parse(const uint8_t *cert, size_t certlen);
void tor_cert_free(tor_cert_t *cert);
int tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out,
- const tor_cert_t *out,
- const ed25519_public_key_t *pubkey);
+ const tor_cert_t *out,
+ const ed25519_public_key_t *pubkey,
+ time_t *expiration_out);
int tor_cert_checksig(tor_cert_t *cert,
const ed25519_public_key_t *pubkey, time_t now);
@@ -71,6 +75,30 @@ 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);
+
+or_handshake_certs_t *or_handshake_certs_new(void);
+void or_handshake_certs_free(or_handshake_certs_t *certs);
+int or_handshake_certs_rsa_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now);
+int or_handshake_certs_ed25519_ok(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now);
+void or_handshake_certs_check_both(int severity,
+ or_handshake_certs_t *certs,
+ tor_tls_t *tls,
+ time_t now,
+ const ed25519_public_key_t **ed_id_out,
+ const common_digests_t **rsa_id_out);
+
+int tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out);
#endif
diff --git a/src/or/transports.c b/src/or/transports.c
index 7a52b737e4..535393b1a1 100644
--- a/src/or/transports.c
+++ b/src/or/transports.c
@@ -91,13 +91,13 @@
#define PT_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "config.h"
#include "circuitbuild.h"
#include "transports.h"
#include "util.h"
#include "router.h"
#include "statefile.h"
-#include "entrynodes.h"
#include "connection_or.h"
#include "ext_orport.h"
#include "control.h"
@@ -430,7 +430,7 @@ add_transport_to_proxy(const char *transport, managed_proxy_t *mp)
{
tor_assert(mp->transports_to_launch);
if (!smartlist_contains_string(mp->transports_to_launch, transport))
- smartlist_add(mp->transports_to_launch, tor_strdup(transport));
+ smartlist_add_strdup(mp->transports_to_launch, transport);
}
/** Called when a SIGHUP occurs. Returns true if managed proxy
@@ -480,7 +480,6 @@ proxy_needs_restart(const managed_proxy_t *mp)
* preparations and then flag its state so that it will be relaunched
* in the next tick. */
static void
-
proxy_prepare_for_restart(managed_proxy_t *mp)
{
transport_t *t_tmp = NULL;
@@ -1322,7 +1321,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp)
tor_free(state_tmp);
}
- smartlist_add(envs, tor_strdup("TOR_PT_MANAGED_TRANSPORT_VER=1"));
+ smartlist_add_strdup(envs, "TOR_PT_MANAGED_TRANSPORT_VER=1");
{
char *transports_to_launch =
diff --git a/src/test/bench.c b/src/test/bench.c
index 30984fda70..99bc686f30 100644
--- a/src/test/bench.c
+++ b/src/test/bench.c
@@ -120,7 +120,7 @@ bench_onion_TAP(void)
uint64_t start, end;
char os[TAP_ONIONSKIN_CHALLENGE_LEN];
char or[TAP_ONIONSKIN_REPLY_LEN];
- crypto_dh_t *dh_out;
+ crypto_dh_t *dh_out = NULL;
key = crypto_pk_new();
key2 = crypto_pk_new();
@@ -175,6 +175,7 @@ bench_onion_TAP(void)
NANOCOUNT(start, end, iters)/1e3);
done:
+ crypto_dh_free(dh_out);
crypto_pk_free(key);
crypto_pk_free(key2);
}
diff --git a/src/test/fuzz/dict/consensus b/src/test/fuzz/dict/consensus
new file mode 100644
index 0000000000..3fcd9ee7ff
--- /dev/null
+++ b/src/test/fuzz/dict/consensus
@@ -0,0 +1,52 @@
+"a"
+"additional-digest"
+"additional-signature"
+"bandwidth-weights"
+"client-versions"
+"consensus-digest"
+"consensus-method"
+"consensus-methods"
+"contact"
+"dir-address"
+"directory-footer"
+"directory-signature"
+"dir-identity-key"
+"dir-key-certificate-version"
+"dir-key-certification"
+"dir-key-crosscert"
+"dir-key-expires"
+"dir-key-published"
+"dir-signing-key"
+"dir-source"
+"fingerprint"
+"fresh-until"
+"id"
+"known-flags"
+"legacy-dir-key"
+"m"
+"network-status-version"
+"opt"
+"p"
+"package"
+"params"
+"pr"
+"published"
+"r"
+"recommended-client-protocols"
+"recommended-relay-protocols"
+"required-client-protocols"
+"required-relay-protocols"
+"s"
+"server-versions"
+"shared-rand-commit"
+"shared-rand-current-value"
+"shared-rand-participate"
+"shared-rand-previous-value"
+"signing-ed25519"
+"v"
+"valid-after"
+"valid-until"
+"vote-digest"
+"vote-status"
+"voting-delay"
+"w"
diff --git a/src/test/fuzz/dict/descriptor b/src/test/fuzz/dict/descriptor
new file mode 100644
index 0000000000..110ee3e820
--- /dev/null
+++ b/src/test/fuzz/dict/descriptor
@@ -0,0 +1,41 @@
+"reject"
+"accept"
+"reject6"
+"accept6"
+"router"
+"ipv6-policy"
+"signing-key"
+"onion-key"
+"ntor-onion-key"
+"router-signature"
+"published"
+"uptime"
+"fingerprint"
+"hibernating"
+"platform"
+"proto"
+"contact"
+"read-history"
+"write-history"
+"extra-info-digest"
+"hidden-service-dir"
+"identity-ed25519"
+"master-key-ed25519"
+"router-sig-ed25519"
+"onion-key-crosscert"
+"ntor-onion-key-crosscert"
+"allow-single-hop-exits"
+"family"
+"caches-extra-info"
+"or-address"
+"opt"
+ "bandwidth"
+"@purpose"
+"tunnelled-dir-server"
+"-----BEGIN"
+"-----END"
+"-----"
+"ED25519 CERT"
+"RSA PUBLIC KEY"
+"CROSSCERT"
+"SIGNATURE"
diff --git a/src/test/fuzz/dict/extrainfo b/src/test/fuzz/dict/extrainfo
new file mode 100644
index 0000000000..eba7a1e4ce
--- /dev/null
+++ b/src/test/fuzz/dict/extrainfo
@@ -0,0 +1,32 @@
+"cell-circuits-per-decile"
+"cell-processed-cells"
+"cell-queued-cells"
+"cell-stats-end"
+"cell-time-in-queue"
+"dirreq-stats-end"
+"dirreq-v2-direct-dl"
+"dirreq-v2-ips"
+"dirreq-v2-reqs"
+"dirreq-v2-resp"
+"dirreq-v2-share"
+"dirreq-v2-tunneled-dl"
+"dirreq-v3-direct-dl"
+"dirreq-v3-ips"
+"dirreq-v3-reqs"
+"dirreq-v3-resp"
+"dirreq-v3-share"
+"dirreq-v3-tunneled-dl"
+"entry-ips"
+"entry-stats-end"
+"exit-kibibytes-read"
+"exit-kibibytes-written"
+"exit-stats-end"
+"exit-streams-opened"
+"extra-info"
+"identity-ed25519"
+"opt"
+"published"
+"read-history"
+"router-sig-ed25519"
+"router-signature"
+"write-history"
diff --git a/src/test/fuzz/dict/hsdescv2 b/src/test/fuzz/dict/hsdescv2
new file mode 100644
index 0000000000..48788301dc
--- /dev/null
+++ b/src/test/fuzz/dict/hsdescv2
@@ -0,0 +1,8 @@
+"introduction-points"
+"permanent-key"
+"protocol-versions"
+"publication-time"
+"rendezvous-service-descriptor"
+"secret-id-part"
+"signature"
+"version"
diff --git a/src/test/fuzz/dict/http b/src/test/fuzz/dict/http
new file mode 100644
index 0000000000..1a7b61e8d4
--- /dev/null
+++ b/src/test/fuzz/dict/http
@@ -0,0 +1,24 @@
+#
+# AFL dictionary for the Tor Directory protocol's HTTP headers
+# ------------------------------------------------------------
+#
+# Extracted from directory_handle_command() in the tor source code
+#
+# Copyright (c) 2016, The Tor Project, Inc.
+# See LICENSE for licensing information
+#
+# Usage:
+# Select the dictionaries relevant to the part of the directory protocol you
+# are fuzzing, and feed them to your fuzzer (if it supports dictionaries).
+
+http_header_body_delimiter = "\x0d\x0a\x0d\x0a"
+http_header_header_delimiter = "\x0d\x0a"
+# multi-character tokens only
+#http_header_value_delimiter = " "
+
+content_length_header = "Content-Length:"
+forwarded_for_header = "Forwarded-For:"
+x_forwarded_for_header = "X-Forwarded-For:"
+
+get_command = "GET"
+post_command = "POST"
diff --git a/src/test/fuzz/dict/iptsv2 b/src/test/fuzz/dict/iptsv2
new file mode 100644
index 0000000000..57791c5e3c
--- /dev/null
+++ b/src/test/fuzz/dict/iptsv2
@@ -0,0 +1,6 @@
+"introduction-point"
+"ip-address"
+"onion-port"
+"onion-key"
+"service-key"
+
diff --git a/src/test/fuzz/dict/microdesc b/src/test/fuzz/dict/microdesc
new file mode 100644
index 0000000000..fdd0567b65
--- /dev/null
+++ b/src/test/fuzz/dict/microdesc
@@ -0,0 +1,7 @@
+"onion-key"
+"ntor-onion-key"
+"id"
+"a"
+"family"
+"p"
+"p6"
diff --git a/src/test/fuzz/fixup_filenames.sh b/src/test/fuzz/fixup_filenames.sh
new file mode 100755
index 0000000000..68efc1abc5
--- /dev/null
+++ b/src/test/fuzz/fixup_filenames.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -e
+
+if [ ! -d "$1" ] ; then
+ echo "I need a directory"
+ exit 1
+fi
+
+for fn in "$1"/* ; do
+ prev=`basename "$fn"`
+ post=`sha256sum "$fn" | sed -e 's/ .*//;'`
+ if [ "$prev" == "$post" ] ; then
+ echo "OK $prev"
+ else
+ echo "mv $prev $post"
+ mv "$fn" "$1/$post"
+ fi
+done
diff --git a/src/test/fuzz/fuzz_consensus.c b/src/test/fuzz/fuzz_consensus.c
new file mode 100644
index 0000000000..f5d22f69ae
--- /dev/null
+++ b/src/test/fuzz/fuzz_consensus.c
@@ -0,0 +1,78 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "networkstatus.h"
+#include "fuzzing.h"
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+static int
+mock_router_produce_hash_final__nohash(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg)
+{
+ (void)start;
+ (void)len;
+ /* we could look at start[..] */
+ if (alg == DIGEST_SHA1)
+ memset(digest, 0x01, 20);
+ else
+ memset(digest, 0x02, 32);
+ return 0;
+}
+
+static int
+mock_signed_digest_equals__yes(const uint8_t *d1, const uint8_t *d2,
+ size_t len)
+{
+ (void) tor_memeq(d1, d2, len);
+ return 1;
+}
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ MOCK(router_compute_hash_final, mock_router_produce_hash_final__nohash);
+ MOCK(signed_digest_equals, mock_signed_digest_equals__yes);
+ ed25519_init();
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ networkstatus_t *ns;
+ char *str = tor_memdup_nulterm(data, sz);
+ const char *eos = NULL;
+ networkstatus_type_t tp = NS_TYPE_CONSENSUS;
+ if (tor_memstr(data, MIN(sz, 1024), "tus vote"))
+ tp = NS_TYPE_VOTE;
+ const char *what = (tp == NS_TYPE_CONSENSUS) ? "consensus" : "vote";
+ ns = networkstatus_parse_vote_from_string(str,
+ &eos,
+ tp);
+ if (ns) {
+ log_debug(LD_GENERAL, "Parsing as %s okay", what);
+ networkstatus_vote_free(ns);
+ } else {
+ log_debug(LD_GENERAL, "Parsing as %s failed", what);
+ }
+ tor_free(str);
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_descriptor.c b/src/test/fuzz/fuzz_descriptor.c
new file mode 100644
index 0000000000..d19386d77f
--- /dev/null
+++ b/src/test/fuzz/fuzz_descriptor.c
@@ -0,0 +1,79 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "routerlist.h"
+#include "routerkeys.h"
+#include "fuzzing.h"
+
+static int
+mock_check_tap_onion_key_crosscert__nocheck(const uint8_t *crosscert,
+ int crosscert_len,
+ const crypto_pk_t *onion_pkey,
+ const ed25519_public_key_t *master_id_pkey,
+ const uint8_t *rsa_id_digest)
+{
+ tor_assert(crosscert && onion_pkey && master_id_pkey && rsa_id_digest);
+ /* we could look at crosscert[..] */
+ (void) crosscert_len;
+ return 0;
+}
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+static int
+mock_router_produce_hash_final__nohash(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg)
+{
+ (void)start;
+ (void)len;
+ /* we could look at start[..] */
+ if (alg == DIGEST_SHA1)
+ memset(digest, 0x01, 20);
+ else
+ memset(digest, 0x02, 32);
+ return 0;
+}
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(check_tap_onion_key_crosscert,
+ mock_check_tap_onion_key_crosscert__nocheck);
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ MOCK(router_compute_hash_final, mock_router_produce_hash_final__nohash);
+ ed25519_init();
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ routerinfo_t *ri;
+ const char *str = (const char*) data;
+ ri = router_parse_entry_from_string((const char *)str,
+ str+sz,
+ 0, 0, 0, NULL);
+ if (ri) {
+ log_debug(LD_GENERAL, "Parsing okay");
+ routerinfo_free(ri);
+ } else {
+ log_debug(LD_GENERAL, "Parsing failed");
+ }
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_extrainfo.c b/src/test/fuzz/fuzz_extrainfo.c
new file mode 100644
index 0000000000..6251e606d0
--- /dev/null
+++ b/src/test/fuzz/fuzz_extrainfo.c
@@ -0,0 +1,65 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "routerlist.h"
+#include "routerkeys.h"
+#include "fuzzing.h"
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+static int
+mock_router_produce_hash_final__nohash(char *digest,
+ const char *start, size_t len,
+ digest_algorithm_t alg)
+{
+ (void)start;
+ (void)len;
+ /* we could look at start[..] */
+ if (alg == DIGEST_SHA1)
+ memset(digest, 0x01, 20);
+ else
+ memset(digest, 0x02, 32);
+ return 0;
+}
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ MOCK(router_compute_hash_final, mock_router_produce_hash_final__nohash);
+ ed25519_init();
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ extrainfo_t *ei;
+ const char *str = (const char*) data;
+ int again = 0;
+ ei = extrainfo_parse_entry_from_string((const char *)str,
+ str+sz,
+ 0, NULL, &again);
+ if (ei) {
+ log_debug(LD_GENERAL, "Parsing okay");
+ extrainfo_free(ei);
+ } else {
+ log_debug(LD_GENERAL, "Parsing failed");
+ }
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_hsdescv2.c b/src/test/fuzz/fuzz_hsdescv2.c
new file mode 100644
index 0000000000..53b7cbe2f7
--- /dev/null
+++ b/src/test/fuzz/fuzz_hsdescv2.c
@@ -0,0 +1,52 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "rendcommon.h"
+#include "fuzzing.h"
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ ed25519_init();
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ rend_service_descriptor_t *desc = NULL;
+ char desc_id[64];
+ char *ipts = NULL;
+ size_t ipts_size, esize;
+ const char *next;
+ char *str = tor_memdup_nulterm(data, sz);
+ (void) rend_parse_v2_service_descriptor(&desc, desc_id, &ipts, &ipts_size,
+ &esize, &next, str, 1);
+ if (desc) {
+ log_debug(LD_GENERAL, "Parsing okay");
+ rend_service_descriptor_free(desc);
+ } else {
+ log_debug(LD_GENERAL, "Parsing failed");
+ }
+ tor_free(ipts);
+ tor_free(str);
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_http.c b/src/test/fuzz/fuzz_http.c
new file mode 100644
index 0000000000..01c3815f18
--- /dev/null
+++ b/src/test/fuzz/fuzz_http.c
@@ -0,0 +1,133 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+
+#define BUFFERS_PRIVATE
+#define DIRECTORY_PRIVATE
+
+#include "or.h"
+#include "backtrace.h"
+#include "buffers.h"
+#include "config.h"
+#include "connection.h"
+#include "directory.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 zlib)
+{
+ log_debug(LD_GENERAL, "%sResponse:\n%u\nConnection: %p\n%s\n",
+ zlib ? "Compressed " : "", (unsigned)len, conn, string);
+}
+
+static int
+mock_directory_handle_command_get(dir_connection_t *conn,
+ const char *headers,
+ const char *body,
+ size_t body_len)
+{
+ (void)conn;
+
+ log_debug(LD_GENERAL, "Method:\nGET\n");
+
+ if (headers) {
+ log_debug(LD_GENERAL, "Header-Length:\n%u\n", (unsigned)strlen(headers));
+ log_debug(LD_GENERAL, "Headers:\n%s\n", headers);
+ }
+
+ log_debug(LD_GENERAL, "Body-Length:\n%u\n", (unsigned)body_len);
+ if (body) {
+ log_debug(LD_GENERAL, "Body:\n%s\n", body);
+ }
+
+ /* Always tell the caller we succeeded */
+ return 0;
+}
+
+static int
+mock_directory_handle_command_post(dir_connection_t *conn,
+ const char *headers,
+ const char *body,
+ size_t body_len)
+{
+ (void)conn;
+
+ log_debug(LD_GENERAL, "Method:\nPOST\n");
+
+ if (headers) {
+ log_debug(LD_GENERAL, "Header-Length:\n%u\n", (unsigned)strlen(headers));
+ log_debug(LD_GENERAL, "Headers:\n%s\n", headers);
+ }
+
+ log_debug(LD_GENERAL, "Body-Length:\n%u\n", (unsigned)body_len);
+ if (body) {
+ log_debug(LD_GENERAL, "Body:\n%s\n", body);
+ }
+
+ /* Always tell the caller we succeeded */
+ 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(directory_handle_command_get, mock_directory_handle_command_get);
+ MOCK(directory_handle_command_post, mock_directory_handle_command_post);
+
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ UNMOCK(connection_write_to_buf_impl_);
+ UNMOCK(directory_handle_command_get);
+ UNMOCK(directory_handle_command_post);
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *stdin_buf, size_t data_size)
+{
+ dir_connection_t dir_conn;
+
+ /* Set up the fake connection */
+ memset(&dir_conn, 0, sizeof(dir_connection_t));
+ dir_conn.base_.type = CONN_TYPE_DIR;
+ /* Apparently tor sets this before directory_handle_command() is called. */
+ dir_conn.base_.address = tor_strdup("replace-this-address.example.com");
+
+ dir_conn.base_.inbuf = buf_new_with_data((char*)stdin_buf, data_size);
+ if (!dir_conn.base_.inbuf) {
+ log_debug(LD_GENERAL, "Zero-Length-Input\n");
+ goto done;
+ }
+
+ /* Parse the headers */
+ int rv = directory_handle_command(&dir_conn);
+
+ /* TODO: check the output is correctly parsed based on the input */
+
+ /* Report the parsed origin address */
+ if (dir_conn.base_.address) {
+ log_debug(LD_GENERAL, "Address:\n%s\n", dir_conn.base_.address);
+ }
+
+ log_debug(LD_GENERAL, "Result:\n%d\n", rv);
+
+ done:
+ /* Reset. */
+ tor_free(dir_conn.base_.address);
+ buf_free(dir_conn.base_.inbuf);
+ dir_conn.base_.inbuf = NULL;
+
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_iptsv2.c b/src/test/fuzz/fuzz_iptsv2.c
new file mode 100644
index 0000000000..341d4880bd
--- /dev/null
+++ b/src/test/fuzz/fuzz_iptsv2.c
@@ -0,0 +1,46 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "rendcommon.h"
+#include "fuzzing.h"
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ ed25519_init();
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ rend_service_descriptor_t *desc =
+ tor_malloc_zero(sizeof(rend_service_descriptor_t));
+ const char *str = (const char*) data;
+ int r = rend_parse_introduction_points(desc, str, sz);
+ if (r >= 0) {
+ log_debug(LD_GENERAL, "Parsing okay: %d", r);
+ } else {
+ log_debug(LD_GENERAL, "Parsing failed");
+ }
+ rend_service_descriptor_free(desc);
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_microdesc.c b/src/test/fuzz/fuzz_microdesc.c
new file mode 100644
index 0000000000..bb89546191
--- /dev/null
+++ b/src/test/fuzz/fuzz_microdesc.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "microdesc.h"
+#include "fuzzing.h"
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ ed25519_init();
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ const char *str = (const char*) data;
+ smartlist_t *result = microdescs_parse_from_string((const char *)str,
+ str+sz,
+ 0, SAVED_NOWHERE, NULL);
+ if (result) {
+ log_debug(LD_GENERAL, "Parsing okay: %d", smartlist_len(result));
+ SMARTLIST_FOREACH(result, microdesc_t *, md, microdesc_free(md));
+ smartlist_free(result);
+ } else {
+ log_debug(LD_GENERAL, "Parsing failed");
+ }
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzz_multi.sh b/src/test/fuzz/fuzz_multi.sh
new file mode 100755
index 0000000000..b4a17ed8cb
--- /dev/null
+++ b/src/test/fuzz/fuzz_multi.sh
@@ -0,0 +1,34 @@
+MEMLIMIT_BYTES=21990500990976
+
+N_CPUS=1
+if [ $# -ge 1 ]; then
+ N_CPUS="$1"
+ shift
+fi
+
+FILTER=echo
+
+for i in `seq -w "$N_CPUS"`; do
+ if [ "$i" -eq 1 ]; then
+ if [ "$N_CPUS" -eq 1 ]; then
+ INSTANCE=""
+ NUMBER=""
+ else
+ INSTANCE="-M"
+ NUMBER="$i"
+ fi
+ else
+ INSTANCE="-S"
+ NUMBER="$i"
+ fi
+ # use whatever remains on the command-line to prefix the fuzzer command
+ # you have to copy and paste and run these commands yourself
+ "$FILTER" "$@" \
+ ../afl/afl-fuzz \
+ -i src/test/fuzz/fuzz_dir_testcase \
+ -o src/test/fuzz/fuzz_dir_findings \
+ -x src/test/fuzz/fuzz_dir_dictionary/fuzz_dir_http_header.dct \
+ -m "$MEMLIMIT_BYTES" \
+ "$INSTANCE" "$NUMBER" \
+ -- src/test/fuzz_dir
+done
diff --git a/src/test/fuzz/fuzz_vrs.c b/src/test/fuzz/fuzz_vrs.c
new file mode 100644
index 0000000000..9301a9bcc8
--- /dev/null
+++ b/src/test/fuzz/fuzz_vrs.c
@@ -0,0 +1,82 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define ROUTERPARSE_PRIVATE
+#define NETWORKSTATUS_PRIVATE
+#include "or.h"
+#include "routerparse.h"
+#include "memarea.h"
+#include "microdesc.h"
+#include "networkstatus.h"
+#include "fuzzing.h"
+
+static void
+mock_dump_desc__nodump(const char *desc, const char *type)
+{
+ (void)desc;
+ (void)type;
+}
+
+static networkstatus_t *dummy_vote = NULL;
+static memarea_t *area = NULL;
+
+int
+fuzz_init(void)
+{
+ disable_signature_checking();
+ MOCK(dump_desc, mock_dump_desc__nodump);
+ ed25519_init();
+ area = memarea_new();
+ dummy_vote = tor_malloc_zero(sizeof(*dummy_vote));
+ dummy_vote->known_flags = smartlist_new();
+ smartlist_split_string(dummy_vote->known_flags,
+ "Authority BadExit Exit Fast Guard HSDir "
+ "NoEdConsensus Running Stable V2Dir Valid",
+ " ", 0, 0);
+ return 0;
+}
+
+int
+fuzz_cleanup(void)
+{
+ SMARTLIST_FOREACH(dummy_vote->known_flags, char *, cp, tor_free(cp));
+ smartlist_free(dummy_vote->known_flags);
+ tor_free(dummy_vote);
+ return 0;
+}
+
+int
+fuzz_main(const uint8_t *data, size_t sz)
+{
+ char *str = tor_memdup_nulterm(data, sz);
+ const char *s;
+ routerstatus_t *rs_ns = NULL, *rs_md = NULL, *rs_vote = NULL;
+ vote_routerstatus_t *vrs = tor_malloc_zero(sizeof(*vrs));
+ smartlist_t *tokens = smartlist_new();
+
+ s = str;
+ rs_ns = routerstatus_parse_entry_from_string(area, &s, tokens,
+ NULL, NULL, 26, FLAV_NS);
+ tor_assert(smartlist_len(tokens) == 0);
+
+ s = str;
+ rs_md = routerstatus_parse_entry_from_string(area, &s, tokens,
+ NULL, NULL, 26, FLAV_MICRODESC);
+ tor_assert(smartlist_len(tokens) == 0);
+
+ s = str;
+ rs_vote = routerstatus_parse_entry_from_string(area, &s, tokens,
+ dummy_vote, vrs, 26, FLAV_NS);
+ tor_assert(smartlist_len(tokens) == 0);
+
+ log_debug(LD_GENERAL,
+ "ns=%p, md=%p, vote=%p", rs_ns, rs_md, rs_vote);
+
+ routerstatus_free(rs_md);
+ routerstatus_free(rs_ns);
+ vote_routerstatus_free(vrs);
+ memarea_clear(area);
+ smartlist_free(tokens);
+ tor_free(str);
+ return 0;
+}
+
diff --git a/src/test/fuzz/fuzzing.h b/src/test/fuzz/fuzzing.h
new file mode 100644
index 0000000000..4295743458
--- /dev/null
+++ b/src/test/fuzz/fuzzing.h
@@ -0,0 +1,13 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#ifndef FUZZING_H
+#define FUZZING_H
+
+int fuzz_init(void);
+int fuzz_cleanup(void);
+int fuzz_main(const uint8_t *data, size_t sz);
+
+void disable_signature_checking(void);
+
+#endif /* FUZZING_H */
+
diff --git a/src/test/fuzz/fuzzing_common.c b/src/test/fuzz/fuzzing_common.c
new file mode 100644
index 0000000000..e4920d3ee7
--- /dev/null
+++ b/src/test/fuzz/fuzzing_common.c
@@ -0,0 +1,190 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+#define CRYPTO_ED25519_PRIVATE
+#include "orconfig.h"
+#include "or.h"
+#include "backtrace.h"
+#include "config.h"
+#include "fuzzing.h"
+#include "crypto.h"
+#include "crypto_ed25519.h"
+
+extern const char tor_git_revision[];
+const char tor_git_revision[] = "";
+
+static or_options_t *mock_options = NULL;
+static const or_options_t *
+mock_get_options(void)
+{
+ return mock_options;
+}
+
+static int
+mock_crypto_pk_public_checksig__nocheck(const crypto_pk_t *env, char *to,
+ size_t tolen,
+ const char *from, size_t fromlen)
+{
+ tor_assert(env && to && from);
+ (void)fromlen;
+ /* We could look at from[0..fromlen-1] ... */
+ tor_assert(tolen >= crypto_pk_keysize(env));
+ memset(to, 0x01, 20);
+ return 20;
+}
+
+static int
+mock_crypto_pk_public_checksig_digest__nocheck(crypto_pk_t *env,
+ const char *data,
+ size_t datalen,
+ const char *sig,
+ size_t siglen)
+{
+ tor_assert(env && data && sig);
+ (void)datalen;
+ (void)siglen;
+ /* We could look at data[..] and sig[..] */
+ return 0;
+}
+
+static int
+mock_ed25519_checksig__nocheck(const ed25519_signature_t *signature,
+ const uint8_t *msg, size_t len,
+ const ed25519_public_key_t *pubkey)
+{
+ tor_assert(signature && msg && pubkey);
+ /* We could look at msg[0..len-1] ... */
+ (void)len;
+ return 0;
+}
+
+static int
+mock_ed25519_checksig_batch__nocheck(int *okay_out,
+ const ed25519_checkable_t *checkable,
+ int n_checkable)
+{
+ tor_assert(checkable);
+ int i;
+ for (i = 0; i < n_checkable; ++i) {
+ /* We could look at messages and signatures XXX */
+ tor_assert(checkable[i].pubkey);
+ tor_assert(checkable[i].msg);
+ if (okay_out)
+ okay_out[i] = 1;
+ }
+ return 0;
+}
+
+static int
+mock_ed25519_impl_spot_check__nocheck(void)
+{
+ return 0;
+}
+
+void
+disable_signature_checking(void)
+{
+ MOCK(crypto_pk_public_checksig,
+ mock_crypto_pk_public_checksig__nocheck);
+ MOCK(crypto_pk_public_checksig_digest,
+ mock_crypto_pk_public_checksig_digest__nocheck);
+ MOCK(ed25519_checksig, mock_ed25519_checksig__nocheck);
+ MOCK(ed25519_checksig_batch, mock_ed25519_checksig_batch__nocheck);
+ MOCK(ed25519_impl_spot_check, mock_ed25519_impl_spot_check__nocheck);
+}
+
+static void
+global_init(void)
+{
+ tor_threads_init();
+ {
+ struct sipkey sipkey = { 1337, 7331 };
+ siphash_set_global_key(&sipkey);
+ }
+
+ /* Initialise logging first */
+ init_logging(1);
+ configure_backtrace_handler(get_version());
+
+ /* set up the options. */
+ mock_options = tor_malloc(sizeof(or_options_t));
+ MOCK(get_options, mock_get_options);
+
+ /* Make BUG() and nonfatal asserts crash */
+ tor_set_failed_assertion_callback(abort);
+}
+
+#ifdef LLVM_FUZZ
+int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
+int
+LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
+{
+ static int initialized = 0;
+ if (!initialized) {
+ global_init();
+ if (fuzz_init() < 0)
+ abort();
+ initialized = 1;
+ }
+
+ return fuzz_main(Data, Size);
+}
+
+#else /* Not LLVM_FUZZ, so AFL. */
+
+int
+main(int argc, char **argv)
+{
+ size_t size;
+
+ global_init();
+
+ /* Disable logging by default to speed up fuzzing. */
+ int loglevel = LOG_ERR;
+
+ for (int i = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "--warn")) {
+ loglevel = LOG_WARN;
+ } else if (!strcmp(argv[i], "--notice")) {
+ loglevel = LOG_NOTICE;
+ } else if (!strcmp(argv[i], "--info")) {
+ loglevel = LOG_INFO;
+ } else if (!strcmp(argv[i], "--debug")) {
+ loglevel = LOG_DEBUG;
+ }
+ }
+
+ {
+ log_severity_list_t s;
+ memset(&s, 0, sizeof(s));
+ set_log_severity_config(loglevel, LOG_ERR, &s);
+ /* ALWAYS log bug warnings. */
+ s.masks[LOG_WARN-LOG_ERR] |= LD_BUG;
+ add_stream_log(&s, "", fileno(stdout));
+ }
+
+ if (fuzz_init() < 0)
+ abort();
+
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+ /* Tell AFL to pause and fork here - ignored if not using AFL */
+ __AFL_INIT();
+#endif
+
+#define MAX_FUZZ_SIZE (128*1024)
+ char *input = read_file_to_str_until_eof(0, MAX_FUZZ_SIZE, &size);
+ tor_assert(input);
+ char *raw = tor_memdup(input, size); /* Because input is nul-terminated */
+ tor_free(input);
+ fuzz_main((const uint8_t*)raw, size);
+ tor_free(raw);
+
+ if (fuzz_cleanup() < 0)
+ abort();
+
+ tor_free(mock_options);
+ UNMOCK(get_options);
+ return 0;
+}
+
+#endif
+
diff --git a/src/test/fuzz/include.am b/src/test/fuzz/include.am
new file mode 100644
index 0000000000..806710879b
--- /dev/null
+++ b/src/test/fuzz/include.am
@@ -0,0 +1,250 @@
+
+FUZZING_CPPFLAGS = \
+ $(src_test_AM_CPPFLAGS) $(TEST_CPPFLAGS)
+FUZZING_CFLAGS = \
+ $(AM_CFLAGS) $(TEST_CFLAGS)
+FUZZING_LDFLAG = \
+ @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@
+FUZZING_LIBS = \
+ src/or/libtor-testing.a \
+ src/common/libor-crypto-testing.a \
+ $(LIBKECCAK_TINY) \
+ $(LIBDONNA) \
+ src/common/libor-testing.a \
+ src/common/libor-ctime-testing.a \
+ src/common/libor-event-testing.a \
+ src/trunnel/libor-trunnel-testing.a \
+ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \
+ @TOR_LIBEVENT_LIBS@ \
+ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \
+ @TOR_SYSTEMD_LIBS@
+
+oss-fuzz-prereqs: \
+ src/or/libtor-testing.a \
+ src/common/libor-crypto-testing.a \
+ $(LIBKECCAK_TINY) \
+ $(LIBDONNA) \
+ src/common/libor-testing.a \
+ src/common/libor-ctime-testing.a \
+ src/common/libor-event-testing.a \
+ src/trunnel/libor-trunnel-testing.a
+
+noinst_HEADERS += \
+ src/test/fuzz/fuzzing.h
+
+src_test_fuzz_fuzz_consensus_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_consensus.c
+src_test_fuzz_fuzz_consensus_CPPFLAGS = $(FUZZING_CPPFLAGS)
+src_test_fuzz_fuzz_consensus_CFLAGS = $(FUZZING_CFLAGS)
+src_test_fuzz_fuzz_consensus_LDFLAGS = $(FUZZING_LDFLAG)
+src_test_fuzz_fuzz_consensus_LDADD = $(FUZZING_LIBS)
+
+src_test_fuzz_fuzz_descriptor_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_descriptor.c
+src_test_fuzz_fuzz_descriptor_CPPFLAGS = $(FUZZING_CPPFLAGS)
+src_test_fuzz_fuzz_descriptor_CFLAGS = $(FUZZING_CFLAGS)
+src_test_fuzz_fuzz_descriptor_LDFLAGS = $(FUZZING_LDFLAG)
+src_test_fuzz_fuzz_descriptor_LDADD = $(FUZZING_LIBS)
+
+src_test_fuzz_fuzz_http_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_http.c
+src_test_fuzz_fuzz_http_CPPFLAGS = $(FUZZING_CPPFLAGS)
+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_hsdescv2_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_hsdescv2.c
+src_test_fuzz_fuzz_hsdescv2_CPPFLAGS = $(FUZZING_CPPFLAGS)
+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_iptsv2_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_iptsv2.c
+src_test_fuzz_fuzz_iptsv2_CPPFLAGS = $(FUZZING_CPPFLAGS)
+src_test_fuzz_fuzz_iptsv2_CFLAGS = $(FUZZING_CFLAGS)
+src_test_fuzz_fuzz_iptsv2_LDFLAGS = $(FUZZING_LDFLAG)
+src_test_fuzz_fuzz_iptsv2_LDADD = $(FUZZING_LIBS)
+
+src_test_fuzz_fuzz_extrainfo_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_extrainfo.c
+src_test_fuzz_fuzz_extrainfo_CPPFLAGS = $(FUZZING_CPPFLAGS)
+src_test_fuzz_fuzz_extrainfo_CFLAGS = $(FUZZING_CFLAGS)
+src_test_fuzz_fuzz_extrainfo_LDFLAGS = $(FUZZING_LDFLAG)
+src_test_fuzz_fuzz_extrainfo_LDADD = $(FUZZING_LIBS)
+
+src_test_fuzz_fuzz_microdesc_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_microdesc.c
+src_test_fuzz_fuzz_microdesc_CPPFLAGS = $(FUZZING_CPPFLAGS)
+src_test_fuzz_fuzz_microdesc_CFLAGS = $(FUZZING_CFLAGS)
+src_test_fuzz_fuzz_microdesc_LDFLAGS = $(FUZZING_LDFLAG)
+src_test_fuzz_fuzz_microdesc_LDADD = $(FUZZING_LIBS)
+
+src_test_fuzz_fuzz_vrs_SOURCES = \
+ src/test/fuzz/fuzzing_common.c \
+ src/test/fuzz/fuzz_vrs.c
+src_test_fuzz_fuzz_vrs_CPPFLAGS = $(FUZZING_CPPFLAGS)
+src_test_fuzz_fuzz_vrs_CFLAGS = $(FUZZING_CFLAGS)
+src_test_fuzz_fuzz_vrs_LDFLAGS = $(FUZZING_LDFLAG)
+src_test_fuzz_fuzz_vrs_LDADD = $(FUZZING_LIBS)
+
+FUZZERS = \
+ src/test/fuzz/fuzz-consensus \
+ src/test/fuzz/fuzz-descriptor \
+ src/test/fuzz/fuzz-extrainfo \
+ src/test/fuzz/fuzz-http \
+ src/test/fuzz/fuzz-hsdescv2 \
+ src/test/fuzz/fuzz-iptsv2 \
+ src/test/fuzz/fuzz-microdesc \
+ src/test/fuzz/fuzz-vrs
+
+
+LIBFUZZER = /home/nickm/build/libfuzz/libFuzzer.a
+LIBFUZZER_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ
+LIBFUZZER_CFLAGS = $(FUZZING_CFLAGS)
+LIBFUZZER_LDFLAG = $(FUZZING_LDFLAG)
+LIBFUZZER_LIBS = $(FUZZING_LIBS) $(LIBFUZZER) -lstdc++
+
+if LIBFUZZER_ENABLED
+src_test_fuzz_lf_fuzz_consensus_SOURCES = \
+ $(src_test_fuzz_fuzz_consensus_SOURCES)
+src_test_fuzz_lf_fuzz_consensus_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+src_test_fuzz_lf_fuzz_consensus_CFLAGS = $(LIBFUZZER_CFLAGS)
+src_test_fuzz_lf_fuzz_consensus_LDFLAGS = $(LIBFUZZER_LDFLAG)
+src_test_fuzz_lf_fuzz_consensus_LDADD = $(LIBFUZZER_LIBS)
+
+src_test_fuzz_lf_fuzz_descriptor_SOURCES = \
+ $(src_test_fuzz_fuzz_descriptor_SOURCES)
+src_test_fuzz_lf_fuzz_descriptor_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+src_test_fuzz_lf_fuzz_descriptor_CFLAGS = $(LIBFUZZER_CFLAGS)
+src_test_fuzz_lf_fuzz_descriptor_LDFLAGS = $(LIBFUZZER_LDFLAG)
+src_test_fuzz_lf_fuzz_descriptor_LDADD = $(LIBFUZZER_LIBS)
+
+src_test_fuzz_lf_fuzz_extrainfo_SOURCES = \
+ $(src_test_fuzz_fuzz_extrainfo_SOURCES)
+src_test_fuzz_lf_fuzz_extrainfo_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+src_test_fuzz_lf_fuzz_extrainfo_CFLAGS = $(LIBFUZZER_CFLAGS)
+src_test_fuzz_lf_fuzz_extrainfo_LDFLAGS = $(LIBFUZZER_LDFLAG)
+src_test_fuzz_lf_fuzz_extrainfo_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)
+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_hsdescv2_SOURCES = \
+ $(src_test_fuzz_fuzz_hsdescv2_SOURCES)
+src_test_fuzz_lf_fuzz_hsdescv2_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+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_iptsv2_SOURCES = \
+ $(src_test_fuzz_fuzz_iptsv2_SOURCES)
+src_test_fuzz_lf_fuzz_iptsv2_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+src_test_fuzz_lf_fuzz_iptsv2_CFLAGS = $(LIBFUZZER_CFLAGS)
+src_test_fuzz_lf_fuzz_iptsv2_LDFLAGS = $(LIBFUZZER_LDFLAG)
+src_test_fuzz_lf_fuzz_iptsv2_LDADD = $(LIBFUZZER_LIBS)
+
+src_test_fuzz_lf_fuzz_microdesc_SOURCES = \
+ $(src_test_fuzz_fuzz_microdesc_SOURCES)
+src_test_fuzz_lf_fuzz_microdesc_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+src_test_fuzz_lf_fuzz_microdesc_CFLAGS = $(LIBFUZZER_CFLAGS)
+src_test_fuzz_lf_fuzz_microdesc_LDFLAGS = $(LIBFUZZER_LDFLAG)
+src_test_fuzz_lf_fuzz_microdesc_LDADD = $(LIBFUZZER_LIBS)
+
+src_test_fuzz_lf_fuzz_vrs_SOURCES = \
+ $(src_test_fuzz_fuzz_vrs_SOURCES)
+src_test_fuzz_lf_fuzz_vrs_CPPFLAGS = $(LIBFUZZER_CPPFLAGS)
+src_test_fuzz_lf_fuzz_vrs_CFLAGS = $(LIBFUZZER_CFLAGS)
+src_test_fuzz_lf_fuzz_vrs_LDFLAGS = $(LIBFUZZER_LDFLAG)
+src_test_fuzz_lf_fuzz_vrs_LDADD = $(LIBFUZZER_LIBS)
+
+LIBFUZZER_FUZZERS = \
+ src/test/fuzz/lf-fuzz-consensus \
+ src/test/fuzz/lf-fuzz-descriptor \
+ src/test/fuzz/lf-fuzz-extrainfo \
+ src/test/fuzz/lf-fuzz-http \
+ src/test/fuzz/lf-fuzz-hsdescv2 \
+ src/test/fuzz/lf-fuzz-iptsv2 \
+ src/test/fuzz/lf-fuzz-microdesc \
+ src/test/fuzz/lf-fuzz-vrs
+
+else
+LIBFUZZER_FUZZERS =
+endif
+
+if OSS_FUZZ_ENABLED
+LIBOSS_FUZZ_CPPFLAGS = $(FUZZING_CPPFLAGS) -DLLVM_FUZZ
+LIBOSS_FUZZ_CFLAGS = $(FUZZING_CFLAGS)
+
+src_test_fuzz_liboss_fuzz_consensus_a_SOURCES = \
+ $(src_test_fuzz_fuzz_consensus_SOURCES)
+src_test_fuzz_liboss_fuzz_consensus_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS)
+src_test_fuzz_liboss_fuzz_consensus_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS)
+
+src_test_fuzz_liboss_fuzz_descriptor_a_SOURCES = \
+ $(src_test_fuzz_fuzz_descriptor_SOURCES)
+src_test_fuzz_liboss_fuzz_descriptor_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS)
+src_test_fuzz_liboss_fuzz_descriptor_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS)
+
+src_test_fuzz_liboss_fuzz_extrainfo_a_SOURCES = \
+ $(src_test_fuzz_fuzz_extrainfo_SOURCES)
+src_test_fuzz_liboss_fuzz_extrainfo_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS)
+src_test_fuzz_liboss_fuzz_extrainfo_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_hsdescv2_a_SOURCES = \
+ $(src_test_fuzz_fuzz_hsdescv2_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_iptsv2_a_SOURCES = \
+ $(src_test_fuzz_fuzz_iptsv2_SOURCES)
+src_test_fuzz_liboss_fuzz_iptsv2_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS)
+src_test_fuzz_liboss_fuzz_iptsv2_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS)
+
+src_test_fuzz_liboss_fuzz_microdesc_a_SOURCES = \
+ $(src_test_fuzz_fuzz_microdesc_SOURCES)
+src_test_fuzz_liboss_fuzz_microdesc_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS)
+src_test_fuzz_liboss_fuzz_microdesc_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS)
+
+src_test_fuzz_liboss_fuzz_vrs_a_SOURCES = \
+ $(src_test_fuzz_fuzz_vrs_SOURCES)
+src_test_fuzz_liboss_fuzz_vrs_a_CPPFLAGS = $(LIBOSS_FUZZ_CPPFLAGS)
+src_test_fuzz_liboss_fuzz_vrs_a_CFLAGS = $(LIBOSS_FUZZ_CFLAGS)
+
+OSS_FUZZ_FUZZERS = \
+ src/test/fuzz/liboss-fuzz-consensus.a \
+ src/test/fuzz/liboss-fuzz-descriptor.a \
+ src/test/fuzz/liboss-fuzz-extrainfo.a \
+ src/test/fuzz/liboss-fuzz-http.a \
+ src/test/fuzz/liboss-fuzz-hsdescv2.a \
+ src/test/fuzz/liboss-fuzz-iptsv2.a \
+ src/test/fuzz/liboss-fuzz-microdesc.a \
+ src/test/fuzz/liboss-fuzz-vrs.a
+else
+OSS_FUZZ_FUZZERS =
+endif
+
+noinst_PROGRAMS += $(FUZZERS) $(LIBFUZZER_FUZZERS)
+noinst_LIBRARIES += $(OSS_FUZZ_FUZZERS)
+oss-fuzz-fuzzers: oss-fuzz-prereqs $(OSS_FUZZ_FUZZERS)
+fuzzers: $(FUZZERS) $(LIBFUZZER_FUZZERS)
+
+test-fuzz-corpora: $(FUZZERS)
+ $(top_srcdir)/src/test/fuzz_static_testcases.sh
diff --git a/src/test/fuzz/minimize.sh b/src/test/fuzz/minimize.sh
new file mode 100755
index 0000000000..87d3dda13c
--- /dev/null
+++ b/src/test/fuzz/minimize.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+set -e
+
+if [ ! -d "$1" ] ; then
+ echo "I need a directory"
+ exit 1
+fi
+
+which=`basename "$1"`
+
+mkdir "$1.out"
+afl-cmin -i "$1" -o "$1.out" -m none "./src/test/fuzz/fuzz-${which}"
+
diff --git a/src/test/fuzz_static_testcases.sh b/src/test/fuzz_static_testcases.sh
new file mode 100755
index 0000000000..bfe1677573
--- /dev/null
+++ b/src/test/fuzz_static_testcases.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# Copyright (c) 2016, The Tor Project, Inc.
+# See LICENSE for licensing information
+
+set -e
+
+if [ -z "${TOR_FUZZ_CORPORA}" ] || [ ! -d "${TOR_FUZZ_CORPORA}" ] ; then
+ echo "You need to set TOR_FUZZ_CORPORA to point to a checkout of "
+ echo "the 'fuzzing-corpora' repository."
+ exit 77
+fi
+
+
+
+for fuzzer in "${builddir:-.}"/src/test/fuzz/fuzz-* ; do
+ f=`basename $fuzzer`
+ case="${f#fuzz-}"
+ if [ -d "${TOR_FUZZ_CORPORA}/${case}" ]; then
+ echo "Running tests for ${case}"
+ for entry in "${TOR_FUZZ_CORPORA}/${case}/"*; do
+ "${fuzzer}" "--err" < "$entry"
+ done
+ else
+ echo "No tests found for ${case}"
+ fi
+done
diff --git a/src/test/include.am b/src/test/include.am
index 8ecfaf10c6..1c0726fd3a 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -8,7 +8,9 @@ TESTS_ENVIRONMENT = \
export builddir="$(builddir)"; \
export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)";
-TESTSCRIPTS = src/test/test_zero_length_keys.sh \
+TESTSCRIPTS = \
+ src/test/fuzz_static_testcases.sh \
+ src/test/test_zero_length_keys.sh \
src/test/test_workqueue_cancel.sh \
src/test/test_workqueue_efd.sh \
src/test/test_workqueue_efd2.sh \
@@ -80,6 +82,7 @@ src_test_test_SOURCES = \
src/test/test_checkdir.c \
src/test/test_circuitlist.c \
src/test/test_circuitmux.c \
+ src/test/test_circuituse.c \
src/test/test_compat_libevent.c \
src/test/test_config.c \
src/test/test_connection.c \
@@ -96,7 +99,11 @@ src_test_test_SOURCES = \
src/test/test_guardfraction.c \
src/test/test_extorport.c \
src/test/test_hs.c \
+ src/test/test_hs_service.c \
+ src/test/test_hs_intropoint.c \
src/test/test_handles.c \
+ src/test/test_hs_cache.c \
+ src/test/test_hs_descriptor.c \
src/test/test_introduce.c \
src/test/test_keypin.c \
src/test/test_link_handshake.c \
@@ -130,6 +137,7 @@ src_test_test_SOURCES = \
src/test/test_helpers.c \
src/test/test_dns.c \
src/test/testing_common.c \
+ src/test/testing_rsakeys.c \
src/ext/tinytest.c
src_test_test_slow_SOURCES = \
@@ -137,6 +145,7 @@ src_test_test_slow_SOURCES = \
src/test/test_crypto_slow.c \
src/test/test_util_slow.c \
src/test/testing_common.c \
+ src/test/testing_rsakeys.c \
src/ext/tinytest.c
src_test_test_memwipe_SOURCES = \
@@ -262,6 +271,7 @@ src_test_test_bt_cl_CPPFLAGS= $(src_test_AM_CPPFLAGS) $(TEST_CPPFLAGS)
EXTRA_DIST += \
src/test/bt_test.py \
src/test/ntor_ref.py \
+ src/test/fuzz_static_testcases.sh \
src/test/slownacl_curve25519.py \
src/test/zero_length_keys.sh \
src/test/test_keygen.sh \
diff --git a/src/test/test-memwipe.c b/src/test/test-memwipe.c
index fd6457416a..484f13dd05 100644
--- a/src/test/test-memwipe.c
+++ b/src/test/test-memwipe.c
@@ -36,7 +36,7 @@ const char *s = NULL;
sum += (unsigned char)buf[i]; \
}
-#ifdef __OpenBSD__
+#ifdef OpenBSD
/* Disable some of OpenBSD's malloc protections for this test. This helps
* us do bad things, such as access freed buffers, without crashing. */
const char *malloc_options="sufjj";
diff --git a/src/test/test-network.sh b/src/test/test-network.sh
index 4d9776822b..10bd370ff3 100755
--- a/src/test/test-network.sh
+++ b/src/test/test-network.sh
@@ -1,8 +1,19 @@
-#! /bin/sh
+#!/bin/sh
+
+# use bash if it is available, as this script doesn't work well in non-bash sh
+# this will be fixed in #19699
+# there is no simple, portable way of checking the name of the shell, so we
+# exec bash even when sh is bash
+if [ -x /bin/bash -a "$USING_BASH" != true ]; then
+ # only do this once
+ export USING_BASH=true
+ exec /bin/bash "$0" "$@"
+fi
# Please do not modify this script, it has been moved to chutney/tools
-ECHO_N="/bin/echo -n"
+export ECHO="${ECHO:-echo}"
+export ECHO_N="${ECHO_N:-/bin/echo -n}"
# Output is prefixed with the name of the script
myname=$(basename $0)
@@ -19,23 +30,24 @@ if [ "$TEST_NETWORK_RECURSING" != true ]; then
# When we switch to using test-network.sh in chutney/tools, --dry-run
# can be removed, because this script will find chutney, then pass all
# arguments to chutney's test-network.sh
- echo "$myname: Parsing command-line arguments to find \$CHUTNEY_PATH"
export TEST_NETWORK_RECURSING=true
+ # passing arguments to a sourced script only works in bash
+ # this will be fixed in #19699
. "$0" --dry-run "$@"
# Call the chutney version of this script, if it exists, and we can find it
if [ -d "$CHUTNEY_PATH" -a -x "$CHUTNEY_PATH/tools/test-network.sh" ]; then
unset NETWORK_DRY_RUN
- echo "$myname: Calling newer chutney script \
+ $ECHO "$myname: Calling newer chutney script \
$CHUTNEY_PATH/tools/test-network.sh"
"$CHUTNEY_PATH/tools/test-network.sh" "$@"
exit $?
else
- echo "$myname: This script has moved to chutney/tools."
- echo "$myname: Please update your chutney using 'git pull'."
+ $ECHO "$myname: This script has moved to chutney/tools."
+ $ECHO "$myname: Please update your chutney using 'git pull'."
# When we switch to using test-network.sh in chutney/tools, we should
# exit with a very loud failure here
- echo "$myname: Falling back to the old tor version of the script."
+ $ECHO "$myname: Falling back to the old tor version of the script."
fi
fi
@@ -89,10 +101,14 @@ do
# process arguments, but don't call any other scripts
export NETWORK_DRY_RUN=true
;;
+ --quiet)
+ export ECHO=true
+ export ECHO_N=true
+ ;;
*)
- echo "$myname: Sorry, I don't know what to do with '$1'."
- echo "$myname: Maybe chutney's test-network.sh understands '$1'."
- echo "$myname: Please update your chutney using 'git pull', and set \
+ $ECHO "$myname: Sorry, I don't know what to do with '$1'."
+ $ECHO "$myname: Maybe chutney's test-network.sh understands '$1'."
+ $ECHO "$myname: Please update your chutney using 'git pull', and set \
\$CHUTNEY_PATH"
# continue processing arguments during a dry run
if [ "$NETWORK_DRY_RUN" != true ]; then
@@ -113,15 +129,15 @@ if [ ! -d "$TOR_DIR" ]; then
if [ -d "$BUILDDIR/src/or" -a -d "$BUILDDIR/src/tools" ]; then
# Choose the build directory
# But only if it looks like one
- echo "$myname: \$TOR_DIR not set, trying \$BUILDDIR"
+ $ECHO "$myname: \$TOR_DIR not set, trying \$BUILDDIR"
export TOR_DIR="$BUILDDIR"
elif [ -d "$PWD/src/or" -a -d "$PWD/src/tools" ]; then
# Guess the tor directory is the current directory
# But only if it looks like one
- echo "$myname: \$TOR_DIR not set, trying \$PWD"
+ $ECHO "$myname: \$TOR_DIR not set, trying \$PWD"
export TOR_DIR="$PWD"
else
- echo "$myname: no \$TOR_DIR, chutney will use \$PATH for tor binaries"
+ $ECHO "$myname: no \$TOR_DIR, chutney will use \$PATH for tor binaries"
unset TOR_DIR
fi
fi
@@ -133,19 +149,19 @@ fi
# - fail and tell the user how to clone the chutney repository
if [ ! -d "$CHUTNEY_PATH" -o ! -x "$CHUTNEY_PATH/chutney" ]; then
if [ -x "$PWD/chutney" ]; then
- echo "$myname: \$CHUTNEY_PATH not valid, trying \$PWD"
+ $ECHO "$myname: \$CHUTNEY_PATH not valid, trying \$PWD"
export CHUTNEY_PATH="$PWD"
elif [ -d "$TOR_DIR" -a -d "$TOR_DIR/../chutney" -a \
-x "$TOR_DIR/../chutney/chutney" ]; then
- echo "$myname: \$CHUTNEY_PATH not valid, trying \$TOR_DIR/../chutney"
+ $ECHO "$myname: \$CHUTNEY_PATH not valid, trying \$TOR_DIR/../chutney"
export CHUTNEY_PATH="$TOR_DIR/../chutney"
else
# TODO: work out how to package and install chutney,
# so users can find it in $PATH
- echo "$myname: missing 'chutney' in \$CHUTNEY_PATH ($CHUTNEY_PATH)"
- echo "$myname: Get chutney: git clone https://git.torproject.org/\
+ $ECHO "$myname: missing 'chutney' in \$CHUTNEY_PATH ($CHUTNEY_PATH)"
+ $ECHO "$myname: Get chutney: git clone https://git.torproject.org/\
chutney.git"
- echo "$myname: Set \$CHUTNEY_PATH to a non-standard location: export \
+ $ECHO "$myname: Set \$CHUTNEY_PATH to a non-standard location: export \
CHUTNEY_PATH=\`pwd\`/chutney"
unset CHUTNEY_PATH
exit 1
@@ -174,11 +190,13 @@ export CHUTNEY_NETWORK=networks/$NETWORK_FLAVOUR
# And finish up if we're doing a dry run
if [ "$NETWORK_DRY_RUN" = true ]; then
# we can't exit here, it breaks argument processing
- return
+ # this only works in bash: return semantics are shell-specific
+ # this will be fixed in #19699
+ return 2>/dev/null || exit
fi
cd "$CHUTNEY_PATH"
-./tools/bootstrap-network.sh $NETWORK_FLAVOUR || exit 2
+./tools/bootstrap-network.sh $NETWORK_FLAVOUR || exit 3
# Sleep some, waiting for the network to bootstrap.
# TODO: Add chutney command 'bootstrap-status' and use that instead.
@@ -186,7 +204,7 @@ BOOTSTRAP_TIME=${BOOTSTRAP_TIME:-35}
$ECHO_N "$myname: sleeping for $BOOTSTRAP_TIME seconds"
n=$BOOTSTRAP_TIME; while [ $n -gt 0 ]; do
sleep 1; n=$(expr $n - 1); $ECHO_N .
-done; echo ""
+done; $ECHO ""
./chutney verify $CHUTNEY_NETWORK
VERIFY_EXIT_STATUS=$?
# work around a bug/feature in make -j2 (or more)
diff --git a/src/test/test.c b/src/test/test.c
index 9a41b976b8..866408e856 100644
--- a/src/test/test.c
+++ b/src/test/test.c
@@ -1190,6 +1190,7 @@ struct testgroup_t testgroups[] = {
{ "checkdir/", checkdir_tests },
{ "circuitlist/", circuitlist_tests },
{ "circuitmux/", circuitmux_tests },
+ { "circuituse/", circuituse_tests },
{ "compat/libevent/", compat_libevent_tests },
{ "config/", config_tests },
{ "connection/", connection_tests },
@@ -1204,7 +1205,11 @@ struct testgroup_t testgroups[] = {
{ "entrynodes/", entrynodes_tests },
{ "guardfraction/", guardfraction_tests },
{ "extorport/", extorport_tests },
- { "hs/", hs_tests },
+ { "legacy_hs/", hs_tests },
+ { "hs_cache/", hs_cache },
+ { "hs_descriptor/", hs_descriptor },
+ { "hs_service/", hs_service_tests },
+ { "hs_intropoint/", hs_intropoint_tests },
{ "introduce/", introduce_tests },
{ "keypin/", keypin_tests },
{ "link-handshake/", link_handshake_tests },
diff --git a/src/test/test.h b/src/test/test.h
index 25336ac83e..2bd58f51c8 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -75,6 +75,8 @@
const char *get_fname(const char *name);
const char *get_fname_rnd(const char *name);
struct crypto_pk_t *pk_generate(int idx);
+void init_pregenerated_keys(void);
+void free_pregenerated_keys(void);
#define US2_CONCAT_2__(a, b) a ## __ ## b
#define US_CONCAT_2__(a, b) a ## _ ## b
@@ -183,6 +185,7 @@ extern struct testcase_t channeltls_tests[];
extern struct testcase_t checkdir_tests[];
extern struct testcase_t circuitlist_tests[];
extern struct testcase_t circuitmux_tests[];
+extern struct testcase_t circuituse_tests[];
extern struct testcase_t compat_libevent_tests[];
extern struct testcase_t config_tests[];
extern struct testcase_t connection_tests[];
@@ -197,6 +200,10 @@ extern struct testcase_t entrynodes_tests[];
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_descriptor[];
+extern struct testcase_t hs_service_tests[];
+extern struct testcase_t hs_intropoint_tests[];
extern struct testcase_t introduce_tests[];
extern struct testcase_t keypin_tests[];
extern struct testcase_t link_handshake_tests[];
diff --git a/src/test/test_bt_cl.c b/src/test/test_bt_cl.c
index 95b4f48f11..709d599f52 100644
--- a/src/test/test_bt_cl.c
+++ b/src/test/test_bt_cl.c
@@ -19,14 +19,12 @@ static int crashtype = 0;
#ifdef __GNUC__
#define NOINLINE __attribute__((noinline))
-#define NORETURN __attribute__((noreturn))
#endif
int crash(int x) NOINLINE;
int oh_what(int x) NOINLINE;
int a_tangled_web(int x) NOINLINE;
int we_weave(int x) NOINLINE;
-static void abort_handler(int s) NORETURN;
#ifdef HAVE_CFLAG_WNULL_DEREFERENCE
DISABLE_GCC_WARNING(null-dereference)
@@ -76,13 +74,6 @@ we_weave(int x)
return a_tangled_web(x) + a_tangled_web(x+1);
}
-static void
-abort_handler(int s)
-{
- (void)s;
- exit(0);
-}
-
int
main(int argc, char **argv)
{
@@ -120,8 +111,6 @@ main(int argc, char **argv)
configure_backtrace_handler(NULL);
- signal(SIGABRT, abort_handler);
-
printf("%d\n", we_weave(2));
clean_up_backtrace_handler();
diff --git a/src/test/test_buffers.c b/src/test/test_buffers.c
index 3408da3aa9..9e7bdb8911 100644
--- a/src/test/test_buffers.c
+++ b/src/test/test_buffers.c
@@ -765,6 +765,49 @@ test_buffers_chunk_size(void *arg)
;
}
+static void
+test_buffers_find_contentlen(void *arg)
+{
+ static const struct {
+ const char *headers;
+ int r;
+ int contentlen;
+ } results[] = {
+ { "Blah blah\r\nContent-Length: 1\r\n\r\n", 1, 1 },
+ { "Blah blah\r\n\r\n", 0, 0 }, /* no content-len */
+ { "Blah blah Content-Length: 1\r\n", 0, 0 }, /* no content-len. */
+ { "Blah blah\r\nContent-Length: 100000\r\n", 1, 100000},
+ { "Blah blah\r\nContent-Length: 1000000000000000000000000\r\n", -1, 0},
+ { "Blah blah\r\nContent-Length: 0\r\n", 1, 0},
+ { "Blah blah\r\nContent-Length: -1\r\n", -1, 0},
+ { "Blah blah\r\nContent-Length: 1x\r\n", -1, 0},
+ { "Blah blah\r\nContent-Length: 1 x\r\n", -1, 0},
+ { "Blah blah\r\nContent-Length: 1 \r\n", 1, 1},
+ { "Blah blah\r\nContent-Length: \r\n", -1, 0},
+ { "Blah blah\r\nContent-Length: ", -1, 0},
+ { "Blah blah\r\nContent-Length: 5050", -1, 0},
+ { NULL, 0, 0 }
+ };
+ int i;
+
+ (void)arg;
+
+ for (i = 0; results[i].headers; ++i) {
+ int r;
+ size_t sz;
+ size_t headerlen = strlen(results[i].headers);
+ char * tmp = tor_memdup(results[i].headers, headerlen);/* ensure no eos */
+ sz = 999; /* to ensure it gets set */
+ 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);
+ }
+ done:
+ ;
+}
+
struct testcase_t buffer_tests[] = {
{ "basic", test_buffers_basic, TT_FORK, NULL, NULL },
{ "copy", test_buffer_copy, TT_FORK, NULL, NULL },
@@ -780,6 +823,7 @@ struct testcase_t buffer_tests[] = {
{ "tls_read_mocked", test_buffers_tls_read_mocked, 0,
NULL, NULL },
{ "chunk_size", test_buffers_chunk_size, 0, NULL, NULL },
+ { "find_contentlen", test_buffers_find_contentlen, 0, NULL, NULL },
END_OF_TESTCASES
};
diff --git a/src/test/test_cell_formats.c b/src/test/test_cell_formats.c
index f429f4291d..22c34b6d6c 100644
--- a/src/test/test_cell_formats.c
+++ b/src/test/test_cell_formats.c
@@ -11,6 +11,7 @@
#include "channel.h"
#include "connection_edge.h"
#include "connection_or.h"
+#include "config.h"
#include "onion.h"
#include "onion_tap.h"
#include "onion_fast.h"
@@ -698,6 +699,7 @@ test_cfmt_extend_cells(void *arg)
tt_int_op(61681, OP_EQ, ec.orport_ipv4.port);
tt_str_op("2002::f0:c51e", OP_EQ, fmt_addr(&ec.orport_ipv6.addr));
tt_int_op(4370, OP_EQ, ec.orport_ipv6.port);
+ tt_assert(ed25519_public_key_is_zero(&ec.ed_pubkey));
tt_mem_op(ec.node_id,OP_EQ, "anthropomorphization", 20);
tt_int_op(cc->cell_type, OP_EQ, CELL_CREATE2);
tt_int_op(cc->handshake_type, OP_EQ, 0x105);
@@ -717,6 +719,37 @@ test_cfmt_extend_cells(void *arg)
tt_mem_op(p2+1+8+22+4,OP_EQ, b, 99+20);
tt_int_op(0, OP_EQ, create_cell_format_relayed(&cell, cc));
+ /* Now let's add an ed25519 key to that extend2 cell. */
+ memcpy(ec.ed_pubkey.pubkey,
+ "brownshoesdontmakeit/brownshoesd", 32);
+
+ /* As before, since we aren't extending by ed25519. */
+ get_options_mutable()->ExtendByEd25519ID = 0;
+ tt_int_op(0, OP_EQ, extend_cell_format(&p2_cmd, &p2_len, p2, &ec));
+ tt_int_op(p2_len, OP_EQ, 89+99-34-20);
+ test_memeq_hex(p2,
+ "02000612F40001F0F1"
+ "0214616e7468726f706f6d6f727068697a6174696f6e"
+ "01050063");
+
+ /* Now try with the ed25519 ID. */
+ get_options_mutable()->ExtendByEd25519ID = 1;
+ tt_int_op(0, OP_EQ, extend_cell_format(&p2_cmd, &p2_len, p2, &ec));
+ tt_int_op(p2_len, OP_EQ, 89+99-34-20 + 34);
+ test_memeq_hex(p2,
+ "03000612F40001F0F1"
+ "0214616e7468726f706f6d6f727068697a6174696f6e"
+ // ed digest follows:
+ "0320" "62726f776e73686f6573646f6e746d616b656"
+ "9742f62726f776e73686f657364"
+ "01050063");
+ /* Can we parse that? Did the key come through right? */
+ memset(&ec, 0, sizeof(ec));
+ tt_int_op(0, OP_EQ, extend_cell_parse(&ec, RELAY_COMMAND_EXTEND2,
+ p2, p2_len));
+ tt_mem_op("brownshoesdontmakeit/brownshoesd", OP_EQ,
+ ec.ed_pubkey.pubkey, 32);
+
/* == Now try parsing some junk */
/* Try a too-long handshake */
@@ -1257,7 +1290,7 @@ struct testcase_t cell_format_tests[] = {
TEST(connected_cells, 0),
TEST(create_cells, 0),
TEST(created_cells, 0),
- TEST(extend_cells, 0),
+ TEST(extend_cells, TT_FORK),
TEST(extended_cells, 0),
TEST(resolved_cells, 0),
TEST(is_destroy, 0),
diff --git a/src/test/test_channel.c b/src/test/test_channel.c
index a9e0634d9e..862bd6dfa6 100644
--- a/src/test/test_channel.c
+++ b/src/test/test_channel.c
@@ -1405,10 +1405,14 @@ 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);
tt_assert(test_cells_written == old_count);
tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0);
+ tt_int_op(smartlist_len(tor_get_captured_bug_log_()), ==, 1);
+ tor_end_capture_bugs_();
+
done:
free_fake_channel(ch);
@@ -1764,6 +1768,112 @@ test_channel_write(void *arg)
return;
}
+static void
+test_channel_id_map(void *arg)
+{
+ (void)arg;
+#define N_CHAN 6
+ char rsa_id[N_CHAN][DIGEST_LEN];
+ ed25519_public_key_t *ed_id[N_CHAN];
+ channel_t *chan[N_CHAN];
+ int i;
+ 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?
+
+ for (i = 0; i < N_CHAN; ++i) {
+ crypto_rand(rsa_id[i], DIGEST_LEN);
+ ed_id[i] = tor_malloc_zero(sizeof(*ed_id[i]));
+ crypto_rand((char*)ed_id[i]->pubkey, sizeof(ed_id[i]->pubkey));
+ }
+
+ /* For channel 3, have no Ed identity. */
+ tor_free(ed_id[3]);
+
+ /* Channel 2 and 4 have same ROSA identity */
+ memcpy(rsa_id[4], rsa_id[2], DIGEST_LEN);
+
+ /* Channel 2 and 4 and 5 have same RSA identity */
+ memcpy(rsa_id[4], rsa_id[2], DIGEST_LEN);
+ memcpy(rsa_id[5], rsa_id[2], DIGEST_LEN);
+
+ /* Channels 2 and 5 have same Ed25519 identity */
+ memcpy(ed_id[5], ed_id[2], sizeof(*ed_id[2]));
+
+ for (i = 0; i < N_CHAN; ++i) {
+ chan[i] = new_fake_channel();
+ channel_register(chan[i]);
+ channel_set_identity_digest(chan[i], rsa_id[i], ed_id[i]);
+ }
+
+ /* Lookup by RSA id only */
+ tt_ptr_op(chan[0], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[0], NULL));
+ tt_ptr_op(chan[1], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[1], NULL));
+ tt_ptr_op(chan[3], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[3], NULL));
+ channel_t *ch;
+ ch = channel_find_by_remote_identity(rsa_id[2], NULL);
+ tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
+ 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 == chan[2] || ch == chan[4] || ch == chan[5]);
+ ch = channel_next_with_rsa_identity(ch);
+ tt_assert(ch == NULL);
+
+ /* As above, but with zero Ed25519 ID (meaning "any ID") */
+ tt_ptr_op(chan[0], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[0], &ed_zero));
+ tt_ptr_op(chan[1], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[1], &ed_zero));
+ tt_ptr_op(chan[3], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[3], &ed_zero));
+ ch = channel_find_by_remote_identity(rsa_id[2], &ed_zero);
+ tt_assert(ch == chan[2] || ch == chan[4] || ch == chan[5]);
+ 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 == chan[2] || ch == chan[4] || ch == chan[5]);
+ ch = channel_next_with_rsa_identity(ch);
+ tt_assert(ch == NULL);
+
+ /* Lookup nonexistent RSA identity */
+ tt_ptr_op(NULL, OP_EQ,
+ channel_find_by_remote_identity("!!!!!!!!!!!!!!!!!!!!", NULL));
+
+ /* Look up by full identity pair */
+ tt_ptr_op(chan[0], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[0], ed_id[0]));
+ tt_ptr_op(chan[1], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[1], ed_id[1]));
+ tt_ptr_op(chan[3], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[3], ed_id[3] /*NULL*/));
+ tt_ptr_op(chan[4], OP_EQ,
+ channel_find_by_remote_identity(rsa_id[4], ed_id[4]));
+ ch = channel_find_by_remote_identity(rsa_id[2], ed_id[2]);
+ tt_assert(ch == chan[2] || ch == chan[5]);
+
+ /* Look up RSA identity with wrong ed25519 identity */
+ tt_ptr_op(NULL, OP_EQ,
+ channel_find_by_remote_identity(rsa_id[4], ed_id[0]));
+ tt_ptr_op(NULL, OP_EQ,
+ channel_find_by_remote_identity(rsa_id[2], ed_id[1]));
+ tt_ptr_op(NULL, OP_EQ,
+ channel_find_by_remote_identity(rsa_id[3], ed_id[1]));
+
+ done:
+ for (i = 0; i < N_CHAN; ++i) {
+ channel_clear_identity_digest(chan[i]);
+ channel_unregister(chan[i]);
+ free_fake_channel(chan[i]);
+ tor_free(ed_id[i]);
+ }
+#undef N_CHAN
+}
+
struct testcase_t channel_tests[] = {
{ "dumpstats", test_channel_dumpstats, TT_FORK, NULL, NULL },
{ "flush", test_channel_flush, TT_FORK, NULL, NULL },
@@ -1776,6 +1886,7 @@ struct testcase_t channel_tests[] = {
{ "queue_incoming", test_channel_queue_incoming, TT_FORK, NULL, NULL },
{ "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL },
{ "write", test_channel_write, TT_FORK, NULL, NULL },
+ { "id_map", test_channel_id_map, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};
diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c
index 08442e01b6..fd98ee40fb 100644
--- a/src/test/test_channeltls.c
+++ b/src/test/test_channeltls.c
@@ -32,6 +32,7 @@ static or_connection_t * tlschan_connection_or_connect_mock(
const tor_addr_t *addr,
uint16_t port,
const char *digest,
+ const ed25519_public_key_t *ed_id,
channel_tls_t *tlschan);
static int tlschan_is_local_addr_mock(const tor_addr_t *addr);
@@ -70,7 +71,7 @@ test_channeltls_create(void *arg)
MOCK(connection_or_connect, tlschan_connection_or_connect_mock);
/* Try connecting */
- ch = channel_tls_connect(&test_addr, 567, test_digest);
+ ch = channel_tls_connect(&test_addr, 567, test_digest, NULL);
tt_assert(ch != NULL);
done:
@@ -119,7 +120,7 @@ test_channeltls_num_bytes_queued(void *arg)
MOCK(connection_or_connect, tlschan_connection_or_connect_mock);
/* Try connecting */
- ch = channel_tls_connect(&test_addr, 567, test_digest);
+ ch = channel_tls_connect(&test_addr, 567, test_digest, NULL);
tt_assert(ch != NULL);
/*
@@ -204,7 +205,7 @@ test_channeltls_overhead_estimate(void *arg)
MOCK(connection_or_connect, tlschan_connection_or_connect_mock);
/* Try connecting */
- ch = channel_tls_connect(&test_addr, 567, test_digest);
+ ch = channel_tls_connect(&test_addr, 567, test_digest, NULL);
tt_assert(ch != NULL);
/* First case: silly low ratios should get clamped to 1.0 */
@@ -266,9 +267,11 @@ static or_connection_t *
tlschan_connection_or_connect_mock(const tor_addr_t *addr,
uint16_t port,
const char *digest,
+ const ed25519_public_key_t *ed_id,
channel_tls_t *tlschan)
{
or_connection_t *result = NULL;
+ (void) ed_id; // XXXX Not yet used.
tt_assert(addr != NULL);
tt_assert(port != 0);
diff --git a/src/test/test_circuitlist.c b/src/test/test_circuitlist.c
index e996c42115..7eed5fe225 100644
--- a/src/test/test_circuitlist.c
+++ b/src/test/test_circuitlist.c
@@ -4,10 +4,12 @@
#define TOR_CHANNEL_INTERNAL_
#define CIRCUITBUILD_PRIVATE
#define CIRCUITLIST_PRIVATE
+#define HS_CIRCUITMAP_PRIVATE
#include "or.h"
#include "channel.h"
#include "circuitbuild.h"
#include "circuitlist.h"
+#include "hs_circuitmap.h"
#include "test.h"
#include "log_test_helpers.h"
@@ -185,6 +187,9 @@ test_rend_token_maps(void *arg)
(void)arg;
(void)tok1; //xxxx
+
+ hs_circuitmap_init();
+
c1 = or_circuit_new(0, NULL);
c2 = or_circuit_new(0, NULL);
c3 = or_circuit_new(0, NULL);
@@ -196,68 +201,68 @@ test_rend_token_maps(void *arg)
tt_int_op(tok3[REND_TOKEN_LEN-1], OP_EQ, '.');
/* No maps; nothing there. */
- tt_ptr_op(NULL, OP_EQ, circuit_get_rendezvous(tok1));
- tt_ptr_op(NULL, OP_EQ, circuit_get_intro_point(tok1));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok1));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok1));
- circuit_set_rendezvous_cookie(c1, tok1);
- circuit_set_intro_point_digest(c2, tok2);
+ hs_circuitmap_register_rend_circ(c1, tok1);
+ hs_circuitmap_register_intro_circ_v2(c2, tok2);
- tt_ptr_op(NULL, OP_EQ, circuit_get_rendezvous(tok3));
- tt_ptr_op(NULL, OP_EQ, circuit_get_intro_point(tok3));
- tt_ptr_op(NULL, OP_EQ, circuit_get_rendezvous(tok2));
- tt_ptr_op(NULL, OP_EQ, circuit_get_intro_point(tok1));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok3));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok2));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok1));
/* Without purpose set, we don't get the circuits */
- tt_ptr_op(NULL, OP_EQ, circuit_get_rendezvous(tok1));
- tt_ptr_op(NULL, OP_EQ, circuit_get_intro_point(tok2));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok1));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2));
c1->base_.purpose = CIRCUIT_PURPOSE_REND_POINT_WAITING;
c2->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT;
/* Okay, make sure they show up now. */
- tt_ptr_op(c1, OP_EQ, circuit_get_rendezvous(tok1));
- tt_ptr_op(c2, OP_EQ, circuit_get_intro_point(tok2));
+ tt_ptr_op(c1, OP_EQ, hs_circuitmap_get_rend_circ(tok1));
+ tt_ptr_op(c2, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2));
/* Two items at the same place with the same token. */
c3->base_.purpose = CIRCUIT_PURPOSE_REND_POINT_WAITING;
- circuit_set_rendezvous_cookie(c3, tok2);
- tt_ptr_op(c2, OP_EQ, circuit_get_intro_point(tok2));
- tt_ptr_op(c3, OP_EQ, circuit_get_rendezvous(tok2));
+ hs_circuitmap_register_rend_circ(c3, tok2);
+ tt_ptr_op(c2, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2));
+ tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_rend_circ(tok2));
/* Marking a circuit makes it not get returned any more */
circuit_mark_for_close(TO_CIRCUIT(c1), END_CIRC_REASON_FINISHED);
- tt_ptr_op(NULL, OP_EQ, circuit_get_rendezvous(tok1));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok1));
circuit_free(TO_CIRCUIT(c1));
c1 = NULL;
/* Freeing a circuit makes it not get returned any more. */
circuit_free(TO_CIRCUIT(c2));
c2 = NULL;
- tt_ptr_op(NULL, OP_EQ, circuit_get_intro_point(tok2));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok2));
/* c3 -- are you still there? */
- tt_ptr_op(c3, OP_EQ, circuit_get_rendezvous(tok2));
+ tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_rend_circ(tok2));
/* Change its cookie. This never happens in Tor per se, but hey. */
c3->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT;
- circuit_set_intro_point_digest(c3, tok3);
+ hs_circuitmap_register_intro_circ_v2(c3, tok3);
- tt_ptr_op(NULL, OP_EQ, circuit_get_rendezvous(tok2));
- tt_ptr_op(c3, OP_EQ, circuit_get_intro_point(tok3));
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_rend_circ(tok2));
+ tt_ptr_op(c3, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3));
/* Now replace c3 with c4. */
c4->base_.purpose = CIRCUIT_PURPOSE_INTRO_POINT;
- circuit_set_intro_point_digest(c4, tok3);
+ hs_circuitmap_register_intro_circ_v2(c4, tok3);
- tt_ptr_op(c4, OP_EQ, circuit_get_intro_point(tok3));
+ tt_ptr_op(c4, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3));
- tt_ptr_op(c3->rendinfo, OP_EQ, NULL);
- tt_ptr_op(c4->rendinfo, OP_NE, NULL);
- tt_mem_op(c4->rendinfo, OP_EQ, tok3, REND_TOKEN_LEN);
+ tt_ptr_op(c3->hs_token, OP_EQ, NULL);
+ tt_ptr_op(c4->hs_token, OP_NE, NULL);
+ tt_mem_op(c4->hs_token->token, OP_EQ, tok3, REND_TOKEN_LEN);
/* Now clear c4's cookie. */
- circuit_set_intro_point_digest(c4, NULL);
- tt_ptr_op(c4->rendinfo, OP_EQ, NULL);
- tt_ptr_op(NULL, OP_EQ, circuit_get_intro_point(tok3));
+ hs_circuitmap_remove_circuit(c4);
+ tt_ptr_op(c4->hs_token, OP_EQ, NULL);
+ tt_ptr_op(NULL, OP_EQ, hs_circuitmap_get_intro_circ_v2(tok3));
done:
if (c1)
diff --git a/src/test/test_circuituse.c b/src/test/test_circuituse.c
new file mode 100644
index 0000000000..27a87660ff
--- /dev/null
+++ b/src/test/test_circuituse.c
@@ -0,0 +1,304 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#define CIRCUITLIST_PRIVATE
+
+#include "or.h"
+#include "test.h"
+#include "test_helpers.h"
+#include "config.h"
+#include "circuitlist.h"
+#include "circuituse.h"
+#include "circuitbuild.h"
+#include "nodelist.h"
+
+static void
+test_circuit_is_available_for_use_ret_false_when_marked_for_close(void *arg)
+{
+ (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));
+
+ done:
+ tor_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_ret_false_when_timestamp_dirty(void *arg)
+{
+ (void)arg;
+
+ circuit_t *circ = tor_malloc(sizeof(circuit_t));
+ circ->timestamp_dirty = 1;
+
+ tt_int_op(0, ==, circuit_is_available_for_use(circ));
+
+ done:
+ tor_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_ret_false_for_non_general_purpose(void *arg)
+{
+ (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));
+
+ done:
+ tor_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_ret_false_for_non_general_origin(void *arg)
+{
+ (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));
+
+ done:
+ tor_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_ret_false_for_non_origin_purpose(void *arg)
+{
+ (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));
+
+ done:
+ tor_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_ret_false_unusable_for_new_conns(void *arg)
+{
+ (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));
+
+ done:
+ circuit_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_returns_false_for_onehop_tunnel(void *arg)
+{
+ (void)arg;
+
+ circuit_t *circ = dummy_origin_circuit_new(30);
+ origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
+ 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));
+
+ done:
+ circuit_free(circ);
+}
+
+static void
+test_circuit_is_available_for_use_returns_true_for_clean_circuit(void *arg)
+{
+ (void)arg;
+
+ circuit_t *circ = dummy_origin_circuit_new(30);
+ origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ);
+ 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));
+
+ done:
+ circuit_free(circ);
+}
+
+static int
+mock_circuit_all_predicted_ports_handled(time_t now,
+ int *need_uptime,
+ int *need_capacity)
+{
+ (void)now;
+
+ if (need_uptime && need_capacity)
+ return 0;
+ return 1;
+}
+
+static consensus_path_type_t
+mock_router_have_unknown_consensus_path(void)
+{
+ return CONSENSUS_PATH_UNKNOWN;
+}
+
+static consensus_path_type_t
+mock_router_have_exit_consensus_path(void)
+{
+ return CONSENSUS_PATH_EXIT;
+}
+
+static void
+test_needs_exit_circuits_ret_false_for_predicted_ports_and_path(void *arg)
+{
+ (void)arg;
+
+ MOCK(circuit_all_predicted_ports_handled,
+ mock_circuit_all_predicted_ports_handled);
+ int needs_uptime = 1;
+ int needs_capacity = 0;
+
+ time_t now = time(NULL);
+ tt_int_op(0, ==, needs_exit_circuits(now, &needs_uptime, &needs_capacity));
+
+ done:
+ UNMOCK(circuit_all_predicted_ports_handled);
+}
+
+static void
+test_needs_exit_circuits_ret_false_for_non_exit_consensus_path(void *arg)
+{
+ (void)arg;
+
+ MOCK(circuit_all_predicted_ports_handled,
+ mock_circuit_all_predicted_ports_handled);
+ int needs_uptime = 1;
+ int needs_capacity = 1;
+ 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));
+
+ done:
+ UNMOCK(circuit_all_predicted_ports_handled);
+ UNMOCK(router_have_consensus_path);
+}
+
+static void
+test_needs_exit_circuits_ret_true_for_predicted_ports_and_path(void *arg)
+{
+ (void)arg;
+
+ MOCK(circuit_all_predicted_ports_handled,
+ mock_circuit_all_predicted_ports_handled);
+ int needs_uptime = 1;
+ int needs_capacity = 1;
+ 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));
+
+ done:
+ UNMOCK(circuit_all_predicted_ports_handled);
+ UNMOCK(router_have_consensus_path);
+}
+
+static void
+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));
+ done: ;
+}
+
+static void
+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));
+ done:
+ UNMOCK(router_have_consensus_path);
+}
+
+static void
+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));
+ done:
+ UNMOCK(router_have_consensus_path);
+}
+
+struct testcase_t circuituse_tests[] = {
+ { "marked",
+ test_circuit_is_available_for_use_ret_false_when_marked_for_close,
+ TT_FORK, NULL, NULL
+ },
+ { "timestamp",
+ test_circuit_is_available_for_use_ret_false_when_timestamp_dirty,
+ TT_FORK, NULL, NULL
+ },
+ { "non_general",
+ test_circuit_is_available_for_use_ret_false_for_non_general_purpose,
+ TT_FORK, NULL, NULL
+ },
+ { "non_general",
+ test_circuit_is_available_for_use_ret_false_for_non_general_origin,
+ TT_FORK, NULL, NULL
+ },
+ { "origin",
+ test_circuit_is_available_for_use_ret_false_for_non_origin_purpose,
+ TT_FORK, NULL, NULL
+ },
+ { "clean",
+ test_circuit_is_available_for_use_ret_false_unusable_for_new_conns,
+ TT_FORK, NULL, NULL
+ },
+ { "onehop",
+ test_circuit_is_available_for_use_returns_false_for_onehop_tunnel,
+ TT_FORK, NULL, NULL
+ },
+ { "clean_circ",
+ test_circuit_is_available_for_use_returns_true_for_clean_circuit,
+ TT_FORK, NULL, NULL
+ },
+ { "exit_f",
+ test_needs_exit_circuits_ret_false_for_predicted_ports_and_path,
+ TT_FORK, NULL, NULL
+ },
+ { "exit_t",
+ test_needs_exit_circuits_ret_true_for_predicted_ports_and_path,
+ TT_FORK, NULL, NULL
+ },
+ { "non_exit",
+ test_needs_exit_circuits_ret_false_for_non_exit_consensus_path,
+ TT_FORK, NULL, NULL
+ },
+ { "true",
+ test_needs_exit_circuits_ret_true_for_predicted_ports_and_path,
+ TT_FORK, NULL, NULL
+ },
+ { "consensus_path_unknown",
+ test_needs_circuits_for_build_ret_false_consensus_path_unknown,
+ TT_FORK, NULL, NULL
+ },
+ { "less_than_max",
+ test_needs_circuits_for_build_ret_false_if_num_less_than_max,
+ TT_FORK, NULL, NULL
+ },
+ { "more_needed",
+ test_needs_circuits_for_build_returns_true_when_more_are_needed,
+ TT_FORK, NULL, NULL
+ },
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_config.c b/src/test/test_config.c
index 89f9b3e2aa..3c2b52ca00 100644
--- a/src/test/test_config.c
+++ b/src/test/test_config.c
@@ -11,6 +11,7 @@
#include "or.h"
#include "address.h"
#include "addressmap.h"
+#include "bridges.h"
#include "circuitmux_ewma.h"
#include "circuitbuild.h"
#include "config.h"
@@ -45,6 +46,8 @@
#include "transports.h"
#include "util.h"
+#include "test_helpers.h"
+
static void
test_config_addressmap(void *arg)
{
@@ -4126,6 +4129,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
config_free_lines(config_port_invalid); config_port_invalid = NULL;
config_port_invalid = mock_config_line("SOCKSPort",
"unix:/tmp/foo/bar NoIPv4Traffic "
+ "NoIPv6Traffic "
"NoOnionTraffic");
ret = parse_port_config(NULL, config_port_invalid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
@@ -4147,6 +4151,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("DNSPort", "127.0.0.1:80 "
+ "NoIPv6Traffic "
"NoIPv4Traffic NoOnionTraffic");
ret = parse_port_config(slout, config_port_valid, NULL, "DNS",
CONN_TYPE_AP_DNS_LISTENER, NULL, 0,
@@ -4162,6 +4167,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
// Test failure if we have DNS but no ipv4 and no ipv6
config_free_lines(config_port_invalid); config_port_invalid = NULL;
config_port_invalid = mock_config_line("SOCKSPort",
+ "NoIPv6Traffic "
"unix:/tmp/foo/bar NoIPv4Traffic");
ret = parse_port_config(NULL, config_port_invalid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
@@ -4174,6 +4180,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:/tmp/foo/bar "
+ "NoIPv6Traffic "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
@@ -4195,6 +4202,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:\"/tmp/foo/ bar\" "
+ "NoIPv6Traffic "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
@@ -4216,6 +4224,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:\"/tmp/foo/ bar "
+ "NoIPv6Traffic "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
@@ -4227,6 +4236,7 @@ test_config_parse_port_config__ports__ports_given(void *data)
SMARTLIST_FOREACH(slout,port_cfg_t *,pf,port_cfg_free(pf));
smartlist_clear(slout);
config_port_valid = mock_config_line("SOCKSPort", "unix:\"\" "
+ "NoIPv6Traffic "
"NoDNSRequest NoIPv4Traffic");
ret = parse_port_config(slout, config_port_valid, NULL, "SOCKS",
CONN_TYPE_AP_LISTENER, NULL, 0,
@@ -4601,7 +4611,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->entry_cfg.ipv4_traffic, OP_EQ, 1);
- tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 0);
+ tt_int_op(port_cfg->entry_cfg.ipv6_traffic, OP_EQ, 1);
// Test failure for a SessionGroup argument with invalid value
config_free_lines(config_port_invalid); config_port_invalid = NULL;
@@ -4700,8 +4710,10 @@ test_config_parse_port_config__ports__ports_given(void *data)
// Test failure when asked to parse an invalid address followed by auto
config_free_lines(config_port_invalid); config_port_invalid = NULL;
config_port_invalid = mock_config_line("DNSPort", "invalidstuff!!:auto");
+ MOCK(tor_addr_lookup, mock_tor_addr_lookup__fail_on_bad_addrs);
ret = parse_port_config(NULL, config_port_invalid, NULL, "DNS", 0,
"127.0.0.46", 0, 0);
+ UNMOCK(tor_addr_lookup);
tt_int_op(ret, OP_EQ, -1);
// Test success with parsing both an address and a real port
@@ -4890,6 +4902,33 @@ test_config_parse_port_config__ports__server_options(void *data)
config_free_lines(config_port_valid); config_port_valid = NULL;
}
+static void
+test_config_parse_log_severity(void *data)
+{
+ int ret;
+ const char *severity_log_lines[] = {
+ "debug file /tmp/debug.log",
+ "debug\tfile /tmp/debug.log",
+ "[handshake]debug [~net,~mm]info notice stdout",
+ "[handshake]debug\t[~net,~mm]info\tnotice\tstdout",
+ NULL
+ };
+ int i;
+ log_severity_list_t *severity;
+
+ (void) data;
+
+ severity = tor_malloc(sizeof(log_severity_list_t));
+ for (i = 0; severity_log_lines[i]; i++) {
+ memset(severity, 0, sizeof(log_severity_list_t));
+ ret = parse_log_severity_config(&severity_log_lines[i], severity);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ done:
+ tor_free(severity);
+}
+
#define CONFIG_TEST(name, flags) \
{ #name, test_config_ ## name, flags, NULL, NULL }
@@ -4916,6 +4955,7 @@ struct testcase_t config_tests[] = {
CONFIG_TEST(parse_port_config__ports__no_ports_given, 0),
CONFIG_TEST(parse_port_config__ports__server_options, 0),
CONFIG_TEST(parse_port_config__ports__ports_given, 0),
+ CONFIG_TEST(parse_log_severity, 0),
END_OF_TESTCASES
};
diff --git a/src/test/test_connection.c b/src/test/test_connection.c
index d394fc9852..5cda4f3175 100644
--- a/src/test/test_connection.c
+++ b/src/test/test_connection.c
@@ -10,6 +10,7 @@
#include "test.h"
#include "connection.h"
+#include "hs_common.h"
#include "main.h"
#include "microdesc.h"
#include "networkstatus.h"
@@ -265,13 +266,9 @@ test_conn_get_rend_setup(const struct testcase_t *tc)
rend_cache_init();
/* TODO: use directory_initiate_command_rend() to do this - maybe? */
- conn->rend_data = tor_malloc_zero(sizeof(rend_data_t));
tor_assert(strlen(TEST_CONN_REND_ADDR) == REND_SERVICE_ID_LEN_BASE32);
- memcpy(conn->rend_data->onion_address,
- TEST_CONN_REND_ADDR,
- REND_SERVICE_ID_LEN_BASE32+1);
- conn->rend_data->hsdirs_fp = smartlist_new();
-
+ conn->rend_data = rend_data_client_create(TEST_CONN_REND_ADDR, NULL, NULL,
+ REND_NO_AUTH);
assert_connection_ok(&conn->base_, time(NULL));
return conn;
@@ -551,7 +548,8 @@ test_conn_get_rend(void *arg)
tt_assert(connection_get_by_type_state_rendquery(
conn->base_.type,
conn->base_.state,
- conn->rend_data->onion_address)
+ rend_data_get_address(
+ conn->rend_data))
== TO_CONN(conn));
tt_assert(connection_get_by_type_state_rendquery(
TEST_CONN_TYPE,
diff --git a/src/test/test_containers.c b/src/test/test_containers.c
index d8b82e0661..41f3f873de 100644
--- a/src/test/test_containers.c
+++ b/src/test/test_containers.c
@@ -501,13 +501,13 @@ test_container_smartlist_pos(void *arg)
(void) arg;
smartlist_t *sl = smartlist_new();
- smartlist_add(sl, tor_strdup("This"));
- smartlist_add(sl, tor_strdup("is"));
- smartlist_add(sl, tor_strdup("a"));
- smartlist_add(sl, tor_strdup("test"));
- smartlist_add(sl, tor_strdup("for"));
- smartlist_add(sl, tor_strdup("a"));
- smartlist_add(sl, tor_strdup("function"));
+ smartlist_add_strdup(sl, "This");
+ smartlist_add_strdup(sl, "is");
+ smartlist_add_strdup(sl, "a");
+ smartlist_add_strdup(sl, "test");
+ smartlist_add_strdup(sl, "for");
+ smartlist_add_strdup(sl, "a");
+ smartlist_add_strdup(sl, "function");
/* Test string_pos */
tt_int_op(smartlist_string_pos(NULL, "Fred"), ==, -1);
@@ -830,7 +830,7 @@ test_container_strmap(void *arg)
found_keys = smartlist_new();
while (!strmap_iter_done(iter)) {
strmap_iter_get(iter,&k,&v);
- smartlist_add(found_keys, tor_strdup(k));
+ smartlist_add_strdup(found_keys, k);
tt_ptr_op(v,OP_EQ, strmap_get(map, k));
if (!strcmp(k, "K2")) {
@@ -882,6 +882,46 @@ test_container_strmap(void *arg)
tor_free(v105);
}
+static void
+test_container_smartlist_remove(void *arg)
+{
+ (void) arg;
+ int array[5];
+ smartlist_t *sl = smartlist_new();
+ int i,j;
+
+ for (j=0; j < 2; ++j)
+ for (i=0; i < 5; ++i)
+ smartlist_add(sl, &array[i]);
+
+ smartlist_remove(sl, &array[0]);
+ smartlist_remove(sl, &array[3]);
+ smartlist_remove(sl, &array[4]);
+ tt_assert(! smartlist_contains(sl, &array[0]));
+ tt_assert(smartlist_contains(sl, &array[1]));
+ tt_assert(smartlist_contains(sl, &array[2]));
+ tt_assert(! smartlist_contains(sl, &array[3]));
+ tt_assert(! smartlist_contains(sl, &array[4]));
+ tt_int_op(smartlist_len(sl), OP_EQ, 4);
+
+ smartlist_clear(sl);
+ for (j=0; j < 2; ++j)
+ for (i=0; i < 5; ++i)
+ smartlist_add(sl, &array[i]);
+
+ smartlist_remove_keeporder(sl, &array[0]);
+ smartlist_remove_keeporder(sl, &array[3]);
+ smartlist_remove_keeporder(sl, &array[4]);
+ tt_int_op(smartlist_len(sl), OP_EQ, 4);
+ tt_ptr_op(smartlist_get(sl, 0), OP_EQ, &array[1]);
+ tt_ptr_op(smartlist_get(sl, 1), OP_EQ, &array[2]);
+ tt_ptr_op(smartlist_get(sl, 2), OP_EQ, &array[1]);
+ tt_ptr_op(smartlist_get(sl, 3), OP_EQ, &array[2]);
+
+ done:
+ smartlist_free(sl);
+}
+
/** Run unit tests for getting the median of a list. */
static void
test_container_order_functions(void *arg)
@@ -1239,6 +1279,7 @@ struct testcase_t container_tests[] = {
CONTAINER_LEGACY(smartlist_digests),
CONTAINER_LEGACY(smartlist_join),
CONTAINER_LEGACY(smartlist_pos),
+ CONTAINER(smartlist_remove, 0),
CONTAINER(smartlist_ints_eq, 0),
CONTAINER_LEGACY(bitarray),
CONTAINER_LEGACY(digestset),
diff --git a/src/test/test_controller.c b/src/test/test_controller.c
index f19c846144..d9c0a1eaac 100644
--- a/src/test/test_controller.c
+++ b/src/test/test_controller.c
@@ -3,12 +3,14 @@
#define CONTROL_PRIVATE
#include "or.h"
+#include "bridges.h"
#include "control.h"
#include "entrynodes.h"
#include "networkstatus.h"
#include "rendservice.h"
#include "routerlist.h"
#include "test.h"
+#include "test_helpers.h"
static void
test_add_onion_helper_keyarg(void *arg)
@@ -185,8 +187,10 @@ test_rend_service_parse_port_config(void *arg)
tor_free(err_msg);
/* bogus IP address */
- cfg = rend_service_parse_port_config("100 1.2.3.4.5:9000",
+ MOCK(tor_addr_lookup, mock_tor_addr_lookup__fail_on_bad_addrs);
+ cfg = rend_service_parse_port_config("100 foo!!.example.com:9000",
" ", &err_msg);
+ UNMOCK(tor_addr_lookup);
tt_assert(!cfg);
tt_str_op(err_msg, OP_EQ, "Unparseable address in hidden service port "
"configuration.");
diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c
index 64a46f7914..d66ddccd4f 100644
--- a/src/test/test_crypto.c
+++ b/src/test/test_crypto.c
@@ -1135,6 +1135,54 @@ test_crypto_sha3_xof(void *arg)
tor_free(mem_op_hex_tmp);
}
+/* Test our MAC-SHA3 function. There are not actually any MAC-SHA3 test
+ * vectors out there for our H(len(k) || k || m) construction. Hence what we
+ * are gonna do is test our crypto_mac_sha3_256() function against manually
+ * doing H(len(k) || k||m). If in the future the Keccak group decides to
+ * standarize an MAC construction and make test vectors, we should
+ * incorporate them here. */
+static void
+test_crypto_mac_sha3(void *arg)
+{
+ const char msg[] = "i am in a library somewhere using my computer";
+ const char key[] = "i'm from the past talking to the future.";
+
+ uint8_t hmac_test[DIGEST256_LEN];
+ char hmac_manual[DIGEST256_LEN];
+
+ (void) arg;
+
+ /* First let's use our nice HMAC-SHA3 function */
+ crypto_mac_sha3_256(hmac_test, sizeof(hmac_test),
+ (uint8_t *) key, strlen(key),
+ (uint8_t *) msg, strlen(msg));
+
+ /* Now let's try a manual H(len(k) || k || m) construction */
+ {
+ char *key_msg_concat = NULL, *all = NULL;
+ int result;
+ const uint64_t key_len_netorder = tor_htonll(strlen(key));
+ size_t all_len;
+
+ tor_asprintf(&key_msg_concat, "%s%s", key, msg);
+ all_len = sizeof(key_len_netorder) + strlen(key_msg_concat);
+ all = tor_malloc_zero(all_len);
+ memcpy(all, &key_len_netorder, sizeof(key_len_netorder));
+ memcpy(all + sizeof(key_len_netorder), key_msg_concat,
+ strlen(key_msg_concat));
+
+ result = crypto_digest256(hmac_manual, all, all_len, DIGEST_SHA3_256);
+ tor_free(key_msg_concat);
+ tor_free(all);
+ tt_int_op(result, ==, 0);
+ }
+
+ /* Now compare the two results */
+ tt_mem_op(hmac_test, OP_EQ, hmac_manual, DIGEST256_LEN);
+
+ done: ;
+}
+
/** Run unit tests for our public key crypto functions */
static void
test_crypto_pk(void *arg)
@@ -2918,6 +2966,7 @@ struct testcase_t crypto_tests[] = {
{ "digest_names", test_crypto_digest_names, 0, NULL, NULL },
{ "sha3", test_crypto_sha3, TT_FORK, NULL, NULL},
{ "sha3_xof", test_crypto_sha3_xof, TT_FORK, NULL, NULL},
+ { "mac_sha3", test_crypto_mac_sha3, TT_FORK, NULL, NULL},
CRYPTO_LEGACY(dh),
{ "aes_iv_AES", test_crypto_aes_iv, TT_FORK, &passthrough_setup,
(void*)"aes" },
diff --git a/src/test/test_dir.c b/src/test/test_dir.c
index cdc56acb89..4e5876fa3c 100644
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@ -23,6 +23,7 @@
#include "directory.h"
#include "dirserv.h"
#include "dirvote.h"
+#include "entrynodes.h"
#include "hibernate.h"
#include "memarea.h"
#include "networkstatus.h"
@@ -678,16 +679,16 @@ test_dir_parse_router_list(void *arg)
routerinfo_t *ri = NULL;
char d[DIGEST_LEN];
- smartlist_add(chunks, tor_strdup(EX_RI_MINIMAL)); // ri 0
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_PORTS)); // bad ri 0
- smartlist_add(chunks, tor_strdup(EX_EI_MAXIMAL)); // ei 0
- smartlist_add(chunks, tor_strdup(EX_EI_BAD_SIG2)); // bad ei --
- smartlist_add(chunks, tor_strdup(EX_EI_BAD_NICKNAME));// bad ei 0
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_SIG1)); // bad ri --
- smartlist_add(chunks, tor_strdup(EX_EI_BAD_PUBLISHED)); // bad ei 1
- smartlist_add(chunks, tor_strdup(EX_RI_MAXIMAL)); // ri 1
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_FAMILY)); // bad ri 1
- smartlist_add(chunks, tor_strdup(EX_EI_MINIMAL)); // ei 1
+ smartlist_add_strdup(chunks, EX_RI_MINIMAL); // ri 0
+ smartlist_add_strdup(chunks, EX_RI_BAD_PORTS); // bad ri 0
+ smartlist_add_strdup(chunks, EX_EI_MAXIMAL); // ei 0
+ smartlist_add_strdup(chunks, EX_EI_BAD_SIG2); // bad ei --
+ smartlist_add_strdup(chunks, EX_EI_BAD_NICKNAME);// bad ei 0
+ smartlist_add_strdup(chunks, EX_RI_BAD_SIG1); // bad ri --
+ smartlist_add_strdup(chunks, EX_EI_BAD_PUBLISHED); // bad ei 1
+ smartlist_add_strdup(chunks, EX_RI_MAXIMAL); // ri 1
+ smartlist_add_strdup(chunks, EX_RI_BAD_FAMILY); // bad ri 1
+ smartlist_add_strdup(chunks, EX_EI_MINIMAL); // ei 1
list = smartlist_join_strings(chunks, "", 0, NULL);
@@ -812,19 +813,19 @@ test_dir_load_routers(void *arg)
#define ADD(str) \
do { \
tt_int_op(0,OP_EQ,router_get_router_hash(str, strlen(str), buf)); \
- smartlist_add(wanted, tor_strdup(hex_str(buf, DIGEST_LEN))); \
+ smartlist_add_strdup(wanted, hex_str(buf, DIGEST_LEN)); \
} while (0)
MOCK(router_get_dl_status_by_descriptor_digest, mock_router_get_dl_status);
update_approx_time(1412510400);
- smartlist_add(chunks, tor_strdup(EX_RI_MINIMAL));
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_FINGERPRINT));
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_SIG2));
- smartlist_add(chunks, tor_strdup(EX_RI_MAXIMAL));
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_PORTS));
- smartlist_add(chunks, tor_strdup(EX_RI_BAD_TOKENS));
+ smartlist_add_strdup(chunks, EX_RI_MINIMAL);
+ smartlist_add_strdup(chunks, EX_RI_BAD_FINGERPRINT);
+ smartlist_add_strdup(chunks, EX_RI_BAD_SIG2);
+ smartlist_add_strdup(chunks, EX_RI_MAXIMAL);
+ smartlist_add_strdup(chunks, EX_RI_BAD_PORTS);
+ smartlist_add_strdup(chunks, EX_RI_BAD_TOKENS);
/* not ADDing MINIMIAL */
ADD(EX_RI_MAXIMAL);
@@ -932,18 +933,18 @@ test_dir_load_extrainfo(void *arg)
#define ADD(str) \
do { \
tt_int_op(0,OP_EQ,router_get_extrainfo_hash(str, strlen(str), buf)); \
- smartlist_add(wanted, tor_strdup(hex_str(buf, DIGEST_LEN))); \
+ smartlist_add_strdup(wanted, hex_str(buf, DIGEST_LEN)); \
} while (0)
mock_ei_insert_list = smartlist_new();
MOCK(router_get_by_extrainfo_digest, mock_get_by_ei_desc_digest);
MOCK(extrainfo_insert, mock_ei_insert);
- smartlist_add(chunks, tor_strdup(EX_EI_MINIMAL));
- smartlist_add(chunks, tor_strdup(EX_EI_BAD_NICKNAME));
- smartlist_add(chunks, tor_strdup(EX_EI_MAXIMAL));
- smartlist_add(chunks, tor_strdup(EX_EI_BAD_PUBLISHED));
- smartlist_add(chunks, tor_strdup(EX_EI_BAD_TOKENS));
+ smartlist_add_strdup(chunks, EX_EI_MINIMAL);
+ smartlist_add_strdup(chunks, EX_EI_BAD_NICKNAME);
+ smartlist_add_strdup(chunks, EX_EI_MAXIMAL);
+ smartlist_add_strdup(chunks, EX_EI_BAD_PUBLISHED);
+ smartlist_add_strdup(chunks, EX_EI_BAD_TOKENS);
/* not ADDing MINIMIAL */
ADD(EX_EI_MAXIMAL);
@@ -1493,6 +1494,15 @@ test_dir_param_voting(void *arg)
tt_int_op(-8,OP_EQ, networkstatus_get_param(&vote4, "ab", -12, -100, -8));
tt_int_op(0,OP_EQ, networkstatus_get_param(&vote4, "foobar", 0, -100, 8));
+ tt_int_op(100,OP_EQ, networkstatus_get_overridable_param(
+ &vote4, -1, "x-yz", 50, 0, 300));
+ tt_int_op(30,OP_EQ, networkstatus_get_overridable_param(
+ &vote4, 30, "x-yz", 50, 0, 300));
+ tt_int_op(0,OP_EQ, networkstatus_get_overridable_param(
+ &vote4, -101, "foobar", 0, -100, 8));
+ tt_int_op(-99,OP_EQ, networkstatus_get_overridable_param(
+ &vote4, -99, "foobar", 0, -100, 8));
+
smartlist_add(votes, &vote1);
/* Do the first tests without adding all the other votes, for
@@ -1874,6 +1884,249 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now)
return;
}
+static void
+test_dir_networkstatus_compute_bw_weights_v10(void *arg)
+{
+ (void) arg;
+ smartlist_t *chunks = smartlist_new();
+ int64_t G, M, E, D, T, weight_scale;
+ int ret;
+ weight_scale = 10000;
+
+ /* no case. one or more of the values is 0 */
+ G = M = E = D = 0;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 0);
+ tt_int_op(smartlist_len(chunks), OP_EQ, 0);
+
+ /* case 1 */
+ /* XXX dir-spec not followed? See #20272. If it isn't closed, then this is
+ * testing current behavior, not spec. */
+ G = E = 10;
+ M = D = 1;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_int_op(smartlist_len(chunks), OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=3333 "
+ "Wbe=3000 Wbg=3000 Wbm=10000 Wdb=10000 Web=10000 Wed=3333 Wee=7000 "
+ "Weg=3333 Wem=7000 Wgb=10000 Wgd=3333 Wgg=7000 Wgm=7000 Wmb=10000 "
+ "Wmd=3333 Wme=3000 Wmg=3000 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 2a E scarce */
+ M = 100;
+ G = 20;
+ E = D = 5;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 Wbe=0 "
+ "Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 "
+ "Wem=10000 Wgb=10000 Wgd=0 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=0 Wme=0 "
+ "Wmg=0 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 2a G scarce */
+ M = 100;
+ E = 20;
+ G = D = 5;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 Wbe=0 "
+ "Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=0 Wee=10000 Weg=0 Wem=10000 "
+ "Wgb=10000 Wgd=10000 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=0 Wme=0 Wmg=0 "
+ "Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 2b1 (Wgg=1, Wmd=Wgd) */
+ M = 10;
+ E = 30;
+ G = 10;
+ D = 100;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=4000 "
+ "Wbe=0 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=2000 Wee=10000 Weg=2000 "
+ "Wem=10000 Wgb=10000 Wgd=4000 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=4000 "
+ "Wme=0 Wmg=0 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 2b2 */
+ M = 60;
+ E = 30;
+ G = 10;
+ D = 100;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=666 Wbe=0 "
+ "Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=3666 Wee=10000 Weg=3666 "
+ "Wem=10000 Wgb=10000 Wgd=5668 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=666 "
+ "Wme=0 Wmg=0 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 2b3 */
+ /* XXX I can't get a combination of values that hits this case without error,
+ * so this just tests that it fails. See #20285. Also see #20284 as 2b3 does
+ * not follow dir-spec. */
+ /* (E < T/3 && G < T/3) && (E+D>=G || G+D>=E) && (M > T/3) */
+ M = 80;
+ E = 30;
+ G = 30;
+ D = 30;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 0);
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 3a G scarce */
+ M = 10;
+ E = 30;
+ G = 10;
+ D = 5;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 "
+ "Wbe=3333 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=0 Wee=6667 Weg=0 "
+ "Wem=6667 Wgb=10000 Wgd=10000 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=0 "
+ "Wme=3333 Wmg=0 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 3a E scarce */
+ M = 10;
+ E = 10;
+ G = 30;
+ D = 5;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 Wbe=0 "
+ "Wbg=3333 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 "
+ "Wem=10000 Wgb=10000 Wgd=0 Wgg=6667 Wgm=6667 Wmb=10000 Wmd=0 Wme=0 "
+ "Wmg=3333 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 3bg */
+ M = 10;
+ E = 30;
+ G = 10;
+ D = 10;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 "
+ "Wbe=3334 Wbg=0 Wbm=10000 Wdb=10000 Web=10000 Wed=0 Wee=6666 Weg=0 "
+ "Wem=6666 Wgb=10000 Wgd=10000 Wgg=10000 Wgm=10000 Wmb=10000 Wmd=0 "
+ "Wme=3334 Wmg=0 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case 3be */
+ M = 10;
+ E = 10;
+ G = 30;
+ D = 10;
+ T = G + M + E + D;
+ ret = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D, T,
+ weight_scale);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=0 Wbe=0 "
+ "Wbg=3334 Wbm=10000 Wdb=10000 Web=10000 Wed=10000 Wee=10000 Weg=10000 "
+ "Wem=10000 Wgb=10000 Wgd=0 Wgg=6666 Wgm=6666 Wmb=10000 Wmd=0 Wme=0 "
+ "Wmg=3334 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case from 21 Jul 2013 (3be) */
+ G = 5483409;
+ M = 1455379;
+ E = 980834;
+ D = 3385803;
+ T = 11305425;
+ 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_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 "
+ "Wmg=3673 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case from 04 Oct 2016 (3a E scarce) */
+ G=29322240;
+ M=4721546;
+ E=1522058;
+ D=9273571;
+ T=44839415;
+ 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_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 "
+ "Wmg=4194 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* case from 04 Sep 2013 (2b1) */
+ G=3091352;
+ M=1838837;
+ E=2109300;
+ D=2469369;
+ T=9508858;
+ 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_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 "
+ "Wmd=317 Wme=5938 Wmg=0 Wmm=10000\n");
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_clear(chunks);
+
+ /* explicitly test initializing weights to 1*/
+ G=1;
+ M=1;
+ E=1;
+ D=1;
+ T=4;
+ 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_str_op(smartlist_get(chunks, 0), OP_EQ, "bandwidth-weights Wbd=3333 "
+ "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");
+
+ done:
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+}
+
static authority_cert_t *mock_cert;
static authority_cert_t *
@@ -3253,17 +3506,88 @@ test_dir_http_handling(void *args)
}
static void
-test_dir_purpose_needs_anonymity(void *arg)
+test_dir_purpose_needs_anonymity_returns_true_by_default(void *arg)
+{
+ (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_()));
+ expect_single_log_msg_containing("Called with dir_purpose=0");
+
+ tor_end_capture_bugs_();
+ done:
+ tor_end_capture_bugs_();
+ teardown_capture_of_logs();
+}
+
+static void
+test_dir_purpose_needs_anonymity_returns_true_for_bridges(void *arg)
{
(void)arg;
- tt_int_op(1, ==, purpose_needs_anonymity(0, ROUTER_PURPOSE_BRIDGE));
- tt_int_op(1, ==, purpose_needs_anonymity(0, ROUTER_PURPOSE_GENERAL));
- tt_int_op(0, ==, purpose_needs_anonymity(DIR_PURPOSE_FETCH_MICRODESC,
- ROUTER_PURPOSE_GENERAL));
+
+ tt_int_op(1, ==, purpose_needs_anonymity(0, ROUTER_PURPOSE_BRIDGE, NULL));
+ tt_int_op(1, ==, 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));
done: ;
}
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,
+ ROUTER_PURPOSE_BRIDGE,
+ "authority.z"));
+ done: ;
+}
+
+static void
+test_dir_purpose_needs_anonymity_returns_true_for_sensitive_purpose(void *arg)
+{
+ (void)arg;
+
+ tt_int_op(1, ==, purpose_needs_anonymity(
+ DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2,
+ ROUTER_PURPOSE_GENERAL, NULL));
+ tt_int_op(1, ==, purpose_needs_anonymity(
+ DIR_PURPOSE_UPLOAD_RENDDESC_V2, 0, NULL));
+ tt_int_op(1, ==, purpose_needs_anonymity(
+ DIR_PURPOSE_FETCH_RENDDESC_V2, 0, NULL));
+ done: ;
+}
+
+static void
+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,
+ ROUTER_PURPOSE_GENERAL, NULL));
+ tt_int_op(0, ==, purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_VOTE, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_UPLOAD_SIGNATURES, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL));
+ tt_int_op(0, ==, purpose_needs_anonymity(
+ DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_FETCH_CONSENSUS, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_FETCH_SERVERDESC, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_FETCH_EXTRAINFO, 0, NULL));
+ tt_int_op(0, ==,
+ purpose_needs_anonymity(DIR_PURPOSE_FETCH_MICRODESC, 0, NULL));
+ done: ;
+}
+
+static void
test_dir_fetch_type(void *arg)
{
(void)arg;
@@ -4035,7 +4359,6 @@ test_dir_should_use_directory_guards(void *data)
tt_int_op(should_use_directory_guards(options), OP_EQ, 0);
tt_int_op(CALLED(public_server_mode), OP_EQ, 1);
- options->UseEntryGuardsAsDirGuards = 1;
options->UseEntryGuards = 1;
options->DownloadExtraInfo = 0;
options->FetchDirInfoEarly = 0;
@@ -4049,29 +4372,24 @@ test_dir_should_use_directory_guards(void *data)
tt_int_op(CALLED(public_server_mode), OP_EQ, 3);
options->UseEntryGuards = 1;
- options->UseEntryGuardsAsDirGuards = 0;
- tt_int_op(should_use_directory_guards(options), OP_EQ, 0);
- tt_int_op(CALLED(public_server_mode), OP_EQ, 4);
- options->UseEntryGuardsAsDirGuards = 1;
-
options->DownloadExtraInfo = 1;
tt_int_op(should_use_directory_guards(options), OP_EQ, 0);
- tt_int_op(CALLED(public_server_mode), OP_EQ, 5);
+ tt_int_op(CALLED(public_server_mode), OP_EQ, 4);
options->DownloadExtraInfo = 0;
options->FetchDirInfoEarly = 1;
tt_int_op(should_use_directory_guards(options), OP_EQ, 0);
- tt_int_op(CALLED(public_server_mode), OP_EQ, 6);
+ tt_int_op(CALLED(public_server_mode), OP_EQ, 5);
options->FetchDirInfoEarly = 0;
options->FetchDirInfoExtraEarly = 1;
tt_int_op(should_use_directory_guards(options), OP_EQ, 0);
- tt_int_op(CALLED(public_server_mode), OP_EQ, 7);
+ tt_int_op(CALLED(public_server_mode), OP_EQ, 6);
options->FetchDirInfoExtraEarly = 0;
options->FetchUselessDescriptors = 1;
tt_int_op(should_use_directory_guards(options), OP_EQ, 0);
- tt_int_op(CALLED(public_server_mode), OP_EQ, 8);
+ tt_int_op(CALLED(public_server_mode), OP_EQ, 7);
options->FetchUselessDescriptors = 0;
done:
@@ -4088,7 +4406,8 @@ directory_initiate_command_routerstatus, (const routerstatus_t *status,
const char *resource,
const char *payload,
size_t payload_len,
- time_t if_modified_since));
+ time_t if_modified_since,
+ circuit_guard_state_t *guardstate));
static void
test_dir_should_not_init_request_to_ourselves(void *data)
@@ -4195,7 +4514,8 @@ NS(directory_initiate_command_routerstatus)(const routerstatus_t *status,
const char *resource,
const char *payload,
size_t payload_len,
- time_t if_modified_since)
+ time_t if_modified_since,
+ circuit_guard_state_t *guardstate)
{
(void)status;
(void)dir_purpose;
@@ -4205,6 +4525,7 @@ NS(directory_initiate_command_routerstatus)(const routerstatus_t *status,
(void)payload;
(void)payload_len;
(void)if_modified_since;
+ (void)guardstate;
CALLED(directory_initiate_command_routerstatus)++;
}
@@ -5148,9 +5469,9 @@ listdir_mock(const char *dname)
(void)dname;
l = smartlist_new();
- smartlist_add(l, tor_strdup("foo"));
- smartlist_add(l, tor_strdup("bar"));
- smartlist_add(l, tor_strdup("baz"));
+ smartlist_add_strdup(l, "foo");
+ smartlist_add_strdup(l, "bar");
+ smartlist_add_strdup(l, "baz");
return l;
}
@@ -5437,6 +5758,67 @@ test_dir_assumed_flags(void *arg)
routerstatus_free(rs);
}
+static void
+test_dir_post_parsing(void *arg)
+{
+ (void) arg;
+
+ /* Test the version parsing from an HS descriptor publish request. */
+ {
+ const char *end;
+ const char *prefix = "/tor/hs/";
+ int version = parse_hs_version_from_post("/tor/hs//publish", prefix, &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ version = parse_hs_version_from_post("/tor/hs/a/publish", prefix, &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ version = parse_hs_version_from_post("/tor/hs/3/publish", prefix, &end);
+ tt_int_op(version, OP_EQ, 3);
+ tt_str_op(end, OP_EQ, "/publish");
+ version = parse_hs_version_from_post("/tor/hs/42/publish", prefix, &end);
+ tt_int_op(version, OP_EQ, 42);
+ tt_str_op(end, OP_EQ, "/publish");
+ version = parse_hs_version_from_post("/tor/hs/18163/publish",prefix, &end);
+ tt_int_op(version, OP_EQ, 18163);
+ tt_str_op(end, OP_EQ, "/publish");
+ version = parse_hs_version_from_post("JUNKJUNKJUNK", prefix, &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ version = parse_hs_version_from_post("/tor/hs/3/publish", "blah", &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ /* Missing the '/' at the end of the prefix. */
+ version = parse_hs_version_from_post("/tor/hs/3/publish", "/tor/hs", &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ version = parse_hs_version_from_post("/random/blah/tor/hs/3/publish",
+ prefix, &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ version = parse_hs_version_from_post("/tor/hs/3/publish/random/junk",
+ prefix, &end);
+ tt_int_op(version, OP_EQ, 3);
+ tt_str_op(end, OP_EQ, "/publish/random/junk");
+ version = parse_hs_version_from_post("/tor/hs/-1/publish", prefix, &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ /* INT_MAX */
+ version = parse_hs_version_from_post("/tor/hs/2147483647/publish",
+ prefix, &end);
+ tt_int_op(version, OP_EQ, INT_MAX);
+ tt_str_op(end, OP_EQ, "/publish");
+ /* INT_MAX + 1*/
+ version = parse_hs_version_from_post("/tor/hs/2147483648/publish",
+ prefix, &end);
+ tt_int_op(version, OP_EQ, -1);
+ tt_ptr_op(end, OP_EQ, NULL);
+ }
+
+ done:
+ ;
+}
+
#define DIR_LEGACY(name) \
{ #name, test_dir_ ## name , TT_FORK, NULL, NULL }
@@ -5470,7 +5852,12 @@ struct testcase_t dir_tests[] = {
DIR(fmt_control_ns, 0),
DIR(dirserv_set_routerstatus_testing, 0),
DIR(http_handling, 0),
- DIR(purpose_needs_anonymity, 0),
+ DIR(purpose_needs_anonymity_returns_true_for_bridges, 0),
+ DIR(purpose_needs_anonymity_returns_false_for_own_bridge_desc, 0),
+ DIR(purpose_needs_anonymity_returns_true_by_default, 0),
+ DIR(purpose_needs_anonymity_returns_true_for_sensitive_purpose, 0),
+ DIR(purpose_needs_anonymity_ret_false_for_non_sensitive_conn, 0),
+ DIR(post_parsing, 0),
DIR(fetch_type, 0),
DIR(packages, 0),
DIR(download_status_schedule, 0),
@@ -5491,6 +5878,7 @@ struct testcase_t dir_tests[] = {
DIR_ARG(find_dl_schedule, TT_FORK, "cf"),
DIR_ARG(find_dl_schedule, TT_FORK, "ca"),
DIR(assumed_flags, 0),
+ DIR(networkstatus_compute_bw_weights_v10, 0),
END_OF_TESTCASES
};
diff --git a/src/test/test_dir_handle_get.c b/src/test/test_dir_handle_get.c
index a0f22f1f0c..a0868f9253 100644
--- a/src/test/test_dir_handle_get.c
+++ b/src/test/test_dir_handle_get.c
@@ -30,6 +30,7 @@
#include "dirserv.h"
#include "torgzip.h"
#include "dirvote.h"
+#include "log_test_helpers.h"
#ifdef _WIN32
/* For mkdir() */
@@ -50,22 +51,10 @@ ENABLE_GCC_WARNING(overlength-strings)
#define NS_MODULE dir_handle_get
-static void
-connection_write_to_buf_mock(const char *string, size_t len,
- connection_t *conn, int zlib)
-{
- (void) zlib;
-
- tor_assert(string);
- tor_assert(conn);
-
- write_to_buf(string, len, conn->outbuf);
-}
-
-#define GET(path) "GET " path " HTTP/1.0\r\n\r\n"
#define NOT_FOUND "HTTP/1.0 404 Not found\r\n\r\n"
#define BAD_REQUEST "HTTP/1.0 400 Bad request\r\n\r\n"
#define SERVER_BUSY "HTTP/1.0 503 Directory busy, try again later\r\n\r\n"
+#define TOO_OLD "HTTP/1.0 404 Consensus is too old\r\n\r\n"
#define NOT_ENOUGH_CONSENSUS_SIGNATURES "HTTP/1.0 404 " \
"Consensus not signed by sufficient number of requested authorities\r\n\r\n"
@@ -1630,6 +1619,7 @@ test_dir_handle_get_status_vote_current_consensus_ns_not_enough_sigs(void* d)
mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t));
mock_ns_val->flavor = FLAV_NS;
mock_ns_val->voters = smartlist_new();
+ mock_ns_val->valid_until = time(NULL);
/* init mock */
init_mock_options();
@@ -1709,6 +1699,62 @@ test_dir_handle_get_status_vote_current_consensus_ns_not_found(void* data)
or_options_free(mock_options); mock_options = NULL;
}
+static void
+test_dir_handle_get_status_vote_current_consensus_too_old(void *data)
+{
+ dir_connection_t *conn = NULL;
+ char *header = NULL;
+ (void)data;
+
+ mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t));
+ mock_ns_val->flavor = FLAV_MICRODESC;
+ mock_ns_val->valid_until = time(NULL) - (60 * 60 * 24) - 1;
+
+ init_mock_options();
+ MOCK(get_options, mock_get_options);
+ MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock);
+ MOCK(networkstatus_get_latest_consensus_by_flavor, mock_ns_get_by_flavor);
+
+ conn = new_dir_conn();
+
+ setup_capture_of_logs(LOG_WARN);
+
+ tt_int_op(0, OP_EQ, directory_handle_command_get(conn,
+ GET("/tor/status-vote/current/consensus-microdesc"), NULL, 0));
+
+ fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE,
+ NULL, NULL, 1, 0);
+ tt_assert(header);
+ tt_str_op(TOO_OLD, OP_EQ, header);
+
+ expect_log_msg_containing("too old");
+
+ tor_free(header);
+ teardown_capture_of_logs();
+
+ setup_capture_of_logs(LOG_WARN);
+
+ tt_int_op(0, OP_EQ, directory_handle_command_get(conn,
+ GET("/tor/status-vote/current/consensus"), NULL, 0));
+
+ fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE,
+ NULL, NULL, 1, 0);
+ tt_assert(header);
+ tt_str_op(TOO_OLD, OP_EQ, header);
+
+ expect_no_log_entry();
+
+ done:
+ teardown_capture_of_logs();
+ UNMOCK(networkstatus_get_latest_consensus_by_flavor);
+ UNMOCK(connection_write_to_buf_impl_);
+ UNMOCK(get_options);
+ connection_free_(TO_CONN(conn));
+ tor_free(header);
+ tor_free(mock_ns_val);
+ or_options_free(mock_options); mock_options = NULL;
+}
+
NS_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr));
int
@@ -2494,6 +2540,7 @@ struct testcase_t dir_handle_get_tests[] = {
DIR_HANDLE_CMD(status_vote_next_authority, 0),
DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_enough_sigs, 0),
DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_found, 0),
+ DIR_HANDLE_CMD(status_vote_current_consensus_too_old, 0),
DIR_HANDLE_CMD(status_vote_current_consensus_ns_busy, 0),
DIR_HANDLE_CMD(status_vote_current_consensus_ns, 0),
DIR_HANDLE_CMD(status_vote_current_d_not_found, 0),
diff --git a/src/test/test_entryconn.c b/src/test/test_entryconn.c
index 9580a1fd3f..50848cfec2 100644
--- a/src/test/test_entryconn.c
+++ b/src/test/test_entryconn.c
@@ -100,7 +100,7 @@ test_entryconn_rewrite_automap_ipv4(void *arg)
ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET);
get_options_mutable()->AutomapHostsOnResolve = 1;
- smartlist_add(get_options_mutable()->AutomapHostsSuffixes, tor_strdup("."));
+ smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes, ".");
parse_virtual_addr_network("127.202.0.0/16", AF_INET, 0, &msg);
/* Automap this on resolve. */
@@ -173,7 +173,7 @@ test_entryconn_rewrite_automap_ipv6(void *arg)
ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET6);
get_options_mutable()->AutomapHostsOnResolve = 1;
- smartlist_add(get_options_mutable()->AutomapHostsSuffixes, tor_strdup("."));
+ smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes, ".");
parse_virtual_addr_network("FE80::/32", AF_INET6, 0, &msg);
/* Automap this on resolve. */
@@ -489,8 +489,8 @@ test_entryconn_rewrite_automap_exit(void *arg)
get_options_mutable()->AutomapHostsOnResolve = 1;
get_options_mutable()->AllowDotExit = 1;
- smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
- tor_strdup(".EXIT"));
+ 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. */
@@ -574,8 +574,8 @@ test_entryconn_rewrite_mapaddress_automap_onion(void *arg)
get_options_mutable()->AutomapHostsOnResolve = 1;
get_options_mutable()->AllowDotExit = 1;
- smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
- tor_strdup(".onion"));
+ smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes,
+ ".onion");
parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg);
config_line_append(&get_options_mutable()->AddressMap,
"MapAddress", "foo.onion abcdefghijklmnop.onion");
@@ -709,8 +709,8 @@ test_entryconn_rewrite_mapaddress_automap_onion2(void *arg)
{
char *msg = NULL;
get_options_mutable()->AutomapHostsOnResolve = 1;
- smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
- tor_strdup(".onion"));
+ smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes,
+ ".onion");
parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg);
config_line_append(&get_options_mutable()->AddressMap,
"MapAddress", "irc.example.com abcdefghijklmnop.onion");
@@ -736,8 +736,8 @@ test_entryconn_rewrite_mapaddress_automap_onion4(void *arg)
{
char *msg = NULL;
get_options_mutable()->AutomapHostsOnResolve = 1;
- smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
- tor_strdup(".onion"));
+ smartlist_add_strdup(get_options_mutable()->AutomapHostsSuffixes,
+ ".onion");
parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg);
test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1);
diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c
index b1c3accfab..e215c60e23 100644
--- a/src/test/test_entrynodes.c
+++ b/src/test/test_entrynodes.c
@@ -3,6 +3,7 @@
#include "orconfig.h"
+#define CIRCUITLIST_PRIVATE
#define STATEFILE_PRIVATE
#define ENTRYNODES_PRIVATE
#define ROUTERLIST_PRIVATE
@@ -10,9 +11,13 @@
#include "or.h"
#include "test.h"
+#include "bridges.h"
+#include "circuitlist.h"
#include "config.h"
+#include "confparse.h"
#include "entrynodes.h"
#include "nodelist.h"
+#include "networkstatus.h"
#include "policies.h"
#include "routerlist.h"
#include "routerparse.h"
@@ -21,6 +26,7 @@
#include "util.h"
#include "test_helpers.h"
+#include "log_test_helpers.h"
/* TODO:
* choose_random_entry() test with state set.
@@ -39,37 +45,120 @@ get_or_state_replacement(void)
return dummy_state;
}
+static networkstatus_t *dummy_consensus = NULL;
+
+static smartlist_t *big_fake_net_nodes = NULL;
+
+static smartlist_t *
+bfn_mock_nodelist_get_list(void)
+{
+ return big_fake_net_nodes;
+}
+
+static networkstatus_t *
+bfn_mock_networkstatus_get_live_consensus(time_t now)
+{
+ (void)now;
+ return dummy_consensus;
+}
+
+static const node_t *
+bfn_mock_node_get_by_id(const char *id)
+{
+ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n,
+ if (fast_memeq(n->identity, id, 20))
+ return n);
+
+ return NULL;
+}
+
/* Unittest cleanup function: Cleanup the fake network. */
static int
-fake_network_cleanup(const struct testcase_t *testcase, void *ptr)
+big_fake_network_cleanup(const struct testcase_t *testcase, void *ptr)
{
(void) testcase;
(void) ptr;
- routerlist_free_all();
- nodelist_free_all();
- entry_guards_free_all();
+ if (big_fake_net_nodes) {
+ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, {
+ tor_free(n->rs);
+ tor_free(n->md);
+ tor_free(n);
+ });
+ smartlist_free(big_fake_net_nodes);
+ }
+
+ UNMOCK(nodelist_get_list);
+ UNMOCK(node_get_by_id);
+ UNMOCK(get_or_state);
+ UNMOCK(networkstatus_get_live_consensus);
or_state_free(dummy_state);
+ dummy_state = NULL;
+ tor_free(dummy_consensus);
return 1; /* NOP */
}
/* Unittest setup function: Setup a fake network. */
static void *
-fake_network_setup(const struct testcase_t *testcase)
+big_fake_network_setup(const struct testcase_t *testcase)
{
- (void) testcase;
+ int i;
+
+ /* These are minimal node_t objects that only contain the aspects of node_t
+ * that we need for entrynodes.c. */
+ const int N_NODES = 271;
+
+ big_fake_net_nodes = smartlist_new();
+ for (i = 0; i < N_NODES; ++i) {
+ node_t *n = tor_malloc_zero(sizeof(node_t));
+ n->md = tor_malloc_zero(sizeof(microdesc_t));
+
+ crypto_rand(n->identity, sizeof(n->identity));
+ n->rs = tor_malloc_zero(sizeof(routerstatus_t));
+
+ memcpy(n->rs->identity_digest, n->identity, DIGEST_LEN);
+
+ n->is_running = n->is_valid = n->is_fast = n->is_stable = 1;
+
+ /* Note: all these guards have the same address, so you'll need to
+ * disable EnforceDistinctSubnets when a restriction is applied. */
+ n->rs->addr = 0x04020202;
+ n->rs->or_port = 1234;
+ n->rs->is_v2_dir = 1;
+ n->rs->has_bandwidth = 1;
+ n->rs->bandwidth_kb = 30;
+
+ /* Call half of the nodes a possible guard. */
+ if (i % 2 == 0) {
+ n->is_possible_guard = 1;
+ n->rs->guardfraction_percentage = 100;
+ n->rs->has_guardfraction = 1;
+ }
+
+ smartlist_add(big_fake_net_nodes, n);
+ }
- /* Setup fake state */
dummy_state = tor_malloc_zero(sizeof(or_state_t));
+ dummy_consensus = tor_malloc_zero(sizeof(networkstatus_t));
+ dummy_consensus->valid_after = approx_time() - 3600;
+ dummy_consensus->valid_until = approx_time() + 3600;
+
+ MOCK(nodelist_get_list, bfn_mock_nodelist_get_list);
+ MOCK(node_get_by_id, bfn_mock_node_get_by_id);
MOCK(get_or_state,
get_or_state_replacement);
-
- /* Setup fake routerlist. */
- helper_setup_fake_routerlist();
-
+ MOCK(networkstatus_get_live_consensus,
+ bfn_mock_networkstatus_get_live_consensus);
/* Return anything but NULL (it's interpreted as test fail) */
- return dummy_state;
+ return (void*)testcase;
+}
+
+static time_t
+mock_randomize_time_no_randomization(time_t a, time_t b)
+{
+ (void) b;
+ return a;
}
static or_options_t mocked_options;
@@ -80,796 +169,2563 @@ mock_get_options(void)
return &mocked_options;
}
-/** Test choose_random_entry() with none of our routers being guard nodes. */
+#define TEST_IPV4_ADDR "123.45.67.89"
+#define TEST_IPV6_ADDR "[1234:5678:90ab:cdef::]"
+
static void
-test_choose_random_entry_no_guards(void *arg)
+test_node_preferred_orport(void *arg)
{
- const node_t *chosen_entry = NULL;
-
- (void) arg;
-
- MOCK(get_options, mock_get_options);
+ (void)arg;
+ tor_addr_t ipv4_addr;
+ const uint16_t ipv4_port = 4444;
+ tor_addr_t ipv6_addr;
+ const uint16_t ipv6_port = 6666;
+ routerinfo_t node_ri;
+ node_t node;
+ tor_addr_port_t ap;
- /* Check that we get a guard if it passes preferred
- * address settings */
+ /* Setup options */
memset(&mocked_options, 0, sizeof(mocked_options));
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientPreferIPv6ORPort = 0;
-
- /* Try to pick an entry even though none of our routers are guards. */
- chosen_entry = choose_random_entry(NULL);
-
- /* Unintuitively, we actually pick a random node as our entry,
- because router_choose_random_node() relaxes its constraints if it
- can't find a proper entry guard. */
- tt_assert(chosen_entry);
+ /* We don't test ClientPreferIPv6ORPort here, because it's used in
+ * nodelist_set_consensus to setup node.ipv6_preferred, which we set
+ * directly. */
+ MOCK(get_options, mock_get_options);
- /* And with the other IP version active */
- mocked_options.ClientUseIPv6 = 1;
- chosen_entry = choose_random_entry(NULL);
- tt_assert(chosen_entry);
+ /* Setup IP addresses */
+ tor_addr_parse(&ipv4_addr, TEST_IPV4_ADDR);
+ tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR);
- /* And with the preference on auto */
- mocked_options.ClientPreferIPv6ORPort = -1;
- chosen_entry = choose_random_entry(NULL);
- tt_assert(chosen_entry);
+ /* Setup node_ri */
+ memset(&node_ri, 0, sizeof(node_ri));
+ node_ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
+ node_ri.or_port = ipv4_port;
+ tor_addr_copy(&node_ri.ipv6_addr, &ipv6_addr);
+ node_ri.ipv6_orport = ipv6_port;
- /* Check that we don't get a guard if it doesn't pass mandatory address
- * settings */
- memset(&mocked_options, 0, sizeof(mocked_options));
- mocked_options.ClientUseIPv4 = 0;
- mocked_options.ClientPreferIPv6ORPort = 0;
+ /* Setup node */
+ memset(&node, 0, sizeof(node));
+ node.ri = &node_ri;
- chosen_entry = choose_random_entry(NULL);
+ /* Check the preferred address is IPv4 if we're only using IPv4, regardless
+ * of whether we prefer it or not */
+ mocked_options.ClientUseIPv4 = 1;
+ mocked_options.ClientUseIPv6 = 0;
+ node.ipv6_preferred = 0;
+ node_get_pref_orport(&node, &ap);
+ tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
+ tt_assert(ap.port == ipv4_port);
- /* If we don't allow IPv4 at all, we don't get a guard*/
- tt_assert(!chosen_entry);
+ node.ipv6_preferred = 1;
+ node_get_pref_orport(&node, &ap);
+ tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
+ tt_assert(ap.port == ipv4_port);
- /* Check that we get a guard if it passes allowed but not preferred address
- * settings */
- memset(&mocked_options, 0, sizeof(mocked_options));
+ /* Check the preferred address is IPv4 if we're using IPv4 and IPv6, but
+ * don't prefer the IPv6 address */
mocked_options.ClientUseIPv4 = 1;
mocked_options.ClientUseIPv6 = 1;
- mocked_options.ClientPreferIPv6ORPort = 1;
-
- chosen_entry = choose_random_entry(NULL);
- tt_assert(chosen_entry);
+ node.ipv6_preferred = 0;
+ node_get_pref_orport(&node, &ap);
+ tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
+ tt_assert(ap.port == ipv4_port);
- /* Check that we get a guard if it passes preferred address settings when
- * they're auto */
- memset(&mocked_options, 0, sizeof(mocked_options));
+ /* Check the preferred address is IPv6 if we prefer it and
+ * ClientUseIPv6 is 1, regardless of ClientUseIPv4 */
mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientPreferIPv6ORPort = -1;
+ mocked_options.ClientUseIPv6 = 1;
+ node.ipv6_preferred = 1;
+ node_get_pref_orport(&node, &ap);
+ tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
+ tt_assert(ap.port == ipv6_port);
- chosen_entry = choose_random_entry(NULL);
- tt_assert(chosen_entry);
+ mocked_options.ClientUseIPv4 = 0;
+ node_get_pref_orport(&node, &ap);
+ tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
+ tt_assert(ap.port == ipv6_port);
- /* And with IPv6 active */
+ /* Check the preferred address is IPv6 if we don't prefer it, but
+ * ClientUseIPv4 is 0 */
+ mocked_options.ClientUseIPv4 = 0;
mocked_options.ClientUseIPv6 = 1;
-
- chosen_entry = choose_random_entry(NULL);
- tt_assert(chosen_entry);
+ node.ipv6_preferred = fascist_firewall_prefer_ipv6_orport(&mocked_options);
+ node_get_pref_orport(&node, &ap);
+ tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
+ tt_assert(ap.port == ipv6_port);
done:
- memset(&mocked_options, 0, sizeof(mocked_options));
UNMOCK(get_options);
}
-/** Test choose_random_entry() with only one of our routers being a
- guard node. */
static void
-test_choose_random_entry_one_possible_guard(void *arg)
+test_entry_guard_describe(void *arg)
{
- const node_t *chosen_entry = NULL;
- node_t *the_guard = NULL;
- smartlist_t *our_nodelist = NULL;
+ (void)arg;
+ entry_guard_t g;
+ memset(&g, 0, sizeof(g));
+ strlcpy(g.nickname, "okefenokee", sizeof(g.nickname));
+ memcpy(g.identity, "theforestprimeval---", DIGEST_LEN);
- (void) arg;
+ tt_str_op(entry_guard_describe(&g), OP_EQ,
+ "okefenokee ($746865666F726573747072696D6576616C2D2D2D)");
- MOCK(get_options, mock_get_options);
-
- /* Set one of the nodes to be a guard. */
- our_nodelist = nodelist_get_list();
- the_guard = smartlist_get(our_nodelist, 4); /* chosen by fair dice roll */
- the_guard->is_possible_guard = 1;
+ done:
+ ;
+}
- /* Check that we get the guard if it passes preferred
- * address settings */
- memset(&mocked_options, 0, sizeof(mocked_options));
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientPreferIPv6ORPort = 0;
+static void
+test_entry_guard_randomize_time(void *arg)
+{
+ const time_t now = 1479153573;
+ const int delay = 86400;
+ const int N = 1000;
+ (void)arg;
- /* Pick an entry. Make sure we pick the node we marked as guard. */
- chosen_entry = choose_random_entry(NULL);
- tt_ptr_op(chosen_entry, OP_EQ, the_guard);
+ time_t t;
+ int i;
+ for (i = 0; i < N; ++i) {
+ t = randomize_time(now, delay);
+ tt_int_op(t, OP_LE, now);
+ tt_int_op(t, OP_GE, now-delay);
+ }
- /* And with the other IP version active */
- mocked_options.ClientUseIPv6 = 1;
- chosen_entry = choose_random_entry(NULL);
- tt_ptr_op(chosen_entry, OP_EQ, the_guard);
+ /* now try the corner cases */
+ for (i = 0; i < N; ++i) {
+ t = randomize_time(100, delay);
+ tt_int_op(t, OP_GE, 1);
+ tt_int_op(t, OP_LE, 100);
- /* And with the preference on auto */
- mocked_options.ClientPreferIPv6ORPort = -1;
- chosen_entry = choose_random_entry(NULL);
- tt_ptr_op(chosen_entry, OP_EQ, the_guard);
+ t = randomize_time(0, delay);
+ tt_int_op(t, OP_EQ, 1);
+ }
- /* Check that we don't get a guard if it doesn't pass mandatory address
- * settings */
- memset(&mocked_options, 0, sizeof(mocked_options));
- mocked_options.ClientUseIPv4 = 0;
- mocked_options.ClientPreferIPv6ORPort = 0;
+ done:
+ ;
+}
- chosen_entry = choose_random_entry(NULL);
+static void
+test_entry_guard_encode_for_state_minimal(void *arg)
+{
+ (void) arg;
+ entry_guard_t *eg = tor_malloc_zero(sizeof(entry_guard_t));
- /* If we don't allow IPv4 at all, we don't get a guard*/
- tt_assert(!chosen_entry);
+ eg->selection_name = tor_strdup("wubwub");
+ memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN);
+ eg->sampled_on_date = 1479081600;
+ eg->confirmed_idx = -1;
- /* Check that we get a node if it passes allowed but not preferred
- * address settings */
- memset(&mocked_options, 0, sizeof(mocked_options));
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientUseIPv6 = 1;
- mocked_options.ClientPreferIPv6ORPort = 1;
+ char *s = NULL;
+ s = entry_guard_encode_for_state(eg);
- chosen_entry = choose_random_entry(NULL);
+ tt_str_op(s, OP_EQ,
+ "in=wubwub "
+ "rsa_id=706C75727079666C75727079736C75727079646F "
+ "sampled_on=2016-11-14T00:00:00 "
+ "listed=0");
- /* We disable the guard check and the preferred address check at the same
- * time, so we can't be sure we get the guard */
- tt_assert(chosen_entry);
+ done:
+ entry_guard_free(eg);
+ tor_free(s);
+}
- /* Check that we get a node if it is allowed but not preferred when settings
- * are auto */
- memset(&mocked_options, 0, sizeof(mocked_options));
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientPreferIPv6ORPort = -1;
+static void
+test_entry_guard_encode_for_state_maximal(void *arg)
+{
+ (void) arg;
+ entry_guard_t *eg = tor_malloc_zero(sizeof(entry_guard_t));
+
+ strlcpy(eg->nickname, "Fred", sizeof(eg->nickname));
+ eg->selection_name = tor_strdup("default");
+ memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN);
+ eg->bridge_addr = tor_malloc_zero(sizeof(tor_addr_port_t));
+ tor_addr_from_ipv4h(&eg->bridge_addr->addr, 0x08080404);
+ eg->bridge_addr->port = 9999;
+ eg->sampled_on_date = 1479081600;
+ eg->sampled_by_version = tor_strdup("1.2.3");
+ eg->unlisted_since_date = 1479081645;
+ eg->currently_listed = 1;
+ eg->confirmed_on_date = 1479081690;
+ eg->confirmed_idx = 333;
+ eg->extra_state_fields = tor_strdup("and the green grass grew all around");
+
+ char *s = NULL;
+ s = entry_guard_encode_for_state(eg);
+
+ tt_str_op(s, OP_EQ,
+ "in=default "
+ "rsa_id=706C75727079666C75727079736C75727079646F "
+ "bridge_addr=8.8.4.4:9999 "
+ "nickname=Fred "
+ "sampled_on=2016-11-14T00:00:00 "
+ "sampled_by=1.2.3 "
+ "unlisted_since=2016-11-14T00:00:45 "
+ "listed=1 "
+ "confirmed_on=2016-11-14T00:01:30 "
+ "confirmed_idx=333 "
+ "and the green grass grew all around");
- chosen_entry = choose_random_entry(NULL);
+ done:
+ entry_guard_free(eg);
+ tor_free(s);
+}
- /* We disable the guard check and the preferred address check at the same
- * time, so we can't be sure we get the guard */
- tt_assert(chosen_entry);
+static void
+test_entry_guard_parse_from_state_minimal(void *arg)
+{
+ (void)arg;
+ char *mem_op_hex_tmp = NULL;
+ entry_guard_t *eg = NULL;
+ time_t t = approx_time();
+
+ eg = entry_guard_parse_from_state(
+ "in=default_plus "
+ "rsa_id=596f75206d6179206e656564206120686f626279");
+ tt_assert(eg);
+
+ tt_str_op(eg->selection_name, OP_EQ, "default_plus");
+ test_mem_op_hex(eg->identity, OP_EQ,
+ "596f75206d6179206e656564206120686f626279");
+ tt_str_op(eg->nickname, OP_EQ, "$596F75206D6179206E656564206120686F626279");
+ tt_ptr_op(eg->bridge_addr, OP_EQ, NULL);
+ tt_i64_op(eg->sampled_on_date, OP_GE, t);
+ tt_i64_op(eg->sampled_on_date, OP_LE, t+86400);
+ tt_i64_op(eg->unlisted_since_date, OP_EQ, 0);
+ tt_ptr_op(eg->sampled_by_version, OP_EQ, NULL);
+ tt_int_op(eg->currently_listed, OP_EQ, 0);
+ tt_i64_op(eg->confirmed_on_date, OP_EQ, 0);
+ tt_int_op(eg->confirmed_idx, OP_EQ, -1);
+
+ tt_int_op(eg->last_tried_to_connect, OP_EQ, 0);
+ tt_int_op(eg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
- /* and with IPv6 active */
- mocked_options.ClientUseIPv6 = 1;
+ done:
+ entry_guard_free(eg);
+ tor_free(mem_op_hex_tmp);
+}
- chosen_entry = choose_random_entry(NULL);
- tt_assert(chosen_entry);
+static void
+test_entry_guard_parse_from_state_maximal(void *arg)
+{
+ (void)arg;
+ char *mem_op_hex_tmp = NULL;
+ entry_guard_t *eg = NULL;
+
+ eg = entry_guard_parse_from_state(
+ "in=fred "
+ "rsa_id=706C75727079666C75727079736C75727079646F "
+ "bridge_addr=[1::3]:9999 "
+ "nickname=Fred "
+ "sampled_on=2016-11-14T00:00:00 "
+ "sampled_by=1.2.3 "
+ "unlisted_since=2016-11-14T00:00:45 "
+ "listed=1 "
+ "confirmed_on=2016-11-14T00:01:30 "
+ "confirmed_idx=333 "
+ "and the green grass grew all around "
+ "rsa_id=all,around");
+ tt_assert(eg);
+
+ test_mem_op_hex(eg->identity, OP_EQ,
+ "706C75727079666C75727079736C75727079646F");
+ tt_str_op(fmt_addr(&eg->bridge_addr->addr), OP_EQ, "1::3");
+ tt_int_op(eg->bridge_addr->port, OP_EQ, 9999);
+ tt_str_op(eg->nickname, OP_EQ, "Fred");
+ tt_i64_op(eg->sampled_on_date, OP_EQ, 1479081600);
+ tt_i64_op(eg->unlisted_since_date, OP_EQ, 1479081645);
+ tt_str_op(eg->sampled_by_version, OP_EQ, "1.2.3");
+ tt_int_op(eg->currently_listed, OP_EQ, 1);
+ tt_i64_op(eg->confirmed_on_date, OP_EQ, 1479081690);
+ tt_int_op(eg->confirmed_idx, OP_EQ, 333);
+ tt_str_op(eg->extra_state_fields, OP_EQ,
+ "and the green grass grew all around rsa_id=all,around");
+
+ tt_int_op(eg->last_tried_to_connect, OP_EQ, 0);
+ tt_int_op(eg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
done:
- memset(&mocked_options, 0, sizeof(mocked_options));
- UNMOCK(get_options);
+ entry_guard_free(eg);
+ tor_free(mem_op_hex_tmp);
}
-/** Helper to conduct tests for populate_live_entry_guards().
+static void
+test_entry_guard_parse_from_state_failure(void *arg)
+{
+ (void)arg;
+ entry_guard_t *eg = NULL;
+
+ /* no selection */
+ eg = entry_guard_parse_from_state(
+ "rsa_id=596f75206d6179206e656564206120686f626270");
+ tt_assert(! eg);
+
+ /* no RSA ID. */
+ eg = entry_guard_parse_from_state("in=default nickname=Fred");
+ tt_assert(! eg);
+
+ /* Bad RSA ID: bad character. */
+ eg = entry_guard_parse_from_state(
+ "in=default "
+ "rsa_id=596f75206d6179206e656564206120686f62627q");
+ tt_assert(! eg);
+
+ /* Bad RSA ID: too long.*/
+ eg = entry_guard_parse_from_state(
+ "in=default "
+ "rsa_id=596f75206d6179206e656564206120686f6262703");
+ tt_assert(! eg);
+
+ /* Bad RSA ID: too short.*/
+ eg = entry_guard_parse_from_state(
+ "in=default "
+ "rsa_id=596f75206d6179206e65656420612");
+ tt_assert(! eg);
- This test adds some entry guards to our list, and then tests
- populate_live_entry_guards() to mke sure it filters them correctly.
+ done:
+ entry_guard_free(eg);
+}
- <b>num_needed</b> is the number of guard nodes we support. It's
- configurable to make sure we function properly with 1 or 3 guard
- nodes configured.
-*/
static void
-populate_live_entry_guards_test_helper(int num_needed)
+test_entry_guard_parse_from_state_partial_failure(void *arg)
+{
+ (void)arg;
+ char *mem_op_hex_tmp = NULL;
+ entry_guard_t *eg = NULL;
+ time_t t = approx_time();
+
+ eg = entry_guard_parse_from_state(
+ "in=default "
+ "rsa_id=706C75727079666C75727079736C75727079646F "
+ "bridge_addr=1.2.3.3.4:5 "
+ "nickname=FredIsANodeWithAStrangeNicknameThatIsTooLong "
+ "sampled_on=2016-11-14T00:00:99 "
+ "sampled_by=1.2.3 stuff in the middle "
+ "unlisted_since=2016-xx-14T00:00:45 "
+ "listed=0 "
+ "confirmed_on=2016-11-14T00:01:30zz "
+ "confirmed_idx=idx "
+ "and the green grass grew all around "
+ "rsa_id=all,around");
+ tt_assert(eg);
+
+ test_mem_op_hex(eg->identity, OP_EQ,
+ "706C75727079666C75727079736C75727079646F");
+ tt_str_op(eg->nickname, OP_EQ, "FredIsANodeWithAStrangeNicknameThatIsTooL");
+ tt_ptr_op(eg->bridge_addr, OP_EQ, NULL);
+ tt_i64_op(eg->sampled_on_date, OP_EQ, t);
+ tt_i64_op(eg->unlisted_since_date, OP_EQ, 0);
+ tt_str_op(eg->sampled_by_version, OP_EQ, "1.2.3");
+ tt_int_op(eg->currently_listed, OP_EQ, 0);
+ tt_i64_op(eg->confirmed_on_date, OP_EQ, 0);
+ tt_int_op(eg->confirmed_idx, OP_EQ, -1);
+ tt_str_op(eg->extra_state_fields, OP_EQ,
+ "stuff in the middle and the green grass grew all around "
+ "rsa_id=all,around");
+
+ tt_int_op(eg->last_tried_to_connect, OP_EQ, 0);
+ tt_int_op(eg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+
+ done:
+ entry_guard_free(eg);
+ tor_free(mem_op_hex_tmp);
+}
+
+static int
+mock_entry_guard_is_listed(guard_selection_t *gs, const entry_guard_t *guard)
{
- smartlist_t *our_nodelist = NULL;
- smartlist_t *live_entry_guards = smartlist_new();
- const smartlist_t *all_entry_guards = get_entry_guards();
- or_options_t *options = get_options_mutable();
- int retval;
+ (void)gs;
+ (void)guard;
+ return 1;
+}
- /* Set NumEntryGuards to the provided number. */
- options->NumEntryGuards = num_needed;
- tt_int_op(num_needed, OP_EQ, decide_num_guards(options, 0));
+static void
+test_entry_guard_parse_from_state_full(void *arg)
+{
+ (void)arg;
+ /* Here's a state I made while testing. The identities and locations for
+ * the bridges are redacted. */
+ const char STATE[] =
+ "Guard in=default rsa_id=214F44BD5B638E8C817D47FF7C97397790BF0345 "
+ "nickname=TotallyNinja sampled_on=2016-11-12T19:32:49 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1\n"
+ "Guard in=default rsa_id=052900AB0EA3ED54BAB84AE8A99E74E8693CE2B2 "
+ "nickname=5OfNovember sampled_on=2016-11-20T04:32:05 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1 confirmed_on=2016-11-22T08:13:28 confirmed_idx=0 "
+ "pb_circ_attempts=4.000000 pb_circ_successes=2.000000 "
+ "pb_successful_circuits_closed=2.000000\n"
+ "Guard in=default rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A "
+ "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1 confirmed_on=2016-11-24T08:45:30 confirmed_idx=4 "
+ "pb_circ_attempts=5.000000 pb_circ_successes=5.000000 "
+ "pb_successful_circuits_closed=5.000000\n"
+ "Guard in=wobblesome rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A "
+ "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1\n"
+ "Guard in=default rsa_id=E9025AD60D86875D5F11548D536CC6AF60F0EF5E "
+ "nickname=maibrunn sampled_on=2016-11-25T22:36:38 "
+ "sampled_by=0.3.0.0-alpha-dev listed=1\n"
+ "Guard in=default rsa_id=DCD30B90BA3A792DA75DC54A327EF353FB84C38E "
+ "nickname=Unnamed sampled_on=2016-11-25T14:34:00 "
+ "sampled_by=0.3.0.0-alpha-dev listed=1\n"
+ "Guard in=bridges rsa_id=8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E "
+ "bridge_addr=24.1.1.1:443 sampled_on=2016-11-25T06:44:14 "
+ "sampled_by=0.3.0.0-alpha-dev listed=1 "
+ "confirmed_on=2016-11-29T10:36:06 confirmed_idx=0 "
+ "pb_circ_attempts=8.000000 pb_circ_successes=8.000000 "
+ "pb_successful_circuits_closed=13.000000\n"
+ "Guard in=bridges rsa_id=5800000000000000000000000000000000000000 "
+ "bridge_addr=37.218.246.143:28366 "
+ "sampled_on=2016-11-18T15:07:34 sampled_by=0.3.0.0-alpha-dev listed=1\n";
+
+ config_line_t *lines = NULL;
+ or_state_t *state = tor_malloc_zero(sizeof(or_state_t));
+ int r = config_get_lines(STATE, &lines, 0);
+ char *msg = NULL;
+ smartlist_t *text = smartlist_new();
+ char *joined = NULL;
- /* The global entry guards smartlist should be empty now. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 0);
+ // So nodes aren't expired. This is Tue, 13 Dec 2016 09:37:14 GMT
+ update_approx_time(1481621834);
- /* Walk the nodelist and add all nodes as entry guards. */
- our_nodelist = nodelist_get_list();
- tt_int_op(smartlist_len(our_nodelist), OP_EQ, HELPER_NUMBER_OF_DESCRIPTORS);
+ MOCK(entry_guard_is_listed, mock_entry_guard_is_listed);
- SMARTLIST_FOREACH_BEGIN(our_nodelist, const node_t *, node) {
- const node_t *node_tmp;
- node_tmp = add_an_entry_guard(node, 0, 1, 0, 0);
- tt_assert(node_tmp);
- } SMARTLIST_FOREACH_END(node);
+ dummy_state = state;
+ MOCK(get_or_state,
+ get_or_state_replacement);
- /* Make sure the nodes were added as entry guards. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ,
- HELPER_NUMBER_OF_DESCRIPTORS);
+ tt_assert(r == 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);
+ guard_selection_t *gs_br =
+ get_guard_selection_by_name("bridges", GS_TYPE_BRIDGE, 0);
+ tt_assert(!gs_br);
+
+ r = entry_guards_parse_state(state, 1, &msg);
+ tt_assert(r == 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);
+ guard_selection_t *gs_wb =
+ get_guard_selection_by_name("wobblesome", GS_TYPE_NORMAL, 0);
+
+ tt_assert(gs_br);
+ tt_assert(gs_df);
+ tt_assert(gs_wb);
+
+ tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 5);
+ tt_int_op(smartlist_len(gs_br->sampled_entry_guards), OP_EQ, 2);
+ tt_int_op(smartlist_len(gs_wb->sampled_entry_guards), OP_EQ, 1);
+
+ /* Try again; make sure it doesn't double-add the guards. */
+ r = entry_guards_parse_state(state, 1, &msg);
+ tt_assert(r == 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);
+ tt_assert(gs_df);
+ tt_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 5);
+ tt_int_op(smartlist_len(gs_br->sampled_entry_guards), OP_EQ, 2);
+
+ /* Re-encode; it should be the same... almost. */
+ {
+ /* (Make a guard nonpersistent first) */
+ entry_guard_t *g = smartlist_get(gs_df->sampled_entry_guards, 0);
+ g->is_persistent = 0;
+ }
+ config_free_lines(lines);
+ lines = state->Guard = NULL; // to prevent double-free.
+ entry_guards_update_state(state);
+ tt_assert(state->Guard);
+ lines = state->Guard;
+
+ config_line_t *ln;
+ for (ln = lines; ln; ln = ln->next) {
+ smartlist_add_asprintf(text, "%s %s\n",ln->key, ln->value);
+ }
+ joined = smartlist_join_strings(text, "", 0, NULL);
+ tt_str_op(joined, OP_EQ,
+ "Guard in=default rsa_id=052900AB0EA3ED54BAB84AE8A99E74E8693CE2B2 "
+ "nickname=5OfNovember sampled_on=2016-11-20T04:32:05 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1 confirmed_on=2016-11-22T08:13:28 confirmed_idx=0 "
+ "pb_circ_attempts=4.000000 pb_circ_successes=2.000000 "
+ "pb_successful_circuits_closed=2.000000\n"
+ "Guard in=default rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A "
+ "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1 confirmed_on=2016-11-24T08:45:30 confirmed_idx=1 "
+ "pb_circ_attempts=5.000000 pb_circ_successes=5.000000 "
+ "pb_successful_circuits_closed=5.000000\n"
+ "Guard in=default rsa_id=E9025AD60D86875D5F11548D536CC6AF60F0EF5E "
+ "nickname=maibrunn sampled_on=2016-11-25T22:36:38 "
+ "sampled_by=0.3.0.0-alpha-dev listed=1\n"
+ "Guard in=default rsa_id=DCD30B90BA3A792DA75DC54A327EF353FB84C38E "
+ "nickname=Unnamed sampled_on=2016-11-25T14:34:00 "
+ "sampled_by=0.3.0.0-alpha-dev listed=1\n"
+ "Guard in=wobblesome rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A "
+ "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1\n"
+ "Guard in=bridges rsa_id=8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E "
+ "bridge_addr=24.1.1.1:443 sampled_on=2016-11-25T06:44:14 "
+ "sampled_by=0.3.0.0-alpha-dev listed=1 "
+ "confirmed_on=2016-11-29T10:36:06 confirmed_idx=0 "
+ "pb_circ_attempts=8.000000 pb_circ_successes=8.000000 "
+ "pb_successful_circuits_closed=13.000000\n"
+ "Guard in=bridges rsa_id=5800000000000000000000000000000000000000 "
+ "bridge_addr=37.218.246.143:28366 "
+ "sampled_on=2016-11-18T15:07:34 sampled_by=0.3.0.0-alpha-dev listed=1\n");
- /* Ensure that all the possible entry guards are enough to satisfy us. */
- tt_int_op(smartlist_len(all_entry_guards), OP_GE, num_needed);
+ done:
+ config_free_lines(lines);
+ tor_free(state);
+ tor_free(msg);
+ UNMOCK(get_or_state);
+ UNMOCK(entry_guard_is_listed);
+ SMARTLIST_FOREACH(text, char *, cp, tor_free(cp));
+ smartlist_free(text);
+ tor_free(joined);
+}
- /* Walk the entry guard list for some sanity checking */
- SMARTLIST_FOREACH_BEGIN(all_entry_guards, const entry_guard_t *, entry) {
- /* Since we called add_an_entry_guard() with 'for_discovery' being
- False, all guards should have made_contact enabled. */
- tt_int_op(entry->made_contact, OP_EQ, 1);
+static void
+test_entry_guard_parse_from_state_broken(void *arg)
+{
+ (void)arg;
+ /* Here's a variation on the previous state. Every line but the first is
+ * busted somehow. */
+ const char STATE[] =
+ /* Okay. */
+ "Guard in=default rsa_id=214F44BD5B638E8C817D47FF7C97397790BF0345 "
+ "nickname=TotallyNinja sampled_on=2016-11-12T19:32:49 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1\n"
+ /* No selection listed. */
+ "Guard rsa_id=052900AB0EA3ED54BAB84AE8A99E74E8693CE2B2 "
+ "nickname=5OfNovember sampled_on=2016-11-20T04:32:05 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1 confirmed_on=2016-11-22T08:13:28 confirmed_idx=0 "
+ "pb_circ_attempts=4.000000 pb_circ_successes=2.000000 "
+ "pb_successful_circuits_closed=2.000000\n"
+ /* Selection is "legacy"!! */
+ "Guard in=legacy rsa_id=7B700C0C207EBD0002E00F499BE265519AC3C25A "
+ "nickname=dc6jgk11 sampled_on=2016-11-28T11:50:13 "
+ "sampled_by=0.3.0.0-alpha-dev "
+ "listed=1 confirmed_on=2016-11-24T08:45:30 confirmed_idx=4 "
+ "pb_circ_attempts=5.000000 pb_circ_successes=5.000000 "
+ "pb_successful_circuits_closed=5.000000\n";
+
+ config_line_t *lines = NULL;
+ or_state_t *state = tor_malloc_zero(sizeof(or_state_t));
+ int r = config_get_lines(STATE, &lines, 0);
+ char *msg = NULL;
- } SMARTLIST_FOREACH_END(entry);
+ dummy_state = state;
+ MOCK(get_or_state,
+ get_or_state_replacement);
- /* First, try to get some fast guards. This should fail. */
- retval = populate_live_entry_guards(live_entry_guards,
- all_entry_guards,
- NULL,
- NO_DIRINFO, /* Don't care about DIRINFO*/
- 0, 0,
- 1); /* We want fast guard! */
- tt_int_op(retval, OP_EQ, 0);
- tt_int_op(smartlist_len(live_entry_guards), OP_EQ, 0);
+ tt_assert(r == 0);
+ tt_assert(lines);
- /* Now try to get some stable guards. This should fail too. */
- retval = populate_live_entry_guards(live_entry_guards,
- all_entry_guards,
- NULL,
- NO_DIRINFO,
- 0,
- 1, /* We want stable guard! */
- 0);
- tt_int_op(retval, OP_EQ, 0);
- tt_int_op(smartlist_len(live_entry_guards), OP_EQ, 0);
+ state->Guard = lines;
- /* Now try to get any guard we can find. This should succeed. */
- retval = populate_live_entry_guards(live_entry_guards,
- all_entry_guards,
- NULL,
- NO_DIRINFO,
- 0, 0, 0); /* No restrictions! */
+ /* First, no-set case. we should get an error. */
+ r = entry_guards_parse_state(state, 0, &msg);
+ tt_int_op(r, OP_LT, 0);
+ tt_ptr_op(msg, OP_NE, NULL);
+ /* 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);
+ tor_free(msg);
- /* Since we had more than enough guards in 'all_entry_guards', we
- should have added 'num_needed' of them to live_entry_guards.
- 'retval' should be 1 since we now have enough live entry guards
- to pick one. */
- tt_int_op(retval, OP_EQ, 1);
- tt_int_op(smartlist_len(live_entry_guards), OP_EQ, num_needed);
+ /* Now see about the set case (which shouldn't happen IRL) */
+ r = entry_guards_parse_state(state, 1, &msg);
+ 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_int_op(smartlist_len(gs_df->sampled_entry_guards), OP_EQ, 1);
done:
- smartlist_free(live_entry_guards);
+ config_free_lines(lines);
+ tor_free(state);
+ tor_free(msg);
+ UNMOCK(get_or_state);
}
-/* Test populate_live_entry_guards() for 1 guard node. */
static void
-test_populate_live_entry_guards_1guard(void *arg)
+test_entry_guard_get_guard_selection_by_name(void *arg)
{
- (void) arg;
+ (void)arg;
+ guard_selection_t *gs1, *gs2, *gs3;
+
+ gs1 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 0);
+ tt_assert(gs1 == NULL);
+ gs1 = get_guard_selection_by_name("unlikely", GS_TYPE_NORMAL, 1);
+ tt_assert(gs1 != 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);
+ gs2 = get_guard_selection_by_name("implausible", GS_TYPE_NORMAL, 1);
+ tt_assert(gs2 != 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);
+ gs3 = get_guard_selection_by_name("default", GS_TYPE_NORMAL, 1);
+ tt_assert(gs3 != NULL);
+ tt_assert(gs3 != gs2);
+ tt_assert(gs3 != gs1);
+ tt_assert(gs3 == get_guard_selection_info());
- populate_live_entry_guards_test_helper(1);
+ done:
+ entry_guards_free_all();
}
-/* Test populate_live_entry_guards() for 3 guard nodes. */
static void
-test_populate_live_entry_guards_3guards(void *arg)
+test_entry_guard_choose_selection_initial(void *arg)
{
- (void) arg;
+ /* Tests for picking our initial guard selection (based on having had
+ * no previous selection */
+ (void)arg;
+ guard_selection_type_t type = GS_TYPE_INFER;
+ const char *name = choose_guard_selection(get_options(),
+ dummy_consensus, NULL, &type);
+ tt_str_op(name, OP_EQ, "default");
+ tt_int_op(type, OP_EQ, GS_TYPE_NORMAL);
+
+ /* If we're using bridges, we get the bridge selection. */
+ get_options_mutable()->UseBridges = 1;
+ name = choose_guard_selection(get_options(),
+ dummy_consensus, NULL, &type);
+ tt_str_op(name, OP_EQ, "bridges");
+ tt_int_op(type, OP_EQ, GS_TYPE_BRIDGE);
+ get_options_mutable()->UseBridges = 0;
+
+ /* If we discard >99% of our guards, though, we should be in the restricted
+ * set. */
+ tt_assert(get_options_mutable()->EntryNodes == NULL);
+ get_options_mutable()->EntryNodes = routerset_new();
+ routerset_parse(get_options_mutable()->EntryNodes, "1.0.0.0/8", "foo");
+ name = choose_guard_selection(get_options(),
+ dummy_consensus, NULL, &type);
+ tt_str_op(name, OP_EQ, "restricted");
+ tt_int_op(type, OP_EQ, GS_TYPE_RESTRICTED);
- populate_live_entry_guards_test_helper(3);
+ done:
+ ;
}
-/** Append some EntryGuard lines to the Tor state at <b>state</b>.
-
- <b>entry_guard_lines</b> is a smartlist containing 2-tuple
- smartlists that carry the key and values of the statefile.
- As an example:
- entry_guard_lines =
- (("EntryGuard", "name 67E72FF33D7D41BF11C569646A0A7B4B188340DF DirCache"),
- ("EntryGuardDownSince", "2014-06-07 16:02:46 2014-06-07 16:02:46"))
-*/
static void
-state_insert_entry_guard_helper(or_state_t *state,
- smartlist_t *entry_guard_lines)
+test_entry_guard_add_single_guard(void *arg)
{
- config_line_t **next, *line;
-
- next = &state->EntryGuards;
- *next = NULL;
-
- /* Loop over all the state lines in the smartlist */
- SMARTLIST_FOREACH_BEGIN(entry_guard_lines, const smartlist_t *,state_lines) {
- /* Get key and value for each line */
- const char *state_key = smartlist_get(state_lines, 0);
- const char *state_value = smartlist_get(state_lines, 1);
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+
+ /* 1: Add a single guard to the sample. */
+ node_t *n1 = smartlist_get(big_fake_net_nodes, 0);
+ time_t now = approx_time();
+ tt_assert(n1->is_possible_guard == 1);
+ entry_guard_t *g1 = entry_guard_add_to_sample(gs, n1);
+ tt_assert(g1);
+
+ /* Make sure its fields look right. */
+ tt_mem_op(n1->identity, OP_EQ, g1->identity, DIGEST_LEN);
+ 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_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);
+
+ /* Make sure it got added. */
+ tt_int_op(1, OP_EQ, smartlist_len(gs->sampled_entry_guards));
+ tt_ptr_op(g1, OP_EQ, smartlist_get(gs->sampled_entry_guards, 0));
+ tt_ptr_op(g1, OP_EQ, get_sampled_guard_with_id(gs, (uint8_t*)n1->identity));
+ const uint8_t bad_id[20] = {0};
+ tt_ptr_op(NULL, OP_EQ, get_sampled_guard_with_id(gs, bad_id));
- *next = line = tor_malloc_zero(sizeof(config_line_t));
- line->key = tor_strdup(state_key);
- tor_asprintf(&line->value, "%s", state_value);
- next = &(line->next);
- } SMARTLIST_FOREACH_END(state_lines);
+ done:
+ guard_selection_free(gs);
}
-/** Free memory occupied by <b>entry_guard_lines</b>. */
static void
-state_lines_free(smartlist_t *entry_guard_lines)
+test_entry_guard_node_filter(void *arg)
{
- SMARTLIST_FOREACH_BEGIN(entry_guard_lines, smartlist_t *, state_lines) {
- char *state_key = smartlist_get(state_lines, 0);
- char *state_value = smartlist_get(state_lines, 1);
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ bridge_line_t *bl = NULL;
+
+ /* Initialize a bunch of node objects that are all guards. */
+#define NUM 7
+ node_t *n[NUM];
+ entry_guard_t *g[NUM];
+ int i;
+ for (i=0; i < NUM; ++i) {
+ n[i] = smartlist_get(big_fake_net_nodes, i*2); // even ones are guards.
+ 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_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, NUM);
- tor_free(state_key);
- tor_free(state_value);
- smartlist_free(state_lines);
- } SMARTLIST_FOREACH_END(state_lines);
+ /* 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_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, NUM);
- smartlist_free(entry_guard_lines);
-}
+ /* Now start doing things to make the guards get filtered out, 1 by 1. */
-/* Tests entry_guards_parse_state(). It creates a fake Tor state with
- a saved entry guard and makes sure that Tor can parse it and
- creates the right entry node out of it.
-*/
-static void
-test_entry_guards_parse_state_simple(void *arg)
-{
- or_state_t *state = or_state_new();
- const smartlist_t *all_entry_guards = get_entry_guards();
- smartlist_t *entry_state_lines = smartlist_new();
- char *msg = NULL;
- int retval;
+ /* 0: Not listed. */
+ g[0]->currently_listed = 0;
- /* Details of our fake guard node */
- const char *nickname = "hagbard";
- const char *fpr = "B29D536DD1752D542E1FBB3C9CE4449D51298212";
- const char *tor_version = "0.2.5.3-alpha-dev";
- const char *added_at = get_yesterday_date_str();
- const char *unlisted_since = "2014-06-08 16:16:50";
+ /* 1: path bias says this guard is maybe eeeevil. */
+ g[1]->pb.path_bias_disabled = 1;
- (void) arg;
+ /* 2: Unreachable address. */
+ n[2]->rs->addr = 0;
- /* The global entry guards smartlist should be empty now. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 0);
+ /* 3: ExcludeNodes */
+ n[3]->rs->addr = 0x90902020;
+ routerset_free(get_options_mutable()->ExcludeNodes);
+ get_options_mutable()->ExcludeNodes = routerset_new();
+ routerset_parse(get_options_mutable()->ExcludeNodes, "144.144.0.0/16", "");
- { /* Prepare the state entry */
+ /* 4: Bridge. */
+ sweep_bridge_list();
+ bl = tor_malloc_zero(sizeof(bridge_line_t));
+ tor_addr_from_ipv4h(&bl->addr, n[4]->rs->addr);
+ bl->port = n[4]->rs->or_port;
+ memcpy(bl->digest, n[4]->identity, 20);
+ bridge_add_from_config(bl);
+ bl = NULL; // prevent free.
- /* Prepare the smartlist to hold the key/value of each line */
- smartlist_t *state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuard");
- smartlist_add_asprintf(state_line, "%s %s %s", nickname, fpr, "DirCache");
- smartlist_add(entry_state_lines, state_line);
+ /* 5: Unreachable. This stays in the filter, but isn't in usable-filtered */
+ g[5]->last_tried_to_connect = approx_time(); // prevent retry.
+ g[5]->is_reachable = GUARD_REACHABLE_NO;
- state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuardAddedBy");
- smartlist_add_asprintf(state_line, "%s %s %s", fpr, tor_version, added_at);
- smartlist_add(entry_state_lines, state_line);
+ /* 6: no change. */
- state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuardUnlistedSince");
- smartlist_add_asprintf(state_line, "%s", unlisted_since);
- smartlist_add(entry_state_lines, state_line);
+ /* Now refilter and inspect. */
+ entry_guards_update_filtered_sets(gs);
+ for (i = 0; i < NUM; ++i) {
+ tt_assert(g[i]->is_filtered_guard == (i == 5 || i == 6));
+ tt_assert(g[i]->is_usable_filtered_guard == (i == 6));
}
+ tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 1);
+
+ /* Now make sure we have no live consensus, and no nodes. Nothing should
+ * pass the filter any more. */
+ tor_free(dummy_consensus);
+ dummy_consensus = NULL;
+ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, node, {
+ memset(node->identity, 0xff, 20);
+ });
+ 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_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 0);
- /* Inject our lines in the state */
- state_insert_entry_guard_helper(state, entry_state_lines);
+ done:
+ guard_selection_free(gs);
+ tor_free(bl);
+#undef NUM
+}
- /* Parse state */
- retval = entry_guards_parse_state(state, 1, &msg);
- tt_int_op(retval, OP_GE, 0);
+static void
+test_entry_guard_expand_sample(void *arg)
+{
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ digestmap_t *node_by_id = digestmap_new();
+
+ entry_guard_t *guard = entry_guards_expand_sample(gs);
+ tt_assert(guard); // the last guard returned.
+
+ // Every sampled guard here should be filtered and reachable for now.
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ,
+ num_reachable_filtered_guards(gs, NULL));
+
+ /* Make sure we got the right number. */
+ tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ,
+ num_reachable_filtered_guards(gs, NULL));
+
+ // Make sure everything we got was from our fake node list, and everything
+ // was unique.
+ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, g) {
+ const node_t *n = bfn_mock_node_get_by_id(g->identity);
+ tt_assert(n);
+ tt_ptr_op(NULL, OP_EQ, digestmap_get(node_by_id, g->identity));
+ digestmap_set(node_by_id, g->identity, (void*) n);
+ int idx = smartlist_pos(big_fake_net_nodes, n);
+ // The even ones are the guards; make sure we got guards.
+ tt_int_op(idx & 1, OP_EQ, 0);
+ } SMARTLIST_FOREACH_END(g);
+
+ // 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_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ,
+ num_reachable_filtered_guards(gs, NULL));
+
+ // Make a few guards unreachable.
+ guard = smartlist_get(gs->sampled_entry_guards, 0);
+ guard->is_usable_filtered_guard = 0;
+ guard = smartlist_get(gs->sampled_entry_guards, 1);
+ guard->is_usable_filtered_guard = 0;
+ guard = smartlist_get(gs->sampled_entry_guards, 2);
+ guard->is_usable_filtered_guard = 0;
+ tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE - 3, OP_EQ,
+ num_reachable_filtered_guards(gs, NULL));
+
+ // This time, expanding the sample will add some more guards.
+ guard = entry_guards_expand_sample(gs);
+ tt_assert(guard); // no guard was added.
+ tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ,
+ num_reachable_filtered_guards(gs, NULL));
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ,
+ num_reachable_filtered_guards(gs, NULL)+3);
+
+ // Still idempotent.
+ guard = entry_guards_expand_sample(gs);
+ tt_assert(! guard); // no guard was added.
+ tt_int_op(DFLT_MIN_FILTERED_SAMPLE_SIZE, OP_EQ,
+ num_reachable_filtered_guards(gs, NULL));
+
+ // Now, do a nasty trick: tell the filter to exclude 31/32 of the guards.
+ // This will cause the sample size to get reeeeally huge, while the
+ // filtered sample size grows only slowly.
+ routerset_free(get_options_mutable()->ExcludeNodes);
+ get_options_mutable()->ExcludeNodes = routerset_new();
+ routerset_parse(get_options_mutable()->ExcludeNodes, "144.144.0.0/16", "");
+ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, {
+ if (n_sl_idx % 64 != 0) {
+ n->rs->addr = 0x90903030;
+ }
+ });
+ entry_guards_update_filtered_sets(gs);
+
+ // Surely (p ~ 1-2**-60), one of our guards has been excluded.
+ tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_LT,
+ DFLT_MIN_FILTERED_SAMPLE_SIZE);
+
+ // Try to regenerate the guards.
+ guard = entry_guards_expand_sample(gs);
+ tt_assert(guard); // no guard was added.
+
+ /* this time, it's possible that we didn't add enough sampled guards. */
+ tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_LE,
+ DFLT_MIN_FILTERED_SAMPLE_SIZE);
+ /* but we definitely didn't exceed the sample maximum. */
+ const int n_guards = 271 / 2;
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_LE,
+ (int)(n_guards * .3));
- /* Test that the guard was registered.
- We need to re-get the entry guard list since its pointer was
- overwritten in entry_guards_parse_state(). */
- all_entry_guards = get_entry_guards();
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 1);
+ done:
+ guard_selection_free(gs);
+ digestmap_free(node_by_id, NULL);
+}
+
+static void
+test_entry_guard_expand_sample_small_net(void *arg)
+{
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+
+ /* Fun corner case: not enough guards to make up our whole sample size. */
+ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n, {
+ if (n_sl_idx >= 15) {
+ tor_free(n->rs);
+ tor_free(n->md);
+ tor_free(n);
+ SMARTLIST_DEL_CURRENT(big_fake_net_nodes, n);
+ } else {
+ n->rs->addr = 0; // make the filter reject this.
+ }
+ });
+
+ entry_guard_t *guard = entry_guards_expand_sample(gs);
+ tt_assert(guard); // the last guard returned -- some guard was added.
+ // half the nodes are guards, so we have 8 guards left. The set
+ // is small, so we sampled everything.
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, 8);
+ tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, 0);
+ done:
+ guard_selection_free(gs);
+}
- { /* Test the entry guard structure */
- char hex_digest[1024];
- char str_time[1024];
+static void
+test_entry_guard_update_from_consensus_status(void *arg)
+{
+ /* Here we're going to have some nodes become un-guardy, and say we got a
+ * new consensus. This should cause those nodes to get detected as
+ * unreachable. */
- const entry_guard_t *e = smartlist_get(all_entry_guards, 0);
- tt_str_op(e->nickname, OP_EQ, nickname); /* Verify nickname */
+ (void)arg;
+ int i;
+ time_t start = approx_time();
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ networkstatus_t *ns_tmp = NULL;
+
+ /* Don't randomly backdate stuff; it will make correctness harder to check.*/
+ MOCK(randomize_time, mock_randomize_time_no_randomization);
+
+ /* First, sample some guards. */
+ entry_guards_expand_sample(gs);
+ int n_sampled_pre = smartlist_len(gs->sampled_entry_guards);
+ int n_filtered_pre = num_reachable_filtered_guards(gs, NULL);
+ tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre);
+ tt_i64_op(n_sampled_pre, OP_GT, 10);
+
+ /* At this point, it should be a no-op to do this: */
+ sampled_guards_update_from_consensus(gs);
+
+ /* Now let's make some of our guards become unlisted. The easiest way to
+ * do that would be to take away their guard flag. */
+ for (i = 0; i < 5; ++i) {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity);
+ n->is_possible_guard = 0;
+ }
- base16_encode(hex_digest, sizeof(hex_digest),
- e->identity, DIGEST_LEN);
- tt_str_op(hex_digest, OP_EQ, fpr); /* Verify fingerprint */
+ update_approx_time(start + 30);
+ {
+ /* try this with no live networkstatus. Nothing should happen! */
+ ns_tmp = dummy_consensus;
+ dummy_consensus = NULL;
+ sampled_guards_update_from_consensus(gs);
+ tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre);
+ tt_i64_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_filtered_pre);
+ /* put the networkstatus back. */
+ dummy_consensus = ns_tmp;
+ ns_tmp = NULL;
+ }
- tt_assert(e->is_dir_cache); /* Verify dirness */
+ /* Now those guards should become unlisted, and drop off the filter, but
+ * stay in the sample. */
+ update_approx_time(start + 60);
+ sampled_guards_update_from_consensus(gs);
+
+ tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre);
+ tt_i64_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_filtered_pre-5);
+ for (i = 0; i < 5; ++i) {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ tt_assert(! g->currently_listed);
+ tt_i64_op(g->unlisted_since_date, OP_EQ, start+60);
+ }
+ for (i = 5; i < n_sampled_pre; ++i) {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ tt_assert(g->currently_listed);
+ tt_i64_op(g->unlisted_since_date, OP_EQ, 0);
+ }
- tt_str_op(e->chosen_by_version, OP_EQ, tor_version); /* Verify version */
+ /* Now re-list one, and remove one completely. */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 0);
+ node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity);
+ n->is_possible_guard = 1;
+ }
+ {
+ /* try removing the node, to make sure we don't crash on an absent node
+ */
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 5);
+ node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity);
+ smartlist_remove(big_fake_net_nodes, n);
+ tor_free(n->rs);
+ tor_free(n->md);
+ tor_free(n);
+ }
+ update_approx_time(start + 300);
+ sampled_guards_update_from_consensus(gs);
+
+ /* guards 1..5 are now unlisted; 0,6,7.. are listed. */
+ tt_i64_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre);
+ for (i = 1; i < 6; ++i) {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ tt_assert(! g->currently_listed);
+ if (i == 5)
+ tt_i64_op(g->unlisted_since_date, OP_EQ, start+300);
+ else
+ tt_i64_op(g->unlisted_since_date, OP_EQ, start+60);
+ }
+ for (i = 0; i < n_sampled_pre; i = (!i) ? 6 : i+1) { /* 0,6,7,8, ... */
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ tt_assert(g->currently_listed);
+ tt_i64_op(g->unlisted_since_date, OP_EQ, 0);
+ }
- tt_assert(e->made_contact); /* All saved guards have been contacted */
+ done:
+ tor_free(ns_tmp); /* in case we couldn't put it back */
+ guard_selection_free(gs);
+ UNMOCK(randomize_time);
+}
- tt_assert(e->bad_since); /* Verify bad_since timestamp */
- format_iso_time(str_time, e->bad_since);
- tt_str_op(str_time, OP_EQ, unlisted_since);
+static void
+test_entry_guard_update_from_consensus_repair(void *arg)
+{
+ /* Here we'll make sure that our code to repair the unlisted-since
+ * times is correct. */
- /* The rest should be unset */
- tt_assert(!e->unreachable_since);
- tt_assert(!e->can_retry);
- tt_assert(!e->path_bias_noticed);
- tt_assert(!e->path_bias_warned);
- tt_assert(!e->path_bias_extreme);
- tt_assert(!e->path_bias_disabled);
- tt_assert(!e->path_bias_use_noticed);
- tt_assert(!e->path_bias_use_extreme);
- tt_assert(!e->last_attempted);
+ (void)arg;
+ int i;
+ time_t start = approx_time();
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+
+ /* Don't randomly backdate stuff; it will make correctness harder to check.*/
+ MOCK(randomize_time, mock_randomize_time_no_randomization);
+
+ /* First, sample some guards. */
+ entry_guards_expand_sample(gs);
+ int n_sampled_pre = smartlist_len(gs->sampled_entry_guards);
+ int n_filtered_pre = num_reachable_filtered_guards(gs, NULL);
+ tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre);
+ tt_i64_op(n_sampled_pre, OP_GT, 10);
+
+ /* Now corrupt the list a bit. Call some unlisted-since-never, and some
+ * listed-and-unlisted-since-a-time. */
+ update_approx_time(start + 300);
+ for (i = 0; i < 3; ++i) {
+ /* these will get a date. */
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity);
+ n->is_possible_guard = 0;
+ g->currently_listed = 0;
+ }
+ for (i = 3; i < 6; ++i) {
+ /* these will become listed. */
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ g->unlisted_since_date = start+100;
+ }
+ setup_full_capture_of_logs(LOG_WARN);
+ sampled_guards_update_from_consensus(gs);
+ expect_log_msg_containing(
+ "was listed, but with unlisted_since_date set");
+ expect_log_msg_containing(
+ "was unlisted, but with unlisted_since_date unset");
+ teardown_capture_of_logs();
+
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre);
+ tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_filtered_pre-3);
+ for (i = 3; i < n_sampled_pre; ++i) {
+ /* these will become listed. */
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, i);
+ if (i < 3) {
+ tt_assert(! g->currently_listed);
+ tt_i64_op(g->unlisted_since_date, OP_EQ, start+300);
+ } else {
+ tt_assert(g->currently_listed);
+ tt_i64_op(g->unlisted_since_date, OP_EQ, 0);
+ }
}
done:
- state_lines_free(entry_state_lines);
- or_state_free(state);
- tor_free(msg);
+ teardown_capture_of_logs();
+ guard_selection_free(gs);
+ UNMOCK(randomize_time);
}
-/** Similar to test_entry_guards_parse_state_simple() but aims to test
- the PathBias-related details of the entry guard. */
static void
-test_entry_guards_parse_state_pathbias(void *arg)
+test_entry_guard_update_from_consensus_remove(void *arg)
{
- or_state_t *state = or_state_new();
- const smartlist_t *all_entry_guards = get_entry_guards();
- char *msg = NULL;
- int retval;
- smartlist_t *entry_state_lines = smartlist_new();
+ /* Now let's check the logic responsible for removing guards from the
+ * sample entirely. */
- /* Path bias details of the fake guard */
- const double circ_attempts = 9;
- const double circ_successes = 8;
- const double successful_closed = 4;
- const double collapsed = 2;
- const double unusable = 0;
- const double timeouts = 1;
+ (void)arg;
+ //int i;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ smartlist_t *keep_ids = smartlist_new();
+ smartlist_t *remove_ids = smartlist_new();
+
+ /* Don't randomly backdate stuff; it will make correctness harder to check.*/
+ MOCK(randomize_time, mock_randomize_time_no_randomization);
+
+ /* First, sample some guards. */
+ entry_guards_expand_sample(gs);
+ int n_sampled_pre = smartlist_len(gs->sampled_entry_guards);
+ int n_filtered_pre = num_reachable_filtered_guards(gs, NULL);
+ tt_i64_op(n_sampled_pre, OP_EQ, n_filtered_pre);
+ tt_i64_op(n_sampled_pre, OP_GT, 10);
+
+ const time_t one_day_ago = approx_time() - 1*24*60*60;
+ const time_t one_year_ago = approx_time() - 365*24*60*60;
+ const time_t two_years_ago = approx_time() - 2*365*24*60*60;
+ /* 0: unlisted for a day. (keep this) */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 0);
+ node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity);
+ n->is_possible_guard = 0;
+ g->currently_listed = 0;
+ g->unlisted_since_date = one_day_ago;
+ smartlist_add(keep_ids, tor_memdup(g->identity, 20));
+ }
+ /* 1: unlisted for a year. (remove this) */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 1);
+ node_t *n = (node_t*) bfn_mock_node_get_by_id(g->identity);
+ n->is_possible_guard = 0;
+ g->currently_listed = 0;
+ g->unlisted_since_date = one_year_ago;
+ smartlist_add(remove_ids, tor_memdup(g->identity, 20));
+ }
+ /* 2: added a day ago, never confirmed. (keep this) */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 2);
+ g->sampled_on_date = one_day_ago;
+ smartlist_add(keep_ids, tor_memdup(g->identity, 20));
+ }
+ /* 3: added a year ago, never confirmed. (remove this) */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 3);
+ g->sampled_on_date = one_year_ago;
+ smartlist_add(remove_ids, tor_memdup(g->identity, 20));
+ }
+ /* 4: added two year ago, confirmed yesterday, primary. (keep this.) */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 4);
+ g->sampled_on_date = one_year_ago;
+ g->confirmed_on_date = one_day_ago;
+ g->confirmed_idx = 0;
+ g->is_primary = 1;
+ smartlist_add(gs->confirmed_entry_guards, g);
+ smartlist_add(gs->primary_entry_guards, g);
+ smartlist_add(keep_ids, tor_memdup(g->identity, 20));
+ }
+ /* 5: added two years ago, confirmed a year ago, primary. (remove this) */
+ {
+ entry_guard_t *g = smartlist_get(gs->sampled_entry_guards, 5);
+ g->sampled_on_date = two_years_ago;
+ g->confirmed_on_date = one_year_ago;
+ g->confirmed_idx = 1;
+ g->is_primary = 1;
+ smartlist_add(gs->confirmed_entry_guards, g);
+ smartlist_add(gs->primary_entry_guards, g);
+ smartlist_add(remove_ids, tor_memdup(g->identity, 20));
+ }
- (void) arg;
+ sampled_guards_update_from_consensus(gs);
- /* The global entry guards smartlist should be empty now. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 0);
+ /* Did we remove the right ones? */
+ SMARTLIST_FOREACH(keep_ids, uint8_t *, id, {
+ tt_assert(get_sampled_guard_with_id(gs, id) != NULL);
+ });
+ SMARTLIST_FOREACH(remove_ids, uint8_t *, id, {
+ tt_want(get_sampled_guard_with_id(gs, id) == NULL);
+ });
- { /* Prepare the state entry */
+ /* Did we remove the right number? */
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_sampled_pre - 3);
- /* Prepare the smartlist to hold the key/value of each line */
- smartlist_t *state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuard");
- smartlist_add_asprintf(state_line,
- "givethanks B29D536DD1752D542E1FBB3C9CE4449D51298212 NoDirCache");
- smartlist_add(entry_state_lines, state_line);
+ done:
+ guard_selection_free(gs);
+ UNMOCK(randomize_time);
+ SMARTLIST_FOREACH(keep_ids, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(remove_ids, char *, cp, tor_free(cp));
+ smartlist_free(keep_ids);
+ smartlist_free(remove_ids);
+}
- state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuardAddedBy");
- smartlist_add_asprintf(state_line,
- "B29D536DD1752D542E1FBB3C9CE4449D51298212 0.2.5.3-alpha-dev "
- "%s", get_yesterday_date_str());
- smartlist_add(entry_state_lines, state_line);
+static void
+test_entry_guard_confirming_guards(void *arg)
+{
+ (void)arg;
+ /* Now let's check the logic responsible for manipulating the list
+ * of confirmed guards */
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ MOCK(randomize_time, mock_randomize_time_no_randomization);
+
+ /* Create the sample. */
+ entry_guards_expand_sample(gs);
+
+ /* Confirm a few guards. */
+ 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, 8);
+ make_guard_confirmed(gs, g2);
+ update_approx_time(start + 10);
+ make_guard_confirmed(gs, g1);
+ make_guard_confirmed(gs, g3);
+
+ /* Were the correct dates and indices fed in? */
+ tt_int_op(g1->confirmed_idx, OP_EQ, 1);
+ tt_int_op(g2->confirmed_idx, OP_EQ, 0);
+ tt_int_op(g3->confirmed_idx, OP_EQ, 2);
+ tt_i64_op(g1->confirmed_on_date, OP_EQ, start+10);
+ tt_i64_op(g2->confirmed_on_date, OP_EQ, start);
+ tt_i64_op(g3->confirmed_on_date, OP_EQ, start+10);
+ tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 0), OP_EQ, g2);
+ tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 1), OP_EQ, g1);
+ tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 2), OP_EQ, g3);
+
+ /* Now make sure we can regenerate the confirmed_entry_guards list. */
+ smartlist_clear(gs->confirmed_entry_guards);
+ g2->confirmed_idx = 0;
+ g1->confirmed_idx = 10;
+ g3->confirmed_idx = 100;
+ entry_guards_update_confirmed(gs);
+ tt_int_op(g1->confirmed_idx, OP_EQ, 1);
+ tt_int_op(g2->confirmed_idx, OP_EQ, 0);
+ tt_int_op(g3->confirmed_idx, OP_EQ, 2);
+ tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 0), OP_EQ, g2);
+ tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 1), OP_EQ, g1);
+ tt_ptr_op(smartlist_get(gs->confirmed_entry_guards, 2), OP_EQ, g3);
+
+ /* Now make sure we can regenerate the confirmed_entry_guards list if
+ * the indices are messed up. */
+ g1->confirmed_idx = g2->confirmed_idx = g3->confirmed_idx = 999;
+ smartlist_clear(gs->confirmed_entry_guards);
+ entry_guards_update_confirmed(gs);
+ tt_int_op(g1->confirmed_idx, OP_GE, 0);
+ tt_int_op(g2->confirmed_idx, OP_GE, 0);
+ tt_int_op(g3->confirmed_idx, OP_GE, 0);
+ tt_int_op(g1->confirmed_idx, OP_LE, 2);
+ tt_int_op(g2->confirmed_idx, OP_LE, 2);
+ tt_int_op(g3->confirmed_idx, OP_LE, 2);
+ g1 = smartlist_get(gs->confirmed_entry_guards, 0);
+ g2 = smartlist_get(gs->confirmed_entry_guards, 1);
+ g3 = smartlist_get(gs->confirmed_entry_guards, 2);
+ tt_int_op(g1->confirmed_idx, OP_EQ, 0);
+ tt_int_op(g2->confirmed_idx, OP_EQ, 1);
+ tt_int_op(g3->confirmed_idx, OP_EQ, 2);
+ tt_assert(g1 != g2);
+ tt_assert(g1 != g3);
+ tt_assert(g2 != g3);
- state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuardUnlistedSince");
- smartlist_add_asprintf(state_line, "2014-06-08 16:16:50");
- smartlist_add(entry_state_lines, state_line);
+ done:
+ UNMOCK(randomize_time);
+ guard_selection_free(gs);
+}
- state_line = smartlist_new();
- smartlist_add_asprintf(state_line, "EntryGuardPathBias");
- smartlist_add_asprintf(state_line, "%f %f %f %f %f %f",
- circ_attempts, circ_successes, successful_closed,
- collapsed, unusable, timeouts);
- smartlist_add(entry_state_lines, state_line);
+static void
+test_entry_guard_sample_reachable_filtered(void *arg)
+{
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ entry_guards_expand_sample(gs);
+ const int N = 10000;
+ bitarray_t *selected = NULL;
+ int i, j;
+
+ /* We've got a sampled list now; let's make one non-usable-filtered; some
+ * confirmed, some primary, some pending.
+ */
+ int n_guards = smartlist_len(gs->sampled_entry_guards);
+ tt_int_op(n_guards, OP_GT, 10);
+ entry_guard_t *g;
+ g = smartlist_get(gs->sampled_entry_guards, 0);
+ g->is_pending = 1;
+ g = smartlist_get(gs->sampled_entry_guards, 1);
+ make_guard_confirmed(gs, g);
+ g = smartlist_get(gs->sampled_entry_guards, 2);
+ g->is_primary = 1;
+ g = smartlist_get(gs->sampled_entry_guards, 3);
+ g->pb.path_bias_disabled = 1;
+
+ entry_guards_update_filtered_sets(gs);
+ gs->primary_guards_up_to_date = 1;
+ tt_int_op(num_reachable_filtered_guards(gs, NULL), OP_EQ, n_guards - 1);
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards);
+
+ // +1 since the one we made disabled will make another one get added.
+ ++n_guards;
+
+ /* Try a bunch of selections. */
+ const struct {
+ int flag; int idx;
+ } tests[] = {
+ { 0, -1 },
+ { SAMPLE_EXCLUDE_CONFIRMED, 1 },
+ { SAMPLE_EXCLUDE_PRIMARY|SAMPLE_NO_UPDATE_PRIMARY, 2 },
+ { SAMPLE_EXCLUDE_PENDING, 0 },
+ { -1, -1},
+ };
+
+ for (j = 0; tests[j].flag >= 0; ++j) {
+ selected = bitarray_init_zero(n_guards);
+ const int excluded_flags = tests[j].flag;
+ const int excluded_idx = tests[j].idx;
+ for (i = 0; i < N; ++i) {
+ g = sample_reachable_filtered_entry_guards(gs, NULL, excluded_flags);
+ tor_assert(g);
+ int pos = smartlist_pos(gs->sampled_entry_guards, g);
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, n_guards);
+ tt_int_op(pos, OP_GE, 0);
+ tt_int_op(pos, OP_LT, n_guards);
+ bitarray_set(selected, pos);
+ }
+ for (i = 0; i < n_guards; ++i) {
+ const int should_be_set = (i != excluded_idx &&
+ i != 3); // filtered out.
+ tt_int_op(!!bitarray_is_set(selected, i), OP_EQ, should_be_set);
+ }
+ bitarray_free(selected);
+ selected = NULL;
}
- /* Inject our lines in the state */
- state_insert_entry_guard_helper(state, entry_state_lines);
+ done:
+ guard_selection_free(gs);
+ bitarray_free(selected);
+}
+
+static void
+test_entry_guard_sample_reachable_filtered_empty(void *arg)
+{
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ /* What if we try to sample from a set of 0? */
+ SMARTLIST_FOREACH(big_fake_net_nodes, node_t *, n,
+ n->is_possible_guard = 0);
- /* Parse state */
- retval = entry_guards_parse_state(state, 1, &msg);
- tt_int_op(retval, OP_GE, 0);
+ entry_guard_t *g = sample_reachable_filtered_entry_guards(gs, NULL, 0);
+ tt_ptr_op(g, OP_EQ, NULL);
- /* Test that the guard was registered */
- all_entry_guards = get_entry_guards();
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 1);
+ done:
+ guard_selection_free(gs);
+}
- { /* Test the path bias of this guard */
- const entry_guard_t *e = smartlist_get(all_entry_guards, 0);
+static void
+test_entry_guard_retry_unreachable(void *arg)
+{
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+
+ entry_guards_expand_sample(gs);
+ /* Let's say that we have two guards, and they're down.
+ */
+ 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);
+ g1->is_reachable = GUARD_REACHABLE_NO;
+ g2->is_reachable = GUARD_REACHABLE_NO;
+ g1->is_primary = 1;
+ g1->failing_since = g2->failing_since = start;
+ g1->last_tried_to_connect = g2->last_tried_to_connect = start;
+
+ /* Wait 5 minutes. Nothing will get retried. */
+ update_approx_time(start + 5 * 60);
+ entry_guard_consider_retry(g1);
+ entry_guard_consider_retry(g2);
+ entry_guard_consider_retry(g3); // just to make sure this doesn't crash.
+ tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
+ tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
+ tt_int_op(g3->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+
+ /* After 30 min, the primary one gets retried */
+ update_approx_time(start + 35 * 60);
+ entry_guard_consider_retry(g1);
+ entry_guard_consider_retry(g2);
+ tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+ tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
+
+ g1->is_reachable = GUARD_REACHABLE_NO;
+ g1->last_tried_to_connect = start + 55*60;
+
+ /* After 1 hour, we'll retry the nonprimary one. */
+ update_approx_time(start + 61 * 60);
+ entry_guard_consider_retry(g1);
+ entry_guard_consider_retry(g2);
+ tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
+ tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+
+ g2->is_reachable = GUARD_REACHABLE_NO;
+ g2->last_tried_to_connect = start + 61*60;
+
+ /* And then the primary one again. */
+ update_approx_time(start + 66 * 60);
+ entry_guard_consider_retry(g1);
+ entry_guard_consider_retry(g2);
+ tt_int_op(g1->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+ tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
- tt_assert(!e->is_dir_cache);
- tt_assert(!e->can_retry);
+ done:
+ guard_selection_free(gs);
+}
- /* XXX tt_double_op doesn't support equality. Cast to int for now. */
- tt_int_op((int)e->circ_attempts, OP_EQ, (int)circ_attempts);
- tt_int_op((int)e->circ_successes, OP_EQ, (int)circ_successes);
- tt_int_op((int)e->successful_circuits_closed, OP_EQ,
- (int)successful_closed);
- tt_int_op((int)e->timeouts, OP_EQ, (int)timeouts);
- tt_int_op((int)e->collapsed_circuits, OP_EQ, (int)collapsed);
- tt_int_op((int)e->unusable_circuits, OP_EQ, (int)unusable);
+static void
+test_entry_guard_manage_primary(void *arg)
+{
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ smartlist_t *prev_guards = smartlist_new();
+
+ /* If no guards are confirmed, we should pick a few reachable guards and
+ * call them all primary. But not confirmed.*/
+ entry_guards_update_primary(gs);
+ int n_primary = smartlist_len(gs->primary_entry_guards);
+ tt_int_op(n_primary, OP_GE, 1);
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, {
+ tt_assert(g->is_primary);
+ tt_assert(g->confirmed_idx == -1);
+ });
+
+ /* Calling it a second time should leave the guards unchanged. */
+ smartlist_add_all(prev_guards, gs->primary_entry_guards);
+ entry_guards_update_primary(gs);
+ tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, n_primary);
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, {
+ tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx));
+ });
+
+ /* If we have one confirmed guard, that guards becomes the first primary
+ * guard, and the other primary guards get kept. */
+
+ /* find a non-primary guard... */
+ entry_guard_t *confirmed = NULL;
+ SMARTLIST_FOREACH(gs->sampled_entry_guards, entry_guard_t *, g, {
+ if (! g->is_primary) {
+ confirmed = g;
+ break;
+ }
+ });
+ tt_assert(confirmed);
+ /* make it confirmed. */
+ make_guard_confirmed(gs, confirmed);
+ /* update the list... */
+ smartlist_clear(prev_guards);
+ smartlist_add_all(prev_guards, gs->primary_entry_guards);
+ entry_guards_update_primary(gs);
+
+ /* and see what's primary now! */
+ tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, n_primary);
+ tt_ptr_op(smartlist_get(gs->primary_entry_guards, 0), OP_EQ, confirmed);
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, {
+ tt_assert(g->is_primary);
+ if (g_sl_idx == 0)
+ continue;
+ tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx - 1));
+ });
+ {
+ entry_guard_t *prev_last_guard = smartlist_get(prev_guards, n_primary-1);
+ tt_assert(! prev_last_guard->is_primary);
}
+ /* Calling it a fourth time should leave the guards unchanged. */
+ smartlist_clear(prev_guards);
+ smartlist_add_all(prev_guards, gs->primary_entry_guards);
+ entry_guards_update_primary(gs);
+ tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, n_primary);
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, g, {
+ tt_ptr_op(g, OP_EQ, smartlist_get(prev_guards, g_sl_idx));
+ });
+
done:
- or_state_free(state);
- state_lines_free(entry_state_lines);
- tor_free(msg);
+ guard_selection_free(gs);
+ smartlist_free(prev_guards);
}
-/* Simple test of entry_guards_set_from_config() by specifying a
- particular EntryNode and making sure it gets picked. */
static void
-test_entry_guards_set_from_config(void *arg)
+test_entry_guard_guard_preferred(void *arg)
{
- or_options_t *options = get_options_mutable();
- const smartlist_t *all_entry_guards = get_entry_guards();
- const char *entrynodes_str = "test003r";
- const node_t *chosen_entry = NULL;
- int retval;
-
(void) arg;
+ entry_guard_t *g1 = tor_malloc_zero(sizeof(entry_guard_t));
+ entry_guard_t *g2 = tor_malloc_zero(sizeof(entry_guard_t));
+
+ g1->confirmed_idx = g2->confirmed_idx = -1;
+ g1->last_tried_to_connect = approx_time();
+ g2->last_tried_to_connect = approx_time();
+
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g1));
- /* Prase EntryNodes as a routerset. */
- options->EntryNodes = routerset_new();
- retval = routerset_parse(options->EntryNodes,
- entrynodes_str,
- "test_entrynodes");
- tt_int_op(retval, OP_GE, 0);
+ /* Neither is pending; priorities equal. */
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1));
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2));
- /* Read nodes from EntryNodes */
- entry_guards_set_from_config(options);
+ /* If one is pending, the pending one has higher priority */
+ g1->is_pending = 1;
+ tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g1, g2));
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1));
- /* Test that only one guard was added. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 1);
+ /* If both are pending, and last_tried_to_connect is equal:
+ priorities equal */
+ g2->is_pending = 1;
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1));
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2));
- /* Make sure it was the guard we specified. */
- chosen_entry = choose_random_entry(NULL);
- tt_str_op(chosen_entry->ri->nickname, OP_EQ, entrynodes_str);
+ /* One had a connection that startied earlier: it has higher priority. */
+ g2->last_tried_to_connect -= 10;
+ tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g2, g1));
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2));
+
+ /* Now, say that g1 is confirmed. It will get higher priority. */
+ g1->confirmed_idx = 5;
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g2, g1));
+ tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g1, g2));
+
+ /* But if g2 was confirmed first, it will get priority */
+ g2->confirmed_idx = 2;
+ tt_int_op(1, OP_EQ, entry_guard_has_higher_priority(g2, g1));
+ tt_int_op(0, OP_EQ, entry_guard_has_higher_priority(g1, g2));
done:
- routerset_free(options->EntryNodes);
+ tor_free(g1);
+ tor_free(g2);
}
static void
-test_entry_is_time_to_retry(void *arg)
+test_entry_guard_select_for_circuit_no_confirmed(void *arg)
{
- entry_guard_t *test_guard;
- time_t now;
- int retval;
+ /* Simpler cases: no gaurds are confirmed yet. */
(void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+
+ /* simple starting configuration */
+ entry_guards_update_primary(gs);
+ unsigned state = 9999;
+
+ entry_guard_t *g = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC,
+ NULL, &state);
+
+ 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(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time());
+
+ // If we do that again, we should get the same guard.
+ entry_guard_t *g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC,
+ NULL, &state);
+ tt_ptr_op(g2, OP_EQ, g);
+
+ // if we mark that guard down, we should get a different primary guard.
+ // auto-retry it.
+ g->is_reachable = GUARD_REACHABLE_NO;
+ g->failing_since = approx_time() - 10;
+ g->last_tried_to_connect = approx_time() - 10;
+ state = 9999;
+ g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ tt_ptr_op(g2, OP_NE, g);
+ 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(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time());
+
+ // If we say that the first primary guard was last tried a long time ago, we
+ // should get an automatic retry on it.
+ g->failing_since = approx_time() - 72*60*60;
+ g->last_tried_to_connect = approx_time() - 72*60*60;
+ state = 9999;
+ g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ tt_ptr_op(g2, OP_EQ, g);
+ tt_assert(g2);
+ tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ tt_i64_op(g2->last_tried_to_connect, OP_EQ, approx_time());
+ tt_int_op(g2->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+
+ // And if we mark ALL the primary guards down, we should get another guard
+ // at random.
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, {
+ guard->is_reachable = GUARD_REACHABLE_NO;
+ guard->last_tried_to_connect = approx_time() - 5;
+ guard->failing_since = approx_time() - 30;
+ });
+ state = 9999;
+ g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ 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(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);
+
+ // As a bonus, maybe we should be retrying the primary guards. Let's say so.
+ mark_primary_guards_maybe_reachable(gs);
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, {
+ tt_int_op(guard->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+ tt_assert(guard->is_usable_filtered_guard == 1);
+ // no change to these fields.
+ tt_i64_op(guard->last_tried_to_connect, OP_EQ, approx_time() - 5);
+ tt_i64_op(guard->failing_since, OP_EQ, approx_time() - 30);
+ });
+
+ /* Let's try again and we should get the first primary guard again */
+ g = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ tt_ptr_op(g, OP_EQ, smartlist_get(gs->primary_entry_guards, 0));
+ g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ 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);
+ tt_ptr_op(g2, OP_NE, g);
- now = time(NULL);
+ done:
+ guard_selection_free(gs);
+}
- test_guard = tor_malloc_zero(sizeof(entry_guard_t));
+static void
+test_entry_guard_select_for_circuit_confirmed(void *arg)
+{
+ /* Case 2: if all the primary guards are down, and there are more confirmed
+ guards, we use a confirmed guard. */
+ (void)arg;
+ int i;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ const int N_CONFIRMED = 10;
+
+ /* slightly more complicated simple starting configuration */
+ entry_guards_update_primary(gs);
+ for (i = 0; i < N_CONFIRMED; ++i) {
+ entry_guard_t *guard = smartlist_get(gs->sampled_entry_guards, i);
+ make_guard_confirmed(gs, guard);
+ }
+ entry_guards_update_primary(gs); // rebuild the primary list.
+
+ unsigned state = 9999;
+
+ // As above, this gives us a primary guard.
+ entry_guard_t *g = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC,
+ NULL, &state);
+ 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(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));
+
+ // But if we mark all the primary guards down...
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, guard, {
+ guard->last_tried_to_connect = approx_time();
+ entry_guards_note_guard_failure(gs, guard);
+ });
+
+ // ... we should get a confirmed guard.
+ state = 9999;
+ g = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ tt_assert(g);
+ tt_assert(! g->is_primary);
+ tt_int_op(g->confirmed_idx, OP_EQ, smartlist_len(gs->primary_entry_guards));
+ tt_assert(g->is_pending);
+ tt_uint_op(state, OP_EQ, GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD);
+ tt_i64_op(g->last_tried_to_connect, OP_EQ, approx_time());
+
+ // And if we try again, we should get a different confirmed guard, since
+ // that one is pending.
+ state = 9999;
+ entry_guard_t *g2 = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC,
+ NULL, &state);
+ tt_assert(g2);
+ tt_assert(! g2->is_primary);
+ tt_ptr_op(g2, OP_NE, g);
+ tt_int_op(g2->confirmed_idx, OP_EQ,
+ smartlist_len(gs->primary_entry_guards)+1);
+ tt_assert(g2->is_pending);
+ 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());
+
+ // If we say that the next confirmed guard in order is excluded, and
+ // we disable EnforceDistinctSubnets, we get the guard AFTER the
+ // one we excluded.
+ 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);
+ tt_ptr_op(g2, OP_NE, NULL);
+ tt_ptr_op(g2, OP_NE, g);
+ tt_int_op(g2->confirmed_idx, OP_EQ,
+ smartlist_len(gs->primary_entry_guards)+3);
+
+ // If we make every confirmed guard become pending then we start poking
+ // other guards.
+ const int n_remaining_confirmed =
+ N_CONFIRMED - 3 - smartlist_len(gs->primary_entry_guards);
+ for (i = 0; i < n_remaining_confirmed; ++i) {
+ g = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ tt_int_op(g->confirmed_idx, OP_GE, 0);
+ tt_assert(g);
+ }
+ state = 9999;
+ g = select_entry_guard_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &state);
+ tt_assert(g);
+ tt_assert(g->is_pending);
+ tt_int_op(g->confirmed_idx, OP_EQ, -1);
+
+ // If we EnforceDistinctSubnets and apply a restriction, we get
+ // nothing, since we put all of the nodes in the same /16.
+ // 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);
+ tt_ptr_op(g2, OP_EQ, NULL);
- test_guard->last_attempted = now - 10;
- test_guard->unreachable_since = now - 1;
+ done:
+ guard_selection_free(gs);
+}
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+static void
+test_entry_guard_select_for_circuit_highlevel_primary(void *arg)
+{
+ /* Play around with selecting primary guards for circuits and markign
+ * them up and down */
+ (void)arg;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+
+ time_t start = approx_time();
+
+ const node_t *node = NULL;
+ circuit_guard_state_t *guard = NULL;
+ entry_guard_t *g;
+ guard_usable_t u;
+ /*
+ * Make sure that the pick-for-circuit API basically works. We'll get
+ * a primary guard, so it'll be usable on completion.
+ */
+ int r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+
+ tt_assert(r == 0);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ g = entry_guard_handle_get(guard->guard);
+ tt_assert(g);
+ tt_mem_op(g->identity, OP_EQ, node->identity, DIGEST_LEN);
+ tt_int_op(g->is_primary, OP_EQ, 1);
+ tt_i64_op(g->last_tried_to_connect, OP_EQ, start);
+ tt_int_op(g->confirmed_idx, OP_EQ, -1);
+
+ /* Call that circuit successful. */
+ update_approx_time(start+15);
+ u = entry_guard_succeeded(&guard);
+ tt_int_op(u, OP_EQ, GUARD_USABLE_NOW); /* We can use it now. */
+ tt_assert(guard);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
+ g = entry_guard_handle_get(guard->guard);
+ tt_assert(g);
+ tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_YES);
+ tt_int_op(g->confirmed_idx, OP_EQ, 0);
+
+ circuit_guard_state_free(guard);
+ guard = NULL;
+ node = NULL;
+ g = NULL;
+
+ /* Try again. We'll also get a primary guard this time. (The same one,
+ in fact.) But this time, we'll say the connection has failed. */
+ update_approx_time(start+35);
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(r == 0);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ tt_i64_op(guard->state_set_at, OP_EQ, start+35);
+ g = entry_guard_handle_get(guard->guard);
+ tt_assert(g);
+ tt_mem_op(g->identity, OP_EQ, node->identity, DIGEST_LEN);
+ tt_int_op(g->is_primary, OP_EQ, 1);
+ tt_i64_op(g->last_tried_to_connect, OP_EQ, start+35);
+ tt_int_op(g->confirmed_idx, OP_EQ, 0); // same one.
+
+ /* It's failed! What will happen to our poor guard? */
+ update_approx_time(start+45);
+ entry_guard_failed(&guard);
+ tt_assert(guard);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_DEAD);
+ tt_i64_op(guard->state_set_at, OP_EQ, start+45);
+ g = entry_guard_handle_get(guard->guard);
+ tt_assert(g);
+ tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
+ tt_i64_op(g->failing_since, OP_EQ, start+45);
+ tt_int_op(g->confirmed_idx, OP_EQ, 0); // still confirmed.
+
+ circuit_guard_state_free(guard);
+ guard = NULL;
+ node = NULL;
+ entry_guard_t *g_prev = g;
+ g = NULL;
+
+ /* Now try a third time. Since the other one is down, we'll get a different
+ * (still primary) guard.
+ */
+ update_approx_time(start+60);
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(r == 0);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ g = entry_guard_handle_get(guard->guard);
+ tt_assert(g);
+ tt_ptr_op(g, OP_NE, g_prev);
+ tt_mem_op(g->identity, OP_EQ, node->identity, DIGEST_LEN);
+ tt_mem_op(g->identity, OP_NE, g_prev->identity, DIGEST_LEN);
+ tt_int_op(g->is_primary, OP_EQ, 1);
+ tt_i64_op(g->last_tried_to_connect, OP_EQ, start+60);
+ tt_int_op(g->confirmed_idx, OP_EQ, -1); // not confirmd now.
+
+ /* Call this one up; watch it get confirmed. */
+ update_approx_time(start+90);
+ u = entry_guard_succeeded(&guard);
+ tt_int_op(u, OP_EQ, GUARD_USABLE_NOW);
+ tt_assert(guard);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
+ g = entry_guard_handle_get(guard->guard);
+ tt_assert(g);
+ tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_YES);
+ tt_int_op(g->confirmed_idx, OP_EQ, 1);
- test_guard->unreachable_since = now - (6*60*60 - 1);
- test_guard->last_attempted = now - (60*60 + 1);
+ done:
+ guard_selection_free(gs);
+ circuit_guard_state_free(guard);
+}
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+static void
+test_entry_guard_select_for_circuit_highlevel_confirm_other(void *arg)
+{
+ (void) arg;
+ const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS;
+
+ /* At the start, we have no confirmed guards. We'll mark the primary guards
+ * down, then confirm something else. As soon as we do, it should become
+ * primary, and we should get it next time. */
+
+ time_t start = approx_time();
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ circuit_guard_state_t *guard = NULL;
+ int i, r;
+ const node_t *node = NULL;
+ guard_usable_t u;
+
+ /* Declare that we're on the internet. */
+ entry_guards_note_internet_connectivity(gs);
+
+ /* Primary guards are down! */
+ for (i = 0; i < N_PRIMARY; ++i) {
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_assert(r == 0);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ entry_guard_failed(&guard);
+ circuit_guard_state_free(guard);
+ guard = NULL;
+ node = NULL;
+ }
- test_guard->last_attempted = now - (60*60 - 1);
+ /* Next guard should be non-primary. */
+ node = NULL;
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_assert(r == 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);
+ tt_int_op(g->confirmed_idx, OP_EQ, -1);
+ tt_int_op(g->is_primary, OP_EQ, 0);
+ tt_int_op(g->is_pending, OP_EQ, 1);
+ (void)start;
+
+ u = entry_guard_succeeded(&guard);
+ /* We're on the internet (by fiat), so this guard will get called "confirmed"
+ * and should immediately become primary.
+ */
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
+ tt_assert(u == GUARD_USABLE_NOW);
+ tt_int_op(g->confirmed_idx, OP_EQ, 0);
+ tt_int_op(g->is_primary, OP_EQ, 1);
+ tt_int_op(g->is_pending, OP_EQ, 0);
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,0);
+ done:
+ guard_selection_free(gs);
+ circuit_guard_state_free(guard);
+}
- test_guard->unreachable_since = now - (6*60*60 + 1);
- test_guard->last_attempted = now - (4*60*60 + 1);
+static void
+test_entry_guard_select_for_circuit_highlevel_primary_retry(void *arg)
+{
+ (void) arg;
+ const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS;
+
+ /* At the start, we have no confirmed guards. We'll mark the primary guards
+ * down, then confirm something else. As soon as we do, it should become
+ * primary, and we should get it next time. */
+
+ time_t start = approx_time();
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ circuit_guard_state_t *guard = NULL, *guard2 = NULL;
+ int i, r;
+ const node_t *node = NULL;
+ entry_guard_t *g;
+ guard_usable_t u;
+
+ /* Declare that we're on the internet. */
+ entry_guards_note_internet_connectivity(gs);
+
+ /* Make primary guards confirmed (so they won't be superseded by a later
+ * guard), then mark them down. */
+ for (i = 0; i < N_PRIMARY; ++i) {
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_assert(r == 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);
+ tt_int_op(g->is_primary, OP_EQ, 1);
+ entry_guard_failed(&guard);
+ circuit_guard_state_free(guard);
+ tt_int_op(g->is_reachable, OP_EQ, GUARD_REACHABLE_NO);
+ guard = NULL;
+ node = NULL;
+ }
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+ /* Get another guard that we might try. */
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_assert(r == 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);
+
+ tt_assert(entry_guards_all_primary_guards_are_down(gs));
+
+ /* And an hour has passed ... */
+ update_approx_time(start + 3600);
+
+ /* Say that guard has succeeded! */
+ u = entry_guard_succeeded(&guard);
+ tt_int_op(u, OP_EQ, GUARD_MAYBE_USABLE_LATER);
+ tt_int_op(guard->state, OP_EQ, GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD);
+ g = entry_guard_handle_get(guard->guard);
+
+ /* The primary guards should have been marked up! */
+ SMARTLIST_FOREACH(gs->primary_entry_guards, entry_guard_t *, pg, {
+ tt_int_op(pg->is_primary, OP_EQ, 1);
+ tt_ptr_op(g, OP_NE, pg);
+ tt_int_op(pg->is_reachable, OP_EQ, GUARD_REACHABLE_MAYBE);
+ });
+
+ /* 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(guard2->state, OP_EQ, GUARD_CIRC_STATE_USABLE_ON_COMPLETION);
+ u = entry_guard_succeeded(&guard2);
+ tt_assert(u == GUARD_USABLE_NOW);
+ tt_int_op(guard2->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
+
+ tt_assert(! entry_guards_all_primary_guards_are_down(gs));
- test_guard->unreachable_since = now - (3*24*60*60 - 1);
- test_guard->last_attempted = now - (4*60*60 + 1);
+ done:
+ guard_selection_free(gs);
+ circuit_guard_state_free(guard);
+ circuit_guard_state_free(guard2);
+}
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+static void
+test_entry_guard_select_and_cancel(void *arg)
+{
+ (void) arg;
+ const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS;
+ int i,r;
+ const node_t *node = NULL;
+ circuit_guard_state_t *guard;
+ guard_selection_t *gs = guard_selection_new("default", GS_TYPE_NORMAL);
+ entry_guard_t *g;
+
+ /* Once more, we mark all the primary guards down. */
+ entry_guards_note_internet_connectivity(gs);
+ for (i = 0; i < N_PRIMARY; ++i) {
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ 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);
+ tt_int_op(g->is_primary, OP_EQ, 1);
+ tt_int_op(g->is_pending, OP_EQ, 0);
+ make_guard_confirmed(gs, g);
+ entry_guard_failed(&guard);
+ circuit_guard_state_free(guard);
+ guard = NULL;
+ node = NULL;
+ }
- test_guard->unreachable_since = now - (3*24*60*60 + 1);
- test_guard->last_attempted = now - (18*60*60 + 1);
+ tt_assert(entry_guards_all_primary_guards_are_down(gs));
+
+ /* Now get another guard we could try... */
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_assert(node);
+ tt_assert(guard);
+ tt_assert(r == 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);
+ tt_int_op(g->is_pending, OP_EQ, 1);
+
+ /* Whoops! We should never have asked for this guard. Cancel the request! */
+ entry_guard_cancel(&guard);
+ tt_assert(guard == NULL);
+ tt_int_op(g->is_primary, OP_EQ, 0);
+ tt_int_op(g->is_pending, OP_EQ, 0);
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+ done:
+ guard_selection_free(gs);
+ circuit_guard_state_free(guard);
+}
- test_guard->unreachable_since = now - (7*24*60*60 - 1);
- test_guard->last_attempted = now - (18*60*60 + 1);
+static void
+test_entry_guard_drop_guards(void *arg)
+{
+ (void) arg;
+ int r;
+ const node_t *node = NULL;
+ circuit_guard_state_t *guard;
+ guard_selection_t *gs = get_guard_selection_info();
+
+ // Pick a guard, to get things set up.
+ r = entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &guard);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_GE,
+ DFLT_MIN_FILTERED_SAMPLE_SIZE);
+ tt_ptr_op(gs, OP_EQ, get_guard_selection_info());
+
+ // Drop all the guards! (This is a bad idea....)
+ remove_all_entry_guards_for_guard_selection(gs);
+ gs = get_guard_selection_info();
+ tt_int_op(smartlist_len(gs->sampled_entry_guards), OP_EQ, 0);
+ tt_int_op(smartlist_len(gs->primary_entry_guards), OP_EQ, 0);
+ tt_int_op(smartlist_len(gs->confirmed_entry_guards), OP_EQ, 0);
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+ done:
+ circuit_guard_state_free(guard);
+ guard_selection_free(gs);
+}
- test_guard->last_attempted = now - (18*60*60 - 1);
+/* Unit test setup function: Create a fake network, and set everything up
+ * for testing the upgrade-a-waiting-circuit code. */
+typedef struct {
+ guard_selection_t *gs;
+ time_t start;
+ circuit_guard_state_t *guard1_state;
+ circuit_guard_state_t *guard2_state;
+ entry_guard_t *guard1;
+ entry_guard_t *guard2;
+ origin_circuit_t *circ1;
+ origin_circuit_t *circ2;
+ smartlist_t *all_origin_circuits;
+} upgrade_circuits_data_t;
+static void *
+upgrade_circuits_setup(const struct testcase_t *testcase)
+{
+ upgrade_circuits_data_t *data = tor_malloc_zero(sizeof(*data));
+ guard_selection_t *gs = data->gs =
+ guard_selection_new("default", GS_TYPE_NORMAL);
+ circuit_guard_state_t *guard;
+ const node_t *node;
+ entry_guard_t *g;
+ int i;
+ const int N_PRIMARY = DFLT_N_PRIMARY_GUARDS;
+ const char *argument = testcase->setup_data;
+ const int make_circ1_succeed = strstr(argument, "c1-done") != NULL;
+ const int make_circ2_succeed = strstr(argument, "c2-done") != NULL;
+
+ big_fake_network_setup(testcase);
+
+ /* We're going to set things up in a state where a circuit will be ready to
+ * be upgraded. Each test can make a single change (or not) that should
+ * block the upgrade.
+ */
+
+ /* First, make all the primary guards confirmed, and down. */
+ data->start = approx_time();
+ entry_guards_note_internet_connectivity(gs);
+ for (i = 0; i < N_PRIMARY; ++i) {
+ entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL, &node, &guard);
+ g = entry_guard_handle_get(guard->guard);
+ make_guard_confirmed(gs, g);
+ entry_guard_failed(&guard);
+ circuit_guard_state_free(guard);
+ }
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,0);
+ /* Grab another couple of guards */
+ data->all_origin_circuits = smartlist_new();
+
+ update_approx_time(data->start + 27);
+ entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &data->guard1_state);
+ origin_circuit_t *circ;
+ data->circ1 = circ = origin_circuit_new();
+ circ->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL;
+ circ->guard_state = data->guard1_state;
+ smartlist_add(data->all_origin_circuits, circ);
+
+ update_approx_time(data->start + 30);
+ entry_guard_pick_for_circuit(gs, GUARD_USAGE_TRAFFIC, NULL,
+ &node, &data->guard2_state);
+ data->circ2 = circ = origin_circuit_new();
+ circ->base_.purpose = CIRCUIT_PURPOSE_C_GENERAL;
+ circ->guard_state = data->guard2_state;
+ smartlist_add(data->all_origin_circuits, circ);
+
+ data->guard1 = entry_guard_handle_get(data->guard1_state->guard);
+ data->guard2 = entry_guard_handle_get(data->guard2_state->guard);
+ tor_assert(data->guard1 != data->guard2);
+ tor_assert(data->guard1_state->state ==
+ GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD);
+ tor_assert(data->guard2_state->state ==
+ GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD);
+
+ guard_usable_t r;
+ update_approx_time(data->start + 32);
+ if (make_circ1_succeed) {
+ r = entry_guard_succeeded(&data->guard1_state);
+ tor_assert(r == GUARD_MAYBE_USABLE_LATER);
+ tor_assert(data->guard1_state->state ==
+ GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD);
+ }
+ update_approx_time(data->start + 33);
+ if (make_circ2_succeed) {
+ r = entry_guard_succeeded(&data->guard2_state);
+ tor_assert(r == GUARD_MAYBE_USABLE_LATER);
+ tor_assert(data->guard2_state->state ==
+ GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD);
+ }
- test_guard->unreachable_since = now - (7*24*60*60 + 1);
- test_guard->last_attempted = now - (36*60*60 + 1);
+ return data;
+}
+static int
+upgrade_circuits_cleanup(const struct testcase_t *testcase, void *ptr)
+{
+ upgrade_circuits_data_t *data = ptr;
+ // circuit_guard_state_free(data->guard1_state); // held in circ1
+ // circuit_guard_state_free(data->guard2_state); // held in circ2
+ guard_selection_free(data->gs);
+ smartlist_free(data->all_origin_circuits);
+ circuit_free(TO_CIRCUIT(data->circ1));
+ circuit_free(TO_CIRCUIT(data->circ2));
+ tor_free(data);
+ return big_fake_network_cleanup(testcase, NULL);
+}
+
+static void
+test_entry_guard_upgrade_a_circuit(void *arg)
+{
+ upgrade_circuits_data_t *data = arg;
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+ /* This is the easy case: we have no COMPLETED circuits, all the
+ * primary guards are down, we have two WAITING circuits: one will
+ * get upgraded to COMPLETED! (The one that started first.)
+ */
- test_guard->unreachable_since = now - (7*24*60*60 + 1);
- test_guard->last_attempted = now - (36*60*60 + 1);
+ smartlist_t *result = smartlist_new();
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc = smartlist_get(result, 0);
- retval = entry_is_time_to_retry(test_guard,now);
- tt_int_op(retval,OP_EQ,1);
+ /* circ1 was started first, so we'll get told to ugrade it... */
+ tt_ptr_op(oc, OP_EQ, data->circ1);
+
+ /* And the guard state should be complete */
+ tt_ptr_op(data->guard1_state, OP_NE, NULL);
+ tt_int_op(data->guard1_state->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
done:
- tor_free(test_guard);
+ smartlist_free(result);
}
-/** XXX Do some tests that entry_is_live() */
static void
-test_entry_is_live(void *arg)
+test_entry_guard_upgrade_blocked_by_live_primary_guards(void *arg)
{
- smartlist_t *our_nodelist = NULL;
- const smartlist_t *all_entry_guards = get_entry_guards();
- const node_t *test_node = NULL;
- const entry_guard_t *test_entry = NULL;
- const char *msg;
- int which_node;
-
- (void) arg;
+ upgrade_circuits_data_t *data = arg;
+
+ /* If any primary guards might be up, we can't upgrade any waiting
+ * circuits.
+ */
+ mark_primary_guards_maybe_reachable(data->gs);
+
+ smartlist_t *result = smartlist_new();
+ int r;
+ setup_capture_of_logs(LOG_DEBUG);
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(result), OP_EQ, 0);
+ expect_log_msg_containing("not all primary guards were definitely down.");
- /* The global entry guards smartlist should be empty now. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ, 0);
+ done:
+ teardown_capture_of_logs();
+ smartlist_free(result);
+}
- /* Walk the nodelist and add all nodes as entry guards. */
- our_nodelist = nodelist_get_list();
- tt_int_op(smartlist_len(our_nodelist), OP_EQ, HELPER_NUMBER_OF_DESCRIPTORS);
+static void
+test_entry_guard_upgrade_blocked_by_lack_of_waiting_circuits(void *arg)
+{
+ upgrade_circuits_data_t *data = arg;
+
+ /* If no circuits are waiting, we can't upgrade anything. (The test
+ * setup in this case was told not to make any of the circuits "waiting".)
+ */
+ smartlist_t *result = smartlist_new();
+ int r;
+ setup_capture_of_logs(LOG_DEBUG);
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(result), OP_EQ, 0);
+ expect_log_msg_containing("Considered upgrading guard-stalled circuits, "
+ "but didn't find any.");
- SMARTLIST_FOREACH_BEGIN(our_nodelist, const node_t *, node) {
- const node_t *node_tmp;
- node_tmp = add_an_entry_guard(node, 0, 1, 0, 0);
- tt_assert(node_tmp);
+ done:
+ teardown_capture_of_logs();
+ smartlist_free(result);
+}
- tt_int_op(node->is_stable, OP_EQ, 0);
- tt_int_op(node->is_fast, OP_EQ, 0);
- } SMARTLIST_FOREACH_END(node);
+static void
+test_entry_guard_upgrade_blocked_by_better_circ_complete(void *arg)
+{
+ upgrade_circuits_data_t *data = arg;
+
+ /* We'll run through the logic of upgrade_a_circuit below...
+ * and then try again to make sure that circ2 isn't also upgraded.
+ */
+
+ smartlist_t *result = smartlist_new();
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc = smartlist_get(result, 0);
+ tt_ptr_op(oc, OP_EQ, data->circ1);
+ tt_ptr_op(data->guard1_state, OP_NE, NULL);
+ tt_int_op(data->guard1_state->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
+
+ /* Now, try again. Make sure that circ2 isn't upgraded. */
+ smartlist_clear(result);
+ setup_capture_of_logs(LOG_DEBUG);
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(result), OP_EQ, 0);
+ expect_log_msg_containing("At least one complete circuit had higher "
+ "priority, so not upgrading.");
- /* Make sure the nodes were added as entry guards. */
- tt_int_op(smartlist_len(all_entry_guards), OP_EQ,
- HELPER_NUMBER_OF_DESCRIPTORS);
+ done:
+ teardown_capture_of_logs();
+ smartlist_free(result);
+}
- /* Now get a random test entry that we will use for this unit test. */
- which_node = 3; /* (chosen by fair dice roll) */
- test_entry = smartlist_get(all_entry_guards, which_node);
+static void
+test_entry_guard_upgrade_not_blocked_by_restricted_circ_complete(void *arg)
+{
+ upgrade_circuits_data_t *data = 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);
+
+ smartlist_t *result = smartlist_new();
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc = smartlist_get(result, 0);
+ tt_ptr_op(oc, OP_EQ, data->circ1);
+ tt_ptr_op(data->guard1_state, OP_NE, NULL);
+ tt_int_op(data->guard1_state->state, OP_EQ, GUARD_CIRC_STATE_COMPLETE);
+
+ /* Now, we try again. Since circ2 has a restriction that circ1 doesn't obey,
+ * circ2 _is_ eligible for upgrade. */
+ smartlist_clear(result);
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc2 = smartlist_get(result, 0);
+ tt_ptr_op(oc2, OP_EQ, data->circ2);
- /* Let's do some entry_is_live() tests! */
+ done:
+ smartlist_free(result);
+}
- /* Require the node to be stable, but it's not. Should fail.
- Also enable 'assume_reachable' because why not. */
- test_node = entry_is_live(test_entry,
- ENTRY_NEED_UPTIME | ENTRY_ASSUME_REACHABLE,
- &msg);
- tt_assert(!test_node);
+static void
+test_entry_guard_upgrade_not_blocked_by_worse_circ_complete(void *arg)
+{
+ upgrade_circuits_data_t *data = arg;
+ smartlist_t *result = smartlist_new();
+ /* here we manually make circ2 COMPLETE, and make sure that circ1
+ * gets made complete anyway, since guard1 has higher priority
+ */
+ update_approx_time(data->start + 300);
+ data->guard2_state->state = GUARD_CIRC_STATE_COMPLETE;
+ data->guard2_state->state_set_at = approx_time();
+ update_approx_time(data->start + 301);
+
+ /* Now, try again. Make sure that circ1 is approved. */
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc = smartlist_get(result, 0);
+ tt_ptr_op(oc, OP_EQ, data->circ1);
- /* Require the node to be fast, but it's not. Should fail. */
- test_node = entry_is_live(test_entry,
- ENTRY_NEED_CAPACITY | ENTRY_ASSUME_REACHABLE,
- &msg);
- tt_assert(!test_node);
+ done:
+ smartlist_free(result);
+}
- /* Don't impose any restrictions on the node. Should succeed. */
- test_node = entry_is_live(test_entry, 0, &msg);
- tt_assert(test_node);
- tt_ptr_op(test_node, OP_EQ, node_get_by_id(test_entry->identity));
+static void
+test_entry_guard_upgrade_blocked_by_better_circ_pending(void *arg)
+{
+ upgrade_circuits_data_t *data = arg;
+
+ /* circ2 is done, but circ1 is still pending. Since circ1 is better,
+ * we won't upgrade circ2. */
+
+ /* XXXX Prop271 -- this is a kludge. I'm making sure circ1 _is_ better,
+ * by messing with the guards' confirmed_idx */
+ make_guard_confirmed(data->gs, data->guard1);
+ {
+ int tmp;
+ tmp = data->guard1->confirmed_idx;
+ data->guard1->confirmed_idx = data->guard2->confirmed_idx;
+ data->guard2->confirmed_idx = tmp;
+ }
- /* Require descriptor for this node. It has one so it should succeed. */
- test_node = entry_is_live(test_entry, ENTRY_NEED_DESCRIPTOR, &msg);
- tt_assert(test_node);
- tt_ptr_op(test_node, OP_EQ, node_get_by_id(test_entry->identity));
+ smartlist_t *result = smartlist_new();
+ setup_capture_of_logs(LOG_DEBUG);
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(smartlist_len(result), OP_EQ, 0);
+ expect_log_msg_containing("but 1 pending circuit(s) had higher guard "
+ "priority, so not upgrading.");
done:
- ; /* XXX */
+ teardown_capture_of_logs();
+ smartlist_free(result);
}
-#define TEST_IPV4_ADDR "123.45.67.89"
-#define TEST_IPV6_ADDR "[1234:5678:90ab:cdef::]"
-
static void
-test_node_preferred_orport(void *arg)
+test_entry_guard_upgrade_not_blocked_by_restricted_circ_pending(void *arg)
{
- (void)arg;
- tor_addr_t ipv4_addr;
- const uint16_t ipv4_port = 4444;
- tor_addr_t ipv6_addr;
- const uint16_t ipv6_port = 6666;
- routerinfo_t node_ri;
- node_t node;
- tor_addr_port_t ap;
+ upgrade_circuits_data_t *data = arg;
+ /* circ2 is done, but circ1 is still pending. But when there is a
+ restriction on circ2 that circ1 can't satisfy, circ1 can't block
+ circ2. */
+
+ /* XXXX Prop271 -- this is a kludge. I'm making sure circ1 _is_ better,
+ * by messing with the guards' confirmed_idx */
+ make_guard_confirmed(data->gs, data->guard1);
+ {
+ int tmp;
+ tmp = data->guard1->confirmed_idx;
+ data->guard1->confirmed_idx = data->guard2->confirmed_idx;
+ data->guard2->confirmed_idx = tmp;
+ }
- /* Setup options */
- memset(&mocked_options, 0, sizeof(mocked_options));
- /* We don't test ClientPreferIPv6ORPort here, because it's used in
- * nodelist_set_consensus to setup node.ipv6_preferred, which we set
- * directly. */
- MOCK(get_options, mock_get_options);
+ data->guard2_state->restrictions =
+ tor_malloc_zero(sizeof(entry_guard_restriction_t));
+ memcpy(data->guard2_state->restrictions->exclude_id,
+ data->guard1->identity, DIGEST_LEN);
+
+ smartlist_t *result = smartlist_new();
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc = smartlist_get(result, 0);
+ tt_ptr_op(oc, OP_EQ, data->circ2);
- /* Setup IP addresses */
- tor_addr_parse(&ipv4_addr, TEST_IPV4_ADDR);
- tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR);
+ done:
+ smartlist_free(result);
+}
- /* Setup node_ri */
- memset(&node_ri, 0, sizeof(node_ri));
- node_ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
- node_ri.or_port = ipv4_port;
- tor_addr_copy(&node_ri.ipv6_addr, &ipv6_addr);
- node_ri.ipv6_orport = ipv6_port;
+static void
+test_entry_guard_upgrade_not_blocked_by_worse_circ_pending(void *arg)
+{
+ upgrade_circuits_data_t *data = arg;
+
+ /* circ1 is done, but circ2 is still pending. Since circ1 is better,
+ * we will upgrade it. */
+ smartlist_t *result = smartlist_new();
+ int r;
+ r = entry_guards_upgrade_waiting_circuits(data->gs,
+ data->all_origin_circuits,
+ result);
+ tt_int_op(r, OP_EQ, 1);
+ tt_int_op(smartlist_len(result), OP_EQ, 1);
+ origin_circuit_t *oc = smartlist_get(result, 0);
+ tt_ptr_op(oc, OP_EQ, data->circ1);
- /* Setup node */
- memset(&node, 0, sizeof(node));
- node.ri = &node_ri;
+ done:
+ smartlist_free(result);
+}
- /* Check the preferred address is IPv4 if we're only using IPv4, regardless
- * of whether we prefer it or not */
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientUseIPv6 = 0;
- node.ipv6_preferred = 0;
- node_get_pref_orport(&node, &ap);
- tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
- tt_assert(ap.port == ipv4_port);
+static void
+test_enty_guard_should_expire_waiting(void *arg)
+{
+ (void)arg;
+ circuit_guard_state_t *fake_state = tor_malloc_zero(sizeof(*fake_state));
+ /* We'll leave "guard" unset -- it won't matter here. */
- node.ipv6_preferred = 1;
- node_get_pref_orport(&node, &ap);
- tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
- tt_assert(ap.port == ipv4_port);
+ /* No state? Can't expire. */
+ tt_assert(! entry_guard_state_should_expire(NULL));
- /* Check the preferred address is IPv4 if we're using IPv4 and IPv6, but
- * don't prefer the IPv6 address */
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientUseIPv6 = 1;
- node.ipv6_preferred = 0;
- node_get_pref_orport(&node, &ap);
- tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
- tt_assert(ap.port == ipv4_port);
+ /* Let's try one that expires. */
+ fake_state->state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD;
+ fake_state->state_set_at =
+ approx_time() - DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT - 1;
- /* Check the preferred address is IPv6 if we prefer it and
- * ClientUseIPv6 is 1, regardless of ClientUseIPv4 */
- mocked_options.ClientUseIPv4 = 1;
- mocked_options.ClientUseIPv6 = 1;
- node.ipv6_preferred = 1;
- node_get_pref_orport(&node, &ap);
- tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
- tt_assert(ap.port == ipv6_port);
+ tt_assert(entry_guard_state_should_expire(fake_state));
- mocked_options.ClientUseIPv4 = 0;
- node_get_pref_orport(&node, &ap);
- tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
- tt_assert(ap.port == ipv6_port);
+ /* But it wouldn't expire if we changed the state. */
+ fake_state->state = GUARD_CIRC_STATE_USABLE_IF_NO_BETTER_GUARD;
+ tt_assert(! entry_guard_state_should_expire(fake_state));
- /* Check the preferred address is IPv6 if we don't prefer it, but
- * ClientUseIPv4 is 0 */
- mocked_options.ClientUseIPv4 = 0;
- mocked_options.ClientUseIPv6 = 1;
- node.ipv6_preferred = fascist_firewall_prefer_ipv6_orport(&mocked_options);
- node_get_pref_orport(&node, &ap);
- tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
- tt_assert(ap.port == ipv6_port);
+ /* And it wouldn't have expired a few seconds ago. */
+ fake_state->state = GUARD_CIRC_STATE_WAITING_FOR_BETTER_GUARD;
+ fake_state->state_set_at =
+ approx_time() - DFLT_NONPRIMARY_GUARD_IDLE_TIMEOUT + 5;
+ tt_assert(! entry_guard_state_should_expire(fake_state));
done:
- UNMOCK(get_options);
+ tor_free(fake_state);
}
-static const struct testcase_setup_t fake_network = {
- fake_network_setup, fake_network_cleanup
+static const struct testcase_setup_t big_fake_network = {
+ big_fake_network_setup, big_fake_network_cleanup
+};
+
+static const struct testcase_setup_t upgrade_circuits = {
+ upgrade_circuits_setup, upgrade_circuits_cleanup
};
+#define BFN_TEST(name) \
+ { #name, test_entry_guard_ ## name, TT_FORK, &big_fake_network, NULL }
+
+#define UPGRADE_TEST(name, arg) \
+ { #name, test_entry_guard_ ## name, TT_FORK, &upgrade_circuits, \
+ (void*)(arg) }
+
struct testcase_t entrynodes_tests[] = {
- { "entry_is_time_to_retry", test_entry_is_time_to_retry,
- TT_FORK, NULL, NULL },
- { "choose_random_entry_no_guards", test_choose_random_entry_no_guards,
- TT_FORK, &fake_network, NULL },
- { "choose_random_entry_one_possibleguard",
- test_choose_random_entry_one_possible_guard,
- TT_FORK, &fake_network, NULL },
- { "populate_live_entry_guards_1guard",
- test_populate_live_entry_guards_1guard,
- TT_FORK, &fake_network, NULL },
- { "populate_live_entry_guards_3guards",
- test_populate_live_entry_guards_3guards,
- TT_FORK, &fake_network, NULL },
- { "entry_guards_parse_state_simple",
- test_entry_guards_parse_state_simple,
- TT_FORK, &fake_network, NULL },
- { "entry_guards_parse_state_pathbias",
- test_entry_guards_parse_state_pathbias,
- TT_FORK, &fake_network, NULL },
- { "entry_guards_set_from_config",
- test_entry_guards_set_from_config,
- TT_FORK, &fake_network, NULL },
- { "entry_is_live",
- test_entry_is_live,
- TT_FORK, &fake_network, NULL },
{ "node_preferred_orport",
test_node_preferred_orport,
0, NULL, NULL },
+ { "entry_guard_describe", test_entry_guard_describe, 0, NULL, NULL },
+ { "randomize_time", test_entry_guard_randomize_time, 0, NULL, NULL },
+ { "encode_for_state_minimal",
+ test_entry_guard_encode_for_state_minimal, 0, NULL, NULL },
+ { "encode_for_state_maximal",
+ test_entry_guard_encode_for_state_maximal, 0, NULL, NULL },
+ { "parse_from_state_minimal",
+ test_entry_guard_parse_from_state_minimal, 0, NULL, NULL },
+ { "parse_from_state_maximal",
+ test_entry_guard_parse_from_state_maximal, 0, NULL, NULL },
+ { "parse_from_state_failure",
+ test_entry_guard_parse_from_state_failure, 0, NULL, NULL },
+ { "parse_from_state_partial_failure",
+ test_entry_guard_parse_from_state_partial_failure, 0, NULL, NULL },
+ { "parse_from_state_full",
+ test_entry_guard_parse_from_state_full, TT_FORK, NULL, NULL },
+ { "parse_from_state_broken",
+ test_entry_guard_parse_from_state_broken, TT_FORK, NULL, NULL },
+ { "get_guard_selection_by_name",
+ test_entry_guard_get_guard_selection_by_name, TT_FORK, NULL, NULL },
+ BFN_TEST(choose_selection_initial),
+ BFN_TEST(add_single_guard),
+ BFN_TEST(node_filter),
+ BFN_TEST(expand_sample),
+ BFN_TEST(expand_sample_small_net),
+ BFN_TEST(update_from_consensus_status),
+ BFN_TEST(update_from_consensus_repair),
+ BFN_TEST(update_from_consensus_remove),
+ BFN_TEST(confirming_guards),
+ BFN_TEST(sample_reachable_filtered),
+ BFN_TEST(sample_reachable_filtered_empty),
+ BFN_TEST(retry_unreachable),
+ BFN_TEST(manage_primary),
+ { "guard_preferred", test_entry_guard_guard_preferred, TT_FORK, NULL, NULL },
+ BFN_TEST(select_for_circuit_no_confirmed),
+ BFN_TEST(select_for_circuit_confirmed),
+ BFN_TEST(select_for_circuit_highlevel_primary),
+ BFN_TEST(select_for_circuit_highlevel_confirm_other),
+ BFN_TEST(select_for_circuit_highlevel_primary_retry),
+ BFN_TEST(select_and_cancel),
+ BFN_TEST(drop_guards),
+
+ UPGRADE_TEST(upgrade_a_circuit, "c1-done c2-done"),
+ UPGRADE_TEST(upgrade_blocked_by_live_primary_guards, "c1-done c2-done"),
+ UPGRADE_TEST(upgrade_blocked_by_lack_of_waiting_circuits, ""),
+ UPGRADE_TEST(upgrade_blocked_by_better_circ_complete, "c1-done c2-done"),
+ UPGRADE_TEST(upgrade_not_blocked_by_restricted_circ_complete,
+ "c1-done c2-done"),
+ UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_complete, "c1-done c2-done"),
+ UPGRADE_TEST(upgrade_blocked_by_better_circ_pending, "c2-done"),
+ UPGRADE_TEST(upgrade_not_blocked_by_restricted_circ_pending,
+ "c2-done"),
+ UPGRADE_TEST(upgrade_not_blocked_by_worse_circ_pending, "c1-done"),
+ { "should_expire_waiting", test_enty_guard_should_expire_waiting, TT_FORK,
+ NULL, NULL },
+
END_OF_TESTCASES
};
diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c
index ae9fc7a243..5b84366e6d 100644
--- a/src/test/test_helpers.c
+++ b/src/test/test_helpers.c
@@ -10,8 +10,10 @@
#include "orconfig.h"
#include "or.h"
+#include "relay.h"
#include "routerlist.h"
#include "nodelist.h"
+#include "buffers.h"
#include "test.h"
#include "test_helpers.h"
@@ -22,6 +24,8 @@ DISABLE_GCC_WARNING(overlength-strings)
* at large. */
#endif
#include "test_descriptors.inc"
+#include "or.h"
+#include "circuitlist.h"
#ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS
ENABLE_GCC_WARNING(overlength-strings)
#endif
@@ -92,3 +96,50 @@ helper_setup_fake_routerlist(void)
UNMOCK(router_descriptor_is_older_than);
}
+void
+connection_write_to_buf_mock(const char *string, size_t len,
+ connection_t *conn, int zlib)
+{
+ (void) zlib;
+
+ tor_assert(string);
+ tor_assert(conn);
+
+ write_to_buf(string, len, conn->outbuf);
+}
+
+/* Set up a fake origin circuit with the specified number of cells,
+ * Return a pointer to the newly-created dummy circuit */
+circuit_t *
+dummy_origin_circuit_new(int n_cells)
+{
+ origin_circuit_t *circ = origin_circuit_new();
+ int i;
+ cell_t cell;
+
+ for (i=0; i < n_cells; ++i) {
+ crypto_rand((void*)&cell, sizeof(cell));
+ cell_queue_append_packed_copy(TO_CIRCUIT(circ),
+ &TO_CIRCUIT(circ)->n_chan_cells,
+ 1, &cell, 1, 0);
+ }
+
+ TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_C_GENERAL;
+ return TO_CIRCUIT(circ);
+}
+
+/** Mock-replacement. As tor_addr_lookup, but always fails on any
+ * address containing a !. This is necessary for running the unit tests
+ * on networks where DNS hijackers think it's helpful to give answers
+ * for things like 1.2.3.4.5 or "invalidstuff!!"
+ */
+int
+mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
+ uint16_t family, tor_addr_t *out)
+{
+ if (name && strchr(name, '!')) {
+ return -1;
+ }
+ return tor_addr_lookup__real(name, family, out);
+}
+
diff --git a/src/test/test_helpers.h b/src/test/test_helpers.h
index 684375e1b1..c6d4d9c41f 100644
--- a/src/test/test_helpers.h
+++ b/src/test/test_helpers.h
@@ -6,11 +6,20 @@
const char *get_yesterday_date_str(void);
+circuit_t * dummy_origin_circuit_new(int num_cells);
+
/* Number of descriptors contained in test_descriptors.txt. */
#define HELPER_NUMBER_OF_DESCRIPTORS 8
void helper_setup_fake_routerlist(void);
+#define GET(path) "GET " path " HTTP/1.0\r\n\r\n"
+void connection_write_to_buf_mock(const char *string, size_t len,
+ connection_t *conn, int zlib);
+
+int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
+ uint16_t family, tor_addr_t *out);
+
extern const char TEST_DESCRIPTORS[];
#endif
diff --git a/src/test/test_hs.c b/src/test/test_hs.c
index 8237bbc50e..fbaabe91d8 100644
--- a/src/test/test_hs.c
+++ b/src/test/test_hs.c
@@ -14,6 +14,7 @@
#include "test.h"
#include "control.h"
#include "config.h"
+#include "hs_common.h"
#include "rendcommon.h"
#include "rendservice.h"
#include "routerset.h"
@@ -136,7 +137,7 @@ test_hs_desc_event(void *arg)
#define STR_DESC_ID_BASE32 "hba3gmcgpfivzfhx5rtfqkfdhv65yrj3"
int ret;
- rend_data_t rend_query;
+ rend_data_v2_t rend_query;
const char *expected_msg;
char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1];
@@ -148,12 +149,13 @@ test_hs_desc_event(void *arg)
/* setup rend_query struct */
memset(&rend_query, 0, sizeof(rend_query));
+ rend_query.base_.version = 2;
strncpy(rend_query.onion_address, STR_HS_ADDR,
REND_SERVICE_ID_LEN_BASE32+1);
rend_query.auth_type = REND_NO_AUTH;
- rend_query.hsdirs_fp = smartlist_new();
- smartlist_add(rend_query.hsdirs_fp, tor_memdup(HSDIR_EXIST_ID,
- DIGEST_LEN));
+ rend_query.base_.hsdirs_fp = smartlist_new();
+ smartlist_add(rend_query.base_.hsdirs_fp, tor_memdup(HSDIR_EXIST_ID,
+ DIGEST_LEN));
/* Compute descriptor ID for replica 0, should be STR_DESC_ID_BASE32. */
ret = rend_compute_v2_desc_id(rend_query.descriptor_id[0],
@@ -167,7 +169,7 @@ test_hs_desc_event(void *arg)
sizeof(desc_id_base32));
/* test request event */
- control_event_hs_descriptor_requested(&rend_query, HSDIR_EXIST_ID,
+ control_event_hs_descriptor_requested(&rend_query.base_, HSDIR_EXIST_ID,
STR_DESC_ID_BASE32);
expected_msg = "650 HS_DESC REQUESTED "STR_HS_ADDR" NO_AUTH "\
STR_HSDIR_EXIST_LONGNAME " " STR_DESC_ID_BASE32 "\r\n";
@@ -178,7 +180,7 @@ test_hs_desc_event(void *arg)
/* test received event */
rend_query.auth_type = REND_BASIC_AUTH;
control_event_hs_descriptor_received(rend_query.onion_address,
- &rend_query, HSDIR_EXIST_ID);
+ &rend_query.base_, HSDIR_EXIST_ID);
expected_msg = "650 HS_DESC RECEIVED "STR_HS_ADDR" BASIC_AUTH "\
STR_HSDIR_EXIST_LONGNAME " " STR_DESC_ID_BASE32"\r\n";
tt_assert(received_msg);
@@ -187,7 +189,7 @@ test_hs_desc_event(void *arg)
/* test failed event */
rend_query.auth_type = REND_STEALTH_AUTH;
- control_event_hs_descriptor_failed(&rend_query,
+ control_event_hs_descriptor_failed(&rend_query.base_,
HSDIR_NONE_EXIST_ID,
"QUERY_REJECTED");
expected_msg = "650 HS_DESC FAILED "STR_HS_ADDR" STEALTH_AUTH "\
@@ -198,7 +200,7 @@ test_hs_desc_event(void *arg)
/* test invalid auth type */
rend_query.auth_type = 999;
- control_event_hs_descriptor_failed(&rend_query,
+ control_event_hs_descriptor_failed(&rend_query.base_,
HSDIR_EXIST_ID,
"QUERY_REJECTED");
expected_msg = "650 HS_DESC FAILED "STR_HS_ADDR" UNKNOWN "\
@@ -221,8 +223,8 @@ test_hs_desc_event(void *arg)
tt_str_op(received_msg, OP_EQ, exp_msg);
tor_free(received_msg);
tor_free(exp_msg);
- SMARTLIST_FOREACH(rend_query.hsdirs_fp, char *, d, tor_free(d));
- smartlist_free(rend_query.hsdirs_fp);
+ SMARTLIST_FOREACH(rend_query.base_.hsdirs_fp, char *, d, tor_free(d));
+ smartlist_free(rend_query.base_.hsdirs_fp);
done:
UNMOCK(queue_control_event_string);
@@ -322,42 +324,46 @@ test_hs_rend_data(void *arg)
client = rend_data_client_create(STR_HS_ADDR, desc_id, client_cookie,
REND_NO_AUTH);
tt_assert(client);
- tt_int_op(client->auth_type, ==, REND_NO_AUTH);
- tt_str_op(client->onion_address, OP_EQ, STR_HS_ADDR);
- tt_mem_op(client->desc_id_fetch, OP_EQ, desc_id, sizeof(desc_id));
- tt_mem_op(client->descriptor_cookie, OP_EQ, client_cookie,
+ rend_data_v2_t *client_v2 = TO_REND_DATA_V2(client);
+ tt_int_op(client_v2->auth_type, ==, 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);
for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) {
- int ret = rend_compute_v2_desc_id(desc_id, client->onion_address,
- client->descriptor_cookie, now, 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_mem_op(client->descriptor_id[rep], OP_EQ, desc_id, sizeof(desc_id));
+ 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->rend_pk_digest), ==, 1);
+ tt_int_op(tor_digest_is_zero(client_v2->rend_pk_digest), ==, 1);
tt_int_op(tor_digest_is_zero(client->rend_cookie), ==, 1);
/* Test dup(). */
client_dup = rend_data_dup(client);
tt_assert(client_dup);
- tt_int_op(client_dup->auth_type, ==, client->auth_type);
- tt_str_op(client_dup->onion_address, OP_EQ, client->onion_address);
- tt_mem_op(client_dup->desc_id_fetch, OP_EQ, client->desc_id_fetch,
- sizeof(client_dup->desc_id_fetch));
- tt_mem_op(client_dup->descriptor_cookie, OP_EQ, client->descriptor_cookie,
- sizeof(client_dup->descriptor_cookie));
+ 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_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));
+ tt_mem_op(client_dup_v2->descriptor_cookie, OP_EQ,
+ client_v2->descriptor_cookie,
+ sizeof(client_dup_v2->descriptor_cookie));
tt_assert(client_dup->hsdirs_fp);
tt_int_op(smartlist_len(client_dup->hsdirs_fp), ==, 0);
for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) {
- tt_mem_op(client_dup->descriptor_id[rep], OP_EQ,
- client->descriptor_id[rep], DIGEST_LEN);
+ 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->rend_pk_digest), ==, 1);
+ 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);
rend_data_free(client);
client = NULL;
@@ -373,18 +379,19 @@ test_hs_rend_data(void *arg)
* zeroed out. */
client = rend_data_client_create(NULL, desc_id, NULL, REND_BASIC_AUTH);
tt_assert(client);
- tt_int_op(client->auth_type, ==, REND_BASIC_AUTH);
- tt_int_op(strlen(client->onion_address), ==, 0);
- tt_mem_op(client->desc_id_fetch, OP_EQ, desc_id, sizeof(desc_id));
- tt_int_op(tor_mem_is_zero(client->descriptor_cookie,
- sizeof(client->descriptor_cookie)), ==, 1);
+ 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_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);
tt_assert(client->hsdirs_fp);
tt_int_op(smartlist_len(client->hsdirs_fp), ==, 0);
for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) {
- tt_int_op(tor_digest_is_zero(client->descriptor_id[rep]), ==, 1);
+ tt_int_op(tor_digest_is_zero(client_v2->descriptor_id[rep]), ==, 1);
}
/* The rest should be zeroed because this is a client request. */
- tt_int_op(tor_digest_is_zero(client->rend_pk_digest), ==, 1);
+ tt_int_op(tor_digest_is_zero(client_v2->rend_pk_digest), ==, 1);
tt_int_op(tor_digest_is_zero(client->rend_cookie), ==, 1);
rend_data_free(client);
client = NULL;
@@ -398,37 +405,39 @@ test_hs_rend_data(void *arg)
service = rend_data_service_create(STR_HS_ADDR, rend_pk_digest,
rend_cookie, REND_NO_AUTH);
tt_assert(service);
- tt_int_op(service->auth_type, ==, REND_NO_AUTH);
- tt_str_op(service->onion_address, OP_EQ, STR_HS_ADDR);
- tt_mem_op(service->rend_pk_digest, OP_EQ, rend_pk_digest,
+ rend_data_v2_t *service_v2 = TO_REND_DATA_V2(service);
+ tt_int_op(service_v2->auth_type, ==, 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);
for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) {
- tt_int_op(tor_digest_is_zero(service->descriptor_id[rep]), ==, 1);
+ tt_int_op(tor_digest_is_zero(service_v2->descriptor_id[rep]), ==, 1);
}
/* The rest should be zeroed because this is a service request. */
- tt_int_op(tor_digest_is_zero(service->descriptor_cookie), ==, 1);
- tt_int_op(tor_digest_is_zero(service->desc_id_fetch), ==, 1);
+ 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);
/* 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->auth_type, ==, service->auth_type);
- tt_str_op(service_dup->onion_address, OP_EQ, service->onion_address);
- tt_mem_op(service_dup->rend_pk_digest, OP_EQ, service->rend_pk_digest,
- sizeof(service_dup->rend_pk_digest));
+ tt_int_op(service_dup_v2->auth_type, ==, 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);
for (rep = 0; rep < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; rep++) {
- tt_int_op(tor_digest_is_zero(service_dup->descriptor_id[rep]), ==, 1);
+ tt_int_op(tor_digest_is_zero(service_dup_v2->descriptor_id[rep]), ==, 1);
}
/* The rest should be zeroed because this is a service request. */
- tt_int_op(tor_digest_is_zero(service_dup->descriptor_cookie), ==, 1);
- tt_int_op(tor_digest_is_zero(service_dup->desc_id_fetch), ==, 1);
+ 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);
done:
rend_data_free(service);
@@ -778,6 +787,126 @@ test_single_onion_poisoning(void *arg)
tor_free(mock_options->DataDirectory);
}
+static rend_service_t *
+helper_create_rend_service(const char *path)
+{
+ rend_service_t *s = tor_malloc_zero(sizeof(rend_service_t));
+ s->ports = smartlist_new();
+ s->intro_nodes = smartlist_new();
+ s->expiring_nodes = smartlist_new();
+ if (path) {
+ s->directory = tor_strdup(path);
+ }
+ return s;
+}
+
+static void
+test_prune_services_on_reload(void *arg)
+{
+ smartlist_t *new = smartlist_new(), *old = smartlist_new();
+ /* Non ephemeral service. */
+ rend_service_t *s1 = helper_create_rend_service("SomePath");
+ /* Create a non ephemeral service with the _same_ path as so we can test the
+ * transfer of introduction point between the same services on reload. */
+ rend_service_t *s2 = helper_create_rend_service(s1->directory);
+ /* Ephemeral service (directory is NULL). */
+ rend_service_t *e1 = helper_create_rend_service(NULL);
+ rend_service_t *e2 = helper_create_rend_service(NULL);
+
+ (void) arg;
+
+ {
+ /* Add both services to the old list. */
+ smartlist_add(old, s1);
+ smartlist_add(old, e1);
+ /* Only put the non ephemeral in the new list. */
+ smartlist_add(new, s1);
+ prune_services_on_reload(old, new);
+ /* We expect that the ephemeral one is in the new list but removed from
+ * the old one. */
+ tt_int_op(smartlist_len(old), OP_EQ, 1);
+ tt_assert(smartlist_get(old, 0) == s1);
+ tt_int_op(smartlist_len(new), OP_EQ, 2);
+ tt_assert(smartlist_get(new, 0) == s1);
+ tt_assert(smartlist_get(new, 1) == e1);
+ /* Cleanup for next test. */
+ smartlist_clear(new);
+ smartlist_clear(old);
+ }
+
+ {
+ /* This test will make sure that only the ephemeral service is kept if the
+ * new list is empty. The old list should contain only the non ephemeral
+ * one. */
+ smartlist_add(old, s1);
+ smartlist_add(old, e1);
+ prune_services_on_reload(old, new);
+ tt_int_op(smartlist_len(old), OP_EQ, 1);
+ tt_assert(smartlist_get(old, 0) == s1);
+ tt_int_op(smartlist_len(new), OP_EQ, 1);
+ tt_assert(smartlist_get(new, 0) == e1);
+ /* Cleanup for next test. */
+ smartlist_clear(new);
+ smartlist_clear(old);
+ }
+
+ {
+ /* This test makes sure that the new list stays the same even from the old
+ * list being completely different. */
+ smartlist_add(new, s1);
+ smartlist_add(new, e1);
+ prune_services_on_reload(old, new);
+ tt_int_op(smartlist_len(old), OP_EQ, 0);
+ tt_int_op(smartlist_len(new), OP_EQ, 2);
+ tt_assert(smartlist_get(new, 0) == s1);
+ tt_assert(smartlist_get(new, 1) == e1);
+ /* Cleanup for next test. */
+ smartlist_clear(new);
+ }
+
+ {
+ rend_intro_point_t ip1;
+ /* This IP should be found in the s2 service after pruning. */
+ smartlist_add(s1->intro_nodes, &ip1);
+ /* Setup our list. */
+ smartlist_add(old, s1);
+ smartlist_add(new, s2);
+ prune_services_on_reload(old, new);
+ tt_int_op(smartlist_len(old), OP_EQ, 1);
+ /* Intro nodes have been moved to the s2 in theory so it must be empty. */
+ tt_int_op(smartlist_len(s1->intro_nodes), OP_EQ, 0);
+ tt_int_op(smartlist_len(new), OP_EQ, 1);
+ rend_service_t *elem = smartlist_get(new, 0);
+ tt_assert(elem);
+ tt_assert(elem == s2);
+ tt_int_op(smartlist_len(elem->intro_nodes), OP_EQ, 1);
+ tt_assert(smartlist_get(elem->intro_nodes, 0) == &ip1);
+ smartlist_clear(s1->intro_nodes);
+ smartlist_clear(s2->intro_nodes);
+ /* Cleanup for next test. */
+ smartlist_clear(new);
+ smartlist_clear(old);
+ }
+
+ {
+ /* Test two ephemeral services. */
+ smartlist_add(old, e1);
+ smartlist_add(old, e2);
+ prune_services_on_reload(old, new);
+ /* Check if they've all been transfered. */
+ tt_int_op(smartlist_len(old), OP_EQ, 0);
+ tt_int_op(smartlist_len(new), OP_EQ, 2);
+ }
+
+ done:
+ rend_service_free(s1);
+ rend_service_free(s2);
+ rend_service_free(e1);
+ rend_service_free(e2);
+ smartlist_free(new);
+ smartlist_free(old);
+}
+
struct testcase_t hs_tests[] = {
{ "hs_rend_data", test_hs_rend_data, TT_FORK,
NULL, NULL },
@@ -798,6 +927,9 @@ struct testcase_t hs_tests[] = {
TT_FORK, &passthrough_setup, (void*)(CREATE_HS_DIR2) },
{ "single_onion_poisoning_create_dir_both", test_single_onion_poisoning,
TT_FORK, &passthrough_setup, (void*)(CREATE_HS_DIR1 | CREATE_HS_DIR2) },
+ { "prune_services_on_reload", test_prune_services_on_reload, TT_FORK,
+ NULL, NULL },
+
END_OF_TESTCASES
};
diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c
new file mode 100644
index 0000000000..1943d0ffac
--- /dev/null
+++ b/src/test/test_hs_cache.c
@@ -0,0 +1,528 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_cache.c
+ * \brief Test hidden service caches.
+ */
+
+#define CONNECTION_PRIVATE
+#define HS_CACHE_PRIVATE
+
+#include "ed25519_cert.h"
+#include "hs_cache.h"
+#include "rendcache.h"
+#include "directory.h"
+#include "connection.h"
+
+#include "test_helpers.h"
+#include "test.h"
+
+/* Build an intro point using a blinded key and an address. */
+static hs_desc_intro_point_t *
+helper_build_intro_point(const ed25519_keypair_t *blinded_kp,
+ const char *addr)
+{
+ 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_link_specifier_t *ls = tor_malloc_zero(sizeof(*ls));
+ ls->u.ap.port = 9001;
+ int family = tor_addr_parse(&ls->u.ap.addr, addr);
+ switch (family) {
+ case AF_INET:
+ ls->type = LS_IPV4;
+ break;
+ case AF_INET6:
+ ls->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);
+ }
+
+ ret = ed25519_keypair_generate(&auth_kp, 0);
+ tt_int_op(ret, ==, 0);
+ ip->auth_key_cert = tor_cert_create(blinded_kp, CERT_TYPE_AUTH_HS_IP_KEY,
+ &auth_kp.pubkey, time(NULL),
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ tt_assert(ip->auth_key_cert);
+
+ ret = curve25519_keypair_generate(&ip->enc_key.curve25519, 0);
+ tt_int_op(ret, ==, 0);
+ ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519;
+ intro_point = ip;
+ done:
+ return intro_point;
+}
+
+/* Return a valid hs_descriptor_t object. */
+static hs_descriptor_t *
+helper_build_hs_desc(uint64_t revision_counter, uint32_t lifetime,
+ ed25519_public_key_t *signing_pubkey)
+{
+ int ret;
+ ed25519_keypair_t blinded_kp;
+ hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc));
+
+ desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX;
+
+ /* Copy only the public key into the descriptor. */
+ memcpy(&desc->plaintext_data.signing_pubkey, signing_pubkey,
+ sizeof(ed25519_public_key_t));
+
+ ret = ed25519_keypair_generate(&blinded_kp, 0);
+ tt_int_op(ret, ==, 0);
+ /* Copy only the public key into the descriptor. */
+ memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey,
+ sizeof(ed25519_public_key_t));
+
+ desc->plaintext_data.signing_key_cert =
+ tor_cert_create(&blinded_kp, CERT_TYPE_SIGNING_HS_DESC, signing_pubkey,
+ time(NULL), 3600, CERT_FLAG_INCLUDE_SIGNING_KEY);
+ tt_assert(desc->plaintext_data.signing_key_cert);
+ desc->plaintext_data.revision_counter = revision_counter;
+ desc->plaintext_data.lifetime_sec = lifetime;
+
+ /* Setup encrypted data section. */
+ desc->encrypted_data.create2_ntor = 1;
+ desc->encrypted_data.auth_types = smartlist_new();
+ smartlist_add(desc->encrypted_data.auth_types, tor_strdup("ed25519"));
+ desc->encrypted_data.intro_points = smartlist_new();
+ /* Add an intro point. */
+ smartlist_add(desc->encrypted_data.intro_points,
+ helper_build_intro_point(&blinded_kp, "1.2.3.4"));
+
+ descp = desc;
+ done:
+ return descp;
+}
+
+/* Static variable used to encoded the HSDir query. */
+static char query_b64[256];
+
+/* Build an HSDir query using a ed25519 public key. */
+static const char *
+helper_get_hsdir_query(const hs_descriptor_t *desc)
+{
+ ed25519_public_to_base64(query_b64, &desc->plaintext_data.blinded_pubkey);
+ return query_b64;
+}
+
+static void
+init_test(void)
+{
+ /* Always needed. Initialize the subsystem. */
+ hs_cache_init();
+ /* We need the v2 cache since our OOM and cache cleanup does poke at it. */
+ rend_cache_init();
+}
+
+static void
+test_directory(void *arg)
+{
+ int ret;
+ size_t oom_size;
+ char *desc1_str = NULL;
+ const char *desc_out;
+ ed25519_keypair_t signing_kp1;
+ hs_descriptor_t *desc1 = NULL;
+
+ (void) arg;
+
+ init_test();
+ /* Generate a valid descriptor with normal values. */
+ ret = ed25519_keypair_generate(&signing_kp1, 0);
+ tt_int_op(ret, ==, 0);
+ desc1 = helper_build_hs_desc(42, 3 * 60 * 60, &signing_kp1.pubkey);
+ tt_assert(desc1);
+ ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str);
+ tt_int_op(ret, OP_EQ, 0);
+
+ /* Very first basic test, should be able to be stored, survive a
+ * clean, found with a lookup and then cleaned by our OOM. */
+ {
+ ret = hs_cache_store_as_dir(desc1_str);
+ tt_int_op(ret, OP_EQ, 0);
+ /* Re-add, it should fail since we already have it. */
+ ret = hs_cache_store_as_dir(desc1_str);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Try to clean now which should be fine, there is at worst few seconds
+ * between the store and this call. */
+ hs_cache_clean_as_dir(time(NULL));
+ /* We should find it in our cache. */
+ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), &desc_out);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(desc_out, OP_EQ, desc1_str);
+ /* 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);
+ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), NULL);
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Store two descriptors and remove the expiring one only. */
+ {
+ ed25519_keypair_t signing_kp_zero;
+ ret = ed25519_keypair_generate(&signing_kp_zero, 0);
+ tt_int_op(ret, ==, 0);
+ hs_descriptor_t *desc_zero_lifetime;
+ desc_zero_lifetime = helper_build_hs_desc(1, 0, &signing_kp_zero.pubkey);
+ tt_assert(desc_zero_lifetime);
+ char *desc_zero_lifetime_str;
+ ret = hs_desc_encode_descriptor(desc_zero_lifetime, &signing_kp_zero,
+ &desc_zero_lifetime_str);
+ tt_int_op(ret, OP_EQ, 0);
+
+ ret = hs_cache_store_as_dir(desc1_str);
+ tt_int_op(ret, OP_EQ, 0);
+ ret = hs_cache_store_as_dir(desc_zero_lifetime_str);
+ tt_int_op(ret, OP_EQ, 0);
+ /* This one should clear out our zero lifetime desc. */
+ hs_cache_clean_as_dir(time(NULL));
+ /* We should find desc1 in our cache. */
+ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), &desc_out);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(desc_out, OP_EQ, desc1_str);
+ /* We should NOT find our zero lifetime desc in our cache. */
+ ret = hs_cache_lookup_as_dir(3,
+ helper_get_hsdir_query(desc_zero_lifetime),
+ NULL);
+ 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);
+ hs_descriptor_free(desc_zero_lifetime);
+ tor_free(desc_zero_lifetime_str);
+ }
+
+ /* Throw junk at it. */
+ {
+ ret = hs_cache_store_as_dir("blah");
+ tt_int_op(ret, OP_EQ, -1);
+ /* Poor attempt at tricking the decoding. */
+ ret = hs_cache_store_as_dir("hs-descriptor 3\nJUNK");
+ tt_int_op(ret, OP_EQ, -1);
+ /* Undecodable base64 query. */
+ ret = hs_cache_lookup_as_dir(3, "blah", NULL);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Decodable base64 query but wrong ed25519 size. */
+ ret = hs_cache_lookup_as_dir(3, "dW5pY29ybg==", NULL);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Test descriptor replacement with revision counter. */
+ {
+ char *new_desc_str;
+
+ /* Add a descriptor. */
+ ret = hs_cache_store_as_dir(desc1_str);
+ tt_int_op(ret, OP_EQ, 0);
+ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), &desc_out);
+ tt_int_op(ret, OP_EQ, 1);
+ /* Bump revision counter. */
+ desc1->plaintext_data.revision_counter++;
+ ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &new_desc_str);
+ tt_int_op(ret, OP_EQ, 0);
+ ret = hs_cache_store_as_dir(new_desc_str);
+ tt_int_op(ret, OP_EQ, 0);
+ /* Look it up, it should have been replaced. */
+ ret = hs_cache_lookup_as_dir(3, helper_get_hsdir_query(desc1), &desc_out);
+ tt_int_op(ret, OP_EQ, 1);
+ tt_str_op(desc_out, OP_EQ, new_desc_str);
+ tor_free(new_desc_str);
+ }
+
+ done:
+ hs_descriptor_free(desc1);
+ tor_free(desc1_str);
+}
+
+static void
+test_clean_as_dir(void *arg)
+{
+ size_t ret;
+ char *desc1_str = NULL;
+ time_t now = time(NULL);
+ hs_descriptor_t *desc1 = NULL;
+ ed25519_keypair_t signing_kp1;
+
+ (void) arg;
+
+ init_test();
+
+ /* Generate a valid descriptor with values. */
+ ret = ed25519_keypair_generate(&signing_kp1, 0);
+ tt_int_op(ret, ==, 0);
+ desc1 = helper_build_hs_desc(42, 3 * 60 * 60, &signing_kp1.pubkey);
+ tt_assert(desc1);
+ ret = hs_desc_encode_descriptor(desc1, &signing_kp1, &desc1_str);
+ tt_int_op(ret, OP_EQ, 0);
+ ret = hs_cache_store_as_dir(desc1_str);
+ tt_int_op(ret, OP_EQ, 0);
+
+ /* 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);
+ /* 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);
+ /* 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);
+ /* 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);
+
+ done:
+ hs_descriptor_free(desc1);
+ tor_free(desc1_str);
+}
+
+/* Test helper: Fetch an HS descriptor from an HSDir (for the hidden service
+ with <b>blinded_key</b>. Return the received descriptor string. */
+static char *
+helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key)
+{
+ int retval;
+
+ char *received_desc = NULL;
+ char *hsdir_query_str = NULL;
+
+ /* The dir conn we are going to simulate */
+ dir_connection_t *conn = NULL;
+
+ /* First extract the blinded public key that we are going to use in our
+ query, and then build the actual query string. */
+ {
+ char hsdir_cache_key[ED25519_BASE64_LEN+1];
+
+ retval = ed25519_public_to_base64(hsdir_cache_key,
+ blinded_key);
+ tt_int_op(retval, ==, 0);
+ tor_asprintf(&hsdir_query_str, GET("/tor/hs/3/%s"), hsdir_cache_key);
+ }
+
+ /* Simulate an HTTP GET request to the HSDir */
+ conn = dir_connection_new(AF_INET);
+ tor_addr_from_ipv4h(&conn->base_.addr, 0x7f000001);
+ TO_CONN(conn)->linked = 1;/* Pretend the conn is encrypted :) */
+ retval = directory_handle_command_get(conn, hsdir_query_str,
+ NULL, 0);
+ tt_int_op(retval, OP_EQ, 0);
+
+ /* Read the descriptor that the HSDir just served us */
+ {
+ char *headers = NULL;
+ size_t body_used = 0;
+
+ fetch_from_buf_http(TO_CONN(conn)->outbuf, &headers, MAX_HEADERS_SIZE,
+ &received_desc, &body_used, 10000, 0);
+ tor_free(headers);
+ }
+
+ done:
+ tor_free(hsdir_query_str);
+ if (conn)
+ connection_free_(TO_CONN(conn));
+
+ return received_desc;
+}
+
+/* Publish a descriptor to the HSDir, then fetch it. Check that the received
+ descriptor matches the published one. */
+static void
+test_upload_and_download_hs_desc(void *arg)
+{
+ int retval;
+ hs_descriptor_t *published_desc = NULL;
+
+ char *published_desc_str = NULL;
+ char *received_desc_str = NULL;
+
+ (void) arg;
+
+ /* Initialize HSDir cache subsystem */
+ init_test();
+
+ /* Test a descriptor not found in the directory cache. */
+ {
+ ed25519_public_key_t blinded_key;
+ memset(&blinded_key.pubkey, 'A', sizeof(blinded_key.pubkey));
+ received_desc_str = helper_fetch_desc_from_hsdir(&blinded_key);
+ tt_int_op(strlen(received_desc_str), OP_EQ, 0);
+ tor_free(received_desc_str);
+ }
+
+ /* Generate a valid descriptor with normal values. */
+ {
+ ed25519_keypair_t signing_kp;
+ retval = ed25519_keypair_generate(&signing_kp, 0);
+ tt_int_op(retval, ==, 0);
+ published_desc = helper_build_hs_desc(42, 3 * 60 * 60, &signing_kp.pubkey);
+ tt_assert(published_desc);
+ retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
+ &published_desc_str);
+ tt_int_op(retval, OP_EQ, 0);
+ }
+
+ /* Publish descriptor to the HSDir */
+ {
+ retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str);
+ tt_int_op(retval, ==, 200);
+ }
+
+ /* Simulate a fetch of the previously published descriptor */
+ {
+ const ed25519_public_key_t *blinded_key;
+ blinded_key = &published_desc->plaintext_data.blinded_pubkey;
+ received_desc_str = helper_fetch_desc_from_hsdir(blinded_key);
+ }
+
+ /* Verify we received the exact same descriptor we published earlier */
+ tt_str_op(received_desc_str, OP_EQ, published_desc_str);
+ tor_free(received_desc_str);
+
+ /* With a valid descriptor in the directory cache, try again an invalid. */
+ {
+ ed25519_public_key_t blinded_key;
+ memset(&blinded_key.pubkey, 'A', sizeof(blinded_key.pubkey));
+ received_desc_str = helper_fetch_desc_from_hsdir(&blinded_key);
+ tt_int_op(strlen(received_desc_str), OP_EQ, 0);
+ }
+
+ done:
+ tor_free(received_desc_str);
+ tor_free(published_desc_str);
+ hs_descriptor_free(published_desc);
+}
+
+/* Test that HSDirs reject outdated descriptors based on their revision
+ * counter. Also test that HSDirs correctly replace old descriptors with newer
+ * descriptors. */
+static void
+test_hsdir_revision_counter_check(void *arg)
+{
+ int retval;
+
+ ed25519_keypair_t signing_kp;
+
+ hs_descriptor_t *published_desc = NULL;
+ char *published_desc_str = NULL;
+
+ char *received_desc_str = NULL;
+ hs_descriptor_t *received_desc = NULL;
+
+ (void) arg;
+
+ /* Initialize HSDir cache subsystem */
+ init_test();
+
+ /* Generate a valid descriptor with normal values. */
+ {
+ retval = ed25519_keypair_generate(&signing_kp, 0);
+ tt_int_op(retval, ==, 0);
+ published_desc = helper_build_hs_desc(1312, 3 * 60 * 60,
+ &signing_kp.pubkey);
+ tt_assert(published_desc);
+ retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
+ &published_desc_str);
+ tt_int_op(retval, OP_EQ, 0);
+ }
+
+ /* Publish descriptor to the HSDir */
+ {
+ retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str);
+ tt_int_op(retval, ==, 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);
+ }
+
+ /* Fetch the published descriptor and validate the revision counter. */
+ {
+ const ed25519_public_key_t *blinded_key;
+
+ 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);
+ tt_assert(received_desc);
+
+ /* Check that the revision counter is correct */
+ tt_u64_op(received_desc->plaintext_data.revision_counter, ==, 1312);
+
+ hs_descriptor_free(received_desc);
+ received_desc = NULL;
+ tor_free(received_desc_str);
+ }
+
+ /* Increment the revision counter and try again. Should work. */
+ {
+ published_desc->plaintext_data.revision_counter = 1313;
+ tor_free(published_desc_str);
+ retval = hs_desc_encode_descriptor(published_desc, &signing_kp,
+ &published_desc_str);
+ tt_int_op(retval, OP_EQ, 0);
+
+ retval = handle_post_hs_descriptor("/tor/hs/3/publish",published_desc_str);
+ tt_int_op(retval, ==, 200);
+ }
+
+ /* Again, fetch the published descriptor and perform the revision counter
+ validation. The revision counter must have changed. */
+ {
+ const ed25519_public_key_t *blinded_key;
+
+ 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);
+ tt_assert(received_desc);
+
+ /* Check that the revision counter is the latest */
+ tt_u64_op(received_desc->plaintext_data.revision_counter, ==, 1313);
+ }
+
+ done:
+ hs_descriptor_free(published_desc);
+ hs_descriptor_free(received_desc);
+ tor_free(received_desc_str);
+ tor_free(published_desc_str);
+}
+
+struct testcase_t hs_cache[] = {
+ /* Encoding tests. */
+ { "directory", test_directory, TT_FORK,
+ NULL, NULL },
+ { "clean_as_dir", test_clean_as_dir, TT_FORK,
+ NULL, NULL },
+ { "hsdir_revision_counter_check", test_hsdir_revision_counter_check, TT_FORK,
+ NULL, NULL },
+ { "upload_and_download_hs_desc", test_upload_and_download_hs_desc, TT_FORK,
+ NULL, NULL },
+
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c
new file mode 100644
index 0000000000..97fe1910b8
--- /dev/null
+++ b/src/test/test_hs_descriptor.c
@@ -0,0 +1,1039 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_descriptor.c
+ * \brief Test hidden service descriptor encoding and decoding.
+ */
+
+#define HS_DESCRIPTOR_PRIVATE
+
+#include "crypto_ed25519.h"
+#include "ed25519_cert.h"
+#include "or.h"
+#include "hs_descriptor.h"
+#include "test.h"
+#include "torcert.h"
+
+static hs_desc_intro_point_t *
+helper_build_intro_point(const ed25519_keypair_t *blinded_kp, time_t now,
+ const char *addr, int legacy)
+{
+ 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_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;
+ break;
+ case AF_INET6:
+ ls->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);
+ }
+
+ ret = ed25519_keypair_generate(&auth_kp, 0);
+ tt_int_op(ret, ==, 0);
+ ip->auth_key_cert = tor_cert_create(blinded_kp, CERT_TYPE_AUTH_HS_IP_KEY,
+ &auth_kp.pubkey, now,
+ HS_DESC_CERT_LIFETIME,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ tt_assert(ip->auth_key_cert);
+
+ if (legacy) {
+ ip->enc_key.legacy = crypto_pk_new();
+ ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY;
+ tt_assert(ip->enc_key.legacy);
+ ret = crypto_pk_generate_key(ip->enc_key.legacy);
+ tt_int_op(ret, ==, 0);
+ } else {
+ ret = curve25519_keypair_generate(&ip->enc_key.curve25519, 0);
+ tt_int_op(ret, ==, 0);
+ ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519;
+ }
+
+ intro_point = ip;
+ done:
+ return intro_point;
+}
+
+/* Return a valid hs_descriptor_t object. If no_ip is set, no introduction
+ * points are added. */
+static hs_descriptor_t *
+helper_build_hs_desc(unsigned int no_ip, ed25519_public_key_t *signing_pubkey)
+{
+ int ret;
+ time_t now = time(NULL);
+ ed25519_keypair_t blinded_kp;
+ hs_descriptor_t *descp = NULL, *desc = tor_malloc_zero(sizeof(*desc));
+
+ desc->plaintext_data.version = HS_DESC_SUPPORTED_FORMAT_VERSION_MAX;
+
+ /* Copy only the public key into the descriptor. */
+ memcpy(&desc->plaintext_data.signing_pubkey, signing_pubkey,
+ sizeof(ed25519_public_key_t));
+
+ ret = ed25519_keypair_generate(&blinded_kp, 0);
+ tt_int_op(ret, ==, 0);
+ /* Copy only the public key into the descriptor. */
+ memcpy(&desc->plaintext_data.blinded_pubkey, &blinded_kp.pubkey,
+ sizeof(ed25519_public_key_t));
+
+ desc->plaintext_data.signing_key_cert =
+ tor_cert_create(&blinded_kp, CERT_TYPE_SIGNING_HS_DESC, signing_pubkey,
+ now, 3600, CERT_FLAG_INCLUDE_SIGNING_KEY);
+ tt_assert(desc->plaintext_data.signing_key_cert);
+ desc->plaintext_data.revision_counter = 42;
+ desc->plaintext_data.lifetime_sec = 3 * 60 * 60;
+
+ /* Setup encrypted data section. */
+ desc->encrypted_data.create2_ntor = 1;
+ desc->encrypted_data.auth_types = smartlist_new();
+ desc->encrypted_data.single_onion_service = 1;
+ smartlist_add(desc->encrypted_data.auth_types, tor_strdup("ed25519"));
+ desc->encrypted_data.intro_points = smartlist_new();
+ if (!no_ip) {
+ /* Add four intro points. */
+ smartlist_add(desc->encrypted_data.intro_points,
+ helper_build_intro_point(&blinded_kp, now, "1.2.3.4", 0));
+ smartlist_add(desc->encrypted_data.intro_points,
+ helper_build_intro_point(&blinded_kp, now, "[2600::1]", 0));
+ smartlist_add(desc->encrypted_data.intro_points,
+ helper_build_intro_point(&blinded_kp, now, "3.2.1.4", 1));
+ smartlist_add(desc->encrypted_data.intro_points,
+ helper_build_intro_point(&blinded_kp, now, "", 1));
+ }
+
+ descp = desc;
+ done:
+ return descp;
+}
+
+static void
+helper_compare_hs_desc(const hs_descriptor_t *desc1,
+ const hs_descriptor_t *desc2)
+{
+ char *addr1 = NULL, *addr2 = NULL;
+ /* Plaintext data section. */
+ tt_int_op(desc1->plaintext_data.version, OP_EQ,
+ desc2->plaintext_data.version);
+ tt_uint_op(desc1->plaintext_data.lifetime_sec, OP_EQ,
+ desc2->plaintext_data.lifetime_sec);
+ tt_assert(tor_cert_eq(desc1->plaintext_data.signing_key_cert,
+ desc2->plaintext_data.signing_key_cert));
+ tt_mem_op(desc1->plaintext_data.signing_pubkey.pubkey, OP_EQ,
+ desc2->plaintext_data.signing_pubkey.pubkey,
+ ED25519_PUBKEY_LEN);
+ tt_mem_op(desc1->plaintext_data.blinded_pubkey.pubkey, OP_EQ,
+ desc2->plaintext_data.blinded_pubkey.pubkey,
+ ED25519_PUBKEY_LEN);
+ tt_u64_op(desc1->plaintext_data.revision_counter, ==,
+ desc2->plaintext_data.revision_counter);
+
+ /* NOTE: We can't compare the encrypted blob because when encoding the
+ * descriptor, the object is immutable thus we don't update it with the
+ * encrypted blob. As contrast to the decoding process where we populate a
+ * descriptor object. */
+
+ /* Encrypted data section. */
+ tt_uint_op(desc1->encrypted_data.create2_ntor, ==,
+ desc2->encrypted_data.create2_ntor);
+
+ /* Authentication type. */
+ tt_int_op(!!desc1->encrypted_data.auth_types, ==,
+ !!desc2->encrypted_data.auth_types);
+ if (desc1->encrypted_data.auth_types && desc2->encrypted_data.auth_types) {
+ tt_int_op(smartlist_len(desc1->encrypted_data.auth_types), ==,
+ smartlist_len(desc2->encrypted_data.auth_types));
+ for (int i = 0; i < smartlist_len(desc1->encrypted_data.auth_types); i++) {
+ tt_str_op(smartlist_get(desc1->encrypted_data.auth_types, i), OP_EQ,
+ smartlist_get(desc2->encrypted_data.auth_types, i));
+ }
+ }
+
+ /* Introduction points. */
+ {
+ tt_assert(desc1->encrypted_data.intro_points);
+ tt_assert(desc2->encrypted_data.intro_points);
+ tt_int_op(smartlist_len(desc1->encrypted_data.intro_points), ==,
+ smartlist_len(desc2->encrypted_data.intro_points));
+ for (int i=0; i < smartlist_len(desc1->encrypted_data.intro_points); i++) {
+ hs_desc_intro_point_t *ip1 = smartlist_get(desc1->encrypted_data
+ .intro_points, i),
+ *ip2 = smartlist_get(desc2->encrypted_data
+ .intro_points, i);
+ tt_assert(tor_cert_eq(ip1->auth_key_cert, ip2->auth_key_cert));
+ tt_int_op(ip1->enc_key_type, OP_EQ, ip2->enc_key_type);
+ tt_assert(ip1->enc_key_type == HS_DESC_KEY_TYPE_LEGACY ||
+ ip1->enc_key_type == HS_DESC_KEY_TYPE_CURVE25519);
+ switch (ip1->enc_key_type) {
+ case HS_DESC_KEY_TYPE_LEGACY:
+ tt_int_op(crypto_pk_cmp_keys(ip1->enc_key.legacy, ip2->enc_key.legacy),
+ OP_EQ, 0);
+ break;
+ case HS_DESC_KEY_TYPE_CURVE25519:
+ tt_mem_op(ip1->enc_key.curve25519.pubkey.public_key, OP_EQ,
+ ip2->enc_key.curve25519.pubkey.public_key,
+ CURVE25519_PUBKEY_LEN);
+ break;
+ }
+
+ tt_int_op(smartlist_len(ip1->link_specifiers), ==,
+ smartlist_len(ip2->link_specifiers));
+ for (int j = 0; j < smartlist_len(ip1->link_specifiers); j++) {
+ hs_desc_link_specifier_t *ls1 = smartlist_get(ip1->link_specifiers, j),
+ *ls2 = smartlist_get(ip2->link_specifiers, j);
+ tt_int_op(ls1->type, ==, ls2->type);
+ switch (ls1->type) {
+ case LS_IPV4:
+ case LS_IPV6:
+ {
+ addr1 = tor_addr_to_str_dup(&ls1->u.ap.addr);
+ addr2 = tor_addr_to_str_dup(&ls2->u.ap.addr);
+ tt_str_op(addr1, OP_EQ, addr2);
+ tor_free(addr1);
+ tor_free(addr2);
+ tt_int_op(ls1->u.ap.port, ==, ls2->u.ap.port);
+ }
+ break;
+ case LS_LEGACY_ID:
+ tt_mem_op(ls1->u.legacy_id, OP_EQ, ls2->u.legacy_id,
+ sizeof(ls1->u.legacy_id));
+ break;
+ default:
+ /* Unknown type, caught it and print its value. */
+ tt_int_op(ls1->type, OP_EQ, -1);
+ }
+ }
+ }
+ }
+
+ done:
+ tor_free(addr1);
+ tor_free(addr2);
+}
+
+/* Test certificate encoding put in a descriptor. */
+static void
+test_cert_encoding(void *arg)
+{
+ int ret;
+ char *encoded = NULL;
+ time_t now = time(NULL);
+ ed25519_keypair_t kp;
+ ed25519_public_key_t signed_key;
+ ed25519_secret_key_t secret_key;
+ tor_cert_t *cert = NULL;
+
+ (void) arg;
+
+ ret = ed25519_keypair_generate(&kp, 0);
+ tt_int_op(ret, == , 0);
+ ret = ed25519_secret_key_generate(&secret_key, 0);
+ tt_int_op(ret, == , 0);
+ ret = ed25519_public_key_generate(&signed_key, &secret_key);
+ tt_int_op(ret, == , 0);
+
+ cert = tor_cert_create(&kp, CERT_TYPE_SIGNING_AUTH, &signed_key,
+ now, 3600 * 2, CERT_FLAG_INCLUDE_SIGNING_KEY);
+ tt_assert(cert);
+
+ /* Test the certificate encoding function. */
+ ret = tor_cert_encode_ed22519(cert, &encoded);
+ tt_int_op(ret, ==, 0);
+
+ /* Validated the certificate string. */
+ {
+ char *end, *pos = encoded;
+ char *b64_cert, buf[256];
+ size_t b64_cert_len;
+ tor_cert_t *parsed_cert;
+
+ tt_int_op(strcmpstart(pos, "-----BEGIN ED25519 CERT-----\n"), ==, 0);
+ pos += strlen("-----BEGIN ED25519 CERT-----\n");
+
+ /* Isolate the base64 encoded certificate and try to decode it. */
+ end = strstr(pos, "-----END ED25519 CERT-----");
+ tt_assert(end);
+ b64_cert = pos;
+ b64_cert_len = end - pos;
+ ret = base64_decode(buf, sizeof(buf), b64_cert, b64_cert_len);
+ tt_int_op(ret, >, 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);
+ ret = tor_cert_eq(cert, parsed_cert);
+ tt_int_op(ret, ==, 1);
+ /* The cert did have the signing key? */
+ ret= ed25519_pubkey_eq(&parsed_cert->signing_key, &kp.pubkey);
+ tt_int_op(ret, ==, 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);
+ pos += strlen("-----END ED25519 CERT-----");
+ }
+
+ done:
+ tor_cert_free(cert);
+ tor_free(encoded);
+}
+
+/* Test the descriptor padding. */
+static void
+test_descriptor_padding(void *arg)
+{
+ char *plaintext;
+ size_t plaintext_len, padded_len;
+ uint8_t *padded_plaintext = NULL;
+
+/* Example: if l = 129, the ceiled division gives 2 and then multiplied by 128
+ * to give 256. With l = 127, ceiled division gives 1 then times 128. */
+#define PADDING_EXPECTED_LEN(l) \
+ CEIL_DIV(l, HS_DESC_PLAINTEXT_PADDING_MULTIPLE) * \
+ HS_DESC_PLAINTEXT_PADDING_MULTIPLE
+
+ (void) arg;
+
+ { /* test #1: no padding */
+ plaintext_len = HS_DESC_PLAINTEXT_PADDING_MULTIPLE;
+ plaintext = tor_malloc(plaintext_len);
+ padded_len = build_plaintext_padding(plaintext, plaintext_len,
+ &padded_plaintext);
+ tt_assert(padded_plaintext);
+ tor_free(plaintext);
+ /* Make sure our padding has been zeroed. */
+ tt_int_op(tor_mem_is_zero((char *) padded_plaintext + plaintext_len,
+ padded_len - plaintext_len), OP_EQ, 1);
+ tor_free(padded_plaintext);
+ /* Never never have a padded length smaller than the plaintext. */
+ tt_int_op(padded_len, OP_GE, plaintext_len);
+ tt_int_op(padded_len, OP_EQ, PADDING_EXPECTED_LEN(plaintext_len));
+ }
+
+ { /* test #2: one byte padding? */
+ plaintext_len = HS_DESC_PLAINTEXT_PADDING_MULTIPLE - 1;
+ plaintext = tor_malloc(plaintext_len);
+ padded_plaintext = NULL;
+ padded_len = build_plaintext_padding(plaintext, plaintext_len,
+ &padded_plaintext);
+ tt_assert(padded_plaintext);
+ tor_free(plaintext);
+ /* Make sure our padding has been zeroed. */
+ tt_int_op(tor_mem_is_zero((char *) padded_plaintext + plaintext_len,
+ padded_len - plaintext_len), OP_EQ, 1);
+ tor_free(padded_plaintext);
+ /* Never never have a padded length smaller than the plaintext. */
+ tt_int_op(padded_len, OP_GE, plaintext_len);
+ tt_int_op(padded_len, OP_EQ, PADDING_EXPECTED_LEN(plaintext_len));
+ }
+
+ { /* test #3: Lots more bytes of padding? */
+ plaintext_len = HS_DESC_PLAINTEXT_PADDING_MULTIPLE + 1;
+ plaintext = tor_malloc(plaintext_len);
+ padded_plaintext = NULL;
+ padded_len = build_plaintext_padding(plaintext, plaintext_len,
+ &padded_plaintext);
+ tt_assert(padded_plaintext);
+ tor_free(plaintext);
+ /* Make sure our padding has been zeroed. */
+ tt_int_op(tor_mem_is_zero((char *) padded_plaintext + plaintext_len,
+ padded_len - plaintext_len), OP_EQ, 1);
+ tor_free(padded_plaintext);
+ /* Never never have a padded length smaller than the plaintext. */
+ tt_int_op(padded_len, OP_GE, plaintext_len);
+ tt_int_op(padded_len, OP_EQ, PADDING_EXPECTED_LEN(plaintext_len));
+ }
+
+ done:
+ return;
+}
+
+static void
+test_link_specifier(void *arg)
+{
+ ssize_t ret;
+ hs_desc_link_specifier_t spec;
+ smartlist_t *link_specifiers = smartlist_new();
+
+ (void) arg;
+
+ /* Always this port. */
+ spec.u.ap.port = 42;
+ smartlist_add(link_specifiers, &spec);
+
+ /* Test IPv4 for starter. */
+ {
+ char *b64, buf[256];
+ uint32_t ipv4;
+ link_specifier_t *ls;
+
+ spec.type = LS_IPV4;
+ ret = tor_addr_parse(&spec.u.ap.addr, "1.2.3.4");
+ tt_int_op(ret, ==, 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);
+ /* First byte is the number of link specifier. */
+ tt_int_op(get_uint8(buf), ==, 1);
+ ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1);
+ tt_int_op(ret, ==, 8);
+ /* Should be 2 bytes for port and 4 bytes for IPv4. */
+ tt_int_op(link_specifier_get_ls_len(ls), ==, 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);
+
+ link_specifier_free(ls);
+ tor_free(b64);
+ }
+
+ /* Test IPv6. */
+ {
+ char *b64, buf[256];
+ uint8_t ipv6[16];
+ link_specifier_t *ls;
+
+ spec.type = LS_IPV6;
+ ret = tor_addr_parse(&spec.u.ap.addr, "[1:2:3:4::]");
+ tt_int_op(ret, ==, 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);
+ /* First byte is the number of link specifier. */
+ tt_int_op(get_uint8(buf), ==, 1);
+ ret = link_specifier_parse(&ls, (uint8_t *) buf + 1, ret - 1);
+ tt_int_op(ret, ==, 20);
+ /* Should be 2 bytes for port and 16 bytes for IPv6. */
+ tt_int_op(link_specifier_get_ls_len(ls), ==, 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);
+
+ link_specifier_free(ls);
+ tor_free(b64);
+ }
+
+ /* Test legacy. */
+ {
+ char *b64, buf[256];
+ uint8_t *id;
+ link_specifier_t *ls;
+
+ spec.type = LS_LEGACY_ID;
+ memset(spec.u.legacy_id, 'Y', sizeof(spec.u.legacy_id));
+ 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);
+ /* First byte is the number of link specifier. */
+ tt_int_op(get_uint8(buf), ==, 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(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);
+ id = link_specifier_getarray_un_legacy_id(ls);
+ tt_mem_op(spec.u.legacy_id, OP_EQ, id, DIGEST_LEN);
+
+ link_specifier_free(ls);
+ tor_free(b64);
+ }
+
+ done:
+ smartlist_free(link_specifiers);
+}
+
+static void
+test_encode_descriptor(void *arg)
+{
+ int ret;
+ char *encoded = NULL;
+ ed25519_keypair_t signing_kp;
+ hs_descriptor_t *desc = NULL;
+
+ (void) arg;
+
+ ret = ed25519_keypair_generate(&signing_kp, 0);
+ tt_int_op(ret, ==, 0);
+ desc = helper_build_hs_desc(0, &signing_kp.pubkey);
+ ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded);
+ tt_int_op(ret, ==, 0);
+ tt_assert(encoded);
+
+ done:
+ hs_descriptor_free(desc);
+ tor_free(encoded);
+}
+
+static void
+test_decode_descriptor(void *arg)
+{
+ int ret;
+ char *encoded = NULL;
+ ed25519_keypair_t signing_kp;
+ hs_descriptor_t *desc = NULL;
+ hs_descriptor_t *decoded = NULL;
+ hs_descriptor_t *desc_no_ip = NULL;
+
+ (void) arg;
+
+ ret = ed25519_keypair_generate(&signing_kp, 0);
+ tt_int_op(ret, ==, 0);
+ desc = helper_build_hs_desc(0, &signing_kp.pubkey);
+
+ /* Give some bad stuff to the decoding function. */
+ ret = hs_desc_decode_descriptor("hladfjlkjadf", NULL, &decoded);
+ tt_int_op(ret, OP_EQ, -1);
+
+ ret = hs_desc_encode_descriptor(desc, &signing_kp, &encoded);
+ tt_int_op(ret, ==, 0);
+ tt_assert(encoded);
+
+ ret = hs_desc_decode_descriptor(encoded, NULL, &decoded);
+ tt_int_op(ret, ==, 0);
+ tt_assert(decoded);
+
+ helper_compare_hs_desc(desc, decoded);
+
+ /* Decode a descriptor with _no_ introduction points. */
+ {
+ ed25519_keypair_t signing_kp_no_ip;
+ ret = ed25519_keypair_generate(&signing_kp_no_ip, 0);
+ tt_int_op(ret, ==, 0);
+ desc_no_ip = helper_build_hs_desc(1, &signing_kp_no_ip.pubkey);
+ 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_assert(encoded);
+ hs_descriptor_free(decoded);
+ ret = hs_desc_decode_descriptor(encoded, NULL, &decoded);
+ tt_int_op(ret, ==, 0);
+ tt_assert(decoded);
+ }
+
+ done:
+ hs_descriptor_free(desc);
+ hs_descriptor_free(desc_no_ip);
+ hs_descriptor_free(decoded);
+ tor_free(encoded);
+}
+
+static void
+test_supported_version(void *arg)
+{
+ int ret;
+
+ (void) arg;
+
+ /* Unsupported. */
+ ret = hs_desc_is_supported_version(42);
+ tt_int_op(ret, OP_EQ, 0);
+ /* To early. */
+ ret = hs_desc_is_supported_version(HS_DESC_SUPPORTED_FORMAT_VERSION_MIN - 1);
+ tt_int_op(ret, OP_EQ, 0);
+ /* One too new. */
+ ret = hs_desc_is_supported_version(HS_DESC_SUPPORTED_FORMAT_VERSION_MAX + 1);
+ tt_int_op(ret, OP_EQ, 0);
+ /* Valid version. */
+ ret = hs_desc_is_supported_version(3);
+ tt_int_op(ret, OP_EQ, 1);
+
+ done:
+ ;
+}
+
+static void
+test_encrypted_data_len(void *arg)
+{
+ int ret;
+ size_t value;
+
+ (void) arg;
+
+ /* No length, error. */
+ ret = encrypted_data_length_is_valid(0);
+ tt_int_op(ret, OP_EQ, 0);
+ /* This value is missing data. */
+ value = HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN;
+ ret = encrypted_data_length_is_valid(value);
+ tt_int_op(ret, OP_EQ, 0);
+ /* Valid value. */
+ value = HS_DESC_PADDED_PLAINTEXT_MAX_LEN + HS_DESC_ENCRYPTED_SALT_LEN +
+ DIGEST256_LEN;
+ ret = encrypted_data_length_is_valid(value);
+ tt_int_op(ret, OP_EQ, 1);
+
+ /* XXX: Test maximum possible size. */
+
+ done:
+ ;
+}
+
+static void
+test_decode_intro_point(void *arg)
+{
+ int ret;
+ char *encoded_ip = NULL;
+ size_t len_out;
+ hs_desc_intro_point_t *ip = NULL;
+ ed25519_keypair_t signing_kp;
+ hs_descriptor_t *desc = NULL;
+
+ (void) arg;
+
+ /* The following certificate expires in 2036. After that, one of the test
+ * will fail because of the expiry time. */
+
+ /* Seperate pieces of a valid encoded introduction point. */
+ const char *intro_point =
+ "introduction-point AQIUMDI5OUYyNjhGQ0E5RDU1Q0QxNTc=";
+ const char *auth_key =
+ "auth-key\n"
+ "-----BEGIN ED25519 CERT-----\n"
+ "AQkACOhAAQW8ltYZMIWpyrfyE/b4Iyi8CNybCwYs6ADk7XfBaxsFAQAgBAD3/BE4\n"
+ "XojGE/N2bW/wgnS9r2qlrkydGyuCKIGayYx3haZ39LD4ZTmSMRxwmplMAqzG/XNP\n"
+ "0Kkpg4p2/VnLFJRdU1SMFo1lgQ4P0bqw7Tgx200fulZ4KUM5z5V7m+a/mgY=\n"
+ "-----END ED25519 CERT-----";
+ const char *enc_key =
+ "enc-key ntor bpZKLsuhxP6woDQ3yVyjm5gUKSk7RjfAijT2qrzbQk0=";
+ const char *enc_key_legacy =
+ "enc-key legacy\n"
+ "-----BEGIN RSA PUBLIC KEY-----\n"
+ "MIGJAoGBAO4bATcW8kW4h6RQQAKEgg+aXCpF4JwbcO6vGZtzXTDB+HdPVQzwqkbh\n"
+ "XzFM6VGArhYw4m31wcP1Z7IwULir7UMnAFd7Zi62aYfU6l+Y1yAoZ1wzu1XBaAMK\n"
+ "ejpwQinW9nzJn7c2f69fVke3pkhxpNdUZ+vplSA/l9iY+y+v+415AgMBAAE=\n"
+ "-----END RSA PUBLIC KEY-----";
+ const char *enc_key_cert =
+ "enc-key-certification\n"
+ "-----BEGIN ED25519 CERT-----\n"
+ "AQsACOhZAUpNvCZ1aJaaR49lS6MCdsVkhVGVrRqoj0Y2T4SzroAtAQAgBABFOcGg\n"
+ "lbTt1DF5nKTE/gU3Fr8ZtlCIOhu1A+F5LM7fqCUupfesg0KTHwyIZOYQbJuM5/he\n"
+ "/jDNyLy9woPJdjkxywaY2RPUxGjLYtMQV0E8PUxWyICV+7y52fTCYaKpYQw=\n"
+ "-----END ED25519 CERT-----";
+ const char *enc_key_cert_legacy =
+ "enc-key-certification\n"
+ "-----BEGIN CROSSCERT-----\n"
+ "Sk28JnVolppHj2VLowJ2xWSFUZWtGqiPRjZPhLOugC0ACOhZgFPA5egeRDUXMM1U\n"
+ "Fn3c7Je0gJS6mVma5FzwlgwggeriF13UZcaT71vEAN/ZJXbxOfQVGMZ0rXuFpjUq\n"
+ "C8CvqmZIwEUaPE1nDFtmnTcucvNS1YQl9nsjH3ejbxc+4yqps/cXh46FmXsm5yz7\n"
+ "NZjBM9U1fbJhlNtOvrkf70K8bLk6\n"
+ "-----END CROSSCERT-----";
+
+ (void) enc_key_legacy;
+ (void) enc_key_cert_legacy;
+
+ /* Start by testing the "decode all intro points" function. */
+ {
+ char *line;
+ ret = ed25519_keypair_generate(&signing_kp, 0);
+ tt_int_op(ret, ==, 0);
+ desc = helper_build_hs_desc(0, &signing_kp.pubkey);
+ tt_assert(desc);
+ /* Only try to decode an incomplete introduction point section. */
+ tor_asprintf(&line, "\n%s", intro_point);
+ ret = decode_intro_points(desc, &desc->encrypted_data, line);
+ tor_free(line);
+ tt_int_op(ret, ==, -1);
+
+ /* Decode one complete intro point. */
+ smartlist_t *lines = smartlist_new();
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) enc_key);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ tor_asprintf(&line, "\n%s", encoded_ip);
+ tor_free(encoded_ip);
+ ret = decode_intro_points(desc, &desc->encrypted_data, line);
+ tor_free(line);
+ smartlist_free(lines);
+ tt_int_op(ret, ==, 0);
+ }
+
+ /* Try to decode a junk string. */
+ {
+ hs_descriptor_free(desc);
+ desc = NULL;
+ ret = ed25519_keypair_generate(&signing_kp, 0);
+ tt_int_op(ret, ==, 0);
+ desc = helper_build_hs_desc(0, &signing_kp.pubkey);
+ const char *junk = "this is not a descriptor";
+ ip = decode_introduction_point(desc, junk);
+ tt_assert(!ip);
+ desc_intro_point_free(ip);
+ ip = NULL;
+ }
+
+ /* Invalid link specifiers. */
+ {
+ smartlist_t *lines = smartlist_new();
+ const char *bad_line = "introduction-point blah";
+ smartlist_add(lines, (char *) bad_line);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) enc_key);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(!ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ desc_intro_point_free(ip);
+ ip = NULL;
+ }
+
+ /* Invalid auth key type. */
+ {
+ smartlist_t *lines = smartlist_new();
+ /* Try to put a valid object that our tokenize function will be able to
+ * parse but that has nothing to do with the auth_key. */
+ const char *bad_line =
+ "auth-key\n"
+ "-----BEGIN UNICORN CERT-----\n"
+ "MIGJAoGBAO4bATcW8kW4h6RQQAKEgg+aXCpF4JwbcO6vGZtzXTDB+HdPVQzwqkbh\n"
+ "XzFM6VGArhYw4m31wcP1Z7IwULir7UMnAFd7Zi62aYfU6l+Y1yAoZ1wzu1XBaAMK\n"
+ "ejpwQinW9nzJn7c2f69fVke3pkhxpNdUZ+vplSA/l9iY+y+v+415AgMBAAE=\n"
+ "-----END UNICORN CERT-----";
+ /* Build intro point text. */
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) bad_line);
+ smartlist_add(lines, (char *) enc_key);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(!ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ }
+
+ /* Invalid enc-key. */
+ {
+ smartlist_t *lines = smartlist_new();
+ const char *bad_line =
+ "enc-key unicorn bpZKLsuhxP6woDQ3yVyjm5gUKSk7RjfAijT2qrzbQk0=";
+ /* Build intro point text. */
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) bad_line);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(!ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ }
+
+ /* Invalid enc-key object. */
+ {
+ smartlist_t *lines = smartlist_new();
+ const char *bad_line = "enc-key ntor";
+ /* Build intro point text. */
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) bad_line);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(!ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ }
+
+ /* Invalid enc-key base64 curv25519 key. */
+ {
+ smartlist_t *lines = smartlist_new();
+ const char *bad_line = "enc-key ntor blah===";
+ /* Build intro point text. */
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) bad_line);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(!ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ }
+
+ /* Invalid enc-key invalid legacy. */
+ {
+ smartlist_t *lines = smartlist_new();
+ const char *bad_line = "enc-key legacy blah===";
+ /* Build intro point text. */
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) bad_line);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(!ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ }
+
+ /* Valid object. */
+ {
+ smartlist_t *lines = smartlist_new();
+ /* Build intro point text. */
+ smartlist_add(lines, (char *) intro_point);
+ smartlist_add(lines, (char *) auth_key);
+ smartlist_add(lines, (char *) enc_key);
+ smartlist_add(lines, (char *) enc_key_cert);
+ encoded_ip = smartlist_join_strings(lines, "\n", 0, &len_out);
+ tt_assert(encoded_ip);
+ ip = decode_introduction_point(desc, encoded_ip);
+ tt_assert(ip);
+ tor_free(encoded_ip);
+ smartlist_free(lines);
+ }
+
+ done:
+ hs_descriptor_free(desc);
+ desc_intro_point_free(ip);
+}
+
+static void
+test_decode_plaintext(void *arg)
+{
+ int ret;
+ hs_desc_plaintext_data_t desc_plaintext;
+ const char *bad_value = "unicorn";
+
+ (void) arg;
+
+#define template \
+ "hs-descriptor %s\n" \
+ "descriptor-lifetime %s\n" \
+ "descriptor-signing-key-cert\n" \
+ "-----BEGIN ED25519 CERT-----\n" \
+ "AQgABjvPAQaG3g+dc6oV/oJV4ODAtkvx56uBnPtBT9mYVuHVOhn7AQAgBABUg3mQ\n" \
+ "myBr4bu5LCr53wUEbW2EXui01CbUgU7pfo9LvJG3AcXRojj6HlfsUs9BkzYzYdjF\n" \
+ "A69Apikgu0ewHYkFFASt7Il+gB3w6J8YstQJZT7dtbtl+doM7ug8B68Qdg8=\n" \
+ "-----END ED25519 CERT-----\n" \
+ "revision-counter %s\n" \
+ "encrypted\n" \
+ "-----BEGIN %s-----\n" \
+ "UNICORN\n" \
+ "-----END MESSAGE-----\n" \
+ "signature m20WJH5agqvwhq7QeuEZ1mYyPWQDO+eJOZUjLhAiKu8DbL17DsDfJE6kXbWy" \
+ "HimbNj2we0enV3cCOOAsmPOaAw\n"
+
+ /* Invalid version. */
+ {
+ char *plaintext;
+ tor_asprintf(&plaintext, template, bad_value, "180", "42", "MESSAGE");
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tor_free(plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Missing fields. */
+ {
+ const char *plaintext = "hs-descriptor 3\n";
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Max length. */
+ {
+ size_t big = 64000;
+ /* Must always be bigger than HS_DESC_MAX_LEN. */
+ tt_int_op(HS_DESC_MAX_LEN, <, big);
+ char *plaintext = tor_malloc_zero(big);
+ memset(plaintext, 'a', big);
+ plaintext[big - 1] = '\0';
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tor_free(plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Bad lifetime value. */
+ {
+ char *plaintext;
+ tor_asprintf(&plaintext, template, "3", bad_value, "42", "MESSAGE");
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tor_free(plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Huge lifetime value. */
+ {
+ char *plaintext;
+ tor_asprintf(&plaintext, template, "3", "7181615", "42", "MESSAGE");
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tor_free(plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Invalid encrypted section. */
+ {
+ char *plaintext;
+ tor_asprintf(&plaintext, template, "3", "180", "42", bad_value);
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tor_free(plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Invalid revision counter. */
+ {
+ char *plaintext;
+ tor_asprintf(&plaintext, template, "3", "180", bad_value, "MESSAGE");
+ ret = hs_desc_decode_plaintext(plaintext, &desc_plaintext);
+ tor_free(plaintext);
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ done:
+ ;
+}
+
+static void
+test_validate_cert(void *arg)
+{
+ int ret;
+ time_t now = time(NULL);
+ ed25519_keypair_t kp;
+ tor_cert_t *cert = NULL;
+
+ (void) arg;
+
+ ret = ed25519_keypair_generate(&kp, 0);
+ tt_int_op(ret, ==, 0);
+
+ /* Cert of type CERT_TYPE_AUTH_HS_IP_KEY. */
+ cert = tor_cert_create(&kp, CERT_TYPE_AUTH_HS_IP_KEY,
+ &kp.pubkey, now, 3600,
+ CERT_FLAG_INCLUDE_SIGNING_KEY);
+ tt_assert(cert);
+ /* Test with empty certificate. */
+ ret = cert_is_valid(NULL, CERT_TYPE_AUTH_HS_IP_KEY, "unicorn");
+ tt_int_op(ret, OP_EQ, 0);
+ /* Test with a bad type. */
+ ret = cert_is_valid(cert, CERT_TYPE_SIGNING_HS_DESC, "unicorn");
+ tt_int_op(ret, OP_EQ, 0);
+ /* Normal validation. */
+ ret = cert_is_valid(cert, CERT_TYPE_AUTH_HS_IP_KEY, "unicorn");
+ tt_int_op(ret, OP_EQ, 1);
+ /* Break signing key so signature verification will fails. */
+ memset(&cert->signing_key, 0, sizeof(cert->signing_key));
+ ret = cert_is_valid(cert, CERT_TYPE_AUTH_HS_IP_KEY, "unicorn");
+ tt_int_op(ret, OP_EQ, 0);
+ tor_cert_free(cert);
+
+ /* Try a cert without including the signing key. */
+ cert = tor_cert_create(&kp, CERT_TYPE_AUTH_HS_IP_KEY, &kp.pubkey, now,
+ 3600, 0);
+ tt_assert(cert);
+ /* Test with a bad type. */
+ ret = cert_is_valid(cert, CERT_TYPE_AUTH_HS_IP_KEY, "unicorn");
+ tt_int_op(ret, OP_EQ, 0);
+
+ done:
+ tor_cert_free(cert);
+}
+
+static void
+test_desc_signature(void *arg)
+{
+ int ret;
+ char *data = NULL, *desc = NULL;
+ char sig_b64[ED25519_SIG_BASE64_LEN + 1];
+ ed25519_keypair_t kp;
+ ed25519_signature_t sig;
+
+ (void) arg;
+
+ ed25519_keypair_generate(&kp, 0);
+ /* Setup a phoony descriptor but with a valid signature token that is the
+ * signature is verifiable. */
+ 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);
+ ret = ed25519_signature_to_base64(sig_b64, &sig);
+ tt_int_op(ret, ==, 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);
+ /* Junk signature. */
+ ret = desc_sig_is_valid("JUNK", &kp.pubkey, desc, strlen(desc));
+ tt_int_op(ret, ==, 0);
+
+ done:
+ tor_free(desc);
+ tor_free(data);
+}
+
+struct testcase_t hs_descriptor[] = {
+ /* Encoding tests. */
+ { "cert_encoding", test_cert_encoding, TT_FORK,
+ NULL, NULL },
+ { "link_specifier", test_link_specifier, TT_FORK,
+ NULL, NULL },
+ { "encode_descriptor", test_encode_descriptor, TT_FORK,
+ NULL, NULL },
+ { "descriptor_padding", test_descriptor_padding, TT_FORK,
+ NULL, NULL },
+
+ /* Decoding tests. */
+ { "decode_descriptor", test_decode_descriptor, TT_FORK,
+ NULL, NULL },
+ { "encrypted_data_len", test_encrypted_data_len, TT_FORK,
+ NULL, NULL },
+ { "decode_intro_point", test_decode_intro_point, TT_FORK,
+ NULL, NULL },
+ { "decode_plaintext", test_decode_plaintext, TT_FORK,
+ NULL, NULL },
+
+ /* Misc. */
+ { "version", test_supported_version, TT_FORK,
+ NULL, NULL },
+ { "validate_cert", test_validate_cert, TT_FORK,
+ NULL, NULL },
+ { "desc_signature", test_desc_signature, TT_FORK,
+ NULL, NULL },
+
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c
new file mode 100644
index 0000000000..b207cd4ce3
--- /dev/null
+++ b/src/test/test_hs_intropoint.c
@@ -0,0 +1,875 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_service.c
+ * \brief Test hidden service functionality.
+ */
+
+#define HS_SERVICE_PRIVATE
+#define HS_INTROPOINT_PRIVATE
+#define RENDSERVICE_PRIVATE
+#define CIRCUITLIST_PRIVATE
+
+#include "test.h"
+#include "log_test_helpers.h"
+#include "crypto.h"
+#include "log_test_helpers.h"
+
+#include "or.h"
+#include "ht.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"
+
+/* Mock function to avoid networking in unittests */
+static int
+mock_send_intro_established_cell(or_circuit_t *circ)
+{
+ (void) circ;
+ return 0;
+}
+
+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;
+}
+
+static or_circuit_t *
+helper_create_intro_circuit(void)
+{
+ or_circuit_t *circ = or_circuit_new(0, NULL);
+ tt_assert(circ);
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
+ done:
+ return circ;
+}
+
+static hs_cell_introduce1_t *
+helper_create_introduce1_cell(void)
+{
+ hs_cell_introduce1_t *cell = NULL;
+ ed25519_keypair_t auth_key_kp;
+
+ /* Generate the auth_key of the cell. */
+ if (ed25519_keypair_generate(&auth_key_kp, 0) < 0) {
+ goto err;
+ }
+
+ cell = hs_cell_introduce1_new();
+ tt_assert(cell);
+
+ /* Set the auth key. */
+ {
+ size_t auth_key_len = sizeof(auth_key_kp.pubkey);
+ hs_cell_introduce1_set_auth_key_type(cell,
+ HS_INTRO_AUTH_KEY_TYPE_ED25519);
+ hs_cell_introduce1_set_auth_key_len(cell, auth_key_len);
+ hs_cell_introduce1_setlen_auth_key(cell, auth_key_len);
+ uint8_t *auth_key_ptr = hs_cell_introduce1_getarray_auth_key(cell);
+ memcpy(auth_key_ptr, auth_key_kp.pubkey.pubkey, auth_key_len);
+ }
+
+ /* Set the cell extentions to none. */
+ {
+ cell_extension_t *ext = cell_extension_new();
+ cell_extension_set_num(ext, 0);
+ hs_cell_introduce1_set_extensions(cell, ext);
+ }
+
+ /* Set the encrypted section to some data. */
+ {
+ size_t enc_len = 128;
+ hs_cell_introduce1_setlen_encrypted(cell, enc_len);
+ uint8_t *enc_ptr = hs_cell_introduce1_getarray_encrypted(cell);
+ memset(enc_ptr, 'a', enc_len);
+ }
+
+ return cell;
+ err:
+ done:
+ hs_cell_introduce1_free(cell);
+ return NULL;
+}
+
+/* Try sending an ESTABLISH_INTRO cell on a circuit that is already an intro
+ * point. Should fail. */
+static void
+test_establish_intro_wrong_purpose(void *arg)
+{
+ int retval;
+ hs_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};
+
+ (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);
+
+ /* 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);
+
+ /* 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);
+
+ done:
+ hs_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)
+{
+ /* 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);
+}
+
+/* Send an empty ESTABLISH_INTRO cell. Should fail. */
+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};
+
+ (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);
+
+ /* Receive the cell. Should fail. */
+ setup_full_capture_of_logs(LOG_INFO);
+ 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);
+
+ done:
+ circuit_free(TO_CIRCUIT(intro_circ));
+}
+
+/* Send an ESTABLISH_INTRO cell with an unknown auth key type. Should fail. */
+static void
+test_establish_intro_wrong_keytype2(void *arg)
+{
+ int retval;
+ hs_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};
+
+ (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);
+
+ /* 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);
+
+ /* Mutate the auth key type! :) */
+ cell_body[0] = 42;
+
+ /* 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("Unrecognized AUTH_KEY_TYPE 42.");
+ teardown_capture_of_logs();
+ tt_int_op(retval, ==, -1);
+
+ done:
+ hs_cell_establish_intro_free(establish_intro_cell);
+ circuit_free(TO_CIRCUIT(intro_circ));
+}
+
+/* Send a legit ESTABLISH_INTRO cell but with a wrong MAC. Should fail. */
+static void
+test_establish_intro_wrong_mac(void *arg)
+{
+ int retval;
+ hs_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};
+
+ (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);
+
+ /* 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);
+ /* Mangle one byte of the MAC. */
+ uint8_t *handshake_ptr =
+ hs_cell_establish_intro_getarray_handshake_mac(establish_intro_cell);
+ handshake_ptr[TRUNNEL_SHA3_256_LEN - 1]++;
+ /* We need to resign the payload with that change. */
+ {
+ ed25519_signature_t sig;
+ ed25519_keypair_t key_struct;
+ /* New keypair for the signature since we don't have access to the private
+ * key material generated earlier when creating the cell. */
+ retval = ed25519_keypair_generate(&key_struct, 0);
+ tt_int_op(retval, OP_EQ, 0);
+ uint8_t *auth_key_ptr =
+ hs_cell_establish_intro_getarray_auth_key(establish_intro_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);
+
+ retval = ed25519_sign_prefixed(&sig, cell_body,
+ cell_len -
+ (ED25519_SIG_LEN +
+ sizeof(establish_intro_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 =
+ hs_cell_establish_intro_getarray_sig(establish_intro_cell);
+ memcpy(sig_ptr, sig.sig, establish_intro_cell->sig_len);
+ /* Re-encode with the new signature. */
+ cell_len = get_establish_intro_payload(cell_body, sizeof(cell_body),
+ establish_intro_cell);
+ }
+
+ /* Receive the cell. Should fail because our MAC is wrong. */
+ 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 handshake_auth not as expected");
+ teardown_capture_of_logs();
+ tt_int_op(retval, ==, -1);
+
+ done:
+ hs_cell_establish_intro_free(establish_intro_cell);
+ circuit_free(TO_CIRCUIT(intro_circ));
+}
+
+/* Send a legit ESTABLISH_INTRO cell but with a wrong auth key length. Should
+ * fail. */
+static void
+test_establish_intro_wrong_auth_key_len(void *arg)
+{
+ int retval;
+ hs_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;
+ size_t bad_auth_key_len = ED25519_PUBKEY_LEN - 1;
+ uint8_t circuit_key_material[DIGEST_LEN] = {0};
+
+ (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);
+
+ /* 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);
+ /* Mangle the auth key length. */
+ hs_cell_establish_intro_set_auth_key_len(establish_intro_cell,
+ bad_auth_key_len);
+ hs_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);
+
+ /* 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);
+
+ done:
+ hs_cell_establish_intro_free(establish_intro_cell);
+ circuit_free(TO_CIRCUIT(intro_circ));
+}
+
+/* Send a legit ESTABLISH_INTRO cell but with a wrong sig length. Should
+ * fail. */
+static void
+test_establish_intro_wrong_sig_len(void *arg)
+{
+ int retval;
+ hs_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;
+ size_t bad_sig_len = ED25519_SIG_LEN - 1;
+ uint8_t circuit_key_material[DIGEST_LEN] = {0};
+
+ (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);
+
+ /* 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);
+ /* Mangle the signature length. */
+ hs_cell_establish_intro_set_sig_len(establish_intro_cell, bad_sig_len);
+ hs_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);
+
+ /* 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);
+
+ done:
+ hs_cell_establish_intro_free(establish_intro_cell);
+ circuit_free(TO_CIRCUIT(intro_circ));
+}
+
+/* Send a legit ESTABLISH_INTRO cell but slightly change the signature. Should
+ * fail. */
+static void
+test_establish_intro_wrong_sig(void *arg)
+{
+ int retval;
+ hs_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};
+
+ (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);
+
+ /* 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);
+
+ /* Mutate the last byte (signature)! :) */
+ 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);
+ expect_log_msg_containing("Failed to verify ESTABLISH_INTRO cell.");
+ teardown_capture_of_logs();
+ tt_int_op(retval, ==, -1);
+
+ done:
+ hs_cell_establish_intro_free(establish_intro_cell);
+ circuit_free(TO_CIRCUIT(intro_circ));
+}
+
+/* Helper function: Send a well-formed v3 ESTABLISH_INTRO cell to
+ * <b>intro_circ</b>. Return the cell. */
+static hs_cell_establish_intro_t *
+helper_establish_intro_v3(or_circuit_t *intro_circ)
+{
+ int retval;
+ hs_cell_establish_intro_t *establish_intro_cell = NULL;
+ uint8_t cell_body[RELAY_PAYLOAD_SIZE];
+ ssize_t cell_len = 0;
+ uint8_t circuit_key_material[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);
+
+ /* 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);
+
+ /* Receive the cell */
+ retval = hs_intro_received_establish_intro(intro_circ, cell_body, cell_len);
+ tt_int_op(retval, ==, 0);
+
+ done:
+ return establish_intro_cell;
+}
+
+/* Helper function: Send a well-formed v2 ESTABLISH_INTRO cell to
+ * <b>intro_circ</b>. Return the public key advertised in the cell. */
+static crypto_pk_t *
+helper_establish_intro_v2(or_circuit_t *intro_circ)
+{
+ crypto_pk_t *key1 = NULL;
+ int retval;
+ uint8_t cell_body[RELAY_PAYLOAD_SIZE];
+ ssize_t cell_len = 0;
+ uint8_t circuit_key_material[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);
+
+ /* 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);
+
+ /* Receive legacy establish_intro */
+ retval = hs_intro_received_establish_intro(intro_circ,
+ cell_body, cell_len);
+ tt_int_op(retval, ==, 0);
+
+ done:
+ return key1;
+}
+
+/** Successfuly register a v2 intro point and a v3 intro point. Ensure that HS
+ * circuitmap is maintained properly. */
+static void
+test_intro_point_registration(void *arg)
+{
+ int retval;
+ hs_circuitmap_ht *the_hs_circuitmap = NULL;
+
+ or_circuit_t *intro_circ = NULL;
+ hs_cell_establish_intro_t *establish_intro_cell = NULL;
+ ed25519_public_key_t auth_key;
+
+ crypto_pk_t *legacy_auth_key = NULL;
+ or_circuit_t *legacy_intro_circ = NULL;
+
+ or_circuit_t *returned_intro_circ = NULL;
+
+ (void) arg;
+
+ MOCK(hs_intro_send_intro_established_cell, mock_send_intro_established_cell);
+
+ hs_circuitmap_init();
+
+ /* Check that the circuitmap is currently empty */
+ {
+ the_hs_circuitmap = get_hs_circuitmap();
+ tt_assert(the_hs_circuitmap);
+ tt_int_op(0, ==, HT_SIZE(the_hs_circuitmap));
+ /* Do a circuitmap query in any case */
+ returned_intro_circ = hs_circuitmap_get_intro_circ_v3(&auth_key);
+ tt_ptr_op(returned_intro_circ, ==, NULL);
+ }
+
+ /* Create a v3 intro point */
+ {
+ intro_circ = or_circuit_new(0, NULL);
+ tt_assert(intro_circ);
+ establish_intro_cell = helper_establish_intro_v3(intro_circ);
+
+ /* 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));
+ get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO,
+ establish_intro_cell);
+ returned_intro_circ = hs_circuitmap_get_intro_circ_v3(&auth_key);
+ tt_ptr_op(intro_circ, ==, returned_intro_circ);
+ }
+
+ /* Create a v2 intro point */
+ {
+ char key_digest[DIGEST_LEN];
+
+ legacy_intro_circ = or_circuit_new(1, NULL);
+ tt_assert(legacy_intro_circ);
+ legacy_auth_key = helper_establish_intro_v2(legacy_intro_circ);
+ tt_assert(legacy_auth_key);
+
+ /* 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));
+
+ /* 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);
+ returned_intro_circ= hs_circuitmap_get_intro_circ_v2((uint8_t*)key_digest);
+ tt_ptr_op(legacy_intro_circ, ==, returned_intro_circ);
+ }
+
+ /* XXX Continue test and try to register a second v3 intro point with the
+ * same auth key. Make sure that old intro circuit gets closed. */
+
+ done:
+ crypto_pk_free(legacy_auth_key);
+ circuit_free(TO_CIRCUIT(intro_circ));
+ circuit_free(TO_CIRCUIT(legacy_intro_circ));
+ hs_cell_establish_intro_free(establish_intro_cell);
+
+ { /* Test circuitmap free_all function. */
+ the_hs_circuitmap = get_hs_circuitmap();
+ tt_assert(the_hs_circuitmap);
+ hs_circuitmap_free_all();
+ the_hs_circuitmap = get_hs_circuitmap();
+ tt_assert(!the_hs_circuitmap);
+ }
+
+ UNMOCK(hs_intro_send_intro_established_cell);
+}
+
+static void
+test_introduce1_suitable_circuit(void *arg)
+{
+ int ret;
+ or_circuit_t *circ = NULL;
+
+ (void) arg;
+
+ /* Valid suitable circuit. */
+ {
+ circ = or_circuit_new(0, NULL);
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
+ ret = circuit_is_suitable_for_introduce1(circ);
+ circuit_free(TO_CIRCUIT(circ));
+ tt_int_op(ret, OP_EQ, 1);
+ }
+
+ /* Test if the circuit purpose safeguard works correctly. */
+ {
+ circ = or_circuit_new(0, NULL);
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT);
+ ret = circuit_is_suitable_for_introduce1(circ);
+ circuit_free(TO_CIRCUIT(circ));
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Test the non-edge circuit safeguard works correctly. */
+ {
+ circ = or_circuit_new(0, NULL);
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
+ /* Bogus pointer, the check is against NULL on n_chan. */
+ circ->base_.n_chan = (channel_t *) circ;
+ ret = circuit_is_suitable_for_introduce1(circ);
+ circuit_free(TO_CIRCUIT(circ));
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Mangle the circuit a bit more so see if our only one INTRODUCE1 cell
+ * limit works correctly. */
+ {
+ circ = or_circuit_new(0, NULL);
+ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_OR);
+ circ->already_received_introduce1 = 1;
+ ret = circuit_is_suitable_for_introduce1(circ);
+ circuit_free(TO_CIRCUIT(circ));
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ done:
+ ;
+}
+
+static void
+test_introduce1_is_legacy(void *arg)
+{
+ int ret;
+ uint8_t request[256];
+
+ (void) arg;
+
+ /* For a cell to be considered legacy, according to the specification, the
+ * first 20 bytes MUST BE non-zero else it's a v3 cell. */
+ memset(request, 'a', DIGEST_LEN);
+ memset(request + DIGEST_LEN, 0, sizeof(request) - DIGEST_LEN);
+ ret = introduce1_cell_is_legacy(request);
+ tt_int_op(ret, OP_EQ, 1);
+
+ /* This is a NON legacy cell. */
+ memset(request, 0, DIGEST_LEN);
+ memset(request + DIGEST_LEN, 'a', sizeof(request) - DIGEST_LEN);
+ ret = introduce1_cell_is_legacy(request);
+ tt_int_op(ret, OP_EQ, 0);
+
+ done:
+ ;
+}
+
+static void
+test_introduce1_validation(void *arg)
+{
+ int ret;
+ hs_cell_introduce1_t *cell = NULL;
+
+ (void) arg;
+
+ /* Create our decoy cell that we'll modify as we go to test the validation
+ * function of that parsed cell. */
+ cell = helper_create_introduce1_cell();
+
+ /* It should NOT be a legacy cell which will trigger a BUG(). */
+ memset(cell->legacy_key_id, 'a', sizeof(cell->legacy_key_id));
+ tor_capture_bugs_(1);
+ ret = validate_introduce1_parsed_cell(cell);
+ tor_end_capture_bugs_();
+ tt_int_op(ret, OP_EQ, -1);
+ /* Reset legacy ID and make sure it's correct. */
+ memset(cell->legacy_key_id, 0, sizeof(cell->legacy_key_id));
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, 0);
+
+ /* Non existing auth key type. */
+ cell->auth_key_type = 42;
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Reset is to correct value and make sure it's correct. */
+ cell->auth_key_type = HS_INTRO_AUTH_KEY_TYPE_ED25519;
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, 0);
+
+ /* Really bad key length. */
+ cell->auth_key_len = 0;
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, -1);
+ cell->auth_key_len = UINT16_MAX;
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Correct size, let's try that. */
+ cell->auth_key_len = sizeof(ed25519_public_key_t);
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, 0);
+ /* Set an invalid size of the auth key buffer. */
+ hs_cell_introduce1_setlen_auth_key(cell, 3);
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Reset auth key buffer and make sure it works. */
+ hs_cell_introduce1_setlen_auth_key(cell, sizeof(ed25519_public_key_t));
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, 0);
+
+ /* Empty encrypted section. */
+ hs_cell_introduce1_setlen_encrypted(cell, 0);
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, -1);
+ /* Reset it to some non zero bytes and validate. */
+ hs_cell_introduce1_setlen_encrypted(cell, 1);
+ ret = validate_introduce1_parsed_cell(cell);
+ tt_int_op(ret, OP_EQ, 0);
+
+ done:
+ hs_cell_introduce1_free(cell);
+}
+
+static void
+test_received_introduce1_handling(void *arg)
+{
+ int ret;
+ uint8_t *request = NULL, buf[128];
+ hs_cell_introduce1_t *cell = NULL;
+ or_circuit_t *circ = NULL;
+
+ (void) arg;
+
+ MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge);
+
+ hs_circuitmap_init();
+
+ /* Too small request length. An INTRODUCE1 expect at the very least a
+ * DIGEST_LEN size. */
+ {
+ circ = helper_create_intro_circuit();
+ ret = hs_intro_received_introduce1(circ, buf, DIGEST_LEN - 1);
+ tt_int_op(ret, OP_EQ, -1);
+ circuit_free(TO_CIRCUIT(circ));
+ }
+
+ /* We have a unit test only for the suitability of a circuit to receive an
+ * INTRODUCE1 cell so from now on we'll only test the handling of a cell. */
+
+ /* Bad request. */
+ {
+ circ = helper_create_intro_circuit();
+ uint8_t test[2]; /* Too small request. */
+ ret = handle_introduce1(circ, test, sizeof(test));
+ tor_free(circ->p_chan);
+ circuit_free(TO_CIRCUIT(circ));
+ tt_int_op(ret, OP_EQ, -1);
+ }
+
+ /* Valid case. */
+ {
+ cell = helper_create_introduce1_cell();
+ ssize_t request_len = hs_cell_introduce1_encoded_len(cell);
+ tt_size_op(request_len, OP_GT, 0);
+ request = tor_malloc_zero(request_len);
+ ssize_t encoded_len =
+ hs_cell_introduce1_encode(request, request_len, cell);
+ tt_size_op(encoded_len, OP_GT, 0);
+
+ circ = helper_create_intro_circuit();
+ or_circuit_t *service_circ = helper_create_intro_circuit();
+ circuit_change_purpose(TO_CIRCUIT(service_circ),
+ CIRCUIT_PURPOSE_INTRO_POINT);
+ /* Register the circuit in the map for the auth key of the cell. */
+ ed25519_public_key_t auth_key;
+ const uint8_t *cell_auth_key =
+ hs_cell_introduce1_getconstarray_auth_key(cell);
+ memcpy(auth_key.pubkey, cell_auth_key, ED25519_PUBKEY_LEN);
+ hs_circuitmap_register_intro_circ_v3(service_circ, &auth_key);
+ ret = hs_intro_received_introduce1(circ, request, request_len);
+ circuit_free(TO_CIRCUIT(circ));
+ circuit_free(TO_CIRCUIT(service_circ));
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ /* Valid legacy cell. */
+ {
+ tor_free(request);
+ hs_cell_introduce1_free(cell);
+ cell = helper_create_introduce1_cell();
+ uint8_t *legacy_key_id = hs_cell_introduce1_getarray_legacy_key_id(cell);
+ memset(legacy_key_id, 'a', DIGEST_LEN);
+ /* Add an arbitrary amount of data for the payload of a v2 cell. */
+ size_t request_len = hs_cell_introduce1_encoded_len(cell) + 256;
+ tt_size_op(request_len, OP_GT, 0);
+ request = tor_malloc_zero(request_len + 256);
+ ssize_t encoded_len =
+ hs_cell_introduce1_encode(request, request_len, cell);
+ tt_size_op(encoded_len, OP_GT, 0);
+
+ circ = helper_create_intro_circuit();
+ or_circuit_t *service_circ = helper_create_intro_circuit();
+ circuit_change_purpose(TO_CIRCUIT(service_circ),
+ CIRCUIT_PURPOSE_INTRO_POINT);
+ /* Register the circuit in the map for the auth key of the cell. */
+ uint8_t token[REND_TOKEN_LEN];
+ memcpy(token, legacy_key_id, sizeof(token));
+ hs_circuitmap_register_intro_circ_v2(service_circ, token);
+ ret = hs_intro_received_introduce1(circ, request, request_len);
+ circuit_free(TO_CIRCUIT(circ));
+ circuit_free(TO_CIRCUIT(service_circ));
+ tt_int_op(ret, OP_EQ, 0);
+ }
+
+ done:
+ hs_cell_introduce1_free(cell);
+ tor_free(request);
+ hs_circuitmap_free_all();
+ UNMOCK(relay_send_command_from_edge_);
+}
+
+struct testcase_t hs_intropoint_tests[] = {
+ { "intro_point_registration",
+ test_intro_point_registration, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_keytype",
+ test_establish_intro_wrong_keytype, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_keytype2",
+ test_establish_intro_wrong_keytype2, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_purpose",
+ test_establish_intro_wrong_purpose, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_sig",
+ test_establish_intro_wrong_sig, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_sig_len",
+ test_establish_intro_wrong_sig_len, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_auth_key_len",
+ test_establish_intro_wrong_auth_key_len, TT_FORK, NULL, NULL },
+
+ { "receive_establish_intro_wrong_mac",
+ test_establish_intro_wrong_mac, TT_FORK, NULL, NULL },
+
+ { "introduce1_suitable_circuit",
+ test_introduce1_suitable_circuit, TT_FORK, NULL, NULL },
+
+ { "introduce1_is_legacy",
+ test_introduce1_is_legacy, TT_FORK, NULL, NULL },
+
+ { "introduce1_validation",
+ test_introduce1_validation, TT_FORK, NULL, NULL },
+
+ { "received_introduce1_handling",
+ test_received_introduce1_handling, TT_FORK, NULL, NULL },
+
+ END_OF_TESTCASES
+};
+
diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c
new file mode 100644
index 0000000000..039d727cea
--- /dev/null
+++ b/src/test/test_hs_service.c
@@ -0,0 +1,111 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file test_hs_service.c
+ * \brief Test hidden service functionality.
+ */
+
+#define HS_SERVICE_PRIVATE
+#define HS_INTROPOINT_PRIVATE
+
+#include "test.h"
+#include "log_test_helpers.h"
+#include "crypto.h"
+
+#include "hs/cell_establish_intro.h"
+#include "hs_service.h"
+#include "hs_intropoint.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 retval;
+ uint8_t circuit_key_material[DIGEST_LEN] = {0};
+ uint8_t buf[RELAY_PAYLOAD_SIZE];
+ hs_cell_establish_intro_t *cell_out = NULL;
+ hs_cell_establish_intro_t *cell_in = NULL;
+
+ crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
+
+ /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we
+ attempt to parse it. */
+ {
+ cell_out = generate_establish_intro_cell(circuit_key_material,
+ sizeof(circuit_key_material));
+ tt_assert(cell_out);
+
+ retval = get_establish_intro_payload(buf, sizeof(buf), cell_out);
+ tt_int_op(retval, >=, 0);
+ }
+
+ /* Parse it as the receiver */
+ {
+ ssize_t parse_result = hs_cell_establish_intro_parse(&cell_in,
+ buf, sizeof(buf));
+ tt_int_op(parse_result, >=, 0);
+
+ retval = verify_establish_intro_cell(cell_in,
+ circuit_key_material,
+ sizeof(circuit_key_material));
+ tt_int_op(retval, >=, 0);
+ }
+
+ done:
+ hs_cell_establish_intro_free(cell_out);
+ hs_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;
+ hs_cell_establish_intro_t *cell = NULL;
+ uint8_t circuit_key_material[DIGEST_LEN] = {0};
+
+ MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed);
+
+ crypto_rand((char *) circuit_key_material, sizeof(circuit_key_material));
+
+ 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.");
+ teardown_capture_of_logs();
+ tt_assert(!cell);
+
+ done:
+ hs_cell_establish_intro_free(cell);
+ UNMOCK(ed25519_sign_prefixed);
+}
+
+struct testcase_t hs_service_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_link_handshake.c b/src/test/test_link_handshake.c
index ddf66f4d34..467bba3a64 100644
--- a/src/test/test_link_handshake.c
+++ b/src/test/test_link_handshake.c
@@ -6,13 +6,27 @@
#define CHANNELTLS_PRIVATE
#define CONNECTION_PRIVATE
#define TOR_CHANNEL_INTERNAL_
+#define TORTLS_PRIVATE
+
+#include "compat.h"
+
+/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in
+ * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */
+DISABLE_GCC_WARNING(redundant-decls)
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+ENABLE_GCC_WARNING(redundant-decls)
+
#include "or.h"
#include "config.h"
#include "connection.h"
#include "connection_or.h"
#include "channeltls.h"
#include "link_handshake.h"
+#include "router.h"
+#include "routerkeys.h"
#include "scheduler.h"
+#include "torcert.h"
#include "test.h"
#include "log_test_helpers.h"
@@ -37,6 +51,16 @@ mock_tls_cert_matches_key(const tor_tls_t *tls, const tor_x509_cert_t *cert)
(void) cert; // XXXX look at this.
return 1;
}
+static tor_tls_t *mock_peer_cert_expect_tortls = NULL;
+static tor_x509_cert_t *mock_peer_cert = NULL;
+static tor_x509_cert_t *
+mock_get_peer_cert(tor_tls_t *tls)
+{
+ if (mock_peer_cert_expect_tortls &&
+ mock_peer_cert_expect_tortls != tls)
+ return NULL;
+ return tor_x509_cert_dup(mock_peer_cert);
+}
static int mock_send_netinfo_called = 0;
static int
@@ -57,14 +81,29 @@ mock_close_for_err(or_connection_t *orconn, int flush)
}
static int mock_send_authenticate_called = 0;
+static int mock_send_authenticate_called_with_type = 0;
static int
mock_send_authenticate(or_connection_t *conn, int type)
{
(void) conn;
- (void) type;
+ mock_send_authenticate_called_with_type = type;
++mock_send_authenticate_called;// XXX check_this
return 0;
}
+static int
+mock_export_key_material(tor_tls_t *tls, uint8_t *secrets_out,
+ const uint8_t *context,
+ size_t context_len,
+ const char *label)
+{
+ (void) tls;
+ (void)secrets_out;
+ (void)context;
+ (void)context_len;
+ (void)label;
+ memcpy(secrets_out, "int getRandomNumber(){return 4;}", 32);
+ return 0;
+}
static tor_x509_cert_t *mock_own_cert = NULL;
static tor_x509_cert_t *
@@ -78,20 +117,23 @@ mock_get_own_cert(tor_tls_t *tls)
static void
test_link_handshake_certs_ok(void *arg)
{
- (void) arg;
-
or_connection_t *c1 = or_connection_new(CONN_TYPE_OR, AF_INET);
or_connection_t *c2 = or_connection_new(CONN_TYPE_OR, AF_INET);
var_cell_t *cell1 = NULL, *cell2 = NULL;
certs_cell_t *cc1 = NULL, *cc2 = NULL;
channel_tls_t *chan1 = NULL, *chan2 = NULL;
crypto_pk_t *key1 = NULL, *key2 = NULL;
+ const int with_ed = !strcmp((const char *)arg, "Ed25519");
+
+ tor_addr_from_ipv4h(&c1->base_.addr, 0x7f000001);
+ tor_addr_from_ipv4h(&c2->base_.addr, 0x7f000001);
scheduler_init();
MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key);
MOCK(connection_or_write_var_cell_to_buf, mock_write_var_cell);
MOCK(connection_or_send_netinfo, mock_send_netinfo);
+ MOCK(tor_tls_get_peer_cert, mock_get_peer_cert);
MOCK(tor_tls_get_own_cert, mock_get_own_cert);
key1 = pk_generate(2);
@@ -103,6 +145,15 @@ test_link_handshake_certs_ok(void *arg)
tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER,
key1, key2, 86400), ==, 0);
+ if (with_ed) {
+ /* If we're making a CERTS cell for an ed handshake, let's make sure we
+ * have some Ed25519 certificates and keys. */
+ init_mock_ed_keys(key2);
+ } else {
+ certs_cell_ed25519_disabled_for_testing = 1;
+ }
+
+ /* c1 has started_here == 1 */
{
const tor_x509_cert_t *link_cert = NULL;
tt_assert(!tor_tls_get_my_certs(1, &link_cert, NULL));
@@ -113,6 +164,7 @@ test_link_handshake_certs_ok(void *arg)
c1->link_proto = 3;
tt_int_op(connection_init_or_handshake_state(c1, 1), ==, 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);
@@ -136,8 +188,13 @@ test_link_handshake_certs_ok(void *arg)
tt_int_op(cell2->payload_len, ==,
certs_cell_parse(&cc2, cell2->payload, cell2->payload_len));
- tt_int_op(2, ==, cc1->n_certs);
- tt_int_op(2, ==, cc2->n_certs);
+ if (with_ed) {
+ tt_int_op(5, ==, cc1->n_certs);
+ tt_int_op(5, ==, cc2->n_certs);
+ } else {
+ tt_int_op(2, ==, cc1->n_certs);
+ tt_int_op(2, ==, cc2->n_certs);
+ }
tt_int_op(certs_cell_get_certs(cc1, 0)->cert_type, ==,
CERTTYPE_RSA1024_ID_AUTH);
@@ -149,6 +206,22 @@ test_link_handshake_certs_ok(void *arg)
tt_int_op(certs_cell_get_certs(cc2, 1)->cert_type, ==,
CERTTYPE_RSA1024_ID_ID);
+ if (with_ed) {
+ tt_int_op(certs_cell_get_certs(cc1, 2)->cert_type, ==,
+ CERTTYPE_ED_ID_SIGN);
+ tt_int_op(certs_cell_get_certs(cc1, 3)->cert_type, ==,
+ CERTTYPE_ED_SIGN_AUTH);
+ tt_int_op(certs_cell_get_certs(cc1, 4)->cert_type, ==,
+ CERTTYPE_RSA1024_ID_EDID);
+
+ tt_int_op(certs_cell_get_certs(cc2, 2)->cert_type, ==,
+ CERTTYPE_ED_ID_SIGN);
+ tt_int_op(certs_cell_get_certs(cc2, 3)->cert_type, ==,
+ CERTTYPE_ED_SIGN_LINK);
+ tt_int_op(certs_cell_get_certs(cc2, 4)->cert_type, ==,
+ CERTTYPE_RSA1024_ID_EDID);
+ }
+
chan1 = tor_malloc_zero(sizeof(*chan1));
channel_tls_common_init(chan1);
c1->chan = chan1;
@@ -159,13 +232,39 @@ test_link_handshake_certs_ok(void *arg)
c1->base_.conn_array_index = -1;
crypto_pk_get_digest(key2, c1->identity_digest);
+ if (with_ed) {
+ const tor_x509_cert_t *linkc, *idc;
+ tor_tls_get_my_certs(1, &linkc, &idc);
+ mock_peer_cert_expect_tortls = c1->tls; /* We should see this tls... */
+ mock_peer_cert = tor_x509_cert_dup(linkc); /* and when we do, the peer's
+ * cert is this... */
+ }
channel_tls_process_certs_cell(cell2, chan1);
+ mock_peer_cert_expect_tortls = NULL;
+ tor_x509_cert_free(mock_peer_cert);
+ mock_peer_cert = NULL;
+
+ tor_assert(c1->handshake_state->authenticated);
tt_assert(c1->handshake_state->received_certs_cell);
- tt_assert(c1->handshake_state->auth_cert == NULL);
- tt_assert(c1->handshake_state->id_cert);
+ tt_assert(c1->handshake_state->certs->auth_cert == NULL);
+ tt_assert(c1->handshake_state->certs->ed_sign_auth == NULL);
+ tt_assert(c1->handshake_state->certs->id_cert);
+ if (with_ed) {
+ tt_assert(c1->handshake_state->certs->ed_sign_link);
+ tt_assert(c1->handshake_state->certs->ed_rsa_crosscert);
+ tt_assert(c1->handshake_state->certs->ed_id_sign);
+ 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_assert(c1->handshake_state->authenticated_rsa);
+ tt_assert(! c1->handshake_state->authenticated_ed25519);
+ }
tt_assert(! tor_mem_is_zero(
- (char*)c1->handshake_state->authenticated_peer_id, 20));
+ (char*)c1->handshake_state->authenticated_rsa_peer_id, 20));
chan2 = tor_malloc_zero(sizeof(*chan2));
channel_tls_common_init(chan2);
@@ -180,18 +279,34 @@ test_link_handshake_certs_ok(void *arg)
channel_tls_process_certs_cell(cell1, chan2);
tt_assert(c2->handshake_state->received_certs_cell);
- tt_assert(c2->handshake_state->auth_cert);
- tt_assert(c2->handshake_state->id_cert);
+ if (with_ed) {
+ tt_assert(c2->handshake_state->certs->ed_sign_auth);
+ tt_assert(c2->handshake_state->certs->ed_rsa_crosscert);
+ 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_assert(c2->handshake_state->certs->id_cert);
tt_assert(tor_mem_is_zero(
- (char*)c2->handshake_state->authenticated_peer_id, 20));
+ (char*)c2->handshake_state->authenticated_rsa_peer_id, 20));
+ /* no authentication has happened yet, since we haen't gotten an AUTH cell.
+ */
+ tt_assert(! c2->handshake_state->authenticated);
+ tt_assert(! c2->handshake_state->authenticated_rsa);
+ tt_assert(! c2->handshake_state->authenticated_ed25519);
done:
UNMOCK(tor_tls_cert_matches_key);
UNMOCK(connection_or_write_var_cell_to_buf);
UNMOCK(connection_or_send_netinfo);
+ UNMOCK(tor_tls_get_peer_cert);
UNMOCK(tor_tls_get_own_cert);
tor_x509_cert_free(mock_own_cert);
- mock_own_cert = NULL;
+ tor_x509_cert_free(mock_peer_cert);
+ mock_own_cert = mock_peer_cert = NULL;
memset(c1->identity_digest, 0, sizeof(c1->identity_digest));
memset(c2->identity_digest, 0, sizeof(c2->identity_digest));
connection_free_(TO_CONN(c1));
@@ -211,6 +326,8 @@ test_link_handshake_certs_ok(void *arg)
}
typedef struct certs_data_s {
+ int is_ed;
+ int is_link_cert;
or_connection_t *c;
channel_tls_t *chan;
certs_cell_t *ccell;
@@ -226,11 +343,13 @@ recv_certs_cleanup(const struct testcase_t *test, void *obj)
UNMOCK(tor_tls_cert_matches_key);
UNMOCK(connection_or_send_netinfo);
UNMOCK(connection_or_close_for_error);
+ UNMOCK(tor_tls_get_peer_cert);
+ UNMOCK(tor_tls_get_own_cert);
if (d) {
tor_free(d->cell);
certs_cell_free(d->ccell);
- connection_or_remove_from_identity_map(d->c);
+ connection_or_clear_identity(d->c);
connection_free_(TO_CONN(d->c));
circuitmux_free(d->chan->base_.cmux);
tor_free(d->chan);
@@ -238,6 +357,7 @@ recv_certs_cleanup(const struct testcase_t *test, void *obj)
crypto_pk_free(d->key2);
tor_free(d);
}
+ routerkeys_free_all();
return 1;
}
@@ -249,11 +369,18 @@ recv_certs_setup(const struct testcase_t *test)
certs_cell_cert_t *ccc1 = NULL;
certs_cell_cert_t *ccc2 = NULL;
ssize_t n;
+ int is_ed = d->is_ed = !strcmpstart(test->setup_data, "Ed25519");
+ int is_rsa = !strcmpstart(test->setup_data, "RSA");
+ int is_link = d->is_link_cert = !strcmpend(test->setup_data, "-Link");
+ int is_auth = !strcmpend(test->setup_data, "-Auth");
+ tor_assert(is_ed != is_rsa);
+ tor_assert(is_link != is_auth);
d->c = or_connection_new(CONN_TYPE_OR, AF_INET);
d->chan = tor_malloc_zero(sizeof(*d->chan));
d->c->chan = d->chan;
d->c->base_.address = tor_strdup("HaveAnAddress");
+ 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);
@@ -264,19 +391,25 @@ recv_certs_setup(const struct testcase_t *test)
tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER,
d->key1, d->key2, 86400), ==, 0);
+ if (is_ed) {
+ init_mock_ed_keys(d->key2);
+ } else {
+ routerkeys_free_all();
+ }
+
d->ccell = certs_cell_new();
ccc1 = certs_cell_cert_new();
certs_cell_add_certs(d->ccell, ccc1);
ccc2 = certs_cell_cert_new();
certs_cell_add_certs(d->ccell, ccc2);
d->ccell->n_certs = 2;
- ccc1->cert_type = 1;
+ ccc1->cert_type = is_link ? 1 : 3;
ccc2->cert_type = 2;
const tor_x509_cert_t *a,*b;
const uint8_t *enca, *encb;
size_t lena, lenb;
- tor_tls_get_my_certs(1, &a, &b);
+ tor_tls_get_my_certs(is_link ? 1 : 0, &a, &b);
tor_x509_cert_get_der(a, &enca, &lena);
tor_x509_cert_get_der(b, &encb, &lenb);
certs_cell_cert_setlen_body(ccc1, lena);
@@ -287,6 +420,41 @@ recv_certs_setup(const struct testcase_t *test)
memcpy(certs_cell_cert_getarray_body(ccc1), enca, lena);
memcpy(certs_cell_cert_getarray_body(ccc2), encb, lenb);
+ if (is_ed) {
+ certs_cell_cert_t *ccc3 = NULL; /* Id->Sign */
+ certs_cell_cert_t *ccc4 = NULL; /* Sign->Link or Sign->Auth. */
+ certs_cell_cert_t *ccc5 = NULL; /* RSAId->Ed Id. */
+ const tor_cert_t *id_sign = get_master_signing_key_cert();
+ const tor_cert_t *secondary =
+ is_link ? get_current_link_cert_cert() : get_current_auth_key_cert();
+ const uint8_t *cc = NULL;
+ size_t cc_sz;
+ get_master_rsa_crosscert(&cc, &cc_sz);
+
+ ccc3 = certs_cell_cert_new();
+ ccc4 = certs_cell_cert_new();
+ ccc5 = certs_cell_cert_new();
+ certs_cell_add_certs(d->ccell, ccc3);
+ certs_cell_add_certs(d->ccell, ccc4);
+ certs_cell_add_certs(d->ccell, ccc5);
+ ccc3->cert_len = id_sign->encoded_len;
+ ccc4->cert_len = secondary->encoded_len;
+ ccc5->cert_len = cc_sz;
+ certs_cell_cert_setlen_body(ccc3, ccc3->cert_len);
+ certs_cell_cert_setlen_body(ccc4, ccc4->cert_len);
+ certs_cell_cert_setlen_body(ccc5, ccc5->cert_len);
+ memcpy(certs_cell_cert_getarray_body(ccc3), id_sign->encoded,
+ ccc3->cert_len);
+ memcpy(certs_cell_cert_getarray_body(ccc4), secondary->encoded,
+ ccc4->cert_len);
+ memcpy(certs_cell_cert_getarray_body(ccc5), cc, ccc5->cert_len);
+ ccc3->cert_type = 4;
+ ccc4->cert_type = is_link ? 5 : 6;
+ ccc5->cert_type = 7;
+
+ d->ccell->n_certs = 5;
+ }
+
d->cell = var_cell_new(4096);
d->cell->command = CELL_CERTS;
@@ -297,6 +465,12 @@ recv_certs_setup(const struct testcase_t *test)
MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key);
MOCK(connection_or_send_netinfo, mock_send_netinfo);
MOCK(connection_or_close_for_error, mock_close_for_err);
+ MOCK(tor_tls_get_peer_cert, mock_get_peer_cert);
+
+ if (is_link) {
+ /* Say that this is the peer's certificate */
+ 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);
@@ -320,9 +494,24 @@ test_link_handshake_recv_certs_ok(void *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->id_cert != NULL);
- tt_assert(d->c->handshake_state->auth_cert == NULL);
+ tt_assert(d->c->handshake_state->certs->id_cert != NULL);
+ tt_assert(d->c->handshake_state->certs->auth_cert == 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);
+ } 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);
+ }
done:
;
@@ -333,17 +522,20 @@ test_link_handshake_recv_certs_ok_server(void *arg)
{
certs_data_t *d = arg;
d->c->handshake_state->started_here = 0;
- certs_cell_get_certs(d->ccell, 0)->cert_type = 3;
- certs_cell_get_certs(d->ccell, 1)->cert_type = 2;
- ssize_t n = certs_cell_encode(d->cell->payload, 2048, d->ccell);
- tt_int_op(n, >, 0);
- d->cell->payload_len = n;
+ 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->id_cert != NULL);
- tt_assert(d->c->handshake_state->auth_cert != NULL);
+ tt_assert(d->c->handshake_state->certs->id_cert != NULL);
+ tt_assert(d->c->handshake_state->certs->link_cert == 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);
+ } else {
+ tt_assert(d->c->handshake_state->certs->ed_sign_auth == NULL);
+ tt_assert(d->c->handshake_state->certs->auth_cert != NULL);
+ }
done:
;
@@ -361,6 +553,8 @@ test_link_handshake_recv_certs_ok_server(void *arg)
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); \
if (require_failure_message) { \
expect_log_msg_containing(require_failure_message); \
} \
@@ -401,12 +595,41 @@ CERTS_FAIL(truncated_3,
d->cell->payload_len = 7;
memcpy(d->cell->payload, "\x01\x01\x00\x05""abc", 7);
})
+CERTS_FAIL(truncated_4, /* ed25519 */
+ {
+ require_failure_message = "It couldn't be parsed";
+ d->cell->payload_len -= 10;
+ })
+CERTS_FAIL(truncated_5, /* ed25519 */
+ {
+ require_failure_message = "It couldn't be parsed";
+ d->cell->payload_len -= 100;
+ })
+
#define REENCODE() do { \
+ 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); \
d->cell->payload_len = n; \
} while (0)
+CERTS_FAIL(truncated_6, /* ed25519 */
+ {
+ /* truncate the link certificate */
+ require_failure_message = "undecodable Ed certificate";
+ certs_cell_cert_setlen_body(certs_cell_get_certs(d->ccell, 3), 7);
+ certs_cell_get_certs(d->ccell, 3)->cert_len = 7;
+ REENCODE();
+ })
+CERTS_FAIL(truncated_7, /* ed25519 */
+ {
+ /* truncate the crosscert */
+ require_failure_message = "Unparseable or overlong crosscert";
+ certs_cell_cert_setlen_body(certs_cell_get_certs(d->ccell, 4), 7);
+ certs_cell_get_certs(d->ccell, 4)->cert_len = 7;
+ REENCODE();
+ })
CERTS_FAIL(not_x509,
{
require_failure_message = "Received undecodable certificate";
@@ -435,6 +658,206 @@ CERTS_FAIL(both_auth,
certs_cell_get_certs(d->ccell, 1)->cert_type = 3;
REENCODE();
})
+CERTS_FAIL(duplicate_id, /* ed25519 */
+ {
+ require_failure_message = "Duplicate Ed25519 certificate";
+ certs_cell_get_certs(d->ccell, 2)->cert_type = 4;
+ certs_cell_get_certs(d->ccell, 3)->cert_type = 4;
+ REENCODE();
+ })
+CERTS_FAIL(duplicate_link, /* ed25519 */
+ {
+ require_failure_message = "Duplicate Ed25519 certificate";
+ certs_cell_get_certs(d->ccell, 2)->cert_type = 5;
+ certs_cell_get_certs(d->ccell, 3)->cert_type = 5;
+ REENCODE();
+ })
+CERTS_FAIL(duplicate_crosscert, /* ed25519 */
+ {
+ require_failure_message = "Duplicate RSA->Ed25519 crosscert";
+ certs_cell_get_certs(d->ccell, 2)->cert_type = 7;
+ certs_cell_get_certs(d->ccell, 3)->cert_type = 7;
+ REENCODE();
+ })
+static void
+test_link_handshake_recv_certs_missing_id(void *arg) /* ed25519 */
+{
+ certs_data_t *d = arg;
+ tt_int_op(certs_cell_getlen_certs(d->ccell), OP_EQ, 5);
+ certs_cell_set_certs(d->ccell, 2, certs_cell_get_certs(d->ccell, 4));
+ certs_cell_set0_certs(d->ccell, 4, NULL); /* prevent free */
+ certs_cell_setlen_certs(d->ccell, 4);
+ d->ccell->n_certs = 4;
+ REENCODE();
+
+ /* 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);
+ done:
+ ;
+}
+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);
+ /* replace this with a valid master->signing cert, but with no
+ * signing key. */
+ const ed25519_keypair_t *mk = get_master_identity_keypair();
+ const ed25519_keypair_t *sk = get_master_signing_keypair();
+ tor_cert_t *bad_cert = tor_cert_create(mk, CERT_TYPE_ID_SIGNING,
+ &sk->pubkey, time(NULL), 86400,
+ 0 /* don't include signer */);
+ certs_cell_cert_setlen_body(cert, bad_cert->encoded_len);
+ memcpy(certs_cell_cert_getarray_body(cert),
+ bad_cert->encoded, bad_cert->encoded_len);
+ cert->cert_len = bad_cert->encoded_len;
+ tor_cert_free(bad_cert);
+ REENCODE();
+ })
+CERTS_FAIL(missing_link, /* ed25519 */
+ {
+ require_failure_message = "No Ed25519 link key";
+ tt_int_op(certs_cell_getlen_certs(d->ccell), OP_EQ, 5);
+ certs_cell_set_certs(d->ccell, 3, certs_cell_get_certs(d->ccell, 4));
+ certs_cell_set0_certs(d->ccell, 4, NULL); /* prevent free */
+ certs_cell_setlen_certs(d->ccell, 4);
+ d->ccell->n_certs = 4;
+ REENCODE();
+ })
+CERTS_FAIL(missing_auth, /* ed25519 */
+ {
+ d->c->handshake_state->started_here = 0;
+ d->c->handshake_state->certs->started_here = 0;
+ require_failure_message = "No Ed25519 link authentication key";
+ tt_int_op(certs_cell_getlen_certs(d->ccell), OP_EQ, 5);
+ certs_cell_set_certs(d->ccell, 3, certs_cell_get_certs(d->ccell, 4));
+ certs_cell_set0_certs(d->ccell, 4, NULL); /* prevent free */
+ certs_cell_setlen_certs(d->ccell, 4);
+ d->ccell->n_certs = 4;
+ REENCODE();
+ })
+CERTS_FAIL(missing_crosscert, /* ed25519 */
+ {
+ require_failure_message = "Missing RSA->Ed25519 crosscert";
+ tt_int_op(certs_cell_getlen_certs(d->ccell), OP_EQ, 5);
+ certs_cell_setlen_certs(d->ccell, 4);
+ d->ccell->n_certs = 4;
+ REENCODE();
+ })
+CERTS_FAIL(missing_rsa_id, /* ed25519 */
+ {
+ require_failure_message = "Missing legacy RSA ID cert";
+ tt_int_op(certs_cell_getlen_certs(d->ccell), OP_EQ, 5);
+ certs_cell_set_certs(d->ccell, 1, certs_cell_get_certs(d->ccell, 4));
+ certs_cell_set0_certs(d->ccell, 4, NULL); /* prevent free */
+ certs_cell_setlen_certs(d->ccell, 4);
+ d->ccell->n_certs = 4;
+ REENCODE();
+ })
+CERTS_FAIL(link_mismatch, /* ed25519 */
+ {
+ require_failure_message = "Link certificate does not match "
+ "TLS certificate";
+ const tor_x509_cert_t *idc;
+ tor_tls_get_my_certs(1, NULL, &idc);
+ tor_x509_cert_free(mock_peer_cert);
+ /* Pretend that the peer cert was something else. */
+ mock_peer_cert = tor_x509_cert_dup(idc);
+ /* No reencode needed. */
+ })
+CERTS_FAIL(bad_ed_sig, /* ed25519 */
+ {
+ require_failure_message = "At least one Ed25519 certificate was "
+ "badly signed";
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 3);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ ssize_t body_len = certs_cell_cert_getlen_body(cert);
+ /* Frob a byte in the signature */
+ body[body_len - 13] ^= 7;
+ REENCODE();
+ })
+CERTS_FAIL(bad_crosscert, /*ed25519*/
+ {
+ require_failure_message = "Invalid RSA->Ed25519 crosscert";
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 4);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ ssize_t body_len = certs_cell_cert_getlen_body(cert);
+ /* Frob a byte in the signature */
+ body[body_len - 13] ^= 7;
+ REENCODE();
+ })
+CERTS_FAIL(bad_rsa_id_cert, /*ed25519*/
+ {
+ require_failure_message = "legacy RSA ID certificate was not valid";
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 1);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ ssize_t body_len = certs_cell_cert_getlen_body(cert);
+ /* Frob a byte in the signature */
+ body[body_len - 13] ^= 7;
+ REENCODE();
+ })
+CERTS_FAIL(expired_rsa_id, /* both */
+ {
+ require_failure_message = "Certificate already expired";
+ /* we're going to replace the identity cert with an expired one. */
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 1);
+ const tor_x509_cert_t *idc;
+ tor_tls_get_my_certs(1, NULL, &idc);
+ X509 *newc = X509_dup(idc->cert);
+ time_t new_end = time(NULL) - 86400 * 10;
+ X509_time_adj(X509_get_notAfter(newc), 0, &new_end);
+ EVP_PKEY *pk = crypto_pk_get_evp_pkey_(d->key2, 1);
+ tt_assert(X509_sign(newc, pk, EVP_sha1()));
+ int len = i2d_X509(newc, NULL);
+ certs_cell_cert_setlen_body(cert, len);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ int len2 = i2d_X509(newc, &body);
+ tt_int_op(len, ==, len2);
+ REENCODE();
+ X509_free(newc);
+ EVP_PKEY_free(pk);
+ })
+CERTS_FAIL(expired_ed_id, /* ed25519 */
+ {
+ /* we're going to replace the Ed Id->sign cert with an expired one. */
+ require_failure_message = "At least one certificate expired";
+ /* We don't need to re-sign, since we check for expiration first. */
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 2);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ /* The expiration field is bytes [2..5]. It is in HOURS since the
+ * epoch. */
+ set_uint32(body+2, htonl(24)); /* Back to jan 2, 1970. */
+ REENCODE();
+ })
+CERTS_FAIL(expired_ed_link, /* ed25519 */
+ {
+ /* we're going to replace the Ed Sign->link cert with an expired one. */
+ require_failure_message = "At least one certificate expired";
+ /* We don't need to re-sign, since we check for expiration first. */
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 3);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ /* The expiration field is bytes [2..5]. It is in HOURS since the
+ * epoch. */
+ set_uint32(body+2, htonl(24)); /* Back to jan 2, 1970. */
+ REENCODE();
+ })
+CERTS_FAIL(expired_crosscert, /* ed25519 */
+ {
+ /* we're going to replace the Ed Sign->link cert with an expired one. */
+ require_failure_message = "Crosscert is expired";
+ /* We don't need to re-sign, since we check for expiration first. */
+ certs_cell_cert_t *cert = certs_cell_get_certs(d->ccell, 4);
+ uint8_t *body = certs_cell_cert_getarray_body(cert);
+ /* The expiration field is bytes [32..35]. once again, HOURS. */
+ set_uint32(body+32, htonl(24)); /* Back to jan 2, 1970. */
+ REENCODE();
+ })
+
CERTS_FAIL(wrong_labels_1,
{
require_failure_message = "The link certificate was not valid";
@@ -459,21 +882,26 @@ CERTS_FAIL(wrong_labels_2,
})
CERTS_FAIL(wrong_labels_3,
{
- require_failure_message = "The certs we wanted were missing";
+ require_failure_message =
+ "The certs we wanted (ID, Link) were missing";
certs_cell_get_certs(d->ccell, 0)->cert_type = 2;
certs_cell_get_certs(d->ccell, 1)->cert_type = 3;
REENCODE();
})
CERTS_FAIL(server_missing_certs,
{
- require_failure_message = "The certs we wanted were missing";
+ require_failure_message =
+ "The certs we wanted (ID, Auth) were missing";
d->c->handshake_state->started_here = 0;
+ d->c->handshake_state->certs->started_here = 0;
+
})
CERTS_FAIL(server_wrong_labels_1,
{
require_failure_message =
"The authentication certificate was not valid";
d->c->handshake_state->started_here = 0;
+ d->c->handshake_state->certs->started_here = 0;
certs_cell_get_certs(d->ccell, 0)->cert_type = 2;
certs_cell_get_certs(d->ccell, 1)->cert_type = 3;
REENCODE();
@@ -487,6 +915,11 @@ test_link_handshake_send_authchallenge(void *arg)
or_connection_t *c1 = or_connection_new(CONN_TYPE_OR, AF_INET);
var_cell_t *cell1=NULL, *cell2=NULL;
+ 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);
+ 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);
@@ -496,15 +929,15 @@ test_link_handshake_send_authchallenge(void *arg)
cell1 = mock_got_var_cell;
tt_int_op(0, ==, connection_or_send_auth_challenge_cell(c1));
cell2 = mock_got_var_cell;
- tt_int_op(36, ==, cell1->payload_len);
- tt_int_op(36, ==, cell2->payload_len);
+ 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_mem_op("\x00\x01\x00\x01", ==, cell1->payload + 32, 4);
- tt_mem_op("\x00\x01\x00\x01", ==, cell2->payload + 32, 4);
+ 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);
done:
@@ -512,6 +945,8 @@ test_link_handshake_send_authchallenge(void *arg)
connection_free_(TO_CONN(c1));
tor_free(cell1);
tor_free(cell2);
+ crypto_pk_free(rsa0);
+ crypto_pk_free(rsa1);
}
typedef struct authchallenge_data_s {
@@ -556,9 +991,9 @@ recv_authchallenge_setup(const struct testcase_t *test)
d->c->handshake_state->received_certs_cell = 1;
d->cell = var_cell_new(128);
d->cell->payload_len = 38;
- d->cell->payload[33] = 2;
- d->cell->payload[35] = 7;
- d->cell->payload[37] = 1;
+ d->cell->payload[33] = 2; /* 2 methods */
+ d->cell->payload[35] = 7; /* This one isn't real */
+ d->cell->payload[37] = 1; /* This is the old RSA one. */
d->cell->command = CELL_AUTH_CHALLENGE;
get_options_mutable()->ORPort_set = 1;
@@ -566,7 +1001,6 @@ 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);
@@ -592,6 +1026,26 @@ test_link_handshake_recv_authchallenge_ok(void *arg)
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 */
+ done:
+ ;
+}
+
+static void
+test_link_handshake_recv_authchallenge_ok_ed25519(void *arg)
+{
+ authchallenge_data_t *d = arg;
+
+ /* Add the ed25519 authentication mechanism here. */
+ d->cell->payload[33] = 3; /* 3 types are supported now. */
+ 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 */
done:
;
}
@@ -655,7 +1109,8 @@ AUTHCHALLENGE_FAIL(badproto,
AUTHCHALLENGE_FAIL(as_server,
require_failure_message = "We didn't originate this "
"connection";
- d->c->handshake_state->started_here = 0;)
+ d->c->handshake_state->started_here = 0;
+ d->c->handshake_state->certs->started_here = 0;)
AUTHCHALLENGE_FAIL(duplicate,
require_failure_message = "We already received one";
d->c->handshake_state->received_auth_challenge = 1)
@@ -673,15 +1128,6 @@ AUTHCHALLENGE_FAIL(nonzero_circid,
require_failure_message = "It had a nonzero circuit ID";
d->cell->circ_id = 1337)
-static tor_x509_cert_t *mock_peer_cert = NULL;
-
-static tor_x509_cert_t *
-mock_get_peer_cert(tor_tls_t *tls)
-{
- (void)tls;
- return tor_x509_cert_dup(mock_peer_cert);
-}
-
static int
mock_get_tlssecrets(tor_tls_t *tls, uint8_t *secrets_out)
{
@@ -701,6 +1147,7 @@ mock_set_circid_type(channel_t *chan,
}
typedef struct authenticate_data_s {
+ int is_ed;
or_connection_t *c1, *c2;
channel_tls_t *chan2;
var_cell_t *cell;
@@ -717,11 +1164,12 @@ authenticate_data_cleanup(const struct testcase_t *test, void *arg)
UNMOCK(tor_tls_get_tlssecrets);
UNMOCK(connection_or_close_for_error);
UNMOCK(channel_set_circid_type);
+ UNMOCK(tor_tls_export_key_material);
authenticate_data_t *d = arg;
if (d) {
tor_free(d->cell);
- connection_or_remove_from_identity_map(d->c1);
- connection_or_remove_from_identity_map(d->c2);
+ connection_or_clear_identity(d->c1);
+ connection_or_clear_identity(d->c2);
connection_free_(TO_CONN(d->c1));
connection_free_(TO_CONN(d->c2));
circuitmux_free(d->chan2->base_.cmux);
@@ -742,6 +1190,7 @@ static void *
authenticate_data_setup(const struct testcase_t *test)
{
authenticate_data_t *d = tor_malloc_zero(sizeof(*d));
+ int is_ed = d->is_ed = (test->setup_data == (void*)3);
scheduler_init();
@@ -751,6 +1200,7 @@ authenticate_data_setup(const struct testcase_t *test)
MOCK(tor_tls_get_tlssecrets, mock_get_tlssecrets);
MOCK(connection_or_close_for_error, mock_close_for_err);
MOCK(channel_set_circid_type, mock_set_circid_type);
+ MOCK(tor_tls_export_key_material, mock_export_key_material);
d->c1 = or_connection_new(CONN_TYPE_OR, AF_INET);
d->c2 = or_connection_new(CONN_TYPE_OR, AF_INET);
tor_addr_from_ipv4h(&d->c1->base_.addr, 0x01020304);
@@ -761,6 +1211,8 @@ authenticate_data_setup(const struct testcase_t *test)
tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER,
d->key1, d->key2, 86400), ==, 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);
@@ -791,21 +1243,37 @@ authenticate_data_setup(const struct testcase_t *test)
const uint8_t *der;
size_t sz;
tor_x509_cert_get_der(id_cert, &der, &sz);
- d->c1->handshake_state->id_cert = tor_x509_cert_decode(der, sz);
- d->c2->handshake_state->id_cert = tor_x509_cert_decode(der, sz);
+ d->c1->handshake_state->certs->id_cert = tor_x509_cert_decode(der, sz);
+ d->c2->handshake_state->certs->id_cert = tor_x509_cert_decode(der, sz);
+
+ if (is_ed) {
+ d->c1->handshake_state->certs->ed_id_sign =
+ tor_cert_dup(get_master_signing_key_cert());
+ d->c2->handshake_state->certs->ed_id_sign =
+ tor_cert_dup(get_master_signing_key_cert());
+ d->c2->handshake_state->certs->ed_sign_auth =
+ tor_cert_dup(get_current_auth_key_cert());
+ } else {
+ tt_assert(! tor_tls_get_my_certs(0, &auth_cert, &id_cert));
+ tor_x509_cert_get_der(auth_cert, &der, &sz);
+ d->c2->handshake_state->certs->auth_cert = tor_x509_cert_decode(der, sz);
+ }
tor_x509_cert_get_der(link_cert, &der, &sz);
mock_peer_cert = tor_x509_cert_decode(der, sz);
tt_assert(mock_peer_cert);
+
mock_own_cert = tor_x509_cert_decode(der, sz);
tt_assert(mock_own_cert);
- tt_assert(! tor_tls_get_my_certs(0, &auth_cert, &id_cert));
- tor_x509_cert_get_der(auth_cert, &der, &sz);
- d->c2->handshake_state->auth_cert = tor_x509_cert_decode(der, sz);
/* Make an authenticate cell ... */
- tt_int_op(0, ==, connection_or_send_authenticate_cell(d->c1,
- AUTHTYPE_RSA_SHA256_TLSSECRET));
+ int authtype;
+ if (is_ed)
+ authtype = AUTHTYPE_ED25519_SHA256_RFC5705;
+ else
+ authtype = AUTHTYPE_RSA_SHA256_TLSSECRET;
+ tt_int_op(0, ==, connection_or_send_authenticate_cell(d->c1, authtype));
+
tt_assert(mock_got_var_cell);
d->cell = mock_got_var_cell;
mock_got_var_cell = NULL;
@@ -831,42 +1299,64 @@ test_link_handshake_auth_cell(void *arg)
/* 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->payload[1], ==, 1);
+ if (d->is_ed)
+ tt_int_op(d->cell->payload[1], ==, 3);
+ else
+ tt_int_op(d->cell->payload[1], ==, 1);
tt_int_op(ntohs(get_uint16(d->cell->payload + 2)), ==,
d->cell->payload_len - 4);
/* Check it out for plausibility... */
auth_ctx_t ctx;
- ctx.is_ed = 0;
+ ctx.is_ed = d->is_ed;
tt_int_op(d->cell->payload_len-4, ==, auth1_parse(&auth1,
d->cell->payload+4,
d->cell->payload_len - 4, &ctx));
tt_assert(auth1);
- tt_mem_op(auth1->type, ==, "AUTH0001", 8);
+ if (d->is_ed) {
+ tt_mem_op(auth1->type, ==, "AUTH0003", 8);
+ } else {
+ tt_mem_op(auth1->type, ==, "AUTH0001", 8);
+ }
tt_mem_op(auth1->tlssecrets, ==, "int getRandomNumber(){return 4;}", 32);
- tt_int_op(auth1_getlen_sig(auth1), >, 120);
/* Is the signature okay? */
- uint8_t sig[128];
- uint8_t digest[32];
-
- auth_pubkey = tor_tls_cert_get_key(d->c2->handshake_state->auth_cert);
- int n = crypto_pk_public_checksig(
+ 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);
+ 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);
+ 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);
- const uint8_t *start = d->cell->payload+4, *end = auth1->end_of_signed;
- crypto_digest256((char*)digest,
- (const char*)start, end-start, DIGEST_SHA256);
- tt_mem_op(sig, ==, digest, 32);
+ tt_int_op(n, ==, 32);
+ crypto_digest256((char*)digest,
+ (const char*)start, end-start, DIGEST_SHA256);
+ tt_mem_op(sig, ==, digest, 32);
+ }
/* Then feed it to c2. */
tt_int_op(d->c2->handshake_state->authenticated, ==, 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);
+ if (d->is_ed) {
+ tt_int_op(d->c2->handshake_state->authenticated_ed25519, ==, 1);
+ tt_int_op(d->c2->handshake_state->authenticated_rsa, ==, 1);
+ } else {
+ tt_int_op(d->c2->handshake_state->authenticated_ed25519, ==, 0);
+ tt_int_op(d->c2->handshake_state->authenticated_rsa, ==, 1);
+ }
done:
auth1_free(auth1);
@@ -900,7 +1390,8 @@ AUTHENTICATE_FAIL(badproto,
d->c2->link_proto = 2)
AUTHENTICATE_FAIL(atclient,
require_failure_message = "We originated this connection";
- d->c2->handshake_state->started_here = 1)
+ d->c2->handshake_state->started_here = 1;
+ d->c2->handshake_state->certs->started_here = 1;)
AUTHENTICATE_FAIL(duplicate,
require_failure_message = "We already got one";
d->c2->handshake_state->received_authenticate = 1)
@@ -924,13 +1415,13 @@ AUTHENTICATE_FAIL(nocerts,
AUTHENTICATE_FAIL(noidcert,
require_failure_message = "We never got an identity "
"certificate";
- tor_x509_cert_free(d->c2->handshake_state->id_cert);
- d->c2->handshake_state->id_cert = NULL)
+ tor_x509_cert_free(d->c2->handshake_state->certs->id_cert);
+ d->c2->handshake_state->certs->id_cert = NULL)
AUTHENTICATE_FAIL(noauthcert,
- require_failure_message = "We never got an authentication "
- "certificate";
- tor_x509_cert_free(d->c2->handshake_state->auth_cert);
- d->c2->handshake_state->auth_cert = NULL)
+ require_failure_message = "We never got an RSA "
+ "authentication certificate";
+ tor_x509_cert_free(d->c2->handshake_state->certs->auth_cert);
+ d->c2->handshake_state->certs->auth_cert = NULL)
AUTHENTICATE_FAIL(tooshort,
require_failure_message = "Cell was way too short";
d->cell->payload_len = 3)
@@ -954,11 +1445,33 @@ AUTHENTICATE_FAIL(badcontent,
"cell body was not as expected";
d->cell->payload[10] ^= 0xff)
AUTHENTICATE_FAIL(badsig_1,
- require_failure_message = "Signature wasn't valid";
+ if (d->is_ed)
+ require_failure_message = "Ed25519 signature wasn't valid";
+ else
+ require_failure_message = "RSA signature wasn't valid";
d->cell->payload[d->cell->payload_len - 5] ^= 0xff)
-
-#define TEST(name, flags) \
- { #name , test_link_handshake_ ## name, (flags), NULL, NULL }
+AUTHENTICATE_FAIL(missing_ed_id,
+ {
+ tor_cert_free(d->c2->handshake_state->certs->ed_id_sign);
+ d->c2->handshake_state->certs->ed_id_sign = NULL;
+ require_failure_message = "Ed authenticate without Ed ID "
+ "cert from peer";
+ })
+AUTHENTICATE_FAIL(missing_ed_auth,
+ {
+ tor_cert_free(d->c2->handshake_state->certs->ed_sign_auth);
+ d->c2->handshake_state->certs->ed_sign_auth = NULL;
+ require_failure_message = "We never got an Ed25519 "
+ "authentication certificate";
+ })
+
+#define TEST_RSA(name, flags) \
+ { #name , test_link_handshake_ ## name, (flags), \
+ &passthrough_setup, (void*)"RSA" }
+
+#define TEST_ED(name, flags) \
+ { #name "_ed25519" , test_link_handshake_ ## name, (flags), \
+ &passthrough_setup, (void*)"Ed25519" }
#define TEST_RCV_AUTHCHALLENGE(name) \
{ "recv_authchallenge/" #name , \
@@ -968,17 +1481,34 @@ AUTHENTICATE_FAIL(badsig_1,
#define TEST_RCV_CERTS(name) \
{ "recv_certs/" #name , \
test_link_handshake_recv_certs_ ## name, TT_FORK, \
- &setup_recv_certs, NULL }
+ &setup_recv_certs, (void*)"RSA-Link" }
+
+#define TEST_RCV_CERTS_RSA(name,type) \
+ { "recv_certs/" #name , \
+ test_link_handshake_recv_certs_ ## name, TT_FORK, \
+ &setup_recv_certs, (void*)type }
+
+#define TEST_RCV_CERTS_ED(name, type) \
+ { "recv_certs/" #name "_ed25519", \
+ test_link_handshake_recv_certs_ ## name, TT_FORK, \
+ &setup_recv_certs, (void*)type }
#define TEST_AUTHENTICATE(name) \
{ "authenticate/" #name , test_link_handshake_auth_ ## name, TT_FORK, \
&setup_authenticate, NULL }
+#define TEST_AUTHENTICATE_ED(name) \
+ { "authenticate/" #name "_ed25519" , test_link_handshake_auth_ ## name, \
+ TT_FORK, &setup_authenticate, (void*)3 }
+
struct testcase_t link_handshake_tests[] = {
- TEST(certs_ok, TT_FORK),
- //TEST(certs_bad, TT_FORK),
+ TEST_RSA(certs_ok, TT_FORK),
+ TEST_ED(certs_ok, TT_FORK),
+
TEST_RCV_CERTS(ok),
- TEST_RCV_CERTS(ok_server),
+ TEST_RCV_CERTS_ED(ok, "Ed25519-Link"),
+ TEST_RCV_CERTS_RSA(ok_server, "RSA-Auth"),
+ TEST_RCV_CERTS_ED(ok_server, "Ed25519-Auth"),
TEST_RCV_CERTS(badstate),
TEST_RCV_CERTS(badproto),
TEST_RCV_CERTS(duplicate),
@@ -988,18 +1518,41 @@ struct testcase_t link_handshake_tests[] = {
TEST_RCV_CERTS(truncated_1),
TEST_RCV_CERTS(truncated_2),
TEST_RCV_CERTS(truncated_3),
+ TEST_RCV_CERTS_ED(truncated_4, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(truncated_5, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(truncated_6, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(truncated_7, "Ed25519-Link"),
TEST_RCV_CERTS(not_x509),
TEST_RCV_CERTS(both_link),
TEST_RCV_CERTS(both_id_rsa),
TEST_RCV_CERTS(both_auth),
+ TEST_RCV_CERTS_ED(duplicate_id, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(duplicate_link, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(duplicate_crosscert, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(missing_crosscert, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(missing_id, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(missing_signing_key, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(missing_link, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(missing_auth, "Ed25519-Auth"),
+ TEST_RCV_CERTS_ED(missing_rsa_id, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(link_mismatch, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(bad_ed_sig, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(bad_rsa_id_cert, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(bad_crosscert, "Ed25519-Link"),
+ TEST_RCV_CERTS_RSA(expired_rsa_id, "RSA-Link"),
+ TEST_RCV_CERTS_ED(expired_rsa_id, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(expired_ed_id, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(expired_ed_link, "Ed25519-Link"),
+ TEST_RCV_CERTS_ED(expired_crosscert, "Ed25519-Link"),
TEST_RCV_CERTS(wrong_labels_1),
TEST_RCV_CERTS(wrong_labels_2),
TEST_RCV_CERTS(wrong_labels_3),
TEST_RCV_CERTS(server_missing_certs),
TEST_RCV_CERTS(server_wrong_labels_1),
- TEST(send_authchallenge, TT_FORK),
+ TEST_RSA(send_authchallenge, TT_FORK),
TEST_RCV_AUTHCHALLENGE(ok),
+ TEST_RCV_AUTHCHALLENGE(ok_ed25519),
TEST_RCV_AUTHCHALLENGE(ok_noserver),
TEST_RCV_AUTHCHALLENGE(ok_unrecognized),
TEST_RCV_AUTHCHALLENGE(badstate),
@@ -1012,6 +1565,7 @@ struct testcase_t link_handshake_tests[] = {
TEST_RCV_AUTHCHALLENGE(nonzero_circid),
TEST_AUTHENTICATE(cell),
+ TEST_AUTHENTICATE_ED(cell),
TEST_AUTHENTICATE(badstate),
TEST_AUTHENTICATE(badproto),
TEST_AUTHENTICATE(atclient),
@@ -1027,6 +1581,9 @@ struct testcase_t link_handshake_tests[] = {
TEST_AUTHENTICATE(tooshort_1),
TEST_AUTHENTICATE(badcontent),
TEST_AUTHENTICATE(badsig_1),
+ TEST_AUTHENTICATE_ED(badsig_1),
+ TEST_AUTHENTICATE_ED(missing_ed_id),
+ TEST_AUTHENTICATE_ED(missing_ed_auth),
//TEST_AUTHENTICATE(),
END_OF_TESTCASES
diff --git a/src/test/test_oom.c b/src/test/test_oom.c
index 6102af01f5..0f97972032 100644
--- a/src/test/test_oom.c
+++ b/src/test/test_oom.c
@@ -15,6 +15,7 @@
#include "config.h"
#include "relay.h"
#include "test.h"
+#include "test_helpers.h"
/* small replacement mock for circuit_mark_for_close_ to avoid doing all
* the other bookkeeping that comes with marking circuits. */
@@ -58,24 +59,6 @@ dummy_or_circuit_new(int n_p_cells, int n_n_cells)
return TO_CIRCUIT(circ);
}
-static circuit_t *
-dummy_origin_circuit_new(int n_cells)
-{
- origin_circuit_t *circ = origin_circuit_new();
- int i;
- cell_t cell;
-
- for (i=0; i < n_cells; ++i) {
- crypto_rand((void*)&cell, sizeof(cell));
- cell_queue_append_packed_copy(TO_CIRCUIT(circ),
- &TO_CIRCUIT(circ)->n_chan_cells,
- 1, &cell, 1, 0);
- }
-
- TO_CIRCUIT(circ)->purpose = CIRCUIT_PURPOSE_C_GENERAL;
- return TO_CIRCUIT(circ);
-}
-
static void
add_bytes_to_buf(buf_t *buf, size_t n_bytes)
{
diff --git a/src/test/test_options.c b/src/test/test_options.c
index e85e11805b..d5782e9ec0 100644
--- a/src/test/test_options.c
+++ b/src/test/test_options.c
@@ -18,6 +18,7 @@
#include "sandbox.h"
#include "memarea.h"
#include "policies.h"
+#include "test_helpers.h"
#define NS_MODULE test_options
@@ -332,7 +333,8 @@ fixed_get_uname(void)
"VirtualAddrNetworkIPv4 127.192.0.0/10\n" \
"VirtualAddrNetworkIPv6 [FE80::]/10\n" \
"SchedulerHighWaterMark__ 42\n" \
- "SchedulerLowWaterMark__ 10\n"
+ "SchedulerLowWaterMark__ 10\n" \
+ "UseEntryGuards 1\n"
typedef struct {
or_options_t *old_opt;
@@ -650,16 +652,18 @@ test_options_validate__authdir(void *ignored)
setup_capture_of_logs(LOG_INFO);
options_test_data_t *tdata = get_options_test_data(
"AuthoritativeDirectory 1\n"
- "Address this.should.not_exist.example.org");
+ "Address this.should.not!exist!.example.org");
sandbox_disable_getaddrinfo_cache();
+ MOCK(tor_addr_lookup, mock_tor_addr_lookup__fail_on_bad_addrs);
ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
+ UNMOCK(tor_addr_lookup);
tt_int_op(ret, OP_EQ, -1);
tt_str_op(msg, OP_EQ, "Failed to resolve/guess local address. See logs for"
" details.");
expect_log_msg("Could not resolve local Address "
- "'this.should.not_exist.example.org'. Failing.\n");
+ "'this.should.not!exist!.example.org'. Failing.\n");
tor_free(msg);
free_options_test_data(tdata);
@@ -1018,7 +1022,7 @@ test_options_validate__transproxy(void *ignored)
ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
tt_int_op(ret, OP_EQ, -1);
-#if !defined(__OpenBSD__) && !defined( DARWIN )
+#if !defined(OpenBSD) && !defined( DARWIN )
tt_str_op(msg, OP_EQ,
"pf-divert is a OpenBSD-specific and OS X/Darwin-specific feature.");
#else
@@ -1087,7 +1091,7 @@ test_options_validate__transproxy(void *ignored)
if (msg) {
TT_DIE(("Expected NULL but got '%s'", msg));
}
-#elif defined(__OpenBSD__)
+#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);
@@ -1794,14 +1798,6 @@ test_options_validate__reachable_addresses(void *ignored)
/* Test IPv4-only clients setting IPv6 preferences */
-#define WARN_PLEASE_USE_IPV6_OR_LOG_MSG \
- "ClientPreferIPv6ORPort 1 is ignored unless tor is using IPv6. " \
- "Please set ClientUseIPv6 1, ClientUseIPv4 0, or configure bridges.\n"
-
-#define WARN_PLEASE_USE_IPV6_DIR_LOG_MSG \
- "ClientPreferIPv6DirPort 1 is ignored unless tor is using IPv6. " \
- "Please set ClientUseIPv6 1, ClientUseIPv4 0, or configure bridges.\n"
-
free_options_test_data(tdata);
tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
"ClientUseIPv4 1\n"
@@ -1811,7 +1807,6 @@ test_options_validate__reachable_addresses(void *ignored)
ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
tt_int_op(ret, OP_EQ, 0);
- expect_log_msg(WARN_PLEASE_USE_IPV6_OR_LOG_MSG);
tor_free(msg);
free_options_test_data(tdata);
@@ -1823,7 +1818,6 @@ test_options_validate__reachable_addresses(void *ignored)
ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);
tt_int_op(ret, OP_EQ, 0);
- expect_log_msg(WARN_PLEASE_USE_IPV6_DIR_LOG_MSG);
tor_free(msg);
/* Now test an IPv4/IPv6 client setting IPv6 preferences */
@@ -1945,6 +1939,19 @@ test_options_validate__use_bridges(void *ignored)
tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
"UseBridges 1\n"
"Bridge 10.0.0.1\n"
+ "UseEntryGuards 0\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,
+ "Setting UseBridges requires also setting UseEntryGuards.");
+ tor_free(msg);
+
+ free_options_test_data(tdata);
+ tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
+ "UseBridges 1\n"
+ "Bridge 10.0.0.1\n"
"Bridge !!!\n"
);
@@ -2819,8 +2826,8 @@ test_options_validate__single_onion(void *ignored)
tt_int_op(ret, OP_EQ, -1);
tt_str_op(msg, OP_EQ, "HiddenServiceNonAnonymousMode is incompatible with "
"using Tor as an anonymous client. Please set "
- "Socks/Trans/NATD/DNSPort to 0, or HiddenServiceNonAnonymousMode "
- "to 0, or use the non-anonymous Tor2webMode.");
+ "Socks/Trans/NATD/DNSPort to 0, or revert "
+ "HiddenServiceNonAnonymousMode to 0.");
tor_free(msg);
free_options_test_data(tdata);
@@ -3037,6 +3044,7 @@ test_options_validate__proxy(void *ignored)
options_test_data_t *tdata = NULL;
sandbox_disable_getaddrinfo_cache();
setup_capture_of_logs(LOG_WARN);
+ MOCK(tor_addr_lookup, mock_tor_addr_lookup__fail_on_bad_addrs);
free_options_test_data(tdata);
tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
@@ -3057,6 +3065,7 @@ test_options_validate__proxy(void *ignored)
tor_free(msg);
free_options_test_data(tdata);
+
tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES
"HttpProxy not_so_valid!\n"
);
@@ -3357,6 +3366,7 @@ test_options_validate__proxy(void *ignored)
policies_free_all();
// sandbox_free_getaddrinfo_cache();
tor_free(msg);
+ UNMOCK(tor_addr_lookup);
}
static void
diff --git a/src/test/test_policy.c b/src/test/test_policy.c
index 1ffdc2cd51..e86d0f0274 100644
--- a/src/test/test_policy.c
+++ b/src/test/test_policy.c
@@ -1031,10 +1031,10 @@ test_policies_general(void *arg)
{
char *policy_strng = NULL;
smartlist_t *chunks = smartlist_new();
- smartlist_add(chunks, tor_strdup("accept "));
+ smartlist_add_strdup(chunks, "accept ");
for (i=1; i<10000; ++i)
smartlist_add_asprintf(chunks, "%d,", i);
- smartlist_add(chunks, tor_strdup("20000"));
+ smartlist_add_strdup(chunks, "20000");
policy_strng = smartlist_join_strings(chunks, "", 0, NULL);
SMARTLIST_FOREACH(chunks, char *, ch, tor_free(ch));
smartlist_free(chunks);
@@ -1048,9 +1048,9 @@ test_policies_general(void *arg)
for (i=1; i<2000; i+=2) {
char buf[POLICY_BUF_LEN];
tor_snprintf(buf, sizeof(buf), "reject *:%d", i);
- smartlist_add(sm, tor_strdup(buf));
+ smartlist_add_strdup(sm, buf);
}
- smartlist_add(sm, tor_strdup("accept *:*"));
+ smartlist_add_strdup(sm, "accept *:*");
policy_str = smartlist_join_strings(sm, ",", 0, NULL);
test_policy_summary_helper( policy_str,
"accept 2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,"
@@ -1587,8 +1587,12 @@ test_policies_getinfo_helper_policies(void *arg)
append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*");
mock_options.IPv6Exit = 1;
- tor_addr_from_ipv4h(&mock_options.OutboundBindAddressIPv4_, TEST_IPV4_ADDR);
- tor_addr_parse(&mock_options.OutboundBindAddressIPv6_, TEST_IPV6_ADDR);
+ tor_addr_from_ipv4h(
+ &mock_options.OutboundBindAddresses[OUTBOUND_ADDR_EXIT][0],
+ TEST_IPV4_ADDR);
+ tor_addr_parse(
+ &mock_options.OutboundBindAddresses[OUTBOUND_ADDR_EXIT][1],
+ TEST_IPV6_ADDR);
mock_options.ExitPolicyRejectPrivate = 1;
mock_options.ExitPolicyRejectLocalInterfaces = 1;
diff --git a/src/test/test_pt.c b/src/test/test_pt.c
index e5cdc5f3cd..f93019f1c4 100644
--- a/src/test/test_pt.c
+++ b/src/test/test_pt.c
@@ -155,9 +155,9 @@ test_pt_get_transport_options(void *arg)
opt_str = get_transport_options_for_server_proxy(mp);
tt_ptr_op(opt_str, OP_EQ, NULL);
- smartlist_add(mp->transports_to_launch, tor_strdup("gruyere"));
- smartlist_add(mp->transports_to_launch, tor_strdup("roquefort"));
- smartlist_add(mp->transports_to_launch, tor_strdup("stnectaire"));
+ smartlist_add_strdup(mp->transports_to_launch, "gruyere");
+ smartlist_add_strdup(mp->transports_to_launch, "roquefort");
+ smartlist_add_strdup(mp->transports_to_launch, "stnectaire");
tt_assert(options);
@@ -305,7 +305,7 @@ tor_get_lines_from_handle_replacement(STDIN_HANDLE *handle,
smartlist_add_asprintf(retval_sl, "SMETHOD mock%d 127.0.0.1:555%d",
times_called, times_called);
} else {
- smartlist_add(retval_sl, tor_strdup("SMETHODS DONE"));
+ smartlist_add_strdup(retval_sl, "SMETHODS DONE");
}
return retval_sl;
diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c
index a5d3f351f8..0d53c78817 100644
--- a/src/test/test_rendcache.c
+++ b/src/test/test_rendcache.c
@@ -10,6 +10,7 @@
#include "router.h"
#include "routerlist.h"
#include "config.h"
+#include "hs_common.h"
#include <openssl/rsa.h>
#include "rend_test_helpers.h"
#include "log_test_helpers.h"
@@ -24,15 +25,16 @@ 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_t *rend_query = tor_malloc_zero(sizeof(rend_data_t));
+ 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(rend_query->onion_address, onion_address,
- sizeof(rend_query->onion_address));
- rend_query->auth_type = REND_NO_AUTH;
+ 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;
}
@@ -144,7 +146,8 @@ test_rend_cache_store_v2_desc_as_client(void *data)
// Test mismatch between service ID and onion address
rend_cache_init();
- strncpy(mock_rend_query->onion_address, "abc", REND_SERVICE_ID_LEN_BASE32+1);
+ strncpy(TO_REND_DATA_V2(mock_rend_query)->onion_address, "abc",
+ REND_SERVICE_ID_LEN_BASE32+1);
ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str,
desc_id_base32,
mock_rend_query, NULL);
@@ -155,12 +158,16 @@ test_rend_cache_store_v2_desc_as_client(void *data)
// Test incorrect descriptor ID
rend_cache_init();
mock_rend_query = mock_rend_data(service_id);
- desc_id_base32[0]++;
+ char orig = desc_id_base32[0];
+ if (desc_id_base32[0] == 'a')
+ desc_id_base32[0] = 'b';
+ else
+ desc_id_base32[0] = 'a';
ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str,
desc_id_base32, mock_rend_query,
NULL);
tt_int_op(ret, OP_EQ, -1);
- desc_id_base32[0]--;
+ desc_id_base32[0] = orig;
rend_cache_free_all();
// Test too old descriptor
@@ -230,9 +237,9 @@ test_rend_cache_store_v2_desc_as_client(void *data)
generate_desc(RECENT_TIME, &desc_holder, &service_id, 3);
mock_rend_query = mock_rend_data(service_id);
- mock_rend_query->auth_type = REND_BASIC_AUTH;
+ TO_REND_DATA_V2(mock_rend_query)->auth_type = REND_BASIC_AUTH;
client_cookie[0] = 'A';
- memcpy(mock_rend_query->descriptor_cookie, client_cookie,
+ memcpy(TO_REND_DATA_V2(mock_rend_query)->descriptor_cookie, client_cookie,
REND_DESC_COOKIE_LEN);
base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id,
DIGEST_LEN);
@@ -250,7 +257,7 @@ test_rend_cache_store_v2_desc_as_client(void *data)
generate_desc(RECENT_TIME, &desc_holder, &service_id, 3);
mock_rend_query = mock_rend_data(service_id);
- mock_rend_query->auth_type = REND_BASIC_AUTH;
+ TO_REND_DATA_V2(mock_rend_query)->auth_type = REND_BASIC_AUTH;
base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id,
DIGEST_LEN);
ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str,
@@ -1078,9 +1085,10 @@ static void
test_rend_cache_clean_v2_descs_as_dir(void *data)
{
rend_cache_entry_t *e;
- time_t now;
+ time_t now, cutoff;
rend_service_descriptor_t *desc;
now = time(NULL);
+ cutoff = now - (REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW);
const char key[DIGEST_LEN] = "abcde";
(void)data;
@@ -1088,7 +1096,7 @@ test_rend_cache_clean_v2_descs_as_dir(void *data)
rend_cache_init();
// Test running with an empty cache
- rend_cache_clean_v2_descs_as_dir(now, 0);
+ rend_cache_clean_v2_descs_as_dir(cutoff);
tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0);
// Test with only one new entry
@@ -1100,38 +1108,15 @@ test_rend_cache_clean_v2_descs_as_dir(void *data)
e->parsed = desc;
digestmap_set(rend_cache_v2_dir, key, e);
- rend_cache_clean_v2_descs_as_dir(now, 0);
+ /* Set the cutoff to minus 10 seconds. */
+ rend_cache_clean_v2_descs_as_dir(cutoff - 10);
tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 1);
// Test with one old entry
- desc->timestamp = now - (REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW + 1000);
- rend_cache_clean_v2_descs_as_dir(now, 0);
- tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0);
-
- // Test with one entry that has an old last served
- e = tor_malloc_zero(sizeof(rend_cache_entry_t));
- e->last_served = now - (REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW + 1000);
- desc = tor_malloc_zero(sizeof(rend_service_descriptor_t));
- desc->timestamp = now;
- desc->pk = pk_generate(0);
- e->parsed = desc;
- digestmap_set(rend_cache_v2_dir, key, e);
-
- rend_cache_clean_v2_descs_as_dir(now, 0);
+ desc->timestamp = cutoff - 1000;
+ rend_cache_clean_v2_descs_as_dir(cutoff);
tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0);
- // Test a run through asking for a large force_remove
- e = tor_malloc_zero(sizeof(rend_cache_entry_t));
- e->last_served = now;
- desc = tor_malloc_zero(sizeof(rend_service_descriptor_t));
- desc->timestamp = now;
- desc->pk = pk_generate(0);
- e->parsed = desc;
- digestmap_set(rend_cache_v2_dir, key, e);
-
- rend_cache_clean_v2_descs_as_dir(now, 20000);
- tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 1);
-
done:
rend_cache_free_all();
}
diff --git a/src/test/test_routerkeys.c b/src/test/test_routerkeys.c
index 24b0da1c46..13059267ac 100644
--- a/src/test/test_routerkeys.c
+++ b/src/test/test_routerkeys.c
@@ -450,8 +450,8 @@ test_routerkeys_ed_keys_init_all(void *arg)
options->DataDirectory = dir;
- tt_int_op(0, ==, load_ed_keys(options, now));
- tt_int_op(0, ==, generate_ed_link_cert(options, now));
+ tt_int_op(1, ==, load_ed_keys(options, now));
+ tt_int_op(0, ==, 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());
@@ -466,7 +466,7 @@ test_routerkeys_ed_keys_init_all(void *arg)
/* 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));
+ 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));
@@ -474,8 +474,8 @@ test_routerkeys_ed_keys_init_all(void *arg)
/* Force a reload: we make new link/auth keys. */
routerkeys_free_all();
- tt_int_op(0, ==, load_ed_keys(options, now));
- tt_int_op(0, ==, generate_ed_link_cert(options, now));
+ 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_assert(tor_cert_eq(link_cert, get_current_link_cert_cert()));
@@ -489,7 +489,7 @@ test_routerkeys_ed_keys_init_all(void *arg)
/* 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));
+ 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_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert()));
@@ -502,8 +502,8 @@ 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(0, ==, load_ed_keys(options, now+100*86400));
- tt_int_op(0, ==, generate_ed_link_cert(options, now+100*86400));
+ 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_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert()));
@@ -520,8 +520,8 @@ 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(0, ==, load_ed_keys(options, now));
- tt_int_op(0, ==, generate_ed_link_cert(options, now));
+ 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_assert(! tor_cert_eq(link_cert, get_current_link_cert_cert()));
@@ -614,6 +614,66 @@ test_routerkeys_cross_certify_tap(void *args)
crypto_pk_free(onion_key);
}
+static void
+test_routerkeys_rsa_ed_crosscert(void *arg)
+{
+ (void)arg;
+ ed25519_public_key_t ed;
+ crypto_pk_t *rsa = pk_generate(2);
+
+ uint8_t *cc = NULL;
+ ssize_t cc_len;
+ time_t expires_in = 1470846177;
+
+ tt_int_op(0, OP_EQ, ed25519_public_from_base64(&ed,
+ "ThisStringCanContainAnythingSoNoKeyHereNowX"));
+ cc_len = tor_make_rsa_ed25519_crosscert(&ed, rsa, expires_in, &cc);
+
+ tt_int_op(cc_len, OP_GT, 0);
+ tt_int_op(cc_len, OP_GT, 37); /* key, expires, siglen */
+ tt_mem_op(cc, OP_EQ, ed.pubkey, 32);
+ time_t expires_out = 3600 * ntohl(get_uint32(cc+32));
+ tt_int_op(expires_out, OP_GE, expires_in);
+ tt_int_op(expires_out, OP_LE, expires_in + 3600);
+
+ tt_int_op(cc_len, OP_EQ, 37 + get_uint8(cc+36));
+
+ tt_int_op(0, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed,
+ expires_in - 10));
+
+ /* Now try after it has expired */
+ tt_int_op(-4, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed,
+ expires_out + 1));
+
+ /* Truncated object */
+ tt_int_op(-2, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len - 2, rsa, &ed,
+ expires_in - 10));
+
+ /* Key not as expected */
+ cc[0] ^= 3;
+ tt_int_op(-3, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed,
+ expires_in - 10));
+ cc[0] ^= 3;
+
+ /* Bad signature */
+ cc[40] ^= 3;
+ tt_int_op(-5, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed,
+ expires_in - 10));
+ cc[40] ^= 3;
+
+ /* Signature of wrong data */
+ cc[0] ^= 3;
+ ed.pubkey[0] ^= 3;
+ tt_int_op(-6, OP_EQ, rsa_ed25519_crosscert_check(cc, cc_len, rsa, &ed,
+ expires_in - 10));
+ cc[0] ^= 3;
+ ed.pubkey[0] ^= 3;
+
+ done:
+ crypto_pk_free(rsa);
+ tor_free(cc);
+}
+
#define TEST(name, flags) \
{ #name , test_routerkeys_ ## name, (flags), NULL, NULL }
@@ -626,6 +686,7 @@ struct testcase_t routerkeys_tests[] = {
TEST(ed_keys_init_all, TT_FORK),
TEST(cross_certify_ntor, 0),
TEST(cross_certify_tap, 0),
+ TEST(rsa_ed_crosscert, 0),
END_OF_TESTCASES
};
diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c
index 088bd257c3..78f1cf16b7 100644
--- a/src/test/test_routerlist.c
+++ b/src/test/test_routerlist.c
@@ -15,6 +15,7 @@
#include "container.h"
#include "directory.h"
#include "dirvote.h"
+#include "entrynodes.h"
#include "microdesc.h"
#include "networkstatus.h"
#include "nodelist.h"
@@ -335,30 +336,6 @@ test_router_pick_directory_server_impl(void *arg)
node_router1->is_valid = 1;
node_router3->is_valid = 1;
- flags |= PDS_FOR_GUARD;
- node_router1->using_as_guard = 1;
- node_router2->using_as_guard = 1;
- node_router3->using_as_guard = 1;
- rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
- tt_assert(rs == NULL);
- node_router1->using_as_guard = 0;
- rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
- tt_assert(rs != NULL);
- tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN));
- rs = NULL;
- node_router2->using_as_guard = 0;
- node_router3->using_as_guard = 0;
-
- /* One not valid, one guard. This should leave one remaining */
- node_router1->is_valid = 0;
- node_router2->using_as_guard = 1;
- rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL);
- tt_assert(rs != NULL);
- tt_assert(tor_memeq(rs->identity_digest, router3_id, DIGEST_LEN));
- rs = NULL;
- node_router1->is_valid = 1;
- node_router2->using_as_guard = 0;
-
/* Manipulate overloaded */
node_router2->rs->last_dir_503_at = now;
@@ -420,6 +397,7 @@ test_router_pick_directory_server_impl(void *arg)
done:
UNMOCK(usable_consensus_flavor);
+
if (router1_id)
tor_free(router1_id);
if (router2_id)
diff --git a/src/test/test_routerset.c b/src/test/test_routerset.c
index 1b526d430b..7efd042ed5 100644
--- a/src/test/test_routerset.c
+++ b/src/test/test_routerset.c
@@ -623,7 +623,7 @@ NS(test_main)(void *arg)
(void)arg;
tgt = routerset_new();
- smartlist_add(src->list, tor_strdup("{xx}"));
+ smartlist_add_strdup(src->list, "{xx}");
routerset_union(tgt, src);
tt_int_op(smartlist_len(tgt->list), OP_NE, 0);
@@ -745,7 +745,7 @@ NS(test_main)(void *arg)
tt_int_op(is_empty, OP_NE, 0);
set = routerset_new();
- smartlist_add(set->list, tor_strdup("{xx}"));
+ smartlist_add_strdup(set->list, "{xx}");
is_empty = routerset_is_empty(set);
routerset_free(set);
set = NULL;
@@ -1616,7 +1616,7 @@ NS(test_main)(void *arg)
NS_MOCK(node_get_by_nickname);
NS(mock_nickname) = "foo";
- smartlist_add(set->list, tor_strdup(NS(mock_nickname)));
+ smartlist_add_strdup(set->list, NS(mock_nickname));
routerset_get_all_nodes(out, set, NULL, 0);
out_len = smartlist_len(out);
@@ -1667,7 +1667,7 @@ NS(test_main)(void *arg)
NS(mock_node).is_running = 0;
NS(mock_nickname) = "foo";
- smartlist_add(set->list, tor_strdup(NS(mock_nickname)));
+ smartlist_add_strdup(set->list, NS(mock_nickname));
routerset_get_all_nodes(out, set, NULL, 1);
out_len = smartlist_len(out);
@@ -1766,7 +1766,7 @@ NS(test_main)(void *arg)
NS_MOCK(nodelist_get_list);
- smartlist_add(set->country_names, tor_strdup("{xx}"));
+ smartlist_add_strdup(set->country_names, "{xx}");
NS(mock_smartlist) = smartlist_new();
routerset_get_all_nodes(out, set, NULL, 1);
@@ -1813,7 +1813,7 @@ NS(test_main)(void *arg)
NS_MOCK(nodelist_get_list);
- smartlist_add(set->country_names, tor_strdup("{xx}"));
+ smartlist_add_strdup(set->country_names, "{xx}");
NS(mock_smartlist) = smartlist_new();
NS(mock_node).is_running = 0;
smartlist_add(NS(mock_smartlist), (void *)&NS(mock_node));
@@ -1985,7 +1985,7 @@ NS(test_main)(void *arg)
int r;
(void)arg;
- smartlist_add(b->list, tor_strdup("{xx}"));
+ smartlist_add_strdup(b->list, "{xx}");
r = routerset_equal(a, b);
routerset_free(a);
routerset_free(b);
@@ -2010,9 +2010,9 @@ NS(test_main)(void *arg)
int r;
(void)arg;
- smartlist_add(a->list, tor_strdup("{aa}"));
- smartlist_add(b->list, tor_strdup("{b1}"));
- smartlist_add(b->list, tor_strdup("{b2}"));
+ smartlist_add_strdup(a->list, "{aa}");
+ smartlist_add_strdup(b->list, "{b1}");
+ smartlist_add_strdup(b->list, "{b2}");
r = routerset_equal(a, b);
routerset_free(a);
routerset_free(b);
@@ -2037,8 +2037,8 @@ NS(test_main)(void *arg)
int r;
(void)arg;
- smartlist_add(a->list, tor_strdup("foo"));
- smartlist_add(b->list, tor_strdup("bar"));
+ smartlist_add_strdup(a->list, "foo");
+ smartlist_add_strdup(b->list, "bar");
r = routerset_equal(a, b);
routerset_free(a);
routerset_free(b);
@@ -2063,8 +2063,8 @@ NS(test_main)(void *arg)
int r;
(void)arg;
- smartlist_add(a->list, tor_strdup("foo"));
- smartlist_add(b->list, tor_strdup("foo"));
+ smartlist_add_strdup(a->list, "foo");
+ smartlist_add_strdup(b->list, "foo");
r = routerset_equal(a, b);
routerset_free(a);
routerset_free(b);
diff --git a/src/test/test_shared_random.c b/src/test/test_shared_random.c
index 056f199b94..026a0f3825 100644
--- a/src/test/test_shared_random.c
+++ b/src/test/test_shared_random.c
@@ -48,7 +48,7 @@ init_authority_state(void)
mock_cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL);
tt_assert(mock_cert);
options->AuthoritativeDir = 1;
- tt_int_op(0, ==, load_ed_keys(options, time(NULL)));
+ tt_int_op(load_ed_keys(options, time(NULL)), OP_GE, 0);
sr_state_init(0, 0);
/* It's possible a commit has been generated in our state depending on
* the phase we are currently in which uses "now" as the starting
@@ -286,7 +286,7 @@ test_sr_commit(void *arg)
tt_assert(auth_cert);
options->AuthoritativeDir = 1;
- tt_int_op(0, ==, load_ed_keys(options, now));
+ tt_int_op(load_ed_keys(options, time(NULL)), OP_GE, 0);
}
/* Generate our commit object and validate it has the appropriate field
@@ -348,12 +348,12 @@ test_sr_commit(void *arg)
/* We'll build a list of values from our commit that our parsing function
* takes from a vote line and see if we can parse it correctly. */
{
- smartlist_add(args, tor_strdup("1"));
- smartlist_add(args,
- tor_strdup(crypto_digest_algorithm_get_name(our_commit->alg)));
- smartlist_add(args, tor_strdup(sr_commit_get_rsa_fpr(our_commit)));
- smartlist_add(args, tor_strdup(our_commit->encoded_commit));
- smartlist_add(args, tor_strdup(our_commit->encoded_reveal));
+ smartlist_add_strdup(args, "1");
+ smartlist_add_strdup(args,
+ crypto_digest_algorithm_get_name(our_commit->alg));
+ smartlist_add_strdup(args, sr_commit_get_rsa_fpr(our_commit));
+ smartlist_add_strdup(args, our_commit->encoded_commit);
+ smartlist_add_strdup(args, our_commit->encoded_reveal);
parsed_commit = sr_parse_commit(args);
tt_assert(parsed_commit);
/* That parsed commit should be _EXACTLY_ like our original commit (we
diff --git a/src/test/test_tortls.c b/src/test/test_tortls.c
index 47455cff83..4bfcea211d 100644
--- a/src/test/test_tortls.c
+++ b/src/test/test_tortls.c
@@ -1091,13 +1091,13 @@ test_tortls_check_lifetime(void *ignored)
time_t now = time(NULL);
tls = tor_malloc_zero(sizeof(tor_tls_t));
- ret = tor_tls_check_lifetime(LOG_WARN, tls, 0, 0);
+ ret = tor_tls_check_lifetime(LOG_WARN, tls, time(NULL), 0, 0);
tt_int_op(ret, OP_EQ, -1);
tls->ssl = tor_malloc_zero(sizeof(SSL));
tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION));
tls->ssl->session->peer = validCert;
- ret = tor_tls_check_lifetime(LOG_WARN, tls, 0, 0);
+ ret = tor_tls_check_lifetime(LOG_WARN, tls, time(NULL), 0, 0);
tt_int_op(ret, OP_EQ, 0);
ASN1_STRING_free(validCert->cert_info->validity->notBefore);
@@ -1105,10 +1105,10 @@ test_tortls_check_lifetime(void *ignored)
ASN1_STRING_free(validCert->cert_info->validity->notAfter);
validCert->cert_info->validity->notAfter = ASN1_TIME_set(NULL, now+60);
- ret = tor_tls_check_lifetime(LOG_WARN, tls, 0, -1000);
+ ret = tor_tls_check_lifetime(LOG_WARN, tls, time(NULL), 0, -1000);
tt_int_op(ret, OP_EQ, -1);
- ret = tor_tls_check_lifetime(LOG_WARN, tls, -1000, 0);
+ ret = tor_tls_check_lifetime(LOG_WARN, tls, time(NULL), -1000, 0);
tt_int_op(ret, OP_EQ, -1);
done:
@@ -2658,18 +2658,18 @@ test_tortls_cert_is_valid(void *ignored)
tor_x509_cert_t *cert = NULL, *scert = NULL;
scert = tor_malloc_zero(sizeof(tor_x509_cert_t));
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0);
tt_int_op(ret, OP_EQ, 0);
cert = tor_malloc_zero(sizeof(tor_x509_cert_t));
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0);
tt_int_op(ret, OP_EQ, 0);
tor_free(scert);
tor_free(cert);
cert = tor_x509_cert_new(read_cert_from(validCertString));
scert = tor_x509_cert_new(read_cert_from(caCertString));
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0);
tt_int_op(ret, OP_EQ, 1);
#ifndef OPENSSL_OPAQUE
@@ -2680,7 +2680,7 @@ test_tortls_cert_is_valid(void *ignored)
ASN1_TIME_free(cert->cert->cert_info->validity->notAfter);
cert->cert->cert_info->validity->notAfter =
ASN1_TIME_set(NULL, time(NULL)-1000000);
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0);
tt_int_op(ret, OP_EQ, 0);
tor_x509_cert_free(cert);
@@ -2689,7 +2689,7 @@ test_tortls_cert_is_valid(void *ignored)
scert = tor_x509_cert_new(read_cert_from(caCertString));
X509_PUBKEY_free(cert->cert->cert_info->key);
cert->cert->cert_info->key = NULL;
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 1);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 1);
tt_int_op(ret, OP_EQ, 0);
#endif
@@ -2700,7 +2700,7 @@ test_tortls_cert_is_valid(void *ignored)
scert = tor_x509_cert_new(read_cert_from(caCertString));
/* This doesn't actually change the key in the cert. XXXXXX */
BN_one(EVP_PKEY_get1_RSA(X509_get_pubkey(cert->cert))->n);
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 1);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 1);
tt_int_op(ret, OP_EQ, 0);
tor_x509_cert_free(cert);
@@ -2709,7 +2709,7 @@ test_tortls_cert_is_valid(void *ignored)
scert = tor_x509_cert_new(read_cert_from(caCertString));
/* This doesn't actually change the key in the cert. XXXXXX */
X509_get_pubkey(cert->cert)->type = EVP_PKEY_EC;
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 1);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 1);
tt_int_op(ret, OP_EQ, 0);
tor_x509_cert_free(cert);
@@ -2718,7 +2718,7 @@ test_tortls_cert_is_valid(void *ignored)
scert = tor_x509_cert_new(read_cert_from(caCertString));
/* This doesn't actually change the key in the cert. XXXXXX */
X509_get_pubkey(cert->cert)->type = EVP_PKEY_EC;
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0);
tt_int_op(ret, OP_EQ, 1);
tor_x509_cert_free(cert);
@@ -2728,7 +2728,7 @@ test_tortls_cert_is_valid(void *ignored)
/* This doesn't actually change the key in the cert. XXXXXX */
X509_get_pubkey(cert->cert)->type = EVP_PKEY_EC;
X509_get_pubkey(cert->cert)->ameth = NULL;
- ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0);
+ ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, time(NULL), 0);
tt_int_op(ret, OP_EQ, 0);
#endif
diff --git a/src/test/test_util.c b/src/test/test_util.c
index 0b707caeeb..ab6fe8ded9 100644
--- a/src/test/test_util.c
+++ b/src/test/test_util.c
@@ -9,6 +9,7 @@
#define CONTROL_PRIVATE
#define UTIL_PRIVATE
#include "or.h"
+#include "buffers.h"
#include "config.h"
#include "control.h"
#include "test.h"
@@ -1059,6 +1060,23 @@ test_util_time(void *arg)
tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04 00:48:22.100", &t_res));
tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04 00:48:22XYZ", &t_res));
+ /* but... that _is_ acceptable if we aren't being strict. */
+ t_res = 0;
+ i = parse_iso_time_("2004-08-04 00:48:22XYZ", &t_res, 0, 0);
+ tt_int_op(0,OP_EQ, i);
+ tt_int_op(t_res,OP_EQ, (time_t)1091580502UL);
+
+ /* try nospace variant. */
+ t_res = 0;
+ i = parse_iso_time_nospace("2004-08-04T00:48:22", &t_res);
+ tt_int_op(0,OP_EQ, i);
+ tt_int_op(t_res,OP_EQ, (time_t)1091580502UL);
+
+ tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04T00:48:22", &t_res));
+ tt_int_op(-1,OP_EQ, parse_iso_time_nospace("2004-08-04 00:48:22", &t_res));
+ tt_int_op(-1,OP_EQ, parse_iso_time("2004-08-04x00:48:22", &t_res));
+ tt_int_op(-1,OP_EQ, parse_iso_time_nospace("2004-08-04x00:48:22", &t_res));
+
/* Test tor_gettimeofday */
end.tv_sec = 4;
@@ -5085,7 +5103,8 @@ test_util_socket(void *arg)
fd1 = tor_open_socket_with_extensions(domain, SOCK_STREAM, 0, 0, 0);
int err = tor_socket_errno(fd1);
- if (fd1 < 0 && err == SOCK_ERRNO(EPROTONOSUPPORT)) {
+ if (fd1 < 0 && (err == SOCK_ERRNO(EPROTONOSUPPORT) ||
+ err == SOCK_ERRNO(EAFNOSUPPORT))) {
/* Assume we're on an IPv4-only or IPv6-only system, and give up now. */
goto done;
}
@@ -5473,26 +5492,26 @@ test_util_calloc_check(void *arg)
{
(void) arg;
/* Easy cases that are good. */
- tt_assert(size_mul_check__(0,0));
- tt_assert(size_mul_check__(0,100));
- tt_assert(size_mul_check__(100,0));
- tt_assert(size_mul_check__(100,100));
+ tt_assert(size_mul_check(0,0));
+ tt_assert(size_mul_check(0,100));
+ tt_assert(size_mul_check(100,0));
+ tt_assert(size_mul_check(100,100));
/* Harder cases that are still good. */
- tt_assert(size_mul_check__(SIZE_MAX, 1));
- tt_assert(size_mul_check__(1, SIZE_MAX));
- tt_assert(size_mul_check__(SIZE_MAX / 10, 9));
- tt_assert(size_mul_check__(11, SIZE_MAX / 12));
+ tt_assert(size_mul_check(SIZE_MAX, 1));
+ tt_assert(size_mul_check(1, SIZE_MAX));
+ tt_assert(size_mul_check(SIZE_MAX / 10, 9));
+ tt_assert(size_mul_check(11, SIZE_MAX / 12));
const size_t sqrt_size_max_p1 = ((size_t)1) << (sizeof(size_t) * 4);
- tt_assert(size_mul_check__(sqrt_size_max_p1, sqrt_size_max_p1 - 1));
+ tt_assert(size_mul_check(sqrt_size_max_p1, sqrt_size_max_p1 - 1));
/* Cases that overflow */
- tt_assert(! size_mul_check__(SIZE_MAX, 2));
- tt_assert(! size_mul_check__(2, SIZE_MAX));
- tt_assert(! size_mul_check__(SIZE_MAX / 10, 11));
- tt_assert(! size_mul_check__(11, SIZE_MAX / 10));
- tt_assert(! size_mul_check__(SIZE_MAX / 8, 9));
- tt_assert(! size_mul_check__(sqrt_size_max_p1, sqrt_size_max_p1));
+ tt_assert(! size_mul_check(SIZE_MAX, 2));
+ tt_assert(! size_mul_check(2, SIZE_MAX));
+ tt_assert(! size_mul_check(SIZE_MAX / 10, 11));
+ tt_assert(! size_mul_check(11, SIZE_MAX / 10));
+ tt_assert(! size_mul_check(SIZE_MAX / 8, 9));
+ tt_assert(! size_mul_check(sqrt_size_max_p1, sqrt_size_max_p1));
done:
;
@@ -5623,6 +5642,33 @@ test_util_monotonic_time_ratchet(void *arg)
;
}
+static void
+test_util_htonll(void *arg)
+{
+ (void)arg;
+#ifdef WORDS_BIGENDIAN
+ const uint64_t res_be = 0x8877665544332211;
+#else
+ const uint64_t res_le = 0x1122334455667788;
+#endif
+
+ tt_u64_op(0, OP_EQ, tor_htonll(0));
+ tt_u64_op(0, OP_EQ, tor_ntohll(0));
+ tt_u64_op(UINT64_MAX, OP_EQ, tor_htonll(UINT64_MAX));
+ tt_u64_op(UINT64_MAX, OP_EQ, tor_ntohll(UINT64_MAX));
+
+#ifdef WORDS_BIGENDIAN
+ tt_u64_op(res_be, OP_EQ, tor_htonll(0x8877665544332211));
+ tt_u64_op(res_be, OP_EQ, tor_ntohll(0x8877665544332211));
+#else
+ tt_u64_op(res_le, OP_EQ, tor_htonll(0x8877665544332211));
+ tt_u64_op(res_le, OP_EQ, tor_ntohll(0x8877665544332211));
+#endif
+
+ done:
+ ;
+}
+
#define UTIL_LEGACY(name) \
{ #name, test_util_ ## name , 0, NULL, NULL }
@@ -5716,6 +5762,7 @@ struct testcase_t util_tests[] = {
UTIL_TEST(calloc_check, 0),
UTIL_TEST(monotonic_time, 0),
UTIL_TEST(monotonic_time_ratchet, TT_FORK),
+ UTIL_TEST(htonll, 0),
END_OF_TESTCASES
};
diff --git a/src/test/test_util_format.c b/src/test/test_util_format.c
index 63a668238c..21a6923c6d 100644
--- a/src/test/test_util_format.c
+++ b/src/test/test_util_format.c
@@ -11,25 +11,14 @@
#define NS_MODULE util_format
-#if !defined(HAVE_HTONLL) && !defined(htonll)
-#ifdef WORDS_BIGENDIAN
-#define htonll(x) (x)
-#else
-static uint64_t
-htonll(uint64_t a)
-{
- return htonl((uint32_t)(a>>32)) | (((uint64_t)htonl((uint32_t)a))<<32);
-}
-#endif
-#endif
-
static void
test_util_format_unaligned_accessors(void *ignored)
{
(void)ignored;
char buf[9] = "onionsoup"; // 6f6e696f6e736f7570
- tt_u64_op(get_uint64(buf+1), OP_EQ, htonll(U64_LITERAL(0x6e696f6e736f7570)));
+ tt_u64_op(get_uint64(buf+1), OP_EQ,
+ tor_htonll(U64_LITERAL(0x6e696f6e736f7570)));
tt_uint_op(get_uint32(buf+1), OP_EQ, htonl(0x6e696f6e));
tt_uint_op(get_uint16(buf+1), OP_EQ, htons(0x6e69));
tt_uint_op(get_uint8(buf+1), OP_EQ, 0x6e);
@@ -43,7 +32,7 @@ test_util_format_unaligned_accessors(void *ignored)
set_uint32(buf+1, htonl(0x78696465));
tt_mem_op(buf, OP_EQ, "oxidestop", 9);
- set_uint64(buf+1, htonll(U64_LITERAL(0x6266757363617465)));
+ set_uint64(buf+1, tor_htonll(U64_LITERAL(0x6266757363617465)));
tt_mem_op(buf, OP_EQ, "obfuscate", 9);
done:
;
@@ -213,6 +202,9 @@ test_util_format_base64_decode(void *ignored)
res = base64_decode(dst, SIZE_T_CEILING+1, src, 10);
tt_int_op(res, OP_EQ, -1);
+ res = base64_decode(dst, 1, real_src, SIZE_MAX/3+1);
+ tt_int_op(res, OP_EQ, -1);
+
const char *s = "T3BhIG11bmRv";
res = base64_decode(dst, 9, s, strlen(s));
tt_int_op(res, OP_EQ, 9);
diff --git a/src/test/testing_common.c b/src/test/testing_common.c
index 9c6580f788..caeae13a38 100644
--- a/src/test/testing_common.c
+++ b/src/test/testing_common.c
@@ -178,65 +178,6 @@ remove_directory(void)
rm_rf(temp_dir);
}
-/** Define this if unit tests spend too much time generating public keys*/
-#define CACHE_GENERATED_KEYS
-
-#define N_PREGEN_KEYS 11
-static crypto_pk_t *pregen_keys[N_PREGEN_KEYS];
-static int next_key_idx;
-
-/** 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.
- * Our only guarantee is that we won't reuse a key till this function has been
- * called several times. The order in which keys are returned is slightly
- * randomized, so that tests that depend on a particular order will not be
- * reliable. */
-crypto_pk_t *
-pk_generate(int idx)
-{
- (void) idx;
-#ifdef CACHE_GENERATED_KEYS
- /* Either skip 1 or 2 keys. */
- next_key_idx += crypto_rand_int_range(1,3);
- next_key_idx %= N_PREGEN_KEYS;
- return crypto_pk_dup_key(pregen_keys[next_key_idx]);
-#else
- crypto_pk_t *result;
- int res;
- result = crypto_pk_new();
- res = crypto_pk_generate_key__real(result);
- tor_assert(!res);
- return result;
-#endif
-}
-
-#ifdef CACHE_GENERATED_KEYS
-static int
-crypto_pk_generate_key_with_bits__get_cached(crypto_pk_t *env, int bits)
-{
- if (bits != 1024)
- return crypto_pk_generate_key_with_bits__real(env, bits);
-
- crypto_pk_t *newkey = pk_generate(0);
- crypto_pk_assign_(env, newkey);
- crypto_pk_free(newkey);
- return 0;
-}
-#endif
-
-/** Free all storage used for the cached key optimization. */
-static void
-free_pregenerated_keys(void)
-{
- unsigned idx;
- for (idx = 0; idx < N_PREGEN_KEYS; ++idx) {
- if (pregen_keys[idx]) {
- crypto_pk_free(pregen_keys[idx]);
- pregen_keys[idx] = NULL;
- }
- }
-}
-
static void *
passthrough_test_setup(const struct testcase_t *testcase)
{
@@ -365,15 +306,7 @@ main(int c, const char **v)
}
tor_set_failed_assertion_callback(an_assertion_failed);
-#ifdef CACHE_GENERATED_KEYS
- for (i = 0; i < N_PREGEN_KEYS; ++i) {
- pregen_keys[i] = crypto_pk_new();
- int r = crypto_pk_generate_key(pregen_keys[i]);
- tor_assert(r == 0);
- }
- MOCK(crypto_pk_generate_key_with_bits,
- crypto_pk_generate_key_with_bits__get_cached);
-#endif
+ init_pregenerated_keys();
atexit(remove_directory);
diff --git a/src/test/testing_rsakeys.c b/src/test/testing_rsakeys.c
new file mode 100644
index 0000000000..134770bb0d
--- /dev/null
+++ b/src/test/testing_rsakeys.c
@@ -0,0 +1,546 @@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "or.h"
+#include "test.h"
+
+/** Define this if unit tests spend too much time generating public keys.
+ * This module is meant to save time by using a bunch of pregenerated RSA
+keys among */
+#define USE_PREGENERATED_RSA_KEYS
+
+#ifdef USE_PREGENERATED_RSA_KEYS
+
+static const char *PREGEN_KEYS_1024[] = {
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICWwIBAAKBgQCZa39BCgq7KWBWFSjGYHhqmTCHvQ7WNEFAb9Mujb6Xn/Zy01fu\n"
+"WIpVvqmAKeLNEziItUm/gB8GwAN+/ZLwL9pufjIp2Ar+yqVXKySioZQxuCgTP2wm\n"
+"Ku0OfmAra1Xbtrkc2OCJllxkyNPrJ/kxfwjWR96UP0+VMbOlkBoEH1FtvwIDAQAB\n"
+"AoGAUXoygeMIYe+OdwkTt48CRHKIwH3aRE5KHSOGPyIOB05vvvmYqD8jcHgqYqNc\n"
+"DNdZXdkRin9LevU8phObFq4DTXp08XggUx4Kk4AdsFKubQtJ8gHm3xlSKbZXX2m/\n"
+"ZF0GRaZtVDQ3TRGh+OBLILt/2jT+BaFKGAyJ7al76F2nprECQQDJyLlteLDFBmrd\n"
+"0kAjNBE50S5YskBCQeQACROfyTKW8lG1J57UBeYjXvbrDFBR4alIS9DEexGai9Gz\n"
+"wxpgKg2nAkEAwqQmPstjHxvqGQRi41uXO026MLxY7dhEqs1aSw3tuT8v17pW3OEa\n"
+"Qxv7JINePZ3+sNN+Ic+3RXBR0QuD7lSSKQJAZjVSF21GvMXfY7SX4D0DbLHUNAE2\n"
+"I1mUz5/JXOpgwazETmpfPS4vwELd93kpRhBz2rbsbFmaNRoVgmSU+5jRiQJAZ1bV\n"
+"g2NilgKxEGU2x3U6Xt8Oqo9lO6omEvUCKnUTsNWuZf/l3FGbKuQxO5qPr3Ex5tny\n"
+"zqrEqBZRKgbOHfxCuQJAbJY5C3Nm5koemr031r00MY2YD1b6+hyKZyPdZ21HpyY8\n"
+"z1kWShL0POjYPX/BnKE1FkpklWcKBb7wkK7dvAKkEQ==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXQIBAAKBgQCyqMM2TfFGV5tVBTVabxLVln8146nDavIdR6q78DCUMh8Zfzkk\n"
+"h9Lbl1NX4RU+AmrCZMPq21/EjIRxRQyRdgPYJVLdp96eGeYnEzmMkqvXiswXvDg/\n"
+"tXqsjyJeYsoHMQWDTpCLfjYo4K1ol1sg8VIs4wQeq5og6QSdmhBoz7MyqQIDAQAB\n"
+"AoGBAIJekey7nZeV8Bxva4ptSRIg+v0I/2VBUiG5nUX9NIW/uV/yrXERx/VDjKaw\n"
+"8b5JJzxpKWnk4RJc83xwRYaT1qMYHiQfybxEI0K9SjhtaThAjtXkQGtZgLJILl3t\n"
+"yh3LPTh1ocwafsKjU6eGYAe/DYn9/QwYHbtyaimcigu4etp9AkEA2DgC+HndoP1i\n"
+"np26Lx+4TG0vAfrVYGSLT9FXwf2iBV3oJvdKqu6wr8ipb1SbshRPcOQd31/mCh6+\n"
+"2BR+d4ddcwJBANOHrlBbGZdHnoEu6kKbPwwkc31IZYqyfSpkqm0Lb2oWZ9SInKfc\n"
+"cz0qpH91p610XUpYmycaJr4K+N8jgrz86HMCQQCoqGBg1Ca2OpCf66bctWB8dTqS\n"
+"z8d7rlIhC8npr1+f0hWRt5pN5Wx7YgoQpq3gZgllpPtMT7DQOhVh1fKkaDnTAkA4\n"
+"XuskPPLX7t0dvhvtviOSH9CrLXTp/mD+wC7uumJpmij3aaSd01DelxOZaAhUYDNQ\n"
+"UcafKAf1E0V5aaQ4qwljAkA9NVN6CtpzzcLrstTKxrx5P1Ylt/0UYQDo1lIaqwrT\n"
+"aOFbXmOungiC9+p/4U7RbX0MEzjFDHCWlaHASviGVgta\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICWwIBAAKBgQDDt2V63APj3JSqaRgofUzhtB+prm0wII4uHyxfOxnpYIELOW5z\n"
+"3UHmkr+B4D+Nif5jIp0i6W4OS4S+YHewKsDsXvXKRIW78KzOt6Le4JI9rSarNjy5\n"
+"aJKksWQRALLCmxP/BdolaBFqF3fIPD5+Zxu8ESgxhkEQI4p7awUp3E730QIDAQAB\n"
+"AoGAZktfAR4p8lkCYydW9yK2ommQ+xEuBK+fYL/uYz/yxSYpjIJSFsEYhrlA21Mo\n"
+"JIRxr8MRuoOjgFk8YnztUeimuHpslDlZDaCBzjRjBRFCMepZNG9xqSEL0u7C+SH6\n"
+"KU5f2x2P6PneBj6WaHZM+6Lf2xHlOoeuaVSUfq2Pk2VBF9kCQQDtawWWNwP0+xea\n"
+"oCAQpanaLzYPjlqZfHJQ1AAI5eSkdf1qmlypIHwOtjAEa6XuEO/Or8RNkNy4nQdw\n"
+"qhcQ7PXDAkEA0wjT6Z+Lrt67FnwPgoSvl4Nukcqw4OWHbBKhaQPsO9+oc3PAXLdD\n"
+"SclUUqDF6NX1yONTV1KrPdz4zElmEua+2wJABm4inZnp2oW+cuqpU6oY+pbSwQMb\n"
+"AxMyyWukgJkxYx7q+SsrHU2K7p8Sl9wOh28f/5oVGAC3aayfGfcRXtz8HwJAIqeO\n"
+"dQzYGU1GF7kjquEzHIRewd4xEZ1fkaW1j9MvFd3ygZL+gbsud41yJWd1WHjaNbTu\n"
+"2KYgrLX+vT1IX844hQJAbg0V7iHlttQqXL7yN09jIjQLprqVhDZCUHS9s9Dxe7fz\n"
+"Ac0ZZD0D6EVNmSmBB71q7kLUWX/W/10d447TLnnfew==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXgIBAAKBgQDhCAjPEockl4lqkvoIb5O3NJJG8NWD31c63e/cPWY6MX5nOM/q\n"
+"avof2eWJxFOk0HQ2BRVwIgNex6kLxtsdw7XE0A5uZorTp9DbRCGMqUqHNhHH9ci2\n"
+"mMPP9jptq3ieWg310bH4Tad8h3WE2npSCDBvxyV6EmuH2rlQW9ZlHNoiRQIDAQAB\n"
+"AoGBAI4PgWggPTqng7PJF5mNvsYQpSutzE0VCL977nmuNUQVjMPjRLarVD4ZU+QW\n"
+"EevhQQv9R5xjjJcgGqL5pchzjeKDm0/LA+AygnZoDMs2O68Neieqvr7cPqr5ALGs\n"
+"WuZvSn+bRJTenvV9sUh2ii0/u3GQbL1v7GWDkIdD7itDbmRhAkEA8iijuEY+W67w\n"
+"7JusjY2MQ2Cm6xxxR0YcnYPzT6UDm+Z7NNJwKscQ6AjayNmxmXGpbUdukzLzXf8y\n"
+"fccI9t6iHQJBAO3kx9nZay0Ktl51QP5o2gwoqRIbnogGfR06KJOlzIPGR0aPn8cg\n"
+"uKq2SiyjewEaSBM6S/4UlxYUmvc3VKnxCEkCQQDpTjg2YQ7RPGIIRA/iLV7Wx3bq\n"
+"C/QjjCwjoi44LK6mdE9928WPoUzrkSRg4EQYpwZqL6kcDrmkdSuLPMipOGQNAkA3\n"
+"KtzlujPOiDNuiEaAORSHyU4b8ue6p7aP9pK+Wq6oyGxzAo+NABuTCx78ZxT5Vnzs\n"
+"aJKC44d+CV0+g0hQ+KJxAkEAqFYzNWIzTHX8DVDdK9BpUaBg1DFxIeP5Kk+/X3FF\n"
+"5BafG08B6OiLf8qIGGsxLXNRjIE0GVp3Sy23FUKtUymP+A==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXQIBAAKBgQDMDk01VwPxQq/BAwOBmfGUP/x5BQn+uxI0Aat6bdWuz/2CsjbS\n"
+"CWD/YLCaPm+DpHp9RMwk4HONJaw4B2XOw3ELPx7y9DEgdC1wZ9wRkJmqr2IJZoZR\n"
+"C7x43nNv+/IXTiRkkljCcMpoL1Tld+L2VbmWR29PdZwvspWRILkEZu1mNwIDAQAB\n"
+"AoGANvFK3KfXSei4xfF3yjeXEmHAKx2uOUZJenNQpqBYPr+F9ODjXd5knZ59LqrM\n"
+"/9cTnBMgHHXK5yBTpKppQSjikLeQ2BF04Ktff9oGqVcS9x/rKo0CREuxsEfawZOW\n"
+"OzOWENp4YcDKGP1I/Ctr185QzStaWrXVQftxmYQ53T77ShECQQDnhabwtqW7rfe4\n"
+"+MfkWEJ9Y2s6iMs3JWnwPOX9G9R39PiAD4vAghHJyHHttS9Ipxmvp0hThu0x7a4g\n"
+"8BfUpqgjAkEA4aFAmzarWKigREAACVTYH2RHpXbuk05vF9WqfMPiEvQUd5a1q6vc\n"
+"xkGZsE3v/TExLjPRZP4FeUNV5sD7THzA3QJBAJxPoRlNx3GCEAlDdfnWGPX9JI09\n"
+"hC40RWUcSI7ttjJTI1+an1kWuBnLChhaRpU/tFjikTNLmmMmPHUihIRfDI8CQG7g\n"
+"3WzpKr8A7vFbOilbxnF2yDaqAYfmTXW7DHMPl/OUetJh/5kDdhT/e9VGF5+nIvH/\n"
+"iPFGW85Bpt8lCtmFnQkCQQDjpp9iy2qesE7KKX4Kv3++QfCJ2w3g7lwg4iyncoDd\n"
+"JrM53p29HROM21R6eekvqeWIe9tEX754b+E/N60ZjpGm\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXgIBAAKBgQDdDn3H+Eu0AW5GKohqDBntw6ubnd3VaJwZGzZyga4J2kLg8peP\n"
+"RAW6GDD6pcHzW+KZbFWHtRk70FSwvmyGcf+DY0r5tfyCHyDGmbJyPR0o6OVCgSFl\n"
+"ccf4eDvbyszzMdlx3uL05ABIpCShoKtEUqvyIQla3Jon+QBwuVkizMzyVwIDAQAB\n"
+"AoGACoKh4Fwh3VEkGRn0mnYw1Wk0Q5Xh8j+jDF6K3C7mQ3mpLGDca+dkDlEQIxq2\n"
+"egeoYnsQJf+qT3m8TRsAtfO9nj7+7IX4BfCtdIi4RNcorbs5YMWtFyaywnM6SQjS\n"
+"+1qf74aL4On9WRO2FtvnTMjFAAkiWNbQp7mWwTmB59i620ECQQDwde6/PwhUzvZh\n"
+"dyslKJdna5RjkDQyDIuh0zD/tFZ0Iko7Luec8q6n52ev/n0OiTLGetUh8goePsPP\n"
+"HVZHidNJAkEA61eMCmmu+GCAg2vJRtL5sDakAXsbP5M9Bf/QVHXtc4EVXHC6T2ld\n"
+"bldOJriNbBThBuPNmlQbssn9FApkyWT4nwJBAIuHIv3+CUuMvBJaH8L0BsaP+g67\n"
+"wk24Ud2Yujnl3rSMoR4uXV8IwqfS8quAs/gXTEs3QyzrUUuzh9NKZqIkK2ECQQCz\n"
+"vivBEDKIlPvSZBJYO25kfXcJgoKvLb9fw5/TwjXXD/HGpnpFiI3JZnjT7gRlVhT/\n"
+"9CDmC/MTvF3EXqPXhXy1AkEAo3a2me23Ljmub21jycSKaCk09dK85QTRRMe9c/hs\n"
+"i+pcGi9ZZW0Mm7cyQo47oXjNurkkv0fEvXIobVTEXAGU7w==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXAIBAAKBgQCv8R1IbfYnE3R3kNeezJ7m02XnyCBDDy0YfrQldQ+urdg1CFye\n"
+"bO0iPniJb8fmV8NW7x6nUZTDznCg+igroKXtK/w0WYmJJiH4A7Oi5xNjAfRIPvJ/\n"
+"J5GI8szS8rH8tp8pW1h8k/kNg2pnBjwQ2U9omhp95RGaHDQSRYzzH/fEFQIDAQAB\n"
+"AoGAcy7+BcH/iZuB/xjzIIJDcUhqibCJ9n0D/+pLU85sYuZrCmUcBZe4M1gEn61v\n"
+"iExilRJc1hthskL/l1POYql8lk+aqeeDuh38fWJj60TCV/sENiuXOsTmoFVA5pNn\n"
+"lwlG8JlpBMsgr1fGqg1C/WLFfMmvXdKVGvpRqI06j7AYUa0CQQDfZ5rI+FhXBlxo\n"
+"PR5CM1LB90DuHUMW+Kqoj0c9d2esXEQM7UqQ/9BiBQbL6Py7Z3VwCxibOqyz7+V7\n"
+"2aGUMAKnAkEAyZy5Mu2tHs6YBBxPYam7huzMUYjddN7ixAZUyGwxQp9kTIF2NbSQ\n"
+"yVDjKrco3s2lO4qj4pSumwVe3GGlsi6G4wJAOOS3pIqqZK84BUvbUtyjLMZ9AKbv\n"
+"GQCG5ZpneB3ahyiQJAKiRL8BIJVLH87b3hYA8GHDCHUu2jwz4xCPd5+qbQJAV0TP\n"
+"pYvb9AnZI25drhiaY7z8dA6aTYxs/A0Bhf/PEteLwtIHKRgP1BR/QG4n8slxTGSm\n"
+"q91P9ypL9XkPECGzoQJBAIMvGEM7ZGevQHBjJ8HhU8IsgT4cYH/XEYb8jRy4F+Ui\n"
+"jKxHPxLuFK4urAZunNUNrqhT0PxbB7hRjtHZrmFkrcc=\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXQIBAAKBgQDSpmV8ncLwc8gXzdFsZGPDtMO7C/IN9jKCIK13WIseMg1APlMt\n"
+"PB5lMQ9fa3m9ZRU0L8HzRo+u/Xdos3yIBI38X2Avy0laGKnQxiOKaDT/5ZHeiBBh\n"
+"nMZjP2WY5V1sgqNP9RD8enE6WaSvq1j0BM++mn9KEe//5+dWD8tboBKF4QIDAQAB\n"
+"AoGBALgVoerdE1Z+WAY1XyaSNHz6o3H6ZnW9CTaex/jb7/dbVikmThnhx842qXCB\n"
+"w8m3ZGhOs/edWkNaTde5wsI6+LhVGco/PWxN4v61jokxUU+5KvUvGacXhXIjzKwG\n"
+"DrNCYmle62QCI1z4+TLQW/Lq+jw2Wzk70NWEvoP58gt5SJoBAkEA9wubRKRs49LW\n"
+"5JNQZ9hjc+mAfP9YK/sMe4jkdloMMWXjSMlF3Z4mI9XQSpfbBqwWIBXsjU/15LIS\n"
+"ftmujZsMKQJBANpJEZI7UFoRdSP7AlM0YJuXWnVGyn/K+VIeEso5AlZdKXCTpxqp\n"
+"9blWq0UVC6jLesZ5UNPuBiAnrBaVwDA8YvkCQF+FQVfdK607TJO80g4VAP9EfcXX\n"
+"BUScIUtytsN8NdKzzpnKGRWDnMOmXI87ABkoWLW3RGuvSyhOIhCiInfmR2ECQASc\n"
+"FmroJcJBLCAeZOYs7P1cLOTdIdmhB7LcP7lVit8YCJAADj9Z536KfgNvdleSNH2M\n"
+"glB3blmvfMrdTrm2DMECQQDj6GJ/Tc2rCsq534xknasVjrgtJMQFxmQCTVgBx9pc\n"
+"gTflJAHAmNDvstacVqeObLCF2ZIvya8fSXGbDOJYeGDv\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXAIBAAKBgQDGgUJAm7vf/3focNGwzv4TkzYF2XwpAirnb61dyxvfug1zKv2k\n"
+"AUg3qACiurR7JrI+kAbmxEnNaKV7ts7uO763wP9KE8YAuFZsp7NFA295rEZhw38T\n"
+"rUlWHMCeaZ3mqW2q8gA14C/ZJCG4gS91SIHLjNGsbHwr2Jvri2ItwIP8FQIDAQAB\n"
+"AoGAONceb32oiHWQkkBr6uL6ogRPPdGO2fdC7c5uqCLWsnOGEmpHAsVTNoym0fIA\n"
+"aBsmgv+e2klukKDccdZg3prA+z7lHcc2a4bIFguF6ei80hLIis/dds66fFXofCzy\n"
+"DMlkncSbJwIvQHG9gblxp9qSKElZF7XjABZEImarfUlakGkCQQD//msGy5N0ZhMI\n"
+"yGMXkwXRJXfmRrIrOqHx6u1eUp4OuqDW+hBz4KCHnWfuRJkNGQIammSf18jPasP5\n"
+"YHyr/LifAkEAxoJ8R8Vusexo9ZjuU44qXCSvJQ26UBV7mn6TGEAn2DRK1RWKDaHv\n"
+"j2vnRjt3CO9WPDQL7SB/1HNAy+dIMPyqywJBAIB6tESIz8zPniX+TJ18UKMTZwXP\n"
+"3YQMvVKpUdDRLjq+OBMtFizSRD9MJOlUzGvibUfkzTPcHRDcyNbUMj4vbIkCQBx4\n"
+"6sqAjvgGKKfRX52sbnb47AYsieSisC/gp8h6qzxfg7w8cqix6WJw36M7ND+b1Iqe\n"
+"DHfeiXc3cLvOWJRuKTECQCEYkujtSjXWb26xaESFWGtUI/nEvCyqYPQAFBpaGzQ3\n"
+"tiTDeKHzypesWYoTxOiNQWCQMLrFGuUbDpYOuDOVNjw=\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXQIBAAKBgQCcwSAfytnspSSDX/sKmCPOMnpuCYeWA4wbz1wLyb63a8/KXhhG\n"
+"6o2W0kt3x1vnGZkeWwZOeBFUqwoc+xHhoNcZFsMOyqbqA3UMZW5cx27MsexRTQHs\n"
+"Go1newu/E+8NNCohY51G7z1Hdo0L6mi/Tldh7puuGsMwKqNG/Vvo/GQDgwIDAQAB\n"
+"AoGBAIUdpBAbjXDe1OET0vYuOMnUKA/l29RS8tpy/zGrg1/0GCM8QNWIPfEEaL4w\n"
+"+CSKonMazYI5iE4kaZQuygKXOdFqKxX8nrGK2hR0DIEUHhhiqyGMUKrf4ELkAJzK\n"
+"tHtcO64OFEU2EGa72wCmyk2MhqhLxWxA7E00x24uvW6pen6xAkEAzHhbzlRgLZ+K\n"
+"QuXmQHEqkGaS2Ccf6c9TA5Bf5S2/5zBl+OqVyJJQH0yrbPYR6Nn1NeSv3R4IDJYg\n"
+"fSZLaVzWHQJBAMRCU6QtTnZoQ97pLvXCSKRYKJF+CnE3zDFTyoJrpK0W1FSnb1EE\n"
+"DWjjdSdMLynf/InX+VOaLk3Gxwjme4NKjh8CQQCg2b4/HplayrsVzY3I/D2jw02Z\n"
+"xY2RfYusrhMCU284DBbsLn8OfiuRs9rXqOyF5ZDFiNXgeROT8zYzvcBtbp7xAkBU\n"
+"ZET9IvJLXjhZISItUXbVHIeNUIqC9sBaMbKx9EGioF97a2gliT2O7cgRtuPM+ODq\n"
+"ETHILlNc5G3vuNRBt4x3AkBV98Y1SZA3TQlUVTsjGraxkFTfU1IlomiOdOwTQ+xZ\n"
+"x+JxhhgZwZ+kgI3PidEufFCTZJ3WO6Wk9gk18Bx7CLjm\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXgIBAAKBgQDq/K7wNW3fcTbaRTjNZlM4W0G7tKeO+X0bca4+9uin3ML3ogNJ\n"
+"6qT/B0QAZB6Vyi9kKa3E8plQkjmPuX8Q27zj2QjEuDZ12RGFnikeOosUhOYiDh3Z\n"
+"T9CHnr6stozzgk79Xd6VI7bqRcgRwbY0uc9QVr6vwddyIfSploSpVcgspQIDAQAB\n"
+"AoGBAJfUpo/sZc6uzxtfCKGmkPTj+ef3hSBbUZuu60AhtxfnC06HrwpOg0eJAUYj\n"
+"aqOsHMziJTYQ7kDiCjE0UMaqxDNS5hueumznq2xM2mSN0nYoktU00kpANVkW4VPA\n"
+"33TB16DyqlKq2/21Rs1g8/8+IKkKDbRLTC//1WqNHASQVoGNAkEA/+z4hxTVXZkr\n"
+"9hz29tAHKURlqzxUEKLnS0eL+XGJRNfGJ+65eXL+gFiIbTnpVeidL1+lKWkZyYzl\n"
+"75cNRdUHhwJBAOsOJ9mUOqTbLW5tzh18ewZGOa1JcxhOvf2E1d56N8tDK6lvoqkF\n"
+"oUUb8kIweDxPLCVLCl8qFrbjn619fxDInXMCQAfEZGKNIlCd5nSoumIRPDZnagKB\n"
+"aTe8CfMB7+CZLoZVWiE6IIzsDYdNqI5QFKHT1nlqmLOiCfNRAGV+GxwEdB8CQQDE\n"
+"sHu4HclU2fMSTOAE3H01qt3om2WsGXfyBI3SNQMrG3IVvkymkwd4BQKbUGPMU5Pl\n"
+"QP3U1CtdruuXCUSijrzxAkEAoqYub6+0zM8fakSQZcZ01TG9Fuo2xVFDCQsvqR3m\n"
+"ZhRT/oinIvOxSh4fQs40bmt1RBmc2L1Is6YB2NTVQEBZDQ==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICWwIBAAKBgQCrf0rPvHYaGYQrc1ciRwaONs8TUvSVmUU98HMYXoFEkBL4CAGH\n"
+"4oNHFk8kXHEOsBED0eccSYegWhqKHSz7PbjmJaXloExWrtx5ea3Twf8VTgcfDWQP\n"
+"0TzD3G1TYjAFPQ1/LAZCpQFmwpMmTGGxegUhOzkpEWXdLVEVc9Uw4C4L2QIDAQAB\n"
+"AoGAZXAJZA5pHM7y6nBynYe9TOkGWru6h7H8zsImkcd0VoWRcrvpi+JjG+0KKsuy\n"
+"46kop0XEmWq0mhgxknfnX0QG1MKTqGMIUGN4qCaezOabIpCOdA4d/pr/mWoNgOWw\n"
+"9Kc/tNCrKxPKsQMAlWP6ktHN30XRSlHgAjSeUVUiNHztvTECQQDUNin2nyIvj8ZA\n"
+"QAsFW9qW+TiTkeUK6yiZ9Gvgf20gwZRWOe5/xnMxVvtN6v7Av1ew/l4VhBoj/w5g\n"
+"ydIZk+2LAkEAzuJwdt+ccllG19qmEcbo9XFafgi2PvlEjPJmT1rHV2ns/7HIMu27\n"
+"PJY36GgExSfFco6VmicaoOt+RKg+5acgqwJBAKQxAEjcGWQ5VsgRhTVxO3DChX7Q\n"
+"TColhrWPwwPhM/s7K92HVzwvvKL5TNmdr9xMb7n3Ja56FouxZVuH6/J0XT8CQAat\n"
+"Mhnz/3WFQg8HRGLAe5YoMVZt64u+uaKe1ARtlo9QoNBjqWVTXL6IzocWjEjcjrey\n"
+"uEtARdC5qNqIX3dD3H8CP3pVCPvpHOTxkUaktmLYowSA1HSfO9wkE6bMCHhkLwXF\n"
+"yTIJ+N7c5u5YN1B6hhVqpKbdnSv+K0MQ0xbfwOWNMw==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXAIBAAKBgQDGQmrKfO3WovoXkOTSh/shO9qjbX4izhg4pccVU3Tp45v/dgAE\n"
+"uDUuaa/clToyH5AhOtuazO/asC3ZNajg1ia5VPzmQU3gtqiIZIEXFaOovPlOrXru\n"
+"wyQnxaGORndJwfDXicG6bUwI+PDpNq8c4VOTujReeF0r74qMSc7TQLVlUQIDAQAB\n"
+"AoGAakR/aTm9YibJVohbnl00xoOGlcLCsXU2lmaFZ3DsYdGWdD+TkvQJzW7ozJtQ\n"
+"Lj2sy6L4wujGR7nXWW3hr2IaLpoc1UoyJpieAZM5os6bMN+N4MCqdcZMlazMtSWV\n"
+"UDO7O7xQGFpcvvZmnfKCyluFaJ5K/tWxP+2TnS1/m0BDRIECQQD5DYvToA0eKBt+\n"
+"7K4eEI8pzDot9NlcL21D86kNgpmuY4pifALU7GvXr299JpFFiYa2A1JVRfpQaoI3\n"
+"hZzz0ze1AkEAy8opWJP+T2q4reD5Qq5UjjrHUXFID23KeJEjh5YF40/bHqyVpWVR\n"
+"UMntNgAzs+13vRij48Zn6I8GRhStaQ3ArQJASPyFS8GN1paeaDXoWPs1WWR2cF1f\n"
+"DbsAZHeVxVXOv+J//ZimI8wdVpodLCoPTLee+NxEVqUpVEPCYY8QjgwKOQJAATmj\n"
+"6f5pxvxzQ8hYd0gpBfngfOLbdgxI7VSiDAyg2G8AeDy9YZMsW/n6zRpPNUO2NpLR\n"
+"WWs18LX7aaxyJnGIuQJBAPPfy9pd4XEFsRBIIe3N23Gua1XkS/407RJtAGm73Vrt\n"
+"QhtWh3i6D5gfpEApMoaE8aaQQ7H0z+0Uh1t8SWesy10=\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICWwIBAAKBgQCc/M/X8etUqrxnmH3PyuAYLIPZhwNySch8qz9NB47izYjxzuBG\n"
+"GSls6H7WeKIrB8UJY1gW8TLkdOLcrI/0hTANNHEPaueOE0xdABFj7tAaiiGPIM25\n"
+"N0wc76me0ZAMYJrZTHk8JZK153y9wInYBwVZreXCVSVf11RuVwe+iFQa5QIDAQAB\n"
+"AoGAQC4XJtivdhDLL6snHFF7pkZkrQTGgu3pOhakrXA+mTigGQOTqvTUe8LdP/9X\n"
+"hTIK+tiTheWcAcxLhx5BSB0/VDKjYhS0ROpTc33Iq9KalOQaTJbBYGA4eagpQjwU\n"
+"jGwr9u2sUsM9WI/Jg0VvLSKhfnNwYIUzLpK3BbWb2qAdh+0CQQDQ2s/8DlibFSBK\n"
+"UsFK7lLpV8UgMk9CkaNM2BPzI8Hsjpp6s3pULVRd36m4YTSg15EEHv7bZ1N/+krX\n"
+"mXb9xUULAkEAwGy5wHsUSjTK+kntkNXjlCU/+9R+HFpzg9Bwm/PqXTBwEWeU24hV\n"
+"iRjPvqPtWFZrWi/nfcviuMaqtdliw1I1zwJAZ2mQxhtMYC2LuYFUWAe9YfClmJWQ\n"
+"jUOTef8bka5I3RqW/t5TWc7AEWMnpDXtWx6hnUrDolt9Cschu7MvKeQ9lQJAL18U\n"
+"46PpPNN+XNuyVoOxgRkihVasrUI/SeYYsuv7eHGiRUagyOLpW9T139LvbV3pE8zT\n"
+"So7VA/Q0towL2lX01QJAGcoBNNouSpum9+5NvGQK1XXsZweawE+pFR2BE5XcjG+n\n"
+"FnaLEUBX7nTxhTU2cSQET1PKRNp568a281NEna0nxw==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXAIBAAKBgQDFOqqGG/VtIScxayZYZ+BT+hcs5W1bD5qRxunbG9O36UVT18UE\n"
+"CWw9HUf0Q5sDMGvVmBxwZ4GjbR5FDPfhIXaRCzobnejJXq/0k+O5NAVkcSPtJvhK\n"
+"AaUqBrWA41vnjKOtJudTsZLfufKafzYwVonze7fXGyVsBRjVwHNS4iqq2QIDAQAB\n"
+"AoGAJCoStI6R3RXUKvKb0GATuTJFZ50WBTmCPTK9FMkwdCuY47vPy2Ky7y3cUMTI\n"
+"urf5PewrYs0H72CFyWGMXkKVi8aOYshsATEXMfGSqOcqXn+UDssRzvabZFlpnAUa\n"
+"WDVt/iN092AdakXNna7/DxrLisDpq8HHJfjtlWGPfkXRg4ECQQDpHeKimTvwJcPc\n"
+"iDa6Qb/n9gwLeRckfzhYtfX1luJYLIOHh+J9vjQN75thenBLQB/B6qlKtOn9ejxg\n"
+"5z+3zIOpAkEA2JbxXVTCOA802p9khvHxDtLHdKi3w/BjjJiC7Mgqo69ZI+s3PB9E\n"
+"F2HJA69kZqpGqvybWHDapjWsq7rcMlxrsQJBAME2yvR3y00VEAyGPc4M1vF8ZqlP\n"
+"uRW/+ETWtEDUyU/JvU6lGt2bu2tdkEyv/cjxIiFIzP4litdT7B1pLc+6S9kCQBwE\n"
+"usiWFGHoJbA6emiyl7qRLdg7kzo3uMkRWa6D3nA6WM+6t/SBHu/faH+fit91G5s2\n"
+"/mmcf8yMmP/GNoIVTqECQFl4Pt6yGiz/YVoYSp35ljY5n3JB6T8o2pOmIrRLuPmT\n"
+"6kgyygtJBAmx5nnQoeG8n08tl9QakWznKzkNJ0DIFKI=\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXQIBAAKBgQDCaOqJ0lsSAEBcnNB6X7BvVcEcol+evi/nJsPe0uT1SbtW50Ch\n"
+"vYOHwK6aQR2C5x9VSs47cLynTL7tNt5d8oeryF3NpI8VTPLImDJCcvUZhS7p4bxn\n"
+"JO+Wm+D/e3TWfyjreuWtdL+Mfimw2gzwWuBEtmj51GzQ89eYm7fh11SB6QIDAQAB\n"
+"AoGAWaakMbZNxPlUtOCjyysBY/Y5vYira7rswD3CKak7aFn+CE9QIMYSN7IFUqEg\n"
+"iNMoQd7jR8nvVX8wtJeO5+gF48W13C3n8FZSrW7c5N3bmfMIgo0xa/TGfeXHP98o\n"
+"7vhH0I58j3ZZt0Q+3wTm7t7WPE/nJzgrCk30TqmoaEmstTkCQQDtV6YZ6juEK2Lp\n"
+"LGUiqohcS/WJxvFrF5+LNpk86Xdgomf6FphZlkq42KYkvl7qibKDcfDqLKTbHHle\n"
+"vQQeCgZ7AkEA0bFHi7F8o4iHtKleBvt4QCj1neA0q3CRDypCI5EqFSrNpxY4Krhh\n"
+"WYSVX+xT00QYaCpKKWfYQztCw7Anylv96wJACl86Mwe5ch0zRV1bThiFvQLUyCCZ\n"
+"jESMBFlueOr6/I4cXSF/puqaeVl+aTyoiTdbRcNE8/bffXPRGgLIm0d04QJBAJSY\n"
+"lmTN789Lby99Xh6AkaSV4ghw26Ip8QHYJmph8npxjK69Niw/4Oy44cnKBVUPSmR2\n"
+"o3tYFY7/Lb7S1D+4lOUCQQDbMQUGVsZT+ZjuOG1bAjIuXoAOfOd3mgH5VgQHjSgJ\n"
+"ourZtlJ4OUpNrq9IfWqPkM+zSE8+0Dk8/9MS5ngBA/SJ\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIICXQIBAAKBgQDNbHjwg+7tVNr9erMLowXRnIcttp4pUJbr3B7Jo/u+kD/Yo3F3\n"
+"4rIKhHpJl1uEHP1QmvAD+4ApFFI2hNG54xYI8dGflxL5HOs5xxyOPpkrwzQ8Qvnv\n"
+"LPg7Gf6PAW9zF4McG4wK0TkrV28G6NhqcPs5VFY6UyvfZ0fEdWAeoWTIfQIDAQAB\n"
+"AoGBAKOmkMp7MLLd8QAS6eSRYSdWHdLrMyES1MjduaFGBF4SKOr7en/Zl6ENXSaX\n"
+"cA7V0XCPnjpt9/HCAKTyNupx4LCeFWiqdu8VGXhlzX8bdb896OSR2brKbxgRY5tF\n"
+"36uL8akrZdrYgocykQCxmRARMB7/rHwDusiamjL6RUZ3+c45AkEA6UPTVmKZQRMr\n"
+"A7Qgg5nXrXo9117Lpqf3FdZ1wdni9V59Ptf5xrx9oGZNZzctJPXSAH4M4cumSJrV\n"
+"sZ1V8qE7AwJBAOFx+5luLrVKrdlG7MyOhTAdhKYUvKIvL4wvVSY6y+L2nNEx/cTx\n"
+"KYbxGC+H1RJbkCS09rYir3VfDRWQ3W1c1n8CQH+X4hn2hO3blkPIW6CgniD+JKWR\n"
+"7MOUTMtdK7yFemfM76VYbgAPSohabSxwOfllnSE30cQQqTw9tXYaIdE98BECQG+M\n"
+"QWxSS0QillB6unIgVqBPCrJOcmNhK4qWZPBMiVNcqI0Nyj2nAeAl7MyfzfqOWY0A\n"
+"CU5nbR+LD2NLUXRqSisCQQCN3IGv1WOWInmA5xhU6vCFDX5u48Dcji7VLJO/Nv/i\n"
+"b/zHKAgjHk5Js7bi5ZWEGaUgA4Jt6cKmGdERheqTMKxx\n"
+"-----END RSA PRIVATE KEY-----\n"
+};
+
+static const char *PREGEN_KEYS_2048[] = {
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIIEpAIBAAKCAQEAoksI1qIuIaFCqT4QbgDvOQCmr9Z9F0E7ku+U5Ep/5dWNANqB\n"
+"bSzAOq0+cxiisfF+H4desoqiWDUwlOwXH74qD3ZsbChhvFUD78cQBWQkF+whLVHb\n"
+"296QmF0LZqosqz9HMS9CdoMUc1brZb78Hb25QIOOjrg25KYHLZHaqcet1wfhHow6\n"
+"Uehc6QTuWgOWFhJnfiXzYgen2o8lnLixxZozhk7Lm7Aix9ur2ckXdQ2Wgny4xw70\n"
+"JW84Hapnd8oFUD98XXrExk4VFuIcA8qo7r7y18II6wx4Cw1suKru6bhW65cM/y51\n"
+"KC4lB7VkvuoJCelRFdM1PfKZLv2tJP63oAqJrQIDAQABAoIBAQCWc38PEqw3avqU\n"
+"UMAEaoNa0bq1Gd8/Nq8WqVnbRSFKHO2pk+cWIb1W6BITuwvgcGKesezdEV4s7apK\n"
+"9I7/U1hEm2Ep50mrwRh0KZM1nD9Fmharn851Bt//D4qpMytT2caS1yADI8NKpZJ1\n"
+"8VZh7+cT4qG+txHUaAIRgbw3VrBWvTIMu6SOSOZm+e3eOr5UU3du1KvjdJHJ2c2k\n"
+"TceHvUdKxV7OYt+BBSN1oBOhs3ajUSRge1v3twRDg3cmbwG0DeXvwHNhGUTcF8IH\n"
+"JO1RF5njbkFvyqdAi3ltjU41zYd4OMuPtrwzFOtxUjKT62Soz109HUXXE2CGKFPZ\n"
+"PVi5/BIhAoGBANN1xqS5BgHszIB0nXbw5ImYpTRmyhO0KsTblBT9+8Q/B7BCK7bM\n"
+"zl+dOPeyvEadSwE7RSMMt6CAlTakWIf3Quw/VZajvXy9C9/LHf52pEKXjxMFMPKE\n"
+"aGLHpQnwMtDi8/H8AEAXxI3hpxB2KVR7sAYHWihSGjRJ6oPGvEmKEkb5AoGBAMR6\n"
+"G2PKz0xk1vFrjfjSY+y13gH/t7xHaXUggjggUSGKaknQh2BDUllXjadeI0fi1eLW\n"
+"r98ZImZZgntAgjaIZ4bAlooTDk4gRHaz9jI+z8lsRwOKnWdiigM7txiXZTMVwMqj\n"
+"o5mMNGMA+A+ACkTViRHmkDI7S/9FqAvnbOqVwgFVAoGBALUcY6WDvwx5B3Jh7tgH\n"
+"XIYpEh3+h8c2gYcX1g3gtvkPTwN8uToY0gz8eOVV1YHZiHsmi4GIi+HRH3usaRMT\n"
+"COOVHzYlSc8Dj57+tdLTRL6wVl9hC9o647ju64DGlI9qQquYPZKniLZIdbFYsu9j\n"
+"/JA9Tc/I+h6czFpPJccKlbrpAoGAAPWXrKUQ3g6f/g3IY66jTkSVEO1uuDyhBzFh\n"
+"cWS3ALLsUe/yuUWa4VTMHEUZZwB0iucBdNVqlZVaTb/C4wFHgCDwmzv8leUScIHw\n"
+"cc5ctV8R+bJzkk2o3tsrybLzi4xPpK2n3tgQaWtXyruVUUC5qpy1l4kylcyBRY2b\n"
+"uomAqQECgYAiCNWtuWIDlRBcvtIB+kHguzcoFT3vTCCNhalTEn0zi/tbi+voQgVJ\n"
+"SDJNptZv+6vRwQ/HfcQtljKIPO6hUZPYaFWRNhgbh7Ay85lRXYXQOottE8ayReBk\n"
+"zZb0fl853Qah4DPsaOugAvhjjKeBmKg6bFWO1z6hj18I3UpDf2YnVQ==\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIIEpQIBAAKCAQEAssO0r37mSJNAkc/ISwXBsu9JjyLeWlsHPAhylQGkSAdp2rjz\n"
+"E6AT0Eh3wrocNO31I4pvHReAuh1QedGY6T1cQwO/WAAhQtRCBQDK12qWRgfbC11y\n"
+"Xu7zNYPd1Z7YIRy+FxhbL5f+lv3rEUv0HUG5c3CWhLtbANKg+jOieIDzA4Yp1s55\n"
+"ynodQBUkTZrwQiT0P8yDSjiasf+clgJRfA1k2XK12KSAMRgyDuPTE4OtBxBvUM3L\n"
+"Zvxs81PsmcOuAG4DLaFTg2a/QkCjt2VC1SYYuh/LVxpL41FFh3eMoK5g5deHkgRe\n"
+"tlywKjAHIDJu/qgNzNgNW7ymwn2CfBvry9h0/wIDAQABAoIBAEMZ4wDdCWPEokAZ\n"
+"Vn2Ss5qO53WrCPuxn42RPjFgZGIFJl7LfbKoK8fK6+lUIrJbf+DPXdX1tIQn7MVN\n"
+"P7CNL8yX44MMyW9kbUOjgIBLqgyvdjFV6lBoMTKtRN+iuE31lATnR5Md4pqaxVnA\n"
+"wOkaepoycM1x5j7w0SwZparF/HIdkYv0y/MysqT9ByupPA4Fqp/iRSrosHXahNtI\n"
+"KZYj1TyERYtuDXq91P4dr/pWq3FmDNI8O3upblkL0YouvG/ZlFLdiNy77XbAyWcX\n"
+"ps3YDddM+vECnXO3+sa3ZxgBYvXJdWrrIzM5A+jCkDRZQGsFAzK5I5/S7C2ljt6i\n"
+"SmzqvMECgYEA16bGy2XTi6KBPb8aev/OBgK9XuGLwUqK1m15mS9Y2qPHmuc22qaZ\n"
+"hw6zginPFrxAEtQWKanhZy4aVqlLkDPLwRnyeuMo1EZAc5B1gZ5ViSAKxBq99hA9\n"
+"eqyakdb+IUQsEnRDxSc2gqUQ0EagksUyw5wGG5Q/CVEALmS/r1SU3KUCgYEA1DYf\n"
+"6JYdzuRtule3vYeWXKf8sOJpdplgWV7tvLrKkQhdE564uwMCYB23HvYfwWqEdDYG\n"
+"fsYg/ur/stk9MDZ3wZKffTEM8V3sX1t1JXnC3ogSAgMGhLZ3ILOLqkoO4BEZJnsS\n"
+"dMdiNijlAtQkqs/BO/UVUAKysCtKP3v/+1775dMCgYEAvLjGFjApfnSbV/cK7IM6\n"
+"wEXbhdIqZOCgOeEaXjVyM/zKbMRVW+oaR3hVHd8KzSG3jQKv1oxFpu9Qu3ByoWLC\n"
+"uF3Ft0debs6ADuJoAyQWROeWpGGmxlUWCGpO5rxYL7KiQxAeUsXrTU+5NBvq4CbV\n"
+"MxwyuCX3OGb7mp4upfiGQcUCgYEAuhVsDYv1P4LXJVvd5viKRV2ZG5KuYC1Ga5fu\n"
+"aFxzXJI07At2eaa94oKsHR494mEBHNZzA5/BN0fiSHZuTWS1xqxH5oOokc6Gg2ez\n"
+"ZdVLp88x20nD4YQPGkHW6tBeEuVrZG7vVC+yU0Ow7bYRISdkjqrusWZsQkbzqI+X\n"
+"fFliEbkCgYEAu8x+47M1ordbI7NmbBGyiyP0r7nMRCZ+KEvGeCNYracWmsnCNnfV\n"
+"zR2UzmwtSainw3Ho8Jv/rWDC8RIDauyBRYEi2VqOnUzT2ca0iymQyLeBCudAQuio\n"
+"drOu4JU8RzZ3Ad6V3DNFnaqmX/7GA9Pa2GI8NJMyb8p1GAGv7Gi8nxc=\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIIEowIBAAKCAQEAt01S8JuEwWy/Hzb90yO2O7oGWq3GfvfDpFOF4OQnwG3kQ/BP\n"
+"4MoPDCYHdqb3iI9aD3vykZA6Q8zpdfGwjm4+bHrgRdiSmZWv8NvRwuQ5Ji9xbiGn\n"
+"hA1XwqH9hvgFTiy6tRvirWSJ7kzH3Q/bEGpCbHUQkwMog4v6yCNKNrjlwjN++eCi\n"
+"gFK/0RMOJMLOs8BD3zY+lKjd/pd8LBRujkMyUF5SryeRueAFjD2sq4OXq8DPABGt\n"
+"zdR6vbTcsi4JwP1Q6y4x0/LIWEprzzewNU63I5E2zj0WnoRGAIM4aF+VuqcHjWUx\n"
+"VWnyLZldSen6lScZ4xj4seitiDbSFvtFkDF6VwIDAQABAoIBAGTP9im2ntDyyjqU\n"
+"uA0DuxomOZBtupniEouyFBOX5/UBe2WSKZxsBNKdp8UuFz3X+aRCeyprtF/NtyjT\n"
+"AFOVdmebPPWtIxOtK9LAUyFo+7VwqmXzxHnwDLBS/2jXx7MzDozFBWpvvRx+xf1i\n"
+"1wy0JEwaJj90oTeYKRkhr5NhJZwkX8zCNYaemBd3kHB3aGWGJasI1Y81UezeRKCn\n"
+"hSbn2CrWalI7pyJ4lsavM11nIq1Eu2ZthJiNCMghbYrHoBHd+iVWiCYchP2rNEWV\n"
+"sdHtaVHtQ9zdZ43bao3OzPu7lAjd6UAbxsuhUe+a2YdDz/+Up+6+BvQf1FCfYIjW\n"
+"KFUdCoECgYEA4t5O+u0V9gkMUhKsevYb0zgc7O/mo8ivN+V++EpAtL0mhiwxeO8p\n"
+"oef0szLyhdULQeLN9pJQDCeAbkGdwIe3L+AKU8o8BFGEWLFysZjMg9In/UTrp5MN\n"
+"mMDy2SRKKu5BqsvdYH302xpZfHq1T2cMNDWE8lrZffduH06Cgq/XEtECgYEAztbj\n"
+"bhFneADnrvk609VnOQvoQEjySeCQKFQFRRI6k/FguqMisL2IRXnMaWammosdeCAg\n"
+"m7eZchnszHIst9cwZUKXUFqmAqeDuWSNdTI7uKZH6nT/A6IDlgdjaHsqhvpK0Ac9\n"
+"ngycdHONitOZh0ZG74pdWjf828Dwzf+CuYjl9KcCgYEAmIvI6ZqvkJ8m5Kzfw1Jn\n"
+"BVCOypbJK8oOX3R2Orea6KzjEYb3wQx3nwFcHX6danYFOskpmqlpH7MT/Y8rZsEa\n"
+"4RsxdoPedTzm08iFiXtn0R9nejp0hlov402iPXXUVSedih3IflBTa1w9XaEY9wog\n"
+"P57ZBSknYzcTmgNtaDiaUnECgYA5sWauhNw/dMEq5QmrnJK2LsQRakdqo+CR3x25\n"
+"LmR4b5Nze51pfvRLrLV/kMpXwQXvQ8bUqFl8og6S2CXxAWzWUcSy/RXhF6h+RbXP\n"
+"Qru1vWvB0fBvqvklF9p6giBSle3YKKzfMNVTBggs+OiR+uA+YHG5gHRfN2nzi5mC\n"
+"9tRtcQKBgBnDSi4lRCjRe9pPnyAYaa4iyBUGhjPysScSLY9orel89+qmTBQ/Py6J\n"
+"0+sefL4ZJaOsuaR2mSSPP/lbSkF9DMFs4tHbBqY+WkVNYLshAkauHwqv26HTVCSd\n"
+"QKzeb7uZw9lNaRIzDvy/3wfCLvXfdDozPFrOUgkyaBN5pJSA/4sv\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIIEogIBAAKCAQEA9qtiDoJWqU/eSlpj381eG6UcDzfMguFh/q4e4s7QVdRYj5J0\n"
+"Msv0PCkti8JHuvQUyncRpOPccBkhNbVjNbjIgw1pHaIZNdVotUDhP0kseRyJ6z3M\n"
+"qbZ5qKn+0mHjVjPNItVDDe6tebYMT1BZpVyRrCOqY2v5z1ecLC+ReygmHgDpzg+L\n"
+"0rWfIxGT10IPZ8pAlcdEn6xt5aEhi7mPCX/xwqfQChPIJz6zVLEC8UaPtvDBohPR\n"
+"6NQTBTeZZAAtzrQ7+oNxfz1v6Fz6RwMei7Q+qOBnMiwpQmbcDBKABM2RnXSpD0LA\n"
+"1GR7/+CiV1HQoShWVvEwrSIlM6jVAJo6iqF6WQIDAQABAoIBAHqwcdxPnfUm4aTP\n"
+"4r9NcZKEhDlZgqJSoiA/0OL1BRC7xrTanmspoLhPrvTF1FG715+Aq8j9AQbMqQUC\n"
+"zG7LEwiEIhV4K9vn4uXMeHy206UFud/E5EhBl695pmJUB/Q3XcAGnQyP+77++o50\n"
+"o7IpIdeiAbzj1uP3aplbq5u7M4JV7fUZWA/368G4HolqFTxcAfBJ05GXlp97BBwY\n"
+"AnY3/pNrKMz0NiPf3nsJHYWK18up0JCLPL3tomc94wuNZ66spIazHIL9aaKY0q3V\n"
+"LkBrelndfYM1m4xRTnSOy6STu0qKTPOpX0C8XBLYs6uiXjRsChqSYwndCCeASaH3\n"
+"LGNIcbUCgYEA/m4qvt8tdT4wEvnE+QUxEELmBtT4UFa3NnQISrzNlhNeI0Zd2xlp\n"
+"SG0/pcw83mG2uX+V5xSaWL5LYfLBkvy83Y0yIWgYbbIkyyCOUZnTpwaDGU/FjWip\n"
+"3TfXf5qpAgiez94sV+MsFpKfG05yxJh5u+3sIyGTVUAxp0HPx4LVgbMCgYEA+DD1\n"
+"fu6ttpuV1UMrsFdjuk6gBvSbyJ9OilY2jT+yE7hSRc/yP3O9ikuR74tNlVrWTnO2\n"
+"0kcYbyLJXE2cGUC2q5e4r8TDGiozNfQ7/OC2M3XaJ+xJk4zMf/8PuDDpWr+18ZXA\n"
+"Pf+ibXWTFvZ6ZeUmpbrrfCrXdvmIZnwVuOI0FcMCgYAZn26emksxq3mb75tumJ9A\n"
+"S/xuY7Q+Iv2Adl7/Z9QscPbiBowdLIn1yUrHn7Hhk2WbeMXX57NDjKZ6zr+/1cQP\n"
+"a9DInHsZUP9zlWu/vAYcpAM/4VC71PaGWMFTEHhExCl6NZ2xnCcsfseXMGdOdSyN\n"
+"SICnaRI1W6mkdnQ+W2a1EQKBgGEKA3KVr6XuPy8bDEHuaTe29irCCQbwAq1j+ABS\n"
+"HzZGoyRYocbdYgZoda7LMJJs6c3SwHCHC66oU0KbtaTKAKImuDdBH2djiJJX4/yD\n"
+"f7mvIpTpdfsS2gJRn7vMo/CvdFv4ySl0gfV6OwCHbmPYrLuv0dLCjWwfNI2dhoC7\n"
+"MNIxAoGAIPSIG4BrShzbeX4c2L18iwIg+NlOcUbtl0Ccr1t6uLGI+ge/6I6T/5XH\n"
+"DPKqYIf0IRYV8suxpfQNKiz/C0NPffA1d1M2hvuAg2v09o2cSwvdcQwdmakKZ5bl\n"
+"sdCuYKdCIwomEUOz/4XgQrJl4XDUqxftJT6/egAjWvcIYvfNCsY=\n"
+"-----END RSA PRIVATE KEY-----\n",
+
+"-----BEGIN RSA PRIVATE KEY-----\n"
+"MIIEowIBAAKCAQEA1yHZMsgRLckL+v6rgpGq9qmxVBNDxeuul1V/QlFyOlcAk5n/\n"
+"uduTalSqGQhc4NEePMxq6nFui4ucpkZOozmcEnhV0N9jld9IB9rLGt4erdg7RKl9\n"
+"+gQ+zTn69j69U36E2I47H4dM69uxeSOyWP2Odxpw+biisa3o8mMz1zCmuj4GMDtG\n"
+"DlnSpthFzgQR6N1pbvxLXrWg5F16GqFiJOD7kXDfy4/l6kB/mDs1T/3r8kav6DqR\n"
+"c/t3aQZxgWGIpI7hc9Qgvp7coZRMey5dNOZEna3tqS8dn2tZlhkpYV5uyFUjmxjG\n"
+"TERSULQ7hvUqW+eshGGsnxFtL7ANnTSc4xECowIDAQABAoIBAFhJJMhpQFuIySjd\n"
+"AGeZ/g4x/3rgWQzNNp4WUR5XLEhy0eLA7ShJywp06kVRoEQGraEHxsyldldAGS5H\n"
+"ZhgoGTufNKB+PHER646FpJpHE1IGjfQUloVW3qr8I1iQ0MOGBWCVpf+/V7rnMsLi\n"
+"+lr421FXgYuJ0QKXuyRVv72M0q9U6i+ml3aVAhgW/19oFg+dW7YccX+9iVyD05Q5\n"
+"KR64tX8xd4wrAqfAgYA3erbbE6GTyHYD5K54kIgfRr/+pIU4qc1L7XOCblnqc/rI\n"
+"BilFysEC634r2MNe66uQvNui4oQTfBcFFlXg0zAmp7d5QE0ApOL6HpCsmbImm2uJ\n"
+"sdFNYyECgYEA716kfEv7HfnF0P3pAP2AOuEsW6t8q0UtWvnHrwRQXQw8Yv90g7kD\n"
+"pUV3/BjD9VQgsQZosbdSn5wbT4j7dypRdrzYk+8m/hBk4Q8M/tWoRGVOn46NudvK\n"
+"/KX0A4ODLuulj8yAZVc7CM5Cdy4GCGJBVO+oVvBUAnHxfZziOyqBw9MCgYEA5hQg\n"
+"HEORzdxvbbfAx1ggvH1Eg1lqRhmpI43PpRkaoqb8jLwXb2CyBeuv3RBft/X2Tr6F\n"
+"mHpe0U1kN/5YEjii/Q/jUX8azIHaUNNSAjrriEeMQZOqFxmhCdiyeXuqg2fbFbhe\n"
+"K3Q6/fsB1xj9OOSwyPMqm/M5U0LsoGjmg8TFE/ECgYAlImKUIdlwOgp1NJ7MF4eo\n"
+"Gryd8AmkLFQv8+YFgb7R4I8RsJ2rva0SG6fUhScJTSbRL7RYNZ9swXP/L7oLL5Z5\n"
+"vCxBLu22pmZv/7y9X/n9ulWrLRtRhQaFkV08mk9knQwPNeOJVTIEWLM49/vZmxyV\n"
+"h6Ru8FOoGXMkUI1MLnj5HwKBgGJLkNhiacVYeuaWDa9c0EeXARFYvxWJ2wAMkvzG\n"
+"9+ErlFQP+7ciyYvMAItidnJii8NilDLrfNzQwpNFf5zxQ3j4M7bapblfdMT5M10u\n"
+"jPfhEWPm0VEjKvDI+p76HYQcd7YU2W6ZLqbZeRTLYUvQMFL5yGduBzyyJ+P0TR9Y\n"
+"jpYRAoGBAM7vYGTprw4w2tTZPFICXVk1bQ0LO06oNRtwkiQTUT6UqPjWMFyvHnmN\n"
+"11SVVBmRZ0RAk6e5eZLFX8WelJ4J4nSOGRcJheCtoEFlO7D1ewAUSbqWJ0pBqp2T\n"
+"gV4oCS8LYe8zReVoYZJjuLwoHvxZzs/hUjc3SI2HRW2W/HQRPC25\n"
+"-----END RSA PRIVATE KEY-----\n"
+};
+
+#define N_PREGEN_KEYS_1024 ARRAY_LENGTH(PREGEN_KEYS_1024)
+static crypto_pk_t *pregen_keys_1024[N_PREGEN_KEYS_1024];
+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
+
+/** 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.
+ * Our only guarantee is that we won't reuse a key till this function has been
+ * called several times. The order in which keys are returned is slightly
+ * randomized, so that tests that depend on a particular order will not be
+ * reliable. */
+static crypto_pk_t *
+pk_generate_internal(int bits)
+{
+ tor_assert(bits == 2048 || bits == 1024);
+
+#ifdef USE_PREGENERATED_RSA_KEYS
+ int *idxp;
+ int n_pregen;
+ crypto_pk_t **pregen_array;
+ if (bits == 2048) {
+ idxp = &next_key_idx_2048;
+ n_pregen = N_PREGEN_KEYS_2048;
+ pregen_array = pregen_keys_2048;
+ } else {
+ idxp = &next_key_idx_1024;
+ n_pregen = N_PREGEN_KEYS_1024;
+ pregen_array = pregen_keys_1024;
+ }
+ /* Either skip 1 or 2 keys. */
+ *idxp += crypto_rand_int_range(1,3);
+ *idxp %= n_pregen;
+ return crypto_pk_dup_key(pregen_array[*idxp]);
+#else
+ 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
+}
+
+crypto_pk_t *
+pk_generate(int idx)
+{
+ (void) idx;
+ return pk_generate_internal(1024);
+}
+
+#ifdef USE_PREGENERATED_RSA_KEYS
+static int
+crypto_pk_generate_key_with_bits__get_cached(crypto_pk_t *env, int bits)
+{
+ if (bits == 1024 || bits == 2048) {
+ crypto_pk_t *newkey = pk_generate_internal(bits);
+ crypto_pk_assign_(env, newkey);
+ crypto_pk_free(newkey);
+ } else {
+ return crypto_pk_generate_key_with_bits__real(env, bits);
+ }
+ return 0;
+}
+#endif
+
+/** Free all storage used for the cached key optimization. */
+void
+free_pregenerated_keys(void)
+{
+#ifdef USE_PREGENERATED_RSA_KEYS
+ unsigned idx;
+ for (idx = 0; idx < N_PREGEN_KEYS_1024; ++idx) {
+ if (pregen_keys_1024[idx]) {
+ crypto_pk_free(pregen_keys_1024[idx]);
+ pregen_keys_1024[idx] = NULL;
+ }
+ }
+ for (idx = 0; idx < N_PREGEN_KEYS_2048; ++idx) {
+ if (pregen_keys_2048[idx]) {
+ crypto_pk_free(pregen_keys_2048[idx]);
+ pregen_keys_2048[idx] = NULL;
+ }
+ }
+#endif
+}
+
+void
+init_pregenerated_keys(void)
+{
+#ifdef USE_PREGENERATED_RSA_KEYS
+ const char *s;
+ crypto_pk_t *pk;
+ unsigned i;
+ for (i = 0; i < N_PREGEN_KEYS_1024; ++i) {
+ pk = pregen_keys_1024[i] = crypto_pk_new();
+ s = PREGEN_KEYS_1024[i];
+ int r = crypto_pk_read_private_key_from_string(pk, s, strlen(s));
+ tor_assert(r == 0);
+ }
+ for (i = 0; i < N_PREGEN_KEYS_2048; ++i) {
+ pk = pregen_keys_2048[i] = crypto_pk_new();
+ s = PREGEN_KEYS_2048[i];
+ int r = crypto_pk_read_private_key_from_string(pk, s, strlen(s));
+ tor_assert(r == 0);
+ }
+
+ MOCK(crypto_pk_generate_key_with_bits,
+ crypto_pk_generate_key_with_bits__get_cached);
+#endif
+}
+
diff --git a/src/trunnel/ed25519_cert.c b/src/trunnel/ed25519_cert.c
index 24988d510b..ee02fda646 100644
--- a/src/trunnel/ed25519_cert.c
+++ b/src/trunnel/ed25519_cert.c
@@ -1,4 +1,4 @@
-/* ed25519_cert.c -- generated by Trunnel v1.4.6.
+/* ed25519_cert.c -- generated by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -28,6 +28,281 @@ int edcert_deadcode_dummy__ = 0;
} \
} while (0)
+create2_cell_body_t *
+create2_cell_body_new(void)
+{
+ create2_cell_body_t *val = trunnel_calloc(1, sizeof(create2_cell_body_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+create2_cell_body_clear(create2_cell_body_t *obj)
+{
+ (void) obj;
+ TRUNNEL_DYNARRAY_WIPE(&obj->handshake_data);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->handshake_data);
+}
+
+void
+create2_cell_body_free(create2_cell_body_t *obj)
+{
+ if (obj == NULL)
+ return;
+ create2_cell_body_clear(obj);
+ trunnel_memwipe(obj, sizeof(create2_cell_body_t));
+ trunnel_free_(obj);
+}
+
+uint16_t
+create2_cell_body_get_handshake_type(const create2_cell_body_t *inp)
+{
+ return inp->handshake_type;
+}
+int
+create2_cell_body_set_handshake_type(create2_cell_body_t *inp, uint16_t val)
+{
+ inp->handshake_type = val;
+ return 0;
+}
+uint16_t
+create2_cell_body_get_handshake_len(const create2_cell_body_t *inp)
+{
+ return inp->handshake_len;
+}
+int
+create2_cell_body_set_handshake_len(create2_cell_body_t *inp, uint16_t val)
+{
+ inp->handshake_len = val;
+ return 0;
+}
+size_t
+create2_cell_body_getlen_handshake_data(const create2_cell_body_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->handshake_data);
+}
+
+uint8_t
+create2_cell_body_get_handshake_data(create2_cell_body_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->handshake_data, idx);
+}
+
+uint8_t
+create2_cell_body_getconst_handshake_data(const create2_cell_body_t *inp, size_t idx)
+{
+ return create2_cell_body_get_handshake_data((create2_cell_body_t*)inp, idx);
+}
+int
+create2_cell_body_set_handshake_data(create2_cell_body_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->handshake_data, idx, elt);
+ return 0;
+}
+int
+create2_cell_body_add_handshake_data(create2_cell_body_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+ if (inp->handshake_data.n_ == UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->handshake_data, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+create2_cell_body_getarray_handshake_data(create2_cell_body_t *inp)
+{
+ return inp->handshake_data.elts_;
+}
+const uint8_t *
+create2_cell_body_getconstarray_handshake_data(const create2_cell_body_t *inp)
+{
+ return (const uint8_t *)create2_cell_body_getarray_handshake_data((create2_cell_body_t*)inp);
+}
+int
+create2_cell_body_setlen_handshake_data(create2_cell_body_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+ if (newlen > UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->handshake_data.allocated_,
+ &inp->handshake_data.n_, inp->handshake_data.elts_, newlen,
+ sizeof(inp->handshake_data.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->handshake_data.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+create2_cell_body_check(const create2_cell_body_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ if (TRUNNEL_DYNARRAY_LEN(&obj->handshake_data) != obj->handshake_len)
+ return "Length mismatch for handshake_data";
+ return NULL;
+}
+
+ssize_t
+create2_cell_body_encoded_len(const create2_cell_body_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != create2_cell_body_check(obj))
+ return -1;
+
+
+ /* Length of u16 handshake_type */
+ result += 2;
+
+ /* Length of u16 handshake_len */
+ result += 2;
+
+ /* Length of u8 handshake_data[handshake_len] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->handshake_data);
+ return result;
+}
+int
+create2_cell_body_clear_errors(create2_cell_body_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+create2_cell_body_encode(uint8_t *output, const size_t avail, const create2_cell_body_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 = create2_cell_body_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = create2_cell_body_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u16 handshake_type */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->handshake_type));
+ written += 2; ptr += 2;
+
+ /* Encode u16 handshake_len */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->handshake_len));
+ written += 2; ptr += 2;
+
+ /* Encode u8 handshake_data[handshake_len] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->handshake_data);
+ trunnel_assert(obj->handshake_len == elt_len);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->handshake_data.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 create2_cell_body_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+create2_cell_body_parse_into(create2_cell_body_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 u16 handshake_type */
+ CHECK_REMAINING(2, truncated);
+ obj->handshake_type = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u16 handshake_len */
+ CHECK_REMAINING(2, truncated);
+ obj->handshake_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u8 handshake_data[handshake_len] */
+ CHECK_REMAINING(obj->handshake_len, truncated);
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->handshake_data, obj->handshake_len, {});
+ obj->handshake_data.n_ = obj->handshake_len;
+ if (obj->handshake_len)
+ memcpy(obj->handshake_data.elts_, ptr, obj->handshake_len);
+ ptr += obj->handshake_len; remaining -= obj->handshake_len;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ trunnel_alloc_failed:
+ return -1;
+}
+
+ssize_t
+create2_cell_body_parse(create2_cell_body_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = create2_cell_body_new();
+ if (NULL == *output)
+ return -1;
+ result = create2_cell_body_parse_into(*output, input, len_in);
+ if (result < 0) {
+ create2_cell_body_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
ed25519_cert_extension_t *
ed25519_cert_extension_new(void)
{
@@ -58,7 +333,7 @@ ed25519_cert_extension_free(ed25519_cert_extension_t *obj)
}
uint16_t
-ed25519_cert_extension_get_ext_length(ed25519_cert_extension_t *inp)
+ed25519_cert_extension_get_ext_length(const ed25519_cert_extension_t *inp)
{
return inp->ext_length;
}
@@ -69,7 +344,7 @@ ed25519_cert_extension_set_ext_length(ed25519_cert_extension_t *inp, uint16_t va
return 0;
}
uint8_t
-ed25519_cert_extension_get_ext_type(ed25519_cert_extension_t *inp)
+ed25519_cert_extension_get_ext_type(const ed25519_cert_extension_t *inp)
{
return inp->ext_type;
}
@@ -80,7 +355,7 @@ ed25519_cert_extension_set_ext_type(ed25519_cert_extension_t *inp, uint8_t val)
return 0;
}
uint8_t
-ed25519_cert_extension_get_ext_flags(ed25519_cert_extension_t *inp)
+ed25519_cert_extension_get_ext_flags(const ed25519_cert_extension_t *inp)
{
return inp->ext_flags;
}
@@ -97,12 +372,17 @@ ed25519_cert_extension_getlen_un_signing_key(const ed25519_cert_extension_t *inp
}
uint8_t
-ed25519_cert_extension_get_un_signing_key(const ed25519_cert_extension_t *inp, size_t idx)
+ed25519_cert_extension_get_un_signing_key(ed25519_cert_extension_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->un_signing_key[idx];
}
+uint8_t
+ed25519_cert_extension_getconst_un_signing_key(const ed25519_cert_extension_t *inp, size_t idx)
+{
+ return ed25519_cert_extension_get_un_signing_key((ed25519_cert_extension_t*)inp, idx);
+}
int
ed25519_cert_extension_set_un_signing_key(ed25519_cert_extension_t *inp, size_t idx, uint8_t elt)
{
@@ -116,6 +396,11 @@ ed25519_cert_extension_getarray_un_signing_key(ed25519_cert_extension_t *inp)
{
return inp->un_signing_key;
}
+const uint8_t *
+ed25519_cert_extension_getconstarray_un_signing_key(const ed25519_cert_extension_t *inp)
+{
+ return (const uint8_t *)ed25519_cert_extension_getarray_un_signing_key((ed25519_cert_extension_t*)inp);
+}
size_t
ed25519_cert_extension_getlen_un_unparsed(const ed25519_cert_extension_t *inp)
{
@@ -128,6 +413,11 @@ ed25519_cert_extension_get_un_unparsed(ed25519_cert_extension_t *inp, size_t idx
return TRUNNEL_DYNARRAY_GET(&inp->un_unparsed, idx);
}
+uint8_t
+ed25519_cert_extension_getconst_un_unparsed(const ed25519_cert_extension_t *inp, size_t idx)
+{
+ return ed25519_cert_extension_get_un_unparsed((ed25519_cert_extension_t*)inp, idx);
+}
int
ed25519_cert_extension_set_un_unparsed(ed25519_cert_extension_t *inp, size_t idx, uint8_t elt)
{
@@ -149,6 +439,11 @@ ed25519_cert_extension_getarray_un_unparsed(ed25519_cert_extension_t *inp)
{
return inp->un_unparsed.elts_;
}
+const uint8_t *
+ed25519_cert_extension_getconstarray_un_unparsed(const ed25519_cert_extension_t *inp)
+{
+ return (const uint8_t *)ed25519_cert_extension_getarray_un_unparsed((ed25519_cert_extension_t*)inp);
+}
int
ed25519_cert_extension_setlen_un_unparsed(ed25519_cert_extension_t *inp, size_t newlen)
{
@@ -410,6 +705,878 @@ ed25519_cert_extension_parse(ed25519_cert_extension_t **output, const uint8_t *i
}
return result;
}
+extend1_cell_body_t *
+extend1_cell_body_new(void)
+{
+ extend1_cell_body_t *val = trunnel_calloc(1, sizeof(extend1_cell_body_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+extend1_cell_body_clear(extend1_cell_body_t *obj)
+{
+ (void) obj;
+}
+
+void
+extend1_cell_body_free(extend1_cell_body_t *obj)
+{
+ if (obj == NULL)
+ return;
+ extend1_cell_body_clear(obj);
+ trunnel_memwipe(obj, sizeof(extend1_cell_body_t));
+ trunnel_free_(obj);
+}
+
+uint32_t
+extend1_cell_body_get_ipv4addr(const extend1_cell_body_t *inp)
+{
+ return inp->ipv4addr;
+}
+int
+extend1_cell_body_set_ipv4addr(extend1_cell_body_t *inp, uint32_t val)
+{
+ inp->ipv4addr = val;
+ return 0;
+}
+uint16_t
+extend1_cell_body_get_port(const extend1_cell_body_t *inp)
+{
+ return inp->port;
+}
+int
+extend1_cell_body_set_port(extend1_cell_body_t *inp, uint16_t val)
+{
+ inp->port = val;
+ return 0;
+}
+size_t
+extend1_cell_body_getlen_onionskin(const extend1_cell_body_t *inp)
+{
+ (void)inp; return 186;
+}
+
+uint8_t
+extend1_cell_body_get_onionskin(extend1_cell_body_t *inp, size_t idx)
+{
+ trunnel_assert(idx < 186);
+ return inp->onionskin[idx];
+}
+
+uint8_t
+extend1_cell_body_getconst_onionskin(const extend1_cell_body_t *inp, size_t idx)
+{
+ return extend1_cell_body_get_onionskin((extend1_cell_body_t*)inp, idx);
+}
+int
+extend1_cell_body_set_onionskin(extend1_cell_body_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < 186);
+ inp->onionskin[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+extend1_cell_body_getarray_onionskin(extend1_cell_body_t *inp)
+{
+ return inp->onionskin;
+}
+const uint8_t *
+extend1_cell_body_getconstarray_onionskin(const extend1_cell_body_t *inp)
+{
+ return (const uint8_t *)extend1_cell_body_getarray_onionskin((extend1_cell_body_t*)inp);
+}
+size_t
+extend1_cell_body_getlen_identity(const extend1_cell_body_t *inp)
+{
+ (void)inp; return 20;
+}
+
+uint8_t
+extend1_cell_body_get_identity(extend1_cell_body_t *inp, size_t idx)
+{
+ trunnel_assert(idx < 20);
+ return inp->identity[idx];
+}
+
+uint8_t
+extend1_cell_body_getconst_identity(const extend1_cell_body_t *inp, size_t idx)
+{
+ return extend1_cell_body_get_identity((extend1_cell_body_t*)inp, idx);
+}
+int
+extend1_cell_body_set_identity(extend1_cell_body_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < 20);
+ inp->identity[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+extend1_cell_body_getarray_identity(extend1_cell_body_t *inp)
+{
+ return inp->identity;
+}
+const uint8_t *
+extend1_cell_body_getconstarray_identity(const extend1_cell_body_t *inp)
+{
+ return (const uint8_t *)extend1_cell_body_getarray_identity((extend1_cell_body_t*)inp);
+}
+const char *
+extend1_cell_body_check(const extend1_cell_body_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
+extend1_cell_body_encoded_len(const extend1_cell_body_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != extend1_cell_body_check(obj))
+ return -1;
+
+
+ /* Length of u32 ipv4addr */
+ result += 4;
+
+ /* Length of u16 port */
+ result += 2;
+
+ /* Length of u8 onionskin[186] */
+ result += 186;
+
+ /* Length of u8 identity[20] */
+ result += 20;
+ return result;
+}
+int
+extend1_cell_body_clear_errors(extend1_cell_body_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+extend1_cell_body_encode(uint8_t *output, const size_t avail, const extend1_cell_body_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 = extend1_cell_body_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = extend1_cell_body_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u32 ipv4addr */
+ trunnel_assert(written <= avail);
+ if (avail - written < 4)
+ goto truncated;
+ trunnel_set_uint32(ptr, trunnel_htonl(obj->ipv4addr));
+ written += 4; ptr += 4;
+
+ /* Encode u16 port */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->port));
+ written += 2; ptr += 2;
+
+ /* Encode u8 onionskin[186] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 186)
+ goto truncated;
+ memcpy(ptr, obj->onionskin, 186);
+ written += 186; ptr += 186;
+
+ /* Encode u8 identity[20] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 20)
+ goto truncated;
+ memcpy(ptr, obj->identity, 20);
+ written += 20; ptr += 20;
+
+
+ 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 extend1_cell_body_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+extend1_cell_body_parse_into(extend1_cell_body_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 u32 ipv4addr */
+ CHECK_REMAINING(4, truncated);
+ obj->ipv4addr = trunnel_ntohl(trunnel_get_uint32(ptr));
+ remaining -= 4; ptr += 4;
+
+ /* Parse u16 port */
+ CHECK_REMAINING(2, truncated);
+ obj->port = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u8 onionskin[186] */
+ CHECK_REMAINING(186, truncated);
+ memcpy(obj->onionskin, ptr, 186);
+ remaining -= 186; ptr += 186;
+
+ /* Parse u8 identity[20] */
+ CHECK_REMAINING(20, truncated);
+ memcpy(obj->identity, ptr, 20);
+ remaining -= 20; ptr += 20;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+}
+
+ssize_t
+extend1_cell_body_parse(extend1_cell_body_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = extend1_cell_body_new();
+ if (NULL == *output)
+ return -1;
+ result = extend1_cell_body_parse_into(*output, input, len_in);
+ if (result < 0) {
+ extend1_cell_body_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
+link_specifier_t *
+link_specifier_new(void)
+{
+ link_specifier_t *val = trunnel_calloc(1, sizeof(link_specifier_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+link_specifier_clear(link_specifier_t *obj)
+{
+ (void) obj;
+ TRUNNEL_DYNARRAY_WIPE(&obj->un_unrecognized);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->un_unrecognized);
+}
+
+void
+link_specifier_free(link_specifier_t *obj)
+{
+ if (obj == NULL)
+ return;
+ link_specifier_clear(obj);
+ trunnel_memwipe(obj, sizeof(link_specifier_t));
+ trunnel_free_(obj);
+}
+
+uint8_t
+link_specifier_get_ls_type(const link_specifier_t *inp)
+{
+ return inp->ls_type;
+}
+int
+link_specifier_set_ls_type(link_specifier_t *inp, uint8_t val)
+{
+ inp->ls_type = val;
+ return 0;
+}
+uint8_t
+link_specifier_get_ls_len(const link_specifier_t *inp)
+{
+ return inp->ls_len;
+}
+int
+link_specifier_set_ls_len(link_specifier_t *inp, uint8_t val)
+{
+ inp->ls_len = val;
+ return 0;
+}
+uint32_t
+link_specifier_get_un_ipv4_addr(const link_specifier_t *inp)
+{
+ return inp->un_ipv4_addr;
+}
+int
+link_specifier_set_un_ipv4_addr(link_specifier_t *inp, uint32_t val)
+{
+ inp->un_ipv4_addr = val;
+ return 0;
+}
+uint16_t
+link_specifier_get_un_ipv4_port(const link_specifier_t *inp)
+{
+ return inp->un_ipv4_port;
+}
+int
+link_specifier_set_un_ipv4_port(link_specifier_t *inp, uint16_t val)
+{
+ inp->un_ipv4_port = val;
+ return 0;
+}
+size_t
+link_specifier_getlen_un_ipv6_addr(const link_specifier_t *inp)
+{
+ (void)inp; return 16;
+}
+
+uint8_t
+link_specifier_get_un_ipv6_addr(link_specifier_t *inp, size_t idx)
+{
+ trunnel_assert(idx < 16);
+ return inp->un_ipv6_addr[idx];
+}
+
+uint8_t
+link_specifier_getconst_un_ipv6_addr(const link_specifier_t *inp, size_t idx)
+{
+ return link_specifier_get_un_ipv6_addr((link_specifier_t*)inp, idx);
+}
+int
+link_specifier_set_un_ipv6_addr(link_specifier_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < 16);
+ inp->un_ipv6_addr[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+link_specifier_getarray_un_ipv6_addr(link_specifier_t *inp)
+{
+ return inp->un_ipv6_addr;
+}
+const uint8_t *
+link_specifier_getconstarray_un_ipv6_addr(const link_specifier_t *inp)
+{
+ return (const uint8_t *)link_specifier_getarray_un_ipv6_addr((link_specifier_t*)inp);
+}
+uint16_t
+link_specifier_get_un_ipv6_port(const link_specifier_t *inp)
+{
+ return inp->un_ipv6_port;
+}
+int
+link_specifier_set_un_ipv6_port(link_specifier_t *inp, uint16_t val)
+{
+ inp->un_ipv6_port = val;
+ return 0;
+}
+size_t
+link_specifier_getlen_un_legacy_id(const link_specifier_t *inp)
+{
+ (void)inp; return 20;
+}
+
+uint8_t
+link_specifier_get_un_legacy_id(link_specifier_t *inp, size_t idx)
+{
+ trunnel_assert(idx < 20);
+ return inp->un_legacy_id[idx];
+}
+
+uint8_t
+link_specifier_getconst_un_legacy_id(const link_specifier_t *inp, size_t idx)
+{
+ return link_specifier_get_un_legacy_id((link_specifier_t*)inp, idx);
+}
+int
+link_specifier_set_un_legacy_id(link_specifier_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < 20);
+ inp->un_legacy_id[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+link_specifier_getarray_un_legacy_id(link_specifier_t *inp)
+{
+ return inp->un_legacy_id;
+}
+const uint8_t *
+link_specifier_getconstarray_un_legacy_id(const link_specifier_t *inp)
+{
+ return (const uint8_t *)link_specifier_getarray_un_legacy_id((link_specifier_t*)inp);
+}
+size_t
+link_specifier_getlen_un_ed25519_id(const link_specifier_t *inp)
+{
+ (void)inp; return 32;
+}
+
+uint8_t
+link_specifier_get_un_ed25519_id(link_specifier_t *inp, size_t idx)
+{
+ trunnel_assert(idx < 32);
+ return inp->un_ed25519_id[idx];
+}
+
+uint8_t
+link_specifier_getconst_un_ed25519_id(const link_specifier_t *inp, size_t idx)
+{
+ return link_specifier_get_un_ed25519_id((link_specifier_t*)inp, idx);
+}
+int
+link_specifier_set_un_ed25519_id(link_specifier_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < 32);
+ inp->un_ed25519_id[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+link_specifier_getarray_un_ed25519_id(link_specifier_t *inp)
+{
+ return inp->un_ed25519_id;
+}
+const uint8_t *
+link_specifier_getconstarray_un_ed25519_id(const link_specifier_t *inp)
+{
+ return (const uint8_t *)link_specifier_getarray_un_ed25519_id((link_specifier_t*)inp);
+}
+size_t
+link_specifier_getlen_un_unrecognized(const link_specifier_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->un_unrecognized);
+}
+
+uint8_t
+link_specifier_get_un_unrecognized(link_specifier_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->un_unrecognized, idx);
+}
+
+uint8_t
+link_specifier_getconst_un_unrecognized(const link_specifier_t *inp, size_t idx)
+{
+ return link_specifier_get_un_unrecognized((link_specifier_t*)inp, idx);
+}
+int
+link_specifier_set_un_unrecognized(link_specifier_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->un_unrecognized, idx, elt);
+ return 0;
+}
+int
+link_specifier_add_un_unrecognized(link_specifier_t *inp, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->un_unrecognized, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+link_specifier_getarray_un_unrecognized(link_specifier_t *inp)
+{
+ return inp->un_unrecognized.elts_;
+}
+const uint8_t *
+link_specifier_getconstarray_un_unrecognized(const link_specifier_t *inp)
+{
+ return (const uint8_t *)link_specifier_getarray_un_unrecognized((link_specifier_t*)inp);
+}
+int
+link_specifier_setlen_un_unrecognized(link_specifier_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+ newptr = trunnel_dynarray_setlen(&inp->un_unrecognized.allocated_,
+ &inp->un_unrecognized.n_, inp->un_unrecognized.elts_, newlen,
+ sizeof(inp->un_unrecognized.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->un_unrecognized.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+link_specifier_check(const link_specifier_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ switch (obj->ls_type) {
+
+ case LS_IPV4:
+ break;
+
+ case LS_IPV6:
+ break;
+
+ case LS_LEGACY_ID:
+ break;
+
+ case LS_ED25519_ID:
+ break;
+
+ default:
+ break;
+ }
+ return NULL;
+}
+
+ssize_t
+link_specifier_encoded_len(const link_specifier_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != link_specifier_check(obj))
+ return -1;
+
+
+ /* Length of u8 ls_type */
+ result += 1;
+
+ /* Length of u8 ls_len */
+ result += 1;
+ switch (obj->ls_type) {
+
+ case LS_IPV4:
+
+ /* Length of u32 un_ipv4_addr */
+ result += 4;
+
+ /* Length of u16 un_ipv4_port */
+ result += 2;
+ break;
+
+ case LS_IPV6:
+
+ /* Length of u8 un_ipv6_addr[16] */
+ result += 16;
+
+ /* Length of u16 un_ipv6_port */
+ result += 2;
+ break;
+
+ case LS_LEGACY_ID:
+
+ /* Length of u8 un_legacy_id[20] */
+ result += 20;
+ break;
+
+ case LS_ED25519_ID:
+
+ /* Length of u8 un_ed25519_id[32] */
+ result += 32;
+ break;
+
+ default:
+
+ /* Length of u8 un_unrecognized[] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->un_unrecognized);
+ break;
+ }
+ return result;
+}
+int
+link_specifier_clear_errors(link_specifier_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+link_specifier_encode(uint8_t *output, const size_t avail, const link_specifier_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 = link_specifier_encoded_len(obj);
+#endif
+
+ uint8_t *backptr_ls_len = NULL;
+
+ if (NULL != (msg = link_specifier_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 ls_type */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->ls_type));
+ written += 1; ptr += 1;
+
+ /* Encode u8 ls_len */
+ backptr_ls_len = ptr;
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->ls_len));
+ written += 1; ptr += 1;
+ {
+ size_t written_before_union = written;
+
+ /* Encode union un[ls_type] */
+ trunnel_assert(written <= avail);
+ switch (obj->ls_type) {
+
+ case LS_IPV4:
+
+ /* Encode u32 un_ipv4_addr */
+ trunnel_assert(written <= avail);
+ if (avail - written < 4)
+ goto truncated;
+ trunnel_set_uint32(ptr, trunnel_htonl(obj->un_ipv4_addr));
+ written += 4; ptr += 4;
+
+ /* Encode u16 un_ipv4_port */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->un_ipv4_port));
+ written += 2; ptr += 2;
+ break;
+
+ case LS_IPV6:
+
+ /* Encode u8 un_ipv6_addr[16] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 16)
+ goto truncated;
+ memcpy(ptr, obj->un_ipv6_addr, 16);
+ written += 16; ptr += 16;
+
+ /* Encode u16 un_ipv6_port */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->un_ipv6_port));
+ written += 2; ptr += 2;
+ break;
+
+ case LS_LEGACY_ID:
+
+ /* Encode u8 un_legacy_id[20] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 20)
+ goto truncated;
+ memcpy(ptr, obj->un_legacy_id, 20);
+ written += 20; ptr += 20;
+ break;
+
+ case LS_ED25519_ID:
+
+ /* Encode u8 un_ed25519_id[32] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 32)
+ goto truncated;
+ memcpy(ptr, obj->un_ed25519_id, 32);
+ written += 32; ptr += 32;
+ break;
+
+ default:
+
+ /* Encode u8 un_unrecognized[] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->un_unrecognized);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->un_unrecognized.elts_, elt_len);
+ written += elt_len; ptr += elt_len;
+ }
+ break;
+ }
+ /* Write the length field back to ls_len */
+ trunnel_assert(written >= written_before_union);
+#if UINT8_MAX < SIZE_MAX
+ if (written - written_before_union > UINT8_MAX)
+ goto check_failed;
+#endif
+ trunnel_set_uint8(backptr_ls_len, (written - written_before_union));
+ }
+
+
+ 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 link_specifier_parse(), but do not allocate the output object.
+ */
+static ssize_t
+link_specifier_parse_into(link_specifier_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 ls_type */
+ CHECK_REMAINING(1, truncated);
+ obj->ls_type = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse u8 ls_len */
+ CHECK_REMAINING(1, truncated);
+ obj->ls_len = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+ {
+ size_t remaining_after;
+ CHECK_REMAINING(obj->ls_len, truncated);
+ remaining_after = remaining - obj->ls_len;
+ remaining = obj->ls_len;
+
+ /* Parse union un[ls_type] */
+ switch (obj->ls_type) {
+
+ case LS_IPV4:
+
+ /* Parse u32 un_ipv4_addr */
+ CHECK_REMAINING(4, fail);
+ obj->un_ipv4_addr = trunnel_ntohl(trunnel_get_uint32(ptr));
+ remaining -= 4; ptr += 4;
+
+ /* Parse u16 un_ipv4_port */
+ CHECK_REMAINING(2, fail);
+ obj->un_ipv4_port = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+ break;
+
+ case LS_IPV6:
+
+ /* Parse u8 un_ipv6_addr[16] */
+ CHECK_REMAINING(16, fail);
+ memcpy(obj->un_ipv6_addr, ptr, 16);
+ remaining -= 16; ptr += 16;
+
+ /* Parse u16 un_ipv6_port */
+ CHECK_REMAINING(2, fail);
+ obj->un_ipv6_port = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+ break;
+
+ case LS_LEGACY_ID:
+
+ /* Parse u8 un_legacy_id[20] */
+ CHECK_REMAINING(20, fail);
+ memcpy(obj->un_legacy_id, ptr, 20);
+ remaining -= 20; ptr += 20;
+ break;
+
+ case LS_ED25519_ID:
+
+ /* Parse u8 un_ed25519_id[32] */
+ CHECK_REMAINING(32, fail);
+ memcpy(obj->un_ed25519_id, ptr, 32);
+ remaining -= 32; ptr += 32;
+ break;
+
+ default:
+
+ /* Parse u8 un_unrecognized[] */
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->un_unrecognized, remaining, {});
+ obj->un_unrecognized.n_ = remaining;
+ if (remaining)
+ memcpy(obj->un_unrecognized.elts_, ptr, remaining);
+ ptr += remaining; remaining -= remaining;
+ break;
+ }
+ if (remaining != 0)
+ goto fail;
+ remaining = remaining_after;
+ }
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ trunnel_alloc_failed:
+ return -1;
+ fail:
+ result = -1;
+ return result;
+}
+
+ssize_t
+link_specifier_parse(link_specifier_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = link_specifier_new();
+ if (NULL == *output)
+ return -1;
+ result = link_specifier_parse_into(*output, input, len_in);
+ if (result < 0) {
+ link_specifier_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
ed25519_cert_t *
ed25519_cert_new(void)
{
@@ -448,7 +1615,7 @@ ed25519_cert_free(ed25519_cert_t *obj)
}
uint8_t
-ed25519_cert_get_version(ed25519_cert_t *inp)
+ed25519_cert_get_version(const ed25519_cert_t *inp)
{
return inp->version;
}
@@ -463,7 +1630,7 @@ ed25519_cert_set_version(ed25519_cert_t *inp, uint8_t val)
return 0;
}
uint8_t
-ed25519_cert_get_cert_type(ed25519_cert_t *inp)
+ed25519_cert_get_cert_type(const ed25519_cert_t *inp)
{
return inp->cert_type;
}
@@ -474,7 +1641,7 @@ ed25519_cert_set_cert_type(ed25519_cert_t *inp, uint8_t val)
return 0;
}
uint32_t
-ed25519_cert_get_exp_field(ed25519_cert_t *inp)
+ed25519_cert_get_exp_field(const ed25519_cert_t *inp)
{
return inp->exp_field;
}
@@ -485,7 +1652,7 @@ ed25519_cert_set_exp_field(ed25519_cert_t *inp, uint32_t val)
return 0;
}
uint8_t
-ed25519_cert_get_cert_key_type(ed25519_cert_t *inp)
+ed25519_cert_get_cert_key_type(const ed25519_cert_t *inp)
{
return inp->cert_key_type;
}
@@ -502,12 +1669,17 @@ ed25519_cert_getlen_certified_key(const ed25519_cert_t *inp)
}
uint8_t
-ed25519_cert_get_certified_key(const ed25519_cert_t *inp, size_t idx)
+ed25519_cert_get_certified_key(ed25519_cert_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->certified_key[idx];
}
+uint8_t
+ed25519_cert_getconst_certified_key(const ed25519_cert_t *inp, size_t idx)
+{
+ return ed25519_cert_get_certified_key((ed25519_cert_t*)inp, idx);
+}
int
ed25519_cert_set_certified_key(ed25519_cert_t *inp, size_t idx, uint8_t elt)
{
@@ -521,8 +1693,13 @@ ed25519_cert_getarray_certified_key(ed25519_cert_t *inp)
{
return inp->certified_key;
}
+const uint8_t *
+ed25519_cert_getconstarray_certified_key(const ed25519_cert_t *inp)
+{
+ return (const uint8_t *)ed25519_cert_getarray_certified_key((ed25519_cert_t*)inp);
+}
uint8_t
-ed25519_cert_get_n_extensions(ed25519_cert_t *inp)
+ed25519_cert_get_n_extensions(const ed25519_cert_t *inp)
{
return inp->n_extensions;
}
@@ -544,6 +1721,11 @@ ed25519_cert_get_ext(ed25519_cert_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->ext, idx);
}
+ const struct ed25519_cert_extension_st *
+ed25519_cert_getconst_ext(const ed25519_cert_t *inp, size_t idx)
+{
+ return ed25519_cert_get_ext((ed25519_cert_t*)inp, idx);
+}
int
ed25519_cert_set_ext(ed25519_cert_t *inp, size_t idx, struct ed25519_cert_extension_st * elt)
{
@@ -577,6 +1759,11 @@ ed25519_cert_getarray_ext(ed25519_cert_t *inp)
{
return inp->ext.elts_;
}
+const struct ed25519_cert_extension_st * const *
+ed25519_cert_getconstarray_ext(const ed25519_cert_t *inp)
+{
+ return (const struct ed25519_cert_extension_st * const *)ed25519_cert_getarray_ext((ed25519_cert_t*)inp);
+}
int
ed25519_cert_setlen_ext(ed25519_cert_t *inp, size_t newlen)
{
@@ -604,12 +1791,17 @@ ed25519_cert_getlen_signature(const ed25519_cert_t *inp)
}
uint8_t
-ed25519_cert_get_signature(const ed25519_cert_t *inp, size_t idx)
+ed25519_cert_get_signature(ed25519_cert_t *inp, size_t idx)
{
trunnel_assert(idx < 64);
return inp->signature[idx];
}
+uint8_t
+ed25519_cert_getconst_signature(const ed25519_cert_t *inp, size_t idx)
+{
+ return ed25519_cert_get_signature((ed25519_cert_t*)inp, idx);
+}
int
ed25519_cert_set_signature(ed25519_cert_t *inp, size_t idx, uint8_t elt)
{
@@ -623,6 +1815,11 @@ ed25519_cert_getarray_signature(ed25519_cert_t *inp)
{
return inp->signature;
}
+const uint8_t *
+ed25519_cert_getconstarray_signature(const ed25519_cert_t *inp)
+{
+ return (const uint8_t *)ed25519_cert_getarray_signature((ed25519_cert_t*)inp);
+}
const char *
ed25519_cert_check(const ed25519_cert_t *obj)
{
@@ -887,3 +2084,630 @@ ed25519_cert_parse(ed25519_cert_t **output, const uint8_t *input, const size_t l
}
return result;
}
+extend2_cell_body_t *
+extend2_cell_body_new(void)
+{
+ extend2_cell_body_t *val = trunnel_calloc(1, sizeof(extend2_cell_body_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+extend2_cell_body_clear(extend2_cell_body_t *obj)
+{
+ (void) obj;
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ls); ++idx) {
+ link_specifier_free(TRUNNEL_DYNARRAY_GET(&obj->ls, idx));
+ }
+ }
+ TRUNNEL_DYNARRAY_WIPE(&obj->ls);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->ls);
+ create2_cell_body_free(obj->create2);
+ obj->create2 = NULL;
+}
+
+void
+extend2_cell_body_free(extend2_cell_body_t *obj)
+{
+ if (obj == NULL)
+ return;
+ extend2_cell_body_clear(obj);
+ trunnel_memwipe(obj, sizeof(extend2_cell_body_t));
+ trunnel_free_(obj);
+}
+
+uint8_t
+extend2_cell_body_get_n_spec(const extend2_cell_body_t *inp)
+{
+ return inp->n_spec;
+}
+int
+extend2_cell_body_set_n_spec(extend2_cell_body_t *inp, uint8_t val)
+{
+ inp->n_spec = val;
+ return 0;
+}
+size_t
+extend2_cell_body_getlen_ls(const extend2_cell_body_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->ls);
+}
+
+struct link_specifier_st *
+extend2_cell_body_get_ls(extend2_cell_body_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->ls, idx);
+}
+
+ const struct link_specifier_st *
+extend2_cell_body_getconst_ls(const extend2_cell_body_t *inp, size_t idx)
+{
+ return extend2_cell_body_get_ls((extend2_cell_body_t*)inp, idx);
+}
+int
+extend2_cell_body_set_ls(extend2_cell_body_t *inp, size_t idx, struct link_specifier_st * elt)
+{
+ link_specifier_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->ls, idx);
+ if (oldval && oldval != elt)
+ link_specifier_free(oldval);
+ return extend2_cell_body_set0_ls(inp, idx, elt);
+}
+int
+extend2_cell_body_set0_ls(extend2_cell_body_t *inp, size_t idx, struct link_specifier_st * elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->ls, idx, elt);
+ return 0;
+}
+int
+extend2_cell_body_add_ls(extend2_cell_body_t *inp, struct link_specifier_st * elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+ if (inp->ls.n_ == UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(struct link_specifier_st *, &inp->ls, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+struct link_specifier_st * *
+extend2_cell_body_getarray_ls(extend2_cell_body_t *inp)
+{
+ return inp->ls.elts_;
+}
+const struct link_specifier_st * const *
+extend2_cell_body_getconstarray_ls(const extend2_cell_body_t *inp)
+{
+ return (const struct link_specifier_st * const *)extend2_cell_body_getarray_ls((extend2_cell_body_t*)inp);
+}
+int
+extend2_cell_body_setlen_ls(extend2_cell_body_t *inp, size_t newlen)
+{
+ struct link_specifier_st * *newptr;
+#if UINT8_MAX < SIZE_MAX
+ if (newlen > UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->ls.allocated_,
+ &inp->ls.n_, inp->ls.elts_, newlen,
+ sizeof(inp->ls.elts_[0]), (trunnel_free_fn_t) link_specifier_free,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->ls.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+struct create2_cell_body_st *
+extend2_cell_body_get_create2(extend2_cell_body_t *inp)
+{
+ return inp->create2;
+}
+const struct create2_cell_body_st *
+extend2_cell_body_getconst_create2(const extend2_cell_body_t *inp)
+{
+ return extend2_cell_body_get_create2((extend2_cell_body_t*) inp);
+}
+int
+extend2_cell_body_set_create2(extend2_cell_body_t *inp, struct create2_cell_body_st *val)
+{
+ if (inp->create2 && inp->create2 != val)
+ create2_cell_body_free(inp->create2);
+ return extend2_cell_body_set0_create2(inp, val);
+}
+int
+extend2_cell_body_set0_create2(extend2_cell_body_t *inp, struct create2_cell_body_st *val)
+{
+ inp->create2 = val;
+ return 0;
+}
+const char *
+extend2_cell_body_check(const extend2_cell_body_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ {
+ const char *msg;
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ls); ++idx) {
+ if (NULL != (msg = link_specifier_check(TRUNNEL_DYNARRAY_GET(&obj->ls, idx))))
+ return msg;
+ }
+ }
+ if (TRUNNEL_DYNARRAY_LEN(&obj->ls) != obj->n_spec)
+ return "Length mismatch for ls";
+ {
+ const char *msg;
+ if (NULL != (msg = create2_cell_body_check(obj->create2)))
+ return msg;
+ }
+ return NULL;
+}
+
+ssize_t
+extend2_cell_body_encoded_len(const extend2_cell_body_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != extend2_cell_body_check(obj))
+ return -1;
+
+
+ /* Length of u8 n_spec */
+ result += 1;
+
+ /* Length of struct link_specifier ls[n_spec] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ls); ++idx) {
+ result += link_specifier_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->ls, idx));
+ }
+ }
+
+ /* Length of struct create2_cell_body create2 */
+ result += create2_cell_body_encoded_len(obj->create2);
+ return result;
+}
+int
+extend2_cell_body_clear_errors(extend2_cell_body_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+extend2_cell_body_encode(uint8_t *output, const size_t avail, const extend2_cell_body_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 = extend2_cell_body_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = extend2_cell_body_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 n_spec */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->n_spec));
+ written += 1; ptr += 1;
+
+ /* Encode struct link_specifier ls[n_spec] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->ls); ++idx) {
+ trunnel_assert(written <= avail);
+ result = link_specifier_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->ls, idx));
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+ }
+ }
+
+ /* Encode struct create2_cell_body create2 */
+ trunnel_assert(written <= avail);
+ result = create2_cell_body_encode(ptr, avail - written, obj->create2);
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+
+
+ 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 extend2_cell_body_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+extend2_cell_body_parse_into(extend2_cell_body_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 n_spec */
+ CHECK_REMAINING(1, truncated);
+ obj->n_spec = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse struct link_specifier ls[n_spec] */
+ TRUNNEL_DYNARRAY_EXPAND(link_specifier_t *, &obj->ls, obj->n_spec, {});
+ {
+ link_specifier_t * elt;
+ unsigned idx;
+ for (idx = 0; idx < obj->n_spec; ++idx) {
+ result = link_specifier_parse(&elt, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ TRUNNEL_DYNARRAY_ADD(link_specifier_t *, &obj->ls, elt, {link_specifier_free(elt);});
+ }
+ }
+
+ /* Parse struct create2_cell_body create2 */
+ result = create2_cell_body_parse(&obj->create2, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ trunnel_alloc_failed:
+ return -1;
+}
+
+ssize_t
+extend2_cell_body_parse(extend2_cell_body_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = extend2_cell_body_new();
+ if (NULL == *output)
+ return -1;
+ result = extend2_cell_body_parse_into(*output, input, len_in);
+ if (result < 0) {
+ extend2_cell_body_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
+link_specifier_list_t *
+link_specifier_list_new(void)
+{
+ link_specifier_list_t *val = trunnel_calloc(1, sizeof(link_specifier_list_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+link_specifier_list_clear(link_specifier_list_t *obj)
+{
+ (void) obj;
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+ link_specifier_free(TRUNNEL_DYNARRAY_GET(&obj->spec, idx));
+ }
+ }
+ TRUNNEL_DYNARRAY_WIPE(&obj->spec);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->spec);
+}
+
+void
+link_specifier_list_free(link_specifier_list_t *obj)
+{
+ if (obj == NULL)
+ return;
+ link_specifier_list_clear(obj);
+ trunnel_memwipe(obj, sizeof(link_specifier_list_t));
+ trunnel_free_(obj);
+}
+
+uint8_t
+link_specifier_list_get_n_spec(const link_specifier_list_t *inp)
+{
+ return inp->n_spec;
+}
+int
+link_specifier_list_set_n_spec(link_specifier_list_t *inp, uint8_t val)
+{
+ inp->n_spec = val;
+ return 0;
+}
+size_t
+link_specifier_list_getlen_spec(const link_specifier_list_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->spec);
+}
+
+struct link_specifier_st *
+link_specifier_list_get_spec(link_specifier_list_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->spec, idx);
+}
+
+ const struct link_specifier_st *
+link_specifier_list_getconst_spec(const link_specifier_list_t *inp, size_t idx)
+{
+ return link_specifier_list_get_spec((link_specifier_list_t*)inp, idx);
+}
+int
+link_specifier_list_set_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt)
+{
+ link_specifier_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->spec, idx);
+ if (oldval && oldval != elt)
+ link_specifier_free(oldval);
+ return link_specifier_list_set0_spec(inp, idx, elt);
+}
+int
+link_specifier_list_set0_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->spec, idx, elt);
+ return 0;
+}
+int
+link_specifier_list_add_spec(link_specifier_list_t *inp, struct link_specifier_st * elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+ if (inp->spec.n_ == UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(struct link_specifier_st *, &inp->spec, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+struct link_specifier_st * *
+link_specifier_list_getarray_spec(link_specifier_list_t *inp)
+{
+ return inp->spec.elts_;
+}
+const struct link_specifier_st * const *
+link_specifier_list_getconstarray_spec(const link_specifier_list_t *inp)
+{
+ return (const struct link_specifier_st * const *)link_specifier_list_getarray_spec((link_specifier_list_t*)inp);
+}
+int
+link_specifier_list_setlen_spec(link_specifier_list_t *inp, size_t newlen)
+{
+ struct link_specifier_st * *newptr;
+#if UINT8_MAX < SIZE_MAX
+ if (newlen > UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->spec.allocated_,
+ &inp->spec.n_, inp->spec.elts_, newlen,
+ sizeof(inp->spec.elts_[0]), (trunnel_free_fn_t) link_specifier_free,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->spec.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+link_specifier_list_check(const link_specifier_list_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ {
+ const char *msg;
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+ if (NULL != (msg = link_specifier_check(TRUNNEL_DYNARRAY_GET(&obj->spec, idx))))
+ return msg;
+ }
+ }
+ if (TRUNNEL_DYNARRAY_LEN(&obj->spec) != obj->n_spec)
+ return "Length mismatch for spec";
+ return NULL;
+}
+
+ssize_t
+link_specifier_list_encoded_len(const link_specifier_list_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != link_specifier_list_check(obj))
+ return -1;
+
+
+ /* Length of u8 n_spec */
+ result += 1;
+
+ /* Length of struct link_specifier spec[n_spec] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+ result += link_specifier_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->spec, idx));
+ }
+ }
+ return result;
+}
+int
+link_specifier_list_clear_errors(link_specifier_list_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+link_specifier_list_encode(uint8_t *output, const size_t avail, const link_specifier_list_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 = link_specifier_list_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = link_specifier_list_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 n_spec */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->n_spec));
+ written += 1; ptr += 1;
+
+ /* Encode struct link_specifier spec[n_spec] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+ trunnel_assert(written <= avail);
+ result = link_specifier_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->spec, idx));
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+ }
+ }
+
+
+ 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 link_specifier_list_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+link_specifier_list_parse_into(link_specifier_list_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 n_spec */
+ CHECK_REMAINING(1, truncated);
+ obj->n_spec = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse struct link_specifier spec[n_spec] */
+ TRUNNEL_DYNARRAY_EXPAND(link_specifier_t *, &obj->spec, obj->n_spec, {});
+ {
+ link_specifier_t * elt;
+ unsigned idx;
+ for (idx = 0; idx < obj->n_spec; ++idx) {
+ result = link_specifier_parse(&elt, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ TRUNNEL_DYNARRAY_ADD(link_specifier_t *, &obj->spec, elt, {link_specifier_free(elt);});
+ }
+ }
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ trunnel_alloc_failed:
+ return -1;
+}
+
+ssize_t
+link_specifier_list_parse(link_specifier_list_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = link_specifier_list_new();
+ if (NULL == *output)
+ return -1;
+ result = link_specifier_list_parse_into(*output, input, len_in);
+ if (result < 0) {
+ link_specifier_list_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
diff --git a/src/trunnel/ed25519_cert.h b/src/trunnel/ed25519_cert.h
index 28f6feef31..782bd59585 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.4.6.
+/* ed25519_cert.h -- generated by by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -10,6 +10,19 @@
#define CERTEXT_SIGNED_WITH_KEY 4
#define CERTEXT_FLAG_AFFECTS_VALIDATION 1
+#define LS_IPV4 0
+#define LS_IPV6 1
+#define LS_LEGACY_ID 2
+#define LS_ED25519_ID 3
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CREATE2_CELL_BODY)
+struct create2_cell_body_st {
+ uint16_t handshake_type;
+ uint16_t handshake_len;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) handshake_data;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct create2_cell_body_st create2_cell_body_t;
#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT_EXTENSION)
struct ed25519_cert_extension_st {
uint16_t ext_length;
@@ -21,6 +34,31 @@ struct ed25519_cert_extension_st {
};
#endif
typedef struct ed25519_cert_extension_st ed25519_cert_extension_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_EXTEND1_CELL_BODY)
+struct extend1_cell_body_st {
+ uint32_t ipv4addr;
+ uint16_t port;
+ uint8_t onionskin[186];
+ uint8_t identity[20];
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct extend1_cell_body_st extend1_cell_body_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_LINK_SPECIFIER)
+struct link_specifier_st {
+ uint8_t ls_type;
+ uint8_t ls_len;
+ uint32_t un_ipv4_addr;
+ uint16_t un_ipv4_port;
+ uint8_t un_ipv6_addr[16];
+ uint16_t un_ipv6_port;
+ uint8_t un_legacy_id[20];
+ uint8_t un_ed25519_id[32];
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) un_unrecognized;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct link_specifier_st link_specifier_t;
#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_ED25519_CERT)
struct ed25519_cert_st {
uint8_t version;
@@ -35,6 +73,112 @@ struct ed25519_cert_st {
};
#endif
typedef struct ed25519_cert_st ed25519_cert_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_EXTEND2_CELL_BODY)
+struct extend2_cell_body_st {
+ uint8_t n_spec;
+ TRUNNEL_DYNARRAY_HEAD(, struct link_specifier_st *) ls;
+ struct create2_cell_body_st *create2;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct extend2_cell_body_st extend2_cell_body_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_LINK_SPECIFIER_LIST)
+struct link_specifier_list_st {
+ uint8_t n_spec;
+ TRUNNEL_DYNARRAY_HEAD(, struct link_specifier_st *) spec;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct link_specifier_list_st link_specifier_list_t;
+/** Return a newly allocated create2_cell_body with all elements set
+ * to zero.
+ */
+create2_cell_body_t *create2_cell_body_new(void);
+/** Release all storage held by the create2_cell_body in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void create2_cell_body_free(create2_cell_body_t *victim);
+/** Try to parse a create2_cell_body 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
+ * create2_cell_body_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t create2_cell_body_parse(create2_cell_body_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * create2_cell_body 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 create2_cell_body_encoded_len(const create2_cell_body_t *obj);
+/** Try to encode the create2_cell_body 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 create2_cell_body_encode(uint8_t *output, size_t avail, const create2_cell_body_t *input);
+/** Check whether the internal state of the create2_cell_body in 'obj'
+ * is consistent. Return NULL if it is, and a short message if it is
+ * not.
+ */
+const char *create2_cell_body_check(const create2_cell_body_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int create2_cell_body_clear_errors(create2_cell_body_t *obj);
+/** Return the value of the handshake_type field of the
+ * create2_cell_body_t in 'inp'
+ */
+uint16_t create2_cell_body_get_handshake_type(const create2_cell_body_t *inp);
+/** Set the value of the handshake_type field of the
+ * create2_cell_body_t in 'inp' to 'val'. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int create2_cell_body_set_handshake_type(create2_cell_body_t *inp, uint16_t val);
+/** Return the value of the handshake_len field of the
+ * create2_cell_body_t in 'inp'
+ */
+uint16_t create2_cell_body_get_handshake_len(const create2_cell_body_t *inp);
+/** Set the value of the handshake_len field of the
+ * create2_cell_body_t in 'inp' to 'val'. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int create2_cell_body_set_handshake_len(create2_cell_body_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the handshake_data
+ * field of the create2_cell_body_t in 'inp'.
+ */
+size_t create2_cell_body_getlen_handshake_data(const create2_cell_body_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * handshake_data of the create2_cell_body_t in 'inp'.
+ */
+uint8_t create2_cell_body_get_handshake_data(create2_cell_body_t *inp, size_t idx);
+/** As create2_cell_body_get_handshake_data, but take and return a
+ * const pointer
+ */
+uint8_t create2_cell_body_getconst_handshake_data(const create2_cell_body_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * handshake_data of the create2_cell_body_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int create2_cell_body_set_handshake_data(create2_cell_body_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field
+ * handshake_data of the create2_cell_body_t in 'inp'.
+ */
+int create2_cell_body_add_handshake_data(create2_cell_body_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field handshake_data
+ * of 'inp'.
+ */
+uint8_t * create2_cell_body_getarray_handshake_data(create2_cell_body_t *inp);
+/** As create2_cell_body_get_handshake_data, but take and return a
+ * const pointer
+ */
+const uint8_t * create2_cell_body_getconstarray_handshake_data(const create2_cell_body_t *inp);
+/** Change the length of the variable-length array field
+ * handshake_data 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 create2_cell_body_setlen_handshake_data(create2_cell_body_t *inp, size_t newlen);
/** Return a newly allocated ed25519_cert_extension with all elements
* set to zero.
*/
@@ -74,7 +218,7 @@ int ed25519_cert_extension_clear_errors(ed25519_cert_extension_t *obj);
/** Return the value of the ext_length field of the
* ed25519_cert_extension_t in 'inp'
*/
-uint16_t ed25519_cert_extension_get_ext_length(ed25519_cert_extension_t *inp);
+uint16_t ed25519_cert_extension_get_ext_length(const ed25519_cert_extension_t *inp);
/** Set the value of the ext_length field of the
* ed25519_cert_extension_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -83,7 +227,7 @@ int ed25519_cert_extension_set_ext_length(ed25519_cert_extension_t *inp, uint16_
/** Return the value of the ext_type field of the
* ed25519_cert_extension_t in 'inp'
*/
-uint8_t ed25519_cert_extension_get_ext_type(ed25519_cert_extension_t *inp);
+uint8_t ed25519_cert_extension_get_ext_type(const ed25519_cert_extension_t *inp);
/** Set the value of the ext_type field of the
* ed25519_cert_extension_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -92,7 +236,7 @@ int ed25519_cert_extension_set_ext_type(ed25519_cert_extension_t *inp, uint8_t v
/** Return the value of the ext_flags field of the
* ed25519_cert_extension_t in 'inp'
*/
-uint8_t ed25519_cert_extension_get_ext_flags(ed25519_cert_extension_t *inp);
+uint8_t ed25519_cert_extension_get_ext_flags(const ed25519_cert_extension_t *inp);
/** Set the value of the ext_flags field of the
* ed25519_cert_extension_t in 'inp' to 'val'. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -105,7 +249,11 @@ size_t ed25519_cert_extension_getlen_un_signing_key(const ed25519_cert_extension
/** Return the element at position 'idx' of the fixed array field
* un_signing_key of the ed25519_cert_extension_t in 'inp'.
*/
-uint8_t ed25519_cert_extension_get_un_signing_key(const ed25519_cert_extension_t *inp, size_t idx);
+uint8_t ed25519_cert_extension_get_un_signing_key(ed25519_cert_extension_t *inp, size_t idx);
+/** As ed25519_cert_extension_get_un_signing_key, but take and return
+ * a const pointer
+ */
+uint8_t ed25519_cert_extension_getconst_un_signing_key(const ed25519_cert_extension_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* un_signing_key of the ed25519_cert_extension_t in 'inp', so that it
* will hold the value 'elt'.
@@ -115,6 +263,10 @@ int ed25519_cert_extension_set_un_signing_key(ed25519_cert_extension_t *inp, siz
* 'inp'.
*/
uint8_t * ed25519_cert_extension_getarray_un_signing_key(ed25519_cert_extension_t *inp);
+/** As ed25519_cert_extension_get_un_signing_key, but take and return
+ * a const pointer
+ */
+const uint8_t * ed25519_cert_extension_getconstarray_un_signing_key(const ed25519_cert_extension_t *inp);
/** Return the length of the dynamic array holding the un_unparsed
* field of the ed25519_cert_extension_t in 'inp'.
*/
@@ -123,6 +275,10 @@ size_t ed25519_cert_extension_getlen_un_unparsed(const ed25519_cert_extension_t
* un_unparsed of the ed25519_cert_extension_t in 'inp'.
*/
uint8_t ed25519_cert_extension_get_un_unparsed(ed25519_cert_extension_t *inp, size_t idx);
+/** As ed25519_cert_extension_get_un_unparsed, but take and return a
+ * const pointer
+ */
+uint8_t ed25519_cert_extension_getconst_un_unparsed(const ed25519_cert_extension_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* un_unparsed of the ed25519_cert_extension_t in 'inp', so that it
* will hold the value 'elt'.
@@ -136,11 +292,308 @@ int ed25519_cert_extension_add_un_unparsed(ed25519_cert_extension_t *inp, uint8_
* 'inp'.
*/
uint8_t * ed25519_cert_extension_getarray_un_unparsed(ed25519_cert_extension_t *inp);
+/** As ed25519_cert_extension_get_un_unparsed, but take and return a
+ * const pointer
+ */
+const uint8_t * ed25519_cert_extension_getconstarray_un_unparsed(const ed25519_cert_extension_t *inp);
/** Change the length of the variable-length array field un_unparsed
* 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 ed25519_cert_extension_setlen_un_unparsed(ed25519_cert_extension_t *inp, size_t newlen);
+/** Return a newly allocated extend1_cell_body with all elements set
+ * to zero.
+ */
+extend1_cell_body_t *extend1_cell_body_new(void);
+/** Release all storage held by the extend1_cell_body in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void extend1_cell_body_free(extend1_cell_body_t *victim);
+/** Try to parse a extend1_cell_body 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
+ * extend1_cell_body_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t extend1_cell_body_parse(extend1_cell_body_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * extend1_cell_body 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 extend1_cell_body_encoded_len(const extend1_cell_body_t *obj);
+/** Try to encode the extend1_cell_body 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 extend1_cell_body_encode(uint8_t *output, size_t avail, const extend1_cell_body_t *input);
+/** Check whether the internal state of the extend1_cell_body in 'obj'
+ * is consistent. Return NULL if it is, and a short message if it is
+ * not.
+ */
+const char *extend1_cell_body_check(const extend1_cell_body_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int extend1_cell_body_clear_errors(extend1_cell_body_t *obj);
+/** Return the value of the ipv4addr field of the extend1_cell_body_t
+ * in 'inp'
+ */
+uint32_t extend1_cell_body_get_ipv4addr(const extend1_cell_body_t *inp);
+/** Set the value of the ipv4addr field of the extend1_cell_body_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int extend1_cell_body_set_ipv4addr(extend1_cell_body_t *inp, uint32_t val);
+/** Return the value of the port field of the extend1_cell_body_t in
+ * 'inp'
+ */
+uint16_t extend1_cell_body_get_port(const extend1_cell_body_t *inp);
+/** Set the value of the port field of the extend1_cell_body_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int extend1_cell_body_set_port(extend1_cell_body_t *inp, uint16_t val);
+/** Return the (constant) length of the array holding the onionskin
+ * field of the extend1_cell_body_t in 'inp'.
+ */
+size_t extend1_cell_body_getlen_onionskin(const extend1_cell_body_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * onionskin of the extend1_cell_body_t in 'inp'.
+ */
+uint8_t extend1_cell_body_get_onionskin(extend1_cell_body_t *inp, size_t idx);
+/** As extend1_cell_body_get_onionskin, but take and return a const
+ * pointer
+ */
+uint8_t extend1_cell_body_getconst_onionskin(const extend1_cell_body_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * onionskin of the extend1_cell_body_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int extend1_cell_body_set_onionskin(extend1_cell_body_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 186-element array field onionskin of
+ * 'inp'.
+ */
+uint8_t * extend1_cell_body_getarray_onionskin(extend1_cell_body_t *inp);
+/** As extend1_cell_body_get_onionskin, but take and return a const
+ * pointer
+ */
+const uint8_t * extend1_cell_body_getconstarray_onionskin(const extend1_cell_body_t *inp);
+/** Return the (constant) length of the array holding the identity
+ * field of the extend1_cell_body_t in 'inp'.
+ */
+size_t extend1_cell_body_getlen_identity(const extend1_cell_body_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * identity of the extend1_cell_body_t in 'inp'.
+ */
+uint8_t extend1_cell_body_get_identity(extend1_cell_body_t *inp, size_t idx);
+/** As extend1_cell_body_get_identity, but take and return a const
+ * pointer
+ */
+uint8_t extend1_cell_body_getconst_identity(const extend1_cell_body_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * identity of the extend1_cell_body_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int extend1_cell_body_set_identity(extend1_cell_body_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 20-element array field identity of 'inp'.
+ */
+uint8_t * extend1_cell_body_getarray_identity(extend1_cell_body_t *inp);
+/** As extend1_cell_body_get_identity, but take and return a const
+ * pointer
+ */
+const uint8_t * extend1_cell_body_getconstarray_identity(const extend1_cell_body_t *inp);
+/** Return a newly allocated link_specifier with all elements set to
+ * zero.
+ */
+link_specifier_t *link_specifier_new(void);
+/** Release all storage held by the link_specifier in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void link_specifier_free(link_specifier_t *victim);
+/** Try to parse a link_specifier 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
+ * link_specifier_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t link_specifier_parse(link_specifier_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * link_specifier 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 link_specifier_encoded_len(const link_specifier_t *obj);
+/** Try to encode the link_specifier 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 link_specifier_encode(uint8_t *output, size_t avail, const link_specifier_t *input);
+/** Check whether the internal state of the link_specifier in 'obj' is
+ * consistent. Return NULL if it is, and a short message if it is not.
+ */
+const char *link_specifier_check(const link_specifier_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int link_specifier_clear_errors(link_specifier_t *obj);
+/** Return the value of the ls_type field of the link_specifier_t in
+ * 'inp'
+ */
+uint8_t link_specifier_get_ls_type(const link_specifier_t *inp);
+/** Set the value of the ls_type field of the link_specifier_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int link_specifier_set_ls_type(link_specifier_t *inp, uint8_t val);
+/** Return the value of the ls_len field of the link_specifier_t in
+ * 'inp'
+ */
+uint8_t link_specifier_get_ls_len(const link_specifier_t *inp);
+/** Set the value of the ls_len field of the link_specifier_t in 'inp'
+ * to 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int link_specifier_set_ls_len(link_specifier_t *inp, uint8_t val);
+/** Return the value of the un_ipv4_addr field of the link_specifier_t
+ * in 'inp'
+ */
+uint32_t link_specifier_get_un_ipv4_addr(const link_specifier_t *inp);
+/** Set the value of the un_ipv4_addr field of the link_specifier_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int link_specifier_set_un_ipv4_addr(link_specifier_t *inp, uint32_t val);
+/** Return the value of the un_ipv4_port field of the link_specifier_t
+ * in 'inp'
+ */
+uint16_t link_specifier_get_un_ipv4_port(const link_specifier_t *inp);
+/** Set the value of the un_ipv4_port field of the link_specifier_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int link_specifier_set_un_ipv4_port(link_specifier_t *inp, uint16_t val);
+/** Return the (constant) length of the array holding the un_ipv6_addr
+ * field of the link_specifier_t in 'inp'.
+ */
+size_t link_specifier_getlen_un_ipv6_addr(const link_specifier_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * un_ipv6_addr of the link_specifier_t in 'inp'.
+ */
+uint8_t link_specifier_get_un_ipv6_addr(link_specifier_t *inp, size_t idx);
+/** As link_specifier_get_un_ipv6_addr, but take and return a const
+ * pointer
+ */
+uint8_t link_specifier_getconst_un_ipv6_addr(const link_specifier_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * un_ipv6_addr of the link_specifier_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int link_specifier_set_un_ipv6_addr(link_specifier_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 16-element array field un_ipv6_addr of
+ * 'inp'.
+ */
+uint8_t * link_specifier_getarray_un_ipv6_addr(link_specifier_t *inp);
+/** As link_specifier_get_un_ipv6_addr, but take and return a const
+ * pointer
+ */
+const uint8_t * link_specifier_getconstarray_un_ipv6_addr(const link_specifier_t *inp);
+/** Return the value of the un_ipv6_port field of the link_specifier_t
+ * in 'inp'
+ */
+uint16_t link_specifier_get_un_ipv6_port(const link_specifier_t *inp);
+/** Set the value of the un_ipv6_port field of the link_specifier_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int link_specifier_set_un_ipv6_port(link_specifier_t *inp, uint16_t val);
+/** Return the (constant) length of the array holding the un_legacy_id
+ * field of the link_specifier_t in 'inp'.
+ */
+size_t link_specifier_getlen_un_legacy_id(const link_specifier_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * un_legacy_id of the link_specifier_t in 'inp'.
+ */
+uint8_t link_specifier_get_un_legacy_id(link_specifier_t *inp, size_t idx);
+/** As link_specifier_get_un_legacy_id, but take and return a const
+ * pointer
+ */
+uint8_t link_specifier_getconst_un_legacy_id(const link_specifier_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * un_legacy_id of the link_specifier_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int link_specifier_set_un_legacy_id(link_specifier_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 20-element array field un_legacy_id of
+ * 'inp'.
+ */
+uint8_t * link_specifier_getarray_un_legacy_id(link_specifier_t *inp);
+/** As link_specifier_get_un_legacy_id, but take and return a const
+ * pointer
+ */
+const uint8_t * link_specifier_getconstarray_un_legacy_id(const link_specifier_t *inp);
+/** Return the (constant) length of the array holding the
+ * un_ed25519_id field of the link_specifier_t in 'inp'.
+ */
+size_t link_specifier_getlen_un_ed25519_id(const link_specifier_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * un_ed25519_id of the link_specifier_t in 'inp'.
+ */
+uint8_t link_specifier_get_un_ed25519_id(link_specifier_t *inp, size_t idx);
+/** As link_specifier_get_un_ed25519_id, but take and return a const
+ * pointer
+ */
+uint8_t link_specifier_getconst_un_ed25519_id(const link_specifier_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * un_ed25519_id of the link_specifier_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int link_specifier_set_un_ed25519_id(link_specifier_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the 32-element array field un_ed25519_id of
+ * 'inp'.
+ */
+uint8_t * link_specifier_getarray_un_ed25519_id(link_specifier_t *inp);
+/** As link_specifier_get_un_ed25519_id, but take and return a const
+ * pointer
+ */
+const uint8_t * link_specifier_getconstarray_un_ed25519_id(const link_specifier_t *inp);
+/** Return the length of the dynamic array holding the un_unrecognized
+ * field of the link_specifier_t in 'inp'.
+ */
+size_t link_specifier_getlen_un_unrecognized(const link_specifier_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * un_unrecognized of the link_specifier_t in 'inp'.
+ */
+uint8_t link_specifier_get_un_unrecognized(link_specifier_t *inp, size_t idx);
+/** As link_specifier_get_un_unrecognized, but take and return a const
+ * pointer
+ */
+uint8_t link_specifier_getconst_un_unrecognized(const link_specifier_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * un_unrecognized of the link_specifier_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int link_specifier_set_un_unrecognized(link_specifier_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field
+ * un_unrecognized of the link_specifier_t in 'inp'.
+ */
+int link_specifier_add_un_unrecognized(link_specifier_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field
+ * un_unrecognized of 'inp'.
+ */
+uint8_t * link_specifier_getarray_un_unrecognized(link_specifier_t *inp);
+/** As link_specifier_get_un_unrecognized, but take and return a const
+ * pointer
+ */
+const uint8_t * link_specifier_getconstarray_un_unrecognized(const link_specifier_t *inp);
+/** Change the length of the variable-length array field
+ * un_unrecognized 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 link_specifier_setlen_un_unrecognized(link_specifier_t *inp, size_t newlen);
/** Return a newly allocated ed25519_cert with all elements set to
* zero.
*/
@@ -179,7 +632,7 @@ int ed25519_cert_clear_errors(ed25519_cert_t *obj);
/** Return the value of the version field of the ed25519_cert_t in
* 'inp'
*/
-uint8_t ed25519_cert_get_version(ed25519_cert_t *inp);
+uint8_t ed25519_cert_get_version(const ed25519_cert_t *inp);
/** Set the value of the version field of the ed25519_cert_t in 'inp'
* to 'val'. Return 0 on success; return -1 and set the error code on
* 'inp' on failure.
@@ -188,7 +641,7 @@ int ed25519_cert_set_version(ed25519_cert_t *inp, uint8_t val);
/** Return the value of the cert_type field of the ed25519_cert_t in
* 'inp'
*/
-uint8_t ed25519_cert_get_cert_type(ed25519_cert_t *inp);
+uint8_t ed25519_cert_get_cert_type(const ed25519_cert_t *inp);
/** Set the value of the cert_type field of the ed25519_cert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -197,7 +650,7 @@ int ed25519_cert_set_cert_type(ed25519_cert_t *inp, uint8_t val);
/** Return the value of the exp_field field of the ed25519_cert_t in
* 'inp'
*/
-uint32_t ed25519_cert_get_exp_field(ed25519_cert_t *inp);
+uint32_t ed25519_cert_get_exp_field(const ed25519_cert_t *inp);
/** Set the value of the exp_field field of the ed25519_cert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -206,7 +659,7 @@ int ed25519_cert_set_exp_field(ed25519_cert_t *inp, uint32_t val);
/** Return the value of the cert_key_type field of the ed25519_cert_t
* in 'inp'
*/
-uint8_t ed25519_cert_get_cert_key_type(ed25519_cert_t *inp);
+uint8_t ed25519_cert_get_cert_key_type(const ed25519_cert_t *inp);
/** Set the value of the cert_key_type field of the ed25519_cert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -219,7 +672,11 @@ size_t ed25519_cert_getlen_certified_key(const ed25519_cert_t *inp);
/** Return the element at position 'idx' of the fixed array field
* certified_key of the ed25519_cert_t in 'inp'.
*/
-uint8_t ed25519_cert_get_certified_key(const ed25519_cert_t *inp, size_t idx);
+uint8_t ed25519_cert_get_certified_key(ed25519_cert_t *inp, size_t idx);
+/** As ed25519_cert_get_certified_key, but take and return a const
+ * pointer
+ */
+uint8_t ed25519_cert_getconst_certified_key(const ed25519_cert_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* certified_key of the ed25519_cert_t in 'inp', so that it will hold
* the value 'elt'.
@@ -229,10 +686,14 @@ int ed25519_cert_set_certified_key(ed25519_cert_t *inp, size_t idx, uint8_t elt)
* 'inp'.
*/
uint8_t * ed25519_cert_getarray_certified_key(ed25519_cert_t *inp);
+/** As ed25519_cert_get_certified_key, but take and return a const
+ * pointer
+ */
+const uint8_t * ed25519_cert_getconstarray_certified_key(const ed25519_cert_t *inp);
/** Return the value of the n_extensions field of the ed25519_cert_t
* in 'inp'
*/
-uint8_t ed25519_cert_get_n_extensions(ed25519_cert_t *inp);
+uint8_t ed25519_cert_get_n_extensions(const ed25519_cert_t *inp);
/** Set the value of the n_extensions field of the ed25519_cert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -246,6 +707,9 @@ size_t ed25519_cert_getlen_ext(const ed25519_cert_t *inp);
* ext of the ed25519_cert_t in 'inp'.
*/
struct ed25519_cert_extension_st * ed25519_cert_get_ext(ed25519_cert_t *inp, size_t idx);
+/** As ed25519_cert_get_ext, but take and return a const pointer
+ */
+ const struct ed25519_cert_extension_st * ed25519_cert_getconst_ext(const ed25519_cert_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* ext of the ed25519_cert_t in 'inp', so that it will hold the value
* 'elt'. Free the previous value, if any.
@@ -261,6 +725,9 @@ int ed25519_cert_add_ext(ed25519_cert_t *inp, struct ed25519_cert_extension_st *
/** Return a pointer to the variable-length array field ext of 'inp'.
*/
struct ed25519_cert_extension_st * * ed25519_cert_getarray_ext(ed25519_cert_t *inp);
+/** As ed25519_cert_get_ext, but take and return a const pointer
+ */
+const struct ed25519_cert_extension_st * const * ed25519_cert_getconstarray_ext(const ed25519_cert_t *inp);
/** Change the length of the variable-length array field ext of 'inp'
* to 'newlen'.Fill extra elements with NULL; free removed elements.
* Return 0 on success; return -1 and set the error code on 'inp' on
@@ -274,7 +741,10 @@ size_t ed25519_cert_getlen_signature(const ed25519_cert_t *inp);
/** Return the element at position 'idx' of the fixed array field
* signature of the ed25519_cert_t in 'inp'.
*/
-uint8_t ed25519_cert_get_signature(const ed25519_cert_t *inp, size_t idx);
+uint8_t ed25519_cert_get_signature(ed25519_cert_t *inp, size_t idx);
+/** As ed25519_cert_get_signature, but take and return a const pointer
+ */
+uint8_t ed25519_cert_getconst_signature(const ed25519_cert_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* signature of the ed25519_cert_t in 'inp', so that it will hold the
* value 'elt'.
@@ -283,6 +753,190 @@ int ed25519_cert_set_signature(ed25519_cert_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 64-element array field signature of 'inp'.
*/
uint8_t * ed25519_cert_getarray_signature(ed25519_cert_t *inp);
+/** As ed25519_cert_get_signature, but take and return a const pointer
+ */
+const uint8_t * ed25519_cert_getconstarray_signature(const ed25519_cert_t *inp);
+/** Return a newly allocated extend2_cell_body with all elements set
+ * to zero.
+ */
+extend2_cell_body_t *extend2_cell_body_new(void);
+/** Release all storage held by the extend2_cell_body in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void extend2_cell_body_free(extend2_cell_body_t *victim);
+/** Try to parse a extend2_cell_body 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
+ * extend2_cell_body_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t extend2_cell_body_parse(extend2_cell_body_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * extend2_cell_body 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 extend2_cell_body_encoded_len(const extend2_cell_body_t *obj);
+/** Try to encode the extend2_cell_body 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 extend2_cell_body_encode(uint8_t *output, size_t avail, const extend2_cell_body_t *input);
+/** Check whether the internal state of the extend2_cell_body in 'obj'
+ * is consistent. Return NULL if it is, and a short message if it is
+ * not.
+ */
+const char *extend2_cell_body_check(const extend2_cell_body_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int extend2_cell_body_clear_errors(extend2_cell_body_t *obj);
+/** Return the value of the n_spec field of the extend2_cell_body_t in
+ * 'inp'
+ */
+uint8_t extend2_cell_body_get_n_spec(const extend2_cell_body_t *inp);
+/** Set the value of the n_spec field of the extend2_cell_body_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int extend2_cell_body_set_n_spec(extend2_cell_body_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the ls field of the
+ * extend2_cell_body_t in 'inp'.
+ */
+size_t extend2_cell_body_getlen_ls(const extend2_cell_body_t *inp);
+/** Return the element at position 'idx' of the dynamic array field ls
+ * of the extend2_cell_body_t in 'inp'.
+ */
+struct link_specifier_st * extend2_cell_body_get_ls(extend2_cell_body_t *inp, size_t idx);
+/** As extend2_cell_body_get_ls, but take and return a const pointer
+ */
+ const struct link_specifier_st * extend2_cell_body_getconst_ls(const extend2_cell_body_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field ls
+ * of the extend2_cell_body_t in 'inp', so that it will hold the value
+ * 'elt'. Free the previous value, if any.
+ */
+int extend2_cell_body_set_ls(extend2_cell_body_t *inp, size_t idx, struct link_specifier_st * elt);
+/** As extend2_cell_body_set_ls, but does not free the previous value.
+ */
+int extend2_cell_body_set0_ls(extend2_cell_body_t *inp, size_t idx, struct link_specifier_st * elt);
+/** Append a new element 'elt' to the dynamic array field ls of the
+ * extend2_cell_body_t in 'inp'.
+ */
+int extend2_cell_body_add_ls(extend2_cell_body_t *inp, struct link_specifier_st * elt);
+/** Return a pointer to the variable-length array field ls of 'inp'.
+ */
+struct link_specifier_st * * extend2_cell_body_getarray_ls(extend2_cell_body_t *inp);
+/** As extend2_cell_body_get_ls, but take and return a const pointer
+ */
+const struct link_specifier_st * const * extend2_cell_body_getconstarray_ls(const extend2_cell_body_t *inp);
+/** Change the length of the variable-length array field ls of 'inp'
+ * to 'newlen'.Fill extra elements with NULL; free removed elements.
+ * Return 0 on success; return -1 and set the error code on 'inp' on
+ * failure.
+ */
+int extend2_cell_body_setlen_ls(extend2_cell_body_t *inp, size_t newlen);
+/** Return the value of the create2 field of the extend2_cell_body_t
+ * in 'inp'
+ */
+struct create2_cell_body_st * extend2_cell_body_get_create2(extend2_cell_body_t *inp);
+/** As extend2_cell_body_get_create2, but take and return a const
+ * pointer
+ */
+const struct create2_cell_body_st * extend2_cell_body_getconst_create2(const extend2_cell_body_t *inp);
+/** Set the value of the create2 field of the extend2_cell_body_t in
+ * 'inp' to 'val'. Free the old value if any. Steals the referenceto
+ * 'val'.Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int extend2_cell_body_set_create2(extend2_cell_body_t *inp, struct create2_cell_body_st *val);
+/** As extend2_cell_body_set_create2, but does not free the previous
+ * value.
+ */
+int extend2_cell_body_set0_create2(extend2_cell_body_t *inp, struct create2_cell_body_st *val);
+/** Return a newly allocated link_specifier_list with all elements set
+ * to zero.
+ */
+link_specifier_list_t *link_specifier_list_new(void);
+/** Release all storage held by the link_specifier_list in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void link_specifier_list_free(link_specifier_list_t *victim);
+/** Try to parse a link_specifier_list 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 link_specifier_list_t. On failure, return -2 if the input
+ * appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t link_specifier_list_parse(link_specifier_list_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * link_specifier_list 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 link_specifier_list_encoded_len(const link_specifier_list_t *obj);
+/** Try to encode the link_specifier_list 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 link_specifier_list_encode(uint8_t *output, size_t avail, const link_specifier_list_t *input);
+/** Check whether the internal state of the link_specifier_list in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *link_specifier_list_check(const link_specifier_list_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int link_specifier_list_clear_errors(link_specifier_list_t *obj);
+/** Return the value of the n_spec field of the link_specifier_list_t
+ * in 'inp'
+ */
+uint8_t link_specifier_list_get_n_spec(const link_specifier_list_t *inp);
+/** Set the value of the n_spec field of the link_specifier_list_t in
+ * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int link_specifier_list_set_n_spec(link_specifier_list_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the spec field of
+ * the link_specifier_list_t in 'inp'.
+ */
+size_t link_specifier_list_getlen_spec(const link_specifier_list_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * spec of the link_specifier_list_t in 'inp'.
+ */
+struct link_specifier_st * link_specifier_list_get_spec(link_specifier_list_t *inp, size_t idx);
+/** As link_specifier_list_get_spec, but take and return a const
+ * pointer
+ */
+ const struct link_specifier_st * link_specifier_list_getconst_spec(const link_specifier_list_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * spec of the link_specifier_list_t in 'inp', so that it will hold
+ * the value 'elt'. Free the previous value, if any.
+ */
+int link_specifier_list_set_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt);
+/** As link_specifier_list_set_spec, but does not free the previous
+ * value.
+ */
+int link_specifier_list_set0_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt);
+/** Append a new element 'elt' to the dynamic array field spec of the
+ * link_specifier_list_t in 'inp'.
+ */
+int link_specifier_list_add_spec(link_specifier_list_t *inp, struct link_specifier_st * elt);
+/** Return a pointer to the variable-length array field spec of 'inp'.
+ */
+struct link_specifier_st * * link_specifier_list_getarray_spec(link_specifier_list_t *inp);
+/** As link_specifier_list_get_spec, but take and return a const
+ * pointer
+ */
+const struct link_specifier_st * const * link_specifier_list_getconstarray_spec(const link_specifier_list_t *inp);
+/** Change the length of the variable-length array field spec of 'inp'
+ * to 'newlen'.Fill extra elements with NULL; free removed elements.
+ * Return 0 on success; return -1 and set the error code on 'inp' on
+ * failure.
+ */
+int link_specifier_list_setlen_spec(link_specifier_list_t *inp, size_t newlen);
#endif
diff --git a/src/trunnel/ed25519_cert.trunnel b/src/trunnel/ed25519_cert.trunnel
index c46f1b6c6b..e424ce5464 100644
--- a/src/trunnel/ed25519_cert.trunnel
+++ b/src/trunnel/ed25519_cert.trunnel
@@ -23,39 +23,6 @@ struct ed25519_cert_extension {
};
}
-/*
-struct cert_revocation {
- u8 prefix[8];
- u8 version IN [1];
- u8 keytype;
- u8 identity_key[32];
- u8 revoked_key[32];
- u64 published;
- u8 n_extensions;
- struct cert_extension ext[n_extensions];
- u8 signature[64];
-}
-
-struct crosscert_ed_rsa {
- u8 ed_key[32];
- u32 expiration_date;
- u8 signature[128];
-}
-
-struct auth02_cell {
- u8 type[8];
- u8 cid[32];
- u8 sid[32];
- u8 cid_ed[32];
- u8 sid_ed[32];
- u8 slog[32];
- u8 clog[32];
- u8 scert[32];
- u8 tlssecrets[32];
- u8 rand[24];
- u8 sig[64];
-}
-
const LS_IPV4 = 0x00;
const LS_IPV6 = 0x01;
const LS_LEGACY_ID = 0x02;
@@ -73,4 +40,27 @@ struct link_specifier {
default: u8 unrecognized[];
};
}
-*/ \ No newline at end of file
+
+struct link_specifier_list {
+ u8 n_spec;
+ struct link_specifier spec[n_spec];
+}
+
+struct extend1_cell_body {
+ u32 ipv4addr;
+ u16 port;
+ u8 onionskin[186];
+ u8 identity[20];
+}
+
+struct create2_cell_body {
+ u16 handshake_type;
+ u16 handshake_len;
+ u8 handshake_data[handshake_len];
+}
+
+struct extend2_cell_body {
+ u8 n_spec;
+ struct link_specifier ls[n_spec];
+ struct create2_cell_body create2;
+}
diff --git a/src/trunnel/hs/cell_common.c b/src/trunnel/hs/cell_common.c
new file mode 100644
index 0000000000..830f2260ee
--- /dev/null
+++ b/src/trunnel/hs/cell_common.c
@@ -0,0 +1,594 @@
+/* cell_common.c -- generated by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "cell_common.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+ do { \
+ (obj)->trunnel_error_code_ = 1; \
+ } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're runnning 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__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label) \
+ do { \
+ if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \
+ goto label; \
+ } \
+ } while (0)
+
+cell_extension_fields_t *
+cell_extension_fields_new(void)
+{
+ cell_extension_fields_t *val = trunnel_calloc(1, sizeof(cell_extension_fields_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+cell_extension_fields_clear(cell_extension_fields_t *obj)
+{
+ (void) obj;
+ TRUNNEL_DYNARRAY_WIPE(&obj->field);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->field);
+}
+
+void
+cell_extension_fields_free(cell_extension_fields_t *obj)
+{
+ if (obj == NULL)
+ return;
+ cell_extension_fields_clear(obj);
+ trunnel_memwipe(obj, sizeof(cell_extension_fields_t));
+ trunnel_free_(obj);
+}
+
+uint8_t
+cell_extension_fields_get_field_type(const cell_extension_fields_t *inp)
+{
+ return inp->field_type;
+}
+int
+cell_extension_fields_set_field_type(cell_extension_fields_t *inp, uint8_t val)
+{
+ inp->field_type = val;
+ return 0;
+}
+uint8_t
+cell_extension_fields_get_field_len(const cell_extension_fields_t *inp)
+{
+ return inp->field_len;
+}
+int
+cell_extension_fields_set_field_len(cell_extension_fields_t *inp, uint8_t val)
+{
+ inp->field_len = val;
+ return 0;
+}
+size_t
+cell_extension_fields_getlen_field(const cell_extension_fields_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->field);
+}
+
+uint8_t
+cell_extension_fields_get_field(cell_extension_fields_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->field, idx);
+}
+
+uint8_t
+cell_extension_fields_getconst_field(const cell_extension_fields_t *inp, size_t idx)
+{
+ return cell_extension_fields_get_field((cell_extension_fields_t*)inp, idx);
+}
+int
+cell_extension_fields_set_field(cell_extension_fields_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->field, idx, elt);
+ return 0;
+}
+int
+cell_extension_fields_add_field(cell_extension_fields_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+ if (inp->field.n_ == UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->field, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+cell_extension_fields_getarray_field(cell_extension_fields_t *inp)
+{
+ return inp->field.elts_;
+}
+const uint8_t *
+cell_extension_fields_getconstarray_field(const cell_extension_fields_t *inp)
+{
+ return (const uint8_t *)cell_extension_fields_getarray_field((cell_extension_fields_t*)inp);
+}
+int
+cell_extension_fields_setlen_field(cell_extension_fields_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+#if UINT8_MAX < SIZE_MAX
+ if (newlen > UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->field.allocated_,
+ &inp->field.n_, inp->field.elts_, newlen,
+ sizeof(inp->field.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->field.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+cell_extension_fields_check(const cell_extension_fields_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ if (TRUNNEL_DYNARRAY_LEN(&obj->field) != obj->field_len)
+ return "Length mismatch for field";
+ return NULL;
+}
+
+ssize_t
+cell_extension_fields_encoded_len(const cell_extension_fields_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != cell_extension_fields_check(obj))
+ return -1;
+
+
+ /* Length of u8 field_type */
+ result += 1;
+
+ /* Length of u8 field_len */
+ result += 1;
+
+ /* Length of u8 field[field_len] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->field);
+ return result;
+}
+int
+cell_extension_fields_clear_errors(cell_extension_fields_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+cell_extension_fields_encode(uint8_t *output, const size_t avail, const cell_extension_fields_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 = cell_extension_fields_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = cell_extension_fields_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 field_type */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->field_type));
+ written += 1; ptr += 1;
+
+ /* Encode u8 field_len */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->field_len));
+ written += 1; ptr += 1;
+
+ /* Encode u8 field[field_len] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->field);
+ trunnel_assert(obj->field_len == elt_len);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->field.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 cell_extension_fields_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+cell_extension_fields_parse_into(cell_extension_fields_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 field_type */
+ CHECK_REMAINING(1, truncated);
+ obj->field_type = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse u8 field_len */
+ CHECK_REMAINING(1, truncated);
+ obj->field_len = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse u8 field[field_len] */
+ CHECK_REMAINING(obj->field_len, truncated);
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->field, obj->field_len, {});
+ obj->field.n_ = obj->field_len;
+ if (obj->field_len)
+ memcpy(obj->field.elts_, ptr, obj->field_len);
+ ptr += obj->field_len; remaining -= obj->field_len;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ trunnel_alloc_failed:
+ return -1;
+}
+
+ssize_t
+cell_extension_fields_parse(cell_extension_fields_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = cell_extension_fields_new();
+ if (NULL == *output)
+ return -1;
+ result = cell_extension_fields_parse_into(*output, input, len_in);
+ if (result < 0) {
+ cell_extension_fields_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
+cell_extension_t *
+cell_extension_new(void)
+{
+ cell_extension_t *val = trunnel_calloc(1, sizeof(cell_extension_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+cell_extension_clear(cell_extension_t *obj)
+{
+ (void) obj;
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) {
+ cell_extension_fields_free(TRUNNEL_DYNARRAY_GET(&obj->fields, idx));
+ }
+ }
+ TRUNNEL_DYNARRAY_WIPE(&obj->fields);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->fields);
+}
+
+void
+cell_extension_free(cell_extension_t *obj)
+{
+ if (obj == NULL)
+ return;
+ cell_extension_clear(obj);
+ trunnel_memwipe(obj, sizeof(cell_extension_t));
+ trunnel_free_(obj);
+}
+
+uint8_t
+cell_extension_get_num(const cell_extension_t *inp)
+{
+ return inp->num;
+}
+int
+cell_extension_set_num(cell_extension_t *inp, uint8_t val)
+{
+ inp->num = val;
+ return 0;
+}
+size_t
+cell_extension_getlen_fields(const cell_extension_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->fields);
+}
+
+struct cell_extension_fields_st *
+cell_extension_get_fields(cell_extension_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->fields, idx);
+}
+
+ const struct cell_extension_fields_st *
+cell_extension_getconst_fields(const cell_extension_t *inp, size_t idx)
+{
+ return cell_extension_get_fields((cell_extension_t*)inp, idx);
+}
+int
+cell_extension_set_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt)
+{
+ cell_extension_fields_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->fields, idx);
+ if (oldval && oldval != elt)
+ cell_extension_fields_free(oldval);
+ return cell_extension_set0_fields(inp, idx, elt);
+}
+int
+cell_extension_set0_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->fields, idx, elt);
+ return 0;
+}
+int
+cell_extension_add_fields(cell_extension_t *inp, struct cell_extension_fields_st * elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+ if (inp->fields.n_ == UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(struct cell_extension_fields_st *, &inp->fields, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+struct cell_extension_fields_st * *
+cell_extension_getarray_fields(cell_extension_t *inp)
+{
+ return inp->fields.elts_;
+}
+const struct cell_extension_fields_st * const *
+cell_extension_getconstarray_fields(const cell_extension_t *inp)
+{
+ return (const struct cell_extension_fields_st * const *)cell_extension_getarray_fields((cell_extension_t*)inp);
+}
+int
+cell_extension_setlen_fields(cell_extension_t *inp, size_t newlen)
+{
+ struct cell_extension_fields_st * *newptr;
+#if UINT8_MAX < SIZE_MAX
+ if (newlen > UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->fields.allocated_,
+ &inp->fields.n_, inp->fields.elts_, newlen,
+ sizeof(inp->fields.elts_[0]), (trunnel_free_fn_t) cell_extension_fields_free,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->fields.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+cell_extension_check(const cell_extension_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ {
+ const char *msg;
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) {
+ if (NULL != (msg = cell_extension_fields_check(TRUNNEL_DYNARRAY_GET(&obj->fields, idx))))
+ return msg;
+ }
+ }
+ if (TRUNNEL_DYNARRAY_LEN(&obj->fields) != obj->num)
+ return "Length mismatch for fields";
+ return NULL;
+}
+
+ssize_t
+cell_extension_encoded_len(const cell_extension_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != cell_extension_check(obj))
+ return -1;
+
+
+ /* Length of u8 num */
+ result += 1;
+
+ /* Length of struct cell_extension_fields fields[num] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) {
+ result += cell_extension_fields_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->fields, idx));
+ }
+ }
+ return result;
+}
+int
+cell_extension_clear_errors(cell_extension_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+cell_extension_encode(uint8_t *output, const size_t avail, const cell_extension_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 = cell_extension_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = cell_extension_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 num */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->num));
+ written += 1; ptr += 1;
+
+ /* Encode struct cell_extension_fields fields[num] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) {
+ trunnel_assert(written <= avail);
+ result = cell_extension_fields_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->fields, idx));
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+ }
+ }
+
+
+ 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 cell_extension_parse(), but do not allocate the output object.
+ */
+static ssize_t
+cell_extension_parse_into(cell_extension_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 num */
+ CHECK_REMAINING(1, truncated);
+ obj->num = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse struct cell_extension_fields fields[num] */
+ TRUNNEL_DYNARRAY_EXPAND(cell_extension_fields_t *, &obj->fields, obj->num, {});
+ {
+ cell_extension_fields_t * elt;
+ unsigned idx;
+ for (idx = 0; idx < obj->num; ++idx) {
+ result = cell_extension_fields_parse(&elt, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ TRUNNEL_DYNARRAY_ADD(cell_extension_fields_t *, &obj->fields, elt, {cell_extension_fields_free(elt);});
+ }
+ }
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ trunnel_alloc_failed:
+ return -1;
+}
+
+ssize_t
+cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = cell_extension_new();
+ if (NULL == *output)
+ return -1;
+ result = cell_extension_parse_into(*output, input, len_in);
+ if (result < 0) {
+ cell_extension_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
diff --git a/src/trunnel/hs/cell_common.h b/src/trunnel/hs/cell_common.h
new file mode 100644
index 0000000000..8999f7da40
--- /dev/null
+++ b/src/trunnel/hs/cell_common.h
@@ -0,0 +1,198 @@
+/* cell_common.h -- generated by by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_CELL_COMMON_H
+#define TRUNNEL_CELL_COMMON_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CELL_EXTENSION_FIELDS)
+struct cell_extension_fields_st {
+ uint8_t field_type;
+ uint8_t field_len;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) field;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct cell_extension_fields_st cell_extension_fields_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_CELL_EXTENSION)
+struct cell_extension_st {
+ uint8_t num;
+ TRUNNEL_DYNARRAY_HEAD(, struct cell_extension_fields_st *) fields;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct cell_extension_st cell_extension_t;
+/** Return a newly allocated cell_extension_fields with all elements
+ * set to zero.
+ */
+cell_extension_fields_t *cell_extension_fields_new(void);
+/** Release all storage held by the cell_extension_fields in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void cell_extension_fields_free(cell_extension_fields_t *victim);
+/** Try to parse a cell_extension_fields 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 cell_extension_fields_t. On failure, return -2 if the
+ * input appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t cell_extension_fields_parse(cell_extension_fields_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * cell_extension_fields 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 cell_extension_fields_encoded_len(const cell_extension_fields_t *obj);
+/** Try to encode the cell_extension_fields 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 cell_extension_fields_encode(uint8_t *output, size_t avail, const cell_extension_fields_t *input);
+/** Check whether the internal state of the cell_extension_fields in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *cell_extension_fields_check(const cell_extension_fields_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int cell_extension_fields_clear_errors(cell_extension_fields_t *obj);
+/** Return the value of the field_type field of the
+ * cell_extension_fields_t in 'inp'
+ */
+uint8_t cell_extension_fields_get_field_type(const cell_extension_fields_t *inp);
+/** Set the value of the field_type field of the
+ * cell_extension_fields_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int cell_extension_fields_set_field_type(cell_extension_fields_t *inp, uint8_t val);
+/** Return the value of the field_len field of the
+ * cell_extension_fields_t in 'inp'
+ */
+uint8_t cell_extension_fields_get_field_len(const cell_extension_fields_t *inp);
+/** Set the value of the field_len field of the
+ * cell_extension_fields_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int cell_extension_fields_set_field_len(cell_extension_fields_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the field field of
+ * the cell_extension_fields_t in 'inp'.
+ */
+size_t cell_extension_fields_getlen_field(const cell_extension_fields_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * field of the cell_extension_fields_t in 'inp'.
+ */
+uint8_t cell_extension_fields_get_field(cell_extension_fields_t *inp, size_t idx);
+/** As cell_extension_fields_get_field, but take and return a const
+ * pointer
+ */
+uint8_t cell_extension_fields_getconst_field(const cell_extension_fields_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * field of the cell_extension_fields_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int cell_extension_fields_set_field(cell_extension_fields_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field field of the
+ * cell_extension_fields_t in 'inp'.
+ */
+int cell_extension_fields_add_field(cell_extension_fields_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field field of
+ * 'inp'.
+ */
+uint8_t * cell_extension_fields_getarray_field(cell_extension_fields_t *inp);
+/** As cell_extension_fields_get_field, but take and return a const
+ * pointer
+ */
+const uint8_t * cell_extension_fields_getconstarray_field(const cell_extension_fields_t *inp);
+/** Change the length of the variable-length array field field 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 cell_extension_fields_setlen_field(cell_extension_fields_t *inp, size_t newlen);
+/** Return a newly allocated cell_extension with all elements set to
+ * zero.
+ */
+cell_extension_t *cell_extension_new(void);
+/** Release all storage held by the cell_extension in 'victim'. (Do
+ * nothing if 'victim' is NULL.)
+ */
+void cell_extension_free(cell_extension_t *victim);
+/** Try to parse a cell_extension 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
+ * cell_extension_t. On failure, return -2 if the input appears
+ * truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * cell_extension 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 cell_extension_encoded_len(const cell_extension_t *obj);
+/** Try to encode the cell_extension 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 cell_extension_encode(uint8_t *output, size_t avail, const cell_extension_t *input);
+/** Check whether the internal state of the cell_extension in 'obj' is
+ * consistent. Return NULL if it is, and a short message if it is not.
+ */
+const char *cell_extension_check(const cell_extension_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int cell_extension_clear_errors(cell_extension_t *obj);
+/** Return the value of the num field of the cell_extension_t in 'inp'
+ */
+uint8_t cell_extension_get_num(const cell_extension_t *inp);
+/** Set the value of the num field of the cell_extension_t in 'inp' to
+ * 'val'. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int cell_extension_set_num(cell_extension_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the fields field of
+ * the cell_extension_t in 'inp'.
+ */
+size_t cell_extension_getlen_fields(const cell_extension_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * fields of the cell_extension_t in 'inp'.
+ */
+struct cell_extension_fields_st * cell_extension_get_fields(cell_extension_t *inp, size_t idx);
+/** As cell_extension_get_fields, but take and return a const pointer
+ */
+ const struct cell_extension_fields_st * cell_extension_getconst_fields(const cell_extension_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * fields of the cell_extension_t in 'inp', so that it will hold the
+ * value 'elt'. Free the previous value, if any.
+ */
+int cell_extension_set_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt);
+/** As cell_extension_set_fields, but does not free the previous
+ * value.
+ */
+int cell_extension_set0_fields(cell_extension_t *inp, size_t idx, struct cell_extension_fields_st * elt);
+/** Append a new element 'elt' to the dynamic array field fields of
+ * the cell_extension_t in 'inp'.
+ */
+int cell_extension_add_fields(cell_extension_t *inp, struct cell_extension_fields_st * elt);
+/** Return a pointer to the variable-length array field fields of
+ * 'inp'.
+ */
+struct cell_extension_fields_st * * cell_extension_getarray_fields(cell_extension_t *inp);
+/** As cell_extension_get_fields, but take and return a const pointer
+ */
+const struct cell_extension_fields_st * const * cell_extension_getconstarray_fields(const cell_extension_t *inp);
+/** Change the length of the variable-length array field fields of
+ * 'inp' to 'newlen'.Fill extra elements with NULL; free removed
+ * elements. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int cell_extension_setlen_fields(cell_extension_t *inp, size_t newlen);
+
+
+#endif
diff --git a/src/trunnel/hs/cell_common.trunnel b/src/trunnel/hs/cell_common.trunnel
new file mode 100644
index 0000000000..1bbec5a1fe
--- /dev/null
+++ b/src/trunnel/hs/cell_common.trunnel
@@ -0,0 +1,12 @@
+/* This file contains common data structure that cells use. */
+
+struct cell_extension_fields {
+ u8 field_type;
+ u8 field_len;
+ u8 field[field_len];
+};
+
+struct cell_extension {
+ u8 num;
+ struct cell_extension_fields fields[num];
+};
diff --git a/src/trunnel/hs/cell_establish_intro.c b/src/trunnel/hs/cell_establish_intro.c
new file mode 100644
index 0000000000..633bd7c214
--- /dev/null
+++ b/src/trunnel/hs/cell_establish_intro.c
@@ -0,0 +1,735 @@
+/* cell_establish_intro.c -- generated by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "cell_establish_intro.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+ do { \
+ (obj)->trunnel_error_code_ = 1; \
+ } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're runnning 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__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label) \
+ do { \
+ if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \
+ goto label; \
+ } \
+ } while (0)
+
+typedef struct cell_extension_st cell_extension_t;
+cell_extension_t *cell_extension_new(void);
+void cell_extension_free(cell_extension_t *victim);
+ssize_t cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in);
+ssize_t cell_extension_encoded_len(const cell_extension_t *obj);
+ssize_t cell_extension_encode(uint8_t *output, size_t avail, const cell_extension_t *input);
+const char *cell_extension_check(const cell_extension_t *obj);
+int cell_extension_clear_errors(cell_extension_t *obj);
+hs_cell_establish_intro_t *
+hs_cell_establish_intro_new(void)
+{
+ hs_cell_establish_intro_t *val = trunnel_calloc(1, sizeof(hs_cell_establish_intro_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+hs_cell_establish_intro_clear(hs_cell_establish_intro_t *obj)
+{
+ (void) obj;
+ TRUNNEL_DYNARRAY_WIPE(&obj->auth_key);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->auth_key);
+ cell_extension_free(obj->extensions);
+ obj->extensions = NULL;
+ TRUNNEL_DYNARRAY_WIPE(&obj->sig);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->sig);
+}
+
+void
+hs_cell_establish_intro_free(hs_cell_establish_intro_t *obj)
+{
+ if (obj == NULL)
+ return;
+ hs_cell_establish_intro_clear(obj);
+ trunnel_memwipe(obj, sizeof(hs_cell_establish_intro_t));
+ trunnel_free_(obj);
+}
+
+const uint8_t *
+hs_cell_establish_intro_get_start_cell(const hs_cell_establish_intro_t *inp)
+{
+ return inp->start_cell;
+}
+uint8_t
+hs_cell_establish_intro_get_auth_key_type(const hs_cell_establish_intro_t *inp)
+{
+ return inp->auth_key_type;
+}
+int
+hs_cell_establish_intro_set_auth_key_type(hs_cell_establish_intro_t *inp, uint8_t val)
+{
+ if (! ((val == 0 || val == 1 || val == 2))) {
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+ }
+ inp->auth_key_type = val;
+ return 0;
+}
+uint16_t
+hs_cell_establish_intro_get_auth_key_len(const hs_cell_establish_intro_t *inp)
+{
+ return inp->auth_key_len;
+}
+int
+hs_cell_establish_intro_set_auth_key_len(hs_cell_establish_intro_t *inp, uint16_t val)
+{
+ inp->auth_key_len = val;
+ return 0;
+}
+size_t
+hs_cell_establish_intro_getlen_auth_key(const hs_cell_establish_intro_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->auth_key);
+}
+
+uint8_t
+hs_cell_establish_intro_get_auth_key(hs_cell_establish_intro_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->auth_key, idx);
+}
+
+uint8_t
+hs_cell_establish_intro_getconst_auth_key(const hs_cell_establish_intro_t *inp, size_t idx)
+{
+ return hs_cell_establish_intro_get_auth_key((hs_cell_establish_intro_t*)inp, idx);
+}
+int
+hs_cell_establish_intro_set_auth_key(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->auth_key, idx, elt);
+ return 0;
+}
+int
+hs_cell_establish_intro_add_auth_key(hs_cell_establish_intro_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+ if (inp->auth_key.n_ == UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->auth_key, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+hs_cell_establish_intro_getarray_auth_key(hs_cell_establish_intro_t *inp)
+{
+ return inp->auth_key.elts_;
+}
+const uint8_t *
+hs_cell_establish_intro_getconstarray_auth_key(const hs_cell_establish_intro_t *inp)
+{
+ return (const uint8_t *)hs_cell_establish_intro_getarray_auth_key((hs_cell_establish_intro_t*)inp);
+}
+int
+hs_cell_establish_intro_setlen_auth_key(hs_cell_establish_intro_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+ if (newlen > UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->auth_key.allocated_,
+ &inp->auth_key.n_, inp->auth_key.elts_, newlen,
+ sizeof(inp->auth_key.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->auth_key.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+struct cell_extension_st *
+hs_cell_establish_intro_get_extensions(hs_cell_establish_intro_t *inp)
+{
+ return inp->extensions;
+}
+const struct cell_extension_st *
+hs_cell_establish_intro_getconst_extensions(const hs_cell_establish_intro_t *inp)
+{
+ return hs_cell_establish_intro_get_extensions((hs_cell_establish_intro_t*) inp);
+}
+int
+hs_cell_establish_intro_set_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val)
+{
+ if (inp->extensions && inp->extensions != val)
+ cell_extension_free(inp->extensions);
+ return hs_cell_establish_intro_set0_extensions(inp, val);
+}
+int
+hs_cell_establish_intro_set0_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val)
+{
+ inp->extensions = val;
+ return 0;
+}
+const uint8_t *
+hs_cell_establish_intro_get_end_mac_fields(const hs_cell_establish_intro_t *inp)
+{
+ return inp->end_mac_fields;
+}
+size_t
+hs_cell_establish_intro_getlen_handshake_mac(const hs_cell_establish_intro_t *inp)
+{
+ (void)inp; return TRUNNEL_SHA3_256_LEN;
+}
+
+uint8_t
+hs_cell_establish_intro_get_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx)
+{
+ trunnel_assert(idx < TRUNNEL_SHA3_256_LEN);
+ return inp->handshake_mac[idx];
+}
+
+uint8_t
+hs_cell_establish_intro_getconst_handshake_mac(const hs_cell_establish_intro_t *inp, size_t idx)
+{
+ return hs_cell_establish_intro_get_handshake_mac((hs_cell_establish_intro_t*)inp, idx);
+}
+int
+hs_cell_establish_intro_set_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < TRUNNEL_SHA3_256_LEN);
+ inp->handshake_mac[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+hs_cell_establish_intro_getarray_handshake_mac(hs_cell_establish_intro_t *inp)
+{
+ return inp->handshake_mac;
+}
+const uint8_t *
+hs_cell_establish_intro_getconstarray_handshake_mac(const hs_cell_establish_intro_t *inp)
+{
+ return (const uint8_t *)hs_cell_establish_intro_getarray_handshake_mac((hs_cell_establish_intro_t*)inp);
+}
+const uint8_t *
+hs_cell_establish_intro_get_end_sig_fields(const hs_cell_establish_intro_t *inp)
+{
+ return inp->end_sig_fields;
+}
+uint16_t
+hs_cell_establish_intro_get_sig_len(const hs_cell_establish_intro_t *inp)
+{
+ return inp->sig_len;
+}
+int
+hs_cell_establish_intro_set_sig_len(hs_cell_establish_intro_t *inp, uint16_t val)
+{
+ inp->sig_len = val;
+ return 0;
+}
+size_t
+hs_cell_establish_intro_getlen_sig(const hs_cell_establish_intro_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->sig);
+}
+
+uint8_t
+hs_cell_establish_intro_get_sig(hs_cell_establish_intro_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->sig, idx);
+}
+
+uint8_t
+hs_cell_establish_intro_getconst_sig(const hs_cell_establish_intro_t *inp, size_t idx)
+{
+ return hs_cell_establish_intro_get_sig((hs_cell_establish_intro_t*)inp, idx);
+}
+int
+hs_cell_establish_intro_set_sig(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->sig, idx, elt);
+ return 0;
+}
+int
+hs_cell_establish_intro_add_sig(hs_cell_establish_intro_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+ if (inp->sig.n_ == UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->sig, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+hs_cell_establish_intro_getarray_sig(hs_cell_establish_intro_t *inp)
+{
+ return inp->sig.elts_;
+}
+const uint8_t *
+hs_cell_establish_intro_getconstarray_sig(const hs_cell_establish_intro_t *inp)
+{
+ return (const uint8_t *)hs_cell_establish_intro_getarray_sig((hs_cell_establish_intro_t*)inp);
+}
+int
+hs_cell_establish_intro_setlen_sig(hs_cell_establish_intro_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+ if (newlen > UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->sig.allocated_,
+ &inp->sig.n_, inp->sig.elts_, newlen,
+ sizeof(inp->sig.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->sig.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+hs_cell_establish_intro_check(const hs_cell_establish_intro_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ if (! (obj->auth_key_type == 0 || obj->auth_key_type == 1 || obj->auth_key_type == 2))
+ return "Integer out of bounds";
+ if (TRUNNEL_DYNARRAY_LEN(&obj->auth_key) != obj->auth_key_len)
+ return "Length mismatch for auth_key";
+ {
+ const char *msg;
+ if (NULL != (msg = cell_extension_check(obj->extensions)))
+ return msg;
+ }
+ if (TRUNNEL_DYNARRAY_LEN(&obj->sig) != obj->sig_len)
+ return "Length mismatch for sig";
+ return NULL;
+}
+
+ssize_t
+hs_cell_establish_intro_encoded_len(const hs_cell_establish_intro_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != hs_cell_establish_intro_check(obj))
+ return -1;
+
+
+ /* Length of u8 auth_key_type IN [0, 1, 2] */
+ result += 1;
+
+ /* Length of u16 auth_key_len */
+ result += 2;
+
+ /* Length of u8 auth_key[auth_key_len] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->auth_key);
+
+ /* Length of struct cell_extension extensions */
+ result += cell_extension_encoded_len(obj->extensions);
+
+ /* Length of u8 handshake_mac[TRUNNEL_SHA3_256_LEN] */
+ result += TRUNNEL_SHA3_256_LEN;
+
+ /* Length of u16 sig_len */
+ result += 2;
+
+ /* Length of u8 sig[sig_len] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->sig);
+ return result;
+}
+int
+hs_cell_establish_intro_clear_errors(hs_cell_establish_intro_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+hs_cell_establish_intro_encode(uint8_t *output, const size_t avail, const hs_cell_establish_intro_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 = hs_cell_establish_intro_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = hs_cell_establish_intro_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 auth_key_type IN [0, 1, 2] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->auth_key_type));
+ written += 1; ptr += 1;
+
+ /* Encode u16 auth_key_len */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->auth_key_len));
+ written += 2; ptr += 2;
+
+ /* Encode u8 auth_key[auth_key_len] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->auth_key);
+ trunnel_assert(obj->auth_key_len == elt_len);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->auth_key.elts_, elt_len);
+ written += elt_len; ptr += elt_len;
+ }
+
+ /* Encode struct cell_extension extensions */
+ trunnel_assert(written <= avail);
+ result = cell_extension_encode(ptr, avail - written, obj->extensions);
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+
+ /* Encode u8 handshake_mac[TRUNNEL_SHA3_256_LEN] */
+ trunnel_assert(written <= avail);
+ if (avail - written < TRUNNEL_SHA3_256_LEN)
+ goto truncated;
+ memcpy(ptr, obj->handshake_mac, TRUNNEL_SHA3_256_LEN);
+ written += TRUNNEL_SHA3_256_LEN; ptr += TRUNNEL_SHA3_256_LEN;
+
+ /* Encode u16 sig_len */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->sig_len));
+ written += 2; ptr += 2;
+
+ /* Encode u8 sig[sig_len] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->sig);
+ trunnel_assert(obj->sig_len == elt_len);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->sig.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 hs_cell_establish_intro_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+hs_cell_establish_intro_parse_into(hs_cell_establish_intro_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;
+ obj->start_cell = ptr;
+
+ /* Parse u8 auth_key_type IN [0, 1, 2] */
+ CHECK_REMAINING(1, truncated);
+ obj->auth_key_type = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+ if (! (obj->auth_key_type == 0 || obj->auth_key_type == 1 || obj->auth_key_type == 2))
+ goto fail;
+
+ /* Parse u16 auth_key_len */
+ CHECK_REMAINING(2, truncated);
+ obj->auth_key_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u8 auth_key[auth_key_len] */
+ CHECK_REMAINING(obj->auth_key_len, truncated);
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->auth_key, obj->auth_key_len, {});
+ obj->auth_key.n_ = obj->auth_key_len;
+ if (obj->auth_key_len)
+ memcpy(obj->auth_key.elts_, ptr, obj->auth_key_len);
+ ptr += obj->auth_key_len; remaining -= obj->auth_key_len;
+
+ /* Parse struct cell_extension extensions */
+ result = cell_extension_parse(&obj->extensions, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ obj->end_mac_fields = ptr;
+
+ /* Parse u8 handshake_mac[TRUNNEL_SHA3_256_LEN] */
+ CHECK_REMAINING(TRUNNEL_SHA3_256_LEN, truncated);
+ memcpy(obj->handshake_mac, ptr, TRUNNEL_SHA3_256_LEN);
+ remaining -= TRUNNEL_SHA3_256_LEN; ptr += TRUNNEL_SHA3_256_LEN;
+ obj->end_sig_fields = ptr;
+
+ /* Parse u16 sig_len */
+ CHECK_REMAINING(2, truncated);
+ obj->sig_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u8 sig[sig_len] */
+ CHECK_REMAINING(obj->sig_len, truncated);
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->sig, obj->sig_len, {});
+ obj->sig.n_ = obj->sig_len;
+ if (obj->sig_len)
+ memcpy(obj->sig.elts_, ptr, obj->sig_len);
+ ptr += obj->sig_len; remaining -= obj->sig_len;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ trunnel_alloc_failed:
+ return -1;
+ fail:
+ result = -1;
+ return result;
+}
+
+ssize_t
+hs_cell_establish_intro_parse(hs_cell_establish_intro_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = hs_cell_establish_intro_new();
+ if (NULL == *output)
+ return -1;
+ result = hs_cell_establish_intro_parse_into(*output, input, len_in);
+ if (result < 0) {
+ hs_cell_establish_intro_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
+hs_cell_intro_established_t *
+hs_cell_intro_established_new(void)
+{
+ hs_cell_intro_established_t *val = trunnel_calloc(1, sizeof(hs_cell_intro_established_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+hs_cell_intro_established_clear(hs_cell_intro_established_t *obj)
+{
+ (void) obj;
+ cell_extension_free(obj->extensions);
+ obj->extensions = NULL;
+}
+
+void
+hs_cell_intro_established_free(hs_cell_intro_established_t *obj)
+{
+ if (obj == NULL)
+ return;
+ hs_cell_intro_established_clear(obj);
+ trunnel_memwipe(obj, sizeof(hs_cell_intro_established_t));
+ trunnel_free_(obj);
+}
+
+struct cell_extension_st *
+hs_cell_intro_established_get_extensions(hs_cell_intro_established_t *inp)
+{
+ return inp->extensions;
+}
+const struct cell_extension_st *
+hs_cell_intro_established_getconst_extensions(const hs_cell_intro_established_t *inp)
+{
+ return hs_cell_intro_established_get_extensions((hs_cell_intro_established_t*) inp);
+}
+int
+hs_cell_intro_established_set_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val)
+{
+ if (inp->extensions && inp->extensions != val)
+ cell_extension_free(inp->extensions);
+ return hs_cell_intro_established_set0_extensions(inp, val);
+}
+int
+hs_cell_intro_established_set0_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val)
+{
+ inp->extensions = val;
+ return 0;
+}
+const char *
+hs_cell_intro_established_check(const hs_cell_intro_established_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ {
+ const char *msg;
+ if (NULL != (msg = cell_extension_check(obj->extensions)))
+ return msg;
+ }
+ return NULL;
+}
+
+ssize_t
+hs_cell_intro_established_encoded_len(const hs_cell_intro_established_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != hs_cell_intro_established_check(obj))
+ return -1;
+
+
+ /* Length of struct cell_extension extensions */
+ result += cell_extension_encoded_len(obj->extensions);
+ return result;
+}
+int
+hs_cell_intro_established_clear_errors(hs_cell_intro_established_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+hs_cell_intro_established_encode(uint8_t *output, const size_t avail, const hs_cell_intro_established_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 = hs_cell_intro_established_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = hs_cell_intro_established_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode struct cell_extension extensions */
+ trunnel_assert(written <= avail);
+ result = cell_extension_encode(ptr, avail - written, obj->extensions);
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+
+
+ 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;
+
+ check_failed:
+ (void)msg;
+ result = -1;
+ goto fail;
+ fail:
+ trunnel_assert(result < 0);
+ return result;
+}
+
+/** As hs_cell_intro_established_parse(), but do not allocate the
+ * output object.
+ */
+static ssize_t
+hs_cell_intro_established_parse_into(hs_cell_intro_established_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 struct cell_extension extensions */
+ result = cell_extension_parse(&obj->extensions, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+}
+
+ssize_t
+hs_cell_intro_established_parse(hs_cell_intro_established_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = hs_cell_intro_established_new();
+ if (NULL == *output)
+ return -1;
+ result = hs_cell_intro_established_parse_into(*output, input, len_in);
+ if (result < 0) {
+ hs_cell_intro_established_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
diff --git a/src/trunnel/hs/cell_establish_intro.h b/src/trunnel/hs/cell_establish_intro.h
new file mode 100644
index 0000000000..725d47cd85
--- /dev/null
+++ b/src/trunnel/hs/cell_establish_intro.h
@@ -0,0 +1,275 @@
+/* cell_establish_intro.h -- generated by by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_CELL_ESTABLISH_INTRO_H
+#define TRUNNEL_CELL_ESTABLISH_INTRO_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+struct cell_extension_st;
+#define TRUNNEL_SHA3_256_LEN 32
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_ESTABLISH_INTRO)
+struct hs_cell_establish_intro_st {
+ const uint8_t *start_cell;
+ uint8_t auth_key_type;
+ uint16_t auth_key_len;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) auth_key;
+ struct cell_extension_st *extensions;
+ const uint8_t *end_mac_fields;
+ uint8_t handshake_mac[TRUNNEL_SHA3_256_LEN];
+ const uint8_t *end_sig_fields;
+ uint16_t sig_len;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) sig;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct hs_cell_establish_intro_st hs_cell_establish_intro_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRO_ESTABLISHED)
+struct hs_cell_intro_established_st {
+ struct cell_extension_st *extensions;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct hs_cell_intro_established_st hs_cell_intro_established_t;
+/** Return a newly allocated hs_cell_establish_intro with all elements
+ * set to zero.
+ */
+hs_cell_establish_intro_t *hs_cell_establish_intro_new(void);
+/** Release all storage held by the hs_cell_establish_intro in
+ * 'victim'. (Do nothing if 'victim' is NULL.)
+ */
+void hs_cell_establish_intro_free(hs_cell_establish_intro_t *victim);
+/** Try to parse a hs_cell_establish_intro 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 hs_cell_establish_intro_t. On failure, return -2 if the
+ * input appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t hs_cell_establish_intro_parse(hs_cell_establish_intro_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * hs_cell_establish_intro 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 hs_cell_establish_intro_encoded_len(const hs_cell_establish_intro_t *obj);
+/** Try to encode the hs_cell_establish_intro 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 hs_cell_establish_intro_encode(uint8_t *output, size_t avail, const hs_cell_establish_intro_t *input);
+/** Check whether the internal state of the hs_cell_establish_intro in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *hs_cell_establish_intro_check(const hs_cell_establish_intro_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int hs_cell_establish_intro_clear_errors(hs_cell_establish_intro_t *obj);
+/** Return the position for start_cell when we parsed this object
+ */
+const uint8_t * hs_cell_establish_intro_get_start_cell(const hs_cell_establish_intro_t *inp);
+/** Return the value of the auth_key_type field of the
+ * hs_cell_establish_intro_t in 'inp'
+ */
+uint8_t hs_cell_establish_intro_get_auth_key_type(const hs_cell_establish_intro_t *inp);
+/** Set the value of the auth_key_type field of the
+ * hs_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_establish_intro_set_auth_key_type(hs_cell_establish_intro_t *inp, uint8_t val);
+/** Return the value of the auth_key_len field of the
+ * hs_cell_establish_intro_t in 'inp'
+ */
+uint16_t hs_cell_establish_intro_get_auth_key_len(const hs_cell_establish_intro_t *inp);
+/** Set the value of the auth_key_len field of the
+ * hs_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_establish_intro_set_auth_key_len(hs_cell_establish_intro_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the auth_key field
+ * of the hs_cell_establish_intro_t in 'inp'.
+ */
+size_t hs_cell_establish_intro_getlen_auth_key(const hs_cell_establish_intro_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * auth_key of the hs_cell_establish_intro_t in 'inp'.
+ */
+uint8_t hs_cell_establish_intro_get_auth_key(hs_cell_establish_intro_t *inp, size_t idx);
+/** As hs_cell_establish_intro_get_auth_key, but take and return a
+ * const pointer
+ */
+uint8_t hs_cell_establish_intro_getconst_auth_key(const hs_cell_establish_intro_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * auth_key of the hs_cell_establish_intro_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int hs_cell_establish_intro_set_auth_key(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field auth_key of
+ * the hs_cell_establish_intro_t in 'inp'.
+ */
+int hs_cell_establish_intro_add_auth_key(hs_cell_establish_intro_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field auth_key of
+ * 'inp'.
+ */
+uint8_t * hs_cell_establish_intro_getarray_auth_key(hs_cell_establish_intro_t *inp);
+/** As hs_cell_establish_intro_get_auth_key, but take and return a
+ * const pointer
+ */
+const uint8_t * hs_cell_establish_intro_getconstarray_auth_key(const hs_cell_establish_intro_t *inp);
+/** Change the length of the variable-length array field auth_key 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 hs_cell_establish_intro_setlen_auth_key(hs_cell_establish_intro_t *inp, size_t newlen);
+/** Return the value of the extensions field of the
+ * hs_cell_establish_intro_t in 'inp'
+ */
+struct cell_extension_st * hs_cell_establish_intro_get_extensions(hs_cell_establish_intro_t *inp);
+/** As hs_cell_establish_intro_get_extensions, but take and return a
+ * const pointer
+ */
+const struct cell_extension_st * hs_cell_establish_intro_getconst_extensions(const hs_cell_establish_intro_t *inp);
+/** Set the value of the extensions field of the
+ * hs_cell_establish_intro_t in 'inp' to 'val'. Free the old value if
+ * any. Steals the referenceto 'val'.Return 0 on success; return -1
+ * and set the error code on 'inp' on failure.
+ */
+int hs_cell_establish_intro_set_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val);
+/** As hs_cell_establish_intro_set_extensions, but does not free the
+ * previous value.
+ */
+int hs_cell_establish_intro_set0_extensions(hs_cell_establish_intro_t *inp, struct cell_extension_st *val);
+/** Return the position for end_mac_fields when we parsed this object
+ */
+const uint8_t * hs_cell_establish_intro_get_end_mac_fields(const hs_cell_establish_intro_t *inp);
+/** Return the (constant) length of the array holding the
+ * handshake_mac field of the hs_cell_establish_intro_t in 'inp'.
+ */
+size_t hs_cell_establish_intro_getlen_handshake_mac(const hs_cell_establish_intro_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * handshake_mac of the hs_cell_establish_intro_t in 'inp'.
+ */
+uint8_t hs_cell_establish_intro_get_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx);
+/** As hs_cell_establish_intro_get_handshake_mac, but take and return
+ * a const pointer
+ */
+uint8_t hs_cell_establish_intro_getconst_handshake_mac(const hs_cell_establish_intro_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * handshake_mac of the hs_cell_establish_intro_t in 'inp', so that it
+ * will hold the value 'elt'.
+ */
+int hs_cell_establish_intro_set_handshake_mac(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the TRUNNEL_SHA3_256_LEN-element array field
+ * handshake_mac of 'inp'.
+ */
+uint8_t * hs_cell_establish_intro_getarray_handshake_mac(hs_cell_establish_intro_t *inp);
+/** As hs_cell_establish_intro_get_handshake_mac, but take and return
+ * a const pointer
+ */
+const uint8_t * hs_cell_establish_intro_getconstarray_handshake_mac(const hs_cell_establish_intro_t *inp);
+/** Return the position for end_sig_fields when we parsed this object
+ */
+const uint8_t * hs_cell_establish_intro_get_end_sig_fields(const hs_cell_establish_intro_t *inp);
+/** Return the value of the sig_len field of the
+ * hs_cell_establish_intro_t in 'inp'
+ */
+uint16_t hs_cell_establish_intro_get_sig_len(const hs_cell_establish_intro_t *inp);
+/** Set the value of the sig_len field of the
+ * hs_cell_establish_intro_t in 'inp' to 'val'. Return 0 on success;
+ * return -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_establish_intro_set_sig_len(hs_cell_establish_intro_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the sig field of
+ * the hs_cell_establish_intro_t in 'inp'.
+ */
+size_t hs_cell_establish_intro_getlen_sig(const hs_cell_establish_intro_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * sig of the hs_cell_establish_intro_t in 'inp'.
+ */
+uint8_t hs_cell_establish_intro_get_sig(hs_cell_establish_intro_t *inp, size_t idx);
+/** As hs_cell_establish_intro_get_sig, but take and return a const
+ * pointer
+ */
+uint8_t hs_cell_establish_intro_getconst_sig(const hs_cell_establish_intro_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * sig of the hs_cell_establish_intro_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int hs_cell_establish_intro_set_sig(hs_cell_establish_intro_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field sig of the
+ * hs_cell_establish_intro_t in 'inp'.
+ */
+int hs_cell_establish_intro_add_sig(hs_cell_establish_intro_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field sig of 'inp'.
+ */
+uint8_t * hs_cell_establish_intro_getarray_sig(hs_cell_establish_intro_t *inp);
+/** As hs_cell_establish_intro_get_sig, but take and return a const
+ * pointer
+ */
+const uint8_t * hs_cell_establish_intro_getconstarray_sig(const hs_cell_establish_intro_t *inp);
+/** Change the length of the variable-length array field sig 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 hs_cell_establish_intro_setlen_sig(hs_cell_establish_intro_t *inp, size_t newlen);
+/** Return a newly allocated hs_cell_intro_established with all
+ * elements set to zero.
+ */
+hs_cell_intro_established_t *hs_cell_intro_established_new(void);
+/** Release all storage held by the hs_cell_intro_established in
+ * 'victim'. (Do nothing if 'victim' is NULL.)
+ */
+void hs_cell_intro_established_free(hs_cell_intro_established_t *victim);
+/** Try to parse a hs_cell_intro_established 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 hs_cell_intro_established_t. On failure, return -2
+ * if the input appears truncated, and -1 if the input is otherwise
+ * invalid.
+ */
+ssize_t hs_cell_intro_established_parse(hs_cell_intro_established_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * hs_cell_intro_established 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 hs_cell_intro_established_encoded_len(const hs_cell_intro_established_t *obj);
+/** Try to encode the hs_cell_intro_established 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 hs_cell_intro_established_encode(uint8_t *output, size_t avail, const hs_cell_intro_established_t *input);
+/** Check whether the internal state of the hs_cell_intro_established
+ * in 'obj' is consistent. Return NULL if it is, and a short message
+ * if it is not.
+ */
+const char *hs_cell_intro_established_check(const hs_cell_intro_established_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int hs_cell_intro_established_clear_errors(hs_cell_intro_established_t *obj);
+/** Return the value of the extensions field of the
+ * hs_cell_intro_established_t in 'inp'
+ */
+struct cell_extension_st * hs_cell_intro_established_get_extensions(hs_cell_intro_established_t *inp);
+/** As hs_cell_intro_established_get_extensions, but take and return a
+ * const pointer
+ */
+const struct cell_extension_st * hs_cell_intro_established_getconst_extensions(const hs_cell_intro_established_t *inp);
+/** Set the value of the extensions field of the
+ * hs_cell_intro_established_t in 'inp' to 'val'. Free the old value
+ * if any. Steals the referenceto 'val'.Return 0 on success; return -1
+ * and set the error code on 'inp' on failure.
+ */
+int hs_cell_intro_established_set_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val);
+/** As hs_cell_intro_established_set_extensions, but does not free the
+ * previous value.
+ */
+int hs_cell_intro_established_set0_extensions(hs_cell_intro_established_t *inp, struct cell_extension_st *val);
+
+
+#endif
diff --git a/src/trunnel/hs/cell_establish_intro.trunnel b/src/trunnel/hs/cell_establish_intro.trunnel
new file mode 100644
index 0000000000..33a133bf67
--- /dev/null
+++ b/src/trunnel/hs/cell_establish_intro.trunnel
@@ -0,0 +1,41 @@
+/*
+ * This contains the definition of the ESTABLISH_INTRO and INTRO_ESTABLISHED
+ * cell for onion service version 3 and onward. The following format is
+ * specified in proposal 224 section 3.1.
+ */
+
+extern struct cell_extension;
+
+const TRUNNEL_SHA3_256_LEN = 32;
+
+/* ESTABLISH_INTRO payload. See details in section 3.1.1 */
+struct hs_cell_establish_intro {
+ /* Indicate the start of the handshake authentication data. */
+ @ptr start_cell;
+
+ /* Authentication key material. */
+ u8 auth_key_type IN [0x00, 0x01, 0x02];
+ u16 auth_key_len;
+ u8 auth_key[auth_key_len];
+
+ /* Extension(s). Reserved fields. */
+ struct cell_extension extensions;
+ @ptr end_mac_fields;
+
+ /* Handshake MAC. */
+ u8 handshake_mac[TRUNNEL_SHA3_256_LEN];
+
+ /* Signature */
+ /* Indicate the end of the handshake authentication data. */
+ @ptr end_sig_fields;
+ u16 sig_len;
+ u8 sig[sig_len];
+};
+
+/* INTRO_ESTABLISHED payload which is an acknowledge of the ESTABLISH_INTRO
+ * cell. For legacy node, this payload is empty so the following only applies
+ * to version >= 3. */
+struct hs_cell_intro_established {
+ /* Extension(s). Reserved fields. */
+ struct cell_extension extensions;
+};
diff --git a/src/trunnel/hs/cell_introduce1.c b/src/trunnel/hs/cell_introduce1.c
new file mode 100644
index 0000000000..5922a086dc
--- /dev/null
+++ b/src/trunnel/hs/cell_introduce1.c
@@ -0,0 +1,1357 @@
+/* cell_introduce1.c -- generated by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#include <stdlib.h>
+#include "trunnel-impl.h"
+
+#include "cell_introduce1.h"
+
+#define TRUNNEL_SET_ERROR_CODE(obj) \
+ do { \
+ (obj)->trunnel_error_code_ = 1; \
+ } while (0)
+
+#if defined(__COVERITY__) || defined(__clang_analyzer__)
+/* If we're runnning 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__
+#else
+#define OR_DEADCODE_DUMMY
+#endif
+
+#define CHECK_REMAINING(nbytes, label) \
+ do { \
+ if (remaining < (nbytes) OR_DEADCODE_DUMMY) { \
+ goto label; \
+ } \
+ } while (0)
+
+typedef struct cell_extension_st cell_extension_t;
+cell_extension_t *cell_extension_new(void);
+void cell_extension_free(cell_extension_t *victim);
+ssize_t cell_extension_parse(cell_extension_t **output, const uint8_t *input, const size_t len_in);
+ssize_t cell_extension_encoded_len(const cell_extension_t *obj);
+ssize_t cell_extension_encode(uint8_t *output, size_t avail, const cell_extension_t *input);
+const char *cell_extension_check(const cell_extension_t *obj);
+int cell_extension_clear_errors(cell_extension_t *obj);
+typedef struct link_specifier_st link_specifier_t;
+link_specifier_t *link_specifier_new(void);
+void link_specifier_free(link_specifier_t *victim);
+ssize_t link_specifier_parse(link_specifier_t **output, const uint8_t *input, const size_t len_in);
+ssize_t link_specifier_encoded_len(const link_specifier_t *obj);
+ssize_t link_specifier_encode(uint8_t *output, size_t avail, const link_specifier_t *input);
+const char *link_specifier_check(const link_specifier_t *obj);
+int link_specifier_clear_errors(link_specifier_t *obj);
+hs_cell_introduce1_t *
+hs_cell_introduce1_new(void)
+{
+ hs_cell_introduce1_t *val = trunnel_calloc(1, sizeof(hs_cell_introduce1_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+hs_cell_introduce1_clear(hs_cell_introduce1_t *obj)
+{
+ (void) obj;
+ TRUNNEL_DYNARRAY_WIPE(&obj->auth_key);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->auth_key);
+ cell_extension_free(obj->extensions);
+ obj->extensions = NULL;
+ TRUNNEL_DYNARRAY_WIPE(&obj->encrypted);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->encrypted);
+}
+
+void
+hs_cell_introduce1_free(hs_cell_introduce1_t *obj)
+{
+ if (obj == NULL)
+ return;
+ hs_cell_introduce1_clear(obj);
+ trunnel_memwipe(obj, sizeof(hs_cell_introduce1_t));
+ trunnel_free_(obj);
+}
+
+size_t
+hs_cell_introduce1_getlen_legacy_key_id(const hs_cell_introduce1_t *inp)
+{
+ (void)inp; return TRUNNEL_SHA1_LEN;
+}
+
+uint8_t
+hs_cell_introduce1_get_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx)
+{
+ trunnel_assert(idx < TRUNNEL_SHA1_LEN);
+ return inp->legacy_key_id[idx];
+}
+
+uint8_t
+hs_cell_introduce1_getconst_legacy_key_id(const hs_cell_introduce1_t *inp, size_t idx)
+{
+ return hs_cell_introduce1_get_legacy_key_id((hs_cell_introduce1_t*)inp, idx);
+}
+int
+hs_cell_introduce1_set_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < TRUNNEL_SHA1_LEN);
+ inp->legacy_key_id[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+hs_cell_introduce1_getarray_legacy_key_id(hs_cell_introduce1_t *inp)
+{
+ return inp->legacy_key_id;
+}
+const uint8_t *
+hs_cell_introduce1_getconstarray_legacy_key_id(const hs_cell_introduce1_t *inp)
+{
+ return (const uint8_t *)hs_cell_introduce1_getarray_legacy_key_id((hs_cell_introduce1_t*)inp);
+}
+uint8_t
+hs_cell_introduce1_get_auth_key_type(const hs_cell_introduce1_t *inp)
+{
+ return inp->auth_key_type;
+}
+int
+hs_cell_introduce1_set_auth_key_type(hs_cell_introduce1_t *inp, uint8_t val)
+{
+ if (! ((val == 0 || val == 1 || val == 2))) {
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+ }
+ inp->auth_key_type = val;
+ return 0;
+}
+uint16_t
+hs_cell_introduce1_get_auth_key_len(const hs_cell_introduce1_t *inp)
+{
+ return inp->auth_key_len;
+}
+int
+hs_cell_introduce1_set_auth_key_len(hs_cell_introduce1_t *inp, uint16_t val)
+{
+ inp->auth_key_len = val;
+ return 0;
+}
+size_t
+hs_cell_introduce1_getlen_auth_key(const hs_cell_introduce1_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->auth_key);
+}
+
+uint8_t
+hs_cell_introduce1_get_auth_key(hs_cell_introduce1_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->auth_key, idx);
+}
+
+uint8_t
+hs_cell_introduce1_getconst_auth_key(const hs_cell_introduce1_t *inp, size_t idx)
+{
+ return hs_cell_introduce1_get_auth_key((hs_cell_introduce1_t*)inp, idx);
+}
+int
+hs_cell_introduce1_set_auth_key(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->auth_key, idx, elt);
+ return 0;
+}
+int
+hs_cell_introduce1_add_auth_key(hs_cell_introduce1_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+ if (inp->auth_key.n_ == UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->auth_key, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+hs_cell_introduce1_getarray_auth_key(hs_cell_introduce1_t *inp)
+{
+ return inp->auth_key.elts_;
+}
+const uint8_t *
+hs_cell_introduce1_getconstarray_auth_key(const hs_cell_introduce1_t *inp)
+{
+ return (const uint8_t *)hs_cell_introduce1_getarray_auth_key((hs_cell_introduce1_t*)inp);
+}
+int
+hs_cell_introduce1_setlen_auth_key(hs_cell_introduce1_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+ if (newlen > UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->auth_key.allocated_,
+ &inp->auth_key.n_, inp->auth_key.elts_, newlen,
+ sizeof(inp->auth_key.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->auth_key.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+struct cell_extension_st *
+hs_cell_introduce1_get_extensions(hs_cell_introduce1_t *inp)
+{
+ return inp->extensions;
+}
+const struct cell_extension_st *
+hs_cell_introduce1_getconst_extensions(const hs_cell_introduce1_t *inp)
+{
+ return hs_cell_introduce1_get_extensions((hs_cell_introduce1_t*) inp);
+}
+int
+hs_cell_introduce1_set_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val)
+{
+ if (inp->extensions && inp->extensions != val)
+ cell_extension_free(inp->extensions);
+ return hs_cell_introduce1_set0_extensions(inp, val);
+}
+int
+hs_cell_introduce1_set0_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val)
+{
+ inp->extensions = val;
+ return 0;
+}
+size_t
+hs_cell_introduce1_getlen_encrypted(const hs_cell_introduce1_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->encrypted);
+}
+
+uint8_t
+hs_cell_introduce1_get_encrypted(hs_cell_introduce1_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->encrypted, idx);
+}
+
+uint8_t
+hs_cell_introduce1_getconst_encrypted(const hs_cell_introduce1_t *inp, size_t idx)
+{
+ return hs_cell_introduce1_get_encrypted((hs_cell_introduce1_t*)inp, idx);
+}
+int
+hs_cell_introduce1_set_encrypted(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->encrypted, idx, elt);
+ return 0;
+}
+int
+hs_cell_introduce1_add_encrypted(hs_cell_introduce1_t *inp, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->encrypted, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+hs_cell_introduce1_getarray_encrypted(hs_cell_introduce1_t *inp)
+{
+ return inp->encrypted.elts_;
+}
+const uint8_t *
+hs_cell_introduce1_getconstarray_encrypted(const hs_cell_introduce1_t *inp)
+{
+ return (const uint8_t *)hs_cell_introduce1_getarray_encrypted((hs_cell_introduce1_t*)inp);
+}
+int
+hs_cell_introduce1_setlen_encrypted(hs_cell_introduce1_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+ newptr = trunnel_dynarray_setlen(&inp->encrypted.allocated_,
+ &inp->encrypted.n_, inp->encrypted.elts_, newlen,
+ sizeof(inp->encrypted.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->encrypted.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+hs_cell_introduce1_check(const hs_cell_introduce1_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ if (! (obj->auth_key_type == 0 || obj->auth_key_type == 1 || obj->auth_key_type == 2))
+ return "Integer out of bounds";
+ if (TRUNNEL_DYNARRAY_LEN(&obj->auth_key) != obj->auth_key_len)
+ return "Length mismatch for auth_key";
+ {
+ const char *msg;
+ if (NULL != (msg = cell_extension_check(obj->extensions)))
+ return msg;
+ }
+ return NULL;
+}
+
+ssize_t
+hs_cell_introduce1_encoded_len(const hs_cell_introduce1_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != hs_cell_introduce1_check(obj))
+ return -1;
+
+
+ /* Length of u8 legacy_key_id[TRUNNEL_SHA1_LEN] */
+ result += TRUNNEL_SHA1_LEN;
+
+ /* Length of u8 auth_key_type IN [0, 1, 2] */
+ result += 1;
+
+ /* Length of u16 auth_key_len */
+ result += 2;
+
+ /* Length of u8 auth_key[auth_key_len] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->auth_key);
+
+ /* Length of struct cell_extension extensions */
+ result += cell_extension_encoded_len(obj->extensions);
+
+ /* Length of u8 encrypted[] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->encrypted);
+ return result;
+}
+int
+hs_cell_introduce1_clear_errors(hs_cell_introduce1_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+hs_cell_introduce1_encode(uint8_t *output, const size_t avail, const hs_cell_introduce1_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 = hs_cell_introduce1_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = hs_cell_introduce1_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 legacy_key_id[TRUNNEL_SHA1_LEN] */
+ trunnel_assert(written <= avail);
+ if (avail - written < TRUNNEL_SHA1_LEN)
+ goto truncated;
+ memcpy(ptr, obj->legacy_key_id, TRUNNEL_SHA1_LEN);
+ written += TRUNNEL_SHA1_LEN; ptr += TRUNNEL_SHA1_LEN;
+
+ /* Encode u8 auth_key_type IN [0, 1, 2] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->auth_key_type));
+ written += 1; ptr += 1;
+
+ /* Encode u16 auth_key_len */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->auth_key_len));
+ written += 2; ptr += 2;
+
+ /* Encode u8 auth_key[auth_key_len] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->auth_key);
+ trunnel_assert(obj->auth_key_len == elt_len);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->auth_key.elts_, elt_len);
+ written += elt_len; ptr += elt_len;
+ }
+
+ /* Encode struct cell_extension extensions */
+ trunnel_assert(written <= avail);
+ result = cell_extension_encode(ptr, avail - written, obj->extensions);
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+
+ /* Encode u8 encrypted[] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->encrypted);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->encrypted.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 hs_cell_introduce1_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+hs_cell_introduce1_parse_into(hs_cell_introduce1_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 legacy_key_id[TRUNNEL_SHA1_LEN] */
+ CHECK_REMAINING(TRUNNEL_SHA1_LEN, truncated);
+ memcpy(obj->legacy_key_id, ptr, TRUNNEL_SHA1_LEN);
+ remaining -= TRUNNEL_SHA1_LEN; ptr += TRUNNEL_SHA1_LEN;
+
+ /* Parse u8 auth_key_type IN [0, 1, 2] */
+ CHECK_REMAINING(1, truncated);
+ obj->auth_key_type = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+ if (! (obj->auth_key_type == 0 || obj->auth_key_type == 1 || obj->auth_key_type == 2))
+ goto fail;
+
+ /* Parse u16 auth_key_len */
+ CHECK_REMAINING(2, truncated);
+ obj->auth_key_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u8 auth_key[auth_key_len] */
+ CHECK_REMAINING(obj->auth_key_len, truncated);
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->auth_key, obj->auth_key_len, {});
+ obj->auth_key.n_ = obj->auth_key_len;
+ if (obj->auth_key_len)
+ memcpy(obj->auth_key.elts_, ptr, obj->auth_key_len);
+ ptr += obj->auth_key_len; remaining -= obj->auth_key_len;
+
+ /* Parse struct cell_extension extensions */
+ result = cell_extension_parse(&obj->extensions, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+
+ /* Parse u8 encrypted[] */
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->encrypted, remaining, {});
+ obj->encrypted.n_ = remaining;
+ if (remaining)
+ memcpy(obj->encrypted.elts_, ptr, remaining);
+ ptr += remaining; remaining -= remaining;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ trunnel_alloc_failed:
+ return -1;
+ fail:
+ result = -1;
+ return result;
+}
+
+ssize_t
+hs_cell_introduce1_parse(hs_cell_introduce1_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = hs_cell_introduce1_new();
+ if (NULL == *output)
+ return -1;
+ result = hs_cell_introduce1_parse_into(*output, input, len_in);
+ if (result < 0) {
+ hs_cell_introduce1_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
+hs_cell_introduce_ack_t *
+hs_cell_introduce_ack_new(void)
+{
+ hs_cell_introduce_ack_t *val = trunnel_calloc(1, sizeof(hs_cell_introduce_ack_t));
+ if (NULL == val)
+ return NULL;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+hs_cell_introduce_ack_clear(hs_cell_introduce_ack_t *obj)
+{
+ (void) obj;
+ cell_extension_free(obj->extensions);
+ obj->extensions = NULL;
+}
+
+void
+hs_cell_introduce_ack_free(hs_cell_introduce_ack_t *obj)
+{
+ if (obj == NULL)
+ return;
+ hs_cell_introduce_ack_clear(obj);
+ trunnel_memwipe(obj, sizeof(hs_cell_introduce_ack_t));
+ trunnel_free_(obj);
+}
+
+uint16_t
+hs_cell_introduce_ack_get_status(const hs_cell_introduce_ack_t *inp)
+{
+ return inp->status;
+}
+int
+hs_cell_introduce_ack_set_status(hs_cell_introduce_ack_t *inp, uint16_t val)
+{
+ if (! ((val == 0 || val == 1 || val == 2))) {
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+ }
+ inp->status = val;
+ return 0;
+}
+struct cell_extension_st *
+hs_cell_introduce_ack_get_extensions(hs_cell_introduce_ack_t *inp)
+{
+ return inp->extensions;
+}
+const struct cell_extension_st *
+hs_cell_introduce_ack_getconst_extensions(const hs_cell_introduce_ack_t *inp)
+{
+ return hs_cell_introduce_ack_get_extensions((hs_cell_introduce_ack_t*) inp);
+}
+int
+hs_cell_introduce_ack_set_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val)
+{
+ if (inp->extensions && inp->extensions != val)
+ cell_extension_free(inp->extensions);
+ return hs_cell_introduce_ack_set0_extensions(inp, val);
+}
+int
+hs_cell_introduce_ack_set0_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val)
+{
+ inp->extensions = val;
+ return 0;
+}
+const char *
+hs_cell_introduce_ack_check(const hs_cell_introduce_ack_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ if (! (obj->status == 0 || obj->status == 1 || obj->status == 2))
+ return "Integer out of bounds";
+ {
+ const char *msg;
+ if (NULL != (msg = cell_extension_check(obj->extensions)))
+ return msg;
+ }
+ return NULL;
+}
+
+ssize_t
+hs_cell_introduce_ack_encoded_len(const hs_cell_introduce_ack_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != hs_cell_introduce_ack_check(obj))
+ return -1;
+
+
+ /* Length of u16 status IN [0, 1, 2] */
+ result += 2;
+
+ /* Length of struct cell_extension extensions */
+ result += cell_extension_encoded_len(obj->extensions);
+ return result;
+}
+int
+hs_cell_introduce_ack_clear_errors(hs_cell_introduce_ack_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+hs_cell_introduce_ack_encode(uint8_t *output, const size_t avail, const hs_cell_introduce_ack_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 = hs_cell_introduce_ack_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = hs_cell_introduce_ack_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u16 status IN [0, 1, 2] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->status));
+ written += 2; ptr += 2;
+
+ /* Encode struct cell_extension extensions */
+ trunnel_assert(written <= avail);
+ result = cell_extension_encode(ptr, avail - written, obj->extensions);
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+
+
+ 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 hs_cell_introduce_ack_parse(), but do not allocate the output
+ * object.
+ */
+static ssize_t
+hs_cell_introduce_ack_parse_into(hs_cell_introduce_ack_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 u16 status IN [0, 1, 2] */
+ CHECK_REMAINING(2, truncated);
+ obj->status = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+ if (! (obj->status == 0 || obj->status == 1 || obj->status == 2))
+ goto fail;
+
+ /* Parse struct cell_extension extensions */
+ result = cell_extension_parse(&obj->extensions, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ fail:
+ result = -1;
+ return result;
+}
+
+ssize_t
+hs_cell_introduce_ack_parse(hs_cell_introduce_ack_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = hs_cell_introduce_ack_new();
+ if (NULL == *output)
+ return -1;
+ result = hs_cell_introduce_ack_parse_into(*output, input, len_in);
+ if (result < 0) {
+ hs_cell_introduce_ack_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
+hs_cell_introduce_encrypted_t *
+hs_cell_introduce_encrypted_new(void)
+{
+ hs_cell_introduce_encrypted_t *val = trunnel_calloc(1, sizeof(hs_cell_introduce_encrypted_t));
+ if (NULL == val)
+ return NULL;
+ val->onion_key_type = 1;
+ return val;
+}
+
+/** Release all storage held inside 'obj', but do not free 'obj'.
+ */
+static void
+hs_cell_introduce_encrypted_clear(hs_cell_introduce_encrypted_t *obj)
+{
+ (void) obj;
+ cell_extension_free(obj->extensions);
+ obj->extensions = NULL;
+ TRUNNEL_DYNARRAY_WIPE(&obj->onion_key);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->onion_key);
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->nspecs); ++idx) {
+ link_specifier_free(TRUNNEL_DYNARRAY_GET(&obj->nspecs, idx));
+ }
+ }
+ TRUNNEL_DYNARRAY_WIPE(&obj->nspecs);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->nspecs);
+ TRUNNEL_DYNARRAY_WIPE(&obj->pad);
+ TRUNNEL_DYNARRAY_CLEAR(&obj->pad);
+}
+
+void
+hs_cell_introduce_encrypted_free(hs_cell_introduce_encrypted_t *obj)
+{
+ if (obj == NULL)
+ return;
+ hs_cell_introduce_encrypted_clear(obj);
+ trunnel_memwipe(obj, sizeof(hs_cell_introduce_encrypted_t));
+ trunnel_free_(obj);
+}
+
+size_t
+hs_cell_introduce_encrypted_getlen_rend_cookie(const hs_cell_introduce_encrypted_t *inp)
+{
+ (void)inp; return TRUNNEL_REND_COOKIE_LEN;
+}
+
+uint8_t
+hs_cell_introduce_encrypted_get_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN);
+ return inp->rend_cookie[idx];
+}
+
+uint8_t
+hs_cell_introduce_encrypted_getconst_rend_cookie(const hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return hs_cell_introduce_encrypted_get_rend_cookie((hs_cell_introduce_encrypted_t*)inp, idx);
+}
+int
+hs_cell_introduce_encrypted_set_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt)
+{
+ trunnel_assert(idx < TRUNNEL_REND_COOKIE_LEN);
+ inp->rend_cookie[idx] = elt;
+ return 0;
+}
+
+uint8_t *
+hs_cell_introduce_encrypted_getarray_rend_cookie(hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->rend_cookie;
+}
+const uint8_t *
+hs_cell_introduce_encrypted_getconstarray_rend_cookie(const hs_cell_introduce_encrypted_t *inp)
+{
+ return (const uint8_t *)hs_cell_introduce_encrypted_getarray_rend_cookie((hs_cell_introduce_encrypted_t*)inp);
+}
+struct cell_extension_st *
+hs_cell_introduce_encrypted_get_extensions(hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->extensions;
+}
+const struct cell_extension_st *
+hs_cell_introduce_encrypted_getconst_extensions(const hs_cell_introduce_encrypted_t *inp)
+{
+ return hs_cell_introduce_encrypted_get_extensions((hs_cell_introduce_encrypted_t*) inp);
+}
+int
+hs_cell_introduce_encrypted_set_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val)
+{
+ if (inp->extensions && inp->extensions != val)
+ cell_extension_free(inp->extensions);
+ return hs_cell_introduce_encrypted_set0_extensions(inp, val);
+}
+int
+hs_cell_introduce_encrypted_set0_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val)
+{
+ inp->extensions = val;
+ return 0;
+}
+uint8_t
+hs_cell_introduce_encrypted_get_onion_key_type(const hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->onion_key_type;
+}
+int
+hs_cell_introduce_encrypted_set_onion_key_type(hs_cell_introduce_encrypted_t *inp, uint8_t val)
+{
+ if (! ((val == 1))) {
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+ }
+ inp->onion_key_type = val;
+ return 0;
+}
+uint16_t
+hs_cell_introduce_encrypted_get_onion_key_len(const hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->onion_key_len;
+}
+int
+hs_cell_introduce_encrypted_set_onion_key_len(hs_cell_introduce_encrypted_t *inp, uint16_t val)
+{
+ inp->onion_key_len = val;
+ return 0;
+}
+size_t
+hs_cell_introduce_encrypted_getlen_onion_key(const hs_cell_introduce_encrypted_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->onion_key);
+}
+
+uint8_t
+hs_cell_introduce_encrypted_get_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->onion_key, idx);
+}
+
+uint8_t
+hs_cell_introduce_encrypted_getconst_onion_key(const hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return hs_cell_introduce_encrypted_get_onion_key((hs_cell_introduce_encrypted_t*)inp, idx);
+}
+int
+hs_cell_introduce_encrypted_set_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->onion_key, idx, elt);
+ return 0;
+}
+int
+hs_cell_introduce_encrypted_add_onion_key(hs_cell_introduce_encrypted_t *inp, uint8_t elt)
+{
+#if SIZE_MAX >= UINT16_MAX
+ if (inp->onion_key.n_ == UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->onion_key, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+hs_cell_introduce_encrypted_getarray_onion_key(hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->onion_key.elts_;
+}
+const uint8_t *
+hs_cell_introduce_encrypted_getconstarray_onion_key(const hs_cell_introduce_encrypted_t *inp)
+{
+ return (const uint8_t *)hs_cell_introduce_encrypted_getarray_onion_key((hs_cell_introduce_encrypted_t*)inp);
+}
+int
+hs_cell_introduce_encrypted_setlen_onion_key(hs_cell_introduce_encrypted_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+#if UINT16_MAX < SIZE_MAX
+ if (newlen > UINT16_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->onion_key.allocated_,
+ &inp->onion_key.n_, inp->onion_key.elts_, newlen,
+ sizeof(inp->onion_key.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->onion_key.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+uint8_t
+hs_cell_introduce_encrypted_get_nspec(const hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->nspec;
+}
+int
+hs_cell_introduce_encrypted_set_nspec(hs_cell_introduce_encrypted_t *inp, uint8_t val)
+{
+ inp->nspec = val;
+ return 0;
+}
+size_t
+hs_cell_introduce_encrypted_getlen_nspecs(const hs_cell_introduce_encrypted_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->nspecs);
+}
+
+struct link_specifier_st *
+hs_cell_introduce_encrypted_get_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->nspecs, idx);
+}
+
+ const struct link_specifier_st *
+hs_cell_introduce_encrypted_getconst_nspecs(const hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return hs_cell_introduce_encrypted_get_nspecs((hs_cell_introduce_encrypted_t*)inp, idx);
+}
+int
+hs_cell_introduce_encrypted_set_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt)
+{
+ link_specifier_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->nspecs, idx);
+ if (oldval && oldval != elt)
+ link_specifier_free(oldval);
+ return hs_cell_introduce_encrypted_set0_nspecs(inp, idx, elt);
+}
+int
+hs_cell_introduce_encrypted_set0_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->nspecs, idx, elt);
+ return 0;
+}
+int
+hs_cell_introduce_encrypted_add_nspecs(hs_cell_introduce_encrypted_t *inp, struct link_specifier_st * elt)
+{
+#if SIZE_MAX >= UINT8_MAX
+ if (inp->nspecs.n_ == UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ TRUNNEL_DYNARRAY_ADD(struct link_specifier_st *, &inp->nspecs, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+struct link_specifier_st * *
+hs_cell_introduce_encrypted_getarray_nspecs(hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->nspecs.elts_;
+}
+const struct link_specifier_st * const *
+hs_cell_introduce_encrypted_getconstarray_nspecs(const hs_cell_introduce_encrypted_t *inp)
+{
+ return (const struct link_specifier_st * const *)hs_cell_introduce_encrypted_getarray_nspecs((hs_cell_introduce_encrypted_t*)inp);
+}
+int
+hs_cell_introduce_encrypted_setlen_nspecs(hs_cell_introduce_encrypted_t *inp, size_t newlen)
+{
+ struct link_specifier_st * *newptr;
+#if UINT8_MAX < SIZE_MAX
+ if (newlen > UINT8_MAX)
+ goto trunnel_alloc_failed;
+#endif
+ newptr = trunnel_dynarray_setlen(&inp->nspecs.allocated_,
+ &inp->nspecs.n_, inp->nspecs.elts_, newlen,
+ sizeof(inp->nspecs.elts_[0]), (trunnel_free_fn_t) link_specifier_free,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->nspecs.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+size_t
+hs_cell_introduce_encrypted_getlen_pad(const hs_cell_introduce_encrypted_t *inp)
+{
+ return TRUNNEL_DYNARRAY_LEN(&inp->pad);
+}
+
+uint8_t
+hs_cell_introduce_encrypted_get_pad(hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return TRUNNEL_DYNARRAY_GET(&inp->pad, idx);
+}
+
+uint8_t
+hs_cell_introduce_encrypted_getconst_pad(const hs_cell_introduce_encrypted_t *inp, size_t idx)
+{
+ return hs_cell_introduce_encrypted_get_pad((hs_cell_introduce_encrypted_t*)inp, idx);
+}
+int
+hs_cell_introduce_encrypted_set_pad(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_SET(&inp->pad, idx, elt);
+ return 0;
+}
+int
+hs_cell_introduce_encrypted_add_pad(hs_cell_introduce_encrypted_t *inp, uint8_t elt)
+{
+ TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->pad, elt, {});
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+
+uint8_t *
+hs_cell_introduce_encrypted_getarray_pad(hs_cell_introduce_encrypted_t *inp)
+{
+ return inp->pad.elts_;
+}
+const uint8_t *
+hs_cell_introduce_encrypted_getconstarray_pad(const hs_cell_introduce_encrypted_t *inp)
+{
+ return (const uint8_t *)hs_cell_introduce_encrypted_getarray_pad((hs_cell_introduce_encrypted_t*)inp);
+}
+int
+hs_cell_introduce_encrypted_setlen_pad(hs_cell_introduce_encrypted_t *inp, size_t newlen)
+{
+ uint8_t *newptr;
+ newptr = trunnel_dynarray_setlen(&inp->pad.allocated_,
+ &inp->pad.n_, inp->pad.elts_, newlen,
+ sizeof(inp->pad.elts_[0]), (trunnel_free_fn_t) NULL,
+ &inp->trunnel_error_code_);
+ if (newlen != 0 && newptr == NULL)
+ goto trunnel_alloc_failed;
+ inp->pad.elts_ = newptr;
+ return 0;
+ trunnel_alloc_failed:
+ TRUNNEL_SET_ERROR_CODE(inp);
+ return -1;
+}
+const char *
+hs_cell_introduce_encrypted_check(const hs_cell_introduce_encrypted_t *obj)
+{
+ if (obj == NULL)
+ return "Object was NULL";
+ if (obj->trunnel_error_code_)
+ return "A set function failed on this object";
+ {
+ const char *msg;
+ if (NULL != (msg = cell_extension_check(obj->extensions)))
+ return msg;
+ }
+ if (! (obj->onion_key_type == 1))
+ return "Integer out of bounds";
+ if (TRUNNEL_DYNARRAY_LEN(&obj->onion_key) != obj->onion_key_len)
+ return "Length mismatch for onion_key";
+ {
+ const char *msg;
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->nspecs); ++idx) {
+ if (NULL != (msg = link_specifier_check(TRUNNEL_DYNARRAY_GET(&obj->nspecs, idx))))
+ return msg;
+ }
+ }
+ if (TRUNNEL_DYNARRAY_LEN(&obj->nspecs) != obj->nspec)
+ return "Length mismatch for nspecs";
+ return NULL;
+}
+
+ssize_t
+hs_cell_introduce_encrypted_encoded_len(const hs_cell_introduce_encrypted_t *obj)
+{
+ ssize_t result = 0;
+
+ if (NULL != hs_cell_introduce_encrypted_check(obj))
+ return -1;
+
+
+ /* Length of u8 rend_cookie[TRUNNEL_REND_COOKIE_LEN] */
+ result += TRUNNEL_REND_COOKIE_LEN;
+
+ /* Length of struct cell_extension extensions */
+ result += cell_extension_encoded_len(obj->extensions);
+
+ /* Length of u8 onion_key_type IN [1] */
+ result += 1;
+
+ /* Length of u16 onion_key_len */
+ result += 2;
+
+ /* Length of u8 onion_key[onion_key_len] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->onion_key);
+
+ /* Length of u8 nspec */
+ result += 1;
+
+ /* Length of struct link_specifier nspecs[nspec] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->nspecs); ++idx) {
+ result += link_specifier_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->nspecs, idx));
+ }
+ }
+
+ /* Length of u8 pad[] */
+ result += TRUNNEL_DYNARRAY_LEN(&obj->pad);
+ return result;
+}
+int
+hs_cell_introduce_encrypted_clear_errors(hs_cell_introduce_encrypted_t *obj)
+{
+ int r = obj->trunnel_error_code_;
+ obj->trunnel_error_code_ = 0;
+ return r;
+}
+ssize_t
+hs_cell_introduce_encrypted_encode(uint8_t *output, const size_t avail, const hs_cell_introduce_encrypted_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 = hs_cell_introduce_encrypted_encoded_len(obj);
+#endif
+
+ if (NULL != (msg = hs_cell_introduce_encrypted_check(obj)))
+ goto check_failed;
+
+#ifdef TRUNNEL_CHECK_ENCODED_LEN
+ trunnel_assert(encoded_len >= 0);
+#endif
+
+ /* Encode u8 rend_cookie[TRUNNEL_REND_COOKIE_LEN] */
+ trunnel_assert(written <= avail);
+ if (avail - written < TRUNNEL_REND_COOKIE_LEN)
+ goto truncated;
+ memcpy(ptr, obj->rend_cookie, TRUNNEL_REND_COOKIE_LEN);
+ written += TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN;
+
+ /* Encode struct cell_extension extensions */
+ trunnel_assert(written <= avail);
+ result = cell_extension_encode(ptr, avail - written, obj->extensions);
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+
+ /* Encode u8 onion_key_type IN [1] */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->onion_key_type));
+ written += 1; ptr += 1;
+
+ /* Encode u16 onion_key_len */
+ trunnel_assert(written <= avail);
+ if (avail - written < 2)
+ goto truncated;
+ trunnel_set_uint16(ptr, trunnel_htons(obj->onion_key_len));
+ written += 2; ptr += 2;
+
+ /* Encode u8 onion_key[onion_key_len] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->onion_key);
+ trunnel_assert(obj->onion_key_len == elt_len);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->onion_key.elts_, elt_len);
+ written += elt_len; ptr += elt_len;
+ }
+
+ /* Encode u8 nspec */
+ trunnel_assert(written <= avail);
+ if (avail - written < 1)
+ goto truncated;
+ trunnel_set_uint8(ptr, (obj->nspec));
+ written += 1; ptr += 1;
+
+ /* Encode struct link_specifier nspecs[nspec] */
+ {
+
+ unsigned idx;
+ for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->nspecs); ++idx) {
+ trunnel_assert(written <= avail);
+ result = link_specifier_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->nspecs, idx));
+ if (result < 0)
+ goto fail; /* XXXXXXX !*/
+ written += result; ptr += result;
+ }
+ }
+
+ /* Encode u8 pad[] */
+ {
+ size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->pad);
+ trunnel_assert(written <= avail);
+ if (avail - written < elt_len)
+ goto truncated;
+ if (elt_len)
+ memcpy(ptr, obj->pad.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 hs_cell_introduce_encrypted_parse(), but do not allocate the
+ * output object.
+ */
+static ssize_t
+hs_cell_introduce_encrypted_parse_into(hs_cell_introduce_encrypted_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 rend_cookie[TRUNNEL_REND_COOKIE_LEN] */
+ CHECK_REMAINING(TRUNNEL_REND_COOKIE_LEN, truncated);
+ memcpy(obj->rend_cookie, ptr, TRUNNEL_REND_COOKIE_LEN);
+ remaining -= TRUNNEL_REND_COOKIE_LEN; ptr += TRUNNEL_REND_COOKIE_LEN;
+
+ /* Parse struct cell_extension extensions */
+ result = cell_extension_parse(&obj->extensions, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+
+ /* Parse u8 onion_key_type IN [1] */
+ CHECK_REMAINING(1, truncated);
+ obj->onion_key_type = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+ if (! (obj->onion_key_type == 1))
+ goto fail;
+
+ /* Parse u16 onion_key_len */
+ CHECK_REMAINING(2, truncated);
+ obj->onion_key_len = trunnel_ntohs(trunnel_get_uint16(ptr));
+ remaining -= 2; ptr += 2;
+
+ /* Parse u8 onion_key[onion_key_len] */
+ CHECK_REMAINING(obj->onion_key_len, truncated);
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->onion_key, obj->onion_key_len, {});
+ obj->onion_key.n_ = obj->onion_key_len;
+ if (obj->onion_key_len)
+ memcpy(obj->onion_key.elts_, ptr, obj->onion_key_len);
+ ptr += obj->onion_key_len; remaining -= obj->onion_key_len;
+
+ /* Parse u8 nspec */
+ CHECK_REMAINING(1, truncated);
+ obj->nspec = (trunnel_get_uint8(ptr));
+ remaining -= 1; ptr += 1;
+
+ /* Parse struct link_specifier nspecs[nspec] */
+ TRUNNEL_DYNARRAY_EXPAND(link_specifier_t *, &obj->nspecs, obj->nspec, {});
+ {
+ link_specifier_t * elt;
+ unsigned idx;
+ for (idx = 0; idx < obj->nspec; ++idx) {
+ result = link_specifier_parse(&elt, ptr, remaining);
+ if (result < 0)
+ goto relay_fail;
+ trunnel_assert((size_t)result <= remaining);
+ remaining -= result; ptr += result;
+ TRUNNEL_DYNARRAY_ADD(link_specifier_t *, &obj->nspecs, elt, {link_specifier_free(elt);});
+ }
+ }
+
+ /* Parse u8 pad[] */
+ TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->pad, remaining, {});
+ obj->pad.n_ = remaining;
+ if (remaining)
+ memcpy(obj->pad.elts_, ptr, remaining);
+ ptr += remaining; remaining -= remaining;
+ trunnel_assert(ptr + remaining == input + len_in);
+ return len_in - remaining;
+
+ truncated:
+ return -2;
+ relay_fail:
+ trunnel_assert(result < 0);
+ return result;
+ trunnel_alloc_failed:
+ return -1;
+ fail:
+ result = -1;
+ return result;
+}
+
+ssize_t
+hs_cell_introduce_encrypted_parse(hs_cell_introduce_encrypted_t **output, const uint8_t *input, const size_t len_in)
+{
+ ssize_t result;
+ *output = hs_cell_introduce_encrypted_new();
+ if (NULL == *output)
+ return -1;
+ result = hs_cell_introduce_encrypted_parse_into(*output, input, len_in);
+ if (result < 0) {
+ hs_cell_introduce_encrypted_free(*output);
+ *output = NULL;
+ }
+ return result;
+}
diff --git a/src/trunnel/hs/cell_introduce1.h b/src/trunnel/hs/cell_introduce1.h
new file mode 100644
index 0000000000..ccd2cda904
--- /dev/null
+++ b/src/trunnel/hs/cell_introduce1.h
@@ -0,0 +1,493 @@
+/* cell_introduce1.h -- generated by by Trunnel v1.5.1.
+ * https://gitweb.torproject.org/trunnel.git
+ * You probably shouldn't edit this file.
+ */
+#ifndef TRUNNEL_CELL_INTRODUCE1_H
+#define TRUNNEL_CELL_INTRODUCE1_H
+
+#include <stdint.h>
+#include "trunnel.h"
+
+struct cell_extension_st;
+struct link_specifier_st;
+#define TRUNNEL_SHA1_LEN 20
+#define TRUNNEL_REND_COOKIE_LEN 20
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRODUCE1)
+struct hs_cell_introduce1_st {
+ uint8_t legacy_key_id[TRUNNEL_SHA1_LEN];
+ uint8_t auth_key_type;
+ uint16_t auth_key_len;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) auth_key;
+ struct cell_extension_st *extensions;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) encrypted;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct hs_cell_introduce1_st hs_cell_introduce1_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRODUCE_ACK)
+struct hs_cell_introduce_ack_st {
+ uint16_t status;
+ struct cell_extension_st *extensions;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct hs_cell_introduce_ack_st hs_cell_introduce_ack_t;
+#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_HS_CELL_INTRODUCE_ENCRYPTED)
+struct hs_cell_introduce_encrypted_st {
+ uint8_t rend_cookie[TRUNNEL_REND_COOKIE_LEN];
+ struct cell_extension_st *extensions;
+ uint8_t onion_key_type;
+ uint16_t onion_key_len;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) onion_key;
+ uint8_t nspec;
+ TRUNNEL_DYNARRAY_HEAD(, struct link_specifier_st *) nspecs;
+ TRUNNEL_DYNARRAY_HEAD(, uint8_t) pad;
+ uint8_t trunnel_error_code_;
+};
+#endif
+typedef struct hs_cell_introduce_encrypted_st hs_cell_introduce_encrypted_t;
+/** Return a newly allocated hs_cell_introduce1 with all elements set
+ * to zero.
+ */
+hs_cell_introduce1_t *hs_cell_introduce1_new(void);
+/** Release all storage held by the hs_cell_introduce1 in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void hs_cell_introduce1_free(hs_cell_introduce1_t *victim);
+/** Try to parse a hs_cell_introduce1 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 hs_cell_introduce1_t. On failure, return -2 if the input
+ * appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t hs_cell_introduce1_parse(hs_cell_introduce1_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * hs_cell_introduce1 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 hs_cell_introduce1_encoded_len(const hs_cell_introduce1_t *obj);
+/** Try to encode the hs_cell_introduce1 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 hs_cell_introduce1_encode(uint8_t *output, size_t avail, const hs_cell_introduce1_t *input);
+/** Check whether the internal state of the hs_cell_introduce1 in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *hs_cell_introduce1_check(const hs_cell_introduce1_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int hs_cell_introduce1_clear_errors(hs_cell_introduce1_t *obj);
+/** Return the (constant) length of the array holding the
+ * legacy_key_id field of the hs_cell_introduce1_t in 'inp'.
+ */
+size_t hs_cell_introduce1_getlen_legacy_key_id(const hs_cell_introduce1_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * legacy_key_id of the hs_cell_introduce1_t in 'inp'.
+ */
+uint8_t hs_cell_introduce1_get_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx);
+/** As hs_cell_introduce1_get_legacy_key_id, but take and return a
+ * const pointer
+ */
+uint8_t hs_cell_introduce1_getconst_legacy_key_id(const hs_cell_introduce1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * legacy_key_id of the hs_cell_introduce1_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int hs_cell_introduce1_set_legacy_key_id(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the TRUNNEL_SHA1_LEN-element array field
+ * legacy_key_id of 'inp'.
+ */
+uint8_t * hs_cell_introduce1_getarray_legacy_key_id(hs_cell_introduce1_t *inp);
+/** As hs_cell_introduce1_get_legacy_key_id, but take and return a
+ * const pointer
+ */
+const uint8_t * hs_cell_introduce1_getconstarray_legacy_key_id(const hs_cell_introduce1_t *inp);
+/** Return the value of the auth_key_type field of the
+ * hs_cell_introduce1_t in 'inp'
+ */
+uint8_t hs_cell_introduce1_get_auth_key_type(const hs_cell_introduce1_t *inp);
+/** Set the value of the auth_key_type field of the
+ * hs_cell_introduce1_t in 'inp' to 'val'. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce1_set_auth_key_type(hs_cell_introduce1_t *inp, uint8_t val);
+/** Return the value of the auth_key_len field of the
+ * hs_cell_introduce1_t in 'inp'
+ */
+uint16_t hs_cell_introduce1_get_auth_key_len(const hs_cell_introduce1_t *inp);
+/** Set the value of the auth_key_len field of the
+ * hs_cell_introduce1_t in 'inp' to 'val'. Return 0 on success; return
+ * -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce1_set_auth_key_len(hs_cell_introduce1_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the auth_key field
+ * of the hs_cell_introduce1_t in 'inp'.
+ */
+size_t hs_cell_introduce1_getlen_auth_key(const hs_cell_introduce1_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * auth_key of the hs_cell_introduce1_t in 'inp'.
+ */
+uint8_t hs_cell_introduce1_get_auth_key(hs_cell_introduce1_t *inp, size_t idx);
+/** As hs_cell_introduce1_get_auth_key, but take and return a const
+ * pointer
+ */
+uint8_t hs_cell_introduce1_getconst_auth_key(const hs_cell_introduce1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * auth_key of the hs_cell_introduce1_t in 'inp', so that it will hold
+ * the value 'elt'.
+ */
+int hs_cell_introduce1_set_auth_key(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field auth_key of
+ * the hs_cell_introduce1_t in 'inp'.
+ */
+int hs_cell_introduce1_add_auth_key(hs_cell_introduce1_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field auth_key of
+ * 'inp'.
+ */
+uint8_t * hs_cell_introduce1_getarray_auth_key(hs_cell_introduce1_t *inp);
+/** As hs_cell_introduce1_get_auth_key, but take and return a const
+ * pointer
+ */
+const uint8_t * hs_cell_introduce1_getconstarray_auth_key(const hs_cell_introduce1_t *inp);
+/** Change the length of the variable-length array field auth_key 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 hs_cell_introduce1_setlen_auth_key(hs_cell_introduce1_t *inp, size_t newlen);
+/** Return the value of the extensions field of the
+ * hs_cell_introduce1_t in 'inp'
+ */
+struct cell_extension_st * hs_cell_introduce1_get_extensions(hs_cell_introduce1_t *inp);
+/** As hs_cell_introduce1_get_extensions, but take and return a const
+ * pointer
+ */
+const struct cell_extension_st * hs_cell_introduce1_getconst_extensions(const hs_cell_introduce1_t *inp);
+/** Set the value of the extensions field of the hs_cell_introduce1_t
+ * in 'inp' to 'val'. Free the old value if any. Steals the
+ * referenceto 'val'.Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int hs_cell_introduce1_set_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val);
+/** As hs_cell_introduce1_set_extensions, but does not free the
+ * previous value.
+ */
+int hs_cell_introduce1_set0_extensions(hs_cell_introduce1_t *inp, struct cell_extension_st *val);
+/** Return the length of the dynamic array holding the encrypted field
+ * of the hs_cell_introduce1_t in 'inp'.
+ */
+size_t hs_cell_introduce1_getlen_encrypted(const hs_cell_introduce1_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * encrypted of the hs_cell_introduce1_t in 'inp'.
+ */
+uint8_t hs_cell_introduce1_get_encrypted(hs_cell_introduce1_t *inp, size_t idx);
+/** As hs_cell_introduce1_get_encrypted, but take and return a const
+ * pointer
+ */
+uint8_t hs_cell_introduce1_getconst_encrypted(const hs_cell_introduce1_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * encrypted of the hs_cell_introduce1_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int hs_cell_introduce1_set_encrypted(hs_cell_introduce1_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field encrypted of
+ * the hs_cell_introduce1_t in 'inp'.
+ */
+int hs_cell_introduce1_add_encrypted(hs_cell_introduce1_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field encrypted of
+ * 'inp'.
+ */
+uint8_t * hs_cell_introduce1_getarray_encrypted(hs_cell_introduce1_t *inp);
+/** As hs_cell_introduce1_get_encrypted, but take and return a const
+ * pointer
+ */
+const uint8_t * hs_cell_introduce1_getconstarray_encrypted(const hs_cell_introduce1_t *inp);
+/** Change the length of the variable-length array field encrypted 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 hs_cell_introduce1_setlen_encrypted(hs_cell_introduce1_t *inp, size_t newlen);
+/** Return a newly allocated hs_cell_introduce_ack with all elements
+ * set to zero.
+ */
+hs_cell_introduce_ack_t *hs_cell_introduce_ack_new(void);
+/** Release all storage held by the hs_cell_introduce_ack in 'victim'.
+ * (Do nothing if 'victim' is NULL.)
+ */
+void hs_cell_introduce_ack_free(hs_cell_introduce_ack_t *victim);
+/** Try to parse a hs_cell_introduce_ack 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 hs_cell_introduce_ack_t. On failure, return -2 if the
+ * input appears truncated, and -1 if the input is otherwise invalid.
+ */
+ssize_t hs_cell_introduce_ack_parse(hs_cell_introduce_ack_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * hs_cell_introduce_ack 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 hs_cell_introduce_ack_encoded_len(const hs_cell_introduce_ack_t *obj);
+/** Try to encode the hs_cell_introduce_ack 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 hs_cell_introduce_ack_encode(uint8_t *output, size_t avail, const hs_cell_introduce_ack_t *input);
+/** Check whether the internal state of the hs_cell_introduce_ack in
+ * 'obj' is consistent. Return NULL if it is, and a short message if
+ * it is not.
+ */
+const char *hs_cell_introduce_ack_check(const hs_cell_introduce_ack_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int hs_cell_introduce_ack_clear_errors(hs_cell_introduce_ack_t *obj);
+/** Return the value of the status field of the
+ * hs_cell_introduce_ack_t in 'inp'
+ */
+uint16_t hs_cell_introduce_ack_get_status(const hs_cell_introduce_ack_t *inp);
+/** Set the value of the status field of the hs_cell_introduce_ack_t
+ * in 'inp' to 'val'. Return 0 on success; return -1 and set the error
+ * code on 'inp' on failure.
+ */
+int hs_cell_introduce_ack_set_status(hs_cell_introduce_ack_t *inp, uint16_t val);
+/** Return the value of the extensions field of the
+ * hs_cell_introduce_ack_t in 'inp'
+ */
+struct cell_extension_st * hs_cell_introduce_ack_get_extensions(hs_cell_introduce_ack_t *inp);
+/** As hs_cell_introduce_ack_get_extensions, but take and return a
+ * const pointer
+ */
+const struct cell_extension_st * hs_cell_introduce_ack_getconst_extensions(const hs_cell_introduce_ack_t *inp);
+/** Set the value of the extensions field of the
+ * hs_cell_introduce_ack_t in 'inp' to 'val'. Free the old value if
+ * any. Steals the referenceto 'val'.Return 0 on success; return -1
+ * and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce_ack_set_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val);
+/** As hs_cell_introduce_ack_set_extensions, but does not free the
+ * previous value.
+ */
+int hs_cell_introduce_ack_set0_extensions(hs_cell_introduce_ack_t *inp, struct cell_extension_st *val);
+/** Return a newly allocated hs_cell_introduce_encrypted with all
+ * elements set to zero.
+ */
+hs_cell_introduce_encrypted_t *hs_cell_introduce_encrypted_new(void);
+/** Release all storage held by the hs_cell_introduce_encrypted in
+ * 'victim'. (Do nothing if 'victim' is NULL.)
+ */
+void hs_cell_introduce_encrypted_free(hs_cell_introduce_encrypted_t *victim);
+/** Try to parse a hs_cell_introduce_encrypted 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 hs_cell_introduce_encrypted_t. On failure, return
+ * -2 if the input appears truncated, and -1 if the input is otherwise
+ * invalid.
+ */
+ssize_t hs_cell_introduce_encrypted_parse(hs_cell_introduce_encrypted_t **output, const uint8_t *input, const size_t len_in);
+/** Return the number of bytes we expect to need to encode the
+ * hs_cell_introduce_encrypted 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 hs_cell_introduce_encrypted_encoded_len(const hs_cell_introduce_encrypted_t *obj);
+/** Try to encode the hs_cell_introduce_encrypted 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 hs_cell_introduce_encrypted_encode(uint8_t *output, size_t avail, const hs_cell_introduce_encrypted_t *input);
+/** Check whether the internal state of the
+ * hs_cell_introduce_encrypted in 'obj' is consistent. Return NULL if
+ * it is, and a short message if it is not.
+ */
+const char *hs_cell_introduce_encrypted_check(const hs_cell_introduce_encrypted_t *obj);
+/** Clear any errors that were set on the object 'obj' by its setter
+ * functions. Return true iff errors were cleared.
+ */
+int hs_cell_introduce_encrypted_clear_errors(hs_cell_introduce_encrypted_t *obj);
+/** Return the (constant) length of the array holding the rend_cookie
+ * field of the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+size_t hs_cell_introduce_encrypted_getlen_rend_cookie(const hs_cell_introduce_encrypted_t *inp);
+/** Return the element at position 'idx' of the fixed array field
+ * rend_cookie of the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+uint8_t hs_cell_introduce_encrypted_get_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** As hs_cell_introduce_encrypted_get_rend_cookie, but take and
+ * return a const pointer
+ */
+uint8_t hs_cell_introduce_encrypted_getconst_rend_cookie(const hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** Change the element at position 'idx' of the fixed array field
+ * rend_cookie of the hs_cell_introduce_encrypted_t in 'inp', so that
+ * it will hold the value 'elt'.
+ */
+int hs_cell_introduce_encrypted_set_rend_cookie(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt);
+/** Return a pointer to the TRUNNEL_REND_COOKIE_LEN-element array
+ * field rend_cookie of 'inp'.
+ */
+uint8_t * hs_cell_introduce_encrypted_getarray_rend_cookie(hs_cell_introduce_encrypted_t *inp);
+/** As hs_cell_introduce_encrypted_get_rend_cookie, but take and
+ * return a const pointer
+ */
+const uint8_t * hs_cell_introduce_encrypted_getconstarray_rend_cookie(const hs_cell_introduce_encrypted_t *inp);
+/** Return the value of the extensions field of the
+ * hs_cell_introduce_encrypted_t in 'inp'
+ */
+struct cell_extension_st * hs_cell_introduce_encrypted_get_extensions(hs_cell_introduce_encrypted_t *inp);
+/** As hs_cell_introduce_encrypted_get_extensions, but take and return
+ * a const pointer
+ */
+const struct cell_extension_st * hs_cell_introduce_encrypted_getconst_extensions(const hs_cell_introduce_encrypted_t *inp);
+/** Set the value of the extensions field of the
+ * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Free the old value
+ * if any. Steals the referenceto 'val'.Return 0 on success; return -1
+ * and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce_encrypted_set_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val);
+/** As hs_cell_introduce_encrypted_set_extensions, but does not free
+ * the previous value.
+ */
+int hs_cell_introduce_encrypted_set0_extensions(hs_cell_introduce_encrypted_t *inp, struct cell_extension_st *val);
+/** Return the value of the onion_key_type field of the
+ * hs_cell_introduce_encrypted_t in 'inp'
+ */
+uint8_t hs_cell_introduce_encrypted_get_onion_key_type(const hs_cell_introduce_encrypted_t *inp);
+/** Set the value of the onion_key_type field of the
+ * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on
+ * success; return -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce_encrypted_set_onion_key_type(hs_cell_introduce_encrypted_t *inp, uint8_t val);
+/** Return the value of the onion_key_len field of the
+ * hs_cell_introduce_encrypted_t in 'inp'
+ */
+uint16_t hs_cell_introduce_encrypted_get_onion_key_len(const hs_cell_introduce_encrypted_t *inp);
+/** Set the value of the onion_key_len field of the
+ * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on
+ * success; return -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce_encrypted_set_onion_key_len(hs_cell_introduce_encrypted_t *inp, uint16_t val);
+/** Return the length of the dynamic array holding the onion_key field
+ * of the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+size_t hs_cell_introduce_encrypted_getlen_onion_key(const hs_cell_introduce_encrypted_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * onion_key of the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+uint8_t hs_cell_introduce_encrypted_get_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** As hs_cell_introduce_encrypted_get_onion_key, but take and return
+ * a const pointer
+ */
+uint8_t hs_cell_introduce_encrypted_getconst_onion_key(const hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * onion_key of the hs_cell_introduce_encrypted_t in 'inp', so that it
+ * will hold the value 'elt'.
+ */
+int hs_cell_introduce_encrypted_set_onion_key(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field onion_key of
+ * the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+int hs_cell_introduce_encrypted_add_onion_key(hs_cell_introduce_encrypted_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field onion_key of
+ * 'inp'.
+ */
+uint8_t * hs_cell_introduce_encrypted_getarray_onion_key(hs_cell_introduce_encrypted_t *inp);
+/** As hs_cell_introduce_encrypted_get_onion_key, but take and return
+ * a const pointer
+ */
+const uint8_t * hs_cell_introduce_encrypted_getconstarray_onion_key(const hs_cell_introduce_encrypted_t *inp);
+/** Change the length of the variable-length array field onion_key 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 hs_cell_introduce_encrypted_setlen_onion_key(hs_cell_introduce_encrypted_t *inp, size_t newlen);
+/** Return the value of the nspec field of the
+ * hs_cell_introduce_encrypted_t in 'inp'
+ */
+uint8_t hs_cell_introduce_encrypted_get_nspec(const hs_cell_introduce_encrypted_t *inp);
+/** Set the value of the nspec field of the
+ * hs_cell_introduce_encrypted_t in 'inp' to 'val'. Return 0 on
+ * success; return -1 and set the error code on 'inp' on failure.
+ */
+int hs_cell_introduce_encrypted_set_nspec(hs_cell_introduce_encrypted_t *inp, uint8_t val);
+/** Return the length of the dynamic array holding the nspecs field of
+ * the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+size_t hs_cell_introduce_encrypted_getlen_nspecs(const hs_cell_introduce_encrypted_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * nspecs of the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+struct link_specifier_st * hs_cell_introduce_encrypted_get_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** As hs_cell_introduce_encrypted_get_nspecs, but take and return a
+ * const pointer
+ */
+ const struct link_specifier_st * hs_cell_introduce_encrypted_getconst_nspecs(const hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * nspecs of the hs_cell_introduce_encrypted_t in 'inp', so that it
+ * will hold the value 'elt'. Free the previous value, if any.
+ */
+int hs_cell_introduce_encrypted_set_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt);
+/** As hs_cell_introduce_encrypted_set_nspecs, but does not free the
+ * previous value.
+ */
+int hs_cell_introduce_encrypted_set0_nspecs(hs_cell_introduce_encrypted_t *inp, size_t idx, struct link_specifier_st * elt);
+/** Append a new element 'elt' to the dynamic array field nspecs of
+ * the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+int hs_cell_introduce_encrypted_add_nspecs(hs_cell_introduce_encrypted_t *inp, struct link_specifier_st * elt);
+/** Return a pointer to the variable-length array field nspecs of
+ * 'inp'.
+ */
+struct link_specifier_st * * hs_cell_introduce_encrypted_getarray_nspecs(hs_cell_introduce_encrypted_t *inp);
+/** As hs_cell_introduce_encrypted_get_nspecs, but take and return a
+ * const pointer
+ */
+const struct link_specifier_st * const * hs_cell_introduce_encrypted_getconstarray_nspecs(const hs_cell_introduce_encrypted_t *inp);
+/** Change the length of the variable-length array field nspecs of
+ * 'inp' to 'newlen'.Fill extra elements with NULL; free removed
+ * elements. Return 0 on success; return -1 and set the error code on
+ * 'inp' on failure.
+ */
+int hs_cell_introduce_encrypted_setlen_nspecs(hs_cell_introduce_encrypted_t *inp, size_t newlen);
+/** Return the length of the dynamic array holding the pad field of
+ * the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+size_t hs_cell_introduce_encrypted_getlen_pad(const hs_cell_introduce_encrypted_t *inp);
+/** Return the element at position 'idx' of the dynamic array field
+ * pad of the hs_cell_introduce_encrypted_t in 'inp'.
+ */
+uint8_t hs_cell_introduce_encrypted_get_pad(hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** As hs_cell_introduce_encrypted_get_pad, but take and return a
+ * const pointer
+ */
+uint8_t hs_cell_introduce_encrypted_getconst_pad(const hs_cell_introduce_encrypted_t *inp, size_t idx);
+/** Change the element at position 'idx' of the dynamic array field
+ * pad of the hs_cell_introduce_encrypted_t in 'inp', so that it will
+ * hold the value 'elt'.
+ */
+int hs_cell_introduce_encrypted_set_pad(hs_cell_introduce_encrypted_t *inp, size_t idx, uint8_t elt);
+/** Append a new element 'elt' to the dynamic array field pad of the
+ * hs_cell_introduce_encrypted_t in 'inp'.
+ */
+int hs_cell_introduce_encrypted_add_pad(hs_cell_introduce_encrypted_t *inp, uint8_t elt);
+/** Return a pointer to the variable-length array field pad of 'inp'.
+ */
+uint8_t * hs_cell_introduce_encrypted_getarray_pad(hs_cell_introduce_encrypted_t *inp);
+/** As hs_cell_introduce_encrypted_get_pad, but take and return a
+ * const pointer
+ */
+const uint8_t * hs_cell_introduce_encrypted_getconstarray_pad(const hs_cell_introduce_encrypted_t *inp);
+/** Change the length of the variable-length array field pad 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 hs_cell_introduce_encrypted_setlen_pad(hs_cell_introduce_encrypted_t *inp, size_t newlen);
+
+
+#endif
diff --git a/src/trunnel/hs/cell_introduce1.trunnel b/src/trunnel/hs/cell_introduce1.trunnel
new file mode 100644
index 0000000000..f7776879cd
--- /dev/null
+++ b/src/trunnel/hs/cell_introduce1.trunnel
@@ -0,0 +1,60 @@
+/*
+ * This contains the definition of the INTRODUCE1 and INTRODUCE_ACK cell for
+ * onion service version 3 and onward. The following format is specified in
+ * proposal 224 section 3.2.
+ */
+
+/* From cell_common.trunnel. */
+extern struct cell_extension;
+/* From ed25519_cert.trunnel. */
+extern struct link_specifier;
+
+const TRUNNEL_SHA1_LEN = 20;
+const TRUNNEL_REND_COOKIE_LEN = 20;
+
+/* INTRODUCE1 payload. See details in section 3.2.1. */
+struct hs_cell_introduce1 {
+ /* Always zeroed. MUST be checked explicitely by the caller. */
+ u8 legacy_key_id[TRUNNEL_SHA1_LEN];
+
+ /* Authentication key material. */
+ u8 auth_key_type IN [0x00, 0x01, 0x02];
+ u16 auth_key_len;
+ u8 auth_key[auth_key_len];
+
+ /* Extension(s). Reserved fields. */
+ struct cell_extension extensions;
+
+ /* Variable length, up to the end of cell. */
+ u8 encrypted[];
+};
+
+/* INTRODUCE_ACK payload. See details in section 3.2.2. */
+struct hs_cell_introduce_ack {
+ /* Status of introduction. */
+ u16 status IN [0x0000, 0x0001, 0x0002];
+
+ /* Extension(s). Reserved fields. */
+ struct cell_extension extensions;
+};
+
+/* Encrypted section of the INTRODUCE1/INTRODUCE2 cell. */
+struct hs_cell_introduce_encrypted {
+ /* Rendezvous cookie. */
+ u8 rend_cookie[TRUNNEL_REND_COOKIE_LEN];
+
+ /* Extension(s). Reserved fields. */
+ struct cell_extension extensions;
+
+ /* Onion key material. */
+ u8 onion_key_type IN [0x01];
+ u16 onion_key_len;
+ u8 onion_key[onion_key_len];
+
+ /* Link specifiers(s) */
+ u8 nspec;
+ struct link_specifier nspecs[nspec];
+
+ /* Optional padding. This might be empty or not. */
+ u8 pad[];
+};
diff --git a/src/trunnel/include.am b/src/trunnel/include.am
index b1448b7cb2..9b26d58615 100644
--- a/src/trunnel/include.am
+++ b/src/trunnel/include.am
@@ -1,4 +1,3 @@
-
noinst_LIBRARIES += \
src/trunnel/libor-trunnel.a
@@ -18,15 +17,21 @@ TRUNNELSOURCES = \
src/ext/trunnel/trunnel.c \
src/trunnel/ed25519_cert.c \
src/trunnel/link_handshake.c \
- src/trunnel/pwbox.c
+ src/trunnel/pwbox.c \
+ src/trunnel/hs/cell_common.c \
+ src/trunnel/hs/cell_establish_intro.c \
+ src/trunnel/hs/cell_introduce1.c
TRUNNELHEADERS = \
src/ext/trunnel/trunnel.h \
src/ext/trunnel/trunnel-impl.h \
- src/trunnel/trunnel-local.h \
+ src/trunnel/trunnel-local.h \
src/trunnel/ed25519_cert.h \
- src/trunnel/link_handshake.h \
- src/trunnel/pwbox.h
+ src/trunnel/link_handshake.h \
+ src/trunnel/pwbox.h \
+ src/trunnel/hs/cell_common.h \
+ src/trunnel/hs/cell_establish_intro.h \
+ src/trunnel/hs/cell_introduce1.h
src_trunnel_libor_trunnel_a_SOURCES = $(TRUNNELSOURCES)
src_trunnel_libor_trunnel_a_CPPFLAGS = -DTRUNNEL_LOCAL_H $(AM_CPPFLAGS)
diff --git a/src/trunnel/link_handshake.c b/src/trunnel/link_handshake.c
index c2717f36bf..887f710d9c 100644
--- a/src/trunnel/link_handshake.c
+++ b/src/trunnel/link_handshake.c
@@ -1,4 +1,4 @@
-/* link_handshake.c -- generated by Trunnel v1.4.6.
+/* link_handshake.c -- generated by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -64,12 +64,17 @@ auth_challenge_cell_getlen_challenge(const auth_challenge_cell_t *inp)
}
uint8_t
-auth_challenge_cell_get_challenge(const auth_challenge_cell_t *inp, size_t idx)
+auth_challenge_cell_get_challenge(auth_challenge_cell_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->challenge[idx];
}
+uint8_t
+auth_challenge_cell_getconst_challenge(const auth_challenge_cell_t *inp, size_t idx)
+{
+ return auth_challenge_cell_get_challenge((auth_challenge_cell_t*)inp, idx);
+}
int
auth_challenge_cell_set_challenge(auth_challenge_cell_t *inp, size_t idx, uint8_t elt)
{
@@ -83,8 +88,13 @@ auth_challenge_cell_getarray_challenge(auth_challenge_cell_t *inp)
{
return inp->challenge;
}
+const uint8_t *
+auth_challenge_cell_getconstarray_challenge(const auth_challenge_cell_t *inp)
+{
+ return (const uint8_t *)auth_challenge_cell_getarray_challenge((auth_challenge_cell_t*)inp);
+}
uint16_t
-auth_challenge_cell_get_n_methods(auth_challenge_cell_t *inp)
+auth_challenge_cell_get_n_methods(const auth_challenge_cell_t *inp)
{
return inp->n_methods;
}
@@ -106,6 +116,11 @@ auth_challenge_cell_get_methods(auth_challenge_cell_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->methods, idx);
}
+uint16_t
+auth_challenge_cell_getconst_methods(const auth_challenge_cell_t *inp, size_t idx)
+{
+ return auth_challenge_cell_get_methods((auth_challenge_cell_t*)inp, idx);
+}
int
auth_challenge_cell_set_methods(auth_challenge_cell_t *inp, size_t idx, uint16_t elt)
{
@@ -131,6 +146,11 @@ auth_challenge_cell_getarray_methods(auth_challenge_cell_t *inp)
{
return inp->methods.elts_;
}
+const uint16_t *
+auth_challenge_cell_getconstarray_methods(const auth_challenge_cell_t *inp)
+{
+ return (const uint16_t *)auth_challenge_cell_getarray_methods((auth_challenge_cell_t*)inp);
+}
int
auth_challenge_cell_setlen_methods(auth_challenge_cell_t *inp, size_t newlen)
{
@@ -342,7 +362,7 @@ auth_ctx_free(auth_ctx_t *obj)
}
uint8_t
-auth_ctx_get_is_ed(auth_ctx_t *inp)
+auth_ctx_get_is_ed(const auth_ctx_t *inp)
{
return inp->is_ed;
}
@@ -382,7 +402,7 @@ certs_cell_cert_free(certs_cell_cert_t *obj)
}
uint8_t
-certs_cell_cert_get_cert_type(certs_cell_cert_t *inp)
+certs_cell_cert_get_cert_type(const certs_cell_cert_t *inp)
{
return inp->cert_type;
}
@@ -393,7 +413,7 @@ certs_cell_cert_set_cert_type(certs_cell_cert_t *inp, uint8_t val)
return 0;
}
uint16_t
-certs_cell_cert_get_cert_len(certs_cell_cert_t *inp)
+certs_cell_cert_get_cert_len(const certs_cell_cert_t *inp)
{
return inp->cert_len;
}
@@ -415,6 +435,11 @@ certs_cell_cert_get_body(certs_cell_cert_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->body, idx);
}
+uint8_t
+certs_cell_cert_getconst_body(const certs_cell_cert_t *inp, size_t idx)
+{
+ return certs_cell_cert_get_body((certs_cell_cert_t*)inp, idx);
+}
int
certs_cell_cert_set_body(certs_cell_cert_t *inp, size_t idx, uint8_t elt)
{
@@ -440,6 +465,11 @@ certs_cell_cert_getarray_body(certs_cell_cert_t *inp)
{
return inp->body.elts_;
}
+const uint8_t *
+certs_cell_cert_getconstarray_body(const certs_cell_cert_t *inp)
+{
+ return (const uint8_t *)certs_cell_cert_getarray_body((certs_cell_cert_t*)inp);
+}
int
certs_cell_cert_setlen_body(certs_cell_cert_t *inp, size_t newlen)
{
@@ -652,12 +682,17 @@ rsa_ed_crosscert_getlen_ed_key(const rsa_ed_crosscert_t *inp)
}
uint8_t
-rsa_ed_crosscert_get_ed_key(const rsa_ed_crosscert_t *inp, size_t idx)
+rsa_ed_crosscert_get_ed_key(rsa_ed_crosscert_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->ed_key[idx];
}
+uint8_t
+rsa_ed_crosscert_getconst_ed_key(const rsa_ed_crosscert_t *inp, size_t idx)
+{
+ return rsa_ed_crosscert_get_ed_key((rsa_ed_crosscert_t*)inp, idx);
+}
int
rsa_ed_crosscert_set_ed_key(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt)
{
@@ -671,8 +706,13 @@ rsa_ed_crosscert_getarray_ed_key(rsa_ed_crosscert_t *inp)
{
return inp->ed_key;
}
+const uint8_t *
+rsa_ed_crosscert_getconstarray_ed_key(const rsa_ed_crosscert_t *inp)
+{
+ return (const uint8_t *)rsa_ed_crosscert_getarray_ed_key((rsa_ed_crosscert_t*)inp);
+}
uint32_t
-rsa_ed_crosscert_get_expiration(rsa_ed_crosscert_t *inp)
+rsa_ed_crosscert_get_expiration(const rsa_ed_crosscert_t *inp)
{
return inp->expiration;
}
@@ -688,7 +728,7 @@ rsa_ed_crosscert_get_end_of_signed(const rsa_ed_crosscert_t *inp)
return inp->end_of_signed;
}
uint8_t
-rsa_ed_crosscert_get_sig_len(rsa_ed_crosscert_t *inp)
+rsa_ed_crosscert_get_sig_len(const rsa_ed_crosscert_t *inp)
{
return inp->sig_len;
}
@@ -710,6 +750,11 @@ rsa_ed_crosscert_get_sig(rsa_ed_crosscert_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->sig, idx);
}
+uint8_t
+rsa_ed_crosscert_getconst_sig(const rsa_ed_crosscert_t *inp, size_t idx)
+{
+ return rsa_ed_crosscert_get_sig((rsa_ed_crosscert_t*)inp, idx);
+}
int
rsa_ed_crosscert_set_sig(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt)
{
@@ -735,6 +780,11 @@ rsa_ed_crosscert_getarray_sig(rsa_ed_crosscert_t *inp)
{
return inp->sig.elts_;
}
+const uint8_t *
+rsa_ed_crosscert_getconstarray_sig(const rsa_ed_crosscert_t *inp)
+{
+ return (const uint8_t *)rsa_ed_crosscert_getarray_sig((rsa_ed_crosscert_t*)inp);
+}
int
rsa_ed_crosscert_setlen_sig(rsa_ed_crosscert_t *inp, size_t newlen)
{
@@ -964,12 +1014,17 @@ auth1_getlen_type(const auth1_t *inp)
}
uint8_t
-auth1_get_type(const auth1_t *inp, size_t idx)
+auth1_get_type(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 8);
return inp->type[idx];
}
+uint8_t
+auth1_getconst_type(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_type((auth1_t*)inp, idx);
+}
int
auth1_set_type(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -983,6 +1038,11 @@ auth1_getarray_type(auth1_t *inp)
{
return inp->type;
}
+const uint8_t *
+auth1_getconstarray_type(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_type((auth1_t*)inp);
+}
size_t
auth1_getlen_cid(const auth1_t *inp)
{
@@ -990,12 +1050,17 @@ auth1_getlen_cid(const auth1_t *inp)
}
uint8_t
-auth1_get_cid(const auth1_t *inp, size_t idx)
+auth1_get_cid(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->cid[idx];
}
+uint8_t
+auth1_getconst_cid(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_cid((auth1_t*)inp, idx);
+}
int
auth1_set_cid(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1009,6 +1074,11 @@ auth1_getarray_cid(auth1_t *inp)
{
return inp->cid;
}
+const uint8_t *
+auth1_getconstarray_cid(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_cid((auth1_t*)inp);
+}
size_t
auth1_getlen_sid(const auth1_t *inp)
{
@@ -1016,12 +1086,17 @@ auth1_getlen_sid(const auth1_t *inp)
}
uint8_t
-auth1_get_sid(const auth1_t *inp, size_t idx)
+auth1_get_sid(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->sid[idx];
}
+uint8_t
+auth1_getconst_sid(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_sid((auth1_t*)inp, idx);
+}
int
auth1_set_sid(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1035,6 +1110,11 @@ auth1_getarray_sid(auth1_t *inp)
{
return inp->sid;
}
+const uint8_t *
+auth1_getconstarray_sid(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_sid((auth1_t*)inp);
+}
size_t
auth1_getlen_u1_cid_ed(const auth1_t *inp)
{
@@ -1042,12 +1122,17 @@ auth1_getlen_u1_cid_ed(const auth1_t *inp)
}
uint8_t
-auth1_get_u1_cid_ed(const auth1_t *inp, size_t idx)
+auth1_get_u1_cid_ed(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->u1_cid_ed[idx];
}
+uint8_t
+auth1_getconst_u1_cid_ed(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_u1_cid_ed((auth1_t*)inp, idx);
+}
int
auth1_set_u1_cid_ed(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1061,6 +1146,11 @@ auth1_getarray_u1_cid_ed(auth1_t *inp)
{
return inp->u1_cid_ed;
}
+const uint8_t *
+auth1_getconstarray_u1_cid_ed(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_u1_cid_ed((auth1_t*)inp);
+}
size_t
auth1_getlen_u1_sid_ed(const auth1_t *inp)
{
@@ -1068,12 +1158,17 @@ auth1_getlen_u1_sid_ed(const auth1_t *inp)
}
uint8_t
-auth1_get_u1_sid_ed(const auth1_t *inp, size_t idx)
+auth1_get_u1_sid_ed(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->u1_sid_ed[idx];
}
+uint8_t
+auth1_getconst_u1_sid_ed(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_u1_sid_ed((auth1_t*)inp, idx);
+}
int
auth1_set_u1_sid_ed(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1087,6 +1182,11 @@ auth1_getarray_u1_sid_ed(auth1_t *inp)
{
return inp->u1_sid_ed;
}
+const uint8_t *
+auth1_getconstarray_u1_sid_ed(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_u1_sid_ed((auth1_t*)inp);
+}
size_t
auth1_getlen_slog(const auth1_t *inp)
{
@@ -1094,12 +1194,17 @@ auth1_getlen_slog(const auth1_t *inp)
}
uint8_t
-auth1_get_slog(const auth1_t *inp, size_t idx)
+auth1_get_slog(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->slog[idx];
}
+uint8_t
+auth1_getconst_slog(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_slog((auth1_t*)inp, idx);
+}
int
auth1_set_slog(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1113,6 +1218,11 @@ auth1_getarray_slog(auth1_t *inp)
{
return inp->slog;
}
+const uint8_t *
+auth1_getconstarray_slog(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_slog((auth1_t*)inp);
+}
size_t
auth1_getlen_clog(const auth1_t *inp)
{
@@ -1120,12 +1230,17 @@ auth1_getlen_clog(const auth1_t *inp)
}
uint8_t
-auth1_get_clog(const auth1_t *inp, size_t idx)
+auth1_get_clog(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->clog[idx];
}
+uint8_t
+auth1_getconst_clog(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_clog((auth1_t*)inp, idx);
+}
int
auth1_set_clog(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1139,6 +1254,11 @@ auth1_getarray_clog(auth1_t *inp)
{
return inp->clog;
}
+const uint8_t *
+auth1_getconstarray_clog(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_clog((auth1_t*)inp);
+}
size_t
auth1_getlen_scert(const auth1_t *inp)
{
@@ -1146,12 +1266,17 @@ auth1_getlen_scert(const auth1_t *inp)
}
uint8_t
-auth1_get_scert(const auth1_t *inp, size_t idx)
+auth1_get_scert(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->scert[idx];
}
+uint8_t
+auth1_getconst_scert(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_scert((auth1_t*)inp, idx);
+}
int
auth1_set_scert(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1165,6 +1290,11 @@ auth1_getarray_scert(auth1_t *inp)
{
return inp->scert;
}
+const uint8_t *
+auth1_getconstarray_scert(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_scert((auth1_t*)inp);
+}
size_t
auth1_getlen_tlssecrets(const auth1_t *inp)
{
@@ -1172,12 +1302,17 @@ auth1_getlen_tlssecrets(const auth1_t *inp)
}
uint8_t
-auth1_get_tlssecrets(const auth1_t *inp, size_t idx)
+auth1_get_tlssecrets(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->tlssecrets[idx];
}
+uint8_t
+auth1_getconst_tlssecrets(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_tlssecrets((auth1_t*)inp, idx);
+}
int
auth1_set_tlssecrets(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1191,6 +1326,11 @@ auth1_getarray_tlssecrets(auth1_t *inp)
{
return inp->tlssecrets;
}
+const uint8_t *
+auth1_getconstarray_tlssecrets(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_tlssecrets((auth1_t*)inp);
+}
const uint8_t *
auth1_get_end_of_fixed_part(const auth1_t *inp)
{
@@ -1203,12 +1343,17 @@ auth1_getlen_rand(const auth1_t *inp)
}
uint8_t
-auth1_get_rand(const auth1_t *inp, size_t idx)
+auth1_get_rand(auth1_t *inp, size_t idx)
{
trunnel_assert(idx < 24);
return inp->rand[idx];
}
+uint8_t
+auth1_getconst_rand(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_rand((auth1_t*)inp, idx);
+}
int
auth1_set_rand(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1222,6 +1367,11 @@ auth1_getarray_rand(auth1_t *inp)
{
return inp->rand;
}
+const uint8_t *
+auth1_getconstarray_rand(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_rand((auth1_t*)inp);
+}
const uint8_t *
auth1_get_end_of_signed(const auth1_t *inp)
{
@@ -1239,6 +1389,11 @@ auth1_get_sig(auth1_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->sig, idx);
}
+uint8_t
+auth1_getconst_sig(const auth1_t *inp, size_t idx)
+{
+ return auth1_get_sig((auth1_t*)inp, idx);
+}
int
auth1_set_sig(auth1_t *inp, size_t idx, uint8_t elt)
{
@@ -1260,6 +1415,11 @@ auth1_getarray_sig(auth1_t *inp)
{
return inp->sig.elts_;
}
+const uint8_t *
+auth1_getconstarray_sig(const auth1_t *inp)
+{
+ return (const uint8_t *)auth1_getarray_sig((auth1_t*)inp);
+}
int
auth1_setlen_sig(auth1_t *inp, size_t newlen)
{
@@ -1647,7 +1807,7 @@ certs_cell_free(certs_cell_t *obj)
}
uint8_t
-certs_cell_get_n_certs(certs_cell_t *inp)
+certs_cell_get_n_certs(const certs_cell_t *inp)
{
return inp->n_certs;
}
@@ -1669,6 +1829,11 @@ certs_cell_get_certs(certs_cell_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->certs, idx);
}
+ const struct certs_cell_cert_st *
+certs_cell_getconst_certs(const certs_cell_t *inp, size_t idx)
+{
+ return certs_cell_get_certs((certs_cell_t*)inp, idx);
+}
int
certs_cell_set_certs(certs_cell_t *inp, size_t idx, struct certs_cell_cert_st * elt)
{
@@ -1702,6 +1867,11 @@ certs_cell_getarray_certs(certs_cell_t *inp)
{
return inp->certs.elts_;
}
+const struct certs_cell_cert_st * const *
+certs_cell_getconstarray_certs(const certs_cell_t *inp)
+{
+ return (const struct certs_cell_cert_st * const *)certs_cell_getarray_certs((certs_cell_t*)inp);
+}
int
certs_cell_setlen_certs(certs_cell_t *inp, size_t newlen)
{
diff --git a/src/trunnel/link_handshake.h b/src/trunnel/link_handshake.h
index 54611b96e8..418662631a 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.4.6.
+/* link_handshake.h -- generated by by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -121,7 +121,11 @@ size_t auth_challenge_cell_getlen_challenge(const auth_challenge_cell_t *inp);
/** Return the element at position 'idx' of the fixed array field
* challenge of the auth_challenge_cell_t in 'inp'.
*/
-uint8_t auth_challenge_cell_get_challenge(const auth_challenge_cell_t *inp, size_t idx);
+uint8_t auth_challenge_cell_get_challenge(auth_challenge_cell_t *inp, size_t idx);
+/** As auth_challenge_cell_get_challenge, but take and return a const
+ * pointer
+ */
+uint8_t auth_challenge_cell_getconst_challenge(const auth_challenge_cell_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* challenge of the auth_challenge_cell_t in 'inp', so that it will
* hold the value 'elt'.
@@ -130,10 +134,14 @@ int auth_challenge_cell_set_challenge(auth_challenge_cell_t *inp, size_t idx, ui
/** Return a pointer to the 32-element array field challenge of 'inp'.
*/
uint8_t * auth_challenge_cell_getarray_challenge(auth_challenge_cell_t *inp);
+/** As auth_challenge_cell_get_challenge, but take and return a const
+ * pointer
+ */
+const uint8_t * auth_challenge_cell_getconstarray_challenge(const auth_challenge_cell_t *inp);
/** Return the value of the n_methods field of the
* auth_challenge_cell_t in 'inp'
*/
-uint16_t auth_challenge_cell_get_n_methods(auth_challenge_cell_t *inp);
+uint16_t auth_challenge_cell_get_n_methods(const auth_challenge_cell_t *inp);
/** Set the value of the n_methods field of the auth_challenge_cell_t
* in 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -147,6 +155,10 @@ size_t auth_challenge_cell_getlen_methods(const auth_challenge_cell_t *inp);
* methods of the auth_challenge_cell_t in 'inp'.
*/
uint16_t auth_challenge_cell_get_methods(auth_challenge_cell_t *inp, size_t idx);
+/** As auth_challenge_cell_get_methods, but take and return a const
+ * pointer
+ */
+uint16_t auth_challenge_cell_getconst_methods(const auth_challenge_cell_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* methods of the auth_challenge_cell_t in 'inp', so that it will hold
* the value 'elt'.
@@ -160,6 +172,10 @@ int auth_challenge_cell_add_methods(auth_challenge_cell_t *inp, uint16_t elt);
* 'inp'.
*/
uint16_t * auth_challenge_cell_getarray_methods(auth_challenge_cell_t *inp);
+/** As auth_challenge_cell_get_methods, but take and return a const
+ * pointer
+ */
+const uint16_t * auth_challenge_cell_getconstarray_methods(const auth_challenge_cell_t *inp);
/** Change the length of the variable-length array field methods of
* 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success;
* return -1 and set the error code on 'inp' on failure.
@@ -174,7 +190,7 @@ auth_ctx_t *auth_ctx_new(void);
void auth_ctx_free(auth_ctx_t *victim);
/** Return the value of the is_ed field of the auth_ctx_t in 'inp'
*/
-uint8_t auth_ctx_get_is_ed(auth_ctx_t *inp);
+uint8_t auth_ctx_get_is_ed(const auth_ctx_t *inp);
/** Set the value of the is_ed field of the auth_ctx_t in 'inp' to
* 'val'. Return 0 on success; return -1 and set the error code on
* 'inp' on failure.
@@ -219,7 +235,7 @@ int certs_cell_cert_clear_errors(certs_cell_cert_t *obj);
/** Return the value of the cert_type field of the certs_cell_cert_t
* in 'inp'
*/
-uint8_t certs_cell_cert_get_cert_type(certs_cell_cert_t *inp);
+uint8_t certs_cell_cert_get_cert_type(const certs_cell_cert_t *inp);
/** Set the value of the cert_type field of the certs_cell_cert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -228,7 +244,7 @@ int certs_cell_cert_set_cert_type(certs_cell_cert_t *inp, uint8_t val);
/** Return the value of the cert_len field of the certs_cell_cert_t in
* 'inp'
*/
-uint16_t certs_cell_cert_get_cert_len(certs_cell_cert_t *inp);
+uint16_t certs_cell_cert_get_cert_len(const certs_cell_cert_t *inp);
/** Set the value of the cert_len field of the certs_cell_cert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -242,6 +258,9 @@ size_t certs_cell_cert_getlen_body(const certs_cell_cert_t *inp);
* body of the certs_cell_cert_t in 'inp'.
*/
uint8_t certs_cell_cert_get_body(certs_cell_cert_t *inp, size_t idx);
+/** As certs_cell_cert_get_body, but take and return a const pointer
+ */
+uint8_t certs_cell_cert_getconst_body(const certs_cell_cert_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* body of the certs_cell_cert_t in 'inp', so that it will hold the
* value 'elt'.
@@ -254,6 +273,9 @@ int certs_cell_cert_add_body(certs_cell_cert_t *inp, uint8_t elt);
/** Return a pointer to the variable-length array field body of 'inp'.
*/
uint8_t * certs_cell_cert_getarray_body(certs_cell_cert_t *inp);
+/** As certs_cell_cert_get_body, but take and return a const pointer
+ */
+const uint8_t * certs_cell_cert_getconstarray_body(const certs_cell_cert_t *inp);
/** Change the length of the variable-length array field body of 'inp'
* to 'newlen'.Fill extra elements with 0. Return 0 on success; return
* -1 and set the error code on 'inp' on failure.
@@ -302,7 +324,11 @@ size_t rsa_ed_crosscert_getlen_ed_key(const rsa_ed_crosscert_t *inp);
/** Return the element at position 'idx' of the fixed array field
* ed_key of the rsa_ed_crosscert_t in 'inp'.
*/
-uint8_t rsa_ed_crosscert_get_ed_key(const rsa_ed_crosscert_t *inp, size_t idx);
+uint8_t rsa_ed_crosscert_get_ed_key(rsa_ed_crosscert_t *inp, size_t idx);
+/** As rsa_ed_crosscert_get_ed_key, but take and return a const
+ * pointer
+ */
+uint8_t rsa_ed_crosscert_getconst_ed_key(const rsa_ed_crosscert_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* ed_key of the rsa_ed_crosscert_t in 'inp', so that it will hold the
* value 'elt'.
@@ -311,10 +337,14 @@ int rsa_ed_crosscert_set_ed_key(rsa_ed_crosscert_t *inp, size_t idx, uint8_t elt
/** Return a pointer to the 32-element array field ed_key of 'inp'.
*/
uint8_t * rsa_ed_crosscert_getarray_ed_key(rsa_ed_crosscert_t *inp);
+/** As rsa_ed_crosscert_get_ed_key, but take and return a const
+ * pointer
+ */
+const uint8_t * rsa_ed_crosscert_getconstarray_ed_key(const rsa_ed_crosscert_t *inp);
/** Return the value of the expiration field of the rsa_ed_crosscert_t
* in 'inp'
*/
-uint32_t rsa_ed_crosscert_get_expiration(rsa_ed_crosscert_t *inp);
+uint32_t rsa_ed_crosscert_get_expiration(const rsa_ed_crosscert_t *inp);
/** Set the value of the expiration field of the rsa_ed_crosscert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -326,7 +356,7 @@ const uint8_t * rsa_ed_crosscert_get_end_of_signed(const rsa_ed_crosscert_t *inp
/** Return the value of the sig_len field of the rsa_ed_crosscert_t in
* 'inp'
*/
-uint8_t rsa_ed_crosscert_get_sig_len(rsa_ed_crosscert_t *inp);
+uint8_t rsa_ed_crosscert_get_sig_len(const rsa_ed_crosscert_t *inp);
/** Set the value of the sig_len field of the rsa_ed_crosscert_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -340,6 +370,9 @@ size_t rsa_ed_crosscert_getlen_sig(const rsa_ed_crosscert_t *inp);
* sig of the rsa_ed_crosscert_t in 'inp'.
*/
uint8_t rsa_ed_crosscert_get_sig(rsa_ed_crosscert_t *inp, size_t idx);
+/** As rsa_ed_crosscert_get_sig, but take and return a const pointer
+ */
+uint8_t rsa_ed_crosscert_getconst_sig(const rsa_ed_crosscert_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* sig of the rsa_ed_crosscert_t in 'inp', so that it will hold the
* value 'elt'.
@@ -352,6 +385,9 @@ int rsa_ed_crosscert_add_sig(rsa_ed_crosscert_t *inp, uint8_t elt);
/** Return a pointer to the variable-length array field sig of 'inp'.
*/
uint8_t * rsa_ed_crosscert_getarray_sig(rsa_ed_crosscert_t *inp);
+/** As rsa_ed_crosscert_get_sig, but take and return a const pointer
+ */
+const uint8_t * rsa_ed_crosscert_getconstarray_sig(const rsa_ed_crosscert_t *inp);
/** Change the length of the variable-length array field sig of 'inp'
* to 'newlen'.Fill extra elements with 0. Return 0 on success; return
* -1 and set the error code on 'inp' on failure.
@@ -398,7 +434,10 @@ size_t auth1_getlen_type(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field type
* of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_type(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_type(auth1_t *inp, size_t idx);
+/** As auth1_get_type, but take and return a const pointer
+ */
+uint8_t auth1_getconst_type(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field type
* of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -406,6 +445,9 @@ int auth1_set_type(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 8-element array field type of 'inp'.
*/
uint8_t * auth1_getarray_type(auth1_t *inp);
+/** As auth1_get_type, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_type(const auth1_t *inp);
/** Return the (constant) length of the array holding the cid field of
* the auth1_t in 'inp'.
*/
@@ -413,7 +455,10 @@ size_t auth1_getlen_cid(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field cid
* of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_cid(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_cid(auth1_t *inp, size_t idx);
+/** As auth1_get_cid, but take and return a const pointer
+ */
+uint8_t auth1_getconst_cid(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field cid
* of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -421,6 +466,9 @@ int auth1_set_cid(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field cid of 'inp'.
*/
uint8_t * auth1_getarray_cid(auth1_t *inp);
+/** As auth1_get_cid, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_cid(const auth1_t *inp);
/** Return the (constant) length of the array holding the sid field of
* the auth1_t in 'inp'.
*/
@@ -428,7 +476,10 @@ size_t auth1_getlen_sid(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field sid
* of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_sid(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_sid(auth1_t *inp, size_t idx);
+/** As auth1_get_sid, but take and return a const pointer
+ */
+uint8_t auth1_getconst_sid(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field sid
* of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -436,6 +487,9 @@ int auth1_set_sid(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field sid of 'inp'.
*/
uint8_t * auth1_getarray_sid(auth1_t *inp);
+/** As auth1_get_sid, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_sid(const auth1_t *inp);
/** Return the (constant) length of the array holding the u1_cid_ed
* field of the auth1_t in 'inp'.
*/
@@ -443,7 +497,10 @@ size_t auth1_getlen_u1_cid_ed(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field
* u1_cid_ed of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_u1_cid_ed(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_u1_cid_ed(auth1_t *inp, size_t idx);
+/** As auth1_get_u1_cid_ed, but take and return a const pointer
+ */
+uint8_t auth1_getconst_u1_cid_ed(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* u1_cid_ed of the auth1_t in 'inp', so that it will hold the value
* 'elt'.
@@ -452,6 +509,9 @@ int auth1_set_u1_cid_ed(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field u1_cid_ed of 'inp'.
*/
uint8_t * auth1_getarray_u1_cid_ed(auth1_t *inp);
+/** As auth1_get_u1_cid_ed, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_u1_cid_ed(const auth1_t *inp);
/** Return the (constant) length of the array holding the u1_sid_ed
* field of the auth1_t in 'inp'.
*/
@@ -459,7 +519,10 @@ size_t auth1_getlen_u1_sid_ed(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field
* u1_sid_ed of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_u1_sid_ed(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_u1_sid_ed(auth1_t *inp, size_t idx);
+/** As auth1_get_u1_sid_ed, but take and return a const pointer
+ */
+uint8_t auth1_getconst_u1_sid_ed(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* u1_sid_ed of the auth1_t in 'inp', so that it will hold the value
* 'elt'.
@@ -468,6 +531,9 @@ int auth1_set_u1_sid_ed(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field u1_sid_ed of 'inp'.
*/
uint8_t * auth1_getarray_u1_sid_ed(auth1_t *inp);
+/** As auth1_get_u1_sid_ed, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_u1_sid_ed(const auth1_t *inp);
/** Return the (constant) length of the array holding the slog field
* of the auth1_t in 'inp'.
*/
@@ -475,7 +541,10 @@ size_t auth1_getlen_slog(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field slog
* of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_slog(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_slog(auth1_t *inp, size_t idx);
+/** As auth1_get_slog, but take and return a const pointer
+ */
+uint8_t auth1_getconst_slog(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field slog
* of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -483,6 +552,9 @@ int auth1_set_slog(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field slog of 'inp'.
*/
uint8_t * auth1_getarray_slog(auth1_t *inp);
+/** As auth1_get_slog, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_slog(const auth1_t *inp);
/** Return the (constant) length of the array holding the clog field
* of the auth1_t in 'inp'.
*/
@@ -490,7 +562,10 @@ size_t auth1_getlen_clog(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field clog
* of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_clog(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_clog(auth1_t *inp, size_t idx);
+/** As auth1_get_clog, but take and return a const pointer
+ */
+uint8_t auth1_getconst_clog(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field clog
* of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -498,6 +573,9 @@ int auth1_set_clog(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field clog of 'inp'.
*/
uint8_t * auth1_getarray_clog(auth1_t *inp);
+/** As auth1_get_clog, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_clog(const auth1_t *inp);
/** Return the (constant) length of the array holding the scert field
* of the auth1_t in 'inp'.
*/
@@ -505,7 +583,10 @@ size_t auth1_getlen_scert(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field
* scert of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_scert(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_scert(auth1_t *inp, size_t idx);
+/** As auth1_get_scert, but take and return a const pointer
+ */
+uint8_t auth1_getconst_scert(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* scert of the auth1_t in 'inp', so that it will hold the value
* 'elt'.
@@ -514,6 +595,9 @@ int auth1_set_scert(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field scert of 'inp'.
*/
uint8_t * auth1_getarray_scert(auth1_t *inp);
+/** As auth1_get_scert, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_scert(const auth1_t *inp);
/** Return the (constant) length of the array holding the tlssecrets
* field of the auth1_t in 'inp'.
*/
@@ -521,7 +605,10 @@ size_t auth1_getlen_tlssecrets(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field
* tlssecrets of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_tlssecrets(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_tlssecrets(auth1_t *inp, size_t idx);
+/** As auth1_get_tlssecrets, but take and return a const pointer
+ */
+uint8_t auth1_getconst_tlssecrets(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field
* tlssecrets of the auth1_t in 'inp', so that it will hold the value
* 'elt'.
@@ -531,6 +618,9 @@ int auth1_set_tlssecrets(auth1_t *inp, size_t idx, uint8_t elt);
* 'inp'.
*/
uint8_t * auth1_getarray_tlssecrets(auth1_t *inp);
+/** As auth1_get_tlssecrets, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_tlssecrets(const auth1_t *inp);
/** Return the position for end_of_fixed_part when we parsed this
* object
*/
@@ -542,7 +632,10 @@ size_t auth1_getlen_rand(const auth1_t *inp);
/** Return the element at position 'idx' of the fixed array field rand
* of the auth1_t in 'inp'.
*/
-uint8_t auth1_get_rand(const auth1_t *inp, size_t idx);
+uint8_t auth1_get_rand(auth1_t *inp, size_t idx);
+/** As auth1_get_rand, but take and return a const pointer
+ */
+uint8_t auth1_getconst_rand(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field rand
* of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -550,6 +643,9 @@ int auth1_set_rand(auth1_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 24-element array field rand of 'inp'.
*/
uint8_t * auth1_getarray_rand(auth1_t *inp);
+/** As auth1_get_rand, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_rand(const auth1_t *inp);
/** Return the position for end_of_signed when we parsed this object
*/
const uint8_t * auth1_get_end_of_signed(const auth1_t *inp);
@@ -561,6 +657,9 @@ size_t auth1_getlen_sig(const auth1_t *inp);
* sig of the auth1_t in 'inp'.
*/
uint8_t auth1_get_sig(auth1_t *inp, size_t idx);
+/** As auth1_get_sig, but take and return a const pointer
+ */
+uint8_t auth1_getconst_sig(const auth1_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* sig of the auth1_t in 'inp', so that it will hold the value 'elt'.
*/
@@ -572,6 +671,9 @@ int auth1_add_sig(auth1_t *inp, uint8_t elt);
/** Return a pointer to the variable-length array field sig of 'inp'.
*/
uint8_t * auth1_getarray_sig(auth1_t *inp);
+/** As auth1_get_sig, but take and return a const pointer
+ */
+const uint8_t * auth1_getconstarray_sig(const auth1_t *inp);
/** Change the length of the variable-length array field sig of 'inp'
* to 'newlen'.Fill extra elements with 0. Return 0 on success; return
* -1 and set the error code on 'inp' on failure.
@@ -613,7 +715,7 @@ const char *certs_cell_check(const certs_cell_t *obj);
int certs_cell_clear_errors(certs_cell_t *obj);
/** Return the value of the n_certs field of the certs_cell_t in 'inp'
*/
-uint8_t certs_cell_get_n_certs(certs_cell_t *inp);
+uint8_t certs_cell_get_n_certs(const certs_cell_t *inp);
/** Set the value of the n_certs field of the certs_cell_t in 'inp' to
* 'val'. Return 0 on success; return -1 and set the error code on
* 'inp' on failure.
@@ -627,6 +729,9 @@ size_t certs_cell_getlen_certs(const certs_cell_t *inp);
* certs of the certs_cell_t in 'inp'.
*/
struct certs_cell_cert_st * certs_cell_get_certs(certs_cell_t *inp, size_t idx);
+/** As certs_cell_get_certs, but take and return a const pointer
+ */
+ const struct certs_cell_cert_st * certs_cell_getconst_certs(const certs_cell_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* certs of the certs_cell_t in 'inp', so that it will hold the value
* 'elt'. Free the previous value, if any.
@@ -643,6 +748,9 @@ int certs_cell_add_certs(certs_cell_t *inp, struct certs_cell_cert_st * elt);
* 'inp'.
*/
struct certs_cell_cert_st * * certs_cell_getarray_certs(certs_cell_t *inp);
+/** As certs_cell_get_certs, but take and return a const pointer
+ */
+const struct certs_cell_cert_st * const * certs_cell_getconstarray_certs(const certs_cell_t *inp);
/** Change the length of the variable-length array field certs of
* 'inp' to 'newlen'.Fill extra elements with NULL; free removed
* elements. Return 0 on success; return -1 and set the error code on
diff --git a/src/trunnel/pwbox.c b/src/trunnel/pwbox.c
index 62662a9369..f4b910bdab 100644
--- a/src/trunnel/pwbox.c
+++ b/src/trunnel/pwbox.c
@@ -1,4 +1,4 @@
-/* pwbox.c -- generated by Trunnel v1.4.6.
+/* pwbox.c -- generated by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -62,7 +62,7 @@ pwbox_encoded_free(pwbox_encoded_t *obj)
}
uint32_t
-pwbox_encoded_get_fixedbytes0(pwbox_encoded_t *inp)
+pwbox_encoded_get_fixedbytes0(const pwbox_encoded_t *inp)
{
return inp->fixedbytes0;
}
@@ -77,7 +77,7 @@ pwbox_encoded_set_fixedbytes0(pwbox_encoded_t *inp, uint32_t val)
return 0;
}
uint32_t
-pwbox_encoded_get_fixedbytes1(pwbox_encoded_t *inp)
+pwbox_encoded_get_fixedbytes1(const pwbox_encoded_t *inp)
{
return inp->fixedbytes1;
}
@@ -92,7 +92,7 @@ pwbox_encoded_set_fixedbytes1(pwbox_encoded_t *inp, uint32_t val)
return 0;
}
uint8_t
-pwbox_encoded_get_header_len(pwbox_encoded_t *inp)
+pwbox_encoded_get_header_len(const pwbox_encoded_t *inp)
{
return inp->header_len;
}
@@ -114,6 +114,11 @@ pwbox_encoded_get_skey_header(pwbox_encoded_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->skey_header, idx);
}
+uint8_t
+pwbox_encoded_getconst_skey_header(const pwbox_encoded_t *inp, size_t idx)
+{
+ return pwbox_encoded_get_skey_header((pwbox_encoded_t*)inp, idx);
+}
int
pwbox_encoded_set_skey_header(pwbox_encoded_t *inp, size_t idx, uint8_t elt)
{
@@ -139,6 +144,11 @@ pwbox_encoded_getarray_skey_header(pwbox_encoded_t *inp)
{
return inp->skey_header.elts_;
}
+const uint8_t *
+pwbox_encoded_getconstarray_skey_header(const pwbox_encoded_t *inp)
+{
+ return (const uint8_t *)pwbox_encoded_getarray_skey_header((pwbox_encoded_t*)inp);
+}
int
pwbox_encoded_setlen_skey_header(pwbox_encoded_t *inp, size_t newlen)
{
@@ -166,12 +176,17 @@ pwbox_encoded_getlen_iv(const pwbox_encoded_t *inp)
}
uint8_t
-pwbox_encoded_get_iv(const pwbox_encoded_t *inp, size_t idx)
+pwbox_encoded_get_iv(pwbox_encoded_t *inp, size_t idx)
{
trunnel_assert(idx < 16);
return inp->iv[idx];
}
+uint8_t
+pwbox_encoded_getconst_iv(const pwbox_encoded_t *inp, size_t idx)
+{
+ return pwbox_encoded_get_iv((pwbox_encoded_t*)inp, idx);
+}
int
pwbox_encoded_set_iv(pwbox_encoded_t *inp, size_t idx, uint8_t elt)
{
@@ -185,6 +200,11 @@ pwbox_encoded_getarray_iv(pwbox_encoded_t *inp)
{
return inp->iv;
}
+const uint8_t *
+pwbox_encoded_getconstarray_iv(const pwbox_encoded_t *inp)
+{
+ return (const uint8_t *)pwbox_encoded_getarray_iv((pwbox_encoded_t*)inp);
+}
size_t
pwbox_encoded_getlen_data(const pwbox_encoded_t *inp)
{
@@ -197,6 +217,11 @@ pwbox_encoded_get_data(pwbox_encoded_t *inp, size_t idx)
return TRUNNEL_DYNARRAY_GET(&inp->data, idx);
}
+uint8_t
+pwbox_encoded_getconst_data(const pwbox_encoded_t *inp, size_t idx)
+{
+ return pwbox_encoded_get_data((pwbox_encoded_t*)inp, idx);
+}
int
pwbox_encoded_set_data(pwbox_encoded_t *inp, size_t idx, uint8_t elt)
{
@@ -218,6 +243,11 @@ pwbox_encoded_getarray_data(pwbox_encoded_t *inp)
{
return inp->data.elts_;
}
+const uint8_t *
+pwbox_encoded_getconstarray_data(const pwbox_encoded_t *inp)
+{
+ return (const uint8_t *)pwbox_encoded_getarray_data((pwbox_encoded_t*)inp);
+}
int
pwbox_encoded_setlen_data(pwbox_encoded_t *inp, size_t newlen)
{
@@ -241,12 +271,17 @@ pwbox_encoded_getlen_hmac(const pwbox_encoded_t *inp)
}
uint8_t
-pwbox_encoded_get_hmac(const pwbox_encoded_t *inp, size_t idx)
+pwbox_encoded_get_hmac(pwbox_encoded_t *inp, size_t idx)
{
trunnel_assert(idx < 32);
return inp->hmac[idx];
}
+uint8_t
+pwbox_encoded_getconst_hmac(const pwbox_encoded_t *inp, size_t idx)
+{
+ return pwbox_encoded_get_hmac((pwbox_encoded_t*)inp, idx);
+}
int
pwbox_encoded_set_hmac(pwbox_encoded_t *inp, size_t idx, uint8_t elt)
{
@@ -260,6 +295,11 @@ pwbox_encoded_getarray_hmac(pwbox_encoded_t *inp)
{
return inp->hmac;
}
+const uint8_t *
+pwbox_encoded_getconstarray_hmac(const pwbox_encoded_t *inp)
+{
+ return (const uint8_t *)pwbox_encoded_getarray_hmac((pwbox_encoded_t*)inp);
+}
const char *
pwbox_encoded_check(const pwbox_encoded_t *obj)
{
diff --git a/src/trunnel/pwbox.h b/src/trunnel/pwbox.h
index 77a813d123..939b3c41a7 100644
--- a/src/trunnel/pwbox.h
+++ b/src/trunnel/pwbox.h
@@ -1,4 +1,4 @@
-/* pwbox.h -- generated by by Trunnel v1.4.6.
+/* pwbox.h -- generated by by Trunnel v1.5.1.
* https://gitweb.torproject.org/trunnel.git
* You probably shouldn't edit this file.
*/
@@ -61,7 +61,7 @@ int pwbox_encoded_clear_errors(pwbox_encoded_t *obj);
/** Return the value of the fixedbytes0 field of the pwbox_encoded_t
* in 'inp'
*/
-uint32_t pwbox_encoded_get_fixedbytes0(pwbox_encoded_t *inp);
+uint32_t pwbox_encoded_get_fixedbytes0(const pwbox_encoded_t *inp);
/** Set the value of the fixedbytes0 field of the pwbox_encoded_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -70,7 +70,7 @@ int pwbox_encoded_set_fixedbytes0(pwbox_encoded_t *inp, uint32_t val);
/** Return the value of the fixedbytes1 field of the pwbox_encoded_t
* in 'inp'
*/
-uint32_t pwbox_encoded_get_fixedbytes1(pwbox_encoded_t *inp);
+uint32_t pwbox_encoded_get_fixedbytes1(const pwbox_encoded_t *inp);
/** Set the value of the fixedbytes1 field of the pwbox_encoded_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -79,7 +79,7 @@ int pwbox_encoded_set_fixedbytes1(pwbox_encoded_t *inp, uint32_t val);
/** Return the value of the header_len field of the pwbox_encoded_t in
* 'inp'
*/
-uint8_t pwbox_encoded_get_header_len(pwbox_encoded_t *inp);
+uint8_t pwbox_encoded_get_header_len(const pwbox_encoded_t *inp);
/** Set the value of the header_len field of the pwbox_encoded_t in
* 'inp' to 'val'. Return 0 on success; return -1 and set the error
* code on 'inp' on failure.
@@ -93,6 +93,10 @@ size_t pwbox_encoded_getlen_skey_header(const pwbox_encoded_t *inp);
* skey_header of the pwbox_encoded_t in 'inp'.
*/
uint8_t pwbox_encoded_get_skey_header(pwbox_encoded_t *inp, size_t idx);
+/** As pwbox_encoded_get_skey_header, but take and return a const
+ * pointer
+ */
+uint8_t pwbox_encoded_getconst_skey_header(const pwbox_encoded_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* skey_header of the pwbox_encoded_t in 'inp', so that it will hold
* the value 'elt'.
@@ -106,6 +110,10 @@ int pwbox_encoded_add_skey_header(pwbox_encoded_t *inp, uint8_t elt);
* 'inp'.
*/
uint8_t * pwbox_encoded_getarray_skey_header(pwbox_encoded_t *inp);
+/** As pwbox_encoded_get_skey_header, but take and return a const
+ * pointer
+ */
+const uint8_t * pwbox_encoded_getconstarray_skey_header(const pwbox_encoded_t *inp);
/** Change the length of the variable-length array field skey_header
* of 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on
* success; return -1 and set the error code on 'inp' on failure.
@@ -118,7 +126,10 @@ size_t pwbox_encoded_getlen_iv(const pwbox_encoded_t *inp);
/** Return the element at position 'idx' of the fixed array field iv
* of the pwbox_encoded_t in 'inp'.
*/
-uint8_t pwbox_encoded_get_iv(const pwbox_encoded_t *inp, size_t idx);
+uint8_t pwbox_encoded_get_iv(pwbox_encoded_t *inp, size_t idx);
+/** As pwbox_encoded_get_iv, but take and return a const pointer
+ */
+uint8_t pwbox_encoded_getconst_iv(const pwbox_encoded_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field iv
* of the pwbox_encoded_t in 'inp', so that it will hold the value
* 'elt'.
@@ -127,6 +138,9 @@ int pwbox_encoded_set_iv(pwbox_encoded_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 16-element array field iv of 'inp'.
*/
uint8_t * pwbox_encoded_getarray_iv(pwbox_encoded_t *inp);
+/** As pwbox_encoded_get_iv, but take and return a const pointer
+ */
+const uint8_t * pwbox_encoded_getconstarray_iv(const pwbox_encoded_t *inp);
/** Return the length of the dynamic array holding the data field of
* the pwbox_encoded_t in 'inp'.
*/
@@ -135,6 +149,9 @@ size_t pwbox_encoded_getlen_data(const pwbox_encoded_t *inp);
* data of the pwbox_encoded_t in 'inp'.
*/
uint8_t pwbox_encoded_get_data(pwbox_encoded_t *inp, size_t idx);
+/** As pwbox_encoded_get_data, but take and return a const pointer
+ */
+uint8_t pwbox_encoded_getconst_data(const pwbox_encoded_t *inp, size_t idx);
/** Change the element at position 'idx' of the dynamic array field
* data of the pwbox_encoded_t in 'inp', so that it will hold the
* value 'elt'.
@@ -147,6 +164,9 @@ int pwbox_encoded_add_data(pwbox_encoded_t *inp, uint8_t elt);
/** Return a pointer to the variable-length array field data of 'inp'.
*/
uint8_t * pwbox_encoded_getarray_data(pwbox_encoded_t *inp);
+/** As pwbox_encoded_get_data, but take and return a const pointer
+ */
+const uint8_t * pwbox_encoded_getconstarray_data(const pwbox_encoded_t *inp);
/** Change the length of the variable-length array field data of 'inp'
* to 'newlen'.Fill extra elements with 0. Return 0 on success; return
* -1 and set the error code on 'inp' on failure.
@@ -159,7 +179,10 @@ size_t pwbox_encoded_getlen_hmac(const pwbox_encoded_t *inp);
/** Return the element at position 'idx' of the fixed array field hmac
* of the pwbox_encoded_t in 'inp'.
*/
-uint8_t pwbox_encoded_get_hmac(const pwbox_encoded_t *inp, size_t idx);
+uint8_t pwbox_encoded_get_hmac(pwbox_encoded_t *inp, size_t idx);
+/** As pwbox_encoded_get_hmac, but take and return a const pointer
+ */
+uint8_t pwbox_encoded_getconst_hmac(const pwbox_encoded_t *inp, size_t idx);
/** Change the element at position 'idx' of the fixed array field hmac
* of the pwbox_encoded_t in 'inp', so that it will hold the value
* 'elt'.
@@ -168,6 +191,9 @@ int pwbox_encoded_set_hmac(pwbox_encoded_t *inp, size_t idx, uint8_t elt);
/** Return a pointer to the 32-element array field hmac of 'inp'.
*/
uint8_t * pwbox_encoded_getarray_hmac(pwbox_encoded_t *inp);
+/** As pwbox_encoded_get_hmac, but take and return a const pointer
+ */
+const uint8_t * pwbox_encoded_getconstarray_hmac(const pwbox_encoded_t *inp);
#endif
diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h
index bfe0262968..0b09a5ca50 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.2.9.14-dev"
+#define VERSION "0.3.0.13-dev"