# `PhoenixKit.Utils.IpAddress`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.22.16/lib/phoenix_kit/utils/ip_address.ex#L1)

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}})
    "8193:3512:1:0:0:0:0:1"

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

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

# `client_address`

```elixir
@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`

```elixir
@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`

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`

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`

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 as "a:b:c:d:e:f:g:h" string
- "unknown" for invalid or missing data

---

*Consult [api-reference.md](api-reference.md) for complete listing*
