-
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
Changes from 3 commits
4039cf5
3e0b6c7
dd7cc53
fa0e2e6
22c403d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,16 +7,13 @@ import ( | |
"strconv" | ||
|
||
ma "github.com/multiformats/go-multiaddr" | ||
mafmt "github.com/multiformats/go-multiaddr-fmt" | ||
manet "github.com/multiformats/go-multiaddr/net" | ||
"github.com/multiformats/go-multibase" | ||
"github.com/multiformats/go-multihash" | ||
) | ||
|
||
var webtransportMA = ma.StringCast("/quic-v1/webtransport") | ||
|
||
var webtransportMatcher = mafmt.And(mafmt.IP, mafmt.Base(ma.P_UDP), mafmt.Base(ma.P_QUIC_V1), mafmt.Base(ma.P_WEBTRANSPORT)) | ||
|
||
func toWebtransportMultiaddr(na net.Addr) (ma.Multiaddr, error) { | ||
addr, err := manet.FromNetAddr(na) | ||
if err != nil { | ||
|
@@ -78,3 +75,59 @@ func addrComponentForCert(hash []byte) (ma.Multiaddr, error) { | |
} | ||
return ma.NewComponent(ma.ProtocolWithCode(ma.P_CERTHASH).Name, certStr) | ||
} | ||
|
||
// IsWebtransportMultiaddrWithCerthash returns true if the given multiaddr is a | ||
// well formed webtransport multiaddr. Returns the number of certhashes found. | ||
func IsWebtransportMultiaddr(multiaddr ma.Multiaddr) (bool, int) { | ||
const ( | ||
init = iota | ||
foundUDP | ||
foundQuicV1 | ||
foundWebTransport | ||
) | ||
state := init | ||
certhashCount := 0 | ||
|
||
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 | ||
} | ||
Comment on lines
+91
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Address matcher doesn't do exactly what I want which is:
I know about address matcher, it just didn't do what I needed. See that hack that I removed in |
||
if c.Protocol().Code == ma.P_CERTHASH && state == foundWebTransport { | ||
certhashCount++ | ||
} | ||
return true | ||
}) | ||
return state == foundWebTransport, certhashCount | ||
} | ||
|
||
// IsWebtransportMultiaddrWithCerthash returns true if the given multiaddr is a | ||
// well formed webtransport multiaddr with a certificate hash. | ||
func IsWebtransportMultiaddrWithCerthash(multiaddr ma.Multiaddr) bool { | ||
ok, n := IsWebtransportMultiaddr(multiaddr) | ||
return ok && n > 0 | ||
} | ||
|
||
// CopyCerthashes copies the certificate hashes from the first multiaddr to the | ||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. Would it make more sense to just have a There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
certhashComponents := make([]ma.Component, 0, 2) // 2 is the common case | ||
ma.ForEach(existingMultiaddrWithCerthashes, func(c ma.Component) bool { | ||
if c.Protocol().Code == ma.P_CERTHASH { | ||
certhashComponents = append(certhashComponents, c) | ||
} | ||
return true | ||
}) | ||
out := multiaddrWithoutCerthashes | ||
for _, certComponent := range certhashComponents { | ||
out = out.Encapsulate(&certComponent) | ||
} | ||
return out | ||
} |
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.
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.
go-libp2p/p2p/transport/webtransport/cert_manager.go
Line 63 in 8c466ab
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.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.
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.
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.
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).
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 🤷.
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:
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 👍.
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.
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:
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
go-libp2p/p2p/transport/webtransport/transport.go
Line 316 in 8c466ab
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☺️