diff --git a/Makefile b/Makefile
index eedce3a..48c561f 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ xcframework:
-framework build/iphoneos.xcarchive/Products/Library/Frameworks/zt.framework \
-framework build/iphonesimulator.xcarchive/Products/Library/Frameworks/zt.framework \
-output lib/zt.xcframework
-
+
rm -rf build/macosx.xcarchive
rm -rf build/iphoneos.xcarchive
rm -rf build/iphonesimulator.xcarchive
@@ -104,6 +104,13 @@ host_jar_debug:
host_jar_release:
$(DIST_BUILD_SCRIPT) host_jar "release"
host_jar: host_jar_debug host_jar_release
+
+host_pinvoke_release:
+ $(DIST_BUILD_SCRIPT) host_pinvoke "release"
+host_pinvoke_debug:
+ $(DIST_BUILD_SCRIPT) host_pinvoke "debug"
+host_pinvoke: host_pinvoke_release host_pinvoke_debug
+
host: host_debug host_release
# Build every target available on this host
diff --git a/dist.sh b/dist.sh
index fcaf424..a30efdf 100755
--- a/dist.sh
+++ b/dist.sh
@@ -263,6 +263,37 @@ host_jar()
# java -cp "lib/debug/macos-x86_64/zt.jar:examples/java/src/main/java" ExampleApp
}
+# Build all ordinary library types for current host
+host_pinvoke()
+{
+ echo "Executing task: " ${FUNCNAME[ 0 ]} "(" $1 ")"
+ NORMALIZED_OSNAME=$OSNAME
+ if [[ $OSNAME = *"darwin"* ]]; then
+ DYNAMIC_LIB_NAME="libzt.dylib"
+ NORMALIZED_OSNAME="macos"
+ fi
+ if [[ $OSNAME = *"linux"* ]]; then
+ DYNAMIC_LIB_NAME="libzt.so"
+ fi
+ # CMake build files
+ BUILD_DIR=$(pwd)/tmp/${NORMALIZED_OSNAME}-$(uname -m)-$1
+ mkdir -p $BUILD_DIR
+ # Where to place results
+ BIN_OUTPUT_DIR=$(pwd)/bin/$1/${NORMALIZED_OSNAME}-$(uname -m)
+ mkdir -p $BIN_OUTPUT_DIR
+ rm -rf $BIN_OUTPUT_DIR/*
+ LIB_OUTPUT_DIR=$(pwd)/lib/$1/${NORMALIZED_OSNAME}-$(uname -m)
+ mkdir -p $LIB_OUTPUT_DIR
+ rm -rf $LIB_OUTPUT_DIR/libzt.a $LIB_OUTPUT_DIR/$DYNAMIC_LIB_NAME $LIB_OUTPUT_DIR/libztcore.a
+ # Build
+ cmake -DZTS_PINVOKE=True -H. -B$BUILD_DIR -DCMAKE_BUILD_TYPE=$1
+ $CMAKE --build $BUILD_DIR $BUILD_CONCURRENCY
+ # Move and clean up
+ mv $BUILD_DIR/bin/* $BIN_OUTPUT_DIR
+ mv $BUILD_DIR/lib/* $LIB_OUTPUT_DIR
+ clean_post_build
+}
+
# Build all ordinary library types for current host
host()
{
diff --git a/examples/csharp/Constants.cs b/examples/csharp/Constants.cs
new file mode 100644
index 0000000..cce07ea
--- /dev/null
+++ b/examples/csharp/Constants.cs
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+using ZeroTier;
+
+namespace ZeroTier
+{
+ public class Constants
+ {
+ // General error codes
+ public static readonly int ERR_OK = 0;
+ public static readonly int ERR_SOCKET = -1;
+ public static readonly int ERR_SERVICE = -2;
+ public static readonly int ERR_ARG = -3;
+ public static readonly int ERR_NO_RESULT = -4;
+ public static readonly int ERR_GENERAL = -5;
+
+ // Node events
+ public static readonly short EVENT_NODE_UP = 200;
+ public static readonly short EVENT_NODE_ONLINE = 201;
+ public static readonly short EVENT_NODE_OFFLINE = 202;
+ public static readonly short EVENT_NODE_DOWN = 203;
+ public static readonly short EVENT_NODE_IDENTITY_COLLISION = 204;
+ public static readonly short EVENT_NODE_UNRECOVERABLE_ERROR = 205;
+ public static readonly short EVENT_NODE_NORMAL_TERMINATION = 206;
+
+ // Network events
+ public static readonly short EVENT_NETWORK_NOT_FOUND = 210;
+ public static readonly short EVENT_NETWORK_CLIENT_TOO_OLD = 211;
+ public static readonly short EVENT_NETWORK_REQ_CONFIG = 212;
+ public static readonly short EVENT_NETWORK_OK = 213;
+ public static readonly short EVENT_NETWORK_ACCESS_DENIED = 214;
+ public static readonly short EVENT_NETWORK_READY_IP4 = 215;
+ public static readonly short EVENT_NETWORK_READY_IP6 = 216;
+ public static readonly short EVENT_NETWORK_READY_IP4_IP6 = 217;
+ public static readonly short EVENT_NETWORK_DOWN = 218;
+ public static readonly short EVENT_NETWORK_UPDATE = 219;
+
+ // Network Stack events
+ public static readonly short EVENT_STACK_UP = 220;
+ public static readonly short EVENT_STACK_DOWN = 221;
+
+ // lwIP netif events
+ public static readonly short EVENT_NETIF_UP = 230;
+ public static readonly short EVENT_NETIF_DOWN = 231;
+ public static readonly short EVENT_NETIF_REMOVED = 232;
+ public static readonly short EVENT_NETIF_LINK_UP = 233;
+ public static readonly short EVENT_NETIF_LINK_DOWN = 234;
+
+ // Peer events
+ public static readonly short EVENT_PEER_DIRECT = 240;
+ public static readonly short EVENT_PEER_RELAY = 241;
+ public static readonly short EVENT_PEER_UNREACHABLE = 242;
+ public static readonly short EVENT_PEER_PATH_DISCOVERED = 243;
+ public static readonly short EVENT_PEER_PATH_DEAD = 244;
+
+ // Route events
+ public static readonly short EVENT_ROUTE_ADDED = 250;
+ public static readonly short EVENT_ROUTE_REMOVED = 251;
+
+ // Address events
+ public static readonly short EVENT_ADDR_ADDED_IP4 = 260;
+ public static readonly short EVENT_ADDR_REMOVED_IP4 = 261;
+ public static readonly short EVENT_ADDR_ADDED_IP6 = 262;
+ public static readonly short EVENT_ADDR_REMOVED_IP6 = 263;
+
+ // Socket error codes
+ public static readonly short EPERM = 1; /* Operation not permitted */
+ public static readonly short ENOENT = 2; /* No such file or directory */
+ public static readonly short ESRCH = 3; /* No such process */
+ public static readonly short EINTR = 4; /* Interrupted system call */
+ public static readonly short EIO = 5; /* I/O error */
+ public static readonly short ENXIO = 6; /* No such device or address */
+ public static readonly short E2BIG = 7; /* Arg list too long */
+ public static readonly short ENOEXEC = 8; /* Exec format error */
+ public static readonly short EBADF = 9; /* Bad file number */
+ public static readonly short ECHILD = 10; /* No child processes */
+ public static readonly short EAGAIN = 11; /* Try again */
+ public static readonly short ENOMEM = 12; /* Out of memory */
+ public static readonly short EACCES = 13; /* Permission denied */
+ public static readonly short EFAULT = 14; /* Bad address */
+ public static readonly short ENOTBLK = 15; /* Block device required */
+ public static readonly short EBUSY = 16; /* Device or resource busy */
+ public static readonly short EEXIST = 17; /* File exists */
+ public static readonly short EXDEV = 18; /* Cross-device link */
+ public static readonly short ENODEV = 19; /* No such device */
+ public static readonly short ENOTDIR = 20; /* Not a directory */
+ public static readonly short EISDIR = 21; /* Is a directory */
+ public static readonly short EINVAL = 22; /* Invalid argument */
+ public static readonly short ENFILE = 23; /* File table overflow */
+ public static readonly short EMFILE = 24; /* Too many open files */
+ public static readonly short ENOTTY = 25; /* Not a typewriter */
+ public static readonly short ETXTBSY = 26; /* Text file busy */
+ public static readonly short EFBIG = 27; /* File too large */
+ public static readonly short ENOSPC = 28; /* No space left on device */
+ public static readonly short ESPIPE = 29; /* Illegal seek */
+ public static readonly short EROFS = 30; /* Read-only file system */
+ public static readonly short EMLINK = 31; /* Too many links */
+ public static readonly short EPIPE = 32; /* Broken pipe */
+ public static readonly short EDOM = 33; /* Math argument out of domain of func */
+ public static readonly short ERANGE = 34; /* Math result not representable */
+ public static readonly short EDEADLK = 35; /* Resource deadlock would occur */
+ public static readonly short ENAMETOOLONG = 36; /* File name too long */
+ public static readonly short ENOLCK = 37; /* No record locks available */
+ public static readonly short ENOSYS = 38; /* Function not implemented */
+ public static readonly short ENOTEMPTY = 39; /* Directory not empty */
+ public static readonly short ELOOP = 40; /* Too many symbolic links encountered */
+ public static readonly short EWOULDBLOCK = EAGAIN; /* Operation would block */
+ public static readonly short ENOMSG = 42; /* No message of desired type */
+ public static readonly short EIDRM = 43; /* Identifier removed */
+ public static readonly short ECHRNG = 44; /* Channel number out of range */
+ public static readonly short EL2NSYNC = 45; /* Level 2 not synchronized */
+ public static readonly short EL3HLT = 46; /* Level 3 halted */
+ public static readonly short EL3RST = 47; /* Level 3 reset */
+ public static readonly short ELNRNG = 48; /* Link number out of range */
+ public static readonly short EUNATCH = 49; /* Protocol driver not attached */
+ public static readonly short ENOCSI = 50; /* No CSI structure available */
+ public static readonly short EL2HLT = 51; /* Level 2 halted */
+ public static readonly short EBADE = 52; /* Invalid exchange */
+ public static readonly short EBADR = 53; /* Invalid request descriptor */
+ public static readonly short EXFULL = 54; /* Exchange full */
+ public static readonly short ENOANO = 55; /* No anode */
+ public static readonly short EBADRQC = 56; /* Invalid request code */
+ public static readonly short EBADSLT = 57; /* Invalid slot */
+
+ public static readonly short EDEADLOCK = EDEADLK;
+
+ public static readonly short EBFONT = 59; /* Bad font file format */
+ public static readonly short ENOSTR = 60; /* Device not a stream */
+ public static readonly short ENODATA = 61; /* No data available */
+ public static readonly short ETIME = 62; /* Timer expired */
+ public static readonly short ENOSR = 63; /* Out of streams resources */
+ public static readonly short ENONET = 64; /* Machine is not on the network */
+ public static readonly short ENOPKG = 65; /* Package not installed */
+ public static readonly short EREMOTE = 66; /* Object is remote */
+ public static readonly short ENOLINK = 67; /* Link has been severed */
+ public static readonly short EADV = 68; /* Advertise error */
+ public static readonly short ESRMNT = 69; /* Srmount error */
+ public static readonly short ECOMM = 70; /* Communication error on send */
+ public static readonly short EPROTO = 71; /* Protocol error */
+ public static readonly short EMULTIHOP = 72; /* Multihop attempted */
+ public static readonly short EDOTDOT = 73; /* RFS specific error */
+ public static readonly short EBADMSG = 74; /* Not a data message */
+ public static readonly short EOVERFLOW = 75; /* Value too large for defined data type */
+ public static readonly short ENOTUNIQ = 76; /* Name not unique on network */
+ public static readonly short EBADFD = 77; /* File descriptor in bad state */
+ public static readonly short EREMCHG = 78; /* Remote address changed */
+ public static readonly short ELIBACC = 79; /* Can not access a needed shared library */
+ public static readonly short ELIBBAD = 80; /* Accessing a corrupted shared library */
+ public static readonly short ELIBSCN = 81; /* .lib section in a.out corrupted */
+ public static readonly short ELIBMAX = 82; /* Attempting to link in too many shared libraries */
+ public static readonly short ELIBEXEC = 83; /* Cannot exec a shared library directly */
+ public static readonly short EILSEQ = 84; /* Illegal byte sequence */
+ public static readonly short ERESTART = 85; /* Interrupted system call should be restarted */
+ public static readonly short ESTRPIPE = 86; /* Streams pipe error */
+ public static readonly short EUSERS = 87; /* Too many users */
+ public static readonly short ENOTSOCK = 88; /* Socket operation on non-socket */
+ public static readonly short EDESTADDRREQ = 89; /* Destination address required */
+ public static readonly short EMSGSIZE = 90; /* Message too long */
+ public static readonly short EPROTOTYPE = 91; /* Protocol wrong type for socket */
+ public static readonly short ENOPROTOOPT = 92; /* Protocol not available */
+ public static readonly short EPROTONOSUPPORT = 93; /* Protocol not supported */
+ public static readonly short ESOCKTNOSUPPORT = 94; /* Socket type not supported */
+ public static readonly short EOPNOTSUPP = 95; /* Operation not supported on transport endpoint */
+ public static readonly short EPFNOSUPPORT = 96; /* Protocol family not supported */
+ public static readonly short EAFNOSUPPORT = 97; /* Address family not supported by protocol */
+ public static readonly short EADDRINUSE = 98; /* Address already in use */
+ public static readonly short EADDRNOTAVAIL = 99; /* Cannot assign requested address */
+ public static readonly short ENETDOWN = 100; /* Network is down */
+ public static readonly short ENETUNREACH = 101; /* Network is unreachable */
+ public static readonly short ENETRESET = 102; /* Network dropped connection because of reset */
+ public static readonly short ECONNABORTED = 103; /* Software caused connection abort */
+ public static readonly short ECONNRESET = 104; /* Connection reset by peer */
+ public static readonly short ENOBUFS = 105; /* No buffer space available */
+ public static readonly short EISCONN = 106; /* Transport endpoint is already connected */
+ public static readonly short ENOTCONN = 107; /* Transport endpoint is not connected */
+ public static readonly short ESHUTDOWN = 108; /* Cannot send after transport endpoint shutdown */
+ public static readonly short ETOOMANYREFS = 109; /* Too many references: cannot splice */
+ public static readonly short ETIMEDOUT = 110; /* Connection timed out */
+ public static readonly short ECONNREFUSED = 111; /* Connection refused */
+ public static readonly short EHOSTDOWN = 112; /* Host is down */
+ public static readonly short EHOSTUNREACH = 113; /* No route to host */
+ public static readonly short EALREADY = 114; /* Operation already in progress */
+ public static readonly short EINPROGRESS = 115; /* Operation now in progress */
+ public static readonly short ESTALE = 116; /* Stale NFS file handle */
+ public static readonly short EUCLEAN = 117; /* Structure needs cleaning */
+ public static readonly short ENOTNAM = 118; /* Not a XENIX named type file */
+ public static readonly short ENAVAIL = 119; /* No XENIX semaphores available */
+ public static readonly short EISNAM = 120; /* Is a named type file */
+ public static readonly short EREMOTEIO = 121; /* Remote I/O error */
+ public static readonly short EDQUOT = 122; /* Quota exceeded */
+ public static readonly short ENOMEDIUM = 123; /* No medium found */
+ public static readonly short EMEDIUMTYPE = 124; /* Wrong medium type */
+
+ public static readonly short INET_ADDRSTRLEN = 16;
+ public static readonly short INET6_ADDRSTRLEN = 46;
+
+ /** 255.255.255.255 */
+ //public static readonly uint IPADDR_NONE =((uint32_t)0xffffffffUL);
+ /** 127.0.0.1 */
+ //public static readonly uint IPADDR_LOOPBACK =((uint32_t)0x7f000001UL);
+ /** 0.0.0.0 */
+ //public static readonly uint IPADDR_ANY =((uint32_t)0x00000000UL);
+ /** 255.255.255.255 */
+ //public static readonly uint IPADDR_BROADCAST =((uint32_t)0xffffffffUL);
+
+ /** 255.255.255.255 */
+ //public static readonly uint INADDR_NONE =IPADDR_NONE;
+ /** 127.0.0.1 */
+ //public static readonly uint INADDR_LOOPBACK =IPADDR_LOOPBACK;
+ /** 0.0.0.0 */
+ //public static readonly uint INADDR_ANY =IPADDR_ANY;
+ /** 255.255.255.255 */
+ //public static readonly uint INADDR_BROADCAST =IPADDR_BROADCAST;
+
+ // Socket protocol types
+ public static readonly short SOCK_STREAM = 0x0001;
+ public static readonly short SOCK_DGRAM = 0x0002;
+ public static readonly short SOCK_RAW = 0x0003;
+ // Socket family types
+ public static readonly short AF_UNSPEC = 0x0000;
+ public static readonly short AF_INET = 0x0002;
+ public static readonly short AF_INET6 = 0x000a;
+ public static readonly short PF_INET = AF_INET;
+ public static readonly short PF_INET6 = AF_INET6;
+ public static readonly short PF_UNSPEC = AF_UNSPEC;
+ // Protocol command types
+ public static readonly short IPPROTO_IP = 0x0000;
+ public static readonly short IPPROTO_ICMP = 0x0001;
+ public static readonly short IPPROTO_TCP = 0x0006;
+ public static readonly short IPPROTO_UDP = 0x0011;
+ public static readonly short IPPROTO_IPV6 = 0x0029;
+ public static readonly short IPPROTO_ICMPV6 = 0x003a;
+ public static readonly short IPPROTO_UDPLITE = 0x0088;
+ public static readonly short IPPROTO_RAW = 0x00ff;
+ // send() and recv() flags
+ public static readonly short MSG_PEEK = 0x0001;
+ public static readonly short MSG_WAITALL = 0x0002; // NOT YET SUPPORTED
+ public static readonly short MSG_OOB = 0x0004; // NOT YET SUPPORTED
+ public static readonly short MSG_DONTWAIT = 0x0008;
+ public static readonly short MSG_MORE = 0x0010;
+
+ // Macro's for defining ioctl() command values
+ /*
+ public static readonly ulong IOCPARM_MASK = 0x7fU;
+ public static readonly ulong IOC_VOID = 0x20000000UL;
+ public static readonly ulong IOC_OUT = 0x40000000UL;
+ public static readonly ulong IOC_IN = 0x80000000UL;
+ public static readonly ulong IOC_INOUT = (IOC_IN | IOC_OUT);
+ public static readonly ulong IO(x,y) = (IOC_VOID | ((x)<<8)|(y));
+ public static readonly ulong IOR(x,y,t) = (IOC_OUT | (((long)sizeof(t) & IOCPARM_MASK)<<16) | ((x)<<8) | (y));
+ public static readonly ulong IOW(x,y,t) = (IOC_IN | (((long)sizeof(t) & IOCPARM_MASK)<<16) | ((x)<<8) | (y));
+ // ioctl() commands
+ public static readonly ulong FIONREAD = IOR('f', 127, unsigned long);
+ public static readonly ulong FIONBIO = IOW('f', 126, unsigned long);
+ */
+
+ // Socket level option number
+ public static readonly short SOL_SOCKET = 0x0fff;
+ // Socket options
+ public static readonly short SO_DEBUG = 0x0001; // NOT YET SUPPORTED
+ public static readonly short SO_ACCEPTCONN = 0x0002;
+ public static readonly short SO_REUSEADDR = 0x0004;
+ public static readonly short SO_KEEPALIVE = 0x0008;
+ public static readonly short SO_DONTROUTE = 0x0010; // NOT YET SUPPORTED
+ public static readonly short SO_BROADCAST = 0x0020;
+ public static readonly short SO_USELOOPBACK = 0x0040; // NOT YET SUPPORTED
+ public static readonly short SO_LINGER = 0x0080;
+ public static readonly short SO_DONTLINGER = ((short)(~SO_LINGER));
+ public static readonly short SO_OOBINLINE = 0x0100; // NOT YET SUPPORTED
+ public static readonly short SO_REUSEPORT = 0x0200; // NOT YET SUPPORTED
+ public static readonly short SO_SNDBUF = 0x1001; // NOT YET SUPPORTED
+ public static readonly short SO_RCVBUF = 0x1002;
+ public static readonly short SO_SNDLOWAT = 0x1003; // NOT YET SUPPORTED
+ public static readonly short SO_RCVLOWAT = 0x1004; // NOT YET SUPPORTED
+ public static readonly short SO_SNDTIMEO = 0x1005;
+ public static readonly short SO_RCVTIMEO = 0x1006;
+ public static readonly short SO_ERROR = 0x1007;
+ public static readonly short SO_TYPE = 0x1008;
+ public static readonly short SO_CONTIMEO = 0x1009;
+ public static readonly short SO_NO_CHECK = 0x100a;
+ public static readonly short SO_BINDTODEVICE = 0x100b;
+ // IPPROTO_IP options
+ public static readonly short IP_TOS = 0x0001;
+ public static readonly short IP_TTL = 0x0002;
+ public static readonly short IP_PKTINFO = 0x0008;
+ // IPPROTO_TCP options
+ public static readonly short TCP_NODELAY = 0x0001;
+ public static readonly short TCP_KEEPALIVE = 0x0002;
+ public static readonly short TCP_KEEPIDLE = 0x0003;
+ public static readonly short TCP_KEEPINTVL = 0x0004;
+ public static readonly short TCP_KEEPCNT = 0x0005;
+ // IPPROTO_IPV6 options
+ public static readonly short IPV6_CHECKSUM = 0x0007; // RFC3542: calculate and insert the ICMPv6 checksum for raw sockets.
+ public static readonly short IPV6_V6ONLY = 0x001b; // RFC3493: boolean control to restrict AF_INET6 sockets to IPv6 communications only.
+ // UDPLITE options
+ public static readonly short UDPLITE_SEND_CSCOV = 0x01; // sender checksum coverage
+ public static readonly short UDPLITE_RECV_CSCOV = 0x02; // minimal receiver checksum coverage
+ // UDPLITE options
+ public static readonly short IP_MULTICAST_TTL = 5;
+ public static readonly short IP_MULTICAST_IF = 6;
+ public static readonly short IP_MULTICAST_LOOP = 7;
+
+ // Multicast options
+ public static readonly short IP_ADD_MEMBERSHIP = 3;
+ public static readonly short IP_DROP_MEMBERSHIP = 4;
+
+ public static readonly short IPV6_JOIN_GROUP = 12;
+ public static readonly short IPV6_ADD_MEMBERSHIP = IPV6_JOIN_GROUP;
+ public static readonly short IPV6_LEAVE_GROUP = 13;
+ public static readonly short IPV6_DROP_MEMBERSHIP = IPV6_LEAVE_GROUP;
+
+ // Polling options
+ public static readonly short POLLIN = 0x001;
+ public static readonly short POLLOUT = 0x002;
+ public static readonly short POLLERR = 0x004;
+ public static readonly short POLLNVAL = 0x008;
+ // Below values are unimplemented
+ public static readonly short POLLRDNORM = 0x010;
+ public static readonly short POLLRDBAND = 0x020;
+ public static readonly short POLLPRI = 0x040;
+ public static readonly short POLLWRNORM = 0x080;
+ public static readonly short POLLWRBAND = 0x100;
+ public static readonly short POLLHUP = 0x200;
+
+ public static readonly short F_GETFL = 0x0003;
+ public static readonly short F_SETFL = 0x0004;
+
+ // File status flags and file access modes for fnctl, these are bits in an int.
+ public static readonly short O_NONBLOCK = 1;
+ public static readonly short O_NDELAY = O_NONBLOCK;
+ public static readonly short O_RDONLY = 2;
+ public static readonly short O_WRONLY = 4;
+ public static readonly short O_RDWR = (short)(O_RDONLY|O_WRONLY);
+
+ public static readonly short MSG_TRUNC = 0x04;
+ public static readonly short MSG_CTRUNC = 0x08;
+
+ public static readonly short SHUT_RD = 0x0;
+ public static readonly short SHUT_WR = 0x1;
+ public static readonly short SHUT_RDWR = 0x2;
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/Event.cs b/examples/csharp/Event.cs
new file mode 100644
index 0000000..e8c24c0
--- /dev/null
+++ b/examples/csharp/Event.cs
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+using System.Net;
+
+using ZeroTier;
+
+namespace ZeroTier
+{
+ /* Convenience structures for exposing lower level operational details to the user.
+ These structures do not have the same memory layout or content as those found in
+ include/ZeroTierSockets.h */
+
+ ///
+ /// Structure containing information about the local Node.
+ ///
+ public class NodeDetails
+ {
+ // Node ID
+ public ulong nodeId;
+
+ /**
+ * The port used by the service to send and receive
+ * all encapsulated traffic
+ */
+ public ushort primaryPort;
+ public ushort secondaryPort;
+ public ushort tertiaryPort;
+
+ /**
+ * ZT version
+ */
+ public byte versionMajor;
+ public byte versionMinor;
+ public byte versionRev;
+ }
+
+ public class MulticastSubscription
+ {
+ ulong macAddress;
+ uint AdditionalDistinguishingInformation;
+ }
+
+ ///
+ /// Structure containing information about virtual networks.
+ ///
+ public class NetworkDetails
+ {
+ public ulong networkId;
+ public ulong macAddress;
+ public string networkName;
+ //public byte status;
+ public byte type;
+ public ushort mtu;
+ public bool bridgingAllowed;
+ public bool broadcastEnabled;
+ //public int portError;
+ public IPAddress[] assignedAddresses;
+ public IPAddress[] routes;
+ public MulticastSubscription[] multicastSubscroptions;
+ }
+
+ ///
+ /// Structure containing state information about the low-level ethernet driver.
+ ///
+ public class NetifDetails
+ {
+ public ulong networkId;
+ public ulong macAddress;
+ public int mtu;
+ }
+
+ ///
+ /// Structure containing routing information for networks.
+ ///
+ public class RouteDetails
+ {
+ public EndPoint target;
+ public EndPoint via;
+ public ushort flags;
+ public ushort metric;
+ }
+
+ ///
+ /// Structure containing information about remote peer reachability.
+ ///
+ public class PeerDetails
+ {
+ ulong nodeId;
+ byte versionMajor;
+ byte versionMinor;
+ byte versionRev;
+ int latency;
+ byte role;
+ IPAddress[] paths;
+ }
+
+ ///
+ /// Structure containing information about assigned addresses.
+ ///
+ public class AddrDetails
+ {
+ ulong networkId;
+ IPAddress address;
+ }
+
+ ///
+ /// Class used to convey details of a low-level network event to the user.
+ ///
+ public class Event
+ {
+ int _eventCode;
+ string _eventName;
+
+ public NodeDetails nodeDetails;
+ public NetworkDetails networkDetails;
+ public NetifDetails netifDetails;
+ public RouteDetails routeDetails;
+ public PeerDetails peerDetails;
+ public AddrDetails addrDetails;
+
+ public Event(int eventCode, string eventName)
+ {
+ _eventCode = eventCode;
+ _eventName = eventName;
+ nodeDetails = null;
+ networkDetails = null;
+ netifDetails = null;
+ routeDetails = null;
+ peerDetails = null;
+ addrDetails = null;
+ }
+
+ public int EventCode {
+ get {
+ return _eventCode;
+ }
+ }
+
+ public string EventName {
+ get {
+ return _eventName;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/Makefile b/examples/csharp/Makefile
new file mode 100644
index 0000000..ad83d2a
--- /dev/null
+++ b/examples/csharp/Makefile
@@ -0,0 +1,12 @@
+debug:
+ cd ../../ && make host_pinvoke_debug
+ cp -f ../../lib/debug/linux-x86_64/libzt.so .
+ mono-csc -out:example.exe *.cs
+
+release:
+ cd ../../ && make host_pinvoke_release
+ cp -f ../../lib/release/linux-x86_64/libzt.so .
+ mono-csc -out:example.exe *.cs
+
+clean:
+ rm -rf libzt.* example.exe
\ No newline at end of file
diff --git a/examples/csharp/Node.cs b/examples/csharp/Node.cs
new file mode 100644
index 0000000..05f4b38
--- /dev/null
+++ b/examples/csharp/Node.cs
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+using System.Runtime.InteropServices;
+using System;
+
+// Prototype of callback used by ZeroTier to signal events to C# application
+public delegate void CSharpCallbackWithStruct(IntPtr msgPtr);
+
+///
+/// ZeroTier SDK
+///
+namespace ZeroTier
+{
+ public delegate void ZeroTierManagedEventCallback(ZeroTier.Event nodeEvent);
+
+ ///
+ /// ZeroTier Node - Virtual network subsystem
+ ///
+ public class Node
+ {
+ static ulong _nodeId = 0x0;
+ static bool _isOnline = false;
+ static bool _joinedAtLeastOneNetwork = false;
+ static bool _hasBeenFreed = false;
+ string _configFilePath;
+ ushort _servicePort;
+ static ZeroTierManagedEventCallback _managedCallback;
+
+ // Callback used internally to ferry events from the C++ layer
+ static void myZeroTierEventCallback(IntPtr msgPtr)
+ {
+ // Marshal the callback message pointer to a structure that we can inspect
+ zts_callback_msg msg =
+ (zts_callback_msg)Marshal.PtrToStructure(msgPtr, typeof(zts_callback_msg));
+
+ ZeroTier.Event newEvent = null;
+
+ // Node events
+ if (msg.eventCode == Constants.EVENT_NODE_UP) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_UP");
+ }
+ if (msg.eventCode == Constants.EVENT_NODE_ONLINE) {
+ _isOnline = true;
+ // Marshal the node details pointer to a structure
+ zts_node_details details =
+ (zts_node_details)Marshal.PtrToStructure(msg.node, typeof(zts_node_details));
+ _nodeId = details.address;
+ newEvent = new ZeroTier.Event(msg.eventCode, "EVENT_NODE_ONLINE");
+ }
+ if (msg.eventCode == Constants.EVENT_NODE_OFFLINE) {
+ _isOnline = false;
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_OFFLINE");
+ }
+ if (msg.eventCode == Constants.EVENT_NODE_NORMAL_TERMINATION) {
+ _isOnline = false;
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_NORMAL_TERMINATION");
+ }
+ if (msg.eventCode == Constants.EVENT_NODE_DOWN) {
+ _isOnline = false;
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_DOWN");
+ }
+ if (msg.eventCode == Constants.EVENT_NODE_IDENTITY_COLLISION) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_IDENTITY_COLLISION");
+ _isOnline = false;
+ }
+ if (msg.eventCode == Constants.EVENT_NODE_UNRECOVERABLE_ERROR) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NODE_UNRECOVERABLE_ERROR");
+ _isOnline = false;
+ }
+
+ // Network events
+ if (msg.eventCode == Constants.EVENT_NETWORK_NOT_FOUND) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_NOT_FOUND");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_REQ_CONFIG) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_REQ_CONFIG");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_ACCESS_DENIED) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_ACCESS_DENIED");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_READY_IP4) {
+ _joinedAtLeastOneNetwork = true;
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_READY_IP4");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_READY_IP6) {
+ _joinedAtLeastOneNetwork = true;
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_READY_IP6");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_DOWN) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_DOWN");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_CLIENT_TOO_OLD) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_CLIENT_TOO_OLD");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_REQ_CONFIG) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_REQ_CONFIG");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_OK) {
+ zts_network_details unmanagedDetails =
+ (zts_network_details)Marshal.PtrToStructure(msg.network, typeof(zts_network_details));
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_OK");
+ newEvent.networkDetails = new NetworkDetails();
+ newEvent.networkDetails.networkId = unmanagedDetails.nwid;
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_ACCESS_DENIED) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_ACCESS_DENIED");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_READY_IP4_IP6) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_READY_IP4_IP6");
+ }
+ if (msg.eventCode == Constants.EVENT_NETWORK_UPDATE) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETWORK_UPDATE");
+ }
+
+ // Stack events
+ if (msg.eventCode == Constants.EVENT_STACK_UP) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_STACK_UP");
+ }
+ if (msg.eventCode == Constants.EVENT_STACK_DOWN) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_STACK_DOWN");
+ }
+
+ // Address events
+ if (msg.eventCode == Constants.EVENT_ADDR_ADDED_IP4) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_ADDED_IP4");
+ }
+ if (msg.eventCode == Constants.EVENT_ADDR_ADDED_IP6) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_ADDED_IP6");
+ }
+ if (msg.eventCode == Constants.EVENT_ADDR_REMOVED_IP4) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP4");
+ }
+ if (msg.eventCode == Constants.EVENT_ADDR_REMOVED_IP6) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP6");
+ }
+ // peer events
+ if (msg.eventCode == Constants.EVENT_PEER_DIRECT) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_DIRECT");
+ }
+ if (msg.eventCode == Constants.EVENT_PEER_RELAY) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_RELAY");
+ }
+ // newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_UNREACHABLE");
+ if (msg.eventCode == Constants.EVENT_PEER_PATH_DISCOVERED) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_PATH_DISCOVERED");
+ }
+ if (msg.eventCode == Constants.EVENT_PEER_PATH_DEAD) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_PEER_PATH_DEAD");
+ }
+
+ // Route events
+ if (msg.eventCode == Constants.EVENT_ROUTE_ADDED) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ROUTE_ADDED");
+ }
+ if (msg.eventCode == Constants.EVENT_ROUTE_REMOVED) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ROUTE_REMOVED");
+ }
+
+ // Netif events
+ if (msg.eventCode == Constants.EVENT_NETIF_UP) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_UP");
+ }
+ if (msg.eventCode == Constants.EVENT_NETIF_DOWN) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_DOWN");
+ }
+ if (msg.eventCode == Constants.EVENT_NETIF_REMOVED) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_REMOVED");
+ }
+ if (msg.eventCode == Constants.EVENT_NETIF_LINK_UP) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_LINK_UP");
+ }
+ if (msg.eventCode == Constants.EVENT_NETIF_LINK_DOWN) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_NETIF_LINK_DOWN");
+ }
+ if (msg.eventCode == Constants.EVENT_ADDR_REMOVED_IP6) {
+ newEvent = new ZeroTier.Event(msg.eventCode,"EVENT_ADDR_REMOVED_IP6");
+ }
+
+ // Pass the converted Event to the managed callback (visible to user)
+ if (newEvent != null) {
+ _managedCallback(newEvent);
+ }
+ }
+
+ ///
+ /// Creates a new Node. Start this object by calling Start().
+ ///
+ /// Where keys and config files will be stored on the filesystem
+ /// Where you would like to receive ZeroTier event notifications
+ /// The port ZeroTier will use to send its encrypted
+ ///
+ public Node(string configFilePath, ZeroTierManagedEventCallback managedCallback, UInt16 servicePort)
+ {
+ if (String.IsNullOrEmpty(configFilePath)) {
+ throw new ArgumentNullException(nameof(configFilePath));
+ }
+ if (managedCallback == null) {
+ throw new ArgumentNullException(nameof(managedCallback));
+ }
+ _nodeId = 0x0;
+ _configFilePath = configFilePath;
+ _servicePort = servicePort;
+ _managedCallback = new ZeroTierManagedEventCallback(managedCallback);
+ }
+
+ ///
+ /// Starts the ZeroTier node/service
+ ///
+ ///
+ public int Start()
+ {
+ if (_hasBeenFreed == true) {
+ throw new ObjectDisposedException("ZeroTier Node has previously been freed. Restart application to create new instance.");
+ }
+ return zts_start(_configFilePath,myZeroTierEventCallback,_servicePort);
+ }
+
+ ///
+ /// Frees (most) resources used by ZeroTier. ZeroTier may not be started again after this call.
+ ///
+ ///
+ public int Free()
+ {
+ _nodeId = 0x0;
+ _hasBeenFreed = true;
+ return zts_free();
+ }
+
+ ///
+ /// Stop all ZeroTier service activity. The service may be started again with Start().
+ ///
+ ///
+ public int Stop()
+ {
+ _nodeId = 0x0;
+ return zts_stop();
+ }
+
+ ///
+ /// Restarts the ZeroTier service, internal stack and driver. (Mostly used for debugging.)
+ ///
+ ///
+ public int Restart()
+ {
+ _nodeId = 0x0;
+ return zts_restart();
+ }
+
+ ///
+ /// Requests to join a ZeroTier network. Remember to authorize your node/device.
+ ///
+ /// Network ID
+ ///
+ public int Join(ulong nwid)
+ {
+ return zts_join(nwid);
+ }
+
+ ///
+ /// Leaves a ZeroTier network.
+ ///
+ ///
+ ///
+ public int Leave(ulong nwid)
+ {
+ return zts_leave(nwid);
+ }
+
+ ///
+ /// Returns whether the Node is online (able to reach the internet.)
+ ///
+ ///
+ public bool IsOnline()
+ {
+ return _isOnline;
+ }
+
+ ///
+ /// Returns whether routing information is available from at least one ZeroTier network.
+ ///
+ ///
+ public bool HasRoutes()
+ {
+ return _joinedAtLeastOneNetwork;
+ }
+
+ public ulong NodeId
+ {
+ get {
+ return _nodeId;
+ }
+ }
+
+ /* Structures and functions used internally to communicate with
+ lower-level C API defined in include/ZeroTierSockets.h */
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_start")]
+ static extern int zts_start(string arg1, CSharpCallbackWithStruct arg2, ushort arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_stop")]
+ static extern int zts_stop();
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_restart")]
+ static extern int zts_restart();
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_free")]
+ static extern int zts_free();
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_join")]
+ static extern int zts_join(ulong arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_leave")]
+ static extern int zts_leave(ulong arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_allow_network_caching")]
+ static extern int zts_allow_network_caching(byte arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_allow_peer_caching")]
+ static extern int zts_allow_peer_caching(byte arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_allow_local_conf")]
+ static extern int zts_allow_local_conf(byte arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_orbit")]
+ static extern int zts_orbit(ulong arg1, ulong arg2);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_deorbit")]
+ static extern int zts_deorbit(ulong arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_get_6plane_addr")]
+ static extern int zts_get_6plane_addr(IntPtr arg1, ulong arg2, ulong arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_get_rfc4193_addr")]
+ static extern int zts_get_rfc4193_addr(IntPtr arg1, ulong arg2, ulong arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_generate_adhoc_nwid_from_range")]
+ static extern ulong zts_generate_adhoc_nwid_from_range(ushort arg1, ushort arg2);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_delay_ms")]
+ static extern void zts_delay_ms(int arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_errno_get")]
+ static extern int zts_errno_get();
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_node_details
+ {
+ public ulong address;
+ }
+
+ /**
+ * Virtual network configuration
+ */
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_network_details
+ {
+ /**
+ * 64-bit ZeroTier network ID
+ */
+ public ulong nwid;
+
+ /**
+ * Ethernet MAC (48 bits) that should be assigned to port
+ */
+ public ulong mac;
+
+ /**
+ * Network name (from network configuration master)
+ */
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
+ public byte[] name;
+
+ /**
+ * Network configuration request status
+ */
+ public byte status; // ?
+
+ /**
+ * Network type
+ */
+ public byte type; // ?
+
+ /**
+ * Maximum interface MTU
+ */
+ public uint mtu;
+
+ /**
+ * If nonzero, the network this port belongs to indicates DHCP availability
+ *
+ * This is a suggestion. The underlying implementation is free to ignore it
+ * for security or other reasons. This is simply a netconf parameter that
+ * means 'DHCP is available on this network.'
+ */
+ public int dhcp;
+
+ /**
+ * If nonzero, this port is allowed to bridge to other networks
+ *
+ * This is informational. If this is false (0), bridged packets will simply
+ * be dropped and bridging won't work.
+ */
+ public int bridge;
+
+ /**
+ * If nonzero, this network supports and allows broadcast (ff:ff:ff:ff:ff:ff) traffic
+ */
+ public int broadcastEnabled;
+
+ /**
+ * If the network is in PORT_ERROR state, this is the (negative) error code most recently reported
+ */
+ public int portError;
+
+ /**
+ * Revision number as reported by controller or 0 if still waiting for config
+ */
+ public ulong netconfRevision;
+
+ /**
+ * Number of assigned addresses
+ */
+ public uint assignedAddressCount;
+
+ /**
+ * ZeroTier-assigned addresses (in sockaddr_storage structures)
+ *
+ * For IP, the port number of the sockaddr_XX structure contains the number
+ * of bits in the address netmask. Only the IP address and port are used.
+ * Other fields like interface number can be ignored.
+ *
+ * This is only used for ZeroTier-managed address assignments sent by the
+ * virtual network's configuration master.
+ */
+ //struct zts_sockaddr_storage assignedAddresses[ZTS_MAX_ZT_ASSIGNED_ADDRESSES];
+
+ /**
+ * Number of ZT-pushed routes
+ */
+ public uint routeCount;
+
+ /**
+ * Routes (excluding those implied by assigned addresses and their masks)
+ */
+ //ZTS_VirtualNetworkRoute routes[ZTS_MAX_NETWORK_ROUTES];
+
+ /**
+ * Number of multicast groups subscribed
+ */
+ public uint multicastSubscriptionCount;
+
+ /**
+ * Multicast groups to which this network's device is subscribed
+ */
+ //struct {
+ // uint64_t mac; /* MAC in lower 48 bits */
+ // uint32_t adi; /* Additional distinguishing information, usually zero except for IPv4 ARP groups */
+ //} multicastSubscriptions[ZTS_MAX_MULTICAST_SUBSCRIPTIONS];
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_callback_msg
+ {
+ public short eventCode;
+ [MarshalAs(UnmanagedType.LPStruct, SizeConst = 4)]
+ public IntPtr node;
+ public IntPtr network;
+ }
+
+
+ ///
+ /// Gets the value of errno from the unmanaged region
+ ///
+ ///
+ public static int ErrNo {
+ get {
+ return zts_errno_get();
+ }
+ }
+ }
+}
diff --git a/examples/csharp/README.md b/examples/csharp/README.md
new file mode 100644
index 0000000..6c8bd99
--- /dev/null
+++ b/examples/csharp/README.md
@@ -0,0 +1,45 @@
+ZeroTier Sockets for C# .NET (Work In Progress)
+=====
+
+This library is a re-implementation of the .NET socket class ([System.Net.Sockets.Socket](https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket)) built atop ZeroTier's SDK using P/INVOKE and is designed to be a direct drop-in replacement. The library consists of three main objects: `ZeroTier.Node`, `ZeroTier.Event`, and `ZeroTier.Socket`. No code change is required in your application beyond a small snippet of startup code, renaming `Socket` to `ZeroTier.Socket` (where applicable) and handling a smattering of events.
+
+
+tl;dr:
+
+```
+using System.Net.Sockets;
+using ZeroTier;
+
+void myCallback(ZeroTier.Event e)
+{
+ Console.WriteLine("{0} ({1})", e.EventCode, e.EventName);
+}
+...
+
+ZeroTier.Node node = new ZeroTier.Node("path", myCallback, 9991);
+
+node.Start();
+node.Join(0xc287ac0b42a6fb4c);
+
+...
+
+ZeroTier.Socket sock = new ZeroTier.Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+
+sock.Connect(remoteEndPoint);
+
+...
+
+node.Stop();
+```
+See [example.cs](./example.cs) for a complete client/server implementation.
+
+## Building and running the example
+
+```
+make debug|release
+./example.exe
+```
+
+## Development notes
+
+The SWIG interface file `zt.i` is only present for historical reference purposes. SWIG generates a ton of unnecessary boilerplate code which is hard to completely prevent using hints. You can generate a new wrapper for yourself using `swig -c++ -csharp -dllimport "./libzt.so" zt.i` but I would not recommend doing so unless you know what you're in for.
\ No newline at end of file
diff --git a/examples/csharp/Socket.cs b/examples/csharp/Socket.cs
new file mode 100644
index 0000000..e232710
--- /dev/null
+++ b/examples/csharp/Socket.cs
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+using System; // For ObjectDisposedException
+using System.Net; // For IPEndPoint
+using System.Net.Sockets; // For SocketException
+using System.Runtime.InteropServices;
+
+using ZeroTier;
+
+///
+/// ZeroTier SDK
+///
+namespace ZeroTier
+{
+ ///
+ /// ZeroTier Socket - An lwIP socket mediated over a ZeroTier virtual link
+ ///
+ public class Socket
+ {
+ /// No error.
+ public static readonly int ZTS_ERR_OK = 0;
+ /// Socket error, see Socket.ErrNo() for additional context.
+ public static readonly int ZTS_ERR_SOCKET = -1;
+ /// You probably did something at the wrong time.
+ public static readonly int ZTS_ERR_SERVICE = -2;
+ /// Invalid argument.
+ public static readonly int ZTS_ERR_ARG = -3;
+ /// No result. (not necessarily an error.)
+ public static readonly int ZTS_ERR_NO_RESULT = -4;
+ /// Consider filing a bug report.
+ public static readonly int ZTS_ERR_GENERAL = -5;
+
+ int _fd;
+ bool _isClosed;
+ bool _isListening;
+
+ AddressFamily _socketFamily;
+ SocketType _socketType;
+ ProtocolType _socketProtocol;
+
+ internal EndPoint _localEndPoint;
+ internal EndPoint _remoteEndPoint;
+
+ public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
+ {
+ int family = -1;
+ int type = -1;
+ int protocol = -1;
+ // Map .NET socket parameters to ZeroTier equivalents
+ switch (addressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ family = Constants.AF_INET;
+ break;
+ case AddressFamily.InterNetworkV6:
+ family = Constants.AF_INET6;
+ break;
+ case AddressFamily.Unknown:
+ family = Constants.AF_UNSPEC;
+ break;
+ }
+ switch (socketType)
+ {
+ case SocketType.Stream:
+ type = Constants.SOCK_STREAM;
+ break;
+ case SocketType.Dgram:
+ type = Constants.SOCK_DGRAM;
+ break;
+ }
+ switch (protocolType)
+ {
+ case ProtocolType.Udp:
+ protocol = Constants.IPPROTO_TCP;
+ break;
+ case ProtocolType.Tcp:
+ protocol = Constants.IPPROTO_UDP;
+ break;
+ case ProtocolType.Unspecified:
+ protocol = 0; // ?
+ break;
+ }
+ if ((_fd = zts_socket(family, type, protocol)) < 0)
+ {
+ throw new SocketException((int)_fd);
+ }
+ _socketFamily = addressFamily;
+ _socketType = socketType;
+ _socketProtocol = protocolType;
+ _isClosed = false;
+ }
+
+ private Socket(int fileDescriptor,
+ AddressFamily addressFamily,
+ SocketType socketType,
+ ProtocolType protocolType,
+ EndPoint localEndPoint,
+ EndPoint remoteEndPoint)
+ {
+ _socketFamily = addressFamily;
+ _socketType = socketType;
+ _socketProtocol = protocolType;
+ _localEndPoint = localEndPoint;
+ _remoteEndPoint = remoteEndPoint;
+ _isClosed = false;
+ _isListening = false;
+ _fd = fileDescriptor;
+ }
+
+ public void Connect(IPEndPoint remoteEndPoint)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ if (_fd < 0) {
+ // Invalid file descriptor
+ throw new SocketException((int)Constants.ERR_SOCKET);
+ }
+ if (remoteEndPoint == null) {
+ throw new ArgumentNullException(nameof(remoteEndPoint));
+ }
+ int err = Constants.ERR_OK;
+ int addrlen = 0;
+ if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
+ {
+ zts_sockaddr_in sa = new zts_sockaddr_in();
+ addrlen = Marshal.SizeOf(typeof(zts_sockaddr_in));
+ switch (remoteEndPoint.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ sa.sin_family = (byte)Constants.AF_INET;
+ break;
+ case AddressFamily.InterNetworkV6:
+ sa.sin_family = (byte)Constants.AF_INET6;
+ break;
+ case AddressFamily.Unknown:
+ sa.sin_family = (byte)Constants.AF_UNSPEC;
+ break;
+ }
+ sa.sin_port = (short)zts_htons((ushort)remoteEndPoint.Port);
+ sa.sin_addr = remoteEndPoint.Address.GetAddressBytes();
+ sa.sin_len = (byte)addrlen; // lwIP-specific
+
+ IntPtr ptr1 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr)));
+ Marshal.StructureToPtr(sa, ptr1, false);
+ //zts_sockaddr sAddr = (zts_sockaddr)Marshal.PtrToStructure(ptr1, typeof(zts_sockaddr));
+ err = zts_connect(_fd, ptr1, (byte)addrlen);
+
+ }
+ if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ /*
+ socketAddress.iSockaddrLength = Marshal.SizeOf(typeof(sockaddr_in6));
+ socketAddress.lpSockAddr = CriticalAllocHandle.FromSize(socketAddress.iSockaddrLength);
+ sockaddr_in6 sa = new sockaddr_in6();
+ sa.sin6_family = (short)AddressFamily.InterNetworkV6;
+ sa.sin6_port = (ushort)endpoint.Port;
+ sa.sin6_addr = endpoint.Address.GetAddressBytes();
+ sa.sin6_scope_id = (uint)endpoint.Address.ScopeId;
+ Marshal.StructureToPtr(sa, (IntPtr)socketAddress.lpSockAddr, false);
+ */
+ }
+ if (err < 0) {
+ throw new SocketException((int)err);
+ }
+ _remoteEndPoint = remoteEndPoint;
+ }
+
+ public void Bind(IPEndPoint localEndPoint)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ if (_fd < 0) {
+ // Invalid file descriptor
+ throw new SocketException((int)Constants.ERR_SOCKET);
+ }
+ if (localEndPoint == null) {
+ throw new ArgumentNullException(nameof(localEndPoint));
+ }
+ int err = Constants.ERR_OK;
+ int addrlen = 0;
+ if (localEndPoint.AddressFamily == AddressFamily.InterNetwork)
+ {
+ zts_sockaddr_in sa = new zts_sockaddr_in();
+ addrlen = Marshal.SizeOf(typeof(zts_sockaddr_in));
+ switch (localEndPoint.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ sa.sin_family = (byte)Constants.AF_INET;
+ break;
+ case AddressFamily.InterNetworkV6:
+ sa.sin_family = (byte)Constants.AF_INET6;
+ break;
+ case AddressFamily.Unknown:
+ sa.sin_family = (byte)Constants.AF_UNSPEC;
+ break;
+ }
+ sa.sin_port = (short)zts_htons((ushort)localEndPoint.Port);
+ sa.sin_addr = localEndPoint.Address.GetAddressBytes();
+ sa.sin_len = (byte)addrlen; // lwIP-specific
+
+ IntPtr ptr1 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr)));
+ Marshal.StructureToPtr(sa, ptr1, false);
+ err = zts_bind(_fd, ptr1, (byte)addrlen);
+ }
+ if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ /*
+ socketAddress.iSockaddrLength = Marshal.SizeOf(typeof(sockaddr_in6));
+ socketAddress.lpSockAddr = CriticalAllocHandle.FromSize(socketAddress.iSockaddrLength);
+ sockaddr_in6 sa = new sockaddr_in6();
+ sa.sin6_family = (short)AddressFamily.InterNetworkV6;
+ sa.sin6_port = (ushort)endpoint.Port;
+ sa.sin6_addr = endpoint.Address.GetAddressBytes();
+ sa.sin6_scope_id = (uint)endpoint.Address.ScopeId;
+ Marshal.StructureToPtr(sa, (IntPtr)socketAddress.lpSockAddr, false);
+ */
+ }
+ if (err < 0) {
+ throw new SocketException((int)err);
+ }
+ _localEndPoint = localEndPoint;
+ }
+
+ public void Listen(int backlog)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ if (_fd < 0) {
+ // Invalid file descriptor
+ throw new SocketException((int)Constants.ERR_SOCKET);
+ }
+ int err = Constants.ERR_OK;
+ if ((err = zts_listen(_fd, backlog)) < 0) {
+ // Invalid backlog value perhaps?
+ throw new SocketException((int)Constants.ERR_SOCKET);
+ }
+ _isListening = true;
+ }
+
+ public Socket Accept()
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ if (_fd < 0) {
+ // Invalid file descriptor
+ throw new SocketException((int)Constants.ERR_SOCKET);
+ }
+ if (_isListening == false) {
+ throw new InvalidOperationException("Socket is not in a listening state. Call Listen() first");
+ }
+ // TODO: Rewrite -- Check for memory leaks
+ // Inform zts_accept of the size of the available address buffer
+ int addrlen = Marshal.SizeOf(typeof(zts_sockaddr_in));
+ IntPtr addrlenPtr = GCHandle.Alloc(addrlen, GCHandleType.Pinned).AddrOfPinnedObject();
+ // Allocate space for address buffer and provide pointer to zts_accept
+ zts_sockaddr_in in4 = new zts_sockaddr_in();
+ IntPtr remoteAddrPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_sockaddr_in)));
+ Marshal.StructureToPtr(in4, remoteAddrPtr, false);
+
+ int err = zts_accept(_fd, remoteAddrPtr, addrlenPtr);
+ if (err < 0) {
+ throw new SocketException((int)err);
+ }
+ in4 = (zts_sockaddr_in)Marshal.PtrToStructure(remoteAddrPtr, typeof(zts_sockaddr_in));
+ // Convert sockaddr contents to IPEndPoint
+ IPAddress ipAddress = new IPAddress(in4.sin_addr);
+ IPEndPoint clientEndPoint = new IPEndPoint(ipAddress, zts_ntohs((ushort)in4.sin_port));
+ // Create new socket by providing file descriptor returned from zts_accept call.
+ Socket clientSocket = new Socket(
+ err, _socketFamily, _socketType, _socketProtocol, _localEndPoint, clientEndPoint);
+ return clientSocket;
+ }
+
+ public void Shutdown(SocketShutdown how)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ int ztHow = 0;
+ switch (how)
+ {
+ case SocketShutdown.Receive:
+ ztHow = Constants.O_RDONLY;
+ break;
+ case SocketShutdown.Send:
+ ztHow = Constants.O_WRONLY;
+ break;
+ case SocketShutdown.Both:
+ ztHow = Constants.O_RDWR;
+ break;
+ }
+ zts_shutdown(_fd, ztHow);
+ }
+
+ public void Close()
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has already been closed");
+ }
+ zts_close(_fd);
+ _isClosed = true;
+ }
+
+ public EndPoint RemoteEndPoint
+ {
+ get {
+ return _remoteEndPoint;
+ }
+ }
+
+ public EndPoint LocalEndPoint
+ {
+ get {
+ return _localEndPoint;
+ }
+ }
+
+ public Int32 Send(Byte[] buffer)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ if (_fd < 0) {
+ throw new SocketException((int)ZeroTier.Constants.ERR_SOCKET);
+ }
+ if (buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ int flags = 0;
+ IntPtr bufferPtr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
+ return zts_send(_fd, bufferPtr, (uint)Buffer.ByteLength(buffer), (int)flags);
+ }
+
+ public Int32 Receive(Byte[] buffer)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ if (_fd < 0) {
+ throw new SocketException((int)ZeroTier.Constants.ERR_SOCKET);
+ }
+ if (buffer == null) {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+ int flags = 0;
+ IntPtr bufferPtr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0);
+ return zts_recv(_fd, bufferPtr, (uint)Buffer.ByteLength(buffer), (int)flags);
+ }
+
+ /* Structures and functions used internally to communicate with
+ lower-level C API defined in include/ZeroTierSockets.h */
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_get_all_stats")]
+ static extern int zts_get_all_stats(IntPtr arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_get_protocol_stats")]
+ static extern int zts_get_protocol_stats(int arg1, IntPtr arg2);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_socket")]
+ static extern int zts_socket(int arg1, int arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_connect")]
+ static extern int zts_connect(int arg1, IntPtr arg2, ushort arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_bind")]
+ static extern int zts_bind(int arg1, IntPtr arg2, ushort arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_listen")]
+ static extern int zts_listen(int arg1, int arg2);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_accept")]
+ static extern int zts_accept(int arg1, IntPtr arg2, IntPtr arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_setsockopt")]
+ static extern int zts_setsockopt(int arg1, int arg2, int arg3, IntPtr arg4, ushort arg5);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_getsockopt")]
+ static extern int zts_getsockopt(int arg1, int arg2, int arg3, IntPtr arg4, IntPtr arg5);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_getsockname")]
+ static extern int zts_getsockname(int arg1, IntPtr arg2, IntPtr arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_getpeername")]
+ static extern int zts_getpeername(int arg1, IntPtr arg2, IntPtr arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_close")]
+ static extern int zts_close(int arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_fcntl")]
+ static extern int zts_fcntl(int arg1, int arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_poll")]
+ static extern int zts_poll(IntPtr arg1, uint arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_ioctl")]
+ static extern int zts_ioctl(int arg1, uint arg2, IntPtr arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_send")]
+ static extern int zts_send(int arg1, IntPtr arg2, uint arg3, int arg4);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_sendto")]
+ static extern int zts_sendto(int arg1, IntPtr arg2, uint arg3, int arg4, IntPtr arg5, ushort arg6);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_sendmsg")]
+ static extern int zts_sendmsg(int arg1, IntPtr arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_recv")]
+ static extern int zts_recv(int arg1, IntPtr arg2, uint arg3, int arg4);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_recvfrom")]
+ static extern int zts_recvfrom(int arg1, IntPtr arg2, uint arg3, int arg4, IntPtr arg5, IntPtr arg6);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_recvmsg")]
+ static extern int zts_recvmsg(int arg1, IntPtr arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_read")]
+ static extern int zts_read(int arg1, IntPtr arg2, uint arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_readv")]
+ static extern int zts_readv(int arg1, IntPtr arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_write")]
+ static extern int zts_write(int arg1, IntPtr arg2, uint arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_writev")]
+ static extern int zts_writev(int arg1, IntPtr arg2, int arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_shutdown")]
+ static extern int zts_shutdown(int arg1, int arg2);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_add_dns_nameserver")]
+ static extern int zts_add_dns_nameserver(IntPtr arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_del_dns_nameserver")]
+ static extern int zts_del_dns_nameserver(IntPtr arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_htons")]
+ static extern ushort zts_htons(ushort arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_htonl")]
+ static extern ushort zts_htonl(ushort arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_ntohs")]
+ static extern ushort zts_ntohs(ushort arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_ntohl")]
+ static extern ushort zts_ntohl(ushort arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_inet_ntop")]
+ static extern string zts_inet_ntop(int arg1, IntPtr arg2, string arg3, ushort arg4);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_inet_pton")]
+ static extern int zts_inet_pton(int arg1, string arg2, IntPtr arg3);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_inet_addr")]
+ static extern ushort zts_inet_addr(string arg1);
+
+ [DllImport("libzt", EntryPoint="CSharp_zts_errno_get")]
+ static extern int zts_errno_get();
+
+ ///
+ /// Gets the value of errno from the unmanaged region
+ ///
+ ///
+ public static int ErrNo {
+ get {
+ return zts_errno_get();
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_sockaddr
+ {
+ public byte sa_len;
+ public byte sa_family;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)]
+ public byte[] sa_data;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_in_addr
+ {
+ public uint s_addr;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_sockaddr_in
+ {
+ public byte sin_len;
+ public byte sin_family;
+ public short sin_port;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
+ public byte[] sin_addr;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
+ public char[] sin_zero; // SIN_ZERO_LEN
+ }
+ }
+}
diff --git a/examples/csharp/example.cs b/examples/csharp/example.cs
new file mode 100644
index 0000000..4651c59
--- /dev/null
+++ b/examples/csharp/example.cs
@@ -0,0 +1,201 @@
+
+using System;
+using System.Threading;
+using System.Net;
+using System.Net.Sockets; // For SocketType, etc
+using System.Text; // For Encoding
+
+using ZeroTier; // For ZeroTier.Node, ZeroTier.Event, and ZeroTier.Socket
+
+public class ExampleApp {
+
+ ZeroTier.Node node;
+
+ /**
+ * Initialize and start ZeroTier
+ */
+ public void StartZeroTier(string configFilePath, ushort servicePort, ulong networkId)
+ {
+ node = new ZeroTier.Node(configFilePath, myZeroTierEventCallback, servicePort);
+ node.Start(); // Network activity only begins after calling Start()
+
+ /* How you do this next part is up to you, but essentially we're waiting for the node
+ to signal to us (via a ZeroTierEvent) that it has access to the internet and is
+ able to talk to one of our root servers. As a convenience you can just periodically check
+ IsOnline() instead of looking for the event via the callback. */
+ while (!node.IsOnline()) { Thread.Sleep(100); }
+
+ /* After the node comes online you may now join/leave networks. You will receive
+ notifications via the callback function regarding the status of your join request as well
+ as any subsequent network-related events such as the assignment of an IP address, added
+ or removed routes, etc. */
+ node.Join(networkId);
+
+ /* Note that ZeroTierSocket calls will fail if there are no routes available, for this
+ reason we should wait to make those calls until the node has indicated to us that at
+ least one network has been joined successfully. */
+ while (!node.HasRoutes()) { Thread.Sleep(100); }
+ }
+
+ /**
+ * Stop ZeroTier
+ */
+ public void StopZeroTier()
+ {
+ node.Stop();
+ }
+
+ /**
+ * Your application should process event messages and return control as soon as possible. Blocking
+ * or otherwise time-consuming operations are not reccomended here.
+ */
+ public void myZeroTierEventCallback(ZeroTier.Event e)
+ {
+ Console.WriteLine("Event.eventCode = {0} ({1})", e.EventCode, e.EventName);
+
+ if (e.EventCode == ZeroTier.Constants.EVENT_NODE_ONLINE) {
+ Console.WriteLine("Node is online");
+ Console.WriteLine(" - Address (NodeId): " + node.NodeId);
+ }
+
+ if (e.EventCode == ZeroTier.Constants.EVENT_NETWORK_OK) {
+ Console.WriteLine(" - Network ID: " + e.networkDetails.networkId.ToString("x16"));
+ }
+ }
+
+ /**
+ * Example server
+ */
+ public void YourServer() {
+ string data = null;
+
+ // Data buffer for incoming data.
+ byte[] bytes = new Byte[1024];
+
+ string serverIP = "0.0.0.0";
+ int port = 8000;
+ IPAddress ipAddress = IPAddress.Parse(serverIP);
+ IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
+
+ Console.WriteLine(localEndPoint.ToString());
+ ZeroTier.Socket listener = new ZeroTier.Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp );
+
+ // Bind the socket to the local endpoint and
+ // listen for incoming connections.
+
+ try {
+ listener.Bind(localEndPoint);
+ listener.Listen(10);
+
+ // Start listening for connections.
+ while (true) {
+ Console.WriteLine("Waiting for a connection...");
+ // Program is suspended while waiting for an incoming connection.
+ Console.WriteLine("accepting...");
+ ZeroTier.Socket handler = listener.Accept();
+ data = null;
+
+ Console.WriteLine("accepted connection from: " + handler.RemoteEndPoint.ToString());
+
+ // An incoming connection needs to be processed.
+ while (true) {
+ int bytesRec = handler.Receive(bytes);
+ data += Encoding.ASCII.GetString(bytes,0,bytesRec);
+ if (data.IndexOf("") > -1) {
+ break;
+ }
+ }
+
+ // Show the data on the console.
+ Console.WriteLine( "Text received : {0}", data);
+
+ // Echo the data back to the client.
+ byte[] msg = Encoding.ASCII.GetBytes(data);
+
+ handler.Send(msg);
+ handler.Shutdown(SocketShutdown.Both);
+ handler.Close();
+ }
+
+ } catch (Exception e) {
+ Console.WriteLine(e.ToString());
+ }
+
+ Console.WriteLine("\nPress ENTER to continue...");
+ Console.Read();
+ }
+
+ /**
+ * Example client
+ */
+ public void YourClient() {
+ // Data buffer for incoming data.
+ byte[] bytes = new byte[1024];
+
+ // Connect to a remote device.
+ try {
+ string serverIP = "10.244.180.7";
+ int port = 8000;
+ IPAddress ipAddress = IPAddress.Parse(serverIP);
+ IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, port);
+
+ // Create a TCP/IP socket.
+ ZeroTier.Socket sender = new ZeroTier.Socket(ipAddress.AddressFamily,
+ SocketType.Stream, ProtocolType.Tcp );
+
+ // Connect the socket to the remote endpoint. Catch any errors.
+ try {
+
+ Console.WriteLine("Socket connecting to {0}...",
+ remoteEndPoint.ToString());
+
+ sender.Connect(remoteEndPoint);
+
+ Console.WriteLine("Socket connected to {0}",
+ sender.RemoteEndPoint.ToString());
+
+ // Encode the data string into a byte array.
+ byte[] msg = Encoding.ASCII.GetBytes("This is a test");
+
+ // Send the data through the socket.
+ int bytesSent = sender.Send(msg);
+
+ // Receive the response from the remote device.
+ int bytesRec = sender.Receive(bytes);
+ Console.WriteLine("Echoed test = {0}",
+ Encoding.ASCII.GetString(bytes,0,bytesRec));
+
+ // Release the socket.
+ sender.Shutdown(SocketShutdown.Both);
+ sender.Close();
+
+ } catch (ArgumentNullException ane) {
+ Console.WriteLine("ArgumentNullException : {0}",ane.ToString());
+ } catch (SocketException se) {
+ Console.WriteLine("SocketException : {0}",se.ToString());
+ } catch (Exception e) {
+ Console.WriteLine("Unexpected exception : {0}", e.ToString());
+ }
+
+ } catch (Exception e) {
+ Console.WriteLine( e.ToString());
+ }
+ }
+}
+
+public class example
+{
+ static void Main()
+ {
+ ExampleApp exampleApp = new ExampleApp();
+
+ ulong networkId = 0x8216ab0a47c622a1;
+ ushort servicePort = 9991;
+ string configFilePath = "path";
+
+ exampleApp.StartZeroTier(configFilePath, servicePort, networkId);
+ exampleApp.YourClient();
+ exampleApp.StopZeroTier();
+ }
+}
+
diff --git a/examples/csharp/zt.i b/examples/csharp/zt.i
new file mode 100644
index 0000000..b2e752c
--- /dev/null
+++ b/examples/csharp/zt.i
@@ -0,0 +1,55 @@
+%module zt
+
+%include
+%include
+%include
+
+// Prevent SWIG from doing anything funny with our types
+%apply unsigned char { uint8_t };
+%apply char { int8_t };
+%apply unsigned short { uint16_t };
+%apply int { int16_t };
+%apply unsigned short { uint32_t };
+%apply int { int32_t };
+%apply unsigned long long { uint64_t };
+%apply long long { int64_t };
+
+%typemap(ctype) zts_sockaddr* "zts_sockaddr*"
+
+// Ignore all classes/structs (We'll define these manually in C#)
+// %rename($ignore, %$isclass) "";
+
+%ignore zts_in6_addr;
+
+%ignore zts_sockaddr;
+%ignore zts_in_addr;
+%ignore zts_sockaddr_in;
+%ignore zts_sockaddr_storage;
+%ignore zts_sockaddr_in6;
+
+%ignore zts_callback_msg;
+%ignore zts_node_details;
+%ignore zts_network_details;
+%ignore zts_netif_details;
+%ignore zts_virtual_network_route;
+%ignore zts_peer_details;
+%ignore zts_addr_details;
+
+#define ZTS_CSHARP=1
+
+%{
+#include "../../include/ZeroTierSockets.h"
+%}
+
+// Typemap for our event callback
+%define %cs_callback(TYPE, CSTYPE)
+ %typemap(ctype) TYPE, TYPE& "void *"
+ %typemap(in) TYPE %{ $1 = ($1_type)$input; %}
+ %typemap(in) TYPE& %{ $1 = ($1_type)&$input; %}
+ %typemap(imtype, out="IntPtr") TYPE, TYPE& "CSTYPE"
+ %typemap(cstype, out="IntPtr") TYPE, TYPE& "CSTYPE"
+ %typemap(csin) TYPE, TYPE& "$csinput"
+%enddef
+%cs_callback(CppCallback, CSharpCallbackWithStruct)
+
+%include "../../include/ZeroTierSockets.h"
diff --git a/examples/csharp/zt_wrap.cxx b/examples/csharp/zt_wrap.cxx
new file mode 100644
index 0000000..4d88a87
--- /dev/null
+++ b/examples/csharp/zt_wrap.cxx
@@ -0,0 +1,1143 @@
+/*
+ * Copyright (c)2013-2020 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2024-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+#ifndef SWIGCSHARP
+#define SWIGCSHARP
+#endif
+
+
+
+#ifdef __cplusplus
+/* SwigValueWrapper is described in swig.swg */
+template class SwigValueWrapper {
+ struct SwigMovePointer {
+ T *ptr;
+ SwigMovePointer(T *p) : ptr(p) { }
+ ~SwigMovePointer() { delete ptr; }
+ SwigMovePointer& operator=(SwigMovePointer& rhs) { T* oldptr = ptr; ptr = 0; delete oldptr; ptr = rhs.ptr; rhs.ptr = 0; return *this; }
+ } pointer;
+ SwigValueWrapper& operator=(const SwigValueWrapper& rhs);
+ SwigValueWrapper(const SwigValueWrapper& rhs);
+public:
+ SwigValueWrapper() : pointer(0) { }
+ SwigValueWrapper& operator=(const T& t) { SwigMovePointer tmp(new T(t)); pointer = tmp; return *this; }
+ operator T&() const { return *pointer.ptr; }
+ T *operator&() { return pointer.ptr; }
+};
+
+template T SwigValueInit() {
+ return T();
+}
+#endif
+
+/* -----------------------------------------------------------------------------
+ * This section contains generic SWIG labels for method/variable
+ * declarations/attributes, and other compiler dependent labels.
+ * ----------------------------------------------------------------------------- */
+
+/* template workaround for compilers that cannot correctly implement the C++ standard */
+#ifndef SWIGTEMPLATEDISAMBIGUATOR
+# if defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x560)
+# define SWIGTEMPLATEDISAMBIGUATOR template
+# elif defined(__HP_aCC)
+/* Needed even with `aCC -AA' when `aCC -V' reports HP ANSI C++ B3910B A.03.55 */
+/* If we find a maximum version that requires this, the test would be __HP_aCC <= 35500 for A.03.55 */
+# define SWIGTEMPLATEDISAMBIGUATOR template
+# else
+# define SWIGTEMPLATEDISAMBIGUATOR
+# endif
+#endif
+
+/* inline attribute */
+#ifndef SWIGINLINE
+# if defined(__cplusplus) || (defined(__GNUC__) && !defined(__STRICT_ANSI__))
+# define SWIGINLINE inline
+# else
+# define SWIGINLINE
+# endif
+#endif
+
+/* attribute recognised by some compilers to avoid 'unused' warnings */
+#ifndef SWIGUNUSED
+# if defined(__GNUC__)
+# if !(defined(__cplusplus)) || (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))
+# define SWIGUNUSED __attribute__ ((__unused__))
+# else
+# define SWIGUNUSED
+# endif
+# elif defined(__ICC)
+# define SWIGUNUSED __attribute__ ((__unused__))
+# else
+# define SWIGUNUSED
+# endif
+#endif
+
+#ifndef SWIG_MSC_UNSUPPRESS_4505
+# if defined(_MSC_VER)
+# pragma warning(disable : 4505) /* unreferenced local function has been removed */
+# endif
+#endif
+
+#ifndef SWIGUNUSEDPARM
+# ifdef __cplusplus
+# define SWIGUNUSEDPARM(p)
+# else
+# define SWIGUNUSEDPARM(p) p SWIGUNUSED
+# endif
+#endif
+
+/* internal SWIG method */
+#ifndef SWIGINTERN
+# define SWIGINTERN static SWIGUNUSED
+#endif
+
+/* internal inline SWIG method */
+#ifndef SWIGINTERNINLINE
+# define SWIGINTERNINLINE SWIGINTERN SWIGINLINE
+#endif
+
+/* exporting methods */
+#if defined(__GNUC__)
+# if (__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+# ifndef GCC_HASCLASSVISIBILITY
+# define GCC_HASCLASSVISIBILITY
+# endif
+# endif
+#endif
+
+#ifndef SWIGEXPORT
+# if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
+# if defined(STATIC_LINKED)
+# define SWIGEXPORT
+# else
+# define SWIGEXPORT __declspec(dllexport)
+# endif
+# else
+# if defined(__GNUC__) && defined(GCC_HASCLASSVISIBILITY)
+# define SWIGEXPORT __attribute__ ((visibility("default")))
+# else
+# define SWIGEXPORT
+# endif
+# endif
+#endif
+
+/* calling conventions for Windows */
+#ifndef SWIGSTDCALL
+# if defined(_WIN32) || defined(__WIN32__) || defined(__CYGWIN__)
+# define SWIGSTDCALL __stdcall
+# else
+# define SWIGSTDCALL
+# endif
+#endif
+
+/* Deal with Microsoft's attempt at deprecating C standard runtime functions */
+#if !defined(SWIG_NO_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) && !defined(_CRT_SECURE_NO_DEPRECATE)
+# define _CRT_SECURE_NO_DEPRECATE
+#endif
+
+/* Deal with Microsoft's attempt at deprecating methods in the standard C++ library */
+#if !defined(SWIG_NO_SCL_SECURE_NO_DEPRECATE) && defined(_MSC_VER) && !defined(_SCL_SECURE_NO_DEPRECATE)
+# define _SCL_SECURE_NO_DEPRECATE
+#endif
+
+/* Deal with Apple's deprecated 'AssertMacros.h' from Carbon-framework */
+#if defined(__APPLE__) && !defined(__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES)
+# define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0
+#endif
+
+/* Intel's compiler complains if a variable which was never initialised is
+ * cast to void, which is a common idiom which we use to indicate that we
+ * are aware a variable isn't used. So we just silence that warning.
+ * See: https://github.com/swig/swig/issues/192 for more discussion.
+ */
+#ifdef __INTEL_COMPILER
+# pragma warning disable 592
+#endif
+
+
+#include
+#include
+#include
+
+
+/* Support for throwing C# exceptions from C/C++. There are two types:
+ * Exceptions that take a message and ArgumentExceptions that take a message and a parameter name. */
+typedef enum {
+ SWIG_CSharpApplicationException,
+ SWIG_CSharpArithmeticException,
+ SWIG_CSharpDivideByZeroException,
+ SWIG_CSharpIndexOutOfRangeException,
+ SWIG_CSharpInvalidCastException,
+ SWIG_CSharpInvalidOperationException,
+ SWIG_CSharpIOException,
+ SWIG_CSharpNullReferenceException,
+ SWIG_CSharpOutOfMemoryException,
+ SWIG_CSharpOverflowException,
+ SWIG_CSharpSystemException
+} SWIG_CSharpExceptionCodes;
+
+typedef enum {
+ SWIG_CSharpArgumentException,
+ SWIG_CSharpArgumentNullException,
+ SWIG_CSharpArgumentOutOfRangeException
+} SWIG_CSharpExceptionArgumentCodes;
+
+typedef void (SWIGSTDCALL* SWIG_CSharpExceptionCallback_t)(const char *);
+typedef void (SWIGSTDCALL* SWIG_CSharpExceptionArgumentCallback_t)(const char *, const char *);
+
+typedef struct {
+ SWIG_CSharpExceptionCodes code;
+ SWIG_CSharpExceptionCallback_t callback;
+} SWIG_CSharpException_t;
+
+typedef struct {
+ SWIG_CSharpExceptionArgumentCodes code;
+ SWIG_CSharpExceptionArgumentCallback_t callback;
+} SWIG_CSharpExceptionArgument_t;
+
+static SWIG_CSharpException_t SWIG_csharp_exceptions[] = {
+ { SWIG_CSharpApplicationException, NULL },
+ { SWIG_CSharpArithmeticException, NULL },
+ { SWIG_CSharpDivideByZeroException, NULL },
+ { SWIG_CSharpIndexOutOfRangeException, NULL },
+ { SWIG_CSharpInvalidCastException, NULL },
+ { SWIG_CSharpInvalidOperationException, NULL },
+ { SWIG_CSharpIOException, NULL },
+ { SWIG_CSharpNullReferenceException, NULL },
+ { SWIG_CSharpOutOfMemoryException, NULL },
+ { SWIG_CSharpOverflowException, NULL },
+ { SWIG_CSharpSystemException, NULL }
+};
+
+static SWIG_CSharpExceptionArgument_t SWIG_csharp_exceptions_argument[] = {
+ { SWIG_CSharpArgumentException, NULL },
+ { SWIG_CSharpArgumentNullException, NULL },
+ { SWIG_CSharpArgumentOutOfRangeException, NULL }
+};
+
+static void SWIGUNUSED SWIG_CSharpSetPendingException(SWIG_CSharpExceptionCodes code, const char *msg) {
+ SWIG_CSharpExceptionCallback_t callback = SWIG_csharp_exceptions[SWIG_CSharpApplicationException].callback;
+ if ((size_t)code < sizeof(SWIG_csharp_exceptions)/sizeof(SWIG_CSharpException_t)) {
+ callback = SWIG_csharp_exceptions[code].callback;
+ }
+ callback(msg);
+}
+
+static void SWIGUNUSED SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpExceptionArgumentCodes code, const char *msg, const char *param_name) {
+ SWIG_CSharpExceptionArgumentCallback_t callback = SWIG_csharp_exceptions_argument[SWIG_CSharpArgumentException].callback;
+ if ((size_t)code < sizeof(SWIG_csharp_exceptions_argument)/sizeof(SWIG_CSharpExceptionArgument_t)) {
+ callback = SWIG_csharp_exceptions_argument[code].callback;
+ }
+ callback(msg, param_name);
+}
+
+
+#ifdef __cplusplus
+extern "C"
+#endif
+SWIGEXPORT void SWIGSTDCALL SWIGRegisterExceptionCallbacks_zt(
+ SWIG_CSharpExceptionCallback_t applicationCallback,
+ SWIG_CSharpExceptionCallback_t arithmeticCallback,
+ SWIG_CSharpExceptionCallback_t divideByZeroCallback,
+ SWIG_CSharpExceptionCallback_t indexOutOfRangeCallback,
+ SWIG_CSharpExceptionCallback_t invalidCastCallback,
+ SWIG_CSharpExceptionCallback_t invalidOperationCallback,
+ SWIG_CSharpExceptionCallback_t ioCallback,
+ SWIG_CSharpExceptionCallback_t nullReferenceCallback,
+ SWIG_CSharpExceptionCallback_t outOfMemoryCallback,
+ SWIG_CSharpExceptionCallback_t overflowCallback,
+ SWIG_CSharpExceptionCallback_t systemCallback) {
+ SWIG_csharp_exceptions[SWIG_CSharpApplicationException].callback = applicationCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpArithmeticException].callback = arithmeticCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpDivideByZeroException].callback = divideByZeroCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpIndexOutOfRangeException].callback = indexOutOfRangeCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpInvalidCastException].callback = invalidCastCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpInvalidOperationException].callback = invalidOperationCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpIOException].callback = ioCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpNullReferenceException].callback = nullReferenceCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpOutOfMemoryException].callback = outOfMemoryCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpOverflowException].callback = overflowCallback;
+ SWIG_csharp_exceptions[SWIG_CSharpSystemException].callback = systemCallback;
+}
+
+#ifdef __cplusplus
+extern "C"
+#endif
+SWIGEXPORT void SWIGSTDCALL SWIGRegisterExceptionArgumentCallbacks_zt(
+ SWIG_CSharpExceptionArgumentCallback_t argumentCallback,
+ SWIG_CSharpExceptionArgumentCallback_t argumentNullCallback,
+ SWIG_CSharpExceptionArgumentCallback_t argumentOutOfRangeCallback) {
+ SWIG_csharp_exceptions_argument[SWIG_CSharpArgumentException].callback = argumentCallback;
+ SWIG_csharp_exceptions_argument[SWIG_CSharpArgumentNullException].callback = argumentNullCallback;
+ SWIG_csharp_exceptions_argument[SWIG_CSharpArgumentOutOfRangeException].callback = argumentOutOfRangeCallback;
+}
+
+
+/* Callback for returning strings to C# without leaking memory */
+typedef char * (SWIGSTDCALL* SWIG_CSharpStringHelperCallback)(const char *);
+static SWIG_CSharpStringHelperCallback SWIG_csharp_string_callback = NULL;
+
+
+#ifdef __cplusplus
+extern "C"
+#endif
+SWIGEXPORT void SWIGSTDCALL SWIGRegisterStringCallback_zt(SWIG_CSharpStringHelperCallback callback) {
+ SWIG_csharp_string_callback = callback;
+}
+
+
+/* Contract support */
+
+#define SWIG_contract_assert(nullreturn, expr, msg) if (!(expr)) {SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentOutOfRangeException, msg, ""); return nullreturn; } else
+
+/* Errors in SWIG */
+#define SWIG_UnknownError -1
+#define SWIG_IOError -2
+#define SWIG_RuntimeError -3
+#define SWIG_IndexError -4
+#define SWIG_TypeError -5
+#define SWIG_DivisionByZero -6
+#define SWIG_OverflowError -7
+#define SWIG_SyntaxError -8
+#define SWIG_ValueError -9
+#define SWIG_SystemError -10
+#define SWIG_AttributeError -11
+#define SWIG_MemoryError -12
+#define SWIG_NullReferenceError -13
+
+
+
+
+#include
+#include
+
+
+#include
+
+
+#include
+#include
+#include
+
+
+#include