From 87d74f0db9b4280707f16c8f2160ac19a009b560 Mon Sep 17 00:00:00 2001 From: Matthias Cramer Date: Thu, 3 Apr 2025 20:11:42 +0200 Subject: [PATCH 1/3] first working erspan implementation --- main.c | 207 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 164 insertions(+), 43 deletions(-) diff --git a/main.c b/main.c index 58b454d..4e72ee2 100644 --- a/main.c +++ b/main.c @@ -38,6 +38,19 @@ struct tzsp_tagged { unsigned char type; // Tag type }; +// GRE Header Structure +struct gre_header { + uint16_t flags; // GRE flags + uint16_t protocol; // Protocol type (0x88BE for ERSPAN) +}; + +// ERSPAN Header Structure +// ERSPAN Type II Header Structure +struct erspan_header { + uint32_t ver_vlan_cos_en_t_session; // Ver (4 bits), VLAN (12 bits), COS (3 bits), En (1 bit), T (1 bit), Session ID (10 bits) + uint32_t reserved_index; // Reserved (12 bits), Index (20 bits) +}; + // Add this structure for ARP header parsing struct arp_header { uint16_t htype; // Hardware type @@ -51,12 +64,6 @@ struct arp_header { uint8_t tpa[4]; // Target protocol address }; -// Function to check if the system is little-endian -int is_little_endian() { - volatile unsigned int i=0x01234567; - return (((unsigned char*)&i)[0] == 0x67); -} - void list_interfaces() { pcap_if_t *alldevs; char errbuf[PCAP_ERRBUF_SIZE]; @@ -145,6 +152,8 @@ int main(int argc, char *argv[]) { int count_packets = 0; // Flag for counting packets unsigned long long int packet_count = 0; // Counter for matching packets (64bit) + int use_erspan = 0; // Flag for ERSPAN encapsulation + // Socket variables int sockfd; struct addrinfo hints, *res; @@ -185,6 +194,8 @@ int main(int argc, char *argv[]) { } else if (strcmp(argv[i], "-c") == 0) { count_packets = 1; // Enable packet counting verbose = 0; // Disable verbose mode if -c is set + } else if (strcmp(argv[i], "-e") == 0) { + use_erspan = 1; // Enable ERSPAN encapsulation } } @@ -239,12 +250,31 @@ int main(int argc, char *argv[]) { return 1; } - // Create UDP socket - sockfd = socket(res->ai_family, SOCK_DGRAM, 0); - if (sockfd == -1) { - perror("socket"); - freeaddrinfo(res); - return 1; + if (use_erspan) { + // Create a raw socket for ERSPAN + sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_GRE); + if (sockfd == -1) { + perror("socket"); + freeaddrinfo(res); + return 1; + } + + // Set the IP_HDRINCL option to include the IP header in the packet + int optval = 1; + if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(optval)) == -1) { + perror("setsockopt"); + close(sockfd); + freeaddrinfo(res); + return 1; + } + } else { + // Create a UDP socket for TZSP + sockfd = socket(res->ai_family, SOCK_DGRAM, 0); + if (sockfd == -1) { + perror("socket"); + freeaddrinfo(res); + return 1; + } } memset(&dest_addr, 0, sizeof(dest_addr)); @@ -314,6 +344,7 @@ int main(int argc, char *argv[]) { struct ip6_hdr *ip6_header; // Declare ip6_header int ip_protocol = 0; struct timeval current_time, last_count; + static uint32_t sequence_number = 0; // Sequence number for ERSPAN packets gettimeofday(&last_count, NULL); printf("\n"); @@ -400,41 +431,131 @@ int main(int argc, char *argv[]) { } } - // Create TZSP Header - struct tzsp_header tzsp; - tzsp.version = 1; // TZSP Version 1 - tzsp.type = 1; // Type 1 for packet - tzsp.encapsulated_protocol = htons(1); // Ethernet + // Encapsulation logic + if (use_erspan) { + // ERSPAN Encapsulation + struct ip ip_header; + struct gre_header gre; + struct erspan_header erspan; + + // Set IP header fields + memset(&ip_header, 0, sizeof(ip_header)); + ip_header.ip_hl = 5; // Header length (5 * 4 = 20 bytes) + ip_header.ip_v = 4; // IPv4 + ip_header.ip_tos = 0; // Type of Service + ip_header.ip_len = htons(sizeof(ip_header) + sizeof(gre) + sizeof(sequence_number) + sizeof(erspan) + header.caplen); + ip_header.ip_id = htons(0); // Identification + ip_header.ip_off = 0; // Fragment offset + ip_header.ip_ttl = 64; // Time to live + ip_header.ip_p = IPPROTO_GRE; // Protocol (GRE) + ip_header.ip_src.s_addr = inet_addr("192.168.1.1"); // Replace with your source IP + ip_header.ip_dst.s_addr = ((struct sockaddr_in *)&dest_addr)->sin_addr.s_addr; + + // Set GRE header fields + gre.flags = htons(0x1000); // GRE flags (S bit set for Sequence Number Present) + gre.protocol = htons(0x88BE); // ERSPAN protocol type + + // Set ERSPAN header fields + uint32_t version = 1; // Version (4 bits) + uint32_t vlan = 100; // VLAN ID (12 bits) + uint32_t cos = 5; // Class of Service (3 bits) + uint32_t en = 0; // Trunk Encapsulation Type (2 bit) + uint32_t t = 1; // Truncated (1 bit) + uint32_t session_id = 42; // Session ID (10 bits) - // Create TZSP Tagged Field for End of Fields - struct tzsp_tagged end_tag; - end_tag.type = 1; // End of Fields + // Combine fields into the 32-bit ver_vlan_cos_en_t_session field + erspan.ver_vlan_cos_en_t_session = + ((version & 0xF) << 28) | // Version (4 bits, shifted to bits 28-31) + ((vlan & 0xFFF) << 16) | // VLAN ID (12 bits, shifted to bits 16-27) + ((cos & 0x7) << 13) | // Class of Service (3 bits, shifted to bits 13-15) + ((en & 0x3) << 11) | // Trunk Encapsulation Type (2 bit, bit 12) + ((t & 0x1) << 10) | // Truncated (1 bit, bit 11) + (session_id & 0x3FF); // Session ID (10 bits, bits 0-9) - // Calculate total length - unsigned short total_length = header.caplen + TZSP_ENCAP_LEN + TZSP_TAGGED_LEN; - tzsp.length = htons(total_length); + // Convert to network byte order + erspan.ver_vlan_cos_en_t_session = htonl(erspan.ver_vlan_cos_en_t_session); - // Allocate memory for TZSP packet - unsigned char *tzsp_packet = (unsigned char *)malloc(total_length); - if (tzsp_packet == NULL) { - perror("malloc"); - continue; // Skip this packet + // Set the reserved and index fields + uint32_t reserved = 0; // Reserved (12 bits) + uint32_t index = 12345; // Index (20 bits) + + // Combine fields into the 32-bit reserved_index field + erspan.reserved_index = + ((reserved & 0xFFF) << 20) | // Reserved (12 bits, bits 20-31) + (index & 0xFFFFF); // Index (20 bits, bits 0-19) + + // Convert to network byte order + erspan.reserved_index = htonl(erspan.reserved_index); + + // Calculate total length + unsigned short total_length = sizeof(ip_header) + sizeof(gre) + sizeof(sequence_number) + sizeof(erspan) + header.caplen; + + // Allocate memory for ERSPAN packet + unsigned char *erspan_packet = (unsigned char *)malloc(total_length); + if (erspan_packet == NULL) { + perror("malloc"); + continue; // Skip this packet + } + + // Copy IP header, GRE header, sequence number, ERSPAN header, and packet data into the new buffer + unsigned char *ptr = erspan_packet; + memcpy(ptr, &ip_header, sizeof(ip_header)); + ptr += sizeof(ip_header); + memcpy(ptr, &gre, sizeof(gre)); + ptr += sizeof(gre); + uint32_t seq_num_network_order = htonl(sequence_number++); + memcpy(ptr, &seq_num_network_order, sizeof(sequence_number)); + ptr += sizeof(sequence_number); + memcpy(ptr, &erspan, sizeof(erspan)); + ptr += sizeof(erspan); + memcpy(ptr, packet, header.caplen); + + // Send packet via raw socket + if (sendto(sockfd, erspan_packet, total_length, 0, (struct sockaddr *)&dest_addr, dest_addr_size) == -1) { + perror("sendto"); + } + + free(erspan_packet); // Free allocated memory + printf("Sent ERSPAN packet with sequence number: %u\n", sequence_number - 1); + } else { + // TZSP Encapsulation + + // Create TZSP Header + struct tzsp_header tzsp; + tzsp.version = 1; // TZSP Version 1 + tzsp.type = 1; // Type 1 for packet + tzsp.encapsulated_protocol = htons(1); // Ethernet + + // Create TZSP Tagged Field for End of Fields + struct tzsp_tagged end_tag; + end_tag.type = 1; // End of Fields + + // Calculate total length + unsigned short total_length = header.caplen + TZSP_ENCAP_LEN + TZSP_TAGGED_LEN; + tzsp.length = htons(total_length); + + // Allocate memory for TZSP packet + unsigned char *tzsp_packet = (unsigned char *)malloc(total_length); + if (tzsp_packet == NULL) { + perror("malloc"); + continue; // Skip this packet + } + + // Copy TZSP header and tagged field and packet data into the new buffer + unsigned char *ptr = tzsp_packet; + memcpy(ptr, &tzsp, TZSP_ENCAP_LEN); + ptr += TZSP_ENCAP_LEN; + memcpy(ptr, &end_tag, TZSP_TAGGED_LEN); + ptr += TZSP_TAGGED_LEN; + memcpy(ptr, packet, header.caplen); + + // Send packet via UDP with TZSP encapsulation + if (sendto(sockfd, tzsp_packet, total_length, 0, (struct sockaddr *)&dest_addr, dest_addr_size) == -1) { + perror("sendto"); + } + + free(tzsp_packet); // Free allocated memory } - - // Copy TZSP header and tagged field and packet data into the new buffer - unsigned char *ptr = tzsp_packet; - memcpy(ptr, &tzsp, TZSP_ENCAP_LEN); - ptr += TZSP_ENCAP_LEN; - memcpy(ptr, &end_tag, TZSP_TAGGED_LEN); - ptr += TZSP_TAGGED_LEN; - memcpy(ptr, packet, header.caplen); - - // Send packet via UDP with TZSP encapsulation - if (sendto(sockfd, tzsp_packet, total_length, 0, (struct sockaddr *)&dest_addr, dest_addr_size) == -1) { - perror("sendto"); - } - - free(tzsp_packet); // Free allocated memory } pcap_freecode(&fp); From 9fcac378ae5a0270f408a00a67b845dd0289cb27 Mon Sep 17 00:00:00 2001 From: Matthias Cramer Date: Thu, 3 Apr 2025 20:39:31 +0200 Subject: [PATCH 2/3] additional parameters --- README.md | 3 +++ main.c | 28 ++++++++++++++++++++++++++-- pcapmirror.8 | 9 +++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd37d55..dc0fa7a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ pcapmirror [options] * -f Specify the capture filter (BPF syntax) * -r Specify the destination host (required) * -p Specify the destination port (default: 37008) +* -e Use ERSPAN encapsulation (default: TZSP) +* -s Specify the source IP address (required for ERSPAN) +* -S Specify the session ID (default: 42, must be between 0 and 1023) * -4 Force IPv4 host lookup * -6 Force IPv6 host lookup * -l List available network interfaces. diff --git a/main.c b/main.c index 4e72ee2..02e3d5a 100644 --- a/main.c +++ b/main.c @@ -126,6 +126,9 @@ void print_usage(const char *program_name) { printf(" -f Specify the capture filter (BPF syntax)\n"); printf(" -r Specify the destination host (required)\n"); printf(" -p Specify the destination port (default: %d)\n", DEFAULT_DEST_PORT); + printf(" -e Use ERSPAN encapsulation (default: TZSP)\n"); + printf(" -s Specify the source IP address (required for ERSPAN)\n"); + printf(" -S Specify the session ID (default: 42, must be between 0 and 1023)\n"); printf(" -4 Force IPv4 host lookup\n"); printf(" -6 Force IPv6 host lookup\n"); printf(" -l List available network interfaces\n"); @@ -153,6 +156,8 @@ int main(int argc, char *argv[]) { unsigned long long int packet_count = 0; // Counter for matching packets (64bit) int use_erspan = 0; // Flag for ERSPAN encapsulation + char *source_address = NULL; // Source IP address, default is NULL + uint32_t session_id = 42; // Session ID (10 bits) // Socket variables int sockfd; @@ -196,6 +201,16 @@ int main(int argc, char *argv[]) { verbose = 0; // Disable verbose mode if -c is set } else if (strcmp(argv[i], "-e") == 0) { use_erspan = 1; // Enable ERSPAN encapsulation + } else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) { + source_address = argv[i + 1]; // Set source IP from command line + i++; // Skip the source IP value + } else if (strcmp(argv[i], "-S") == 0 && i + 1 < argc) { + session_id = atoi(argv[i + 1]); // Set session ID from command line + if (session_id > 1023) { // Validate session ID (must fit in 10 bits) + fprintf(stderr, "Error: Session ID must be between 0 and 1023.\n"); + return 1; + } + i++; // Skip the session ID value } } @@ -448,8 +463,18 @@ int main(int argc, char *argv[]) { ip_header.ip_off = 0; // Fragment offset ip_header.ip_ttl = 64; // Time to live ip_header.ip_p = IPPROTO_GRE; // Protocol (GRE) - ip_header.ip_src.s_addr = inet_addr("192.168.1.1"); // Replace with your source IP ip_header.ip_dst.s_addr = ((struct sockaddr_in *)&dest_addr)->sin_addr.s_addr; + + if (source_address != NULL) { + if (inet_pton(AF_INET, source_address, &(ip_header.ip_src)) != 1) { + fprintf(stderr, "Error: Invalid source IP address '%s'\n", source_address); + return 1; + } + } else { + ip_header.ip_src.s_addr = inet_addr("192.168.1.1"); // Default source IP + } + + ip_header.ip_src.s_addr = inet_addr("192.168.1.1"); // Replace with your source IP // Set GRE header fields gre.flags = htons(0x1000); // GRE flags (S bit set for Sequence Number Present) @@ -461,7 +486,6 @@ int main(int argc, char *argv[]) { uint32_t cos = 5; // Class of Service (3 bits) uint32_t en = 0; // Trunk Encapsulation Type (2 bit) uint32_t t = 1; // Truncated (1 bit) - uint32_t session_id = 42; // Session ID (10 bits) // Combine fields into the 32-bit ver_vlan_cos_en_t_session field erspan.ver_vlan_cos_en_t_session = diff --git a/pcapmirror.8 b/pcapmirror.8 index a2f3b1f..6c73608 100644 --- a/pcapmirror.8 +++ b/pcapmirror.8 @@ -24,6 +24,15 @@ Specify the destination host (required). .B \-p \fIport\fR Specify the destination port (default: 37008). .TP +.B \-e +Use ERSPAN encapsulation. +.TP +.B \-s \fIsource_ip\fR +Specify the source IP address (required for ERSPAN). +.TP +.B \-S \fIsession_id\fR +Specify the session ID (default: 42, must be between 0 and 1023) +.TP .B \-4 Force IPv4 host lookup. .TP From c202697f7b29417b6d8aac144f64e1ee1fb84025 Mon Sep 17 00:00:00 2001 From: Matthias Cramer Date: Sun, 20 Apr 2025 16:53:26 +0200 Subject: [PATCH 3/3] ERSPAN RELEASE --- .gitlab-ci.yml | 2 +- README.md | 2 +- debian/changelog | 8 ++++++++ debian/control | 4 ++-- pcapmirror.spec | 8 ++++++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6bfccb6..001a667 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ stages: variables: DEBIAN_FRONTEND: noninteractive - VERSION: 0.5 + VERSION: 0.6 build-bookworm: stage: build diff --git a/README.md b/README.md index dc0fa7a..125afac 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # pcapmirror ![pcapmirror logo](logo/pcapmirror_logo_small.png) -pcapmirror is a command-line tool for capturing network traffic and mirroring it to a remote destination using [TZSP encapsulation](https://en.wikipedia.org/wiki/TZSP). It leverages the `libpcap` library for packet capture and provides options for filtering traffic based on BPF syntax. This tool is useful for network monitoring, intrusion detection, and remote packet analysis. +pcapmirror is a command-line tool for capturing network traffic and mirroring it to a remote destination using [TZSP encapsulation](https://en.wikipedia.org/wiki/TZSP) or [ERSPAN](https://datatracker.ietf.org/doc/html/draft-foschiano-erspan-01). It leverages the `libpcap` library for packet capture and provides options for filtering traffic based on BPF syntax. This tool is useful for network monitoring, intrusion detection, and remote packet analysis. ## Usage diff --git a/debian/changelog b/debian/changelog index 191d8ca..93b1718 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +pcapmirror (0.6-1) unstable; urgency=medium + + * Erspan Encapsulation support + * added option -e to set the encapsulation type + * added option -S to set ERSPAN session id + + -- Matthias Cramer Sun, 20 Apr 2025 16:50:00 +0200 + pcapmirror (0.5-1) unstable; urgency=medium * new option -c to count matching packets (overrides verbose mode) diff --git a/debian/control b/debian/control index f5bc20e..a7b0520 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, libpcap0.8 Description: A simple packet mirroring tool using libpcap pcapmirror is a command-line tool for capturing network traffic and - mirroring it to a remote destination using TZSP encapsulation. It - leverages the libpcap library for packet capture and provides options + mirroring it to a remote destination using TZSP or ERSPAN encapsulation. + It leverages the libpcap library for packet capture and provides options for filtering traffic based on BPF syntax. This tool is useful for network monitoring, intrusion detection, and remote packet analysis. \ No newline at end of file diff --git a/pcapmirror.spec b/pcapmirror.spec index 3ab7d81..cb1ab81 100644 --- a/pcapmirror.spec +++ b/pcapmirror.spec @@ -10,7 +10,7 @@ BuildRequires: make BuildRequires: libpcap-devel %description -pcapmirror is a command-line tool for capturing and mirroring network traffic using TZSP encapsulation. It leverages the `libpcap` library for packet capture and supports BPF syntax for filtering traffic. +pcapmirror is a command-line tool for capturing and mirroring network traffic using TZSP or ERSPAN encapsulation. It leverages the `libpcap` library for packet capture and supports BPF syntax for filtering traffic. %build %make_build @@ -26,7 +26,11 @@ pcapmirror is a command-line tool for capturing and mirroring network traffic us %changelog -* Sat Mar 29 2025 Matthias Cramer 0.5-1 +* Sun Apr 20 2025 Matthias Cramer 0.6-1 +- Erspan Encapsulation support +- added option -e to set the encapsulation type +- added option -S to set ERSPAN session id +* Sat Mar 29 2025 Matthias Cramer 0.5-1 - new option -c to count matching packets (overrides verbose mode) - reworked packet decoder to also decode arp, vlan and qinq packets - well known protocols numbers are now decoded