diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs new file mode 100644 index 0000000000..2431574d4e --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class Libuv + { + private IntPtr _module = IntPtr.Zero; + + [DllImport("kernel32")] + public static extern IntPtr LoadLibrary(string dllToLoad); + + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + + [DllImport("kernel32")] + public static extern bool FreeLibrary(IntPtr hModule); + + + public void Load(string dllToLoad) + { + var module = LoadLibrary(dllToLoad); + foreach (var field in GetType().GetTypeInfo().DeclaredFields) + { + var procAddress = GetProcAddress(module, field.Name.TrimStart('_')); + if (procAddress == IntPtr.Zero) + { + continue; + } + var value = Marshal.GetDelegateForFunctionPointer(procAddress, field.FieldType); + field.SetValue(this, value); + } + } + + public int Check(int statusCode) + { + if (statusCode < 0) + { + throw new Exception("Status code " + statusCode); + } + return statusCode; + } + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_loop_init(UvLoopHandle a0); + uv_loop_init _uv_loop_init; + public void loop_init(UvLoopHandle handle) + { + Check(_uv_loop_init(handle)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_loop_close(IntPtr a0); + uv_loop_close _uv_loop_close; + public void loop_close(UvLoopHandle handle) + { + Check(_uv_loop_close(handle.DangerousGetHandle())); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_run(UvLoopHandle handle, int mode); + uv_run _uv_run; + public int run(UvLoopHandle handle, int mode) + { + return Check(_uv_run(handle, mode)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void uv_stop(UvLoopHandle handle); + uv_stop _uv_stop; + public void stop(UvLoopHandle handle) + { + _uv_stop(handle); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_close_cb(IntPtr handle); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void uv_close(IntPtr handle, uv_close_cb close_cb); + uv_close _uv_close; + public void close(UvHandle handle, uv_close_cb close_cb) + { + _uv_close(handle.DangerousGetHandle(), close_cb); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_async_cb(IntPtr handle); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb); + uv_async_init _uv_async_init; + public void async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb) + { + Check(_uv_async_init(loop, handle, cb)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_async_send(UvAsyncHandle handle); + uv_async_send _uv_async_send; + public void async_send(UvAsyncHandle handle) + { + Check(_uv_async_send(handle)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle); + uv_tcp_init _uv_tcp_init; + public void tcp_init(UvLoopHandle loop, UvTcpHandle handle) + { + Check(_uv_tcp_init(loop, handle)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags); + uv_tcp_bind _uv_tcp_bind; + public void tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags) + { + Check(_uv_tcp_bind(handle, ref addr, flags)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_connection_cb(IntPtr server, int status); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_listen(UvStreamHandle handle, int backlog, uv_connection_cb cb); + uv_listen _uv_listen; + public void listen(UvStreamHandle handle, int backlog, uv_connection_cb cb) + { + Check(_uv_listen(handle, backlog, cb)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_accept(UvStreamHandle server, UvStreamHandle client); + uv_accept _uv_accept; + public void accept(UvStreamHandle server, UvStreamHandle client) + { + Check(_uv_accept(server, client)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_alloc_cb(IntPtr server, int suggested_size, out uv_buf_t buf); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_read_cb(IntPtr server, int nread, ref uv_buf_t buf); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb); + uv_read_start _uv_read_start; + public void read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb) + { + Check(_uv_read_start(handle, alloc_cb, read_cb)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_read_stop(UvStreamHandle handle); + uv_read_stop _uv_read_stop; + public void read_stop(UvStreamHandle handle) + { + Check(_uv_read_stop(handle)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_ip4_addr(string ip, int port, out sockaddr addr); + + uv_ip4_addr _uv_ip4_addr; + public void ip4_addr(string ip, int port, out sockaddr addr) + { + Check(_uv_ip4_addr(ip, port, out addr)); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int uv_ip6_addr(string ip, int port, out sockaddr addr); + + uv_ip6_addr _uv_ip6_addr; + public void ip6_addr(string ip, int port, out sockaddr addr) + { + Check(_uv_ip6_addr(ip, port, out addr)); + } + + public struct sockaddr + { + long w; + long x; + long y; + long z; + } + + public struct uv_buf_t + { + public uint len; + public IntPtr memory; + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs new file mode 100644 index 0000000000..6d8f919abb --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class UvAsyncHandle : UvHandle + { + private static Libuv.uv_async_cb _uv_async_cb = AsyncCb; + + unsafe static void AsyncCb(IntPtr handle) + { + GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle); + var self = (UvAsyncHandle)gcHandle.Target; + self._callback.Invoke(); + } + + private Action _callback; + + public void Init(UvLoopHandle loop, Action callback) + { + CreateHandle(loop, 256); + _callback = callback; + _uv.async_init(loop, this, _uv_async_cb); + } + + private void UvAsyncCb(IntPtr handle) + { + _callback.Invoke(); + } + + public void Send() + { + _uv.async_send(this); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs new file mode 100644 index 0000000000..d03240e45c --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public abstract class UvHandle : SafeHandle + { + protected Libuv _uv; + static Libuv.uv_close_cb _close_cb = DestroyHandle; + + public UvHandle() : base(IntPtr.Zero, true) + { + } + + public override bool IsInvalid + { + get + { + return handle == IntPtr.Zero; + } + } + + unsafe protected void CreateHandle(Libuv uv, int size) + { + _uv = uv; + handle = Marshal.AllocCoTaskMem(size); + *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + } + + protected void CreateHandle(UvLoopHandle loop, int size) + { + CreateHandle(loop._uv, size); + } + protected override bool ReleaseHandle() + { + var memory = handle; + if (memory != IntPtr.Zero) + { + _uv.close(this, _close_cb); + handle = IntPtr.Zero; + } + return true; + } + + unsafe protected static void DestroyHandle(IntPtr memory) + { + var gcHandlePtr = *(IntPtr*)memory; + if (gcHandlePtr != IntPtr.Zero) + { + GCHandle.FromIntPtr(gcHandlePtr).Free(); + } + Marshal.FreeCoTaskMem(memory); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs new file mode 100644 index 0000000000..73bab4b5f7 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class UvLoopHandle : UvHandle + { + public void Init(Libuv uv) + { + CreateHandle(uv, 256); + _uv.loop_init(this); + } + + + public int Run(int mode = 0) + { + return _uv.run(this, mode); + } + + public void Stop() + { + _uv.stop(this); + } + + protected override bool ReleaseHandle() + { + var memory = this.handle; + if (memory != IntPtr.Zero) + { + _uv.loop_close(this); + handle = IntPtr.Zero; + DestroyHandle(memory); + } + return true; + } + + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs new file mode 100644 index 0000000000..1436852863 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public abstract class UvStreamHandle : UvHandle + { + private Libuv.uv_connection_cb _connection_cb; + private Libuv.uv_alloc_cb _alloc_cb; + private Libuv.uv_read_cb _read_cb; + + private Action _connection; + private Action _alloc; + private Action _read; + + public void Listen(int backlog, Action connection) + { + _connection_cb = OnConnection; + _connection = connection; + _uv.listen(this, 10, _connection_cb); + } + + public void OnConnection(IntPtr server, int status) + { + _connection(status, this); + } + + public void Accept(UvStreamHandle handle) + { + _uv.accept(this, handle); + } + + public void ReadStart(Action read) + { + _alloc_cb = OnAlloc; + _read_cb = OnRead; + _read = read; + _uv.read_start(this, _alloc_cb, _read_cb); + } + + private void OnAlloc(IntPtr server, int suggested_size, out Libuv.uv_buf_t buf) + { + buf = new Libuv.uv_buf_t + { + memory = Marshal.AllocCoTaskMem(suggested_size), + len = (uint)suggested_size, + }; + } + + private void OnRead(IntPtr server, int nread, ref Libuv.uv_buf_t buf) + { + if (nread == -4095) + { + _read(0, null, this); + Marshal.FreeCoTaskMem(buf.memory); + return; + } + var length = _uv.Check(nread); + var data = new byte[length]; + Marshal.Copy(buf.memory, data, 0, length); + Marshal.FreeCoTaskMem(buf.memory); + + _read(length, data, this); + } + + public void ReadStop() + { + _uv.read_stop(this); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs new file mode 100644 index 0000000000..0962ff650c --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class UvTcpHandle : UvStreamHandle + { + public void Init(UvLoopHandle loop) + { + CreateHandle(loop, 256); + _uv.tcp_init(loop, this); + } + + public void Bind(IPEndPoint endpoint) + { + Libuv.sockaddr addr; + _uv.ip4_addr(endpoint.Address.ToString(), endpoint.Port, out addr); + _uv.tcp_bind(this, ref addr, 0); + } + } +} diff --git a/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs b/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs new file mode 100644 index 0000000000..90e4168e28 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestralTests/NetworkingTests.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Server.Kestrel.Networking; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Server.KestralTests +{ + /// + /// Summary description for NetworkingTests + /// + public class NetworkingTests + { + Libuv _uv; + public NetworkingTests() + { + _uv = new Libuv(); + _uv.Load("libuv.dll"); + } + + [Fact] + public async Task LoopCanBeInitAndClose() + { + var loop = new UvLoopHandle(); + loop.Init(_uv); + loop.Run(); + loop.Close(); + } + + [Fact] + public async Task AsyncCanBeSent() + { + var loop = new UvLoopHandle(); + loop.Init(_uv); + var trigger = new UvAsyncHandle(); + var called = false; + trigger.Init(loop, () => + { + called = true; + trigger.Close(); + }); + trigger.Send(); + loop.Run(); + loop.Close(); + Assert.True(called); + } + + [Fact] + public async Task SocketCanBeInitAndClose() + { + var loop = new UvLoopHandle(); + loop.Init(_uv); + var tcp = new UvTcpHandle(); + tcp.Init(loop); + tcp.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + tcp.Close(); + loop.Run(); + loop.Close(); + } + + + [Fact] + public async Task SocketCanListenAndAccept() + { + var loop = new UvLoopHandle(); + loop.Init(_uv); + var tcp = new UvTcpHandle(); + tcp.Init(loop); + tcp.Bind(new IPEndPoint(IPAddress.Loopback, 54321)); + tcp.Listen(10, (status, handle) => + { + var tcp2 = new UvTcpHandle(); + tcp2.Init(loop); + tcp.Accept(tcp2); + tcp2.Close(); + tcp.Close(); + }); + var t = Task.Run(async () => + { + var socket = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + await Task.Factory.FromAsync( + socket.BeginConnect, + socket.EndConnect, + new IPEndPoint(IPAddress.Loopback, 54321), + null, + TaskCreationOptions.None); + socket.Close(); + }); + loop.Run(); + loop.Close(); + await t; + } + + + [Fact] + public async Task SocketCanRead() + { + int bytesRead = 0; + var loop = new UvLoopHandle(); + loop.Init(_uv); + var tcp = new UvTcpHandle(); + tcp.Init(loop); + tcp.Bind(new IPEndPoint(IPAddress.Loopback, 54321)); + tcp.Listen(10, (status, handle) => + { + var tcp2 = new UvTcpHandle(); + tcp2.Init(loop); + tcp.Accept(tcp2); + tcp2.ReadStart((nread, data, handle2) => + { + bytesRead += nread; + if (nread == 0) + { + tcp2.Close(); + } + }); + tcp.Close(); + }); + var t = Task.Run(async () => + { + var socket = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + await Task.Factory.FromAsync( + socket.BeginConnect, + socket.EndConnect, + new IPEndPoint(IPAddress.Loopback, 54321), + null, + TaskCreationOptions.None); + await Task.Factory.FromAsync( + socket.BeginSend, + socket.EndSend, + new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, + SocketFlags.None, + null, + TaskCreationOptions.None); + socket.Close(); + }); + loop.Run(); + loop.Close(); + await t; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.KestralTests/libuv.dll b/test/Microsoft.AspNet.Server.KestralTests/libuv.dll new file mode 100644 index 0000000000..d3a5834718 Binary files /dev/null and b/test/Microsoft.AspNet.Server.KestralTests/libuv.dll differ