Reputation: 97
A little summary of what I'm trying to do. I have a Phoenix app running on an AWS Elastic Beanstalk instance and I'm sending REST API requests containing PDFs for manipulation (splitting, merging etc). Each Request is saved in the database. This is what my requests
schema looks like:
schema "requests" do
field :body, :string
field :endpoint, :string
field :method, :string
field :request_host, :string
field :response_body, :string
field :response_code, :integer
field :work_group_id, :integer
field :identifier, :string
field :responded_at, :utc_datetime
timestamps()
end
Through Graphql, I'm making a request from a separate RAILS server and displaying the hostname, inserted_at
field, and response_code
field of all the requests.
I'm experiencing problems trying to resolve the client's host name. This is the Erlang method I'm using where the method argument remote_ip is relatively conn.remote_ip
:
{:ok, {:hostent, request_host, _, _, _, _}} = :inet.gethostbyaddr(remote_ip)
This method returns my Phoenix app's request host and not the clients'.
What am I doing wrong here?
Thanks in advance :)
Upvotes: 1
Views: 1102
Reputation: 1490
This library is available to simplify handling of IP addresses behind proxies.
https://github.com/ajvondrak/remote_ip
The author has a nice writeup of applicable use-cases and why the library is probably a better choice than rolling your own: here
Upvotes: 2
Reputation: 11743
As described in the docs:
[The
remote_ip
] field is meant to be overwritten by plugs that understand e.g. theX-Forwarded-For
header or HAProxy's PROXY protocol. It defaults to peer's IP.
This is elaborated upon here:
When your app is running behind a proxy like Nginx, then the request will look like it's coming from Nginx, i.e. the IP will be 127.0.0.1. Similarly, If Nginx is behind a CDN, then all the requests will come from the IP of the CDN.
So you can write a plug to overwrite the remote_ip
field of the Plug.Conn
. Following is an example of such a plug. This example is copied from this blog post.
defmodule MyApp.Plug.PublicIp do
@moduledoc "Get public IP address of request from x-forwarded-for header"
@behaviour Plug
@app :my_app
def init(opts), do: opts
def call(%{assigns: %{ip: _}} = conn, _opts), do: conn
def call(conn, _opts) do
process(conn, Plug.Conn.get_req_header(conn, "x-forwarded-for"))
end
def process(conn, []) do
Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(get_peer_ip(conn))))
end
def process(conn, vals) do
if Application.get_env(@app, :trust_x_forwarded_for, false) do ip_address = get_ip_address(conn, vals) # Rewrite standard remote_ip field with value from header
# See https://hexdocs.pm/plug/Plug.Conn.html
conn = %{conn | remote_ip: ip_address}
Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(ip_address)))
else
Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(get_peer_ip(conn))))
end
end
defp get_ip_address(conn, vals)
defp get_ip_address(conn, []), do: get_peer_ip(conn)
defp get_ip_address(conn, [val | _]) do
# Split into multiple values
comps = val
|> String.split(~r{\s*,\s*}, trim: true)
|> Enum.filter(&(&1 != "unknown")) # Get rid of "unknown" values
|> Enum.map(&(hd(String.split(&1, ":")))) # Split IP from port, if any
|> Enum.filter(&(&1 != "")) # Filter out blanks
|> Enum.map(&(parse_address(&1))) # Parse address into :inet.ip_address tuple
|> Enum.filter(&(is_public_ip(&1))) # Elminate internal IP addreses, e.g. 192.168.1.1
case comps do
[] -> get_peer_ip(conn)
[comp | _] -> comp
end
end
@spec get_peer_ip(Plug.Conn.t) :: :inet.ip_address
defp get_peer_ip(conn) do
{ip, _port} = conn.peer
ip
end
@spec parse_address(String.t) :: :inet.ip_address
defp parse_address(ip) do
case :inet.parse_ipv4strict_address(to_charlist(ip)) do
{:ok, ip_address} -> ip_address
{:error, :einval} -> :einval
end
end
# Whether the input is a valid, public IP address
# http://en.wikipedia.org/wiki/Private_network
@spec is_public_ip(:inet.ip_address | atom) :: boolean
defp is_public_ip(ip_address) do
case ip_address do
{10, _, _, _} -> false
{192, 168, _, _} -> false
{172, second, _, _} when second >= 16 and second <= 31 -> false
{127, 0, 0, _} -> false
{_, _, _, _} -> true
:einval -> false
end
end
end
Upvotes: 1