Open ge9 opened 8 months ago
IIUIC, what you requested is actually ADM(Address-Dependent Mapping) + ADF(Address-Dependent Filtering) with maximum external source port reuse if possible.
Then we will have following bi-directional NAT binding for packet translation and filtering. This can be easily modeled with O(1) hashmap in eBPF.
(internal source address, internal source port, destination address)
<=> (external source address, external source port, destination address)
And we need the following transformed mapping for querying if a certain external source port has been assigned for destination addresses to determine if that port can be reused.
(external source address, external source port)
=> { (destination address), ... } "set of all related destination addresses"
OR
(external source address, destination address)
=> { (external source port), ... } "set of all related external source ports"
This can be modeled with O(1) hashmap with nested value of O(1) hashmap/hashset or O(n) list in normal program. However it's not possible to have nested hashmap in eBPF, so we would need to model it in hashmap with value linked list. And that would be a O(n) operation which would be inefficient, so I am not considering implement that yet until we have nested hashmap support in eBPF.
This is expecially useful in ISP services which provide IP address shared with other tens of users and only hundreds of ports per user (such ISPs do exist in my country, Japan).
A proper ISP CGNAT should have a IP address pool though, i.e. your source port would be mapped to another external IP address if all ports of previous mapped external IP have been assigned for other users.
UDP hole punching will succeed between netfilter and netfilter, but not between netfilter and Symmetric NAT.
If you are individual user, I would recommend just use port forwarding solutions like miniupnp ,#5 or plain old nftable/iptables DNAT rule if you have control over your hole punching software.
Also the external port exhaust can be addressed by adding more external IP address, i.e. IP address pool. It's a planned feature.
Closing as not planned.
First, thank you very much for quick and detailed answer. I appreciate your deep consideration of the possibility of what I requested. However, I have one question. You suggested that we should have hashmap from a external port to the set of destination addresses, but don't we only have to look up the destination address in this hashmap?
(internal source address, internal source port, destination address)
<=> (external source address, external source port, destination address)
If we find some [internal source port] related to the [external source port] and [destination address], the port can't be used and we will search for another external source port. Also, this is not so important and you may already understand this, but this NAT not necessarily conform to RFC 4787's definition of ADM (though this NAT has some kind of address dependent mapping) because it can assign the same external port for two connections from the same internal port to two different destinations.
A proper ISP CGNAT should have a IP address pool though, i.e. your source port would be mapped to another external IP address if all ports of previous mapped external IP have been assigned for other users.
Some of ISPs I mentioned actually provides an explicit single global IP address (shared with other users) and a designated set of 240 ports (so each user should have NAT device) . I believe there is no such a thing like "ip address pool" for this kind of ISPs.
If you are individual user, I would recommend just use port forwarding solutions like miniupnp ,https://github.com/EHfive/bpf-full-cone-nat/issues/5 or plain old nftable/iptables DNAT rule if you have control over your hole punching software.
Yes, but (if I understand correctly) quasi-EIM/ADF (restricted cone) NAT is better in that it provide good UDP hole punching ability for all devices connected to the linux router.
If we find some [internal source port] related to the [external source port] and [destination address], the port can't be used and we will search for another external source port.
Oh, you are right. I have over-complicated the port assignment problem. What left is actually just filling the "external source port" part of this binding, and it's finitely bounded to 65535 tries.
Binding Table
(internal source address, internal source port, destination address)
<=> (external source address, <external source port>, destination address)
However a lookup of existing external port assignment is required for reusing. Thus we can introduce the mapping described below, it's also finitely bounded to 65535 iterations.
Port Assignments Table
(internal source address, internal source port, external source address)
=> { (external source port), .. }
"all assigned external source port, maybe sorted by last assignment time"
We can either iterate all assigned ports or just choose the first external port in port assignments entry and then fallback to random port assignment, it would be bounded to [1, 65535] + 65535 tries.
Note that port assignments entry can not be sorted by usage due to limited eBPF data structure support, I would not elaborate details here. But this could potentially break EIM-ish characteristic and does not match Netfilter port assignments behavior so I doubt this policy can work in eBPF, especially in congested network with multiple hosts sharing the same external port.
it can assign the same external port for two connections from the same internal port to two different destinations
Sure, but from my understanding it's still ADM as what you called "quasi-EIM" is totally valid by binding definition of ADM. The mapping is more about providing information uniquely define the packet translation for both direction. And obviously you can't employ filtering behavior more relax than ADF(i.e. EIF).
# All these bindings are unique, this is EIM-ish
(192.168.1.2, 12345, 203.0.113.1)
<=> (x.x.x.x, 54321, 203.0.113.1)
(192.168.1.2, 12345, 203.0.113.2)
<=> (x.x.x.x, 54321, 203.0.113.2)
# If 203.0.113.3 is on same remote host of 203.0.113.1 or 203.0.113.2,
# this NAT is no longer EIM-ish
(192.168.1.9, 12345, 203.0.113.3)
<=> (x.x.x.x, 54321, 203.0.113.3)
Reopen as the efficiency issue I thought was false, though this would be at very low priority.
Also this can actually be extended for APDM + APDF by adding "destination port" to binding definition.
Yes, finding an available port is the problem.
Port Assignments Table
(internal source address, internal source port, external source address)
=> { (external source port), .. }
"all assigned external source port, maybe sorted by last assignment time"
This is also an option, but I think only the last-assigned port will be sufficient for my "quasi-EIM" behavior (or did you already mean this by "just choose the first external port in port assignments entry and then fallback to random port assignment" ?). netfilter seems to try to use the last assigned port, but when the port is not available, it apparently doesn't try to use the second last assigned port.
I expect finding port with random number will succeed after a few tries in realistic cases (e.g. a family of 10 people shares 200 ports), but the less ports and the more users (hosts), the less EIM-ish this NAT will become.
Sure, but from my understanding it's still ADM as what you called "quasi-EIM" is totally valid by binding definition of ADM. The mapping is more about providing information uniquely define the packet translation for both direction. And obviously you can't employ filtering behavior more relax than ADF(i.e. EIF).
Yes, it's conceptually ADM and basically behaves like EIM, a kind of "non-deterministic NAT" that RFC 4787 deprecates...
Thank you for reopening this issue. I'll wait for it.
Hi, Sorry if this feature request is arrogant (at least it's outside the scope of "full cone NAT"), but I'm very interested in this topic. As you may know, netfilter's NAT behaves as follows:
Port overloading is useful because it can save the number of external ports consumed; When we have only 100 external port, it can manage far more than 100 connections at once (100 port per endpoint). This is expecially useful in ISP services which provide IP address shared with other tens of users and only hundreds of ports per user (such ISPs do exist in my country, Japan). However, since it is basically EIM/APDF (Port restricted cone NAT), UDP hole punching will succeed between netfilter and netfilter, but not between netfilter and Symmetric NAT.
Therefore, we should use mapping not per [address, port] but per only address. Such a NAT can be Address Dependent Filtering, because we can find the mapping for traffic from unknown port of known address. Of course, with this NAT policy, we can do "port overloading" only if the address of the endpoint is different (192.168.1.1:40000 <-> example.com:80 and 192.168.1.2:40000 <-> example.com:8080 won't be able to share port). However, this won't actually be disadvantage because we rarely want to access different ports of the same address. We usually use only 53 or 80 or 443 of the specific address and will be content with port overloading only for different address.
Since this NAT is basically EIM/ADF, UDP hole punching between this NAT and symmetric NAT (as long as the address is preserved).
I don't have knowledge on eBPF and don't know how difficult this would be, but hope this fascinating feature.