add gatunad server and winforms client v1 (TCP-only)
Raw-Ethernet tunnel over ethertype 0x6969 to bypass WFP killswitches. Server (Rust, AF_PACKET + classic BPF) relays TCP to 127.0.0.1 services. Client (.NET 8 WinForms, SharpPcap/Npcap) discovers upstreams and exposes local loopback listeners. Wire protocol: 6-byte header, 7 frame types, MANIFEST with labeled upstreams. UDP types reserved, unimplemented.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
//! Upstream table: cmdline parsing and MANIFEST entry construction.
|
||||
|
||||
use crate::frame::{UpstreamEntry, PROTO_TCP, PROTO_UDP};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user