PhoenixKit.Utils.IpAddress (phoenix_kit v2.28.1)

Copy Markdown View Source

Utilities for extracting and formatting IP addresses.

Supports both IPv4 and IPv6 addresses with proper error handling. Prevents Protocol.UndefinedError when working with IPv6 tuples.

Usage

Extract from LiveView socket:

ip = PhoenixKit.Utils.IpAddress.extract_from_socket(socket)

Extract from Plug.Conn:

ip = PhoenixKit.Utils.IpAddress.extract_from_conn(conn)

Extract from peer_data directly:

ip = PhoenixKit.Utils.IpAddress.extract_ip_address(%{address: {192, 168, 1, 1}})

Examples

iex> PhoenixKit.Utils.IpAddress.extract_ip_address(%{address: {192, 168, 1, 1}})
"192.168.1.1"

iex> PhoenixKit.Utils.IpAddress.extract_ip_address(%{address: {8193, 3512, 1, 0, 0, 0, 0, 1}})
"2001:db8:1::1"

iex> PhoenixKit.Utils.IpAddress.extract_ip_address(nil)
"unknown"

iex> PhoenixKit.Utils.IpAddress.extract_ip_address(%{})
"unknown"

Summary

Functions

The visitor's address as best a site behind a reverse proxy can tell.

The visitor's address as a LiveView socket knows it — or nil when the socket cannot tell.

Extracts the visitor's IP address from a Plug.Conn.

Extracts the visitor's IP address from a Phoenix.LiveView socket.

Extracts IP address from peer_data map.

The network an address belongs to — what to count or compare visitors by.

Functions

client_address(conn)

@spec client_address(Plug.Conn.t()) :: String.t() | nil

The visitor's address as best a site behind a reverse proxy can tell.

extract_from_conn/1 answers the TCP peer, which behind nginx (or in a container) is the proxy's own address for every visitor. This one takes conn.remote_ip (which a host's RemoteIp plug already rewrites) and, when that is a loopback or private address — a proxy on the same box or the same network — the LAST entry of x-forwarded-for, the one that proxy appended (a visitor can send their own header; they cannot control what nginx appends after it), then x-real-ip. A public remote_ip is trusted as is. Returns nil when nothing is known.

iex> conn = %Plug.Conn{remote_ip: {127, 0, 0, 1}, req_headers: [{"x-forwarded-for", "9.9.9.9, 203.0.113.7"}]}
iex> PhoenixKit.Utils.IpAddress.client_address(conn)
"203.0.113.7"

client_address_from_socket(socket)

@spec client_address_from_socket(Phoenix.LiveView.Socket.t()) :: String.t() | nil

The visitor's address as a LiveView socket knows it — or nil when the socket cannot tell.

From the connect info the endpoint passes: a public :peer_data address is the visitor; a loopback or private one is a proxy, and only the forwarded address in :x_headers (x-forwarded-for, x-real-ip) names the visitor then. An endpoint that passes no :x_headers — core's installed endpoint passes :peer_data only — leaves a proxied socket with no answer, and nil is that answer: the caller must not treat the proxy as the visitor.

extract_from_conn(conn)

Extracts the visitor's IP address from a Plug.Conn.

Proxy-aware: delegates to client_address/1, which reads x-forwarded-for / x-real-ip when conn.remote_ip is a loopback or private address (a reverse proxy on the same box or network), and trusts a public conn.remote_ip as-is.

Parameters

  • conn: Plug.Conn struct

Returns

  • IP address string or "unknown" (never nil — callers that need to tell "known" apart from "unknown" should call client_address/1 directly)

extract_from_socket(socket)

Extracts the visitor's IP address from a Phoenix.LiveView socket.

Proxy-aware: delegates to client_address_from_socket/1, which reads the connect info's :x_headers when the socket's :peer_data is a loopback or private address, and trusts a public :peer_data address as-is.

Parameters

  • socket: Phoenix.LiveView.Socket struct

Returns

  • IP address string or "unknown" (never nil — callers that need to tell "known" apart from "unknown" should call client_address_from_socket/1 directly). "unknown" also covers a host endpoint that never declared :peer_data in the socket's connect_info — see that function's docs.

extract_ip_address(arg1)

Extracts IP address from peer_data map.

Handles both IPv4 (4-tuple) and IPv6 (8-tuple) addresses. Returns "unknown" for nil, invalid, or missing data.

Parameters

  • peer_data: Map with :address key containing IP tuple, or nil

Returns

  • IPv4 as "a.b.c.d" string
  • IPv6 in its standard compressed hex form ("2001:db8::1")
  • "unknown" for invalid or missing data

network(address)

@spec network(String.t() | nil) :: String.t() | nil

The network an address belongs to — what to count or compare visitors by.

An IPv4 address is its own network. An IPv6 address is its /64: that is the block one household, phone or server is handed, and every address in it is theirs to use — operating systems rotate a temporary address inside it daily, and anyone can pick a fresh one per request. Keyed on the full address, a rate limit is one new address away from empty and a session "changes IP" every day.

Exceptions, where a /64 would put many hosts in one bucket:

  • IPv4-mapped (::ffff:a.b.c.d) and well-known NAT64 (64:ff9b::/96) unmap to the embedded IPv4, as does deprecated IPv4-compatible (::a.b.c.d)
  • loopback (::1), unspecified (::), and link-local (fe80::/10) are the address itself

Anything that does not parse (including "unknown") comes back unchanged.

iex> PhoenixKit.Utils.IpAddress.network("2a0d:3344:6a:c310:88f8:482c:e41a:9ef5")
"2a0d:3344:6a:c310::/64"

iex> PhoenixKit.Utils.IpAddress.network("203.0.113.7")
"203.0.113.7"

iex> PhoenixKit.Utils.IpAddress.network("::ffff:203.0.113.7")
"203.0.113.7"

iex> PhoenixKit.Utils.IpAddress.network("unknown")
"unknown"