refact: tls, native-tls fallback rustls-tls (#13263)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-11-03 23:21:01 +08:00
committed by GitHub
parent 44a28aa5bd
commit 910dcf2036
70 changed files with 1184 additions and 318 deletions

View File

@@ -1,5 +1,5 @@
use super::HbbHttpResponse;
use crate::hbbs_http::create_http_client;
use crate::hbbs_http::create_http_client_with_url;
use hbb_common::{config::LocalConfig, log, ResultType};
use reqwest::blocking::Client;
use serde_derive::{Deserialize, Serialize};
@@ -104,7 +104,7 @@ pub struct AuthBody {
}
pub struct OidcSession {
client: Client,
client: Option<Client>,
state_msg: &'static str,
failed_msg: String,
code_url: Option<OidcAuthUrl>,
@@ -131,7 +131,7 @@ impl Default for UserStatus {
impl OidcSession {
fn new() -> Self {
Self {
client: create_http_client(),
client: None,
state_msg: REQUESTING_ACCOUNT_AUTH,
failed_msg: "".to_owned(),
code_url: None,
@@ -142,24 +142,36 @@ impl OidcSession {
}
}
fn ensure_client(api_server: &str) {
let mut write_guard = OIDC_SESSION.write().unwrap();
if write_guard.client.is_none() {
// This URL is used to detect the appropriate TLS implementation for the server.
let login_option_url = format!("{}/api/login-options", &api_server);
let client = create_http_client_with_url(&login_option_url);
write_guard.client = Some(client);
}
}
fn auth(
api_server: &str,
op: &str,
id: &str,
uuid: &str,
) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
let resp = OIDC_SESSION
.read()
.unwrap()
.client
.post(format!("{}/api/oidc/auth", api_server))
.json(&serde_json::json!({
"op": op,
"id": id,
"uuid": uuid,
"deviceInfo": crate::ui_interface::get_login_device_info(),
}))
.send()?;
Self::ensure_client(api_server);
let resp = if let Some(client) = &OIDC_SESSION.read().unwrap().client {
client
.post(format!("{}/api/oidc/auth", api_server))
.json(&serde_json::json!({
"op": op,
"id": id,
"uuid": uuid,
"deviceInfo": crate::ui_interface::get_login_device_info(),
}))
.send()?
} else {
hbb_common::bail!("http client not initialized");
};
let status = resp.status();
match resp.try_into() {
Ok(v) => Ok(v),
@@ -179,13 +191,12 @@ impl OidcSession {
&format!("{}/api/oidc/auth-query", api_server),
&[("code", code), ("id", id), ("uuid", uuid)],
)?;
Ok(OIDC_SESSION
.read()
.unwrap()
.client
.get(url)
.send()?
.try_into()?)
Self::ensure_client(api_server);
if let Some(client) = &OIDC_SESSION.read().unwrap().client {
Ok(client.get(url).send()?.try_into()?)
} else {
hbb_common::bail!("http client not initialized")
}
}
fn reset(&mut self) {

View File

@@ -1,4 +1,4 @@
use super::create_http_client_async;
use super::create_http_client_async_with_url;
use hbb_common::{
bail,
lazy_static::lazy_static,
@@ -132,7 +132,7 @@ async fn do_download(
auto_del_dur: Option<Duration>,
mut rx_cancel: UnboundedReceiver<()>,
) -> ResultType<bool> {
let client = create_http_client_async();
let client = create_http_client_async_with_url(&url).await;
let mut is_all_downloaded = false;
tokio::select! {

View File

@@ -1,23 +1,49 @@
use hbb_common::config::Config;
use hbb_common::log::info;
use hbb_common::proxy::{Proxy, ProxyScheme};
use reqwest::blocking::Client as SyncClient;
use reqwest::Client as AsyncClient;
use hbb_common::{
async_recursion::async_recursion,
config::{Config, Socks5Server},
log::{self, info},
proxy::{Proxy, ProxyScheme},
tls::{
get_cached_tls_accept_invalid_cert, get_cached_tls_type, is_plain, upsert_tls_cache,
TlsType,
},
};
use reqwest::{blocking::Client as SyncClient, Client as AsyncClient};
macro_rules! configure_http_client {
($builder:expr, $Client: ty) => {{
($builder:expr, $tls_type:expr, $danger_accept_invalid_cert:expr, $Client: ty) => {{
// https://github.com/rustdesk/rustdesk/issues/11569
// https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.no_proxy
let mut builder = $builder.no_proxy();
#[cfg(any(target_os = "android", target_os = "ios"))]
match hbb_common::verifier::client_config() {
Ok(client_config) => {
builder = builder.use_preconfigured_tls(client_config);
match $tls_type {
TlsType::Plain => {}
TlsType::NativeTls => {
builder = builder.use_native_tls();
if $danger_accept_invalid_cert {
builder = builder.danger_accept_invalid_certs(true);
}
}
Err(e) => {
hbb_common::log::error!("Failed to get client config: {}", e);
TlsType::Rustls => {
#[cfg(any(target_os = "android", target_os = "ios"))]
match hbb_common::verifier::client_config($danger_accept_invalid_cert) {
Ok(client_config) => {
builder = builder.use_preconfigured_tls(client_config);
}
Err(e) => {
hbb_common::log::error!("Failed to get client config: {}", e);
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
builder = builder.use_rustls_tls();
if $danger_accept_invalid_cert {
builder = builder.danger_accept_invalid_certs(true);
}
}
}
}
let client = if let Some(conf) = Config::get_socks() {
let proxy_result = Proxy::from_conf(&conf, None);
@@ -70,12 +96,241 @@ macro_rules! configure_http_client {
}};
}
pub fn create_http_client() -> SyncClient {
pub fn create_http_client(tls_type: TlsType, danger_accept_invalid_cert: bool) -> SyncClient {
let builder = SyncClient::builder();
configure_http_client!(builder, SyncClient)
configure_http_client!(builder, tls_type, danger_accept_invalid_cert, SyncClient)
}
pub fn create_http_client_async() -> AsyncClient {
pub fn create_http_client_async(
tls_type: TlsType,
danger_accept_invalid_cert: bool,
) -> AsyncClient {
let builder = AsyncClient::builder();
configure_http_client!(builder, AsyncClient)
configure_http_client!(builder, tls_type, danger_accept_invalid_cert, AsyncClient)
}
pub fn get_url_for_tls<'a>(url: &'a str, proxy_conf: &'a Option<Socks5Server>) -> &'a str {
if is_plain(url) {
if let Some(conf) = proxy_conf {
if conf.proxy.starts_with("https://") {
return &conf.proxy;
}
}
}
url
}
pub fn create_http_client_with_url(url: &str) -> SyncClient {
let proxy_conf = Config::get_socks();
let tls_url = get_url_for_tls(url, &proxy_conf);
let tls_type = get_cached_tls_type(tls_url);
let is_tls_type_cached = tls_type.is_some();
let tls_type = tls_type.unwrap_or(TlsType::Rustls);
let tls_danger_accept_invalid_cert = get_cached_tls_accept_invalid_cert(tls_url);
create_http_client_with_url_(
url,
tls_url,
tls_type,
is_tls_type_cached,
tls_danger_accept_invalid_cert,
tls_danger_accept_invalid_cert,
)
}
fn create_http_client_with_url_(
url: &str,
tls_url: &str,
tls_type: TlsType,
is_tls_type_cached: bool,
danger_accept_invalid_cert: Option<bool>,
original_danger_accept_invalid_cert: Option<bool>,
) -> SyncClient {
let mut client = create_http_client(tls_type, danger_accept_invalid_cert.unwrap_or(false));
if is_tls_type_cached && original_danger_accept_invalid_cert.is_some() {
return client;
}
if let Err(e) = client.head(url).send() {
if e.is_request() {
match (tls_type, is_tls_type_cached, danger_accept_invalid_cert) {
(TlsType::Rustls, _, None) => {
log::warn!(
"Failed to connect to server {} with rustls-tls: {:?}, trying accept invalid cert",
tls_url,
e
);
client = create_http_client_with_url_(
url,
tls_url,
tls_type,
is_tls_type_cached,
Some(true),
original_danger_accept_invalid_cert,
);
}
(TlsType::Rustls, false, Some(_)) => {
log::warn!(
"Failed to connect to server {} with rustls-tls: {:?}, trying native-tls",
tls_url,
e
);
client = create_http_client_with_url_(
url,
tls_url,
TlsType::NativeTls,
is_tls_type_cached,
original_danger_accept_invalid_cert,
original_danger_accept_invalid_cert,
);
}
(TlsType::NativeTls, _, None) => {
log::warn!(
"Failed to connect to server {} with native-tls: {:?}, trying accept invalid cert",
tls_url,
e
);
client = create_http_client_with_url_(
url,
tls_url,
tls_type,
is_tls_type_cached,
Some(true),
original_danger_accept_invalid_cert,
);
}
_ => {
log::error!(
"Failed to connect to server {} with {:?}, err: {:?}.",
tls_url,
tls_type,
e
);
}
}
} else {
log::warn!(
"Failed to connect to server {} with {:?}, err: {}.",
tls_url,
tls_type,
e
);
}
} else {
log::info!(
"Successfully connected to server {} with {:?}",
tls_url,
tls_type
);
upsert_tls_cache(
tls_url,
tls_type,
danger_accept_invalid_cert.unwrap_or(false),
);
}
client
}
pub async fn create_http_client_async_with_url(url: &str) -> AsyncClient {
let proxy_conf = Config::get_socks();
let tls_url = get_url_for_tls(url, &proxy_conf);
let tls_type = get_cached_tls_type(tls_url);
let is_tls_type_cached = tls_type.is_some();
let tls_type = tls_type.unwrap_or(TlsType::Rustls);
let danger_accept_invalid_cert = get_cached_tls_accept_invalid_cert(tls_url);
create_http_client_async_with_url_(
url,
tls_url,
tls_type,
is_tls_type_cached,
danger_accept_invalid_cert,
danger_accept_invalid_cert,
)
.await
}
#[async_recursion]
async fn create_http_client_async_with_url_(
url: &str,
tls_url: &str,
tls_type: TlsType,
is_tls_type_cached: bool,
danger_accept_invalid_cert: Option<bool>,
original_danger_accept_invalid_cert: Option<bool>,
) -> AsyncClient {
let mut client =
create_http_client_async(tls_type, danger_accept_invalid_cert.unwrap_or(false));
if is_tls_type_cached && original_danger_accept_invalid_cert.is_some() {
return client;
}
if let Err(e) = client.head(url).send().await {
match (tls_type, is_tls_type_cached, danger_accept_invalid_cert) {
(TlsType::Rustls, _, None) => {
log::warn!(
"Failed to connect to server {} with rustls-tls: {:?}, trying accept invalid cert",
tls_url,
e
);
client = create_http_client_async_with_url_(
url,
tls_url,
tls_type,
is_tls_type_cached,
Some(true),
original_danger_accept_invalid_cert,
)
.await;
}
(TlsType::Rustls, false, Some(_)) => {
log::warn!(
"Failed to connect to server {} with rustls-tls: {:?}, trying native-tls",
tls_url,
e
);
client = create_http_client_async_with_url_(
url,
tls_url,
TlsType::NativeTls,
is_tls_type_cached,
original_danger_accept_invalid_cert,
original_danger_accept_invalid_cert,
)
.await;
}
(TlsType::NativeTls, _, None) => {
log::warn!(
"Failed to connect to server {} with native-tls: {:?}, trying accept invalid cert",
tls_url,
e
);
client = create_http_client_async_with_url_(
url,
tls_url,
tls_type,
is_tls_type_cached,
Some(true),
original_danger_accept_invalid_cert,
)
.await;
}
_ => {
log::error!(
"Failed to connect to server {} with {:?}, err: {:?}.",
tls_url,
tls_type,
e
);
}
}
} else {
log::info!(
"Successfully connected to server {} with {:?}",
tls_url,
tls_type
);
upsert_tls_cache(
tls_url,
tls_type,
danger_accept_invalid_cert.unwrap_or(false),
);
}
client
}

View File

@@ -1,4 +1,4 @@
use crate::hbbs_http::create_http_client;
use crate::hbbs_http::create_http_client_with_url;
use bytes::Bytes;
use hbb_common::{bail, config::Config, lazy_static, log, ResultType};
use reqwest::blocking::{Body, Client};
@@ -25,51 +25,57 @@ pub fn is_enable() -> bool {
}
pub fn run(rx: Receiver<RecordState>) {
let mut uploader = RecordUploader {
client: create_http_client(),
api_server: crate::get_api_server(
std::thread::spawn(move || {
let api_server = crate::get_api_server(
Config::get_option("api-server"),
Config::get_option("custom-rendezvous-server"),
),
filepath: Default::default(),
filename: Default::default(),
upload_size: Default::default(),
running: Default::default(),
last_send: Instant::now(),
};
std::thread::spawn(move || loop {
if let Err(e) = match rx.recv() {
Ok(state) => match state {
RecordState::NewFile(filepath) => uploader.handle_new_file(filepath),
RecordState::NewFrame => {
if uploader.running {
uploader.handle_frame(false)
} else {
Ok(())
);
// This URL is used for TLS connectivity testing and fallback detection.
let login_option_url = format!("{}/api/login-options", &api_server);
let client = create_http_client_with_url(&login_option_url);
let mut uploader = RecordUploader {
client,
api_server,
filepath: Default::default(),
filename: Default::default(),
upload_size: Default::default(),
running: Default::default(),
last_send: Instant::now(),
};
loop {
if let Err(e) = match rx.recv() {
Ok(state) => match state {
RecordState::NewFile(filepath) => uploader.handle_new_file(filepath),
RecordState::NewFrame => {
if uploader.running {
uploader.handle_frame(false)
} else {
Ok(())
}
}
}
RecordState::WriteTail => {
if uploader.running {
uploader.handle_tail()
} else {
Ok(())
RecordState::WriteTail => {
if uploader.running {
uploader.handle_tail()
} else {
Ok(())
}
}
}
RecordState::RemoveFile => {
if uploader.running {
uploader.handle_remove()
} else {
Ok(())
RecordState::RemoveFile => {
if uploader.running {
uploader.handle_remove()
} else {
Ok(())
}
}
},
Err(e) => {
log::trace!("upload thread stop: {}", e);
break;
}
},
Err(e) => {
log::trace!("upload thread stop: {}", e);
break;
} {
uploader.running = false;
log::error!("upload stop: {}", e);
}
} {
uploader.running = false;
log::error!("upload stop: {}", e);
}
});
}