#![warn(non_camel_case_types, non_upper_case_globals, unused_qualifications)]
#[macro_use]
extern crate log;
use std::env;
use std::net::Ipv4Addr;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;
use iptrap::privilegesdrop;
use iptrap::strsliceescape::StrSliceEscape;
use iptrap::EmptyTcpPacket;
use iptrap::ETHERTYPE_IP;
use iptrap::{checksum, cookie};
use iptrap::{DataLinkType, Pcap, PcapPacket};
use iptrap::{EtherHeader, IpHeader, TcpHeader};
use iptrap::{PacketDissector, PacketDissectorFilter};
use iptrap::{TH_ACK, TH_RST, TH_SYN};
use serde_json::json;
static STREAM_PORT: u16 = 9922;
static SSH_PORT: u16 = 22;
fn send_tcp_synack(
sk: cookie::SipHashKey,
chan: &Sender<EmptyTcpPacket>,
dissector: &PacketDissector,
ts: u64,
) {
let s_etherhdr: &EtherHeader = &unsafe { *dissector.etherhdr_ptr };
assert!(s_etherhdr.ether_type == ETHERTYPE_IP.to_be());
let s_iphdr: &IpHeader = &unsafe { *dissector.iphdr_ptr };
let s_tcphdr: &TcpHeader = &unsafe { *dissector.tcphdr_ptr };
let mut sa_packet: EmptyTcpPacket = EmptyTcpPacket::new();
sa_packet.etherhdr.ether_shost = s_etherhdr.ether_dhost;
sa_packet.etherhdr.ether_dhost = s_etherhdr.ether_shost;
sa_packet.iphdr.ip_src = s_iphdr.ip_dst;
sa_packet.iphdr.ip_dst = s_iphdr.ip_src;
checksum::ip_header(&mut sa_packet.iphdr);
sa_packet.tcphdr.th_sport = s_tcphdr.th_dport;
sa_packet.tcphdr.th_dport = s_tcphdr.th_sport;
sa_packet.tcphdr.th_flags = TH_SYN | TH_ACK;
sa_packet.tcphdr.th_ack = (u32::from_be(s_tcphdr.th_seq) + 1u32).to_be();
sa_packet.tcphdr.th_seq = cookie::tcp(
sa_packet.iphdr.ip_src,
sa_packet.iphdr.ip_dst,
sa_packet.tcphdr.th_sport,
sa_packet.tcphdr.th_dport,
sk,
ts,
);
checksum::tcp_header(&sa_packet.iphdr, &mut sa_packet.tcphdr);
let _ = chan.send(sa_packet);
}
fn send_tcp_rst(chan: &Sender<EmptyTcpPacket>, dissector: &PacketDissector) {
let s_etherhdr: &EtherHeader = &unsafe { *dissector.etherhdr_ptr };
assert!(s_etherhdr.ether_type == ETHERTYPE_IP.to_be());
let s_iphdr: &IpHeader = &unsafe { *dissector.iphdr_ptr };
let s_tcphdr: &TcpHeader = &unsafe { *dissector.tcphdr_ptr };
let mut rst_packet: EmptyTcpPacket = EmptyTcpPacket::new();
rst_packet.etherhdr.ether_shost = s_etherhdr.ether_dhost;
rst_packet.etherhdr.ether_dhost = s_etherhdr.ether_shost;
rst_packet.iphdr.ip_src = s_iphdr.ip_dst;
rst_packet.iphdr.ip_dst = s_iphdr.ip_src;
checksum::ip_header(&mut rst_packet.iphdr);
rst_packet.tcphdr.th_sport = s_tcphdr.th_dport;
rst_packet.tcphdr.th_dport = s_tcphdr.th_sport;
rst_packet.tcphdr.th_ack = s_tcphdr.th_seq;
rst_packet.tcphdr.th_seq = s_tcphdr.th_ack;
rst_packet.tcphdr.th_flags = TH_RST | TH_ACK;
checksum::tcp_header(&rst_packet.iphdr, &mut rst_packet.tcphdr);
let _ = chan.send(rst_packet);
}
fn log_tcp_ack(
zmq_ctx: &mut zmq::Socket,
sk: cookie::SipHashKey,
dissector: &PacketDissector,
ts: u64,
) -> bool {
if dissector.tcp_data.is_empty() {
return false;
}
let s_iphdr: &IpHeader = &unsafe { *dissector.iphdr_ptr };
let s_tcphdr: &TcpHeader = &unsafe { *dissector.tcphdr_ptr };
let ack_cookie = cookie::tcp(
s_iphdr.ip_dst,
s_iphdr.ip_src,
s_tcphdr.th_dport,
s_tcphdr.th_sport,
sk,
ts,
);
let wanted_cookie = (u32::from_be(ack_cookie) + 1u32).to_be();
if s_tcphdr.th_ack != wanted_cookie {
let ts_alt = ts - 0x40;
let ack_cookie_alt = cookie::tcp(
s_iphdr.ip_dst,
s_iphdr.ip_src,
s_tcphdr.th_dport,
s_tcphdr.th_sport,
sk,
ts_alt,
);
let wanted_cookie_alt = (u32::from_be(ack_cookie_alt) + 1u32).to_be();
if s_tcphdr.th_ack != wanted_cookie_alt {
return false;
}
}
let tcp_data_str = String::from_utf8_lossy(&dissector.tcp_data).into_owned();
let ip_src = s_iphdr.ip_src;
let dport = u16::from_be(s_tcphdr.th_dport);
let record = json!({
"ts": ts,
"ip_src": format!("{}.{}.{}.{}", ip_src[0], ip_src[1], ip_src[2], ip_src[3]),
"dport": dport,
"payload": tcp_data_str.escape_default_except_lf()
});
let json = record.to_string();
let _ = zmq_ctx.send(json.as_bytes(), 0);
info!("{}", json);
true
}
fn usage() {
println!("Usage: iptrap <device> <local ip address> <uid> <gid>");
}
fn packet_should_be_bypassed(dissector: &PacketDissector) -> bool {
let th_dport = unsafe { *dissector.tcphdr_ptr }.th_dport;
th_dport == STREAM_PORT.to_be() || th_dport == SSH_PORT.to_be()
}
#[allow(unreachable_code)]
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 5 {
return usage();
}
let local_addr: Ipv4Addr = match args[2].parse() {
Ok(local_ip) => local_ip,
Err(_) => {
return usage();
}
};
let local_ip = local_addr.octets().to_vec();
let pcap = Pcap::open_live(&args[1]).unwrap();
privilegesdrop::switch_user(args[3].parse().ok(), args[4].parse().ok());
match pcap.data_link_type() {
DataLinkType::Ethernet => (),
_ => panic!("Unsupported data link type"),
}
let sk = cookie::SipHashKey::new();
let filter = PacketDissectorFilter::new(local_ip);
let pcap_arc = Arc::new(pcap);
let (packetwriter_chan, packetwriter_port): (Sender<EmptyTcpPacket>, Receiver<EmptyTcpPacket>) =
channel();
let pcap_arc0 = pcap_arc.clone();
thread::spawn(move || loop {
let pkt = packetwriter_port.recv().unwrap();
let _ = pcap_arc0.send_packet(&pkt);
});
let zmq_ctx = zmq::Context::new();
let mut zmq_socket = zmq_ctx.socket(zmq::SocketType::PUB).unwrap();
let _ = zmq_socket.set_linger(1);
let _ = zmq_socket.bind(&format!("tcp://0.0.0.0:{}", STREAM_PORT));
let updater = coarsetime::Updater::new(1000).start().unwrap();
let mut pkt_opt: Option<PcapPacket>;
while {
pkt_opt = pcap_arc.next_packet();
pkt_opt.is_some()
} {
let pkt = pkt_opt.unwrap();
let dissector = match PacketDissector::new(&filter, pkt.ll_data) {
Ok(dissector) => dissector,
Err(_) => {
continue;
}
};
if packet_should_be_bypassed(&dissector) {
continue;
}
let ts = coarsetime::Clock::recent_since_epoch().as_secs();
let th_flags = unsafe { *dissector.tcphdr_ptr }.th_flags;
if th_flags == TH_SYN {
send_tcp_synack(sk, &packetwriter_chan, &dissector, ts);
} else if (th_flags & TH_ACK) == TH_ACK
&& (th_flags & TH_SYN) == 0
&& log_tcp_ack(&mut zmq_socket, sk, &dissector, ts)
{
send_tcp_rst(&packetwriter_chan, &dissector);
}
}
updater.stop().unwrap();
}