Passing client IP to socket

Hi, I am trying to pass remote client IP from conn to socket without success. Below is my code:

In router.ex:

pipeline :browser do
  ...
  plug :put_client_ip
end

where the plug is:

def put_client_ip(conn, _), do:
  conn |> Plug.Conn.assign(:remote_ip, conn.remote_ip)

In mount/3:

sock = sock 
  |> assign_new(:remote_ip, fn -> {127, 0, 0, 1} end)
Logger.info "IP: #{inspect(sock.assigns.remote_ip)}"

I get this result:

12:33:44.456 [info]  IP: {0, 0, 0, 0, 0, 65535, 32512, 1}

12:33:44.458 [info]  Sent 200 in 4ms

12:33:45.022 [info]  CONNECTED TO Phoenix.LiveView.Socket in 188µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "xxx", "vsn" => "2.0.0"}

12:33:45.091 [info]  IP: {127, 0, 0, 1}

So it looks like the IP is lost (correct is the IPv6 one) when the Websocket has been established! Anyone has an idea why?

Thanks a lot!

5 Likes

You only have access to the connection and its assigns on the initial mount.

As a workaround, you could add the :remote_ip to the session in your plug:

def put_client_ip(conn, _), do: Plug.Conn.put_session(conn, :remote_ip, conn.remote_ip)

then, in mount, pattern match on it:

def mount(_params, %{"remote_ip" => remote_ip}, socket) do
...
end
6 Likes

This is not specific to the IP. assign_new is an optimization to allow reusing of values in conn.assigns when mounting the liveview for the initial static render of the http request. For any subsequent mounts conn.assigns is no longer available, which means you need to be able to compute the same value using the function you have as second parameter of assign_new.

hi @sfusato,

that was exactly my solution, but I was hoping not to use session as the medium.

Thanks for answering!

I was looking for a solution for the same thing but I would like to have the IPv4 address, how can I achieve this?

@andreyuhai the conn.remote_ip value is just a tuple. So you can pattern match on it to filter out non-IPv4 address (if that is what you want), i.e., {_, _, _, _} is qualifed as IPv4.

Generally, also watch out that you’re getting the client IP and not the reverse proxy IP if you’re deploying behind one. I think this remote_ip | Hex is all you need, at least you did a few phx || plug versions back.

IPv4 can also embedded in IPv6. I wrote a small function to extract one out of the IPv6 if it is embedded:

  def unembed({_,_,_,_} = ip4),         do: {:ok, ip4}
  def unembed({0,0,0,0,0,65535,ab,cd})  do 
    a = ab >>> 8
    b = ab &&& 255
    c = cd >>> 8
    d = cd &&& 255
    {:ok, {a, b, c, d}}
  end
  def unembed({_,_,_,_,_,_,_,_} = ip6), do: {:ok, ip6}
  def unembed(_),                       do: {:error, :bad_ip}

The proper way to do this today is to add the :peer_data option to your socket’s connect_info:
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#get_connect_info/1-examples

9 Likes

A post was split to a new topic: Websocket Client Issue