11

How to get remote_ip from socket in phoenixframework? I can get it from conn in View, but not in Channel.

Many thanks for help!

3 Answers3

5

Copy of the answer provided here: https://elixirforum.com/t/phoenix-socket-channels-security-ip-identification/1463/3 (all the credit goes to https://elixirforum.com/u/arjan)

Phoenix 1.4 update:

Since Phoenix 1.4, you can get connection information from the underlying transport. What kind of information you get is transport dependent, but with the WebSocket transport it is possible to retrieve the peer info (ip address) and a list of x- headers (for x-forwarded-for resolving).

Configure your socket like this in your endpoint.ex:

socket("/socket", MyApp.Web.UserSocket,
  websocket: [connect_info: [:peer_data, :x_headers]],
  longpoll: [connect_info: [:peer_data, :x_headers]]
)

And then your UserSocket module must expose a connect/3 function like this:

def connect(_params, socket, connect_info) do
  {:ok, socket}
end

On connect, the connect_info parameter now contains info from the transport:

info: %{
  peer_data: %{address: {127, 0, 0, 1}, port: 52372, ssl_cert: nil},
  x_headers: []
}

UPDATE

If your Phoenix app is not handling traffic directly and receives it from reverse proxy like nginx then peer_data will have nginx IP address, not the client's. To fix this you can tell nginx (or whatever proxy you use) to pass original IP in the headers and then read it later.

So your phoenix location should look something like this:

location /phoenix/ {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://phoenix/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
}

and your socket code should have this:

defp get_ip_address(%{x_headers: headers_list}) do
  header = Enum.find(headers_list, fn {key, _val} -> key == "x-real-ip" end)

  case header do
    nil ->
      nil

    {_key, value} ->
      value

    _ ->
      nil
  end
end

defp get_ip_address(_) do
  nil
end

and change connect to something like this

def connect(params, socket, connect_info) do
  socket = assign(socket, :ip_address, get_ip_address(connect_info))
  {:ok, socket}      
end
Victor Ivanov
  • 130
  • 1
  • 5
  • I'm using this code, and it seems the the address in peer_data is not the public remote IP. I only get private ips (like 10.x.x.x). Is there a way to get the remote public IP? – freedrull Apr 02 '20 at 04:25
  • sure, you just have to tell your reverse proxy server to pass that information to Phoenix. See https://serverfault.com/questions/955533/nginx-as-reverse-proxy-doesnt-pass-client-host or just google it. UPDATE Looks like I can't add formatted code inside comments, so I will update the answer – Victor Ivanov Apr 03 '20 at 10:30
  • 1
    Thanks. I realized this was due to proxies on Heroku, so I am using x-forwarded-for from the x_headers instead as a solution. I was trying to use peer_data.address before. – freedrull Apr 03 '20 at 10:42
4

The answer right now is: you can't. You can't access the connection in channels because channels are transport agnostic. Open up an issue in Phoenix detailing your user case so the Phoenix team can act on it.

José Valim
  • 50,409
  • 12
  • 130
  • 115
1

Good news! as of LiveView 0.17.7 it's available out of the box:

see https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html

to summarize:

in endpoint.ex find the socket definition and add

socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [:peer_data, session: @session_options]]

in the socket mount() function

def mount(_params, _session, socket) do
    peer_data = get_connect_info(socket, :peer_data)
    {:ok, socket}
end

note: it's only available on mount() and terminate()

MoVod
  • 1,083
  • 10
  • 10