Merge branch 'dev'

This commit is contained in:
Duke Leto
2020-10-06 07:29:49 -04:00
34 changed files with 3143 additions and 486 deletions

View File

@@ -2,8 +2,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
AC_PREREQ([2.60])
define(_CLIENT_VERSION_MAJOR, 3)
define(_CLIENT_VERSION_MINOR, 5)
define(_CLIENT_VERSION_REVISION, 0)
define(_CLIENT_VERSION_BUILD, 5)
define(_CLIENT_VERSION_REVISION, 1)
define(_CLIENT_VERSION_BUILD, 50)
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))
define(_CLIENT_VERSION_IS_RELEASE, true)

View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Copyright (c) 2019-2020 The Hush developers
# Released under the GPLv3
#TODO: autodect version number, error handling
FILE="hush-3.5.0-linux-amd64.tar"
mkdir build
cp sapling*.params build/
cd src
cp komodod komodo-cli komodo-tx hushd hush-cli hush-tx hush-smart-chain ../build
cd ../build
tar -f $FILE -c *
gzip $FILE

View File

@@ -1,9 +1,8 @@
package=openssl
$(package)_version=1.1.1a
$(package)_download_path=https://www.openssl.org/source/old/1.1.1
$(package)_version=1.1.1h
$(package)_download_path=https://www.openssl.org/source
$(package)_file_name=$(package)-$($(package)_version).tar.gz
$(package)_sha256_hash=fc20130f8b7cbd2fb918b2f14e2f429e109c31ddd0fb38fc5d71d9ffed3f9f41
$(package)_patches=ssl_fix.patch
$(package)_sha256_hash=5c9ca8774bd7b03e5784f26ae9e9e6d749c9da2438545077e6b3d755a06595d9
define $(package)_set_vars
$(package)_config_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)"
@@ -14,39 +13,40 @@ $(package)_config_opts+=no-async
$(package)_config_opts+=no-bf
$(package)_config_opts+=no-blake2
$(package)_config_opts+=no-camellia
$(package)_config_opts+=no-capieng
#$(package)_config_opts+=no-capieng
$(package)_config_opts+=no-cast
$(package)_config_opts+=no-chacha
#$(package)_config_opts+=no-chacha
$(package)_config_opts+=no-cmac
$(package)_config_opts+=no-cms
$(package)_config_opts+=no-comp
#$(package)_config_opts+=no-comp
$(package)_config_opts+=no-crypto-mdebug
$(package)_config_opts+=no-crypto-mdebug-backtrace
$(package)_config_opts+=no-ct
#$(package)_config_opts+=no-ct
#$(package)_config_opts+=no-des
$(package)_config_opts+=no-dgram
$(package)_config_opts+=no-dsa
#$(package)_config_opts+=no-dsa
$(package)_config_opts+=no-dso
$(package)_config_opts+=no-dtls
$(package)_config_opts+=no-dtls1
$(package)_config_opts+=no-dtls1-method
$(package)_config_opts+=no-dynamic-engine
$(package)_config_opts+=no-ec2m
$(package)_config_opts+=no-ec_nistp_64_gcc_128
#$(package)_config_opts+=no-ec2m
#$(package)_config_opts+=no-ec_nistp_64_gcc_128
$(package)_config_opts+=no-egd
$(package)_config_opts+=no-engine
$(package)_config_opts+=no-err
#$(package)_config_opts+=no-err
$(package)_config_opts+=no-gost
$(package)_config_opts+=no-heartbeats
$(package)_config_opts+=no-idea
#$(package)_config_opts+=no-idea
$(package)_config_opts+=no-md2
$(package)_config_opts+=no-md4
$(package)_config_opts+=no-mdc2
$(package)_config_opts+=no-multiblock
$(package)_config_opts+=no-nextprotoneg
$(package)_config_opts+=no-ocb
$(package)_config_opts+=no-ocsp
$(package)_config_opts+=no-poly1305
$(package)_config_opts+=no-posix-io
#$(package)_config_opts+=no-ocsp
#$(package)_config_opts+=no-poly1305
#$(package)_config_opts+=no-posix-io
$(package)_config_opts+=no-psk
$(package)_config_opts+=no-rc2
$(package)_config_opts+=no-rc4
@@ -58,21 +58,24 @@ $(package)_config_opts+=no-scrypt
$(package)_config_opts+=no-sctp
$(package)_config_opts+=no-seed
$(package)_config_opts+=no-shared
#$(package)_config_opts+=no-sock
$(package)_config_opts+=no-srp
$(package)_config_opts+=no-srtp
$(package)_config_opts+=no-ssl
$(package)_config_opts+=no-ssl3
$(package)_config_opts+=no-ssl3-method
$(package)_config_opts+=no-ssl-trace
$(package)_config_opts+=no-stdio
$(package)_config_opts+=no-tls1
$(package)_config_opts+=no-tls1-method
#$(package)_config_opts+=no-stdio
#$(package)_config_opts+=no-tls
#$(package)_config_opts+=no-tls1
#$(package)_config_opts+=no-tls1-method
$(package)_config_opts+=no-ts
$(package)_config_opts+=no-ui
$(package)_config_opts+=no-unit-test
$(package)_config_opts+=no-weak-ssl-ciphers
$(package)_config_opts+=no-whirlpool
$(package)_config_opts+=no-zlib
$(package)_config_opts+=no-zlib-dynamic
#$(package)_config_opts+=no-zlib
#$(package)_config_opts+=no-zlib-dynamic
$(package)_config_opts+=$($(package)_cflags) $($(package)_cppflags)
$(package)_config_opts+=-DPURIFY
$(package)_config_opts_linux=-fPIC -Wa,--noexecstack
@@ -89,9 +92,8 @@ $(package)_config_opts_i686_mingw32=mingw
endef
define $(package)_preprocess_cmds
sed -i.old 's/built on: $date/built on: not available/' util/mkbuildinf.pl && \
sed -i.old "s|\"engines\", \"apps\", \"test\"|\"engines\"|" Configure && \
patch -p1 < $($(package)_patch_dir)/ssl_fix.patch
sed -i.old 's/built on: $$$$date/built on: date not available/' util/mkbuildinf.pl && \
sed -i.old "s|\"engines\", \"apps\", \"test\"|\"engines\"|" Configure
endef
define $(package)_config_cmds

View File

@@ -1,273 +0,0 @@
From f725fe5b4b6504df08e30f5194d321c3025e2336 Mon Sep 17 00:00:00 2001
From: Matt Caswell <matt@openssl.org>
Date: Tue, 20 Nov 2018 15:32:55 +0000
Subject: [PATCH] Fix a RUN_ONCE bug
We have a number of instances where there are multiple "init" functions for
a single CRYPTO_ONCE variable, e.g. to load config automatically or to not
load config automatically. Unfortunately the RUN_ONCE mechanism was not
correctly giving the right return value where an alternative init function
was being used.
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/7983)
---
crypto/init.c | 38 +++++++++-----
include/internal/thread_once.h | 92 ++++++++++++++++++++++++++++++++++
ssl/ssl_init.c | 6 ++-
3 files changed, 121 insertions(+), 15 deletions(-)
diff --git a/crypto/init.c b/crypto/init.c
index 209d1a483da..f20a12f069a 100644
--- a/crypto/init.c
+++ b/crypto/init.c
@@ -177,12 +177,6 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_nodelete)
static CRYPTO_ONCE load_crypto_strings = CRYPTO_ONCE_STATIC_INIT;
static int load_crypto_strings_inited = 0;
-DEFINE_RUN_ONCE_STATIC(ossl_init_no_load_crypto_strings)
-{
- /* Do nothing in this case */
- return 1;
-}
-
DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_strings)
{
int ret = 1;
@@ -201,6 +195,13 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_crypto_strings)
return ret;
}
+DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_load_crypto_strings,
+ ossl_init_load_crypto_strings)
+{
+ /* Do nothing in this case */
+ return 1;
+}
+
static CRYPTO_ONCE add_all_ciphers = CRYPTO_ONCE_STATIC_INIT;
DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_ciphers)
{
@@ -218,6 +219,13 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_ciphers)
return 1;
}
+DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_add_all_ciphers,
+ ossl_init_add_all_ciphers)
+{
+ /* Do nothing */
+ return 1;
+}
+
static CRYPTO_ONCE add_all_digests = CRYPTO_ONCE_STATIC_INIT;
DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_digests)
{
@@ -235,7 +243,8 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_add_all_digests)
return 1;
}
-DEFINE_RUN_ONCE_STATIC(ossl_init_no_add_algs)
+DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_add_all_digests,
+ ossl_init_add_all_digests)
{
/* Do nothing */
return 1;
@@ -255,7 +264,7 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_config)
config_inited = 1;
return 1;
}
-DEFINE_RUN_ONCE_STATIC(ossl_init_no_config)
+DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_config, ossl_init_config)
{
#ifdef OPENSSL_INIT_DEBUG
fprintf(stderr,
@@ -595,8 +604,9 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
return 0;
if ((opts & OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS)
- && !RUN_ONCE(&load_crypto_strings,
- ossl_init_no_load_crypto_strings))
+ && !RUN_ONCE_ALT(&load_crypto_strings,
+ ossl_init_no_load_crypto_strings,
+ ossl_init_load_crypto_strings))
return 0;
if ((opts & OPENSSL_INIT_LOAD_CRYPTO_STRINGS)
@@ -604,7 +614,8 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
return 0;
if ((opts & OPENSSL_INIT_NO_ADD_ALL_CIPHERS)
- && !RUN_ONCE(&add_all_ciphers, ossl_init_no_add_algs))
+ && !RUN_ONCE_ALT(&add_all_ciphers, ossl_init_no_add_all_ciphers,
+ ossl_init_add_all_ciphers))
return 0;
if ((opts & OPENSSL_INIT_ADD_ALL_CIPHERS)
@@ -612,7 +623,8 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
return 0;
if ((opts & OPENSSL_INIT_NO_ADD_ALL_DIGESTS)
- && !RUN_ONCE(&add_all_digests, ossl_init_no_add_algs))
+ && !RUN_ONCE_ALT(&add_all_digests, ossl_init_no_add_all_digests,
+ ossl_init_add_all_digests))
return 0;
if ((opts & OPENSSL_INIT_ADD_ALL_DIGESTS)
@@ -624,7 +636,7 @@ int OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings)
return 0;
if ((opts & OPENSSL_INIT_NO_LOAD_CONFIG)
- && !RUN_ONCE(&config, ossl_init_no_config))
+ && !RUN_ONCE_ALT(&config, ossl_init_no_config, ossl_init_config))
return 0;
if (opts & OPENSSL_INIT_LOAD_CONFIG) {
diff --git a/include/internal/thread_once.h b/include/internal/thread_once.h
index 224244353ab..e268a959ef3 100644
--- a/include/internal/thread_once.h
+++ b/include/internal/thread_once.h
@@ -9,6 +9,20 @@
#include <openssl/crypto.h>
+/*
+ * DEFINE_RUN_ONCE: Define an initialiser function that should be run exactly
+ * once. It takes no arguments and returns and int result (1 for success or
+ * 0 for failure). Typical usage might be:
+ *
+ * DEFINE_RUN_ONCE(myinitfunc)
+ * {
+ * do_some_initialisation();
+ * if (init_is_successful())
+ * return 1;
+ *
+ * return 0;
+ * }
+ */
#define DEFINE_RUN_ONCE(init) \
static int init(void); \
int init##_ossl_ret_ = 0; \
@@ -17,10 +31,30 @@
init##_ossl_ret_ = init(); \
} \
static int init(void)
+
+/*
+ * DECLARE_RUN_ONCE: Declare an initialiser function that should be run exactly
+ * once that has been defined in another file via DEFINE_RUN_ONCE().
+ */
#define DECLARE_RUN_ONCE(init) \
extern int init##_ossl_ret_; \
void init##_ossl_(void);
+/*
+ * DEFINE_RUN_ONCE_STATIC: Define an initialiser function that should be run
+ * exactly once. This function will be declared as static within the file. It
+ * takes no arguments and returns and int result (1 for success or 0 for
+ * failure). Typical usage might be:
+ *
+ * DEFINE_RUN_ONCE_STATIC(myinitfunc)
+ * {
+ * do_some_initialisation();
+ * if (init_is_successful())
+ * return 1;
+ *
+ * return 0;
+ * }
+ */
#define DEFINE_RUN_ONCE_STATIC(init) \
static int init(void); \
static int init##_ossl_ret_ = 0; \
@@ -30,6 +64,46 @@
} \
static int init(void)
+/*
+ * DEFINE_RUN_ONCE_STATIC_ALT: Define an alternative initialiser function. This
+ * function will be declared as static within the file. It takes no arguments
+ * and returns an int result (1 for success or 0 for failure). An alternative
+ * initialiser function is expected to be associated with a primary initialiser
+ * function defined via DEFINE_ONCE_STATIC where both functions use the same
+ * CRYPTO_ONCE object to synchronise. Where an alternative initialiser function
+ * is used only one of the primary or the alternative initialiser function will
+ * ever be called - and that function will be called exactly once. Definitition
+ * of an alternative initialiser function MUST occur AFTER the definition of the
+ * primary initialiser function.
+ *
+ * Typical usage might be:
+ *
+ * DEFINE_RUN_ONCE_STATIC(myinitfunc)
+ * {
+ * do_some_initialisation();
+ * if (init_is_successful())
+ * return 1;
+ *
+ * return 0;
+ * }
+ *
+ * DEFINE_RUN_ONCE_STATIC_ALT(myaltinitfunc, myinitfunc)
+ * {
+ * do_some_alternative_initialisation();
+ * if (init_is_successful())
+ * return 1;
+ *
+ * return 0;
+ * }
+ */
+#define DEFINE_RUN_ONCE_STATIC_ALT(initalt, init) \
+ static int initalt(void); \
+ static void initalt##_ossl_(void) \
+ { \
+ init##_ossl_ret_ = initalt(); \
+ } \
+ static int initalt(void)
+
/*
* RUN_ONCE - use CRYPTO_THREAD_run_once, and check if the init succeeded
* @once: pointer to static object of type CRYPTO_ONCE
@@ -43,3 +117,21 @@
*/
#define RUN_ONCE(once, init) \
(CRYPTO_THREAD_run_once(once, init##_ossl_) ? init##_ossl_ret_ : 0)
+
+/*
+ * RUN_ONCE_ALT - use CRYPTO_THREAD_run_once, to run an alternative initialiser
+ * function and check if that initialisation succeeded
+ * @once: pointer to static object of type CRYPTO_ONCE
+ * @initalt: alternative initialiser function name that was previously given to
+ * DEFINE_RUN_ONCE_STATIC_ALT. This function must return 1 for
+ * success or 0 for failure.
+ * @init: primary initialiser function name that was previously given to
+ * DEFINE_RUN_ONCE_STATIC. This function must return 1 for success or
+ * 0 for failure.
+ *
+ * The return value is 1 on success (*) or 0 in case of error.
+ *
+ * (*) by convention, since the init function must return 1 on success.
+ */
+#define RUN_ONCE_ALT(once, initalt, init) \
+ (CRYPTO_THREAD_run_once(once, initalt##_ossl_) ? init##_ossl_ret_ : 0)
diff --git a/ssl/ssl_init.c b/ssl/ssl_init.c
index c0ccb9304a6..96526472c57 100644
--- a/ssl/ssl_init.c
+++ b/ssl/ssl_init.c
@@ -134,7 +134,8 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_load_ssl_strings)
return 1;
}
-DEFINE_RUN_ONCE_STATIC(ossl_init_no_load_ssl_strings)
+DEFINE_RUN_ONCE_STATIC_ALT(ossl_init_no_load_ssl_strings,
+ ossl_init_load_ssl_strings)
{
/* Do nothing in this case */
return 1;
@@ -207,7 +208,8 @@ int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS * settings)
return 0;
if ((opts & OPENSSL_INIT_NO_LOAD_SSL_STRINGS)
- && !RUN_ONCE(&ssl_strings, ossl_init_no_load_ssl_strings))
+ && !RUN_ONCE_ALT(&ssl_strings, ossl_init_no_load_ssl_strings,
+ ossl_init_load_ssl_strings))
return 0;
if ((opts & OPENSSL_INIT_LOAD_SSL_STRINGS)

View File

@@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.10.
.TH HUSH-CLI "1" "September 2020" "hush-cli v3.5.0" "User Commands"
.TH HUSH-CLI "1" "October 2020" "hush-cli v3.5.1" "User Commands"
.SH NAME
hush-cli \- manual page for hush-cli v3.5.0
hush-cli \- manual page for hush-cli v3.5.1
.SH DESCRIPTION
Hush RPC client version v3.5.0\-beta6\-cb09e7fed\-dirty
Hush RPC client version v3.5.1\-dd8ab3df6
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.
@@ -71,7 +71,7 @@ Timeout in seconds during HTTP requests, or 0 for no timeout. (default:
Read extra arguments from standard input, one per line until EOF/Ctrl\-D
(recommended for sensitive information such as passphrases)
.SH COPYRIGHT
Hush Daemon version v3.5.0-beta6-cb09e7fed-dirty
Hush Daemon version v3.5.1-dd8ab3df6
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.

View File

@@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.10.
.TH HUSH-TX "1" "September 2020" "hush-tx v3.5.0" "User Commands"
.TH HUSH-TX "1" "October 2020" "hush-tx v3.5.1" "User Commands"
.SH NAME
hush-tx \- manual page for hush-tx v3.5.0
hush-tx \- manual page for hush-tx v3.5.1
.SH DESCRIPTION
Hush komodo\-tx utility version v3.5.0\-beta6\-cb09e7fed\-dirty
Hush komodo\-tx utility version v3.5.1\-dd8ab3df6
.SS "Usage:"
.TP
komodo\-tx [options] <hex\-tx> [commands]
@@ -84,7 +84,7 @@ set=NAME:JSON\-STRING
.IP
Set register NAME to given JSON\-STRING
.SH COPYRIGHT
Hush Daemon version v3.5.0-beta6-cb09e7fed-dirty
Hush Daemon version v3.5.1-dd8ab3df6
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.

View File

@@ -1,10 +1,10 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.10.
.TH HUSHD "1" "September 2020" "hushd v3.5.0" "User Commands"
.TH HUSHD "1" "October 2020" "hushd v3.5.1" "User Commands"
.SH NAME
hushd \- manual page for hushd v3.5.0
hushd \- manual page for hushd v3.5.1
.SH DESCRIPTION
Found binary: ./komodod
Hush Daemon version v3.5.0\-beta6\-cb09e7fed\-dirty
Hush Daemon version v3.5.1\-dd8ab3df6
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.
@@ -145,6 +145,12 @@ Connection options:
.IP
Add a node to connect to and attempt to keep the connection open
.HP
\fB\-asmap=\fR<file>
.IP
Specify asn mapping used for bucketing of the peers (default:
ip_asn.map). Relative paths will be prefixed by the net\-specific datadir
location.
.HP
\fB\-banscore=\fR<n>
.IP
Threshold for disconnecting misbehaving peers (default: 100)
@@ -253,6 +259,28 @@ Tor control port to use if onion listening enabled (default:
.IP
Tor control port password (default: empty)
.HP
\fB\-tls=\fR<option>
.IP
Specify TLS usage (default: 1 => enabled and preferred, yet compatible);
other options are \fB\-tls\fR=\fI\,0\/\fR to disable TLS and \fB\-tls\fR=\fI\,only\/\fR to enforce it
.HP
\fB\-tlskeypath=\fR<path>
.IP
Full path to a private key
.HP
\fB\-tlskeypwd=\fR<password>
.IP
Password for a private key encryption (default: not set, i.e. private
key will be stored unencrypted)
.HP
\fB\-tlscertpath=\fR<path>
.IP
Full path to a certificate
.HP
\fB\-tlstrustdir=\fR<path>
.IP
Full path to a trusted certificates directory
.HP
\fB\-whitebind=\fR<addr>
.IP
Bind to given address and whitelist peers connecting to it. Use
@@ -645,7 +673,7 @@ Starting supply, default is 0
.IP
Enforce transaction\-rate limit, default 0
.SH COPYRIGHT
Hush Daemon version v3.5.0-beta6-cb09e7fed-dirty
Hush Daemon version v3.5.1-dd8ab3df6
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.

View File

@@ -50,6 +50,7 @@ LIBSECP256K1=secp256k1/libsecp256k1.la
LIBCRYPTOCONDITIONS=cryptoconditions/libcryptoconditions_core.la
LIBUNIVALUE=univalue/libunivalue.la
LIBZCASH=libzcash.a
LIBHUSH=libhush.a
if ENABLE_ZMQ
LIBBITCOIN_ZMQ=libbitcoin_zmq.a
@@ -78,7 +79,8 @@ EXTRA_LIBRARIES += \
$(LIBBITCOIN_COMMON) \
$(LIBBITCOIN_SERVER) \
$(LIBBITCOIN_CLI) \
libzcash.a
libzcash.a \
libhush.a
if ENABLE_WALLET
BITCOIN_INCLUDES += $(BDB_CPPFLAGS)
EXTRA_LIBRARIES += $(LIBBITCOIN_WALLET)
@@ -158,6 +160,7 @@ BITCOIN_CORE_H = \
core_io.h \
core_memusage.h \
deprecation.h \
fs.h \
hash.h \
httprpc.h \
httpserver.h \
@@ -213,6 +216,7 @@ BITCOIN_CORE_H = \
txdb.h \
txmempool.h \
ui_interface.h \
util/asmap.h \
uint256.h \
uint252.h \
undo.h \
@@ -238,6 +242,8 @@ BITCOIN_CORE_H = \
zmq/zmqnotificationinterface.h \
zmq/zmqpublishnotifier.h
LIBHUSH_H = \
hush/utiltls.h
obj/build.h: FORCE
@$(MKDIR_P) $(builddir)/obj
@@ -281,6 +287,7 @@ libbitcoin_server_a_SOURCES = \
cc/betprotocol.cpp \
chain.cpp \
checkpoints.cpp \
fs.cpp \
crosschain.cpp \
crosschain_authority.cpp \
deprecation.cpp \
@@ -314,7 +321,8 @@ libbitcoin_server_a_SOURCES = \
txmempool.cpp \
validationinterface.cpp \
$(BITCOIN_CORE_H) \
$(LIBZCASH_H)
$(LIBZCASH_H) \
$(LIBHUSH_H)
if ENABLE_ZMQ
libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS)
@@ -378,7 +386,6 @@ if EXPERIMENTAL_ASM
crypto_libbitcoin_crypto_a_SOURCES += crypto/sha256_sse4.cpp
endif
if ENABLE_MINING
EQUIHASH_TROMP_SOURCES = \
pow/tromp/equi_miner.h \
@@ -450,6 +457,7 @@ libbitcoin_util_a_SOURCES = \
utilmoneystr.cpp \
utilstrencodings.cpp \
utiltime.cpp \
util/asmap.cpp \
$(BITCOIN_CORE_H) \
$(LIBZCASH_H)
@@ -491,6 +499,7 @@ komodod_LDADD = \
$(LIBBITCOIN_ZMQ) \
$(LIBBITCOIN_CRYPTO) \
$(LIBZCASH) \
$(LIBHUSH) \
$(LIBLEVELDB) \
$(LIBMEMENV) \
$(LIBSECP256K1) \
@@ -556,6 +565,7 @@ komodo_cli_LDADD = \
$(CRYPTO_LIBS) \
$(EVENT_LIBS) \
$(LIBZCASH) \
$(LIBHUSH) \
$(LIBBITCOIN_CRYPTO) \
$(LIBZCASH_LIBS)
@@ -590,6 +600,7 @@ komodo_tx_LDADD = \
$(LIBBITCOIN_UTIL) \
$(LIBSECP256K1) \
$(LIBZCASH) \
$(LIBHUSH) \
$(LIBBITCOIN_CRYPTO) \
$(LIBZCASH_LIBS) \
$(LIBCRYPTOCONDITIONS)
@@ -649,6 +660,20 @@ libzcashconsensus_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
endif
#
# hush-specific sources #
libhush_a_SOURCES = \
hush/utiltls.cpp\
hush/tlsmanager.cpp
libhush_a_CPPFLAGS = -DMULTICORE -fopenmp -fPIC -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS $(HARDENED_CPPFLAGS) -pipe -O1 -g -Wstack-protector -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES)
libhush_a_CXXFLAGS = $(HARDENED_CXXFLAGS) -fwrapv -fno-strict-aliasing
libhush_a_LDFLAGS = $(HARDENED_LDFLAGS)
libhush_a_CPPFLAGS += -DMONTGOMERY_OUTPUT
#
CLEANFILES = leveldb/libleveldb.a leveldb/libmemenv.a *.gcda *.gcno */*.gcno wallet/*/*.gcno
DISTCLEANFILES = obj/build.h

View File

@@ -10,7 +10,9 @@ komodo_test_SOURCES = \
test-komodo/test_coinimport.cpp \
test-komodo/test_eval_bet.cpp \
test-komodo/test_eval_notarisation.cpp \
test-komodo/test_parse_notarisation.cpp
test-komodo/test_parse_notarisation.cpp \
test-komodo/test_addrman.cpp \
test-komodo/test_netbase_tests.cpp
komodo_test_CPPFLAGS = $(komodod_CPPFLAGS)

View File

@@ -24,19 +24,25 @@
#include "serialize.h"
#include "streams.h"
int CAddrInfo::GetTriedBucket(const uint256& nKey) const
int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector<bool> &asmap) const
{
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetHash().GetCheapHash();
uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetCheapHash();
return hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetCheapHash();
int tried_bucket = hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
uint32_t mapped_as = GetMappedAS(asmap);
LogPrint("net", "IP %s mapped to AS%i belongs to tried bucket %i\n", ToStringIP(), mapped_as, tried_bucket);
return tried_bucket;
}
int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src) const
int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const std::vector<bool> &asmap) const
{
std::vector<unsigned char> vchSourceGroupKey = src.GetGroup();
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetHash().GetCheapHash();
std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(asmap);
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << vchSourceGroupKey).GetHash().GetCheapHash();
uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetHash().GetCheapHash();
return hash2 % ADDRMAN_NEW_BUCKET_COUNT;
int new_bucket = hash2 % ADDRMAN_NEW_BUCKET_COUNT;
uint32_t mapped_as = GetMappedAS(asmap);
LogPrint("net", "IP %s mapped to AS%i belongs to new bucket %i\n", ToStringIP(), mapped_as, new_bucket);
return new_bucket;
}
int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const
@@ -176,7 +182,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
assert(info.nRefCount == 0);
// which tried bucket to move the entry to
int nKBucket = info.GetTriedBucket(nKey);
int nKBucket = info.GetTriedBucket(nKey, m_asmap);
int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
// first make space to add it (the existing tried entry there is moved to new, deleting whatever is there).
@@ -192,7 +198,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId)
nTried--;
// find which new bucket it belongs to
int nUBucket = infoOld.GetNewBucket(nKey);
int nUBucket = infoOld.GetNewBucket(nKey, m_asmap);
int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket);
ClearNew(nUBucket, nUBucketPos);
assert(vvNew[nUBucket][nUBucketPos] == -1);
@@ -302,7 +308,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP
fNew = true;
}
int nUBucket = pinfo->GetNewBucket(nKey, source);
int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap);
int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket);
if (vvNew[nUBucket][nUBucketPos] != nId) {
bool fInsert = vvNew[nUBucket][nUBucketPos] == -1;
@@ -454,7 +460,7 @@ int CAddrMan::Check_()
if (vvTried[n][i] != -1) {
if (!setTried.count(vvTried[n][i]))
return -11;
if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey) != n)
if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey, m_asmap) != n)
return -17;
if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i)
return -18;
@@ -531,3 +537,30 @@ void CAddrMan::Connected_(const CService& addr, int64_t nTime)
int CAddrMan::RandomInt(int nMax){
return GetRandInt(nMax);
}
std::vector<bool> CAddrMan::DecodeAsmap(fs::path path)
{
std::vector<bool> bits;
FILE *filestr = fsbridge::fopen(path, "rb");
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
if (file.IsNull()) {
LogPrintf("Failed to open asmap file from disk\n");
return bits;
}
fseek(filestr, 0, SEEK_END);
int length = ftell(filestr);
LogPrintf("Opened asmap file %s (%d bytes) from disk\n", path, length);
fseek(filestr, 0, SEEK_SET);
char cur_byte;
for (int i = 0; i < length; ++i) {
file >> cur_byte;
for (int bit = 0; bit < 8; ++bit) {
bits.push_back((cur_byte >> bit) & 1);
}
}
if (!SanityCheckASMap(bits)) {
LogPrintf("Sanity check of asmap file %s failed\n", path);
return {};
}
return bits;
}

View File

@@ -27,6 +27,10 @@
#include "sync.h"
#include "timedata.h"
#include "util.h"
#include "fs.h"
#include "clientversion.h"
#include "hash.h"
#include "netbase.h"
#include <map>
#include <set>
@@ -98,15 +102,15 @@ public:
}
//! Calculate in which "tried" bucket this entry belongs
int GetTriedBucket(const uint256 &nKey) const;
int GetTriedBucket(const uint256 &nKey, const std::vector<bool> &asmap) const;
//! Calculate in which "new" bucket this entry belongs, given a certain source
int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const;
int GetNewBucket(const uint256 &nKey, const CNetAddr& src, const std::vector<bool> &asmap) const;
//! Calculate in which "new" bucket this entry belongs, using its default source
int GetNewBucket(const uint256 &nKey) const
int GetNewBucket(const uint256 &nKey, const std::vector<bool> &asmap) const
{
return GetNewBucket(nKey, source);
return GetNewBucket(nKey, source, asmap);
}
//! Calculate in which position of a bucket to store this entry.
@@ -187,6 +191,7 @@ public:
*/
class CAddrMan
{
friend class CAddrManTest;
private:
//! critical section to protect the inner data structures
mutable CCriticalSection cs;
@@ -265,9 +270,29 @@ protected:
void Connected_(const CService &addr, int64_t nTime);
public:
// Compressed IP->ASN mapping, loaded from a file when a node starts.
// Should be always empty if no file was provided.
// This mapping is then used for bucketing nodes in Addrman.
//
// If asmap is provided, nodes will be bucketed by
// AS they belong to, in order to make impossible for a node
// to connect to several nodes hosted in a single AS.
// This is done in response to Erebus attack, but also to generally
// diversify the connections every node creates,
// especially useful when a large fraction of nodes
// operate under a couple of cloud providers.
//
// If a new asmap was provided, the existing records
// would be re-bucketed accordingly.
std::vector<bool> m_asmap;
// Read asmap from provided binary file
static std::vector<bool> DecodeAsmap(fs::path path);
/**
* serialized format:
* * version byte (currently 1)
* * version byte (1 for pre-asmap files, 2 for files including asmap version)
* * 0x20 + nKey (serialized as if it were a vector, for backward compatibility)
* * nNew
* * nTried
@@ -294,12 +319,12 @@ public:
* We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has
* very little in common.
*/
template<typename Stream>
void Serialize(Stream &s) const
template<typename Stream>
void Serialize(Stream &s) const
{
LOCK(cs);
unsigned char nVersion = 1;
unsigned char nVersion = 2;
s << nVersion;
s << ((unsigned char)32);
s << nKey;
@@ -310,9 +335,9 @@ public:
s << nUBuckets;
std::map<int, int> mapUnkIds;
int nIds = 0;
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) {
mapUnkIds[(*it).first] = nIds;
const CAddrInfo &info = (*it).second;
for (const auto& entry : mapInfo) {
mapUnkIds[entry.first] = nIds;
const CAddrInfo &info = entry.second;
if (info.nRefCount) {
assert(nIds != nNew); // this means nNew was wrong, oh ow
s << info;
@@ -320,8 +345,8 @@ public:
}
}
nIds = 0;
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) {
const CAddrInfo &info = (*it).second;
for (const auto& entry : mapInfo) {
const CAddrInfo &info = entry.second;
if (info.fInTried) {
assert(nIds != nTried); // this means nTried was wrong, oh ow
s << info;
@@ -342,6 +367,13 @@ public:
}
}
}
// Store asmap version after bucket entries so that it
// can be ignored by older clients for backward compatibility.
uint256 asmap_version;
if (m_asmap.size() != 0) {
asmap_version = SerializeHash(m_asmap);
}
s << asmap_version;
}
template<typename Stream>
@@ -350,7 +382,6 @@ public:
LOCK(cs);
Clear();
unsigned char nVersion;
s >> nVersion;
unsigned char nKeySize;
@@ -380,16 +411,6 @@ public:
mapAddr[info] = n;
info.nRandomPos = vRandom.size();
vRandom.push_back(n);
if (nVersion != 1 || nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) {
// In case the new table data cannot be used (nVersion unknown, or bucket count wrong),
// immediately try to give them a reference based on their primary source address.
int nUBucket = info.GetNewBucket(nKey);
int nUBucketPos = info.GetBucketPosition(nKey, true, nUBucket);
if (vvNew[nUBucket][nUBucketPos] == -1) {
vvNew[nUBucket][nUBucketPos] = n;
info.nRefCount++;
}
}
}
nIdCount = nNew;
@@ -398,7 +419,7 @@ public:
for (int n = 0; n < nTried; n++) {
CAddrInfo info;
s >> info;
int nKBucket = info.GetTriedBucket(nKey);
int nKBucket = info.GetTriedBucket(nKey, m_asmap);
int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
if (vvTried[nKBucket][nKBucketPos] == -1) {
info.nRandomPos = vRandom.size();
@@ -414,7 +435,9 @@ public:
}
nTried -= nLost;
// Deserialize positions in the new table (if possible).
// Store positions in the new table buckets to apply later (if possible).
std::map<int, int> entryToBucket; // Represents which entry belonged to which bucket when serializing
for (int bucket = 0; bucket < nUBuckets; bucket++) {
int nSize = 0;
s >> nSize;
@@ -422,12 +445,38 @@ public:
int nIndex = 0;
s >> nIndex;
if (nIndex >= 0 && nIndex < nNew) {
CAddrInfo &info = mapInfo[nIndex];
entryToBucket[nIndex] = bucket;
}
}
}
uint256 supplied_asmap_version;
if (m_asmap.size() != 0) {
supplied_asmap_version = SerializeHash(m_asmap);
}
uint256 serialized_asmap_version;
if (nVersion > 1) {
s >> serialized_asmap_version;
}
for (int n = 0; n < nNew; n++) {
CAddrInfo &info = mapInfo[n];
int bucket = entryToBucket[n];
int nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
if (nVersion == 1 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) {
if (nVersion == 2 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 &&
info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS && serialized_asmap_version == supplied_asmap_version) {
// Bucketing has not changed, using existing bucket positions for the new table
vvNew[bucket][nUBucketPos] = n;
info.nRefCount++;
vvNew[bucket][nUBucketPos] = nIndex;
}
} else {
// In case the new table data cannot be used (nVersion unknown, bucket count wrong or new asmap),
// try to give them a reference based on their primary source address.
LogPrint("addrman", "Bucketing method was updated, re-bucketing addrman entries from disk\n");
bucket = info.GetNewBucket(nKey, m_asmap);
nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
if (vvNew[bucket][nUBucketPos] == -1) {
vvNew[bucket][nUBucketPos] = n;
info.nRefCount++;
}
}
}
@@ -452,6 +501,7 @@ public:
void Clear()
{
LOCK(cs);
std::vector<int>().swap(vRandom);
nKey = GetRandHash();
for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
@@ -468,6 +518,8 @@ public:
nIdCount = 0;
nTried = 0;
nNew = 0;
mapInfo.clear();
mapAddr.clear();
}
CAddrMan()

View File

@@ -34,7 +34,7 @@
// Must be kept in sync with configure.ac !
#define CLIENT_VERSION_MAJOR 3
#define CLIENT_VERSION_MINOR 5
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_REVISION 1
#define CLIENT_VERSION_BUILD 50
//! Set to true for release, false for prerelease or test build

View File

@@ -87,6 +87,27 @@ void static inline WriteBE64(unsigned char* ptr, uint64_t x)
memcpy(ptr, (char*)&v, 8);
}
/** Return the smallest number n such that (x >> n) == 0 (or 64 if the highest bit in x is set. */
uint64_t static inline CountBits(uint64_t x)
{
#if HAVE_DECL___BUILTIN_CLZL
if (sizeof(unsigned long) >= sizeof(uint64_t)) {
return x ? 8 * sizeof(unsigned long) - __builtin_clzl(x) : 0;
}
#endif
#if HAVE_DECL___BUILTIN_CLZLL
if (sizeof(unsigned long long) >= sizeof(uint64_t)) {
return x ? 8 * sizeof(unsigned long long) - __builtin_clzll(x) : 0;
}
#endif
int ret = 0;
while (x) {
x >>= 1;
++ret;
}
return ret;
}
int inline init_and_check_sodium()
{
if (sodium_init() == -1) {
@@ -124,5 +145,4 @@ int inline init_and_check_sodium()
return 0;
}
#endif // BITCOIN_CRYPTO_COMMON_H

15
src/fs.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "fs.h"
namespace fsbridge {
FILE *fopen(const fs::path& p, const char *mode)
{
return ::fopen(p.string().c_str(), mode);
}
FILE *freopen(const fs::path& p, const char *mode, FILE *stream)
{
return ::freopen(p.string().c_str(), mode, stream);
}
} // fsbridge

25
src/fs.h Normal file
View File

@@ -0,0 +1,25 @@
// Copyright (c) 2017 The Bitcoin Core developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#ifndef KOMODO_FS_H
#define KOMODO_FS_H
#include <stdio.h>
#include <string>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
/** Filesystem operations and types */
namespace fs = boost::filesystem;
/** Bridge operations to C stdio */
namespace fsbridge {
FILE *fopen(const fs::path& p, const char *mode);
FILE *freopen(const fs::path& p, const char *mode, FILE *stream);
};
#endif // KOMODO_FS_H

9
src/hush/tlsenums.h Normal file
View File

@@ -0,0 +1,9 @@
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
namespace hush
{
typedef enum { SSL_ACCEPT, SSL_CONNECT, SSL_SHUTDOWN } SSLConnectionRoutine;
typedef enum { CLIENT_CONTEXT, SERVER_CONTEXT } TLSContextType;
}

508
src/hush/tlsmanager.cpp Normal file
View File

@@ -0,0 +1,508 @@
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "utiltls.h"
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include "../util.h"
#include "../protocol.h"
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include "tlsmanager.h"
using namespace std;
namespace hush
{
/**
* @brief If verify_callback always returns 1, the TLS/SSL handshake will not be terminated with respect to verification failures and the connection will be established.
*
* @param preverify_ok
* @param chainContext
* @return int
*/
int tlsCertVerificationCallback(int preverify_ok, X509_STORE_CTX* chainContext)
{
return 1;
}
/**
* @brief Wait for a given SSL connection event.
*
* @param eRoutine a SSLConnectionRoutine value which determines the type of the event.
* @param hSocket
* @param ssl pointer to an SSL instance.
* @param timeoutSec timeout in seconds.
* @return int returns nError corresponding to the connection event.
*/
int TLSManager::waitFor(SSLConnectionRoutine eRoutine, SOCKET hSocket, SSL* ssl, int timeoutSec)
{
int nErr = 0;
ERR_clear_error(); // clear the error queue
while (true) {
switch (eRoutine) {
case SSL_CONNECT:
nErr = SSL_connect(ssl);
break;
case SSL_ACCEPT:
nErr = SSL_accept(ssl);
break;
case SSL_SHUTDOWN:
nErr = SSL_shutdown(ssl);
break;
default:
return -1;
}
if (eRoutine == SSL_SHUTDOWN) {
if (nErr >= 0)
break;
} else {
if (nErr == 1)
break;
}
int sslErr = SSL_get_error(ssl, nErr);
if (sslErr != SSL_ERROR_WANT_READ && sslErr != SSL_ERROR_WANT_WRITE) {
LogPrint("net", "TLS: WARNING: %s: %s: ssl_err_code: %s; errno: %s\n", __FILE__, __func__, ERR_error_string(sslErr, NULL), strerror(errno));
nErr = -1;
break;
}
fd_set socketSet;
FD_ZERO(&socketSet);
FD_SET(hSocket, &socketSet);
struct timeval timeout = {timeoutSec, 0};
if (sslErr == SSL_ERROR_WANT_READ) {
int result = select(hSocket + 1, &socketSet, NULL, NULL, &timeout);
if (result == 0) {
LogPrint("net", "TLS: ERROR: %s: %s: WANT_READ timeout\n", __FILE__, __func__);
nErr = -1;
break;
} else if (result == -1) {
LogPrint("net", "TLS: ERROR: %s: %s: WANT_READ ssl_err_code: %s; errno: %s\n", __FILE__, __func__, ERR_error_string(sslErr, NULL), strerror(errno));
nErr = -1;
break;
}
} else {
int result = select(hSocket + 1, NULL, &socketSet, NULL, &timeout);
if (result == 0) {
LogPrint("net", "TLS: ERROR: %s: %s: WANT_WRITE timeout\n", __FILE__, __func__);
nErr = -1;
break;
} else if (result == -1) {
LogPrint("net", "TLS: ERROR: %s: %s: WANT_WRITE ssl_err_code: %s; errno: %s\n", __FILE__, __func__, ERR_error_string(sslErr, NULL), strerror(errno));
nErr = -1;
break;
}
}
}
return nErr;
}
/**
* @brief establish TLS connection to an address
*
* @param hSocket socket
* @param addrConnect the outgoing address
* @param tls_ctx_client TLS Client context
* @return SSL* returns a ssl* if successful, otherwise returns NULL.
*/
SSL* TLSManager::connect(SOCKET hSocket, const CAddress& addrConnect)
{
LogPrint("net", "TLS: establishing connection tid=%X peerid=%s\n", pthread_self(), addrConnect.ToString());
SSL* ssl = NULL;
bool bConnectedTLS = false;
if ((ssl = SSL_new(tls_ctx_client))) {
if (SSL_set_fd(ssl, hSocket)) {
if (TLSManager::waitFor(SSL_CONNECT, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000)) == 1)
bConnectedTLS = true;
}
}
if (bConnectedTLS) {
LogPrintf("TLS: connection to %s has been established. Using cipher: %s\n", addrConnect.ToString(), SSL_get_cipher(ssl));
} else {
LogPrintf("TLS: %s: TLS connection to %s failed\n", __func__, addrConnect.ToString());
if (ssl) {
SSL_free(ssl);
ssl = NULL;
}
}
return ssl;
}
/**
* @brief Initialize TLS Context
*
* @param ctxType context type
* @param privateKeyFile private key file path
* @param certificateFile certificate key file path
* @param trustedDirs trusted directories
* @return SSL_CTX* returns the context.
*/
SSL_CTX* TLSManager::initCtx(
TLSContextType ctxType,
const boost::filesystem::path& privateKeyFile,
const boost::filesystem::path& certificateFile,
const std::vector<boost::filesystem::path>& trustedDirs)
{
if (!boost::filesystem::exists(privateKeyFile) ||
!boost::filesystem::exists(certificateFile))
return NULL;
bool bInitialized = false;
SSL_CTX* tlsCtx = NULL;
if ((tlsCtx = SSL_CTX_new(ctxType == SERVER_CONTEXT ? TLS_server_method() : TLS_client_method()))) {
SSL_CTX_set_mode(tlsCtx, SSL_MODE_AUTO_RETRY);
int rootCertsNum = LoadDefaultRootCertificates(tlsCtx);
int trustedPathsNum = 0;
for (boost::filesystem::path trustedDir : trustedDirs) {
if (SSL_CTX_load_verify_locations(tlsCtx, NULL, trustedDir.string().c_str()) == 1)
trustedPathsNum++;
}
if (rootCertsNum == 0 && trustedPathsNum == 0)
LogPrintf("TLS: WARNING: %s: %s: failed to set up verified certificates. It will be impossible to verify peer certificates. \n", __FILE__, __func__);
SSL_CTX_set_verify(tlsCtx, SSL_VERIFY_PEER, tlsCertVerificationCallback);
if (SSL_CTX_use_certificate_file(tlsCtx, certificateFile.string().c_str(), SSL_FILETYPE_PEM) > 0) {
if (SSL_CTX_use_PrivateKey_file(tlsCtx, privateKeyFile.string().c_str(), SSL_FILETYPE_PEM) > 0) {
if (SSL_CTX_check_private_key(tlsCtx)) {
bInitialized = true;
} else {
LogPrintf("TLS: ERROR: %s: %s: private key does not match the certificate public key\n", __FILE__, __func__);
}
} else
LogPrintf("TLS: ERROR: %s: %s: failed to use privateKey file\n", __FILE__, __func__);
} else {
LogPrintf("TLS: ERROR: %s: %s: failed to use certificate file\n", __FILE__, __func__);
ERR_print_errors_fp(stderr);
}
} else {
LogPrintf("TLS: ERROR: %s: %s: failed to create TLS context\n", __FILE__, __func__);
}
if (!bInitialized) {
if (tlsCtx) {
SSL_CTX_free(tlsCtx);
tlsCtx = NULL;
}
}
SSL_CTX_set_cipher_list(tlsCtx, ""); // removes all <= TLS1.2 ciphers
// default is "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
// Nodes will randomly choose to prefer one suite or the other, to create diversity on the network
// and not be in the situation where all nodes have the same list so the first is always used
if(GetRand(100) > 50) {
LogPrintf("%s: Preferring TLS_AES256-GCM-SHA384\n", __func__);
SSL_CTX_set_ciphersuites(tlsCtx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256");
} else {
LogPrintf("%s: Preferring TLS_CHACHA20-POLY1305\n", __func__);
SSL_CTX_set_ciphersuites(tlsCtx, "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384");
}
/*
STACK_OF(SSL_CIPHER) *sk = SSL_CTX_get_ciphers(tlsCtx);
for (int i = 0; i < sk_SSL_CIPHER_num(sk); i++)
{
const SSL_CIPHER *c = sk_SSL_CIPHER_value(sk, i);
LogPrintf("%s: AVAILABLE CIPHER %s\n", __func__, SSL_CIPHER_get_name(c));
}
*/
return tlsCtx;
}
/**
* @brief load the certificate credentials from file.
*
* @return true returns true is successful.
* @return false returns false if an error has occured.
*/
bool TLSManager::prepareCredentials()
{
boost::filesystem::path
defaultKeyPath(GetDataDir() / TLS_KEY_FILE_NAME),
defaultCertPath(GetDataDir() / TLS_CERT_FILE_NAME);
CredentialsStatus credStatus =
VerifyCredentials(
boost::filesystem::path(GetArg("-tlskeypath", defaultKeyPath.string())),
boost::filesystem::path(GetArg("-tlscertpath", defaultCertPath.string())),
GetArg("-tlskeypwd", ""));
bool bPrepared = (credStatus == credOk);
if (!bPrepared) {
if (!mapArgs.count("-tlskeypath") && !mapArgs.count("-tlscertpath")) {
// Default paths were used
if (credStatus == credAbsent) {
// Generate new credentials (key and self-signed certificate on it) only if credentials were absent previously
//
bPrepared = GenerateCredentials(
defaultKeyPath,
defaultCertPath,
GetArg("-tlskeypwd", ""));
}
}
}
return bPrepared;
}
/**
* @brief accept a TLS connection
*
* @param hSocket the TLS socket.
* @param addr incoming address.
* @param tls_ctx_server TLS server context.
* @return SSL* returns pointer to the ssl object if successful, otherwise returns NULL
*/
SSL* TLSManager::accept(SOCKET hSocket, const CAddress& addr)
{
LogPrint("net", "TLS: accepting connection from %s (tid = %X)\n", addr.ToString(), pthread_self());
SSL* ssl = NULL;
bool bAcceptedTLS = false;
if ((ssl = SSL_new(tls_ctx_server))) {
if (SSL_set_fd(ssl, hSocket)) {
if (TLSManager::waitFor(SSL_ACCEPT, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000)) == 1)
bAcceptedTLS = true;
}
}
if (bAcceptedTLS) {
LogPrintf("TLS: connection from %s has been accepted. Using cipher: %s\n", addr.ToString(), SSL_get_cipher(ssl));
} else {
LogPrintf("TLS: ERROR: %s: %s: TLS connection from %s failed\n", __FILE__, __func__, addr.ToString());
if (ssl) {
SSL_free(ssl);
ssl = NULL;
}
}
return ssl;
}
/**
* @brief Determines whether a string exists in the non-TLS address pool.
*
* @param strAddr The address.
* @param vPool Pool to search in.
* @param cs reference to the corresponding CCriticalSection.
* @return true returns true if address exists in the given pool.
* @return false returns false if address doesnt exist in the given pool.
*/
bool TLSManager::isNonTLSAddr(const string& strAddr, const vector<NODE_ADDR>& vPool, CCriticalSection& cs)
{
LOCK(cs);
return (find(vPool.begin(), vPool.end(), NODE_ADDR(strAddr)) != vPool.end());
}
/**
* @brief Removes non-TLS node addresses based on timeout.
*
* @param vPool
* @param cs
*/
void TLSManager::cleanNonTLSPool(std::vector<NODE_ADDR>& vPool, CCriticalSection& cs)
{
LOCK(cs);
vector<NODE_ADDR> vDeleted;
BOOST_FOREACH (NODE_ADDR nodeAddr, vPool) {
if ((GetTimeMillis() - nodeAddr.time) >= 900000) {
vDeleted.push_back(nodeAddr);
LogPrint("net", "TLS: Node %s is deleted from the non-TLS pool\n", nodeAddr.ipAddr);
}
}
BOOST_FOREACH (NODE_ADDR nodeAddrDeleted, vDeleted) {
vPool.erase(
remove(
vPool.begin(),
vPool.end(),
nodeAddrDeleted),
vPool.end());
}
}
/**
* @brief Handles send and recieve functionality in TLS Sockets.
*
* @param pnode reference to the CNode object.
* @param fdsetRecv
* @param fdsetSend
* @param fdsetError
* @return int returns -1 when socket is invalid. returns 0 otherwise.
*/
int TLSManager::threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fdsetSend, fd_set& fdsetError)
{
//
// Receive
//
bool recvSet = false, sendSet = false, errorSet = false;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
return -1;
recvSet = FD_ISSET(pnode->hSocket, &fdsetRecv);
sendSet = FD_ISSET(pnode->hSocket, &fdsetSend);
errorSet = FD_ISSET(pnode->hSocket, &fdsetError);
}
if (recvSet || errorSet) {
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
if (lockRecv) {
{
// typical socket buffer is 8K-64K
// maximum record size is 16kB for SSLv3/TLSv1
char pchBuf[0x10000];
bool bIsSSL = false;
int nBytes = 0, nRet = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
LogPrint("net", "Receive: connection with %s is already closed\n", pnode->addr.ToString());
return -1;
}
bIsSSL = (pnode->ssl != NULL);
if (bIsSSL) {
ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
nBytes = SSL_read(pnode->ssl, pchBuf, sizeof(pchBuf));
nRet = SSL_get_error(pnode->ssl, nBytes);
} else {
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
nRet = WSAGetLastError();
}
}
if (nBytes > 0) {
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
pnode->CloseSocketDisconnect();
pnode->nLastRecv = GetTime();
pnode->nRecvBytes += nBytes;
pnode->RecordBytesRecv(nBytes);
} else if (nBytes == 0) {
// socket closed gracefully (peer disconnected)
//
if (!pnode->fDisconnect)
LogPrint("net", "socket closed (%s)\n", pnode->addr.ToString());
pnode->CloseSocketDisconnect();
} else if (nBytes < 0) {
// error
//
if (bIsSSL) {
if (nRet != SSL_ERROR_WANT_READ && nRet != SSL_ERROR_WANT_WRITE) // SSL_read() operation has to be repeated because of SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE (https://wiki.openssl.org/index.php/Manual:SSL_read(3)#NOTES)
{
if (!pnode->fDisconnect)
LogPrintf("ERROR: SSL_read %s\n", ERR_error_string(nRet, NULL));
pnode->CloseSocketDisconnect();
} else {
// preventive measure from exhausting CPU usage
//
MilliSleep(1); // 1 msec
}
} else {
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS) {
if (!pnode->fDisconnect)
LogPrintf("ERROR: socket recv %s\n", NetworkErrorString(nRet));
pnode->CloseSocketDisconnect();
}
}
}
}
}
}
//
// Send
//
if (sendSet) {
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend)
SocketSendData(pnode);
}
return 0;
}
/**
* @brief Initialization of the server and client contexts
*
* @return true returns True if successful.
* @return false returns False if an error has occured.
*/
bool TLSManager::initialize()
{
bool bInitializationStatus = false;
// Initialization routines for the OpenSSL library
SSL_load_error_strings();
ERR_load_crypto_strings();
OpenSSL_add_ssl_algorithms(); // OpenSSL_add_ssl_algorithms() always returns "1", so it is safe to discard the return value.
namespace fs = boost::filesystem;
fs::path certFile = GetArg("-tlscertpath", "");
if (!fs::exists(certFile))
certFile = (GetDataDir() / TLS_CERT_FILE_NAME);
fs::path privKeyFile = GetArg("-tlskeypath", "");
if (!fs::exists(privKeyFile)) {
privKeyFile = (GetDataDir() / TLS_KEY_FILE_NAME);
}
std::vector<fs::path> trustedDirs;
fs::path trustedDir = GetArg("-tlstrustdir", "");
if (fs::exists(trustedDir)) {
// Use only the specified trusted directory
trustedDirs.push_back(trustedDir);
} else {
// If specified directory can't be used, then setting the default trusted directories
trustedDirs = GetDefaultTrustedDirectories();
}
for (fs::path dir : trustedDirs)
LogPrintf("TLS: trusted directory '%s' will be used\n", dir.string().c_str());
// Initialization of the server and client contexts
if ((tls_ctx_server = TLSManager::initCtx(SERVER_CONTEXT, privKeyFile, certFile, trustedDirs)))
{
if ((tls_ctx_client = TLSManager::initCtx(CLIENT_CONTEXT, privKeyFile, certFile, trustedDirs)))
{
LogPrint("net", "TLS: contexts are initialized\n");
bInitializationStatus = true;
} else {
LogPrintf("TLS: ERROR: %s: %s: failed to initialize TLS client context\n", __FILE__, __func__);
SSL_CTX_free (tls_ctx_server);
}
} else {
LogPrintf("TLS: ERROR: %s: %s: failed to initialize TLS server context\n", __FILE__, __func__);
}
return bInitializationStatus;
}
}

61
src/hush/tlsmanager.h Normal file
View File

@@ -0,0 +1,61 @@
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "utiltls.h"
#include "tlsenums.h"
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include "../util.h"
#include "../protocol.h"
#include "../net.h"
#include "sync.h"
#include <boost/filesystem/path.hpp>
#include <boost/foreach.hpp>
#include <boost/signals2/signal.hpp>
#ifdef WIN32
#include <string.h>
#else
#include <fcntl.h>
#endif
using namespace std;
namespace hush
{
typedef struct _NODE_ADDR {
std::string ipAddr;
int64_t time; // time in msec, of an attempt to connect via TLS
_NODE_ADDR(std::string _ipAddr, int64_t _time = 0) : ipAddr(_ipAddr), time(_time) {}
bool operator==(const _NODE_ADDR b) const
{
return (ipAddr == b.ipAddr);
}
} NODE_ADDR, *PNODE_ADDR;
/**
* @brief A class to wrap some of hush specific TLS functionalities used in the net.cpp
*
*/
class TLSManager
{
public:
int waitFor(SSLConnectionRoutine eRoutine, SOCKET hSocket, SSL* ssl, int timeoutSec);
SSL* connect(SOCKET hSocket, const CAddress& addrConnect);
SSL_CTX* initCtx(
TLSContextType ctxType,
const boost::filesystem::path& privateKeyFile,
const boost::filesystem::path& certificateFile,
const std::vector<boost::filesystem::path>& trustedDirs);
bool prepareCredentials();
SSL* accept(SOCKET hSocket, const CAddress& addr);
bool isNonTLSAddr(const string& strAddr, const vector<NODE_ADDR>& vPool, CCriticalSection& cs);
void cleanNonTLSPool(std::vector<NODE_ADDR>& vPool, CCriticalSection& cs);
int threadSocketHandler(CNode* pnode, fd_set& fdsetRecv, fd_set& fdsetSend, fd_set& fdsetError);
bool initialize();
};
}

495
src/hush/utiltls.cpp Normal file
View File

@@ -0,0 +1,495 @@
// Copyright (c) 2017 The Zen Core developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include <stdio.h>
#include <vector>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include "util.h"
#include "utiltls.h"
namespace hush {
// Set of most common default trusted certificates directories used by OpenSSL
static const char* defaultTrustedDirs[] =
{
#ifdef WIN32
""
#elif MAC_OSX
"/System/Library/OpenSSL/certs"
#else // Linux build
"/etc/ssl/certs",
"/usr/local/ssl/certs",
"/usr/lib/ssl/certs",
"/usr/share/ssl/certs",
"/etc/pki/tls/certs",
"/var/lib/ca-certificates"
#endif
};
// Default root certificates (PEM encoded)
static const char defaultRootCerts[] =
{
// // Example of specifying a certificate
// //
// "-----BEGIN CERTIFICATE-----\n"
// "MIIDYDCCAkigAwIBAgIJAJMakdoBYY67MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n"
// "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n"
// "aWRnaXRzIFB0eSBMdGQwHhcNMTcwODE0MTc0MTMyWhcNNDQxMjMwMTc0MTMyWjBF\n"
// "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n"
// "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
// "CgKCAQEAzNV+SPRCKSEGlntfpCRMVSfz99NoEo3K1SRyw6GTSb1LNSTQCn1EsCSH\n"
// "cVZTmyfjcTHpwz4aF14yw8lQC42f218AOsG1DV5suCaUXhSmZlajMkvEJVwfBOft\n"
// "xpcqE1fA9wovXlnJLXVgyJGMc896S8tcbrCU/l/BsqKh5QX8N60MQ3w376nSGvVP\n"
// "ussN8bVH3aKRwjhateqx1GRt0GPnM8/u7EkgF8Bc+m8WZYcUfkPC5Am2D0MO1HOA\n"
// "u3IKxXZMs/fYd6nF5DZBwg+D23EP/V8oqenn8ilvrSORq5PguOl1QoDyY66PhmjN\n"
// "L9c4Spxw8HXUDlrfuSQn2NJnw1XhdQIDAQABo1MwUTAdBgNVHQ4EFgQU/KD+n5Bz\n"
// "QLbp09qKzwwyNwOQU4swHwYDVR0jBBgwFoAU/KD+n5BzQLbp09qKzwwyNwOQU4sw\n"
// "DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVtprBxZD6O+WNYUM\n"
// "ksdKiVVoszEJXlt7wajuaPBPK/K3buxE9FLVxS+LiH1PUhPCc6V28guyKWwn109/\n"
// "4WnO51LQjygvd7SaePlbiO7iIatkOk4oETJQZ+tEJ7fv/NITY/GQUfgPNkANmPPz\n"
// "Mz9I6He8XhIpO6NGuDG+74aR1RhvR3PWJJYT0QpL0STVR4qTc/HfnymF5XnnjOYZ\n"
// "mwzT8jXX5dhLYwJmyPBS+uv+oa1quM/FitA63N9anYtRBiPaBtund9Ikjat1hM0h\n"
// "neo2tz7Mfsgjb0aiORtiyaH2OetvwR0QuCSVPnknkfGWPDINdUdkgKyA1PX58Smw\n"
// "vaXEcw==\n"
// "-----END CERTIFICATE-----"
""
};
// Generates RSA keypair (a private key of 'bits' length for a specified 'uPublicKey')
//
static EVP_PKEY* GenerateRsaKey(int bits, BN_ULONG uPublicKey)
{
EVP_PKEY *evpPrivKey = NULL;
BIGNUM *pubKey = BN_new();
if (pubKey)
{
if (BN_set_word(pubKey, uPublicKey))
{
RSA *privKey = RSA_new();
if (privKey)
{
if (RAND_poll() && // The pseudo-random number generator must be seeded prior to calling RSA_generate_key_ex(). (https://www.openssl.org/docs/man1.1.0/crypto/RSA_generate_key.html)
RSA_generate_key_ex(privKey, bits, pubKey, NULL))
{
if ((evpPrivKey = EVP_PKEY_new()))
{
if (!EVP_PKEY_assign_RSA(evpPrivKey, privKey))
{
EVP_PKEY_free(evpPrivKey);
evpPrivKey = NULL;
}
}
}
if(!evpPrivKey) // EVP_PKEY_assign_RSA uses the supplied key internally
RSA_free(privKey);
}
}
BN_free(pubKey);
}
return evpPrivKey;
}
// Generates certificate for a specified public key using a corresponding private key (both of them should be specified in the 'keypair').
//
static X509* GenerateCertificate(EVP_PKEY *keypair)
{
if (!keypair)
return NULL;
X509 *cert = X509_new();
if (cert)
{
bool bCertSigned = false;
long sn = 0;
if (RAND_bytes((unsigned char*)&sn, sizeof sn) &&
ASN1_INTEGER_set(X509_get_serialNumber(cert), sn))
{
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), (60 * 60 * 24 * CERT_VALIDITY_DAYS));
// setting a public key from the keypair
if (X509_set_pubkey(cert, keypair))
{
X509_NAME *subjectName = X509_get_subject_name(cert);
if (subjectName)
{
// an issuer name is the same as a subject name, due to certificate is self-signed
if (X509_set_issuer_name(cert, subjectName))
{
// private key from keypair is used; signature will be set inside of the cert
bCertSigned = X509_sign(cert, keypair, EVP_sha512());
}
}
}
}
if (!bCertSigned)
{
X509_free(cert);
cert = NULL;
}
}
return cert;
}
// Stores key to file, specified by the 'filePath'
//
static bool StoreKey(EVP_PKEY *key, const boost::filesystem::path &filePath, const std::string &passphrase)
{
if (!key)
return false;
bool bStored = false;
FILE *keyfd = fopen(filePath.string().c_str(), "wb");
if (keyfd)
{
const EVP_CIPHER* pCipher = NULL;
if (passphrase.length() && (pCipher = EVP_aes_256_cbc()))
bStored = PEM_write_PrivateKey(keyfd, key, pCipher, NULL, 0, NULL, (void*)passphrase.c_str());
else
bStored = PEM_write_PrivateKey(keyfd, key, NULL, NULL, 0, NULL, NULL);
fclose(keyfd);
}
return bStored;
}
// Stores certificate to file, specified by the 'filePath'
//
static bool StoreCertificate(X509 *cert, const boost::filesystem::path &filePath)
{
if (!cert)
return false;
bool bStored = false;
FILE *certfd = fopen(filePath.string().c_str(), "wb");
if (certfd)
{
bStored = PEM_write_X509(certfd, cert);
fclose(certfd);
}
return bStored;
}
// Loads key from file, specified by the 'filePath'
//
static EVP_PKEY* LoadKey(const boost::filesystem::path &filePath, const std::string &passphrase)
{
if (!boost::filesystem::exists(filePath))
return NULL;
EVP_PKEY *key = NULL;
FILE *keyfd = fopen(filePath.string().c_str(), "rb");
if (keyfd)
{
key = PEM_read_PrivateKey(keyfd, NULL, NULL, passphrase.length() ? (void*)passphrase.c_str() : NULL);
fclose(keyfd);
}
return key;
}
// Loads certificate from file, specified by the 'filePath'
//
static X509* LoadCertificate(const boost::filesystem::path &filePath)
{
if (!boost::filesystem::exists(filePath))
return NULL;
X509 *cert = NULL;
FILE *certfd = fopen(filePath.string().c_str(), "rb");
if (certfd)
{
cert = PEM_read_X509(certfd, NULL, NULL, NULL);
fclose(certfd);
}
return cert;
}
// Verifies if the private key in 'key' matches the public key in 'cert'
// (Signs random bytes on 'key' and verifies signature correctness on public key from 'cert')
//
static bool IsMatching(EVP_PKEY *key, X509 *cert)
{
if (!key || !cert)
return false;
bool bIsMatching = false;
EVP_PKEY_CTX *ctxSign = EVP_PKEY_CTX_new(key, NULL);
if (ctxSign)
{
if (EVP_PKEY_sign_init(ctxSign) == 1 &&
EVP_PKEY_CTX_set_signature_md(ctxSign, EVP_sha512()) > 0)
{
unsigned char digest[SHA512_DIGEST_LENGTH] = { 0 };
size_t digestSize = sizeof digest, signatureSize = 0;
if (RAND_bytes((unsigned char*)&digest, digestSize) && // set random bytes as a digest
EVP_PKEY_sign(ctxSign, NULL, &signatureSize, digest, digestSize) == 1) // determine buffer length
{
unsigned char *signature = (unsigned char*)OPENSSL_malloc(signatureSize);
if (signature)
{
if (EVP_PKEY_sign(ctxSign, signature, &signatureSize, digest, digestSize) == 1)
{
EVP_PKEY *pubkey = X509_get_pubkey(cert);
if (pubkey)
{
EVP_PKEY_CTX *ctxVerif = EVP_PKEY_CTX_new(pubkey, NULL);
if (ctxVerif)
{
if (EVP_PKEY_verify_init(ctxVerif) == 1 &&
EVP_PKEY_CTX_set_signature_md(ctxVerif, EVP_sha512()) > 0)
{
bIsMatching = (EVP_PKEY_verify(ctxVerif, signature, signatureSize, digest, digestSize) == 1);
}
EVP_PKEY_CTX_free(ctxVerif);
}
EVP_PKEY_free(pubkey);
}
}
OPENSSL_free(signature);
}
}
}
EVP_PKEY_CTX_free(ctxSign);
}
return bIsMatching;
}
// Checks the correctness of a private-public key pair and the validity of a certificate using public key from key pair
//
static bool CheckCredentials(EVP_PKEY *key, X509 *cert)
{
if (!key || !cert)
return false;
bool bIsOk = false;
// Validating the correctness of a private-public key pair, depending on a key type
//
switch (EVP_PKEY_base_id(key))
{
case EVP_PKEY_RSA:
case EVP_PKEY_RSA2:
{
RSA *rsaKey = EVP_PKEY_get1_RSA(key);
if (rsaKey)
{
bIsOk = (RSA_check_key(rsaKey) == 1);
RSA_free(rsaKey);
}
break;
}
// Currently only RSA keys are supported.
// Other key types can be added here in further.
default:
bIsOk = false;
}
// Verifying if the private key matches the public key in certificate
if (bIsOk)
bIsOk = IsMatching(key, cert);
return bIsOk;
}
// Verifies credentials (a private key, a certificate for public key and a correspondence between the private and the public key)
//
CredentialsStatus VerifyCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase)
{
CredentialsStatus status = credAbsent;
EVP_PKEY *key = NULL;
X509 *cert = NULL;
key = LoadKey(keyPath, passphrase);
cert = LoadCertificate(certPath);
if (key && cert)
status = CheckCredentials(key, cert) ? credOk : credNonConsistent;
else if (!key && !cert)
status = credAbsent;
else
status = credPartiallyAbsent;
if (key)
EVP_PKEY_free(key);
if (cert)
X509_free(cert);
return status;
}
// Generates public key pair and the self-signed certificate for it, and then stores them by the specified paths 'keyPath' and 'certPath' respectively.
//
bool GenerateCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase)
{
bool bGenerated = false;
EVP_PKEY *key = NULL;
X509 *cert = NULL;
// Generating RSA key and the self-signed certificate for it
//
key = GenerateRsaKey(TLS_RSA_KEY_SIZE, RSA_F4);
if (key)
{
cert = GenerateCertificate(key);
if (cert)
{
if (StoreKey(key, keyPath, passphrase) &&
StoreCertificate(cert, certPath))
{
bGenerated = true;
LogPrintStr("TLS: New private key and self-signed certificate were generated successfully\n");
}
X509_free(cert);
}
EVP_PKEY_free(key);
}
return bGenerated;
}
// Checks if certificate of a peer is valid (by internal means of the TLS protocol)
//
// Validates peer certificate using a chain of CA certificates.
// If some of intermediate CA certificates are absent in the trusted certificates store, then validation status will be 'false')
//
bool ValidatePeerCertificate(SSL *ssl)
{
if (!ssl)
return false;
bool bIsOk = false;
X509 *cert = SSL_get_peer_certificate (ssl);
if (cert)
{
// NOTE: SSL_get_verify_result() is only useful in connection with SSL_get_peer_certificate (https://www.openssl.org/docs/man1.0.2/ssl/SSL_get_verify_result.html)
//
bIsOk = (SSL_get_verify_result(ssl) == X509_V_OK);
X509_free(cert);
}
else
{
LogPrint("net", "TLS: Peer does not have certificate\n");
bIsOk = false;
}
return bIsOk;
}
// Check if a given context is set up with a cert that can be validated by this context
//
bool ValidateCertificate(SSL_CTX *ssl_ctx)
{
if (!ssl_ctx)
return false;
bool bIsOk = false;
X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
if (store)
{
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
if (ctx)
{
if (X509_STORE_CTX_init(ctx, store, SSL_CTX_get0_certificate(ssl_ctx), NULL) == 1)
bIsOk = X509_verify_cert(ctx) == 1;
X509_STORE_CTX_free(ctx);
}
}
return bIsOk;
}
// Creates the list of available OpenSSL default directories for trusted certificates storage
//
std::vector<boost::filesystem::path> GetDefaultTrustedDirectories()
{
namespace fs = boost::filesystem;
std::vector<fs::path> defaultDirectoriesList;
// Default certificates directory specified in OpenSSL build
fs::path libDefaultDir = X509_get_default_cert_dir();
if (fs::exists(libDefaultDir))
defaultDirectoriesList.push_back(libDefaultDir);
// Check and set all possible standard default directories
for (const char *dir : defaultTrustedDirs)
{
fs::path defaultDir(dir);
if (defaultDir != libDefaultDir &&
fs::exists(defaultDir))
defaultDirectoriesList.push_back(defaultDir);
}
return defaultDirectoriesList;
}
// Loads default root certificates (placed in the 'defaultRootCerts') into the specified context.
// Returns the number of loaded certificates.
//
int LoadDefaultRootCertificates(SSL_CTX *ctx)
{
if (!ctx)
return 0;
int certsLoaded = 0;
// Certificate text buffer 'defaultRootCerts' is a C string with certificates in PEM format
BIO *memBuf = BIO_new_mem_buf(defaultRootCerts, -1);
if (memBuf)
{
X509 *cert = NULL;
while ((cert = PEM_read_bio_X509(memBuf, NULL, 0, NULL)))
{
if (X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert) > 0)
certsLoaded++;
X509_free(cert);
}
BIO_free(memBuf);
}
return certsLoaded;
}
}

57
src/hush/utiltls.h Normal file
View File

@@ -0,0 +1,57 @@
// Copyright (c) 2017 The Zen Core developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#ifndef UTILTLS_H
#define UTILTLS_H
#include <boost/filesystem/path.hpp>
namespace hush {
#define TLS_KEY_FILE_NAME "key.pem" // default name of a private key
#define TLS_CERT_FILE_NAME "cert.pem" // default name of a certificate
#define CERT_VALIDITY_DAYS (365 * 10) // period of validity, in days, for a self-signed certificate
#define TLS_RSA_KEY_SIZE 2048 // size of a private RSA key, in bits, that will be generated, if no other key is specified
typedef enum {credOk, credNonConsistent, credAbsent, credPartiallyAbsent} CredentialsStatus;
// Verifies credentials (a private key, a certificate for public key and a correspondence between the private and the public key)
//
CredentialsStatus VerifyCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase);
// Generates public key pair and the self-signed certificate for it, and then stores them by the specified paths 'keyPath' and 'certPath' respectively.
//
bool GenerateCredentials(
const boost::filesystem::path &keyPath,
const boost::filesystem::path &certPath,
const std::string &passphrase);
// Checks if certificate of a peer is valid (by internal means of the TLS protocol)
//
// Validates peer certificate using a chain of CA certificates.
// If some of intermediate CA certificates are absent in the trusted certificates store, then validation status will be 'false')
//
bool ValidatePeerCertificate(SSL *ssl);
// Check if a given context is set up with a cert that can be validated by this context
//
bool ValidateCertificate(SSL_CTX *ssl_ctx);
// Creates the list of available OpenSSL default directories for trusted certificates storage
//
std::vector<boost::filesystem::path> GetDefaultTrustedDirectories();
// Loads default root certificates (placed in the 'defaultRootCerts') into the specified context.
// Returns the number of loaded certificates.
//
int LoadDefaultRootCertificates(SSL_CTX *ctx);
}
#endif // UTILTLS_H

View File

@@ -126,6 +126,9 @@ enum BindFlags {
};
static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h
//////////////////////////////////////////////////////////////////////////////
@@ -399,6 +402,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-zindex", strprintf(_("Maintain extra statistics about shielded transactions and payments (default: %u)"), 0));
strUsage += HelpMessageGroup(_("Connection options:"));
strUsage += HelpMessageOpt("-addnode=<ip>", _("Add a node to connect to and attempt to keep the connection open"));
strUsage += HelpMessageOpt("-asmap=<file>", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME));
strUsage += HelpMessageOpt("-banscore=<n>", strprintf(_("Threshold for disconnecting misbehaving peers (default: %u)"), 100));
strUsage += HelpMessageOpt("-bantime=<n>", strprintf(_("Number of seconds to keep misbehaving peers from reconnecting (default: %u)"), 86400));
strUsage += HelpMessageOpt("-bind=<addr>", _("Bind to given address and always listen on it. Use [host]:port notation for IPv6"));
@@ -426,6 +430,11 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-timeout=<n>", strprintf(_("Specify connection timeout in milliseconds (minimum: 1, default: %d)"), DEFAULT_CONNECT_TIMEOUT));
strUsage += HelpMessageOpt("-torcontrol=<ip>:<port>", strprintf(_("Tor control port to use if onion listening enabled (default: %s)"), DEFAULT_TOR_CONTROL));
strUsage += HelpMessageOpt("-torpassword=<pass>", _("Tor control port password (default: empty)"));
strUsage += HelpMessageOpt("-tls=<option>", _("Specify TLS usage (default: 1 => enabled and preferred, yet compatible); other options are -tls=0 to disable TLS and -tls=only to enforce it"));
strUsage += HelpMessageOpt("-tlskeypath=<path>", _("Full path to a private key"));
strUsage += HelpMessageOpt("-tlskeypwd=<password>", _("Password for a private key encryption (default: not set, i.e. private key will be stored unencrypted)"));
strUsage += HelpMessageOpt("-tlscertpath=<path>", _("Full path to a certificate"));
strUsage += HelpMessageOpt("-tlstrustdir=<path>", _("Full path to a trusted certificates directory"));
strUsage += HelpMessageOpt("-whitebind=<addr>", _("Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6"));
strUsage += HelpMessageOpt("-whitelist=<netmask>", _("Whitelist peers connecting from the given netmask or IP address. Can be specified multiple times.") +
" " + _("Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool, useful e.g. for a gateway"));
@@ -736,7 +745,7 @@ bool InitSanityCheck(void)
if (!glibc_sanity_test() || !glibcxx_sanity_test()) {
fprintf(stderr,"%s: glibc insanity!\n", __FUNCTION__);
return false;
}
}
return true;
}
@@ -801,8 +810,8 @@ static void ZC_LoadParams(
if (files_exist(sapling_spend, sapling_output)) {
LogPrintf("Found sapling params in /usr/share/hush\n");
found=true;
}
}
}
}
if (!found) {
// Try ..
@@ -811,8 +820,8 @@ static void ZC_LoadParams(
if (files_exist(sapling_spend, sapling_output)) {
LogPrintf("Found sapling params in ..\n");
found = true;
}
}
}
if (!found) {
// This will catch the case of any external software (i.e. GUI wallets) needing params and installed in same dir as hush3.git
@@ -821,7 +830,7 @@ static void ZC_LoadParams(
if (files_exist(sapling_spend, sapling_output)) {
LogPrintf("Found sapling params in ../hush3\n");
found = true;
}
}
}
if (!found) {
@@ -831,7 +840,7 @@ static void ZC_LoadParams(
if (files_exist(sapling_spend, sapling_output)) {
LogPrintf("Found sapling params in /Applications/Contents/MacOS\n");
found = true;
}
}
}
if (!found) {
@@ -841,7 +850,7 @@ static void ZC_LoadParams(
if (files_exist(sapling_spend, sapling_output)) {
LogPrintf("Found sapling params in /Applications/Contents/MacOS\n");
found = true;
}
}
}
if (!found) {
@@ -853,7 +862,7 @@ static void ZC_LoadParams(
LogPrintf("Found sapling params in ~/.zcash\n");
found = true;
}
}
}
if (!found) {
// No Sapling params, at least we tried
@@ -891,7 +900,7 @@ static void ZC_LoadParams(
static_assert( sizeof(boost::filesystem::path::value_type) == sizeof(codeunit), "librustzcash not configured correctly");
auto sapling_spend_str = sapling_spend.native();
auto sapling_spend_str = sapling_spend.native();
auto sapling_output_str = sapling_output.native();
LogPrintf("Loading Sapling (Spend) parameters from %s\n", sapling_spend.string().c_str());
@@ -1024,13 +1033,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
fprintf(stderr,"%s zmerge error\n", __FUNCTION__);
return InitError(_("RPC method z_mergetoaddress requires -experimentalfeatures."));
}
}
}
//fprintf(stderr,"%s tik2\n", __FUNCTION__);
// Set this early so that parameter interactions go to console
fPrintToConsole = GetBoolArg("-printtoconsole", false);
fLogTimestamps = GetBoolArg("-logtimestamps", true);
fLogIPs = GetBoolArg("-logips", false);
fLogTimestamps = GetBoolArg("-logtimestamps", true);
fLogIPs = GetBoolArg("-logips", false);
LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
@@ -1079,6 +1088,31 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("%s: parameter interaction: -externalip set -> setting -discover=0\n", __func__);
}
// Read asmap file if configured
if (mapArgs.count("-asmap")) {
fs::path asmap_path = fs::path(GetArg("-asmap", ""));
if (asmap_path.empty()) {
asmap_path = DEFAULT_ASMAP_FILENAME;
}
if (!asmap_path.is_absolute()) {
asmap_path = GetDataDir() / asmap_path;
}
if (!fs::exists(asmap_path)) {
InitError(strprintf(_("Could not find asmap file %s"), asmap_path));
return false;
}
std::vector<bool> asmap = CAddrMan::DecodeAsmap(asmap_path);
if (asmap.size() == 0) {
InitError(strprintf(_("Could not parse asmap file %s"), asmap_path));
return false;
}
const uint256 asmap_version = SerializeHash(asmap);
addrman.m_asmap = std::move(asmap); // //node.connman->SetAsmap(std::move(asmap));
LogPrintf("Using asmap version %s for IP bucketing\n", asmap_version.ToString());
} else {
LogPrintf("Using /16 prefix for IP bucketing\n");
}
if (GetBoolArg("-salvagewallet", false)) {
// Rewrite just private keys: rescan to find transactions
if (SoftSetBoolArg("-rescan", true))
@@ -1525,7 +1559,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
// -proxy sets a proxy for all outgoing network traffic
// -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default
std::string proxyArg = GetArg("-proxy", "");
SetLimited(NET_TOR);
SetLimited(NET_ONION);
if (proxyArg != "" && proxyArg != "0") {
proxyType addrProxy = proxyType(CService(proxyArg, 9050), proxyRandomize);
if (!addrProxy.IsValid())
@@ -1533,9 +1567,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
SetProxy(NET_IPV4, addrProxy);
SetProxy(NET_IPV6, addrProxy);
SetProxy(NET_TOR, addrProxy);
SetProxy(NET_ONION, addrProxy);
SetNameProxy(addrProxy);
SetLimited(NET_TOR, false); // by default, -proxy sets onion as reachable, unless -noonion later
SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later
}
//fprintf(stderr,"%s tik20\n", __FUNCTION__);
@@ -1545,19 +1579,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
std::string onionArg = GetArg("-onion", "");
if (onionArg != "") {
if (onionArg == "0") { // Handle -noonion/-onion=0
SetLimited(NET_TOR); // set onions as unreachable
SetLimited(NET_ONION); // set onions as unreachable
} else {
proxyType addrOnion = proxyType(CService(onionArg, 9050), proxyRandomize);
if (!addrOnion.IsValid())
return InitError(strprintf(_("Invalid -onion address: '%s'"), onionArg));
SetProxy(NET_TOR, addrOnion);
SetLimited(NET_TOR, false);
SetProxy(NET_ONION, addrOnion);
SetLimited(NET_ONION, false);
}
}
// see Step 2: parameter interactions for more information about these
fListen = GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = GetBoolArg("-discover", true);
fListen = GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = GetBoolArg("-discover", true);
fNameLookup = GetBoolArg("-dns", true);
//fprintf(stderr,"%s tik22\n", __FUNCTION__);
@@ -1603,6 +1637,24 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
BOOST_FOREACH(const std::string& strDest, mapMultiArgs["-seednode"])
AddOneShot(strDest);
if (mapArgs.count("-tlskeypath")) {
boost::filesystem::path pathTLSKey(GetArg("-tlskeypath", ""));
if (!boost::filesystem::exists(pathTLSKey))
return InitError(strprintf(_("Cannot find TLS key file: '%s'"), pathTLSKey.string()));
}
if (mapArgs.count("-tlscertpath")) {
boost::filesystem::path pathTLSCert(GetArg("-tlscertpath", ""));
if (!boost::filesystem::exists(pathTLSCert))
return InitError(strprintf(_("Cannot find TLS cert file: '%s'"), pathTLSCert.string()));
}
if (mapArgs.count("-tlstrustdir")) {
boost::filesystem::path pathTLSTrustredDir(GetArg("-tlstrustdir", ""));
if (!boost::filesystem::exists(pathTLSTrustredDir))
return InitError(strprintf(_("Cannot find trusted certificates directory: '%s'"), pathTLSTrustredDir.string()));
}
#if ENABLE_ZMQ
pzmqNotificationInterface = CZMQNotificationInterface::CreateWithArguments(mapArgs);
@@ -2119,7 +2171,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
LOCK(cs_main);
fHaveGenesis = (chainActive.Tip() != NULL);
MilliSleep(10);
}
}
if (!fHaveGenesis) {
MilliSleep(10);

View File

@@ -33,6 +33,7 @@
#include "scheduler.h"
#include "ui_interface.h"
#include "crypto/common.h"
#include "hush/utiltls.h"
#ifdef _WIN32
#include <string.h>
@@ -43,6 +44,12 @@
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <hush/tlsmanager.cpp>
using namespace hush;
// Dump addresses to peers.dat every 15 minutes (900s)
#define DUMP_ADDRESSES_INTERVAL 900
@@ -61,6 +68,9 @@
#endif
#endif
#define USE_TLS
#define COMPAT_NON_TLS // enables compatibility with nodes, that still doesn't support TLS connections
using namespace std;
namespace {
@@ -95,6 +105,7 @@ CAddrMan addrman;
int nMaxConnections = DEFAULT_MAX_PEER_CONNECTIONS;
bool fAddressesInitialized = false;
std::string strSubVersion;
TLSManager tlsmanager = TLSManager();
vector<CNode*> vNodes;
CCriticalSection cs_vNodes;
@@ -122,6 +133,20 @@ static boost::condition_variable messageHandlerCondition;
static CNodeSignals g_signals;
CNodeSignals& GetNodeSignals() { return g_signals; }
// OpenSSL server and client contexts
SSL_CTX *tls_ctx_server, *tls_ctx_client;
static bool operator==(_NODE_ADDR a, _NODE_ADDR b)
{
return (a.ipAddr == b.ipAddr);
}
static std::vector<NODE_ADDR> vNonTLSNodesInbound;
static CCriticalSection cs_vNonTLSNodesInbound;
static std::vector<NODE_ADDR> vNonTLSNodesOutbound;
static CCriticalSection cs_vNonTLSNodesOutbound;
void AddOneShot(const std::string& strDest)
{
LOCK(cs_vOneShots);
@@ -409,8 +434,59 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
addrman.Attempt(addrConnect);
SSL *ssl = NULL;
#ifdef USE_TLS
/* TCP connection is ready. Do client side SSL. */
#ifdef COMPAT_NON_TLS
{
LOCK(cs_vNonTLSNodesOutbound);
NODE_ADDR nodeAddr(addrConnect.ToStringIP());
bool bUseTLS = ((GetBoolArg("-tls", true) || GetArg("-tls", "") == "only") && find(vNonTLSNodesOutbound.begin(),
vNonTLSNodesOutbound.end(),
nodeAddr) == vNonTLSNodesOutbound.end());
if (bUseTLS)
{
ssl = tlsmanager.connect(hSocket, addrConnect);
if (!ssl)
{
if (GetArg("-tls", "") != "only")
{
// Further reconnection will be made in non-TLS (unencrypted) mode if mandatory tls is not set
vNonTLSNodesOutbound.push_back(NODE_ADDR(addrConnect.ToStringIP(), GetTimeMillis()));
}
CloseSocket(hSocket);
return NULL;
}
}
else
{
LogPrintf ("Connection to %s will be unencrypted\n", addrConnect.ToString());
vNonTLSNodesOutbound.erase(
remove(
vNonTLSNodesOutbound.begin(),
vNonTLSNodesOutbound.end(),
nodeAddr),
vNonTLSNodesOutbound.end());
}
}
#else
ssl = TLSManager::connect(hSocket, addrConnect);
if(!ssl)
{
CloseSocket(hSocket);
return NULL;
}
#endif // COMPAT_NON_TLS
#endif // USE_TLS
// Add node
CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false);
CNode* pnode = new CNode(hSocket, addrConnect, pszDest ? pszDest : "", false, ssl);
pnode->AddRef();
{
@@ -433,11 +509,33 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
void CNode::CloseSocketDisconnect()
{
fDisconnect = true;
{
LOCK(cs_hSocket);
if (hSocket != INVALID_SOCKET)
{
try
{
LogPrint("net", "disconnecting peer=%d\n", id);
}
catch(std::bad_alloc&)
{
// when the node is shutting down, the call above might use invalid memory resulting in a
// std::bad_alloc exception when instantiating internal objs for handling log category
LogPrintf("(node is probably shutting down) disconnecting peer=%d\n", id);
}
if (ssl)
{
tlsmanager.waitFor(SSL_SHUTDOWN, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000));
SSL_free(ssl);
ssl = NULL;
}
CloseSocket(hSocket);
}
}
// in case this fails, we'll empty the recv buffer when the CNode is deleted
TRY_LOCK(cs_vRecvMsg, lockRecv);
@@ -568,10 +666,13 @@ void CNode::AddWhitelistedRange(const CSubNet &subnet) {
vWhitelistedRange.push_back(subnet);
}
void CNode::copyStats(CNodeStats &stats)
void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap)
{
stats.nodeid = this->GetId();
stats.nServices = nServices;
stats.addr = addr;
// stats.addrBind = addrBind;
stats.m_mapped_as = addr.GetMappedAS(m_asmap);
stats.nLastSend = nLastSend;
stats.nLastRecv = nLastRecv;
stats.nTimeConnected = nTimeConnected;
@@ -602,6 +703,12 @@ void CNode::copyStats(CNodeStats &stats)
// Leave string empty if addrLocal invalid (not filled in yet)
stats.addrLocal = addrLocal.IsValid() ? addrLocal.ToString() : "";
// If ssl != NULL it means TLS connection was established successfully
{
LOCK(cs_hSocket);
stats.fTLSEstablished = (ssl != NULL) && (SSL_get_state(ssl) == TLS_ST_OK);
}
}
// requires LOCK(cs_vRecvMsg)
@@ -700,8 +807,34 @@ void SocketSendData(CNode *pnode)
while (it != pnode->vSendMsg.end()) {
const CSerializeData &data = *it;
assert(data.size() > pnode->nSendOffset);
int nBytes = send(pnode->hSocket, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT);
if (nBytes > 0) {
bool bIsSSL = false;
int nBytes = 0, nRet = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
{
LogPrint("net", "Send: connection with %s is already closed\n", pnode->addr.ToString());
break;
}
bIsSSL = (pnode->ssl != NULL);
if (bIsSSL)
{
ERR_clear_error(); // clear the error queue, otherwise we may be reading an old error that occurred previously in the current thread
nBytes = SSL_write(pnode->ssl, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset);
nRet = SSL_get_error(pnode->ssl, nBytes);
}
else
{
nBytes = send(pnode->hSocket, &data[pnode->nSendOffset], data.size() - pnode->nSendOffset, MSG_NOSIGNAL | MSG_DONTWAIT);
nRet = WSAGetLastError();
}
}
if (nBytes > 0)
{
pnode->nLastSend = GetTime();
pnode->nSendBytes += nBytes;
pnode->nSendOffset += nBytes;
@@ -715,15 +848,33 @@ void SocketSendData(CNode *pnode)
break;
}
} else {
if (nBytes < 0) {
if (nBytes <= 0) {
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
//
if (bIsSSL)
{
LogPrintf("socket send error %s\n", NetworkErrorString(nErr));
if (nRet != SSL_ERROR_WANT_READ && nRet != SSL_ERROR_WANT_WRITE)
{
LogPrintf("ERROR: SSL_write %s; closing connection\n", ERR_error_string(nRet, NULL));
pnode->CloseSocketDisconnect();
}
else
{
// preventive measure from exhausting CPU usage
//
MilliSleep(1); // 1 msec
}
}
else
{
if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS)
{
LogPrintf("ERROR: send %s; closing connection\n", NetworkErrorString(nRet));
pnode->CloseSocketDisconnect();
}
}
}
// couldn't send anything at all
break;
}
@@ -801,8 +952,8 @@ public:
CSHA256 hashA, hashB;
std::vector<unsigned char> vchA(32), vchB(32);
vchGroupA = a->addr.GetGroup();
vchGroupB = b->addr.GetGroup();
vchGroupA = a->addr.GetGroup(addrman.m_asmap);
vchGroupB = b->addr.GetGroup(addrman.m_asmap);
hashA.Write(begin_ptr(vchGroupA), vchGroupA.size());
hashB.Write(begin_ptr(vchGroupB), vchGroupB.size());
@@ -898,14 +1049,14 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
int64_t nMostConnectionsTime = 0;
std::map<std::vector<unsigned char>, std::vector<CNodeRef> > mapAddrCounts;
BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) {
mapAddrCounts[node->addr.GetGroup()].push_back(node);
int64_t grouptime = mapAddrCounts[node->addr.GetGroup()][0]->nTimeConnected;
size_t groupsize = mapAddrCounts[node->addr.GetGroup()].size();
mapAddrCounts[node->addr.GetGroup(addrman.m_asmap)].push_back(node);
int64_t grouptime = mapAddrCounts[node->addr.GetGroup(addrman.m_asmap)][0]->nTimeConnected;
size_t groupsize = mapAddrCounts[node->addr.GetGroup(addrman.m_asmap)].size();
if (groupsize > nMostConnections || (groupsize == nMostConnections && grouptime > nMostConnectionsTime)) {
nMostConnections = groupsize;
nMostConnectionsTime = grouptime;
naMostConnections = node->addr.GetGroup();
naMostConnections = node->addr.GetGroup(addrman.m_asmap);
}
}
@@ -1003,7 +1154,59 @@ static void AcceptConnection(const ListenSocket& hListenSocket) {
setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (void*)&set, sizeof(int));
#endif
CNode* pnode = new CNode(hSocket, addr, "", true);
SSL *ssl = NULL;
SetSocketNonBlocking(hSocket, true);
#ifdef USE_TLS
/* TCP connection is ready. Do server side SSL. */
#ifdef COMPAT_NON_TLS
{
LOCK(cs_vNonTLSNodesInbound);
NODE_ADDR nodeAddr(addr.ToStringIP());
bool bUseTLS = ((GetBoolArg("-tls", true) || GetArg("-tls", "") == "only") && find(vNonTLSNodesInbound.begin(),
vNonTLSNodesInbound.end(),
nodeAddr) == vNonTLSNodesInbound.end());
if (bUseTLS)
{
ssl = tlsmanager.accept( hSocket, addr);
if(!ssl)
{
if (GetArg("-tls", "") != "only")
{
// Further reconnection will be made in non-TLS (unencrypted) mode if mandatory tls is not set
vNonTLSNodesInbound.push_back(NODE_ADDR(addr.ToStringIP(), GetTimeMillis()));
}
CloseSocket(hSocket);
return;
}
}
else
{
LogPrintf ("TLS: Connection from %s will be unencrypted\n", addr.ToString());
vNonTLSNodesInbound.erase(
remove(
vNonTLSNodesInbound.begin(),
vNonTLSNodesInbound.end(),
nodeAddr
),
vNonTLSNodesInbound.end());
}
}
#else
ssl = TLSManager::accept( hSocket, addr);
if(!ssl)
{
CloseSocket(hSocket);
return;
}
#endif // COMPAT_NON_TLS
#endif // USE_TLS
CNode* pnode = new CNode(hSocket, addr, "", true, ssl);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
@@ -1015,6 +1218,18 @@ static void AcceptConnection(const ListenSocket& hListenSocket) {
}
}
#if defined(USE_TLS) && defined(COMPAT_NON_TLS)
void ThreadNonTLSPoolsCleaner()
{
while (true)
{
tlsmanager.cleanNonTLSPool(vNonTLSNodesInbound, cs_vNonTLSNodesInbound);
tlsmanager.cleanNonTLSPool(vNonTLSNodesOutbound, cs_vNonTLSNodesOutbound);
MilliSleep(DEFAULT_CONNECT_TIMEOUT); // sleep and sleep_for are interruption points, which will throw boost::thread_interrupted
}
}
#endif // USE_TLS && COMPAT_NON_TLS
void ThreadSocketHandler()
{
unsigned int nPrevNodeCount = 0;
@@ -1109,8 +1324,10 @@ void ThreadSocketHandler()
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes)
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET)
continue;
FD_SET(pnode->hSocket, &fdsetError);
hSocketMax = max(hSocketMax, pnode->hSocket);
have_fds = true;
@@ -1190,61 +1407,8 @@ void ThreadSocketHandler()
{
boost::this_thread::interruption_point();
//
// Receive
//
if (pnode->hSocket == INVALID_SOCKET)
if (tlsmanager.threadSocketHandler(pnode,fdsetRecv,fdsetSend,fdsetError)==-1)
continue;
if (FD_ISSET(pnode->hSocket, &fdsetRecv) || FD_ISSET(pnode->hSocket, &fdsetError))
{
TRY_LOCK(pnode->cs_vRecvMsg, lockRecv);
if (lockRecv)
{
{
// typical socket buffer is 8K-64K
char pchBuf[0x10000];
int nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
if (nBytes > 0)
{
if (!pnode->ReceiveMsgBytes(pchBuf, nBytes))
pnode->CloseSocketDisconnect();
pnode->nLastRecv = GetTime();
pnode->nRecvBytes += nBytes;
pnode->RecordBytesRecv(nBytes);
}
else if (nBytes == 0)
{
// socket closed gracefully
if (!pnode->fDisconnect)
LogPrint("net", "socket closed\n");
pnode->CloseSocketDisconnect();
}
else if (nBytes < 0)
{
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
{
if (!pnode->fDisconnect)
LogPrintf("socket recv error %s\n", NetworkErrorString(nErr));
pnode->CloseSocketDisconnect();
}
}
}
}
}
//
// Send
//
if (pnode->hSocket == INVALID_SOCKET)
continue;
if (FD_ISSET(pnode->hSocket, &fdsetSend))
{
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend)
SocketSendData(pnode);
}
//
// Inactivity checking
@@ -1423,7 +1587,7 @@ void ThreadOpenConnections()
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes) {
if (!pnode->fInbound) {
setConnected.insert(pnode->addr.GetGroup());
setConnected.insert(pnode->addr.GetGroup(addrman.m_asmap));
nOutbound++;
}
}
@@ -1437,7 +1601,7 @@ void ThreadOpenConnections()
CAddrInfo addr = addrman.Select();
// if we selected an invalid address, restart
if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr))
if (!addr.IsValid() || setConnected.count(addr.GetGroup(addrman.m_asmap)) || IsLocal(addr))
break;
// If we didn't find an appropriate destination after trying 100 addresses fetched from addrman,
@@ -1561,6 +1725,28 @@ bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOu
CNode* pnode = ConnectNode(addrConnect, pszDest);
boost::this_thread::interruption_point();
#if defined(USE_TLS) && defined(COMPAT_NON_TLS)
if (!pnode)
{
string strDest;
int port;
if (!pszDest)
strDest = addrConnect.ToStringIP();
else
SplitHostPort(string(pszDest), port, strDest);
if (tlsmanager.isNonTLSAddr(strDest, vNonTLSNodesOutbound, cs_vNonTLSNodesOutbound))
{
// Attempt to reconnect in non-TLS mode
pnode = ConnectNode(addrConnect, pszDest);
boost::this_thread::interruption_point();
}
}
#endif
if (!pnode)
return false;
if (grantOutbound)
@@ -1688,6 +1874,11 @@ bool BindListenPort(const CService &addrBind, string& strError, bool fWhiteliste
#endif
// Set to non-blocking, incoming connections will also inherit this
//
// WARNING!
// On Linux, the new socket returned by accept() does not inherit file
// status flags such as O_NONBLOCK and O_ASYNC from the listening
// socket. http://man7.org/linux/man-pages/man2/accept.2.html
if (!SetSocketNonBlocking(hListenSocket, true)) {
strError = strprintf("BindListenPort: Setting listening socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
@@ -1816,6 +2007,23 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
Discover(threadGroup);
#ifdef USE_TLS
if (!tlsmanager.prepareCredentials())
{
LogPrintf("TLS: ERROR: %s: %s: Credentials weren't loaded. Node can't be started.\n", __FILE__, __func__);
return;
}
if (!tlsmanager.initialize())
{
LogPrintf("TLS: ERROR: %s: %s: TLS initialization failed. Node can't be started.\n", __FILE__, __func__);
return;
}
#else
LogPrintf("TLS is not used!\n");
#endif
// skip DNS seeds for staked chains.
extern int8_t is_STAKED(const char *chain_name);
extern char ASSETCHAINS_SYMBOL[65];
@@ -1843,6 +2051,11 @@ void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler)
// Process messages
threadGroup.create_thread(boost::bind(&TraceThread<void (*)()>, "msghand", &ThreadMessageHandler));
#if defined(USE_TLS) && defined(COMPAT_NON_TLS)
// Clean pools of addresses for non-TLS connections
threadGroup.create_thread(boost::bind(&TraceThread<void (*)()>, "poolscleaner", &ThreadNonTLSPoolsCleaner));
#endif
// Dump network addresses
scheduler.scheduleEvery(&DumpAddresses, DUMP_ADDRESSES_INTERVAL);
}
@@ -2101,11 +2314,12 @@ bool CAddrDB::Read(CAddrMan& addr)
unsigned int ReceiveFloodSize() { return 1000*GetArg("-maxreceivebuffer", 5*1000); }
unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); }
CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNameIn, bool fInboundIn) :
CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNameIn, bool fInboundIn, SSL *sslIn) :
ssSend(SER_NETWORK, INIT_PROTO_VERSION),
addrKnown(5000, 0.001),
setInventoryKnown(SendBufferSize() / 1000)
{
ssl = sslIn;
nServices = 0;
hSocket = hSocketIn;
nRecvVersion = INIT_PROTO_VERSION;
@@ -2160,7 +2374,21 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
CNode::~CNode()
{
// No need to make a lock on cs_hSocket, because before deletion CNode object is removed from the vNodes vector, so any other thread hasn't access to it.
// Removal is synchronized with read and write routines, so all of them will be completed to this moment.
if (hSocket != INVALID_SOCKET)
{
if (ssl)
{
tlsmanager.waitFor(SSL_SHUTDOWN, hSocket, ssl, (DEFAULT_CONNECT_TIMEOUT / 1000));
SSL_free(ssl);
ssl = NULL;
}
CloseSocket(hSocket);
}
if (pfilter)
delete pfilter;
@@ -2261,3 +2489,16 @@ void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend)
LEAVE_CRITICAL_SECTION(cs_vSend);
}
void CopyNodeStats(std::vector<CNodeStats>& vstats)
{
vstats.clear();
LOCK(cs_vNodes);
vstats.reserve(vNodes.size());
BOOST_FOREACH(CNode* pnode, vNodes) {
CNodeStats stats;
pnode->copyStats(stats, addrman.m_asmap);
vstats.push_back(stats);
}
}

View File

@@ -46,6 +46,10 @@
#include <boost/foreach.hpp>
#include <boost/signals2/signal.hpp>
// Enable OpenSSL Support for Hush
#include <openssl/bio.h>
#include <openssl/ssl.h>
class CAddrMan;
class CBlockIndex;
class CScheduler;
@@ -94,9 +98,25 @@ bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhite
void StartNode(boost::thread_group& threadGroup, CScheduler& scheduler);
bool StopNode();
void SocketSendData(CNode *pnode);
SSL_CTX* create_context(bool server_side);
EVP_PKEY *generate_key();
X509 *generate_x509(EVP_PKEY *pkey);
bool write_to_disk(EVP_PKEY *pkey, X509 *x509);
void configure_context(SSL_CTX *ctx, bool server_side);
static boost::filesystem::path tlsKeyPath;
static boost::filesystem::path tlsCertPath;
// OpenSSL related variables for metrics.cpp
static std::string routingsecrecy;
static std::string cipherdescription;
static std::string securitylevel;
static std::string validationdescription;
typedef int NodeId;
class CNodeStats;
void CopyNodeStats(std::vector<CNodeStats>& vstats);
struct CombinerAll
{
typedef bool result_type;
@@ -177,6 +197,9 @@ extern CCriticalSection cs_nLastNodeId;
/** Subversion as sent to the P2P network in `version` messages */
extern std::string strSubVersion;
extern SSL_CTX *tls_ctx_server;
extern SSL_CTX *tls_ctx_client;
struct LocalServiceInfo {
int nScore;
int nPort;
@@ -190,6 +213,7 @@ class CNodeStats
public:
NodeId nodeid;
uint64_t nServices;
bool fTLSEstablished;
int64_t nLastSend;
int64_t nLastRecv;
int64_t nTimeConnected;
@@ -205,6 +229,11 @@ public:
double dPingTime;
double dPingWait;
std::string addrLocal;
// Address of this peer
CAddress addr;
// Bind address of our side of the connection
// CAddress addrBind; // https://github.com/bitcoin/bitcoin/commit/a7e3c2814c8e49197889a4679461be42254e5c51
uint32_t m_mapped_as;
};
@@ -256,9 +285,13 @@ public:
class CNode
{
public:
// OpenSSL
SSL *ssl;
// socket
uint64_t nServices;
SOCKET hSocket;
CCriticalSection cs_hSocket;
CDataStream ssSend;
size_t nSendSize; // total size of all vSendMsg entries
size_t nSendOffset; // offset inside the first vSendMsg already sent
@@ -277,7 +310,10 @@ public:
int64_t nTimeConnected;
int64_t nTimeOffset;
uint32_t prevtimes[16];
// Address of this peer
CAddress addr;
// Bind address of our side of the connection
// const CAddress addrBind; // https://github.com/bitcoin/bitcoin/commit/a7e3c2814c8e49197889a4679461be42254e5c51
std::string addrName;
CService addrLocal;
int nVersion;
@@ -352,7 +388,7 @@ public:
// Whether a ping is requested.
bool fPingQueued;
CNode(SOCKET hSocketIn, const CAddress &addrIn, const std::string &addrNameIn = "", bool fInboundIn = false);
CNode(SOCKET hSocketIn, const CAddress &addrIn, const std::string &addrNameIn = "", bool fInboundIn = false, SSL *sslIn = NULL);
~CNode();
private:
@@ -423,7 +459,7 @@ public:
if (addr.IsValid() && !addrKnown.contains(addr.GetKey())) {
if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) {
vAddrToSend[insecure_rand() % vAddrToSend.size()] = addr;
} else {
} else {
vAddrToSend.push_back(addr);
}
}
@@ -646,7 +682,7 @@ public:
static bool Unban(const CSubNet &ip);
static void GetBanned(std::map<CSubNet, int64_t> &banmap);
void copyStats(CNodeStats &stats);
void copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap);
static bool IsWhitelistedRange(const CNetAddr &ip);
static void AddWhitelistedRange(const CSubNet &subnet);

View File

@@ -30,6 +30,7 @@
#include "random.h"
#include "util.h"
#include "utilstrencodings.h"
#include "crypto/common.h" // for ReadBE32
#ifdef __APPLE__
#ifdef HAVE_GETADDRINFO_A
@@ -65,6 +66,9 @@ bool fNameLookup = false;
static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
// 0xFD + sha256("bitcoin")[0:5]
static const unsigned char g_internal_prefix[] = { 0xFD, 0x6B, 0x88, 0xC0, 0x87, 0x24 };
// Need ample time for negotiation for very slow proxies such as Tor (milliseconds)
static const int SOCKS5_RECV_TIMEOUT = 20 * 1000;
@@ -72,7 +76,7 @@ enum Network ParseNetwork(std::string net) {
boost::to_lower(net);
if (net == "ipv4") return NET_IPV4;
if (net == "ipv6") return NET_IPV6;
if (net == "tor" || net == "onion") return NET_TOR;
if (net == "tor" || net == "onion") return NET_ONION;
return NET_UNROUTABLE;
}
@@ -81,7 +85,7 @@ std::string GetNetworkName(enum Network net) {
{
case NET_IPV4: return "ipv4";
case NET_IPV6: return "ipv6";
case NET_TOR: return "onion";
case NET_ONION: return "onion";
default: return "";
}
}
@@ -879,6 +883,11 @@ bool CNetAddr::IsRoutable() const
return IsValid() && !(IsRFC1918() || IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || (IsRFC4193() && !IsTor()) || IsRFC4843() || IsLocal());
}
bool CNetAddr::IsInternal() const
{
return memcmp(ip, g_internal_prefix, sizeof(g_internal_prefix)) == 0;
}
enum Network CNetAddr::GetNetwork() const
{
if (!IsRoutable())
@@ -888,7 +897,7 @@ enum Network CNetAddr::GetNetwork() const
return NET_IPV4;
if (IsTor())
return NET_TOR;
return NET_ONION;
return NET_IPV6;
}
@@ -949,11 +958,88 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
return true;
}
bool CNetAddr::HasLinkedIPv4() const
{
return IsRoutable() && (IsIPv4() || IsRFC6145() || IsRFC6052() || IsRFC3964() || IsRFC4380());
}
uint32_t CNetAddr::GetLinkedIPv4() const
{
if (IsIPv4() || IsRFC6145() || IsRFC6052()) {
// IPv4, mapped IPv4, SIIT translated IPv4: the IPv4 address is the last 4 bytes of the address
return ReadBE32(ip + 12);
} else if (IsRFC3964()) {
// 6to4 tunneled IPv4: the IPv4 address is in bytes 2-6
return ReadBE32(ip + 2);
} else if (IsRFC4380()) {
// Teredo tunneled IPv4: the IPv4 address is in the last 4 bytes of the address, but bitflipped
return ~ReadBE32(ip + 12);
}
assert(false);
}
uint32_t CNetAddr::GetNetClass() const {
uint32_t net_class = NET_IPV6;
if (IsLocal()) {
net_class = 255;
}
if (IsInternal()) {
net_class = NET_INTERNAL;
} else if (!IsRoutable()) {
net_class = NET_UNROUTABLE;
} else if (HasLinkedIPv4()) {
net_class = NET_IPV4;
} else if (IsTor()) {
net_class = NET_ONION;
}
return net_class;
}
uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const {
uint32_t net_class = GetNetClass();
if (asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) {
return 0; // Indicates not found, safe because AS0 is reserved per RFC7607.
}
std::vector<bool> ip_bits(128);
if (HasLinkedIPv4()) {
// For lookup, treat as if it was just an IPv4 address (pchIPv4 prefix + IPv4 bits)
for (int8_t byte_i = 0; byte_i < 12; ++byte_i) {
for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) {
ip_bits[byte_i * 8 + bit_i] = (pchIPv4[byte_i] >> (7 - bit_i)) & 1;
}
}
uint32_t ipv4 = GetLinkedIPv4();
for (int i = 0; i < 32; ++i) {
ip_bits[96 + i] = (ipv4 >> (31 - i)) & 1;
}
} else {
// Use all 128 bits of the IPv6 address otherwise
for (int8_t byte_i = 0; byte_i < 16; ++byte_i) {
uint8_t cur_byte = GetByte(15 - byte_i);
for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) {
ip_bits[byte_i * 8 + bit_i] = (cur_byte >> (7 - bit_i)) & 1;
}
}
}
uint32_t mapped_as = Interpret(asmap, ip_bits);
return mapped_as;
}
// get canonical identifier of an address' group
// no two connections will be attempted to addresses with the same group
std::vector<unsigned char> CNetAddr::GetGroup() const
std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) const
{
std::vector<unsigned char> vchRet;
// If non-empty asmap is supplied and the address is IPv4/IPv6,
// return ASN to be used for bucketing.
uint32_t asn = GetMappedAS(asmap);
if (asn != 0) { // Either asmap was empty, or address has non-asmappable net class (e.g. TOR).
vchRet.push_back(NET_IPV6); // IPv4 and IPv6 with same ASN should be in the same bucket
for (int i = 0; i < 4; i++) {
vchRet.push_back((asn >> (8 * i)) & 0xFF);
}
return vchRet;
}
int nClass = NET_IPV6;
int nStartByte = 0;
int nBits = 16;
@@ -994,7 +1080,7 @@ std::vector<unsigned char> CNetAddr::GetGroup() const
}
else if (IsTor())
{
nClass = NET_TOR;
nClass = NET_ONION;
nStartByte = 6;
nBits = 4;
}
@@ -1072,11 +1158,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
case NET_IPV4: return REACH_IPV4;
case NET_IPV6: return fTunnel ? REACH_IPV6_WEAK : REACH_IPV6_STRONG; // only prefer giving our IPv6 address if it's not tunnelled
}
case NET_TOR:
case NET_ONION:
switch(ourNet) {
default: return REACH_DEFAULT;
case NET_IPV4: return REACH_IPV4; // Tor users can connect to IPv4 as well
case NET_TOR: return REACH_PRIVATE;
case NET_ONION: return REACH_PRIVATE;
}
case NET_TEREDO:
switch(ourNet) {
@@ -1093,7 +1179,7 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const
case NET_TEREDO: return REACH_TEREDO;
case NET_IPV6: return REACH_IPV6_WEAK;
case NET_IPV4: return REACH_IPV4;
case NET_TOR: return REACH_PRIVATE; // either from Tor, or don't care about our address
case NET_ONION: return REACH_PRIVATE; // either from Tor, or don't care about our address
}
}
}
@@ -1443,3 +1529,8 @@ bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking)
return true;
}
bool SanityCheckASMap(const std::vector<bool>& asmap)
{
return SanityCheckASMap(asmap, 128); // For IP address lookups, the input is 128 bits
}

View File

@@ -26,6 +26,7 @@
#include "compat.h"
#include "serialize.h"
#include "util/asmap.h"
#include <stdint.h>
#include <string>
@@ -47,7 +48,8 @@ enum Network
NET_UNROUTABLE = 0,
NET_IPV4,
NET_IPV6,
NET_TOR,
NET_ONION,
NET_INTERNAL,
NET_MAX,
};
@@ -91,6 +93,7 @@ class CNetAddr
bool IsTor() const;
bool IsLocal() const;
bool IsRoutable() const;
bool IsInternal() const;
bool IsValid() const;
bool IsMulticast() const;
enum Network GetNetwork() const;
@@ -99,7 +102,19 @@ class CNetAddr
unsigned int GetByte(int n) const;
uint64_t GetHash() const;
bool GetInAddr(struct in_addr* pipv4Addr) const;
std::vector<unsigned char> GetGroup() const;
uint32_t GetNetClass() const;
//! For IPv4, mapped IPv4, SIIT translated IPv4, Teredo, 6to4 tunneled addresses, return the relevant IPv4 address as a uint32.
uint32_t GetLinkedIPv4() const;
//! Whether this address has a linked IPv4 address (see GetLinkedIPv4()).
bool HasLinkedIPv4() const;
// The AS on the BGP path to the node we use to diversify
// peers in AddrMan bucketing based on the AS infrastructure.
// The ip->AS mapping depends on how asmap is constructed.
uint32_t GetMappedAS(const std::vector<bool> &asmap) const;
std::vector<unsigned char> GetGroup(const std::vector<bool> &asmap) const;
int GetReachabilityFrom(const CNetAddr *paddrPartner = NULL) const;
CNetAddr(const struct in6_addr& pipv6Addr);
@@ -223,4 +238,6 @@ bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking);
*/
struct timeval MillisToTimeval(int64_t nTimeout);
bool SanityCheckASMap(const std::vector<bool>& asmap);
#endif // BITCOIN_NETBASE_H

View File

@@ -208,6 +208,7 @@ UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
" \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n"
" \"timeoffset\": xxxxx, (numeric) the time offset (deprecated, always 0)\n"
" \"connections\": xxxxx, (numeric) the number of connections\n"
" \"tls_connections\": xxxxx, (numeric) the number of TLS connections\n"
" \"proxy\": \"host:port\", (string, optional) the proxy used by the server\n"
" \"difficulty\": xxxxxx, (numeric) the current difficulty\n"
" \"testnet\": true|false, (boolean) if the server is using testnet or not\n"
@@ -288,6 +289,7 @@ UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
}
obj.push_back(Pair("timeoffset", 0));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("tls_connections", (int)std::count_if(vNodes.begin(), vNodes.end(), [](CNode* n) {return n->ssl != NULL;})));
obj.push_back(Pair("proxy", (proxy.IsValid() ? proxy.proxy.ToStringIPPort() : string())));
obj.push_back(Pair("testnet", Params().TestnetToBeDeprecatedFieldRPC()));
obj.push_back(Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())));

View File

@@ -30,12 +30,14 @@
#include "util.h"
#include "version.h"
#include "deprecation.h"
#include "hush/utiltls.h"
#include <boost/foreach.hpp>
#include <univalue.h>
using namespace std;
using namespace hush;
UniValue getconnectioncount(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
@@ -78,19 +80,6 @@ UniValue ping(const UniValue& params, bool fHelp, const CPubKey& mypk)
return NullUniValue;
}
static void CopyNodeStats(std::vector<CNodeStats>& vstats)
{
vstats.clear();
LOCK(cs_vNodes);
vstats.reserve(vNodes.size());
BOOST_FOREACH(CNode* pnode, vNodes) {
CNodeStats stats;
pnode->copyStats(stats);
vstats.push_back(stats);
}
}
UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 0)
@@ -104,6 +93,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
" \"addr\":\"host:port\", (string) The ip address and port of the peer\n"
" \"addrlocal\":\"ip:port\", (string) local address\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
" \"tls_established\": true:false, (boolean) Status of TLS connection\n"
" \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n"
" \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n"
" \"bytessent\": n, (numeric) The total bytes sent\n"
@@ -146,13 +136,19 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
obj.push_back(Pair("addr", stats.addrName));
if (!(stats.addrLocal.empty()))
obj.push_back(Pair("addrlocal", stats.addrLocal));
// if (stats.addrBind.IsValid())
// obj.push_back(Pair("addrbind", stats.addrBind.ToString()));
if (stats.m_mapped_as != 0) {
obj.push_back(Pair("mapped_as", uint64_t(stats.m_mapped_as)));
}
obj.push_back(Pair("services", strprintf("%016x", stats.nServices)));
obj.push_back(Pair("tls_established", stats.fTLSEstablished));
obj.push_back(Pair("lastsend", stats.nLastSend));
obj.push_back(Pair("lastrecv", stats.nLastRecv));
obj.push_back(Pair("bytessent", stats.nSendBytes));
obj.push_back(Pair("bytesrecv", stats.nRecvBytes));
obj.push_back(Pair("conntime", stats.nTimeConnected));
obj.push_back(Pair("timeoffset", 0));
obj.push_back(Pair("timeoffset", 0));
obj.push_back(Pair("pingtime", stats.dPingTime));
if (stats.dPingWait > 0.0)
obj.push_back(Pair("pingwait", stats.dPingWait));
@@ -505,6 +501,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n"
" \"timeoffset\": xxxxx, (numeric) the time offset (deprecated, always 0)\n"
" \"connections\": xxxxx, (numeric) the number of connections\n"
" \"tls_connections\": xxxxx, (numeric) the number of TLS connections\n"
" \"networks\": [ (array) information per network\n"
" {\n"
" \"name\": \"xxx\", (string) network (ipv4, ipv6 or onion)\n"
@@ -539,6 +536,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
obj.push_back(Pair("localservices", strprintf("%016x", nLocalServices)));
obj.push_back(Pair("timeoffset", 0));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("tls_connections", (int)std::count_if(vNodes.begin(), vNodes.end(), [](CNode* n) {return n->ssl != NULL;})));
obj.push_back(Pair("networks", GetNetworksInfo()));
obj.push_back(Pair("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())));
UniValue localAddresses(UniValue::VARR);

View File

@@ -0,0 +1,862 @@
#include <gtest/gtest.h>
#include "addrman.h"
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <string>
#include "hash.h"
#include "random.h"
#include "util/asmap.h"
#include "netbase.h"
#include "chainparams.h"
#include "tinyformat.h"
#include "utilstrencodings.h"
#define NODE_NONE 0
// https://stackoverflow.com/questions/16491675/how-to-send-custom-message-in-google-c-testing-framework/29155677
#define GTEST_COUT_NOCOLOR std::cerr << "[ ] [ INFO ] "
namespace testing
{
namespace internal
{
enum GTestColor {
COLOR_DEFAULT,
COLOR_RED,
COLOR_GREEN,
COLOR_YELLOW
};
extern void ColoredPrintf(GTestColor color, const char* fmt, ...);
}
}
#define PRINTF(...) do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, "[ ] "); testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } while(0)
// C++ stream interface
class TestCout : public std::stringstream
{
public:
~TestCout()
{
PRINTF("%s",str().c_str());
}
};
#define GTEST_COUT_COLOR TestCout()
using namespace std;
/* xxd -i est-komodo/data/asmap.raw | sed 's/unsigned char/static unsigned const char/g' */
static unsigned const char asmap_raw[] = {
0xfb, 0x03, 0xec, 0x0f, 0xb0, 0x3f, 0xc0, 0xfe, 0x00, 0xfb, 0x03, 0xec,
0x0f, 0xb0, 0x3f, 0xc0, 0xfe, 0x00, 0xfb, 0x03, 0xec, 0x0f, 0xb0, 0xff,
0xff, 0xfe, 0xff, 0xed, 0xb0, 0xff, 0xd4, 0x86, 0xe6, 0x28, 0x29, 0x00,
0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x99, 0x01, 0x00, 0x80, 0x01,
0x80, 0x04, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x1c, 0xf0, 0x39
};
unsigned int asmap_raw_len = 59;
class CAddrManTest : public CAddrMan
{
private:
uint64_t state;
bool deterministic;
public:
explicit CAddrManTest(bool makeDeterministic = true,
std::vector<bool> asmap = std::vector<bool>())
{
if (makeDeterministic) {
// Set addrman addr placement to be deterministic.
MakeDeterministic();
}
deterministic = makeDeterministic;
m_asmap = asmap;
state = 1;
}
void PrintInternals()
{
GTEST_COUT_NOCOLOR << "mapInfo.size() = " << mapInfo.size() << std::endl;
GTEST_COUT_NOCOLOR << "nNew = " << nNew << std::endl;
}
//! Ensure that bucket placement is always the same for testing purposes.
void MakeDeterministic()
{
nKey.SetNull();
seed_insecure_rand(true);
}
int RandomInt(int nMax)
{
state = (CHashWriter(SER_GETHASH, 0) << state).GetHash().GetCheapHash();
return (unsigned int)(state % nMax);
}
CAddrInfo* Find(const CNetAddr& addr, int* pnId = NULL)
{
return CAddrMan::Find(addr, pnId);
}
CAddrInfo* Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId = NULL)
{
return CAddrMan::Create(addr, addrSource, pnId);
}
void Delete(int nId)
{
CAddrMan::Delete(nId);
}
// Used to test deserialization
std::pair<int, int> GetBucketAndEntry(const CAddress& addr)
{
// LOCK(cs);
int nId = mapAddr[addr];
for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) {
for (int entry = 0; entry < ADDRMAN_BUCKET_SIZE; ++entry) {
if (nId == vvNew[bucket][entry]) {
return std::pair<int, int>(bucket, entry);
}
}
}
return std::pair<int, int>(-1, -1);
}
void Clear()
{
CAddrMan::Clear();
if (deterministic) {
nKey.SetNull();
seed_insecure_rand(true);
}
}
};
static CNetAddr ResolveIP(const std::string& ip)
{
vector<CNetAddr> vIPs;
CNetAddr addr;
if (LookupHost(ip.c_str(), vIPs)) {
addr = vIPs[0];
} else
{
// it was BOOST_CHECK_MESSAGE, but we can't use ASSERT or EXPECT outside a test
GTEST_COUT_COLOR << strprintf("failed to resolve: %s", ip) << std::endl;
}
return addr;
}
static CService ResolveService(const std::string& ip, const int port = 0)
{
CService serv;
if (!Lookup(ip.c_str(), serv, port, false))
GTEST_COUT_COLOR << strprintf("failed to resolve: %s:%i", ip, port) << std::endl;
return serv;
}
static std::vector<bool> FromBytes(const unsigned char* source, int vector_size) {
std::vector<bool> result(vector_size);
for (int byte_i = 0; byte_i < vector_size / 8; ++byte_i) {
unsigned char cur_byte = source[byte_i];
for (int bit_i = 0; bit_i < 8; ++bit_i) {
result[byte_i * 8 + bit_i] = (cur_byte >> bit_i) & 1;
}
}
return result;
}
namespace TestAddrmanTests {
TEST(TestAddrmanTests, display_constants) {
// Not actually the test, just used to display constants
GTEST_COUT_COLOR << "ADDRMAN_NEW_BUCKET_COUNT = " << ADDRMAN_NEW_BUCKET_COUNT << std::endl;
GTEST_COUT_COLOR << "ADDRMAN_TRIED_BUCKET_COUNT = " << ADDRMAN_TRIED_BUCKET_COUNT << std::endl;
GTEST_COUT_COLOR << "ADDRMAN_BUCKET_SIZE = " << ADDRMAN_BUCKET_SIZE << std::endl;
}
TEST(TestAddrmanTests, addrman_simple) {
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
CNetAddr source = CNetAddr("252.2.2.2");
// Test 1: Does Addrman respond correctly when empty.
ASSERT_TRUE(addrman.size() == 0);
CAddrInfo addr_null = addrman.Select();
ASSERT_TRUE(addr_null.ToString() == "[::]:0");
// Test 2: Does Addrman::Add work as expected.
CService addr1 = CService("250.1.1.1", 8333);
addrman.Add(CAddress(addr1, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 1);
CAddrInfo addr_ret1 = addrman.Select();
ASSERT_TRUE(addr_ret1.ToString() == "250.1.1.1:8333");
// Test 3: Does IP address deduplication work correctly.
// Expected dup IP should not be added.
CService addr1_dup = CService("250.1.1.1", 8333);
addrman.Add(CAddress(addr1_dup, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 1);
// Test 5: New table has one addr and we add a diff addr we should
// have two addrs.
CService addr2 = CService("250.1.1.2", 8333);
addrman.Add(CAddress(addr2, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 2);
// Test 6: AddrMan::Clear() should empty the new table.
addrman.Clear();
ASSERT_TRUE(addrman.size() == 0);
CAddrInfo addr_null2 = addrman.Select();
ASSERT_TRUE(addr_null2.ToString() == "[::]:0");
}
TEST(TestAddrmanTests, addrman_ports) {
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
CNetAddr source = CNetAddr("252.2.2.2");
ASSERT_TRUE(addrman.size() == 0);
// Test 7; Addr with same IP but diff port does not replace existing addr.
CService addr1 = CService("250.1.1.1", 8333);
addrman.Add(CAddress(addr1, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 1);
CService addr1_port = CService("250.1.1.1", 8334);
addrman.Add(CAddress(addr1_port, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 1);
CAddrInfo addr_ret2 = addrman.Select();
ASSERT_TRUE(addr_ret2.ToString() == "250.1.1.1:8333");
// Test 8: Add same IP but diff port to tried table, it doesn't get added.
// Perhaps this is not ideal behavior but it is the current behavior.
addrman.Good(CAddress(addr1_port, NODE_NONE));
ASSERT_TRUE(addrman.size() == 1);
bool newOnly = true;
CAddrInfo addr_ret3 = addrman.Select(newOnly);
ASSERT_TRUE(addr_ret3.ToString() == "250.1.1.1:8333");
}
TEST(TestAddrmanTests, addrman_select) {
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
CNetAddr source = CNetAddr("252.2.2.2");
// Test 9: Select from new with 1 addr in new.
CService addr1 = CService("250.1.1.1", 8333);
addrman.Add(CAddress(addr1, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 1);
bool newOnly = true;
CAddrInfo addr_ret1 = addrman.Select(newOnly);
ASSERT_TRUE(addr_ret1.ToString() == "250.1.1.1:8333");
// Test 10: move addr to tried, select from new expected nothing returned.
addrman.Good(CAddress(addr1, NODE_NONE));
ASSERT_TRUE(addrman.size() == 1);
CAddrInfo addr_ret2 = addrman.Select(newOnly);
ASSERT_TRUE(addr_ret2.ToString() == "[::]:0");
CAddrInfo addr_ret3 = addrman.Select();
ASSERT_TRUE(addr_ret3.ToString() == "250.1.1.1:8333");
ASSERT_TRUE(addrman.size() == 1);
// Add three addresses to new table.
CService addr2 = CService("250.3.1.1", 8333);
CService addr3 = CService("250.3.2.2", 9999);
CService addr4 = CService("250.3.3.3", 9999);
addrman.Add(CAddress(addr2, NODE_NONE), CService("250.3.1.1", 8333));
addrman.Add(CAddress(addr3, NODE_NONE), CService("250.3.1.1", 8333));
addrman.Add(CAddress(addr4, NODE_NONE), CService("250.4.1.1", 8333));
// Add three addresses to tried table.
CService addr5 = CService("250.4.4.4", 8333);
CService addr6 = CService("250.4.5.5", 7777);
CService addr7 = CService("250.4.6.6", 8333);
addrman.Add(CAddress(addr5, NODE_NONE), CService("250.3.1.1", 8333));
addrman.Good(CAddress(addr5, NODE_NONE));
addrman.Add(CAddress(addr6, NODE_NONE), CService("250.3.1.1", 8333));
addrman.Good(CAddress(addr6, NODE_NONE));
addrman.Add(CAddress(addr7, NODE_NONE), CService("250.1.1.3", 8333));
addrman.Good(CAddress(addr7, NODE_NONE));
// Test 11: 6 addrs + 1 addr from last test = 7.
ASSERT_TRUE(addrman.size() == 7);
// Test 12: Select pulls from new and tried regardless of port number.
ASSERT_TRUE(addrman.Select().ToString() == "250.4.6.6:8333");
ASSERT_TRUE(addrman.Select().ToString() == "250.3.2.2:9999");
ASSERT_TRUE(addrman.Select().ToString() == "250.3.3.3:9999");
ASSERT_TRUE(addrman.Select().ToString() == "250.4.4.4:8333");
}
TEST(TestAddrmanTests, addrman_new_collisions)
{
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
CNetAddr source = CNetAddr("252.2.2.2");
ASSERT_TRUE(addrman.size() == 0);
for (unsigned int i = 1; i < 18; i++) {
CService addr = CService("250.1.1." + boost::to_string(i));
addrman.Add(CAddress(addr, NODE_NONE), source);
//Test 13: No collision in new table yet.
ASSERT_TRUE(addrman.size() == i);
}
//Test 14: new table collision!
CService addr1 = CService("250.1.1.18");
addrman.Add(CAddress(addr1, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 17);
CService addr2 = CService("250.1.1.19");
addrman.Add(CAddress(addr2, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 18);
}
TEST(TestAddrmanTests, addrman_tried_collisions)
{
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
CNetAddr source = CNetAddr("252.2.2.2");
ASSERT_TRUE(addrman.size() == 0);
for (unsigned int i = 1; i < 80; i++) {
CService addr = CService("250.1.1." + boost::to_string(i));
addrman.Add(CAddress(addr, NODE_NONE), source);
addrman.Good(CAddress(addr, NODE_NONE));
//Test 15: No collision in tried table yet.
// GTEST_COUT << addrman.size() << std::endl;
ASSERT_TRUE(addrman.size() == i);
}
//Test 16: tried table collision!
CService addr1 = CService("250.1.1.80");
addrman.Add(CAddress(addr1, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 79);
CService addr2 = CService("250.1.1.81");
addrman.Add(CAddress(addr2, NODE_NONE), source);
ASSERT_TRUE(addrman.size() == 80);
}
TEST(TestAddrmanTests, addrman_find)
{
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
ASSERT_TRUE(addrman.size() == 0);
CAddress addr1 = CAddress(CService("250.1.2.1", 8333), NODE_NONE);
CAddress addr2 = CAddress(CService("250.1.2.1", 9999), NODE_NONE);
CAddress addr3 = CAddress(CService("251.255.2.1", 8333), NODE_NONE);
CNetAddr source1 = CNetAddr("250.1.2.1");
CNetAddr source2 = CNetAddr("250.1.2.2");
addrman.Add(addr1, source1);
addrman.Add(addr2, source2);
addrman.Add(addr3, source1);
// Test 17: ensure Find returns an IP matching what we searched on.
CAddrInfo* info1 = addrman.Find(addr1);
ASSERT_TRUE(info1);
if (info1)
ASSERT_TRUE(info1->ToString() == "250.1.2.1:8333");
// Test 18; Find does not discriminate by port number.
CAddrInfo* info2 = addrman.Find(addr2);
ASSERT_TRUE(info2);
if (info2)
ASSERT_TRUE(info2->ToString() == info1->ToString());
// Test 19: Find returns another IP matching what we searched on.
CAddrInfo* info3 = addrman.Find(addr3);
ASSERT_TRUE(info3);
if (info3)
ASSERT_TRUE(info3->ToString() == "251.255.2.1:8333");
}
TEST(TestAddrmanTests, addrman_create)
{
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
ASSERT_TRUE(addrman.size() == 0);
CAddress addr1 = CAddress(CService("250.1.2.1", 8333), NODE_NONE);
CNetAddr source1 = CNetAddr("250.1.2.1");
int nId;
CAddrInfo* pinfo = addrman.Create(addr1, source1, &nId);
// Test 20: The result should be the same as the input addr.
ASSERT_TRUE(pinfo->ToString() == "250.1.2.1:8333");
CAddrInfo* info2 = addrman.Find(addr1);
ASSERT_TRUE(info2->ToString() == "250.1.2.1:8333");
}
TEST(TestAddrmanTests, addrman_delete)
{
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
ASSERT_TRUE(addrman.size() == 0);
CAddress addr1 = CAddress(CService("250.1.2.1", 8333), NODE_NONE);
CNetAddr source1 = CNetAddr("250.1.2.1");
int nId;
addrman.Create(addr1, source1, &nId);
// Test 21: Delete should actually delete the addr.
ASSERT_TRUE(addrman.size() == 1);
addrman.Delete(nId);
ASSERT_TRUE(addrman.size() == 0);
CAddrInfo* info2 = addrman.Find(addr1);
ASSERT_TRUE(info2 == NULL);
}
TEST(TestAddrmanTests, addrman_getaddr)
{
CAddrManTest addrman;
// Set addrman addr placement to be deterministic.
addrman.MakeDeterministic();
// Test 22: Sanity check, GetAddr should never return anything if addrman
// is empty.
ASSERT_TRUE(addrman.size() == 0);
vector<CAddress> vAddr1 = addrman.GetAddr();
ASSERT_TRUE(vAddr1.size() == 0);
CAddress addr1 = CAddress(CService("250.250.2.1", 8333), NODE_NONE);
addr1.nTime = GetTime(); // Set time so isTerrible = false
CAddress addr2 = CAddress(CService("250.251.2.2", 9999), NODE_NONE);
addr2.nTime = GetTime();
CAddress addr3 = CAddress(CService("251.252.2.3", 8333), NODE_NONE);
addr3.nTime = GetTime();
CAddress addr4 = CAddress(CService("252.253.3.4", 8333), NODE_NONE);
addr4.nTime = GetTime();
CAddress addr5 = CAddress(CService("252.254.4.5", 8333), NODE_NONE);
addr5.nTime = GetTime();
CNetAddr source1 = CNetAddr("250.1.2.1");
CNetAddr source2 = CNetAddr("250.2.3.3");
// Test 23: Ensure GetAddr works with new addresses.
addrman.Add(addr1, source1);
addrman.Add(addr2, source2);
addrman.Add(addr3, source1);
addrman.Add(addr4, source2);
addrman.Add(addr5, source1);
// GetAddr returns 23% of addresses, 23% of 5 is 1 rounded down.
ASSERT_TRUE(addrman.GetAddr().size() == 1);
// Test 24: Ensure GetAddr works with new and tried addresses.
addrman.Good(CAddress(addr1, NODE_NONE));
addrman.Good(CAddress(addr2, NODE_NONE));
ASSERT_TRUE(addrman.GetAddr().size() == 1);
// Test 25: Ensure GetAddr still returns 23% when addrman has many addrs.
for (unsigned int i = 1; i < (8 * 256); i++) {
int octet1 = i % 256;
int octet2 = (i / 256) % 256;
int octet3 = (i / (256 * 2)) % 256;
string strAddr = boost::to_string(octet1) + "." + boost::to_string(octet2) + "." + boost::to_string(octet3) + ".23";
CAddress addr = CAddress(CService(strAddr), NODE_NONE);
// Ensure that for all addrs in addrman, isTerrible == false.
addr.nTime = GetTime();
addrman.Add(addr, CNetAddr(strAddr));
if (i % 8 == 0)
addrman.Good(addr);
}
vector<CAddress> vAddr = addrman.GetAddr();
size_t percent23 = (addrman.size() * 23) / 100;
ASSERT_TRUE(vAddr.size() == percent23);
ASSERT_TRUE(vAddr.size() == 461);
// (Addrman.size() < number of addresses added) due to address collisons.
ASSERT_TRUE(addrman.size() == 2007);
}
TEST(TestAddrmanTests, caddrinfo_get_tried_bucket_legacy)
{
CAddrManTest addrman;
CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE);
CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE);
CNetAddr source1 = ResolveIP("250.1.1.1");
CAddrInfo info1 = CAddrInfo(addr1, source1);
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
std::vector<bool> asmap; // use /16
ASSERT_EQ(info1.GetTriedBucket(nKey1, asmap), 40);
// Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue.
ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap));
// Test: Two addresses with same IP but different ports can map to
// different buckets because they have different keys.
CAddrInfo info2 = CAddrInfo(addr2, source1);
ASSERT_TRUE(info1.GetKey() != info2.GetKey());
ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap));
std::set<int> buckets;
for (int i = 0; i < 255; i++) {
CAddrInfo infoi = CAddrInfo(
CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE),
ResolveIP("250.1.1." + boost::to_string(i)));
int bucket = infoi.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the same /16 prefix should
// never get more than 8 buckets with legacy grouping
ASSERT_EQ(buckets.size(), 8U);
buckets.clear();
for (int j = 0; j < 255; j++) {
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250." + boost::to_string(j) + ".1.1"), NODE_NONE),
ResolveIP("250." + boost::to_string(j) + ".1.1"));
int bucket = infoj.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the different /16 prefix should map to more than
// 8 buckets with legacy grouping
ASSERT_EQ(buckets.size(), 160U);
}
TEST(TestAddrmanTests, caddrinfo_get_new_bucket_legacy)
{
CAddrManTest addrman;
CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE);
CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE);
CNetAddr source1 = ResolveIP("250.1.2.1");
CAddrInfo info1 = CAddrInfo(addr1, source1);
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
std::vector<bool> asmap; // use /16
// Test: Make sure the buckets are what we expect
ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), 786);
ASSERT_EQ(info1.GetNewBucket(nKey1, source1, asmap), 786);
// Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue.
ASSERT_TRUE(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap));
// Test: Ports should not affect bucket placement in the addr
CAddrInfo info2 = CAddrInfo(addr2, source1);
ASSERT_TRUE(info1.GetKey() != info2.GetKey());
ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap));
std::set<int> buckets;
for (int i = 0; i < 255; i++) {
CAddrInfo infoi = CAddrInfo(
CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE),
ResolveIP("250.1.1." + boost::to_string(i)));
int bucket = infoi.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the same group (\16 prefix for IPv4) should
// always map to the same bucket.
ASSERT_EQ(buckets.size(), 1U);
buckets.clear();
for (int j = 0; j < 4 * 255; j++) {
CAddrInfo infoj = CAddrInfo(CAddress(
ResolveService(
boost::to_string(250 + (j / 255)) + "." + boost::to_string(j % 256) + ".1.1"), NODE_NONE),
ResolveIP("251.4.1.1"));
int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the same source groups should map to NO MORE
// than 64 buckets.
ASSERT_TRUE(buckets.size() <= 64);
buckets.clear();
for (int p = 0; p < 255; p++) {
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250.1.1.1"), NODE_NONE),
ResolveIP("250." + boost::to_string(p) + ".1.1"));
int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the different source groups should map to MORE
// than 64 buckets.
ASSERT_TRUE(buckets.size() > 64);
}
// The following three test cases use asmap_raw[] from asmap.raw file
// We use an artificial minimal mock mapping
// 250.0.0.0/8 AS1000
// 101.1.0.0/16 AS1
// 101.2.0.0/16 AS2
// 101.3.0.0/16 AS3
// 101.4.0.0/16 AS4
// 101.5.0.0/16 AS5
// 101.6.0.0/16 AS6
// 101.7.0.0/16 AS7
// 101.8.0.0/16 AS8
TEST(TestAddrmanTests, caddrinfo_get_tried_bucket)
{
CAddrManTest addrman;
CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE);
CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE);
CNetAddr source1 = ResolveIP("250.1.1.1");
CAddrInfo info1 = CAddrInfo(addr1, source1);
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
ASSERT_EQ(info1.GetTriedBucket(nKey1, asmap), 236);
// Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue.
ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap));
// Test: Two addresses with same IP but different ports can map to
// different buckets because they have different keys.
CAddrInfo info2 = CAddrInfo(addr2, source1);
ASSERT_TRUE(info1.GetKey() != info2.GetKey());
ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap));
std::set<int> buckets;
for (int j = 0; j < 255; j++) {
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("101." + boost::to_string(j) + ".1.1"), NODE_NONE),
ResolveIP("101." + boost::to_string(j) + ".1.1"));
int bucket = infoj.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the different /16 prefix MAY map to more than
// 8 buckets.
ASSERT_TRUE(buckets.size() > 8);
buckets.clear();
for (int j = 0; j < 255; j++) {
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250." + boost::to_string(j) + ".1.1"), NODE_NONE),
ResolveIP("250." + boost::to_string(j) + ".1.1"));
int bucket = infoj.GetTriedBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the different /16 prefix MAY NOT map to more than
// 8 buckets.
ASSERT_TRUE(buckets.size() == 8);
}
TEST(TestAddrmanTests, caddrinfo_get_new_bucket)
{
CAddrManTest addrman;
CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE);
CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE);
CNetAddr source1 = ResolveIP("250.1.2.1");
CAddrInfo info1 = CAddrInfo(addr1, source1);
uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash();
uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash();
std::vector<bool> asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
// Test: Make sure the buckets are what we expect
ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), 795);
ASSERT_EQ(info1.GetNewBucket(nKey1, source1, asmap), 795);
// Test: Make sure key actually randomizes bucket placement. A fail on
// this test could be a security issue.
ASSERT_TRUE(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap));
// Test: Ports should not affect bucket placement in the addr
CAddrInfo info2 = CAddrInfo(addr2, source1);
ASSERT_TRUE(info1.GetKey() != info2.GetKey());
ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap));
std::set<int> buckets;
for (int i = 0; i < 255; i++) {
CAddrInfo infoi = CAddrInfo(
CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE),
ResolveIP("250.1.1." + boost::to_string(i)));
int bucket = infoi.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the same /16 prefix
// usually map to the same bucket.
ASSERT_EQ(buckets.size(), 1U);
buckets.clear();
for (int j = 0; j < 4 * 255; j++) {
CAddrInfo infoj = CAddrInfo(CAddress(
ResolveService(
boost::to_string(250 + (j / 255)) + "." + boost::to_string(j % 256) + ".1.1"), NODE_NONE),
ResolveIP("251.4.1.1"));
int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the same source /16 prefix should not map to more
// than 64 buckets.
ASSERT_TRUE(buckets.size() <= 64);
buckets.clear();
for (int p = 0; p < 255; p++) {
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250.1.1.1"), NODE_NONE),
ResolveIP("101." + boost::to_string(p) + ".1.1"));
int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the different source /16 prefixes usually map to MORE
// than 1 bucket.
ASSERT_TRUE(buckets.size() > 1);
buckets.clear();
for (int p = 0; p < 255; p++) {
CAddrInfo infoj = CAddrInfo(
CAddress(ResolveService("250.1.1.1"), NODE_NONE),
ResolveIP("250." + boost::to_string(p) + ".1.1"));
int bucket = infoj.GetNewBucket(nKey1, asmap);
buckets.insert(bucket);
}
// Test: IP addresses in the different source /16 prefixes sometimes map to NO MORE
// than 1 bucket.
ASSERT_TRUE(buckets.size() == 1);
}
TEST(TestAddrmanTests, addrman_serialization)
{
std::vector<bool> asmap1 = FromBytes(asmap_raw, sizeof(asmap_raw) * 8);
CAddrManTest addrman_asmap1(true, asmap1);
CAddrManTest addrman_asmap1_dup(true, asmap1);
CAddrManTest addrman_noasmap;
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
CAddress addr = CAddress(ResolveService("250.1.1.1"), NODE_NONE);
CNetAddr default_source;
addrman_asmap1.Add(addr, default_source);
stream << addrman_asmap1;
// serizalizing/deserializing addrman with the same asmap
stream >> addrman_asmap1_dup;
std::pair<int, int> bucketAndEntry_asmap1 = addrman_asmap1.GetBucketAndEntry(addr);
std::pair<int, int> bucketAndEntry_asmap1_dup = addrman_asmap1_dup.GetBucketAndEntry(addr);
ASSERT_TRUE(bucketAndEntry_asmap1.second != -1);
ASSERT_TRUE(bucketAndEntry_asmap1_dup.second != -1);
ASSERT_TRUE(bucketAndEntry_asmap1.first == bucketAndEntry_asmap1_dup.first);
ASSERT_TRUE(bucketAndEntry_asmap1.second == bucketAndEntry_asmap1_dup.second);
// deserializing asmaped peers.dat to non-asmaped addrman
stream << addrman_asmap1;
stream >> addrman_noasmap;
std::pair<int, int> bucketAndEntry_noasmap = addrman_noasmap.GetBucketAndEntry(addr);
ASSERT_TRUE(bucketAndEntry_noasmap.second != -1);
ASSERT_TRUE(bucketAndEntry_asmap1.first != bucketAndEntry_noasmap.first);
ASSERT_TRUE(bucketAndEntry_asmap1.second != bucketAndEntry_noasmap.second);
// deserializing non-asmaped peers.dat to asmaped addrman
addrman_asmap1.Clear();
addrman_noasmap.Clear();
addrman_noasmap.Add(addr, default_source);
// GTEST_COUT_COLOR << addr.ToString() << " - " << default_source.ToString() << " - " << addrman_noasmap.size() << std::endl;
// addrman_noasmap.PrintInternals();
stream << addrman_noasmap;
// std::string strHex = HexStr(stream.begin(), stream.end());
// GTEST_COUT_COLOR << strHex << std::endl;
stream >> addrman_asmap1;
std::pair<int, int> bucketAndEntry_asmap1_deser = addrman_asmap1.GetBucketAndEntry(addr);
ASSERT_TRUE(bucketAndEntry_asmap1_deser.second != -1);
ASSERT_TRUE(bucketAndEntry_asmap1_deser.first != bucketAndEntry_noasmap.first);
ASSERT_TRUE(bucketAndEntry_asmap1_deser.first == bucketAndEntry_asmap1_dup.first);
ASSERT_TRUE(bucketAndEntry_asmap1_deser.second == bucketAndEntry_asmap1_dup.second);
// used to map to different buckets, now maps to the same bucket.
addrman_asmap1.Clear();
addrman_noasmap.Clear();
CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE);
CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE);
addrman_noasmap.Add(addr, default_source);
addrman_noasmap.Add(addr2, default_source);
std::pair<int, int> bucketAndEntry_noasmap_addr1 = addrman_noasmap.GetBucketAndEntry(addr1);
std::pair<int, int> bucketAndEntry_noasmap_addr2 = addrman_noasmap.GetBucketAndEntry(addr2);
ASSERT_TRUE(bucketAndEntry_noasmap_addr1.first != bucketAndEntry_noasmap_addr2.first);
ASSERT_TRUE(bucketAndEntry_noasmap_addr1.second != bucketAndEntry_noasmap_addr2.second);
stream << addrman_noasmap;
stream >> addrman_asmap1;
std::pair<int, int> bucketAndEntry_asmap1_deser_addr1 = addrman_asmap1.GetBucketAndEntry(addr1);
std::pair<int, int> bucketAndEntry_asmap1_deser_addr2 = addrman_asmap1.GetBucketAndEntry(addr2);
ASSERT_TRUE(bucketAndEntry_asmap1_deser_addr1.first == bucketAndEntry_asmap1_deser_addr2.first);
ASSERT_TRUE(bucketAndEntry_asmap1_deser_addr1.second != bucketAndEntry_asmap1_deser_addr2.second);
}
}

View File

@@ -0,0 +1,78 @@
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include "addrman.h"
#include <string>
#include "netbase.h"
#define GTEST_COUT_NOCOLOR std::cerr << "[ ] [ INFO ] "
namespace testing
{
namespace internal
{
enum GTestColor {
COLOR_DEFAULT,
COLOR_RED,
COLOR_GREEN,
COLOR_YELLOW
};
extern void ColoredPrintf(GTestColor color, const char* fmt, ...);
}
}
#define PRINTF(...) do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, "[ ] "); testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } while(0)
// C++ stream interface
class TestCout : public std::stringstream
{
public:
~TestCout()
{
PRINTF("%s",str().c_str());
}
};
#define GTEST_COUT_COLOR TestCout()
using namespace std;
static CNetAddr ResolveIP(const std::string& ip)
{
vector<CNetAddr> vIPs;
CNetAddr addr;
if (LookupHost(ip.c_str(), vIPs)) {
addr = vIPs[0];
} else
{
// it was BOOST_CHECK_MESSAGE, but we can't use ASSERT outside a test
GTEST_COUT_COLOR << strprintf("failed to resolve: %s", ip) << std::endl;
}
return addr;
}
namespace TestNetBaseTests {
TEST(TestAddrmanTests, netbase_getgroup) {
std::vector<bool> asmap; // use /16
ASSERT_TRUE(ResolveIP("127.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // Local -> !Routable()
ASSERT_TRUE(ResolveIP("257.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // !Valid -> !Routable()
ASSERT_TRUE(ResolveIP("10.0.0.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC1918 -> !Routable()
ASSERT_TRUE(ResolveIP("169.254.1.1").GetGroup(asmap) == std::vector<unsigned char>({0})); // RFC3927 -> !Routable()
ASSERT_TRUE(ResolveIP("1.2.3.4").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // IPv4
// std::vector<unsigned char> vch = ResolveIP("4.3.2.1").GetGroup(asmap);
// GTEST_COUT_COLOR << boost::to_string((int)vch[0]) << boost::to_string((int)vch[1]) << boost::to_string((int)vch[2]) << std::endl;
ASSERT_TRUE(ResolveIP("::FFFF:0:102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6145
ASSERT_TRUE(ResolveIP("64:FF9B::102:304").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC6052
ASSERT_TRUE(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC3964
ASSERT_TRUE(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV4, 1, 2})); // RFC4380
ASSERT_TRUE(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_ONION, 239})); // Tor
ASSERT_TRUE(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net
ASSERT_TRUE(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector<unsigned char>({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6
}
}

View File

@@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(netbase_networks)
BOOST_CHECK(CNetAddr("::1").GetNetwork() == NET_UNROUTABLE);
BOOST_CHECK(CNetAddr("8.8.8.8").GetNetwork() == NET_IPV4);
BOOST_CHECK(CNetAddr("2001::8888").GetNetwork() == NET_IPV6);
BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_TOR);
BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_ONION);
}
BOOST_AUTO_TEST_CASE(netbase_properties)
@@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(netbase_getgroup)
BOOST_CHECK(CNetAddr("64:FF9B::102:304").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC6052
BOOST_CHECK(CNetAddr("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC3964
BOOST_CHECK(CNetAddr("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC4380
BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == boost::assign::list_of((unsigned char)NET_TOR)(239)); // Tor
BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == boost::assign::list_of((unsigned char)NET_ONION)(239)); // Tor
BOOST_CHECK(CNetAddr("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(4)(112)(175)); //he.net
BOOST_CHECK(CNetAddr("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(32)(1)); //IPv6
}

View File

@@ -530,8 +530,8 @@ void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& r
// if -onion isn't set to something else.
if (GetArg("-onion", "") == "") {
proxyType addrOnion = proxyType(CService("127.0.0.1", 9050), true);
SetProxy(NET_TOR, addrOnion);
SetLimited(NET_TOR, false);
SetProxy(NET_ONION, addrOnion);
SetLimited(NET_ONION, false);
}
// Finally - now create the service

187
src/util/asmap.cpp Normal file
View File

@@ -0,0 +1,187 @@
// Copyright (c) 2019-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <map>
#include <vector>
#include <assert.h>
#include <crypto/common.h>
namespace {
constexpr uint32_t INVALID = 0xFFFFFFFF;
uint32_t DecodeBits(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos, uint8_t minval, const std::vector<uint8_t> &bit_sizes)
{
uint32_t val = minval;
bool bit;
for (std::vector<uint8_t>::const_iterator bit_sizes_it = bit_sizes.begin();
bit_sizes_it != bit_sizes.end(); ++bit_sizes_it) {
if (bit_sizes_it + 1 != bit_sizes.end()) {
if (bitpos == endpos) break;
bit = *bitpos;
bitpos++;
} else {
bit = 0;
}
if (bit) {
val += (1 << *bit_sizes_it);
} else {
for (int b = 0; b < *bit_sizes_it; b++) {
if (bitpos == endpos) return INVALID; // Reached EOF in mantissa
bit = *bitpos;
bitpos++;
val += bit << (*bit_sizes_it - 1 - b);
}
return val;
}
}
return INVALID; // Reached EOF in exponent
}
enum class Instruction : uint32_t
{
RETURN = 0,
JUMP = 1,
MATCH = 2,
DEFAULT = 3,
};
const std::vector<uint8_t> TYPE_BIT_SIZES{0, 0, 1};
Instruction DecodeType(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
{
return Instruction(DecodeBits(bitpos, endpos, 0, TYPE_BIT_SIZES));
}
const std::vector<uint8_t> ASN_BIT_SIZES{15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
uint32_t DecodeASN(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
{
return DecodeBits(bitpos, endpos, 1, ASN_BIT_SIZES);
}
const std::vector<uint8_t> MATCH_BIT_SIZES{1, 2, 3, 4, 5, 6, 7, 8};
uint32_t DecodeMatch(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
{
return DecodeBits(bitpos, endpos, 2, MATCH_BIT_SIZES);
}
const std::vector<uint8_t> JUMP_BIT_SIZES{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30};
uint32_t DecodeJump(std::vector<bool>::const_iterator& bitpos, const std::vector<bool>::const_iterator& endpos)
{
return DecodeBits(bitpos, endpos, 17, JUMP_BIT_SIZES);
}
}
uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip)
{
std::vector<bool>::const_iterator pos = asmap.begin();
const std::vector<bool>::const_iterator endpos = asmap.end();
uint8_t bits = ip.size();
uint32_t default_asn = 0;
uint32_t jump, match, matchlen;
Instruction opcode;
while (pos != endpos) {
opcode = DecodeType(pos, endpos);
if (opcode == Instruction::RETURN) {
default_asn = DecodeASN(pos, endpos);
if (default_asn == INVALID) break; // ASN straddles EOF
return default_asn;
} else if (opcode == Instruction::JUMP) {
jump = DecodeJump(pos, endpos);
if (jump == INVALID) break; // Jump offset straddles EOF
if (bits == 0) break; // No input bits left
if (pos + jump < pos) break; // overflow
if (pos + jump >= endpos) break; // Jumping past EOF
if (ip[ip.size() - bits]) {
pos += jump;
}
bits--;
} else if (opcode == Instruction::MATCH) {
match = DecodeMatch(pos, endpos);
if (match == INVALID) break; // Match bits straddle EOF
matchlen = CountBits(match) - 1;
if (bits < matchlen) break; // Not enough input bits
for (uint32_t bit = 0; bit < matchlen; bit++) {
if ((ip[ip.size() - bits]) != ((match >> (matchlen - 1 - bit)) & 1)) {
return default_asn;
}
bits--;
}
} else if (opcode == Instruction::DEFAULT) {
default_asn = DecodeASN(pos, endpos);
if (default_asn == INVALID) break; // ASN straddles EOF
} else {
break; // Instruction straddles EOF
}
}
assert(false); // Reached EOF without RETURN, or aborted (see any of the breaks above) - should have been caught by SanityCheckASMap below
return 0; // 0 is not a valid ASN
}
bool SanityCheckASMap(const std::vector<bool>& asmap, int bits)
{
const std::vector<bool>::const_iterator begin = asmap.begin(), endpos = asmap.end();
std::vector<bool>::const_iterator pos = begin;
std::vector<std::pair<uint32_t, int>> jumps; // All future positions we may jump to (bit offset in asmap -> bits to consume left)
jumps.reserve(bits);
Instruction prevopcode = Instruction::JUMP;
bool had_incomplete_match = false;
while (pos != endpos) {
uint32_t offset = pos - begin;
if (!jumps.empty() && offset >= jumps.back().first) return false; // There was a jump into the middle of the previous instruction
Instruction opcode = DecodeType(pos, endpos);
if (opcode == Instruction::RETURN) {
if (prevopcode == Instruction::DEFAULT) return false; // There should not be any RETURN immediately after a DEFAULT (could be combined into just RETURN)
uint32_t asn = DecodeASN(pos, endpos);
if (asn == INVALID) return false; // ASN straddles EOF
if (jumps.empty()) {
// Nothing to execute anymore
if (endpos - pos > 7) return false; // Excessive padding
while (pos != endpos) {
if (*pos) return false; // Nonzero padding bit
++pos;
}
return true; // Sanely reached EOF
} else {
// Continue by pretending we jumped to the next instruction
offset = pos - begin;
if (offset != jumps.back().first) return false; // Unreachable code
bits = jumps.back().second; // Restore the number of bits we would have had left after this jump
jumps.pop_back();
prevopcode = Instruction::JUMP;
}
} else if (opcode == Instruction::JUMP) {
uint32_t jump = DecodeJump(pos, endpos);
if (jump == INVALID) return false; // Jump offset straddles EOF
if (pos + jump < pos) return false; // overflow
if (pos + jump > endpos) return false; // Jump out of range
if (bits == 0) return false; // Consuming bits past the end of the input
--bits;
uint32_t jump_offset = pos - begin + jump;
if (!jumps.empty() && jump_offset >= jumps.back().first) return false; // Intersecting jumps
jumps.emplace_back(jump_offset, bits);
prevopcode = Instruction::JUMP;
} else if (opcode == Instruction::MATCH) {
uint32_t match = DecodeMatch(pos, endpos);
if (match == INVALID) return false; // Match bits straddle EOF
int matchlen = CountBits(match) - 1;
if (prevopcode != Instruction::MATCH) had_incomplete_match = false;
if (matchlen < 8 && had_incomplete_match) return false; // Within a sequence of matches only at most one should be incomplete
had_incomplete_match = (matchlen < 8);
if (bits < matchlen) return false; // Consuming bits past the end of the input
bits -= matchlen;
prevopcode = Instruction::MATCH;
} else if (opcode == Instruction::DEFAULT) {
if (prevopcode == Instruction::DEFAULT) return false; // There should not be two successive DEFAULTs (they could be combined into one)
uint32_t asn = DecodeASN(pos, endpos);
if (asn == INVALID) return false; // ASN straddles EOF
prevopcode = Instruction::DEFAULT;
} else {
return false; // Instruction straddles EOF
}
}
return false; // Reached EOF without RETURN instruction
}

15
src/util/asmap.h Normal file
View File

@@ -0,0 +1,15 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_UTIL_ASMAP_H
#define BITCOIN_UTIL_ASMAP_H
#include <stdint.h>
#include <vector>
uint32_t Interpret(const std::vector<bool> &asmap, const std::vector<bool> &ip);
bool SanityCheckASMap(const std::vector<bool>& asmap, int bits);
#endif // BITCOIN_UTIL_ASMAP_H

View File

@@ -1018,7 +1018,7 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
pblockindex = chainActive[wtxHeight];
::ClearSingleNoteWitnessCache(nd);
LogPrintf("Setting Initial Sapling Witness for tx %s, %i of %i\n", wtxHash.ToString(), nWitnessTxIncrement, nWitnessTotalTxCount);
LogPrintf("%s: Setting Initial Sapling Witness for tx %s, %i of %i\n", __func__, wtxHash.ToString(), nWitnessTxIncrement, nWitnessTotalTxCount);
SaplingMerkleTree saplingTree;
blockRoot = pblockindex->pprev->hashFinalSaplingRoot;
@@ -1063,6 +1063,8 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
}
}
if(fZdebug)
LogPrintf("%s: nMinimumHeight=%d\n",__func__, nMinimumHeight);
return nMinimumHeight;
}
@@ -1077,10 +1079,11 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
return;
}
uint256 sproutRoot;
uint256 saplingRoot;
CBlockIndex* pblockindex = chainActive[startHeight];
int height = chainActive.Height();
if(fZdebug)
LogPrintf("%s: height=%d, startHeight=%d\n", __func__, height, startHeight);
while (pblockindex) {
@@ -2816,8 +2819,8 @@ void CWallet::ReacceptWalletTransactions()
bool CWalletTx::RelayWalletTransaction()
{
int64_t nNow = GetTime();
if(fZdebug)
LogPrintf("%s: now=%li\n",__func__,nNow);
//if(fZdebug)
// LogPrintf("%s: now=%li\n",__func__,nNow);
if ( pwallet == 0 )
{
//fprintf(stderr,"unexpected null pwallet in RelayWalletTransaction\n");