hamncheese/modules/n2nvpn/n2nvpn.cpp
2023-09-14 16:25:45 -05:00

436 lines
14 KiB
C++

#include <ctime>
#include <sys/time.h>
#include "n2nvpn.h"
extern "C" {
#include "stddclmr.h"
#include "random_numbers.h"
#include "sn_selection.h"
#include "pearson.h"
#include "uthash.h"
#ifdef _WIN32
#include "win32/defs.h"
#else
#include <arpa/inet.h> // for inet_ntoa
#include <netinet/in.h> // 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_binding", "ip_address", "port"), &N2NVPN::set_binding);
ClassDB::bind_method(D_METHOD("set_compression", "enabled"), &N2NVPN::set_compression);
ClassDB::bind_method(D_METHOD("set_management", "password", "port"), &N2NVPN::set_management);
ClassDB::bind_method(D_METHOD("set_network", "user_name", "network_name", "network_password"), &N2NVPN::set_network);
ClassDB::bind_method(D_METHOD("set_registration", "interval", "ttl"), &N2NVPN::set_registration);
ClassDB::bind_method(D_METHOD("set_routing", "enabled"), &N2NVPN::set_routing);
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;
peer_info_t *peerList;
Array results;
int x;
macstr_t macBuf;
char *typeName;
char typeKnown[] = "known";
char typePending[] = "pending";
// First pass, use these values:
peerList = _eee->pending_peers;
typeName = typePending;
// Run two passes, each over a different peer list.
for (x=0; x<2; x++) {
HASH_ITER(hh, peerList, peer, tempPeer) {
if (peer->dev_addr.net_addr != 0) {
net = htonl(peer->dev_addr.net_addr);
Dictionary entry;
entry[Variant("mac_addr")] = Variant(macaddr_str(macBuf, peer->mac_addr));
// dev_addr
// dev_desc
// sock
// socket_fd
// preferred_sock
// last_cookie
// auth
entry[Variant("timeout")] = Variant(peer->timeout);
entry[Variant("purgeable")] = Variant(peer->purgeable);
entry[Variant("last_seen")] = Variant(peer->last_seen);
entry[Variant("last_p2p")] = Variant(peer->last_p2p);
entry[Variant("last_sent_query")] = Variant(peer->last_sent_query);
// selection_criterion
entry[Variant("last_valid_time_stamp")] = Variant(peer->last_valid_time_stamp);
// ip_addr
entry[Variant("local")] = Variant(peer->local);
entry[Variant("uptime")] = Variant(peer->uptime);
// version
entry[Variant("type")] = Variant(typeName);
entry[Variant("ip")] = Variant(inet_ntoa(*(struct in_addr *) &net));
results.append(entry);
}
}
// Second pass, use these values:
peerList = _eee->known_peers;
typeName = typeKnown;
}
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 = INADDR_ANY; // 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_AES; // Use AES encryption
// _conf.network_traffic_filter_rules
_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_binding(String ip, int16_t port) {
if (ip != "") {
_conf.bind_address = ntohl(inet_addr(ip.ascii().get_data()));
} else {
_conf.bind_address = INADDR_ANY;
}
_conf.local_port = port; // Local listen port, or 0 for any.
}
void N2NVPN::set_compression(bool enabled) {
_conf.compression = (enabled ? N2N_COMPRESSION_ID_ZSTD : N2N_COMPRESSION_ID_NONE);
}
void N2NVPN::set_management(String password, int16_t port) {
if (password != "") {
_conf.mgmt_password_hash = pearson_hash_64((uint8_t *)password.ascii().get_data(), strlen(password.ascii().get_data()));
}
_conf.mgmt_port = port;
}
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_registration(int16_t interval, int16_t ttl) {
_conf.register_interval = interval;
_conf.register_ttl = ttl;
}
void N2NVPN::set_routing(bool enabled) {
_conf.allow_routing = enabled ? 1 : 0;
}
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;
}