skip to content

A multi-homed Linux server holds several IP addresses, and connections it initiates arrive at the peer carrying an unexpected source address. How does the kernel choose the source address for a locally generated packet, and how can you pin it?

level: middleimportance: nice to knowfreq 38%

answer

  1. it falls out of routing, not the socket
  2. an explicit bind overrides everything
  3. the route can carry a preferred source
  4. primary versus secondary addresses matter
  5. one command shows nexthop, device and src

basics

~20 s

Unless the application binds a specific address, the source IP is a by-product of the routing decision: the kernel uses the chosen route's preferred-source attribute if it has one, otherwise an address of the egress interface. ip route get <dst> prints the address that would be used.

solid answer

~50 s

Source selection is not a property of the socket by default — it falls out of routing. The order is: if the application called `bind()` with an explicit address, that wins outright. Otherwise the kernel performs the route lookup for the destination, and if the winning route carries a preferred-source attribute (shown as `src` in `ip route show`) it uses that; failing that, it picks an address configured on the egress interface, preferring a primary address in the same network as the nexthop. So the answer to "which IP will my outbound traffic appear to come from" is decided by which interface the route sends the packet out of. `ip route get 203.0.113.5` reports the nexthop, the device and the `src` value together. To pin it, either bind in the application, or attach `src` to the route with `ip route change ... src <addr>`.

code

bash · 8 lines
bash
# what the kernel would use for this destination
ip route get 203.0.113.5

# test the decision for a packet already carrying a source address
ip route get 203.0.113.5 from 10.0.0.7

# pin the preferred source for everything taking the default route
ip route change default via 192.168.1.1 dev eth0 src 192.168.1.60

go deeper

for a junior

Know that unless an application explicitly binds an address, the source IP comes from the routing decision, and that ip route get <dst> shows which one would be used.

for a middle

Explain the order — explicit bind, then the route's preferred source, then an address of the egress interface — and why secondary addresses are skipped. Know how to attach src to a route.

for a senior

Recognise the symptom from the peer's side: allow-list rejections and audit-log mismatches caused by a source change after a VPN, an extra NIC or a routing change. Choose deliberately between binding in the app and changing the route.

for a principal

Own the identity story: which addresses a fleet presents to partners, whether that is pinned per application or per host, and how it stays stable through interface changes, IPv6 privacy extensions and failover.

## Why the question comes up at all A server with one address has no ambiguity. Add a second address, a second NIC, a VPN interface or a container bridge, and outbound connections suddenly start arriving somewhere with a source address nobody chose deliberately. This matters because peers frequently care: firewall allow-lists, database host rules, API allow-lists and audit logs all key off the source address. The traffic reaches its destination and is rejected there, which sends people hunting through firewalls when the fault is a routing attribute at home. ## The selection order For a locally generated packet the kernel resolves the source address like this: **1. An explicit bind wins.** If the process called `bind()` on the socket with a concrete address before connecting, that address is used and no selection happens. Many clients expose this — `curl --interface`, `ssh -b`, and a bind-address setting in most client libraries all end up here. **2. The route's preferred source.** The routing decision comes next, and the winning route may carry a preferred-source attribute, rendered as `src` in the table: ``` 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.50 default via 192.168.1.1 dev eth0 src 192.168.1.50 metric 100 ``` The kernel-generated connected routes get one automatically from the address that created them. Routes you add by hand do not, unless you say so. **3. Otherwise, an address of the egress interface.** With no preferred source the kernel picks an address configured on the outgoing device, preferring one whose scope suits the destination and, where possible, one in the same network as the nexthop. Crucially it prefers a **primary** address. When you add a second address in the same subnet as an existing one, the kernel marks it *secondary*, and secondary addresses are not chosen for source selection — which is why adding an extra IP to a NIC does not change how the box appears to the world. Because step 3 depends on the egress interface, changing routing changes identity. Bring up a VPN or a policy rule that steers traffic out of a different NIC, and the source address changes with it even though nothing about the socket changed. ## Seeing the decision `ip route get` answers all three parts of the routing decision at once: ``` $ ip route get 203.0.113.5 203.0.113.5 via 192.168.1.1 dev eth0 src 192.168.1.50 uid 1000 ``` Nexthop, egress device, and the source address the kernel would stamp on the packet. You can also test a hypothetical: `ip route get 203.0.113.5 from 10.0.0.7` asks what would happen for a packet already carrying that source, which is how you check policy rules that select a table by source address. ## Pinning the source address There are three levers, in decreasing order of bluntness: - **Bind in the application.** The most precise, because it affects only that process. Preferred when one service must present a specific identity and the rest of the box should not change. - **Attach `src` to the route.** `ip route change default via 192.168.1.1 dev eth0 src 192.168.1.60` makes every unbound socket taking that route use 192.168.1.60. Machine-wide for the destinations that route covers, and invisible to application code — document it, or the next engineer will not find it. - **Policy routing.** When traffic must leave a *different* interface depending on its source, source selection alone is the wrong tool; you need routing rules that select a separate table. That is a different mechanism from the `src` attribute, which only labels packets on a path already chosen. A fourth approach, rewriting the source address as the packet leaves, exists in the firewall layer, but for locally generated traffic it is a heavier and more surprising instrument than simply setting the route attribute. ## The IPv6 wrinkle IPv6 hosts routinely hold several addresses at once — link-local, a stable global address, and often temporary privacy addresses. Selection there follows the standardised rules of RFC 6724, which prefer, among other things, matching scope, a source whose prefix shares the longest match with the destination, and temporary addresses over stable ones for outbound connections when privacy extensions are active. That last preference is a common surprise: an IPv6 server whose peer allow-lists its stable address sees connections arrive from a rotating temporary one until privacy extensions are turned off for that interface. ## The habit to build When a peer complains about an unexpected source address, do not start with their firewall. Run `ip route get <their address>` on the sending host. The `src` field in that one line usually explains the whole ticket.

  • You add a second IP to an interface that already has one in the same subnet, but outbound traffic still uses the old address. Why?
    The new address is marked secondary. Source selection prefers a primary address on the egress interface, so a secondary is never chosen implicitly. Either bind to it in the application or attach it to the relevant route as the preferred source with `ip route change ... src <addr>`.
  • How do you check which source address a specific destination would get, without opening a connection?
    `ip route get <destination>` performs the real lookup and prints the nexthop, egress interface and the `src` value the kernel would use. Adding `from <address>` tests the decision for a packet that already carries a given source, which is how you validate policy rules that pick a table by source address.
  • When is setting `src` on a route the wrong tool?
    When traffic must actually leave a different interface depending on its source. The `src` attribute only labels packets on a path the routing decision has already chosen; it cannot redirect them. Steering by source requires policy routing — a rule selecting a separate table with its own nexthop.

saying these in an interview costs you the question

  • Believing the source address is chosen by the socket, not by routing
  • Assuming the interface's first-listed address is always used
  • Expecting a newly added secondary address to be used automatically
  • Thinking `src` on a route redirects traffic as well as labelling it
  • Ignoring IPv6 temporary addresses when a peer allow-lists an IP

context