From eab6d83a5fbbc663dc8880f4d7d77c2a71c12ed8 Mon Sep 17 00:00:00 2001 From: MeexReay Date: Wed, 13 Nov 2024 00:20:52 +0300 Subject: [PATCH] incoming ip forwarding and wildcard domain matching --- Cargo.toml | 5 +-- README.md | 21 ++++++----- conf.yml | 7 ++-- src/flowgate/config.rs | 33 +++++++++++++----- src/flowgate/server.rs | 79 ++++++++++++++++++++++++++++++++++++------ 5 files changed, 113 insertions(+), 32 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 454d07d..f4a9435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,14 @@ version = "0.1.2" edition = "2021" [dependencies] -openssl = { version = "0.10.66", optional = true } -rustls = { version = "0.23.14", optional = true } +openssl = { version = "0.10.68", optional = true } +rustls = { version = "0.23.16", optional = true } rustls-pemfile = { version = "2.2.0", optional = true } serde_yml = "0.0.12" log = "0.4.22" colog = "1.3.0" threadpool = "1.8.1" +wildcard_ex = "0.1.2" [features] default = ["use-openssl"] diff --git a/README.md b/README.md index 3256bf9..7fca97c 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,17 @@ TODO: Default `conf.yml`: ```yml -http_host: localhost:80 # Http server host -https_host: localhost:443 # Https server host +http_host: localhost:80 # Http server host +https_host: localhost:443 # Https server host threadpool_size: 10 # Threadpool size (count of threads that accept requests) (optional, default - 10) connection_timeout: 10 # Read and write timeout of connections in seconds (optional, default - 10) +incoming_ip_forwarding: none # Read IP forwarding on incoming connections (optional, default - none) sites: - - domain: localhost # Site domain + - domain: localhost # Site domain (use wildcard matching) host: localhost:8080 # Http server host - ip_forwarding: simple # IP forwarding type (header/simple) (optional, default - header) + ip_forwarding: simple # IP forwarding method type (optional, default - header) enable_keep_alive: true # Enable keep-alive connections (optional, default - true) support_keep_alive: true # Does server supports keep-alive connections (optional, default - true) # ssl_cert: "/path/to/public/certificate.txt" # Ssl public certificate file (optional) @@ -34,10 +35,14 @@ sites: ### IP forwaring types -- Simple:\ - Appends `ip:port\n` to the request -- Header:\ - Adds header `X-Real-IP: ip:port` to the request +- None (`none`):\ + Do nothing +- Modern (`modern`):\ + Appends encoded to bytes ip to the beginning of the request +- Simple (`simple`):\ + Appends `ip:port\n` to the beginning of the request +- Header (`header[:HEADER_NAME]`):\ + Adds header `HEADER_NAME: ip:port` to the request ## How to run diff --git a/conf.yml b/conf.yml index dc06538..e9d2be2 100644 --- a/conf.yml +++ b/conf.yml @@ -3,12 +3,13 @@ https_host: localhost:443 # Https server host threadpool_size: 10 # Threadpool size (count of threads that accept requests) (optional, default - 10) connection_timeout: 10 # Read and write timeout of connections in seconds (optional, default - 10) +incoming_ip_forwarding: none # Read IP forwarding on incoming connections (optional, default - none) sites: - - domain: localhost # Site domain + - domain: localhost # Site domain (use wildcard matching) host: localhost:8080 # Http server host - ip_forwarding: simple # IP forwarding type (header/simple) (optional, default - header) + ip_forwarding: simple # IP forwarding method type (optional, default - header) enable_keep_alive: true # Enable keep-alive connections (optional, default - true) support_keep_alive: true # Does server supports keep-alive connections (optional, default - true) # ssl_cert: "/path/to/public/certificate.txt" # Ssl public certificate file (optional) - # ssl_key: "/path/to/private/key.txt" # Ssl private key file (optional) + # ssl_key: "/path/to/private/key.txt" # Ssl private key file (optional) \ No newline at end of file diff --git a/src/flowgate/config.rs b/src/flowgate/config.rs index 3a15563..f2630b3 100644 --- a/src/flowgate/config.rs +++ b/src/flowgate/config.rs @@ -1,6 +1,7 @@ use std::{fs, net::TcpStream, sync::Arc, time::Duration}; use serde_yml::{Number, Value}; +use wildcard_ex::is_match_simple; use super::SslCert; @@ -20,18 +21,26 @@ impl SiteConfig { } } -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum IpForwarding { Simple, - Header + Header(String), + Modern, + None } impl IpForwarding { pub fn from_name(name: &str) -> Option { match name { + "none" => Some(IpForwarding::None), "simple" => Some(IpForwarding::Simple), - "header" => Some(IpForwarding::Header), - _ => None + "modern" => Some(IpForwarding::Modern), + "header" => Some(IpForwarding::Header(String::from("X-Real-IP"))), + name => if name.starts_with("header:") { + Some(IpForwarding::Header(name[7..].to_string())) + } else { + None + } } } } @@ -42,7 +51,8 @@ pub struct Config { pub http_host: String, pub https_host: String, pub threadpool_size: usize, - pub connection_timeout: Duration + pub connection_timeout: Duration, + pub incoming_ip_forwarding: IpForwarding } impl Config { @@ -57,6 +67,10 @@ impl Config { .unwrap_or(&Value::Number(Number::from(10))).as_u64()? as usize; let connection_timeout = Duration::from_secs(doc.get("connection_timeout") .unwrap_or(&Value::Number(Number::from(10))).as_u64()?); + let incoming_ip_forwarding = doc.get("incoming_ip_forwarding") + .map(|o| o.as_str()).flatten() + .map(|o| IpForwarding::from_name(o)).flatten() + .unwrap_or(IpForwarding::None); let mut sites: Vec = Vec::new(); @@ -88,7 +102,7 @@ impl Config { ip_forwarding: s.get("ip_forwarding") .map(|o| o.as_str()).flatten() .map(|o| IpForwarding::from_name(o)).flatten() - .unwrap_or(IpForwarding::Header), + .unwrap_or(IpForwarding::Header("X-Real-IP".to_string())), }; sites.push(site); @@ -101,13 +115,14 @@ impl Config { http_host, https_host, threadpool_size, - connection_timeout - }) + connection_timeout, + incoming_ip_forwarding + }.clone()) } pub fn get_site(&self, domain: &str) -> Option<&SiteConfig> { for i in self.sites.as_ref() { - if i.domain == domain { + if is_match_simple(&i.domain, domain) { return Some(i); } } diff --git a/src/flowgate/server.rs b/src/flowgate/server.rs index 75df89e..4de752d 100644 --- a/src/flowgate/server.rs +++ b/src/flowgate/server.rs @@ -1,9 +1,5 @@ use std::{ - io::{Read, Write}, - net::{SocketAddr, TcpListener, TcpStream}, - sync::Arc, - thread, - time::Duration + io::{Read, Write}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, TcpStream}, str::FromStr, sync::Arc, thread, time::Duration }; use log::info; @@ -216,6 +212,48 @@ impl FlowgateServer { https: bool, connected: Option<(TcpStream, SiteConfig, bool, String)> ) -> Option<(TcpStream, SiteConfig, bool, String)> { + let mut addr = addr; + + match &config.incoming_ip_forwarding { + IpForwarding::Simple => { + let mut header = Vec::new(); + + { + let mut buf = [0; 1]; + + while let Ok(1) = stream.read(&mut buf) { + let byte = buf[0]; + if byte == b'\n' { break } + header.push(byte); + } + } + + addr = SocketAddr::from_str(&String::from_utf8(header).ok()?).ok()?; + }, + IpForwarding::Modern => { + let mut ipver = [0; 1]; + stream.read(&mut ipver).ok()?; + addr = match ipver[0] { + 0x01 => { + let mut octets = [0; 4]; + stream.read(&mut octets).ok()?; + let mut port = [0; 2]; + stream.read(&mut port).ok()?; + let port = u16::from_be_bytes(port); + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(octets), port)) + }, 0x02 => { + let mut octets = [0; 16]; + stream.read(&mut octets).ok()?; + let mut port = [0; 2]; + stream.read(&mut port).ok()?; + let port = u16::from_be_bytes(port); + SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(octets), port, 0, 0)) + }, _ => { return None }, + }; + }, + _ => { } + } + let mut head = Vec::new(); { @@ -254,6 +292,12 @@ impl FlowgateServer { .filter(|l| l.contains(": ")) .map(|l| l.split_once(": ").unwrap()) .collect(); + + if let IpForwarding::Header(header) = &config.incoming_ip_forwarding { + if let Some(ip) = headers.iter().find(|o| o.0 == header).map(|o| o.1) { + addr = SocketAddr::from_str(ip).ok()?; + } + } let mut connected: (TcpStream, SiteConfig, bool, String) = if connected.is_none() { let mut host = String::new(); @@ -281,21 +325,22 @@ impl FlowgateServer { .map(|o| o.1.parse().ok()) .flatten() .unwrap_or(0usize); - + let mut reqbuf: Vec = Vec::new(); - match connected.1.ip_forwarding { - IpForwarding::Header => { + match &connected.1.ip_forwarding { + IpForwarding::Header(header) => { reqbuf.append(&mut status.to_string().as_bytes().to_vec()); reqbuf.append(&mut b"\r\n".to_vec()); for (key, value) in &headers { - if *key == "X-Real-IP" { continue } + if *key == header { continue } reqbuf.append(&mut key.to_string().as_bytes().to_vec()); reqbuf.append(&mut b": ".to_vec()); reqbuf.append(&mut value.to_string().as_bytes().to_vec()); reqbuf.append(&mut b"\r\n".to_vec()); } - reqbuf.append(&mut b"X-Real-IP: ".to_vec()); + reqbuf.append(&mut header.as_bytes().to_vec()); + reqbuf.append(&mut b": ".to_vec()); reqbuf.append(&mut addr.to_string().as_bytes().to_vec()); reqbuf.append(&mut b"\r\n\r\n".to_vec()); }, @@ -305,6 +350,20 @@ impl FlowgateServer { reqbuf.append(&mut head.clone()); reqbuf.append(&mut b"\r\n\r\n".to_vec()); }, + IpForwarding::Modern => { + reqbuf.push(if addr.is_ipv4() { 0x01 } else { 0x02 }); + match addr.ip() { + IpAddr::V4(ip) => { + reqbuf.append(&mut ip.octets().to_vec()); + }, IpAddr::V6(ip) => { + reqbuf.append(&mut ip.octets().to_vec()); + } + } + reqbuf.append(&mut addr.port().to_be_bytes().to_vec()); + reqbuf.append(&mut head.clone()); + reqbuf.append(&mut b"\r\n\r\n".to_vec()); + }, + IpForwarding::None => { } } connected.0.write_all(&reqbuf).ok()?;