3

I keep getting a SocketException: Address already in use when running my program multiple times.

Minimal example:

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace test
{
    class Program
    {
        static TcpListener listener;
        static void Main(string[] args)
        {
            listener = new TcpListener(new IPEndPoint(IPAddress.Any, 3349));
            listener.Start();
            listener.BeginAcceptSocket(Accept, null);
            Console.WriteLine("Started!");

            // Simulate a random other client connecting, nc localhost 3349 does the same thing
            var client = new TcpClient();
            client.Connect("localhost", 3349);

            Thread.Sleep(2000);
            listener.Stop();

            Console.WriteLine("Done!");
        }

        static void Accept(IAsyncResult result)
        {
            using(var socket = listener.EndAcceptSocket(result))
            {
                Console.WriteLine("Accepted socket");
                Thread.Sleep(500);
                socket.Shutdown(SocketShutdown.Both);
            }

            Console.WriteLine("Socket fully closed");
        }
    }
}

Run the program twice (dotnet run): the first time it will complete normally, but the second time it will fail saying "Address already in use".

Note that the missing dispose of client is not the problem here -- I can replicate the same error manually by using nc localhost 3349.

How can I clean up the listener so that I don't run into the error?

OS & .NET info:

dotnet --version
2.1.103

lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.4 LTS
Release:        16.04
Codename:       xenial

This problem is not present on Windows. It also doesn't occur when using mono, so this seems to be specific to Microsoft's linux implementation.

cmpxchg8b
  • 661
  • 5
  • 16
  • A workaround might be to use [SetSocketOption()](https://msdn.microsoft.com/en-us/library/e160993d(v=vs.110).aspx) to set [ReuseAddress](https://msdn.microsoft.com/en-us/library/system.net.sockets.socketoptionname(v=vs.110).aspx) to `true`. – itsme86 Jul 16 '18 at 18:53
  • @itsme86 Thanks! I hadn't thought of that yet. Would you happen to know if that option has any side-effects? (as long as there's never more than one instance of the program running at the same time) – cmpxchg8b Jul 16 '18 at 20:14
  • I've used it myself in the past with no side effects. If there are any, I'm not aware of them. – itsme86 Jul 16 '18 at 20:15
  • 1
    It's pretty standard in servers to use `SO_REUSEADDR`. The likelihood of problems from using it is actually pretty small. The reason for the issue in the first place is explained here: https://stackoverflow.com/a/3233022/1076479 – Gil Hamilton Jul 16 '18 at 22:29

1 Answers1

4

This is actually intended behaviour. To fix the error, you should set the ReuseAddress socket option to true, if and only if the code is not running on Windows:

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
    socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
}

The difference between Windows and Linux (and presumably MacOS) is caused by the differences in the socket implementations:

  • On Windows, you can bind to an address and IP unless there's another connection active on the same address/IP combination.
  • On Linux, you can bind to an address unless there's any other connection on the same address/IP combination. This means that you cannot bind if there's still a connection in the TIME_WAIT or CLOSE_WAIT state.

As explained in this other question, SO_REUSEADDR can be used to relax these restrictions on Linux.

Mono has tried to emulate the behaviour of Windows by setting SO_REUSEADDR to true by default. The official .NET core runtime does not do this, and thus will cause the "Address already in use" error on Linux.

However this does not mean that it's safe to always set SO_REUSEADDR! On Windows, SO_REUSEADDR means something slightly different (source):

A socket with SO_REUSEADDR can always bind to exactly the same source address and port as an already bound socket, even if the other socket did not have this option set when it was bound. This behavior is somewhat dangerous because it allows an application "to steal" the connected port of another application.

Therefore, SO_REUSEADDR should only be set on non-Windows systems.

cmpxchg8b
  • 661
  • 5
  • 16