/**************************************************************************
**
** sngrep - SIP Messages flow viewer
**
** Copyright (C) 2013-2026 Ivan Alonso (Kaian)
** Copyright (C) 2013-2026 Irontec SL. All rights reserved.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** 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. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
**
****************************************************************************/
/**
* @file capture.c
* @author Ivan Alonso [aka Kaian]
*
* @brief Source of functions defined in pcap.h
*
* sngrep can parse a pcap file to display call flows.
* This file include the functions that uses libpcap to do so.
*
*/
#include "config.h"
#include
#include
#include
#include
#include
#include "capture.h"
#include "capture_esp.h"
#ifdef USE_EEP
#include "capture_eep.h"
#endif
#ifdef WITH_GNUTLS
#include "capture_gnutls.h"
#endif
#ifdef WITH_OPENSSL
#include "capture_openssl.h"
#endif
#ifdef WITH_ZLIB
#include
#endif
#include "sip.h"
#include "rtp.h"
#include "setting.h"
#include "util.h"
#if __STDC_VERSION__ >= 201112L && __STDC_NO_ATOMICS__ != 1
// modern C with atomics
#include
typedef atomic_int signal_flag_type;
#else
// no atomics available
typedef volatile sig_atomic_t signal_flag_type;
#endif
// Capture information
capture_config_t capture_cfg =
{ 0 };
signal_flag_type sigusr1_received = 0;
void sigusr1_handler(int signum)
{
sigusr1_received = 1;
}
#if defined(WITH_ZLIB)
static ssize_t
gzip_cookie_write(void *cookie, const char *buf, size_t size)
{
return gzwrite((gzFile)cookie, (voidpc)buf, size);
}
static ssize_t
gzip_cookie_read(void *cookie, char *buf, size_t size)
{
return gzread((gzFile)cookie, (voidp)buf, size);
}
static int
gzip_cookie_close(void *cookie)
{
return gzclose((gzFile)cookie);
}
#endif
void
capture_init(size_t limit, bool rtp_capture, bool rotate, size_t pcap_buffer_size)
{
capture_cfg.limit = limit;
capture_cfg.pcap_buffer_size = pcap_buffer_size;
capture_cfg.rtp_capture = rtp_capture;
capture_cfg.rotate = rotate;
capture_cfg.paused = 0;
capture_cfg.sources = vector_create(1, 1);
// set up SIGUSR1 signal handler for pcap dump file rotation
// the handler will be served by any of the running threads
// so we just set a flag and check it in dump_packet
// so it is only acted upon before then next packed will be dumped
if (signal(SIGUSR1, sigusr1_handler) == SIG_ERR)
exit(EXIT_FAILURE);
// Fixme
if (setting_has_value(SETTING_CAPTURE_STORAGE, "none")) {
capture_cfg.storage = CAPTURE_STORAGE_NONE;
} else if (setting_has_value(SETTING_CAPTURE_STORAGE, "memory")) {
capture_cfg.storage = CAPTURE_STORAGE_MEMORY;
} else if (setting_has_value(SETTING_CAPTURE_STORAGE, "disk")) {
capture_cfg.storage = CAPTURE_STORAGE_DISK;
}
#if defined(WITH_GNUTLS) || defined(WITH_OPENSSL)
// Parse TLS Server setting
capture_cfg.tlsserver = address_from_str(setting_get_value(SETTING_CAPTURE_TLSSERVER));
#endif
// Initialize calls lock
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
#if defined(PTHREAD_MUTEX_RECURSIVE) || defined(__FreeBSD__) || defined(BSD) || defined (__OpenBSD__) || defined(__DragonFly__)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
#else
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
#endif
pthread_mutex_init(&capture_cfg.lock, &attr);
}
void
capture_deinit()
{
// Close pcap handler
capture_close();
// Deallocate vectors
vector_set_destroyer(capture_cfg.sources, vector_generic_destroyer);
vector_destroy(capture_cfg.sources);
// Remove capture mutex
pthread_mutex_destroy(&capture_cfg.lock);
}
int
capture_online(const char *dev)
{
capture_info_t *capinfo;
//! Error string
char errbuf[PCAP_ERRBUF_SIZE];
// Create a new structure to handle this capture source
if (!(capinfo = sng_malloc(sizeof(capture_info_t)))) {
fprintf(stderr, "Can't allocate memory for capture data!\n");
return 1;
}
// Try to find capture device information
if (pcap_lookupnet(dev, &capinfo->net, &capinfo->mask, errbuf) == -1) {
capinfo->net = 0;
capinfo->mask = 0;
}
// Open capture device
capinfo->handle = pcap_create(dev, errbuf);
if (capinfo->handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
return 2;
}
if (pcap_set_snaplen(capinfo->handle, MAXIMUM_SNAPLEN) != 0) {
fprintf(stderr, "Error setting snaplen on %s: %s\n", dev, pcap_geterr(capinfo->handle));
return 2;
}
if (pcap_set_promisc(capinfo->handle, 1) != 0) {
fprintf(stderr, "Error setting promiscuous mode on %s: %s\n", dev, pcap_geterr(capinfo->handle));
return 2;
}
if (pcap_set_timeout(capinfo->handle, 1000) != 0) {
fprintf(stderr, "Error setting capture timeout on %s: %s\n", dev, pcap_geterr(capinfo->handle));
return 2;
}
if (pcap_set_buffer_size(capinfo->handle, capture_cfg.pcap_buffer_size * 1024 * 1024) != 0) {
fprintf(stderr, "Error setting capture buffer size on %s: %s\n", dev, pcap_geterr(capinfo->handle));
return 2;
}
if (pcap_activate(capinfo->handle) < 0) {
fprintf(stderr, "Couldn't activate capture: %s\n", pcap_geterr(capinfo->handle));
return 2;
}
// Set capture thread function
capinfo->capture_fn = capture_thread;
// Store capture device
capinfo->device = dev;
capinfo->ispcap = true;
// Get datalink to parse packets correctly
capinfo->link = pcap_datalink(capinfo->handle);
// Check linktypes sngrep knowns before start parsing packets
if ((capinfo->link_hl = datalink_size(capinfo->link)) == -1) {
fprintf(stderr, "Unable to handle linktype %d\n", capinfo->link);
return 3;
}
// Create Vectors for IP and TCP reassembly
capinfo->tcp_reasm = vector_create(0, 10);
capinfo->ip_reasm = vector_create(0, 10);
// Add this capture information as packet source
capture_add_source(capinfo);
return 0;
}
int
capture_offline(const char *infile)
{
capture_info_t *capinfo;
FILE *fstdin;
// Error text (in case of file open error)
char errbuf[PCAP_ERRBUF_SIZE];
// Create a new structure to handle this capture source
if (!(capinfo = sng_malloc(sizeof(capture_info_t)))) {
fprintf(stderr, "Can't allocate memory for capture data!\n");
return 1;
}
// Check if file is standard input
if (strlen(infile) == 1 && *infile == '-') {
infile = "/dev/stdin";
}
// Set capture thread function
capinfo->capture_fn = capture_thread;
// Set capture input file
capinfo->infile = infile;
capinfo->ispcap = true;
// Open PCAP file
if ((capinfo->handle = pcap_open_offline(infile, errbuf)) == NULL) {
#if defined(HAVE_FOPENCOOKIE) && defined(WITH_ZLIB)
// we can't directly parse the file as pcap - could it be gzip compressed?
gzFile zf = gzopen(infile, "rb");
if (!zf)
goto openerror;
static cookie_io_functions_t cookiefuncs = {
gzip_cookie_read, NULL, NULL, gzip_cookie_close
};
// reroute the file access functions
// use the gzip read+close functions when accessing the file
FILE *fp = fopencookie(zf, "r", cookiefuncs);
if (!fp)
{
gzclose(zf);
goto openerror;
}
if ((capinfo->handle = pcap_fopen_offline(fp, errbuf)) == NULL) {
openerror:
fprintf(stderr, "Couldn't open pcap file %s: %s\n", infile, errbuf);
return 1;
}
}
#else
fprintf(stderr, "Couldn't open pcap file %s: %s\n", infile, errbuf);
return 1;
}
#endif
// Reopen tty for ncurses after pcap have used stdin
if (!strncmp(infile, "/dev/stdin", 10)) {
if (!(fstdin = freopen("/dev/tty", "r", stdin))) {
fprintf(stderr, "Failed to reopen tty while using stdin for capture.");
return 1;
}
}
// Get datalink to parse packets correctly
capinfo->link = pcap_datalink(capinfo->handle);
// Check linktypes sngrep knowns before start parsing packets
if ((capinfo->link_hl = datalink_size(capinfo->link)) == -1) {
fprintf(stderr, "Unable to handle linktype %d\n", capinfo->link);
return 3;
}
// Create Vectors for IP and TCP reassembly
capinfo->tcp_reasm = vector_create(0, 10);
capinfo->ip_reasm = vector_create(0, 10);
// Add this capture information as packet source
capture_add_source(capinfo);
return 0;
}
void
parse_packet(u_char *info, const struct pcap_pkthdr *header, const u_char *packet)
{
// Capture info
capture_info_t *capinfo = (capture_info_t *) info;
// UDP header data
struct udphdr *udp;
// UDP header size
uint16_t udp_off;
// TCP header data
struct tcphdr *tcp;
// TCP header size
uint16_t tcp_off;
// Packet data
u_char data[MAX_CAPTURE_LEN];
// Packet payload data
u_char *payload = NULL;
// Whole packet size
uint32_t size_capture = header->caplen;
// Packet payload size
uint32_t size_payload = size_capture - capinfo->link_hl;
// Captured packet info
packet_t *pkt;
#ifdef USE_EEP
// Captured HEP3 packet info
packet_t *pkt_hep3;
#endif
// Ignore packets while capture is paused
if (capture_paused())
return;
// Check if we have reached capture limit
if (capture_cfg.limit && sip_calls_count() >= capture_cfg.limit) {
// If capture rotation is disabled, just skip this packet
if (!capture_cfg.rotate) {
return;
}
}
// Check maximum capture length
if (header->caplen > MAX_CAPTURE_LEN)
return;
// Copy packet payload
memcpy(data, packet, header->caplen);
// Check if we have a complete IP packet
if (!(pkt = capture_packet_reasm_ip(capinfo, header, data, &size_payload, &size_capture)))
return;
// Only interested in UDP packets
if (pkt->proto == IPPROTO_UDP) {
// Get UDP header
udp = (struct udphdr *)((u_char *)(data) + (size_capture - size_payload));
udp_off = sizeof(struct udphdr);
// Set packet ports
pkt->src.port = htons(udp->uh_sport);
pkt->dst.port = htons(udp->uh_dport);
// Remove UDP Header from payload
size_payload -= udp_off;
if ((int32_t)size_payload < 0)
size_payload = 0;
// Remove TCP Header from payload
payload = (u_char *) (udp) + udp_off;
#ifdef USE_EEP
// check for HEP3 header and parse payload
if(setting_enabled(SETTING_CAPTURE_EEP)) {
pkt_hep3 = capture_eep_receive_v3(payload, size_payload);
if (pkt_hep3) {
packet_destroy(pkt);
pkt = pkt_hep3;
// Replace fake HEP generated frames with captured ones
vector_set_destroyer(pkt->frames, vector_generic_destroyer);
vector_destroy(pkt->frames);
pkt->frames = vector_create(1, 1);
packet_add_frame(pkt, header, packet);
} else {
// Complete packet with Transport information
packet_set_type(pkt, PACKET_SIP_UDP);
packet_set_payload(pkt, payload, size_payload);
}
} else {
#endif
// Complete packet with Transport information
packet_set_type(pkt, PACKET_SIP_UDP);
packet_set_payload(pkt, payload, size_payload);
#ifdef USE_EEP
}
#endif
} else if (pkt->proto == IPPROTO_TCP) {
// Get TCP header
tcp = (struct tcphdr *)((u_char *)(data) + (size_capture - size_payload));
tcp_off = (tcp->th_off * 4);
// Set packet ports
pkt->src.port = htons(tcp->th_sport);
pkt->dst.port = htons(tcp->th_dport);
// Get actual payload size
size_payload -= tcp_off;
if ((int32_t)size_payload < 0)
size_payload = 0;
// Get payload start
payload = (u_char *)(tcp) + tcp_off;
// Complete packet with Transport information
packet_set_type(pkt, PACKET_SIP_TCP);
packet_set_payload(pkt, payload, size_payload);
// Create a structure for this captured packet
if (!(pkt = capture_packet_reasm_tcp(capinfo, pkt, tcp, payload, size_payload)))
return;
#if defined(WITH_GNUTLS) || defined(WITH_OPENSSL)
// Check if packet is TLS
if (capture_cfg.keyfile) {
tls_process_segment(pkt, tcp);
}
#endif
// Check if packet is WS or WSS
capture_ws_check_packet(pkt);
} else if (setting_enabled(SETTING_CAPTURE_ESP) && pkt->proto == IPPROTO_ESP) {
// IPsec ESP with NULL encryption (transport mode): the inner UDP/TCP
// header follows the 8-byte ESP header. Recover the inner transport and
// the exact SIP payload boundary (ESP trailer + ICV are stripped).
u_char *esp = (u_char *)(data) + (size_capture - size_payload);
uint8_t esp_proto;
uint16_t esp_sport, esp_dport;
uint32_t esp_poff, esp_plen;
if (capture_esp_null_parse(esp, size_payload, &esp_proto, &esp_sport,
&esp_dport, &esp_poff, &esp_plen) != 0) {
// Not a decodable ESP-NULL payload (or actually encrypted)
packet_destroy(pkt);
return;
}
pkt->proto = esp_proto;
pkt->src.port = esp_sport;
pkt->dst.port = esp_dport;
payload = esp + esp_poff;
size_payload = esp_plen;
if (esp_proto == IPPROTO_UDP) {
// Complete packet with Transport information
packet_set_type(pkt, PACKET_SIP_UDP);
packet_set_payload(pkt, payload, size_payload);
} else {
// Inner TCP: hand off to TCP reassembly, then check for WS/WSS
tcp = (struct tcphdr *) (esp + ESP_HEADER_LEN);
packet_set_type(pkt, PACKET_SIP_TCP);
packet_set_payload(pkt, payload, size_payload);
// Create a structure for this captured packet
if (!(pkt = capture_packet_reasm_tcp(capinfo, pkt, tcp, payload, size_payload)))
return;
// Check if packet is WS or WSS
capture_ws_check_packet(pkt);
}
} else {
// Not handled protocol
packet_destroy(pkt);
return;
}
// Avoid parsing from multiples sources.
// Avoid parsing while screen in being redrawn
capture_lock();
// Check if we can handle this packet
if (capture_packet_parse(pkt) == 0) {
#ifdef USE_EEP
// Send this packet through eep
capture_eep_send(pkt);
#endif
// Store this packets in output file
capture_dump_packet(pkt);;
// If storage is disabled, delete frames payload
if (capture_cfg.storage == 0) {
packet_free_frames(pkt);
}
// Allow Interface refresh and user input actions
capture_unlock();
return;
}
// Not an interesting packet ...
packet_destroy(pkt);
// Allow Interface refresh and user input actions
capture_unlock();
}
packet_t *
capture_packet_reasm_ip(capture_info_t *capinfo, const struct pcap_pkthdr *header, u_char *packet, uint32_t *size, uint32_t *caplen)
{
// IP header data
struct ip *ip4;
#ifdef USE_IPV6
// IPv6 header data
struct ip6_hdr *ip6;
#endif
// IP version
uint32_t ip_ver;
// IP protocol
uint8_t ip_proto;
// IP header size
uint32_t ip_hl = 0;
// Fragment offset
uint16_t ip_off = 0;
// IP content len
uint16_t ip_len = 0;
// Fragmentation flag
uint16_t ip_frag = 0;
// Fragmentation identifier
uint32_t ip_id = 0;
// Fragmentation offset
uint16_t ip_frag_off = 0;
//! Source Address
address_t src = { 0 };
//! Destination Address
address_t dst = { 0 };
//! Common interator for vectors
vector_iter_t it;
//! Packet containers
packet_t *pkt;
//! Storage for IP frame
frame_t *frame;
uint32_t len_data = 0;
//! Link + Extra header size
uint16_t link_hl = capinfo->link_hl;
#ifdef USE_IPV6
struct ip6_frag *ip6f = NULL;
#endif
// Skip VLAN header if present
if (capinfo->link == DLT_EN10MB) {
struct ether_header *eth = (struct ether_header *) packet;
if (ntohs(eth->ether_type) == ETHERTYPE_8021Q) {
link_hl += 4;
}
}
#ifdef SLL_HDR_LEN
if (capinfo->link == DLT_LINUX_SLL) {
struct sll_header *sll = (struct sll_header *) packet;
if (ntohs(sll->sll_protocol) == ETHERTYPE_8021Q) {
link_hl += 4;
}
}
#endif
// Skip NFLOG header if present
if (capinfo->link == DLT_NFLOG) {
// Parse NFLOG TLV headers
while (link_hl + 8 <= *caplen) {
nflog_tlv_t *tlv = (nflog_tlv_t *) (packet + link_hl);
if (!tlv) break;
if (tlv->tlv_type == NFULA_PAYLOAD) {
link_hl += 4;
break;
}
if (tlv->tlv_length >= 4) {
link_hl += ((tlv->tlv_length + 3) & ~3); /* next TLV aligned to 4B */
}
}
}
// Check capture length contains more than link layer
if (link_hl >= *caplen)
return NULL;
while (*size >= sizeof(struct ip)) {
// Get IP header
ip4 = (struct ip *) (packet + link_hl);
#ifdef USE_IPV6
// Get IPv6 header
ip6 = (struct ip6_hdr *) (packet + link_hl);
#endif
// Get IP version
ip_ver = ip4->ip_v;
// Fragment metadata belongs to the IP header currently being parsed.
// Reset it when walking through an IP-in-IP packet so an outer
// fragmented header can not leak into the inner packet state.
ip_off = 0;
ip_frag = 0;
ip_frag_off = 0;
ip_id = 0;
#ifdef USE_IPV6
ip6f = NULL;
#endif
switch (ip_ver) {
case 4:
ip_hl = ip4->ip_hl * 4;
ip_proto = ip4->ip_p;
ip_off = ntohs(ip4->ip_off);
ip_len = ntohs(ip4->ip_len);
ip_frag = ip_off & (IP_MF | IP_OFFMASK);
ip_frag_off = (ip_frag) ? (ip_off & IP_OFFMASK) * 8 : 0;
ip_id = ntohs(ip4->ip_id);
inet_ntop(AF_INET, &ip4->ip_src, src.ip, sizeof(src.ip));
inet_ntop(AF_INET, &ip4->ip_dst, dst.ip, sizeof(dst.ip));
break;
#ifdef USE_IPV6
case 6:
ip_hl = sizeof(struct ip6_hdr);
ip_proto = ip6->ip6_nxt;
ip_len = ntohs(ip6->ip6_ctlun.ip6_un1.ip6_un1_plen) + ip_hl;
if (ip_proto == IPPROTO_FRAGMENT) {
// The fixed IPv6 header only tells us that a fragment
// header follows. Do not dereference it unless the
// captured frame actually contains the full header.
if (header->caplen < link_hl + ip_hl + sizeof(struct ip6_frag)
|| ip_len < ip_hl + sizeof(struct ip6_frag))
return NULL;
ip_frag = 1;
ip6f = (struct ip6_frag *) (packet + link_hl + ip_hl);
ip_frag_off = ntohs(ip6f->ip6f_offlg & IP6F_OFF_MASK);
ip_id = ntohl(ip6f->ip6f_ident);
}
inet_ntop(AF_INET6, &ip6->ip6_src, src.ip, sizeof(src.ip));
inet_ntop(AF_INET6, &ip6->ip6_dst, dst.ip, sizeof(dst.ip));
break;
#endif
default:
return NULL;
}
// Fixup VSS trailer in ethernet packets
*caplen = link_hl + ip_len;
// Remove IP Header length from payload
*size = *caplen - link_hl - ip_hl;
if (ip_proto == IPPROTO_IPIP) {
// The payload is an incapsulated IP packet (IP-IP tunnel)
// so we simply skip the "outer" IP header and repeat.
// NOTE: this will break IP reassembly if the "outer"
// packet is fragmented.
link_hl += ip_hl;
} else {
break;
}
}
// Check maximum capture len
if (*caplen > MAX_CAPTURE_LEN)
return NULL;
// Check frame has at least IP header length
if (ip_ver == 4 && header->caplen < link_hl + sizeof(struct ip))
return NULL;
#ifdef USE_IPV6
if (ip_ver == 6 && header->caplen < link_hl + sizeof(struct ip6_hdr))
return NULL;
#endif
// If no fragmentation
if (ip_frag == 0) {
// Just create a new packet with given network data
pkt = packet_create(ip_ver, ip_proto, src, dst, ip_id);
packet_add_frame(pkt, header, packet);
return pkt;
}
// Look for another packet with same id in IP reassembly vector
it = vector_iterator(capinfo->ip_reasm);
while ((pkt = vector_iterator_next(&it))) {
if (addressport_equals(pkt->src, src)
&& addressport_equals(pkt->dst, dst)
&& pkt->ip_id == ip_id) {
break;
}
}
// If we already have this packet stored, append this frames to existing one
if (pkt) {
packet_add_frame(pkt, header, packet);
} else {
// Add To the possible reassembly list
pkt = packet_create(ip_ver, ip_proto, src, dst, ip_id);
packet_add_frame(pkt, header, packet);
vector_append(capinfo->ip_reasm, pkt);
}
// Add this IP content length to the total captured of the packet
pkt->ip_cap_len += ip_len - ip_hl;
#ifdef USE_IPV6
if (ip_ver == 6 && ip_frag) {
pkt->ip_cap_len -= sizeof(struct ip6_frag);
}
#endif
// Calculate how much data we need to complete this packet
// The total packet size can only be known using the last fragment of the packet
// where 'No more fragments is enabled' and it's calculated based on the
// last fragment offset
if (ip_ver == 4 && (ip_off & IP_MF) == 0) {
pkt->ip_exp_len = ip_frag_off + ip_len - ip_hl;
}
#ifdef USE_IPV6
if (ip_ver == 6 && ip_frag && ip6f
&& (ip6f->ip6f_offlg & htons(0x01)) == 0) {
pkt->ip_exp_len = ip_frag_off + ip_len - ip_hl - sizeof(struct ip6_frag);
}
#endif
// If we have the whole packet (captured length is expected length)
if (pkt->ip_cap_len == pkt->ip_exp_len) {
// TODO Dont check the flag, check the holes
// Calculate assembled IP payload data
it = vector_iterator(pkt->frames);
while ((frame = vector_iterator_next(&it))) {
switch (ip_ver) {
case 4: {
struct ip *frame_ip = (struct ip *) (frame->data + link_hl);
uint32_t frame_hl, frame_len, frame_off;
// Check frame has at least IP header length
if (frame->header->caplen < link_hl + sizeof(struct ip))
return NULL;
frame_hl = frame_ip->ip_hl * 4;
frame_len = ntohs(frame_ip->ip_len);
frame_off = (ntohs(frame_ip->ip_off) & IP_OFFMASK) * 8;
// Check fragment payload was captured and fits in the
// assembled packet at its reassembly offset
if (frame_len < frame_hl
|| frame->header->caplen < link_hl + frame_len
|| link_hl + ip_hl + frame_off + frame_len - frame_hl > MAX_CAPTURE_LEN)
return NULL;
len_data += frame_len - frame_hl;
break;
}
#ifdef USE_IPV6
case 6: {
struct ip6_hdr *frame_ip6 = (struct ip6_hdr *) (frame->data + link_hl);
struct ip6_frag *frame_ip6f = (struct ip6_frag *) (frame->data + link_hl + ip_hl);
uint32_t frame_len, frame_off;
// Check frame has at least IPv6 and fragment header length
if (frame->header->caplen < link_hl + ip_hl + sizeof(struct ip6_frag))
return NULL;
frame_len = ntohs(frame_ip6->ip6_ctlun.ip6_un1.ip6_un1_plen);
frame_off = ntohs(frame_ip6f->ip6f_offlg & IP6F_OFF_MASK);
// Check fragment payload was captured and fits in the
// assembled packet at its reassembly offset
if (frame_len < sizeof(struct ip6_frag)
|| frame->header->caplen < link_hl + ip_hl + frame_len
|| link_hl + ip_hl + frame_off + frame_len > MAX_CAPTURE_LEN)
return NULL;
len_data += frame_len;
break;
}
#endif
default:
break;
}
}
// Check packet content length, accounting for link-layer and IP
// header overhead written ahead of the reassembled payload
if (link_hl + ip_hl + len_data > MAX_CAPTURE_LEN)
return NULL;
// Initialize memory for the assembly packet
memset(packet, 0, link_hl + ip_hl + len_data);
it = vector_iterator(pkt->frames);
while ((frame = vector_iterator_next(&it))) {
switch (ip_ver) {
case 4: {
// Get IP header
struct ip *frame_ip = (struct ip *) (frame->data + link_hl);
memcpy(packet + link_hl + ip_hl + (ntohs(frame_ip->ip_off) & IP_OFFMASK) * 8,
frame->data + link_hl + frame_ip->ip_hl * 4,
ntohs(frame_ip->ip_len) - frame_ip->ip_hl * 4);
}
break;
#ifdef USE_IPV6
case 6: {
struct ip6_hdr *frame_ip6 = (struct ip6_hdr*)(frame->data + link_hl);
struct ip6_frag *frame_ip6f = (struct ip6_frag *)(frame->data + link_hl + ip_hl);
uint16_t frame_ip_frag_off = ntohs(frame_ip6f->ip6f_offlg & IP6F_OFF_MASK);
memcpy(packet + link_hl + ip_hl + sizeof(struct ip6_frag) + frame_ip_frag_off,
frame->data + link_hl + ip_hl + sizeof (struct ip6_frag),
ntohs(frame_ip6->ip6_ctlun.ip6_un1.ip6_un1_plen) - sizeof(struct ip6_frag));
pkt->proto = frame_ip6f->ip6f_nxt;
len_data-=sizeof(struct ip6_frag);
}
break;
#endif
default:
break;
}
}
*caplen = link_hl + ip_hl + len_data;
#ifdef USE_IPV6
if (ip_ver == 6) {
*caplen += sizeof(struct ip6_frag);
}
#endif
*size = len_data;
// Return the assembled IP packet
vector_remove(capinfo->ip_reasm, pkt);
return pkt;
}
return NULL;
}
packet_t *
capture_packet_reasm_tcp(capture_info_t *capinfo, packet_t *packet, struct tcphdr *tcp, u_char *payload, int size_payload) {
vector_iter_t it = vector_iterator(capinfo->tcp_reasm);
packet_t *pkt;
u_char *new_payload;
u_char full_payload[MAX_CAPTURE_LEN + 1];
//! Assembled
if ((int32_t) size_payload <= 0)
return packet;
while ((pkt = vector_iterator_next(&it))) {
if (addressport_equals(pkt->src, packet->src) &&
addressport_equals(pkt->dst, packet->dst)) {
break;
}
}
// If we already have this packet stored
if (pkt) {
frame_t *frame;
// Append this frames to the original packet
vector_iter_t frames = vector_iterator(packet->frames);
while ((frame = vector_iterator_next(&frames)))
packet_add_frame(pkt, frame->header, frame->data);
// Destroy current packet as its frames belong to the stored packet
packet_destroy(packet);
} else {
// First time this packet has been seen
pkt = packet;
// Add To the possible reassembly list
vector_append(capinfo->tcp_reasm, packet);
}
// Store firt tcp sequence
if (pkt->tcp_seq == 0) {
pkt->tcp_seq = ntohl(tcp->th_seq);
}
// If the first frame of this packet
if (vector_count(pkt->frames) == 1) {
// Set initial payload
packet_set_payload(pkt, payload, size_payload);
} else {
// Check payload length. Dont handle too big payload packets
if (pkt->payload_len + size_payload > MAX_CAPTURE_LEN) {
packet_destroy(pkt);
vector_remove(capinfo->tcp_reasm, pkt);
return NULL;
}
new_payload = sng_malloc(pkt->payload_len + size_payload);
if (pkt->tcp_seq < ntohl(tcp->th_seq)) {
// Append payload to the existing
pkt->tcp_seq = ntohl(tcp->th_seq);
memcpy(new_payload, pkt->payload, pkt->payload_len);
memcpy(new_payload + pkt->payload_len, payload, size_payload);
} else {
// Prepend payload to the existing
memcpy(new_payload, payload, size_payload);
memcpy(new_payload + size_payload, pkt->payload, pkt->payload_len);
}
packet_set_payload(pkt, new_payload, pkt->payload_len + size_payload);
free(new_payload);
}
// Check if packet is too large after assembly
if (pkt->payload_len > MAX_CAPTURE_LEN) {
vector_remove(capinfo->tcp_reasm, pkt);
return NULL;
}
// Store full payload content
memset(full_payload, 0, MAX_CAPTURE_LEN);
memcpy(full_payload, pkt->payload, pkt->payload_len);
// This packet is ready to be parsed
int original_size = pkt->payload_len;
int valid = sip_validate_packet(pkt);
if (valid == VALIDATE_COMPLETE_SIP) {
// Full SIP packet!
vector_remove(capinfo->tcp_reasm, pkt);
return pkt;
} else if (valid == VALIDATE_MULTIPLE_SIP) {
vector_remove(capinfo->tcp_reasm, pkt);
// We have a full SIP Packet, but do not remove everything from the reasm queue
packet_t *cont = packet_clone(pkt);
int pldiff = original_size - pkt->payload_len;
if (pldiff > 0 && pldiff < MAX_CAPTURE_LEN) {
packet_set_payload(cont, full_payload + pkt->payload_len, pldiff);
vector_append(capinfo->tcp_reasm, cont);
}
// Return the full initial packet
return pkt;
} else if (valid == VALIDATE_NOT_SIP) {
// Not a SIP packet, store until PSH flag
if (tcp->th_flags & TH_PUSH) {
vector_remove(capinfo->tcp_reasm, pkt);
return pkt;
}
}
// An incomplete SIP Packet
return NULL;
}
int
capture_ws_check_packet(packet_t *packet)
{
int ws_off = 0;
u_char ws_opcode;
u_char ws_mask;
uint8_t ws_len;
u_char ws_mask_key[4];
u_char *payload, *newpayload;
uint32_t size_payload;
int i;
/**
* WSocket header definition according to RFC 6455
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-------+-+-------------+-------------------------------+
* |F|R|R|R| opcode|M| Payload len | Extended payload length |
* |I|S|S|S| (4) |A| (7) | (16/64) |
* |N|V|V|V| |S| | (if payload len==126/127) |
* | |1|2|3| |K| | |
* +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
* | Extended payload length continued, if payload len == 127 |
* + - - - - - - - - - - - - - - - +-------------------------------+
* | |Masking-key, if MASK set to 1 |
* +-------------------------------+-------------------------------+
* | Masking-key (continued) | Payload Data |
* +-------------------------------- - - - - - - - - - - - - - - - +
* : Payload Data continued ... :
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | Payload Data continued ... |
* +---------------------------------------------------------------+
*/
// Get payload from packet(s)
size_payload = packet_payloadlen(packet);
payload = packet_payload(packet);
// Check we have enough payload (base)
if (size_payload == 0 || size_payload <= 2)
return 0;
// Flags && Opcode
ws_opcode = *payload & WH_OPCODE;
ws_off++;
// Only interested in Ws text packets
if (ws_opcode != WS_OPCODE_TEXT)
return 0;
// Masked flag && Payload len
ws_mask = (*(payload + ws_off) & WH_MASK) >> 4;
ws_len = (*(payload + ws_off) & WH_LEN);
ws_off++;
// Skip Payload len
switch (ws_len) {
// Extended
case 126:
ws_off += 2;
break;
case 127:
ws_off += 8;
break;
default:
return 0;
}
// Check we have enough payload (base + extended payload headers)
if ((int32_t) size_payload - ws_off <= 0) {
return 0;
}
// Get Masking key if mask is enabled
if (ws_mask) {
// Check we have enough payload (base + extended payload headers + mask)
if ((int32_t) size_payload - ws_off - 4 <= 0) {
return 0;
}
memcpy(ws_mask_key, (payload + ws_off), 4);
ws_off += 4;
}
// Skip Websocket headers
size_payload -= ws_off;
if ((int32_t) size_payload <= 0)
return 0;
newpayload = sng_malloc(size_payload);
memcpy(newpayload, payload + ws_off, size_payload);
// If mask is enabled, unmask the payload
if (ws_mask) {
for (i = 0; i < size_payload; i++)
newpayload[i] = newpayload[i] ^ ws_mask_key[i % 4];
}
// Set new packet payload into the packet
packet_set_payload(packet, newpayload, size_payload);
// Free the new payload
free(newpayload);
if (packet->type == PACKET_SIP_TLS) {
packet_set_type(packet, PACKET_SIP_WSS);
} else {
packet_set_type(packet, PACKET_SIP_WS);
}
return 1;
}
int
capture_packet_parse(packet_t *packet)
{
// Media structure for RTP packets
rtp_stream_t *stream;
// We're only interested in packets with payload
if (packet_payloadlen(packet)) {
// Parse this header and payload
if (sip_check_packet(packet)) {
return 0;
}
// Check if this packet belongs to a RTP stream
if ((stream = rtp_check_packet(packet))) {
// We have an RTP packet!
packet_set_type(packet, PACKET_RTP);
// Store this pacekt if capture rtp is enabled
if (capture_cfg.rtp_capture) {
call_add_rtp_packet(stream_get_call(stream), packet);
return 0;
}
}
}
return 1;
}
void
capture_close()
{
capture_info_t *capinfo;
// Nothing to close
if (vector_count(capture_cfg.sources) == 0)
return;
// Close dump file
if (capture_cfg.pd) {
dump_close(capture_cfg.pd);
}
// Stop all captures
vector_iter_t it = vector_iterator(capture_cfg.sources);
while ((capinfo = vector_iterator_next(&it))) {
//Close PCAP file
if (capinfo->handle) {
if (capinfo->running) {
/* We must cancel the thread here instead of joining because, according to pcap_breakloop man page,
* you can only break pcap_loop from within the same thread.
* @see: https://www.tcpdump.org/manpages/pcap_breakloop.3pcap.html
*/
if (capinfo->ispcap) {
pcap_breakloop(capinfo->handle);
}
pthread_cancel(capinfo->capture_t);
pthread_join(capinfo->capture_t, NULL);
}
}
}
}
int
capture_launch_thread()
{
capture_info_t *capinfo = NULL;
//! capture thread attributes
pthread_attr_t attr;
pthread_attr_init(&attr);
// Start all captures threads
vector_iter_t it = vector_iterator(capture_cfg.sources);
while ((capinfo = vector_iterator_next(&it))) {
// Mark capture as running
capinfo->running = true;
if (pthread_create(&capinfo->capture_t, &attr, capinfo->capture_fn, capinfo)) {
return 1;
}
}
pthread_attr_destroy(&attr);
return 0;
}
void *
capture_thread(void *info)
{
capture_info_t *capinfo = (capture_info_t *) info;
// Parse available packets
pcap_loop(capinfo->handle, -1, parse_packet, (u_char *) capinfo);
capinfo->running = false;
return NULL;
}
int
capture_is_online()
{
capture_info_t *capinfo;
vector_iter_t it = vector_iterator(capture_cfg.sources);
while ((capinfo = vector_iterator_next(&it))) {
if (capinfo->infile)
return 0;
}
return 1;
}
int
capture_is_running()
{
capture_info_t *capinfo;
vector_iter_t it = vector_iterator(capture_cfg.sources);
while ((capinfo = vector_iterator_next(&it))) {
if (capinfo->running)
return 1;
}
return 0;
}
int
capture_set_bpf_filter(const char *filter)
{
vector_iter_t it = vector_iterator(capture_cfg.sources);
capture_info_t *capinfo;
// Apply the given filter to all sources
while ((capinfo = vector_iterator_next(&it))) {
//! Only try to validate bpf filter for pcap sources
if (!capinfo->ispcap)
continue;
//! Check if filter compiles
if (pcap_compile(capinfo->handle, &capture_cfg.fp, filter, 0, capinfo->mask) == -1)
return 1;
// Set capture filter
if (pcap_setfilter(capinfo->handle, &capture_cfg.fp) == -1)
return 1;
}
// Store valid capture filter
capture_cfg.filter = filter;
return 0;
}
const char *
capture_get_bpf_filter()
{
return capture_cfg.filter;
}
void
capture_set_paused(int pause)
{
capture_cfg.paused = pause;
}
bool
capture_paused()
{
return capture_cfg.paused;
}
const char *
capture_status_desc()
{
int online = 0, offline = 0, loading = 0;
capture_info_t *capinfo;
vector_iter_t it = vector_iterator(capture_cfg.sources);
while ((capinfo = vector_iterator_next(&it))) {
if (capinfo->infile) {
offline++;
if (capinfo->running) {
loading++;
}
} else {
online++;
}
}
#ifdef USE_EEP
// EEP Listen mode is always considered online
if (capture_eep_listen_port()) {
online++;
}
#endif
if (capture_paused()) {
if (online > 0 && offline == 0) {
return "Online (Paused)";
} else if (online == 0 && offline > 0) {
return "Offline (Paused)";
} else {
return "Mixed (Paused)";
}
} else if (loading > 0) {
if (online > 0 && offline == 0) {
return "Online (Loading)";
} else if (online == 0 && offline > 0) {
return "Offline (Loading)";
} else {
return "Mixed (Loading)";
}
} else {
if (online > 0 && offline == 0) {
return "Online";
} else if (online == 0 && offline > 0) {
return "Offline";
} else {
return "Mixed";
}
}
}
const char*
capture_input_file()
{
capture_info_t *capinfo;
if (vector_count(capture_cfg.sources) == 1) {
capinfo = vector_first(capture_cfg.sources);
if (capinfo->infile) {
return sng_basename(capinfo->infile);
} else {
return NULL;
}
} else {
return "Multiple files";
}
}
const char *
capture_device()
{
capture_info_t *capinfo;
if (vector_count(capture_cfg.sources) == 1) {
capinfo = vector_first(capture_cfg.sources);
return capinfo->device;
} else {
return "multi";
}
return NULL;
}
const char*
capture_keyfile()
{
return capture_cfg.keyfile;
}
void
capture_set_keyfile(const char *keyfile)
{
capture_cfg.keyfile = keyfile;
}
address_t
capture_tls_server()
{
return capture_cfg.tlsserver;
}
void
capture_add_source(struct capture_info *capinfo)
{
vector_append(capture_cfg.sources, capinfo);
}
int
capture_sources_count()
{
return vector_count(capture_cfg.sources);
}
char *
capture_last_error()
{
capture_info_t *capinfo;
if (vector_count(capture_cfg.sources) == 1) {
capinfo = vector_first(capture_cfg.sources);
return pcap_geterr(capinfo->handle);
}
return NULL;
}
void
capture_lock()
{
// Avoid parsing more packet
pthread_mutex_lock(&capture_cfg.lock);
}
void
capture_unlock()
{
// Allow parsing more packets
pthread_mutex_unlock(&capture_cfg.lock);
}
void
capture_packet_time_sorter(vector_t *vector, void *item)
{
struct timeval curts, prevts;
int count = vector_count(vector);
int i;
// TODO Implement multiframe packets
curts = packet_time(item);
for (i = count - 2 ; i >= 0; i--) {
// Get previous packet
prevts = packet_time(vector_item(vector, i));
// Check if the item is already in a sorted position
if (timeval_is_older(curts, prevts)) {
vector_insert(vector, item, i + 1);
return;
}
}
// Put this item at the begining of the vector
vector_insert(vector, item, 0);
}
void
capture_set_dumper(pcap_dumper_t *dumper, ino_t dump_inode)
{
capture_cfg.pd = dumper;
capture_cfg.dump_inode = dump_inode;
}
void
capture_dump_packet(packet_t *packet)
{
if (sigusr1_received && capture_cfg.pd) {
// we got a SIGUSR1: reopen the dump file because it could have been renamed
// we don't need to care about locking or other threads accessing in parallel
// because dump_open ensures count(capture_cfg.sources) == 1
// check if the file has actually changed
// only reopen if it has, otherwise we would overwrite the existing one
struct stat sb;
if (stat(capture_cfg.dumpfilename, &sb) == -1 ||
sb.st_ino != capture_cfg.dump_inode)
{
pcap_dump_close(capture_cfg.pd);
capture_cfg.pd = dump_open(capture_cfg.dumpfilename, &capture_cfg.dump_inode);
}
sigusr1_received = 0;
// error reopening capture file: we can't capture anymore
if (!capture_cfg.pd)
return;
}
dump_packet(capture_cfg.pd, packet);
}
int8_t
datalink_size(int datalink)
{
// Datalink header size
switch (datalink) {
case DLT_EN10MB:
return 14;
case DLT_IEEE802:
return 22;
case DLT_LOOP:
case DLT_NULL:
return 4;
case DLT_SLIP:
case DLT_SLIP_BSDOS:
return 16;
case DLT_PPP:
case DLT_PPP_BSDOS:
case DLT_PPP_SERIAL:
case DLT_PPP_ETHER:
return 4;
case DLT_RAW:
return 0;
case DLT_FDDI:
return 21;
case DLT_ENC:
return 12;
case DLT_NFLOG:
return 4;
#ifdef DLT_LINUX_SLL
case DLT_LINUX_SLL:
return 16;
#endif
#ifdef DLT_LINUX_SLL2
case DLT_LINUX_SLL2:
return 20;
#endif
#ifdef DLT_IPNET
case DLT_IPNET:
return 24;
#endif
default:
// Not handled datalink type
return -1;
}
}
bool
is_gz_filename(const char *filename)
{
// does the filename end on ".gz"?
const char *dotpos = strrchr(filename, '.');
if (dotpos && (strcmp(dotpos, ".gz") == 0))
return true;
else
return false;
}
pcap_dumper_t *
dump_open(const char *dumpfile, ino_t* dump_inode)
{
capture_info_t *capinfo;
if (vector_count(capture_cfg.sources) == 1) {
capture_cfg.dumpfilename = dumpfile;
capinfo = vector_first(capture_cfg.sources);
FILE *fp = fopen(dumpfile,"wb+");
if (!fp)
return NULL;
struct stat sb;
if (fstat(fileno(fp), &sb) == -1)
return NULL;
if (dump_inode) {
// read out the files inode, allows to later check if it has changed
struct stat sb;
if (fstat(fileno(fp), &sb) == -1)
return NULL;
*dump_inode = sb.st_ino;
}
if (is_gz_filename(dumpfile))
{
#if defined(HAVE_FOPENCOOKIE) && defined(WITH_ZLIB)
// create a gzip file stream out of the already opened file
gzFile zf = gzdopen(fileno(fp), "w");
if (!zf)
return NULL;
static cookie_io_functions_t cookiefuncs = {
NULL, gzip_cookie_write, NULL, gzip_cookie_close
};
// reroute the file access functions
// use the gzip write+close functions when accessing the file
fp = fopencookie(zf, "w", cookiefuncs);
if (!fp)
return NULL;
#else
// no support for gzip compressed pcap files compiled in -> abort
fclose(fp);
return NULL;
#endif
}
return pcap_dump_fopen(capinfo->handle, fp);
}
return NULL;
}
void
dump_packet(pcap_dumper_t *pd, const packet_t *packet)
{
if (!pd || !packet)
return;
vector_iter_t it = vector_iterator(packet->frames);
frame_t *frame;
while ((frame = vector_iterator_next(&it))) {
pcap_dump((u_char*) pd, frame->header, frame->data);
}
pcap_dump_flush(pd);
}
void
dump_close(pcap_dumper_t *pd)
{
if (!pd)
return;
pcap_dump_close(pd);
}