Skip to content

Instantly share code, notes, and snippets.

@majek
Created December 17, 2019 14:46
Show Gist options
  • Save majek/9668716d4bb88fbe562037a912d0ae35 to your computer and use it in GitHub Desktop.
Save majek/9668716d4bb88fbe562037a912d0ae35 to your computer and use it in GitHub Desktop.
gVisor tcpdump
/*
* Usage: ./gvtcpdump | tcpdump -n -r -
*/
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <pcap/pcap.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
const char *optstring_from_long_options(const struct option *opt)
{
static char optstring[256] = {0};
char *osp = optstring;
for (; opt->name != NULL; opt++) {
if (opt->flag == 0 && opt->val > 0 && opt->val < 256) {
*osp++ = opt->val;
switch (opt->has_arg) {
case optional_argument:
*osp++ = ':';
*osp++ = ':';
break;
case required_argument:
*osp++ = ':';
break;
}
}
}
*osp++ = '\0';
if (osp - optstring >= (int)sizeof(optstring)) {
abort();
}
return optstring;
}
sig_atomic_t done = 0;
static void ctrlc_handler()
{
done += 1;
if (done > 1) {
exit(-1);
}
}
int main(int argc, char **argv)
{
long long desired_count = -1;
int interface = -1;
int batch_buffered = 0;
{
static struct option long_options[] = {
{"interface", required_argument, 0, 'i'},
{"count", required_argument, 0, 'c'},
{"batch-buffered", no_argument, 0, 'b'},
{NULL, 0, 0, 0}};
optind = 1;
int v;
while (1) {
int option_index = 0;
int arg = getopt_long(
argc, argv,
optstring_from_long_options(long_options),
long_options, &option_index);
if (arg == -1) {
break;
}
switch (arg) {
default:
case 0:
fprintf(stderr, "Unknown option: %s",
argv[optind]);
exit(-1);
break;
case '?':
exit(-1);
break;
case 'i':
v = atoi(optarg);
if (v > 0) {
interface = v;
} else {
interface = if_nametoindex(optarg);
if (interface == 0) {
error(-1, errno,
"if_nametoindex(%s)",
optarg);
}
}
break;
case 'c':
desired_count = atoll(optarg);
break;
case 'b':
batch_buffered = 1;
break;
}
}
}
struct sigaction signal_action = {
.sa_flags = 0, // don't restart the blocking call
.sa_handler = ctrlc_handler,
};
sigaction(SIGINT, &signal_action, NULL);
signal_action = (struct sigaction){
.sa_flags = 0, // don't restart the blocking call
.sa_handler = SIG_IGN};
sigaction(SIGPIPE, &signal_action, NULL);
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (fd < 0) {
error(-1, errno, "socket(AF_PACKET, SOCK_RAW)");
}
// Drop all the packets
struct sock_filter code[] = {
{0x06, 0, 0, 0x00000000}, // ret #0
};
struct sock_fprog bpf = {
.len = sizeof(code) / sizeof(code[0]),
.filter = code,
};
int r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (r < 0) {
fprintf(stderr, "[!] setsocktopt(SO_ATTACH_FILTER) = %s\n",
strerror(errno));
}
// Flush socket
while (1) {
r = recv(fd, NULL, 0, MSG_TRUNC | MSG_DONTWAIT);
if (r < 0) {
break;
}
}
// Select interface
if (interface >= 0) {
struct sockaddr_ll sa_ll = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_ALL),
.sll_ifindex = interface,
};
r = bind(fd, (struct sockaddr *)&sa_ll, sizeof(sa_ll));
if (r < 0) {
error(-1, errno, "bind(SOCK_RAW, ifindex=%d)",
interface);
}
}
int val = 1024 * 1024;
r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val));
if (r < 0) {
fprintf(stderr, "[!] setsocktopt(SO_RCVBUF) = %s\n",
strerror(errno));
}
val = 1;
r = setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val));
if (r < 0) {
fprintf(stderr, "[!] setsocktopt(SO_TIMESTAMP) = %s\n",
strerror(errno));
}
val = 1;
r = setsockopt(fd, SOL_PACKET, PACKET_AUXDATA, &val, sizeof(val));
if (r < 0) {
fprintf(stderr, "[!] setsocktopt(PACKET_AUXDATA, 1) = %s\n",
strerror(errno));
}
if (batch_buffered == 0) {
// by default flush stdout often, to allow for piping into
// tcpdump
setbuf(stdout, NULL);
}
// DLT_LINUX_SLL, DLT_RAW ???
pcap_t *pcap = pcap_open_dead(DLT_EN10MB, 65536 + 64);
pcap_dumper_t *dump = pcap_dump_fopen(pcap, stdout);
// Allow packets to fly in
val = 1;
r = setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val));
if (r < 0) {
fprintf(stderr, "[!] setsocktopt(SO_DETACH_FILTER) = %s\n",
strerror(errno));
}
long long count = 0;
while (1) {
if (done) {
break;
}
if (desired_count >= 0 && count >= desired_count) {
break;
}
char buf[65536 + 64];
struct sockaddr_ll sa_ll = {};
struct iovec iovec = {
.iov_base = buf,
.iov_len = sizeof(buf),
};
char control[128];
struct msghdr msg = {
.msg_name = &sa_ll,
.msg_namelen = sizeof(sa_ll),
.msg_iov = &iovec,
.msg_iovlen = 1,
.msg_control = control,
.msg_controllen = sizeof(control),
.msg_flags = MSG_TRUNC,
};
ssize_t tot_length = recvmsg(fd, &msg, 0);
if (tot_length < 0) {
if (errno == EAGAIN || errno == EINTR) {
continue;
}
fprintf(stderr, "[!] recvmsg() = %s\n",
strerror(errno));
continue;
}
struct timeval *tv = NULL;
struct timeval tv2;
struct tpacket_auxdata *tp_aux = NULL;
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
int l = cmsg->cmsg_level;
int t = cmsg->cmsg_type;
if (l == SOL_SOCKET && t == SO_TIMESTAMP) {
tv = (struct timeval *)CMSG_DATA(cmsg);
} else if (l == SOL_PACKET && t == PACKET_AUXDATA) {
tp_aux = (struct tpacket_auxdata *)CMSG_DATA(
cmsg);
}
}
if (tv == NULL) {
gettimeofday(&tv2, NULL);
tv = &tv2;
}
ssize_t caplen = tot_length;
if (tot_length > (ssize_t)sizeof(buf)) {
caplen = sizeof(buf);
}
struct pcap_pkthdr h = {
.ts = *tv, // timeval
.caplen = caplen,
.len = tot_length, // or higher
};
pcap_dump((void *)dump, &h, (void *)buf);
count += 1;
}
pcap_dump_close(dump);
fflush(stdout);
fflush(stderr);
for (r = 0; r < 10; r++) {
sched_yield();
}
struct tpacket_stats tp_stats;
socklen_t len = sizeof(tp_stats);
r = getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &tp_stats, &len);
if (r < 0) {
fprintf(stderr, "[!] getsocktopt(PACKET_STATISTICS) = %s\n",
strerror(errno));
} else {
fprintf(stderr,
"%lld packets captured\n%d packets received by "
"filter\n%d packets dropped\n",
count, tp_stats.tp_packets, tp_stats.tp_drops);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment