From dc028e94514d5cc0575bf8dd259dcdfffe7f44d4 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Tue, 8 Oct 2019 09:50:54 +0000 Subject: embeddable-dll-service: add all functionality to ringlogger --- embeddable-dll-service/csharp/Program.cs | 44 ++++++- embeddable-dll-service/csharp/Ringlogger.cs | 190 +++++++++++++++++++++++++--- 2 files changed, 211 insertions(+), 23 deletions(-) diff --git a/embeddable-dll-service/csharp/Program.cs b/embeddable-dll-service/csharp/Program.cs index 27cefdc9..d99e66fd 100644 --- a/embeddable-dll-service/csharp/Program.cs +++ b/embeddable-dll-service/csharp/Program.cs @@ -8,19 +8,49 @@ using System.Net.Sockets; using System.IO; using System.Text; using System.Diagnostics; +using System.Threading; +using System.Runtime.InteropServices; namespace Tunnel { class Program { - static void Main(string[] args) + + [DllImport("kernel32.dll")] + private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handler, bool add); + private delegate bool SetConsoleCtrlEventHandler(UInt32 signal); + + public static void Main(string[] args) { if (args.Length == 2 && args[0] == "/service") { Service.Run(args[1]); return; } + + var baseDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + var configFile = Path.Combine(baseDirectory, "demobox.conf"); + var logFile = Path.Combine(baseDirectory, "log.bin"); + + try { File.Delete(logFile); } catch { } + Ringlogger log = new Ringlogger(logFile, "GUI"); + + var logPrintingThread = new Thread(() => + { + var cursor = Ringlogger.CursorAll; + while (Thread.CurrentThread.IsAlive) + { + var lines = log.FollowFromCursor(ref cursor); + foreach (var line in lines) + Console.WriteLine(line); + Thread.Sleep(300); + } + }); + logPrintingThread.Start(); + + log.Write("Generating keys"); var keys = Keypair.Generate(); + log.Write("Exchanging keys with demo server"); var client = new TcpClient("demo.wireguard.com", 42912); var stream = client.GetStream(); var reader = new StreamReader(stream, Encoding.UTF8); @@ -37,15 +67,21 @@ namespace Tunnel if (status != "OK") throw new InvalidOperationException(String.Format("Server status is {0}", status)); + SetConsoleCtrlHandler(delegate + { + Service.Remove(configFile); + Environment.Exit(0); + return true; + }, true); + + log.Write("Writing config file to disk"); var configFileContents = String.Format("[Interface]\nPrivateKey = {0}\nAddress = {1}/24\nDNS = 8.8.8.8, 8.8.4.4\n\n[Peer]\nPublicKey = {2}\nEndpoint = demo.wireguard.com:{3}\nAllowedIPs = 0.0.0.0/0\n", keys.Private, internalIP, serverPubkey, serverPort); - var configFile = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "demobox.conf"); File.WriteAllText(configFile, configFileContents); try { Service.Add(configFile); - Console.WriteLine("=== Press enter to exit ==="); - Console.ReadLine(); + logPrintingThread.Join(); } finally { diff --git a/embeddable-dll-service/csharp/Ringlogger.cs b/embeddable-dll-service/csharp/Ringlogger.cs index 711c22e7..c265142c 100644 --- a/embeddable-dll-service/csharp/Ringlogger.cs +++ b/embeddable-dll-service/csharp/Ringlogger.cs @@ -7,40 +7,192 @@ using System; using System.IO; using System.IO.MemoryMappedFiles; using System.Text; +using System.Collections.Generic; +using System.Threading; +using System.Runtime.CompilerServices; namespace Tunnel { public class Ringlogger { - private readonly MemoryMappedViewAccessor _viewAccessor; + private struct UnixTimestamp + { + private Int64 _ns; + public UnixTimestamp(Int64 ns) => _ns = ns; + public bool IsEmpty => _ns == 0; + public static UnixTimestamp Empty => new UnixTimestamp(0); + public static UnixTimestamp Now + { + get + { + var now = DateTimeOffset.UtcNow; + var ns = (now.Subtract(DateTimeOffset.FromUnixTimeSeconds(0)).Ticks * 100) % 1000000000; + return new UnixTimestamp(now.ToUnixTimeSeconds() * 1000000000 + ns); + } + } + public Int64 Nanoseconds => _ns; + public override string ToString() + { + return DateTimeOffset.FromUnixTimeSeconds(_ns / 1000000000).LocalDateTime.ToString("yyyy'-'MM'-'dd HH':'mm':'ss'.'") + ((_ns % 1000000000).ToString() + "00000").Substring(0, 6); + } + } + private struct Line + { + private const int maxLineLength = 512; + private const int offsetTimeNs = 0; + private const int offsetLine = 8; + + private readonly MemoryMappedViewAccessor _view; + private readonly int _start; + public Line(MemoryMappedViewAccessor view, UInt32 index) => (_view, _start) = (view, (int)(Log.HeaderBytes + index * Bytes)); + + public static int Bytes => maxLineLength + offsetLine; + + public UnixTimestamp Timestamp + { + get => new UnixTimestamp(_view.ReadInt64(_start + offsetTimeNs)); + set => _view.Write(_start + offsetTimeNs, value.Nanoseconds); + } + + public string Text + { + get + { + var textBytes = new byte[maxLineLength]; + _view.ReadArray(_start + offsetLine, textBytes, 0, textBytes.Length); + var nullByte = Array.IndexOf(textBytes, 0); + if (nullByte <= 0) + return null; + return Encoding.UTF8.GetString(textBytes, 0, nullByte); + } + set + { + if (value == null) + { + _view.WriteArray(_start + offsetLine, new byte[maxLineLength], 0, maxLineLength); + return; + } + var textBytes = Encoding.UTF8.GetBytes(value); + var bytesToWrite = Math.Min(maxLineLength - 1, textBytes.Length); + _view.Write(_start + offsetLine + bytesToWrite, (byte)0); + _view.WriteArray(_start + offsetLine, textBytes, 0, bytesToWrite); + } + } - public Ringlogger(string filename) + public override string ToString() + { + var time = Timestamp; + if (time.IsEmpty) + return null; + var text = Text; + if (text == null) + return null; + return string.Format("{0}: {1}", time, text); + } + } + private struct Log { - var file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); - var mmap = MemoryMappedFile.CreateFromFile(file, null, 0, MemoryMappedFileAccess.Read, HandleInheritability.None, false); - _viewAccessor = mmap.CreateViewAccessor(0, 8 + 2048 * (512 + 8), MemoryMappedFileAccess.Read); - if (_viewAccessor.ReadUInt32(0) != 0xbadbabe) - throw new InvalidDataException("The provided file is missing the magic number."); + private const UInt32 maxLines = 2048; + private const UInt32 magic = 0xbadbabe; + private const int offsetMagic = 0; + private const int offsetNextIndex = 4; + private const int offsetLines = 8; + + private readonly MemoryMappedViewAccessor _view; + public Log(MemoryMappedViewAccessor view) => _view = view; + + public static int HeaderBytes => offsetLines; + public static int Bytes => (int)(HeaderBytes + Line.Bytes * maxLines); + + public UInt32 ExpectedMagic => magic; + public UInt32 Magic + { + get => _view.ReadUInt32(offsetMagic); + set => _view.Write(offsetMagic, value); + } + + public UInt32 NextIndex + { + get => _view.ReadUInt32(offsetNextIndex); + set => _view.Write(offsetNextIndex, value); + } + public unsafe UInt32 InsertNextIndex() => (UInt32)Interlocked.Increment(ref Unsafe.AsRef((_view.SafeMemoryMappedViewHandle.DangerousGetHandle() + offsetNextIndex).ToPointer())); + + public UInt32 LineCount => maxLines; + public Line this[UInt32 i] => new Line(_view, i % maxLines); + + public void Clear() => _view.WriteArray(0, new byte[Log.Bytes], 0, Log.Bytes); + } + + private readonly Log _log; + private readonly string _tag; + + public Ringlogger(string filename, string tag) + { + var file = File.Open(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete); + file.SetLength(Log.Bytes); + var mmap = MemoryMappedFile.CreateFromFile(file, null, 0, MemoryMappedFileAccess.ReadWrite, HandleInheritability.None, false); + var view = mmap.CreateViewAccessor(0, Log.Bytes, MemoryMappedFileAccess.ReadWrite); + _log = new Log(view); + if (_log.Magic != _log.ExpectedMagic) + { + _log.Clear(); + _log.Magic = _log.ExpectedMagic; + } + _tag = tag; + } + + public void Write(string line) + { + var time = UnixTimestamp.Now; + var entry = _log[_log.InsertNextIndex() - 1]; + entry.Timestamp = UnixTimestamp.Empty; + entry.Text = null; + entry.Text = string.Format("[{0}] {1}", _tag, line.Trim()); + entry.Timestamp = time; } public void WriteTo(TextWriter writer) { - var start = _viewAccessor.ReadUInt32(4); - for (var i = 0; i < 2048; ++i) + var start = _log.NextIndex; + for (UInt32 i = 0; i < _log.LineCount; ++i) { - var lineOffset = 8 + (8 + 512) * ((i + start) % 2048); - var timeNs = _viewAccessor.ReadInt64(lineOffset); - if (timeNs == 0) + var entry = _log[i + start]; + if (entry.Timestamp.IsEmpty) + continue; + var text = entry.ToString(); + if (text == null) continue; - var textBytes = new byte[512]; - _viewAccessor.ReadArray(lineOffset + 8, textBytes, 0, textBytes.Length); - var nullByte = Array.IndexOf(textBytes, 0); - if (nullByte <= 0) + writer.WriteLine(text); + } + } + + public static readonly UInt32 CursorAll = UInt32.MaxValue; + public List FollowFromCursor(ref UInt32 cursor) + { + var lines = new List((int)_log.LineCount); + var i = cursor; + var all = cursor == CursorAll; + if (all) + i = _log.NextIndex; + for (UInt32 l = 0; l < _log.LineCount; ++l, ++i) + { + if (!all && i % _log.LineCount == _log.NextIndex % _log.LineCount) + break; + var entry = _log[i]; + if (entry.Timestamp.IsEmpty) + { + if (all) + continue; + break; + } + cursor = (i + 1) % _log.LineCount; + var text = entry.ToString(); + if (text == null) continue; - var text = Encoding.UTF8.GetString(textBytes, 0, nullByte); - var time = DateTimeOffset.FromUnixTimeMilliseconds(timeNs / 1000000).ToString("yyyy'-'MM'-'dd HH':'mm':'ss'.'ffffff"); - writer.WriteLine(String.Format("{0}: {1}", time, text)); + lines.Add(text); } + return lines; } } } -- cgit v1.2.3-59-g8ed1b