From 20847c006305f5876de4289d606c1e2191037356 Mon Sep 17 00:00:00 2001 From: Logan007 Date: Sat, 25 Apr 2020 23:46:50 +0200 Subject: [PATCH 01/12] fixed typo --- transform_aes.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/transform_aes.c b/transform_aes.c index 59d3281..79a144a 100644 --- a/transform_aes.c +++ b/transform_aes.c @@ -43,7 +43,7 @@ typedef unsigned char n2n_aes_ivec_t[N2N_AES_IVEC_SIZE]; typedef struct transop_aes { -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 EVP_CIPHER_CTX *enc_ctx; /* openssl's reusable evp_* encryption context */ EVP_CIPHER_CTX *dec_ctx; /* openssl's reusable evp_* decryption context */ const EVP_CIPHER *cipher; /* cipher to use: e.g. EVP_aes_128_cbc */ @@ -61,7 +61,7 @@ typedef struct transop_aes { static int transop_deinit_aes(n2n_trans_op_t *arg) { transop_aes_t *priv = (transop_aes_t *)arg->priv; -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 EVP_CIPHER_CTX_free(priv->enc_ctx); EVP_CIPHER_CTX_free(priv->dec_ctx); #endif @@ -74,7 +74,7 @@ static int transop_deinit_aes(n2n_trans_op_t *arg) { /* ****************************************************** */ -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 /* get any erorr message out of openssl taken from https://en.wikibooks.org/wiki/OpenSSL/Error_handling */ char *openssl_err_as_string (void) { @@ -168,7 +168,7 @@ static int transop_encode_aes(n2n_trans_op_t * arg, set_aes_cbc_iv(priv, enc_ivec, iv_seed); -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 EVP_CIPHER_CTX *ctx = priv->enc_ctx; int evp_len; int evp_ciphertext_len; @@ -248,7 +248,7 @@ static int transop_decode_aes(n2n_trans_op_t * arg, set_aes_cbc_iv(priv, dec_ivec, iv_seed); -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 EVP_CIPHER_CTX *ctx = priv->dec_ctx; int evp_len; int evp_plaintext_len; @@ -259,7 +259,7 @@ static int transop_decode_aes(n2n_trans_op_t * arg, evp_plaintext_len = evp_len; if(1 == EVP_DecryptFinal_ex(ctx, assembly + evp_len, &evp_len)) { evp_plaintext_len += evp_len; - + if(evp_plaintext_len != len) traceEvent(TRACE_ERROR, "decode_aes openssl decryption: decrypted %u bytes where %u were expected.\n", evp_plaintext_len, len); @@ -319,7 +319,7 @@ static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_si size_t key_mat_buf_length; /* Clear out any old possibly longer key matter. */ -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 memset(&(priv->key), 0, sizeof(priv->key) ); #else memset(&(priv->enc_key), 0, sizeof(priv->enc_key) ); @@ -344,15 +344,15 @@ static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_si */ if(key_size >= 65) { -#ifdef OPENSSL_1_1 - priv->cipher = EVP_aes_256_cbc(); +#ifdef HAVE_OPENSSL_1_1 + priv->cipher = EVP_chacha20(); #endif aes_key_size_bytes = AES256_KEY_BYTES; SHA512(key, key_size, key_mat_buf); key_mat_buf_length = SHA512_DIGEST_LENGTH; } else if(key_size >= 44) { -#ifdef OPENSSL_1_1 - priv->cipher = EVP_aes_192_cbc(); +#ifdef HAVE_OPENSSL_1_1 + priv->cipher = EVP_chacha20(); #endif aes_key_size_bytes = AES192_KEY_BYTES; SHA384(key, key_size, key_mat_buf); @@ -360,8 +360,8 @@ static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_si SHA256(key_mat_buf, SHA384_DIGEST_LENGTH, key_mat_buf + SHA384_DIGEST_LENGTH); key_mat_buf_length = SHA384_DIGEST_LENGTH + SHA256_DIGEST_LENGTH; } else { -#ifdef OPENSSL_1_1 - priv->cipher = EVP_aes_128_cbc(); +#ifdef HAVE_OPENSSL_1_1 + priv->cipher = EVP_chacha20(); #endif aes_key_size_bytes = AES128_KEY_BYTES; SHA256(key, key_size, key_mat_buf); @@ -381,7 +381,7 @@ static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_si /* setup of key, used for the CBC encryption */ aes_key_size_bits = 8 * aes_key_size_bytes; -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 memcpy (priv->key, key_mat_buf, aes_key_size_bytes); #else AES_set_encrypt_key(key_mat_buf, aes_key_size_bits, &(priv->enc_key)); @@ -425,7 +425,7 @@ int n2n_transop_aes_cbc_init(const n2n_edge_conf_t *conf, n2n_trans_op_t *ttt) { } ttt->priv = priv; -#ifdef OPENSSL_1_1 +#ifdef HAVE_OPENSSL_1_1 /* Setup openssl's reusable evp_* contexts for encryption and decryption*/ if(!(priv->enc_ctx = EVP_CIPHER_CTX_new())) { traceEvent(TRACE_ERROR, "openssl's evp_* encryption context creation: %s\n", openssl_err_as_string()); From 453489b1e8271ca8be35a7551b05c97b1ca2227b Mon Sep 17 00:00:00 2001 From: Logan007 Date: Sat, 25 Apr 2020 23:55:14 +0200 Subject: [PATCH 02/12] fixed typo --- transform_aes.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/transform_aes.c b/transform_aes.c index 79a144a..df78cf4 100644 --- a/transform_aes.c +++ b/transform_aes.c @@ -259,7 +259,7 @@ static int transop_decode_aes(n2n_trans_op_t * arg, evp_plaintext_len = evp_len; if(1 == EVP_DecryptFinal_ex(ctx, assembly + evp_len, &evp_len)) { evp_plaintext_len += evp_len; - + if(evp_plaintext_len != len) traceEvent(TRACE_ERROR, "decode_aes openssl decryption: decrypted %u bytes where %u were expected.\n", evp_plaintext_len, len); @@ -345,14 +345,14 @@ static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_si if(key_size >= 65) { #ifdef HAVE_OPENSSL_1_1 - priv->cipher = EVP_chacha20(); + priv->cipher = EVP_aes_256_cbc(); #endif aes_key_size_bytes = AES256_KEY_BYTES; SHA512(key, key_size, key_mat_buf); key_mat_buf_length = SHA512_DIGEST_LENGTH; } else if(key_size >= 44) { #ifdef HAVE_OPENSSL_1_1 - priv->cipher = EVP_chacha20(); + priv->cipher = EVP_aes_192_cbc(); #endif aes_key_size_bytes = AES192_KEY_BYTES; SHA384(key, key_size, key_mat_buf); @@ -361,7 +361,7 @@ static int setup_aes_key(transop_aes_t *priv, const uint8_t *key, ssize_t key_si key_mat_buf_length = SHA384_DIGEST_LENGTH + SHA256_DIGEST_LENGTH; } else { #ifdef HAVE_OPENSSL_1_1 - priv->cipher = EVP_chacha20(); + priv->cipher = EVP_aes_128_cbc(); #endif aes_key_size_bytes = AES128_KEY_BYTES; SHA256(key, key_size, key_mat_buf); From 0ff7eac6fc79ad59e0b950c80906fc5b897d1b9a Mon Sep 17 00:00:00 2001 From: Logan007 Date: Sun, 26 Apr 2020 20:41:28 +0200 Subject: [PATCH 03/12] made openssl_err_as_string static to avoid conflicts with other ciphers that would also use their own function - those can't rely on the AES module being there --- transform_aes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transform_aes.c b/transform_aes.c index 59d3281..f994c8f 100644 --- a/transform_aes.c +++ b/transform_aes.c @@ -77,7 +77,7 @@ static int transop_deinit_aes(n2n_trans_op_t *arg) { #ifdef OPENSSL_1_1 /* get any erorr message out of openssl taken from https://en.wikibooks.org/wiki/OpenSSL/Error_handling */ -char *openssl_err_as_string (void) { +static char *openssl_err_as_string (void) { BIO *bio = BIO_new (BIO_s_mem ()); ERR_print_errors (bio); char *buf = NULL; From 9076ed1babc2421d312323e02d17ecf52e81b0ab Mon Sep 17 00:00:00 2001 From: Logan007 Date: Sun, 3 May 2020 18:48:59 +0200 Subject: [PATCH 04/12] added optional payload compression (lzo) for all transforms --- edge.c | 10 ++++++- edge_utils.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++-- n2n.h | 16 ++++++++---- n2n_wire.h | 1 + 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/edge.c b/edge.c index 5b470c4..f00aa05 100644 --- a/edge.c +++ b/edge.c @@ -175,6 +175,8 @@ static void help() { #ifdef N2N_HAVE_AES printf("-A | Use AES CBC for encryption (default=use twofish).\n"); #endif + printf("-z | Enable lzo1x compression for outgoing data packets\n"); + printf(" | (default=disabled).\n"); printf("-E | Accept multicast MAC addresses (default=drop).\n"); printf("-S | Do not connect P2P. Always use the supernode.\n"); #ifdef __linux__ @@ -291,6 +293,12 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e } #endif + case 'z': + { + conf->compression = N2N_COMPRESSION_ID_LZO; + break; + } + case 'l': /* supernode-list */ if(optargument) { if(edge_conf_add_supernode(conf, optargument) != 0) { @@ -398,7 +406,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:" + "k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:SDL:z" #ifdef N2N_HAVE_AES "A" #endif diff --git a/edge_utils.c b/edge_utils.c index acae39e..38910ed 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -47,6 +47,10 @@ #define IP4_MIN_SIZE 20 #define UDP_SIZE 8 +/* heap allocation for compression as per lzo example doc */ +#define HEAP_ALLOC(var,size) lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ] +static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS); + /* ************************************** */ static const char * supernode_ip(const n2n_edge_t * eee); @@ -218,12 +222,10 @@ n2n_edge_t* edge_init(const tuntap_dev *dev, const n2n_edge_conf_t *conf, int *r eee->pending_peers = NULL; eee->sup_attempts = N2N_EDGE_SUP_ATTEMPTS; -#ifdef NOT_USED if(lzo_init() != LZO_E_OK) { traceEvent(TRACE_ERROR, "LZO compression error"); goto edge_init_error; } -#endif for(i=0; isn_num; ++i) traceEvent(TRACE_NORMAL, "supernode %u => %s\n", i, (conf->sn_ip_array[i])); @@ -944,6 +946,15 @@ static int handle_PACKET(n2n_edge_t * eee, n2n_transform_t rx_transop_id; rx_transop_id = (n2n_transform_t)pkt->transform; + /* optional compression is encoded in uppermost bit of transform field. + * this is an intermediate solution to maintain compatibility until some + * upcoming major release (3.0?) brings up changes in packet structure anyway + * in the course of which a dedicated compression field could be spent. + * REVISIT then. */ + uint16_t rx_compression_id; + + rx_compression_id = (uint16_t)rx_transop_id >> (8*sizeof((uint16_t)rx_transop_id)-N2N_COMPRESSION_ID_BITLEN); + rx_transop_id &= (1 << (8*sizeof((uint16_t)rx_transop_id)-N2N_COMPRESSION_ID_BITLEN)) -1; if(rx_transop_id == eee->conf.transop_id) { uint8_t is_multicast; @@ -953,6 +964,28 @@ static int handle_PACKET(n2n_edge_t * eee, eth_payload, N2N_PKT_BUF_SIZE, payload, psize, pkt->srcMac); ++(eee->transop.rx_cnt); /* stats */ + + /* decompress if necessary */ + uint8_t * deflation_buffer = 0; + uint32_t deflated_len; + switch (rx_compression_id) { + case N2N_COMPRESSION_ID_LZO: + deflation_buffer = malloc (N2N_PKT_BUF_SIZE); + lzo1x_decompress (eth_payload, eth_size, deflation_buffer, (lzo_uint*)&deflated_len, NULL); + break; + + default: + break; + } + + 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); + memcpy(eth_payload ,deflation_buffer, deflated_len ); + eth_size = deflated_len; + free (deflation_buffer); + } + is_multicast = (is_ip6_discovery(eth_payload, eth_size) || is_ethMulticast(eth_payload, eth_size)); if(eee->conf.drop_multicast && is_multicast) { @@ -1310,6 +1343,41 @@ static void send_packet2net(n2n_edge_t * eee, pkt.sock.family=0; /* do not encode sock */ pkt.transform = tx_transop_idx; + // 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; + switch (eee->conf.compression) { + case N2N_COMPRESSION_ID_LZO: + compression_buffer = malloc (len + len / 16 + 64 + 3); + if (lzo1x_1_compress(tap_pkt, len, compression_buffer, (lzo_uint*)&compression_len, wrkmem) == LZO_E_OK) { + if (compression_len < len) { + pkt.compression = N2N_COMPRESSION_ID_LZO; + } + } + break; + + default: + break; + } + + if (pkt.compression) { + traceEvent (TRACE_DEBUG, "payload compression [id: %u]: compressed %u bytes to %u bytes\n", + pkt.compression, len, compression_len); + + memcpy (tap_pkt, compression_buffer, compression_len); + len = compression_len; + free (compression_buffer); + } + } + /* optional compression is encoded in uppermost bits of transform field. + * this is an intermediate solution to maintain compatibility until some + * upcoming major release (3.0?) brings up changes in packet structure anyway + * in the course of which a dedicated compression field could be spent. + * REVISIT then. */ + pkt.transform = pkt.transform | (pkt.compression << (8*sizeof(pkt.transform)-N2N_COMPRESSION_ID_BITLEN)); + idx=0; encode_PACKET(pktbuf, &idx, &cmn, &pkt); @@ -1916,6 +1984,7 @@ void edge_init_conf_defaults(n2n_edge_conf_t *conf) { conf->local_port = 0 /* any port */; conf->mgmt_port = N2N_EDGE_MGMT_PORT; /* 5644 by default */ conf->transop_id = N2N_TRANSFORM_ID_NULL; + conf->compression = N2N_COMPRESSION_ID_NONE; conf->drop_multicast = 1; conf->allow_p2p = 1; conf->disable_pmtu_discovery = 1; diff --git a/n2n.h b/n2n.h index dbdb5a3..ada157f 100644 --- a/n2n.h +++ b/n2n.h @@ -162,11 +162,16 @@ typedef struct tuntap_dev { #define MSG_TYPE_PEER_INFO 9 #define MSG_TYPE_QUERY_PEER 10 -/* Set N2N_COMPRESSION_ENABLED to 0 to disable lzo1x compression of ethernet - * frames. Doing this will break compatibility with the standard n2n packet - * format so do it only for experimentation. All edges must be built with the - * same value if they are to understand each other. */ -#define N2N_COMPRESSION_ENABLED 1 +/* 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. */ +#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_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 + its own field in the packet. REVISIT then. */ #define DEFAULT_MTU 1290 @@ -210,6 +215,7 @@ typedef struct n2n_edge_conf { n2n_sn_name_t sn_ip_array[N2N_EDGE_NUM_SUPERNODES]; n2n_community_t community_name; /**< The community. 16 full octets. */ n2n_transform_t transop_id; /**< The transop to use. */ + uint16_t compression; /**< Compress outgoing data packets before encryption */ uint8_t dyn_ip_mode; /**< Interface IP address is dynamically allocated, eg. DHCP. */ uint8_t allow_routing; /**< Accept packet no to interface address. */ uint8_t drop_multicast; /**< Multicast ethernet addresses. */ diff --git a/n2n_wire.h b/n2n_wire.h index 15aff9e..0a64f4a 100644 --- a/n2n_wire.h +++ b/n2n_wire.h @@ -137,6 +137,7 @@ typedef struct n2n_PACKET n2n_mac_t dstMac; n2n_sock_t sock; uint16_t transform; + uint16_t compression; } n2n_PACKET_t; /* Linked with n2n_register_super in n2n_pc_t. Only from edge to supernode. */ From aa3b2b17a5ac5f8501012fc0dc7dcdf798de563d Mon Sep 17 00:00:00 2001 From: Logan007 Date: Tue, 5 May 2020 22:22:01 +0200 Subject: [PATCH 05/12] allowed flexibe iv seed size --- transform_aes.c | 64 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/transform_aes.c b/transform_aes.c index 59d3281..3096be6 100644 --- a/transform_aes.c +++ b/transform_aes.c @@ -35,7 +35,9 @@ /* AES plaintext preamble */ #define TRANSOP_AES_VER_SIZE 1 /* Support minor variants in encoding in one module. */ -#define TRANSOP_AES_IV_SEED_SIZE 8 /* size of transmitted random part of IV in bytes; leave it set to 8 for now */ +#define TRANSOP_AES_IV_SEED_SIZE 8 /* size of transmitted random part of IV in bytes; could range + * from 0=lowest security (constant IV) to 16=higest security + * (fully random IV); default=8 */ #define TRANSOP_AES_IV_PADDING_SIZE (N2N_AES_IVEC_SIZE - TRANSOP_AES_IV_SEED_SIZE) #define TRANSOP_AES_IV_KEY_BYTES (AES128_KEY_BYTES) /* use AES128 for IV encryption */ #define TRANSOP_AES_PREAMBLE_SIZE (TRANSOP_AES_VER_SIZE + TRANSOP_AES_IV_SEED_SIZE) @@ -94,12 +96,35 @@ char *openssl_err_as_string (void) { /* ****************************************************** */ -static void set_aes_cbc_iv(transop_aes_t *priv, n2n_aes_ivec_t ivec, uint64_t iv_seed) { +/* convert a given number of bytes from memory to hex string; taken (and modified) from + https://stackoverflow.com/questions/6357031/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-in-c */ +const char* to_hex(unsigned char * in, size_t insz, char * out, size_t outsz) +{ + unsigned char * pin = in; + const char * hex = "0123456789abcdef"; + char * pout = out; + for(; pin < in+insz; pout +=2, pin++){ + pout[0] = hex[(*pin>>4) & 0xF]; + pout[1] = hex[ *pin & 0xF]; + if (pout + 2 - out > outsz){ + /* Better to truncate output string than overflow buffer */ + /* it would be still better to either return a status */ + /* or ensure the target buffer is large enough and it never happen */ + break; + } + } + pout[2] = 0; + return out; +} + +/* ****************************************************** */ + +static void set_aes_cbc_iv(transop_aes_t *priv, n2n_aes_ivec_t ivec, uint8_t * iv_seed) { uint8_t iv_full[N2N_AES_IVEC_SIZE]; /* Extend the seed to full block size with padding value */ memcpy(iv_full, priv->iv_pad_val, TRANSOP_AES_IV_PADDING_SIZE); - memcpy(iv_full + TRANSOP_AES_IV_PADDING_SIZE, &iv_seed, TRANSOP_AES_IV_SEED_SIZE); + memcpy(iv_full + TRANSOP_AES_IV_PADDING_SIZE, iv_seed, TRANSOP_AES_IV_SEED_SIZE); /* Encrypt the IV with secret key to make it unpredictable. * As discussed in https://github.com/ntop/n2n/issues/72, it's important to @@ -115,7 +140,7 @@ static void set_aes_cbc_iv(transop_aes_t *priv, n2n_aes_ivec_t ivec, uint64_t iv /** The aes packet format consists of: * * - a 8-bit aes encoding version in clear text - * - a 64-bit random IV seed + * - a TRANSOP_AES_IV_SEED_SIZE-sized [bytes] random IV seed * - encrypted payload. * * [V|II|DDDDDDDDDDDDDDDDDDDDD] @@ -135,7 +160,7 @@ static int transop_encode_aes(n2n_trans_op_t * arg, if((in_len + TRANSOP_AES_PREAMBLE_SIZE) <= out_len) { int len=-1; size_t idx=0; - uint64_t iv_seed = 0; + uint8_t iv_seed[TRANSOP_AES_IV_SEED_SIZE]; uint8_t padding = 0; n2n_aes_ivec_t enc_ivec = {0}; @@ -144,12 +169,20 @@ static int transop_encode_aes(n2n_trans_op_t * arg, /* Encode the aes format version. */ encode_uint8(outbuf, &idx, N2N_AES_TRANSFORM_VERSION); - /* Generate and encode the IV seed. - * Using two calls to rand() because RAND_MAX is usually < 64bit - * (e.g. linux) and sometimes < 32bit (e.g. Windows). - */ - iv_seed = ((((uint64_t)rand() & 0xFFFFFFFF)) << 32) | rand(); - encode_buf(outbuf, &idx, &iv_seed, TRANSOP_AES_IV_SEED_SIZE); + /* Generate and encode the IV seed using as many calls to rand() as neccessary. + * Note: ( N2N_AES_IV_SEED_SIZE % sizeof(rand_value) ) not neccessarily equals 0. */ + uint32_t rand_value; + int8_t i; + for (i = TRANSOP_AES_IV_SEED_SIZE; i >= sizeof(rand_value); i -= sizeof(rand_value)) { + rand_value = rand(); // CONCERN: rand() is not consideren cryptographicly secure, REPLACE later + memcpy(iv_seed + TRANSOP_AES_IV_SEED_SIZE - i, &rand_value, sizeof(rand_value)); + } + /* Are there bytes left to fill? */ + if (i != 0) { + rand_value = rand(); // CONCERN: rand() is not consideren cryptographicly secure, REPLACE later + memcpy(iv_seed, &rand_value, i); + } + encode_buf(outbuf, &idx, iv_seed, TRANSOP_AES_IV_SEED_SIZE); /* Encrypt the assembly contents and write the ciphertext after the iv seed. */ /* len is set to the length of the cipher plain text to be encrpyted @@ -164,7 +197,9 @@ static int transop_encode_aes(n2n_trans_op_t * arg, len2 = ((len / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE; /* Round up to next whole AES adding at least one byte. */ padding = (len2-len); assembly[len2 - 1] = padding; - traceEvent(TRACE_DEBUG, "padding = %u, seed = %016llx", padding, iv_seed); + + char iv_seed_hex[2 * N2N_AES_IVEC_SIZE + 1]; + traceEvent(TRACE_DEBUG, "padding = %u, seed = 0x%s", padding, to_hex (iv_seed, TRANSOP_AES_IV_SEED_SIZE, iv_seed_hex, 2 * N2N_AES_IVEC_SIZE + 1) ); set_aes_cbc_iv(priv, enc_ivec, iv_seed); @@ -229,7 +264,7 @@ static int transop_decode_aes(n2n_trans_op_t * arg, size_t rem=in_len; size_t idx=0; uint8_t aes_enc_ver=0; - uint64_t iv_seed=0; + uint8_t iv_seed[TRANSOP_AES_IV_SEED_SIZE]; /* Get the encoding version to make sure it is supported */ decode_uint8(&aes_enc_ver, inbuf, &rem, &idx ); @@ -238,7 +273,8 @@ static int transop_decode_aes(n2n_trans_op_t * arg, /* Get the IV seed */ decode_buf((uint8_t *)&iv_seed, TRANSOP_AES_IV_SEED_SIZE, inbuf, &rem, &idx); - traceEvent(TRACE_DEBUG, "decode_aes %lu with seed %016llx", in_len, iv_seed); + char iv_seed_hex[2 * N2N_AES_IVEC_SIZE + 1]; + traceEvent(TRACE_DEBUG, "decode_aes %lu with seed 0x%s", in_len, to_hex (iv_seed, TRANSOP_AES_IV_SEED_SIZE, iv_seed_hex, 2 * N2N_AES_IVEC_SIZE + 1) ); len = (in_len - TRANSOP_AES_PREAMBLE_SIZE); From 928fc9f22e95a04797f73d95665ce51c1f665a4f Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sat, 23 May 2020 14:33:10 +0200 Subject: [PATCH 06/12] Add ability to insert linux routes in n2n The new -n option can now be used to setup routes directly from the edge command line. It supports both network routes and default gateway routes. Here is an example: - "-n 192.168.100.0/24:192.168.99.1" routes the 192.168.100.0/24 network traffic via 192.168.99.1 - "-n 0.0.0.0/0:192.168.99.1" routes all the host traffic via 192.168.99.1 --- edge.c | 42 +++++- edge_utils.c | 357 ++++++++++++++++++++++++++++++++++++++++++++++++- n2n.h | 12 +- tuntap_linux.c | 5 + 4 files changed, 407 insertions(+), 9 deletions(-) diff --git a/edge.c b/edge.c index f00aa05..c303240 100644 --- a/edge.c +++ b/edge.c @@ -135,6 +135,7 @@ static void help() { #endif /* #ifndef WIN32 */ #ifdef __linux__ "[-T ]" + "[-n cidr:gateway] " #endif "[-m ] " "-l \n" @@ -181,6 +182,7 @@ static void help() { printf("-S | Do not connect P2P. Always use the supernode.\n"); #ifdef __linux__ printf("-T | TOS for packets (e.g. 0x48 for SSH like priority)\n"); + printf("-n | Route an IPv4 network via the gw. Use 0.0.0.0/0 for the default gw. Can be set multiple times.\n"); #endif printf("-v | Make more verbose. Repeat as required.\n"); printf("-t | Management UDP Port (for multiple edges on a machine).\n"); @@ -347,6 +349,43 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e break; } + + case 'n': + { + char cidr_net[64], gateway[64]; + n2n_route_t route; + + if(sscanf(optargument, "%63[^/]/%d:%63s", cidr_net, &route.net_bitlen, gateway) != 3) { + traceEvent(TRACE_WARNING, "Bad cidr/gateway format '%d'. See -h.", optargument); + break; + } + + route.net_addr = inet_addr(cidr_net); + route.gateway = inet_addr(gateway); + + if((route.net_bitlen < 0) || (route.net_bitlen > 32)) { + traceEvent(TRACE_WARNING, "Bad prefix '%d' in '%s'", route.net_bitlen, optargument); + break; + } + + if(route.net_addr == INADDR_NONE) { + traceEvent(TRACE_WARNING, "Bad network '%s' in '%s'", cidr_net, optargument); + break; + } + + if(route.net_addr == INADDR_NONE) { + traceEvent(TRACE_WARNING, "Bad gateway '%s' in '%s'", gateway, optargument); + break; + } + + traceEvent(TRACE_DEBUG, "Adding %s/%d via %s", cidr_net, route.net_bitlen, gateway); + + conf->routes = realloc(conf->routes, sizeof(struct n2n_route) * (conf->num_routes + 1)); + conf->routes[conf->num_routes] = route; + conf->num_routes++; + + break; + } #endif case 's': /* Subnet Mask */ @@ -411,7 +450,7 @@ static int loadFromCLI(int argc, char *argv[], n2n_edge_conf_t *conf, n2n_priv_c "A" #endif #ifdef __linux__ - "T:" + "T:n:" #endif , long_options, NULL)) != '?') { @@ -766,6 +805,7 @@ int main(int argc, char* argv[]) { /* Cleanup */ edge_term(eee); + edge_term_conf(&conf); tuntap_close(&tuntap); if(conf.encrypt_key) free(conf.encrypt_key); diff --git a/edge_utils.c b/edge_utils.c index 38910ed..6f2faf6 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -41,6 +41,11 @@ #define ARP_PERIOD_INTERVAL (10) /* sec */ #endif +#ifdef __linux__ +#include +#include +#endif + #define ETH_FRAMESIZE 14 #define IP4_SRCOFFSET 12 #define IP4_DSTOFFSET 16 @@ -60,7 +65,9 @@ static void check_peer_registration_needed(n2n_edge_t * eee, const n2n_mac_t mac, const n2n_sock_t * peer); static int edge_init_sockets(n2n_edge_t *eee, int udp_local_port, int mgmt_port, uint8_t tos); -static void supernode2addr(n2n_sock_t * sn, const n2n_sn_name_t addrIn); +static int edge_init_routes(n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes); +static void edge_cleanup_routes(n2n_edge_t *eee); +static int supernode2addr(n2n_sock_t * sn, const n2n_sn_name_t addrIn); static void check_known_peer_sock_change(n2n_edge_t * eee, uint8_t from_supernode, const n2n_mac_t mac, @@ -109,6 +116,7 @@ struct n2n_edge { tuntap_dev device; /**< All about the TUNTAP device */ n2n_trans_op_t transop; /**< The transop to use when encoding */ n2n_cookie_t last_cookie; /**< Cookie sent in last REGISTER_SUPER. */ + n2n_route_t *sn_route_to_clean; /**< Supernode route to clean */ /* Sockets */ n2n_sock_t supernode; @@ -256,7 +264,12 @@ n2n_edge_t* edge_init(const tuntap_dev *dev, const n2n_edge_conf_t *conf, int *r traceEvent(TRACE_WARNING, "Encryption is disabled in edge"); if(edge_init_sockets(eee, conf->local_port, conf->mgmt_port, conf->tos) < 0) { - traceEvent(TRACE_ERROR, "Error: socket setup failed"); + traceEvent(TRACE_ERROR, "socket setup failed"); + goto edge_init_error; + } + + if(edge_init_routes(eee, conf->routes, conf->num_routes) < 0) { + traceEvent(TRACE_ERROR, "routes setup failed"); goto edge_init_error; } @@ -309,16 +322,16 @@ static int is_valid_peer_sock(const n2n_sock_t *sock) { * REVISIT: This is a really bad idea. The edge will block completely while the * hostname resolution is performed. This could take 15 seconds. */ -static void supernode2addr(n2n_sock_t * sn, const n2n_sn_name_t addrIn) { +static int supernode2addr(n2n_sock_t * sn, const n2n_sn_name_t addrIn) { n2n_sn_name_t addr; const char *supernode_host; + int rv = 0; memcpy(addr, addrIn, N2N_EDGE_SN_HOST_SIZE); supernode_host = strtok(addr, ":"); - if(supernode_host) - { + if(supernode_host) { in_addr_t sn_addr; char *supernode_port = strtok(NULL, ":"); const struct addrinfo aihints = {0, PF_INET, 0, 0, 0, NULL, NULL, NULL}; @@ -350,6 +363,7 @@ static void supernode2addr(n2n_sock_t * sn, const n2n_sn_name_t addrIn) { { /* Should only return IPv4 addresses due to aihints. */ traceEvent(TRACE_WARNING, "Failed to resolve supernode IPv4 address for %s", supernode_host); + rv = -1; } freeaddrinfo(ainfo); /* free everything allocated by getaddrinfo(). */ @@ -359,10 +373,15 @@ static void supernode2addr(n2n_sock_t * sn, const n2n_sn_name_t addrIn) { sn_addr = inet_addr(supernode_host); /* uint32_t */ memcpy(sn->addr.v4, &(sn_addr), IPV4_SIZE); sn->family=AF_INET; + rv = -2; } - } else + } else { traceEvent(TRACE_WARNING, "Wrong supernode parameter (-l )"); + rv = -3; + } + + return(rv); } /* ************************************** */ @@ -1907,6 +1926,9 @@ void edge_term(n2n_edge_t * eee) { clear_peer_list(&eee->known_peers); eee->transop.deinit(&eee->transop); + + edge_cleanup_routes(eee); + free(eee); } @@ -1978,6 +2000,322 @@ static int edge_init_sockets(n2n_edge_t *eee, int udp_local_port, int mgmt_port, /* ************************************** */ +#ifdef __linux__ + +static uint32_t get_gateway_ip() { + FILE *fd; + char *token = NULL; + char *gateway_ip_str = NULL; + char buf[256]; + uint32_t gateway = 0; + + if(!(fd = fopen("/proc/net/route", "r"))) + return(0); + + while(fgets(buf, sizeof(buf), fd)) { + if(strtok(buf, "\t") && (token = strtok(NULL, "\t")) && (!strcmp(token, "00000000"))) { + token = strtok(NULL, "\t"); + + if(token) { + struct in_addr addr; + + addr.s_addr = strtoul(token, NULL, 16); + gateway_ip_str = inet_ntoa(addr); + + if(gateway_ip_str) { + gateway = addr.s_addr; + break; + } + } + } + } + + fclose(fd); + + return(gateway); +} + +static char* route_cmd_to_str(int cmd, const n2n_route_t *route, char *buf, size_t bufsize) { + const char *cmd_str; + struct in_addr addr; + char netbuf[64], gwbuf[64]; + + switch(cmd) { + case RTM_NEWROUTE: + cmd_str = "ADD"; + break; + case RTM_DELROUTE: + cmd_str = "DELETE"; + break; + default: + cmd_str = "?"; + } + + addr.s_addr = route->net_addr; + inet_ntop(AF_INET, &addr, netbuf, sizeof(netbuf)); + addr.s_addr = route->gateway; + inet_ntop(AF_INET, &addr, gwbuf, sizeof(gwbuf)); + + snprintf(buf, bufsize, "[%s] %s/%d via %s", cmd_str, netbuf, route->net_bitlen, gwbuf); + + return(buf); +} + +/* Adapted from https://olegkutkov.me/2019/08/29/modifying-linux-network-routes-using-netlink/ */ +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/* Add new data to rtattr */ +static int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + traceEvent(TRACE_ERROR, "rtattr_add error: message exceeded bound of %d\n", maxlen); + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + + if(alen) + memcpy(RTA_DATA(rta), data, alen); + + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return 0; +} + +static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx, uint8_t ignore_failure) { + int rv = -1; + int rv2; + char nl_buf[8192]; /* >= 8192 to avoid truncation, see "man 7 netlink" */ + struct iovec iov; + struct msghdr msg; + struct sockaddr_nl sa; + uint8_t read_reply = 1; + int nl_sock; + + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[4096]; + } nl_request; + + if((nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) { + traceEvent(TRACE_ERROR, "netlink socket creation failed [%d]: %s", errno, strerror(errno)); + return(-1); + } + + /* Subscribe to route change events */ + iov.iov_base = nl_buf; + iov.iov_len = sizeof(nl_buf); + + memset(&sa, 0, sizeof(sa)); + sa.nl_family = PF_NETLINK; + sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_NOTIFY; + sa.nl_pid = getpid(); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + /* Subscribe to route events */ + if(bind(nl_sock, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + traceEvent(TRACE_ERROR, "netlink socket bind failed [%d]: %s", errno, strerror(errno)); + goto out; + } + + /* Initialize request structure */ + memset(&nl_request, 0, sizeof(nl_request)); + nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags; + nl_request.n.nlmsg_type = cmd; + nl_request.r.rtm_family = AF_INET; + nl_request.r.rtm_table = RT_TABLE_MAIN; + nl_request.r.rtm_scope = RT_SCOPE_NOWHERE; + + /* Set additional flags if NOT deleting route */ + if(cmd != RTM_DELROUTE) { + nl_request.r.rtm_protocol = RTPROT_BOOT; + nl_request.r.rtm_type = RTN_UNICAST; + } + + nl_request.r.rtm_family = AF_INET; + nl_request.r.rtm_dst_len = route->net_bitlen; + + /* Select scope, for simplicity we supports here only IPv6 and IPv4 */ + if(nl_request.r.rtm_family == AF_INET6) + nl_request.r.rtm_scope = RT_SCOPE_UNIVERSE; + else + nl_request.r.rtm_scope = RT_SCOPE_LINK; + + /* Set gateway */ + if(route->net_bitlen) { + if(rtattr_add(&nl_request.n, sizeof(nl_request), RTA_GATEWAY, &route->gateway, 4) < 0) + goto out; + + nl_request.r.rtm_scope = 0; + nl_request.r.rtm_family = AF_INET; + } + + /* Don't set destination and interface in case of default gateways */ + if(route->net_bitlen) { + /* Set destination network */ + if(rtattr_add(&nl_request.n, sizeof(nl_request), /*RTA_NEWDST*/ RTA_DST, &route->net_addr, 4) < 0) + goto out; + + /* Set interface */ + if(if_idx > 0) { + if(rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int)) < 0) + goto out; + } + } + + /* Send message to the netlink */ + if((rv2 = send(nl_sock, &nl_request, sizeof(nl_request), 0)) != sizeof(nl_request)) { + traceEvent(TRACE_ERROR, "netlink send failed [%d]: %s", errno, strerror(errno)); + goto out; + } + + /* Wait for the route notification. Assume that the first reply we get is the correct one. */ + while(read_reply) { + ssize_t len = recvmsg(nl_sock, &msg, 0); + struct nlmsghdr *nh; + + for(nh = (struct nlmsghdr *)nl_buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { + /* Stop after the first reply */ + read_reply = 0; + + if(nh->nlmsg_type == NLMSG_ERROR) { + char buf[256]; + struct nlmsgerr *err = NLMSG_DATA(nh); + int errcode = err->error; + + if(errcode < 0) + errcode = -errcode; + + /* Ignore EEXIST as existing rules are ok */ + if(!ignore_failure && (errcode != EEXIST)) { + traceEvent(TRACE_ERROR, "[err=%d] route: %s", errcode, route_cmd_to_str(cmd, route, buf, sizeof(buf))); + goto out; + } + } + + if(nh->nlmsg_type == NLMSG_DONE) + break; + + if(nh->nlmsg_type == cmd) + break; + } + } + + rv = 0; + +out: + close(nl_sock); + + return(rv); +} +#endif + +/* Add the user-provided routes to the linux routing table. Network routes + * are bound to the n2n TAP device, so they are automatically removed when + * the TAP device is destroyed. */ +static int edge_init_routes(n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes) { +#ifdef __linux__ + int i; + + for(i=0; inet_addr == 0) && (route->net_bitlen == 0)) { + /* This is a default gateway rule. We need to: + * + * 1. Add a route to the supernode via the host internet gateway + * 2. Add the new default gateway route + * + * Instead of modifying the system default gateway, we use the trick + * of adding a route to the 0.0.0.0/1 network, which takes precedence + * over the default gateway (0.0.0.0/0). This leaves the default + * gateway unchanged so that after n2n is stopped the cleanup is + * easier. + */ + n2n_sock_t sn; + n2n_route_t custom_route; + + if(eee->sn_route_to_clean) { + traceEvent(TRACE_ERROR, "Only one default gateway route allowed"); + return(-1); + } + + if(eee->conf.sn_num != 1) { + traceEvent(TRACE_ERROR, "Only one supernode supported with routes"); + return(-1); + } + + if(supernode2addr(&sn, eee->conf.sn_ip_array[0]) < 0) + return(-1); + + if(sn.family != AF_INET) { + traceEvent(TRACE_ERROR, "Only IPv4 routes supported"); + return(-1); + } + + custom_route.net_addr = *((u_int32_t*)sn.addr.v4); + custom_route.net_bitlen = 32; + custom_route.gateway = get_gateway_ip(); + + if(!custom_route.gateway) { + traceEvent(TRACE_ERROR, "could not determine the gateway IP address"); + return(-1); + } + + /* ip route add supernode via internet_gateway */ + if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, -1, 0) < 0) + return(-1); + + /* Save the route to delete it when n2n is stopped */ + eee->sn_route_to_clean = calloc(1, sizeof(n2n_route_t)); + + /* Store a copy of the rules into the runtime to delete it during shutdown */ + if(eee->sn_route_to_clean) + *eee->sn_route_to_clean = custom_route; + + /* ip route add 0.0.0.0/1 via n2n_gateway */ + custom_route.net_addr = 0; + custom_route.net_bitlen = 1; + custom_route.gateway = route->gateway; + + if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, eee->device.if_idx, 0) < 0) + return(-1); + } else { + /* ip route add net via n2n_gateway */ + if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, route, eee->device.if_idx, 0) < 0) + return(-1); + } + } +#endif + + return(0); +} + +/* ************************************** */ + +static void edge_cleanup_routes(n2n_edge_t *eee) { + if(eee->sn_route_to_clean) { + /* ip route del supernode via internet_gateway */ + routectl(RTM_DELROUTE, 0, eee->sn_route_to_clean, -1, 1 /* can fail as we have dropped capabilities */); + free(eee->sn_route_to_clean); + } +} + +/* ************************************** */ + void edge_init_conf_defaults(n2n_edge_conf_t *conf) { memset(conf, 0, sizeof(*conf)); @@ -1998,6 +2336,12 @@ void edge_init_conf_defaults(n2n_edge_conf_t *conf) { /* ************************************** */ +void edge_term_conf(n2n_edge_conf_t *conf) { + if(conf->routes) free(conf->routes); +} + +/* ************************************** */ + const n2n_edge_conf_t* edge_get_conf(const n2n_edge_t *eee) { return(&eee->conf); } @@ -2050,6 +2394,7 @@ int quick_edge_init(char *device_name, char *community_name, rv = run_edge_loop(eee, keep_on_running); edge_term(eee); + edge_term_conf(&conf); quick_edge_init_end: tuntap_close(&tuntap); diff --git a/n2n.h b/n2n.h index ada157f..017e80c 100644 --- a/n2n.h +++ b/n2n.h @@ -73,8 +73,6 @@ #include #ifdef __linux__ -#include -#include #define N2N_CAN_NAME_IFACE 1 #endif /* #ifdef __linux__ */ @@ -139,6 +137,7 @@ typedef struct ether_hdr ether_hdr_t; #ifndef WIN32 typedef struct tuntap_dev { int fd; + int if_idx; uint8_t mac_addr[6]; uint32_t ip_addr, device_mask; uint16_t mtu; @@ -211,11 +210,19 @@ struct peer_info { typedef char n2n_sn_name_t[N2N_EDGE_SN_HOST_SIZE]; +typedef struct n2n_route { + in_addr_t net_addr; + int net_bitlen; + in_addr_t gateway; +} n2n_route_t; + typedef struct n2n_edge_conf { n2n_sn_name_t sn_ip_array[N2N_EDGE_NUM_SUPERNODES]; + n2n_route_t *routes; /**< Networks to route through n2n */ n2n_community_t community_name; /**< The community. 16 full octets. */ n2n_transform_t transop_id; /**< The transop to use. */ uint16_t compression; /**< Compress outgoing data packets before encryption */ + uint16_t num_routes; /**< Number of routes in routes */ uint8_t dyn_ip_mode; /**< Interface IP address is dynamically allocated, eg. DHCP. */ uint8_t allow_routing; /**< Accept packet no to interface address. */ uint8_t drop_multicast; /**< Multicast ethernet addresses. */ @@ -344,6 +351,7 @@ void edge_init_conf_defaults(n2n_edge_conf_t *conf); int edge_verify_conf(const n2n_edge_conf_t *conf); int edge_conf_add_supernode(n2n_edge_conf_t *conf, const char *ip_and_port); const n2n_edge_conf_t* edge_get_conf(const n2n_edge_t *eee); +void edge_term_conf(n2n_edge_conf_t *conf); /* Public functions */ n2n_edge_t* edge_init(const tuntap_dev *dev, const n2n_edge_conf_t *conf, int *rv); diff --git a/tuntap_linux.c b/tuntap_linux.c index ae10445..b3f6fe5 100644 --- a/tuntap_linux.c +++ b/tuntap_linux.c @@ -21,6 +21,8 @@ #ifdef __linux__ #include +#include +#include #include #include @@ -170,6 +172,7 @@ int tuntap_open(tuntap_dev *device, sa.nl_groups = RTMGRP_LINK; sa.nl_pid = getpid(); + memset(&msg, 0, sizeof(msg)); msg.msg_name = &sa; msg.msg_namelen = sizeof(sa); msg.msg_iov = &iov; @@ -229,6 +232,8 @@ int tuntap_open(tuntap_dev *device, device->ip_addr = inet_addr(device_ip); device->device_mask = inet_addr(device_mask); + device->if_idx = if_nametoindex(dev); + return(device->fd); } From 62b953042530d5e06fb2b11019cc04b3d0ecf434 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sat, 23 May 2020 16:44:47 +0200 Subject: [PATCH 07/12] Add missing ifdef --- edge_utils.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/edge_utils.c b/edge_utils.c index 6f2faf6..e6f0580 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -2183,6 +2183,8 @@ static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx, uint8_t } /* Wait for the route notification. Assume that the first reply we get is the correct one. */ + traceEvent(TRACE_DEBUG, "waiting for netlink response..."); + while(read_reply) { ssize_t len = recvmsg(nl_sock, &msg, 0); struct nlmsghdr *nh; @@ -2307,11 +2309,13 @@ static int edge_init_routes(n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_r /* ************************************** */ static void edge_cleanup_routes(n2n_edge_t *eee) { +#ifdef __linux__ if(eee->sn_route_to_clean) { /* ip route del supernode via internet_gateway */ routectl(RTM_DELROUTE, 0, eee->sn_route_to_clean, -1, 1 /* can fail as we have dropped capabilities */); free(eee->sn_route_to_clean); } +#endif } /* ************************************** */ From 0e48e2f24c33391ab9a0b73e5fba636089cabbfb Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sat, 23 May 2020 17:17:32 +0200 Subject: [PATCH 08/12] Add support for linux capabilities to proper routes cleanup --- configure.seed | 6 ++++++ edge.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ edge_utils.c | 27 +++++++++++++++------------ 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/configure.seed b/configure.seed index d1f0004..067351c 100644 --- a/configure.seed +++ b/configure.seed @@ -49,6 +49,12 @@ if test x$pcap_immediate_mode != x; then AC_DEFINE([HAVE_PCAP_IMMEDIATE_MODE], [], [Have pcap_immediate_mode]) fi +AC_CHECK_LIB([cap], [cap_get_proc], cap=true) +if test x$cap != x; then + LDFLAGS="${LDFLAGS} -lcap" + AC_DEFINE([HAVE_LIBCAP],[1],[Support for linux capabilities]) +fi + MACHINE=`uname -m` SYSTEM=`uname -s` diff --git a/edge.c b/edge.c index c303240..17b7b40 100644 --- a/edge.c +++ b/edge.c @@ -37,6 +37,21 @@ /* ***************************************************** */ +#ifdef HAVE_LIBCAP + +#include +#include + +static cap_value_t cap_values[] = { + //CAP_NET_RAW, /* Use RAW and PACKET sockets */ + CAP_NET_ADMIN /* Needed to performs routes cleanup at exit */ +}; + +int num_cap = sizeof(cap_values)/sizeof(cap_value_t); +#endif + +/* ***************************************************** */ + typedef struct n2n_priv_config { char tuntap_dev_name[N2N_IFNAMSIZ]; char ip_mode[N2N_IF_MODE_SIZE]; @@ -681,6 +696,9 @@ int main(int argc, char* argv[]) { #ifndef WIN32 struct passwd *pw = NULL; #endif +#ifdef HAVE_LIBCAP + cap_t caps; +#endif /* Defaults */ edge_init_conf_defaults(&conf); @@ -774,6 +792,22 @@ int main(int argc, char* argv[]) { #endif /* #ifndef WIN32 */ #ifndef WIN32 + +#ifdef HAVE_LIBCAP + /* Before dropping the privileges, retain capabilities to regain them in future. */ + caps = cap_get_proc(); + + cap_set_flag(caps, CAP_PERMITTED, num_cap, cap_values, CAP_SET); + cap_set_flag(caps, CAP_EFFECTIVE, num_cap, cap_values, CAP_SET); + + if((cap_set_proc(caps) != 0) || (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) != 0)) + traceEvent(TRACE_WARNING, "Unable to retain permitted capabilities [%s]\n", strerror(errno)); +#else +#ifndef __APPLE__ + traceEvent(TRACE_WARNING, "n2n has not been compiled with libcap-dev. Some commands may fail."); +#endif +#endif /* HAVE_LIBCAP */ + if((ec.userid != 0) || (ec.groupid != 0)) { traceEvent(TRACE_NORMAL, "Dropping privileges to uid=%d, gid=%d", (signed int)ec.userid, (signed int)ec.groupid); @@ -803,6 +837,17 @@ int main(int argc, char* argv[]) { rc = run_edge_loop(eee, &keep_on_running); print_edge_stats(eee); +#ifdef HAVE_LIBCAP + /* Before completing the cleanup, regain the capabilities as some + * cleanup tasks require them (e.g. routes cleanup). */ + cap_set_flag(caps, CAP_EFFECTIVE, num_cap, cap_values, CAP_SET); + + if(cap_set_proc(caps) != 0) + traceEvent(TRACE_WARNING, "Could not regain the capabilities [%s]\n", strerror(errno)); + + cap_free(caps); +#endif + /* Cleanup */ edge_term(eee); edge_term_conf(&conf); diff --git a/edge_utils.c b/edge_utils.c index e6f0580..182eb91 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -2042,10 +2042,10 @@ static char* route_cmd_to_str(int cmd, const n2n_route_t *route, char *buf, size switch(cmd) { case RTM_NEWROUTE: - cmd_str = "ADD"; + cmd_str = "Add"; break; case RTM_DELROUTE: - cmd_str = "DELETE"; + cmd_str = "Delete"; break; default: cmd_str = "?"; @@ -2056,7 +2056,7 @@ static char* route_cmd_to_str(int cmd, const n2n_route_t *route, char *buf, size addr.s_addr = route->gateway; inet_ntop(AF_INET, &addr, gwbuf, sizeof(gwbuf)); - snprintf(buf, bufsize, "[%s] %s/%d via %s", cmd_str, netbuf, route->net_bitlen, gwbuf); + snprintf(buf, bufsize, "%s %s/%d via %s", cmd_str, netbuf, route->net_bitlen, gwbuf); return(buf); } @@ -2088,10 +2088,11 @@ static int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data return 0; } -static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx, uint8_t ignore_failure) { +static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx) { int rv = -1; int rv2; char nl_buf[8192]; /* >= 8192 to avoid truncation, see "man 7 netlink" */ + char route_buf[256]; struct iovec iov; struct msghdr msg; struct sockaddr_nl sa; @@ -2194,7 +2195,6 @@ static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx, uint8_t read_reply = 0; if(nh->nlmsg_type == NLMSG_ERROR) { - char buf[256]; struct nlmsgerr *err = NLMSG_DATA(nh); int errcode = err->error; @@ -2202,8 +2202,8 @@ static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx, uint8_t errcode = -errcode; /* Ignore EEXIST as existing rules are ok */ - if(!ignore_failure && (errcode != EEXIST)) { - traceEvent(TRACE_ERROR, "[err=%d] route: %s", errcode, route_cmd_to_str(cmd, route, buf, sizeof(buf))); + if(errcode != EEXIST) { + traceEvent(TRACE_ERROR, "[err=%d] route: %s", errcode, route_cmd_to_str(cmd, route, route_buf, sizeof(route_buf))); goto out; } } @@ -2211,11 +2211,14 @@ static int routectl(int cmd, int flags, n2n_route_t *route, int if_idx, uint8_t if(nh->nlmsg_type == NLMSG_DONE) break; - if(nh->nlmsg_type == cmd) + if(nh->nlmsg_type == cmd) { + traceEvent(TRACE_DEBUG, "Found netlink reply"); break; + } } } + traceEvent(TRACE_DEBUG, route_cmd_to_str(cmd, route, route_buf, sizeof(route_buf))); rv = 0; out: @@ -2278,7 +2281,7 @@ static int edge_init_routes(n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_r } /* ip route add supernode via internet_gateway */ - if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, -1, 0) < 0) + if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, -1) < 0) return(-1); /* Save the route to delete it when n2n is stopped */ @@ -2293,11 +2296,11 @@ static int edge_init_routes(n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_r custom_route.net_bitlen = 1; custom_route.gateway = route->gateway; - if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, eee->device.if_idx, 0) < 0) + if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, eee->device.if_idx) < 0) return(-1); } else { /* ip route add net via n2n_gateway */ - if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, route, eee->device.if_idx, 0) < 0) + if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, route, eee->device.if_idx) < 0) return(-1); } } @@ -2312,7 +2315,7 @@ static void edge_cleanup_routes(n2n_edge_t *eee) { #ifdef __linux__ if(eee->sn_route_to_clean) { /* ip route del supernode via internet_gateway */ - routectl(RTM_DELROUTE, 0, eee->sn_route_to_clean, -1, 1 /* can fail as we have dropped capabilities */); + routectl(RTM_DELROUTE, 0, eee->sn_route_to_clean, -1); free(eee->sn_route_to_clean); } #endif From 2a28bd1952c0fdded5317ef1d0ee50d8aeec8b9d Mon Sep 17 00:00:00 2001 From: Emanuele Faranda Date: Sat, 23 May 2020 15:35:51 +0000 Subject: [PATCH 09/12] New routing instructions --- README.md | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 66d910f..0ef019e 100644 --- a/README.md +++ b/README.md @@ -47,35 +47,22 @@ Now the supernode service should be up and running on port 1234. On your edge no ## Routing the traffic -On linux, n2n provides a standard TAP interface, so routing works gracefully via the standard system utilities as follows. +Reaching a remote network or tunneling all the internet traffic via n2n are two common tasks which require a proper routing setup. In this context, the `server` is the edge node which provides access to the remote network/internet, whereas the `client` is the connecting edge node. -In this example host1 is the edge router (with n2n IP 192.168.100.1), whereas host2 is the client. - -Here is how to configure host1: +In order to enable routing, the `server` must be configured as follows: 1. Add the `-r` option to the edge options to enable routing 2. Enable packet forwarding with `sudo sysctl -w net.ipv4.ip_forward=1` -3. Possibly configure iptables to `ACCEPT` the packets on the `FORWARD` chain. +3. Enable IP masquerading: `sudo iptables -t nat -A POSTROUTING -j MASQUERADE` -On host2, run the `edge` program as normal to join the host1 community. +On the client side, the easiest way to configure routing is via the `-n` option. For example: -In order to forward all the internet traffic via host2: +- In order to connect to the remote network `192.168.100.0/24`, use `-n 192.168.100.0/24:10.0.0.1` +- In order to tunnel all the internet traffic, use `-n 0.0.0.0/0:10.0.0.1` -```sh -# Determine the current gateway (e.g. 192.168.1.1) -$ ip route show default +10.0.0.1 is the IP address of the gateway to use to route the specified network. It should correspond to the IP address of the `server` within n2n. Multiple `-n` options can be specified. -# Add a route to reach the supernode via such gateway -$ sudo ip route add supernode.ntop.org via 192.168.1.1 - -# Forward all the internet traffic via host1 -$ sudo ip route del default -$ sudo ip route add default via 192.168.100.1 -``` - -This process can be greatly simplified by using the [n2n_gateway.sh](doc/n2n_gateway.sh) script. - -See [Routing.md](doc/Routing.md) for other use cases and in depth explanation. +As an alternative to the `-n` option, the `ip route` linux command can be manually used. See the [n2n_gateway.sh](doc/n2n_gateway.sh) script for an example. See also [Routing.md](doc/Routing.md) for other use cases and in depth explanation. ## Manual Compilation From a014f5a68bf7243abd2cc95388ff036b127cd0e9 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sun, 24 May 2020 11:25:12 +0200 Subject: [PATCH 10/12] Windows compilation fix --- Makefile.in | 3 --- edge.c | 4 ++-- sn.c | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Makefile.in b/Makefile.in index 14f5599..fbb0c29 100644 --- a/Makefile.in +++ b/Makefile.in @@ -91,9 +91,6 @@ example_sn_embed: example_sn_embed.c $(N2N_LIB) n2n.h example_edge_embed: example_edge_embed.c $(N2N_LIB) n2n.h $(CC) $(CFLAGS) $< $(LDFLAGS) $(N2N_LIB) $(LIBS_EDGE) -o $@ -.c.o: n2n.h n2n_transforms.h n2n_wire.h twofish.h Makefile - $(CC) $(CFLAGS) -c $< -o $@ - %.gz : % gzip -c $< > $@ diff --git a/edge.c b/edge.c index 17b7b40..c45d5d4 100644 --- a/edge.c +++ b/edge.c @@ -660,7 +660,7 @@ static void daemonize() { static int keep_on_running; -#ifdef __linux__ +#if defined(__linux__) || defined(WIN32) #ifdef WIN32 BOOL WINAPI term_handler(DWORD sig) #else @@ -682,7 +682,7 @@ static void term_handler(int sig) return(TRUE); #endif } -#endif +#endif /* defined(__linux__) || defined(WIN32) */ /* *************************************************** */ diff --git a/sn.c b/sn.c index 0a86a38..9d7c26b 100644 --- a/sn.c +++ b/sn.c @@ -930,7 +930,7 @@ static void dump_registrations(int signo) { static int keep_running; -#ifdef __linux__ +#if defined(__linux__) || defined(WIN32) #ifdef WIN32 BOOL WINAPI term_handler(DWORD sig) #else @@ -952,7 +952,7 @@ static void term_handler(int sig) return(TRUE); #endif } -#endif +#endif /* defined(__linux__) || defined(WIN32) */ /* *************************************************** */ From 5044fcde2675bb73f2ae2cc1923e608f11cf1589 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sun, 24 May 2020 11:36:56 +0200 Subject: [PATCH 11/12] Add linux capabilities dependency to cmake --- CMakeLists.txt | 8 ++++++++ edge_utils.c | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 674b05e..fe2319f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,14 @@ target_link_libraries(example_edge_embed n2n) add_executable(example_sn_embed example_sn_embed.c) target_link_libraries(example_sn_embed n2n) +# Linux Capabilities +find_library(CAP_LIB cap) +if(CAP_LIB) + target_link_libraries(edge cap) + set(CMAKE_REQUIRED_LIBRARIES ${CAP_LIB}) + ADD_DEFINITIONS("-DHAVE_LIBCAP") +endif() + install(TARGETS edge supernode RUNTIME DESTINATION sbin LIBRARY DESTINATION lib diff --git a/edge_utils.c b/edge_utils.c index 182eb91..c387edc 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -2063,7 +2063,7 @@ static char* route_cmd_to_str(int cmd, const n2n_route_t *route, char *buf, size /* Adapted from https://olegkutkov.me/2019/08/29/modifying-linux-network-routes-using-netlink/ */ #define NLMSG_TAIL(nmsg) \ - ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + ((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) /* Add new data to rtattr */ static int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) From 3d9d3a13f7f0e4ada2c5861807ed2dce990b6a49 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Wed, 27 May 2020 10:54:12 +0200 Subject: [PATCH 12/12] Windows cmake fix Fixes #248 --- CMakeLists.txt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fe2319f..8f52c16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,13 +92,15 @@ target_link_libraries(example_edge_embed n2n) add_executable(example_sn_embed example_sn_embed.c) target_link_libraries(example_sn_embed n2n) -# Linux Capabilities -find_library(CAP_LIB cap) -if(CAP_LIB) - target_link_libraries(edge cap) - set(CMAKE_REQUIRED_LIBRARIES ${CAP_LIB}) - ADD_DEFINITIONS("-DHAVE_LIBCAP") -endif() +if(NOT DEFINED WIN32) + # Linux Capabilities + find_library(CAP_LIB cap) + if(CAP_LIB) + target_link_libraries(edge cap) + set(CMAKE_REQUIRED_LIBRARIES ${CAP_LIB}) + ADD_DEFINITIONS("-DHAVE_LIBCAP") + endif() +endif(NOT DEFINED WIN32) install(TARGETS edge supernode RUNTIME DESTINATION sbin