After a partner's traffic is published into a container, why does every request log the same internal source address?
answer
- translation rewrites both ends
- the reply must return through the host
- one source address for every caller
- controls keyed on address degrade silently
- only the hop that knew it can convey it
basics
~20 sThe publishing hop rewrites the destination so packets reach the container and, in most implementations, rewrites the source too so replies return through the host to be un-translated. The application therefore sees one internal address for every client.
solid answer
~50 sPublishing translates addresses on the way in. The destination must be rewritten, or the packet never reaches the container. The **source** is commonly rewritten as well, to the host's address on the container's private network, because the reply has to come back through the machine that holds the translation state — if the container answered the client directly, the client would receive a reply from an address it never contacted. The consequence is that the peer address the application reads is the host's, identically, for every caller. Anything keyed on client address silently degrades: per-client rate limits collapse into one bucket, address allowlists match everyone or no one, and audit trails record an internal address. Nothing inside the container can recover the original — only the hop that still knew it can pass it on.
code
pseudocode · 14 lines# one request from the partner, followed through the publishing hop
inbound at the host:
source = partnerAddress : 51234
destination = hostAddress : 8080
# rewrite 1 - required, or the packet reaches nothing
destination = containerAddress : 8080
# rewrite 2 - common, so the reply returns through the translating host
source = hostAddressOnPrivateNetwork : 44001
what the process reads as its peer:
peerAddress = hostAddressOnPrivateNetwork # never partnerAddressgo deeper
Remember that publishing rewrites addresses on the way in, so the address the application sees may belong to the host rather than to whoever sent the request.
Explain both rewrites and why the second one exists — the reply has to return through the machine holding the translation state — and say exactly which value the application ends up reading.
Show that you have been bitten: name the controls that keep reporting success on one rewritten address, and rule out the fixes that look plausible from inside the container.
Set the rule for the estate. Decide where the client address is established along the path, who is permitted to assert it, and how a service knows the value it enforces on can be trusted.
## What the publishing hop does to a packet A request from a partner integration arrives at the host with a source of the partner's address and a destination of the host's address and published port. Two rewrites can happen before the application sees it. - The **destination** is rewritten to the container's address and container port. This is not optional — without it the packet has nowhere to go, because nothing on the host itself is listening. - The **source** is often rewritten too, to the host's address on the container's private network. This one is about the return path. The second rewrite surprises people, so it is worth stating why it exists. Translation is stateful: the host remembers the mapping so that the reply can be turned back into a reply from the host address and port the client dialled. If the container replied straight to the partner's address, the partner would receive an answer from an address it never contacted, and would discard it. Rewriting the source guarantees the reply passes back through the host that holds the state. Some designs avoid the source rewrite by arranging the return route differently, and platforms genuinely differ here — but the common publishing hop on a single host does rewrite it, and where it does, the original address is gone by the time the process reads the connection. ## Who still knows the client's address | | knows the real client address? | |---|---| | the client itself | yes, trivially | | the host's translation state | yes, for the life of the entry | | the container's network stack | no — it only ever saw the rewritten source | | the application process | no, and no local setting can change that | The important row is the last one. This is not a logging configuration problem, a framework problem, or a verbosity problem. The information was discarded upstream of the process, and no amount of instrumentation inside the container will reconstruct it. ## What quietly breaks The danger is that nothing errors. Every control keyed on the caller's address keeps running and keeps producing plausible output: - **Per-client rate limiting** collapses into one bucket. Every request appears to come from the same source, so either one noisy partner throttles everybody, or the limit is raised until it protects nobody. - **Address allowlists** become all-or-nothing: the single rewritten address is either permitted, letting everything through, or denied, locking everyone out. - **Audit trails and access logs** record an internal address, so the record of who did what is worthless after the fact — usually discovered during an incident review, when it is too late to go back. - **Abuse and lockout logic** — counting failed attempts per address — counts them all together, so one client's failures lock out the rest. - **Anything reasoning about the caller's network origin** collapses to a single origin. ## What does not fix it 1. **Reading the peer address of the socket from inside the container.** That is precisely the rewritten value; it is where the wrong data came from. 2. **Moving the rate limiter inside the application.** The limiter is not the problem; its key is. 3. **More logging.** The field is faithfully recorded — it is simply not the client's address. 4. **Assuming the request body or a session identifier stands in for the address.** It identifies a user, not a network origin, and the controls above were built to work before anyone is identified. ## Where the fix has to live The original address must be carried by the hop that still had it, and handed to the application as data rather than inferred from the connection. That means a conveyed value from the layer in front — which is a piece of proxy-layer design with its own conventions and its own pitfalls, and belongs to that subject rather than to this one. Two points do belong here. First, **a conveyed address is a claim, not a fact**: whatever passes it must be the only path to the application, or a caller can simply assert whichever address it prefers, and an allowlist keyed on that value becomes a bypass rather than a control. Second, **the right time to discover this is at design time**. If a service is going to enforce anything per client, the address must be treated as something that has to be delivered deliberately along the path, and the path checked end to end — because the failure mode is not an outage. It is a control that reports success while protecting nothing.
- Why rewrite the source at all, when only the destination has to change?So the reply comes back through the machine holding the translation state and can be un-translated. Answering the client directly from the container would deliver a reply from an address the client never contacted, and it would be discarded. Some designs solve the return path by routing instead.
- Which controls degrade first, and why is that dangerous?Per-client rate limits, address allowlists, failed-attempt lockouts and audit trails. None of them fail loudly: they keep running against a single rewritten address and keep reporting success, so the gap is usually found during an incident review rather than a test.
A call reaching you through a building's front desk shows the desk's extension, not the caller's number. The desk still knows who rang; your phone never will unless the desk tells you.
saying these in an interview costs you the question
- Thinks the socket's peer address inside the container is the real client
- Says the address is unrecoverable in principle once traffic is published
- Claims per-client limits still work because each client gets a distinct source port
- Proposes more logging or a higher log level as the fix
- Trusts a conveyed client address without controlling who can set it