Search code examples
network-programmingrust

Unable to correctly parse packet payloads using Pnet and Etherparse


I'm currently using the Pnet and Etherparse libraries for rust to try and analyze all incoming packets to my machine (Ubuntu 24.04). However, whenever I try and read the payload retrieved by the datalink layer, it either has no payload at all if the packet is a TCP datagram, or the payload is garbled if it is a UDP or ICMP datagram.

I have tried working with just the Pnet or Etherparse libraries separately to resolve the problem but the issue still occurs, and the messages sent via UDP or ICMP are garbled regardless of the source (sending a datagram to my localhost via netcat or external hosts sporadically sending packets). The UDP case particularly confuses me since all payloads should be valid UTF-8 but it appears as though the random noise thrown in with the message isn't. Below you can find both my code and example snippets of the output. Any and all help would be very very appreciated!

pub fn listen_for_incoming_packets(self) -> Result<(), Error> {
    loop {
        
      for interface in pnet::datalink::interfaces() {

        let (mut receiver, _) = match pnet::datalink::channel(&interface, pnet::datalink::Config::default()) {
          Ok(Ethernet(transmitter, receiver)) => (receiver, transmitter),
          Ok(_) => panic!("Yikes!"),
          Err(e) => panic!("Error: {e}")
        };

        thread::spawn(move || {
          while let Ok(packet) = receiver.next() {

            match PacketHeaders::from_ethernet_slice(&packet) {
              Err(value) => println!("Err {:?}", value),
              Ok(value) => {
                  println!("Transport protocol: {:?}", value.transport);
                  println!("Payload: {:?}", 
                  value.payload.slice().iter().map(|u| char::from(*u)).collect::<String>());
                  value.payload.slice().iter().map(|u| char::from(*u)).collect::<String>());
              }
            }
          }
        })
        .join()
        .expect("Ah!");
      }

    }

    Ok(())
  }

When using TCP:

Sent

foo@bar:~$ echo "AAAA" | nc localhost 9000

received

Transport protocol: Some(Tcp(TcpHeader { source_port: 33828, destination_port: 9000, sequence_number: 3517588129, acknowledgment_number: 0, ns: false, fin: false, syn: true, rst: false, psh: false, ack: false, urg: false, ece: false, cwr: false, window_size: 65495, checksum: 65072, urgent_pointer: 0, options: [MaximumSegmentSize(65495), SelectiveAcknowledgementPermitted, Timestamp(2910614175, 0), Noop, WindowScale(7)] }))
Payload: ""

When using UDP:

Sent

foo@bar:~$ echo "AAAA" | nc localhost 9000

Received

Transport protocol: Some(Icmpv4(Icmpv4Header { icmp_type: DestinationUnreachable(Port), checksum: 30923 }))
Payload: "E\0\0!¨-@\0@\u{11}\u{94}\u{9c}\u{7f}\0\0\u{1}\u{7f}\0\0\u{1}ÖX#(\0\rþ AAAA\n"

Solution

  • In the TCP case, your data is never sent. TCP requires a connection to be established before it can send any data. What you see are the packets sent by nc to establish the connection and the refusal sent by the server because no application is listening on that port.

    For ICMP, what you are showing us is a "destination unreachable" error that is returned by the server (again because nobody is listening on this specific port). According to Wikipedia, the payload is made of the IP header of the original request followed by the beginning of the original payload. The "random noise" you're seeing is the original IP header (which has no reason to be valid UTF-8).