/************************************************************************** ** ** 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); }