Direct Server Return (DSR)
In traditional load balancing architectures (such as NAT or Reverse Proxy modes), incoming requests and outgoing responses pass through the load balancer. The load balancer receives a client's request, forwards it to a backend server, waits for the server to process the request, and then relays the response back to the client.
Direct Server Return (DSR)—also known as LVS-DR (Direct Routing) in Linux Virtual Server environments—is a Layer 4 load balancing technique that alters this asymmetry.
In a DSR architecture: 1. Request Path: The client sends a request to the Load Balancer's Virtual IP (VIP). The load balancer modifies only the destination MAC address of the packet (leaving the IP addresses intact) and forwards it directly to a backend server. 2. Response Path: The backend server processes the request and responds directly to the client, bypassing the load balancer completely.
┌─────────────────┐
│ Client │
└────────┬────────┘
│
1. Request │ 3. Direct Response
(Dst: VIP) │ (Src: VIP, Dst: Client)
▼ ▲
┌─────────────────┐ │
│ Load Balancer │ │
└────────┬────────┘ │
│ │
2. Forwarded │ │
(Dst MAC updated) │
▼ │
┌─────────────────┐ │
│ Backend Server ├────────┘
└─────────────────┘
What Problem Does This Solve?
The primary challenge DSR addresses is bandwidth asymmetry in high-throughput network services.
Eliminating the Load Balancer Network Bottleneck
In most web applications, video streaming services, and API platforms, incoming requests are extremely small (e.g., a few hundred bytes for an HTTP GET request), while outgoing responses are massive (e.g., megabytes or gigabytes of HTML, JSON, images, or video streams).
In traditional NAT/Proxy modes, the load balancer must process both inbound and outbound traffic. The return bandwidth of the load balancer quickly becomes a bottleneck, forcing organizations to deploy expensive, high-spec load balancing clusters simply to process egress bytes.
DSR offloads 90%+ of the total network traffic volume from the load balancer, allowing a single load balancing node to handle massive amounts of traffic without choking on return bandwidth.
Lower Latency
By removing a hop on the return path, DSR reduces packet latency. Responses go directly from the backend server's network interface to the core router and out to the client.
Reduced CPU & Memory Load on Balancers
Since the load balancer does not terminate TCP connections or re-packetize return traffic, its CPU and memory overhead remain minimal, freeing up capacity to handle higher request rates.
Implementation Details
To successfully deploy DSR, specific network and application conditions must be met:
Layer 2 Network Adjacency Requirement
DSR relies on modifying the target Ethernet frame's Destination MAC address without changing the destination IP (VIP). Because MAC addresses are only routable within a local broadcast domain, the Load Balancer and all Backend Servers must reside on the same Layer 2 network segment (VLAN/Subnet).
The VIP on Backend Servers
Because the load balancer does not change the destination IP of incoming packets, the packet arrives at the backend server with its destination set to the VIP (e.g., 192.168.100.1).
For the backend server's operating system kernel to process this packet instead of dropping it, the VIP must be bound locally to a loopback (lo) or dummy interface on every backend server.
The ARP Problem
If multiple backend servers have the VIP assigned to a local interface, they will all attempt to answer ARP (Address Resolution Protocol) requests from the local network router asking, "Who has IP 192.168.100.1?"
If a backend server answers an ARP request for the VIP, the network switch will route client traffic directly to that backend server, bypassing the load balancer entirely (ARP Spoofing / Flapping).
Therefore, ARP Suppression must be strictly configured on all backend servers so that only the load balancer responds to ARP queries for the VIP.
Implementation in Linux
Here is how to implement DSR using Linux Kernel IPVS on the load balancer, alongside sysctl ARP suppression on backend servers.
Architecture Assumptions
- VIP:
192.168.100.1 - Load Balancer (IPVS):
192.168.100.10 - Backend Server 1:
192.168.100.11 - Backend Server 2:
192.168.100.12
Configure Backend Servers
Perform these steps on every backend server (192.168.100.11, 192.168.100.12).
Assign the VIP to the Loopback Interface
Assign the VIP as a /32 address on the lo interface so the server accepts traffic destined for the VIP without broadcasting it to the network:
Configure ARP Suppression via Sysctl
Prevent the backend server from responding to ARP requests for the VIP on its physical interface (eth0):
Edit /etc/sysctl.conf:
# Do not reply to ARP queries unless the IP is configured on the incoming interface
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.eth0.arp_ignore = 1
# Ensure the kernel uses the best local address when sending ARP queries
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.eth0.arp_announce = 2
Configure the Load Balancer (IPVS / Keepalived)
On the load balancer node (192.168.100.10), configure IPVS to use Direct Routing (-g flag in ipvsadm or lb_kind DR in Keepalived).
Using ipvsadm directly (not recommended)
# 1. Bind the VIP to the physical/loopback interface on the Load Balancer
ip addr add 192.168.100.1/32 dev eth0
# 2. Add Virtual Service (TCP port 80 using Round Robin)
ipvsadm -A -t 192.168.100.1:80 -s rr
# 3. Add Real Servers in Gateway/Direct Routing Mode (-g)
ipvsadm -a -t 192.168.100.1:80 -r 192.168.100.11:80 -g
ipvsadm -a -t 192.168.100.1:80 -r 192.168.100.12:80 -g
Using Keepalived (/etc/keepalived/keepalived.conf): (recommended). You will get backend healthcheck for free.
virtual_server 192.168.100.1 80 {
delay_loop 3
lb_algo rr
lb_kind DR # Specifies Direct Routing (DSR)
protocol TCP
real_server 192.168.100.11 80 {
weight 1
TCP_CHECK {
connect_port 80
connect_timeout 3
}
}
real_server 192.168.100.12 80 {
weight 1
TCP_CHECK {
connect_port 80
connect_timeout 3
}
}
}
Verification
Verify IPVS routing table on the Load Balancer:
Test HTTP Request from a external client:
Role of Policy-Based Routing (PBR) in DSR
While standard DSR requires the load balancer and backend servers to be in the same Layer 2 domain (same VLAN/subnet), Policy-Based Routing (PBR) allows you to extend DSR across Layer 3 networks (cross-subnet deployments).
How PBR Enables Cross-Subnet DSR
When the load balancer and backend servers are in different subnets, the load balancer cannot forward packets simply by changing the destination MAC address. Instead, it encapsulates the original packet (for example, using IP-in-IP or GRE tunneling) and routes it to the backend server across Layer 3 routers.
Once the packet arrives at the backend server:
- Decapsulation: The backend decapsulates the packet to reveal the original request destined for the Virtual IP (VIP).
- Asymmetric Egress Control: When the backend application generates a response, standard routing tables might attempt to send the response back through the load balancer or default gateway, breaking the direct return path.
- PBR Interception: PBR rules configured on the backend server or its local default router inspect outgoing traffic. If the source IP matches the VIP (e.g.,
192.168.100.1), PBR overrides the standard routing table and forces the response packet directly out to the upstream internet router.
Example Linux PBR Configuration on a Backend Server
To ensure outbound traffic sourced from the VIP skips local gateway restrictions and routes directly to the core network router (10.0.0.1):
# 1. Create a custom routing table (Table ID 200)
ip route add default via 10.0.0.1 dev eth0 table 200
# 2. Add a policy rule to match traffic sourced from the VIP
ip rule add from 192.168.100.1 lookup 200
# 3. Flush the route cache to apply rules immediately
ip route flush cache
By leveraging PBR, enterprise architectures can deploy load balancers in a centralized network tier while distributing backend servers across multiple isolated subnets or data centers without losing the performance benefits of DSR.