// SPDX-License-Identifier: MIT // // Copyright 2023 Cisco Systems, Inc. and its affiliates // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. /// This example shows a basic packet logger using libpnet extended by parsing ERSPAN pckets /// An original example can be found here https://github.com/libpnet/libpnet/blob/main/examples/packetdump.rs extern crate pnet; use std::env; use std::io::{self, Write}; use std::net::IpAddr; use std::process; use pnet::datalink::{self, NetworkInterface}; use pnet::packet::arp::ArpPacket; use pnet::packet::ethernet::{EthernetPacket, EtherTypes, MutableEthernetPacket}; use pnet::packet::icmp::{echo_reply, echo_request, IcmpPacket, IcmpTypes}; use pnet::packet::icmpv6::Icmpv6Packet; use pnet::packet::ip::{IpNextHeaderProtocol, IpNextHeaderProtocols}; use pnet::packet::ipv4::Ipv4Packet; use pnet::packet::ipv6::Ipv6Packet; use pnet::packet::Packet; use pnet::packet::tcp::TcpPacket; use pnet::packet::udp::UdpPacket; use pnet::util::MacAddr; use erspan::{erspan_decap, ErspanHeader}; use erspan::ErspanError::InvalidTransportProtocol; fn handle_udp_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let udp = UdpPacket::new(packet); if let Some(udp) = udp { println!( "[{}]: UDP Packet: {}:{} > {}:{}; length: {}", interface_name, source, udp.get_source(), destination, udp.get_destination(), udp.get_length() ); } else { println!("[{}]: Malformed UDP Packet", interface_name); } } fn handle_icmp_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let icmp_packet = IcmpPacket::new(packet); if let Some(icmp_packet) = icmp_packet { match icmp_packet.get_icmp_type() { IcmpTypes::EchoReply => { let echo_reply_packet = echo_reply::EchoReplyPacket::new(packet).unwrap(); println!( "[{}]: ICMP echo reply {} -> {} (seq={:?}, id={:?})", interface_name, source, destination, echo_reply_packet.get_sequence_number(), echo_reply_packet.get_identifier() ); } IcmpTypes::EchoRequest => { let echo_request_packet = echo_request::EchoRequestPacket::new(packet).unwrap(); println!( "[{}]: ICMP echo request {} -> {} (seq={:?}, id={:?})", interface_name, source, destination, echo_request_packet.get_sequence_number(), echo_request_packet.get_identifier() ); } _ => println!( "[{}]: ICMP packet {} -> {} (type={:?})", interface_name, source, destination, icmp_packet.get_icmp_type() ), } } else { println!("[{}]: Malformed ICMP Packet", interface_name); } } fn handle_icmpv6_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let icmpv6_packet = Icmpv6Packet::new(packet); if let Some(icmpv6_packet) = icmpv6_packet { println!( "[{}]: ICMPv6 packet {} -> {} (type={:?})", interface_name, source, destination, icmpv6_packet.get_icmpv6_type() ) } else { println!("[{}]: Malformed ICMPv6 Packet", interface_name); } } fn handle_tcp_packet(interface_name: &str, source: IpAddr, destination: IpAddr, packet: &[u8]) { let tcp = TcpPacket::new(packet); if let Some(tcp) = tcp { println!( "[{}]: TCP Packet: {}:{} > {}:{}; length: {}", interface_name, source, tcp.get_source(), destination, tcp.get_destination(), packet.len() ); } else { println!("[{}]: Malformed TCP Packet", interface_name); } } fn handle_transport_protocol( interface_name: &str, source: IpAddr, destination: IpAddr, protocol: IpNextHeaderProtocol, packet: &[u8], ) { match protocol { IpNextHeaderProtocols::Udp => { handle_udp_packet(interface_name, source, destination, packet) } IpNextHeaderProtocols::Tcp => { handle_tcp_packet(interface_name, source, destination, packet) } IpNextHeaderProtocols::Icmp => { handle_icmp_packet(interface_name, source, destination, packet) } IpNextHeaderProtocols::Icmpv6 => { handle_icmpv6_packet(interface_name, source, destination, packet) } _ => println!( "[{}]: Unknown {} packet: {} > {}; protocol: {:?} length: {}", interface_name, match source { IpAddr::V4(..) => "IPv4", _ => "IPv6", }, source, destination, protocol, packet.len() ), } } fn handle_ipv4_packet(interface_name: &str, ethernet: &EthernetPacket) { let header = Ipv4Packet::new(ethernet.payload()); if let Some(header) = header { handle_transport_protocol( interface_name, IpAddr::V4(header.get_source()), IpAddr::V4(header.get_destination()), header.get_next_level_protocol(), header.payload(), ); } else { println!("[{}]: Malformed IPv4 Packet", interface_name); } } fn handle_ipv6_packet(interface_name: &str, ethernet: &EthernetPacket) { let header = Ipv6Packet::new(ethernet.payload()); if let Some(header) = header { handle_transport_protocol( interface_name, IpAddr::V6(header.get_source()), IpAddr::V6(header.get_destination()), header.get_next_header(), header.payload(), ); } else { println!("[{}]: Malformed IPv6 Packet", interface_name); } } fn handle_arp_packet(interface_name: &str, ethernet: &EthernetPacket) { let header = ArpPacket::new(ethernet.payload()); if let Some(header) = header { println!( "[{}]: ARP packet: {}({}) > {}({}); operation: {:?}", interface_name, ethernet.get_source(), header.get_sender_proto_addr(), ethernet.get_destination(), header.get_target_proto_addr(), header.get_operation() ); } else { println!("[{}]: Malformed ARP Packet", interface_name); } } fn handle_ethernet_frame(interface: &NetworkInterface, ethernet: &EthernetPacket) { let interface_name = &interface.name[..]; match ethernet.get_ethertype() { EtherTypes::Ipv4 => handle_ipv4_packet(interface_name, ethernet), EtherTypes::Ipv6 => handle_ipv6_packet(interface_name, ethernet), EtherTypes::Arp => handle_arp_packet(interface_name, ethernet), _ => println!( "[{}]: Unknown packet: {} > {}; ethertype: {:?} length: {}", interface_name, ethernet.get_source(), ethernet.get_destination(), ethernet.get_ethertype(), ethernet.packet().len() ), } } fn main() { use pnet::datalink::Channel::Ethernet; let iface_name = match env::args().nth(1) { Some(n) => n, None => { writeln!(io::stderr(), "USAGE: packetdump ").unwrap(); process::exit(1); } }; let interface_names_match = |iface: &NetworkInterface| iface.name == iface_name; // Find the network interface with the provided name let interfaces = datalink::interfaces(); let interface = interfaces .into_iter() .filter(interface_names_match) .next() .unwrap_or_else(|| panic!("No such network interface: {}", iface_name)); println!("Local interfaces: {:?}", datalink::interfaces().iter().map(|i| i.name.clone()).collect::>()); // Create a channel to receive on let (_, mut rx) = match datalink::channel(&interface, Default::default()) { Ok(Ethernet(tx, rx)) => (tx, rx), Ok(_) => panic!("packetdump: unhandled channel type"), Err(e) => panic!("packetdump: unable to create channel: {}", e), }; loop { let mut buf: [u8; 1600] = [0u8; 1600]; let mut fake_ethernet_frame = MutableEthernetPacket::new(&mut buf[..]).unwrap(); match rx.next() { Ok(packet) => { let payload_offset; if cfg!(any(target_os = "macos", target_os = "ios")) && interface.is_up() && !interface.is_broadcast() && ((!interface.is_loopback() && interface.is_point_to_point()) || interface.is_loopback()) { if interface.is_loopback() { // The pnet code for BPF loopback adds a zero'd out Ethernet header payload_offset = 14; } else { // Maybe is TUN interface payload_offset = 0; } if packet.len() > payload_offset { let version = Ipv4Packet::new(&packet[payload_offset..]) .unwrap() .get_version(); if version == 4 { fake_ethernet_frame.set_destination(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_source(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_ethertype(EtherTypes::Ipv4); fake_ethernet_frame.set_payload(&packet[payload_offset..]); handle_ethernet_frame(&interface, &fake_ethernet_frame.to_immutable()); continue; } else if version == 6 { fake_ethernet_frame.set_destination(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_source(MacAddr(0, 0, 0, 0, 0, 0)); fake_ethernet_frame.set_ethertype(EtherTypes::Ipv6); fake_ethernet_frame.set_payload(&packet[payload_offset..]); handle_ethernet_frame(&interface, &fake_ethernet_frame.to_immutable()); continue; } } } match erspan_decap(packet) { Ok(p) => report_gre(&interface, p), Err(e) if e == InvalidTransportProtocol => { // Not GRE, try to parse as parse as a standard packet handle_ethernet_frame(&interface, &EthernetPacket::new(packet).unwrap()); } Err(e) => { println!("{:?}", e) } } } Err(e) => panic!("packetdump: unable to receive packet: {}", e), } } fn report_gre(interface: &NetworkInterface, gre_packet: ErspanHeader) { print!("[{}]: GRE ({:?}) {} > {} session {} truncated: {} >>> ", interface.name, gre_packet.version, gre_packet.source, gre_packet.destination, gre_packet.session_id, gre_packet.truncated); let packet_inside = &EthernetPacket::new(&*gre_packet.original_data_packet).unwrap(); handle_ethernet_frame(&interface, packet_inside); } }