tcp_shell: Support dumping packets to a pcap file.

For analysis in Wireshark (and other tools.)
It should be possible to use this mode with
a FIFO for real-time analysis, too.
This commit is contained in:
Augustin Cavalier
2023-12-30 15:55:09 -05:00
parent da4dbfa47a
commit 87cfc3b883
3 changed files with 235 additions and 98 deletions
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2023, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef PCAP_H
#define PCAP_H
#include <SupportDefs.h>
struct pcap_header {
uint32 magic;
uint16 version_major;
uint16 version_minor;
int32 timezone;
uint32 timestamp_accuracy;
uint32 max_packet_length;
uint32 linktype;
} _PACKED;
#define PCAP_MAGIC (0xa1b2c3d4)
#define PCAP_LINKTYPE_RAW (101) /* Raw IPv4/IPv6 */
#define PCAP_LINKTYPE_IPV4 (228)
#define PCAP_LINKTYPE_IPV6 (229)
struct pcap_packet_header {
uint32 ts_sec;
uint32 ts_usec;
uint32 included_len;
uint32 original_len;
} _PACKED;
#endif // PCAP_H
+168 -67
View File
@@ -9,6 +9,7 @@
#include "argv.h" #include "argv.h"
#include "tcp.h" #include "tcp.h"
#include "pcap.h"
#include "utility.h" #include "utility.h"
#include <NetBufferUtilities.h> #include <NetBufferUtilities.h>
@@ -26,8 +27,11 @@
#include <module.h> #include <module.h>
#include <Locker.h> #include <Locker.h>
#include <ctype.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/ip.h>
#include <ctype.h>
#include <errno.h>
#include <new> #include <new>
#include <set> #include <set>
#include <stdio.h> #include <stdio.h>
@@ -95,7 +99,8 @@ static std::set<uint32> sDropList;
static bigtime_t sRoundTripTime = 0; static bigtime_t sRoundTripTime = 0;
static bool sIncreasingRoundTrip = false; static bool sIncreasingRoundTrip = false;
static bool sRandomRoundTrip = false; static bool sRandomRoundTrip = false;
static bool sTCPDump = true; static void (*sPacketMonitor)(net_buffer *, int32, bool) = NULL;
static int sPcapFD = -1;
static bigtime_t sStartTime; static bigtime_t sStartTime;
static double sRandomReorder = 0.0; static double sRandomReorder = 0.0;
static std::set<uint32> sReorderList; static std::set<uint32> sReorderList;
@@ -938,8 +943,6 @@ domain_get_mtu(net_protocol *protocol, const struct sockaddr *address)
status_t status_t
domain_receive_data(net_buffer *buffer) domain_receive_data(net_buffer *buffer)
{ {
static bigtime_t lastTime = 0;
uint32 packetNumber = atomic_add(&sPacketNumber, 1); uint32 packetNumber = atomic_add(&sPacketNumber, 1);
bool drop = false; bool drop = false;
@@ -957,10 +960,84 @@ domain_receive_data(net_buffer *buffer)
snooze(sRoundTripTime / 2 + add); snooze(sRoundTripTime / 2 + add);
} }
if (sTCPDump) { if (sPacketMonitor != NULL) {
sPacketMonitor(buffer, packetNumber, drop);
} else if (drop)
printf("<**** DROPPED %ld ****>\n", packetNumber);
if (drop) {
gNetBufferModule.free(buffer);
return B_OK;
}
return gTCPModule->receive_data(buffer);
}
status_t
domain_error(net_error error, net_buffer* data)
{
return B_ERROR;
}
status_t
domain_error_reply(net_protocol* self, net_buffer* cause,
net_error error, net_error_data* errorData)
{
return B_ERROR;
}
net_protocol_module_info gDomainModule = {
{
NULL,
0,
std_ops
},
NET_PROTOCOL_ATOMIC_MESSAGES,
NULL, // init
NULL, // uninit
domain_open,
domain_close,
domain_free,
domain_connect,
domain_accept,
domain_control,
NULL, // getsockopt
NULL, // setsockopt
domain_bind,
domain_unbind,
domain_listen,
domain_shutdown,
domain_send_data,
domain_send_routed_data,
domain_send_avail,
domain_read_data,
domain_read_avail,
domain_get_domain,
domain_get_mtu,
domain_receive_data,
NULL, // deliver_data
domain_error,
domain_error_reply,
NULL, // attach_ancillary_data
NULL, // process_ancillary_data
};
// #pragma mark - packet capture
static void
dump_printf(net_buffer* buffer, int32 packetNumber, bool willBeDropped)
{
static bigtime_t lastTime = 0;
NetBufferHeaderReader<tcp_header> bufferHeader(buffer); NetBufferHeaderReader<tcp_header> bufferHeader(buffer);
if (bufferHeader.Status() < B_OK) if (bufferHeader.Status() < B_OK)
return bufferHeader.Status(); return;
tcp_header &header = bufferHeader.Data(); tcp_header &header = bufferHeader.Data();
@@ -1045,74 +1122,88 @@ domain_receive_data(net_buffer *buffer)
} }
} }
if (drop) if (willBeDropped)
printf(" <DROPPED>"); printf(" <DROPPED>");
printf("\33[0m\n"); printf("\33[0m\n");
} else if (drop) }
printf("<**** DROPPED %ld ****>\n", packetNumber);
if (drop) {
gNetBufferModule.free(buffer); static void
return B_OK; dump_pcap(net_buffer* buffer, int32 packetNumber, bool willBeDropped)
{
const bigtime_t time = real_time_clock_usecs();
struct pcap_packet_header pcap_header;
pcap_header.ts_sec = time / 1000000;
pcap_header.ts_usec = time % 1000000;
pcap_header.included_len = sizeof(struct ip) + buffer->size;
pcap_header.original_len = pcap_header.included_len;
struct ip ip_header;
ip_header.ip_v = IPVERSION;
ip_header.ip_hl = sizeof(struct ip) >> 2;
ip_header.ip_tos = 0;
ip_header.ip_len = htons(sizeof(struct ip) + buffer->size);
ip_header.ip_id = htons(packetNumber);
ip_header.ip_off = 0;
ip_header.ip_ttl = 254;
ip_header.ip_p = IPPROTO_TCP;
ip_header.ip_sum = 0;
ip_header.ip_src.s_addr = ((sockaddr_in*)buffer->source)->sin_addr.s_addr;
ip_header.ip_dst.s_addr = ((sockaddr_in*)buffer->destination)->sin_addr.s_addr;
size_t count = 16, used = 0;
iovec vecs[count];
vecs[used].iov_base = &pcap_header;
vecs[used].iov_len = sizeof(pcap_header);
used++;
vecs[used].iov_base = &ip_header;
vecs[used].iov_len = sizeof(ip_header);
used++;
used += gNetBufferModule.get_iovecs(buffer, vecs + used, count - used);
static mutex writesLock = MUTEX_INITIALIZER("pcap writes");
MutexLocker _(writesLock);
ssize_t written = writev(sPcapFD, vecs, used);
if (written != (pcap_header.included_len + sizeof(pcap_packet_header))) {
fprintf(stderr, "writing to pcap file failed\n");
exit(1);
}
}
static bool
setup_dump_pcap(const char* file)
{
sPcapFD = open(file, O_CREAT | O_WRONLY | O_TRUNC);
if (sPcapFD < 0) {
fprintf(stderr, "tcp_shell: Failed to open output pcap file: %d\n",
errno);
return false;
} }
return gTCPModule->receive_data(buffer); struct pcap_header header;
header.magic = PCAP_MAGIC;
header.version_major = 2;
header.version_minor = 4;
header.timezone = 0;
header.timestamp_accuracy = 0;
header.max_packet_length = 65535;
header.linktype = PCAP_LINKTYPE_IPV4;
if (write(sPcapFD, &header, sizeof(header)) != sizeof(header)) {
fprintf(stderr, "tcp_shell: Failed to write pcap file header: %d\n",
errno);
return false;
}
sPacketMonitor = dump_pcap;
return true;
} }
status_t
domain_error(net_error error, net_buffer* data)
{
return B_ERROR;
}
status_t
domain_error_reply(net_protocol* self, net_buffer* cause,
net_error error, net_error_data* errorData)
{
return B_ERROR;
}
net_protocol_module_info gDomainModule = {
{
NULL,
0,
std_ops
},
NET_PROTOCOL_ATOMIC_MESSAGES,
NULL, // init
NULL, // uninit
domain_open,
domain_close,
domain_free,
domain_connect,
domain_accept,
domain_control,
NULL, // getsockopt
NULL, // setsockopt
domain_bind,
domain_unbind,
domain_listen,
domain_shutdown,
domain_send_data,
domain_send_routed_data,
domain_send_avail,
domain_read_data,
domain_read_avail,
domain_get_domain,
domain_get_mtu,
domain_receive_data,
NULL, // deliver_data
domain_error,
domain_error_reply,
NULL, // attach_ancillary_data
NULL, // process_ancillary_data
};
// #pragma mark - test // #pragma mark - test
@@ -1595,8 +1686,18 @@ do_help(int argc, char** argv)
int int
main(int argc, char** argv) main(int argc, char* argv[])
{ {
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-w") == 0 && (i + 1) < argc) {
if (!setup_dump_pcap(argv[++i]))
return 1;
}
}
if (sPacketMonitor == NULL)
sPacketMonitor = dump_printf;
status_t status = init_timers(); status_t status = init_timers();
if (status < B_OK) { if (status < B_OK) {
fprintf(stderr, "tcp_tester: Could not initialize timers: %s\n", fprintf(stderr, "tcp_tester: Could not initialize timers: %s\n",