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:
@@ -26,7 +26,7 @@ SimpleTest tcp_shell :
|
|||||||
argv.c
|
argv.c
|
||||||
ipv4_address.cpp
|
ipv4_address.cpp
|
||||||
|
|
||||||
: be libkernelland_emu.so [ TargetLibstdc++ ]
|
: be libkernelland_emu.so [ TargetLibstdc++ ]
|
||||||
;
|
;
|
||||||
|
|
||||||
SimpleTest BufferQueueTest :
|
SimpleTest BufferQueueTest :
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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,97 +960,8 @@ domain_receive_data(net_buffer *buffer)
|
|||||||
snooze(sRoundTripTime / 2 + add);
|
snooze(sRoundTripTime / 2 + add);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sTCPDump) {
|
if (sPacketMonitor != NULL) {
|
||||||
NetBufferHeaderReader<tcp_header> bufferHeader(buffer);
|
sPacketMonitor(buffer, packetNumber, drop);
|
||||||
if (bufferHeader.Status() < B_OK)
|
|
||||||
return bufferHeader.Status();
|
|
||||||
|
|
||||||
tcp_header &header = bufferHeader.Data();
|
|
||||||
|
|
||||||
bigtime_t now = system_time();
|
|
||||||
if (lastTime == 0)
|
|
||||||
lastTime = now;
|
|
||||||
|
|
||||||
printf("\33[0m% 3ld %8.6f (%8.6f) ", packetNumber, (now - sStartTime) / 1000000.0,
|
|
||||||
(now - lastTime) / 1000000.0);
|
|
||||||
lastTime = now;
|
|
||||||
|
|
||||||
if (is_server((sockaddr *)buffer->source))
|
|
||||||
printf("\33[31mserver > client: ");
|
|
||||||
else
|
|
||||||
printf("client > server: ");
|
|
||||||
|
|
||||||
int32 length = buffer->size - header.HeaderLength();
|
|
||||||
|
|
||||||
if ((header.flags & TCP_FLAG_PUSH) != 0)
|
|
||||||
putchar('P');
|
|
||||||
if ((header.flags & TCP_FLAG_SYNCHRONIZE) != 0)
|
|
||||||
putchar('S');
|
|
||||||
if ((header.flags & TCP_FLAG_FINISH) != 0)
|
|
||||||
putchar('F');
|
|
||||||
if ((header.flags & TCP_FLAG_RESET) != 0)
|
|
||||||
putchar('R');
|
|
||||||
if ((header.flags
|
|
||||||
& (TCP_FLAG_SYNCHRONIZE | TCP_FLAG_FINISH | TCP_FLAG_PUSH | TCP_FLAG_RESET)) == 0)
|
|
||||||
putchar('.');
|
|
||||||
|
|
||||||
printf(" %lu:%lu (%lu)", header.Sequence(), header.Sequence() + length, length);
|
|
||||||
if ((header.flags & TCP_FLAG_ACKNOWLEDGE) != 0)
|
|
||||||
printf(" ack %lu", header.Acknowledge());
|
|
||||||
|
|
||||||
printf(" win %u", header.AdvertisedWindow());
|
|
||||||
|
|
||||||
if (header.HeaderLength() > sizeof(tcp_header)) {
|
|
||||||
int32 size = header.HeaderLength() - sizeof(tcp_header);
|
|
||||||
|
|
||||||
tcp_option *option;
|
|
||||||
uint8 optionsBuffer[1024];
|
|
||||||
if (gBufferModule->direct_access(buffer, sizeof(tcp_header),
|
|
||||||
size, (void **)&option) != B_OK) {
|
|
||||||
if (size > 1024) {
|
|
||||||
printf("options too large to take into account (%ld bytes)\n", size);
|
|
||||||
size = 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
gBufferModule->read(buffer, sizeof(tcp_header), optionsBuffer, size);
|
|
||||||
option = (tcp_option *)optionsBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (size > 0) {
|
|
||||||
uint32 length = 1;
|
|
||||||
switch (option->kind) {
|
|
||||||
case TCP_OPTION_END:
|
|
||||||
case TCP_OPTION_NOP:
|
|
||||||
break;
|
|
||||||
case TCP_OPTION_MAX_SEGMENT_SIZE:
|
|
||||||
printf(" <mss %u>", ntohs(option->max_segment_size));
|
|
||||||
length = 4;
|
|
||||||
break;
|
|
||||||
case TCP_OPTION_WINDOW_SHIFT:
|
|
||||||
printf(" <ws %u>", option->window_shift);
|
|
||||||
length = 3;
|
|
||||||
break;
|
|
||||||
case TCP_OPTION_TIMESTAMP:
|
|
||||||
printf(" <ts %lu:%lu>", option->timestamp.value, option->timestamp.reply);
|
|
||||||
length = 10;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
length = option->length;
|
|
||||||
// make sure we don't end up in an endless loop
|
|
||||||
if (length == 0)
|
|
||||||
size = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
size -= length;
|
|
||||||
option = (tcp_option *)((uint8 *)option + length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drop)
|
|
||||||
printf(" <DROPPED>");
|
|
||||||
printf("\33[0m\n");
|
|
||||||
} else if (drop)
|
} else if (drop)
|
||||||
printf("<**** DROPPED %ld ****>\n", packetNumber);
|
printf("<**** DROPPED %ld ****>\n", packetNumber);
|
||||||
|
|
||||||
@@ -1113,6 +1027,183 @@ net_protocol_module_info gDomainModule = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #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);
|
||||||
|
if (bufferHeader.Status() < B_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tcp_header &header = bufferHeader.Data();
|
||||||
|
|
||||||
|
bigtime_t now = system_time();
|
||||||
|
if (lastTime == 0)
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
printf("\33[0m% 3ld %8.6f (%8.6f) ", packetNumber, (now - sStartTime) / 1000000.0,
|
||||||
|
(now - lastTime) / 1000000.0);
|
||||||
|
lastTime = now;
|
||||||
|
|
||||||
|
if (is_server((sockaddr *)buffer->source))
|
||||||
|
printf("\33[31mserver > client: ");
|
||||||
|
else
|
||||||
|
printf("client > server: ");
|
||||||
|
|
||||||
|
int32 length = buffer->size - header.HeaderLength();
|
||||||
|
|
||||||
|
if ((header.flags & TCP_FLAG_PUSH) != 0)
|
||||||
|
putchar('P');
|
||||||
|
if ((header.flags & TCP_FLAG_SYNCHRONIZE) != 0)
|
||||||
|
putchar('S');
|
||||||
|
if ((header.flags & TCP_FLAG_FINISH) != 0)
|
||||||
|
putchar('F');
|
||||||
|
if ((header.flags & TCP_FLAG_RESET) != 0)
|
||||||
|
putchar('R');
|
||||||
|
if ((header.flags
|
||||||
|
& (TCP_FLAG_SYNCHRONIZE | TCP_FLAG_FINISH | TCP_FLAG_PUSH | TCP_FLAG_RESET)) == 0)
|
||||||
|
putchar('.');
|
||||||
|
|
||||||
|
printf(" %lu:%lu (%lu)", header.Sequence(), header.Sequence() + length, length);
|
||||||
|
if ((header.flags & TCP_FLAG_ACKNOWLEDGE) != 0)
|
||||||
|
printf(" ack %lu", header.Acknowledge());
|
||||||
|
|
||||||
|
printf(" win %u", header.AdvertisedWindow());
|
||||||
|
|
||||||
|
if (header.HeaderLength() > sizeof(tcp_header)) {
|
||||||
|
int32 size = header.HeaderLength() - sizeof(tcp_header);
|
||||||
|
|
||||||
|
tcp_option *option;
|
||||||
|
uint8 optionsBuffer[1024];
|
||||||
|
if (gBufferModule->direct_access(buffer, sizeof(tcp_header),
|
||||||
|
size, (void **)&option) != B_OK) {
|
||||||
|
if (size > 1024) {
|
||||||
|
printf("options too large to take into account (%ld bytes)\n", size);
|
||||||
|
size = 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
gBufferModule->read(buffer, sizeof(tcp_header), optionsBuffer, size);
|
||||||
|
option = (tcp_option *)optionsBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size > 0) {
|
||||||
|
uint32 length = 1;
|
||||||
|
switch (option->kind) {
|
||||||
|
case TCP_OPTION_END:
|
||||||
|
case TCP_OPTION_NOP:
|
||||||
|
break;
|
||||||
|
case TCP_OPTION_MAX_SEGMENT_SIZE:
|
||||||
|
printf(" <mss %u>", ntohs(option->max_segment_size));
|
||||||
|
length = 4;
|
||||||
|
break;
|
||||||
|
case TCP_OPTION_WINDOW_SHIFT:
|
||||||
|
printf(" <ws %u>", option->window_shift);
|
||||||
|
length = 3;
|
||||||
|
break;
|
||||||
|
case TCP_OPTION_TIMESTAMP:
|
||||||
|
printf(" <ts %lu:%lu>", option->timestamp.value, option->timestamp.reply);
|
||||||
|
length = 10;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
length = option->length;
|
||||||
|
// make sure we don't end up in an endless loop
|
||||||
|
if (length == 0)
|
||||||
|
size = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size -= length;
|
||||||
|
option = (tcp_option *)((uint8 *)option + length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willBeDropped)
|
||||||
|
printf(" <DROPPED>");
|
||||||
|
printf("\33[0m\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark - test
|
// #pragma mark - test
|
||||||
|
|
||||||
|
|
||||||
@@ -1286,7 +1377,7 @@ cleanup_context(struct context& context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark -
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
static void do_help(int argc, char** argv);
|
static void do_help(int argc, char** argv);
|
||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user