Dealing with clients coming from the same subnet as the real servers when using Tproxy can be a pain. It creates an issue because once Haproxy is running transparently, it will allow the real server to see the client IP so the real server will reply directly back to the client bypassing the load balancer.

The obvious solution to this is to mess with the real servers routing tables as we often do with other load balancing methods such as L4 single subnet NAT mode as shown below for Linux Real Servers:

For L4 single subnet NAT mode we need to modify the local network route by changing to a higher metric.

First, remove the local subnet route :

route del -net 10.0.0.0 netmask 255.0.0.0 dev eth0

Then re-add with a higher metric :

route add -net 10.0.0.0 netmask 255.0.0.0 metric 2000 dev eth0

Next we need to make sure that local network access uses the load balancer as its next hop :

route add -net 10.0.0.0 netmask 255.0.0.0 gateway 10.0.0.21 metric 0 dev eth0

Once configured, any local traffic (same subnet) is then handled by this manual route and any external traffic is handled by the default gateway (which also points at the load balancer).

So what about if we don’t want to change the local subnet routes?

Those of you that have read our HAProxy with Tproxy Blog will know that adding a “source” configuration line to the VIP is the final piece to the puzzle when configuring transparent 2 arm HAProxy.

Haproxy also allows you to specify the “source” line for each real server or even by backend. This allows you to use ACL’s to decide if traffic is sent to a transparent server or not. This method is especially useful when you don’t want to start messing with routing tables other than changing the default gateway. Obviously if you don’t need client transparency for local subnet traffic, but just want the client IP for the logs you can still use X-Forwarded-For.

The first example is for a simple listen style VIP where you have a single server (server 3) set aside for non transparent traffic :

listen VIP_Name 192.168.2.87:80
mode http
option forwardfor
cookie SERVERID insert nocache indirect
acl local_subnet src 10.0.0.0/8
server server1 10.0.0.60:80 weight 1 cookie server1 check source 0.0.0.0 usesrc clientip
server server2 10.0.0.61:80 weight 1 cookie server2 check source 0.0.0.0 usesrc clientip
server server3 10.0.0.60:80 weight 0 cookie server3 check
server backup 127.0.0.1:80 backup
use-server server3 if local_subnet

So what are we doing here? We have an ACL “acl local_subnet src 10.0.0.0/8” to identify clients from the server side subnet and “use-server server3 if local_subnet” which selects our non transparent server. In this example I have set the non transparent server to a weight of 0 so it won’t be selected for normal client traffic.

Next is a more complicated example utilising a frontend/backend style config which allows for an entire non transparent backend.

frontend http
bind 192.168.2.87:80
mode http
acl local_subnet src 10.0.0.0/8
use_backend www-non-transparent if local_subnet
default_backend wwwbackend www
mode http
option forwardfor
cookie SERVERID insert nocache indirect
source 0.0.0.0 usesrc clientip
server server1 10.0.0.60:80 weight 1 cookie server1 check
server server2 10.0.0.61:80 weight 1 cookie server2 check
server backup 127.0.0.1:80 backup
backend www-non-transparentmode http
option forwardfor
cookie SERVERID insert nocache indirect
server server1 10.0.0.60:80 weight 1 cookie server1 check
server server2 10.0.0.61:80 weight 1 cookie server2 check
server backup 127.0.0.1:80 backup

As often is the case, there are many ways to achieve this, so mileage for this as a solution will vary. However it’s a lot less disruptive than changing routing tables and allows same subnet traffic to work so long as they can already route to the VIP in the second subnet.