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.