Plug Cowboy - Search dynamically for a free port to run the server

I am building a simple http server using Plug.Cowboy (no Phoenix pls!) to serve a json API for a customer. A microservice.
The problem is the customer has a complex environment with multiple services of many sorts, containers, microservices etc. that are serving on various ports in his server. Therefore we were requested to develop the service to look dynamically and launch itself on a free/unused port.
When we build the Elixir app it is requested to declare the port at Plug initialization or it defaults to 4000/4040:

 def start(_type, _args) do
    children = [
      {Plug.Cowboy, scheme: :http, plug: MyApp, port: 1234}
    ]
 ...

How can I re-factory this to search the first available unused port in a certain range ?

Thanks in advance,
Q.T.

If you put port: 0 it seems to look for an available port and use it. I’m not sure if this is safe to be counted on though. I tried it out because in erlang gen_tcp.listen finds an available port when you set port to 0.

4 Likes

It should be safe at least on Linux, see the source: linux/inet_connection_sock.c at 38f80f42147ff658aff218edb0a88c37e58bf44f · torvalds/linux · GitHub

This seems to be a Unix convention of getting a random available port. If you then need to know what port you got, possibly you can dig at the Cowboy socket to gain that information.

2 Likes

You cannot limit range, but as other have said, using port 0 will give you random port from ephemeral range (configured by the OS). The problem though is how to later extract that port from within your application (it is not done automatically for you).

However using ephemeral ports for listening with your services is a little bit weird, what exactly you are trying to accomplish?

2 Likes

Indeed I need the port number to be registered and reported by the app. Otherwise it is totally useless.
Moreover I don’t need a random port, the requirement is to find a port in a given range.

I need something like this in Java:

public int nextFreePort(int from, int to) {
    int port = randPort(from, to);
    while (true) {
        if (isLocalPortFree(port)) {
            return port;
        } else {
            port = ThreadLocalRandom.current().nextInt(from, to);
        }
    }
}

private boolean isLocalPortFree(int port) {
    try {
        new ServerSocket(port).close();
        return true;
    } catch (IOException e) {
        return false;
    }
}

You could probably use Erlang -- gen_tcp (not tested):

def start(_type, _args) do
  children = [
    {Plug.Cowboy, scheme: :http, plug: MyApp, port: get_free_port(4000)}
  ]
  ...
end

defp get_free_port(start) do
  case :gen_tcp.listen(start, [:binary]) do
    {:ok, socket} ->
      :ok = :gen_tcp.close(socket)
      start

    {:error, :eaddrinuse} ->
      get_free_port(start + 1)
  end
end
4 Likes

Just beware that this solution is susceptible to TOCTOU.

4 Likes

Could be, but seems that there is no other solution.

You could just try listing on a port with the actual server, if it doesn’t fail your there. No separation between checking and using.

2 Likes

What if it fails ?
Crashes the server: error → reason: eaddrinuse
I need a loop for the range of ports.

1 Like

Instead of using Plug.Cowboy you could also drop down to using cowboy directly. Put something like this in a loop over your port range

iex(11)> :cowboy.start_clear(:port_test, [port: 55555], %{})

08:59:52.401 [error] Failed to start Ranch listener :port_test in :ranch_tcp:listen([cacerts: :..., key: :..., cert: :..., port: 55555]) for reason :eaddrinuse (address already in use)

{:error, :eaddrinuse}
iex(12)> :cowboy.start_clear(:port_test, [port: 55556], %{})
{:ok, #PID<0.474.0>}
1 Like

Another thing you could do is create your own module that initializes the server using the command below and then supervise that instead. Your start_link function would call this command and loop over the port until you get one that works for you.

Plug.Cowboy.http MyPlug, [], port: 80

One thing I thought about your method though, if your app crashes or you do a new deploy or your cowboy process crashes then you can get a new port every time. It seems like your client should be reserving a port for your app on their machines.

1 Like

Question is - why you need it at all? Because maybe there is better solution for that.

1 Like

I use this to start multiple http servers for testing my ex_ari library. I’m not sure it’s useful in production, but could give you some ideas.

1 Like

@hauleth, please read my original post to understand why. This is the customer’s request. It is not my job to question it.
You’re right, it might be there a better solution. This is the reason I opened this discussion.

1 Like

I think I’d be tempted to solve this using a launch script that uses netstat or similar to find a suitable available port, then setting an ENV variable before launching the server. It might be possible to do something similar directly in Elixir, before launching the Phoenix process, but I suspect the launch script approach would be quicker & easier to implement.

Setting the env before running the server would increase the window between time-of-check and time-of-use,.

Yes, with a couple of milliseconds. And the point is ?

This should have ended with a question mark. I’m trying to learn from this thread and from your tone it seems you have taken my post as an attack. So good luck :+1:

Yes, there may in fact be several seconds’ delay, as the application needs to boot before the socket is opened. I think most solutions will suffer from this to some extent. Hopefully the deployment environment will have a service manager (e.g. systemd, k8s, …) that is capable of restarting services that exit unexpectedly, so this condition should not be too difficult to handle.