mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 22:39:46 +00:00
fix http upgrades (#1980)
* fix http upgrades * use addr as authority directly * move code to fn in net_utils Co-authored-by: Stephen Chavez <stephen.chavez12@gmail.com>
This commit is contained in:
@@ -80,7 +80,7 @@ embassy_container_init = { path = "../libs/embassy_container_init" }
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
hmac = "0.12.1"
|
hmac = "0.12.1"
|
||||||
http = "0.2.8"
|
http = "0.2.8"
|
||||||
hyper = "0.14.20"
|
hyper = { version = "0.14.20", features = ["full"] }
|
||||||
hyper-ws-listener = "0.2.0"
|
hyper-ws-listener = "0.2.0"
|
||||||
imbl = "2.0.0"
|
imbl = "2.0.0"
|
||||||
indexmap = { version = "1.9.1", features = ["serde"] }
|
indexmap = { version = "1.9.1", features = ["serde"] }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use hyper::{Body, Client, Error as HyperError, Request, Response};
|
use hyper::{Body, Error as HyperError, Request, Response};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
|
|
||||||
@@ -51,5 +51,3 @@ pub struct GeneratedCertificateMountPoint(());
|
|||||||
pub type HttpHandler = Arc<
|
pub type HttpHandler = Arc<
|
||||||
dyn Fn(Request<Body>) -> BoxFuture<'static, Result<Response<Body>, HyperError>> + Send + Sync,
|
dyn Fn(Request<Body>) -> BoxFuture<'static, Result<Response<Body>, HyperError>> + Send + Sync,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub type HttpClient = Client<hyper::client::HttpConnector>;
|
|
||||||
|
|||||||
@@ -126,3 +126,14 @@ impl TryFrom<Uri> for ResourceFqdn {
|
|||||||
Self::from_str(&value.to_string())
|
Self::from_str(&value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_upgrade_req(req: &Request<Body>) -> bool {
|
||||||
|
req.headers()
|
||||||
|
.get("connection")
|
||||||
|
.and_then(|c| c.to_str().ok())
|
||||||
|
.map(|c| {
|
||||||
|
c.split(",")
|
||||||
|
.any(|c| c.trim().eq_ignore_ascii_case("upgrade"))
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,20 +5,19 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use http::{Method, Request, Response};
|
use http::uri::{Authority, Scheme};
|
||||||
use hyper::upgrade::Upgraded;
|
use http::{Request, Response, Uri};
|
||||||
use hyper::{Body, Error as HyperError};
|
use hyper::{Body, Error as HyperError};
|
||||||
use models::{InterfaceId, PackageId};
|
use models::{InterfaceId, PackageId};
|
||||||
use openssl::pkey::{PKey, Private};
|
use openssl::pkey::{PKey, Private};
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tracing::{error, info, instrument};
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
use crate::net::net_utils::ResourceFqdn;
|
use crate::net::net_utils::{is_upgrade_req, ResourceFqdn};
|
||||||
use crate::net::ssl::SslManager;
|
use crate::net::ssl::SslManager;
|
||||||
use crate::net::vhost_controller::VHOSTController;
|
use crate::net::vhost_controller::VHOSTController;
|
||||||
use crate::net::{HttpClient, HttpHandler, InterfaceMetadata, PackageNetInfo};
|
use crate::net::{HttpHandler, InterfaceMetadata, PackageNetInfo};
|
||||||
use crate::{Error, ResultExt};
|
use crate::{Error, ResultExt};
|
||||||
|
|
||||||
pub struct ProxyController {
|
pub struct ProxyController {
|
||||||
@@ -85,59 +84,43 @@ impl ProxyController {
|
|||||||
self.inner.lock().await.get_embassy_hostname()
|
self.inner.lock().await.get_embassy_hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn proxy(
|
async fn proxy(
|
||||||
client: HttpClient,
|
client: &hyper::Client<hyper::client::HttpConnector>,
|
||||||
req: Request<Body>,
|
mut req: Request<Body>,
|
||||||
|
addr: SocketAddr,
|
||||||
) -> Result<Response<Body>, HyperError> {
|
) -> Result<Response<Body>, HyperError> {
|
||||||
if Method::CONNECT == req.method() {
|
let mut uri = std::mem::take(req.uri_mut()).into_parts();
|
||||||
// Received an HTTP request like:
|
|
||||||
// ```
|
|
||||||
// CONNECT www.domain.com:443 HTTP/1.1s
|
|
||||||
// Host: www.domain.com:443
|
|
||||||
// Proxy-Connection: Keep-Alive
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// When HTTP method is CONNECT we should return an empty body
|
|
||||||
// then we can eventually upgrade the connection and talk a new protocol.
|
|
||||||
//
|
|
||||||
// Note: only after client received an empty body with STATUS_OK can the
|
|
||||||
// connection be upgraded, so we can't return a response inside
|
|
||||||
// `on_upgrade` future.
|
|
||||||
|
|
||||||
tokio::task::spawn(async move {
|
uri.scheme = Some(Scheme::HTTP);
|
||||||
let addr = req.uri().clone();
|
uri.authority = Authority::from_str(&addr.to_string()).ok();
|
||||||
|
match Uri::from_parts(uri) {
|
||||||
|
Ok(uri) => *req.uri_mut() = uri,
|
||||||
|
Err(e) => error!("Error rewriting uri: {}", e),
|
||||||
|
}
|
||||||
|
let addr = dbg!(req.uri().to_string());
|
||||||
|
|
||||||
match hyper::upgrade::on(req).await {
|
if is_upgrade_req(&req) {
|
||||||
Ok(upgraded) => {
|
let upgraded_req = hyper::upgrade::on(&mut req);
|
||||||
if let Err(e) = Self::tunnel(upgraded, addr.to_string()).await {
|
let mut res = client.request(req).await?;
|
||||||
error!("server io error: {}", e);
|
let upgraded_res = hyper::upgrade::on(&mut res);
|
||||||
}
|
tokio::spawn(async move {
|
||||||
}
|
if let Err(e) = async {
|
||||||
Err(e) => error!("upgrade error: {}", e),
|
let mut req = upgraded_req.await?;
|
||||||
|
let mut res = upgraded_res.await?;
|
||||||
|
tokio::io::copy_bidirectional(&mut req, &mut res).await?;
|
||||||
|
|
||||||
|
Ok::<_, color_eyre::eyre::Report>(())
|
||||||
|
}
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("error binding together tcp streams for {}: {}", addr, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Ok(res)
|
||||||
Ok(Response::new(Body::empty()))
|
|
||||||
} else {
|
} else {
|
||||||
client.request(req).await
|
client.request(req).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a TCP connection to host:port, build a tunnel between the connection and
|
|
||||||
// the upgraded connection
|
|
||||||
async fn tunnel(mut upgraded: Upgraded, addr: String) -> std::io::Result<()> {
|
|
||||||
let mut server = TcpStream::connect(addr).await?;
|
|
||||||
|
|
||||||
let (from_client, from_server) =
|
|
||||||
tokio::io::copy_bidirectional(&mut upgraded, &mut server).await?;
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"client wrote {} bytes and received {} bytes",
|
|
||||||
from_client, from_server
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
struct ProxyControllerInner {
|
struct ProxyControllerInner {
|
||||||
ssl_manager: SslManager,
|
ssl_manager: SslManager,
|
||||||
@@ -263,7 +246,7 @@ impl ProxyControllerInner {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let svc_handler =
|
let svc_handler =
|
||||||
Self::create_docker_handle(docker_ipv4.to_string(), lan_port_config.internal)
|
Self::create_docker_handle((docker_ipv4, lan_port_config.internal).into())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
self.add_handle(
|
self.add_handle(
|
||||||
@@ -282,28 +265,12 @@ impl ProxyControllerInner {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_docker_handle(internal_ip: String, port: u16) -> HttpHandler {
|
async fn create_docker_handle(internal_addr: SocketAddr) -> HttpHandler {
|
||||||
let svc_handler: HttpHandler = Arc::new(move |mut req| {
|
let svc_handler: HttpHandler = Arc::new(move |req| {
|
||||||
let proxy_addr = internal_ip.clone();
|
let client = hyper::client::Client::builder()
|
||||||
async move {
|
.set_host(false)
|
||||||
let client = HttpClient::new();
|
.build_http();
|
||||||
|
async move { ProxyController::proxy(&client, req, internal_addr).await }.boxed()
|
||||||
let uri_string = format!(
|
|
||||||
"http://{}:{}{}",
|
|
||||||
proxy_addr,
|
|
||||||
port,
|
|
||||||
req.uri()
|
|
||||||
.path_and_query()
|
|
||||||
.map(|x| x.as_str())
|
|
||||||
.unwrap_or("/")
|
|
||||||
);
|
|
||||||
|
|
||||||
let uri = uri_string.parse().unwrap();
|
|
||||||
*req.uri_mut() = uri;
|
|
||||||
|
|
||||||
ProxyController::proxy(client, req).await
|
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
svc_handler
|
svc_handler
|
||||||
|
|||||||
Reference in New Issue
Block a user