Move to RusTLS, Static binary build, performance improvements.

This commit is contained in:
Ara Sadoyan
2025-05-19 20:20:15 +02:00
parent 0885ee0b7a
commit b33f1796e1
14 changed files with 922 additions and 447 deletions

View File

@@ -1,6 +1,9 @@
mod utils;
mod web;
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main() {
web::start::run();
}

View File

@@ -109,7 +109,7 @@ async fn consul_request(url: String, whitelist: Option<Vec<ServiceMapping>>, tok
Some(upstreams)
}
async fn get_by_http(url: String, token: Option<String>) -> Option<DashMap<String, (Vec<(String, u16, bool)>, AtomicUsize)>> {
async fn get_by_http(url: String, token: Option<String>) -> Option<DashMap<String, (Vec<(String, u16, bool, bool)>, AtomicUsize)>> {
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
if let Some(token) = token {
@@ -118,7 +118,7 @@ async fn get_by_http(url: String, token: Option<String>) -> Option<DashMap<Strin
let to = Duration::from_secs(1);
let u = client.get(url).timeout(to).send();
let mut values = Vec::new();
let upstreams: DashMap<String, (Vec<(String, u16, bool)>, AtomicUsize)> = DashMap::new();
let upstreams: DashMap<String, (Vec<(String, u16, bool, bool)>, AtomicUsize)> = DashMap::new();
match u.await {
Ok(r) => {
let jason = r.json::<Vec<Service>>().await;
@@ -127,7 +127,7 @@ async fn get_by_http(url: String, token: Option<String>) -> Option<DashMap<Strin
for service in whitelist {
let addr = service.tagged_addresses.get("lan_ipv4").unwrap().address.clone();
let prt = service.tagged_addresses.get("lan_ipv4").unwrap().port.clone();
let to_add = (addr, prt, false);
let to_add = (addr, prt, false, false);
values.push(to_add);
}
}

View File

@@ -2,7 +2,7 @@ use crate::utils::structs::{UpstreamsDashMap, UpstreamsIdMap};
use crate::utils::tools::*;
use dashmap::DashMap;
use log::{error, info, warn};
use reqwest::Client;
use reqwest::{Client, Version};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use std::time::Duration;
@@ -20,32 +20,50 @@ pub async fn hc2(upslist: Arc<UpstreamsDashMap>, fullist: Arc<UpstreamsDashMap>,
for val in fclone.iter() {
let host = val.key();
let inner = DashMap::new();
let mut _scheme: (String, u16, bool) = ("".to_string(), 0, false);
let mut _scheme: (String, u16, bool, bool) = ("".to_string(), 0, false, false);
for path_entry in val.value().iter() {
// let inner = DashMap::new();
let path = path_entry.key();
let mut innervec= Vec::new();
for k in path_entry.value().0 .iter().enumerate() {
let (ip, port, _ssl) = k.1;
let mut _pref = "";
let (ip, port, _ssl, _version) = k.1;
let mut _link = String::new();
let tls = detect_tls(ip, port).await;
match tls {
true => _pref = "https://",
false => _pref = "http://",
let mut is_h2 = false;
// if tls.1 == Some(Version::HTTP_11) {
// println!(" V1: ==> {:?}", tls.1)
// }else if tls.1 == Some(Version::HTTP_2) {
// is_h2 = true;
// println!(" V2: ==> {:?}", tls.1)
// }
if tls.1 == Some(Version::HTTP_2) {
is_h2 = true;
// println!(" V2: ==> {} ==> {:?}", tls.0, tls.1)
}
if _pref == "https://" {
_scheme = (ip.to_string(), *port, true);
}else {
_scheme = (ip.to_string(), *port, false);
match tls.0 {
true => _link = format!("https://{}:{}{}", ip, port, path),
false => _link = format!("http://{}:{}{}", ip, port, path),
}
let link = format!("{}{}:{}{}", _pref, ip, port, path);
let resp = http_request(link.as_str(), params.0, "").await;
match resp {
// if _pref == "https://" {
// _scheme = (ip.to_string(), *port, true);
// }else {
// _scheme = (ip.to_string(), *port, false);
// }
_scheme = (ip.to_string(), *port, tls.0, is_h2);
// let link = format!("{}{}:{}{}", _pref, ip, port, path);
let resp = http_request(_link.as_str(), params.0, "").await;
match resp.0 {
true => {
if resp.1 {
_scheme = (ip.to_string(), *port, tls.0, true);
}
innervec.push(_scheme.clone());
}
false => {
warn!("Dead Upstream : {}", link);
warn!("Dead Upstream : {}", _link);
}
}
}
@@ -73,12 +91,12 @@ pub async fn hc2(upslist: Arc<UpstreamsDashMap>, fullist: Arc<UpstreamsDashMap>,
}
#[allow(dead_code)]
async fn http_request(url: &str, method: &str, payload: &str) -> bool {
async fn http_request(url: &str, method: &str, payload: &str) -> (bool, bool) {
let client = Client::builder().danger_accept_invalid_certs(true).build().unwrap();
let timeout = Duration::from_secs(1);
if !["POST", "GET", "HEAD"].contains(&method) {
error!("Method {} not supported. Only GET|POST|HEAD are supported ", method);
return false;
return (false, false);
}
async fn send_request(client: &Client, method: &str, url: &str, payload: &str, timeout: Duration) -> Option<reqwest::Response> {
match method {
@@ -92,11 +110,12 @@ async fn http_request(url: &str, method: &str, payload: &str) -> bool {
match send_request(&client, method, url, payload, timeout).await {
Some(response) => {
let status = response.status().as_u16();
(99..499).contains(&status)
((99..499).contains(&status), false)
}
None => {
let fallback_url = url.replace("https", "http");
ping_grpc(&fallback_url).await
// let fallback_url = url.replace("https", "http");
// ping_grpc(&fallback_url).await
(ping_grpc(&url).await, true)
}
}
}
@@ -108,7 +127,10 @@ pub async fn ping_grpc(addr: &str) -> bool {
let endpoint = endpoint.timeout(Duration::from_secs(2));
match tokio::time::timeout(Duration::from_secs(3), endpoint.connect()).await {
Ok(Ok(_channel)) => true,
Ok(Ok(_channel)) => {
// println!("{:?} ==> {:?} ==> {}", endpoint, _channel, addr);
true
}
_ => false,
}
} else {
@@ -116,20 +138,17 @@ pub async fn ping_grpc(addr: &str) -> bool {
}
}
async fn detect_tls(ip: &str, port: &u16) -> bool {
async fn detect_tls(ip: &str, port: &u16) -> (bool, Option<Version>) {
let url = format!("https://{}:{}", ip, port);
let client = Client::builder()
.timeout(Duration::from_secs(2))
.danger_accept_invalid_certs(true) // skip cert validation for testing
.build()
.unwrap();
// let url = format!("{}:{}", ip, port);
let client = Client::builder().timeout(Duration::from_secs(2)).danger_accept_invalid_certs(true).build().unwrap();
match client.get(&url).send().await {
Ok(_) => true,
Ok(response) => (true, Some(response.version())),
Err(e) => {
if e.is_builder() || e.is_connect() || e.to_string().contains("tls") {
false
(false, None)
} else {
false
(false, None)
}
}
}

View File

@@ -87,7 +87,7 @@ pub fn load_configuration(d: &str, kind: &str) -> Option<Configuration> {
if let Some((ip, port_str)) = server.split_once(':') {
if let Ok(port) = port_str.parse::<u16>() {
// server_list.push((ip.to_string(), port, path_config.ssl));
server_list.push((ip.to_string(), port, true));
server_list.push((ip.to_string(), port, true, false));
}
}
}

View File

@@ -3,9 +3,9 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::AtomicUsize;
pub type UpstreamsDashMap = DashMap<String, DashMap<String, (Vec<(String, u16, bool)>, AtomicUsize)>>;
pub type UpstreamsDashMap = DashMap<String, DashMap<String, (Vec<(String, u16, bool, bool)>, AtomicUsize)>>;
pub type UpstreamsIdMap = DashMap<String, (String, u16, bool, bool)>;
pub type Headers = DashMap<String, DashMap<String, Vec<(String, String)>>>;
pub type UpstreamsIdMap = DashMap<String, (String, u16, bool)>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceMapping {

View File

@@ -16,8 +16,8 @@ pub fn print_upstreams(upstreams: &UpstreamsDashMap) {
let path = path_entry.key();
println!(" Path: {}", path);
for (ip, port, ssl) in path_entry.value().0.clone() {
println!(" ===> IP: {}, Port: {}, SSL: {}", ip, port, ssl);
for (ip, port, ssl, vers) in path_entry.value().0.clone() {
println!(" ===> IP: {}, Port: {}, SSL: {}, H2: {}", ip, port, ssl, vers);
}
}
}
@@ -139,7 +139,7 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
let hash = hasher.finalize();
let hex_hash = base16ct::lower::encode_string(&hash);
let hh = hex_hash[0..50].to_string();
cloned.insert(id, (hh.clone(), 0000, false));
cloned.insert(id, (hh.clone(), 0000, false, false));
cloned.insert(hh, x.to_owned());
}
new_inner_map.insert(path.clone(), new_vec);

View File

@@ -4,12 +4,12 @@ use std::sync::atomic::Ordering;
#[async_trait]
pub trait GetHost {
fn get_host(&self, peer: &str, path: &str, backend_id: Option<&str>) -> Option<(String, u16, bool)>;
fn get_host(&self, peer: &str, path: &str, backend_id: Option<&str>) -> Option<(String, u16, bool, bool)>;
fn get_header(&self, peer: &str, path: &str) -> Option<Vec<(String, String)>>;
}
#[async_trait]
impl GetHost for LB {
fn get_host(&self, peer: &str, path: &str, backend_id: Option<&str>) -> Option<(String, u16, bool)> {
fn get_host(&self, peer: &str, path: &str, backend_id: Option<&str>) -> Option<(String, u16, bool, bool)> {
if let Some(b) = backend_id {
if let Some(bb) = self.ump_byid.get(b) {
// println!("BIB :===> {:?}", Some(bb.value()));
@@ -19,7 +19,7 @@ impl GetHost for LB {
let host_entry = self.ump_upst.get(peer)?;
let mut current_path = path.to_string();
let mut best_match: Option<(String, u16, bool)> = None;
let mut best_match: Option<(String, u16, bool, bool)> = None;
loop {
if let Some(entry) = host_entry.get(&current_path) {
let (servers, index) = entry.value();

View File

@@ -33,52 +33,6 @@ impl ProxyHttp for LB {
fn new_ctx(&self) -> Self::CTX {
Context { backend_id: String::new() }
}
async fn upstream_peer(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<Box<HttpPeer>> {
let host_name = return_header_host(&session);
match host_name {
Some(host) => {
// session.req_header_mut().headers.insert("X-Host-Name", host.to_string().parse().unwrap());
let mut backend_id = None;
if self.extraparams.load().stickysessions {
if let Some(cookies) = session.req_header().headers.get("cookie") {
if let Ok(cookie_str) = cookies.to_str() {
for cookie in cookie_str.split(';') {
let trimmed = cookie.trim();
if let Some(value) = trimmed.strip_prefix("backend_id=") {
backend_id = Some(value);
break;
}
}
}
}
}
let ddr = self.get_host(host, host, backend_id);
match ddr {
Some((host, port, ssl)) => {
// let mut peer = Box::new(HttpPeer::new((host, port), ssl, String::new()));
let mut peer = Box::new(HttpPeer::new((host.clone(), port.clone()), ssl, String::new()));
if session.is_http2() {
peer.options.alpn = ALPN::H2;
}
_ctx.backend_id = format!("{}:{}:{}", host.clone(), port.clone(), ssl);
Ok(peer)
}
None => {
warn!("Upstream not found. Host: {:?}, Path: {}", host, session.req_header().uri);
Ok(return_no_host(&self.config.local_server))
}
}
}
None => {
warn!("Upstream not found. Host: {:?}, Path: {}", host_name, session.req_header().uri);
Ok(return_no_host(&self.config.local_server))
}
}
}
async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
if let Some(auth) = self.extraparams.load().authentication.get("authorization") {
let authenticated = authenticate(&auth.value(), &session);
@@ -95,6 +49,58 @@ impl ProxyHttp for LB {
// };
Ok(false)
}
async fn upstream_peer(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<Box<HttpPeer>> {
let host_name = return_header_host(&session);
match host_name {
Some(hostname) => {
// session.req_header_mut().headers.insert("X-Host-Name", host.to_string().parse().unwrap());
let mut backend_id = None;
if self.extraparams.load().stickysessions {
if let Some(cookies) = session.req_header().headers.get("cookie") {
if let Ok(cookie_str) = cookies.to_str() {
for cookie in cookie_str.split(';') {
let trimmed = cookie.trim();
if let Some(value) = trimmed.strip_prefix("backend_id=") {
backend_id = Some(value);
break;
}
}
}
}
}
let ddr = self.get_host(hostname, hostname, backend_id);
match ddr {
Some((address, port, ssl, is_h2)) => {
let mut peer = Box::new(HttpPeer::new((address.clone(), port.clone()), ssl, String::new()));
// if session.is_http2() {
if is_h2 {
peer.options.alpn = ALPN::H2;
}
if ssl {
peer.sni = hostname.to_string();
peer.options.verify_cert = false;
peer.options.verify_hostname = false;
}
// println!(" ==> {} ==> {} => {} => {:?}", hostname, address.as_str(), peer.options.alpn, is_h2);
_ctx.backend_id = format!("{}:{}:{}", address.clone(), port.clone(), ssl);
Ok(peer)
}
None => {
warn!("Upstream not found. Host: {:?}, Path: {}", hostname, session.req_header().uri);
Ok(return_no_host(&self.config.local_server))
}
}
}
None => {
warn!("Upstream not found. Host: {:?}, Path: {}", host_name, session.req_header().uri);
Ok(return_no_host(&self.config.local_server))
}
}
}
async fn upstream_request_filter(&self, _session: &mut Session, _upstream_request: &mut RequestHeader, _ctx: &mut Self::CTX) -> Result<()> {
let clientip = _session.client_addr();
match clientip {
@@ -118,7 +124,6 @@ impl ProxyHttp for LB {
async fn response_filter(&self, _session: &mut Session, _upstream_response: &mut ResponseHeader, _ctx: &mut Self::CTX) -> Result<()> {
// _upstream_response.insert_header("X-Proxied-From", "Fooooooooooooooo").unwrap();
if self.extraparams.load().stickysessions {
let backend_id = _ctx.backend_id.clone();
if let Some(bid) = self.ump_byid.get(&backend_id) {

View File

@@ -5,10 +5,12 @@ use dashmap::DashMap;
use log::info;
use pingora_core::prelude::{background_service, Opt};
use pingora_core::server::Server;
use rustls::crypto::ring::default_provider;
use std::env;
use std::sync::Arc;
pub fn run() {
default_provider().install_default().expect("Failed to install rustls crypto provider");
let parameters = Some(Opt::parse_args()).unwrap();
let file = parameters.conf.clone().unwrap();
let maincfg = crate::utils::parceyaml::parce_main_config(file.as_str());