CAP_NET_BIND_SERVICE being the most useful, but this is only needed until bind. I don't know much about this, but the docs for capset(2) says "thread" so I think that means this needs to be done prior to starting tokio(and it's threads).
I looked at this a bit and it looks like reading the config needs to be moved out of async and some other uncomfortable changes. I feel there should be a new type to hold the sockets as they get passed down the call chain and then destructured, also the sockets need to be std::net::{TcpListener, UdpSocket} and latter wrapped in the tokio async types.
These changes are not massive, but it demonstrates why multi-platform codebases are bad. Is it worth having this complexity on platforms like Windows where it will see no benefit? There is an argument to be made for having separate projects.
I was playing with this a bit and then stopped when I started changing rpxy-bin/src/main.rs, here is that diff. It was edited so much it won't apply, my toml setup alphabetizes things and changes whitespace also rust use statements are alphabetized. I also wasn't looking at all for use cases other than my own.
diff --git a/rpxy-lib/Cargo.toml b/rpxy-lib/Cargo.toml
index 8fac76c..5c95418 100644
--- a/rpxy-lib/Cargo.toml
+++ b/rpxy-lib/Cargo.toml
@@ -1,18 +1,20 @@
+[target.'cfg(target_os = "linux")'.dependencies]
+caps = '0.5.5'
diff --git a/rpxy-lib/src/proxy/proxy_main.rs b/rpxy-lib/src/proxy/proxy_main.rs
index 5244ecf..69102c3 100644
--- a/rpxy-lib/src/proxy/proxy_main.rs
+++ b/rpxy-lib/src/proxy/proxy_main.rs
@@ -24,6 +24,14 @@ use std::{net::SocketAddr, sync::Arc, time::Duration};
use tokio::time::timeout;
use tokio_util::sync::CancellationToken;
+fn drop_capabilities() -> Result<(), caps::errors::CapsError> {
+ #[cfg(target_os = "linux")]
+ {
+ caps::clear(None, caps::CapSet::Permitted)?;
+ }
+ Ok(())
+}
+
/// Wrapper function to handle request for HTTP/1.1 and HTTP/2
/// HTTP/3 is handled in proxy_h3.rs which directly calls the message handler
async fn serve_request<T>(
@@ -120,6 +128,7 @@ where
let tcp_socket = bind_tcp_socket(&self.listening_on)?;
let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?;
info!("Start TCP proxy serving with HTTP request for configured host names");
+ let _ = drop_capabilities();
while let Ok((stream, client_addr)) = tcp_listener.accept().await {
self.serve_connection(TokioIo::new(stream), client_addr, None);
}
@@ -131,6 +140,10 @@ where
/// Start with TLS (HTTPS)
pub(super) async fn start_with_tls(&self, cancel_token: CancellationToken) -> RpxyResult<()> {
+ let udp_socket = super::socket::bind_udp_socket(&self.listening_on)?;
+ let tcp_socket = bind_tcp_socket(&self.listening_on)?;
+ let _ = drop_capabilities();
+
#[cfg(not(any(feature = "http3-quinn", feature = "http3-s2n")))]
{
self.tls_listener_service().await?;
@@ -145,7 +159,7 @@ where
let cancel_token = cancel_token.clone();
async move {
select! {
- _ = self_clone.tls_listener_service().fuse() => {
+ _ = self_clone.tls_listener_service(tcp_socket).fuse() => {
error!("TCP proxy service for TLS exited");
cancel_token.cancel();
},
@@ -159,7 +173,7 @@ where
let self_clone = self.clone();
async move {
select! {
- _ = self_clone.h3_listener_service().fuse() => {
+ _ = self_clone.h3_listener_service(udp_socket).fuse() => {
error!("UDP proxy service for QUIC exited");
cancel_token.cancel();
},
@@ -173,7 +187,7 @@ where
Ok(())
} else {
- self.tls_listener_service().await?;
+ self.tls_listener_service(tcp_socket).await?;
error!("TCP proxy service for TLS exited");
Ok(())
}
@@ -181,11 +195,10 @@ where
}
// TCP Listener Service, i.e., http/2 and http/1.1
- async fn tls_listener_service(&self) -> RpxyResult<()> {
+ async fn tls_listener_service(&self, tcp_socket: tokio::net::TcpSocket) -> RpxyResult<()> {
let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else {
return Err(RpxyError::NoCertificateReloader);
};
- let tcp_socket = bind_tcp_socket(&self.listening_on)?;
let tcp_listener = tcp_socket.listen(self.globals.proxy_config.tcp_listen_backlog)?;
info!("Start TCP proxy serving with HTTPS request for configured host names");
diff --git a/rpxy-lib/src/proxy/proxy_quic_quinn.rs b/rpxy-lib/src/proxy/proxy_quic_quinn.rs
index c316ed9..05fd4e8 100644
--- a/rpxy-lib/src/proxy/proxy_quic_quinn.rs
+++ b/rpxy-lib/src/proxy/proxy_quic_quinn.rs
@@ -13,7 +13,7 @@ impl<T> Proxy<T>
where
T: Send + Sync + Connect + Clone + 'static,
{
- pub(super) async fn h3_listener_service(&self) -> RpxyResult<()> {
+ pub(super) async fn h3_listener_service(&self, udp_socket: std::net::UdpSocket) -> RpxyResult<()> {
let Some(mut server_crypto_rx) = self.globals.cert_reloader_rx.clone() else {
return Err(RpxyError::NoCertificateReloader);
};
@@ -45,7 +45,6 @@ where
server_config_h3.max_incoming(self.globals.proxy_config.h3_max_concurrent_connections as usize);
// To reuse address
- let udp_socket = bind_udp_socket(&self.listening_on)?;
let runtime =
quinn::default_runtime().ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No async runtime found"))?;
let endpoint = Endpoint::new(quinn::EndpointConfig::default(), Some(server_config_h3), udp_socket, runtime)?;
CAP_NET_BIND_SERVICEbeing the most useful, but this is only needed until bind. I don't know much about this, but the docs for capset(2) says "thread" so I think that means this needs to be done prior to starting tokio(and it's threads).I looked at this a bit and it looks like reading the config needs to be moved out of async and some other uncomfortable changes. I feel there should be a new type to hold the sockets as they get passed down the call chain and then destructured, also the sockets need to be
std::net::{TcpListener, UdpSocket}and latter wrapped in the tokio async types.These changes are not massive, but it demonstrates why multi-platform codebases are bad. Is it worth having this complexity on platforms like Windows where it will see no benefit? There is an argument to be made for having separate projects.
I was playing with this a bit and then stopped when I started changing
rpxy-bin/src/main.rs, here is that diff. It was edited so much it won't apply, my toml setup alphabetizes things and changes whitespace also rust use statements are alphabetized. I also wasn't looking at all for use cases other than my own.