use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use tokio::io::AsyncReadExt;
use super::header::MAX_PROXY_ADDR_LEN;
use super::header::PP2_TYPE_CRC32C;
use super::header::ProxyHeader;
use super::header::ProxyTransport;
use super::header::ProxyVersion;
use super::header::apply_tlvs;
fn locate_crc32c_tlv(mut buf: &[u8]) -> Option<(usize, u32)> {
let mut offset = 0usize;
while buf.len() >= 3 {
let kind = buf[0];
let len = u16::from_be_bytes([buf[1], buf[2]]) as usize;
if buf.len() < 3 + len {
return None;
}
if kind == PP2_TYPE_CRC32C && len == 4 {
let value_offset = offset + 3;
let v = &buf[3..7];
let expected = u32::from_be_bytes([v[0], v[1], v[2], v[3]]);
return Some((value_offset, expected));
}
buf = &buf[3 + len..];
offset += 3 + len;
}
None
}
fn verify_v2_crc32c(
sig: &[u8; 12],
hdr: &[u8; 4],
addr_buf: &[u8],
tlv_start: usize,
) -> Option<bool> {
if tlv_start >= addr_buf.len() {
return None;
}
let (value_offset_in_tlvs, expected) = locate_crc32c_tlv(&addr_buf[tlv_start..])?;
let zero_at_in_addr = tlv_start + value_offset_in_tlvs;
let mut full = Vec::with_capacity(12 + 4 + addr_buf.len());
full.extend_from_slice(sig);
full.extend_from_slice(hdr);
full.extend_from_slice(addr_buf);
let zero_at = 16 + zero_at_in_addr;
full[zero_at..zero_at + 4].copy_from_slice(&[0, 0, 0, 0]);
let computed = crc32c::crc32c(&full);
Some(computed == expected)
}
pub(crate) async fn parse_v2<R: AsyncReadExt + Unpin>(
reader: &mut R,
sig: &[u8; 12],
) -> std::io::Result<ProxyHeader> {
let mut hdr = [0u8; 4];
reader.read_exact(&mut hdr).await?;
let ver_cmd = hdr[0];
let version = (ver_cmd >> 4) & 0x0F;
let command = ver_cmd & 0x0F;
if version != 2 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("unsupported PROXY v2 version: {version}"),
));
}
let fam_proto = hdr[1];
let family = (fam_proto >> 4) & 0x0F;
let protocol = fam_proto & 0x0F;
let addr_len = u16::from_be_bytes([hdr[2], hdr[3]]) as usize;
if addr_len > MAX_PROXY_ADDR_LEN {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("PROXY v2 addr_len {addr_len} exceeds {MAX_PROXY_ADDR_LEN}"),
));
}
let mut addr_buf = vec![0u8; addr_len];
if addr_len > 0 {
reader.read_exact(&mut addr_buf).await?;
}
if command == 0 {
return Ok(ProxyHeader::empty(
ProxyVersion::V2,
ProxyTransport::Unknown,
));
}
let transport = match protocol {
1 => ProxyTransport::Tcp,
2 => ProxyTransport::Udp,
_ => ProxyTransport::Unknown,
};
let mut header = ProxyHeader::empty(ProxyVersion::V2, transport);
let consumed: usize = match family {
1 if addr_buf.len() >= 12 => {
let src_ip = Ipv4Addr::new(addr_buf[0], addr_buf[1], addr_buf[2], addr_buf[3]);
let dst_ip = Ipv4Addr::new(addr_buf[4], addr_buf[5], addr_buf[6], addr_buf[7]);
let src_port = u16::from_be_bytes([addr_buf[8], addr_buf[9]]);
let dst_port = u16::from_be_bytes([addr_buf[10], addr_buf[11]]);
header.source = Some(SocketAddr::new(IpAddr::V4(src_ip), src_port));
header.destination = Some(SocketAddr::new(IpAddr::V4(dst_ip), dst_port));
12
}
2 if addr_buf.len() >= 36 => {
let src_ip = Ipv6Addr::from(<[u8; 16]>::try_from(&addr_buf[0..16]).unwrap());
let dst_ip = Ipv6Addr::from(<[u8; 16]>::try_from(&addr_buf[16..32]).unwrap());
let src_port = u16::from_be_bytes([addr_buf[32], addr_buf[33]]);
let dst_port = u16::from_be_bytes([addr_buf[34], addr_buf[35]]);
header.source = Some(SocketAddr::new(IpAddr::V6(src_ip), src_port));
header.destination = Some(SocketAddr::new(IpAddr::V6(dst_ip), dst_port));
36
}
3 if addr_buf.len() >= 216 => {
let src = parse_unix_path(&addr_buf[0..108]);
let dst = parse_unix_path(&addr_buf[108..216]);
header.source_unix = src;
header.destination_unix = dst;
216
}
_ => 0,
};
header.crc32c_verified = verify_v2_crc32c(sig, &hdr, &addr_buf, consumed);
if header.crc32c_verified == Some(false) {
tracing::warn!("PROXY v2 CRC32C mismatch — header may be corrupt or spoofed");
}
if consumed < addr_buf.len() {
apply_tlvs(&mut header, &addr_buf[consumed..]);
}
Ok(header)
}
fn parse_unix_path(bytes: &[u8]) -> Option<std::path::PathBuf> {
let nul = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
if nul == 0 {
return None;
}
std::str::from_utf8(&bytes[..nul])
.ok()
.map(|s| std::path::PathBuf::from(s.to_string()))
}