-
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
transports/tcp: Fix port reuse using Arc<RwLock> for listen_addrs #2670
transports/tcp: Fix port reuse using Arc<RwLock> for listen_addrs #2670
Conversation
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.
Thank you @Pj50 for providing the patch as well as augmenting the test.
With this change, I find the cloning behavior of GenTcpConfig
non-intuitive, given that the clone would share the listen_addrs
field when PortReuse::Enabled
.
What do you think of removing the derived Clone
on GenTcpConfig
?
diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs
index 6e6eb771..0badcc24 100644
--- a/transports/tcp/src/lib.rs
+++ b/transports/tcp/src/lib.rs
@@ -66,7 +66,7 @@ use std::{
use provider::{IfEvent, Provider};
/// The configuration for a TCP/IP transport capability for libp2p.
-#[derive(Clone, Debug)]
+#[derive(Debug)]
pub struct GenTcpConfig<T> {
/// The type of the I/O provider.
_impl: std::marker::PhantomData<T>,
@@ -247,7 +247,7 @@ where
/// let listen_addr2: Multiaddr = "/ip4/127.0.0.1/tcp/9002".parse().unwrap();
///
/// let mut tcp1 = TcpConfig::new().port_reuse(true);
- /// let mut listener1 = tcp1.clone().listen_on(listen_addr1.clone()).expect("listener");
+ /// let mut listener1 = tcp1.listen_on(listen_addr1.clone()).expect("listener");
/// match listener1.next().await.expect("event")? {
/// ListenerEvent::NewAddress(listen_addr) => {
/// println!("Listening on {:?}", listen_addr);
@@ -258,7 +258,7 @@ where
/// }
///
/// let mut tcp2 = TcpConfig::new().port_reuse(true);
- /// let mut listener2 = tcp2.clone().listen_on(listen_addr2).expect("listener");
+ /// let mut listener2 = tcp2.listen_on(listen_addr2).expect("listener");
/// match listener2.next().await.expect("event")? {
/// ListenerEvent::NewAddress(listen_addr) => {
/// println!("Listening on {:?}", listen_addr);
listen_addrs.insert((ip, port)); | ||
listen_addrs | ||
.write() | ||
.expect("`register()` and `unregister()` never panic while holding the lock") |
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 will update these in a follow up pull request.
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.
If you remove Clone on GenTcpConfig what about GenDnsConfig<GenTcpConfig, ...> which also implements Clone ?
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 would advocate for removing Clone
from GenDnsConfig
as well, though I would prefer that to happen in a separate pull request.
Can GenDnsConfig
be Clone
even if it is wrapping GenTcpConfig
which is not Clone
?
Yes, given that GenDnsConfig
wraps the GenTcpConfig
in an Arc<Mutex<_>>
.
Hope I am not missing something @Pj50.
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 tried removing Clone from GenDnsConfig, however
Line 206 in 4aa84bf
let ws_dns_tcp = websocket::WsConfig::new(dns_tcp.clone()); |
does not compile any more. I don't know how to fix it properly, so I leave it to you, and you're right I think it would be much better to fix it in another pull request.
transports/tcp/src/lib.rs
Outdated
// Obtain a future socket through dialing | ||
// FIXME: Check that the port used by socket is the same as the port returned by local_dial_addr |
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.
How about checking the ListenerEvent::Upgrade { remote_addr, ..}
in async fn listener
above instead?
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.
OK I updated the code using an mpsc channel to pass the reused port between the dialer and the listener, and cheking in the listener upgrade event that the remote port is the same as the one received in the channel.
You asked whether this can be included in the git history. I am not sure what legal implications this might have. For the sake of simplicity I would not include it. Is that fine by you? That is not to say, that I don't appreciate the sponsorship! Thanks for the help. |
3b0c773
to
fb6e3be
Compare
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.
Thanks for the addition to the tests!
Followed up on the Clone
discussion. What do you think?
transports/tcp/src/lib.rs
Outdated
@@ -943,8 +985,9 @@ mod tests { | |||
#[cfg(feature = "async-io")] | |||
{ | |||
let (ready_tx, ready_rx) = mpsc::channel(1); | |||
let listener = listener::<async_io::Tcp>(addr.clone(), ready_tx); | |||
let dialer = dialer::<async_io::Tcp>(addr.clone(), ready_rx); | |||
let (port_reuse_tx, port_reuse_rx) = mpsc::channel(1); |
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.
How about using a oneshot here?
https://docs.rs/futures/latest/futures/channel/oneshot/fn.channel.html
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.
OK. Updated the commit with a oneshot.
transports/tcp/src/lib.rs
Outdated
// Receive the dialer tcp port reuse | ||
let remote_port_reuse = port_reuse_rx.next().await.unwrap(); | ||
// And check it is the same as the remote port used for upgrade | ||
assert_eq!(remote_addr.pop().unwrap(), remote_port_reuse); |
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.
🙏
listen_addrs.insert((ip, port)); | ||
listen_addrs | ||
.write() | ||
.expect("`register()` and `unregister()` never panic while holding the lock") |
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 would advocate for removing Clone
from GenDnsConfig
as well, though I would prefer that to happen in a separate pull request.
Can GenDnsConfig
be Clone
even if it is wrapping GenTcpConfig
which is not Clone
?
Yes, given that GenDnsConfig
wraps the GenTcpConfig
in an Arc<Mutex<_>>
.
Hope I am not missing something @Pj50.
fb6e3be
to
6864348
Compare
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.
Thanks @Pj50 for the help here, going all the way from debugging to proposing the fix 🙏
Sponsored by: Stormshield
Description
See reported issue #2651 for details.
Change checklist
two tests in transports/tcp (port_reuse_dialing and port_reuse_listening) have been enhanced for testing fixed regression.
protocols/dcutr/examples test has been run successfully with the fix.
I have performed a self-review of my own code
I have made corresponding changes to the documentation
I have added tests that prove my fix is effective or that my feature works
A changelog entry has been made in the appropriate crates