I'm experiencing a weird problem; I have two applications, a frontend and a backend. The frontend sends messages to the backend using UDP. I have specific reasons to use UDP, but one of them is that I don't care when the backend is down or unreachable for some reason. I just want the messages to get lost. Yes, you read that right 😉
Now forget about the backend. Let's play the scenario the backend isn't up and the frontend sends a UDP packet to the backend anyway. The client sends the message(s), which will never arrive, just fine on Windows. As intended. On Linux (Debian to be exact) what happens is that I get a SocketException (111): Connection refused
. This doesn't happen when I send the messages to a (non existing) remote host. However, as soon as the host is localhost
or 127.0.0.1
or even 192.168.1.1
(the machine's LAN IP) the exception is thrown. When the host is 8.8.8.8
or 192.168.1.10
(some random host in my LAN) or 192.168.1.200
(a non-existing host in my LAN) the exception is not thrown.
Because I thought I was going crazy I tested this with other languages and they all seem to behave correctly (i.e. sending messages (that will never arrive), no exceptions).
Here's what works, ALL on the same Debian 10 machine:
Python:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
sock.sendto(b"Hello, World!", ("127.0.0.1", 12345))
print(".", end = '')
PHP:
<?php
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$msg = "Hello world!";
$len = strlen($msg);
while (true) {
socket_sendto($sock, $msg, $len, 0, '127.0.0.1', 12345);
echo '.';
}
Perl:
use Socket;
socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die "socket: $!";
$MSG = "Hello world!";
$endpoint = sockaddr_in(12345, inet_aton("127.0.0.1"));
while(true) {
send(SOCKET, $MSG, 0, $endpoint) == length($MSG) or die "cannot send: $!";
print(".");
}
For all three above scripts the output is (as expected): .............…
etc.
Now the .Net (C#) versions (I started out asynchronously but in my search I even went back to synchronous):
C# Synchronous:
static void Main(string[] args)
{
var data = Encoding.UTF8.GetBytes("Hello world");
var client = new UdpClient("127.0.0.1", 12345);
while (true) {
client.Send(data, data.Length);
Console.Write(".");
}
}
C# Asynchronous:
static async Task Main(string[] args)
{
var data = Encoding.UTF8.GetBytes("Hello world");
var client = new UdpClient("127.0.0.1", 12345);
while (true) {
await client.SendAsync(data, data.Length).ConfigureAwait(false);
Console.Write(".");
}
}
I even rewrote the code to not use a UdpClient
but a Socket
; same result. .Net keeps throwing a SocketException (111): Connection refused
.
The only thing I found that may be related was this over at PerlMonks:
Snippet from socket man page SOCKET OPTIONS section for option SO_BSDCOMPAT:
If enabled ICMP errors received for a UDP socket will not be passed to the user program. In later kernel versions, support for this option has been phased out: Linux 2.4 silently ignores it, and Linux 2.6 generates a kernel warning (printk()) if a program uses this option.
But since I'm unable to set any flags even remotely resembling something like SO_BSDCOMPAT
this seems to be a dead end (and may not even be the problem in the first place).
Does anyone have ANY clue as to what is going on here?
Summarized: Above C# works fine on Windows when there's no "backend" (or server) listening; messages just get lost - as intended. On Linux (Debian 10) an exception is thrown for C# code but Python, Perl and PHP seem to work just fine, ruling out the OS (IMHO). I'm running dotnet 5.0.202
, Windows 10 and Debian 10 (Linux myhost 4.19.0-13-amd64 #1 SMP Debian 4.19.160-2 (2020-11-28) x86_64 GNU/Linux)
- all up-to-date.
Ok, you gotta be kidding me... As I said:
I even rewrote the code to not use a
UdpClient
but aSocket
...but I used the .Send()
and .SendAsync()
methods on the socket. I just tried it with the .SendTo()
and .SendToAsync()
methods and that seems to work. Which... kind of makes sense because for the .Send()
and .SendAsync()
methods to work you need to invoke .Connect()
first and for .SendTo()
/.SendToAsync()
allows you to specify the remote endpoint in the SendTo...
call itself.
static void Main(string[] args)
{
var data = Encoding.UTF8.GetBytes("Hello world");
var socket = new Socket(SocketType.Dgram, ProtocolType.Udp);
var ep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12345);
while (true) {
socket.SendTo(data, ep);
Console.Write(".");
}
}
So this can / will be marked as solved.