diff --git a/NetworkResurrector.Abstractions/INotifier.cs b/NetworkResurrector.Abstractions/INotifier.cs new file mode 100644 index 0000000..04d1f9e --- /dev/null +++ b/NetworkResurrector.Abstractions/INotifier.cs @@ -0,0 +1,8 @@ +namespace NetworkResurrector.Abstractions +{ + public interface INotifier + { + void NotifyMessage(string message); + void NotifyError(string message); + } +} diff --git a/NetworkResurrector.Abstractions/IWakeOnLanService.cs b/NetworkResurrector.Abstractions/IWakeOnLanService.cs new file mode 100644 index 0000000..3901008 --- /dev/null +++ b/NetworkResurrector.Abstractions/IWakeOnLanService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace NetworkResurrector.Abstractions +{ + public interface IWakeOnLanService + { + Task<(bool success, string message)> Wake(string macAddress); + } +} diff --git a/NetworkResurrector.Abstractions/NetworkResurrector.Abstractions.csproj b/NetworkResurrector.Abstractions/NetworkResurrector.Abstractions.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/NetworkResurrector.Abstractions/NetworkResurrector.Abstractions.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/NetworkResurrector.Api/Controllers/ResurrectorController.cs b/NetworkResurrector.Api/Controllers/ResurrectorController.cs index 6bcc75b..e9160e0 100644 --- a/NetworkResurrector.Api/Controllers/ResurrectorController.cs +++ b/NetworkResurrector.Api/Controllers/ResurrectorController.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using NetworkResurrector.Application.Commands; using NetworkResurrector.Application.Queries; using System.Threading.Tasks; @@ -31,5 +32,12 @@ namespace NetworkResurrector.Api.Controllers { return Ok("Valid"); } + + [HttpPost("wake")] + public async Task WakeMachine([FromBody] WakeMachine wakeMachine) + { + var result = await _mediator.Send(wakeMachine); + return Ok(result); + } } } diff --git a/NetworkResurrector.Api/Extensions/WakeOnLanExtensions.cs b/NetworkResurrector.Api/Extensions/WakeOnLanExtensions.cs new file mode 100644 index 0000000..ce7a454 --- /dev/null +++ b/NetworkResurrector.Api/Extensions/WakeOnLanExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetworkResurrector.WakeOnLan; + +namespace NetworkResurrector.Api.Extensions +{ + public static class WakeOnLanExtensions + { + public static void AddWakeOnLan(this IServiceCollection services, IConfiguration configuration) + { + services.AddWakeOnLanService(); + } + } +} diff --git a/NetworkResurrector.Api/NetworkResurrector.Api.csproj b/NetworkResurrector.Api/NetworkResurrector.Api.csproj index 6f5a4b1..8105434 100644 --- a/NetworkResurrector.Api/NetworkResurrector.Api.csproj +++ b/NetworkResurrector.Api/NetworkResurrector.Api.csproj @@ -23,6 +23,7 @@ + diff --git a/NetworkResurrector.Api/Startup.cs b/NetworkResurrector.Api/Startup.cs index 81e9148..61bc16b 100644 --- a/NetworkResurrector.Api/Startup.cs +++ b/NetworkResurrector.Api/Startup.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using NetworkResurrector.Api.Authentication; +using NetworkResurrector.Api.Extensions; using NetworkResurrector.Api.Swagger; using NetworkResurrector.Application; using Newtonsoft.Json; @@ -17,13 +18,13 @@ namespace NetworkResurrector.Api { public class Startup { + private readonly IConfiguration _configuration; + public Startup(IConfiguration configuration) { - Configuration = configuration; + _configuration = configuration; } - public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { @@ -48,6 +49,9 @@ namespace NetworkResurrector.Api // Application services.AddApplicationServices(); + + // WakeOnLan + services.AddWakeOnLan(_configuration); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/NetworkResurrector.Application/CommandHandlers/WakeMachineHandler.cs b/NetworkResurrector.Application/CommandHandlers/WakeMachineHandler.cs new file mode 100644 index 0000000..397e26b --- /dev/null +++ b/NetworkResurrector.Application/CommandHandlers/WakeMachineHandler.cs @@ -0,0 +1,38 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using NetworkResurrector.Abstractions; +using NetworkResurrector.Application.Commands; +using NetworkResurrector.Application.Events; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkResurrector.Application.CommandHandlers +{ + public class WakeMachineHandler : IRequestHandler + { + private readonly ILogger _logger; + private readonly IWakeOnLanService _wakeOnLanService; + + public WakeMachineHandler(ILogger logger, IWakeOnLanService wakeOnLanService) + { + _logger = logger; + _wakeOnLanService = wakeOnLanService; + } + + public async Task Handle(WakeMachine command, CancellationToken cancellationToken) + { + try + { + var (success, status) = await _wakeOnLanService.Wake(command.MacAddress); + return new MachineWaked(success, status); + } + catch (Exception ex) + { + var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}"; + _logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}"); + return new MachineWaked(false, $"{ex.Message} {correlationIdMsg}"); + } + } + } +} diff --git a/NetworkResurrector.Application/Commands/WakeMachine.cs b/NetworkResurrector.Application/Commands/WakeMachine.cs new file mode 100644 index 0000000..2ace6a3 --- /dev/null +++ b/NetworkResurrector.Application/Commands/WakeMachine.cs @@ -0,0 +1,15 @@ +using NetworkResurrector.Application.Events; +using System; + +namespace NetworkResurrector.Application.Commands +{ + public class WakeMachine : Command + { + public string MacAddress { get; set; } + + public WakeMachine(string macAddress) : base(new Metadata() { CorrelationId = Guid.NewGuid() }) + { + MacAddress = macAddress; + } + } +} \ No newline at end of file diff --git a/NetworkResurrector.Application/Events/MachineWaked.cs b/NetworkResurrector.Application/Events/MachineWaked.cs new file mode 100644 index 0000000..f249f9d --- /dev/null +++ b/NetworkResurrector.Application/Events/MachineWaked.cs @@ -0,0 +1,14 @@ +namespace NetworkResurrector.Application.Events +{ + public class MachineWaked + { + public bool Success { get; } + public string Status { get; } + + public MachineWaked(bool success, string status) + { + Success = success; + Status = status; + } + } +} diff --git a/NetworkResurrector.Application/NetworkResurrector.Application.csproj b/NetworkResurrector.Application/NetworkResurrector.Application.csproj index dd8ca69..78e26c2 100644 --- a/NetworkResurrector.Application/NetworkResurrector.Application.csproj +++ b/NetworkResurrector.Application/NetworkResurrector.Application.csproj @@ -14,6 +14,7 @@ + diff --git a/NetworkResurrector.WakeOnLan/DependencyInjectionExtensions.cs b/NetworkResurrector.WakeOnLan/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..87ea4fa --- /dev/null +++ b/NetworkResurrector.WakeOnLan/DependencyInjectionExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using NetworkResurrector.Abstractions; + +namespace NetworkResurrector.WakeOnLan +{ + public static class DependencyInjectionExtensions + { + public static void AddWakeOnLanService(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + } + } +} diff --git a/NetworkResurrector.WakeOnLan/NetworkResurrector.WakeOnLan.csproj b/NetworkResurrector.WakeOnLan/NetworkResurrector.WakeOnLan.csproj new file mode 100644 index 0000000..a0dc859 --- /dev/null +++ b/NetworkResurrector.WakeOnLan/NetworkResurrector.WakeOnLan.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + + + + + + + + + + + + diff --git a/NetworkResurrector.WakeOnLan/WolClient.cs b/NetworkResurrector.WakeOnLan/WolClient.cs new file mode 100644 index 0000000..b6c34f1 --- /dev/null +++ b/NetworkResurrector.WakeOnLan/WolClient.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Net.Sockets; + +namespace NetworkResurrector.WakeOnLan +{ + internal class WolClient : UdpClient + { + private readonly ILogger _logger; + + /// + /// Initializes a new instance of . + /// + public WolClient(ILogger logger) : base() + { + _logger = logger; + } + + /// + /// Sets up the UDP client to broadcast packets. + /// + /// if the UDP client is set in + /// broadcast mode. + public bool SetClientInBrodcastMode() + { + bool broadcast = false; + if (this.Active) + { + try + { + this.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 0); + _logger.LogInformation("WolClient => SetClientInBrodcastMode succedded."); + broadcast = true; + } + catch(Exception ex) + { + broadcast = false; + _logger.LogError("WolClient => SetClientInBrodcastMode failed.", ex); + } + } + + return broadcast; + } + } +} diff --git a/NetworkResurrector.WakeOnLan/WolDriver.cs b/NetworkResurrector.WakeOnLan/WolDriver.cs new file mode 100644 index 0000000..019bf63 --- /dev/null +++ b/NetworkResurrector.WakeOnLan/WolDriver.cs @@ -0,0 +1,92 @@ +using NetworkResurrector.Abstractions; +using System.Globalization; +using System.Net; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NetworkResurrector.WakeOnLan +{ + internal class WolDriver : IWakeOnLanService + { + private readonly WolClient _client; + + public WolDriver(WolClient client) + { + _client = client; + } + + public async Task<(bool success, string message)> Wake(string macAddress) + { + //remove all non 0-9, A-F, a-f characters + macAddress = Regex.Replace(macAddress, @"[^0-9A-Fa-f]", ""); + + //check if mac adress length is valid + if (macAddress.Length != 12) + return (false, "Invalid MAC address."); + else + return await Wakeup(macAddress); + } + + #region Wakeup + /// + /// Wakes up the machine with the given . + /// + /// + /// + /// + /// + /// The motherboard must support Wake On LAN. + /// The NIC must support Wake On LAN. + /// There must be a wire connecting the motherboard's WOL port to + /// the NIC's WOL port. Usually there always is a connection on most of + /// the PCs. + /// The Wake On LAN feature must be enabled in the motherboard's + /// BIOS. Usually this is also enabled by default, but you might like to + /// check again. + /// The "Good Connection" light on the back of the NIC must be lit + /// when the machine is off. (By default always good if you are not + /// facing any network issues) + /// Port 12287 (0x2FFF) must be open. (By default it should be + /// open unless some antivirus or any other such program has changed + /// settings.) + /// Packets cannot be broadcast across the Internet.  That's why + /// it's called Wake On Lan, not Wake On Internet. + /// To find your MAC address, run the MSINFO32.EXE tool that is a + /// part of Windows and navigate to Components > Network > Adapter + /// or simply type nbtstat -a <your hostname < at command prompt. + /// e.g. nbtstat -a mymachinename or nbtstat -A 10.2.100.213. + /// The MAC address of the host which has to be + /// woken up. + /// + private async Task<(bool success, string message)> Wakeup(string macAddress) + { + _client.Connect(new IPAddress(0xffffffff) /*255.255.255.255  i.e broadcast*/, 0x2fff /*port = 12287*/); + + if (!_client.SetClientInBrodcastMode()) + return (false, "Remote client could not be set in broadcast mode!"); + + int byteCount = 0; + byte[] bytes = new byte[102]; + + for (int trailer = 0; trailer < 6; trailer++) + { + bytes[byteCount++] = 0xFF; + } + + for (int macPackets = 0; macPackets < 16; macPackets++) + { + int i = 0; + for (int macBytes = 0; macBytes < 6; macBytes++) + { + bytes[byteCount++] = byte.Parse(macAddress.Substring(i, 2), NumberStyles.HexNumber); + i += 2; + } + } + + int returnValue = await _client.SendAsync(bytes, byteCount); + + return (true, $"{returnValue} bytes sent to {macAddress}"); + } + #endregion + } +} diff --git a/NetworkResurrector.sln b/NetworkResurrector.sln index 3a84ceb..c47a381 100644 --- a/NetworkResurrector.sln +++ b/NetworkResurrector.sln @@ -13,9 +13,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkResurrector.Application", "NetworkResurrector.Application\NetworkResurrector.Application.csproj", "{15D65D65-CC96-45DE-8590-AF9132889D98}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkResurrector.Application", "NetworkResurrector.Application\NetworkResurrector.Application.csproj", "{15D65D65-CC96-45DE-8590-AF9132889D98}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkResurrector.Domain", "NetworkResurrector.Domain\NetworkResurrector.Domain.csproj", "{EC78E88E-22DC-4FFD-881E-DEECF0D2494E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkResurrector.Domain", "NetworkResurrector.Domain\NetworkResurrector.Domain.csproj", "{EC78E88E-22DC-4FFD-881E-DEECF0D2494E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetworkResurrector.WakeOnLan", "NetworkResurrector.WakeOnLan\NetworkResurrector.WakeOnLan.csproj", "{A7B3FCE1-70F5-4EAE-A8FB-EF938AE1D105}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkResurrector.Abstractions", "NetworkResurrector.Abstractions\NetworkResurrector.Abstractions.csproj", "{B7408385-ED73-4ED3-9654-9AFF8CDFDA8D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -35,6 +39,14 @@ Global {EC78E88E-22DC-4FFD-881E-DEECF0D2494E}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC78E88E-22DC-4FFD-881E-DEECF0D2494E}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC78E88E-22DC-4FFD-881E-DEECF0D2494E}.Release|Any CPU.Build.0 = Release|Any CPU + {A7B3FCE1-70F5-4EAE-A8FB-EF938AE1D105}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7B3FCE1-70F5-4EAE-A8FB-EF938AE1D105}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7B3FCE1-70F5-4EAE-A8FB-EF938AE1D105}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7B3FCE1-70F5-4EAE-A8FB-EF938AE1D105}.Release|Any CPU.Build.0 = Release|Any CPU + {B7408385-ED73-4ED3-9654-9AFF8CDFDA8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7408385-ED73-4ED3-9654-9AFF8CDFDA8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7408385-ED73-4ED3-9654-9AFF8CDFDA8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7408385-ED73-4ED3-9654-9AFF8CDFDA8D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dependencies.props b/dependencies.props index 0bd60d6..40ace42 100644 --- a/dependencies.props +++ b/dependencies.props @@ -9,7 +9,5 @@ 7.0.0 6.0.0 5.3.1 - 3.1.3 - 3.1.3 \ No newline at end of file