29 Commits
0.3 ... 0.5

Author SHA1 Message Date
e3097eec3a back to original no benefit 2025-03-30 15:32:28 +02:00
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
3a078c5ba2 try to upload artifacts to registry 2025-03-22 17:39:09 +01:00
8 changed files with 475 additions and 146 deletions

View File

@@ -1,18 +1,21 @@
stages:
- build
- publish
variables:
DEBIAN_FRONTEND: noninteractive
VERSION: 0.5
build-bookworm:
stage: build
needs: []
only:
- tags
tags:
- bookworm
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
- dpkg-buildpackage -uc -us
- mkdir -p build
@@ -20,22 +23,32 @@ build-bookworm:
artifacts:
paths:
- build/*.deb
- build/*.dsc
- build/*.tar.xz
- build/*.tar.gz
- build/*.changes
- build/*.buildinfo
- build/*.diff.gz
- build
publish-bookworm:
stage: publish
needs:
- build-bookworm
dependencies:
- build-bookworm
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_amd64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/bookworm/pcapmirror_$VERSION-1_amd64.deb'
build-sid:
stage: build
needs: []
only:
- tags
tags:
- sid
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
- dpkg-buildpackage -uc -us
- mkdir -p build
@@ -43,16 +56,26 @@ build-sid:
artifacts:
paths:
- build/*.deb
- build/*.dsc
- build/*.tar.xz
- build/*.tar.gz
- build/*.changes
- build/*.buildinfo
- build/*.diff.gz
- build
publish-sid:
stage: publish
needs:
- build-sid
dependencies:
- build-sid
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_amd64.deb ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/pcapmirror/sid/pcapmirror_$VERSION-1_amd64.deb'
build-rocky9:
stage: build
needs: []
only:
- tags
tags:
@@ -60,7 +83,7 @@ build-rocky9:
script:
- dnf install -y libpcap-devel
- 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
- rpmbuild -ba pcapmirror.spec
- mkdir -p build
@@ -69,10 +92,26 @@ build-rocky9:
artifacts:
paths:
- build/*
- build
publish-rocky9:
stage: publish
needs:
- build-rocky9
dependencies:
- build-rocky9
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-*.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:
stage: build
needs: []
only:
- tags
tags:
@@ -80,7 +119,7 @@ build-rocky8:
script:
- dnf install -y libpcap-devel
- 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
- rpmbuild -ba pcapmirror.spec
- mkdir -p build
@@ -89,4 +128,85 @@ build-rocky8:
artifacts:
paths:
- build/*
- build
publish-rocky8:
stage: publish
needs:
- build-rocky8
dependencies:
- build-rocky8
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-*.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: publish
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: publish
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 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.
## Usage
@@ -9,12 +12,16 @@ pcapmirror [options]
### Options:
* -i <interface>: Specify the capture interface (e.g., eth0).
* -f <filter>: Specify the capture filter in BPF syntax (e.g., tcp port 80).
* -r <ip_address>: Specify the destination IP address (required).
* -p <port>: Specify the destination port (default: 37008).
* -v: Enable verbose mode (prints packet information).
* -h: Show this help message.
* -i <interface> Specify the capture interface
* -f <filter> Specify the capture filter (BPF syntax)
* -r <host/ipv4/ipv6> Specify the destination host (required)
* -p <port> Specify the destination port (default: 37008)
* -4 Force IPv4 host lookup
* -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:
@@ -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.
## 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
### 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:
```bash
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.
Dependencies
### Dependencies
libpcap: You need to have libpcap installed on your system. On Debian/Ubuntu systems, you can install it using:
```bash
sudo apt-get install libpcap-dev
@@ -57,7 +81,7 @@ sudo yum install libpcap-devel
```
## 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
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
* 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 <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>
#define ENABLE_IPV6
#ifdef ENABLE_IPV6
#include <netinet/ip6.h> // Include for IPv6 header definition
#endif
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pcap.h>
#include <netinet/in.h>
#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 TZSP_ENCAP_LEN 4 // Length of TZSP encapsulation header
#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
struct tzsp_header {
@@ -39,38 +38,118 @@ struct tzsp_tagged {
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
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];
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) {
printf("Usage: %s [options]\n", program_name);
printf("Options:\n");
printf(" -i <interface> Specify the capture interface\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(" -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(" -c Count matching packets (overrides verbose mode)\n");
printf(" -h Show this help message\n");
printf("Example:\n");
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[]) {
pcap_if_t *alldevs;
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 *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 i;
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
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
if (argc == 1 || (argc == 2 && strcmp(argv[1], "-h") == 0)) {
@@ -78,18 +157,6 @@ int main(int argc, char *argv[]) {
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
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) {
@@ -104,56 +171,107 @@ int main(int argc, char *argv[]) {
print_usage(argv[0]);
return 0;
} 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
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
dest_port = atoi(argv[i + 1]); // Set destination port from command line
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
if (dest_ip == NULL) {
if (mirror_host == NULL && !list_interfaces_flag) {
fprintf(stderr, "Error: Destination IP address is required.\n");
print_usage(argv[0]);
return 1;
}
if (inet_pton(AF_INET, dest_ip, &dest_addr.sin_addr) <= 0) {
perror("inet_pton");
// Check that interface is not any
if (dev_name != NULL && strcmp(dev_name, "any") == 0) {
fprintf(stderr, "Error: Interface 'any' is not supported.\n");
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 (dev_name == NULL) {
if (pcap_findalldevs(&alldevs, errbuf) == -1) {
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
return(1);
if (force_ipv4) {
hints.ai_family = AF_INET; // Force IPv4
} else if (force_ipv6) {
hints.ai_family = AF_INET6; // Force IPv6
}
// Print the available devices for debugging
/*
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");
if (getaddrinfo(mirror_host, NULL, &hints, &res) != 0) {
perror("getaddrinfo");
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 {
// Interface specified via command line, no need to find all devices
alldevs = NULL; // Set alldevs to NULL to avoid potential issues
fprintf(stderr, "Unknown address family\n");
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;
struct bpf_program fp;
bpf_u_int32 mask;
@@ -165,29 +283,20 @@ int main(int argc, char *argv[]) {
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) {
fprintf(stderr, "Couldn't open device %s: %s\n", dev_name, errbuf);
if (alldevs != NULL) {
pcap_freealldevs(alldevs);
}
return(2);
}
if (pcap_compile(handle, &fp, filter_exp, 1, net) == -1) {
fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
if (alldevs != NULL) {
pcap_freealldevs(alldevs);
}
pcap_close(handle);
return(2);
}
if (pcap_setfilter(handle, &fp) == -1) {
fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
if (alldevs != NULL) {
pcap_freealldevs(alldevs);
}
pcap_close(handle);
return(2);
}
@@ -195,54 +304,95 @@ int main(int argc, char *argv[]) {
struct pcap_pkthdr header;
const u_char *packet;
char source_ip_str[INET6_ADDRSTRLEN], dest_ip_str[INET6_ADDRSTRLEN];
struct ip *ip_header;
#ifdef ENABLE_IPV6
struct ip6_hdr *ip6_header;
#endif
struct ip *ip_header; // Declare ip4_header
struct ip6_hdr *ip6_header; // Declare ip6_header
int ip_protocol = 0;
struct timeval current_time, last_count;
printf("Using interface: %s\n", dev_name);
printf("Using filter: %s\n", filter_exp);
printf("Destination IP: %s\n", dest_ip);
printf("Destination Port: %d\n", dest_port);
gettimeofday(&last_count, NULL);
printf("\n");
while (1) {
packet = pcap_next(handle, &header);
if (packet == NULL)
continue;
// Assuming Ethernet header is 14 bytes
// Check IP version
ip_header = (struct ip*)(packet + ETHERNET_HEADER_LENGTH);
ip_protocol = ip_header->ip_v;
if (count_packets) {
packet_count++;
if (ip_protocol == 4) {
// IPv4
inet_ntop(AF_INET, &(ip_header->ip_src), source_ip_str, INET6_ADDRSTRLEN);
inet_ntop(AF_INET, &(ip_header->ip_dst), dest_ip_str, INET6_ADDRSTRLEN);
gettimeofday(&current_time, NULL);
long elapsed_ms = current_time.tv_sec * 1000 + (current_time.tv_usec /1000)-
(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) {
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 (ip_protocol == 6) {
// IPv6
ip6_header = (struct ip6_hdr*)(packet + ETHERNET_HEADER_LENGTH);
} else if (ether_type == ETHERTYPE_IPV6) {
// Handle IPv6 traffic
ip6_header = (struct ip6_hdr *)(packet + ETHERNET_HEADER_LENGTH + vlan_offset);
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);
if (verbose) {
printf("IPv6 Packet: %s -> %s, Next Header: %d\n",
source_ip_str, dest_ip_str, ip6_header->ip6_nxt);
printf("IPv6 Packet: %s -> %s, Next Header: %s\n",
source_ip_str, dest_ip_str, lookup_protocol_name(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
struct tzsp_header tzsp;
@@ -274,7 +424,7 @@ int main(int argc, char *argv[]) {
memcpy(ptr, packet, header.caplen);
// 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");
}
@@ -283,9 +433,6 @@ int main(int argc, char *argv[]) {
pcap_freecode(&fp);
pcap_close(handle);
if (alldevs != NULL) {
pcap_freealldevs(alldevs); // Free the device list only if devices were found
}
close(sockfd);
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
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
Specify the capture filter in BPF syntax (e.g., tcp port 80).
.TP
.B \-r \fIip_address\fR
Specify the destination IP address (required).
.B \-r \fIhost/ipv4/ipv6\fR
Specify the destination host (required).
.TP
.B \-p \fIport\fR
Specify the destination port (default: 37008).
.TP
.B \-4
Force IPv4 host lookup.
.TP
.B \-6
Force IPv6 host lookup.
.TP
.B \-l
List available network interfaces.
.TP
.B \-v
Enable verbose mode (prints packet information).
.TP
.B \-c
Count matching packets (overrides verbose mode)
.TP
.B \-h
Show this help message.

View File

@@ -1,5 +1,5 @@
Name: pcapmirror
Version: 0.3
Version: 0.5
Release: %(perl -e 'print time()')%{?dist}
Summary: A simple packet capture mirror
License: BSD 3-Clause License
@@ -26,6 +26,15 @@ pcapmirror is a command-line tool for capturing and mirroring network traffic us
%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
- added manpage
* Sat Mar 22 2025 Matthias Cramer <cramer@freesone.net> 0.2-1