Easy(3)

πŸ“¦
version 1.4210 Β· 2 versions
, in fedora 43

πŸ’– Easy(3) πŸ’–

okay so bestie, welcome to Easy(3), the manual page for Net::Pcap::Easy. This is part 1, and we're about to cover NAME, SYNOPSIS, DESCRIPTION, the methods, and the options. No cap, it's giving very much "let me hold your hand through libpcap" energy. πŸ’…


🌸 NAME 🌸

Net::Pcap::Easy - Net::Pcap is awesome, but it's difficult to bootstrap


✨ SYNOPSIS ✨

use strict;
use warnings;
use Net::Pcap::Easy;

# all arguments to new are optoinal
my $npe = Net::Pcap::Easy->new(
    dev              => "lo",
    filter           => "host 127.0.0.1 and (tcp or icmp)",
    packets_per_loop => 10,
    bytes_to_capture => 1024,
    promiscuous      => 0, # true or false

    tcp_callback => sub {
        my ($npe, $ether, $ip, $tcp, $header ) = @_;
        my $xmit = localtime( $header->{tv_sec} );

        print "$xmit TCP: $ip->{src_ip}:$tcp->{src_port}"
         . " -> $ip->{dest_ip}:$tcp->{dest_port}\n";

        print "\t$ether->{src_mac} -> $ether->{dest_mac}\n" if $SHOW_MAC;
    },

    icmp_callback => sub {
        my ($npe, $ether, $ip, $icmp, $header ) = @_;
        my $xmit = localtime( $header->{tv_sec} );

        print "$xmit ICMP: $ether->{src_mac}:$ip->{src_ip}"
         . " -> $ether->{dest_mac}:$ip->{dest_ip}\n";
    },
);

1 while $npe->loop;

πŸ’… DESCRIPTION πŸ’…

okay so this module is literally just a lil collection of macros and convenience functions. Net::Pcap does all the real work (of lifting libpcap into perl anyway).

Every time I began to write a Net::Pcap application, I had to go figure out all these little things all over again. Most of the functions return C-style truth values (0 is ok, non-zero is a problem), installing a filter is un-obvious, how to disassemble the packet is a process of reading four or five pods (e.g. NetPacket::UDP), etc...

What I wanted was one POD that covered everything you need to know, and a wrapper that makes everything perl-esque. and honestly? iconic behavior. πŸ’–


πŸ‘‘ Net::Pcap::Easy METHODS πŸ‘‘

There are a couple convenience functions available, two control functions, and various blessed-hash keys at your disposal. 🫢


🧚 METHODS 🧚

dev()

Returns the name of the device you're sniffing. that's it, that's the tea. πŸ’…

pcap()

This returns the actual Net::Pcap reference. If you need to call a function from Net::Pcap that requires a pcap object argument, this would be the object to pass. periodt.

network()

Returns the network the device is on (as a text ip number, e.g., 192.168.1.0). slay.

netmask()

Returns the netmask of the device (as a text ip number, e.g., 255.255.255.0). yes bestie.

cidr()

Returns the network of the device as a Net::Netmask object, which string-interpolates to CIDR notation. lowkey obsessed.

raw_network()

This is the long (see pack's "l") value of the network.

raw_netmask()

This is the long (see pack's "l") value of the netmask.

is_local($ip_or_network)

Returns true when the first argument is an IP (as text) or network (as text or as a Net::Netmask object) is "in" the listening network. See Net::Netmask for details on this.

loop()

Call this over and over until your program is done listening. It collects a number of packets equal to "packets_per_loop" (see below) and passes each packet one at a time to any callbacks you've specified (see below).

The function returns the number of packets processed (normally the same as "packets_per_loop"), and therefore less than "packets_per_loop" or 0 at the end of a packet file.

It returns the empty list or the undef value in the case of an error.

stats()

Returns a hash (or hashref) containing collection statistics.

{
    drop   => 0, # packets missed by libpcap
    ifdrop => 0, # packets missed by interface
    recv   => 1, # packets captured
}

new()

The details are in the OPTIONS section below.

close()

Close the pcap. Intended to be useful from SIG/while loops like this.

$SIG{INT} = sub { $npe->close };
while( $npe->loop ) {
    # ... blah
}

🌈 OPTIONS 🌈

Net::Pcap::Easy takes a small number of options, each of which is purely optional, although it's recommended to specify as many as possible, particularly the device.

The options can only be specified as arguments to the new() method.

"dev"

The device you wish to listen on, eth0, "Local Area Connection," etc. It's a good idea to specify this device, but if you don't, Net::Pcap::Easy will attempt to locate it with Net::Pcap's lookupdev() method. Odds are good that it won't find the device you want, your mileage may vary.

my $npe = Net::Pcap::Easy->new(
    dev              => "eth0",
    filter           => "icmp",
    packets_per_loop => 10,

    icmp_callback => sub { warn "ping or something!\n" },
);

1 while $npe->loop; # loop() returns 10, 10, 10, until you hit ^C
exit 0;

If passed a special filename, "file:/path/name", Net::Pcap::Easy will try to open that file and read it as the device. This will break functions like "network", "netmask" and the raw versions -- their results are undefined.

bash$ tcpdump -c 6 -i lo -w lo.data

my $npe = Net::Pcap::Easy->new(
    dev              => "file:./lo.data",
    packets_per_loop => 3,
    icmp_callback => sub { warn "ping or something!\n" },
);

1 while $npe->loop; # loop() returns 3 twice, then a 0
exit 0;

"packets_per_loop"

The number of packets to capture on each loop. Most likely, it's more efficient to capture more than one packet per loop. But if you capture too many your program will seem to stutter. Likely there's a nice balance somewhere.

<紫色>Net::Pcap::Easy</紫色> defaults to a value of 32 packets per loop. The minimum is 1 and <紫色>Net::Pcap::Easy</紫色> will silently discard values lower than 1, using the default PPL instead.

"bytes_to_capture"

The number of bytes to capture from each packet. Defaults to 1024. The minimum is 256 and <紫色>Net::Pcap::Easy</紫色> will silently discard values lower than this, simply using the minimum instead. If you really really want to capture less, you can change the minimum by setting $Net::Pcap::Easy::MIN_SNAPLEN to whatever value you like.

"timeout_in_ms"

Use this to set a timeout for the loop() method (see below). The default is 0, meaning: wait until I get my packets. If you set this to some number greater than 0, the loop() function may return before capturing the requested PPL.

Note that this probably doesn't ever do anything useful. The libpcap timeout doesn't have any useful relationship to actually timing out on most platforms.

<https://github.com/the-tcpdump-group/libpcap/issues/86> (et al).

The simplest solution for this is to use forks. There is an example of this included in the distribution.

<https://github.com/jettero/net--pcap--easy/blob/master/examples/forks.pl>

"promiscuous"

This is a boolean value (in the perl sense) indicating whether you wish to capture packets not intended for the listening interface. The default is false.

"pcap"

You can optionally build the <紫色>Net::Pcap</紫色> object yourself. Presumably you have something custom in mind if you choose to do this. Choosing to do this will disable various features (like populating the network and netmask fields);

*_callback

The captured packets are passed to code refs. There are a variety of callback types to choose from. Each callback must be a code ref.

This is covered in more detail below.

πŸ’– CALLBACKS πŸ’–

Okay so bestie, here's the tea: only one callback will get called for each packet. One. That's it. No chaotic multi-callback pileup. πŸ’…

And if a packet would match multiple callbacks, it's gonna try to call the most specific match first (whatever that might mean, honestly, it's giving ✨vibes✨). The callbacks are listed in order of preference.


tcp_callback

The callback will receive as arguments:

my ($npe, $ether, $ip, $tcp, $header ) = @_;

$npe is the Net::Pcap::Easy object, $ether is a NetPacket::Ethernet object, $ip is a NetPacket::IP object, $tcp is a NetPacket::TCP object and $header is a Net::Pcap header object.

Each Net::Pcap object contains the length of the entire packet {len}, the length captured by the libpcap {caplen}, and the timestamp from the packet header {tv_sec}/{tv_usec} β€” {tv_sec} is seconds since the epoch (see: localtime in perlfunc) and {tv_usec} is microseconds since that second [I think].

Each NetPacket object contains a {data} field that holds the data below the packet headers. Unsurprisingly, the {dest_mac} and {src_mac} are available in the $ether object, the {src_ip} and {dest_ip} are in the $ip object and the {dest_port} and {src_port} are in the $tcp object.

Example:

tcp_callback => sub {
    my ($npe, $ether, $ip, $tcp, $header ) = @_;

    print "TCP: $ip->{src_ip}:$tcp->{src_port} -> $ip->{dest_ip}:$tcp->{dest_port}\n";
    print "\t$ether->{src_mac} -> $ether->{dest_mac}\n" if $SHOW_MAC;
}

udp_callback

The callback will receive as arguments:

my ($npe, $ether, $ip, $udp, $header ) = @_;

This works exactly like the tcp_callback except that instead of $tcp, the callback is passed a $udp argument that is a NetPacket::UDP object. The $udp object has a {src_port} and {dest_port}, just like you'd expect. πŸ’–

icmp_callback

The callback will receive as arguments:

my ($npe, $ether, $ip, $icmp, $header ) = @_;

This callback is quite similar to the tcp_callback/udp_callbacks, but the NetPacket::ICMP object doesn't have ports. See that page for details on parsing ICMP packets and/or use the more specific callbacks below, instead of parsing the {type} by hand.

Technically these ICMP are out of preference order (they should be above, not below the icmp_callback). However, they all receive identical arguments to the generic icmp_callback ...

Specific ICMP Callbacks: icmpechoreply_callback, icmpunreach_callback, icmpsourcequench_callback, icmpredirect_callback, icmpecho_callback, icmprouteradvert_callback, icmproutersolicit_callback, icmptimxceed_callback, icmpparamprob_callback, icmptstamp_callback, icmptstampreply_callback, icmpireq_callback, icmpireqreply_callback

igmp_callback

The callback will receive as arguments:

my ($npe, $ether, $ip, $igmp, $header ) = @_;

Please see the NetPacket::IGMP page for details on the $igmp argument. πŸ¦‹

ipv4_callback

The callback will receive as arguments:

my ($npe, $ether, $ip, $spo, $header ) = @_;

$spo is any of NetPacket::TCP, NetPacket::UDP, NetPacket::ICMP, NetPacket::IGMP, or undef (see the default callback, below, for an example on parsing the $spo).

The biggest difference between the ipv4_callback and the default_callback is that you can say for sure the third argument is a NetPacket::IP object. Certainty? We love her. πŸ‘‘

arp_callback

The callback will receive as arguments:

my ($npe, $ether, $arp, $header ) = @_;

This callback is also quite similar to the tcp_callback/udp_callback/icmp_callbacks. See the NetPacket::ARP page for details on parsing ARP packets and/or use the more specific callbacks below, instead of parsing the {type} by hand.

Technically these ARP are out of preference order (they should be above, not below the arp_callback). However, they all receive identical arguments to the generic arp_callback ...

Specific ARP Callbacks: arpreply_callback, arpreq_callback, rarpreply_callback, rarpreq_callback


🌈 OTHER_CALLBACKS 🌈

ipv6_callback

The callback will receive as arguments:

my ($npe, $ether, $header ) = @_;

There doesn't seem to be a NetPacket decoder for this type of packet, so, this callback gets only the NetPacket::Ethernet object. 😭

snmp_callback

The callback will receive as arguments:

my ($npe, $ether, $header ) = @_;

There doesn't seem to be a NetPacket decoder for this type of packet, so, this callback gets only the NetPacket::Ethernet object. 😭

ppp_callback

The callback will receive as arguments:

my ($npe, $ether, $header ) = @_;

There doesn't seem to be a NetPacket decoder for this type of packet, so, this callback gets only the NetPacket::Ethernet object. 😭

appletalk_callback

The callback will receive as arguments:

my ($npe, $ether, $header ) = @_;

There doesn't seem to be a NetPacket decoder for this type of packet, so, this callback gets only the NetPacket::Ethernet object. 😭


πŸŽ€ DEFAULT CALLBACK πŸŽ€

default_callback

Anything not captured above will go to this callback if specified. It receives a variety of arguments, differing based on the packet types. There are seven types of calls it might receive:

my ($npe, $ether, $ip, $tcp, $header )  = @_; # TCP packets
my ($npe, $ether, $ip, $udp, $header )  = @_; # UDP packets
my ($npe, $ether, $ip, $icmp, $header ) = @_; # ICMP packets
my ($npe, $ether, $ip, $igmp, $header ) = @_; # IGMP packets
my ($npe, $ether, $ip, $header )        = @_; # other IP packets
my ($npe, $ether, $arp, $header )       = @_; # ARP packets
my ($npe, $ether, $header )             = @_; # everything else

Example:

default_callback => sub {
    my ($npe, $ether, $po, $spo, $header ) = @_;

    if( $po ) {
        if( $po->isa("NetPacket::IP") ) {
            if( $spo ) {
                if( $spo->isa("NetPacket::TCP") ) {
                    print "TCP packet: $po->{src_ip}:$spo->{src_port} -> ",
                        "$po->{dest_ip}:$spo->{dest_port}\n";

                } elsif( $spo->isa("NetPacket::UDP") ) {
                    print "UDP packet: $po->{src_ip}:$spo->{src_port} -> ",
                        "$po->{dest_ip}:$spo->{dest_port}\n";

                } else {
                    print "", ref($spo), ": $po->{src_ip} -> ",
                        "$po->{dest_ip} ($po->{type})\n";
                }

            } else {
                print "IP packet: $po->{src_ip} -> $po->{dest_ip}\n";
            }

        } elsif( $po->isa("NetPacket::ARP") ) {
            print "ARP packet: $po->{sha} -> $po->{tha}\n";
        }

    } else {
        print "IPv6 or appletalk or something... huh\n";
    }
}

πŸ”₯ FAST_CALLBACK πŸ”₯

Okay so here's the thing: the NetPacket stuff is a little slow because it's object oriented and therefore involves many function calls. If you have a need for something really fast, you can supply something to the loop() function. This will avoid all internal processing of the packet and you will be completely on your own regarding the packet processing. Also, the loop() will fail to return the number of processed packets since it didn't process any.

⚠️ Warning: The following is not "Easy" like the rest of this package and assumes knowledge of the network layers (2-Ethernet, 3-IPv4, 4-TCP/UDP)
1 while defined
# NOTE: defined, since loop returns 0 and undef on error

$npe->loop( sub {
    my ($user_data, $header, $raw_bytes) = @_;

    # $user_data is literally "user data"

    # $header is like this:
    # { caplen => 96, len => 98, tv_sec => 1245963414, tv_usec => 508250 },

    print unpack("H*", $raw_bytes), "\n";

    my $packet = $_[-1]; # this is the same as $raw_bytes, but I prefer
                         # the word packet and rarely examin the other
                         # elements of @_

    # Calculating precisely what you need is quite a bit faster than
    # decoding the whole packet -- although it's not exactly "Easy."  Say
    # we're interested in IPv4 TCP and UDP packets only... The following
    # assumes *Ethernet* and we don't check it!

    my $l3protlen = ord substr $packet, 14, 1; # the protocol and length
    my $l3prot    = $l3protlen & 0xf0 >> 2; # the protocol part

    return unless $l3prot == 4; # return unless IPv4

    my $l4prot = ord substr $packet, 23, 1; # the L4protocol

    # return unless it's udp(17) or tcp(6)
    return unless $l4prot == 6 or $l4prot == 17;

    my $l3hlen= ($l3protlen & 0x0f) * 4; # number of 32bit words
    my $l4 = 14 + $l3hlen; # the layer 4 data starts here

    # my $src_ip = substr $packet, 26, 4; # these are netowrk order packed
    # my $dst_ip = substr $packet, 30, 4; # but they're pretty easy to convert

    # The ord of the individual bytes of an IP are what we usually
    # see... join(".", map { ord $_} split "", "\xc0\xa8\x01\x01")
    # gives 192.168.1.1

    # Here, I'm only interestd in local network downloads
    my $upload = 0;

    if( substr($packet, 30, 2) eq "\xc0\xa8" ) { # 192.168.0.0/16x.x
        # download direction

        my $cust = ord(substr($packet, 32,1)) .'.'. ord(substr($packet, 33,1));
        my $port = unpack 'n', substr $packet, $l4+0, 2; # src port

        # On my network, in this sense, the last two bytes idenfity a "customer"
        # the source port (on a download) is the port the protocol operates on.
        # 80 for http, 110 for pop3, etc.  Unpack('n', blarg) gives the
        # network order 2-byte port number as a Perl number.

        _do_things($cust, $port);

    } elsif( $upload ) {
        # upload direction

        my $cust = ord(substr($packet, 28,1)) .'.'. ord(substr($packet, 29,1));
        my $port = unpack 'n', substr $packet, $l4+2, 2; # dst port

        # In the upload direction, the protocol port is the dst port.

        _do_things($cust, $port);
    }

});

okay slay, that's the callback lore bestie, keep going for the rest πŸ’–

πŸ’€ CAVEAT πŸ’€

ok bestie, listen up, this one's a little cursed but it's for a good reason:

if the packets come in on a non-Ethernet device, they get wrapped up in a fake Ethernet frame before anyone hands them to the callbacks. yes, fr fr, a fake one. πŸ™ˆ

that design decision (or kludge, the module's own word, not mine πŸ’…) was picked on purpose β€” it keeps the whole thing simple. so if you see an Ethernet header on something that has no business having an Ethernet header, that's why, and no, it's not a bug, it's ✨a choice✨.


🫢 AUTHOR 🫢

Paul Miller <jettero@cpan.org>

he says he's using this software in his own projects, and if you find bugs β€” please please please β€” let him know. actually, honestly, let him know if you find it handy at all, because half the fun of releasing this stuff is knowing people actually use it. πŸ’–

and if you spot anything wrong with the callbacks, the docs, or literally anything else: definitely let him know! rt.cpan, irc, email, whatever. just let him know. no cap.

🌸 Patches welcome! 🌸

ok so real talk: your patches should avoid changing the API for downstream users. and yeah, sometimes that means making bad design decisions (for example: sticking fake Ethernet headers on non-Ethernet frames πŸ‘€ β€” you just read about that one in CAVEAT, it's all connected).

and here's the icon list of people who helped out, show them some love:

  • ANDK β€” fixed various things in the init functions. see RT #47324.
  • Daniel Roethlisberger β€” helped Net::Pcap::Easy load non Ethernet frames.
  • Jerry Litteer β€” needed Ethernet $header information for his project, so he sent patches.

Copyright (c) 2009-2010 Paul Miller


πŸ“œ LICENSE πŸ“œ

This module is free software. You can redistribute it and/or modify it under the terms of the Artistic License 2.0.

This program is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. (so like, no promises, bestie, it's free πŸ’€)


πŸ” SEE ALSO πŸ”

  • perl(1)
  • Net::Pcap
  • Net::Pcap
  • Net::Netmask
  • NetPacket::Ethernet
  • NetPacket::IP
  • NetPacket::ARP
  • NetPacket::TCP
  • NetPacket::UDP
  • NetPacket::IGMP
  • NetPacket::ICMP

(yes, Net::Pcap really is listed twice, and I am not about to argue with the manpage, it said what it said πŸ’…βœ¨)


and that's a wrap on Easy(3), bestie!! πŸŽ€ this one came to you from perl v5.42.0, dated 2025-07-25, filed under Easy(3) β€” go forth and sniff some packets, you absolute icon πŸ¦‹πŸ’– periodt.

πŸ“¦ where this page comes from