Compare commits

..

27 Commits

Author SHA1 Message Date
a2c0ad8bfb paralell build try 2025-03-30 15:26:01 +02:00
e39c130a69 extended changelog 2025-03-30 15:18:00 +02:00
a128da1328 vlan and qinq handling, protocol printout 2025-03-30 15:15:24 +02:00
99fbd6f600 Bump version to 0.5 2025-03-29 11:47:39 +01:00
5e595b0ae7 new packet match count option 2025-03-29 10:45:34 +01:00
5ee45654f2 works on OpenBSD 2025-03-28 21:55:33 +01:00
1da4934e59 include cleanup, compiles on openbsd, but does not work 2025-03-28 21:17:19 +01:00
0897fa1755 new packet decoder with arp support. removed default filter 2025-03-28 20:26:05 +01:00
1233bf5ede code cleanup 2025-03-26 21:27:18 +01:00
038a6de68e any interface is not supported because of missing ethernet header 2025-03-26 07:36:41 +01:00
39a8b7c016 added -l to list all interfaces 2025-03-25 06:42:58 +01:00
4356a49271 0.4 build test 2025-03-24 13:43:41 +01:00
c6caf71afd puiblish fix, removed debug echo 2025-03-23 20:11:51 +01:00
c2166bb7ec typo 2025-03-23 20:07:01 +01:00
eb41f3b6e4 bormal buld 64bit 2025-03-23 20:05:44 +01:00
a17b8379bb matrix build 2025-03-23 20:01:09 +01:00
1b98555ed3 armhf 2025-03-23 19:02:21 +01:00
c640aa6f22 pios fix 2025-03-23 18:57:20 +01:00
c6584cde87 typo 2025-03-23 13:57:37 +01:00
4300e30f04 propper ip6 support, raspberry builds 2025-03-23 13:55:17 +01:00
0fdd4a9783 updated readme and added logos 2025-03-23 11:20:39 +01:00
49173fef25 let's try again 2025-03-22 19:00:45 +01:00
8e586dade0 paralell 2025-03-22 18:28:55 +01:00
c7e16616a1 again for the stupid ones 2025-03-22 18:11:13 +01:00
9d91fda7e1 PACKAGENAME not defined 2025-03-22 18:08:15 +01:00
3c007c3fcc test2 2025-03-22 18:04:29 +01:00
8ff71daa36 upload debug 2025-03-22 18:01:26 +01:00
8 changed files with 430 additions and 140 deletions

View File

@@ -1,19 +1,20 @@
stages: stages:
- build - build
- publish
variables: variables:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
VERSION: 0.5
build-bookworm: build-bookworm:
stage: build stage: build
needs: []
only: only:
- tags - tags
tags: tags:
- bookworm - bookworm
script: script:
- tar -czf ../pcapmirror_0.3.orig.tar.gz --exclude=debian . - tar -czf ../pcapmirror_$VERSION.orig.tar.gz --exclude=debian .
- apt-get update && apt-get install -y libpcap-dev - apt-get update && apt-get install -y libpcap-dev
- dpkg-buildpackage -uc -us - dpkg-buildpackage -uc -us
- mkdir -p build - mkdir -p build
@@ -24,7 +25,9 @@ build-bookworm:
- build - build
publish-bookworm: publish-bookworm:
stage: publish stage: build
needs:
- build-bookworm
dependencies: dependencies:
- build-bookworm - build-bookworm
only: only:
@@ -33,16 +36,18 @@ publish-bookworm:
- bookworm - bookworm
script: script:
- apt-get update && apt-get install -y curl - apt-get update && apt-get install -y curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror_0.3-1_amd64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGENAME}/bookworm/pcapmirror_0.3-1_amd64.deb' - ls -la build
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror_$VERSION-1_amd64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/bookworm/pcapmirror_$VERSION-1_amd64.deb'
build-sid: build-sid:
stage: build stage: build
needs: []
only: only:
- tags - tags
tags: tags:
- sid - sid
script: script:
- tar -czf ../pcapmirror_0.3.orig.tar.gz --exclude=debian . - tar -czf ../pcapmirror_$VERSION.orig.tar.gz --exclude=debian .
- apt-get update && apt-get install -y libpcap-dev - apt-get update && apt-get install -y libpcap-dev
- dpkg-buildpackage -uc -us - dpkg-buildpackage -uc -us
- mkdir -p build - mkdir -p build
@@ -53,7 +58,9 @@ build-sid:
- build - build
publish-sid: publish-sid:
stage: publish stage: build
needs:
- build-sid
dependencies: dependencies:
- build-sid - build-sid
only: only:
@@ -62,10 +69,12 @@ publish-sid:
- bookworm - bookworm
script: script:
- apt-get update && apt-get install -y curl - apt-get update && apt-get install -y curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror_0.3-1_amd64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGENAME}/sid/pcapmirror_0.3-1_amd64.deb' - ls -la build
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror_$VERSION-1_amd64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/sid/pcapmirror_$VERSION-1_amd64.deb'
build-rocky9: build-rocky9:
stage: build stage: build
needs: []
only: only:
- tags - tags
tags: tags:
@@ -73,7 +82,7 @@ build-rocky9:
script: script:
- dnf install -y libpcap-devel - dnf install -y libpcap-devel
- mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} - mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
- tar -czf /root/rpmbuild/SOURCES/pcapmirror-v0.3.tar.gz --exclude=debian --exclude=.git . - tar -czf /root/rpmbuild/SOURCES/pcapmirror-v$VERSION.tar.gz --exclude=debian --exclude=.git .
- cp -r * /root/rpmbuild/BUILD - cp -r * /root/rpmbuild/BUILD
- rpmbuild -ba pcapmirror.spec - rpmbuild -ba pcapmirror.spec
- mkdir -p build - mkdir -p build
@@ -85,7 +94,9 @@ build-rocky9:
- build - build
publish-rocky9: publish-rocky9:
stage: publish stage: build
needs:
- build-rocky9
dependencies: dependencies:
- build-rocky9 - build-rocky9
only: only:
@@ -94,10 +105,12 @@ publish-rocky9:
- bookworm - bookworm
script: script:
- apt-get update && apt-get install -y curl - apt-get update && apt-get install -y curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror-0.3-*.el8.x86_64.rpm ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGENAME}/rockylinux9/pcapmirror-0.3-1.el8.x86_64.rpm' - ls -la build
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror-$VERSION-*.el9.x86_64.rpm ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/rockylinux9/pcapmirror-$VERSION-1.el8.x86_64.rpm'
build-rocky8: build-rocky8:
stage: build stage: build
needs: []
only: only:
- tags - tags
tags: tags:
@@ -105,7 +118,7 @@ build-rocky8:
script: script:
- dnf install -y libpcap-devel - dnf install -y libpcap-devel
- mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} - mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
- tar -czf /root/rpmbuild/SOURCES/pcapmirror-v0.3.tar.gz --exclude=debian --exclude=.git . - tar -czf /root/rpmbuild/SOURCES/pcapmirror-v$VERSION.tar.gz --exclude=debian --exclude=.git .
- cp -r * /root/rpmbuild/BUILD - cp -r * /root/rpmbuild/BUILD
- rpmbuild -ba pcapmirror.spec - rpmbuild -ba pcapmirror.spec
- mkdir -p build - mkdir -p build
@@ -117,7 +130,9 @@ build-rocky8:
- build - build
publish-rocky8: publish-rocky8:
stage: publish stage: build
needs:
- build-rocky8
dependencies: dependencies:
- build-rocky8 - build-rocky8
only: only:
@@ -126,5 +141,71 @@ publish-rocky8:
- bookworm - bookworm
script: script:
- apt-get update && apt-get install -y curl - apt-get update && apt-get install -y curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror-0.3-*.el8.x86_64.rpm ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGENAME}/rockylinux8/pcapmirror-0.3-1.el8.x86_64.rpm' - ls -la build
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror-$VERSION-*.el8.x86_64.rpm ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/rockylinux8/pcapmirror-$VERSION-1.el8.x86_64.rpm'
build-pios12:
stage: build
needs: []
only:
- tags
tags:
- pios12
script:
- tar -czf ../pcapmirror_$VERSION.orig.tar.gz --exclude=debian .
- apt-get update && apt-get install -y libpcap-dev
- dpkg-buildpackage -uc -us
- mkdir -p build
- mv ../pcapmirror*.* build/
artifacts:
paths:
- build
publish-pios12:
stage: build
needs:
- build-pios12
dependencies:
- build-pios12
only:
- tags
tags:
- bookworm
script:
- apt-get update && apt-get install -y curl
- ls -la build
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror_$VERSION-1_armhf.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/bookworm/pcapmirror_$VERSION-1_armhf.deb'
build-pios12-64:
stage: build
needs: []
only:
- tags
tags:
- pios12-64
script:
- tar -czf ../pcapmirror_$VERSION.orig.tar.gz --exclude=debian .
- apt-get update && apt-get install -y libpcap-dev
- dpkg-buildpackage -uc -us
- mkdir -p build
- mv ../pcapmirror*.* build/
artifacts:
paths:
- build
publish-pios12-64:
stage: build
needs:
- build-pios12-64
dependencies:
- build-pios12-64
only:
- tags
tags:
- bookworm
script:
- apt-get update && apt-get install -y curl
- ls -la build
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file build/pcapmirror_$VERSION-1_arm64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/bookworm/pcapmirror_$VERSION-1_arm64.deb'

View File

@@ -1,4 +1,7 @@
# pcapmirror # 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). 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 ## Usage
@@ -9,12 +12,16 @@ pcapmirror [options]
### Options: ### Options:
* -i <interface>: Specify the capture interface (e.g., eth0). * -i <interface> Specify the capture interface
* -f <filter>: Specify the capture filter in BPF syntax (e.g., tcp port 80). * -f <filter> Specify the capture filter (BPF syntax)
* -r <ip_address>: Specify the destination IP address (required). * -r <host/ipv4/ipv6> Specify the destination host (required)
* -p <port>: Specify the destination port (default: 37008). * -p <port> Specify the destination port (default: 37008)
* -v: Enable verbose mode (prints packet information). * -4 Force IPv4 host lookup
* -h: Show this help message. * -6 Force IPv6 host lookup
* -l List available network interfaces.
* -v Enable verbose mode
* -c Count matching packets (overrides verbose mode)
* -h Show this help message
### Example: ### Example:
@@ -31,8 +38,25 @@ With this tool, you can mirror traffic directly to a running [Wireshark](https:/
To avoid capturing traffic from your own monitoring machine, configure Wireshark with a capture filter of udp port 37008 or udp dst port 37008. Also, verify that your firewall permits this UDP traffic. To avoid capturing traffic from your own monitoring machine, configure Wireshark with a capture filter of udp port 37008 or udp dst port 37008. Also, verify that your firewall permits this UDP traffic.
## Original Download Location
[https://git.freestone.net/cramer/pcapmirror](https://git.freestone.net/cramer/pcapmirror)
## Packages
On the original download location you will also find several prebuilt packages.
## Compile and Install ## Compile and Install
### Supported Operating Systems
Source is tested to build and function on the following operating systems
* Debian Linux 12 + unstable (sid)
* Rocky Linux 8 + 9
* PiOS 12 (bookworm)
* OpenBSD 7.6
* MacOS 15
Compile the program: Compile the program:
```bash ```bash
make make
@@ -45,7 +69,7 @@ make install
This will copy the pcapmirror executable to bin. You may need to adjust the PREFIX variable in the Makefile if you want to install it to a different location. This will copy the pcapmirror executable to bin. You may need to adjust the PREFIX variable in the Makefile if you want to install it to a different location.
Dependencies ### Dependencies
libpcap: You need to have libpcap installed on your system. On Debian/Ubuntu systems, you can install it using: libpcap: You need to have libpcap installed on your system. On Debian/Ubuntu systems, you can install it using:
```bash ```bash
sudo apt-get install libpcap-dev sudo apt-get install libpcap-dev
@@ -57,7 +81,7 @@ sudo yum install libpcap-devel
``` ```
## Build debian package ## Build debian package
If you have never built a debian pakage you probably need debhelper: If you have never built a debian package you probably need debhelper:
```bash ```bash
sudo apt-get install debhelper sudo apt-get install debhelper
``` ```

17
debian/changelog vendored
View File

@@ -1,3 +1,20 @@
pcapmirror (0.5-1) unstable; urgency=medium
* 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
* works now on MacOS and OpenBSD
-- Matthias Cramer <cramer@freestone.net> Fri, 29 Mar 2025 13:40:00 +0100
pcapmirror (0.4-1) unstable; urgency=medium
* IPv6 support for remote destination
* remote destination can now also be hostname
* added option to enforce IPv4 and IPv6 for remote destination
-- Matthias Cramer <cramer@freestone.net> Fri, 24 Mar 2025 13:40:00 +0100
pcapmirror (0.3-1) unstable; urgency=medium pcapmirror (0.3-1) unstable; urgency=medium
* added manpage * added manpage

BIN
logo/pcapmirror_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

347
main.c
View File

@@ -6,25 +6,24 @@ Copyright (c) 2025, Matthias Cramer, cramer@freestone.net
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <pcap.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h> #include <unistd.h>
#include <string.h>
#define ENABLE_IPV6 #include <sys/types.h>
#include <sys/socket.h>
#ifdef ENABLE_IPV6 #include <pcap.h>
#include <netinet/ip6.h> // Include for IPv6 header definition #include <netinet/in.h>
#endif #include <netinet/ip.h>
#include <netinet/ip6.h>
#include <net/if_arp.h>
#include <netinet/if_ether.h> // For Ethernet and ARP headers
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#define DEFAULT_DEST_PORT 37008 // Default TZSP port #define DEFAULT_DEST_PORT 37008 // Default TZSP port
#define TZSP_ENCAP_LEN 4 // Length of TZSP encapsulation header #define TZSP_ENCAP_LEN 4 // Length of TZSP encapsulation header
#define TZSP_TAGGED_LEN 1 // Length of TZSP tagged field header (type) #define TZSP_TAGGED_LEN 1 // Length of TZSP tagged field header (type)
#define ETHERNET_HEADER_LENGTH 14 #define ETHERNET_HEADER_LENGTH 14 // Assuming Ethernet header is 14 bytes
// TZSP Header Structure // TZSP Header Structure
struct tzsp_header { struct tzsp_header {
@@ -39,38 +38,118 @@ struct tzsp_tagged {
unsigned char type; // Tag type unsigned char type; // Tag type
}; };
// Add this structure for ARP header parsing
struct arp_header {
uint16_t htype; // Hardware type
uint16_t ptype; // Protocol type
uint8_t hlen; // Hardware address length
uint8_t plen; // Protocol address length
uint16_t oper; // Operation (1 = request, 2 = reply)
uint8_t sha[6]; // Sender hardware address
uint8_t spa[4]; // Sender protocol address
uint8_t tha[6]; // Target hardware address
uint8_t tpa[4]; // Target protocol address
};
// Function to check if the system is little-endian // Function to check if the system is little-endian
int is_little_endian() { int is_little_endian() {
volatile unsigned int i=0x01234567; volatile unsigned int i=0x01234567;
return (((unsigned char*)&i)[0] == 0x67); return (((unsigned char*)&i)[0] == 0x67);
} }
void list_interfaces() {
pcap_if_t *alldevs;
char errbuf[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs(&alldevs, errbuf) == -1) {
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
return;
}
printf("Available network interfaces:\n");
for (pcap_if_t *d = alldevs; d != NULL; d = d->next) {
printf("%s", d->name);
if (d->description) {
printf(" (%s)", d->description);
}
printf("\n");
}
pcap_freealldevs(alldevs);
}
// Function to lookup protocol name or return protocol number as a string
const char *lookup_protocol_name(int protocol) {
static char buf[5]; // Buffer to hold protocol number as a string
switch (protocol) {
case 1:
return "ICMP";
case 2:
return "IGMP";
case 6:
return "TCP";
case 17:
return "UDP";
case 41:
return "IPv6";
case 47:
return "GRE";
case 50:
return "ESP";
case 51:
return "AH";
case 58:
return "ICMPv6";
case 89:
return "OSPF";
case 112:
return "VRRP";
case 124:
return "ISIS";
case 132:
return "SCTP";
default:
snprintf(buf, sizeof(buf), "%d", protocol); // Convert protocol number to string
return buf;
}
}
void print_usage(const char *program_name) { void print_usage(const char *program_name) {
printf("Usage: %s [options]\n", program_name); printf("Usage: %s [options]\n", program_name);
printf("Options:\n"); printf("Options:\n");
printf(" -i <interface> Specify the capture interface\n"); printf(" -i <interface> Specify the capture interface\n");
printf(" -f <filter> Specify the capture filter (BPF syntax)\n"); printf(" -f <filter> Specify the capture filter (BPF syntax)\n");
printf(" -r <ip_address> Specify the destination IP address (required)\n"); printf(" -r <host/ipv4/ipv6> Specify the destination host (required)\n");
printf(" -p <port> Specify the destination port (default: %d)\n", DEFAULT_DEST_PORT); printf(" -p <port> Specify the destination port (default: %d)\n", DEFAULT_DEST_PORT);
printf(" -4 Force IPv4 host lookup\n");
printf(" -6 Force IPv6 host lookup\n");
printf(" -l List available network interfaces\n");
printf(" -v Enable verbose mode\n"); printf(" -v Enable verbose mode\n");
printf(" -c Count matching packets (overrides verbose mode)\n");
printf(" -h Show this help message\n"); printf(" -h Show this help message\n");
printf("Example:\n"); printf("Example:\n");
printf(" %s -i eth0 -f 'tcp port 80' -v -r 192.168.1.100 -p 47008\n", program_name); printf(" %s -i eth0 -f 'tcp port 80' -v -r 192.168.1.100 -p 47008\n", program_name);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
pcap_if_t *alldevs;
char errbuf[PCAP_ERRBUF_SIZE]; char errbuf[PCAP_ERRBUF_SIZE];
char *filter_exp = "tcp port 8088"; // Default filter char *filter_exp = ""; // Default filter
char *dev_name = NULL; // Device name char *dev_name = NULL; // Device name
char *dest_ip = NULL; // Destination IP, no default value char *mirror_host = NULL; // Destination IP, no default value
int dest_port = DEFAULT_DEST_PORT; // Destination port, default value int dest_port = DEFAULT_DEST_PORT; // Destination port, default value
int i; int i;
int verbose = 0; // Verbose flag, default is false int verbose = 0; // Verbose flag, default is false
int force_ipv4 = 0; // Flag to force IPv4 lookup
int force_ipv6 = 0; // Flag to force IPv6 lookup
int list_interfaces_flag = 0; // Flag to list interfaces
// Add a variable to track the count of matching packets
int count_packets = 0; // Flag for counting packets
unsigned long long int packet_count = 0; // Counter for matching packets (64bit)
// Socket variables // Socket variables
int sockfd; int sockfd;
struct sockaddr_in dest_addr; struct addrinfo hints, *res;
struct sockaddr_storage dest_addr; // Declare dest_addr
int dest_addr_size;
// Check if no arguments are given or if help is requested // Check if no arguments are given or if help is requested
if (argc == 1 || (argc == 2 && strcmp(argv[1], "-h") == 0)) { if (argc == 1 || (argc == 2 && strcmp(argv[1], "-h") == 0)) {
@@ -78,18 +157,6 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }
// Create UDP socket
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
return 1;
}
// Set destination address
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Default to localhost
dest_addr.sin_port = htons(dest_port);
// Parse command-line arguments // Parse command-line arguments
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) { if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
@@ -104,56 +171,107 @@ int main(int argc, char *argv[]) {
print_usage(argv[0]); print_usage(argv[0]);
return 0; return 0;
} else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
dest_ip = argv[i + 1]; // Set destination IP from command line mirror_host = argv[i + 1]; // Set destination IP from command line
i++; // Skip the IP value i++; // Skip the IP value
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
dest_port = atoi(argv[i + 1]); // Set destination port from command line dest_port = atoi(argv[i + 1]); // Set destination port from command line
i++; // Skip the port value i++; // Skip the port value
} else if (strcmp(argv[i], "-4") == 0) {
force_ipv4 = 1; // Force IPv4 lookup
} else if (strcmp(argv[i], "-6") == 0) {
force_ipv6 = 1; // Force IPv6 lookup
} else if (strcmp(argv[i], "-l") == 0) {
list_interfaces_flag = 1; // Set flag to list interfaces
} else if (strcmp(argv[i], "-c") == 0) {
count_packets = 1; // Enable packet counting
verbose = 0; // Disable verbose mode if -c is set
} }
} }
if (list_interfaces_flag) {
list_interfaces();
return 0;
}
// Check if destination IP is provided // Check if destination IP is provided
if (dest_ip == NULL) { if (mirror_host == NULL && !list_interfaces_flag) {
fprintf(stderr, "Error: Destination IP address is required.\n"); fprintf(stderr, "Error: Destination IP address is required.\n");
print_usage(argv[0]); print_usage(argv[0]);
return 1; return 1;
} }
if (inet_pton(AF_INET, dest_ip, &dest_addr.sin_addr) <= 0) { // Check that interface is not any
perror("inet_pton"); if (dev_name != NULL && strcmp(dev_name, "any") == 0) {
fprintf(stderr, "Error: Interface 'any' is not supported.\n");
return 1; return 1;
} }
dest_addr.sin_port = htons(dest_port); // Set the port // Resolve the destination address
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
hints.ai_socktype = SOCK_DGRAM; // Datagram socket
// If no interface is specified, find all devices if (force_ipv4) {
if (dev_name == NULL) { hints.ai_family = AF_INET; // Force IPv4
if (pcap_findalldevs(&alldevs, errbuf) == -1) { } else if (force_ipv6) {
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf); hints.ai_family = AF_INET6; // Force IPv6
return(1);
} }
// Print the available devices for debugging if (getaddrinfo(mirror_host, NULL, &hints, &res) != 0) {
/* perror("getaddrinfo");
pcap_if_t *device;
printf("Available devices:\n");
for (device = alldevs; device != NULL; device = device->next) {
printf("%s - %s\n", device->name, (device->description != NULL) ? device->description : "No description available");
}
*/
// Use the first device if no device is specified
if (alldevs == NULL) {
fprintf(stderr, "No devices found. Make sure you have permissions to capture traffic.\n");
return 1; return 1;
} }
dev_name = alldevs->name; // Use the name of the first device // Calculate dest_addr size
if (res->ai_family == AF_INET) {
dest_addr_size = sizeof(struct sockaddr_in);
} else if (res->ai_family == AF_INET6) {
dest_addr_size = sizeof(struct sockaddr_in6);
} else { } else {
// Interface specified via command line, no need to find all devices fprintf(stderr, "Unknown address family\n");
alldevs = NULL; // Set alldevs to NULL to avoid potential issues freeaddrinfo(res);
return 1;
} }
// Create UDP socket
sockfd = socket(res->ai_family, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
freeaddrinfo(res);
return 1;
}
memset(&dest_addr, 0, sizeof(dest_addr));
// Set the destination address
if (res->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
ipv4->sin_port = htons(dest_port);
memcpy(&dest_addr, ipv4, sizeof(struct sockaddr_in));
} else if (res->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
ipv6->sin6_port = htons(dest_port);
memcpy(&dest_addr, ipv6, sizeof(struct sockaddr_in6));
}
// Resolve the destination IP address
char resolved_ip[INET6_ADDRSTRLEN];
if (res->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
inet_ntop(AF_INET, &(ipv4->sin_addr), resolved_ip, INET6_ADDRSTRLEN);
} else if (res->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
inet_ntop(AF_INET6, &(ipv6->sin6_addr), resolved_ip, INET6_ADDRSTRLEN);
}
// Free the address info
freeaddrinfo(res);
printf("Using interface: %s\n", dev_name);
printf("Using filter: %s\n", filter_exp);
printf("Resolved Destination IP: %s\n", resolved_ip);
printf("Destination Port: %d\n", dest_port);
pcap_t *handle; pcap_t *handle;
struct bpf_program fp; struct bpf_program fp;
bpf_u_int32 mask; bpf_u_int32 mask;
@@ -165,29 +283,20 @@ int main(int argc, char *argv[]) {
mask = 0; mask = 0;
} }
handle = pcap_open_live(dev_name, BUFSIZ, 1, 1000, errbuf); handle = pcap_open_live(dev_name, BUFSIZ, 1, 100, errbuf);
if (handle == NULL) { if (handle == NULL) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev_name, errbuf); fprintf(stderr, "Couldn't open device %s: %s\n", dev_name, errbuf);
if (alldevs != NULL) {
pcap_freealldevs(alldevs);
}
return(2); return(2);
} }
if (pcap_compile(handle, &fp, filter_exp, 1, net) == -1) { if (pcap_compile(handle, &fp, filter_exp, 1, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle)); fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
if (alldevs != NULL) {
pcap_freealldevs(alldevs);
}
pcap_close(handle); pcap_close(handle);
return(2); return(2);
} }
if (pcap_setfilter(handle, &fp) == -1) { if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle)); fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
if (alldevs != NULL) {
pcap_freealldevs(alldevs);
}
pcap_close(handle); pcap_close(handle);
return(2); return(2);
} }
@@ -195,54 +304,95 @@ int main(int argc, char *argv[]) {
struct pcap_pkthdr header; struct pcap_pkthdr header;
const u_char *packet; const u_char *packet;
char source_ip_str[INET6_ADDRSTRLEN], dest_ip_str[INET6_ADDRSTRLEN]; char source_ip_str[INET6_ADDRSTRLEN], dest_ip_str[INET6_ADDRSTRLEN];
struct ip *ip_header; struct ip *ip_header; // Declare ip4_header
#ifdef ENABLE_IPV6 struct ip6_hdr *ip6_header; // Declare ip6_header
struct ip6_hdr *ip6_header;
#endif
int ip_protocol = 0; int ip_protocol = 0;
struct timeval current_time, last_count;
printf("Using interface: %s\n", dev_name); gettimeofday(&last_count, NULL);
printf("Using filter: %s\n", filter_exp); printf("\n");
printf("Destination IP: %s\n", dest_ip);
printf("Destination Port: %d\n", dest_port);
while (1) { while (1) {
packet = pcap_next(handle, &header); packet = pcap_next(handle, &header);
if (packet == NULL) if (packet == NULL)
continue; continue;
// Assuming Ethernet header is 14 bytes if (count_packets) {
// Check IP version packet_count++;
ip_header = (struct ip*)(packet + ETHERNET_HEADER_LENGTH);
ip_protocol = ip_header->ip_v;
if (ip_protocol == 4) { gettimeofday(&current_time, NULL);
// IPv4
inet_ntop(AF_INET, &(ip_header->ip_src), source_ip_str, INET6_ADDRSTRLEN); long elapsed_ms = current_time.tv_sec * 1000 + (current_time.tv_usec /1000)-
inet_ntop(AF_INET, &(ip_header->ip_dst), dest_ip_str, INET6_ADDRSTRLEN); (last_count.tv_sec * 1000 + (last_count.tv_usec /1000));
if (elapsed_ms >= 500) {
printf("\rPacket count: %llu", packet_count);
fflush(stdout);
last_count = current_time; // Reset the timer
}
}
if (verbose) { if (verbose) {
printf("IPv4 Packet: %s -> %s, IP Protocol: %d\n",
source_ip_str, dest_ip_str, ip_header->ip_p); // Parse Ethernet header
struct ether_header *eth_header = (struct ether_header *)packet;
// Check EtherType
uint16_t ether_type = ntohs(eth_header->ether_type);
int vlan_offset = 0; // Offset for VLAN-tagged packets
// Check for VLAN tags (including Q-in-Q)
while (ether_type == 0x8100 || ether_type == 0x88A8) {
// VLAN tag is present
vlan_offset += 4; // Each VLAN tag adds 4 bytes
uint16_t vlan_tag = ntohs(*(uint16_t *)(packet + ETHERNET_HEADER_LENGTH + vlan_offset - 4));
uint16_t vlan_id = vlan_tag & 0x0FFF; // Extract VLAN ID (12 bits)
uint8_t vlan_pcp = (vlan_tag >> 13) & 0x07; // Extract Priority Code Point (3 bits)
uint8_t vlan_dei = (vlan_tag >> 12) & 0x01; // Extract Drop Eligible Indicator (1 bit)
printf("VLAN Tag: VLAN ID=%d, PCP=%d, DEI=%d\n", vlan_id, vlan_pcp, vlan_dei);
// Update EtherType to the next protocol
ether_type = ntohs(*(uint16_t *)(packet + ETHERNET_HEADER_LENGTH + vlan_offset - 2));
} }
if (ether_type == ETHERTYPE_IP) {
// Handle IPv4 traffic
ip_header = (struct ip *)(packet + ETHERNET_HEADER_LENGTH + vlan_offset);
ip_protocol = ip_header->ip_v & 0x0F; // Get IP version
if (ip_protocol == 4) {
inet_ntop(AF_INET, &(ip_header->ip_src.s_addr), source_ip_str, INET6_ADDRSTRLEN);
inet_ntop(AF_INET, &(ip_header->ip_dst.s_addr), dest_ip_str, INET6_ADDRSTRLEN);
printf("IPv4 Packet: %s -> %s, IP Protocol: %s\n",
source_ip_str, dest_ip_str, lookup_protocol_name(ip_header->ip_p));
} }
#ifdef ENABLE_IPV6 } else if (ether_type == ETHERTYPE_IPV6) {
else if (ip_protocol == 6) { // Handle IPv6 traffic
// IPv6 ip6_header = (struct ip6_hdr *)(packet + ETHERNET_HEADER_LENGTH + vlan_offset);
ip6_header = (struct ip6_hdr*)(packet + ETHERNET_HEADER_LENGTH);
inet_ntop(AF_INET6, &(ip6_header->ip6_src), source_ip_str, INET6_ADDRSTRLEN); inet_ntop(AF_INET6, &(ip6_header->ip6_src), source_ip_str, INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &(ip6_header->ip6_dst), dest_ip_str, INET6_ADDRSTRLEN); inet_ntop(AF_INET6, &(ip6_header->ip6_dst), dest_ip_str, INET6_ADDRSTRLEN);
if (verbose) { printf("IPv6 Packet: %s -> %s, Next Header: %s\n",
printf("IPv6 Packet: %s -> %s, Next Header: %d\n", source_ip_str, dest_ip_str, lookup_protocol_name(ip6_header->ip6_nxt));
source_ip_str, dest_ip_str, ip6_header->ip6_nxt); } else if (ether_type == ETHERTYPE_ARP) {
// Handle ARP traffic
struct arp_header *arp = (struct arp_header *)(packet + ETHERNET_HEADER_LENGTH + vlan_offset);
printf("ARP Packet: Operation: %s\n",
(ntohs(arp->oper) == 1) ? "Request" : "Reply");
printf("Sender MAC: %02x:%02x:%02x:%02x:%02x:%02x, Sender IP: %d.%d.%d.%d\n",
arp->sha[0], arp->sha[1], arp->sha[2], arp->sha[3], arp->sha[4], arp->sha[5],
arp->spa[0], arp->spa[1], arp->spa[2], arp->spa[3]);
printf("Target MAC: %02x:%02x:%02x:%02x:%02x:%02x, Target IP: %d.%d.%d.%d\n",
arp->tha[0], arp->tha[1], arp->tha[2], arp->tha[3], arp->tha[4], arp->tha[5],
arp->tpa[0], arp->tpa[1], arp->tpa[2], arp->tpa[3]);
} else {
printf("Non-IP/ARP Packet, EtherType: 0x%04x\n", ether_type);
} }
} }
#endif
else {
printf("Non-IP Packet\n");
continue;
}
// Create TZSP Header // Create TZSP Header
struct tzsp_header tzsp; struct tzsp_header tzsp;
@@ -274,7 +424,7 @@ int main(int argc, char *argv[]) {
memcpy(ptr, packet, header.caplen); memcpy(ptr, packet, header.caplen);
// Send packet via UDP with TZSP encapsulation // Send packet via UDP with TZSP encapsulation
if (sendto(sockfd, tzsp_packet, total_length, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == -1) { if (sendto(sockfd, tzsp_packet, total_length, 0, (struct sockaddr *)&dest_addr, dest_addr_size) == -1) {
perror("sendto"); perror("sendto");
} }
@@ -283,9 +433,6 @@ int main(int argc, char *argv[]) {
pcap_freecode(&fp); pcap_freecode(&fp);
pcap_close(handle); pcap_close(handle);
if (alldevs != NULL) {
pcap_freealldevs(alldevs); // Free the device list only if devices were found
}
close(sockfd); close(sockfd);
return(0); return(0);
} }

View File

@@ -1,4 +1,4 @@
.TH PCAPMIRROR 1 "March 22, 2025" "pcapmirror 0.3" "User Commands" .TH PCAPMIRROR 1 "March 24, 2025" "pcapmirror 0.5" "User Commands"
.SH NAME .SH NAME
pcapmirror \- A command-line tool for capturing and mirroring network traffic pcapmirror \- A command-line tool for capturing and mirroring network traffic
@@ -18,15 +18,27 @@ Specify the capture interface (e.g., eth0).
.B \-f \fIfilter\fR .B \-f \fIfilter\fR
Specify the capture filter in BPF syntax (e.g., tcp port 80). Specify the capture filter in BPF syntax (e.g., tcp port 80).
.TP .TP
.B \-r \fIip_address\fR .B \-r \fIhost/ipv4/ipv6\fR
Specify the destination IP address (required). Specify the destination host (required).
.TP .TP
.B \-p \fIport\fR .B \-p \fIport\fR
Specify the destination port (default: 37008). Specify the destination port (default: 37008).
.TP .TP
.B \-4
Force IPv4 host lookup.
.TP
.B \-6
Force IPv6 host lookup.
.TP
.B \-l
List available network interfaces.
.TP
.B \-v .B \-v
Enable verbose mode (prints packet information). Enable verbose mode (prints packet information).
.TP .TP
.B \-c
Count matching packets (overrides verbose mode)
.TP
.B \-h .B \-h
Show this help message. Show this help message.

View File

@@ -1,5 +1,5 @@
Name: pcapmirror Name: pcapmirror
Version: 0.3 Version: 0.5
Release: %(perl -e 'print time()')%{?dist} Release: %(perl -e 'print time()')%{?dist}
Summary: A simple packet capture mirror Summary: A simple packet capture mirror
License: BSD 3-Clause License License: BSD 3-Clause License
@@ -26,6 +26,15 @@ pcapmirror is a command-line tool for capturing and mirroring network traffic us
%changelog %changelog
* Sat Mar 29 2025 Matthias Cramer <cramer@freesone.net> 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
- works now on MacOS and OpenBSD
* Mon Mar 24 2025 Matthias Cramer <cramer@freesone.net> 0.4-1
- IPv6 support for remote destination
- remote destination can now also be hostname
- added option to enforce IPv4 and IPv6 for remote destination
* Sat Mar 22 2025 Matthias Cramer <cramer@freesone.net> 0.3-1 * Sat Mar 22 2025 Matthias Cramer <cramer@freesone.net> 0.3-1
- added manpage - added manpage
* Sat Mar 22 2025 Matthias Cramer <cramer@freesone.net> 0.2-1 * Sat Mar 22 2025 Matthias Cramer <cramer@freesone.net> 0.2-1