diff --git a/examples/csharp/Socket.cs b/examples/csharp/Socket.cs
index 28d1bab..4f240aa 100644
--- a/examples/csharp/Socket.cs
+++ b/examples/csharp/Socket.cs
@@ -13,7 +13,7 @@
using System; // For ObjectDisposedException
using System.Net; // For IPEndPoint
-using System.Net.Sockets; // For SocketException
+using System.Net.Sockets; // For ZeroTier.SocketException
using System.Runtime.InteropServices;
using ZeroTier;
@@ -23,16 +23,6 @@ using ZeroTier;
///
namespace ZeroTier
{
- public class ZeroTierException : Exception
- {
- public ZeroTierException(int _serviceErrorCode, int _socketErrorCode) {
- ServiceErrorCode = _serviceErrorCode;
- SocketErrorCode = _socketErrorCode;
- }
- public int ServiceErrorCode { get; set; }
- public int SocketErrorCode { get; set; }
- }
-
///
/// ZeroTier Socket - An lwIP socket mediated over a ZeroTier virtual link
///
@@ -54,6 +44,7 @@ namespace ZeroTier
int _fd;
bool _isClosed;
bool _isListening;
+ bool _isBlocking;
AddressFamily _socketFamily;
SocketType _socketType;
@@ -62,6 +53,13 @@ namespace ZeroTier
internal EndPoint _localEndPoint;
internal EndPoint _remoteEndPoint;
+ private void InitializeInternalFlags()
+ {
+ _isClosed = false;
+ _isListening = false;
+ _isBlocking = false;
+ }
+
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
{
int family = -1;
@@ -103,12 +101,12 @@ namespace ZeroTier
}
if ((_fd = zts_socket(family, type, protocol)) < 0)
{
- throw new SocketException((int)_fd);
+ throw new ZeroTier.SocketException((int)_fd);
}
_socketFamily = addressFamily;
_socketType = socketType;
_socketProtocol = protocolType;
- _isClosed = false;
+ InitializeInternalFlags();
}
private Socket(int fileDescriptor,
@@ -123,9 +121,8 @@ namespace ZeroTier
_socketProtocol = protocolType;
_localEndPoint = localEndPoint;
_remoteEndPoint = remoteEndPoint;
- _isClosed = false;
- _isListening = false;
_fd = fileDescriptor;
+ InitializeInternalFlags();
}
public void Connect(IPEndPoint remoteEndPoint)
@@ -135,7 +132,7 @@ namespace ZeroTier
}
if (_fd < 0) {
// Invalid file descriptor
- throw new SocketException((int)Constants.ERR_SOCKET);
+ throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
}
if (remoteEndPoint == null) {
throw new ArgumentNullException(nameof(remoteEndPoint));
@@ -182,7 +179,7 @@ namespace ZeroTier
*/
}
if (err < 0) {
- throw new ZeroTierException(err, ZeroTier.Node.ErrNo);
+ throw new ZeroTier.SocketException(err, ZeroTier.Node.ErrNo);
}
_remoteEndPoint = remoteEndPoint;
}
@@ -194,7 +191,7 @@ namespace ZeroTier
}
if (_fd < 0) {
// Invalid file descriptor
- throw new SocketException((int)Constants.ERR_SOCKET);
+ throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
}
if (localEndPoint == null) {
throw new ArgumentNullException(nameof(localEndPoint));
@@ -239,7 +236,7 @@ namespace ZeroTier
*/
}
if (err < 0) {
- throw new SocketException((int)err);
+ throw new ZeroTier.SocketException((int)err);
}
_localEndPoint = localEndPoint;
}
@@ -251,12 +248,12 @@ namespace ZeroTier
}
if (_fd < 0) {
// Invalid file descriptor
- throw new SocketException((int)Constants.ERR_SOCKET);
+ throw new ZeroTier.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);
+ throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
}
_isListening = true;
}
@@ -268,7 +265,7 @@ namespace ZeroTier
}
if (_fd < 0) {
// Invalid file descriptor
- throw new SocketException((int)Constants.ERR_SOCKET);
+ throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
}
if (_isListening == false) {
throw new InvalidOperationException("Socket is not in a listening state. Call Listen() first");
@@ -284,7 +281,7 @@ namespace ZeroTier
int err = zts_accept(_fd, remoteAddrPtr, addrlenPtr);
if (err < 0) {
- throw new SocketException((int)err);
+ throw new ZeroTier.SocketException(err, ZeroTier.Node.ErrNo);
}
in4 = (zts_sockaddr_in)Marshal.PtrToStructure(remoteAddrPtr, typeof(zts_sockaddr_in));
// Convert sockaddr contents to IPEndPoint
@@ -340,13 +337,79 @@ namespace ZeroTier
}
}
+ public bool Blocking
+ {
+ get {
+ return _isBlocking;
+ }
+ set {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ int opts = 0;
+ if ((opts = zts_fcntl(_fd, (int)(ZeroTier.Constants.F_GETFL), 0)) < 0) {
+ throw new ZeroTier.SocketException(opts, ZeroTier.Node.ErrNo);
+ }
+ Console.WriteLine("before.opts={0}", opts);
+ if (value) { // Blocking
+ opts = opts & (~(ZeroTier.Constants.O_NONBLOCK));
+ }
+ if (!value) { // Non-Blocking
+ opts = opts | (int)(ZeroTier.Constants.O_NONBLOCK);
+ }
+ Console.WriteLine("after.opts={0}", opts);
+ if ((opts = zts_fcntl(_fd, ZeroTier.Constants.F_SETFL, (int)opts)) < 0) {
+ throw new ZeroTier.SocketException(opts, ZeroTier.Node.ErrNo);
+ }
+ _isBlocking = value;
+ }
+ }
+
+ public bool Poll(int microSeconds, System.Net.Sockets.SelectMode mode)
+ {
+ if (_isClosed) {
+ throw new ObjectDisposedException("Socket has been closed");
+ }
+ zts_pollfd poll_set = new zts_pollfd();
+ poll_set.fd = _fd;
+ if (mode == SelectMode.SelectRead) {
+ poll_set.events = (short)((byte)ZeroTier.Constants.POLLIN);
+ }
+ if (mode == SelectMode.SelectWrite) {
+ poll_set.events = (short)((byte)ZeroTier.Constants.POLLOUT);
+ }
+ if (mode == SelectMode.SelectError) {
+ poll_set.events = (short)((byte)ZeroTier.Constants.POLLERR | (byte)ZeroTier.Constants.POLLNVAL);
+ }
+ IntPtr poll_fd_ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(zts_pollfd)));
+ Marshal.StructureToPtr(poll_set, poll_fd_ptr, false);
+ int result = 0;
+ int timeout_ms = (microSeconds / 1000);
+ uint numfds = 1;
+ if ((result = zts_poll(poll_fd_ptr, numfds, timeout_ms)) < 0) {
+ throw new ZeroTier.SocketException(result, ZeroTier.Node.ErrNo);
+ }
+ poll_set = (zts_pollfd)Marshal.PtrToStructure(poll_fd_ptr, typeof(zts_pollfd));
+ if (result == 0) { return false; } // No events
+ if (mode == SelectMode.SelectRead) {
+ return ((byte)poll_set.revents & (byte)ZeroTier.Constants.POLLIN) != 0;
+ }
+ if (mode == SelectMode.SelectWrite) {
+ return ((byte)poll_set.revents & (byte)ZeroTier.Constants.POLLOUT) != 0;
+ }
+ if (mode == SelectMode.SelectError) {
+ return ((poll_set.revents & (byte)ZeroTier.Constants.POLLERR) != 0) || ((poll_set.revents & (byte)ZeroTier.Constants.POLLNVAL) != 0);
+ }
+ return false;
+ }
+
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);
+ throw new ZeroTier.SocketException((int)ZeroTier.Constants.ERR_SOCKET);
}
if (buffer == null) {
throw new ArgumentNullException(nameof(buffer));
@@ -362,7 +425,7 @@ namespace ZeroTier
throw new ObjectDisposedException("Socket has been closed");
}
if (_fd < 0) {
- throw new SocketException((int)ZeroTier.Constants.ERR_SOCKET);
+ throw new ZeroTier.SocketException((int)ZeroTier.Constants.ERR_SOCKET);
}
if (buffer == null) {
throw new ArgumentNullException(nameof(buffer));
@@ -483,10 +546,7 @@ namespace ZeroTier
[DllImport("libzt", EntryPoint="CSharp_zts_errno_get")]
static extern int zts_errno_get();
- ///
- /// Gets the value of errno from the unmanaged region
- ///
- ///
+ /// The value of errno for the low-level socket layer
public static int ErrNo {
get {
return zts_errno_get();
@@ -519,5 +579,13 @@ namespace ZeroTier
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public char[] sin_zero; // SIN_ZERO_LEN
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct zts_pollfd
+ {
+ public int fd;
+ public short events;
+ public short revents;
+ }
}
}
diff --git a/examples/csharp/SocketException.cs b/examples/csharp/SocketException.cs
new file mode 100644
index 0000000..7945fc0
--- /dev/null
+++ b/examples/csharp/SocketException.cs
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+namespace ZeroTier
+{
+ /// Exception class for ZeroTier service and low-level socket errors
+ public class SocketException : Exception
+ {
+ public SocketException(int _serviceErrorCode)
+ : base(String.Format("ServiceErrorCode={0} (See Constants.cs for error code meanings)", _serviceErrorCode))
+ {
+ ServiceErrorCode = _serviceErrorCode;
+ }
+ public SocketException(int _serviceErrorCode, int _socketErrorCode)
+ : base(String.Format("ServiceErrorCode={0}, SocketErrorCode={1} (See Constants.cs for error code meanings)", _serviceErrorCode, _socketErrorCode))
+ {
+ ServiceErrorCode = _serviceErrorCode;
+ SocketErrorCode = _socketErrorCode;
+ }
+ /// High-level service error code. See Constants.cs
+ public int ServiceErrorCode { get; }
+
+ /// Low-level socket error code. See Constants.cs
+ public int SocketErrorCode { get; }
+ }
+}
\ No newline at end of file
diff --git a/examples/csharp/example.cs b/examples/csharp/example.cs
index ff2cc1d..d717275 100644
--- a/examples/csharp/example.cs
+++ b/examples/csharp/example.cs
@@ -87,11 +87,31 @@ public class ExampleApp {
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;
+ bool nonblocking = true;
- Console.WriteLine("accepted connection from: " + handler.RemoteEndPoint.ToString());
+ ZeroTier.Socket handler;
+
+ if (nonblocking) { // Non-blocking style Accept() loop using Poll()
+ Console.WriteLine("Starting non-blocking Accept() loop...");
+ listener.Blocking = false;
+ // loop
+ int timeout = 1000000; // microseconds (1 second)
+ while (true) {
+ Console.WriteLine("Polling... (for data or incoming connections)");
+ if (listener.Poll(timeout, SelectMode.SelectRead)) {
+ Console.WriteLine("Detected event (SelectRead). Accepting...");
+ handler = listener.Accept();
+ break;
+ }
+ Thread.Sleep(1000);
+ }
+ }
+ else { // Blocking style
+ Console.WriteLine("Starting blocking Accept() call...");
+ handler = listener.Accept();
+ }
+ data = null;
+ Console.WriteLine("Accepted connection from: " + handler.RemoteEndPoint.ToString());
// An incoming connection needs to be processed.
while (true) {
@@ -112,7 +132,7 @@ public class ExampleApp {
handler.Close();
}
- } catch (ZeroTier.ZeroTierException e) {
+ } catch (ZeroTier.SocketException e) {
Console.WriteLine(e);
Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode);
}
@@ -136,7 +156,6 @@ public class ExampleApp {
// Connect the socket to the remote endpoint. Catch any errors.
try {
-
Console.WriteLine("Socket connecting to {0}...",
remoteServerEndPoint.ToString());
@@ -162,9 +181,7 @@ public class ExampleApp {
} catch (ArgumentNullException ane) {
Console.WriteLine("ArgumentNullException : {0}",ane.ToString());
- } catch (SocketException se) {
- Console.WriteLine("SocketException : {0}",se.ToString());
- } catch (ZeroTier.ZeroTierException e) {
+ } catch (ZeroTier.SocketException e) {
Console.WriteLine(e);
Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode);
}