Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/remote_ip.ex b/lib/remote_ip.ex
index b11a5d7..57369e6 100644
--- a/lib/remote_ip.ex
+++ b/lib/remote_ip.ex
@@ -1,106 +1,111 @@
defmodule RemoteIp do
@moduledoc """
A plug to overwrite the `Plug.Conn`'s `remote_ip` based on headers such as
`X-Forwarded-For`.
To use, add the `RemoteIp` plug to your app's plug pipeline:
```elixir
defmodule MyApp do
use Plug.Builder
plug RemoteIp
end
```
There are 2 options that can be passed in:
* `:headers` - A list of strings naming the `req_headers` to use when
deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded
x-forwarded-for x-client-ip x-real-ip]`.
* `:proxies` - A list of strings in
[CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of
known proxies. Defaults to `[]`.
For example, if you know you are behind proxies in the IP block 1.2.x.x that
use the `X-Foo`, `X-Bar`, and `X-Baz` headers, you could say
```elixir
defmodule MyApp do
use Plug.Builder
plug RemoteIp, headers: ~w[x-foo x-bar x-baz], proxies: ~w[1.2.0.0/16]
end
```
Note that, due to limitations in the
[inet_cidr](https://github.com/Cobenian/inet_cidr) library used to parse
them, `:proxies` **must** be written in full CIDR notation, even if
specifying just a single IP. So instead of `"127.0.0.1"` and `"a:b::c:d"`,
you would use `"127.0.0.1/32"` and `"a:b::c:d/128"`.
"""
@behaviour Plug
@headers ~w[
forwarded
x-forwarded-for
x-client-ip
x-real-ip
]
@proxies []
# https://en.wikipedia.org/wiki/Loopback
# https://en.wikipedia.org/wiki/Private_network
@reserved ~w[
127.0.0.0/8
::1/128
fc00::/7
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
]
def init(opts \\ []) do
headers = Keyword.get(opts, :headers, @headers)
headers = MapSet.new(headers)
reserved = Keyword.get(opts, :reserved, @reserved)
proxies = Keyword.get(opts, :proxies, @proxies) ++ reserved
proxies = proxies |> Enum.map(&InetCidr.parse/1)
{headers, proxies}
end
def call(conn, {headers, proxies}) do
case last_forwarded_ip(conn, headers, proxies) do
- nil -> conn
- ip -> %{conn | remote_ip: ip}
+ nil ->
+ Plug.Conn.assign(conn, :remote_ip_found, false)
+
+ ip ->
+ conn
+ |> Map.put(:remote_ip, ip)
+ |> Plug.Conn.assign(:remote_ip_found, true)
end
end
defp last_forwarded_ip(conn, headers, proxies) do
conn
|> ips_from(headers)
|> last_ip_forwarded_through(proxies)
end
defp ips_from(%Plug.Conn{req_headers: headers}, allowed) do
RemoteIp.Headers.parse(headers, allowed)
end
defp last_ip_forwarded_through(ips, proxies) do
ips
|> Enum.reverse()
|> Enum.find(&forwarded?(&1, proxies))
end
defp forwarded?(ip, proxies) do
!proxy?(ip, proxies)
end
defp proxy?(ip, proxies) do
Enum.any?(proxies, fn proxy -> InetCidr.contains?(proxy, ip) end)
end
end
diff --git a/test/remote_ip_test.exs b/test/remote_ip_test.exs
index 961dd15..f5a5e91 100644
--- a/test/remote_ip_test.exs
+++ b/test/remote_ip_test.exs
@@ -1,303 +1,316 @@
defmodule RemoteIpTest do
use ExUnit.Case, async: true
use Plug.Test
# put_req_header/3 will obliterate existing values, whereas we want to
# append multiple values for the same header.
#
def add_req_header(%Plug.Conn{req_headers: headers} = conn, header, value) do
%{conn | req_headers: headers ++ [{header, value}]}
end
def forwarded(conn, value), do: add_req_header(conn, "forwarded", value)
def x_forwarded_for(conn, ip), do: add_req_header(conn, "x-forwarded-for", ip)
def x_client_ip(conn, ip), do: add_req_header(conn, "x-client-ip", ip)
def x_real_ip(conn, ip), do: add_req_header(conn, "x-real-ip", ip)
def custom(conn, ip), do: add_req_header(conn, "custom", ip)
def remote_ip(conn, opts \\ []) do
RemoteIp.call(conn, RemoteIp.init(opts)).remote_ip
end
# Not a real IP address, but RemoteIp shouldn't ever be actually manipulating
# this value. So, in this Conn, we use :peer as a canary in the coalmine.
#
@conn %Plug.Conn{remote_ip: :peer}
test "zero hops (i.e., no forwarding headers)" do
assert :peer == @conn |> remote_ip
assert :peer == @conn |> remote_ip(headers: ~w[])
assert :peer == @conn |> remote_ip(headers: ~w[custom])
assert :peer == @conn |> remote_ip(proxies: ~w[])
assert :peer == @conn |> remote_ip(proxies: ~w[0.0.0.0/0 ::/0])
assert :peer == @conn |> remote_ip(headers: ~w[], proxies: ~w[])
assert :peer == @conn |> remote_ip(headers: ~w[], proxies: ~w[0.0.0.0/0 ::/0])
assert :peer == @conn |> remote_ip(headers: ~w[custom], proxies: ~w[])
assert :peer == @conn |> remote_ip(headers: ~w[custom], proxies: ~w[0.0.0.0/0 ::/0])
end
+ describe "remote_ip_found flag" do
+ test "found" do
+ conn = @conn |> x_forwarded_for("1.2.3.4") |> RemoteIp.call(RemoteIp.init([]))
+ assert conn.remote_ip == {1, 2, 3, 4}
+ assert conn.assigns[:remote_ip_found] == true
+ end
+
+ test "not found" do
+ conn = @conn |> RemoteIp.call(RemoteIp.init([]))
+ assert conn.assigns[:remote_ip_found] == false
+ end
+ end
+
describe "one hop" do
test "from an unknown IP" do
assert :peer == @conn |> forwarded("for=unknown") |> remote_ip
assert :peer == @conn |> x_forwarded_for("not_an_ip") |> remote_ip
assert :peer == @conn |> custom("_obf") |> remote_ip(headers: ~w[custom])
end
test "from a loopback IP" do
assert :peer == @conn |> forwarded("for=127.0.0.1") |> remote_ip
assert :peer == @conn |> x_client_ip("::1") |> remote_ip
assert :peer == @conn |> custom("127.0.0.2") |> remote_ip(headers: ~w[custom])
end
test "from a private IP" do
assert :peer == @conn |> forwarded("for=10.0.0.1") |> remote_ip
assert :peer == @conn |> x_real_ip("172.16.0.1") |> remote_ip
assert :peer == @conn |> x_forwarded_for("fd00::") |> remote_ip
assert :peer == @conn |> custom("192.168.0.1") |> remote_ip(headers: ~w[custom])
end
test "from a public IP configured as a known proxy" do
assert :peer == @conn |> forwarded("for=1.2.3.4") |> remote_ip(proxies: ~w[1.2.3.4/32])
assert :peer == @conn |> x_client_ip("::a") |> remote_ip(proxies: ~w[::a/128])
assert :peer == @conn |> custom("1.2.3.4") |> remote_ip(headers: ~w[custom], proxies: ~w[1.2.0.0/16])
end
test "from a public IP not configured as a known proxy" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=1.2.3.4") |> remote_ip(proxies: ~w[::/0])
assert {1, 2, 3, 4, 5, 6, 7, 8} == @conn |> x_real_ip("1:2:3:4:5:6:7:8") |> remote_ip(proxies: ~w[1:1::/64])
assert {1, 2, 3, 4} == @conn |> custom("1.2.3.4") |> remote_ip(headers: ~w[custom])
end
end
describe "two hops" do
test "from unknown to unknown" do
assert :peer == @conn |> forwarded("for=unknown,for=_obf") |> remote_ip
assert :peer == @conn |> x_forwarded_for("_obf,not_an_ip") |> remote_ip
assert :peer == @conn |> custom("unknown,unknown") |> remote_ip(headers: ~w[custom])
end
test "from unknown to loopback" do
assert :peer == @conn |> forwarded("for=_obf,for=127.0.0.1") |> remote_ip
assert :peer == @conn |> x_client_ip("unknown,::1") |> remote_ip
assert :peer == @conn |> custom("not_an_ip, 127.0.0.2") |> remote_ip(headers: ~w[custom])
end
test "from unknown to private" do
assert :peer == @conn |> forwarded("for=unknown,for=10.10.10.10") |> remote_ip
assert :peer == @conn |> x_real_ip("_obf, fc00::ABCD") |> remote_ip
assert :peer == @conn |> x_forwarded_for("not_an_ip,192.168.0.4") |> remote_ip
assert :peer == @conn |> custom("unknown,172.16.72.1") |> remote_ip(headers: ~w[custom])
end
test "from unknown to proxy" do
assert :peer == @conn |> forwarded("for=_obf,for=1.2.3.4") |> remote_ip(proxies: ~w[1.2.3.4/32])
assert :peer == @conn |> x_client_ip("unknown,a:b:c:d:e:f::") |> remote_ip(proxies: ~w[::/0])
assert :peer == @conn |> custom("not_an_ip,1.2.3.4") |> remote_ip(headers: ~w[custom], proxies: ~w[1.0.0.0/8])
end
test "from unknown to non-proxy" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=unknown,for=1.2.3.4") |> remote_ip(proxies: ~w[1.2.3.5/32])
assert {0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x0} == @conn |> x_real_ip("_obf,a:b:c:d:e:f::") |> remote_ip
assert {1, 2, 3, 4} == @conn |> custom("not_an_ip,1.2.3.4") |> remote_ip(headers: ~w[custom], proxies: ~w[8.6.7.5/32 3:0:9::/64])
end
test "from loopback to unknown" do
assert :peer == @conn |> forwarded("for=\"[::1]\",for=unknown") |> remote_ip
assert :peer == @conn |> x_forwarded_for("127.0.0.1,not_an_ip") |> remote_ip
assert :peer == @conn |> custom("127.0.0.2,_obfuscated_ipaddr") |> remote_ip(headers: ~w[custom])
end
test "from loopback to loopback" do
assert :peer == @conn |> forwarded("for=127.0.0.1, for=127.0.0.1") |> remote_ip
assert :peer == @conn |> x_client_ip("::1, ::1") |> remote_ip
assert :peer == @conn |> custom("::1, 127.0.0.1") |> remote_ip(headers: ~w[custom])
end
test "from loopback to private" do
assert :peer == @conn |> forwarded("for=127.0.0.10, for=\"[fc00::1]\"") |> remote_ip
assert :peer == @conn |> x_real_ip("::1, 192.168.1.2") |> remote_ip
assert :peer == @conn |> custom("127.0.0.1, 172.16.0.1") |> remote_ip(headers: ~w[custom])
assert :peer == @conn |> custom("127.1.2.3, 10.10.10.1") |> remote_ip(headers: ~w[custom])
end
test "from loopback to proxy" do
assert :peer == @conn |> forwarded("for=127.0.0.1 , for=1.2.3.4") |> remote_ip(proxies: ~w[1.2.3.4/32])
assert :peer == @conn |> x_forwarded_for("::1, 1.2.3.4") |> remote_ip(proxies: ~w[1.2.3.0/24])
assert :peer == @conn |> custom("127.0.0.2, 2001:0db8:85a3:0000:0000:8A2E:0370:7334") |> remote_ip(headers: ~w[custom], proxies: ~w[2001:0db8:85a3::8A2E:0370:7334/128])
end
test "from loopback to non-proxy" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=127.0.0.1, for=1.2.3.4") |> remote_ip
assert {1, 2, 3, 4} == @conn |> x_client_ip("::1, 1.2.3.4") |> remote_ip(proxies: ~w[2.0.0.0/8])
assert {0x2001, 0x0db8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7334} == @conn |> custom("::1, 2001:0db8:85a3:0000:0000:8A2E:0370:7334") |> remote_ip(headers: ~w[custom], proxies: ~w[fe80:0000:0000:0000:0202:b3ff:fe1e:8329/128])
end
test "from private to unknown" do
assert :peer == @conn |> forwarded("for=10.10.10.10,for=unknown") |> remote_ip
assert :peer == @conn |> x_forwarded_for("fc00::ABCD, _obf") |> remote_ip
assert :peer == @conn |> x_real_ip("192.168.0.4, not_an_ip") |> remote_ip
assert :peer == @conn |> custom("172.16.72.1, unknown") |> remote_ip(headers: ~w[custom])
end
test "from private to loopback" do
assert :peer == @conn |> forwarded("for=\"[fc00::1]\", for=127.0.0.10") |> remote_ip
assert :peer == @conn |> forwarded("for=10.10.10.1, for=127.1.2.3") |> remote_ip
assert :peer == @conn |> x_client_ip("192.168.1.2, ::1") |> remote_ip
assert :peer == @conn |> custom("172.16.0.1, 127.0.0.1") |> remote_ip(headers: ~w[custom])
end
test "from private to private" do
assert :peer == @conn |> forwarded("for=172.16.0.1, for=\"[fc00::1]\"") |> remote_ip
assert :peer == @conn |> x_real_ip("192.168.0.1, 192.168.0.2") |> remote_ip
assert :peer == @conn |> custom("10.0.0.1, 10.0.0.2") |> remote_ip(headers: ~w[custom])
end
test "from private to proxy" do
assert :peer == @conn |> forwarded("for=\"[fc00::1:2:3]\", for=1.2.3.4") |> remote_ip(proxies: ~w[0.0.0.0/0])
assert :peer == @conn |> forwarded("for=10.0.10.0, for=\"[::1.2.3.4]\"") |> remote_ip(proxies: ~w[::/64])
assert :peer == @conn |> x_forwarded_for("192.168.0.1,1.2.3.4") |> remote_ip(proxies: ~w[1.2.0.0/16])
assert :peer == @conn |> custom("172.16.1.2, 3.4.5.6") |> remote_ip(headers: ~w[custom], proxies: ~w[3.0.0.0/8])
end
test "from private to non-proxy" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=\"[fc00::1:2:3]\", for=1.2.3.4") |> remote_ip
assert {0, 0, 0, 0, 0, 0, 258, 772} == @conn |> forwarded("for=10.0.10.0, for=\"[::1.2.3.4]\"") |> remote_ip(proxies: ~w[255.0.0.0/8])
assert {1, 2, 3, 4} == @conn |> x_client_ip("192.168.0.1,1.2.3.4") |> remote_ip
assert {3, 4, 5, 6} == @conn |> custom("172.16.1.2 , 3.4.5.6") |> remote_ip(headers: ~w[custom], proxies: ~w[1.2.3.4/32])
end
test "from proxy to unknown" do
assert :peer == @conn |> forwarded("for=1.2.3.4,for=_obf") |> remote_ip(proxies: ~w[1.2.3.4/32])
assert :peer == @conn |> x_real_ip("a:b:c:d:e:f::,unknown") |> remote_ip(proxies: ~w[::/0])
assert :peer == @conn |> custom("1.2.3.4,not_an_ip") |> remote_ip(headers: ~w[custom], proxies: ~w[1.0.0.0/8])
end
test "from proxy to loopback" do
assert :peer == @conn |> forwarded("for=1.2.3.4, for=127.0.0.1") |> remote_ip(proxies: ~w[1.2.3.4/32])
assert :peer == @conn |> x_forwarded_for("1.2.3.4, ::1") |> remote_ip(proxies: ~w[1.2.3.0/24])
assert :peer == @conn |> custom("2001:0db8:85a3:0000:0000:8A2E:0370:7334, 127.0.0.2") |> remote_ip(headers: ~w[custom], proxies: ~w[2001:0db8:85a3::8A2E:0370:7334/128])
end
test "from proxy to private" do
assert :peer == @conn |> forwarded("for=1.2.3.4, for=\"[fc00::1:2:3]\"") |> remote_ip(proxies: ~w[0.0.0.0/0])
assert :peer == @conn |> forwarded("for=\"[::1.2.3.4]\", for=10.0.10.0") |> remote_ip(proxies: ~w[::/64])
assert :peer == @conn |> x_client_ip("1.2.3.4,192.168.0.1") |> remote_ip(proxies: ~w[1.2.0.0/16])
assert :peer == @conn |> custom("3.4.5.6 , 172.16.1.2") |> remote_ip(headers: ~w[custom], proxies: ~w[3.0.0.0/8])
end
test "from proxy to proxy" do
assert :peer == @conn |> forwarded("for=1.2.3.4, for=1.2.3.5") |> remote_ip(proxies: ~w[1.2.3.0/24])
assert :peer == @conn |> x_real_ip("a:b:c:d::,1:2:3:4::") |> remote_ip(proxies: ~w[a:b:c:d::/128 1:2:3:4::/64])
assert :peer == @conn |> custom("1.2.3.4, 3.4.5.6") |> remote_ip(headers: ~w[custom], proxies: ~w[1.2.3.4/32 3.4.5.6/32])
end
test "from proxy to non-proxy" do
assert {3, 4, 5, 6} == @conn |> forwarded("for=1.2.3.4,for=3.4.5.6") |> remote_ip(proxies: ~w[1.2.3.4/32])
assert {0, 0, 0, 0, 3, 4, 5, 6} == @conn |> x_forwarded_for("::1:2:3:4, ::3:4:5:6") |> remote_ip(proxies: ~w[::1:2:3:4/128])
assert {3, 4, 5, 6} == @conn |> custom("1.2.3.4, 3.4.5.6") |> remote_ip(headers: ~w[custom], proxies: ~w[1.2.3.4/32])
end
test "from non-proxy to unknown" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=1.2.3.4,for=not_an_ip") |> remote_ip
assert {0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x0} == @conn |> x_client_ip("a:b:c:d:e:f::,unknown") |> remote_ip(proxies: ~w[b::/64])
assert {1, 2, 3, 4} == @conn |> custom("1.2.3.4,_obf") |> remote_ip(headers: ~w[custom])
end
test "from non-proxy to loopback" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=1.2.3.4, for=127.0.0.1") |> remote_ip(proxies: ~w[abcd::/32])
assert {1, 2, 3, 4} == @conn |> x_real_ip("1.2.3.4, ::1") |> remote_ip(proxies: ~w[4.3.2.1/32])
assert {0x2001, 0x0db8, 0x85a3, 0x0000, 0x0000, 0x8a2e, 0x0370, 0x7334} == @conn |> custom("2001:0db8:85a3:0000:0000:8A2E:0370:7334, 127.0.0.2") |> remote_ip(headers: ~w[custom])
end
test "from non-proxy to private" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=1.2.3.4, for=\"[fc00::1:2:3]\"") |> remote_ip
assert {0, 0, 0, 0, 0, 0, 258, 772} == @conn |> forwarded("for=\"[::1.2.3.4]\", for=10.0.10.0") |> remote_ip(proxies: ~w[1:2:3:4::/64])
assert {1, 2, 3, 4} == @conn |> x_forwarded_for("1.2.3.4,192.168.0.1") |> remote_ip(proxies: ~w[1.2.3.5/32])
assert {3, 4, 5, 6} == @conn |> custom("3.4.5.6 , 172.16.1.2") |> remote_ip(headers: ~w[custom])
end
test "from non-proxy to proxy" do
assert {1, 2, 3, 4} == @conn |> forwarded("for=1.2.3.4,for=3.4.5.6") |> remote_ip(proxies: ~w[3.4.5.6/32])
assert {0, 0, 0, 0, 1, 2, 3, 4} == @conn |> x_client_ip("::1:2:3:4, ::3:4:5:6") |> remote_ip(proxies: ~w[::3:4:5:6/128])
assert {1, 2, 3, 4} == @conn |> custom("1.2.3.4, 3.4.5.6") |> remote_ip(headers: ~w[custom], proxies: ~w[3.4.5.0/24])
end
test "from non-proxy to non-proxy" do
assert {3, 4, 5, 6} == @conn |> forwarded("for=1.2.3.4,for=3.4.5.6") |> remote_ip
assert {0, 0, 0, 0, 3, 4, 5, 6} == @conn |> x_real_ip("::1:2:3:4, ::3:4:5:6") |> remote_ip
assert {3, 4, 5, 6} == @conn |> custom("1.2.3.4, 3.4.5.6") |> remote_ip(headers: ~w[custom], proxies: ~w[5.6.7.8/32])
end
end
test "several hops" do
conn = @conn |> forwarded("for=3.4.5.6") |> forwarded("for=10.0.0.1") |> forwarded("for=192.168.0.1")
assert {3, 4, 5, 6} == conn |> remote_ip
conn = @conn |> x_real_ip("9.9.9.9, 172.31.4.4, 3.4.5.6, 10.0.0.1")
assert {3, 4, 5, 6} == conn |> remote_ip
conn = @conn |> custom("fe80::0202:b3ff:fe1e:8329") |> custom("::1") |> custom("::1")
assert {0xfe80, 0x0000, 0x0000, 0x0000, 0x0202, 0xb3ff, 0xfe1e, 0x8329} == conn |> remote_ip(headers: ~w[custom])
conn = @conn
|> x_forwarded_for("2001:0db8:85a3::8a2e:0370:7334")
|> x_forwarded_for("fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1")
|> x_forwarded_for("unknown, fc00::, fe00::, fdff::")
assert {0xfe80, 0x0000, 0x0000, 0x0000, 0x0202, 0xb3ff, 0xfe1e, 0x8329} == conn |> remote_ip(proxies: ~w[fe00::/128])
end
test "allowed headers" do
conn = @conn
|> put_req_header("a", "1.2.3.4")
|> put_req_header("b", "2.3.4.5")
|> put_req_header("c", "3.4.5.6")
assert :peer == conn |> remote_ip(headers: ~w[])
assert {1, 2, 3, 4} == conn |> remote_ip(headers: ~w[a])
assert {2, 3, 4, 5} == conn |> remote_ip(headers: ~w[a b])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[a c])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[a b c])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[a c b])
assert {2, 3, 4, 5} == conn |> remote_ip(headers: ~w[b])
assert {2, 3, 4, 5} == conn |> remote_ip(headers: ~w[b a])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[b c])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[b a c])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[b c a])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[c])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[c a])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[c b])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[c a b])
assert {3, 4, 5, 6} == conn |> remote_ip(headers: ~w[c b a])
end
test "allowed headers maintain relative ordering" do
headers = ~w[a b c]
a = fn conn -> put_req_header(conn, "a", "1.2.3.4") end
b = fn conn -> put_req_header(conn, "b", "2.3.4.5") end
c = fn conn -> put_req_header(conn, "c", "3.4.5.6") end
assert :peer == @conn |> remote_ip(headers: headers)
assert {1, 2, 3, 4} == @conn |> a.() |> remote_ip(headers: headers)
assert {2, 3, 4, 5} == @conn |> a.() |> b.() |> remote_ip(headers: headers)
assert {3, 4, 5, 6} == @conn |> a.() |> c.() |> remote_ip(headers: headers)
assert {3, 4, 5, 6} == @conn |> a.() |> b.() |> c.() |> remote_ip(headers: headers)
assert {2, 3, 4, 5} == @conn |> a.() |> c.() |> b.() |> remote_ip(headers: headers)
assert {2, 3, 4, 5} == @conn |> b.() |> remote_ip(headers: headers)
assert {1, 2, 3, 4} == @conn |> b.() |> a.() |> remote_ip(headers: headers)
assert {3, 4, 5, 6} == @conn |> b.() |> c.() |> remote_ip(headers: headers)
assert {3, 4, 5, 6} == @conn |> b.() |> a.() |> c.() |> remote_ip(headers: headers)
assert {1, 2, 3, 4} == @conn |> b.() |> c.() |> a.() |> remote_ip(headers: headers)
assert {3, 4, 5, 6} == @conn |> c.() |> remote_ip(headers: headers)
assert {1, 2, 3, 4} == @conn |> c.() |> a.() |> remote_ip(headers: headers)
assert {2, 3, 4, 5} == @conn |> c.() |> b.() |> remote_ip(headers: headers)
assert {2, 3, 4, 5} == @conn |> c.() |> a.() |> b.() |> remote_ip(headers: headers)
assert {1, 2, 3, 4} == @conn |> c.() |> b.() |> a.() |> remote_ip(headers: headers)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 6:21 PM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39513
Default Alt Text
(20 KB)

Event Timeline