use anyhow::Result;
use etherparse::{IpSlice, TcpSlice};
use anyhow::anyhow;
#[cfg(target_os = "linux")]
use std::sync::atomic::Ordering;
use crate::opt;
use crate::platform;
use crate::tls;
mod fake;
mod hoptab;
struct PktView<'a> {
ip: IpSlice<'a>,
tcp: TcpSlice<'a>
}
impl<'a> PktView<'a> {
#[inline]
fn from_raw(raw: &'a [u8]) -> Result<Self> {
let ip = IpSlice::from_slice(raw)?;
let tcp = TcpSlice::from_slice(ip.payload().payload)?;
Ok(Self { ip, tcp })
}
#[inline]
fn ttl(&self) -> u8 {
use etherparse::IpSlice;
match &self.ip {
IpSlice::Ipv4(v4) => v4.header().ttl(),
IpSlice::Ipv6(v6) => v6.header().hop_limit()
}
}
#[inline]
fn saddr(&self) -> std::net::IpAddr {
self.ip.source_addr()
}
#[inline]
fn daddr(&self) -> std::net::IpAddr {
self.ip.destination_addr()
}
}
fn build_packet(
view: &PktView,
start: u32,
end: Option<u32>,
out_buf: &mut Vec<u8>,
payload: Option<&[u8]>,
ttl: Option<u8>,
tcp_checksum: Option<u16>
) -> Result<()> {
use etherparse::*;
let ip = &view.ip;
let tcp = &view.tcp;
let payload = payload.unwrap_or(tcp.payload());
let end = end.unwrap_or(payload.len().try_into()?);
if start > end || payload.len() < end as usize {
return Err(anyhow!("invalid index"));
}
let opts = tcp.options();
let mut tcp_hdr = tcp.to_header();
tcp_hdr.sequence_number += start;
let (builder, l3_len) = match ip {
IpSlice::Ipv4(hdr) => {
let mut ip_hdr = hdr.header().to_header();
if let Some(t) = ttl { ip_hdr.time_to_live = t; };
let exts = hdr.extensions().to_header();
let l3_len = ip_hdr.header_len() + exts.header_len();
(PacketBuilder::ip(IpHeaders::Ipv4(
ip_hdr,
hdr.extensions().to_header()
)), l3_len)
},
IpSlice::Ipv6(hdr) => {
let mut ip6_hdr = hdr.header().to_header();
if let Some(t) = ttl { ip6_hdr.hop_limit = t; };
let l3_len = Ipv6Header::LEN;
(PacketBuilder::ip(IpHeaders::Ipv6(
ip6_hdr,
Default::default()
)), l3_len)
}
};
let builder = builder.tcp_header(tcp_hdr).options_raw(opts)?;
let payload = &payload[start as usize..end as usize];
out_buf.clear();
builder.write(out_buf, payload)?;
if let Some(cs) = tcp_checksum {
let tcp_csum_off = l3_len + 16;
if out_buf.len() < tcp_csum_off + 2 {
return Err(anyhow!("packet too short for tcp checksum patch"));
}
out_buf[tcp_csum_off..tcp_csum_off + 2].copy_from_slice(&cs.to_be_bytes());
}
Ok(())
}
fn build_segment(
view: &PktView,
start: u32,
end: Option<u32>,
out_buf: &mut Vec<u8>
) -> Result<()> {
build_packet(view, start, end, out_buf, None, None, None)
}
fn send_segment(
view: &PktView,
start: u32,
end: Option<u32>,
buf: &mut Vec<u8>
) -> Result<()> {
use platform::send_to_raw;
if opt::fake() {
fake::fake_clienthello(view, start, end, buf)?;
send_to_raw(buf, view.daddr())?;
}
build_segment(view, start, end, buf)?;
send_to_raw(buf, view.daddr())?;
Ok(())
}
fn send_split(view: &PktView, order: &[opt::Segment], buf: &mut Vec<u8>) -> Result<()> {
let payload_len = view.tcp.payload().len() as u32;
for &opt::Segment(start, end) in order {
if start >= payload_len {
crate::warn!(
"send_split: segment {} exceeds payload len {payload_len}, skipping",
opt::Segment(start, end)
);
continue;
}
let end = if end == u32::MAX || end > payload_len { None } else { Some(end) };
send_segment(view, start, end, buf)?;
if end.is_some() {
std::thread::sleep(std::time::Duration::from_millis(opt::delay_ms()));
}
}
crate::debug!(
"send_split: dst={} order={:?} tcp_payload_len={}",
view.daddr(),
order,
payload_len
);
Ok(())
}
fn infer_hops(ttl: u8) -> u8 {
let origin = if ttl <= 64 {
64u8
} else if ttl <= 126 {
128u8
} else {
255u8
};
origin - ttl
}
fn put_hop_1(pkt: &[u8]) -> Result<()> {
let view = PktView::from_raw(pkt)?;
let addr = view.saddr();
let ttl = view.ttl();
let hop = infer_hops(view.ttl());
crate::debug!(
"put_hop_1: {}: observed ttl={}, put hop={}",
addr, ttl, hop
);
hoptab::put(addr, hop);
Ok(())
}
pub fn put_hop(pkt: &[u8]) {
if let Err(e) = put_hop_1(pkt) {
crate::warn!("put_hop: {}", e);
}
}
pub fn handle_packet(pkt: &[u8], buf: &mut Vec::<u8>) -> Result<bool> {
#[cfg(target_os = "linux")]
let is_filtered = platform::IS_U32_SUPPORTED.load(Ordering::Relaxed);
#[cfg(windows)]
let is_filtered = true;
let view = PktView::from_raw(pkt)?;
if !is_filtered && !tls::is_client_hello(view.tcp.payload()) {
return Ok(false);
}
send_split(&view, opt::segment_order().segments(), buf)?;
Ok(true)
}
#[macro_export]
macro_rules! handle_packet {
($bytes:expr, $buf:expr, handled => $on_handled:expr, rejected => $on_rejected:expr $(,)?) => {{
match crate::pkt::handle_packet($bytes, $buf) {
Ok(true) => { $on_handled }
Ok(false) => { $on_rejected }
Err(e) => {
crate::warn!("handle_packet: {e}");
$on_rejected
}
}
}};
}