Skip to content

Using QUIC/HTTP3 to replace utp-native for the Data Transfer Layer in the networking domain #234

Closed
1 of 1 issue completed

Description

@CMCDragonkai

Specification

We can separate the networking to 3 layers:

  • P2P application layer - e.g. kademlia, automerge, nodes domain
  • RPC layer - e.g. grpc (and once considered jsonrpc but it's too late now)
  • Data Transfer layer - UTP, Wireguard, QUIC

The Data Transfer Layer is particularly special since it is the lowest part of our stack and is always fundamentally built on top of UDP. These are its characteristics:

  • Must be built on top of UDP (due to hole-punching requirements)
  • Capable of NAT-traversal via hole punch packets
  • Capable of being proxied for traversing symmetric NAT
  • End to end encrypted - based on MTLS or otherwise
  • Low latency and high throughput (and ideally low power for maintaining the connection liveness)
  • Compatible with Linux, Mac, Windows, iOS, Android

Currently we use the TLS on top of UTP protocol as the Data Transfer Layer. This is implemented with a forward and reverse proxy termination points bridging TCP to the UTP protocol.

The proxy termination bridges was necessary due to us using GRPC as the RPC layer and its implementation fixing on HTTP2 stack, but with an escape hatch via a connect proxy. This is more of an implementation detail than anything else. But it does give us a fairly generic system that can turn any TCP-protocol into something NAT-traversable.

The usage of MTLS also enables a seamless usage of the same X.509 certificate that we already use for the rest of PK.

The underlying library being used here is utp-native https://github.com/mafintosh/utp-native, it is C++ module that incorporates the utp C++ library https://github.com/bittorrent/libutp and wraps it into a NodeJS module. It works fine for Linux, Window, and Mac. However there is no clear path for usage on Android or iOS. See mafintosh/utp-native#30. This could be done by using https://github.com/janeasystems/nodejs-mobile (but this is not as popular), or by compiling libutp natively for iOS and Android, and then wrapping it out as native code on NativeScript/React Native.

The utp-native is also OLD. It has some issues like:

All of this means that continuing down with trying to use utp-native might just mean flogging a dead horse.

An alternative already existed, and we had previously used it inside Matrix OS and that's Wireguard. The reason for not using when we first started is that there are no nodejs libraries available for it when we started, and we need something quick to prototype with. Many existing P2P applications have been built on top of UTP protocol especially in the NodeJS ecosystem, so that's basically where we started. Even then we went on a journey trying to use a raw JS UTP library that didn't work before eventually arriving on utp-native and still having to adapt it in our network domain.

Trying to use WG will be a lot of work however, and there are many things we have to consider if it is going to work.

Wireguard it's own issues. It is of course will be a C/C++ codebase as well. Originally it was made for Linux only. Now it is available inside and outside the Linux kernel. However for an application like PK, wireguard would have to be a userspace library. The great things all of this is now available: https://github.com/cloudflare/boringtun. With boringtun, it is claimed that it works all major desktops and android/ios and it's all userspace. It's a rust library exposing a C interface that can be wrapped as a native module in JS (just like how we use utp-native and leveldown). It is however NEW and so may a bunch of bugs: https://github.com/cloudflare/boringtun/issues

An additional issue is that Wireguard doesn't use X.509 certificates. It would completely replace the MTLS portion of the codebase, this is fine as we can always derive subkeys from the rootkey for WG utilisation. We would need to however understand how to deal with the certificate verification given that we use a cert chain when rotating root keys. There is no chain in Wireguard, so any key rotation here would end up breaking any connections, unless one were to connect and then verify at a higher level.

As for hole punching, it's possible that it does this automatically, but we would need to investigate its interface for hole punching to see how we would implement our hole punch relay and proxy relay mechanisms #182.

One advantage of using Wireguard is that we are already using Wireguard inside MatrixOS, and we can share expertise and knowledge/tooling between MatrixOS and Polykey. Only note that WG in MatrixOS is the in-kernel one, and not a userspace one. And our work in hole punch and proxy relay could then be shared to MatrixOS which can benefit from it as well.

Another alternative is QUIC. This is now available natively in NodeJS:

Because QUIC is so low level. It seems like a drop-in replacement for the combination of TLS + UTP. One advantage is that this drops the utp-native dependency requirement. However this doesn't solve how one might use QUIC on Android/iOS which is the main reason we want to make a switch. If we are going to do a whole heap of work to make use of UTP on Android/iOS we might as well spend that work upgrading to a more well-supported system.

One huge advantage of QUIC is that we can maintain the usage of TLS that we already use to secure GRPC client TLS #229, and doesn't involve a different protocol. It seems TLS isn't going anywhere, and wireguard is unlikely to ever be used in general web contexts which relies on the certificate authority system. There is also a risk that wireguard packets maybe blocked on corporate firewalls unlike QUIC which is going to look like HTTP3 packets.

This is likely to impact the browser integration where a browser extension is acting as a client. Already we have problems with using GRPC in our RPC layer so that the browser extension can use the same client protocol as we do with our CLI and GUI, so adding in wireguard is not going to help in the case of CLI/GUI and browser extension communication unless this gets resolve: cloudflare/boringtun#139. So it does seem choosing wireguard would bifurcate our data transfer layer between agent to agent and agent to client which is also not nice.

Integration and migration

With the quic system functional we can begin the migration to using quic.

There are two parts to this, the server side and client side. Client side is made up of the nodes domain with the NodeConenction encapsulating the QUICClient and RPCClient. This should be a reasonable drop-in for the existing systems.The server side is made up of a single QUICServer and a RPCServer with the server manifest.

Additional context

At any case we are going to probably need to drop down to native to make sure that we can support all platforms.

Tasks

  • 1. nodes domain needs changes
    • 1. NodeConnection needs to be gutted and replaced with RPCClient and QUICClient usage. Besides this, usage of the NodeConnection is mostly the same.
    • 2. Tests need to be updated
  • 2. Verification logic needs to be transplanted for use with quic.
    • 1. This needs to be tested.
  • 3. Ensure that the proper connection information is provided by the streams from the quic system.
    • 1. This needs to be tested.
  • 4. PolykeyAgent needs to be updated
    • 1. GRPC agent server needs to be replaced with a RPCServer and QUICServer combo
    • 2. Proxy needs to be removed.
  • 5. Agent domain needs to be migrated to using the agnostic RPC code.
    • 1. Tests need to be migrated
  • 6. Old code needs to be removed
    • 1. network domain gutted
    • 2. GRPC domain gutted
    • 3. Remove protobuf? and other package dependencies that are not used anymore.
  • 7. tests!
    • 1. network domain tests need to be removed, any tests still needed should be transplanted.
    • 2. grpc domain tests need to be removed, any tests still needed should be transplanted.
  • [ ] 8. Update relevant handlers with pagination
  • [ ] 9. Update agent handlers to be timed cancellable, implement cancellation.

Activity

  1. CMCDragonkai commented on Oct 20, 2021

    @CMCDragonkai
    MemberAuthor

    Another issue is the forward proxy being used to bridge the gRPC required TCP port over HTTP. This would be nice to not be needed, but seems to require the grpc http2 usage to be worked out.

  2. CMCDragonkai commented on Dec 24, 2021

    @CMCDragonkai
    MemberAuthor

    Relevant issue: nodejs/node#38233 (comment)

    The way we are using UTP in our networking domain means we will need to send datagrams for hole punching, but still need a stream API to act as a tunnel/proxy.

    The networking domain is pretty useful as it can be used for other P2P applications in the future and existing HTTP connections can work there.

  3. CMCDragonkai commented on Jan 5, 2022

    @CMCDragonkai
    MemberAuthor

    Hyperswarm mentions "distributed hole punching", worth looking into how this is done and whether there are lessons we can apply here: https://hypercore-protocol.org/protocol/#hyperswarm.

    Note that they are using noise, while we are using TLS, and if quic becomes available then we stick with TLS.

  4. changed the title [-]Attempt using Wireguard or QUIC to replace utp-native for the Data Transfer Layer in the networking domain[/-] [+]Attempt using QUIC to replace utp-native for the Data Transfer Layer in the networking domain[/+] on May 11, 2022
  5. changed the title [-]Attempt using QUIC to replace utp-native for the Data Transfer Layer in the networking domain[/-] [+]Using QUIC/HTTP3 to replace utp-native for the Data Transfer Layer in the networking domain[/+] on May 11, 2022
  6. CMCDragonkai commented on May 11, 2022

    @CMCDragonkai
    MemberAuthor

    I've changed this title to talk about QUIC/HTTP3. I don't think we're going down the line of wireguard at all.

    It makes sense that most platforms (mobile, desktop, nodejs, web) will eventually support HTTP3/QUIC when it becomes a "web standard".

    This means that all platforms will basically have a UDP-native transport.

    Currently this is not true though. So this is a long-term standardisation process will eventually reduce the complexity of our networking stack.

    One critical requirement is the ability to send fire-and-forget UDP "messages" as a punch packet for NAT-busting. If the HTTP3/QUIC implementation does not provide this ability, it's not really useful, as we need this to do NAT-busting.

    Here's a list of HTTP3/QUIC implementations to keep track of:

  7. CMCDragonkai commented on May 11, 2022

    @CMCDragonkai
    MemberAuthor

    Critical features of the data transfer layer:

    1. The ability to do a fire-and-forget punch-message for the purposes of busting through NAT.
    2. The ability to multiplex separate connections into 1 socket, and therefore that means 1 port. This is necessary for NAT busting. A connection is between node to node. This means the same port/socket is used for multiple connections to different nodes.
    3. The ability to multiplex separate queries and streams on the same connection. This is not the same as multiplexing connections. This allows asynchronous communication on a connection, meaning queries and streams are not blocking each other.
    4. Must be a common web standard that is likely to be implemented by all platforms and therefore not require a C-native library to implement.

    Currently our network stack achieves 3/4 except 4. The utp-native gives us messages that implement 1., utp-native can multiple independent utp connections into 1 socket, HTTP2 enables 3..

    The go version of QUIC has implemented this quic-go/quic-go#1464

  8. CMCDragonkai commented on May 19, 2022

    @CMCDragonkai
    MemberAuthor

    Implementation of QUIC in C which could be embedded as a native addon: https://github.com/microsoft/msquic

  9. CMCDragonkai commented on Nov 9, 2022

    @CMCDragonkai
    MemberAuthor

    The ability to send fire and forget messages in QUIC is specified in RFC 9221 https://www.rfc-editor.org/rfc/rfc9221.html.

    image

    This is a more recent RFC (finalized in April 2022?), so it is possible that not all QUIC implementations support it.

    I believe this is necessary to use QUIC for NAT hole punching. So we must examine the QUIC implementations for this support.

  10. 28 remaining items

  11. added a commit that references this issue on Jun 5, 2023
  12. CMCDragonkai commented on Jun 26, 2023

    @CMCDragonkai
    MemberAuthor

    What is the status of this task list? I see that this will be closed from #525.

    Note that I have closed MatrixAI/js-quic#1 because all subtasks were done and a prototype prerelease was provided which is being integrated in #525. However new refactorings is taking place in MatrixAI/js-quic#26

  13. tegefaulkes commented on Jun 26, 2023

    @tegefaulkes
    Contributor

    #525 has the most up to date task list. Most of the conversion has been done except for a few points.

    1. Blocked on custom TLS verification and secured event changes in js-quic
    2. Reverse connections need to be handled and tracked in the NodeConnectionManager. Also blocked by 1.
    3. Monitors can be applied to the object map locking in the NodeConnectionManager it may simplify some logic.
    4. Tests need to be fixed and updates and general cleaning up done.
  14. CMCDragonkai commented on Jun 26, 2023

    @CMCDragonkai
    MemberAuthor

    Reverse connections need to be handled and tracked in the NodeConnectionManager. Also blocked by

    Blocked by what?

  15. tegefaulkes commented on Jun 26, 2023

    @tegefaulkes
    Contributor

    Sorry, mistype, blocked by the custom TLS verification changes in point 1.

  16. CMCDragonkai commented on Jul 10, 2023

    @CMCDragonkai
    MemberAuthor

    Remember to tick off the tasks when done too.

  17. tegefaulkes commented on Jul 11, 2023

    @tegefaulkes
    Contributor

    The task list is being updated in the agent migration PR. #525

    I'll mirror the progress here.

  18. CMCDragonkai commented on Jul 23, 2023

    @CMCDragonkai
    MemberAuthor

    @tegefaulkes can you update the task list here too:

    image

  19. CMCDragonkai commented on Jul 23, 2023

    @CMCDragonkai
    MemberAuthor

    Oh I see you've ticked them already but 8/9 is not done, that's for stage 2.

  20. CMCDragonkai commented on Aug 1, 2023

    @CMCDragonkai
    MemberAuthor

    Great! Just need to integrate pagination, time cancellability into all handlers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

designRequires designdevelopmentStandard developmentepicBig issue with multiple subissuesr&d:polykey:core activity 4End to End Networking behind Consumer NAT DevicesresearchRequires research

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions