Linux NATting does not support NAT hole punching?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi!

I have run two Chrome browsers each behind their own Linux NAT. I have
then tried to instruct them to establish a P2P connection with each
other using WebRTC. WebRTC is a standard framework that allows P2P
communication between browsers and which is available from JavaScript.

It turned out that the two browsers were not able to establish a P2P
connection. I have looked further into this (see below for details)
and it seems to me like the Linux NAT behaves in a way that is far
from optimal for supporting nat hole punching and a way that is
probably not supported by standard techniques used by WebRTC and other
P2P protocols.

Can this really be true or am I missing something? I have tried to
Google the problem but I did not find any discussions of this nor
details on exactly how NATing works in Linux.

In the rest of this email I give more details on an experiment I did
to confirm the problem. Note that the setup in this experiment is
slightly simpler than the one sketched above.

Suppose you have four hosts SRV1, SRV2, NAT and CLIENT where:

SRV1, SRV2 and NAT are on the same public network
NAT acts as a Linux natting router for CLI giving CLI access to the
public network.

Suppose CLI allocates a UDP port X and sends a packet from X to SRV1
and then to SRV2. This works fine. NAT will preserve the port X when
it forwards the packet to SRV1 and SRV2 and further SRV1 and SRV2 can
reply back and the packet will get back to CLI.

Suppose now CLI allocates a UDP port Y and sends a packet from Y but
this time only to SRV1. Suppose now that SRV2 sends a packet to port Y
on NAT. This packet is not forwarded back to CLI by NAT which I think
it should, but it is no big deal. What nat hole punching would
normally do is now to let CLI send a packet from Y to SRV2. However,
when this happens, NAT allocates a new port Z and uses that port when
forwaring the packet to SRV2. And it is not possible for SRV2 to send
packets to CLI on port Y - SRV2 will have to use port Z.

So it seems like that the fact that SRV2 sends a packet to CLI (which
is discarded) before CLI sends a packet to SRV2, has the side effect
that the packet from CLI to SRV2 is NATTed using a different port -
which is very bad for nat hole punching.

I have created a script that illustrates the problem. Below is both
commented output of the script followed by the script itself.

In this script:

NAT has address 10.0.0.1 on the public network and 192.168.1.1 on the
private network
SRV1 has address 10.0.0.2
SRV2 has address 10.0.0.3
CLI has address 192.168.1.2

Here is commented output of the sript.

First, we look at the case X=5001 which is where things work fine.

# CLI sends a packet to SRV1:
19:01:54.097299 IP 192.168.1.2.5001 > 10.0.0.2.6001: UDP, length 0
19:01:54.097337 IP 10.0.0.1.5001 > 10.0.0.2.6001: UDP, length 0

# SRV1 replies back and the packets gets back to CLI:
19:01:54.641166 IP 10.0.0.2.6001 > 10.0.0.1.5001: UDP, length 0
19:01:54.641186 IP 10.0.0.2.6001 > 192.168.1.2.5001: UDP, length 0

# CLI next sends a pakcet to SRV2 from the same port:
19:01:55.181445 IP 192.168.1.2.5001 > 10.0.0.3.6001: UDP, length 0
19:01:55.181513 IP 10.0.0.1.5001 > 10.0.0.3.6001: UDP, length 0

# SRV2 replies back and the packets gets to CLI:
19:01:55.729383 IP 10.0.0.3.6001 > 10.0.0.1.5001: UDP, length 0
19:01:55.729403 IP 10.0.0.3.6001 > 192.168.1.2.5001: UDP, length 0

Next, we look at the case Y=5002 which does not work

# CLI sends a packet to SRV1
19:01:56.768882 IP 192.168.1.2.5002 > 10.0.0.2.6002: UDP, length 0
19:01:56.768907 IP 10.0.0.1.5002 > 10.0.0.2.6002: UDP, length 0

# SRV1 replies back and the packets gets back to CLI:
19:01:57.302381 IP 10.0.0.2.6002 > 10.0.0.1.5002: UDP, length 0
19:01:57.302399 IP 10.0.0.2.6002 > 192.168.1.2.5002: UDP, length 0

# SRV2 sends a packet to CLI on Y - this is NOT forwared to CLI
19:01:57.833242 IP 10.0.0.3.6002 > 10.0.0.1.5002: UDP, length 0

# CLI sends a packet to SRV2 from Y. This packet is forwarded with port Z=1024
19:01:58.364815 IP 192.168.1.2.5002 > 10.0.0.3.6002: UDP, length 0
19:01:58.364834 IP 10.0.0.1.1024 > 10.0.0.3.6002: UDP, length 0

# SRV2 tries again to send a packet to CLI on Y - this is still not
forwarded to CLI:
19:01:58.897158 IP 10.0.0.3.6002 > 10.0.0.1.5002: UDP, length 0

And here is the script:

#!/bin/bash

# Root bridge - hosts on this have 10.0.0.0/24 addresses
ip netns add nsroot
ip netns exec nsroot ip link add bridge type bridge
ip netns exec nsroot ip link set dev bridge up

# NAT host - has address on 10.0.0.1 on the root bridge
ip netns add nsnath
ip link add nstmp1 type veth peer name nstmp2
ip link set nstmp1 netns nsnath name veth0
ip link set nstmp2 netns nsroot name veth0
ip netns exec nsroot ip link set dev veth0 up
ip netns exec nsroot ip link set veth0 master bridge
ip netns exec nsnath ip addr add 10.0.0.1/24 dev veth0
ip netns exec nsnath ip link set dev lo up
ip netns exec nsnath ip link set dev veth0 up

# NAT bridge  - hosts on this havd 192.168.1.0/24 addresses
ip netns add nsnatb
ip netns exec nsnatb ip link add bridge type bridge
ip netns exec nsnatb ip link set dev bridge up

# Connect NAT host to NAT bridge with address 192.168.1.1
ip link add nstmp1 type veth peer name nstmp2
ip link set nstmp1 netns nsnath name veth1
ip link set nstmp2 netns nsnatb name veth0
ip netns exec nsnatb ip link set dev veth0 up
ip netns exec nsnatb ip link set veth0 master bridge
ip netns exec nsnath ip addr add 192.168.1.1/24 dev veth1
ip netns exec nsnath ip link set dev lo up
ip netns exec nsnath ip link set dev veth1 up
ip netns exec nsnath route add default gw 192.168.1.1 dev veth1

# Set of the NAT host to forward
ip netns exec nsnath modprobe iptable_nat
ip netns exec nsnath iptables -t nat -A POSTROUTING -o veth0 -j SNAT
--to-source 10.0.0.1
ip netns exec nsnath echo 1 > /proc/sys/net/ipv4/ip_forward

# CLI host - this is behind the nat with address 192.168.1.2
ip netns add nscli
ip link add nstmp1 type veth peer name nstmp2
ip link set nstmp1 netns nscli name veth0
ip link set nstmp2 netns nsnatb name veth1
ip netns exec nsnatb ip link set dev veth1 up
ip netns exec nsnatb ip link set veth1 master bridge
ip netns exec nscli ip addr add 192.168.1.2/24 dev veth0
ip netns exec nscli ip link set dev lo up
ip netns exec nscli ip link set dev veth0 up
ip netns exec nscli route add default gw 192.168.1.1 dev veth0

# SRV1 and SRV2 - this is just one host with the two addresses
10.0.0.2 and 10.0.0.3
ip netns add nssrv
ip link add nstmp1 type veth peer name nstmp2
ip link set nstmp1 netns nssrv name veth0
ip link set nstmp2 netns nsroot name veth1
ip netns exec nsroot ip link set dev veth1 up
ip netns exec nsroot ip link set veth1 master bridge
ip netns exec nssrv ip addr add 10.0.0.2/24 dev veth0
ip netns exec nssrv ip addr add 10.0.0.3/24 dev veth0
ip netns exec nssrv ip link set dev lo up
ip netns exec nssrv ip link set dev veth0 up

# Start tcpdump
ip netns exec nsnath tcpdump -n -l -i veth0 udp > external 2>&1 &
ip netns exec nsnath tcpdump -n -l -i veth1 udp > internal 2>&1 &

# Run commands to send UDP packets
sleep 1
ip netns exec nscli sendip -p ipv4 -p udp -us 5001 -ud 6001 -is
192.168.1.2 10.0.0.2
sleep 0.5
ip netns exec nssrv sendip -p ipv4 -p udp -us 6001 -ud 5001 -is
10.0.0.2 10.0.0.1
sleep 0.5
ip netns exec nscli sendip -p ipv4 -p udp -us 5001 -ud 6001 -is
192.168.1.2 10.0.0.3
sleep 0.5
ip netns exec nssrv sendip -p ipv4 -p udp -us 6001 -ud 5001 -is
10.0.0.3 10.0.0.1
sleep 1
ip netns exec nscli sendip -p ipv4 -p udp -us 5002 -ud 6002 -is
192.168.1.2 10.0.0.2
sleep 0.5
ip netns exec nssrv sendip -p ipv4 -p udp -us 6002 -ud 5002 -is
10.0.0.2 10.0.0.1
sleep 0.5
ip netns exec nssrv sendip -p ipv4 -p udp -us 6002 -ud 5002 -is
10.0.0.3 10.0.0.1
sleep 0.5
ip netns exec nscli sendip -p ipv4 -p udp -us 5002 -ud 6002 -is
192.168.1.2 10.0.0.3
sleep 0.5
ip netns exec nssrv sendip -p ipv4 -p udp -us 6002 -ud 5002 -is
10.0.0.3 10.0.0.1
sleep 1

# Kill everything again
killall tcpdump
ip netns delete nssrv
ip netns delete nscli
ip netns delete nsnatb
ip netns delete nsnath
ip netns delete nsroot
--
To unsubscribe from this list: send the line "unsubscribe netfilter" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Netfilter Development]     [Linux Kernel Networking Development]     [Netem]     [Berkeley Packet Filter]     [Linux Kernel Development]     [Advanced Routing & Traffice Control]     [Bugtraq]

  Powered by Linux