2026-08-12 18:20:51 +00:00
|
|
|
//! Upstream table: cmdline parsing and MANIFEST entry construction.
|
|
|
|
|
|
2026-08-13 08:08:05 +00:00
|
|
|
use crate::frame::{Frame, UpstreamEntry, PROTO_TCP, PROTO_UDP};
|
|
|
|
|
|
|
|
|
|
/// Read the system hostname via `gethostname(2)`.
|
|
|
|
|
pub fn get_hostname() -> String {
|
|
|
|
|
let mut buf = [0u8; 256];
|
|
|
|
|
let ret = unsafe {
|
|
|
|
|
libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf.len())
|
|
|
|
|
};
|
|
|
|
|
if ret == 0 {
|
|
|
|
|
let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
|
|
|
|
|
String::from_utf8_lossy(&buf[..len]).into_owned()
|
|
|
|
|
} else {
|
|
|
|
|
String::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-12 18:20:51 +00:00
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
2026-08-13 07:44:02 +00:00
|
|
|
#[allow(dead_code)]
|
2026-08-12 18:20:51 +00:00
|
|
|
pub enum Proto {
|
|
|
|
|
Tcp,
|
|
|
|
|
Udp,
|
|
|
|
|
}
|
|
|
|
|
impl Proto {
|
|
|
|
|
pub fn as_u8(self) -> u8 {
|
|
|
|
|
match self {
|
|
|
|
|
Proto::Tcp => PROTO_TCP,
|
|
|
|
|
Proto::Udp => PROTO_UDP,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct Upstream {
|
|
|
|
|
pub id: u8,
|
|
|
|
|
pub proto: Proto,
|
|
|
|
|
pub port: u16,
|
|
|
|
|
pub label: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct UpstreamTable(pub Vec<Upstream>);
|
|
|
|
|
|
|
|
|
|
impl UpstreamTable {
|
|
|
|
|
pub fn get(&self, id: u8) -> Option<&Upstream> {
|
|
|
|
|
self.0.iter().find(|u| u.id == id)
|
|
|
|
|
}
|
|
|
|
|
pub fn entries(&self) -> Vec<UpstreamEntry> {
|
|
|
|
|
self.0
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|u| UpstreamEntry {
|
|
|
|
|
id: u.id,
|
|
|
|
|
proto: u.proto.as_u8(),
|
|
|
|
|
port: u.port,
|
|
|
|
|
label: u.label.clone(),
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a single `PORT[:label]` argument. v1 only accepts bare integers or
|
|
|
|
|
/// `int:label`; an explicit `proto:` prefix is reserved for the UDP extension.
|
|
|
|
|
pub fn parse_port_arg(id: u8, s: &str) -> Result<Upstream, String> {
|
|
|
|
|
let (port_str, label) = match s.split_once(':') {
|
|
|
|
|
Some((p, l)) => (p, Some(l.to_string())),
|
|
|
|
|
None => (s, None),
|
|
|
|
|
};
|
|
|
|
|
let port: u16 = port_str
|
|
|
|
|
.parse()
|
|
|
|
|
.map_err(|_| format!("invalid port value: {port_str}"))?;
|
|
|
|
|
if port == 0 {
|
|
|
|
|
return Err(format!("port must be > 0: {s}"));
|
|
|
|
|
}
|
|
|
|
|
if let Some(l) = &label {
|
|
|
|
|
if l.is_empty() {
|
|
|
|
|
return Err(format!("empty label: {s}"));
|
|
|
|
|
}
|
|
|
|
|
if l.len() > 255 {
|
|
|
|
|
return Err(format!("label too long (max 255 bytes): {s}"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(Upstream { id, proto: Proto::Tcp, port, label })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn build_table(args: &[String]) -> Result<UpstreamTable, String> {
|
|
|
|
|
if args.is_empty() {
|
|
|
|
|
return Err("at least one PORT is required".into());
|
|
|
|
|
}
|
|
|
|
|
if args.len() > 255 {
|
|
|
|
|
return Err("too many upstreams (max 255)".into());
|
|
|
|
|
}
|
|
|
|
|
let mut v = Vec::with_capacity(args.len());
|
|
|
|
|
for (i, a) in args.iter().enumerate() {
|
|
|
|
|
let id = (i + 1) as u8;
|
|
|
|
|
v.push(parse_port_arg(id, a)?);
|
|
|
|
|
}
|
|
|
|
|
Ok(UpstreamTable(v))
|
|
|
|
|
}
|