Support tickets arrive saying the app shows one address while the server logged another, then asking which one is lying. Neither is; they are two observations of two different things. An address bound to an interface, an address bound to a socket, and a source address seen by a remote service answer three separate questions. A VPN adds a network context to the phone and does not collapse those three into one device-wide number. Read the field names in the ticket before you read the addresses.

What the ticket usually describes

Picture a phone on Wi-Fi with a tunnel up. The Wi-Fi interface holds a LAN address, the tunnel interface holds an assigned inner address, and a request that leaves through the tunnel arrives at a public service from whatever source survives any translation on the way. An app printing the inner address and a server logging the translated source are both reporting accurately, and the two values will never match.

The three observations differ in what they can prove, not only in what they describe.

ObservationWhat it describesWhat it cannot prove
An address on a local interfaceOne configured attachment: Wi-Fi, cellular or a tunnelThe source a remote site receives
A socket's local endpointThe binding used by that one connectionThe address after translation further along
The peer address at a serviceWhat arrived at that observation pointAny other interface on the phone

Keep that table next to the code that fills these values into a ticket, because most of the confusion here is a column mix-up rather than a networking fault.

What a socket call can and cannot answer

At the API level, getsockname reports the local endpoint currently bound to a specific socket. For a connected socket that is the source the kernel chose for that connection, which is already more specific than any interface listing. It is not a public address lookup, and calling it on an unbound socket tells you almost nothing. Whatever it returns can still be rewritten later in the path, so only the service's own observation is authoritative about what the service received.

On Apple platforms the interface side has a separate surface: tunnel settings list the addresses assigned to the tunnel interface. Reading those tells you how the tunnel was configured, and it does not tell you which path a given request took, because routing and per-app rules decide that per connection.

Split tunnelling makes the gap visible. One request may leave through the tunnel while the next, aimed at a different destination or address family, does not, so two results from the same app seconds apart can be honest and different.

Pick the observation that matches the question

Choose deliberately rather than instrumenting everything. For what the backend sees, log the source at the backend, and where a reverse proxy sits in front, read the client address from the forwarding header your deployment actually sets rather than from anything the client sent.

For whether the tunnel is configured, read the interface. For whether this app's traffic used it, send traffic from that app to a destination you control and compare the source at both ends.

Per-app rules deserve their own sentence. A browser result is evidence about that browser, and a managed per-app tunnel can put two apps on the same phone on different paths at the same moment. Verifying coverage means sending traffic from the app in question, in the address family in question.

Interface names are not a detector

Do not use an interface name as a VPN check either. The names are not a public contract, and a phone can hold several attachments while only one of them carries your request.

Name the field after the observation it holds. local_interface_address, socket_local_endpoint and service_observed_source are clumsy and unambiguous, and unambiguous is what you want when the value ends up in a support ticket a week later. A single column called device IP will collect all three and then be trusted as though it were one.

Keep identity out of the address column

A shared VPN exit is not a user identity, and a private address is not an access credential. Both describe a path rather than a person, and treating either as authentication turns a routing accident into an authorization decision.

When somebody asks which address the phone has, the useful reply is a question about which connection and which observer. The getsockname definition is worth quoting for the middle case, because it settles the socket question without pretending to settle the other two.

Then write down which of the three your product actually needs to display, and delete the other two from the interface.

פֿאַרבונדענע אַרטיקלען