Add non-blocking IO support and improved exception handling in C# wrapper
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
using System; // For ObjectDisposedException
|
using System; // For ObjectDisposedException
|
||||||
using System.Net; // For IPEndPoint
|
using System.Net; // For IPEndPoint
|
||||||
using System.Net.Sockets; // For SocketException
|
using System.Net.Sockets; // For ZeroTier.SocketException
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using ZeroTier;
|
using ZeroTier;
|
||||||
@@ -23,16 +23,6 @@ using ZeroTier;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
namespace 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; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ZeroTier Socket - An lwIP socket mediated over a ZeroTier virtual link
|
/// ZeroTier Socket - An lwIP socket mediated over a ZeroTier virtual link
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,6 +44,7 @@ namespace ZeroTier
|
|||||||
int _fd;
|
int _fd;
|
||||||
bool _isClosed;
|
bool _isClosed;
|
||||||
bool _isListening;
|
bool _isListening;
|
||||||
|
bool _isBlocking;
|
||||||
|
|
||||||
AddressFamily _socketFamily;
|
AddressFamily _socketFamily;
|
||||||
SocketType _socketType;
|
SocketType _socketType;
|
||||||
@@ -62,6 +53,13 @@ namespace ZeroTier
|
|||||||
internal EndPoint _localEndPoint;
|
internal EndPoint _localEndPoint;
|
||||||
internal EndPoint _remoteEndPoint;
|
internal EndPoint _remoteEndPoint;
|
||||||
|
|
||||||
|
private void InitializeInternalFlags()
|
||||||
|
{
|
||||||
|
_isClosed = false;
|
||||||
|
_isListening = false;
|
||||||
|
_isBlocking = false;
|
||||||
|
}
|
||||||
|
|
||||||
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
|
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
|
||||||
{
|
{
|
||||||
int family = -1;
|
int family = -1;
|
||||||
@@ -103,12 +101,12 @@ namespace ZeroTier
|
|||||||
}
|
}
|
||||||
if ((_fd = zts_socket(family, type, protocol)) < 0)
|
if ((_fd = zts_socket(family, type, protocol)) < 0)
|
||||||
{
|
{
|
||||||
throw new SocketException((int)_fd);
|
throw new ZeroTier.SocketException((int)_fd);
|
||||||
}
|
}
|
||||||
_socketFamily = addressFamily;
|
_socketFamily = addressFamily;
|
||||||
_socketType = socketType;
|
_socketType = socketType;
|
||||||
_socketProtocol = protocolType;
|
_socketProtocol = protocolType;
|
||||||
_isClosed = false;
|
InitializeInternalFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Socket(int fileDescriptor,
|
private Socket(int fileDescriptor,
|
||||||
@@ -123,9 +121,8 @@ namespace ZeroTier
|
|||||||
_socketProtocol = protocolType;
|
_socketProtocol = protocolType;
|
||||||
_localEndPoint = localEndPoint;
|
_localEndPoint = localEndPoint;
|
||||||
_remoteEndPoint = remoteEndPoint;
|
_remoteEndPoint = remoteEndPoint;
|
||||||
_isClosed = false;
|
|
||||||
_isListening = false;
|
|
||||||
_fd = fileDescriptor;
|
_fd = fileDescriptor;
|
||||||
|
InitializeInternalFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Connect(IPEndPoint remoteEndPoint)
|
public void Connect(IPEndPoint remoteEndPoint)
|
||||||
@@ -135,7 +132,7 @@ namespace ZeroTier
|
|||||||
}
|
}
|
||||||
if (_fd < 0) {
|
if (_fd < 0) {
|
||||||
// Invalid file descriptor
|
// Invalid file descriptor
|
||||||
throw new SocketException((int)Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
if (remoteEndPoint == null) {
|
if (remoteEndPoint == null) {
|
||||||
throw new ArgumentNullException(nameof(remoteEndPoint));
|
throw new ArgumentNullException(nameof(remoteEndPoint));
|
||||||
@@ -182,7 +179,7 @@ namespace ZeroTier
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
throw new ZeroTierException(err, ZeroTier.Node.ErrNo);
|
throw new ZeroTier.SocketException(err, ZeroTier.Node.ErrNo);
|
||||||
}
|
}
|
||||||
_remoteEndPoint = remoteEndPoint;
|
_remoteEndPoint = remoteEndPoint;
|
||||||
}
|
}
|
||||||
@@ -194,7 +191,7 @@ namespace ZeroTier
|
|||||||
}
|
}
|
||||||
if (_fd < 0) {
|
if (_fd < 0) {
|
||||||
// Invalid file descriptor
|
// Invalid file descriptor
|
||||||
throw new SocketException((int)Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
if (localEndPoint == null) {
|
if (localEndPoint == null) {
|
||||||
throw new ArgumentNullException(nameof(localEndPoint));
|
throw new ArgumentNullException(nameof(localEndPoint));
|
||||||
@@ -239,7 +236,7 @@ namespace ZeroTier
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
throw new SocketException((int)err);
|
throw new ZeroTier.SocketException((int)err);
|
||||||
}
|
}
|
||||||
_localEndPoint = localEndPoint;
|
_localEndPoint = localEndPoint;
|
||||||
}
|
}
|
||||||
@@ -251,12 +248,12 @@ namespace ZeroTier
|
|||||||
}
|
}
|
||||||
if (_fd < 0) {
|
if (_fd < 0) {
|
||||||
// Invalid file descriptor
|
// Invalid file descriptor
|
||||||
throw new SocketException((int)Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
int err = Constants.ERR_OK;
|
int err = Constants.ERR_OK;
|
||||||
if ((err = zts_listen(_fd, backlog)) < 0) {
|
if ((err = zts_listen(_fd, backlog)) < 0) {
|
||||||
// Invalid backlog value perhaps?
|
// Invalid backlog value perhaps?
|
||||||
throw new SocketException((int)Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
_isListening = true;
|
_isListening = true;
|
||||||
}
|
}
|
||||||
@@ -268,7 +265,7 @@ namespace ZeroTier
|
|||||||
}
|
}
|
||||||
if (_fd < 0) {
|
if (_fd < 0) {
|
||||||
// Invalid file descriptor
|
// Invalid file descriptor
|
||||||
throw new SocketException((int)Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
if (_isListening == false) {
|
if (_isListening == false) {
|
||||||
throw new InvalidOperationException("Socket is not in a listening state. Call Listen() first");
|
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);
|
int err = zts_accept(_fd, remoteAddrPtr, addrlenPtr);
|
||||||
if (err < 0) {
|
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));
|
in4 = (zts_sockaddr_in)Marshal.PtrToStructure(remoteAddrPtr, typeof(zts_sockaddr_in));
|
||||||
// Convert sockaddr contents to IPEndPoint
|
// 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)
|
public Int32 Send(Byte[] buffer)
|
||||||
{
|
{
|
||||||
if (_isClosed) {
|
if (_isClosed) {
|
||||||
throw new ObjectDisposedException("Socket has been closed");
|
throw new ObjectDisposedException("Socket has been closed");
|
||||||
}
|
}
|
||||||
if (_fd < 0) {
|
if (_fd < 0) {
|
||||||
throw new SocketException((int)ZeroTier.Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)ZeroTier.Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
if (buffer == null) {
|
if (buffer == null) {
|
||||||
throw new ArgumentNullException(nameof(buffer));
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
@@ -362,7 +425,7 @@ namespace ZeroTier
|
|||||||
throw new ObjectDisposedException("Socket has been closed");
|
throw new ObjectDisposedException("Socket has been closed");
|
||||||
}
|
}
|
||||||
if (_fd < 0) {
|
if (_fd < 0) {
|
||||||
throw new SocketException((int)ZeroTier.Constants.ERR_SOCKET);
|
throw new ZeroTier.SocketException((int)ZeroTier.Constants.ERR_SOCKET);
|
||||||
}
|
}
|
||||||
if (buffer == null) {
|
if (buffer == null) {
|
||||||
throw new ArgumentNullException(nameof(buffer));
|
throw new ArgumentNullException(nameof(buffer));
|
||||||
@@ -483,10 +546,7 @@ namespace ZeroTier
|
|||||||
[DllImport("libzt", EntryPoint="CSharp_zts_errno_get")]
|
[DllImport("libzt", EntryPoint="CSharp_zts_errno_get")]
|
||||||
static extern int zts_errno_get();
|
static extern int zts_errno_get();
|
||||||
|
|
||||||
/// <summary>
|
/// <value>The value of errno for the low-level socket layer</value>
|
||||||
/// Gets the value of errno from the unmanaged region
|
|
||||||
/// </summary>
|
|
||||||
/// <value></value>
|
|
||||||
public static int ErrNo {
|
public static int ErrNo {
|
||||||
get {
|
get {
|
||||||
return zts_errno_get();
|
return zts_errno_get();
|
||||||
@@ -519,5 +579,13 @@ namespace ZeroTier
|
|||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
public char[] sin_zero; // SIN_ZERO_LEN
|
public char[] sin_zero; // SIN_ZERO_LEN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
struct zts_pollfd
|
||||||
|
{
|
||||||
|
public int fd;
|
||||||
|
public short events;
|
||||||
|
public short revents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
examples/csharp/SocketException.cs
Normal file
38
examples/csharp/SocketException.cs
Normal file
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>Exception class for ZeroTier service and low-level socket errors</summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
/// <value>High-level service error code. See Constants.cs</value>
|
||||||
|
public int ServiceErrorCode { get; }
|
||||||
|
|
||||||
|
/// <value>Low-level socket error code. See Constants.cs</value>
|
||||||
|
public int SocketErrorCode { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,11 +87,31 @@ public class ExampleApp {
|
|||||||
while (true) {
|
while (true) {
|
||||||
Console.WriteLine("Waiting for a connection...");
|
Console.WriteLine("Waiting for a connection...");
|
||||||
// Program is suspended while waiting for an incoming connection.
|
// Program is suspended while waiting for an incoming connection.
|
||||||
Console.WriteLine("accepting...");
|
bool nonblocking = true;
|
||||||
ZeroTier.Socket handler = listener.Accept();
|
|
||||||
data = null;
|
|
||||||
|
|
||||||
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.
|
// An incoming connection needs to be processed.
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -112,7 +132,7 @@ public class ExampleApp {
|
|||||||
handler.Close();
|
handler.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ZeroTier.ZeroTierException e) {
|
} catch (ZeroTier.SocketException e) {
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode);
|
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.
|
// Connect the socket to the remote endpoint. Catch any errors.
|
||||||
try {
|
try {
|
||||||
|
|
||||||
Console.WriteLine("Socket connecting to {0}...",
|
Console.WriteLine("Socket connecting to {0}...",
|
||||||
remoteServerEndPoint.ToString());
|
remoteServerEndPoint.ToString());
|
||||||
|
|
||||||
@@ -162,9 +181,7 @@ public class ExampleApp {
|
|||||||
|
|
||||||
} catch (ArgumentNullException ane) {
|
} catch (ArgumentNullException ane) {
|
||||||
Console.WriteLine("ArgumentNullException : {0}",ane.ToString());
|
Console.WriteLine("ArgumentNullException : {0}",ane.ToString());
|
||||||
} catch (SocketException se) {
|
} catch (ZeroTier.SocketException e) {
|
||||||
Console.WriteLine("SocketException : {0}",se.ToString());
|
|
||||||
} catch (ZeroTier.ZeroTierException e) {
|
|
||||||
Console.WriteLine(e);
|
Console.WriteLine(e);
|
||||||
Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode);
|
Console.WriteLine("ServiveErrorCode={0} SocketErrorCode={1}", e.ServiceErrorCode, e.SocketErrorCode);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user