On Fri, Mar 03, 2023 at 04:04:37PM +0100, Amaury Denoyelle wrote:
> > Can anyone say sth. about client port allocation in haproxy? Is it done
> > manually in some cases? Or is that a task that is completely done by the OS?
> 
> To my knowledge, haproxy does not explicitely select the port when
> connecting to a backend server unless a specific "source" statement is
> used, so this should be the responsibility of the OS. Have you checked
> that your ephemeral port range is big enough ?
> 
> $ sysctl net.ipv4.ip_local_port_range net.ipv4.ip_local_reserved_ports

Indeed, source ports are chosen by the operating system. The problem
Jack reports is a well-known issue in NAT+multi-homed environments and
is purely an architectural one:

                  client  1.2.3.4:5678
                    |
                    |
              /~~~~~~~~~~~~\
             {   internet   }
              `~~~~~~~~~~~~'
                /       \
               /         \
             ISP A       ISP B
              |           |
             (X)         (X)
              \           /
  2.2.2.2:443  \         /  3.3.3.3:443
             +-------------+
             |     DNAT    |
             +-------------+
                    |
                 server    4.4.4.4:443

The DNAT box that receives traffic from both ISPs needs to store
a session in its table that contains this:

   ext src,dst                int src,dst
   1.2.3.4:5678 2.2.2.2:443   1.2.3.4:5678 4.4.4.4:443

This way when the server responds to 1.2.3.4:5678, the NAT box reads this
NAT table in reverse direction and finds that 4.4.4.4:443->1.2.3.4:5678
must be translated to 2.2.2.2:443->1.2.3.4:5678.

But what if the same source arrives on the other public IP ? you'll get
a new entry:

   ext src,dst                int src,dst
   1.2.3.4:5678 3.3.3.3:443   1.2.3.4:5678 4.4.4.4:443

and suddenly your NAT table contains a conflict for the reverse lookup
needed for responses:

   ext src,dst                int src,dst
   1.2.3.4:5678 2.2.2.2:443   1.2.3.4:5678 4.4.4.4:443
   1.2.3.4:5678 3.3.3.3:443   1.2.3.4:5678 4.4.4.4:443

which one to pick for 4.4.4.4:443->1.2.3.4:5678 ?

In order to avoid this, some NAT boxes support to implement a
double-nat mechanism for incoming traffic. They'll either set a
specific source per original destination, or will assign different
port ranges, so you could have this:

   ext src,dst                int src,dst
   1.2.3.4:5678 2.2.2.2:443   4.4.4.2:5678 4.4.4.4:443
   1.2.3.4:5678 3.3.3.3:443   4.4.4.3:5678 4.4.4.4:443

The problem is that the server doesn't receive the original IPs
anymore, which can be a problem for filtering or even legal
logging. In this case you'll need to rely on the NAT box's
logging, or it should implement a mechanism to pass the source
address via an auxiliary mechanism such as the PROXY protocol.

A much simpler method for the device is in fact to assign two
IP addresses to the server and configure the DNAT box to use a
distinct IP address for each ISP:

                  client  1.2.3.4:5678
                    |
                    |
              /~~~~~~~~~~~~\
             {   internet   }
              `~~~~~~~~~~~~'
                /       \
               /         \
             ISP A       ISP B
              |           |
             (X)         (X)
              \           /
  2.2.2.2:443  \         /  3.3.3.3:443
             +-------------+
             |     DNAT    |
             +-------------+
                  |  |
      4.4.4.4:443 |  | 4.4.4.5:443
                 server

This way there is no more ambiguity in the DNAT table:

   ext src,dst                int src,dst
   1.2.3.4:5678 2.2.2.2:443   1.2.3.4:5678 4.4.4.4:443
   1.2.3.4:5678 3.3.3.3:443   1.2.3.4:5678 4.4.4.5:443

If the server responds from 4.4.4.4 then the outgoing source
is necessarily 2.2.2.2 but if it responds from 4.4.4.5, then
the outgoing source is necessarily 3.3.3.3.

If for any reason you cannot fix this deployment this way and
can only act on the haproxy side (e.g. the server is at a
customer's who doesn't want to fix their broken setup and who
decided that it was your problem only), then as Amaury suggests,
the "source" directive allows you to enforce source port ranges
for outgoing traffic. Then you can just have two sets of non-
overlapping ports for each target address:

    server isp-a 2.2.2.2:443 source 0.0.0.0:20000-29999
    server isp-b 3.3.3.3:443 source 0.0.0.0:30000-39999

That way there will never be any conflict in the fortigate's table
and the return traffic will always find a unique path.

But if the setup is yours, I strongly encourage you to fix it as
explained above because any other client could face this problem.
And in some mobile environments which make use of CGN (carrier-grade
NAT), it is possible that a mobile client will use a very narrow
source port range and will frequently reuse the same ports, which
can cause exactly this problem to happen when connecting to that
site.

Hoping this helps,
Willy

Reply via email to