-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(quic): fix address translation #4896
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ use quic::Provider; | |
use rand::RngCore; | ||
use std::future::Future; | ||
use std::io; | ||
use std::net::Ipv4Addr; | ||
use std::num::NonZeroU8; | ||
use std::task::Poll; | ||
use std::time::Duration; | ||
|
@@ -236,6 +237,57 @@ fn new_tcp_quic_transport() -> (PeerId, Boxed<(PeerId, StreamMuxerBox)>) { | |
(peer_id, transport) | ||
} | ||
|
||
#[cfg(feature = "async-std")] | ||
#[test] | ||
fn test_address_translation_async_std() { | ||
test_address_translation::<quic::async_std::Provider>() | ||
} | ||
|
||
#[cfg(feature = "tokio")] | ||
#[test] | ||
fn test_address_translation_tokio() { | ||
test_address_translation::<quic::tokio::Provider>() | ||
} | ||
|
||
fn test_address_translation<T>() | ||
where | ||
T: Provider, | ||
{ | ||
let (_peer_id, transport) = create_default_transport::<T>(); | ||
|
||
let port = 42; | ||
let quic_listen_addr = Multiaddr::empty() | ||
.with(Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))) | ||
.with(Protocol::Udp(port)) | ||
.with(Protocol::QuicV1); | ||
Comment on lines
+259
to
+262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps a helper function that takes in an |
||
let observed_ip = Ipv4Addr::new(123, 45, 67, 8); | ||
let quic_observed_addr = Multiaddr::empty() | ||
.with(Protocol::Ip4(observed_ip)) | ||
.with(Protocol::Udp(1)) | ||
.with(Protocol::QuicV1) | ||
.with(Protocol::P2p(PeerId::random())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this one important? |
||
|
||
let translated = transport | ||
.address_translation(&quic_listen_addr, &quic_observed_addr) | ||
.unwrap(); | ||
let mut iter = translated.iter(); | ||
assert_eq!(iter.next(), Some(Protocol::Ip4(observed_ip))); | ||
assert_eq!(iter.next(), Some(Protocol::Udp(port))); | ||
assert_eq!(iter.next(), Some(Protocol::QuicV1)); | ||
assert_eq!(iter.next(), None); | ||
Comment on lines
+273
to
+277
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be cleaner to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is translated 1:1 from TCP test, I can update it, but then TCP should probably be updated too (and maybe others?). |
||
|
||
let tcp_addr = Multiaddr::empty() | ||
.with(Protocol::Ip4(Ipv4Addr::new(87, 65, 43, 21))) | ||
.with(Protocol::Tcp(1)); | ||
|
||
assert!(transport | ||
.address_translation(&quic_listen_addr, &tcp_addr) | ||
.is_none()); | ||
assert!(transport | ||
.address_translation(&tcp_addr, &quic_observed_addr) | ||
.is_none()); | ||
} | ||
|
||
#[cfg(feature = "async-std")] | ||
#[async_std::test] | ||
async fn tcp_and_quic() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what issue this is addressing.
The
libp2p_core::address_translation
function takes the port oflisten
and the IP ofobserved
. This is relevant in the case of TCP without port-reuse where one does not listen on the port of an outgoing connection, i.e. here were one does not listen of the port ofobserved
.The relevant section of the
address_translation
doc comment:rust-libp2p/core/src/translation.rs
Lines 29 to 32 in 338e467
I argue that this is not relevant for QUIC, given that QUIC will use the same UDP port for both outgoing and incoming connections. The port of
observed
is either equal to the port oflisten
, or, if the local node is behind a NAT, equal to the public NAT port mapping oflisten
. In the former caseaddress_translation
is a no-op. In the latter caseaddress_translation
returns the wrong port, namely LAN port, not the public Internet port.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that with the upcoming redesign of #4568 we can get rid of
Transport::address_translation
. Whether a connection has been established using port-reuse or not will be visible to aNetworkBehaviour
. Thus e.g.libp2p-identify
receiving an observed address can judge itself, whether it needs to alter any ports.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also see #2289 (comment) for past discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This addresses the problem of ephemeral ports with QUIC. At least when behind NAT with port forwarding, without address translation you'll end up with invalid external port. The more annoying thing is that it will be valid for the duration of Autonat probing, but when later anyone else tries to reach the node, the port is no longer functional.
This is most certainly not true in practice, at least in some cases. Even on my machine with pfSense acting as a router, I have seen random ports all over the place observed by remote nodes.
Yes
No, in this case address translation will result in correct port. I guess it depends on how to do port forwarding, it should be possible for router to map ports both ways, but I can't imagine many users actually doing it, it seems that external ports are mapped onto LAN ports, but probably rarely outgoing LAN ports are mapped onto specific WAN ports. This is my educated guess based on observation, I'm not claiming some consumer routers are not doing that.
Interesting. Well, then this will need to be adjusted when that happens, but in the meantime network needs to function and lack of address translation was a big issue for us since many nodes were not able to discover their public addresses with QUIC (that we use almost exclusively in place of TCP now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means the current implementation is still wrong though as it will happily return an observed TCP address as the translated address. If QUIC is "before" TCP in the transport stack, it would return a wrong address, wouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See lines 252-256 above, the protocol is checked there and returns
None
if it doesn't match. Same is done in TCP and it works properly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah you are right, I missed that because the diff didn't show it 🙄
I don't really understand why we need to change anything here then. QUIC doesn't have a concept of ephemeral ports so
address_translation
can't do anything useful. If you still observe changing ports with QUIC, it might be that you are sitting behind a symmetric NAT. We can't do anything against that I am afraid.We are aware of this issue. Unfortunately, it is a design flaw in AutoNAT and one of the things that prompted the design of AutoNATv2. In v2, the server uses a different outgoing port to perform the dial-back, meaning we don't accidentally "hole-punch" through the NAT and get a more trustworthy result that the address is actually reachable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I still might have port forwarding configured such that the port matching my listening port is still reachable from the outside. This is what we recommend our users to do when they are behind NAT and it seems to work. It doesn't cover the case when forwarded port doesn't match listening port, in that case user has to specify external address explicitly, but it is still useful to do address translation, as mentioned above, in worst case it is no-op.