@@ -22,7 +22,7 @@ defmodule Bypass.Instance do
2222
2323 def init ( [ opts ] ) do
2424 # Get a free port from the OS
25- case :ranch_tcp . listen ( ip: listen_ip ( ) , port: Keyword . get ( opts , :port , 0 ) ) do
25+ case :ranch_tcp . listen ( so_reuseport ( ) ++ [ ip: listen_ip ( ) , port: Keyword . get ( opts , :port , 0 ) ] ) do
2626 { :ok , socket } ->
2727 { :ok , port } = :inet . port ( socket )
2828 :erlang . port_close ( socket )
@@ -305,7 +305,7 @@ defmodule Bypass.Instance do
305305
306306 defp do_up ( port , ref ) do
307307 plug_opts = [ self ( ) ]
308- { :ok , socket } = :ranch_tcp . listen ( ip: listen_ip ( ) , port: port )
308+ { :ok , socket } = :ranch_tcp . listen ( so_reuseport ( ) ++ [ ip: listen_ip ( ) , port: port ] )
309309 cowboy_opts = cowboy_opts ( port , ref , socket )
310310 { :ok , _pid } = Plug.Cowboy . http ( Bypass.Plug , plug_opts , cowboy_opts )
311311 socket
@@ -376,6 +376,37 @@ defmodule Bypass.Instance do
376376 end
377377 end
378378
379+ # Use raw socket options to set SO_REUSEPORT so we fix {:error, :eaddrinuse} - where the OS errors
380+ # when we attempt to listen on the same port as before, since it's still considered in use.
381+ #
382+ # See https://lwn.net/Articles/542629/ for details on SO_REUSEPORT.
383+ #
384+ # See https://github.com/aetrion/erl-dns/blob/0c8d768/src/erldns_server_sup.erl#L81 for an
385+ # Erlang library using this approach.
386+ #
387+ # We want to do this:
388+ #
389+ # int optval = 1;
390+ # setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
391+ #
392+ # Use the following C program to find the values on each OS:
393+ #
394+ # #include <stdio.h>
395+ # #include <sys/socket.h>
396+ #
397+ # int main() {
398+ # printf("SOL_SOCKET: %d\n", SOL_SOCKET);
399+ # printf("SO_REUSEPORT: %d\n", SO_REUSEPORT);
400+ # return 0;
401+ # }
402+ defp so_reuseport ( ) do
403+ case :os . type ( ) do
404+ { :unix , :linux } -> [ { :raw , 1 , 15 , << 1 :: 32 - native >> } ]
405+ { :unix , :darwin } -> [ { :raw , 65535 , 512 , << 1 :: 32 - native >> } ]
406+ _ -> [ ]
407+ end
408+ end
409+
379410 # This is used to override the default behaviour of ranch_tcp
380411 # and limit the range of interfaces it will listen on to just
381412 # the configured interface. Loopback is a default interface.
0 commit comments