Re: tcp mode: client port selection

2023-03-04 Thread Willy Tarreau
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|
 +-+
|
 server4.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,dstint 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,dstint 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,dstint 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,dstint 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,dstint 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:2-2
server isp-b 3.3.3.3:443 source 0.0.0.0:3-3

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 th

Re: tcp mode: client port selection

2023-03-03 Thread Amaury Denoyelle
On Fri, Mar 03, 2023 at 09:35:45AM +0100, Jack Bauer wrote:
> Am Do., 2. März 2023 um 17:52 Uhr schrieb Amaury Denoyelle <
> adenoye...@haproxy.com>:
> >
> > It seems you do not use 'option redispatch' in your configuration so a
> > retry will never be conducted on another server. Therefore, your problem
> > is probably not related to haproxy retries.
> >
> From the documentation (
> http://docs.haproxy.org/2.7/configuration.html#4-option%20redispatch) one
> could or should conclude, that option redispatch is only working in HTTP
> mode.

I confirm that it works also for proxy on TCP mode and that the
documentation is confusing.

> Even if it is also working in TCP mode and we are not using it in the
> configuration, haproxy makes connections with the same client ip port to
> another target server.
> 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

-- 
Amaury Denoyelle



Re: tcp mode: client port selection

2023-03-03 Thread Jack Bauer
Am Do., 2. März 2023 um 17:52 Uhr schrieb Amaury Denoyelle <
adenoye...@haproxy.com>:

>
> It seems you do not use 'option redispatch' in your configuration so a
> retry will never be conducted on another server. Therefore, your problem
> is probably not related to haproxy retries.
>

>From the documentation (
http://docs.haproxy.org/2.7/configuration.html#4-option%20redispatch) one
could or should conclude, that option redispatch is only working in HTTP
mode.

Even if it is also working in TCP mode and we are not using it in the
configuration, haproxy makes connections with the same client ip port to
another target server.
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?

-Jack


Re: tcp mode: client port selection

2023-03-02 Thread Amaury Denoyelle
On Thu, Mar 02, 2023 at 02:39:25PM +0100, Jack Bauer wrote:
> Hi,
> [...]
> Therefore it might be necessary to understand how haproxy chooses the
> client-ip-port when creating new tcp connections to backend servers.
> Especially how haproxy behaves when a connection attempt fails and a retry
> is triggered because this retry might change the backend server.
> Will the retry use the same client port? If yes, is this intended?
> 

It seems you do not use 'option redispatch' in your configuration so a
retry will never be conducted on another server. Therefore, your problem
is probably not related to haproxy retries.

-- 
Amaury Denoyelle



tcp mode: client port selection

2023-03-02 Thread Jack Bauer
Hi,

I have a question regarding source port selection when using haproxy in tcp
mode.

We are using haproxy with the following scenario:

| public haproxy with tcpmode | -> | PNAT Fortigate FW | -> | internal Host
|

The Fortigate is reachable via two ISPs (ISP-A, ISP-B) and has port
forwards (NAT) to an internal host.
The haproxy backend configuration contains both public IPs assigned to the
Fortigate FW, enabling a distribution of the connections on both ISP
connections (haproxy config appended).

In this scenario we are getting "session clash" messages on the Fortigate
FW. A session clash message indicates, that the tuple of  for tcp connection tracking (sessions in the
Fortigate world) is reused, before the previous session was closed.

>From the Fortigate knowledgebase, the tupel is calculated from
[HAPROXY-IP]:49660
and [INTERNAL-HOST-IP]:443
(Ports used from the example message appended, where 49660 is the haproxy
client ip port)

The message shows, that haproxy created a session / tcp connection using
the ISP-B-IP and then created a new connection using the ISP-A-IP using the
same client-ip-port as in the previous connection.

This leads to unexpected behaviour (e.g. packet drop) and I want to get rid
of these messages (and behaviour).

Therefore it might be necessary to understand how haproxy chooses the
client-ip-port when creating new tcp connections to backend servers.
Especially how haproxy behaves when a connection attempt fails and a retry
is triggered because this retry might change the backend server.
Will the retry use the same client port? If yes, is this intended?


Best regards,
Jack



Additional infos:
===
- Connection / session creation rate is low at around 3/s with peaks of
25/s.
- connection duration is low
- peak total concurrent connections is around 600


haproxy.cfg:
==
global
maxconn 1
log stdout format raw daemon notice

defaults
log global
mode tcp
retries 2
timeout client 120s
timeout connect 4s
timeout server 120s
timeout check 5s
option tcpka
option tcplog
option logasap
balance leastconn


frontend tcp_443_frontend_with_proxy_protocol
mode tcp
bind *:443 accept-proxy
maxconn $MAX_CONN
default_backend tcp_443_backend


backend tcp_443_backend
mode tcp
fullconn $MAX_CONN
option tcp-check
default-server init-addr libc,last,none
server ip1_port1 [ISP-A-IP]:443 send-proxy maxconn $MAX_CONN check inter 1s
server ip2_port1 [ISP-B-IP]:443 send-proxy maxconn $MAX_CONN check inter 1s





Example message (the actual IP addresses have been replaced by
placeholders):
===
New Status
[HAPROXY-IP]:49660->[ISP-A-IP]:443([INTERNAL-HOST-IP]:443) dir=0 act=1
hook=4
[HAPROXY-IP]:49660->[INTERNAL-HOST-IP]:443([INTERNAL-FW-IP]:22686) dir=1
act=2 hook=0
[INTERNAL-HOST-IP]:443->[INTERNAL-FW-IP]:22686([HAPROXY-IP]:49660) dir=1
act=1 hook=4
[INTERNAL-HOST-IP]:443->[HAPROXY-IP]:49660([ISP-A-IP]:443)

Old Status
[HAPROXY-IP]:49660->[ISP-B-IP]:443([INTERNAL-HOST-IP]:443) dir=0 act=1
hook=4
[HAPROXY-IP]:49660->[INTERNAL-HOST-IP]:443([INTERNAL-FW-IP]:49660) dir=1
act=2 hook=0
[INTERNAL-HOST-IP]:443->[INTERNAL-FW-IP]:49660([HAPROXY-IP]:49660) dir=1
act=1 hook=4
[INTERNAL-HOST-IP]:443->[HAPROXY-IP]:49660([ISP-B-IP]:443)