-
Notifications
You must be signed in to change notification settings - Fork 961
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
Transport trait #1789
Transport trait #1789
Conversation
Can you indicate what is the purpose of this PR? |
The purpose of this PR to allow port reuse for outgoing tcp connections and support for quic. As you know quic doesn't fit naturally with the current transport traits, and the current quic implementation only allows for one quic listener and dials on the same port as that listener ignoring the multiaddr passed to In addition to that the translate address logic is not correct for quic or tcp with port reuse. When traversing a nat the nat may assign a new port, since we are using the same port for incoming and outgoing connections the reason why the observed address is different is because of the nat. The reason for using async-io is because the macros are a nuissance when debugging (can't insert a panic or unwrap because it will give me the line where the macro was declared) and obfuscate the code. Some more background are in #1722, #1563 and #1787, and the closed pr #1667 |
I guess the way to review this would be to start with commit e8dbdec which shows how it all fits together. The first two commits are also fairly small they make the changes needed for the feature. The rest of the commits are updating the transports to the new api and fixing the tests. I guess most of the added code are from tests and a little more boilerplatte in the transports required for extracting the dialer into a separate struct. |
@tomaka I assume you don't like the new transport trait much? It's about letting you dial on the same port as the listening port. The dialer returned by the transport will dial on an arbitrary port. If you try to adapt the quic branch to this new api I think you'll find it's a more natural fit. If you have an alternative suggestion, I'm open to suggestions... |
The following are some high-level comments after reading through the related issues and skimming the PR. I do think that eventually it would be nice to permit port reuse for both listening and dialing, e.g. via
Regarding Regarding
I think I don't fully understand the motivation behind changing the It seems to me that a new implementation of the relay protocol as you mentioned in step 2 of #1722, and relay servers in general as already mentioned in #1722 (comment), may be the best starting point for better and interoperable NAT traversal with libp2p.
I'm personally also not fond of the macro-based abstraction for
I'm still trying to understand this point. My current understanding is the following: The current implementation of |
I'm not sure it's that easy. The main reason for the current approach is that it works, it was a little tricky to get it to work. One problem is that there can be multiple listeners for the same transport. So the transport would need to know it's listening ports. Some examples: let config = TcpConfig::new().port_reuse(true);
let stream1 = config.listen_on(ip4);
let stream2 = config.listen_on(ip6);
let fallback = TcpConfig::new();
let transport = config.or_transport(fallback);
transport.dial(ip4); // dial on ip4
transport.dial(same ip4); // dial on fallback let config = TcpConfig::new().port_reuse(true);
let stream1 = config.listen_on(ip4);
let fallback = TcpConfig::new();
let transport = config.or_transport(fallback);
transport.dial(ip6); // dial on fallback
We need to distinguish between two kinds of nat's. Endpoint independent and endpoint dependent. In an endpoint independent nat we have a map from
Sounds reasonable, but I guess some more discussion is needed to determine if that is the way forward? With a little bit more code async-std-resolver can be removed and async-io + trust-dns-resolver can be used directly. |
Thanks for pointing that out, I did in fact not consider NATs that do endpoint-independent filtering of inbound traffic. However, based on all the sources I've seen, including the one you linked to, such NATs are very rare. In contrast, endpoint-independent mapping combined with endpoint-dependent filtering seems to be a very common combination that can be successfully traversed with hole-punching techniques, e.g. "parallel TCP hole punching" (if the TCP simultaneous open were resolved first for libp2p). Symmetric NATs, i.e. that also do endpoint-dependent mapping, are obviously the most annoying and usually need a full relay to overcome. That is not to say that it is not useful to make it easy to traverse NATs that conveniently do both endpoint-independent mapping & filtering by just advertising the observed mapped port to all peers, as I now understand you suggest to do here when port-reuse is "enabled", it just doesn't seem to get us very far in terms of NAT traversal overall.
I didn't mean to imply that it is necessarily easy, but am still hoping that there is a better solution that consolidates the required changes on a smaller scale. There is on one hand the configuration of port-reuse on a concrete transport like TCP (where I still think it shouldn't be the default), and with that the decision of the transport and / or user code which ports to use for listening and dialing actions. On the other hand there is the "address translation" of observed addresses under consideration of the listener addresses and ports as well as transport configuration and possibly other transport state. So rather than just calling the current simple IP |
well it's a first step. endpoint dependent filtering can be overcome by simultaneously opening connections from both parties, however that requires an external coordination server. the address translation performed by the default
If I understand correctly you're suggesting something like the following for both tcp and quic: #[derive(Clone)]
struct TcpTransport {
config: Arc<TcpConfig>,
listeners: Arc<Mutex<Vec<Multiaddr>>>,
}
let transport = TcpConfig::new().to_transport(); The reason this approach was dismissed by me was because this would be the only place a So I suggest the following path forward:
Discovering a relay server, registering with the relay server, detecting if the nat is eip and hole punching or relaying connections using the relay is left as issues for follow-up work. NOTE: the reason for tcp port reuse is because it is a requirement for tcp hole punching to work |
Sounds good to me.
I guess, but since the pub struct TcpConfig {
listen_addrs: Arc<RwLock<Vec<Multiaddr>>>,
... existing config options
} as I would hope
I understand that uncontended locks can be a code smell, but the APIs are such that you can use (cloned) transports given to a
Sounds good to me, though I would suggest that each of
👍 |
There are still a couple of todos, but would like to clear some questions in the meantime:
TODO
part one of #1722, closes #1563, closes #1787