use crate::nat::helper::{Expectation, Helper, NatMapping, PacketHelper, PROTO_TCP};
use crate::nat::nat::Nat;
use crate::{checksum, combine_checksums, pseudo_header_checksum, Protocol};
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr};
use std::time::{Duration, Instant};
const IRC_EXPECT_TIMEOUT: Duration = Duration::from_secs(60);
#[derive(Debug)]
pub struct IrcHelper {
ports: HashSet<u16>,
}
impl IrcHelper {
pub fn new(ports: &[u16]) -> IrcHelper {
let set: HashSet<u16> = if ports.is_empty() {
[6667].into_iter().collect()
} else {
ports.iter().copied().collect()
};
IrcHelper { ports: set }
}
}
impl Helper for IrcHelper {
fn name(&self) -> &str {
"irc"
}
}
impl PacketHelper for IrcHelper {
fn match_outbound(&self, proto: u8, dst_port: u16) -> bool {
proto == PROTO_TCP && self.ports.contains(&dst_port)
}
fn process_outbound(&self, nat: &Nat, pkt: Vec<u8>, _m: &NatMapping) -> Vec<u8> {
let ihl = (pkt[0] & 0x0F) as usize * 4;
if pkt.len() < ihl + 20 {
return pkt;
}
let data_off = (pkt[ihl + 12] >> 4) as usize * 4;
if data_off < 20 || pkt[ihl..].len() < data_off {
return pkt;
}
let payload = &pkt[ihl + data_off..];
if payload.is_empty() {
return pkt;
}
let dcc_start = match find_subslice(payload, b"\x01DCC ") {
Some(p) => p,
None => return pkt,
};
let dcc_end_rel = match payload[dcc_start + 1..].iter().position(|&b| b == 0x01) {
Some(p) => p,
None => return pkt,
};
let dcc_end = dcc_start + 1 + dcc_end_rel;
let body = &payload[dcc_start + 1..dcc_end];
let fields: Vec<&[u8]> = body
.split(|b| b.is_ascii_whitespace())
.filter(|f| !f.is_empty())
.collect();
if fields.len() < 4 {
return pkt;
}
let cmd = match std::str::from_utf8(fields[1]) {
Ok(s) => s,
Err(_) => return pkt,
};
if cmd != "SEND" && cmd != "CHAT" {
return pkt;
}
let (ip_idx, port_idx) = if cmd == "SEND" && fields.len() >= 5 {
(3, 4)
} else {
(fields.len() - 2, fields.len() - 1)
};
let ip_val: u32 = match std::str::from_utf8(fields[ip_idx])
.ok()
.and_then(|s| s.parse().ok())
{
Some(v) => v,
None => return pkt,
};
let port_val: u16 = match std::str::from_utf8(fields[port_idx])
.ok()
.and_then(|s| s.parse().ok())
{
Some(v) => v,
None => return pkt,
};
let inside_ip = Ipv4Addr::from(ip_val);
let outside_port = match nat.create_mapping(PROTO_TCP, inside_ip, port_val) {
Some(p) => p,
None => return pkt,
};
nat.add_expectation(Expectation {
proto: PROTO_TCP,
remote_ip: Ipv4Addr::UNSPECIFIED,
remote_port: 0,
inside_ip,
inside_port: port_val,
expires: Instant::now() + IRC_EXPECT_TIMEOUT,
});
let outside_octets = match nat.outside_addr() {
Some(a) => a.octets(),
None => return pkt,
};
let outside_u32 = u32::from_be_bytes(outside_octets);
let mut new_dcc = Vec::with_capacity(body.len() + 8);
new_dcc.push(0x01);
for (i, f) in fields.iter().enumerate() {
if i > 0 {
new_dcc.push(b' ');
}
if i == ip_idx {
new_dcc.extend_from_slice(outside_u32.to_string().as_bytes());
} else if i == port_idx {
new_dcc.extend_from_slice(outside_port.to_string().as_bytes());
} else {
new_dcc.extend_from_slice(f);
}
}
new_dcc.push(0x01);
let mut new_payload = Vec::with_capacity(payload.len() + 16);
new_payload.extend_from_slice(&payload[..dcc_start]);
new_payload.extend_from_slice(&new_dcc);
new_payload.extend_from_slice(&payload[dcc_end + 1..]);
rebuild_tcp_packet(&pkt, ihl, data_off, &new_payload)
}
}
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}
fn rebuild_tcp_packet(pkt: &[u8], ihl: usize, tcp_data_off: usize, new_payload: &[u8]) -> Vec<u8> {
let total_len = ihl + tcp_data_off + new_payload.len();
let mut out = vec![0u8; total_len];
out[..ihl].copy_from_slice(&pkt[..ihl]);
out[ihl..ihl + tcp_data_off].copy_from_slice(&pkt[ihl..ihl + tcp_data_off]);
out[ihl + tcp_data_off..].copy_from_slice(new_payload);
out[2..4].copy_from_slice(&(total_len as u16).to_be_bytes());
out[10..12].copy_from_slice(&[0, 0]);
let ip_csum = checksum(&out[..ihl]);
out[10..12].copy_from_slice(&ip_csum.to_be_bytes());
let tcp_len = (total_len - ihl) as u16;
let src = Ipv4Addr::new(out[12], out[13], out[14], out[15]);
let dst = Ipv4Addr::new(out[16], out[17], out[18], out[19]);
out[ihl + 16..ihl + 18].copy_from_slice(&[0, 0]);
let ph = pseudo_header_checksum(Protocol::TCP, IpAddr::V4(src), IpAddr::V4(dst), tcp_len);
let seg = checksum(&out[ihl..]);
let tcsum = combine_checksums(ph, seg);
out[ihl + 16..ihl + 18].copy_from_slice(&tcsum.to_be_bytes());
out
}