Summary
- RFC 2391 let one virtual address represent a server pool while an LSNAT selected a member for each new session and fixed the translation state for all later packets.
- Round robin, session counts, traffic, weights, route cost and pings were decision inputs, not proofs of real application capacity or success.
- Declaring a host dead could stop new assignments, but it could not move an already-bound session; selection, persistence, failover and outcome were separate receipts.
One address, several possible machines
The scaling problem looked simple from the client. Demand had grown beyond what one server could comfortably handle. Several servers could answer the same kind of request. The client should not need a new protocol, a new address or knowledge of the pool every time capacity changed.
RFC 2391 proposed an intermediary that made those conditions disappear behind one visible address. It combined the address rewriting of RFC 1631 with a selector for new sessions. The virtual server address could refer to one machine or to a set. Members could be added, removed or replaced while the LSNAT became the focal point for change.
That convenience moved authority. The address no longer named the machine that would do the work. It named an entry into a decision process. The LSNAT interpreted a new flow, consulted its pool and policy, selected a member, and altered the packet so that the chosen server received it.
This was not the anycast problem described by RFC 1546. Anycast made a service address routable toward one of several instances, with particular difficulty when successive datagrams could choose differently. RFC 2391 inserted a stateful translator. It chose a server for a session and then had to remember that choice. Its central question was not merely “which instance did this packet reach?” but “which stored association now controls every packet in this conversation?”
The first choice became packet-path state
RFC 2391 divided translation into three phases. During session binding, an incoming session was associated with a server address. That association established the translation parameters for every later datagram. During lookup and translation, subsequent packets were matched to the stored session and rewritten. During unbinding, the selected server ceased to be responsible because the session was considered finished.
The binding was therefore more than a routing hint. On the client-to-server path the translator could replace the destination address and port. On the return path it could replace source fields so that the client continued to see the virtual service. IP and transport checksums had to follow the changes. The client did not observe a new endpoint halfway through the exchange; it observed the continuity manufactured by the translator.
That continuity imposed a path constraint. Every request and response for the session had to pass through the same LSNAT. If one direction bypassed the box, the packet would not receive the matching translation. If a different translator lacked the binding, it could not reconstruct the conversation merely from the advertised virtual address.
The RFC's direct statement is decisive: once a session had been assigned to a host, it could not move to a different host until it ended. The selector made a choice at admission time. It did not implement live transfer of transport state, application state or unfinished work.
“Least loaded” was a model, not an observation of truth
The document explored several ways to choose a server. Round robin was cheap and blind: it did not inspect load at all. Least-sessions counted the bindings already assigned to each member. That looked more responsive, but only by assuming that sessions consumed comparable resources and that servers had comparable ability.
Counting packets or bytes provided another approximation. A server receiving less traffic might have more spare capacity, or it might be trapped in expensive computation after receiving a small request. Network volume was observable at the LSNAT; CPU pressure, memory contention, disk latency and application queues were not.
Weights let operators encode beliefs. An FTP session could be valued as costlier than a Telnet session; one machine could be rated as stronger than another. The arithmetic became more expressive, but its inputs remained estimates. A carefully calculated wrong weight was still a wrong model.
RFC 2391 acknowledged the limit openly. Precise real-time knowledge of unused capacity at a remote server was difficult. The translator could infer it non-intrusively, or servers could actively report capacity. The latter approach improved visibility only by creating another protocol, another timing problem and another source of stale or misleading state.
Distributed pools added route cost. A selector could combine the cost of reaching a server with its assigned sessions or measured traffic. An unreachable path could be assigned infinite cost so no new load went there. Yet a route metric remained evidence about access, not evidence that the application behind the address was healthy or that an already accepted job would complete.
The selection decision was local and necessarily provisional. Its inputs answered “where should the next session go under this model?” They did not answer “which server possesses the most real capacity?” and certainly did not answer “will this user's transaction succeed?”
A dead-server rule protected the next session
RFC 2391 was alert to the worst form of bad selection: sending fresh sessions into a machine that no longer replied. It proposed heuristic liveness checks. The LSNAT could ping pool members. It could also watch for datagrams from a member after assigning a new session. If no response appeared after a few seconds, it could declare that server dead and stop assigning it new sessions.
Even recovery was framed as another experiment. After a pause, the selector might give the server new sessions again and observe its response time. A member moved from eligible to ineligible and back according to local evidence and policy. A ping response did not prove the application was ready; the absence of an application response did not identify whether the server, path, service or request was at fault.
The limit follows from two parts of the RFC and should not be blurred. Dead-host detection stopped new assignments. Existing sessions could not be moved before they ended. A host might therefore be removed from the admission set while every session already bound to it failed. Spare servers in the pool did not inherit those conversations.
That is the difference between failure avoidance and failover. Avoidance changes the next selection. Failover preserves or reconstructs the state needed for work already underway. RFC 2391 described the former, not a transparent implementation of the latter.
RFC 3022 later made the same dependency visible at the translator layer. Rerouting flows after one NAT failed could itself break them unless replacement devices shared configuration and session state. A new path without the old binding was not the same session. RFC 3234 eventually classified this as hard state: without a replicated copy, failure meant restart rather than continuation.
Ending a session was another uncertain decision
Bindings could not remain forever. The translator needed to release them when the server was no longer responsible. TCP offered visible signals such as FIN and RST, although packet loss, retransmission or a reboot could still leave the intermediary uncertain. UDP did not carry a universal end-of-session message. Other protocols could diverge even further from the translator's idea of a session.
The consequence was heuristic unbinding. An idle timer might delete a mapping whose application was merely quiet. A long timer might preserve state for a conversation that had already disappeared. RFC 2663 later stated the problem precisely: the NAT's session can differ from the application's session, and no generally correct timeout separates prolonged silence from death.
This uncertainty mattered because unbinding was not clerical cleanup. It altered who was considered responsible for future matching packets and freed finite table or port resources. The operator chose timeouts under competing risks: premature loss of continuity, or accumulation of stale state.
RFC 4787's later requirements for UDP mapping timers did not abolish the trade. They standardized minimum behaviour for a different NAT context while documenting considerable implementation variation. The running system still had to decide when observed silence justified deleting authority.
LS-NAPT bought topology freedom with more dependence
The basic LSNAT topology kept the server pool behind a border where both directions naturally crossed the translator. RFC 2391 also described LS-NAPT, which rewrote enough of both sides to force client and server traffic back through the box. Servers no longer had to sit inside one confined stub domain, and access links could expand.
The price was explicit. More fields had to be translated. The mechanism applied only to TCP and UDP in that configuration. Available client ports imposed a ceiling on simultaneous sessions. The design relaxed a physical placement constraint by increasing state, processing and address-port dependence at the intermediary.
This trade was characteristic of middleboxes. They can deploy useful behaviour without changing endpoints, precisely because they intercept and reinterpret traffic the endpoints think is direct. RFC 1631 presented that attraction for NAT and also named its cost: end-to-end meaning moved out of the address and into network-maintained state.
RFC 7098 later used the term “persistence” for the guarantee that a session stays on one server until completion. It described modern stateful load balancers storing a tuple-to-server association after the first packets. That vocabulary clarifies RFC 2391, but it must not be stretched. Persistence means a stable choice, not a movable choice. It holds a session to its server; it does not guarantee that the server survives.
The virtual address was the first receipt, not the last
The evidence chain can now be stated without compression. The virtual address identifies the service entrance. Configuration identifies the eligible pool. A metric or report supplies a partial view of current conditions. Policy chooses a member. A binding records that choice. Symmetric traversal and translation preserve it. A server reply demonstrates some running behaviour. Only the application result shows whether the intended work occurred.
No earlier layer may impersonate the later one. A configured member is not necessarily live. A live host is not necessarily ready. A lightly loaded server is not necessarily authorized or correct. A binding is not a completed request. A stable packet path is not a successful application session.
Heng Lu's notes describe this as a discipline of reality layers. A specification or record can coordinate meaning, but it does not create the running state it describes. RFC 2391 is a useful historical example because the intermediary was built to make one address look simple. Its own text nevertheless preserved the harder truth: the apparent endpoint depended on local selection, retained state, forced traversal and imperfect observations.
The pool could offer alternatives for the next arrival. It could not turn an established conversation into fungible work. In RFC 2391, the most consequential line was not the load-sharing formula. It was the binding: once the choice became state, “another healthy server exists” stopped being a recovery plan.
Sources
- RFC 2391 — Load Sharing using IP Network Address Translation
- RFC Editor record for RFC 2391
- IETF Datatracker history for RFC 2391
- RFC Editor errata search for RFC 2391
- RFC 1631 — The IP Network Address Translator
- RFC 1794 — DNS Support for Load Balancing
- RFC 2663 — IP Network Address Translator Terminology and Considerations
- RFC 3022 — Traditional IP Network Address Translator
- RFC 3234 — Middleboxes: Taxonomy and Issues
- RFC 4787 — Network Address Translation Behavioral Requirements for Unicast UDP
- RFC 5382 — NAT Behavioral Requirements for TCP
- RFC 7098 — Using the IPv6 Flow Label for Load Balancing in Server Farms
- Heng Lu — Running-Code Primacy
- Heng Lu — Minimum Initial Specification, Localized Future Decision, and Voluntary Adoption
- Heng Lu — Reality Layers, Symbolic Power, and Clarity
Member Briefing
Deeper Profile Context
Sign in with the right membership level to unlock the full briefing and source notes.
Only for Strategic Circle
Strategic Circle
Open to all readers. Unlock profile briefings after joining and signing in.
Join Strategic CircleOnly for Leadership Alliance
Leadership Alliance
For qualified IP-asset owners and management; sign in to unlock alliance briefings.
Join Leadership Alliance
