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

QUIC support #98

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

QUIC support #98

wants to merge 2 commits into from

Conversation

emillynge
Copy link

Since @juzi5201314 indicated that he would close #64 I now bring this PR.
Many thanks to juzi for his PR, looking at your approach and reading the comments in the PR was very helpful!

This PR is aware of #93 and an effort has been made to ensure QuicTransport::accept is cancel safe. But honestly, it is very difficult to determine if that is in fact the case.

This PR is standalone and does not immediately require merging of #95 which is much more experimental.

Just to kickstart the review, I want to address some comments in #64

rust_tls vs native_tls

I may be possible to drop in a native_tls version of quinn-proto::crypto, but that is a lot of work, that I don't think I can put in, for this particular feature. Also, I might have to rewrite certain other parts of quinn as well that seems to, only be available when using the rust_tlsfeature of the quinncrate.

If the compilation time / executable size is a problem, then I would propose making QUIC a non-default feature.

Releasing UDP sockets in tests

I have spent a long time trying to get the tests working without using a pre-determined sleep between shutdown and restart of the server. This is what I came up with:

  • make sure that rathole::run does not exit before the currently running server instance coroutine has joined
  • add new async fn close(a: Self::Acceptor) method to the Transport trait such that graceful termination of the QUIC connections can be awaited
  • await close in Server::run to ensure that socket is released before coroutine joins

I hope that the above solution is acceptable. The only other option is to rebind to a new random UDP port from a drop on the Acceptor.

@rapiz1
Copy link
Owner

rapiz1 commented Jan 13, 2022

examples/tls/testserver.pfx looks like a slipping file?

Copy link
Owner

@rapiz1 rapiz1 left a comment

Choose a reason for hiding this comment

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

Some initial and simple feedback. Also please check the output of CI Lints

Cargo.toml Outdated Show resolved Hide resolved
Cargo.toml Outdated
quinn = { version = "0.8.0", optional = true}
rustls = { version = "0.20", default-features = false, features = ["quic"], optional = true }
rustls-pemfile = { version = "0.2.1", optional = true }
openssl = { version = "0.10.38", optional = true }
Copy link
Owner

Choose a reason for hiding this comment

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

I see the only usage of openssl is openssl::pkcs12::Pkcs12, which is a large library and requires effort to make it compile on multiple platforms. Since we're already using rustls, it will be better to manage to get rid of openssl. There are some pkcs12 crates on crates.io, if we insist pkcs12 for QUIC. But I wonder what method does QUIC natively provide to work with certificates and so on?

Copy link
Author

Choose a reason for hiding this comment

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

I'm absolutely not a fan of pkcs. I just wanted to stick with the existing pattern.

Copy link
Author

Choose a reason for hiding this comment

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

Looking into native-tls I notice that they also use openssl for parsing p12. So I don't see the benefit of using a different crate when the method I'm using is already being compiled in via native-tls

Copy link
Owner

@rapiz1 rapiz1 Jan 13, 2022

Choose a reason for hiding this comment

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

it matters when you compile with feature quic and without tls :)
openssl is really a pain to compile. Dropping it will possibly make the feature combination server,client,quic available on more platforms than server,client,tls.
openssl already causes trouble for cross compiling rathole and I have to degrade cross to work around. Someone requests for a official risc-v release but I can't do it simply because of the cross compiling stuff about openssl.
tls chooses to use openssl only because it supports IP as hostname and that's many people want. I wish to shift all tls dependencies to rustls at certain point.

Copy link
Author

Choose a reason for hiding this comment

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

So, I just tried out the only really promising crate that does not end up relying on openssl: https://github.com/hjiayz/p12

Unfortunately it choked on the p12 file I created myself. As I said, I am absolutely not a fan of PKCS12 - precisely because you never known quite which libraries, are compatible with with PKCS12 formats. Unless of course you choose a huge lib like openssl that has almost everything covered. (See rustls/rustls#150 where they decline to add support n rusttls)
I don't really want to pull in a half baked pkcs12 parser that only works for some users.

I would suggest the following in a separate PR:

  • implement reading certs and keys from PEM files directly, as an alternative to supplying pfx file.
  • create a new "pfx" feature that pulls in openssl for users who want it and enables support for pfx in QUIC transport.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, i think native-tls depends on openssl when compiled on Linux platform. When compiled on Mac or Windows, I think it uses platform specific deps instead.

The way I have setup cargo.toml forces openssl to be compiled regardless of platform.

I would need to figure out how to do this per platform scoping to fix the builds.

I may have to, at least temporarily, restrict usage of pfx in quic to Linux only.

Copy link
Owner

Choose a reason for hiding this comment

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

BTW, I'm open to the change of specifying the certificate in other ways, if pkcs12 is troublesome to work with.

Copy link
Author

@emillynge emillynge Jan 14, 2022

Choose a reason for hiding this comment

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

Pkcs is often very troublesome. Usually the problem stems from jks keystores that pretend to be p12 stores, yet not entirely compatible.
Also a problem the other way around, when a Java app expects a jks but is provided with an openssl p12.

I will implement reading from pem files.

Can we scope quic-pfx to be available on Linux only?

Copy link
Owner

Choose a reason for hiding this comment

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

I will implement reading from pem files.
Can we scope quic-pfx to be available on Linux only?

If we have an alternative way to use the certificate, I think we can drop pfx entirely in this PR, as you previously suggested

Copy link
Author

Choose a reason for hiding this comment

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

native-tls unfortunately will not support PEM files only.
Only certs can be loaded from PEM files, the keys must still come from pfx.

src/transport/quic.rs Outdated Show resolved Hide resolved
src/server.rs Outdated Show resolved Hide resolved
tests/integration_test.rs Outdated Show resolved Hide resolved
@rapiz1
Copy link
Owner

rapiz1 commented Jan 13, 2022

This PR is aware of #93 and an effort has been made to ensure QuicTransport::accept is cancel safe. But honestly, it is very difficult to determine if that is in fact the case.

Seems safe to me 🤔

src/transport/quic.rs Outdated Show resolved Hide resolved
@emillynge
Copy link
Author

examples/tls/testserver.pfx looks like a slipping file?

No, identity.pfx does not provide a certificate that QUIC will accept. AFAIK QUIC requires that the server cert contains a DNS SAN. identity.pfx has a SAN, but it is IP type.

@rapiz1
Copy link
Owner

rapiz1 commented Jan 13, 2022

There are two files, examples/tls/test_server.pfx and examples/tls/testserver.pfx. Only the former has been used in toml files.

@emillynge
Copy link
Author

Ahh, yeah that file. that was not supposed to be committed. I will delete

Copy link
Owner

@rapiz1 rapiz1 left a comment

Choose a reason for hiding this comment

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

Looks great. Some nitpicking

tests/for_udp/quic_transport.toml Outdated Show resolved Hide resolved
src/transport/quic.rs Outdated Show resolved Hide resolved
src/transport/quic.rs Outdated Show resolved Hide resolved
src/transport/noise.rs Outdated Show resolved Hide resolved
src/transport/quic.rs Outdated Show resolved Hide resolved
}

async fn connect(&self, addr: &str) -> Result<Self::Stream> {
let mut endpoint = quinn::Endpoint::client("[::]:0".parse().unwrap())
Copy link
Owner

Choose a reason for hiding this comment

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

I'm surprised that this works for connecting to IPv4 address since it's binding to a V6 address. Some dual stack magic inside?

Copy link
Author

Choose a reason for hiding this comment

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

I thibnk there's quite a bit of magic in quinn. I have not looked.

Copy link
Owner

Choose a reason for hiding this comment

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

In that case, we can see how the test going on different platforms after you have lint fixed.

Copy link
Owner

Choose a reason for hiding this comment

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

So the tests failed.
See below for how to handle the address stuff. You can refactor out a function called get_bind_addr_for_udp or so and call it

rathole/src/helper.rs

Lines 43 to 58 in 1fe3509

/// Create a UDP socket and connect to `addr`
pub async fn udp_connect<A: ToSocketAddrs>(addr: A) -> Result<UdpSocket> {
let addr = lookup_host(addr)
.await?
.next()
.ok_or(anyhow!("Failed to lookup the host"))?;
let bind_addr = match addr {
SocketAddr::V4(_) => "0.0.0.0:0",
SocketAddr::V6(_) => ":::0",
};
let s = UdpSocket::bind(bind_addr).await?;
s.connect(addr).await?;
Ok(s)
}

@emillynge
Copy link
Author

Squashed the branch since rebasing was getting unwieldy with so many small commits and reverts

@emillynge
Copy link
Author

I see tests failing.

Can only test linux locally, and that passes on my desktop...

Dunno why the tests are failing

@rapiz1
Copy link
Owner

rapiz1 commented Jan 18, 2022

Jan 18 02:53:19.113  INFO handle{service=echo}: rathole::client: Starting 092c79e8f80e559e404bcf660c48f3522b67aba9ff1484b0367e1a4ddef7431d
Jan 18 02:53:19.113  INFO handle{service=pingpong}: rathole::client: Starting c78862c4afddaa20fd3ff12e5e270480706499341ca5d1d7437ec9668556805b
Jan 18 02:53:20.031  INFO test{config_path="tests/for_udp/quic_transport.toml" t=Udp}: integration_test: start the server
Jan 18 02:53:20.039  INFO config_watcher{path="tests/for_udp/quic_transport.toml"}: rathole::config_watcher: Start watching the config
Jan 18 02:53:20.040  INFO rathole::server: Listening at 0.0.0.0:2332
Jan 18 02:53:20.182  INFO test{config_path="tests/for_tcp/quic_transport.toml" t=Tcp}: integration_test: start the server
Jan 18 02:53:20.189  INFO config_watcher{path="tests/for_tcp/quic_transport.toml"}: rathole::config_watcher: Start watching the config
Jan 18 02:53:20.194  INFO rathole::server: Listening at 0.0.0.0:2333
Jan 18 02:53:22.059  INFO test{config_path="tests/for_udp/quic_transport.toml" t=Udp}: integration_test: echo
test udp ... FAILED
Jan 18 02:53:22.231  INFO test{config_path="tests/for_tcp/quic_transport.toml" t=Tcp}: integration_test: echo
error: test failed, to rerun pass '--test integration_test'
test tcp ... FAILED
Jan 18 02:53:19.113  INFO handle{service=echo}: rathole::client: Starting 092c79e8f80e559e404bcf660c48f3522b67aba9ff1484b0367e1a4ddef7431d

shows that the client tried to connect to the server, but by the end of the test, it had not succeeded nor returned any feedback. This is possibly caused by a IPv6 bounded socket connecting to a IPv4 address.

I think this reveals two potential issues to address:

  1. QUIC connect can't return an error immediately when the target port is not listened, due to the nature of UDP, which is the cause of no feedback. This can be solved either by
    1. Enforce a timeout at https://github.com/emillynge/rathole/blob/7f083c21b7f0f637b28337c48c74f43830488aa4/src/client.rs#L393-L397
    2. Utilize some QUIC features or enforce a timeout in the transport connect implementation. I wonder if there's already some timeout setting for QUIC connect.
  2. As I previously mentioned, a IPv4 bind socket can't connect to a IPv6 address or vice versa. See https://docs.rs/quinn/latest/quinn/struct.Endpoint.html#method.client about dual stack.

@rapiz1
Copy link
Owner

rapiz1 commented Jan 27, 2022

Hi @emillynge What's the status of this? I think we are really close to get this merged.

@emillynge
Copy link
Author

Hi @emillynge What's the status of this? I think we are really close to get this merged.

I'm on vacation now, and then next week my parental leave is over and I'll have to go to work. I will try to wrap this up in the coming month, but my time is very limited now unfortunately.

@Gurkengewuerz
Copy link

Hey @emillynge may I ask nicely what the situation is with this PR? 👀

@cssivision
Copy link

@emillynge if you don't mind, I can pick up this and finish this PR.

@emillynge
Copy link
Author

emillynge commented Apr 19, 2022

@emillynge if you don't mind, I can pick up this and finish this PR.

@cssivision
I would be very pleased if you do so.

Can I transfer/share ownership of PR and branch somehow?

@Gurkengewuerz
Copy link

@cssivision can fork your repository with the PR branch and open a new one at least. But i think you can also add him as a member to your forked rathole repository so he can push changed. Correct me if i'am wrong

@aa51513
Copy link

aa51513 commented Mar 8, 2023

What is the future of this pull request? @rapiz1

The reason I'm asking this is that I'm thinking about implementing a new feature of QUIC as a transport type for rathole

should I submit a whole new pull request?

@rapiz1 rapiz1 force-pushed the main branch 4 times, most recently from a6601fd to 33b31dd Compare October 1, 2023 08:48
@rapiz1 rapiz1 force-pushed the main branch 4 times, most recently from 78f1157 to 97541af Compare October 1, 2023 09:13
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.

6 participants