use trojan_proto::{AddressRef, HostRef};
use crate::error::Socks5Error;
#[derive(Debug)]
pub struct Socks5UdpHeader<'a> {
pub address: AddressRef<'a>,
pub header_len: usize,
pub payload: &'a [u8],
}
pub fn parse_socks5_udp(buf: &[u8]) -> Result<Socks5UdpHeader<'_>, Socks5Error> {
if buf.len() < 4 {
return Err(Socks5Error::FragmentedUdp);
}
let frag = buf[2];
if frag != 0x00 {
return Err(Socks5Error::FragmentedUdp);
}
let atyp = buf[3];
let mut offset = 4;
let (host, addr_len) = match atyp {
0x01 => {
if buf.len() < offset + 6 {
return Err(Socks5Error::UnsupportedAddressType(atyp));
}
let ip: [u8; 4] = buf[offset..offset + 4].try_into().unwrap();
(HostRef::Ipv4(ip), 4)
}
0x03 => {
if buf.len() < offset + 1 {
return Err(Socks5Error::UnsupportedAddressType(atyp));
}
let domain_len = buf[offset] as usize;
if buf.len() < offset + 1 + domain_len + 2 {
return Err(Socks5Error::UnsupportedAddressType(atyp));
}
let domain = &buf[offset + 1..offset + 1 + domain_len];
(HostRef::Domain(domain), 1 + domain_len)
}
0x04 => {
if buf.len() < offset + 18 {
return Err(Socks5Error::UnsupportedAddressType(atyp));
}
let ip: [u8; 16] = buf[offset..offset + 16].try_into().unwrap();
(HostRef::Ipv6(ip), 16)
}
_ => return Err(Socks5Error::UnsupportedAddressType(atyp)),
};
offset += addr_len;
if buf.len() < offset + 2 {
return Err(Socks5Error::UnsupportedAddressType(atyp));
}
let port = u16::from_be_bytes([buf[offset], buf[offset + 1]]);
offset += 2;
Ok(Socks5UdpHeader {
address: AddressRef { host, port },
header_len: offset,
payload: &buf[offset..],
})
}
#[allow(clippy::cast_possible_truncation)]
pub fn write_socks5_udp(address: &AddressRef<'_>, payload: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(32 + payload.len());
buf.extend_from_slice(&[0x00, 0x00, 0x00]);
match &address.host {
HostRef::Ipv4(ip) => {
buf.push(0x01);
buf.extend_from_slice(ip);
}
HostRef::Domain(domain) => {
buf.push(0x03);
buf.push(domain.len() as u8);
buf.extend_from_slice(domain);
}
HostRef::Ipv6(ip) => {
buf.push(0x04);
buf.extend_from_slice(ip);
}
}
buf.extend_from_slice(&address.port.to_be_bytes());
buf.extend_from_slice(payload);
buf
}
#[cfg(test)]
mod tests {
use super::{parse_socks5_udp, write_socks5_udp};
use trojan_proto::{AddressRef, HostRef};
#[test]
fn parse_roundtrip_ipv4() {
let address = AddressRef {
host: HostRef::Ipv4([8, 8, 8, 8]),
port: 53,
};
let payload = b"ping";
let packet = write_socks5_udp(&address, payload);
let parsed = parse_socks5_udp(&packet).unwrap();
assert_eq!(parsed.address, address);
assert_eq!(parsed.payload, payload);
}
#[test]
fn parse_rejects_fragmented_udp() {
let address = AddressRef {
host: HostRef::Ipv4([1, 1, 1, 1]),
port: 53,
};
let payload = b"data";
let mut packet = write_socks5_udp(&address, payload);
packet[2] = 0x01; parse_socks5_udp(&packet).unwrap_err();
}
}