diff --git a/src/log4net.Tests/Appender/TelnetAppenderTest.cs b/src/log4net.Tests/Appender/TelnetAppenderTest.cs index 5a06c23e..60ace78e 100644 --- a/src/log4net.Tests/Appender/TelnetAppenderTest.cs +++ b/src/log4net.Tests/Appender/TelnetAppenderTest.cs @@ -23,6 +23,7 @@ using System.Diagnostics; using System.IO; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using System.Threading; @@ -139,6 +140,8 @@ void WaitFor(string what, string expected) private const string WelcomeMessage = "TelnetAppender"; + private const string TooManyConnectionsMessage = "Sorry - Too many connections."; + [Test] public void TelnetTest() { @@ -398,7 +401,7 @@ private static LoggingEvent CreateEvent(string message) /// /// Binding to the loopback address has to keep the port unreachable from other machines, which - /// is what an operator asking for it wants. + /// is what an operator asking for it wants. A peer other than the appender counts as unreachable. /// [Test] [NonParallelizable] @@ -431,7 +434,9 @@ public void ListenAddressBindsOnlyThatAddress() } using Socket external = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - Assert.That(() => external.Connect(new IPEndPoint(routable!, port)), Throws.TypeOf()); + string greeting = ReadGreeting(external, new IPEndPoint(routable!, port)); + Assert.That(greeting, Does.Not.StartWith(WelcomeMessage).And.Not.StartWith(TooManyConnectionsMessage), + () => DescribeUnexpectedConnection(external, port, greeting)); } finally { @@ -534,6 +539,60 @@ public override void Flush() } } + /// + /// Connects and returns the first line the peer sends, empty if it refuses, closes or stays silent. + /// + private static string ReadGreeting(Socket socket, IPEndPoint endPoint) + { + StringBuilder text = new(); + try + { + socket.Connect(endPoint); + // Generous, since a slow accept must not pass as silence. Only a silent proxy pays for it. + socket.ReceiveTimeout = (int)_receiveTimeout.TotalMilliseconds; + byte[] buffer = new byte[256]; + int read; + do + { + read = socket.Receive(buffer); + text.Append(Encoding.ASCII.GetString(buffer, 0, read)); + } + while (read > 0 && Array.IndexOf(buffer, (byte)'\n', 0, read) < 0); + } + catch (SocketException) + { + // Refused, or silent until the timeout: either way the appender did not answer. + } + return text.ToString(); + } + + /// + /// Collects what a reviewer needs to tell who accepted a connection that should have been refused. + /// + private static string DescribeUnexpectedConnection(Socket connected, int port, string greeting) + { + StringBuilder text = new(); + text.AppendLine($"peer sent: {greeting.Trim()}"); + // A failing lookup must not hide the assertion this message belongs to. + try + { + text.AppendLine($"connected from {connected.LocalEndPoint} to {connected.RemoteEndPoint}"); + text.AppendLine($"host name {Dns.GetHostName()} resolves to [{string.Join(", ", (object[])Dns.GetHostAddresses(Dns.GetHostName()))}]"); + foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces()) + { + string[] addresses = Array.ConvertAll([.. nic.GetIPProperties().UnicastAddresses], a => a.Address.ToString()); + text.AppendLine($"interface {nic.Name} ({nic.NetworkInterfaceType}, {nic.OperationalStatus}): {string.Join(", ", addresses)}"); + } + IPEndPoint[] listeners = Array.FindAll(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners(), l => l.Port == port); + text.AppendLine($"listeners on port {port}: [{string.Join(", ", (object[])listeners)}]"); + } + catch (Exception e) when (!e.IsFatal()) + { + text.AppendLine($"diagnostics failed: {e.Message}"); + } + return text.ToString(); + } + /// /// Asks the OS for a currently unused TCP port - a fixed port would collide with /// other tests or processes on the build machine.