-
Notifications
You must be signed in to change notification settings - Fork 1.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
webtransport: Add helpers to transform webtransport multiaddrs in AddrsFactory #2227
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.
It's unfortunate that we need this. It's a lot of additional API for something that "should just work". AddrsFactory
really is a terrbile API, and we should work on cleaning that up soon.
ma.ForEach(multiaddr, func(c ma.Component) bool { | ||
if c.Protocol().Code == ma.P_QUIC_V1 && state == init { | ||
state = foundUDP | ||
} | ||
if c.Protocol().Code == ma.P_QUIC_V1 && state == foundUDP { | ||
state = foundQuicV1 | ||
} | ||
if c.Protocol().Code == ma.P_WEBTRANSPORT && state == foundQuicV1 { | ||
state = foundWebTransport | ||
} |
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.
Why not use an address matcher? We shouldn't be rewriting basic multiaddr functions here.
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.
Address matcher doesn't do exactly what I want which is:
- I don't care if it's ipv4/dns4/ipv6/dns6
- I don't care if it has certhashes or not, but if it does tell me how many.
I know about address matcher, it just didn't do what I needed. See that hack that I removed in CanDial
. This is pretty straightforward, but it would be good to expand the address matcher to handle these use cases. Doing that in this PR is out of scope though.
libp2p_test.go
Outdated
AddrsFactory(func(multiaddrs []ma.Multiaddr) []ma.Multiaddr { | ||
customAddrWithCertHash := customAddr | ||
for _, addr := range multiaddrs { | ||
if webtransport.IsWebtransportMultiaddrWithCerthash(addr) { | ||
customAddrWithCertHash = webtransport.CopyCerthashes(addr, customAddrWithCertHash) | ||
break | ||
} | ||
} | ||
return []ma.Multiaddr{customAddrWithCertHash} | ||
}), |
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.
What's the reasoning behind surfacing this requirement to any libp2p applications who require something like webtransport + port forwarding rather than just handling it internally (e.g. applying this automatically in the host constructor in config.go)?
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 don't think we could do the right thing in 100% of cases. For example, what if the server is listening on two webtransport addresses, which one are you forwarding to (the cerhashes need to line up)? This is something the application using go-libp2p has insight, but not go-libp2p itself. Another example, maybe you also want to return both multiaddrs instead of replacing it.
At a high level though, I think this option AddrsFactory
is hard to use, and I'd like a better api that covers 90% of use cases in a simpler way.
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.
the cerhashes need to line up
Aren't the certhashes always exactly the same on every webtransport interface you're listening on, so it doesn't matter what interface's certhashes you choose they should all be the same.
func newCertManager(hostKey ic.PrivKey, clock clock.Clock) (*certManager, error) { |
An alternative approach to copy certhashes here could've been to just recalculate the deterministic certhashes and use them, but those functions are unexported in the webtransport
package (as they probably should be since they're definitely internal implementation details). The way you've implemented this (and me in #2224) happens to be slightly less abstraction breaking.
At a high level though, I think this option AddrsFactory is hard to use, and I'd like a better api that covers 90% of use cases in a simpler way.
Better APIs sound😄. However, I don't want to rely on future refactors to fix existing issues especially if they could take a while to materialize.
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.
Aren't the certhashes always exactly the same on every webtransport interface you're listening on, so it doesn't matter what interface's certhashes you choose they should all be the same.
Good point. But this may not always be true. Maybe this is an argument for trying to do this ourselves?
You could have an address that doesn't have certhashes (I don't think we support this right now, but we want to) because it has a valid TLS cert. In that case you would want to forward something without a certhash. Automatically appending a certhash would be wrong.
#2224 was helpful, I took the tests from it. But I didn't like the change where config.go now has special knowledge about WebTransport addresses. I want to keep AddrsFactory as simple as possible.
However, I don't want to rely on future refactors to fix existing issues especially if they could take a while to materialize.
This change should unblock kubo, no?
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.
Happy to come up with a better change that keeps AddrsFactory simple and makes things better for the user if you have any suggestions!
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.
Good point. But this may not always be true. Maybe this is an argument for trying to do this ourselves?
Yeah, my hope is to ask fewer people to worry about the complexity here assuming it's not much worse in the central place we're dealing with it (e.g. in go-libp2p).
This change should unblock kubo, no?
Yeah, it will. Technically no changes were needed to unblock kubo as none of the changes made here access private fields/functions, these functions do make things easier though 😄. However, needing to add those extra lines of code seems like to surprise people.
If we went the currently proposed route I'd probably try to save other people, at least in the IPFS ecosystem, the hassle and also add it in a boxo helper so other IPFS implementations don't need to worry too much about it (e.g. ipfs/boxo#230). Maybe that'll help enough 🤷.
You could have an address that doesn't have certhashes (I don't think we support this right now, but we want to) because it has a valid TLS cert.
Yeah that's a good point. We could certainly do this and that would complicate things, although IIUC it would only cause a problem in a case where:
- You have an externally provided TLS cert for that address
- The TLS cert lives outside of go-libp2p (where the constructor couldn't access it) and that part of the QUIC connection establishment happens outside of go-libp2p
Maybe this a reasonable thing. I have no idea how common such a QUIC proxy setup is in general or would be for go-libp2p users. I don't know if people would complain too much if they had to pass extra options to deal with externally established connections.
My preference is to try and eat the complexity so the users don't have to. Given the cases not handled by automatic fixing in the constructor seem to not exist quite yet it seems like we can protect go-libp2p users from thinking about this for a little while longer which is 👍.
Happy to come up with a better change that keeps AddrsFactory simple and makes things better for the user if you have any suggestions!
Unfortunately, nothing better than what I've suggested is coming to mind at the moment. I can see the advantages to both views here, although I have my preference 😄. I'll take whatever the maintainers decide and incorporate with it in the downstreams I help maintain though.
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.
- The TLS cert lives outside of go-libp2p (where the constructor couldn't access it) and that part of the QUIC connection establishment happens outside of go-libp2p
No, we could pass a static tls config to the constructor and that would be fine. This isn't like websockets.
Okay. I think eating this complexity/messiness for the user is the nice thing to do. I'll update this PR.
Concretely:
- If the user returns a webtransport multiaddr that doesn't have a certhash, and all our known addrs have a certhash, copy the certhash over.
- If we only have a webtransport multiaddr without a certhash (client passed a static tls config), don't copy.
- If we have both, log a warning and don't copy the certhash.
Does that seem right?
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.
Yeah that seems right to me. I suspect if we reach a scenario where someone is trying to instantiate two or more webtransport libp2p transports (e.g. 1+ with CA certs and 1 with the autogenerated ones) and they're having trouble specifying to the AddrsFactory
how to signal "please give me autogenerated certs" that we can deal with it then.
I couldn't find an issue for enabling being a server with CA certs for webtransport (since they're not currently supported
return nil, errors.New("static TLS config not supported on WebTransport") |
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.
#2230 is the issue.
I've updated the PR. Thanks for your input @aschmahmann
if factory == nil { | ||
return fmt.Errorf("cannot specify a nil address factory") |
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.
Putting this in this function is likely fine for everyone, but why not in config.go? Technically users can also instantiate directly with config.NewNode()
Line 288 in 8c466ab
func (cfg *Config) NewNode() (host.Host, error) { |
Is the idea to give people a way out of this functionality if there ends up being a problem/the user wants a little more control?
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.
The idea is that this is just middleware. It just wraps the function and the rest of the code doesn't care that this changed.
Another reason to not put it in config.go is that function is already a bit unwieldy
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.
Technically users can also instantiate directly with
config.NewNode()
Line 288 in 8c466ab
func (cfg *Config) NewNode() (host.Host, error) { although I couldn't find any examples of anyone who did.
Oh no, please don't do that, @aschmahmann! config
will be removed soon-ish.
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.
Lol no problem will keep not using it 😜. Was just flagging the code path here as exposed/not having receiving this fix. If you just want to leave it alone your call though.
if factory == nil { | ||
return fmt.Errorf("cannot specify a nil address factory") |
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.
Technically users can also instantiate directly with
config.NewNode()
Line 288 in 8c466ab
func (cfg *Config) NewNode() (host.Host, error) { although I couldn't find any examples of anyone who did.
Oh no, please don't do that, @aschmahmann! config
will be removed soon-ish.
// second. If there are no multiaddrs in the first, the second is returned | ||
// unchanged. Otherwise a new multiaddr is returned with certhashes appeneded | ||
// to it | ||
func CopyCerthashes(existingMultiaddrWithCerthashes, multiaddrWithoutCerthashes ma.Multiaddr) ma.Multiaddr { |
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.
Would it make more sense to just have a AddCerthashes(ma.Multiaddr) ma.Multiaddr
that adds current set of certhashes?
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.
It would for the other thing, but not for this. It would need a reference to the
transport which would be annoying. This is a pure function that can be used in
the AddrsFactory with no other context.
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.
LGTM, modulo one suggestion for the comment.
// This will give us the certhash. All certhashes across all listening addresses. | ||
multiaddrWithCerthash = inMultiaddr | ||
} else if ok && n == 0 { | ||
// This is possible if we are listening with a valid static TLS cert. |
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.
// This is possible if we are listening with a valid static TLS cert. | |
// This shouldn't be possible, since we don't support static TLS certs |
I think the AddrsFactory is a bit hard to use. This PR doesn't address that.
This PR adds some helper functions to allow users to do the correct thing inside AddrsFactory. Fixes #2223
Users can add their own webtransport mappings by copying certhashes from an existing multiaddr. For example,
This also allows users to do this only for specifics addresses in case they are listening on multiple addresses.