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 QUIC Address Discovery to iroh #3049

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Conversation

ramfox
Copy link
Contributor

@ramfox ramfox commented Dec 14, 2024

Description

There were two main things to "solve" on the iroh side:

  1. gross circular dependency between magicsock and the quinn endpoint
  2. needing to handle QAD packets in a special way, like we do with STUN packets that get sent from net-report

The first was accomplished by figuring out how to make MagicSock::spawn be the location that builds the quinn::Endpoint. This is what was changed:

  • implemented AsyncUdpSocket on MagicSock itself, rather than magicsock::Handle
  • magicsock::Handle now contains the quinn::Endpoint
  • we now pass down a server_config as a MagicSock::Option
  • in MagicSock::spawn:
    • after building the MagicSock we now build the quinn::Endpoint using Arc<MagicSock> as the AsyncUdpSocket
    • then we clone the quinn::Endpoint and passed it to the magicsock::Actor (which is independent of MagicSock)
    • give the quinn::Endpoint to the magicsock::Handle, which is the actual struct that we use to interact with the magicsocket
  • the iroh::Endpoint now interacts with the quinn::Endpoint using msock.endpoint() in all places

The second was accomplished by keeping a list of special "QAD addresses" on the NodeMap (NodeMap::qad_addrs), and using specific methods to add_qad_addrs, get_qad_addrs_for_send and get_qad_addrs_for_recv to deal with the fickle way that the quinn::Endpoint expects SocketAddrs to behave when it dials and when it receives packets:

  • before we do a net-report, we first attempt to resolve the RelayUrls in the RelayMap to get the SocketAddrs that we expect we will dial when we do a QAD probe
  • ~we add those addresses to the NodeMap ~
  • on the "send" side of the AsyncUdpSocket, after we do our normal checks for QuicMappedAddrs, we then check to see if the QuicMappedAddr is actually just a normal socket addr that we expect to use for QAD. If so, we just send the packets using the get_qad_addr_for_send address
  • on the "recv" side of the AsyncUdpSocket, after we check to see if the recv'd address can be mapped to a QuicMappedAddr & therefore can be received using our normal process, we then check to see if the address is one that we expect for QAD. If so, we make sure to associate the packet with the get_qad_addr_for_recv address and pass it along

The most "unreliable" bits of this are due to dns. Before running a net report, we now have to do dns discovery for all the RelayUrls. I've capped this at 300 ms but it means that until we cache the dns responses then we have this delay before starting net-report. I've also done a bit of a cheat: when we initially start the magicsock::Actor, I've added a call to resolve_qad_addrs that has a timeout of 10 ms, just to send out the dns packets and hopefully get a jump on caching the responses.

The second was accomplished by creating a new IpMappedAddrs struct that keeps track of IpMappedAddrs to the actual SocketAddr that it represents. This is akin to how we deal with QuicMappedAddr (which is now called NodeIdMappedAddr. netreport now takes an optional IpMappedAddrs, and when it resolves a RelayUrl, it adds the resolved IP addr to the IpMappedAddrs, and uses the mapped address when sending on QUIC. In iroh, we check to see if the destination or source address is an IpMappedAddr or a NodeIdMappedAddr. If so, it maps the addresses correctly before returning the transmit to the endpoint or sending the transmit over UDP.

Most interesting bit

So...if someone adds an IP address to IpMappedAddrs, we can allow folks to use the Endpoint as a sort of normal quinn::Endpoint, bypassing our special iroh holepunching sauce.

depends on #3032

Change checklist

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All breaking changes documented.

@ramfox ramfox changed the base branch from main to ramfox/net-report-simplify December 14, 2024 07:27
Copy link

github-actions bot commented Dec 14, 2024

Documentation for this PR has been generated and is available at: https://n0-computer.github.io/iroh/pr/3049/docs/iroh/

Last updated: 2025-01-17T21:00:32Z

@ramfox ramfox force-pushed the ramfox/qad-in-iroh branch 3 times, most recently from 2a51de3 to d18f3d1 Compare December 14, 2024 07:47
@ramfox ramfox self-assigned this Dec 14, 2024
@flub
Copy link
Contributor

flub commented Dec 16, 2024

The second was accomplished by keeping a list of special "QAD addresses" on the NodeMap

The downside of this approach is that you need to lock the NodeMap once again on every single try_send and poll_recv call. That's not great.

What if we made a separate mapping outside of the NodeMap for this? We can make a new type IpMappedAddr (and rename QuicMappedAddr to NodeIdMappedAddr while we're at it). It would be a mirror of the current QuicMappedAddr but with a different ADDR_SUBNET.

So we use a new subnet in our existing IPv6 Unique Local Addr space. This subnet works just like the existing subnet otherwise. But is used to map real IP addresses to other fake ones.

This allows the code that needs to check whether something is a IpMappedAddr or a QuicMappedAddr (or NodeIdMappedAddr) to simply check the IPv6 prefix with no need to get any locks until it is know which one you have.

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.

The way of connecting the quinn::Endpoint into the MagicSock is nice!

iroh/src/endpoint.rs Outdated Show resolved Hide resolved
iroh/src/magicsock.rs Show resolved Hide resolved
iroh/src/magicsock/node_map.rs Outdated Show resolved Hide resolved
iroh/src/magicsock.rs Outdated Show resolved Hide resolved
iroh/src/magicsock.rs Outdated Show resolved Hide resolved
@ramfox ramfox force-pushed the ramfox/net-report-simplify branch from 1e24436 to 4f9db39 Compare December 16, 2024 16:48
@ramfox ramfox force-pushed the ramfox/qad-in-iroh branch 2 times, most recently from 59b2744 to ee67d96 Compare January 7, 2025 22:41
@ramfox ramfox changed the base branch from ramfox/net-report-simplify to main January 8, 2025 16:40
@ramfox ramfox force-pushed the ramfox/qad-in-iroh branch 3 times, most recently from 8b69c62 to 96aa2a5 Compare January 10, 2025 21:08
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.

So my biggest question is about why IpMappedAddr maps to a SocketAddr rather than an Ipv6Addr. But then I also have that question about NodeIdMappedAddr which I don't think should be solved in this PR. And since the IpMappedAddr works as is, I'm happy to defer that as well if you'd prefer.

Otherwise this looks really good, though I guess we agreed that the IpMappedAddr would move into net-report for now.

iroh-base/src/ip_mapped_addrs.rs Outdated Show resolved Hide resolved
return Err(IpMappedAddrError(String::from("not mapped addr")));
}
let octets = addr.ip().octets();
if octets[6..8] != IpMappedAddr::ADDR_SUBNET {
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to check that the entire prefix matches, [0..8], no? Just the subnet is not enough.

With some luck you can compute the full prefix in a const fn, but I'm not sure if that's possible. You can always just write a full const PREFIX: [u8; 8] = [0xfd, 21, 7, 10, 81, 11, 0, 1]; next to the existing constants.

Copy link
Contributor Author

@ramfox ramfox Jan 17, 2025

Choose a reason for hiding this comment

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

I adjusted this. I just checked each section of the slice, similar to how we fill the slice when we generate the addrs. Can you please look at this again and let me know if it's acceptable?

iroh-base/src/ip_mapped_addrs.rs Outdated Show resolved Hide resolved
iroh-base/src/ip_mapped_addrs.rs Outdated Show resolved Hide resolved
iroh-base/src/ip_mapped_addrs.rs Outdated Show resolved Hide resolved
iroh-base/src/ip_mapped_addrs.rs Outdated Show resolved Hide resolved
iroh/src/magicsock.rs Outdated Show resolved Hide resolved
iroh/src/magicsock.rs Outdated Show resolved Hide resolved
SocketAddr::V6(addr) => {
if addr.port() != MAPPED_ADDR_PORT {
anyhow::bail!("not a mapped addr");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I have the same comments here as on the IpMappedAddr. But I do appreciate that this already had a dummy port in it. So let's not change that. I'll see if I can remove it reasonably in a followup PR and so I'm happy to leave this as-is for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

made the change already and here was my logic for why it was okay to do:

We never in the code checked the port before, that was something I added with the try_from, so I'm comfortable taking it "away". Now we have a method NodeIdMappedAddr::socket_addr that returns the addr w/ the MAPPED_ADDR_PORT for when we need to use the socket address.

iroh/src/magicsock.rs Outdated Show resolved Hide resolved
@ramfox ramfox force-pushed the ramfox/qad-in-iroh branch from 39b2cca to 7177bf0 Compare January 15, 2025 21:03
“ramfox” added 11 commits January 17, 2025 15:46
`MagicSock::spawn` creates a `quinn::Endpoint`. `MagicSock` is now an `AsyncUdpSocket`, and can be passed into the `quinn::Endpoint`. `magicsock::Handle` now owns the `quinn::Endpoint`, and `iroh::Endpoint` interacts with the `quinn::Endpoint` through `Handle::endpoint()`.

This allows us to pass the `quinn::Endpoint` to the `magicsock::Actor` for use in QAD, without any circular dependencies.
Also, `NodeIdMappedAddr` and `IpMappedAddr` `try_from` is implemented for `Ipv6Addr`
refactors `NodeIdMappedAddr` to contain a `Ipv6Addr` rather than a `SocketAddr`
@ramfox ramfox force-pushed the ramfox/qad-in-iroh branch from 2b99483 to 2c94b82 Compare January 17, 2025 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🏗 In progress
Development

Successfully merging this pull request may close these issues.

2 participants