436 lines
14 KiB
C++
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;
|
|
}
|