From e7d7eb20ce46dd2e566ca98008e0a8901dffec08 Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Fri, 25 Sep 2026 21:32:02 +0200 Subject: [PATCH 1/2] report who accepted the connection in ListenAddressBindsOnlyThatAddress #328 --- .../Appender/TelnetAppenderTest.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/log4net.Tests/Appender/TelnetAppenderTest.cs b/src/log4net.Tests/Appender/TelnetAppenderTest.cs index 5a06c23e..d421398f 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; @@ -431,7 +432,16 @@ public void ListenAddressBindsOnlyThatAddress() } using Socket external = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - Assert.That(() => external.Connect(new IPEndPoint(routable!, port)), Throws.TypeOf()); + SocketException? refused = null; + try + { + external.Connect(new IPEndPoint(routable!, port)); + } + catch (SocketException e) + { + refused = e; + } + Assert.That(refused, Is.Not.Null, () => DescribeUnexpectedConnection(external, port)); } finally { @@ -534,6 +544,36 @@ public override void Flush() } } + /// + /// Collects what a reviewer needs to tell who accepted a connection that should have been refused. + /// + private static string DescribeUnexpectedConnection(Socket connected, int port) + { + StringBuilder text = new(); + 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)}]"); + // The appender greets every client, so its welcome line identifies it as the peer. + try + { + connected.ReceiveTimeout = 2_000; + byte[] buffer = new byte[256]; + int read = connected.Receive(buffer); + text.AppendLine($"peer sent: {Encoding.ASCII.GetString(buffer, 0, read).Trim()}"); + } + catch (SocketException e) + { + text.AppendLine($"peer sent nothing: {e.SocketErrorCode}"); + } + 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. From a6afc70a030524a944f8dff4ceb310389fcd539c Mon Sep 17 00:00:00 2001 From: Jan Friedrich Date: Mon, 28 Sep 2026 08:56:52 +0200 Subject: [PATCH 2/2] fail ListenAddressBindsOnlyThatAddress only on an appender reply #328 - A filtering proxy (ESET on Linux) accepts the connection and closes it, so a successful connect does not mean the appender is reachable - The test now fails only on the greeting or the too-many-connections reply, which only a wrongly bound appender sends --- .../Appender/TelnetAppenderTest.cs | 73 ++++++++++++------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/log4net.Tests/Appender/TelnetAppenderTest.cs b/src/log4net.Tests/Appender/TelnetAppenderTest.cs index d421398f..60ace78e 100644 --- a/src/log4net.Tests/Appender/TelnetAppenderTest.cs +++ b/src/log4net.Tests/Appender/TelnetAppenderTest.cs @@ -140,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() { @@ -399,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] @@ -432,16 +434,9 @@ public void ListenAddressBindsOnlyThatAddress() } using Socket external = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - SocketException? refused = null; - try - { - external.Connect(new IPEndPoint(routable!, port)); - } - catch (SocketException e) - { - refused = e; - } - Assert.That(refused, Is.Not.Null, () => DescribeUnexpectedConnection(external, port)); + string greeting = ReadGreeting(external, new IPEndPoint(routable!, port)); + Assert.That(greeting, Does.Not.StartWith(WelcomeMessage).And.Not.StartWith(TooManyConnectionsMessage), + () => DescribeUnexpectedConnection(external, port, greeting)); } finally { @@ -545,31 +540,55 @@ public override void Flush() } /// - /// Collects what a reviewer needs to tell who accepted a connection that should have been refused. + /// Connects and returns the first line the peer sends, empty if it refuses, closes or stays silent. /// - private static string DescribeUnexpectedConnection(Socket connected, int port) + private static string ReadGreeting(Socket socket, IPEndPoint endPoint) { StringBuilder text = new(); - 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()) + 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) { - string[] addresses = Array.ConvertAll([.. nic.GetIPProperties().UnicastAddresses], a => a.Address.ToString()); - text.AppendLine($"interface {nic.Name} ({nic.NetworkInterfaceType}, {nic.OperationalStatus}): {string.Join(", ", addresses)}"); + // Refused, or silent until the timeout: either way the appender did not answer. } - IPEndPoint[] listeners = Array.FindAll(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners(), l => l.Port == port); - text.AppendLine($"listeners on port {port}: [{string.Join(", ", (object[])listeners)}]"); - // The appender greets every client, so its welcome line identifies it as the peer. + 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 { - connected.ReceiveTimeout = 2_000; - byte[] buffer = new byte[256]; - int read = connected.Receive(buffer); - text.AppendLine($"peer sent: {Encoding.ASCII.GetString(buffer, 0, read).Trim()}"); + 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 (SocketException e) + catch (Exception e) when (!e.IsFatal()) { - text.AppendLine($"peer sent nothing: {e.SocketErrorCode}"); + text.AppendLine($"diagnostics failed: {e.Message}"); } return text.ToString(); }