libpnet icon indicating copy to clipboard operation
libpnet copied to clipboard

Constructing multi-layer packets

Open mikebromwich opened this issue 3 years ago • 1 comments

I'm struggling to understand the principles of how to correctly create multi-layer packets (such as ICMP->IPv4->Ethernet) with libpnet - in particular, how to correctly define packet/buffer/payload sizes between the layers. I seem to be getting inconsistent behaviour between the various layers - and so I am probably misunderstanding how this is supposed to work.

I am working from the 'outside-in' so (for example) creating an ICMP message, then wrapping that in IPv4, then wrapping that in Ethernet. This is already requiring me to make three allocations/copies - but more fundamentally, I can't see how to manage the payload extents consistently.

My example code is below.

The EchoReplyPacket.packet_size() reports 8 - so this is just the header (excludes the payload). EchoReplyPacket.payload().len() reports 32. (total 40 bytes).

Ipv4Packet.packet_size() reports 60 - so this includes both the header (20 bytes) and the ICMP payload (40) (total 60 bytes). Ipv4Packet.payload().size reports 40.

Ethernet.packet_size() reports 14 - so this is just the header. Ethernet.payload().len() reports 60. (total 74 bytes).

This seems inconsistent. Why does the IPv4 packet_size include the payload - but Ethernet and ICMP do not?

Also, even though I have created the Ipv4Packet with the appropriately sized buffer, I also have to call set_total_length before I can set the payload (otherwise panic) - is this correct? I don't seem to have to do this for Ethernet or ICMP layers.

Ideally, I would just create a single generously-sized buffer at the outset, and the write Ethernet, IPv4, ICMP layers in sequence - then patch-up the lengths and checksums. Then, send the appropriate slice of this buffer to the network. If anyone has an examples of this approach, it would be helpful/appreciated.

Thanks,

let echo_request_packet = EchoRequestPacket::new(received_ipv4.payload()).unwrap();

// Construct ICMP Echo Reply
let mut echo_reply_buffer = vec![0u8;echo_request_packet.packet_size()+echo_request_packet.payload().len()];
let mut echo_reply_packet = pnet::packet::icmp::echo_reply::MutableEchoReplyPacket::new(&mut echo_reply_buffer).unwrap();

echo_reply_packet.set_identifier(echo_request_packet.get_identifier());
echo_reply_packet.set_sequence_number(echo_request_packet.get_sequence_number());
echo_reply_packet.set_payload(echo_request_packet.payload());
echo_reply_packet.set_checksum(pnet::util::checksum(echo_reply_packet.packet(),1));

println!("ICMP echo reply size {}",echo_reply_packet.packet_size());
println!("ICMP echo reply payload size {}",echo_reply_packet.payload().len());

// Construct IPv4 Packet
let ipv4_len = echo_request_packet.packet_size()+echo_request_packet.payload().len()+20;
let mut ipv4_buffer = vec![0u8; ipv4_len];
let mut ipv4_packet = MutableIpv4Packet::new(&mut ipv4_buffer).unwrap();

ipv4_packet.set_version(4);
ipv4_packet.set_header_length(5);
ipv4_packet.set_dscp(4);
ipv4_packet.set_ecn(1);
ipv4_packet.set_ttl(64);
ipv4_packet.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
ipv4_packet.set_source(received_ipv4.get_destination());
ipv4_packet.set_destination(received_ipv4.get_source());
ipv4_packet.set_total_length(ipv4_len.try_into().unwrap());
ipv4_packet.set_payload(echo_reply_packet.packet_mut());
ipv4_packet.set_checksum(pnet::packet::ipv4::checksum(&ipv4_packet.to_immutable()));
println!("IPv4 size {}",ipv4_packet.packet_size());
println!("IPv4 payload size {}",ipv4_packet.payload().len());

// Construct Ethernet frame
let ethernet_len = ipv4_packet.packet_size()+14;
let mut ethernet_buffer = vec![0u8; ethernet_len];
let mut ethernet_packet = MutableEthernetPacket::new(&mut ethernet_buffer).unwrap();
                
ethernet_packet.set_destination(ethernet.get_source());
ethernet_packet.set_source(ethernet.get_destination());
ethernet_packet.set_ethertype(EtherTypes::Ipv4);
ethernet_packet.set_payload(ipv4_packet.packet_mut());
println!("Eth size {}",ethernet_packet.packet_size());
println!("Eth payload size {}",ethernet_packet.payload().len());


mikebromwich avatar Aug 21 '22 13:08 mikebromwich

packet_size appears to be broken: https://github.com/libpnet/libpnet/issues/519

andrewbaxter avatar Aug 29 '24 11:08 andrewbaxter