Created
December 17, 2019 14:46
-
-
Save majek/9668716d4bb88fbe562037a912d0ae35 to your computer and use it in GitHub Desktop.
gVisor tcpdump
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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