libpnet / libpnet/libpnet

Constructing multi-layer packets

Open
#580 1 comment 4 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
2.6k
Forks
329
PR merge metrics
No merged PRs in 30d

Description

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());


Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing packet_size(), payload(), set_payload(), and set_total_length() across EchoReplyPacket, MutableIpv4Packet, and MutableEthernetPacket in the Rust example. Done means documenting the differing size semantics and a supported single-buffer construction approach, including the required length and checksum sequencing.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
networking
Issue type
Documentation
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.