Skip to content

Instantly share code, notes, and snippets.

@c-bata
Last active September 10, 2023 11:08
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save c-bata/ca188c0184715efc2660422b4b3851c6 to your computer and use it in GitHub Desktop.
Save c-bata/ca188c0184715efc2660422b4b3851c6 to your computer and use it in GitHub Desktop.
Capture packets from bpf devices on macOS.
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/bpf.h>
#include <netinet/in.h>
#include <net/if.h>
#include "bpf.h"
void print_bpf_options(BpfOption option)
{
fprintf(stderr, "BpfOption:\n");
fprintf(stderr, " BPF Device: %s\n", option.deviceName);
fprintf(stderr, " Network Interface: %s\n", option.interfaceName);
fprintf(stderr, " Buffer Length: %d\n", option.bufferLength);
}
void print_bpf_sniffer_params(BpfSniffer sniffer)
{
fprintf(stderr, "BpfSniffer:\n");
fprintf(stderr, " Opened BPF Device: %s\n", sniffer.deviceName);
fprintf(stderr, " Buffer Length: %d\n", sniffer.bufferLength);
}
int pick_bpf_device(BpfSniffer *sniffer)
{
char dev[11] = {0};
for (int i = 0; i < 99; ++i) {
sprintf(dev, "/dev/bpf%i", i);
sniffer->fd = open(dev, O_RDWR);
if (sniffer->fd != -1) {
strcpy(sniffer->deviceName, dev);
return 0;
}
}
return -1;
}
int new_bpf_sniffer(BpfOption option, BpfSniffer *sniffer)
{
if (strlen(option.deviceName) == 0) {
if (pick_bpf_device(sniffer) == -1)
return -1;
} else {
sniffer->fd = open(option.deviceName, O_RDWR);
if (sniffer->fd != -1)
return -1;
}
if (option.bufferLength == 0) {
/* Get Buffer Length */
if (ioctl(sniffer->fd, BIOCGBLEN, &sniffer->bufferLength) == -1) {
perror("ioctl BIOCGBLEN");
return -1;
}
} else {
/* Set Buffer Length */
/* The buffer must be set before the file is attached to an interface with BIOCSETIF. */
if (ioctl(sniffer->fd, BIOCSBLEN, &option.bufferLength) == -1) {
perror("ioctl BIOCSBLEN");
return -1;
}
sniffer->bufferLength = option.bufferLength;
}
struct ifreq interface;
strcpy(interface.ifr_name, option.interfaceName);
if(ioctl(sniffer->fd, BIOCSETIF, &interface) > 0) {
perror("ioctl BIOCSETIF");
return -1;
}
unsigned int enable = 1;
if (ioctl(sniffer->fd, BIOCIMMEDIATE, &enable) == -1) {
perror("ioctl BIOCIMMEDIATE");
return -1;
}
if (ioctl(sniffer->fd, BIOCPROMISC, NULL) == -1) {
perror("ioctl BIOCPROMISC");
return -1;
}
sniffer->readBytesConsumed = 0;
sniffer->lastReadLength = 0;
sniffer->buffer = malloc(sizeof(char) * sniffer->bufferLength);
return 0;
}
int read_bpf_packet_data(BpfSniffer *sniffer, CapturedInfo *info)
{
struct bpf_hdr *bpfPacket;
if (sniffer->readBytesConsumed + sizeof(sniffer->buffer) >= sniffer->lastReadLength) {
sniffer->readBytesConsumed = 0;
memset(sniffer->buffer, 0, sniffer->bufferLength);
ssize_t lastReadLength = read(sniffer->fd, sniffer->buffer, sniffer->bufferLength);
if (lastReadLength == -1) {
sniffer->lastReadLength = 0;
perror("read bpf packet:");
return -1;
}
sniffer->lastReadLength = (unsigned int) lastReadLength;
}
bpfPacket = (struct bpf_hdr*)((long)sniffer->buffer + (long)sniffer->readBytesConsumed);
info->data = sniffer->buffer + (long)sniffer->readBytesConsumed + bpfPacket->bh_hdrlen;
sniffer->readBytesConsumed += BPF_WORDALIGN(bpfPacket->bh_hdrlen + bpfPacket->bh_caplen);
return bpfPacket->bh_datalen;
}
int close_bpf_sniffer(BpfSniffer *sniffer)
{
free(sniffer->buffer);
if (close(sniffer->fd) == -1)
return -1;
return 0;
}
#ifndef _PCAP_BPF
#define _PCAP_BPF
typedef struct {
char deviceName[11];
char interfaceName[16];
unsigned int bufferLength;
} BpfOption;
typedef struct {
int fd;
char deviceName[11];
unsigned int bufferLength;
unsigned int lastReadLength;
unsigned int readBytesConsumed;
char *buffer;
} BpfSniffer;
typedef struct {
char *data;
} CapturedInfo;
void print_bpf_options(BpfOption option);
void print_bpf_sniffer_params(BpfSniffer sniffer);
int new_bpf_sniffer(BpfOption option, BpfSniffer *sniffer);
int read_bpf_packet_data(BpfSniffer *sniffer, CapturedInfo *info);
int close_bpf_sniffer(BpfSniffer *sniffer);
#endif /* _PCAP_BPF */
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/bpf.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include "bpf.h"
int main()
{
BpfOption option;
strcpy(option.interfaceName, "en0");
option.bufferLength = 32767;
print_bpf_options(option);
BpfSniffer sniffer;
if (new_bpf_sniffer(option, &sniffer) == -1)
return 1;
print_bpf_sniffer_params(sniffer);
CapturedInfo info;
int dataLength;
while((dataLength = read_bpf_packet_data(&sniffer, &info)) != -1) {
printf("--------------------------------------------------\n");
printf("Payload length: %d\n", dataLength);
struct ether_header* eh = (struct ether_header*)info.data;
printf(" Ethernet Frame\n");
printf(" src mac address: %x:%x:%x:%x:%x:%x\n",
eh->ether_shost[0],
eh->ether_shost[1],
eh->ether_shost[2],
eh->ether_shost[3],
eh->ether_shost[4],
eh->ether_shost[5]);
printf(" dst mac address: %x:%x:%x:%x:%x:%x\n",
eh->ether_dhost[0],
eh->ether_dhost[1],
eh->ether_dhost[2],
eh->ether_dhost[3],
eh->ether_dhost[4],
eh->ether_dhost[5]);
if (ntohs(eh->ether_type) == ETHERTYPE_IP) {
printf(" type: IPv4, %x\n", eh->ether_type);
struct ip* ip = (struct ip*)((long)eh + sizeof(struct ether_header));
printf(" IP Frame\n");
printf(" headerLength: %d\n", ip->ip_hl * 4);
printf(" version: %d\n", ip->ip_v);
printf(" protocol: %d\n", ip->ip_p);
printf(" ttl: %d\n", ip->ip_ttl);
printf(" dst ip: %s\n", inet_ntoa(ip->ip_dst));
printf(" src ip: %s\n", inet_ntoa(ip->ip_src));
if (ip->ip_p == IPPROTO_TCP) {
struct tcphdr* tcp = (struct tcphdr*)((long)ip + (ip->ip_hl * 4));
printf(" TCP Packet\n");
printf(" dst port: %d\n", tcp->th_dport);
printf(" src port: %d\n", tcp->th_sport);
}
} else {
printf(" type: Other, %x\n", eh->ether_type);
}
}
close_bpf_sniffer(&sniffer);
return 0;
}
@c-bata
Copy link
Author

c-bata commented Oct 26, 2018

How to bulid

#!/bin/bash
set -e
gcc -c bpf.c
ar rusv libbpf.a bpf.o
gcc -o pcap main.c -L. -lbpf

LOC

$ ls *.[ch] | xargs wc -l
     130 bpf.c
      33 bpf.h
      80 main.c
     243 total

Result

$ ./pcap
BpfOption:
  BPF Device:
  Network Interface: en0
  Buffer Length: 32767
BpfSniffer:
  Opened BPF Device: /dev/bpf2
  Buffer Length: 32767
--------------------------------------------------
 Ethernet Frame
  src mac address: f0:18:98:78:87:b7
  dest mac address: d8:6c:63:4c:c7:50
  type: IPv4, 8
 IP Frame
  headerLength: 20
  version: 4
  ttl: 64
  dest ip: 192.168.0.3
  src ip: 192.168.0.8
 TCP Frame
  dest port: 18719
  src port: 23516
--------------------------------------------------
 Ethernet Frame
  src mac address: d8:6c:63:4c:c7:50
  dest mac address: f0:18:98:78:87:b7
  type: IPv4, 8
 IP Frame
  headerLength: 20
  version: 4
  ttl: 64
  dest ip: 192.168.0.8
  src ip: 192.168.0.3
 TCP Frame
  dest port: 23516
  src port: 18719
--------------------------------------------------
 Ethernet Frame
  src mac address: f0:18:98:78:87:b7
  dest mac address: d8:6c:63:4c:c7:50
  type: IPv4, 8
 IP Frame
  headerLength: 20
  version: 4
  ttl: 64
  dest ip: 192.168.0.3
  src ip: 192.168.0.8
 TCP Frame
  dest port: 18719
  src port: 23516
--------------------------------------------------
 Ethernet Frame
  src mac address: f0:18:98:78:87:b7
  dest mac address: c4:7d:4f:b6:77:c3
  type: Other, dd86
--------------------------------------------------
 Ethernet Frame
  src mac address: c4:7d:4f:b6:77:c3
  dest mac address: f0:18:98:78:87:b7
  type: Other, dd86

@alexgershberg
Copy link

Hi! This is quite fascinating, what resources have you used to figure out how to interface with BPFs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment