From 087d8a820cf9aa57d7c6a028c1dadecbe4d7fed6 Mon Sep 17 00:00:00 2001 From: heri16 <527101+heri16@users.noreply.github.com> Date: Tue, 4 Aug 2020 05:06:59 +0800 Subject: [PATCH 1/7] first commit of node example --- examples/node/auto-top.gypi | 8 + examples/node/auto.gypi | 11 + examples/node/autogypi.json | 6 + examples/node/binding.cc | 424 ++++++++++++++++++++++++++++++++++++ examples/node/binding.gyp | 22 ++ examples/node/package.json | 21 ++ 6 files changed, 492 insertions(+) create mode 100644 examples/node/auto-top.gypi create mode 100644 examples/node/auto.gypi create mode 100644 examples/node/autogypi.json create mode 100644 examples/node/binding.cc create mode 100644 examples/node/binding.gyp create mode 100644 examples/node/package.json diff --git a/examples/node/auto-top.gypi b/examples/node/auto-top.gypi new file mode 100644 index 00000000..7671b8cf --- /dev/null +++ b/examples/node/auto-top.gypi @@ -0,0 +1,8 @@ +# Automatically generated file. Edits will be lost. +# Based on: autogypi.json + +{ + "includes": [ + "node_modules/nbind/src/nbind-common.gypi" + ] +} diff --git a/examples/node/auto.gypi b/examples/node/auto.gypi new file mode 100644 index 00000000..49d2b0ba --- /dev/null +++ b/examples/node/auto.gypi @@ -0,0 +1,11 @@ +# Automatically generated file. Edits will be lost. +# Based on: autogypi.json + +{ + "include_dirs": [ + "node_modules/nan" + ], + "includes": [ + "node_modules/nbind/src/nbind.gypi" + ] +} diff --git a/examples/node/autogypi.json b/examples/node/autogypi.json new file mode 100644 index 00000000..a62cdbda --- /dev/null +++ b/examples/node/autogypi.json @@ -0,0 +1,6 @@ +{ + "dependencies": [ + "nbind" + ], + "includes": [] +} diff --git a/examples/node/binding.cc b/examples/node/binding.cc new file mode 100644 index 00000000..4dcf7280 --- /dev/null +++ b/examples/node/binding.cc @@ -0,0 +1,424 @@ +/** + * libzt binding + */ + +#include +#include +#include +#include +#include + +#include "ZeroTierSockets.h" +#include "nbind/api.h" + +/** + * + * IDENTITIES and AUTHORIZATION: + * + * - Upon the first execution of this code, a new identity will be generated and placed in + * the location given in the first argument to zts_start(path, ...). If you accidentally + * duplicate the identity files and use them simultaneously in a different node instance + * you will experience undefined behavior and it is likely nothing will work. + * + * - You must authorize the node ID provided by the ZTS_EVENT_NODE_ONLINE callback to join + * your network, otherwise nothing will happen. This can be done manually or via + * our web API: https://my.zerotier.com/help/api + * + * - Exceptions to the above rule are: + * 1) Joining a public network (such as "earth") + * 2) Joining an Ad-hoc network, (no controller and therefore requires no authorization.) + * + * + * ESTABLISHING A CONNECTION: + * + * - Creating a standard socket connection generally works the same as it would using + * an ordinary socket interface, however with libzt there is a subtle difference in + * how connections are established which may cause confusion: + * + * The underlying virtual ZT layer creates what are called "transport-triggered links" + * between nodes. That is, links are not established until an attempt to communicate + * with a peer has taken place. The side effect is that the first few packets sent from + * a libzt instance are usually relayed via our free infrastructure and it isn't until a + * root server has passed contact information to both peers that a direct connection will be + * established. Therefore, it is required that multiple connection attempts be undertaken + * when initially communicating with a peer. After a transport-triggered link is + * established libzt will inform you via ZTS_EVENT_PEER_DIRECT for a specific peer ID. No + * action is required on your part for this callback event. + * + * Note: In these initial moments before ZTS_EVENT_PEER_DIRECT has been received for a + * specific peer, traffic may be slow, jittery and there may be high packet loss. + * This will subside within a couple of seconds. + * + * + * ERROR HANDLING: + * + * - libzt's API is actually composed of two categories of functions with slightly + * different error reporting mechanisms. + * + * Category 1: Control functions (zts_start, zts_join, zts_get_peer_status, etc). Errors + * returned by these functions can be any of the following: + * + * ZTS_ERR_OK // No error + * ZTS_ERR_SOCKET // Socket error, see zts_errno + * ZTS_ERR_SERVICE // You probably did something at the wrong time + * ZTS_ERR_ARG // Invalid argument + * ZTS_ERR_NO_RESULT // No result (not necessarily an error) + * ZTS_ERR_GENERAL // Consider filing a bug report + * + * Category 2: Sockets (zts_socket, zts_bind, zts_connect, zts_listen, etc). + * Errors returned by these functions can be the same as the above. With + * the added possibility of zts_errno being set. Much like standard + * errno this will provide a more specific reason for an error's occurrence. + * See ZeroTierSockets.h for values. + * + * + * API COMPATIBILITY WITH HOST OS: + * + * - While the ZeroTier socket interface can coexist with your host OS's own interface in + * the same file with no type and naming conflicts, try not to mix and match host + * OS/libzt structures, functions, or constants. It may look similar and may even work + * some of the time but there enough differences that it will cause headaches. Here + * are a few guidelines: + * + * If you are calling a zts_* function, use the appropriate ZTS_* constants: + * + * zts_socket(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0); (CORRECT) + * zts_socket(AF_INET6, SOCK_DGRAM, 0); (INCORRECT) + * + * If you are calling a zts_* function, use the appropriate zts_* structure: + * + * struct zts_sockaddr_in in4; <------ Note the zts_* prefix + * ... + * zts_bind(fd, (struct zts_sockaddr *)&in4, sizeof(struct zts_sockaddr_in)) < 0) + * + */ +class ZeroTier +{ +public: + /** + * @brief Starts the ZeroTier service and notifies user application of events via callback + * + * @param path path directory where configuration files are stored + * @param callback User-specified callback for ZTS_EVENT_* events + * @return ZTS_ERR_OK on success. ZTS_ERR_SERVICE or ZTS_ERR_ARG on failure + */ + static int start(const char *configFilePath, uint16_t servicePort) + { + // Bring up ZeroTier service + + int err = ZTS_ERR_OK; + + if((err = zts_start(configFilePath, &myZeroTierEventCallback, servicePort)) != ZTS_ERR_OK) { + printf("Unable to start service, error = %d.\n", err); + return err; + } + printf("Waiting for node to come online...\n"); + while (!myNode.online) { zts_delay_ms(50); } + + return err; + } + + /** + * @brief Stops the ZeroTier service and brings down all virtual network interfaces + * + * @usage While the ZeroTier service will stop, the stack driver (with associated timers) + * will remain active in case future traffic processing is required. To stop all activity + * and free all resources use zts_free() instead. + * @return ZTS_ERR_OK on success. ZTS_ERR_SERVICE on failure. + */ + static int stop() + { + return zts_stop(); + } + + /** + * @brief Restart the ZeroTier service. + * + * @usage This call will block until the service has been brought offline. Then + * it will return and the user application can then watch for the appropriate + * startup callback events. + * @return ZTS_ERR_OK on success. ZTS_ERR_SERVICE on failure. + */ + static int restart() + { + return zts_restart(); + } + + /** + * @brief Stop all background services, bring down all interfaces, free all resources. After + * calling this function an application restart will be required before the library can be + * used again. + * + * @usage This should be called at the end of your program or when you do not anticipate + * communicating over ZeroTier + * @return ZTS_ERR_OK on success. ZTS_ERR_SERVICE on failure. + */ + static int free() + { + return zts_free(); + } + + static int join(const char *networkId) + { + // Join ZeroTier network + + uint64_t nwid = strtoull(networkId,NULL,16); // Network ID to join + + int err = ZTS_ERR_OK; + + if((err = zts_join(nwid)) != ZTS_ERR_OK) { + printf("Unable to join network, error = %d.\n", err); + return err; + } + printf("Joining network %llx\n", nwid); + printf("Don't forget to authorize this device in my.zerotier.com or the web API!\n"); + while (!myNode.joinedAtLeastOneNetwork) { zts_delay_ms(50); } + + return err; + } + + static int connectStream(const char *remoteAddr, const int remotePort) + { + return connect(ZTS_AF_INET, ZTS_SOCK_STREAM, 0, remoteAddr, remotePort); + } + + static int connectDgram(const char *remoteAddr, const int remotePort) + { + return connect(ZTS_AF_INET, ZTS_SOCK_DGRAM, 0, remoteAddr, remotePort); + } + + static int connectRaw(const char *remoteAddr, const int remotePort, const int protocol) + { + return connect(ZTS_AF_INET, ZTS_SOCK_RAW, protocol, remoteAddr, remotePort); + } + + static int connectStream6(const char *remoteAddr, const int remotePort) + { + return connect(ZTS_AF_INET6, ZTS_SOCK_STREAM, 0, remoteAddr, remotePort); + } + + static int connectDgram6(const char *remoteAddr, const int remotePort) + { + return connect(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0, remoteAddr, remotePort); + } + + static int connectRaw6(const char *remoteAddr, const int remotePort, const int protocol) + { + return connect(ZTS_AF_INET6, ZTS_SOCK_RAW, protocol, remoteAddr, remotePort); + } + + static int connect(const int socket_family, const int socket_type, const int protocol, const char *remoteAddr, const int remotePort) + { + const struct zts_sockaddr *addr; + zts_socklen_t addrlen; + + if (socket_family == ZTS_AF_INET) { + struct zts_sockaddr_in in4 = sockaddr_in(remoteAddr, remotePort); + addr = (const struct zts_sockaddr *)&in4; + addrlen = sizeof(in4); + } else { + printf("IPv6 NOT IMPLEMENTED.\n"); + return -1; + } + + int fd; + if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { + printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); + return -1; + } + + // Retries are often required since ZT uses transport-triggered links (explained above) + int err = ZTS_ERR_OK; + for (;;) { + printf("Connecting to remote host...\n"); + if ((err = zts_connect(fd, addr, addrlen)) < 0) { + printf("Error connecting to remote host (fd=%d, ret=%d, zts_errno=%d). Trying again.\n", + fd, err, zts_errno); + zts_close(fd); + printf("Creating socket...\n"); + if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { + printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); + return -1; + } + zts_delay_ms(250); + } + else { + printf("Connected.\n"); + break; + } + } + + return fd; + } + + static ssize_t read(int fd, nbind::Buffer buf) + { + return zts_read(fd, buf.data(), buf.length()); + } + + static ssize_t write(int fd, nbind::Buffer buf) + { + return zts_write(fd, buf.data(), buf.length()); + } + + static ssize_t recv(int fd, nbind::Buffer buf, int flags) + { + return zts_recv(fd, buf.data(), buf.length(), flags); + } + + static ssize_t send(int fd, nbind::Buffer buf, int flags) + { + return zts_send(fd, buf.data(), buf.length(), flags); + } + + static int close(int fd) + { + return zts_close(fd); + } + + static zts_sockaddr_in sockaddr_in(const char *remoteAddr, const int remotePort) + { + struct zts_sockaddr_in in4; + in4.sin_port = zts_htons(remotePort); + #if defined(_WIN32) + in4.sin_addr.S_addr = zts_inet_addr(remoteAddr); + #else + in4.sin_addr.s_addr = zts_inet_addr(remoteAddr); + #endif + in4.sin_family = ZTS_AF_INET; + return in4; + } + +private: + static struct Node + { + Node() : online(false), joinedAtLeastOneNetwork(false), id(0) {} + bool online; + bool joinedAtLeastOneNetwork; + uint64_t id; + // etc + } myNode; + + /* Callback handler, you should return control from this function as quickly as you can + to ensure timely receipt of future events. You should not call libzt API functions from + this function unless it's something trivial like zts_inet_ntop() or similar that has + no state-change implications. */ + static void myZeroTierEventCallback(void *msgPtr) + { + struct zts_callback_msg *msg = (struct zts_callback_msg *)msgPtr; + + // Node events + if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { + printf("ZTS_EVENT_NODE_ONLINE --- This node's ID is %llx\n", msg->node->address); + myNode.id = msg->node->address; + myNode.online = true; + } + if (msg->eventCode == ZTS_EVENT_NODE_OFFLINE) { + printf("ZTS_EVENT_NODE_OFFLINE --- Check your physical Internet connection, router, firewall, etc. What ports are you blocking?\n"); + myNode.online = false; + } + if (msg->eventCode == ZTS_EVENT_NODE_NORMAL_TERMINATION) { + printf("ZTS_EVENT_NODE_NORMAL_TERMINATION\n"); + } + + // Virtual network events + if (msg->eventCode == ZTS_EVENT_NETWORK_NOT_FOUND) { + printf("ZTS_EVENT_NETWORK_NOT_FOUND --- Are you sure %llx is a valid network?\n", + msg->network->nwid); + } + if (msg->eventCode == ZTS_EVENT_NETWORK_REQ_CONFIG) { + printf("ZTS_EVENT_NETWORK_REQ_CONFIG --- Requesting config for network %llx, please wait a few seconds...\n", msg->network->nwid); + } + if (msg->eventCode == ZTS_EVENT_NETWORK_ACCESS_DENIED) { + printf("ZTS_EVENT_NETWORK_ACCESS_DENIED --- Access to virtual network %llx has been denied. Did you authorize the node yet?\n", + msg->network->nwid); + } + if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP4) { + printf("ZTS_EVENT_NETWORK_READY_IP4 --- Network config received. IPv4 traffic can now be sent over network %llx\n", + msg->network->nwid); + myNode.joinedAtLeastOneNetwork = true; + } + if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP6) { + printf("ZTS_EVENT_NETWORK_READY_IP6 --- Network config received. IPv6 traffic can now be sent over network %llx\n", + msg->network->nwid); + myNode.joinedAtLeastOneNetwork = true; + } + if (msg->eventCode == ZTS_EVENT_NETWORK_DOWN) { + printf("ZTS_EVENT_NETWORK_DOWN --- %llx\n", msg->network->nwid); + } + + // Address events + if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP4) { + char ipstr[ZTS_INET_ADDRSTRLEN]; + struct zts_sockaddr_in *in4 = (struct zts_sockaddr_in*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_NEW_IP4 --- This node's virtual address on network %llx is %s\n", + msg->addr->nwid, ipstr); + } + if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP6) { + char ipstr[ZTS_INET6_ADDRSTRLEN]; + struct zts_sockaddr_in6 *in6 = (struct zts_sockaddr_in6*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_NEW_IP6 --- This node's virtual address on network %llx is %s\n", + msg->addr->nwid, ipstr); + } + if (msg->eventCode == ZTS_EVENT_ADDR_REMOVED_IP4) { + char ipstr[ZTS_INET_ADDRSTRLEN]; + struct zts_sockaddr_in *in4 = (struct zts_sockaddr_in*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_REMOVED_IP4 --- The virtual address %s for this node on network %llx has been removed.\n", + ipstr, msg->addr->nwid); + } + if (msg->eventCode == ZTS_EVENT_ADDR_REMOVED_IP6) { + char ipstr[ZTS_INET6_ADDRSTRLEN]; + struct zts_sockaddr_in6 *in6 = (struct zts_sockaddr_in6*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_REMOVED_IP6 --- The virtual address %s for this node on network %llx has been removed.\n", + ipstr, msg->addr->nwid); + } + // Peer events + if (msg->peer) { + if (msg->peer->role == ZTS_PEER_ROLE_PLANET) { + /* Safe to ignore, these are our roots. They orchestrate the P2P connection. + You might also see other unknown peers, these are our network controllers. */ + return; + } + if (msg->eventCode == ZTS_EVENT_PEER_DIRECT) { + printf("ZTS_EVENT_PEER_DIRECT --- A direct path is known for node=%llx\n", + msg->peer->address); + } + if (msg->eventCode == ZTS_EVENT_PEER_RELAY) { + printf("ZTS_EVENT_PEER_RELAY --- No direct path to node=%llx\n", msg->peer->address); + } + if (msg->eventCode == ZTS_EVENT_PEER_PATH_DISCOVERED) { + printf("ZTS_EVENT_PEER_PATH_DISCOVERED --- A new direct path was discovered for node=%llx\n", + msg->peer->address); + } + if (msg->eventCode == ZTS_EVENT_PEER_PATH_DEAD) { + printf("ZTS_EVENT_PEER_PATH_DEAD --- A direct path has died for node=%llx\n", + msg->peer->address); + } + } + } +}; + +#include "nbind/nbind.h" + +NBIND_CLASS(ZeroTier) { + method(start); + method(join); + method(connectStream); + method(connectDgram); + method(connectRaw); + method(connectStream6); + method(connectDgram6); + method(connectRaw6); + method(connect); + method(read); + method(write); + method(recv); + method(send); + method(restart); + method(stop); + method(free); +} diff --git a/examples/node/binding.gyp b/examples/node/binding.gyp new file mode 100644 index 00000000..7b5eb693 --- /dev/null +++ b/examples/node/binding.gyp @@ -0,0 +1,22 @@ +{ + "targets": [ + { + "include_dirs": [ + "libzt/lib/debug/linux-x86_64", + "libzt/include" + ], + "libraries": [ + " Date: Tue, 4 Aug 2020 16:47:28 +0800 Subject: [PATCH 2/7] 1st working version of node example --- examples/node/.gitignore | 2 + examples/node/binding.cc | 336 +++++++++++++++++++++---------------- examples/node/binding.gyp | 18 +- examples/node/libzt.js | 41 +++++ examples/node/package.json | 3 +- 5 files changed, 253 insertions(+), 147 deletions(-) create mode 100644 examples/node/.gitignore create mode 100644 examples/node/libzt.js diff --git a/examples/node/.gitignore b/examples/node/.gitignore new file mode 100644 index 00000000..85342f46 --- /dev/null +++ b/examples/node/.gitignore @@ -0,0 +1,2 @@ +/.zerotier +/libzt \ No newline at end of file diff --git a/examples/node/binding.cc b/examples/node/binding.cc index 4dcf7280..cf0d2f9c 100644 --- a/examples/node/binding.cc +++ b/examples/node/binding.cc @@ -11,6 +11,122 @@ #include "ZeroTierSockets.h" #include "nbind/api.h" +struct Node +{ + Node() : online(false), joinedAtLeastOneNetwork(false), id(0) {} + + bool online; + bool joinedAtLeastOneNetwork; + uint64_t id; + // etc + + bool getOnline() { return online; } + bool getJoinedAtLeastOneNetwork() { return joinedAtLeastOneNetwork; } + uint64_t getId() { return id; } +} myNode; + +/* Callback handler, you should return control from this function as quickly as you can +to ensure timely receipt of future events. You should not call libzt API functions from +this function unless it's something trivial like zts_inet_ntop() or similar that has +no state-change implications. */ +void myZeroTierEventCallback(void *msgPtr) +{ + struct zts_callback_msg *msg = (struct zts_callback_msg *)msgPtr; + + // Node events + if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { + printf("ZTS_EVENT_NODE_ONLINE --- This node's ID is %llx\n", msg->node->address); + myNode.id = msg->node->address; + myNode.online = true; + } + if (msg->eventCode == ZTS_EVENT_NODE_OFFLINE) { + printf("ZTS_EVENT_NODE_OFFLINE --- Check your physical Internet connection, router, firewall, etc. What ports are you blocking?\n"); + myNode.online = false; + } + if (msg->eventCode == ZTS_EVENT_NODE_NORMAL_TERMINATION) { + printf("ZTS_EVENT_NODE_NORMAL_TERMINATION\n"); + } + + // Virtual network events + if (msg->eventCode == ZTS_EVENT_NETWORK_NOT_FOUND) { + printf("ZTS_EVENT_NETWORK_NOT_FOUND --- Are you sure %llx is a valid network?\n", + msg->network->nwid); + } + if (msg->eventCode == ZTS_EVENT_NETWORK_REQ_CONFIG) { + printf("ZTS_EVENT_NETWORK_REQ_CONFIG --- Requesting config for network %llx, please wait a few seconds...\n", msg->network->nwid); + } + if (msg->eventCode == ZTS_EVENT_NETWORK_ACCESS_DENIED) { + printf("ZTS_EVENT_NETWORK_ACCESS_DENIED --- Access to virtual network %llx has been denied. Did you authorize the node yet?\n", + msg->network->nwid); + } + if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP4) { + printf("ZTS_EVENT_NETWORK_READY_IP4 --- Network config received. IPv4 traffic can now be sent over network %llx\n", + msg->network->nwid); + myNode.joinedAtLeastOneNetwork = true; + } + if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP6) { + printf("ZTS_EVENT_NETWORK_READY_IP6 --- Network config received. IPv6 traffic can now be sent over network %llx\n", + msg->network->nwid); + myNode.joinedAtLeastOneNetwork = true; + } + if (msg->eventCode == ZTS_EVENT_NETWORK_DOWN) { + printf("ZTS_EVENT_NETWORK_DOWN --- %llx\n", msg->network->nwid); + } + + // Address events + if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP4) { + char ipstr[ZTS_INET_ADDRSTRLEN]; + struct zts_sockaddr_in *in4 = (struct zts_sockaddr_in*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_NEW_IP4 --- This node's virtual address on network %llx is %s\n", + msg->addr->nwid, ipstr); + } + if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP6) { + char ipstr[ZTS_INET6_ADDRSTRLEN]; + struct zts_sockaddr_in6 *in6 = (struct zts_sockaddr_in6*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_NEW_IP6 --- This node's virtual address on network %llx is %s\n", + msg->addr->nwid, ipstr); + } + if (msg->eventCode == ZTS_EVENT_ADDR_REMOVED_IP4) { + char ipstr[ZTS_INET_ADDRSTRLEN]; + struct zts_sockaddr_in *in4 = (struct zts_sockaddr_in*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_REMOVED_IP4 --- The virtual address %s for this node on network %llx has been removed.\n", + ipstr, msg->addr->nwid); + } + if (msg->eventCode == ZTS_EVENT_ADDR_REMOVED_IP6) { + char ipstr[ZTS_INET6_ADDRSTRLEN]; + struct zts_sockaddr_in6 *in6 = (struct zts_sockaddr_in6*)&(msg->addr->addr); + zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); + printf("ZTS_EVENT_ADDR_REMOVED_IP6 --- The virtual address %s for this node on network %llx has been removed.\n", + ipstr, msg->addr->nwid); + } + // Peer events + if (msg->peer) { + if (msg->peer->role == ZTS_PEER_ROLE_PLANET) { + /* Safe to ignore, these are our roots. They orchestrate the P2P connection. + You might also see other unknown peers, these are our network controllers. */ + return; + } + if (msg->eventCode == ZTS_EVENT_PEER_DIRECT) { + printf("ZTS_EVENT_PEER_DIRECT --- A direct path is known for node=%llx\n", + msg->peer->address); + } + if (msg->eventCode == ZTS_EVENT_PEER_RELAY) { + printf("ZTS_EVENT_PEER_RELAY --- No direct path to node=%llx\n", msg->peer->address); + } + if (msg->eventCode == ZTS_EVENT_PEER_PATH_DISCOVERED) { + printf("ZTS_EVENT_PEER_PATH_DISCOVERED --- A new direct path was discovered for node=%llx\n", + msg->peer->address); + } + if (msg->eventCode == ZTS_EVENT_PEER_PATH_DEAD) { + printf("ZTS_EVENT_PEER_PATH_DEAD --- A direct path has died for node=%llx\n", + msg->peer->address); + } + } +} + /** * * IDENTITIES and AUTHORIZATION: @@ -98,17 +214,17 @@ class ZeroTier /** * @brief Starts the ZeroTier service and notifies user application of events via callback * - * @param path path directory where configuration files are stored - * @param callback User-specified callback for ZTS_EVENT_* events + * @param configPath path directory where configuration files are stored + * @param servicePort proit which ZeroTier service will listen on * @return ZTS_ERR_OK on success. ZTS_ERR_SERVICE or ZTS_ERR_ARG on failure */ - static int start(const char *configFilePath, uint16_t servicePort) + static int start(const char *configPath, uint16_t servicePort) { // Bring up ZeroTier service int err = ZTS_ERR_OK; - if((err = zts_start(configFilePath, &myZeroTierEventCallback, servicePort)) != ZTS_ERR_OK) { + if((err = zts_start(configPath, &myZeroTierEventCallback, servicePort)) != ZTS_ERR_OK) { printf("Unable to start service, error = %d.\n", err); return err; } @@ -158,6 +274,12 @@ class ZeroTier return zts_free(); } + /** + * @brief Join a network + * + * @param networkId A 16-digit hexadecimal virtual network ID + * @return ZTS_ERR_OK on success. ZTS_ERR_SERVICE or ZTS_ERR_ARG on failure. + */ static int join(const char *networkId) { // Join ZeroTier network @@ -207,20 +329,25 @@ class ZeroTier return connect(ZTS_AF_INET6, ZTS_SOCK_RAW, protocol, remoteAddr, remotePort); } + /** + * @brief Connect a socket to a remote host (sets zts_errno) + * + * @param socket_family Address family (ZTS_AF_INET, ZTS_AF_INET6) + * @param socket_type Type of socket (ZTS_SOCK_STREAM, ZTS_SOCK_DGRAM, ZTS_SOCK_RAW) + * @param protocol Protocols supported on this socket + * @param remoteAddr Remote Address to connect to + * @param remotePort Remote Port to connect to + * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ static int connect(const int socket_family, const int socket_type, const int protocol, const char *remoteAddr, const int remotePort) { - const struct zts_sockaddr *addr; - zts_socklen_t addrlen; - - if (socket_family == ZTS_AF_INET) { - struct zts_sockaddr_in in4 = sockaddr_in(remoteAddr, remotePort); - addr = (const struct zts_sockaddr *)&in4; - addrlen = sizeof(in4); - } else { + if (socket_family == ZTS_AF_INET6) { printf("IPv6 NOT IMPLEMENTED.\n"); return -1; } + struct zts_sockaddr_in in4 = sockaddr_in(remoteAddr, remotePort); + int fd; if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); @@ -231,11 +358,11 @@ class ZeroTier int err = ZTS_ERR_OK; for (;;) { printf("Connecting to remote host...\n"); - if ((err = zts_connect(fd, addr, addrlen)) < 0) { + if ((err = zts_connect(fd, (const struct zts_sockaddr *)&in4, sizeof(in4))) < 0) { printf("Error connecting to remote host (fd=%d, ret=%d, zts_errno=%d). Trying again.\n", fd, err, zts_errno); zts_close(fd); - printf("Creating socket...\n"); + // printf("Creating socket...\n"); if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); return -1; @@ -248,9 +375,36 @@ class ZeroTier } } + // Set non-blocking mode + fcntl(fd, ZTS_F_SETFL, ZTS_O_NONBLOCK); + return fd; } + static int fcntlSetBlocking(int fd, bool isBlocking) + { + int flags = fcntl(fd, ZTS_F_GETFL, 0); + if (isBlocking) { + flags &= ~ZTS_O_NONBLOCK; + } else { + flags &= ZTS_O_NONBLOCK; + } + return fcntl(fd, ZTS_F_SETFL, flags); + } + + /** + * @brief Issue file control commands on a socket + * + * @param fd File descriptor + * @param cmd + * @param flags + * @return + */ + static int fcntl(int fd, int cmd, int flags) + { + return zts_fcntl(fd, cmd, flags); + } + static ssize_t read(int fd, nbind::Buffer buf) { return zts_read(fd, buf.data(), buf.length()); @@ -289,136 +443,36 @@ class ZeroTier return in4; } -private: - static struct Node - { - Node() : online(false), joinedAtLeastOneNetwork(false), id(0) {} - bool online; - bool joinedAtLeastOneNetwork; - uint64_t id; - // etc - } myNode; - - /* Callback handler, you should return control from this function as quickly as you can - to ensure timely receipt of future events. You should not call libzt API functions from - this function unless it's something trivial like zts_inet_ntop() or similar that has - no state-change implications. */ - static void myZeroTierEventCallback(void *msgPtr) - { - struct zts_callback_msg *msg = (struct zts_callback_msg *)msgPtr; - - // Node events - if (msg->eventCode == ZTS_EVENT_NODE_ONLINE) { - printf("ZTS_EVENT_NODE_ONLINE --- This node's ID is %llx\n", msg->node->address); - myNode.id = msg->node->address; - myNode.online = true; - } - if (msg->eventCode == ZTS_EVENT_NODE_OFFLINE) { - printf("ZTS_EVENT_NODE_OFFLINE --- Check your physical Internet connection, router, firewall, etc. What ports are you blocking?\n"); - myNode.online = false; - } - if (msg->eventCode == ZTS_EVENT_NODE_NORMAL_TERMINATION) { - printf("ZTS_EVENT_NODE_NORMAL_TERMINATION\n"); - } - - // Virtual network events - if (msg->eventCode == ZTS_EVENT_NETWORK_NOT_FOUND) { - printf("ZTS_EVENT_NETWORK_NOT_FOUND --- Are you sure %llx is a valid network?\n", - msg->network->nwid); - } - if (msg->eventCode == ZTS_EVENT_NETWORK_REQ_CONFIG) { - printf("ZTS_EVENT_NETWORK_REQ_CONFIG --- Requesting config for network %llx, please wait a few seconds...\n", msg->network->nwid); - } - if (msg->eventCode == ZTS_EVENT_NETWORK_ACCESS_DENIED) { - printf("ZTS_EVENT_NETWORK_ACCESS_DENIED --- Access to virtual network %llx has been denied. Did you authorize the node yet?\n", - msg->network->nwid); - } - if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP4) { - printf("ZTS_EVENT_NETWORK_READY_IP4 --- Network config received. IPv4 traffic can now be sent over network %llx\n", - msg->network->nwid); - myNode.joinedAtLeastOneNetwork = true; - } - if (msg->eventCode == ZTS_EVENT_NETWORK_READY_IP6) { - printf("ZTS_EVENT_NETWORK_READY_IP6 --- Network config received. IPv6 traffic can now be sent over network %llx\n", - msg->network->nwid); - myNode.joinedAtLeastOneNetwork = true; - } - if (msg->eventCode == ZTS_EVENT_NETWORK_DOWN) { - printf("ZTS_EVENT_NETWORK_DOWN --- %llx\n", msg->network->nwid); - } - - // Address events - if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP4) { - char ipstr[ZTS_INET_ADDRSTRLEN]; - struct zts_sockaddr_in *in4 = (struct zts_sockaddr_in*)&(msg->addr->addr); - zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); - printf("ZTS_EVENT_ADDR_NEW_IP4 --- This node's virtual address on network %llx is %s\n", - msg->addr->nwid, ipstr); - } - if (msg->eventCode == ZTS_EVENT_ADDR_ADDED_IP6) { - char ipstr[ZTS_INET6_ADDRSTRLEN]; - struct zts_sockaddr_in6 *in6 = (struct zts_sockaddr_in6*)&(msg->addr->addr); - zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); - printf("ZTS_EVENT_ADDR_NEW_IP6 --- This node's virtual address on network %llx is %s\n", - msg->addr->nwid, ipstr); - } - if (msg->eventCode == ZTS_EVENT_ADDR_REMOVED_IP4) { - char ipstr[ZTS_INET_ADDRSTRLEN]; - struct zts_sockaddr_in *in4 = (struct zts_sockaddr_in*)&(msg->addr->addr); - zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); - printf("ZTS_EVENT_ADDR_REMOVED_IP4 --- The virtual address %s for this node on network %llx has been removed.\n", - ipstr, msg->addr->nwid); - } - if (msg->eventCode == ZTS_EVENT_ADDR_REMOVED_IP6) { - char ipstr[ZTS_INET6_ADDRSTRLEN]; - struct zts_sockaddr_in6 *in6 = (struct zts_sockaddr_in6*)&(msg->addr->addr); - zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); - printf("ZTS_EVENT_ADDR_REMOVED_IP6 --- The virtual address %s for this node on network %llx has been removed.\n", - ipstr, msg->addr->nwid); - } - // Peer events - if (msg->peer) { - if (msg->peer->role == ZTS_PEER_ROLE_PLANET) { - /* Safe to ignore, these are our roots. They orchestrate the P2P connection. - You might also see other unknown peers, these are our network controllers. */ - return; - } - if (msg->eventCode == ZTS_EVENT_PEER_DIRECT) { - printf("ZTS_EVENT_PEER_DIRECT --- A direct path is known for node=%llx\n", - msg->peer->address); - } - if (msg->eventCode == ZTS_EVENT_PEER_RELAY) { - printf("ZTS_EVENT_PEER_RELAY --- No direct path to node=%llx\n", msg->peer->address); - } - if (msg->eventCode == ZTS_EVENT_PEER_PATH_DISCOVERED) { - printf("ZTS_EVENT_PEER_PATH_DISCOVERED --- A new direct path was discovered for node=%llx\n", - msg->peer->address); - } - if (msg->eventCode == ZTS_EVENT_PEER_PATH_DEAD) { - printf("ZTS_EVENT_PEER_PATH_DEAD --- A direct path has died for node=%llx\n", - msg->peer->address); - } - } - } + static Node getMyNode() { return myNode; } }; #include "nbind/nbind.h" +NBIND_CLASS(Node) { + getter(getOnline); + getter(getJoinedAtLeastOneNetwork); + getter(getId); +} + NBIND_CLASS(ZeroTier) { - method(start); - method(join); - method(connectStream); - method(connectDgram); - method(connectRaw); - method(connectStream6); - method(connectDgram6); - method(connectRaw6); - method(connect); - method(read); - method(write); - method(recv); - method(send); - method(restart); - method(stop); - method(free); + method(start); + method(join); + method(connectStream); + method(connectDgram); + method(connectRaw); + method(connectStream6); + method(connectDgram6); + method(connectRaw6); + method(connect); + method(read); + method(write); + method(recv); + method(send); + method(fcntlSetBlocking); + method(fcntl); + method(close); + method(restart); + method(stop); + method(free); + method(getMyNode); } diff --git a/examples/node/binding.gyp b/examples/node/binding.gyp index 7b5eb693..5aa3e1dc 100644 --- a/examples/node/binding.gyp +++ b/examples/node/binding.gyp @@ -2,17 +2,25 @@ "targets": [ { "include_dirs": [ - "libzt/lib/debug/linux-x86_64", - "libzt/include" - ], - "libraries": [ - " process.stderr.write('.'), 100) + +// Receive some data +const _read = () => { + const buf = Buffer.alloc(32) + let bytes = -1 + do { + bytes = ZeroTier.recv(fd, buf, 0) + if (bytes > 0) { process.stdout.write(buf.toString('utf8')) } + } while (bytes > 0); + + if (!ZeroTier.getMyNode().online || buf.toString('utf8').includes("exit")) { + // Close the socket + ZeroTier.close(fd) + // Stop ZeroTier service + ZeroTier.stop() + // Clear the interval + clearInterval(heartbeat) + } else { + setTimeout(_read, 500) + } +} +_read() + diff --git a/examples/node/package.json b/examples/node/package.json index e64dc76e..74aa392f 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "preinstall": "mkdir -p libzt; cd libzt; ln -sf ../../../include include; ln -sf ../../../lib lib", + "preinstall": "mkdir -p libzt; cd libzt; ln -sf ../../../include; ln -sf ../../../lib", "autogypi": "autogypi", "node-gyp": "node-gyp", "ndts": "ndts", @@ -14,6 +14,7 @@ "author": "", "license": "MIT", "dependencies": { + "@mcesystems/nbind": "^0.3.18", "autogypi": "^0.2.2", "nbind": "github:charto/nbind", "node-gyp": "^7.0.0" From 80861c11268016c5de05e1a891800fbd2519676e Mon Sep 17 00:00:00 2001 From: heri16 <527101+heri16@users.noreply.github.com> Date: Tue, 4 Aug 2020 22:02:56 +0800 Subject: [PATCH 3/7] add libzt.createConnection() --- examples/node/binding.cc | 12 + examples/node/libzt.js | 371 ++++++++++++++- examples/node/package-lock.json | 788 ++++++++++++++++++++++++++++++++ examples/node/package.json | 1 + examples/node/test.js | 24 + 5 files changed, 1171 insertions(+), 25 deletions(-) create mode 100644 examples/node/package-lock.json create mode 100644 examples/node/test.js diff --git a/examples/node/binding.cc b/examples/node/binding.cc index cf0d2f9c..1cf58b91 100644 --- a/examples/node/binding.cc +++ b/examples/node/binding.cc @@ -415,6 +415,17 @@ class ZeroTier return zts_write(fd, buf.data(), buf.length()); } + static ssize_t writev(int fd, std::vector bufs) + { + std::size_t size = bufs.size(); + zts_iovec iov[size]; + for (std::size_t i = 0; i != size; ++i) { + iov[i].iov_base = bufs[i].data(); + iov[i].iov_len = bufs[i].length(); + } + return zts_writev(fd, iov, bufs.size()); + } + static ssize_t recv(int fd, nbind::Buffer buf, int flags) { return zts_recv(fd, buf.data(), buf.length(), flags); @@ -466,6 +477,7 @@ NBIND_CLASS(ZeroTier) { method(connect); method(read); method(write); + method(writev); method(recv); method(send); method(fcntlSetBlocking); diff --git a/examples/node/libzt.js b/examples/node/libzt.js index 62e7814d..4e9744ba 100644 --- a/examples/node/libzt.js +++ b/examples/node/libzt.js @@ -1,41 +1,362 @@ -var nbind = require('@mcesystems/nbind') -var ZeroTier = nbind.init().lib.ZeroTier +'use strict'; -// Start ZeroTier service -ZeroTier.start(".zerotier", 9994); +const net = require('net'); +const nbind = require('@mcesystems/nbind') +const ZeroTier = nbind.init().lib.ZeroTier -// Join virtual network -ZeroTier.join("8056c2e21c000001"); -// Open the socket -let fd = ZeroTier.connectStream("29.49.7.203", 4444); +/* + * EXAMPLE USAGE + * Usage: `nc -lv 4444` + */ -// Send some data -ZeroTier.send(fd, Buffer.from("Name?\n", 'utf8'), 0) +function example() { + // Start ZeroTier service + ZeroTier.start(".zerotier", 9994); -// Set blocking read mode -// ZeroTier.fcntlSetBlocking(fd, true); -let heartbeat = setInterval(() => process.stderr.write('.'), 100) + // Join virtual network + ZeroTier.join("8056c2e21c000001"); -// Receive some data -const _read = () => { + // Open the socket + let fd = ZeroTier.connectStream("29.49.7.203", 4444); + + // Send some data + ZeroTier.send(fd, Buffer.from("Name?\n", 'utf8'), 0) + + // Set blocking read mode + // ZeroTier.fcntlSetBlocking(fd, true); + let heartbeat = setInterval(() => process.stderr.write('.'), 100) + + // Receive some data + const _read = () => { const buf = Buffer.alloc(32) let bytes = -1 do { - bytes = ZeroTier.recv(fd, buf, 0) - if (bytes > 0) { process.stdout.write(buf.toString('utf8')) } + bytes = ZeroTier.recv(fd, buf, 0) + if (bytes > 0) { process.stdout.write(buf.toString('utf8')) } } while (bytes > 0); if (!ZeroTier.getMyNode().online || buf.toString('utf8').includes("exit")) { - // Close the socket - ZeroTier.close(fd) - // Stop ZeroTier service - ZeroTier.stop() - // Clear the interval - clearInterval(heartbeat) + // Close the socket + ZeroTier.close(fd) + // Stop ZeroTier service + ZeroTier.stop() + // Clear the interval + clearInterval(heartbeat) } else { - setTimeout(_read, 500) + setTimeout(_read, 500) + } + } + _read() +} + + +// Target API: +// +// let s = net.connect({port: 80, host: 'google.com'}, function() { +// ... +// }); +// +// There are various forms: +// +// connect(options, [cb]) +// connect(port, [host], [cb]) +// connect(path, [cb]); +// +function connect(...args) { + const normalized = net._normalizeArgs(args); + const options = normalized[0]; + // debug('createConnection', normalized); + const socket = new Socket(options); + + if (options.timeout) { + socket.setTimeout(options.timeout); + } + + return socket.connect(normalized); +} + +/* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L1107 + */ +function afterConnect(status, self, req, readable, writable) { + // const self = handle[owner_symbol]; + + // Callback may come after call to destroy + if (self.destroyed) { + return; + } + + // debug('afterConnect'); + + // assert(self.connecting); + self.connecting = false; + self._sockname = null; + + if (status === 0) { + self.readable = readable; + if (!self._writableState.ended) + self.writable = writable; + self._unrefTimer(); + + self.emit('connect'); + self.emit('ready'); + + // Start the first read, or get an immediate EOF. + // this doesn't actually consume any bytes, because len=0. + if (readable && !self.isPaused()) + self.read(0); + + } else { + self.connecting = false; + let details; + if (req.localAddress && req.localPort) { + details = req.localAddress + ':' + req.localPort; + } + const ex = new Error(status, + 'connect', + req.address, + req.port, + details); + if (details) { + ex.localAddress = req.localAddress; + ex.localPort = req.localPort; + } + self.destroy(ex); + } +} + +function writeGeneric(self, chunk, encoding, callback) { + const buf = (!self.decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk + + let bytes + const err = ZeroTier.send(self._fd, buf, 0) + switch (err) { + case -1: + callback(new Error("ZeroTier Socket error")) + break + case -2: + callback(new Error("ZeroTier Service error")) + break + case -3: + callback(new Error("ZeroTier Invalid argument")) + break + default: + bytes = err + callback(null) + } + + return { + async: true, + bytes: bytes, + } +} + +function writevGeneric(self, chunks, callback) { + const bufs = chunks.map(({ chunk, encoding }) => (!self.decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk) + + let bytes + const err = ZeroTier.writev(self._fd, bufs) + switch (err) { + case -1: + callback(new Error("ZeroTier Socket error")) + break + case -2: + callback(new Error("ZeroTier Service error")) + break + case -3: + callback(new Error("ZeroTier Invalid argument")) + break + default: + bytes = err + callback(null) + } + + return { + async: true, + bytes: bytes, + } +} + +class Socket extends net.Socket { + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L929 + */ + connect(...args) { + let normalized; + // If passed an array, it's treated as an array of arguments that have + // already been normalized (so we don't normalize more than once). This has + // been solved before in https://github.com/nodejs/node/pull/12342, but was + // reverted as it had unintended side effects. + if (Array.isArray(args[0])) { + normalized = args[0]; + } else { + normalized = net._normalizeArgs(args); + } + const options = normalized[0]; + const cb = normalized[1]; + + // if (this.write !== net.Socket.prototype.write) + // this.write = net.Socket.prototype.write; + + if (this.destroyed) { + this._handle = null; + this._peername = null; + this._sockname = null; + } + + // const { path } = options; + // const pipe = !!path; + // debug('pipe', pipe, path); + + // if (!this._handle) { + // this._handle = pipe ? + // new Pipe(PipeConstants.SOCKET) : + // new TCP(TCPConstants.SOCKET); + // initSocketHandle(this); + // } + + if (cb !== null) { + this.once('connect', cb); + } + + this._unrefTimer(); + + this.connecting = true; + this.writable = true; + + // if (pipe) { + // validateString(path, 'options.path'); + // defaultTriggerAsyncIdScope( + // this[async_id_symbol], internalConnect, this, path + // ); + // } else { + // lookupAndConnect(this, options); + // } + + const { host, port } = options; + // If host is an IP, skip performing a lookup + const addressType = net.isIP(host); + if (addressType) { + this._fd = ZeroTier.connectStream(host, port); + afterConnect(0, this, {}, true, true); + } else { + throw new Error("DNS LOOKUP NOT IMPLEMENTED"); + } + + return this; + } + + /* + * https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_read_size_1 + */ + _read(size) { + // debug('_read'); + + if (this.connecting) { + // debug('_read wait for connection'); + this.once('connect', () => this._read(size)); + return + } + + if (!this.readChunk || this.readChunk.length < size) { + this.readChunk = Buffer.alloc(size) + } + + let bytes = -1 + let moreData = true + do { + bytes = ZeroTier.recv(this._fd, this.readChunk, 0) + switch (bytes) { + case -2: + throw new Error("ZeroTier Service error") + case -3: + throw new Error("ZeroTier Invalid argument") + default: + if (bytes > 0) { + // this.bytesRead += bytes + moreData = this.push(this.readChunk) + } + } + } while (bytes > 0 && moreData) + + if (moreData) { setTimeout(() => this._read(size), 500) } + } + + /* + * https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_writev_chunks_callback + */ + _writev(chunks, cb) { + this._writeGeneric(true, chunks, '', cb); + } + + /* + * https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_write_chunk_encoding_callback_1 + */ + _write(data, encoding, cb) { + this._writeGeneric(false, data, encoding, cb); + } + + /* + * https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_final_callback + */ + _final(callback) { + const err = ZeroTier.close(this._fd) + + switch (err) { + case -1: + return callback(new Error("ZeroTier Socket error")) + break + case -2: + return callback(new Error("ZeroTier Service error")) + break + default: + return super._final(callback) + } + } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L760 + */ + _writeGeneric(writev, data, encoding, cb) { + // If we are still connecting, then buffer this for later. + // The Writable logic will buffer up any more writes while + // waiting for this one to be done. + if (this.connecting) { + this._pendingData = data; + this._pendingEncoding = encoding; + this.once('connect', function connect() { + this._writeGeneric(writev, data, encoding, cb); + }); + return; + } + this._pendingData = null; + this._pendingEncoding = ''; + + // if (!this._handle) { + // cb(new ERR_SOCKET_CLOSED()); + // return false; + // } + + this._unrefTimer(); + + let req; + if (writev) + req = writevGeneric(this, data, cb); + else + req = writeGeneric(this, data, encoding, cb); + if (req.async) { + // this[kLastWriteQueueSize] = req.bytes; } + } } -_read() +module.exports = { + example, + start: ZeroTier.start, + join: ZeroTier.join, + connect, + createConnection: connect, + Socket, + Stream: Socket, // Legacy naming +}; diff --git a/examples/node/package-lock.json b/examples/node/package-lock.json new file mode 100644 index 00000000..4a2e0742 --- /dev/null +++ b/examples/node/package-lock.json @@ -0,0 +1,788 @@ +{ + "name": "libzt", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@mcesystems/nbind": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@mcesystems/nbind/-/nbind-0.3.18.tgz", + "integrity": "sha512-gZFv881rT/nTNNl92K97DqbFVECiniEHg4ABle7WFKklQSCge7ILY58otnKvCgoqrrS/9Mv//mTf+2k3MPSGXA==", + "requires": { + "emscripten-library-decorator": "~0.2.2", + "mkdirp": "~0.5.1", + "nan": "^2.9.2" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "autogypi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/autogypi/-/autogypi-0.2.2.tgz", + "integrity": "sha1-JYurX3hXdVsJvqxqZB/qEw/0Yi0=", + "requires": { + "bluebird": "^3.4.0", + "commander": "~2.9.0", + "resolve": "~1.1.7" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emscripten-library-decorator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/emscripten-library-decorator/-/emscripten-library-decorator-0.2.2.tgz", + "integrity": "sha1-0DXwI+KoTGgwXMhCze6jjmdoPEA=" + }, + "env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, + "nbind": { + "version": "github:charto/nbind#fe3abe05462d1b7559e0933e7f83802e8f05af27", + "from": "github:charto/nbind", + "requires": { + "emscripten-library-decorator": "~0.2.2", + "mkdirp": "~0.5.1", + "nan": "^2.9.2" + } + }, + "node-gyp": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.0.0.tgz", + "integrity": "sha512-ZW34qA3CJSPKDz2SJBHKRvyNQN0yWO5EGKKksJc+jElu9VA468gwJTyTArC1iOXU7rN3Wtfg/CMt/dBAOFIjvg==", + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.3", + "nopt": "^4.0.3", + "npmlog": "^4.1.2", + "request": "^2.88.2", + "rimraf": "^2.6.3", + "semver": "^7.3.2", + "tar": "^6.0.1", + "which": "^2.0.2" + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "tar": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/examples/node/package.json b/examples/node/package.json index 74aa392f..05bf3b94 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -5,6 +5,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "start": "node test.js", "preinstall": "mkdir -p libzt; cd libzt; ln -sf ../../../include; ln -sf ../../../lib", "autogypi": "autogypi", "node-gyp": "node-gyp", diff --git a/examples/node/test.js b/examples/node/test.js new file mode 100644 index 00000000..2aeb3cb4 --- /dev/null +++ b/examples/node/test.js @@ -0,0 +1,24 @@ +'use strict' + +const libzt = require('./libzt') + +// libzt.example() + +libzt.start(".zerotier", 9994) + +libzt.join("8056c2e21c000001") + +// Usage: `nc -lv 4444` +let client = libzt.createConnection({ port: 4444, host: '29.49.7.203' }, () => { + // 'connect' listener. + console.log('connected to server!'); + // client.write('world!\r\n'); +}); +client.write("Name?\n", 'utf8'); +client.on('data', (data) => { + console.log(data.toString()); + client.end(); +}); +client.on('end', () => { + console.log('disconnected from server'); +}); From fcc8dcfd6cc4e11793ce83d11dfe8f92bef16088 Mon Sep 17 00:00:00 2001 From: heri16 <527101+heri16@users.noreply.github.com> Date: Wed, 5 Aug 2020 17:25:55 +0800 Subject: [PATCH 4/7] small bug fix --- examples/node/libzt.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/node/libzt.js b/examples/node/libzt.js index 4e9744ba..09087657 100644 --- a/examples/node/libzt.js +++ b/examples/node/libzt.js @@ -127,7 +127,8 @@ function afterConnect(status, self, req, readable, writable) { } function writeGeneric(self, chunk, encoding, callback) { - const buf = (!self.decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk + const decodeStrings = self._writableState && self._writableState.decodeStrings + const buf = (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk let bytes const err = ZeroTier.send(self._fd, buf, 0) @@ -153,7 +154,8 @@ function writeGeneric(self, chunk, encoding, callback) { } function writevGeneric(self, chunks, callback) { - const bufs = chunks.map(({ chunk, encoding }) => (!self.decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk) + const decodeStrings = self._writableState && self._writableState.decodeStrings + const bufs = chunks.map(({ chunk, encoding }) => (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk) let bytes const err = ZeroTier.writev(self._fd, bufs) @@ -359,4 +361,7 @@ module.exports = { createConnection: connect, Socket, Stream: Socket, // Legacy naming + restart: ZeroTier.restart, + stop: ZeroTier.stop, + free: ZeroTier.free, }; From 58c4cb9e7da63ac4d31674133075cfe3b8a571e9 Mon Sep 17 00:00:00 2001 From: heri16 <527101+heri16@users.noreply.github.com> Date: Wed, 5 Aug 2020 17:42:31 +0800 Subject: [PATCH 5/7] make libzt.a position_independent_code --- CMakeLists.txt | 11 ++++++----- examples/node/binding.gyp | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e236981..0884505c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,13 +426,14 @@ set_target_properties (ztcore PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${INTERMEDIATE_LIBRARY_OUTPUT_PATH}) # libzt.a -add_library (${STATIC_LIB_NAME} STATIC $ -$ -$ -$ -$ ${libztSrcGlob}) +add_library (${STATIC_LIB_NAME} STATIC $ +$ +$ +$ +$ ${libztSrcGlob}) set_target_properties (${STATIC_LIB_NAME} PROPERTIES OUTPUT_NAME zt + POSITION_INDEPENDENT_CODE ON LIBRARY_OUTPUT_DIRECTORY ${INTERMEDIATE_LIBRARY_OUTPUT_PATH}) set_target_properties (${STATIC_LIB_NAME} PROPERTIES COMPILE_FLAGS "${ZT_FLAGS}") if (BUILDING_WIN) diff --git a/examples/node/binding.gyp b/examples/node/binding.gyp index 5aa3e1dc..a53ef2a6 100644 --- a/examples/node/binding.gyp +++ b/examples/node/binding.gyp @@ -2,7 +2,6 @@ "targets": [ { "include_dirs": [ - "libzt/lib/release/linux-x86_64", "libzt/include", ], "includes": [ @@ -13,7 +12,7 @@ ], "conditions":[ ["OS=='linux' and target_arch=='x64'", { - "libraries": [ "<(module_root_dir)/libzt/lib/release/linux-x86_64/libzt.so" ] + "libraries": [ "<(module_root_dir)/libzt/lib/release/linux-x86_64/libzt.a" ] }], ["OS=='mac' and target_arch=='x64'", { "libraries": [ "<(module_root_dir)/libzt/lib/release/macos-x86_64/libzt.a" ] From 42ab8c2aeeb85039c41ffc479b66daef0ff717dc Mon Sep 17 00:00:00 2001 From: heri16 <527101+heri16@users.noreply.github.com> Date: Thu, 6 Aug 2020 11:48:36 +0800 Subject: [PATCH 6/7] major rewrite to handle schemantics --- examples/node/binding.cc | 412 ++++++++++++---- examples/node/libzt.js | 833 ++++++++++++++++++++++++-------- examples/node/stream_commons.js | 242 ++++++++++ examples/node/test.js | 13 +- 4 files changed, 1197 insertions(+), 303 deletions(-) create mode 100644 examples/node/stream_commons.js diff --git a/examples/node/binding.cc b/examples/node/binding.cc index 1cf58b91..f1d8b0e6 100644 --- a/examples/node/binding.cc +++ b/examples/node/binding.cc @@ -127,6 +127,19 @@ void myZeroTierEventCallback(void *msgPtr) } } +zts_sockaddr_in sockaddr_in(const char *remoteAddr, const int remotePort) +{ + struct zts_sockaddr_in in4; + in4.sin_port = zts_htons(remotePort); +#if defined(_WIN32) + in4.sin_addr.S_addr = zts_inet_addr(remoteAddr); +#else + in4.sin_addr.s_addr = zts_inet_addr(remoteAddr); +#endif + in4.sin_family = ZTS_AF_INET; + return in4; +} + /** * * IDENTITIES and AUTHORIZATION: @@ -211,6 +224,8 @@ void myZeroTierEventCallback(void *msgPtr) class ZeroTier { public: + static Node getMyNode() { return myNode; } + /** * @brief Starts the ZeroTier service and notifies user application of events via callback * @@ -299,89 +314,220 @@ class ZeroTier return err; } - static int connectStream(const char *remoteAddr, const int remotePort) + static int openStream() { - return connect(ZTS_AF_INET, ZTS_SOCK_STREAM, 0, remoteAddr, remotePort); + return open(ZTS_AF_INET, ZTS_SOCK_STREAM, 0); } - static int connectDgram(const char *remoteAddr, const int remotePort) + static int openDgram() { - return connect(ZTS_AF_INET, ZTS_SOCK_DGRAM, 0, remoteAddr, remotePort); + return open(ZTS_AF_INET, ZTS_SOCK_DGRAM, 0); } - static int connectRaw(const char *remoteAddr, const int remotePort, const int protocol) + static int openRaw(const int protocol) { - return connect(ZTS_AF_INET, ZTS_SOCK_RAW, protocol, remoteAddr, remotePort); + return open(ZTS_AF_INET, ZTS_SOCK_RAW, protocol); } - static int connectStream6(const char *remoteAddr, const int remotePort) + static int openStream6() { - return connect(ZTS_AF_INET6, ZTS_SOCK_STREAM, 0, remoteAddr, remotePort); + return open(ZTS_AF_INET6, ZTS_SOCK_STREAM, 0); } - static int connectDgram6(const char *remoteAddr, const int remotePort) + static int openDgram6() { - return connect(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0, remoteAddr, remotePort); + return open(ZTS_AF_INET6, ZTS_SOCK_DGRAM, 0); } - static int connectRaw6(const char *remoteAddr, const int remotePort, const int protocol) + static int openRaw6(const int protocol) { - return connect(ZTS_AF_INET6, ZTS_SOCK_RAW, protocol, remoteAddr, remotePort); + return open(ZTS_AF_INET6, ZTS_SOCK_RAW, protocol); } /** - * @brief Connect a socket to a remote host (sets zts_errno) + * @brief Create a socket (sets zts_errno) * * @param socket_family Address family (ZTS_AF_INET, ZTS_AF_INET6) * @param socket_type Type of socket (ZTS_SOCK_STREAM, ZTS_SOCK_DGRAM, ZTS_SOCK_RAW) * @param protocol Protocols supported on this socket + * @return Numbered file descriptor on success. ZTS_ERR_SERVICE or ZTS_ERR_SOCKET on failure. + */ + static int open(const int socket_family, const int socket_type, const int protocol) + { + int fd; + if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { + printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); + } + return fd; + } + + /** + * @brief Close a socket (sets zts_errno) + * + * @param fd Socket file descriptor + * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE on failure. + */ + static int close(int fd) + { + return zts_close(fd); + } + + /** + * @brief Shut down some aspect of a socket (sets zts_errno) + * + * @param fd Socket file descriptor + * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static int shutdown(int fd) + { + return zts_shutdown(fd, ZTS_SHUT_RDWR); + } + + /** + * @brief Bind a socket to a virtual interface (sets zts_errno) + * + * @param fd Socket file descriptor * @param remoteAddr Remote Address to connect to * @param remotePort Remote Port to connect to * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. */ - static int connect(const int socket_family, const int socket_type, const int protocol, const char *remoteAddr, const int remotePort) + static int bind(int fd, const char *localAddr, const int localPort) { - if (socket_family == ZTS_AF_INET6) { - printf("IPv6 NOT IMPLEMENTED.\n"); - return -1; + struct zts_sockaddr_in in4 = sockaddr_in(localAddr, localPort); + + int err = ZTS_ERR_OK; + if ((err = zts_bind(fd, (const struct zts_sockaddr *)&in4, sizeof(in4))) < 0) { + printf("Error binding to interface (fd=%d, ret=%d, zts_errno=%d).\n", + fd, err, zts_errno); } + return err; + } - struct zts_sockaddr_in in4 = sockaddr_in(remoteAddr, remotePort); + static int bind6(int fd, const char *remoteAddr, const int remotePort) + { + printf("IPv6 NOT IMPLEMENTED.\n"); + return ZTS_ERR_ARG; + } - int fd; - if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { - printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); - return -1; - } + /** + * @brief Connect a socket to a remote host (sets zts_errno) + * + * @param fd Socket file descriptor + * @param remoteAddr Remote Address to connect to + * @param remotePort Remote Port to connect to + * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static int connect(int fd, const char *remoteAddr, const int remotePort) + { + struct zts_sockaddr_in in4 = sockaddr_in(remoteAddr, remotePort); // Retries are often required since ZT uses transport-triggered links (explained above) int err = ZTS_ERR_OK; - for (;;) { - printf("Connecting to remote host...\n"); - if ((err = zts_connect(fd, (const struct zts_sockaddr *)&in4, sizeof(in4))) < 0) { - printf("Error connecting to remote host (fd=%d, ret=%d, zts_errno=%d). Trying again.\n", + if ((err = zts_connect(fd, (const struct zts_sockaddr *)&in4, sizeof(in4))) < 0) { + printf("Error connecting to remote host (fd=%d, ret=%d, zts_errno=%d).\n", fd, err, zts_errno); - zts_close(fd); - // printf("Creating socket...\n"); - if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { - printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); - return -1; - } - zts_delay_ms(250); - } - else { - printf("Connected.\n"); - break; - } + } else { + // Set non-blocking mode + fcntl(fd, ZTS_F_SETFL, ZTS_O_NONBLOCK); } + return err; - // Set non-blocking mode - fcntl(fd, ZTS_F_SETFL, ZTS_O_NONBLOCK); + // int err = ZTS_ERR_OK; + // for (;;) { + // printf("Connecting to remote host...\n"); + // if ((err = zts_connect(fd, (const struct zts_sockaddr *)&in4, sizeof(in4))) < 0) { + // printf("Error connecting to remote host (fd=%d, ret=%d, zts_errno=%d). Trying again.\n", + // fd, err, zts_errno); + // zts_close(fd); + // // printf("Creating socket...\n"); + // if ((fd = zts_socket(socket_family, socket_type, protocol)) < 0) { + // printf("Error creating ZeroTier socket (fd=%d, zts_errno=%d).\n", fd, zts_errno); + // return -1; + // } + // zts_delay_ms(250); + // } + // else { + // printf("Connected.\n"); + // break; + // } + // } + } + + static int connect6(int fd, const char *remoteAddr, const int remotePort) + { + printf("IPv6 NOT IMPLEMENTED.\n"); + return ZTS_ERR_ARG; + } - return fd; + /** + * @brief Read bytes from socket onto buffer (sets zts_errno) + * + * @param fd Socket file descriptor + * @param buf Pointer to data buffer + * @return Byte count received on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static ssize_t read(int fd, nbind::Buffer buf) + { + return zts_read(fd, buf.data(), buf.length()); + } + + /** + * @brief Write bytes from buffer to socket (sets zts_errno) + * + * @param fd Socket file descriptor + * @param buf Pointer to data buffer + * @return Byte count sent on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static ssize_t write(int fd, nbind::Buffer buf) + { + return zts_write(fd, buf.data(), buf.length()); + } + + /** + * @brief Write data from multiple buffers to socket. (sets zts_errno) + * + * @param fd Socket file descriptor + * @param bufs Array of source buffers + * @return Byte count sent on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static ssize_t writev(int fd, std::vector bufs) + { + std::size_t size = bufs.size(); + zts_iovec iov[size]; + for (std::size_t i = 0; i != size; ++i) { + iov[i].iov_base = bufs[i].data(); + iov[i].iov_len = bufs[i].length(); + } + return zts_writev(fd, iov, bufs.size()); + } + + /** + * @brief Receive data from remote host (sets zts_errno) + * + * @param fd Socket file descriptor + * @param buf Pointer to data buffer + * @param flags + * @return Byte count received on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static ssize_t recv(int fd, nbind::Buffer buf, int flags) + { + return zts_recv(fd, buf.data(), buf.length(), flags); + } + + /** + * @brief Send data to remote host (sets zts_errno) + * + * @param fd Socket file descriptor + * @param buf data buffer + * @param flags + * @return Byte count sent on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static ssize_t send(int fd, nbind::Buffer buf, int flags) + { + return zts_send(fd, buf.data(), buf.length(), flags); } - static int fcntlSetBlocking(int fd, bool isBlocking) + static int setBlocking(int fd, bool isBlocking) { int flags = fcntl(fd, ZTS_F_GETFL, 0); if (isBlocking) { @@ -392,6 +538,27 @@ class ZeroTier return fcntl(fd, ZTS_F_SETFL, flags); } + static int setNoDelay(int fd, bool isNdelay) + { + int flags = fcntl(fd, ZTS_F_GETFL, 0); + if (isNdelay) { + flags &= ~ZTS_O_NDELAY; + } else { + flags &= ZTS_O_NDELAY; + } + return fcntl(fd, ZTS_F_SETFL, flags); + } + + static int setKeepalive(int fd, int yes) + { + return setsockopt(fd, ZTS_SOL_SOCKET, ZTS_SO_KEEPALIVE, &yes, sizeof(yes)); + } + + static int setKeepidle(int fd, int idle) + { + return setsockopt(fd, ZTS_IPPROTO_TCP, ZTS_TCP_KEEPIDLE, &idle, sizeof(idle)); + } + /** * @brief Issue file control commands on a socket * @@ -405,56 +572,109 @@ class ZeroTier return zts_fcntl(fd, cmd, flags); } - static ssize_t read(int fd, nbind::Buffer buf) + /** + * @brief Set socket options (sets zts_errno) + * + * @param fd Socket file descriptor + * @param level Protocol level to which option name should apply + * @param optname Option name to set + * @param optval Source of option value to set + * @param optlen Length of option value + * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static int setsockopt(int fd, int level, int optname, const void *optval, zts_socklen_t optlen) { - return zts_read(fd, buf.data(), buf.length()); + return zts_setsockopt(fd, level, optname, optval, optlen); } - static ssize_t write(int fd, nbind::Buffer buf) + /** + * @brief Get socket options (sets zts_errno) + * + * @param fd Socket file descriptor + * @param level Protocol level to which option name should apply + * @param optname Option name to get + * @param optval Where option value will be stored + * @param optlen Length of value + * @return ZTS_ERR_OK on success. ZTS_ERR_SOCKET, ZTS_ERR_SERVICE, ZTS_ERR_ARG on failure. + */ + static int getsockopt(int fd, int level, int optname, void *optval, zts_socklen_t *optlen) { - return zts_write(fd, buf.data(), buf.length()); + return zts_getsockopt(fd, level, optname, optval, optlen); } - static ssize_t writev(int fd, std::vector bufs) + /** + * @brief Get socket name (sets zts_errno) + * + * @param fd Socket file descriptor + * @param addr Name associated with this socket + * @param addrlen Length of name + * @return Sockaddress structure + */ + static zts_sockaddr_in getsockname(int fd) { - std::size_t size = bufs.size(); - zts_iovec iov[size]; - for (std::size_t i = 0; i != size; ++i) { - iov[i].iov_base = bufs[i].data(); - iov[i].iov_len = bufs[i].length(); - } - return zts_writev(fd, iov, bufs.size()); + struct zts_sockaddr_in in4; + zts_socklen_t addrlen; + zts_getsockname(fd, (struct zts_sockaddr *)&in4, &addrlen); + return in4; } - static ssize_t recv(int fd, nbind::Buffer buf, int flags) + static zts_sockaddr_in6 getsockname6(int fd) { - return zts_recv(fd, buf.data(), buf.length(), flags); + struct zts_sockaddr_in6 in6; + zts_socklen_t addrlen; + zts_getsockname(fd, (struct zts_sockaddr *)&in6, &addrlen); + return in6; } - static ssize_t send(int fd, nbind::Buffer buf, int flags) + /** + * @brief Get the peer name for the remote end of a connected socket + * + * @param fd Socket file descriptor + * @param addr Name associated with remote end of this socket + * @param addrlen Length of name + * @return Sockaddress structure + */ + static zts_sockaddr_in getpeername(int fd) { - return zts_send(fd, buf.data(), buf.length(), flags); + struct zts_sockaddr_in in4; + zts_socklen_t addrlen; + zts_getpeername(fd, (struct zts_sockaddr *)&in4, &addrlen); + return in4; } - static int close(int fd) + static zts_sockaddr_in6 getpeername6(int fd) { - return zts_close(fd); + struct zts_sockaddr_in6 in6; + zts_socklen_t addrlen; + zts_getpeername(fd, (struct zts_sockaddr *)&in6, &addrlen); + return in6; } - static zts_sockaddr_in sockaddr_in(const char *remoteAddr, const int remotePort) - { - struct zts_sockaddr_in in4; - in4.sin_port = zts_htons(remotePort); - #if defined(_WIN32) - in4.sin_addr.S_addr = zts_inet_addr(remoteAddr); - #else - in4.sin_addr.s_addr = zts_inet_addr(remoteAddr); - #endif - in4.sin_family = ZTS_AF_INET; - return in4; - } - - static Node getMyNode() { return myNode; } + /** + * Convert IPv4 and IPv6 address structures to human-readable text form. + * + * @param af Address family (ZTS_AF_INET, ZTS_AF_INET6) + * @param src Pointer to source address structure + * @param dst Pointer to destination character array + * @param size Size of the destination buffer + * @return On success, returns a non-null pointer to the destination character array + */ + static const char * inet_ntop(const zts_sockaddr in) + { + if (in.sa_family == ZTS_AF_INET) { + const zts_sockaddr_in *in4 = (const zts_sockaddr_in *)∈ + char ipstr[ZTS_INET_ADDRSTRLEN]; + zts_inet_ntop(ZTS_AF_INET, &(in4->sin_addr), ipstr, ZTS_INET_ADDRSTRLEN); + return ipstr; + } else if (in.sa_family == ZTS_AF_INET6) { + const zts_sockaddr_in6 *in6 = (const zts_sockaddr_in6 *)∈ + char ipstr[ZTS_INET6_ADDRSTRLEN]; + zts_inet_ntop(ZTS_AF_INET6, &(in6->sin6_addr), ipstr, ZTS_INET6_ADDRSTRLEN); + return ipstr; + } else { + return ""; + } + } }; #include "nbind/nbind.h" @@ -467,24 +687,44 @@ NBIND_CLASS(Node) { NBIND_CLASS(ZeroTier) { method(start); + method(restart); + method(stop); + method(free); + method(join); - method(connectStream); - method(connectDgram); - method(connectRaw); - method(connectStream6); - method(connectDgram6); - method(connectRaw6); + + method(openStream); + method(openDgram); + method(openRaw); + method(openStream6); + method(openDgram6); + method(openRaw6); + method(open); + method(close); + method(shutdown); + + method(bind); + method(bind6); method(connect); + method(connect6); + method(read); method(write); method(writev); method(recv); method(send); - method(fcntlSetBlocking); + + method(setBlocking); + method(setNoDelay); + method(setKeepalive); + method(setKeepidle); method(fcntl); - method(close); - method(restart); - method(stop); - method(free); + + method(getsockname); + method(getsockname6); + method(getpeername); + method(getpeername6); + method(inet_ntop); + method(getMyNode); } diff --git a/examples/node/libzt.js b/examples/node/libzt.js index 09087657..683f5961 100644 --- a/examples/node/libzt.js +++ b/examples/node/libzt.js @@ -1,53 +1,101 @@ 'use strict'; const net = require('net'); +const stream = require('stream'); +const { types: { isUint8Array } } = require('util'); + const nbind = require('@mcesystems/nbind') const ZeroTier = nbind.init().lib.ZeroTier +const { + errnoException, + writevGeneric, + writeGeneric, + onStreamRead, + kAfterAsyncWrite, + kHandle, + kUpdateTimer, + // setStreamTimeout, + kBuffer, + kBufferCb, + kBufferGen +} = require('./stream_commons'); + +const kLastWriteQueueSize = Symbol('lastWriteQueueSize'); + /* - * EXAMPLE USAGE + * EXAMPLE of Low-level usage * Usage: `nc -lv 4444` */ -function example() { +function example(nwid, address, port) { // Start ZeroTier service ZeroTier.start(".zerotier", 9994); // Join virtual network - ZeroTier.join("8056c2e21c000001"); + ZeroTier.join(nwid); - // Open the socket - let fd = ZeroTier.connectStream("29.49.7.203", 4444); + // Connect the socket + const _connect = (address, port, callback) => { + // Open the socket + const fd = ZeroTier.openStream(); + if (fd < 0) { callback(new Error('Could not open socket, errno: ' + fd)); return; } - // Send some data - ZeroTier.send(fd, Buffer.from("Name?\n", 'utf8'), 0) + // Try connect + const status = ZeroTier.connect(fd, address, port); - // Set blocking read mode - // ZeroTier.fcntlSetBlocking(fd, true); - let heartbeat = setInterval(() => process.stderr.write('.'), 100) + console.log(status); + if (status === 0) { + callback(null, fd); + } else { + // Close previous socket + ZeroTier.close(fd); + setTimeout(_connect, 250, address, port, callback); + } + } // Receive some data - const _read = () => { + const _read = (fd, callback) => { const buf = Buffer.alloc(32) let bytes = -1 do { bytes = ZeroTier.recv(fd, buf, 0) - if (bytes > 0) { process.stdout.write(buf.toString('utf8')) } + if (bytes > 0) { callback(null, buf); } } while (bytes > 0); if (!ZeroTier.getMyNode().online || buf.toString('utf8').includes("exit")) { - // Close the socket - ZeroTier.close(fd) - // Stop ZeroTier service - ZeroTier.stop() - // Clear the interval - clearInterval(heartbeat) + callback('end'); } else { - setTimeout(_read, 500) + setTimeout(_read, 500, fd, callback) } } - _read() + + _connect(address, port, (err, fd) => { + if (err) { console.error(err); return; } + console.debug("Connected."); + + // Send some data + ZeroTier.send(fd, Buffer.from("Name?\n", 'utf8'), 0); + + // Set blocking read mode + // ZeroTier.setBlocking(fd, true); + const heartbeat = setInterval(() => process.stderr.write('.'), 100); + + _read(fd, (stop, buf) => { + if (stop) { + // Close the socket + ZeroTier.close(fd); + // Stop ZeroTier service + ZeroTier.stop(); + // Clear the interval + clearInterval(heartbeat); + return; + } + + process.stdout.write(buf.toString('utf8')); + }); + }); } @@ -67,7 +115,8 @@ function connect(...args) { const normalized = net._normalizeArgs(args); const options = normalized[0]; // debug('createConnection', normalized); - const socket = new Socket(options); + + const socket = new Socket(Object.assign({ handle: new ZTCP() }, options)); if (options.timeout) { socket.setTimeout(options.timeout); @@ -76,213 +125,528 @@ function connect(...args) { return socket.connect(normalized); } +/* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L567 + */ +function tryReadStart(socket) { + // Not already reading, start the flow + // debug('Socket._handle.readStart'); + socket._handle.reading = true; + const err = socket._handle.readStart(); + if (err) + socket.destroy(errnoException(err, 'read')); +} + /* * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L1107 */ -function afterConnect(status, self, req, readable, writable) { - // const self = handle[owner_symbol]; +// function afterConnect(status, self, req, readable, writable) { +// // const self = handle[owner_symbol]; + +// // Callback may come after call to destroy +// if (self.destroyed) { +// return; +// } + +// // debug('afterConnect'); + +// // assert(self.connecting); +// self.connecting = false; +// self._sockname = null; + +// if (status === 0) { +// self.readable = readable; +// if (!self._writableState.ended) +// self.writable = writable; +// self._unrefTimer(); + +// self.emit('connect'); +// self.emit('ready'); + +// // Start the first read, or get an immediate EOF. +// // this doesn't actually consume any bytes, because len=0. +// if (readable && !self.isPaused()) +// self.read(0); + +// } else { +// self.connecting = false; +// let details; +// if (req.localAddress && req.localPort) { +// details = req.localAddress + ':' + req.localPort; +// } +// const ex = new Error(status, +// 'connect', +// req.address, +// req.port, +// details); +// if (details) { +// ex.localAddress = req.localAddress; +// ex.localPort = req.localPort; +// } +// self.destroy(ex); +// } +// } + +// function afterShutdown(self, _status) { +// // const self = this.handle[owner_symbol]; + +// // debug('afterShutdown destroyed=%j', self.destroyed, +// // self._readableState); + +// this.callback(); + +// // Callback may come after call to destroy. +// if (self.destroyed) +// return; + +// if (!self.readable || self.readableEnded) { +// // debug('readableState ended, destroying'); +// self.destroy(); +// } +// } + +// function writeGeneric(self, chunk, encoding, callback) { +// const decodeStrings = self._writableState && self._writableState.decodeStrings +// const buf = (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk + +// let bytes +// const err = ZeroTier.send(self._fd, buf, 0) +// switch (err) { +// case -1: +// callback(new Error("ZeroTier Socket error")) +// break +// case -2: +// callback(new Error("ZeroTier Service error")) +// break +// case -3: +// callback(new Error("ZeroTier Invalid argument")) +// break +// default: +// bytes = err +// callback() +// } + +// return { +// async: true, +// bytes: bytes, +// } +// } + +// function writevGeneric(self, chunks, callback) { +// const decodeStrings = self._writableState && self._writableState.decodeStrings +// const bufs = chunks.map(({ chunk, encoding }) => (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk) + +// let bytes +// const err = ZeroTier.writev(self._fd, bufs) +// switch (err) { +// case -1: +// callback(new Error("ZeroTier Socket error")) +// break +// case -2: +// callback(new Error("ZeroTier Service error")) +// break +// case -3: +// callback(new Error("ZeroTier Invalid argument")) +// break +// default: +// bytes = err +// callback() +// } + +// return { +// async: true, +// bytes: bytes, +// } +// } + + + +class ZTCP { + bytesRead = 0 + bytesWritten = 0 + writeQueueSize = 0 + + _fd = null + _reading = false + readTimer = null + + get reading() { + return this._reading; + } - // Callback may come after call to destroy - if (self.destroyed) { - return; + set reading(val) { + return this._reading = val; } - // debug('afterConnect'); + readStart() { + if (!this._buf) { + this._buf = Buffer.alloc(128); + } - // assert(self.connecting); - self.connecting = false; - self._sockname = null; + let bytes = 0 + do { + bytes = ZeroTier.read(this._fd, this._buf) + if (bytes >= 0) { + this.bytesRead += bytes; + bytes = 0; + } + switch (bytes) { + case -2: + throw new Error("ZeroTier Service error") + case -3: + throw new Error("ZeroTier Invalid argument") + default: + if (bytes > 0) { + this.bytesRead += bytes + this._buf = this.onread(this._buf) + } + } + } while (bytes > 0 && this._reading) - if (status === 0) { - self.readable = readable; - if (!self._writableState.ended) - self.writable = writable; - self._unrefTimer(); + if (this._reading) { readTimer = setTimeout(() => this._read(size), 500) } + } - self.emit('connect'); - self.emit('ready'); + readStop() { + if (readTimer) { + clearTimeout(readTimer); + readTimer = null; + } + this._reading = false + } - // Start the first read, or get an immediate EOF. - // this doesn't actually consume any bytes, because len=0. - if (readable && !self.isPaused()) - self.read(0); + writev(req, chunks, allBuffers) { + let bufs = []; - } else { - self.connecting = false; - let details; - if (req.localAddress && req.localPort) { - details = req.localAddress + ':' + req.localPort; + if (allBuffers) { + bufs = chunks; + } else { + const arr = chunks; + for (let i = 0; i < arr.length; i+=2) { + const chunk = arr[i]; + const encoding = arr[i+1]; + chunks.push(Buffer.from(chunk, encoding)); + } } - const ex = new Error(status, - 'connect', - req.address, - req.port, - details); - if (details) { - ex.localAddress = req.localAddress; - ex.localPort = req.localPort; + + let bytes = ZeroTier.writev(this._fd, bufs); + if (bytes >= 0) { + this.bytesWritten += bytes; + bytes = 0; } - self.destroy(ex); + + const status = bytes; + // https://github.com/nodejs/node/blob/v12.18.3/lib/internal/stream_base_commons.js#L80 + if (req.oncomplete) { req.oncomplete.call(req, status); } + return status; } -} -function writeGeneric(self, chunk, encoding, callback) { - const decodeStrings = self._writableState && self._writableState.decodeStrings - const buf = (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk - - let bytes - const err = ZeroTier.send(self._fd, buf, 0) - switch (err) { - case -1: - callback(new Error("ZeroTier Socket error")) - break - case -2: - callback(new Error("ZeroTier Service error")) - break - case -3: - callback(new Error("ZeroTier Invalid argument")) - break - default: - bytes = err - callback(null) - } - - return { - async: true, - bytes: bytes, + writeBuffer(req, buf) { + let bytes = ZeroTier.write(this._fd, buf); + if (bytes >= 0) { + this.bytesWritten += bytes; + bytes = 0; + } + + const status = bytes; + // https://github.com/nodejs/node/blob/v12.18.3/lib/internal/stream_base_commons.js#L80 + if (req.oncomplete) { req.oncomplete.call(req, status); } + return status; } -} -function writevGeneric(self, chunks, callback) { - const decodeStrings = self._writableState && self._writableState.decodeStrings - const bufs = chunks.map(({ chunk, encoding }) => (!decodeStrings && !Buffer.isBuffer(chunk)) ? Buffer.from(chunk, encoding) : chunk) - - let bytes - const err = ZeroTier.writev(self._fd, bufs) - switch (err) { - case -1: - callback(new Error("ZeroTier Socket error")) - break - case -2: - callback(new Error("ZeroTier Service error")) - break - case -3: - callback(new Error("ZeroTier Invalid argument")) - break - default: - bytes = err - callback(null) - } - - return { - async: true, - bytes: bytes, + writeLatin1String(req, data) { + return this.writeBuffer(req, Buffer.from(data, 'latin1')); } -} -class Socket extends net.Socket { - /* - * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L929 - */ - connect(...args) { - let normalized; - // If passed an array, it's treated as an array of arguments that have - // already been normalized (so we don't normalize more than once). This has - // been solved before in https://github.com/nodejs/node/pull/12342, but was - // reverted as it had unintended side effects. - if (Array.isArray(args[0])) { - normalized = args[0]; + writeUtf8String(req, data) { + return this.writeBuffer(req, Buffer.from(data, 'utf8')); + } + + writeAsciiString(req, data) { + return this.writeBuffer(req, Buffer.from(data, 'ascii')); + } + + writeUcs2String(req, data) { + return this.writeBuffer(req, Buffer.from(data, 'ucs2')); + } + + getAsyncId() { + return -1; + } + + useUserBuffer(buf) { + this._buf = buf; + } + + setBlocking(newValue) { + return ZeroTier.setBlocking(this._fd, newValue); + } + + setNoDelay(newValue) { + return ZeroTier.setNoDelay(this._fd, newValue); + } + + setKeepalive(enable, initialDelay) { + ZeroTier.setKeepidle(initialDelay); + return ZeroTier.setKeepalive(this._fd, +enable); + } + + bind(localAddress, localPort) { + return ZeroTier.bind(this._fd, localAddress, localPort); + } + + bind6(localAddress, localPort, _flags) { + return ZeroTier.bind6(this._fd, localAddress, localPort); + } + + open(fd) { + if (fd) { + this._fd = fd; + return 0; } else { - normalized = net._normalizeArgs(args); + const err = ZeroTier.openStream(); + if (err < 0) { + return err; + } else { + this._fd = err; + return 0; + } } - const options = normalized[0]; - const cb = normalized[1]; + } + + close(callback) { + const err = ZeroTier.close(this._fd); + this._fd = null; + if (callback) { callback(err); } + } - // if (this.write !== net.Socket.prototype.write) - // this.write = net.Socket.prototype.write; + shutdown(req) { + const status = ZeroTier.shutdown(this._fd); + // https://github.com/nodejs/node/blob/v12.18.3/test/parallel/test-tcp-wrap-connect.js + if (req.oncomplete) { req.oncomplete.call(req, status, this); } + return status; + } - if (this.destroyed) { - this._handle = null; - this._peername = null; - this._sockname = null; + connect(req, address, port) { + let status = ZeroTier.connect(this._fd, address, port); + + // Retries are often required since ZT uses transport-triggered links + if (status !== 0) { + let count = 0; + while (count < 10) { + // Close previous socket + this.close(); + status = this.open(); + if (status !== 0) { + // Break if reopen-socket fails + break; + } + + // Reconnect + status = ZeroTier.connect(this._fd, address, port); + if (status === 0) { break; } + + count++; + } } - // const { path } = options; - // const pipe = !!path; - // debug('pipe', pipe, path); + // https://github.com/nodejs/node/blob/v12.18.3/test/parallel/test-tcp-wrap-connect.js + if (req && req.oncomplete) { req.oncomplete.call(status, this, req, true, true); } - // if (!this._handle) { - // this._handle = pipe ? - // new Pipe(PipeConstants.SOCKET) : - // new TCP(TCPConstants.SOCKET); - // initSocketHandle(this); - // } + return status; + } - if (cb !== null) { - this.once('connect', cb); + connect6(req, address, port) { + let status = ZeroTier.connect6(this._fd, address, port); + + // Retries are often required since ZT uses transport-triggered links + if (status !== 0) { + let count = 0; + while (count < 10) { + // Close previous socket + this.close(); + status = this.open(); + if (status !== 0) { + // Break if reopen-socket fails + break; + } + + // Reconnect + status = ZeroTier.connect6(this._fd, address, port); + if (status === 0) { break; } + + count++; + } } - this._unrefTimer(); + // https://github.com/nodejs/node/blob/v12.18.3/test/parallel/test-tcp-wrap-connect.js + if (req.oncomplete) { req.oncomplete.call(status, this, req, true, true); } - this.connecting = true; - this.writable = true; + return status; + } - // if (pipe) { - // validateString(path, 'options.path'); - // defaultTriggerAsyncIdScope( - // this[async_id_symbol], internalConnect, this, path - // ); - // } else { - // lookupAndConnect(this, options); - // } + getpeername(out) { + const in4 = ZeroTier.getpeername(this._fd); + out.address = ZeroTier.inet_ntop(in4); + out.family = in4.sin_family; + out.port = in4.sin_port; + return 0 + } - const { host, port } = options; - // If host is an IP, skip performing a lookup - const addressType = net.isIP(host); - if (addressType) { - this._fd = ZeroTier.connectStream(host, port); - afterConnect(0, this, {}, true, true); - } else { - throw new Error("DNS LOOKUP NOT IMPLEMENTED"); - } + getsockname(out) { + const in4 = ZeroTier.getsockname(this._fd); + out.address = ZeroTier.inet_ntop(in4); + out.family = in4.sin_family; + out.port = in4.sin_port; + return 0; + } + + listen(port) { + // TODO + // this.onconnection + } - return this; + fchmod(mode) { + // TODO + return 0; + } +} + +class Socket extends net.Socket { + [kLastWriteQueueSize] = 0; + [kBuffer] = null; + [kBufferCb] = null; + [kBufferGen] = null; + + [kHandle] = null; + get _handle() { return this[kHandle]; } + set _handle(v) { return this[kHandle] = v; } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L929 + */ + // connect(...args) { + // let normalized; + // // If passed an array, it's treated as an array of arguments that have + // // already been normalized (so we don't normalize more than once). This has + // // been solved before in https://github.com/nodejs/node/pull/12342, but was + // // reverted as it had unintended side effects. + // if (Array.isArray(args[0])) { + // normalized = args[0]; + // } else { + // normalized = net._normalizeArgs(args); + // } + // const options = normalized[0]; + // const cb = normalized[1]; + + // if (this.write !== net.Socket.prototype.write) + // this.write = net.Socket.prototype.write; + + // if (this.destroyed) { + // this._handle = null; + // this._peername = null; + // this._sockname = null; + // } + + // if (!this._handle) { + // this._handle = new ZTCP(); + // initSocketHandle(this); + // } + + // if (cb !== null) { + // this.once('connect', cb); + // } + + // this._unrefTimer(); + + // this.connecting = true; + // this.writable = true; + + // const { host, port } = options; + // // If host is an IP, skip performing a lookup + // const addressType = net.isIP(host); + // if (addressType) { + // this._fd = ZeroTier.connectStream(host, port); + // afterConnect(0, this, {}, true, true); + // } else { + // throw new Error("DNS LOOKUP NOT IMPLEMENTED"); + // } + + // return this; + // } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L596 + */ + pause() { + if (this[kBuffer] && !this.connecting && this._handle && + this._handle.reading) { + this._handle.reading = false; + if (!this.destroyed) { + const err = this._handle.readStop(); + if (err) + this.destroy(errnoException(err, 'read')); + } + } + return stream.Duplex.prototype.pause.call(this); + } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L610 + */ + resume() { + if (this[kBuffer] && !this.connecting && this._handle && + !this._handle.reading) { + tryReadStart(this); + } + return stream.Duplex.prototype.resume.call(this); + } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L619 + */ + read(n) { + if (this[kBuffer] && !this.connecting && this._handle && + !this._handle.reading) { + tryReadStart(this); + } + return stream.Duplex.prototype.read.call(this, n); } /* * https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_read_size_1 */ - _read(size) { + _read(n) { // debug('_read'); - if (this.connecting) { + if (this.connecting || !this._handle) { // debug('_read wait for connection'); - this.once('connect', () => this._read(size)); - return - } - - if (!this.readChunk || this.readChunk.length < size) { - this.readChunk = Buffer.alloc(size) + this.once('connect', () => this._read(n)); + } else if (!this._handle.reading) { + tryReadStart(this); } - let bytes = -1 - let moreData = true - do { - bytes = ZeroTier.recv(this._fd, this.readChunk, 0) - switch (bytes) { - case -2: - throw new Error("ZeroTier Service error") - case -3: - throw new Error("ZeroTier Invalid argument") - default: - if (bytes > 0) { - // this.bytesRead += bytes - moreData = this.push(this.readChunk) - } - } - } while (bytes > 0 && moreData) + // if (!this.readChunk || this.readChunk.length < n) { + // this.readChunk = Buffer.alloc(n) + // } - if (moreData) { setTimeout(() => this._read(size), 500) } + // let bytes = -1 + // let moreData = true + // do { + // bytes = ZeroTier.recv(this._fd, this.readChunk, 0) + // switch (bytes) { + // case -2: + // throw new Error("ZeroTier Service error") + // case -3: + // throw new Error("ZeroTier Invalid argument") + // default: + // if (bytes > 0) { + // // this.bytesRead += bytes + // moreData = this.push(this.readChunk) + // } + // } + // } while (bytes > 0 && moreData) + + // if (moreData) { setTimeout(() => this._read(n), 500) } } /* @@ -302,20 +666,27 @@ class Socket extends net.Socket { /* * https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_final_callback */ - _final(callback) { - const err = ZeroTier.close(this._fd) - - switch (err) { - case -1: - return callback(new Error("ZeroTier Socket error")) - break - case -2: - return callback(new Error("ZeroTier Service error")) - break - default: - return super._final(callback) - } - } + // _final(cb) { + // // If still connecting - defer handling `_final` until 'connect' will happen + // if (this.pending) { + // // debug('_final: not yet connected'); + // return this.once('connect', () => this._final(cb)); + // } + + // if (!this._handle) + // return cb(); + + // // debug('_final: not ended, call shutdown()'); + + // // const req = new ShutdownWrap(); + // const req = {}; + // req.oncomplete = afterShutdown; + // req.handle = this._handle; + // req.callback = cb; + // // const err = this._handle.shutdown(req); + // const err = ZeroTier.shutdown(this._fd); + // return afterShutdown.call(req, this, 0); + // } /* * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L760 @@ -334,34 +705,74 @@ class Socket extends net.Socket { } this._pendingData = null; this._pendingEncoding = ''; - - // if (!this._handle) { - // cb(new ERR_SOCKET_CLOSED()); - // return false; - // } - + + if (!this._handle) { + cb(new Error('ERR_SOCKET_CLOSED')); + return false; + } + this._unrefTimer(); - + let req; if (writev) req = writevGeneric(this, data, cb); else req = writeGeneric(this, data, encoding, cb); - if (req.async) { - // this[kLastWriteQueueSize] = req.bytes; + if (req.async) + this[kLastWriteQueueSize] = req.bytes; + } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L552 + */ + get bufferSize() { + if (this._handle) { + return this[kLastWriteQueueSize] + this.writableLength; } } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L756 + */ + [kAfterAsyncWrite]() { + this[kLastWriteQueueSize] = 0; + } + + /* + * https://github.com/nodejs/node/blob/v12.18.3/lib/net.js#L468 + */ + _onTimeout() { + const handle = this._handle; + const lastWriteQueueSize = this[kLastWriteQueueSize]; + if (lastWriteQueueSize > 0 && handle) { + // `lastWriteQueueSize !== writeQueueSize` means there is + // an active write in progress, so we suppress the timeout. + const { writeQueueSize } = handle; + if (lastWriteQueueSize !== writeQueueSize) { + this[kLastWriteQueueSize] = writeQueueSize; + this._unrefTimer(); + return; + } + } + // debug('_onTimeout'); + this.emit('timeout'); + } + + get [kUpdateTimer]() { + return this._unrefTimer; + } } module.exports = { - example, start: ZeroTier.start, join: ZeroTier.join, + restart: ZeroTier.restart, + stop: ZeroTier.stop, + free: ZeroTier.free, + example, connect, createConnection: connect, Socket, Stream: Socket, // Legacy naming - restart: ZeroTier.restart, - stop: ZeroTier.stop, - free: ZeroTier.free, + TCP: ZTCP, }; diff --git a/examples/node/stream_commons.js b/examples/node/stream_commons.js new file mode 100644 index 00000000..072641d2 --- /dev/null +++ b/examples/node/stream_commons.js @@ -0,0 +1,242 @@ +'use strict'; + +const kMaybeDestroy = Symbol('kMaybeDestroy'); +const kUpdateTimer = Symbol('kUpdateTimer'); +const kAfterAsyncWrite = Symbol('kAfterAsyncWrite'); +const kHandle = Symbol('kHandle'); +const kSession = Symbol('kSession'); + +// const debug = require('internal/util/debuglog').debuglog('stream'); +const kBuffer = Symbol('kBuffer'); +const kBufferGen = Symbol('kBufferGen'); +const kBufferCb = Symbol('kBufferCb'); + +let excludedStackFn; + +function errnoException(err, syscall, original) { + // TODO(joyeecheung): We have to use the type-checked + // getSystemErrorName(err) to guard against invalid arguments from users. + // This can be replaced with [ code ] = errmap.get(err) when this method + // is no longer exposed to user land. + if (util === undefined) util = require('util'); + const code = util.getSystemErrorName(err); + const message = original ? + `${syscall} ${code} ${original}` : `${syscall} ${code}`; + + // eslint-disable-next-line no-restricted-syntax + const ex = new Error(message); + // TODO(joyeecheung): errno is supposed to err, like in uvException + ex.code = ex.errno = code; + ex.syscall = syscall; + + // eslint-disable-next-line no-restricted-syntax + Error.captureStackTrace(ex, excludedStackFn || errnoException); + return ex; +} + +function handleWriteReq(req, data, encoding) { + const { handle } = req; + + switch (encoding) { + case 'buffer': + { + const ret = handle.writeBuffer(req, data); + // if (streamBaseState[kLastWriteWasAsync]) + // req.buffer = data; + return ret; + } + case 'latin1': + case 'binary': + return handle.writeLatin1String(req, data); + case 'utf8': + case 'utf-8': + return handle.writeUtf8String(req, data); + case 'ascii': + return handle.writeAsciiString(req, data); + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return handle.writeUcs2String(req, data); + default: + { + const buffer = Buffer.from(data, encoding); + const ret = handle.writeBuffer(req, buffer); + // if (streamBaseState[kLastWriteWasAsync]) + // req.buffer = buffer; + return ret; + } + } +} + +function onWriteComplete(status) { + debug('onWriteComplete', status, this.error); + + const stream = this.handle[owner_symbol]; + + if (stream.destroyed) { + if (typeof this.callback === 'function') + this.callback(null); + return; + } + + if (status < 0) { + const ex = errnoException(status, 'write', this.error); + stream.destroy(ex, this.callback); + return; + } + + stream[kUpdateTimer](); + stream[kAfterAsyncWrite](this); + + if (typeof this.callback === 'function') + this.callback(null); +} + +function createWriteWrap(handle) { + // const req = new WriteWrap(); + const req = {}; + + req.handle = handle; + req.oncomplete = onWriteComplete; + req.async = false; + req.bytes = 0; + req.buffer = null; + + return req; +} + +function writevGeneric(self, data, cb) { + const req = createWriteWrap(self[kHandle]); + const allBuffers = data.allBuffers; + let chunks; + if (allBuffers) { + chunks = data; + for (let i = 0; i < data.length; i++) + data[i] = data[i].chunk; + } else { + chunks = new Array(data.length << 1); + for (let i = 0; i < data.length; i++) { + const entry = data[i]; + chunks[i * 2] = entry.chunk; + chunks[i * 2 + 1] = entry.encoding; + } + } + const err = req.handle.writev(req, chunks, allBuffers); + + // Retain chunks + if (err === 0) req._chunks = chunks; + + afterWriteDispatched(self, req, err, cb); + return req; +} + +function writeGeneric(self, data, encoding, cb) { + const req = createWriteWrap(self[kHandle]); + const err = handleWriteReq(req, data, encoding); + + afterWriteDispatched(self, req, err, cb); + return req; +} + +function afterWriteDispatched(self, req, err, cb) { + // req.bytes = streamBaseState[kBytesWritten]; + // req.async = !!streamBaseState[kLastWriteWasAsync]; + + if (err !== 0) + return self.destroy(errnoException(err, 'write', req.error), cb); + + if (!req.async) { + cb(); + } else { + req.callback = cb; + } +} + +function onStreamRead(arrayBuffer, offset, nread) { + // const nread = streamBaseState[kReadBytesOrError]; + + const handle = this; + const stream = this[owner_symbol]; + + stream[kUpdateTimer](); + + if (nread > 0 && !stream.destroyed) { + let ret; + let result; + const userBuf = stream[kBuffer]; + if (userBuf) { + result = (stream[kBufferCb](nread, userBuf) !== false); + const bufGen = stream[kBufferGen]; + if (bufGen !== null) { + const nextBuf = bufGen(); + if (isUint8Array(nextBuf)) + stream[kBuffer] = ret = nextBuf; + } + } else { + // const offset = streamBaseState[kArrayBufferOffset]; + const buf = Buffer.from(arrayBuffer, offset, nread); + result = stream.push(buf); + } + if (!result) { + handle.reading = false; + if (!stream.destroyed) { + const err = handle.readStop(); + if (err) + stream.destroy(errnoException(err, 'read')); + } + } + + return ret; + } + + if (nread === 0) { + return; + } + + // if (nread !== UV_EOF) { + // return stream.destroy(errnoException(nread, 'read')); + // } + + // Defer this until we actually emit end + if (stream._readableState.endEmitted) { + if (stream[kMaybeDestroy]) + stream[kMaybeDestroy](); + } else { + if (stream[kMaybeDestroy]) + stream.on('end', stream[kMaybeDestroy]); + + // TODO(ronag): Without this `readStop`, `onStreamRead` + // will be called once more (i.e. after Readable.ended) + // on Windows causing a ECONNRESET, failing the + // test-https-truncate test. + if (handle.readStop) { + const err = handle.readStop(); + if (err) + return stream.destroy(errnoException(err, 'read')); + } + + // Push a null to signal the end of data. + // Do it before `maybeDestroy` for correct order of events: + // `end` -> `close` + stream.push(null); + stream.read(0); + } +} + +module.exports = { + errnoException, + createWriteWrap, + writevGeneric, + writeGeneric, + onStreamRead, + kAfterAsyncWrite, + kMaybeDestroy, + kUpdateTimer, + kHandle, + kSession, + // setStreamTimeout, + kBuffer, + kBufferCb, + kBufferGen +}; diff --git a/examples/node/test.js b/examples/node/test.js index 2aeb3cb4..91c3a820 100644 --- a/examples/node/test.js +++ b/examples/node/test.js @@ -2,7 +2,7 @@ const libzt = require('./libzt') -// libzt.example() +// libzt.example("8056c2e21c000001", "29.49.7.203", 4444) libzt.start(".zerotier", 9994) @@ -10,15 +10,16 @@ libzt.join("8056c2e21c000001") // Usage: `nc -lv 4444` let client = libzt.createConnection({ port: 4444, host: '29.49.7.203' }, () => { - // 'connect' listener. console.log('connected to server!'); - // client.write('world!\r\n'); }); -client.write("Name?\n", 'utf8'); +client.on('ready', () => { + client.write("Name?\n", 'utf8'); +}); client.on('data', (data) => { - console.log(data.toString()); - client.end(); + console.log(data.toString('utf8').trimEnd()); + if (data.toString('utf8').includes("exit")) { client.end(); } }); client.on('end', () => { console.log('disconnected from server'); + libzt.stop() }); From 57f7799e4f382385613f85f715ae20a9f876f89e Mon Sep 17 00:00:00 2001 From: heri16 <527101+heri16@users.noreply.github.com> Date: Fri, 7 Aug 2020 19:25:36 +0800 Subject: [PATCH 7/7] Update README --- README.md | 3 +++ examples/node/README.md | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 examples/node/README.md diff --git a/README.md b/README.md index 965ceba4..e92057c4 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ To build both `release` and `debug` libraries for only your host's architecture ``` make update; make patch; make host +# OR +brew install cmake +make clean; make update && make patch && make host_release CC=clang CXX=clang++ ``` Typical build output: diff --git a/examples/node/README.md b/examples/node/README.md new file mode 100644 index 00000000..f5a62609 --- /dev/null +++ b/examples/node/README.md @@ -0,0 +1,35 @@ +## Building from source + +The npm install script will attempt to statically link to `libzt.a`. + +You first need `Cmake` to build the fPIC-version of the `libzt.a` library. + +To build both `release` and `debug` libraries for only your host's architecture use `make host`. Or optionally `make host_release` for release only. To build everything including things like iOS frameworks, Android packages, etc, use `make all`. Possible build targets can be seen by using `make list`. Resultant libraries will be placed in `/lib`: + +``` +brew install cmake +(cd ../.. ; make clean; make update && make patch && make host_release CC=clang CXX=clang++) +npm install +npm start +``` + +Typical build output: + +``` +lib +├── release +| └── linux-x86_64 +| ├── libzt.a +| └── libzt.so +| └── macos-x86_64 +| ├── libzt.a +└── debug + └── ... +``` + +## Licensing + +ZeroTier is licensed under the BSL version 1.1. See [LICENSE.txt](./LICENSE.txt) and the ZeroTier pricing page for details. ZeroTier is free to use internally in businesses and academic institutions and for non-commercial purposes. Certain types of commercial use such as building closed-source apps and devices based on ZeroTier or offering ZeroTier network controllers and network management as a SaaS service require a commercial license. + +A small amount of third party code is also included in ZeroTier and is not subject to our BSL license. See [AUTHORS.md](ext/ZeroTierOne/AUTHORS.md) for a list of third party code, where it is included, and the licenses that apply to it. All of the third party code in ZeroTier is liberally licensed (MIT, BSD, Apache, public domain, etc.). If you want a commercial license to use the ZeroTier SDK in your product contact us directly via [contact@zerotier.com](mailto:contact@zerotier.com) +