Expand C API and simplify NodeService
This commit is contained in:
97
examples/c/adhoc.c
Normal file
97
examples/c/adhoc.c
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Pingable node joined to controller-less adhoc network with a 6PLANE addressing scheme
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
|
||||
Ad-hoc Network:
|
||||
|
||||
ffSSSSEEEE000000
|
||||
| | | |
|
||||
| | | Reserved for future use, must be 0
|
||||
| | End of port range (hex)
|
||||
| Start of port range (hex)
|
||||
Reserved ZeroTier address prefix indicating a controller-less network.
|
||||
|
||||
Ad-hoc networks are public (no access control) networks that have no network controller. Instead
|
||||
their configuration and other credentials are generated locally. Ad-hoc networks permit only IPv6
|
||||
UDP and TCP unicast traffic (no multicast or broadcast) using 6plane format NDP-emulated IPv6
|
||||
addresses. In addition an ad-hoc network ID encodes an IP port range. UDP packets and TCP SYN
|
||||
(connection open) packets are only allowed to destination ports within the encoded range.
|
||||
|
||||
For example ff00160016000000 is an ad-hoc network allowing only SSH, while ff0000ffff000000 is an
|
||||
ad-hoc network allowing any UDP or TCP port.
|
||||
|
||||
Keep in mind that these networks are public and anyone in the entire world can join them. Care must
|
||||
be taken to avoid exposing vulnerable services or sharing unwanted files or other resources.
|
||||
|
||||
*/
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 3) {
|
||||
printf("\nUsage:\n");
|
||||
printf("adhoc <adhocStartPort> <adhocEndPort>\n");
|
||||
exit(0);
|
||||
}
|
||||
int err = ZTS_ERR_OK;
|
||||
|
||||
uint16_t adhocStartPort = atoi(argv[1]); // Start of port range your application will use
|
||||
uint16_t adhocEndPort = atoi(argv[2]); // End of port range your application will use
|
||||
uint64_t net_id = zts_net_compute_adhoc_id(adhocStartPort, adhocEndPort);
|
||||
|
||||
// Start node and get identity
|
||||
|
||||
printf("Starting node...\n");
|
||||
zts_node_start();
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
uint64_t node_id = zts_node_get_id();
|
||||
printf("My public identity (node ID) is %llx\n", node_id);
|
||||
char keypair[ZTS_ID_STR_BUF_LEN] = { 0 };
|
||||
uint16_t len = ZTS_ID_STR_BUF_LEN;
|
||||
if (zts_node_get_id_pair(keypair, &len) != ZTS_ERR_OK) {
|
||||
printf("Error getting identity keypair. Exiting.\n");
|
||||
}
|
||||
printf("Identity [public/secret pair] = %s\n", keypair);
|
||||
|
||||
// Join the adhoc network
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
// Get address
|
||||
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
if ((err = zts_addr_compute_rfc4193_str(net_id, node_id, ipstr, ZTS_IP_MAX_STR_LEN))
|
||||
!= ZTS_ERR_OK) {
|
||||
printf("Unable to compute address (error = %d). Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
printf("Join %llx from another machine and ping6 me at %s\n", net_id, ipstr);
|
||||
|
||||
// Do network stuff!
|
||||
// zts_socket, zts_connect, etc
|
||||
|
||||
while (1) {
|
||||
zts_util_delay(500); // Idle indefinitely
|
||||
}
|
||||
|
||||
printf("Stopping node\n");
|
||||
return zts_node_stop();
|
||||
}
|
||||
113
examples/c/callbackapi.c
Normal file
113
examples/c/callbackapi.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Pingable node that demonstrates basic usage of the callback API. To see
|
||||
* more exhaustive examples look at test/selftest.c
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void on_zts_event(void* msgPtr)
|
||||
{
|
||||
zts_event_msg_t* msg = (zts_event_msg_t*)msgPtr;
|
||||
// Node events
|
||||
if (msg->event_code == ZTS_EVENT_NODE_ONLINE) {
|
||||
printf("ZTS_EVENT_NODE_ONLINE --- This node's ID is %llx\n", msg->node->node_id);
|
||||
}
|
||||
if (msg->event_code == ZTS_EVENT_NODE_OFFLINE) {
|
||||
printf("ZTS_EVENT_NODE_OFFLINE --- Check your physical Internet connection, router, "
|
||||
"firewall, etc. What ports are you blocking?\n");
|
||||
}
|
||||
// Virtual network events
|
||||
if (msg->event_code == ZTS_EVENT_NETWORK_NOT_FOUND) {
|
||||
printf(
|
||||
"ZTS_EVENT_NETWORK_NOT_FOUND --- Are you sure %llx is a valid network?\n",
|
||||
msg->network->net_id);
|
||||
}
|
||||
if (msg->event_code == 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->net_id);
|
||||
}
|
||||
if (msg->event_code == 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->net_id);
|
||||
}
|
||||
// Address events
|
||||
if (msg->event_code == ZTS_EVENT_ADDR_ADDED_IP6) {
|
||||
char ipstr[ZTS_INET6_ADDRSTRLEN] = { 0 };
|
||||
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 --- Join %llx and ping me at %s\n",
|
||||
msg->addr->net_id,
|
||||
ipstr);
|
||||
}
|
||||
|
||||
// To see more exhaustive examples look at test/selftest.c
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("\nUsage:\n");
|
||||
printf("pingable-node <net_id>\n");
|
||||
exit(0);
|
||||
}
|
||||
uint64_t net_id = strtoull(argv[1], NULL, 16);
|
||||
|
||||
zts_init_set_event_handler(&on_zts_event);
|
||||
|
||||
printf("Starting node...\n");
|
||||
zts_node_start();
|
||||
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("My public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
char keypair[ZTS_ID_STR_BUF_LEN] = { 0 };
|
||||
uint16_t len = ZTS_ID_STR_BUF_LEN;
|
||||
if (zts_node_get_id_pair(keypair, &len) != ZTS_ERR_OK) {
|
||||
printf("Error getting identity keypair. Exiting.\n");
|
||||
}
|
||||
printf("Identity [public/secret pair] = %s\n", keypair);
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("Waiting for address assignment from network\n");
|
||||
int err = 0;
|
||||
while (! (err = zts_addr_is_assigned(net_id, ZTS_AF_INET))) {
|
||||
zts_util_delay(500);
|
||||
}
|
||||
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
zts_addr_get_str(net_id, ZTS_AF_INET, ipstr, ZTS_IP_MAX_STR_LEN);
|
||||
printf("Join %llx from another machine and ping me at %s\n", net_id, ipstr);
|
||||
|
||||
// Do network stuff!
|
||||
// zts_socket, zts_connect, etc
|
||||
|
||||
while (1) {
|
||||
zts_util_delay(500); // Idle indefinitely
|
||||
}
|
||||
|
||||
printf("Stopping node\n");
|
||||
return zts_node_stop();
|
||||
}
|
||||
109
examples/c/centralapi.cpp
Normal file
109
examples/c/centralapi.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
// For optional JSON parsing
|
||||
#include "../ext/ZeroTierOne/ext/json/json.hpp"
|
||||
|
||||
void process_response(char* response, int http_resp_code)
|
||||
{
|
||||
if (http_resp_code == 0) {
|
||||
// Request failed at library level, do nothing. There would be no HTTP code at this point.
|
||||
return;
|
||||
}
|
||||
printf("Raw response string (%d) = %s\n", http_resp_code, response);
|
||||
// Parse into navigable JSON object
|
||||
if (http_resp_code < 200 || http_resp_code >= 300) {
|
||||
return;
|
||||
}
|
||||
nlohmann::json res = nlohmann::json::parse(response);
|
||||
if (! res.is_object()) {
|
||||
fprintf(stderr, "Unable to parse (root element is not a JSON object)");
|
||||
}
|
||||
// Pretty print JSON blob
|
||||
std::cout << std::setw(4) << res << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 3) {
|
||||
printf("\nlibzt example central API client\n");
|
||||
printf("centralapi <central_url> <api_token>\n");
|
||||
exit(0);
|
||||
}
|
||||
char* central_url = argv[1]; // API endpoint
|
||||
char* api_token = argv[2]; // User token (generate at my.zerotier.com)
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use the ZeroTier Central API to:
|
||||
*
|
||||
* - Get the status of our hosted service (or your own)
|
||||
* - Create a network
|
||||
* - Get the full configuration of a network
|
||||
* - Authorize/Deauthorize nodes on a network
|
||||
*
|
||||
* This example does not start a node (though you can if you wish.) This portion of the
|
||||
* libzt API is merely a wrapper around our web API endpoint (https://my.zerotier.com/help/api).
|
||||
* The HTTP requests are done via libcurl. This API is thread-safe but not multi-threaded.
|
||||
*
|
||||
* Error Codes:
|
||||
* -2 : [ZTS_ERR_SERVICE] The API may not have been initialized properly
|
||||
* -3 : [ZTS_ERR_ARG] Invalid argument
|
||||
* [100-500] : Standard HTTP error codes
|
||||
*
|
||||
* Usage example: centralapi https://my.zerotier.com e7no7nVRFItge7no7cVR5Ibge7no8nV1
|
||||
*
|
||||
*/
|
||||
|
||||
int err = ZTS_ERR_OK;
|
||||
// Buffer to store server response as JSON string blobs
|
||||
char rbuf[ZTS_CENTRAL_RESP_BUF_DEFAULT_SZ] = { 0 };
|
||||
|
||||
// Provide URL to Central API server and user API token generated at https://my.zerotier.com
|
||||
printf("Initializing Central API client...\n");
|
||||
if ((err = zts_central_init(central_url, api_token, rbuf, ZTS_CENTRAL_RESP_BUF_DEFAULT_SZ))
|
||||
!= ZTS_ERR_OK) {
|
||||
fprintf(stderr, "Error while initializing client's Central API parameters\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
zts_central_set_verbose(false); // (optional) Turn on reporting from libcurl
|
||||
zts_central_set_access_mode(ZTS_CENTRAL_READ /*| ZTS_CENTRAL_WRITE*/);
|
||||
|
||||
int http_res_code = 0;
|
||||
|
||||
// Get hosted service status
|
||||
printf("Requesting Central API server status (/api/status):\n");
|
||||
if ((err = zts_central_status_get(&http_res_code)) != ZTS_ERR_OK) {
|
||||
fprintf(stderr, "Error (%d) making the request.\n", err);
|
||||
}
|
||||
else {
|
||||
process_response(rbuf, http_res_code);
|
||||
}
|
||||
// Get network config
|
||||
int64_t nwid = 0x1234567890abcdef;
|
||||
printf("Requesting network config: /api/network/%llx\n", nwid);
|
||||
if ((err = zts_central_net_get(&http_res_code, nwid)) != ZTS_ERR_OK) {
|
||||
fprintf(stderr, "Error (%d) making the request.\n", err);
|
||||
}
|
||||
else {
|
||||
process_response(rbuf, http_res_code);
|
||||
}
|
||||
// Authorize a node on a network
|
||||
int64_t nodeid = 0x9934343434;
|
||||
printf("Authorizing: /api/network/%llx/member/%llx\n", nwid, nodeid);
|
||||
if ((err = zts_central_node_auth(&http_res_code, nwid, nodeid, ZTS_CENTRAL_NODE_AUTH_TRUE))
|
||||
!= ZTS_ERR_OK) {
|
||||
fprintf(stderr, "Error (%d) making the request.\n", err);
|
||||
}
|
||||
else {
|
||||
process_response(rbuf, http_res_code);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
105
examples/c/client.c
Normal file
105
examples/c/client.c
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Simple socket-based client application
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 5) {
|
||||
printf("\nlibzt example client\n");
|
||||
printf("client <id_storage_path> <net_id> <remote_addr> <remote_port>\n");
|
||||
exit(0);
|
||||
}
|
||||
char* storage_path = argv[1];
|
||||
uint64_t net_id = strtoull(argv[2], NULL, 16);
|
||||
char* remote_addr = argv[3];
|
||||
int remote_port = atoi(argv[4]);
|
||||
int err = ZTS_ERR_OK;
|
||||
|
||||
// Initialize node
|
||||
|
||||
if ((err = zts_init_from_storage(storage_path)) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Start node
|
||||
|
||||
if ((err = zts_node_start()) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
printf("Public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
|
||||
// Join network
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
printf("Don't forget to authorize this device in my.zerotier.com or the web API!\n");
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
// Get assigned address (of the family type we care about)
|
||||
|
||||
int family = zts_util_get_ip_family(remote_addr);
|
||||
|
||||
printf("Waiting for address assignment from network\n");
|
||||
while (! (err = zts_addr_is_assigned(net_id, family))) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
zts_addr_get_str(net_id, family, ipstr, ZTS_IP_MAX_STR_LEN);
|
||||
printf("IP address on network %llx is %s\n", net_id, ipstr);
|
||||
|
||||
// BEGIN Socket Stuff
|
||||
|
||||
char* msgStr = (char*)"Welcome to the machine";
|
||||
int bytes = 0, fd;
|
||||
char recvBuf[128] = { 0 };
|
||||
memset(recvBuf, 0, sizeof(recvBuf));
|
||||
|
||||
// Connect to remote host
|
||||
|
||||
// Can also use traditional: zts_socket(), zts_connect(), etc
|
||||
|
||||
printf("Attempting to connect...\n");
|
||||
while ((fd = zts_simple_tcp_client(remote_addr, remote_port)) < 0) {
|
||||
printf("Re-attempting to connect...\n");
|
||||
}
|
||||
|
||||
// Data I/O
|
||||
|
||||
printf("Sending message string to server...\n");
|
||||
if ((bytes = zts_write(fd, msgStr, strlen(msgStr))) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, bytes, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Sent %d bytes: %s\n", bytes, msgStr);
|
||||
printf("Reading message string from server...\n");
|
||||
if ((bytes = zts_read(fd, recvBuf, sizeof(recvBuf))) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, bytes, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Read %d bytes: %s\n", bytes, recvBuf);
|
||||
|
||||
// Close
|
||||
|
||||
zts_close(fd);
|
||||
return zts_node_stop();
|
||||
}
|
||||
96
examples/c/nonblockingclient.c
Normal file
96
examples/c/nonblockingclient.c
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Simple socket-based client application
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 5) {
|
||||
printf("\nlibzt example client\n");
|
||||
printf("client <id_storage_path> <net_id> <remote_addr> <remote_port>\n");
|
||||
exit(0);
|
||||
}
|
||||
char* storage_path = argv[1];
|
||||
uint64_t net_id = strtoull(argv[2], NULL, 16);
|
||||
char* remote_addr = argv[3];
|
||||
int remote_port = atoi(argv[4]);
|
||||
int err = ZTS_ERR_OK;
|
||||
|
||||
// Initialize node
|
||||
|
||||
if ((err = zts_init_from_storage(storage_path)) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Start node
|
||||
|
||||
if ((err = zts_node_start()) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("Public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Don't forget to authorize this device in my.zerotier.com or the web API!\n");
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
// Sockets
|
||||
|
||||
char* msgStr = (char*)"Welcome to the machine";
|
||||
int bytes = 0, fd;
|
||||
char recvBuf[128] = { 0 };
|
||||
memset(recvBuf, 0, sizeof(recvBuf));
|
||||
|
||||
// Create socket
|
||||
|
||||
if ((fd = zts_socket(ZTS_AF_INET, ZTS_SOCK_STREAM, 0)) < 0) {
|
||||
printf("Error (fd=%d, zts_errno=%d). Exiting.\n", fd, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Connect
|
||||
|
||||
// Can also use:
|
||||
// zts_connect(int fd, const struct zts_sockaddr* addr, zts_socklen_t addrlen);
|
||||
while (zts_simple_connect(fd, remote_addr, remote_port, 0) != ZTS_ERR_OK) {
|
||||
printf("Attempting to connect...\n");
|
||||
}
|
||||
|
||||
// Data I/O
|
||||
|
||||
// Wait random intervals to send a message to the server
|
||||
// The non-blocking aspect of this example is server-side
|
||||
while (1) {
|
||||
if ((bytes = zts_send(fd, msgStr, strlen(msgStr), 0)) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, bytes, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("zts_send()=%d\n", bytes);
|
||||
zts_util_delay((rand() % 100) * 50);
|
||||
}
|
||||
|
||||
zts_close(fd);
|
||||
return zts_node_stop();
|
||||
}
|
||||
169
examples/c/nonblockingserver.c
Normal file
169
examples/c/nonblockingserver.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Simple socket-based server application
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 5) {
|
||||
printf("\nlibzt example server\n");
|
||||
printf("server <id_storage_path> <net_id> <local_addr> <local_port>\n");
|
||||
exit(0);
|
||||
}
|
||||
char* storage_path = argv[1];
|
||||
uint64_t net_id = strtoull(argv[2], NULL, 16);
|
||||
char* local_addr = argv[3];
|
||||
int local_port = atoi(argv[4]);
|
||||
int fd, accfd;
|
||||
int err = ZTS_ERR_OK;
|
||||
|
||||
// Initialize node
|
||||
|
||||
if ((err = zts_init_from_storage(storage_path)) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Start node
|
||||
|
||||
if ((err = zts_node_start()) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("Public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Don't forget to authorize this device in my.zerotier.com or the web API!\n");
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("Waiting for address assignment from network\n");
|
||||
while (! (err = zts_addr_is_assigned(net_id, ZTS_AF_INET))) {
|
||||
zts_util_delay(500);
|
||||
}
|
||||
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
zts_addr_get_str(net_id, ZTS_AF_INET, ipstr, ZTS_IP_MAX_STR_LEN);
|
||||
printf("Assigned IP address: %s\n", ipstr);
|
||||
|
||||
// Sockets
|
||||
|
||||
printf("Creating socket...\n");
|
||||
if ((fd = zts_socket(ZTS_AF_INET, ZTS_SOCK_STREAM, 0)) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, err, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Binding...\n");
|
||||
// Can also use:
|
||||
// zts_bind(int fd, const struct zts_sockaddr* addr, zts_socklen_t addrlen)
|
||||
if ((err = zts_simple_bind(fd, local_addr, local_port) < 0)) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, err, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Listening...\n");
|
||||
int backlog = 100;
|
||||
if ((err = zts_listen(fd, backlog)) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, err, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int bytes = 0;
|
||||
char recvBuf[128] = { 0 };
|
||||
|
||||
while (1) {
|
||||
// Accept
|
||||
// Can also use
|
||||
// zts_accept(int fd, struct zts_sockaddr* addr, zts_socklen_t* addrlen)
|
||||
|
||||
char ipstr[ZTS_INET6_ADDRSTRLEN] = { 0 };
|
||||
int port = 0;
|
||||
printf("Accepting on listening socket...\n");
|
||||
if ((accfd = zts_simple_accept(fd, ipstr, ZTS_INET6_ADDRSTRLEN, &port)) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, err, zts_errno);
|
||||
}
|
||||
printf("Accepted connection from %s:%d\n", ipstr, port);
|
||||
}
|
||||
|
||||
// Data I/O
|
||||
|
||||
// Technique 1: ZTS_O_NONBLOCK
|
||||
if (0) {
|
||||
zts_fcntl(fd, ZTS_F_SETFL, ZTS_O_NONBLOCK);
|
||||
zts_fcntl(accfd, ZTS_F_SETFL, ZTS_O_NONBLOCK);
|
||||
while (1) {
|
||||
bytes = zts_recv(accfd, recvBuf, sizeof(recvBuf), 0);
|
||||
printf("zts_recv(%d, ...)=%d\n", accfd, bytes);
|
||||
zts_util_delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
// Technique 2: zts_select
|
||||
if (0) {
|
||||
struct zts_timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 50000;
|
||||
int result = 0;
|
||||
zts_fd_set active_fd_set, read_fd_set;
|
||||
ZTS_FD_ZERO(&active_fd_set);
|
||||
ZTS_FD_SET(accfd, &active_fd_set);
|
||||
while (1) {
|
||||
read_fd_set = active_fd_set;
|
||||
if ((result = zts_select(ZTS_FD_SETSIZE, &read_fd_set, NULL, NULL, &tv) < 0)) {
|
||||
// perror ("select");
|
||||
exit(1);
|
||||
}
|
||||
for (int i = 0; i < ZTS_FD_SETSIZE; i++) {
|
||||
if (ZTS_FD_ISSET(i, &read_fd_set)) {
|
||||
bytes = zts_recv(accfd, recvBuf, sizeof(recvBuf), 0);
|
||||
printf("zts_recv(%d, ...)=%d\n", i, bytes);
|
||||
}
|
||||
// ZTS_FD_CLR(i, &active_fd_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Technique 3: zts_poll
|
||||
if (1) {
|
||||
int numfds = 0;
|
||||
struct zts_pollfd poll_set[16];
|
||||
memset(poll_set, '\0', sizeof(poll_set));
|
||||
poll_set[0].fd = accfd;
|
||||
poll_set[0].events = ZTS_POLLIN;
|
||||
numfds++;
|
||||
int result = 0;
|
||||
int timeout_ms = 50;
|
||||
while (1) {
|
||||
result = zts_poll(poll_set, numfds, timeout_ms);
|
||||
printf("zts_poll()=%d\n", result);
|
||||
for (int i = 0; i < numfds; i++) {
|
||||
if (poll_set[i].revents & ZTS_POLLIN) {
|
||||
bytes = zts_recv(poll_set[i].fd, recvBuf, sizeof(recvBuf), 0);
|
||||
printf("zts_recv(%d, ...)=%d\n", i, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = zts_close(fd);
|
||||
return zts_node_stop();
|
||||
}
|
||||
109
examples/c/nostorage.c
Normal file
109
examples/c/nostorage.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Demonstrates how to manage ZeroTier node identities (public/secret keypairs) without
|
||||
* local storage (e.g. zts_init_from_storage().)
|
||||
*
|
||||
* WARNING: This prints secret keys to your terminal.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
char cache_data[ZTS_STORE_DATA_LEN];
|
||||
|
||||
void on_zts_event(void* msgPtr)
|
||||
{
|
||||
zts_event_msg_t* msg = (zts_event_msg_t*)msgPtr;
|
||||
int len = msg->len; // Length of message (or structure)
|
||||
|
||||
if (msg->event_code == ZTS_EVENT_NODE_ONLINE) {
|
||||
printf("ZTS_EVENT_NODE_ONLINE\n");
|
||||
}
|
||||
|
||||
// Copy data to a buffer that you have allocated or write it to storage.
|
||||
// The data pointed to by msg->cache will be invalid after this function
|
||||
// returns.
|
||||
|
||||
memset(cache_data, 0, ZTS_STORE_DATA_LEN);
|
||||
|
||||
if (msg->event_code == ZTS_EVENT_STORE_IDENTITY_PUBLIC) {
|
||||
printf("ZTS_EVENT_STORE_IDENTITY_PUBLIC (len=%d)\n", msg->len);
|
||||
printf("identity.public = [ %.*s ]\n", len, msg->cache);
|
||||
memcpy(cache_data, msg->cache, len);
|
||||
}
|
||||
if (msg->event_code == ZTS_EVENT_STORE_IDENTITY_SECRET) {
|
||||
printf("ZTS_EVENT_STORE_IDENTITY_SECRET (len=%d)\n", msg->len);
|
||||
printf("identity.secret = [ %.*s ]\n", len, msg->cache);
|
||||
memcpy(cache_data, msg->cache, len);
|
||||
// Same data can be retrieved via: zts_node_get_id_pair()
|
||||
}
|
||||
if (msg->event_code == ZTS_EVENT_STORE_PLANET) {
|
||||
printf("ZTS_EVENT_STORE_PLANET (len=%d)\n", msg->len);
|
||||
// Binary data
|
||||
memcpy(cache_data, msg->cache, len);
|
||||
}
|
||||
if (msg->event_code == ZTS_EVENT_STORE_PEER) {
|
||||
printf("ZTS_EVENT_STORE_PEER (len=%d)\n", msg->len);
|
||||
// Binary data
|
||||
memcpy(cache_data, msg->cache, len);
|
||||
}
|
||||
if (msg->event_code == ZTS_EVENT_STORE_NETWORK) {
|
||||
printf("ZTS_EVENT_STORE_NETWORK (len=%d)\n", msg->len);
|
||||
// Binary data
|
||||
memcpy(cache_data, msg->cache, len);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int err = ZTS_ERR_OK;
|
||||
|
||||
// Initialize node
|
||||
|
||||
zts_init_set_event_handler(&on_zts_event);
|
||||
|
||||
// Start node
|
||||
|
||||
printf("Starting node...\n");
|
||||
int generate_new_id = 1;
|
||||
if (generate_new_id) {
|
||||
// OPTION A
|
||||
// Generate new automatically ID if no prior init called
|
||||
zts_node_start();
|
||||
}
|
||||
else {
|
||||
// OPTION B
|
||||
// Copy your key here
|
||||
char identity[ZTS_ID_STR_BUF_LEN] = { 0 };
|
||||
int len = ZTS_ID_STR_BUF_LEN;
|
||||
|
||||
// Generate key (optional):
|
||||
// int key_len;
|
||||
// zts_id_new(identity, &key_len);
|
||||
|
||||
// Load pre-existing identity from buffer
|
||||
zts_init_from_memory(identity, len);
|
||||
zts_node_start();
|
||||
}
|
||||
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
// Do network stuff!
|
||||
// zts_socket, zts_connect, etc
|
||||
|
||||
printf("Node %llx is now online. Idling.\n", zts_node_get_id());
|
||||
while (1) {
|
||||
zts_util_delay(500); // Idle indefinitely
|
||||
}
|
||||
|
||||
printf("Stopping node\n");
|
||||
return zts_node_stop();
|
||||
}
|
||||
67
examples/c/pingable-node.c
Normal file
67
examples/c/pingable-node.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Pingable node
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("\nUsage:\n");
|
||||
printf("pingable-node <net_id>\n");
|
||||
exit(0);
|
||||
}
|
||||
uint64_t net_id = strtoull(argv[1], NULL, 16);
|
||||
|
||||
printf("Starting node...\n");
|
||||
zts_node_start();
|
||||
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("My public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
char keypair[ZTS_ID_STR_BUF_LEN] = { 0 };
|
||||
uint16_t len = ZTS_ID_STR_BUF_LEN;
|
||||
if (zts_node_get_id_pair(keypair, &len) != ZTS_ERR_OK) {
|
||||
printf("Error getting identity keypair. Exiting.\n");
|
||||
}
|
||||
printf("Identity [public/secret pair] = %s\n", keypair);
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("Waiting for address assignment from network\n");
|
||||
int err = 0;
|
||||
while (! (err = zts_addr_is_assigned(net_id, ZTS_AF_INET))) {
|
||||
zts_util_delay(500);
|
||||
}
|
||||
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
zts_addr_get_str(net_id, ZTS_AF_INET, ipstr, ZTS_IP_MAX_STR_LEN);
|
||||
printf("Join %llx from another machine and ping me at %s\n", net_id, ipstr);
|
||||
|
||||
// Do network stuff!
|
||||
// zts_socket, zts_connect, etc
|
||||
|
||||
while (1) {
|
||||
zts_util_delay(500); // Idle indefinitely
|
||||
}
|
||||
|
||||
printf("Stopping node\n");
|
||||
return zts_node_stop();
|
||||
}
|
||||
111
examples/c/server.c
Normal file
111
examples/c/server.c
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Simple socket-based server application
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 5) {
|
||||
printf("\nlibzt example server\n");
|
||||
printf("server <id_storage_path> <net_id> <local_addr> <local_port>\n");
|
||||
exit(0);
|
||||
}
|
||||
char* storage_path = argv[1];
|
||||
uint64_t net_id = strtoull(argv[2], NULL, 16);
|
||||
char* local_addr = argv[3];
|
||||
int local_port = atoi(argv[4]);
|
||||
int fd, accfd;
|
||||
int err = ZTS_ERR_OK;
|
||||
|
||||
// Initialize node
|
||||
|
||||
if ((err = zts_init_from_storage(storage_path)) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Start node
|
||||
|
||||
if ((err = zts_node_start()) != ZTS_ERR_OK) {
|
||||
printf("Unable to start service, error = %d. Exiting.\n", err);
|
||||
exit(1);
|
||||
}
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
printf("Public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
|
||||
// Join network
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
printf("Don't forget to authorize this device in my.zerotier.com or the web API!\n");
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
// Get assigned address (of the family type we care about)
|
||||
|
||||
int family = zts_util_get_ip_family(local_addr);
|
||||
|
||||
printf("Waiting for address assignment from network\n");
|
||||
while (! (err = zts_addr_is_assigned(net_id, family))) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
zts_addr_get_str(net_id, family, ipstr, ZTS_IP_MAX_STR_LEN);
|
||||
printf("IP address on network %llx is %s\n", net_id, ipstr);
|
||||
|
||||
// BEGIN Socket Stuff
|
||||
|
||||
// Accept incoming connection
|
||||
|
||||
// Can also use traditional: zts_socket(), zts_bind(), zts_listen(), zts_accept(), etc.
|
||||
|
||||
char remote_addr[ZTS_INET6_ADDRSTRLEN] = { 0 };
|
||||
int remote_port = 0;
|
||||
int len = ZTS_INET6_ADDRSTRLEN;
|
||||
if ((accfd = zts_simple_tcp_server(local_addr, local_port, remote_addr, len, &remote_port))
|
||||
< 0) {
|
||||
printf("Error (fd=%d, zts_errno=%d). Exiting.\n", accfd, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Accepted connection from %s:%d\n", remote_addr, remote_port);
|
||||
|
||||
// Data I/O
|
||||
|
||||
int bytes = 0;
|
||||
char recvBuf[128] = { 0 };
|
||||
|
||||
printf("Reading message string from client...\n");
|
||||
if ((bytes = zts_read(accfd, recvBuf, sizeof(recvBuf))) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, bytes, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Read %d bytes: %s\n", bytes, recvBuf);
|
||||
printf("Sending message string to client...\n");
|
||||
if ((bytes = zts_write(accfd, recvBuf, bytes)) < 0) {
|
||||
printf("Error (fd=%d, ret=%d, zts_errno=%d). Exiting.\n", fd, bytes, zts_errno);
|
||||
exit(1);
|
||||
}
|
||||
printf("Sent %d bytes: %s\n", bytes, recvBuf);
|
||||
|
||||
// Close
|
||||
|
||||
printf("Closing connection socket\n");
|
||||
err = zts_close(accfd);
|
||||
err = zts_close(fd);
|
||||
return zts_node_stop();
|
||||
}
|
||||
130
examples/c/statistics.c
Normal file
130
examples/c/statistics.c
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* libzt C API example
|
||||
*
|
||||
* Pingable node that also displays protocol statistics that are
|
||||
* useful for debugging.
|
||||
*/
|
||||
|
||||
#include "ZeroTierSockets.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("\nUsage:\n");
|
||||
printf("pingable-node <net_id>\n");
|
||||
exit(0);
|
||||
}
|
||||
uint64_t net_id = strtoull(argv[1], NULL, 16);
|
||||
|
||||
printf("Starting node...\n");
|
||||
zts_node_start();
|
||||
|
||||
printf("Waiting for node to come online\n");
|
||||
while (! zts_node_is_online()) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("My public identity (node ID) is %llx\n", zts_node_get_id());
|
||||
char keypair[ZTS_ID_STR_BUF_LEN] = { 0 };
|
||||
uint16_t len = ZTS_ID_STR_BUF_LEN;
|
||||
if (zts_node_get_id_pair(keypair, &len) != ZTS_ERR_OK) {
|
||||
printf("Error getting identity keypair. Exiting.\n");
|
||||
}
|
||||
printf("Identity [public/secret pair] = %s\n", keypair);
|
||||
|
||||
printf("Joining network %llx\n", net_id);
|
||||
if (zts_net_join(net_id) != ZTS_ERR_OK) {
|
||||
printf("Unable to join network. Exiting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printf("Waiting for join to complete\n");
|
||||
while (zts_net_count() < 1) {
|
||||
zts_util_delay(50);
|
||||
}
|
||||
|
||||
printf("Waiting for address assignment from network\n");
|
||||
int err = 0;
|
||||
while (! (err = zts_addr_is_assigned(net_id, ZTS_AF_INET))) {
|
||||
zts_util_delay(500);
|
||||
}
|
||||
|
||||
char ipstr[ZTS_IP_MAX_STR_LEN] = { 0 };
|
||||
zts_addr_get_str(net_id, ZTS_AF_INET, ipstr, ZTS_IP_MAX_STR_LEN);
|
||||
printf("Join %llx from another machine and ping me at %s\n", net_id, ipstr);
|
||||
|
||||
// Do network stuff!
|
||||
// zts_socket, zts_connect, etc
|
||||
|
||||
// Show protocol statistics
|
||||
|
||||
zts_stats_counter_t s = { 0 };
|
||||
|
||||
while (1) {
|
||||
zts_util_delay(1000);
|
||||
if ((err = zts_stats_get_all(&s)) == ZTS_ERR_NO_RESULT) {
|
||||
printf("no results\n");
|
||||
continue;
|
||||
}
|
||||
printf("\n\n");
|
||||
|
||||
printf(
|
||||
" link_tx=%9d, link_rx=%9d, link_drop=%9d, link_err=%9d\n",
|
||||
s.link_tx,
|
||||
s.link_rx,
|
||||
s.link_drop,
|
||||
s.link_err);
|
||||
printf(
|
||||
"etharp_tx=%9d, etharp_rx=%9d, etharp_drop=%9d, etharp_err=%9d\n",
|
||||
s.etharp_tx,
|
||||
s.etharp_rx,
|
||||
s.etharp_drop,
|
||||
s.etharp_err);
|
||||
printf(
|
||||
" ip4_tx=%9d, ip4_rx=%9d, ip4_drop=%9d, ip4_err=%9d\n",
|
||||
s.ip4_tx,
|
||||
s.ip4_rx,
|
||||
s.ip4_drop,
|
||||
s.ip4_err);
|
||||
printf(
|
||||
" ip6_tx=%9d, ip6_rx=%9d, ip6_drop=%9d, ip6_err=%9d\n",
|
||||
s.ip6_tx,
|
||||
s.ip6_rx,
|
||||
s.ip6_drop,
|
||||
s.ip6_err);
|
||||
printf(
|
||||
" icmp4_tx=%9d, icmp4_rx=%9d, icmp4_drop=%9d, icmp4_err=%9d\n",
|
||||
s.icmp4_tx,
|
||||
s.icmp4_rx,
|
||||
s.icmp4_drop,
|
||||
s.icmp4_err);
|
||||
printf(
|
||||
" icmp6_tx=%9d, icmp6_rx=%9d, icmp6_drop=%9d, icmp6_err=%9d\n",
|
||||
s.icmp6_tx,
|
||||
s.icmp6_rx,
|
||||
s.icmp6_drop,
|
||||
s.icmp6_err);
|
||||
printf(
|
||||
" udp_tx=%9d, udp_rx=%9d, udp_drop=%9d, udp_err=%9d\n",
|
||||
s.udp_tx,
|
||||
s.udp_rx,
|
||||
s.udp_drop,
|
||||
s.udp_err);
|
||||
printf(
|
||||
" tcp_tx=%9d, tcp_rx=%9d, tcp_drop=%9d, tcp_err=%9d\n",
|
||||
s.tcp_tx,
|
||||
s.tcp_rx,
|
||||
s.tcp_drop,
|
||||
s.tcp_err);
|
||||
printf(
|
||||
" nd6_tx=%9d, nd6_rx=%9d, nd6_drop=%9d, nd6_err=%9d\n",
|
||||
s.nd6_tx,
|
||||
s.nd6_rx,
|
||||
s.nd6_drop,
|
||||
s.nd6_err);
|
||||
}
|
||||
return zts_node_stop();
|
||||
}
|
||||
Reference in New Issue
Block a user