announce server hostname in MANIFEST, display in client
Server reads its hostname via gethostname(2) and includes it as a length-prefixed UTF-8 string at the start of the MANIFEST payload. Client displays 'Server: <hostname> — <MAC>' in a label above the upstream list. Both sides updated for the new MANIFEST format: [hostname_len:1][hostname:N][entries...] Protocol version unchanged (still 1); MANIFEST payload layout change is backward-incompatible but both sides ship together.
This commit is contained in:
+24
-11
@@ -39,7 +39,7 @@ pub struct UpstreamEntry {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Frame {
|
||||
Discover,
|
||||
Manifest(Vec<UpstreamEntry>),
|
||||
Manifest { hostname: String, entries: Vec<UpstreamEntry> },
|
||||
Open { upstream_id: u8 },
|
||||
OpenAck { session_id: u32, upstream_id: u8 },
|
||||
OpenNak { upstream_id: u8, reason: u8 },
|
||||
@@ -94,8 +94,12 @@ impl Frame {
|
||||
pub fn encode(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Frame::Discover => build(TYPE_DISCOVER, 0, Vec::new()),
|
||||
Frame::Manifest(entries) => {
|
||||
Frame::Manifest { hostname, entries } => {
|
||||
let mut payload = Vec::new();
|
||||
let hn_bytes = hostname.as_bytes();
|
||||
let hn_len = hn_bytes.len().min(255) as u8;
|
||||
payload.push(hn_len);
|
||||
payload.extend_from_slice(&hn_bytes[..hn_len as usize]);
|
||||
for e in entries {
|
||||
encode_entry(&mut payload, e);
|
||||
}
|
||||
@@ -143,29 +147,38 @@ impl Frame {
|
||||
Ok(Frame::Discover)
|
||||
}
|
||||
TYPE_MANIFEST => {
|
||||
if payload.is_empty() {
|
||||
return Err(DecodeError::BadPayload("MANIFEST missing hostname prefix"));
|
||||
}
|
||||
let hn_len = payload[0] as usize;
|
||||
if 1 + hn_len > payload.len() {
|
||||
return Err(DecodeError::BadPayload("MANIFEST hostname truncated"));
|
||||
}
|
||||
let hostname = String::from_utf8_lossy(&payload[1..1 + hn_len]).into_owned();
|
||||
let rest = &payload[1 + hn_len..];
|
||||
let mut entries = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < payload.len() {
|
||||
if i + 5 > payload.len() {
|
||||
while i < rest.len() {
|
||||
if i + 5 > rest.len() {
|
||||
return Err(DecodeError::BadPayload("MANIFEST entry truncated"));
|
||||
}
|
||||
let id = payload[i];
|
||||
let proto = payload[i + 1];
|
||||
let port = u16::from_be_bytes([payload[i + 2], payload[i + 3]]);
|
||||
let label_len = payload[i + 4] as usize;
|
||||
let id = rest[i];
|
||||
let proto = rest[i + 1];
|
||||
let port = u16::from_be_bytes([rest[i + 2], rest[i + 3]]);
|
||||
let label_len = rest[i + 4] as usize;
|
||||
i += 5;
|
||||
if i + label_len > payload.len() {
|
||||
if i + label_len > rest.len() {
|
||||
return Err(DecodeError::BadPayload("MANIFEST label truncated"));
|
||||
}
|
||||
let label = if label_len == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(String::from_utf8_lossy(&payload[i..i + label_len]).into_owned())
|
||||
Some(String::from_utf8_lossy(&rest[i..i + label_len]).into_owned())
|
||||
};
|
||||
i += label_len;
|
||||
entries.push(UpstreamEntry { id, proto, port, label });
|
||||
}
|
||||
Ok(Frame::Manifest(entries))
|
||||
Ok(Frame::Manifest { hostname, entries })
|
||||
}
|
||||
TYPE_OPEN => {
|
||||
if payload.len() != 1 {
|
||||
|
||||
+9
-3
@@ -52,6 +52,8 @@ async fn main() -> ExitCode {
|
||||
}
|
||||
};
|
||||
|
||||
let hostname = Arc::new(upstream::get_hostname());
|
||||
|
||||
let link = match Link::open(&args.iface) {
|
||||
Ok(l) => Arc::new(l),
|
||||
Err(e) => {
|
||||
@@ -112,7 +114,7 @@ async fn main() -> ExitCode {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
handle_frame(frame, src, &tx, &store, &next_id, &table).await;
|
||||
handle_frame(frame, src, &tx, &store, &next_id, &table, &hostname).await;
|
||||
}
|
||||
Ok(None) => {
|
||||
// Ignorable frame (outgoing/short/mismatch) or transient; do not
|
||||
@@ -135,10 +137,14 @@ async fn handle_frame(
|
||||
store: &SessionStore,
|
||||
next_id: &Arc<AtomicU32>,
|
||||
table: &Arc<crate::upstream::UpstreamTable>,
|
||||
hostname: &Arc<String>,
|
||||
) {
|
||||
match frame {
|
||||
Frame::Discover => {
|
||||
let manifest = Frame::Manifest(table.entries());
|
||||
let manifest = Frame::Manifest {
|
||||
hostname: (**hostname).clone(),
|
||||
entries: table.entries(),
|
||||
};
|
||||
let _ = tx.send((src, manifest.encode())).await;
|
||||
}
|
||||
Frame::Open { upstream_id } => {
|
||||
@@ -202,6 +208,6 @@ async fn handle_frame(
|
||||
store.lock().expect("store poisoned").remove(&session_id);
|
||||
}
|
||||
// Not expected from a client; ignore.
|
||||
Frame::Manifest(_) | Frame::OpenAck { .. } | Frame::OpenNak { .. } => {}
|
||||
Frame::Manifest { .. } | Frame::OpenAck { .. } | Frame::OpenNak { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -1,6 +1,20 @@
|
||||
//! Upstream table: cmdline parsing and MANIFEST entry construction.
|
||||
|
||||
use crate::frame::{UpstreamEntry, PROTO_TCP, PROTO_UDP};
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
|
||||
Reference in New Issue
Block a user