incoming ip forwarding and wildcard domain matching

This commit is contained in:
MeexReay 2024-11-13 00:20:52 +03:00
parent e59dbd28fd
commit eab6d83a5f
5 changed files with 113 additions and 32 deletions

View File

@ -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"]

View File

@ -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

View File

@ -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)

View File

@ -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<IpForwarding> {
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<SiteConfig> = 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);
}
}

View File

@ -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<u8> = 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()?;