44
loading...
This website collects cookies to deliver better user experience
fn main() {
let mut nic = tun_tap::Iface::without_packet_info("tun0", tun_tap::Mode::Tun).unwrap();
let mut buf = [0u8; 1500];
loop {
let nbytes = nic.recv(&mut buf[..]).unwrap();
match etherparse::Ipv4HeaderSlice::from_slice(&buf[..nbytes]) {
Ok(iph) => {
let src = iph.source_addr();
let dst = iph.destination_addr();
let proto = iph.protocol();
if proto != 1 {
continue;
}
let data_buf = &buf[iph.slice().len()..nbytes];
if let Some(mut c) = Connection::start(
iph,
data_buf,
).unwrap() {
println!("connection c started!");
c.respond(&mut nic).unwrap();
println!("responded to {} packet from {} ", proto, src);
}
}
Err(e) => {
eprintln!("ignoring weird packet {:?}", e);
}
}
}
}
main
, we create our device and a buf
and keep reading from it in a loop. Whenever we find an IPv4
header, etherparse
already takes care of the parsing for us and gives us the iph
object.proto == 1
for ICMP echo and echo reply packets, thus we skip everything else:if proto != 1 {
continue;
}
data
, which is everything except header bytes and create a connection:let data_buf = &buf[iph.slice().len()..nbytes];
if let Some(mut c) = Connection::start(
iph,
data_buf,
).unwrap() {
println!("connection c started!");
c.respond(&mut nic).unwrap();
println!("responded to {} packet from {} ", proto, src);
}
Connection
but for sake of conforming to John's TCP implementation, I use one as well.Connection
to distinguish multiple streams and detect out of order sequence numbers.pub struct Connection {
ip: etherparse::Ipv4Header,
icmp_id: u16,
seq_no: u16,
}
impl Connection {
pub fn start(iph: etherparse::Ipv4HeaderSlice, data: &[u8]) -> std::io::Result<Option<Self>> {
let mut c = Connection {
ip: etherparse::Ipv4Header::new(
0,
64,
etherparse::IpTrafficClass::Icmp,
[
iph.destination()[0],
iph.destination()[1],
iph.destination()[2],
iph.destination()[3],
],
[
iph.source()[0],
iph.source()[1],
iph.source()[2],
iph.source()[3],
],
),
icmp_id: u16::from_be_bytes(data[4..6].try_into().unwrap()),
seq_no: u16::from_be_bytes(data[6..8].try_into().unwrap()),
};
Ok(Some(c))
}
pub fn respond(&mut self, nic: &mut tun_tap::Iface,) -> std::io::Result<usize> {
let mut buf = [0u8; 1500];
self.ip.set_payload_len(84-20 as usize);
use std::io::Write;
let mut unwritten = &mut buf[..];
self.ip.write(&mut unwritten);
let mut icmp_reply = [0u8; 64];
// type
icmp_reply[0] = ICMP_ECHO_REPLY;
// code, always 0
icmp_reply[1] = 0;
// checksum = 2 & 3, empty for now
icmp_reply[2] = 0x00;
icmp_reply[3] = 0x00;
// id = 4 & 5
icmp_reply[4] = ((self.icmp_id >> 8) & 0xff) as u8;
icmp_reply[5] = (self.icmp_id & 0xff) as u8;
// seq_no = 6 & 7
icmp_reply[6] = ((self.seq_no >> 8) & 0xff) as u8;
icmp_reply[7] = (self.seq_no & 0xff) as u8;
unwritten.write(&icmp_reply);
let unwritten = unwritten.len();
nic.send(&buf[..buf.len() - unwritten])?;
Ok(0)
}
}
Connection
type and implement two methods: start
and respond
. All start
does is create an IP header with the source and destination swapped.respond
, we start writing the actual ICMP packet. We start by setting IP's size to 84, which if you remember from last part was the Total Length of our IP packet. 20 bytes is the IP header so we deduce that and prepare a 64 bytes buffer to hold our ICMP.0
which is echo reply, leave checksum as 0
, and copy in the identifier
and sequence number
.08 Type 0th byte = Echo message
00 Code 1st byte
492b Checksum 2-3rd byte
5514 Identifier 4-5th byte = id 21780 (in raw capture)
0001 Sequence Number 6-7th byte = seq 1
ping
it with any IP address associated with it and get:# ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=1626615676072 ms
wrong data byte #16 should be 0x10 but was 0x0
#16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
#48 0 0 0 0 0 0 0 0
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=1626615677081 ms
wrong data byte #16 should be 0x10 but was 0x0
#16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
#48 0 0 0 0 0 0 0 0
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=1626615678105 ms
wrong data byte #16 should be 0x10 but was 0x0
#16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
#48 0 0 0 0 0 0 0 0
0x10
, our #17 to be 0x11
and so on. So we add:for i in 16..64-8 {
icmp_reply[i+8] = (0x10 + (i - 16)) as u8;
}
respond
method and try ping
again:# ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=1626615769165 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=1626615770169 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=1626615771193 ms
64 bytes from 192.168.0.3: icmp_seq=4 ttl=64 time=1626615772217 ms
64 bytes from 192.168.0.3: icmp_seq=5 ttl=64 time=1626615773241 ms
ping
back and forth, only all the times
are all wrong.The data received in the echo message must be returned in the echo reply message.
0x10
, 0x11
to my packet, I copy the original values:icmp_reply[8..64].clone_from_slice(&self.data[8..64]);
# ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.162 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.214 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.174 ms
64 bytes from 192.168.0.3: icmp_seq=4 ttl=64 time=0.226 ms
ping
but if you use Wireshark:fn calculate_checksum(data: &mut [u8]) {
let mut f = 0;
let mut chk: u32 = 0;
while f + 2 <= data.len() {
chk += u16::from_le_bytes(data[f..f+2].try_into().unwrap()) as u32;
f += 2;
}
while chk > 0xffff {
chk = (chk & 0xffff) + (chk >> 2*8);
}
let mut chk = chk as u16;
chk = !chk & 0xffff;
// endianness
//chk = chk >> 8 | ((chk & 0xff) << 8);
data[3] = (chk >> 8) as u8;
data[2] = (chk & 0xff) as u8;
}