public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector
@ 2021-04-21 11:16 Dietmar Maurer
  2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 1/5] http: rename EitherStream to MaybeTlsStream Dietmar Maurer
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Dietmar Maurer @ 2021-04-21 11:16 UTC (permalink / raw)
  To: pbs-devel

This time without using an external crate.

Note: Secured proxy connections are not supported (rarely used feature?)

Dietmar Maurer (5):
  http: rename EitherStream to MaybeTlsStream
  MaybeTlsStream: implement poll_write_vectored()
  new http client implementation SimpleHttp (avoid static HTTP_CLIENT)
  HttpsConnector: code cleanup
  HttpsConnector: add proxy support

 src/api2/node/apt.rs      |   8 +-
 src/tools/async_io.rs     |  90 +++++++---
 src/tools/http.rs         | 336 +++++++++++++++++++++++++++-----------
 src/tools/subscription.rs |  10 +-
 4 files changed, 316 insertions(+), 128 deletions(-)

-- 
2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [RFC proxmox-backup 1/5] http: rename EitherStream to MaybeTlsStream
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
@ 2021-04-21 11:16 ` Dietmar Maurer
  2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 2/5] MaybeTlsStream: implement poll_write_vectored() Dietmar Maurer
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dietmar Maurer @ 2021-04-21 11:16 UTC (permalink / raw)
  To: pbs-devel

And rename the enum values. Added an additional enum called Proxied.

The enum in now more specialized, but we only use it for the http client anyways.
---
 src/tools/async_io.rs | 64 +++++++++++++++++++++++++++----------------
 src/tools/http.rs     | 20 ++++++--------
 2 files changed, 48 insertions(+), 36 deletions(-)

diff --git a/src/tools/async_io.rs b/src/tools/async_io.rs
index 844afaa9..963f6fdd 100644
--- a/src/tools/async_io.rs
+++ b/src/tools/async_io.rs
@@ -1,4 +1,4 @@
-//! Generic AsyncRead/AsyncWrite utilities.
+//! AsyncRead/AsyncWrite utilities.
 
 use std::io;
 use std::os::unix::io::{AsRawFd, RawFd};
@@ -9,41 +9,52 @@ use futures::stream::{Stream, TryStream};
 use futures::ready;
 use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
 use tokio::net::TcpListener;
-use hyper::client::connect::Connection;
-
-pub enum EitherStream<L, R> {
-    Left(L),
-    Right(R),
+use tokio_openssl::SslStream;
+use hyper::client::connect::{Connection, Connected};
+
+/// Asynchronous stream, possibly encrypted and proxied
+///
+/// Usefule for HTTP client implementations using hyper.
+pub enum MaybeTlsStream<S> {
+    Normal(S),
+    Proxied(S),
+    Secured(SslStream<S>),
 }
 
-impl<L: AsyncRead + Unpin, R: AsyncRead + Unpin> AsyncRead for EitherStream<L, R> {
+impl<S: AsyncRead + AsyncWrite + Unpin> AsyncRead for MaybeTlsStream<S> {
     fn poll_read(
         self: Pin<&mut Self>,
         cx: &mut Context,
         buf: &mut ReadBuf,
     ) -> Poll<Result<(), io::Error>> {
         match self.get_mut() {
-            EitherStream::Left(ref mut s) => {
+            MaybeTlsStream::Normal(ref mut s) => {
+                Pin::new(s).poll_read(cx, buf)
+            }
+            MaybeTlsStream::Proxied(ref mut s) => {
                 Pin::new(s).poll_read(cx, buf)
             }
-            EitherStream::Right(ref mut s) => {
+            MaybeTlsStream::Secured(ref mut s) => {
                 Pin::new(s).poll_read(cx, buf)
             }
         }
     }
 }
 
-impl<L: AsyncWrite + Unpin, R: AsyncWrite + Unpin> AsyncWrite for EitherStream<L, R> {
+impl<S: AsyncRead + AsyncWrite + Unpin> AsyncWrite for MaybeTlsStream<S> {
     fn poll_write(
         self: Pin<&mut Self>,
         cx: &mut Context,
         buf: &[u8],
     ) -> Poll<Result<usize, io::Error>> {
         match self.get_mut() {
-            EitherStream::Left(ref mut s) => {
+            MaybeTlsStream::Normal(ref mut s) => {
+                Pin::new(s).poll_write(cx, buf)
+            }
+            MaybeTlsStream::Proxied(ref mut s) => {
                 Pin::new(s).poll_write(cx, buf)
             }
-            EitherStream::Right(ref mut s) => {
+            MaybeTlsStream::Secured(ref mut s) => {
                 Pin::new(s).poll_write(cx, buf)
             }
         }
@@ -51,10 +62,13 @@ impl<L: AsyncWrite + Unpin, R: AsyncWrite + Unpin> AsyncWrite for EitherStream<L
 
     fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
         match self.get_mut() {
-            EitherStream::Left(ref mut s) => {
+            MaybeTlsStream::Normal(ref mut s) => {
                 Pin::new(s).poll_flush(cx)
             }
-            EitherStream::Right(ref mut s) => {
+            MaybeTlsStream::Proxied(ref mut s) => {
+                Pin::new(s).poll_flush(cx)
+            }
+            MaybeTlsStream::Secured(ref mut s) => {
                 Pin::new(s).poll_flush(cx)
             }
         }
@@ -62,25 +76,27 @@ impl<L: AsyncWrite + Unpin, R: AsyncWrite + Unpin> AsyncWrite for EitherStream<L
 
     fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
         match self.get_mut() {
-            EitherStream::Left(ref mut s) => {
+            MaybeTlsStream::Normal(ref mut s) => {
                 Pin::new(s).poll_shutdown(cx)
             }
-            EitherStream::Right(ref mut s) => {
+            MaybeTlsStream::Proxied(ref mut s) => {
+                Pin::new(s).poll_shutdown(cx)
+            }
+            MaybeTlsStream::Secured(ref mut s) => {
                 Pin::new(s).poll_shutdown(cx)
             }
         }
     }
 }
 
-// we need this for crate::client::http_client:
-impl Connection for EitherStream<
-    tokio::net::TcpStream,
-    Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>,
-> {
-    fn connected(&self) -> hyper::client::connect::Connected {
+// we need this for the hyper http client
+impl <S: Connection + AsyncRead + AsyncWrite + Unpin> Connection for MaybeTlsStream<S>
+{
+    fn connected(&self) -> Connected {
         match self {
-            EitherStream::Left(s) => s.connected(),
-            EitherStream::Right(s) => s.get_ref().connected(),
+            MaybeTlsStream::Normal(s) => s.connected(),
+            MaybeTlsStream::Proxied(s) => s.connected().proxy(true),
+            MaybeTlsStream::Secured(s) => s.get_ref().connected(),
         }
     }
 }
diff --git a/src/tools/http.rs b/src/tools/http.rs
index d08ce451..3cd3af4e 100644
--- a/src/tools/http.rs
+++ b/src/tools/http.rs
@@ -10,9 +10,11 @@ use hyper::client::{Client, HttpConnector};
 use http::{Request, Response};
 use openssl::ssl::{SslConnector, SslMethod};
 use futures::*;
+use tokio::net::TcpStream;
+use tokio_openssl::SslStream;
 
 use crate::tools::{
-    async_io::EitherStream,
+    async_io::MaybeTlsStream,
     socket::{
         set_tcp_keepalive,
         PROXMOX_BACKUP_TCP_KEEPALIVE_TIME,
@@ -100,13 +102,8 @@ impl HttpsConnector {
     }
 }
 
-type MaybeTlsStream = EitherStream<
-    tokio::net::TcpStream,
-    Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>,
->;
-
 impl hyper::service::Service<Uri> for HttpsConnector {
-    type Response = MaybeTlsStream;
+    type Response = MaybeTlsStream<TcpStream>;
     type Error = Error;
     #[allow(clippy::type_complexity)]
     type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
@@ -140,12 +137,11 @@ impl hyper::service::Service<Uri> for HttpsConnector {
             let _ = set_tcp_keepalive(conn.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
 
             if is_https {
-                let conn: tokio_openssl::SslStream<tokio::net::TcpStream> = tokio_openssl::SslStream::new(config?.into_ssl(&host)?, conn)?;
-                let mut conn = Box::pin(conn);
-                conn.as_mut().connect().await?;
-                Ok(MaybeTlsStream::Right(conn))
+                let mut conn: SslStream<TcpStream> = SslStream::new(config?.into_ssl(&host)?, conn)?;
+                Pin::new(&mut conn).connect().await?;
+                Ok(MaybeTlsStream::Secured(conn))
             } else {
-                Ok(MaybeTlsStream::Left(conn))
+                Ok(MaybeTlsStream::Normal(conn))
             }
         }.boxed()
     }
-- 
2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [RFC proxmox-backup 2/5] MaybeTlsStream: implement poll_write_vectored()
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
  2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 1/5] http: rename EitherStream to MaybeTlsStream Dietmar Maurer
@ 2021-04-21 11:16 ` Dietmar Maurer
  2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 3/5] new http client implementation SimpleHttp (avoid static HTTP_CLIENT) Dietmar Maurer
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dietmar Maurer @ 2021-04-21 11:16 UTC (permalink / raw)
  To: pbs-devel

This is just an performance optimization.
---
 src/tools/async_io.rs | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/src/tools/async_io.rs b/src/tools/async_io.rs
index 963f6fdd..83110912 100644
--- a/src/tools/async_io.rs
+++ b/src/tools/async_io.rs
@@ -60,6 +60,32 @@ impl<S: AsyncRead + AsyncWrite + Unpin> AsyncWrite for MaybeTlsStream<S> {
         }
     }
 
+    fn poll_write_vectored(
+        self: Pin<&mut Self>,
+        cx: &mut Context<'_>,
+        bufs: &[io::IoSlice<'_>],
+    ) -> Poll<Result<usize, io::Error>> {
+        match self.get_mut() {
+            MaybeTlsStream::Normal(ref mut s) => {
+                Pin::new(s).poll_write_vectored(cx, bufs)
+            }
+            MaybeTlsStream::Proxied(ref mut s) => {
+                Pin::new(s).poll_write_vectored(cx, bufs)
+            }
+            MaybeTlsStream::Secured(ref mut s) => {
+                Pin::new(s).poll_write_vectored(cx, bufs)
+            }
+        }
+    }
+
+    fn is_write_vectored(&self) -> bool {
+        match self {
+            MaybeTlsStream::Normal(s) => s.is_write_vectored(),
+            MaybeTlsStream::Proxied(s) => s.is_write_vectored(),
+            MaybeTlsStream::Secured(s) => s.is_write_vectored(),
+        }
+    }
+
     fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), io::Error>> {
         match self.get_mut() {
             MaybeTlsStream::Normal(ref mut s) => {
-- 
2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [RFC proxmox-backup 3/5] new http client implementation SimpleHttp (avoid static HTTP_CLIENT)
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
  2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 1/5] http: rename EitherStream to MaybeTlsStream Dietmar Maurer
  2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 2/5] MaybeTlsStream: implement poll_write_vectored() Dietmar Maurer
@ 2021-04-21 11:17 ` Dietmar Maurer
  2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 4/5] HttpsConnector: code cleanup Dietmar Maurer
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dietmar Maurer @ 2021-04-21 11:17 UTC (permalink / raw)
  To: pbs-devel

This one will have proxy support.
---
 src/api2/node/apt.rs      |   8 ++-
 src/tools/http.rs         | 122 +++++++++++++++++++++-----------------
 src/tools/subscription.rs |  10 ++--
 3 files changed, 80 insertions(+), 60 deletions(-)

diff --git a/src/api2/node/apt.rs b/src/api2/node/apt.rs
index e77b89fa..dbdb2019 100644
--- a/src/api2/node/apt.rs
+++ b/src/api2/node/apt.rs
@@ -7,7 +7,7 @@ use proxmox::api::{api, RpcEnvironment, RpcEnvironmentType, Permission};
 use proxmox::api::router::{Router, SubdirMap};
 
 use crate::server::WorkerTask;
-use crate::tools::{apt, http, subscription};
+use crate::tools::{apt, http::SimpleHttp, subscription};
 
 use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
 use crate::api2::types::{Authid, APTUpdateInfo, NODE_SCHEMA, UPID_SCHEMA};
@@ -194,10 +194,12 @@ fn apt_get_changelog(
         bail!("Package '{}' not found", name);
     }
 
+    let mut client = SimpleHttp::new();
+
     let changelog_url = &pkg_info[0].change_log_url;
     // FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it
     if changelog_url.starts_with("http://download.proxmox.com/") {
-        let changelog = crate::tools::runtime::block_on(http::get_string(changelog_url, None))
+        let changelog = crate::tools::runtime::block_on(client.get_string(changelog_url, None))
             .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
         Ok(json!(changelog))
 
@@ -221,7 +223,7 @@ fn apt_get_changelog(
         auth_header.insert("Authorization".to_owned(),
             format!("Basic {}", base64::encode(format!("{}:{}", key, id))));
 
-        let changelog = crate::tools::runtime::block_on(http::get_string(changelog_url, Some(&auth_header)))
+        let changelog = crate::tools::runtime::block_on(client.get_string(changelog_url, Some(&auth_header)))
             .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?;
         Ok(json!(changelog))
 
diff --git a/src/tools/http.rs b/src/tools/http.rs
index 3cd3af4e..f19d6527 100644
--- a/src/tools/http.rs
+++ b/src/tools/http.rs
@@ -1,5 +1,4 @@
 use anyhow::{Error, format_err, bail};
-use lazy_static::lazy_static;
 use std::task::{Context, Poll};
 use std::os::unix::io::AsRawFd;
 use std::collections::HashMap;
@@ -21,68 +20,85 @@ use crate::tools::{
     },
 };
 
-lazy_static! {
-    static ref HTTP_CLIENT: Client<HttpsConnector, Body> = {
-        let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
-        let httpc = HttpConnector::new();
-        let https = HttpsConnector::with_connector(httpc, connector);
-        Client::builder().build(https)
-    };
+/// Asyncrounous HTTP client implementation
+pub struct SimpleHttp {
+    client: Client<HttpsConnector, Body>,
 }
 
-pub async fn get_string(uri: &str, extra_headers: Option<&HashMap<String, String>>) -> Result<String, Error> {
-    let mut request = Request::builder()
-        .method("GET")
-        .uri(uri)
-        .header("User-Agent", "proxmox-backup-client/1.0");
+impl SimpleHttp {
 
-    if let Some(hs) = extra_headers {
-        for (h, v) in hs.iter() {
-            request = request.header(h, v);
-        }
+    pub fn new() -> Self {
+        let ssl_connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
+        Self::with_ssl_connector(ssl_connector)
     }
 
-    let request = request.body(Body::empty())?;
-
-    let res = HTTP_CLIENT.request(request).await?;
+    pub fn with_ssl_connector(ssl_connector: SslConnector) -> Self {
+        let connector = HttpConnector::new();
+        let https = HttpsConnector::with_connector(connector, ssl_connector);
+        let client = Client::builder().build(https);
+        Self { client }
+    }
 
-    let status = res.status();
-    if !status.is_success() {
-        bail!("Got bad status '{}' from server", status)
+    pub async fn post(
+        &mut self,
+        uri: &str,
+        body: Option<String>,
+        content_type: Option<&str>,
+    ) -> Result<Response<Body>, Error> {
+
+        let body = if let Some(body) = body {
+            Body::from(body)
+        } else {
+            Body::empty()
+        };
+        let content_type = content_type.unwrap_or("application/json");
+
+        let request = Request::builder()
+            .method("POST")
+            .uri(uri)
+            .header("User-Agent", "proxmox-backup-client/1.0")
+            .header(hyper::header::CONTENT_TYPE, content_type)
+            .body(body)?;
+
+        self.client.request(request)
+            .map_err(Error::from)
+            .await
     }
 
-    response_body_string(res).await
-}
+    pub async fn get_string(
+        &mut self,
+        uri: &str,
+        extra_headers: Option<&HashMap<String, String>>,
+    ) -> Result<String, Error> {
 
-pub async fn response_body_string(res: Response<Body>) -> Result<String, Error> {
-    let buf = hyper::body::to_bytes(res).await?;
-    String::from_utf8(buf.to_vec())
-        .map_err(|err| format_err!("Error converting HTTP result data: {}", err))
-}
+        let mut request = Request::builder()
+            .method("GET")
+            .uri(uri)
+            .header("User-Agent", "proxmox-backup-client/1.0");
+
+        if let Some(hs) = extra_headers {
+            for (h, v) in hs.iter() {
+                request = request.header(h, v);
+            }
+        }
 
-pub async fn post(
-    uri: &str,
-    body: Option<String>,
-    content_type: Option<&str>,
-) -> Result<Response<Body>, Error> {
-    let body = if let Some(body) = body {
-        Body::from(body)
-    } else {
-        Body::empty()
-    };
-    let content_type = content_type.unwrap_or("application/json");
-
-    let request = Request::builder()
-        .method("POST")
-        .uri(uri)
-        .header("User-Agent", "proxmox-backup-client/1.0")
-        .header(hyper::header::CONTENT_TYPE, content_type)
-        .body(body)?;
-
-
-    HTTP_CLIENT.request(request)
-        .map_err(Error::from)
-        .await
+        let request = request.body(Body::empty())?;
+
+        let res = self.client.request(request).await?;
+
+        let status = res.status();
+        if !status.is_success() {
+            bail!("Got bad status '{}' from server", status)
+        }
+
+        Self::response_body_string(res).await
+    }
+
+    pub async fn response_body_string(res: Response<Body>) -> Result<String, Error> {
+        let buf = hyper::body::to_bytes(res).await?;
+        String::from_utf8(buf.to_vec())
+            .map_err(|err| format_err!("Error converting HTTP result data: {}", err))
+    }
 }
 
 #[derive(Clone)]
diff --git a/src/tools/subscription.rs b/src/tools/subscription.rs
index 9b9534ac..9a920aee 100644
--- a/src/tools/subscription.rs
+++ b/src/tools/subscription.rs
@@ -6,8 +6,7 @@ use regex::Regex;
 
 use proxmox::api::api;
 
-use crate::tools;
-use crate::tools::http;
+use crate::tools::{self, http::SimpleHttp};
 use proxmox::tools::fs::{replace_file, CreateOptions};
 
 /// How long the local key is valid for in between remote checks
@@ -102,10 +101,13 @@ async fn register_subscription(
         "ip": "localhost",
         "check_token": challenge,
     });
+
+    let mut client = SimpleHttp::new();
+
     let uri = "https://shop.maurer-it.com/modules/servers/licensing/verify.php";
     let query = tools::json_object_to_query(params)?;
-    let response = http::post(uri, Some(query), Some("application/x-www-form-urlencoded")).await?;
-    let body = http::response_body_string(response).await?;
+    let response = client.post(uri, Some(query), Some("application/x-www-form-urlencoded")).await?;
+    let body = SimpleHttp::response_body_string(response).await?;
 
     Ok((body, challenge))
 }
-- 
2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [RFC proxmox-backup 4/5] HttpsConnector: code cleanup
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
                   ` (2 preceding siblings ...)
  2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 3/5] new http client implementation SimpleHttp (avoid static HTTP_CLIENT) Dietmar Maurer
@ 2021-04-21 11:17 ` Dietmar Maurer
  2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 5/5] HttpsConnector: add proxy support Dietmar Maurer
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dietmar Maurer @ 2021-04-21 11:17 UTC (permalink / raw)
  To: pbs-devel

---
 src/tools/http.rs | 42 ++++++++++++++++++++----------------------
 1 file changed, 20 insertions(+), 22 deletions(-)

diff --git a/src/tools/http.rs b/src/tools/http.rs
index f19d6527..a0dbfd01 100644
--- a/src/tools/http.rs
+++ b/src/tools/http.rs
@@ -3,6 +3,7 @@ use std::task::{Context, Poll};
 use std::os::unix::io::AsRawFd;
 use std::collections::HashMap;
 use std::pin::Pin;
+use std::sync::Arc;
 
 use hyper::{Uri, Body};
 use hyper::client::{Client, HttpConnector};
@@ -103,17 +104,16 @@ impl SimpleHttp {
 
 #[derive(Clone)]
 pub struct HttpsConnector {
-    http: HttpConnector,
-    ssl_connector: std::sync::Arc<SslConnector>,
+    connector: HttpConnector,
+    ssl_connector: Arc<SslConnector>,
 }
 
 impl HttpsConnector {
-    pub fn with_connector(mut http: HttpConnector, ssl_connector: SslConnector) -> Self {
-        http.enforce_http(false);
-
+    pub fn with_connector(mut connector: HttpConnector, ssl_connector: SslConnector) -> Self {
+        connector.enforce_http(false);
         Self {
-            http,
-            ssl_connector: std::sync::Arc::new(ssl_connector),
+            connector,
+            ssl_connector: Arc::new(ssl_connector),
         }
     }
 }
@@ -130,22 +130,20 @@ impl hyper::service::Service<Uri> for HttpsConnector {
     }
 
     fn call(&mut self, dst: Uri) -> Self::Future {
-        let mut this = self.clone();
-        async move {
-            let is_https = dst
-                .scheme()
-                .ok_or_else(|| format_err!("missing URL scheme"))?
-                == "https";
-
-            let host = dst
-                .host()
-                .ok_or_else(|| format_err!("missing hostname in destination url?"))?
-                .to_string();
+        let mut connector = self.connector.clone();
+        let ssl_connector = Arc::clone(&self.ssl_connector);
+        let is_https = dst.scheme() == Some(&http::uri::Scheme::HTTPS);
+        let host = match dst.host() {
+            Some(host) => host.to_owned(),
+            None => {
+                return futures::future::err(format_err!("missing URL scheme")).boxed();
+            }
+        };
 
-            let config = this.ssl_connector.configure();
+        async move {
+            let config = ssl_connector.configure()?;
             let dst_str = dst.to_string(); // for error messages
-            let conn = this
-                .http
+            let conn = connector
                 .call(dst)
                 .await
                 .map_err(|err| format_err!("error connecting to {} - {}", dst_str, err))?;
@@ -153,7 +151,7 @@ impl hyper::service::Service<Uri> for HttpsConnector {
             let _ = set_tcp_keepalive(conn.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
 
             if is_https {
-                let mut conn: SslStream<TcpStream> = SslStream::new(config?.into_ssl(&host)?, conn)?;
+                let mut conn: SslStream<TcpStream> = SslStream::new(config.into_ssl(&host)?, conn)?;
                 Pin::new(&mut conn).connect().await?;
                 Ok(MaybeTlsStream::Secured(conn))
             } else {
-- 
2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] [RFC proxmox-backup 5/5] HttpsConnector: add proxy support
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
                   ` (3 preceding siblings ...)
  2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 4/5] HttpsConnector: code cleanup Dietmar Maurer
@ 2021-04-21 11:17 ` Dietmar Maurer
  2021-04-21 12:12 ` [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Wolfgang Bumiller
  2021-04-21 14:33 ` [pbs-devel] applied-series: " Thomas Lamprecht
  6 siblings, 0 replies; 8+ messages in thread
From: Dietmar Maurer @ 2021-04-21 11:17 UTC (permalink / raw)
  To: pbs-devel

---
 src/api2/node/apt.rs      |   2 +-
 src/tools/http.rs         | 176 +++++++++++++++++++++++++++++++++-----
 src/tools/subscription.rs |   2 +-
 3 files changed, 156 insertions(+), 24 deletions(-)

diff --git a/src/api2/node/apt.rs b/src/api2/node/apt.rs
index dbdb2019..44b13edd 100644
--- a/src/api2/node/apt.rs
+++ b/src/api2/node/apt.rs
@@ -194,7 +194,7 @@ fn apt_get_changelog(
         bail!("Package '{}' not found", name);
     }
 
-    let mut client = SimpleHttp::new();
+    let mut client = SimpleHttp::new(None); // TODO: pass proxy_config
 
     let changelog_url = &pkg_info[0].change_log_url;
     // FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it
diff --git a/src/tools/http.rs b/src/tools/http.rs
index a0dbfd01..6f00d6e0 100644
--- a/src/tools/http.rs
+++ b/src/tools/http.rs
@@ -10,7 +10,14 @@ use hyper::client::{Client, HttpConnector};
 use http::{Request, Response};
 use openssl::ssl::{SslConnector, SslMethod};
 use futures::*;
-use tokio::net::TcpStream;
+use tokio::{
+    io::{
+        AsyncBufReadExt,
+        AsyncWriteExt,
+        BufStream,
+    },
+    net::TcpStream,
+};
 use tokio_openssl::SslStream;
 
 use crate::tools::{
@@ -21,6 +28,14 @@ use crate::tools::{
     },
 };
 
+/// HTTP Proxy Configuration
+#[derive(Clone)]
+pub struct ProxyConfig {
+    pub host: String,
+    pub port: u16,
+    pub force_connect: bool,
+}
+
 /// Asyncrounous HTTP client implementation
 pub struct SimpleHttp {
     client: Client<HttpsConnector, Body>,
@@ -28,18 +43,27 @@ pub struct SimpleHttp {
 
 impl SimpleHttp {
 
-    pub fn new() -> Self {
+    pub fn new(proxy_config: Option<ProxyConfig>) -> Self {
         let ssl_connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
-        Self::with_ssl_connector(ssl_connector)
+        Self::with_ssl_connector(ssl_connector, proxy_config)
     }
 
-    pub fn with_ssl_connector(ssl_connector: SslConnector) -> Self {
+    pub fn with_ssl_connector(ssl_connector: SslConnector, proxy_config: Option<ProxyConfig>) -> Self {
         let connector = HttpConnector::new();
-        let https = HttpsConnector::with_connector(connector, ssl_connector);
+        let mut https = HttpsConnector::with_connector(connector, ssl_connector);
+        if let Some(proxy_config) = proxy_config {
+            https.set_proxy(proxy_config);
+        }
         let client = Client::builder().build(https);
         Self { client }
     }
 
+    pub async fn request(&self, request: Request<Body>) -> Result<Response<Body>, Error> {
+        self.client.request(request)
+            .map_err(Error::from)
+            .await
+    }
+
     pub async fn post(
         &mut self,
         uri: &str,
@@ -106,6 +130,7 @@ impl SimpleHttp {
 pub struct HttpsConnector {
     connector: HttpConnector,
     ssl_connector: Arc<SslConnector>,
+    proxy: Option<ProxyConfig>,
 }
 
 impl HttpsConnector {
@@ -114,8 +139,56 @@ impl HttpsConnector {
         Self {
             connector,
             ssl_connector: Arc::new(ssl_connector),
+            proxy: None,
         }
     }
+
+    pub fn set_proxy(&mut self, proxy: ProxyConfig) {
+        self.proxy = Some(proxy);
+    }
+
+    async fn secure_stream(
+        tcp_stream: TcpStream,
+        ssl_connector: &SslConnector,
+        host: &str,
+    ) -> Result<MaybeTlsStream<TcpStream>, Error> {
+        let config = ssl_connector.configure()?;
+        let mut conn: SslStream<TcpStream> = SslStream::new(config.into_ssl(host)?, tcp_stream)?;
+        Pin::new(&mut conn).connect().await?;
+        Ok(MaybeTlsStream::Secured(conn))
+    }
+
+    async fn parse_connect_status(
+        stream: &mut BufStream<TcpStream>,
+    ) -> Result<(), Error> {
+
+        let mut status_str = String::new();
+
+        // TODO: limit read-length
+
+        if stream.read_line(&mut status_str).await? == 0 {
+            bail!("proxy connect failed - unexpected EOF")
+        }
+
+        if !(status_str.starts_with("HTTP/1.1 200") || status_str.starts_with("HTTP/1.0 200")) {
+            bail!("proxy connect failed - invalid status: {}", status_str)
+        }
+
+        loop {
+            // skip rest until \r\n
+            let mut response = String::new();
+            if stream.read_line(&mut response).await? == 0 {
+                bail!("proxy connect failed - unexpected EOF")
+            }
+            if response.len() > 8192 {
+                bail!("proxy connect failed - long lines in connect rtesponse")
+            }
+            if response == "\r\n" {
+                break;
+            }
+        }
+        Ok(())
+    }
 }
 
 impl hyper::service::Service<Uri> for HttpsConnector {
@@ -124,9 +197,10 @@ impl hyper::service::Service<Uri> for HttpsConnector {
     #[allow(clippy::type_complexity)]
     type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
 
-    fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
-        // This connector is always ready, but others might not be.
-        Poll::Ready(Ok(()))
+    fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+        self.connector
+            .poll_ready(ctx)
+            .map_err(|err| err.into())
     }
 
     fn call(&mut self, dst: Uri) -> Self::Future {
@@ -139,24 +213,82 @@ impl hyper::service::Service<Uri> for HttpsConnector {
                 return futures::future::err(format_err!("missing URL scheme")).boxed();
             }
         };
+        let port = dst.port_u16().unwrap_or(if is_https { 443 } else { 80 });
+
+        if let Some(ref proxy) = self.proxy {
+
+            let use_connect = is_https || proxy.force_connect;
+
+            let proxy_url = format!("{}:{}", proxy.host, proxy.port);
+            let proxy_uri = match Uri::builder()
+                .scheme("http")
+                .authority(proxy_url.as_str())
+                .path_and_query("/")
+                .build()
+            {
+                Ok(uri) => uri,
+                Err(err) => return futures::future::err(err.into()).boxed(),
+            };
+
+            if use_connect {
+                async move {
+
+                    let proxy_stream = connector
+                        .call(proxy_uri)
+                        .await
+                        .map_err(|err| format_err!("error connecting to {} - {}", proxy_url, err))?;
 
-        async move {
-            let config = ssl_connector.configure()?;
-            let dst_str = dst.to_string(); // for error messages
-            let conn = connector
-                .call(dst)
-                .await
-                .map_err(|err| format_err!("error connecting to {} - {}", dst_str, err))?;
+                    let _ = set_tcp_keepalive(proxy_stream.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
 
-            let _ = set_tcp_keepalive(conn.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
+                    let mut stream = BufStream::new(proxy_stream);
 
-            if is_https {
-                let mut conn: SslStream<TcpStream> = SslStream::new(config.into_ssl(&host)?, conn)?;
-                Pin::new(&mut conn).connect().await?;
-                Ok(MaybeTlsStream::Secured(conn))
+                    let connect_request = format!(
+                        "CONNECT {0}:{1} HTTP/1.1\r\n\
+                         Host: {0}:{1}\r\n\r\n",
+                        host, port,
+                    );
+
+                    stream.write_all(connect_request.as_bytes()).await?;
+                    stream.flush().await?;
+
+                    Self::parse_connect_status(&mut stream).await?;
+
+                    let tcp_stream = stream.into_inner();
+
+                    if is_https {
+                        Self::secure_stream(tcp_stream, &ssl_connector, &host).await
+                    } else {
+                        Ok(MaybeTlsStream::Normal(tcp_stream))
+                    }
+                }.boxed()
             } else {
-                Ok(MaybeTlsStream::Normal(conn))
+               async move {
+                   let tcp_stream = connector
+                       .call(proxy_uri)
+                       .await
+                       .map_err(|err| format_err!("error connecting to {} - {}", proxy_url, err))?;
+
+                   let _ = set_tcp_keepalive(tcp_stream.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
+
+                   Ok(MaybeTlsStream::Proxied(tcp_stream))
+               }.boxed()
             }
-        }.boxed()
+        } else {
+            async move {
+                let dst_str = dst.to_string(); // for error messages
+                let tcp_stream = connector
+                    .call(dst)
+                    .await
+                    .map_err(|err| format_err!("error connecting to {} - {}", dst_str, err))?;
+
+                let _ = set_tcp_keepalive(tcp_stream.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
+
+                if is_https {
+                    Self::secure_stream(tcp_stream, &ssl_connector, &host).await
+                } else {
+                    Ok(MaybeTlsStream::Normal(tcp_stream))
+                }
+            }.boxed()
+        }
     }
 }
diff --git a/src/tools/subscription.rs b/src/tools/subscription.rs
index 9a920aee..eaaf0389 100644
--- a/src/tools/subscription.rs
+++ b/src/tools/subscription.rs
@@ -102,7 +102,7 @@ async fn register_subscription(
         "check_token": challenge,
     });
 
-    let mut client = SimpleHttp::new();
+    let mut client = SimpleHttp::new(None); // TODO: pass proxy_config
 
     let uri = "https://shop.maurer-it.com/modules/servers/licensing/verify.php";
     let query = tools::json_object_to_query(params)?;
-- 
2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
                   ` (4 preceding siblings ...)
  2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 5/5] HttpsConnector: add proxy support Dietmar Maurer
@ 2021-04-21 12:12 ` Wolfgang Bumiller
  2021-04-21 14:33 ` [pbs-devel] applied-series: " Thomas Lamprecht
  6 siblings, 0 replies; 8+ messages in thread
From: Wolfgang Bumiller @ 2021-04-21 12:12 UTC (permalink / raw)
  To: Dietmar Maurer; +Cc: pbs-devel

looks fine to me, I'll have to rebase the http usage in my acme series
when this is pushed

On Wed, Apr 21, 2021 at 01:16:57PM +0200, Dietmar Maurer wrote:
> This time without using an external crate.
> 
> Note: Secured proxy connections are not supported (rarely used feature?)
> 
> Dietmar Maurer (5):
>   http: rename EitherStream to MaybeTlsStream
>   MaybeTlsStream: implement poll_write_vectored()
>   new http client implementation SimpleHttp (avoid static HTTP_CLIENT)
>   HttpsConnector: code cleanup
>   HttpsConnector: add proxy support
> 
>  src/api2/node/apt.rs      |   8 +-
>  src/tools/async_io.rs     |  90 +++++++---
>  src/tools/http.rs         | 336 +++++++++++++++++++++++++++-----------
>  src/tools/subscription.rs |  10 +-
>  4 files changed, 316 insertions(+), 128 deletions(-)
> 
> -- 
> 2.20.1




^ permalink raw reply	[flat|nested] 8+ messages in thread

* [pbs-devel] applied-series: [RFC proxmox-backup 0/5] proxy support for HttpsConnector
  2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
                   ` (5 preceding siblings ...)
  2021-04-21 12:12 ` [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Wolfgang Bumiller
@ 2021-04-21 14:33 ` Thomas Lamprecht
  6 siblings, 0 replies; 8+ messages in thread
From: Thomas Lamprecht @ 2021-04-21 14:33 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Dietmar Maurer

On 21.04.21 13:16, Dietmar Maurer wrote:
> This time without using an external crate.
> 
> Note: Secured proxy connections are not supported (rarely used feature?)
> 
> Dietmar Maurer (5):
>   http: rename EitherStream to MaybeTlsStream
>   MaybeTlsStream: implement poll_write_vectored()
>   new http client implementation SimpleHttp (avoid static HTTP_CLIENT)
>   HttpsConnector: code cleanup
>   HttpsConnector: add proxy support
> 
>  src/api2/node/apt.rs      |   8 +-
>  src/tools/async_io.rs     |  90 +++++++---
>  src/tools/http.rs         | 336 +++++++++++++++++++++++++++-----------
>  src/tools/subscription.rs |  10 +-
>  4 files changed, 316 insertions(+), 128 deletions(-)
> 

LGTM too, so applied, thanks!

@Dylan, please use this and Wolfgang's in-progress node config as new base
for the proxy feature. For the latter you may want for the next version,
or talk/coordinate with Wolfgang as we decided against implementing a direct
serde parser for now.




^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-04-21 14:39 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-21 11:16 [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Dietmar Maurer
2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 1/5] http: rename EitherStream to MaybeTlsStream Dietmar Maurer
2021-04-21 11:16 ` [pbs-devel] [RFC proxmox-backup 2/5] MaybeTlsStream: implement poll_write_vectored() Dietmar Maurer
2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 3/5] new http client implementation SimpleHttp (avoid static HTTP_CLIENT) Dietmar Maurer
2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 4/5] HttpsConnector: code cleanup Dietmar Maurer
2021-04-21 11:17 ` [pbs-devel] [RFC proxmox-backup 5/5] HttpsConnector: add proxy support Dietmar Maurer
2021-04-21 12:12 ` [pbs-devel] [RFC proxmox-backup 0/5] proxy support for HttpsConnector Wolfgang Bumiller
2021-04-21 14:33 ` [pbs-devel] applied-series: " Thomas Lamprecht

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal