diff --git a/README.md b/README.md index 0ef019e..fb99257 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,11 @@ n2n edge nodes use twofish encryption by default for compatibility reasons with of the edge nodes, their IP address and the community are sent in cleartext. When encryption is enabled, the supernode will not be able to decrypt the traffic exchanged between -two edge nodes, but it will now that edge A is talking with edge B. +two edge nodes, but it will know that edge A is talking with edge B. Recently AES encryption support has been implemented, which increases both security and performance, -so it is recommended to enable it on all the edge nodes by specifying the `-A` option. +so it is recommended to enable it on all the edge nodes that must have the -Ax value. When possible +(i.e. when n2n is compiled with OpenSSL 1.1) we recommend to use -A4 A benchmark of the encryption methods is available when compiled from source with `tools/n2n-benchmark`. diff --git a/configure.seed b/configure.seed index 067351c..db8c933 100644 --- a/configure.seed +++ b/configure.seed @@ -13,14 +13,24 @@ else GIT_RELEASE=${N2N_VERSION_SHORT} fi +N2N_LIBS= + +AC_CHECK_LIB([zstd], [ZSTD_compress]) + +if test "x$ac_cv_lib_zstd_ZSTD_compress" != xyes; then + AC_MSG_RESULT(Building n2n without ZSTD support) +else + AC_DEFINE([N2N_HAVE_ZSTD], [], [Have ZSTD support]) + N2N_LIBS="-lzstd ${N2N_LIBS}" +fi + AC_CHECK_LIB([crypto], [AES_cbc_encrypt]) -N2N_LIBS= if test "x$ac_cv_lib_crypto_AES_cbc_encrypt" != xyes; then AC_MSG_RESULT(Building n2n without AES support) else AC_DEFINE([N2N_HAVE_AES], [], [Have AES support]) - N2N_LIBS=-lcrypto + N2N_LIBS="-lcrypto ${N2N_LIBS}" fi OLD_CFLAGS="${CFLAGS}" diff --git a/edge.c b/edge.c index bbb0af5..4d5ffce 100644 --- a/edge.c +++ b/edge.c @@ -159,7 +159,7 @@ static void help() { #ifndef __APPLE__ "[-D] " #endif - "[-r] [-E] [-v] [-i ] [-L ] [-t ] [-A[]] [-h]\n\n"); + "[-r] [-E] [-v] [-i ] [-L ] [-t ] [-A[]] [-z[]] [-h]\n\n"); #if defined(N2N_CAN_NAME_IFACE) printf("-d | tun device name\n"); @@ -189,16 +189,22 @@ static void help() { #endif printf("-r | Enable packet forwarding through n2n community.\n"); printf("-A1 | Disable payload encryption. Do not use with -k.\n"); - printf("-A2 | Use Twofish for payload encryption (default). Requires a key.\n"); + printf("-A2 | Use Twofish for payload encryption (default). Requires a key (-k).\n"); #ifdef N2N_HAVE_AES - printf("-A3 or -A (deprecated) | Use AES-CBC for payload encryption. Requires a key.\n"); + printf("-A3 or -A (deprecated) | Use AES-CBC for payload encryption. Requires a key (-k).\n"); #endif #ifdef HAVE_OPENSSL_1_1 printf("-A4 | Use ChaCha20 for payload encryption. Requires a key.\n"); printf("-A5 | Use Speck for payload encryption. Requires a key.\n"); #endif - printf("-z | Enable lzo1x compression for outgoing data packets\n"); - printf(" | (default=disabled).\n"); +#ifdef HAVE_OPENSSL_1_1 + printf("-A4 | Use ChaCha20 for payload encryption. Requires a key (-k).\n"); +#endif + printf("-z1 or -z | Enable lzo1x compression for outgoing data packets\n"); +#ifdef N2N_HAVE_ZSTD + printf("-z2 | Enable zstd compression for outgoing data packets\n"); +#endif + printf(" | (default=compression disabled)\n"); printf("-E | Accept multicast MAC addresses (default=drop).\n"); printf("-S | Do not connect P2P. Always use the supernode.\n"); #ifdef __linux__ @@ -221,6 +227,46 @@ static void help() { /* *************************************************** */ +static void setPayloadEncryption( n2n_edge_conf_t *conf, int cipher) { + /* even though 'cipher' and 'conf->transop_id' share the same encoding scheme, + * a switch-statement under conditional compilation is used to sort out the + * unsupported ciphers */ + switch (cipher) { + case 1: + { + conf->transop_id = N2N_TRANSFORM_ID_NULL; + break; + } + case 2: + { + conf->transop_id = N2N_TRANSFORM_ID_TWOFISH; + break; + } +#ifdef N2N_HAVE_AES + case 3: + { + conf->transop_id = N2N_TRANSFORM_ID_AESCBC; + break; + } +#endif +#ifdef HAVE_OPENSSL_1_1 + case 4: + { + conf->transop_id = N2N_TRANSFORM_ID_CHACHA20; + break; + } +#endif + default: + { + conf->transop_id = N2N_TRANSFORM_ID_INVAL; + traceEvent(TRACE_NORMAL, "the %s cipher given by -A_ option is not supported in this version.", transop_str(cipher)); + exit(1); + } + } +} + +/* *************************************************** */ + static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_edge_conf_t *conf) { /* traceEvent(TRACE_NORMAL, "Option %c = %s", optkey, optargument ? optargument : ""); */ @@ -309,62 +355,53 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e case 'A': { - int cipher = N2N_TRANSFORM_ID_AESCBC; // default, if '-A' only + int cipher; + if (optargument) { cipher = atoi(optargument); } else { traceEvent(TRACE_NORMAL, "the use of the solitary -A switch is deprecated and might not be supported in future versions. " "please use -A3 instead to choose a the AES-CBC cipher for payload encryption."); + + cipher = N2N_TRANSFORM_ID_AESCBC; // default, if '-A' only + } + + setPayloadEncryption(conf, cipher); + break; + } + + case 'z': + { + int compression = N2N_COMPRESSION_ID_LZO; // default, if '-z' only + if (optargument) { + compression = atoi(optargument); } - /* even though 'cipher' and 'conf->transop_id' share the same encoding scheme, + /* even though 'compression' and 'conf->compression' share the same encoding scheme, * a switch-statement under conditional compilation is used to sort out the - * unsupported ciphers */ - switch (cipher) { + * unsupported optarguments */ + switch (compression) { case 1: { - conf->transop_id = N2N_TRANSFORM_ID_NULL; + conf->compression = N2N_COMPRESSION_ID_LZO; break; } +#ifdef N2N_HAVE_ZSTD case 2: { - conf->transop_id = N2N_TRANSFORM_ID_TWOFISH; - break; - } -#ifdef N2N_HAVE_AES - case 3: - { - conf->transop_id = N2N_TRANSFORM_ID_AESCBC; - break; - } -#endif -#ifdef HAVE_OPENSSL_1_1 - case 4: - { - conf->transop_id = N2N_TRANSFORM_ID_CHACHA20; + conf->compression = N2N_COMPRESSION_ID_ZSTD; break; } #endif - case 5: - { - conf->transop_id = N2N_TRANSFORM_ID_SPECK; - break; - } default: { - conf->transop_id = N2N_TRANSFORM_ID_INVAL; - traceEvent(TRACE_NORMAL, "the %s cipher given by -A_ option is not supported in this version.", transop_str(cipher)); - exit(1); + conf->compression = N2N_COMPRESSION_ID_NONE; + traceEvent(TRACE_NORMAL, "the %s compression given by -z_ option is not supported in this version.", compression_str(compression)); + exit(1); // to make the user aware } } break; } - case 'z': - { - conf->compression = N2N_COMPRESSION_ID_LZO; - break; - } - case 'l': /* supernode-list */ if(optargument) { if(edge_conf_add_supernode(conf, optargument) != 0) { @@ -509,10 +546,7 @@ static int loadFromCLI(int argc, char *argv[], n2n_edge_conf_t *conf, n2n_priv_c u_char c; while((c = getopt_long(argc, argv, - "k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:SDL:z" - - "A::" - + "k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:SDL:zA:z::" #ifdef __linux__ "T:n:" #endif @@ -796,6 +830,8 @@ int main(int argc, char* argv[]) { #if defined(HAVE_OPENSSL_1_1) traceEvent(TRACE_NORMAL, "Using %s", OpenSSL_version(0)); #endif + + traceEvent(TRACE_NORMAL, "Using compression: %s.", compression_str(conf.compression)); traceEvent(TRACE_NORMAL, "Using %s cipher.", transop_str(conf.transop_id)); /* Random seed */ diff --git a/edge_utils.c b/edge_utils.c index 587c56c..47c0fd3 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -18,6 +18,7 @@ #include "n2n.h" #include "lzoconf.h" +#include #ifdef WIN32 #include @@ -158,6 +159,17 @@ const char* transop_str(enum n2n_transform tr) { /* ************************************** */ +const char* compression_str(uint8_t cmpr) { + switch(cmpr) { + case N2N_COMPRESSION_ID_NONE: return("none"); + case N2N_COMPRESSION_ID_LZO: return("lzo1x"); + case N2N_COMPRESSION_ID_ZSTD: return("zstd"); + default: return("invalid"); + }; +} + +/* ************************************** */ + /** Destination 01:00:5E:00:00:00 - 01:00:5E:7F:FF:FF is multicast ethernet. */ static int is_ethMulticast(const void * buf, size_t bufsize) { @@ -237,6 +249,10 @@ n2n_edge_t* edge_init(const tuntap_dev *dev, const n2n_edge_conf_t *conf, int *r goto edge_init_error; } +#ifdef N2N_HAVE_ZSTD + // zstd does not require initialization. if it were required, this would be a good place +#endif + for(i=0; isn_num; ++i) traceEvent(TRACE_NORMAL, "supernode %u => %s\n", i, (conf->sn_ip_array[i])); @@ -261,7 +277,6 @@ n2n_edge_t* edge_init(const tuntap_dev *dev, const n2n_edge_conf_t *conf, int *r case N2N_TRANSFORM_ID_SPECK: rc = n2n_transop_speck_init(&eee->conf, &eee->transop); break; - default: rc = n2n_transop_null_init(&eee->conf, &eee->transop); } @@ -997,20 +1012,37 @@ static int handle_PACKET(n2n_edge_t * eee, /* decompress if necessary */ uint8_t * deflation_buffer = 0; - uint32_t deflated_len; + int32_t deflated_len; switch (rx_compression_id) { + case N2N_COMPRESSION_ID_NONE: + break; // continue afterwards + case N2N_COMPRESSION_ID_LZO: - deflation_buffer = malloc (N2N_PKT_BUF_SIZE); + deflation_buffer = malloc (N2N_PKT_BUF_SIZE); lzo1x_decompress (eth_payload, eth_size, deflation_buffer, (lzo_uint*)&deflated_len, NULL); break; - - default: +#ifdef N2N_HAVE_ZSTD + case N2N_COMPRESSION_ID_ZSTD: + deflated_len = N2N_PKT_BUF_SIZE; + deflation_buffer = malloc (deflated_len); + deflated_len = (int32_t)ZSTD_decompress (deflation_buffer, deflated_len, eth_payload, eth_size); + if (ZSTD_isError(deflated_len)) { + traceEvent (TRACE_ERROR, "payload decompression failed with zstd error '%s'.", + ZSTD_getErrorName(deflated_len)); + free (deflation_buffer); + return (-1); // cannot help it + } break; +#endif + default: + traceEvent (TRACE_ERROR, "payload decompression failed: received packet indicating unsupported %s compression.", + compression_str(rx_compression_id)); + return (-1); // cannot handle it } if (rx_compression_id) { - traceEvent (TRACE_DEBUG, "payload decompression [id: %u]: deflated %u bytes to %u bytes", - rx_compression_id, eth_size, (int)deflated_len); + traceEvent (TRACE_DEBUG, "payload decompression [%s]: deflated %u bytes to %u bytes", + compression_str(rx_compression_id), eth_size, (int)deflated_len); memcpy(eth_payload ,deflation_buffer, deflated_len ); eth_size = deflated_len; free (deflation_buffer); @@ -1375,9 +1407,11 @@ static void send_packet2net(n2n_edge_t * eee, // compression needs to be tried before encode_PACKET is called for compression indication gets encoded there pkt.compression = N2N_COMPRESSION_ID_NONE; + if (eee->conf.compression) { uint8_t * compression_buffer; - uint32_t compression_len; + int32_t compression_len; + switch (eee->conf.compression) { case N2N_COMPRESSION_ID_LZO: compression_buffer = malloc (len + len / 16 + 64 + 3); @@ -1387,14 +1421,30 @@ static void send_packet2net(n2n_edge_t * eee, } } break; - +#ifdef N2N_HAVE_ZSTD + case N2N_COMPRESSION_ID_ZSTD: + compression_len = N2N_PKT_BUF_SIZE + 128; + compression_buffer = malloc (compression_len); // leaves enough room, for exact size call compression_len = ZSTD_compressBound (len); (slower) + compression_len = (int32_t)ZSTD_compress(compression_buffer, compression_len, tap_pkt, len, ZSTD_COMPRESSION_LEVEL) ; + if (!ZSTD_isError(compression_len)) { + if (compression_len < len) { + pkt.compression = N2N_COMPRESSION_ID_ZSTD; + } + } else { + traceEvent (TRACE_ERROR, "payload compression failed with zstd error '%s'.", + ZSTD_getErrorName(compression_len)); + free (compression_buffer); + // continue with unset without pkt.compression --> will send uncompressed + } + break; +#endif default: break; } if (pkt.compression) { - traceEvent (TRACE_DEBUG, "payload compression [id: %u]: compressed %u bytes to %u bytes\n", - pkt.compression, len, compression_len); + traceEvent (TRACE_DEBUG, "payload compression [%s]: compressed %u bytes to %u bytes\n", + compression_str(pkt.compression), len, compression_len); memcpy (tap_pkt, compression_buffer, compression_len); len = compression_len; diff --git a/n2n.h b/n2n.h index e286d24..7041fa3 100644 --- a/n2n.h +++ b/n2n.h @@ -164,9 +164,15 @@ typedef struct tuntap_dev { /* N2N compression indicators. */ /* Compression is disabled by default for outgoing packets if no cli * option is given. All edges are built with decompression support so - * they are able to understand each other. */ + * they are able to understand each other (this applies to lzo only). */ #define N2N_COMPRESSION_ID_NONE 0 /* default, see edge_init_conf_defaults(...) in edge_utils.c */ -#define N2N_COMPRESSION_ID_LZO 1 /* set if '-z' cli option is present, see setOption(...) in edge.c */ +#define N2N_COMPRESSION_ID_LZO 1 /* set if '-z1' or '-z' cli option is present, see setOption(...) in edge.c */ +#ifdef N2N_HAVE_ZSTD +#define N2N_COMPRESSION_ID_ZSTD 2 /* set if '-z2' cli option is present, available only if compiled with zstd lib */ +#define ZSTD_COMPRESSION_LEVEL 7 /* 1 (faster) ... 22 (more compression) */ +#endif +// with the next major packet structure update, make '0' = invalid, and '1' = no compression +// '2' = LZO, '3' = ZSTD, ... REVISIT then (also: change all occurences in source). #define N2N_COMPRESSION_ID_BITLEN 3 /* number of bits used for encoding compression id in the uppermost bits of transform_id; will be obsolete as soon as compression gets @@ -200,7 +206,6 @@ struct peer_info { HASH_ADD(hh,head,mac_addr,sizeof(n2n_mac_t),add) #define HASH_FIND_PEER(head,mac,out) \ HASH_FIND(hh,head,mac,sizeof(n2n_mac_t),out) - #define N2N_EDGE_SN_HOST_SIZE 48 #define N2N_EDGE_NUM_SUPERNODES 2 #define N2N_EDGE_SUP_ATTEMPTS 3 /* Number of failed attmpts before moving on to next supernode. */ @@ -369,6 +374,7 @@ int quick_edge_init(char *device_name, char *community_name, int sn_init(n2n_sn_t *sss); void sn_term(n2n_sn_t *sss); int run_sn_loop(n2n_sn_t *sss, int *keep_running); +const char* compression_str(uint8_t cmpr); const char* transop_str(enum n2n_transform tr); #endif /* _N2N_H_ */ diff --git a/tools/benchmark.c b/tools/benchmark.c index b2491f4..a74765f 100644 --- a/tools/benchmark.c +++ b/tools/benchmark.c @@ -100,6 +100,7 @@ int main(int argc, char * argv[]) { #ifdef HAVE_OPENSSL_1_1 n2n_trans_op_t transop_cc20; #endif + n2n_trans_op_t transop_speck; n2n_edge_conf_t conf; @@ -120,7 +121,7 @@ int main(int argc, char * argv[]) { n2n_transop_cc20_init(&conf, &transop_cc20); #endif n2n_transop_speck_init(&conf, &transop_speck); - + /* Run the tests */ run_transop_benchmark("transop_null", &transop_null, &conf, pktbuf); run_transop_benchmark("transop_twofish", &transop_twofish, &conf, pktbuf); diff --git a/transform_cc20.c b/transform_cc20.c index 389cfe5..f937775 100644 --- a/transform_cc20.c +++ b/transform_cc20.c @@ -120,9 +120,6 @@ static int transop_encode_cc20(n2n_trans_op_t * arg, /* Generate and encode the IV. */ set_cc20_iv(priv, enc_ivec); encode_buf(outbuf, &idx, &enc_ivec, N2N_CC20_IVEC_SIZE); - traceEvent(TRACE_DEBUG, "encode_cc20 iv=%016llx:%016llx", - htobe64(*(uint64_t*)&enc_ivec[0]), - htobe64(*(uint64_t*)&enc_ivec[8]) ); /* Encrypt the assembly contents and write the ciphertext after the iv. */ /* len is set to the length of the cipher plain text to be encrpyted @@ -198,9 +195,6 @@ static int transop_decode_cc20(n2n_trans_op_t * arg, /* Get the IV */ decode_buf((uint8_t *)&dec_ivec, N2N_CC20_IVEC_SIZE, inbuf, &rem, &idx); - traceEvent(TRACE_DEBUG, "decode_cc20 iv=%016llx:%016llx", - htobe64(*(uint64_t*)&dec_ivec[0]), - htobe64(*(uint64_t*)&dec_ivec[8]) ); EVP_CIPHER_CTX *ctx = priv->dec_ctx; int evp_len;