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

feat: add MagicEndpoint to iroh-net #1133

Merged
merged 29 commits into from
Jul 3, 2023

Conversation

Frando
Copy link
Member

@Frando Frando commented Jun 26, 2023

This is a first pass at adding a MagicEndpoint that combines a magicsock and a quinn endpoint. The API is minimalistic so far and allows to both accept and create connections. I also added two utility functions to extract the ALPN and PeerId on incoming connections, and a maximally-simple example to test things.

An endpoint-like struct that can create and accept connections will be needed for peer-to-peer sockets as e.g. in the upcoming gossipswarm impl. (It could also work with functions only but would involve more code copying I think).

I mostly copied code around from the existing listen and dial functions. No tests or other code is yet converted over to actually use this. Sharing now to get feedback on the API.

Copy link
Contributor

@flub flub left a comment

Choose a reason for hiding this comment

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

I've also been wondering about simplifying the use of the magicsocket. But I was initially thinking of adding a bit of functionality to Conn (or maybe a new MagicEndpoint, this is a pretty good name) that would have a method to give you a new quinn::Endpoint which is configured to use this magicsocket.

That way you'd have to call .add_know_addrs() and .get_mapping_addr() before you can call .connect() on a quinn::Endpoint making the relation between both sockets endpoints a bit more explicit. But it would not put us in the game of having to wrap all quinn::Endpoint APIs.

host_name: "derp.iroh.network".into(),
stun_only: false,
stun_port: 3478,
ipv4: UseIpv4::Some("35.175.99.113".parse().unwrap()),
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW I tend to slightly prefer one of the non-.parse().unwrap() variants, e.g. (35.175.99.133).into() or similar (writing this from memory after a holiday - so that's probably wrong!).

More interestingly though: are we fine with hardcoding an IP address here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ack to the first comment. Wrt to the second, I don't know ;-) Copied it from iroh/config.rs.

Copy link
Contributor

Choose a reason for hiding this comment

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

More interestingly though: are we fine with hardcoding an IP address here?

It is not great, but okay for the moment I would say

iroh-net/src/endpoint.rs Outdated Show resolved Hide resolved
client_config
};

debug!("connecting to {}: (via {} - {:?})", peer_id, addr, addrs);
Copy link
Contributor

Choose a reason for hiding this comment

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

i wonder if the addrs is really correct here? it could have even more addresses than we have here in the addrs variable i think?

Copy link
Member Author

Choose a reason for hiding this comment

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

Very much possible, I copied this from iroh-net/src/client.rs:132 and did not check it at all

iroh-net/src/endpoint.rs Outdated Show resolved Hide resolved
@Frando
Copy link
Member Author

Frando commented Jun 26, 2023

I've also been wondering about simplifying the use of the magicsocket. But I was initially thinking of adding a bit of functionality to Conn (or maybe a new MagicEndpoint, this is a pretty good name) that would have a method to give you a new quinn::Endpoint which is configured to use this magicsocket.

Aha, yeah that would work as well. Somehow, the notion of connect(peer_id) is quite appealing to me though.

That way you'd have to call .add_know_addrs() and .get_mapping_addr() before you can call .connect() on a quinn::Endpoint making the relation between both sockets endpoints a bit more explicit. But it would not put us in the game of having to wrap all quinn::Endpoint APIs.

That is true. However, the API surface of quinn::Endpoint is really small when omitting the constructors which are taken care of in either case. Method-wise, it's just:

  • accept, connect: already implemented here
  • local_addr is mostly useless unless coupled with get_mapping_addr I think? So the wrapping would make sense here IMO
  • set_server_config and rebind: not sure if exposing those in the magicsock setup is needed / makes sense
  • close and wait_idle: those should be exposed

flub
flub previously approved these changes Jun 26, 2023
iroh-net/src/endpoint.rs Outdated Show resolved Hide resolved
@Frando
Copy link
Member Author

Frando commented Jun 27, 2023

Thanks for the reviews! I added a MagicEndpointBuilder and also more docs.

@dignifiedquire
Copy link
Contributor

@Frando would be great if you could do the integration/usage of it in iroh itself, so we can see how and if it works there as needed for the current use case.

pub async fn bind(self, bind_addr: SocketAddr) -> anyhow::Result<MagicEndpoint> {
let keypair = self.keypair.unwrap_or_else(|| Keypair::generate());
let tls_server_config =
tls::make_server_config(&keypair, self.alpn_protocols, self.keylog)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

does this error if alpn_protocols is empty?

@Frando
Copy link
Member Author

Frando commented Jun 28, 2023

@Frando would be great if you could do the integration/usage of it in iroh itself, so we can see how and if it works there as needed for the current use case.

Allright, I took a stab at this, the latest commit uses MagicEndpoint throughout iroh.

@dignifiedquire
Copy link
Contributor

Allright, I took a stab at this, the latest commit uses MagicEndpoint throughout iroh.

Very nice and clean :) really like having this abstraction.

@dignifiedquire
Copy link
Contributor

@Frando the last integration I would love to see is replacing the structure in hp/magicsock/conn.rs tests

@Frando
Copy link
Member Author

Frando commented Jun 28, 2023

@Frando the last integration I would love to see is replacing the structure in hp/magicsock/conn.rs tests

I just had a look at this. The tests access more internals of the magic socket than the current API exposes. Most seems to me for validation of those internals.

  • they access the tracked_endpoints() of a magic socket to check if some peer id is in there.
  • they also update the network map completely whereas the currently-exposed API on the MagicEndpoint only allows to add_known_addr(peer_id, socket_addrs)

I haven't worked with the internals of the magic socket much yet so not sure if those are needed outside of testing. So, should I ..

  1. expose tracked_endpoints and set_network_map on the MagicEndpoint or
  2. allow access to the magic socket Conn on the MagicEndpint through pub (or pub(crate) methods magicsocket() and magicsocket_mut(), and keeps tests mostly the same, or
  3. restructure some of the tests to use less internals and work more with PeerIds only (likely possible, but not sure if the current setup is intended)?

@Frando
Copy link
Member Author

Frando commented Jun 28, 2023

And CI passed on the last commit, including CLI tests! Only netsim failures, but seems unrelated (github bucket something something)

@dignifiedquire
Copy link
Contributor

dignifiedquire commented Jun 28, 2023

I think a first pass just exposing things that are needed using cfg(test) is fine. I can take a look in a second round if it makes sense to restructure the tests.

dignifiedquire
dignifiedquire previously approved these changes Jun 29, 2023
Copy link
Contributor

@dignifiedquire dignifiedquire left a comment

Choose a reason for hiding this comment

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

some smaller nits, other than that this is looking good :)

@Frando
Copy link
Member Author

Frando commented Jun 29, 2023

I addressed the last comments and rebased onto main. Let's see what CI says.

One thing I'm not super sure about is the netmap managment. But I think this can be refined later.

@@ -2649,7 +2658,7 @@ mod tests {
nodes: vec![DerpNode {
name: "t1".into(),
region_id: 1,
host_name: "http://test-node.invalid".parse().unwrap(),
host_name: "https://test-node.invalid".parse().unwrap(),
Copy link
Contributor

Choose a reason for hiding this comment

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

does this need to be https?

Copy link
Contributor

Choose a reason for hiding this comment

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

probably does, just wondering why this change was necessary now

Copy link
Member Author

Choose a reason for hiding this comment

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

good question. I can't tell for sure, but this way the tests passed.

pub mod hp;
pub mod magic_endpoint;
Copy link
Contributor

Choose a reason for hiding this comment

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

can we make this private, if we reexport the MagicEndpoint?

Copy link
Member Author

Choose a reason for hiding this comment

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

There's also the MagicEndpointBuilder struct and accept_conn functions in there (and possible get_alpn and get_peer_id, see below). Not sure if they should all be at the top level?
I think I'd defer this to later, currently pretty much everything in iroh-net is public anyway - and once things stabilize a bit we might want to change this overall and actually define the public API surface for iroh-net?

}

/// Extract the [`PeerId`] from the peer's TLS certificate.
pub async fn get_peer_id(connection: &quinn::Connection) -> anyhow::Result<PeerId> {
Copy link
Contributor

Choose a reason for hiding this comment

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

does this need to be public?

Copy link
Member Author

Choose a reason for hiding this comment

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

So they are used in the accept_conn above, which definitely should be public. I left them public, because get_alpn already works after the first handshake phase (on quinn::Connecting) whereas get_peer_id only works on the established quinn::Connection. So I wasn't sure if maybe you'd want to do the alpn extraction earlier, potentially abort if it's not what you expected, and then to get_peer_id afterwards. However I don't think it makes much of a difference, so also fine to make them non-public for now and advise to use accept_conn.

Copy link
Contributor

Choose a reason for hiding this comment

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

lets leave it as is for now then

flub
flub previously approved these changes Jun 30, 2023
Copy link
Contributor

@flub flub left a comment

Choose a reason for hiding this comment

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

Nice! Most of my comments are about the public docs from a user point of view. But nothing is blocking.

iroh-net/src/magic_endpoint.rs Show resolved Hide resolved
iroh-net/src/magic_endpoint.rs Show resolved Hide resolved
iroh-net/src/magic_endpoint.rs Show resolved Hide resolved
iroh-net/src/magic_endpoint.rs Outdated Show resolved Hide resolved
/// Connect to a remote endpoint, creating an endpoint on the fly.
///
/// The PeerId and the ALPN protocol are required. If you happen to know dialable addresses of
/// the remote endpoint, they can be specified and will be added to the endpoint's peer map.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to describe what the "endpoint's peer map" means?

Copy link
Member Author

@Frando Frando Jul 3, 2023

Choose a reason for hiding this comment

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

I removed the term for now and took over the description you proposed in connect

.conn
.get_mapping_addr(&node_key)
.await
.expect("just inserted");
Copy link
Contributor

Choose a reason for hiding this comment

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

There is an .await involved... so who knows?

Copy link
Member Author

Choose a reason for hiding this comment

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

True! Made it an error instead. Will have to check in practice if this occurs, but not panicing sounds like the right thing to do.

iroh-net/src/magic_endpoint.rs Outdated Show resolved Hide resolved
iroh-net/src/magic_endpoint.rs Outdated Show resolved Hide resolved
}

/// Accept an incoming connection and extract the client-provided [`PeerId`] and ALPN protocol.
pub async fn accept_conn(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd be tempted to experiment with an extension trait or a wrapper for Connecting (and I guess Accept). But we can do that later.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup I thought about that too. However we'd either have to use async_trait or write the futures by hand. I'd defer this to post-merge I think.

@@ -2826,7 +2820,7 @@ mod tests {
})
}

fn setup_logging() {
pub fn setup_logging() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I kind of wish you copied in iroh-bytes/src/test_utils.rs to iroh-net/src/test_utils.rs and used it's setup_logging call for your functions instead of making this pub... But I tried to clean this up earlier and utterly failed failed. For new #[tokio::test] tests it should just work though, since they're single-threaded runtimes. Just don't try change existing tests.

But if you don't want to do that now that's fine, it's my fault for not managing to clean this up before.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes this sounds good to me too. I also want to expose run_derp_and_stun in a test_util module also to other crates (i.e. iroh-gossip) that use iroh-net and need to test things with a local derper.
However I'd prefer to do this after the merge in a new PR.

@dignifiedquire dignifiedquire merged commit 4597cb3 into n0-computer:main Jul 3, 2023
14 of 15 checks passed
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