/* * 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 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 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(); } } } }