Skip to content
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

identify detects if address translation is required #4

Conversation

stormshield-ebzh
Copy link

Description

The goal of this MR is to show how identify could detect if a connection was dialed with port_use or not and forward the information to the swarm.
The swarm will trigger a transport.address_translation() only when required (when dialed without port reuse)

Now address_translation() is only called when a translation is needed , so in the tcp transport we do not have to manage whether a multiaddr was dialed with or without port reuse

Related libp2p#4568

@umgefahren
Copy link
Owner

Thanks for the work 🚀 I will review it more closely tomorrow. It would be great if @thomaseizinger could sign off on this as well, since these changes to identify are probably beyond the scope of my grant and my expertise. @jxs

swarm/src/lib.rs Outdated
@@ -1132,39 +1134,69 @@ where

self.pending_handler_event = Some((peer_id, handler, event));
}
ToSwarm::NewExternalAddrCandidate(addr) => {
ToSwarm::NewExternalAddrCandidate {
endpoint,
Copy link

@thomaseizinger thomaseizinger May 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of this API change. It feels like we are passing a lot of information back and forth here. If we don't actually need any state from the TCP transport to do the translation, perhaps this is the time to just remove Transport::address_translation entirely?

AFAIK, all transports just delegate to the generic address_translation function anyway! Plus, all behaviours get told about all listen addresses. So, can we instead:

  • Remember all listen addresses in the identify behaviour
  • Call the generic address_translation function from there (we might even be able to move it to the identify behaviour which would have the added benefit of it being removed from the public API of libp2p-core!)
  • In the identify behaviour, we know which addresses need translation, so we can call it only for those
  • All other addresses we can just emit as is

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your advise Thomas !
I implemented the address translation without using Transport::address_translation
(I did not removed yet Transport::address_translation but it is not used anymore)

  • listen addresses are already stored in identify
  • translation is done when both conditions are meet:
    • a multiaddr of a transport requiring translation (use is_addr_tcp and is_quic_addr taken from the tcp/quic transport implementations)
    • connection was dialed without port reuse
  • call the address_translation function
  • If translation gave an address emit the ToSwarm::NewExternalAddrCandidate with the translated address, otherwise emit the ToSwarm::NewExternalAddrCandidate with the observed address,

If this approach is ok, i will :

  • remove Transport::address_translation function
  • remove the is_addr_tcp and is_quic_addr for the transports
  • keep the generic address_translation in lib_p2p_core because used in mdns (but not for nat purpose)

Copy link

@thomaseizinger thomaseizinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent work @stormshield-ebzh ! Thank you for this!

Much simpler than I expected too :)

It will need some testing but I think it makes sense to do this together with the already existing Transport API changes. Furthermore, if address_translation is now only used in mDNS and identify, we should move it to libp2p-swarm (one less thing to worry about in libp2p-core, yey! cc @jxs).

It would also be great to have some tests around this. Now that this feature is in libp2p-identify, can we write a test for that there? Some manual testing wouldn't hurt too.

protocols/identify/src/behaviour.rs Outdated Show resolved Hide resolved
Comment on lines +44 to +69
/// Whether an [`Multiaddr`] is a valid for the QUIC transport.
fn is_quic_addr(addr: &Multiaddr, v1: bool) -> bool {
use Protocol::*;
let mut iter = addr.iter();
let Some(first) = iter.next() else {
return false;
};
let Some(second) = iter.next() else {
return false;
};
let Some(third) = iter.next() else {
return false;
};
let fourth = iter.next();
let fifth = iter.next();

matches!(first, Ip4(_) | Ip6(_) | Dns(_) | Dns4(_) | Dns6(_))
&& matches!(second, Udp(_))
&& if v1 {
matches!(third, QuicV1)
} else {
matches!(third, Quic)
}
&& matches!(fourth, Some(P2p(_)) | None)
&& fifth.is_none()
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this came up another time already. Why do we need to perform address translation for QUIC addresses? QUIC never uses ephemeral ports (to my knowledge).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autonat server should use ephemeral port when dialing back a peer.
otherwise the server dial-back will always succeed (and will report a false positive to the peer) because the peer has already opened the states in the firewalls when dialing the server.
=> translation for QUIC addresses is required

@umgefahren : When dialing back, the server should use a dedicated quic endpoint (not localy binded) and should not reuse a listner endpoint (this is now not the case)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autonat server should use ephemeral port when dialing back a peer. otherwise the server dial-back will always succeed (and will report a false positive to the peer) because the peer has already opened the states in the firewalls when dialing the server. => translation for QUIC addresses is required

@umgefahren : When dialing back, the server should use a dedicated quic endpoint (not localy binded) and should not reuse a listner endpoint (this is now not the case)

You are right. Thank you for laying it out for me. We need to do address translation because we (libp2p) decide to explicitly use an ephemeral port for a new QUIC connection to avoid accidental hole-punching despite QUIC itself not needing it.

I think this is worthwhile documenting here because anybody coming across this will think "hey, QUIC always uses stable ports so why would be bother with address translation for QUIC". (Also cc @mxinden, I know you too have been confused by this in the past)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autonat server should use ephemeral port when dialing back a peer. otherwise the server dial-back will always succeed (and will report a false positive to the peer) because the peer has already opened the states in the firewalls when dialing the server. => translation for QUIC addresses is required

@umgefahren : When dialing back, the server should use a dedicated quic endpoint (not localy binded) and should not reuse a listner endpoint (this is now not the case)

I will change it so that when Endpoint is Dialer and PortUse::Reuse we dial a dedicated quic endpoint.

@umgefahren
Copy link
Owner

I will merge this now

@umgefahren umgefahren merged commit 7bea1c2 into umgefahren:transport-redesign Jun 1, 2024
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants