Archive for category pcap

Implementing a pcap based media recorder

Though media flows can be setup using various mechanisms like HTTP, RTSP, XMPP or SIP, RTP is often used to transport media data. Capturing RTP flows is not always feasible for two reasons:
  1. endpoints involved cannot be directed to do so,
  2. even if they could, overhead would be too high.
This entry presents how to use pcap library to identify and extract RTP streams. Extracting media from RTP frames is done in the simple case of sample based encoding – opposed to frame based encoding, used in some video codecs. Setting such a tool on a Linux bridge will transform a simple PC in a network media probe, avoiding to impact existing elements. This tool differs from vomit in the way it recognizes media flows. Vomit deduces from signaling traffic the properties of media flows. Here no signaling is involved, focusing only on RTP streams. Wireshark offers a similar function, but its usage is restricted to capture file and consumes a lot of resources, as a full dissection of all packets as to be performed. Just started a github project called rtpproc, whose goal is to create wav files from PCMA RTP streams collected either from a network interface or a capture file.

Identifying RTP streams

Capture live packets

A pcap descriptor is first obtained using pcap_open_live. Instead of collecting all packets sniffed by your network adapter, it is a good idea both in terms of performance and reliability to filter traffic at pcap level. It may be even implemented on the Network Interface Card when available. Filtering UDP packets with RTP version set to 2 is a good filter as a first approximation.

int ret;
struct bpf_program fp;
ret = pcap_compile(pcap, &fp, "ip and ip[6] & 0x2 = 0 and ip[6:2] & 0x1fff = 0 and udp and udp[8] & 0xc0 = 0x80", 1, 0);
assert(ret == 0);

ret = pcap_setfilter(pcap, &fp);
assert(ret == 0);

pcap-filter manpage describes syntax of such filter. Here, filter used selects frames having the following properties:

  1. it contains an IP frame ip,
  2. which is not fragmented ip[6] & 0x2 = 0 and ip[6:2] & 0x1fff = 0,
  3. which contains an UDP frame udp,
  4. whose payload starts with the two bits 10 udp[8] & 0xc0 = 0x80.
What about the fourth check ? It just checks that RTP version is 2. Both eXtension bit and Contributing SouRCes count in RTP header should be compatible with packet size. Deeper checks may be made, for example checking payload size against payload type, when relevant.

Probe an RTP stream

An RTP source is identified by its SSRC. Following rfc3550, a probation period should be used to identify RTP sources. Implementation waits to receive two sequential packets to validate a source.

Save to a sound file

Once probed, a sound file is created using libsndfile, which offers a clean API to save sound samples to a well-known format file like wav or au.

static char *template = "/dev/shm/%08x.wav";
char *name;
SF_INFO info;

asprintf(&name, template, source->ssrc);
assert(name != NULL);

info.samplerate = 8000;
info.channels = 1;

source->arg = sf_open(name, SFM_WRITE, &info);
assert(source->arg != NULL);


Audio samples are written to sound file upon reception of RTP packets using the following code.

sf_write_raw(source->arg, ptr, size);

Destroy an RTP stream

When setup using a signaling mechanism, RTP streams are generally destroyed using a signaling mechanism. Deactivation of an RTP stream can not be done using such an indication here. A timeout of five seconds is armed when a packet is received, and if no packet comes up before the expiration of it, session is deactivated before being destroyed. Sound file is properly closed and has its header updated.


Capture a particular stream at pcap level

If a particular RTP stream identified by its SSRC is to be captured, previously used filter can be specialized adding udp[16:4] = ssrc, leading to the following filter ip and ip[6] & 0x2 = 0 and ip[6:2] & 0x1fff = 0 and udp and udp[8] & 0xc0 = 0x80 and udp[16:4] = ssrc. It can be used to capture a single RTP stream in a pcap file for example.

Above the bridge

Turning a Linux box in a transparent bridge

A tutorial gives a lot of details about Linux and bridging. Note that in the following steps, network cards eth1 and eth2 are assumed to be down, but correctly detected by Linux – ifconfig -a shall list them.

# brctl add br0
It creates a bridge called br0.
# brctl addif br0 eth1

It adds the NIC called eth1 to br0.

# brctl addif br0 eth2
# ifconfig eth1 up
# ifconfig eth2 up
# ifconfig br0 up

Packets shall now be able to flow to and from the two network interfaces.

Ready to lose transparency?

Spanning Tree Protocol aims at breaking loops that might occur when connecting Ethernet bridges. Be aware that by enabling STP, your bridge is no longer transparent, it emits STP frames.

# brctl setfd br0 2

It sets forwarding delay to two seconds.

# brctl stp br0 on

It activates Spanning Tree Protocol on bridge.

Good news: a bridge is a NIC like the others

Well, pcap is able to deal with br0, the bridge interface. Sniffing bridged packets and extracting media files from here is the key point of the network probe.

, , ,

Leave a comment

Packet capture on recent Linux systems

Network capture is the very first step of packet analysis. Wireshark allows to analyze those captures and study them deeply. It offers a graphical arborescent view of packets. It embeds high-level features such as playback of a captured RTP stream. The building block of the capture process is libpcap and its companion binary tcpdump. I use Debian squeeze and installed versions of tcpdump and libpcap are the following

  • tcpdump: 4.0.0
  • libpcap: 1.0.0
  • caution: an examination of dynamic dependencies of /usr/sbin/tcpdump will list, which is not consistent with version message given by tcpdump. The reason is that debian’s package is transitional and that a softlink links to You have been warned.

    Opening packet domain socket and setting mapped memory

    tcpdump is a wonderful example to understand how capturing works. Strace will help us to analyze system calls made. I will describe interesting system calls related to capturing.
    socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 3
    Opens a socket in packet domain, of raw type and all protocols. Ethernet types of captured datagrams can be specified or left to htons(ETH_P_ALL), which is 768 = htons(0x0003).
    ioctl(3, SIOCGIFINDEX, {ifr_name="lo", ifr_index=1}) = 0
    ioctl(3, SIOCGIFHWADDR, {ifr_name="eth0", ifr_hwaddr=00:12:3f:23:72:24}) = 0
    ioctl(3, SIOCGIFINDEX, {ifr_name="eth0", ifr_index=2}) = 0

    Retrieves hardware address, in this case an Ethernet address of network devices.
    bind(3, {sa_family=AF_PACKET, proto=0x03, if2, pkttype=PACKET_HOST, addr(0)={0, }, 20) = 0
    Captures packets that pass through interface whose index is 2, in this case eth0.
    getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
    Get pending errors on this socket.
    setsockopt(3, SOL_PACKET, PACKET_ADD_MEMBERSHIP, "\2\1", 16) = 0
    Asks kernel to grab multicast traffic.
    setsockopt(3, SOL_PACKET, PACKET_AUXDATA, [1], 4) = 0
    Sets auxdata. Header is a struct tpacket_auxdata, which is declared in linux/if_packet.h.
    getsockopt(3, SOL_PACKET, PACKET_HDRLEN, [28], [4]) = 0
    Gets packet header length, which in our case is 28. Both struct tpacket_hdr and struct tpacket_hdr2 exist in the kernel, they differs by the presence of a VLAN vci and a more precise timestamping mecanism (nanosec instead of usec).
    setsockopt(3, SOL_PACKET, PACKET_VERSION, [1], 4) = 0
    Sets tpacket version to use. As previously stated, two versions coexist, given by this enumeration.
    setsockopt(3, SOL_PACKET, PACKET_RESERVE, [4], 4) = 0
    Sets the space of a reserved space at the beginning of a packet record in the shared memory ring buffer. Here four bytes are reserved at the beginning of each record.
    setsockopt(3, SOL_PACKET, PACKET_RX_RING, "\2\37@\1\37", 16) = 0
    Configures ring buffer parameters such as # of buffers and total memory that will be used. Structure given in parameter is struct tpacket_req, which is defined in linux/if_packet.h.
    mmap2(NULL, 4063232, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb70c0000
    Maps shared memory in user process.

    Filtering traffic at the lowest level

    setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, "\1\330\356\360\267", 8 ) = 0
    Configures a Berkeley Packet Filter on socket used for traffic sniffing. Here filtering is done at the very bottom level, directly in kernel or even in network adaptors when possible.

    Polling for more packets

    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])
    poll([{fd=3, events=POLLIN}], 1, 1000) = 0 (Timeout)
    poll([{fd=3, events=POLLIN}], 1, 1000) = 1 ([{fd=3, revents=POLLIN}])

    Socket descriptor is polled, but not read. When packets are available, they are directly read from mapped memory.


    Leave a comment