How to write cross-platform packet capture from scratch in 1000 LOC.

How supports the both of Linux and macOS

The packet capture tool receives and analyzes all packets flowing through the network. The way how Promiscuous mode allows a network device to intercept and read each network packets regardless of the target address. Most of NICs (Network Interface Cards) are support it.

RAW Socket

When reading ethernet frames on Linux, we need to use RAW Socket.

  1. Open socket descriptor with `PF_PACKET` as a protocol family, `SOCK_RAW` as a socket type and `htons(ETH_P_ALL)` as a protocol.
    int soc = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)))
  2. Retrieving information about network interface from the interface name.
    ioctl(soc, SIOCGIFINDEX, &if_req)
  3. Binding the socket descriptor to the interface.
    bind(soc, (struct sockaddr *) &sa, sizeof(sa))
  4. Get flags of the interface and enable promiscuous mode and set the interface up.
    ioctl(soc, SIOCGIFFLAGS, &if_req);
    if_req.ifr_flags = if_req.ifr_flags|IFF_PROMISC|IFF_UP;
    ioctl(soc, SIOCSIFFLAGS, &if_req);
struct timeval timeout;
fd_set mask;
int width, len, ready;
while (g_gotsig == 0) {
FD_ZERO(&mask);
FD_SET(soc, &mask);
width = doc + 1;
timeout.tv_sec = 8;
timeout.tv_usec = 0;
ready = select(width, &mask, NULL, NULL, &timeout);
if (ready == -1) {
perror("select");
break;
} else if (ready == 0) {
fprintf(stderr, "select timeout");
break;
}
if (FD_ISSET(sniffer->fd, &mask)){
if ((len = recv(soc, buffer, >buf_len, 0)) == -1){
perror("recv:");
return -1;
}
}
}

Berkeley Packet Filters

We need to use BPF(Berkeley Packet Filter) at BSD systems including macOS.
BPF provides virtual machine to filter packets in kernel space. And BPF devices are used to read data.

$ ls /dev/bpf?
/dev/bpf0 /dev/bpf1 /dev/bpf2 /dev/bpf3 /dev/bpf4 /dev/bpf5 /dev/bpf6 /dev/bpf7 /dev/bpf8 /dev/bpf9
  1. Open a bpf device.
    fd = open(params.device, O_RDWR)
  2. Set buffer length or get buffer length.
    ioctl(fd, BIOCSBLEN, &params.buf_len) : set buffer length
    ioctl(fd, BIOCGBLEN, &params.buf_len) : get buffer length
  3. Bind a BPF device into the interface.
    ioctl(fd, BIOCSETIF, &if_req)
  4. Enable promiscuous mode.
    ioctl(fd, BIOCPROMISC, NULL)
typedef struct {
int fd;
char device[11];
unsigned int buf_len;
char *buffer;
unsigned int last_read_len;
unsigned int read_bytes_consumed;
} Sniffer;
int
parse_bpf_packets(Sniffer *sniffer, CapturedInfo *info)
{
if (sniffer->read_bytes_consumed + sizeof(sniffer->buffer)
>= sniffer->last_read_len) {
return 0;
}
info->bpf_hdr = (struct bpf_hdr*)((long)sniffer->buffer +
(long)sniffer->read_bytes_consumed);
info->data = sniffer->buffer + \
(long)sniffer->read_bytes_consumed + \
info->bpf_hdr->bh_hdrlen;
sniffer->read_bytes_consumed += BPF_WORDALIGN(
info->bpf_hdr->bh_hdrlen + info->bpf_hdr->bh_caplen);
return info->bpf_hdr->bh_datalen;
}

References

Implementations I referred are below:

  • https://github.com/bpk-t/packet_capture
  • https://github.com/google/gopacket/blob/master/bsdbpf/bsd_bpf_sniffer.go
  • https://www.freebsd.org/cgi/man.cgi?bpf(4)

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Masashi SHIBATA

Masashi SHIBATA

115 Followers

Creator of go-prompt and kube-prompt. Optuna core-dev. Kubeflow/Katib reviewer. GitHub: c-bata