[System] Check SocketOptionName.ReuseAddress support.
authorMarcos Henrich <marcos.henrich@xamarin.com>
Thu, 19 Nov 2015 10:53:16 +0000 (10:53 +0000)
committerMarcos Henrich <marcos.henrich@xamarin.com>
Wed, 17 Feb 2016 09:56:14 +0000 (09:56 +0000)
Operating systems that use linux kernels before 3.9 do not support
binding multiple socket to the same address and port.

In those systems even if it was possible to set
SocketOptionName.ReuseAddress an exception was thrown on the second
bind.

These changes introduce a SocketException that is thrown when
SocketOptionName.ReuseAddress is set on a system that will fail to bind
multiple times to the same address.

mcs/class/System/System.Net.Sockets/Socket.cs
mcs/class/System/Test/System.Net.Sockets/SocketTest.cs

index 0f1c977a71d57169dc91c54c8baa329f65d18b38..98d5dc42d0a6bbe79e28a89e15b6c7add1a5ac54 100644 (file)
@@ -3163,27 +3163,24 @@ namespace System.Net.Sockets
 
                public void SetSocketOption (SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue)
                {
-                       ThrowIfDisposedAndClosed ();
-
-                       int error;
                        int int_val = optionValue ? 1 : 0;
-                       SetSocketOption_internal (safe_handle, optionLevel, optionName, null, null, int_val, out error);
 
-                       if (error != 0) {
-                               if (error == (int) SocketError.InvalidArgument)
-                                       throw new ArgumentException ();
-                               throw new SocketException (error);
-                       }
+                       SetSocketOption (optionLevel, optionName, int_val);
                }
 
                public void SetSocketOption (SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue)
                {
                        ThrowIfDisposedAndClosed ();
 
+                       if (optionName == SocketOptionName.ReuseAddress && optionValue != 0 && !SupportsPortReuse ())
+                               throw new SocketException ((int) SocketError.OperationNotSupported, "Operating system sockets do not support ReuseAddress.\nIf your socket is not intended to bind to the same address and port multiple times remove this option, otherwise you should ignore this exception inside a try catch and check that ReuseAddress is true before binding to the same address and port multiple times.");
+
                        int error;
                        SetSocketOption_internal (safe_handle, optionLevel, optionName, null, null, optionValue, out error);
 
                        if (error != 0) {
+                               if (error == (int) SocketError.InvalidArgument)
+                                       throw new ArgumentException ();
                                throw new SocketException (error);
                        }
                }
index a53a5d5624ab010ece48667b8bfe72719d856110..14a4490fa4bd4b71116477d8b57676193c167576 100755 (executable)
@@ -3500,27 +3500,6 @@ namespace MonoTests.System.Net.Sockets
                        s.Close ();
                }
 
-#if MONOTOUCH
-               // when the linker is enabled then reflection won't work and would throw an NRE
-               // this is also always true for iOS - so we do not need to poke internals
-               static bool SupportsPortReuse ()
-               {
-                       return true;
-               }
-#else
-               static bool? supportsPortReuse;
-               static bool SupportsPortReuse ()
-               {
-                       if (supportsPortReuse.HasValue)
-                               return supportsPortReuse.Value;
-
-                       supportsPortReuse = (bool) typeof (Socket).GetMethod ("SupportsPortReuse",
-                                       BindingFlags.Static | BindingFlags.NonPublic)
-                                       .Invoke (null, new object [] {});
-                       return supportsPortReuse.Value;
-               }
-#endif
-
                // Test case for bug #31557
                [Test]
                public void TcpDoubleBind ()
@@ -3529,22 +3508,28 @@ namespace MonoTests.System.Net.Sockets
                                                SocketType.Stream, ProtocolType.Tcp))
                        using (Socket ss = new Socket (AddressFamily.InterNetwork,
                                                SocketType.Stream, ProtocolType.Tcp)) {
-                               s.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+                               var supportsReuseAddress = true;
+                               try {
+                                       s.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+                               } catch (SocketException e) {
+                                       // Exception is thrown when ReuseAddress is not supported
+                                       supportsReuseAddress = false;
+                               }
+
                                var ep = new IPEndPoint (IPAddress.Any, NetworkHelpers.FindFreePort ());
                                s.Bind (ep);
                                s.Listen(1);
 
-                               ss.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+                               if (supportsReuseAddress)
+                                       ss.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
 
-                               Exception ex = null;
                                try {
                                        ss.Bind (new IPEndPoint (IPAddress.Any, ep.Port));
                                        ss.Listen(1);
+                                       if (!supportsReuseAddress)
+                                               Assert.Fail ("Reusing address is not supported, exception was expected on second bind.");
                                } catch (SocketException e) {
-                                       ex = e;
                                }
-
-                               Assert.AreEqual (SupportsPortReuse (), ex == null);
                        }
                }