#include #include #include "n2nvpn.h" extern "C" { #include "random_numbers.h" #include "sn_selection.h" #include "uthash.h" #ifdef _WIN32 #include "win32/defs.h" #else #include // for inet_ntoa #include // for in_addr, htonl, in_addr_t #endif void send_register_super(n2n_edge_t *eee); void send_query_peer(n2n_edge_t *eee, const n2n_mac_t dst_mac); int supernode_connect(n2n_edge_t *eee); int supernode_disconnect(n2n_edge_t *eee); int fetch_and_eventually_process_data(n2n_edge_t *eee, SOCKET sock, uint8_t *pktbuf, uint16_t *expected, uint16_t *position, time_t now); uint8_t resolve_check(n2n_resolve_parameter_t *param, uint8_t resolution_request, time_t now); } N2NVPN::N2NVPN() { n2n_srand(n2n_seed()); } void N2NVPN::_bind_methods() { ClassDB::bind_method(D_METHOD("get_ip"), &N2NVPN::get_ip); ClassDB::bind_method(D_METHOD("get_mac"), &N2NVPN::get_mac); ClassDB::bind_method(D_METHOD("get_peers"), &N2NVPN::get_peers); ClassDB::bind_method(D_METHOD("reset_configuration"), &N2NVPN::reset_configuration); ClassDB::bind_method(D_METHOD("set_addresses", "ip_address", "mac_address"), &N2NVPN::set_addresses); ClassDB::bind_method(D_METHOD("set_compression", "enabled"), &N2NVPN::set_compression); ClassDB::bind_method(D_METHOD("set_network", "user_name", "network_name", "network_password"), &N2NVPN::set_network); ClassDB::bind_method(D_METHOD("set_supernode", "address", "port"), &N2NVPN::set_supernode); ClassDB::bind_method(D_METHOD("start_network"), &N2NVPN::start_network); ClassDB::bind_method(D_METHOD("stop_network"), &N2NVPN::stop_network); } void N2NVPN::_edge_thread_function() { print_line("Entering run_edge_loop()"); run_edge_loop(_eee); // Blocks until _keep_running == false print_line("Exited run_edge_loop()"); edge_term_conf(&_eee->conf); print_line("Closing tuntap"); tuntap_close(&_eee->device); print_line("Terminating edge"); edge_term(_eee); print_line("Exiting thread"); } String N2NVPN::get_ip() { String result(_eee->tuntap_priv_conf.ip_addr); return result; } String N2NVPN::get_mac() { String result(_eee->tuntap_priv_conf.device_mac); return result; } Array N2NVPN::get_peers() { in_addr_t net; peer_info_t *peer; peer_info_t *tempPeer; Array results; int x; HASH_ITER(hh, _eee->pending_peers, peer, tempPeer) { if (peer->dev_addr.net_addr != 0) { net = htonl(peer->dev_addr.net_addr); Dictionary entry; entry[Variant("type")] = Variant("pending"); entry[Variant("ip")] = Variant(inet_ntoa(*(struct in_addr *)&net)); results.append(entry); } } HASH_ITER(hh, _eee->known_peers, peer, tempPeer) { if (peer->dev_addr.net_addr != 0) { net = htonl(peer->dev_addr.net_addr); Dictionary entry; entry[Variant("type")] = Variant("known"); entry[Variant("ip")] = Variant(inet_ntoa(*(struct in_addr *)&net)); results.append(entry); } } return results; } void N2NVPN::reset_configuration() { edge_init_conf_defaults(&_conf); _conf.allow_p2p = 1; // Whether to allow peer-to-peer communication _conf.allow_routing = 1; // Whether to allow the edge to route packets to other edges _conf.disable_pmtu_discovery = 1; // Whether to disable the path MTU discovery _conf.drop_multicast = 0; // Whether to disable multicast _conf.tuntap_ip_mode = TUNTAP_IP_MODE_SN_ASSIGN; // How to set the IP address _conf.local_port = 0; // What port to use (0 = any port) _conf.mgmt_port = N2N_EDGE_MGMT_PORT; // Edge management port (5644 by default) _conf.register_interval = 1; // Interval for both UDP NAT hole punching and supernode registration _conf.register_ttl = 1; // Interval for UDP NAT hole punching through supernode _conf.tos = 16; // Type of service for sent packets _conf.transop_id = N2N_TRANSFORM_ID_TWOFISH; // Use the twofish encryption _conf.compression = N2N_COMPRESSION_ID_ZSTD; _ip_address = ""; // Empty addresses will cause them to be generated / acquired. _mac_address = ""; } void N2NVPN::set_addresses(const String ip, const String mac) { _ip_address = ip; _mac_address = mac; } void N2NVPN::set_compression(bool enabled) { _conf.compression = (enabled ? N2N_COMPRESSION_ID_ZSTD : N2N_COMPRESSION_ID_NONE); } void N2NVPN::set_network(const String user_name, const String network_name, const String network_password) { _user_name = user_name; _network_password = network_password; snprintf((char *)_conf.community_name, sizeof(_conf.community_name), "%s", network_name.ascii().get_data()); // Community to connect to _conf.encrypt_key = (char *)strdup(_network_password.ascii().get_data()); // Secret to decrypt & encrypt with } void N2NVPN::set_supernode(const String address, int port) { String supernode = address + ":" + String::num_int64((int64_t)port); edge_conf_add_supernode(&_conf, supernode.ascii().get_data()); } int N2NVPN::start_network() { int rc; tuntap_dev tuntap; n2n_tuntap_priv_config_t ec; int runlevel; time_t now; time_t last_action; fd_set socket_mask; struct timeval wait_time; int seek_answer; uint16_t expected; uint16_t position; unsigned char pktbuf[N2N_SN_PKTBUF_SIZE + sizeof(uint16_t)]; peer_info_t *scan; peer_info_t *scan_tmp; if (edge_verify_conf(&_conf) != 0) { return -1; } _eee = edge_init(&_conf, &rc); if (_eee == nullptr) { return -3; } memset(&ec, 0, sizeof(ec)); ec.mtu = DEFAULT_MTU; #ifndef _WIN32 snprintf(ec.tuntap_dev_name, sizeof(ec.tuntap_dev_name), N2N_EDGE_DEFAULT_DEV_NAME); #endif snprintf(ec.netmask, sizeof(ec.netmask), N2N_EDGE_DEFAULT_NETMASK); // This doesn't handle actual DHCP. if (_ip_address == "") { // Automatic. _eee->conf.tuntap_ip_mode = TUNTAP_IP_MODE_SN_ASSIGN; } else { // Static _eee->conf.tuntap_ip_mode = TUNTAP_IP_MODE_STATIC; snprintf((char *)&ec.ip_mode, N2N_IF_MODE_SIZE, "static"); snprintf((char *)&ec.ip_addr, N2N_NETMASK_STR_SIZE, "%s", _ip_address.ascii().get_data()); } memcpy(&(_eee->tuntap_priv_conf), &ec, sizeof(ec)); expected = sizeof(uint16_t); position = 0; runlevel = 0; seek_answer = 1; _eee->last_sup = 0; _eee->curr_sn = _eee->conf.supernodes; supernode_connect(_eee); print_line("Runlevel 0"); while (runlevel < 5) { now = time(nullptr); // we do not use switch-case because we also check for 'greater than' if (runlevel == 0) { /* PING to all known supernodes */ last_action = now; _eee->sn_pong = 0; // (re-)initialize the number of max concurrent pings (decreases by calling send_query_peer) _eee->conf.number_max_sn_pings = NUMBER_SN_PINGS_INITIAL; send_query_peer(_eee, null_mac); runlevel++; print_line("Runlevel", runlevel); } if (runlevel == 1) { /* PING has been sent to all known supernodes */ if (_eee->sn_pong) { // first answer _eee->sn_pong = 0; sn_selection_sort(&(_eee->conf.supernodes)); _eee->curr_sn = _eee->conf.supernodes; supernode_connect(_eee); runlevel++; print_line("Runlevel", runlevel); } else if (last_action <= (now - BOOTSTRAP_TIMEOUT)) { // timeout runlevel--; print_line("Runlevel", runlevel); // skip waiting for answer to directly go to send PING again seek_answer = 0; } } // by the way, have every later PONG cause the remaining (!) list to be sorted because the entries // before have already been tried; as opposed to initial PONG, do not change curr_sn if (runlevel > 1) { if (_eee->sn_pong) { _eee->sn_pong = 0; if (_eee->curr_sn->hh.next) { sn_selection_sort((peer_info_t**)&(_eee->curr_sn->hh.next)); // here, it is hard to determine from which one, so no details to output } } } if (runlevel == 2) { /* send REGISTER_SUPER to get auto ip address from a supernode */ if (_eee->conf.tuntap_ip_mode == TUNTAP_IP_MODE_SN_ASSIGN) { last_action = now; _eee->sn_wait = 1; send_register_super(_eee); runlevel++; print_line("Runlevel", runlevel); } else { runlevel += 2; /* skip waiting for TUNTAP IP address */ print_line("Runlevel", runlevel); } } if (runlevel == 3) { /* REGISTER_SUPER to get auto ip address from a sn has been sent */ if (!_eee->sn_wait) { /* TUNTAP IP address received */ runlevel++; print_line("Runlevel", runlevel); // it should be from curr_sn, but we can't determine definitely here, so no details to output } else if (last_action <= (now - BOOTSTRAP_TIMEOUT)) { // timeout, so try next supernode if (_eee->curr_sn->hh.next) _eee->curr_sn = (peer_info *)_eee->curr_sn->hh.next; else _eee->curr_sn = _eee->conf.supernodes; supernode_connect(_eee); runlevel--; print_line("Runlevel", runlevel); // skip waiting for answer to direcly go to send REGISTER_SUPER again seek_answer = 0; } } if (runlevel == 4) { /* configure the TUNTAP device, including routes */ print_line("ec:"); print_line("dev_name:",ec.tuntap_dev_name); print_line(" ip_mode:",ec.ip_mode ); print_line(" netmask:",ec.netmask ); print_line(" ip_addr:",ec.ip_addr ); print_line(" mac:",ec.device_mac ); print_line(" mtu:",ec.mtu ); print_line(" metric:",ec.metric ); print_line("_eee->tuntap_priv_conf:"); print_line("dev_name:",_eee->tuntap_priv_conf.tuntap_dev_name); print_line(" ip_mode:",_eee->tuntap_priv_conf.ip_mode ); print_line(" netmask:",_eee->tuntap_priv_conf.netmask ); print_line(" ip_addr:",_eee->tuntap_priv_conf.ip_addr ); print_line(" mac:",_eee->tuntap_priv_conf.device_mac ); print_line(" mtu:",_eee->tuntap_priv_conf.mtu ); print_line(" metric:",_eee->tuntap_priv_conf.metric ); if (tuntap_open(&tuntap, _eee->tuntap_priv_conf.tuntap_dev_name, _eee->tuntap_priv_conf.ip_mode, _eee->tuntap_priv_conf.ip_addr, _eee->tuntap_priv_conf.netmask, _eee->tuntap_priv_conf.device_mac, _eee->tuntap_priv_conf.mtu, _eee->tuntap_priv_conf.metric) < 0) return -2; memcpy(&_eee->device, &tuntap, sizeof(tuntap)); runlevel = 5; print_line("Runlevel",runlevel); // no more answers required seek_answer = 0; } // we usually wait for some answer, there however are exceptions when going back to a previous runlevel if (seek_answer) { FD_ZERO(&socket_mask); FD_SET(_eee->sock, &socket_mask); wait_time.tv_sec = BOOTSTRAP_TIMEOUT; wait_time.tv_usec = 0; if (select(_eee->sock + 1, &socket_mask, nullptr, nullptr, &wait_time) > 0) { if (FD_ISSET(_eee->sock, &socket_mask)) { fetch_and_eventually_process_data(_eee, _eee->sock, pktbuf, &expected, &position, now); } } } seek_answer = 1; resolve_check(_eee->resolve_parameter, 0 /* no intermediate resolution requirement at this point */, now); } // allow a higher number of pings for first regular round of ping // to quicker get an inital 'supernode selection criterion overview' _eee->conf.number_max_sn_pings = NUMBER_SN_PINGS_INITIAL; // shape supernode list; make current one the first on the list HASH_ITER(hh, _eee->conf.supernodes, scan, scan_tmp) { if (scan == _eee->curr_sn) sn_selection_criterion_good(&(scan->selection_criterion)); else sn_selection_criterion_default(&(scan->selection_criterion)); } sn_selection_sort(&(_eee->conf.supernodes)); // do not immediately ping again, allow some time _eee->last_sweep = now - SWEEP_TIME + 2 * BOOTSTRAP_TIMEOUT; _eee->sn_wait = 1; _eee->last_register_req = 0; _keep_running = true; _eee->keep_running = &_keep_running; _edge_thread = new std::thread(&N2NVPN::_edge_thread_function, this); return 0; } int N2NVPN::stop_network() { print_line("Stopping thread"); _keep_running = false; print_line("Joining thread"); _edge_thread->join(); print_line("Joined"); return 0; }