Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F114013
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 6:21 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39513
Default Alt Text
(20 KB)
Attached To
Mode
R27 remote_ip
Attached
Detach File
Event Timeline
Log In to Comment