/*
 * netsniff-ng - the packet sniffing beast
 * Copyright 2009 - 2013 Daniel Borkmann.
 * Copyright 2010 Emmanuel Roullit.
 * Subject to the GPL, version 2.
 */

#ifndef PCAP_IO_H
#define PCAP_IO_H

#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <net/if_arp.h>
#include <linux/if.h>
#include <linux/if_packet.h>

#include "built_in.h"
#include "die.h"
#include "dev.h"
#include "ioops.h"
#include "linktype.h"

#define TCPDUMP_MAGIC				0xa1b2c3d4
#define ORIGINAL_TCPDUMP_MAGIC			TCPDUMP_MAGIC
#define NSEC_TCPDUMP_MAGIC			0xa1b23c4d
#define ORIGINAL_TCPDUMP_MAGIC_LL		0xb1b2c3d4	/* Internal dummy just for mapping */
#define NSEC_TCPDUMP_MAGIC_LL			0xb1b23c4d	/* Internal dummy just for mapping */
#define KUZNETZOV_TCPDUMP_MAGIC			0xa1b2cd34
#define BORKMANN_TCPDUMP_MAGIC			0xa1e2cb12

#define PCAP_VERSION_MAJOR			2
#define PCAP_VERSION_MINOR			4
#define PCAP_DEFAULT_SNAPSHOT_LEN		65535

#define PCAP_TSOURCE_SOFTWARE			1
#define PCAP_TSOURCE_SYS_HARDWARE		2
#define PCAP_TSOURCE_RAW_HARDWARE		3

struct pcap_filehdr {
	uint32_t magic;
	uint16_t version_major;
	uint16_t version_minor;
	int32_t  thiszone;
	uint32_t sigfigs;
	uint32_t snaplen;
	uint32_t linktype;
};

struct pcap_timeval {
	int32_t tv_sec;
	int32_t tv_usec;
};

struct pcap_timeval_ns {
	int32_t tv_sec;
	int32_t tv_nsec;
};

struct pcap_ll {
	uint16_t pkttype;
	uint16_t hatype;
	uint16_t len;
	uint8_t addr[8];
	uint16_t protocol;
};

struct pcap_pkthdr {
	struct pcap_timeval ts;
	uint32_t caplen;
	uint32_t len;
};

struct pcap_pkthdr_ns {
	struct pcap_timeval_ns ts;
	uint32_t caplen;
	uint32_t len;
};

struct pcap_pkthdr_ll {
	struct pcap_timeval ts;
	uint32_t caplen;
	uint32_t len;
	struct pcap_ll ll;
};

struct pcap_pkthdr_ns_ll {
	struct pcap_timeval_ns ts;
	uint32_t caplen;
	uint32_t len;
	struct pcap_ll ll;
};

struct pcap_pkthdr_kuz {
	struct pcap_timeval ts;
	uint32_t caplen;
	uint32_t len;
	uint32_t ifindex;
	uint16_t protocol;
	uint8_t pkttype;
};

struct pcap_pkthdr_bkm {
	struct pcap_timeval_ns ts;
	uint32_t caplen;
	uint32_t len;
	uint16_t tsource;
	uint16_t ifindex;
	uint16_t protocol;
	uint8_t hatype;
	uint8_t pkttype;
};

typedef union {
	struct pcap_pkthdr		ppo;
	struct pcap_pkthdr_ns		ppn;
	struct pcap_pkthdr_ll		ppo_ll;
	struct pcap_pkthdr_ns_ll	ppn_ll;
	struct pcap_pkthdr_kuz		ppk;
	struct pcap_pkthdr_bkm		ppb;
	uint8_t				raw;
} pcap_pkthdr_t;

enum pcap_type {
	DEFAULT		  =	ORIGINAL_TCPDUMP_MAGIC,
	NSEC		  =	NSEC_TCPDUMP_MAGIC,
	DEFAULT_LL	  =	ORIGINAL_TCPDUMP_MAGIC_LL,
	NSEC_LL		  =	NSEC_TCPDUMP_MAGIC_LL,
	KUZNETZOV	  =	KUZNETZOV_TCPDUMP_MAGIC,
	BORKMANN	  =	BORKMANN_TCPDUMP_MAGIC,

	DEFAULT_SWAPPED	  =	___constant_swab32(ORIGINAL_TCPDUMP_MAGIC),
	NSEC_SWAPPED	  =	___constant_swab32(NSEC_TCPDUMP_MAGIC),
	DEFAULT_LL_SWAPPED =	___constant_swab32(ORIGINAL_TCPDUMP_MAGIC_LL),
	NSEC_LL_SWAPPED	  =	___constant_swab32(NSEC_TCPDUMP_MAGIC_LL),
	KUZNETZOV_SWAPPED =	___constant_swab32(KUZNETZOV_TCPDUMP_MAGIC),
	BORKMANN_SWAPPED  =	___constant_swab32(BORKMANN_TCPDUMP_MAGIC),
};

enum pcap_ops_groups {
	PCAP_OPS_RW = 0,
	PCAP_OPS_SG,
	PCAP_OPS_MM,
};

enum pcap_mode {
	PCAP_MODE_RD = 0,
	PCAP_MODE_WR,
};

struct pcap_file_ops {
	void (*init_once_pcap)(bool enforce_prio);
	int (*pull_fhdr_pcap)(int fd, uint32_t *magic, uint32_t *linktype);
	int (*push_fhdr_pcap)(int fd, uint32_t magic, uint32_t linktype);
	int (*prepare_access_pcap)(int fd, enum pcap_mode mode, bool jumbo);
	ssize_t (*write_pcap)(int fd, pcap_pkthdr_t *phdr, enum pcap_type type,
			      const uint8_t *packet, size_t len);
	ssize_t (*read_pcap)(int fd, pcap_pkthdr_t *phdr, enum pcap_type type,
			     uint8_t *packet, size_t len);
	void (*prepare_close_pcap)(int fd, enum pcap_mode mode);
	void (*fsync_pcap)(int fd);
};

extern const struct pcap_file_ops pcap_rw_ops __maybe_unused;
extern const struct pcap_file_ops pcap_sg_ops __maybe_unused;
extern const struct pcap_file_ops pcap_mm_ops __maybe_unused;

static inline void sockaddr_to_ll(const struct sockaddr_ll *sll,
				  struct pcap_ll *ll)
{
	ll->pkttype = cpu_to_be16(sll->sll_pkttype);
	ll->hatype = cpu_to_be16(sll->sll_hatype);
	ll->len = cpu_to_be16(sll->sll_halen);
	ll->protocol = sll->sll_protocol; /* already be16 */

	memcpy(ll->addr, sll->sll_addr, sizeof(ll->addr));
}

static inline void ll_to_sockaddr(const struct pcap_ll *ll,
				  struct sockaddr_ll *sll)
{
	sll->sll_pkttype = be16_to_cpu(ll->pkttype);
	sll->sll_hatype = be16_to_cpu(ll->hatype);
	sll->sll_halen = be16_to_cpu(ll->len);
	sll->sll_protocol = ll->protocol; /* stays be16 */

	memcpy(sll->sll_addr, ll->addr, sizeof(ll->addr));
}

static inline uint16_t tp_to_pcap_tsource(uint32_t status)
{
	if (status & TP_STATUS_TS_RAW_HARDWARE)
		return PCAP_TSOURCE_RAW_HARDWARE;
	else if (status & TP_STATUS_TS_SYS_HARDWARE)
		return PCAP_TSOURCE_SYS_HARDWARE;
	else if (status & TP_STATUS_TS_SOFTWARE)
		return PCAP_TSOURCE_SOFTWARE;
	else
		return 0;
}

static inline int pcap_devtype_to_linktype(int dev_type)
{
	switch (dev_type) {
	case ARPHRD_TUNNEL:
	case ARPHRD_TUNNEL6:
	case ARPHRD_LOOPBACK:
	case ARPHRD_SIT:
	case ARPHRD_IPDDP:
	case ARPHRD_IPGRE:
	case ARPHRD_IP6GRE:
	case ARPHRD_ETHER:
		return LINKTYPE_EN10MB;
	case ARPHRD_IEEE80211_RADIOTAP:
		return LINKTYPE_IEEE802_11_RADIOTAP;
	case ARPHRD_IEEE80211_PRISM:
	case ARPHRD_IEEE80211:
		return LINKTYPE_IEEE802_11;
	case ARPHRD_NETLINK:
		return LINKTYPE_NETLINK;
	case ARPHRD_EETHER:
		return LINKTYPE_EN3MB;
	case ARPHRD_AX25:
		return LINKTYPE_AX25;
	case ARPHRD_CHAOS:
		return LINKTYPE_CHAOS;
	case ARPHRD_PRONET:
		return LINKTYPE_PRONET;
	case ARPHRD_IEEE802_TR:
	case ARPHRD_IEEE802:
		return LINKTYPE_IEEE802;
	case ARPHRD_INFINIBAND:
		return LINKTYPE_INFINIBAND;
	case ARPHRD_ATM:
		return LINKTYPE_ATM_CLIP;
	case ARPHRD_DLCI:
		return LINKTYPE_FRELAY;
	case ARPHRD_ARCNET:
		return LINKTYPE_ARCNET_LINUX;
	case ARPHRD_CSLIP:
	case ARPHRD_CSLIP6:
	case ARPHRD_SLIP6:
	case ARPHRD_SLIP:
		return LINKTYPE_SLIP;
	case ARPHRD_PPP:
		return LINKTYPE_PPP;
	case ARPHRD_CAN:
		return LINKTYPE_CAN20B;
	case ARPHRD_ECONET:
		return LINKTYPE_ECONET;
	case ARPHRD_RAWHDLC:
	case ARPHRD_CISCO:
		return LINKTYPE_C_HDLC;
	case ARPHRD_FDDI:
		return LINKTYPE_FDDI;
	case ARPHRD_IEEE802154_MONITOR:
	case ARPHRD_IEEE802154:
		return LINKTYPE_IEEE802_15_4_LINUX;
	case ARPHRD_IRDA:
		return LINKTYPE_LINUX_IRDA;
	default:
		return LINKTYPE_NULL;
	}
}

static inline bool link_has_sll_hdr(uint32_t link_type)
{
	switch (link_type) {
	case LINKTYPE_NETLINK:
	case LINKTYPE_LINUX_SLL:
	case ___constant_swab32(LINKTYPE_NETLINK):
	case ___constant_swab32(LINKTYPE_LINUX_SLL):
		return true;
	default:
		return false;
	}
}

static inline int pcap_dev_to_linktype(const char *ifname)
{
	return pcap_devtype_to_linktype(device_type(ifname));
}

static inline void pcap_check_magic(uint32_t magic)
{
	switch (magic) {

	case ORIGINAL_TCPDUMP_MAGIC:
	case NSEC_TCPDUMP_MAGIC:
	case KUZNETZOV_TCPDUMP_MAGIC:
	case BORKMANN_TCPDUMP_MAGIC:

	case ___constant_swab32(ORIGINAL_TCPDUMP_MAGIC):
	case ___constant_swab32(NSEC_TCPDUMP_MAGIC):
	case ___constant_swab32(KUZNETZOV_TCPDUMP_MAGIC):
	case ___constant_swab32(BORKMANN_TCPDUMP_MAGIC):
		break;

	default:
		panic("This file has an unsupported pcap magic number(0x%x)\n", magic);
	}
}

static inline bool pcap_magic_is_swapped(uint32_t magic)
{
	bool swapped = false;

	switch (magic) {
	case ___constant_swab32(ORIGINAL_TCPDUMP_MAGIC):
	case ___constant_swab32(NSEC_TCPDUMP_MAGIC):
	case ___constant_swab32(KUZNETZOV_TCPDUMP_MAGIC):
	case ___constant_swab32(BORKMANN_TCPDUMP_MAGIC):
		swapped = true;
	}

	return swapped;
}

static inline u32 pcap_get_length(pcap_pkthdr_t *phdr, enum pcap_type type)
{
	switch (type) {
#define CASE_RET_CAPLEN(what, member, swap, extra) \
	case (what): \
		return (swap ? ___constant_swab32(phdr->member.caplen) : \
		        phdr->member.caplen) - extra

	CASE_RET_CAPLEN(DEFAULT, ppo, 0, 0);
	CASE_RET_CAPLEN(NSEC, ppn, 0, 0);
	CASE_RET_CAPLEN(DEFAULT_LL, ppo_ll, 0, sizeof(struct pcap_ll));
	CASE_RET_CAPLEN(NSEC_LL, ppn_ll, 0, sizeof(struct pcap_ll));
	CASE_RET_CAPLEN(KUZNETZOV, ppk, 0, 0);
	CASE_RET_CAPLEN(BORKMANN, ppb, 0, 0);

	CASE_RET_CAPLEN(DEFAULT_SWAPPED, ppo, 1, 0);
	CASE_RET_CAPLEN(NSEC_SWAPPED, ppn, 1, 0);
	CASE_RET_CAPLEN(DEFAULT_LL_SWAPPED, ppo_ll, 1, sizeof(struct pcap_ll));
	CASE_RET_CAPLEN(NSEC_LL_SWAPPED, ppn_ll, 1, sizeof(struct pcap_ll));
	CASE_RET_CAPLEN(KUZNETZOV_SWAPPED, ppk, 1, 0);
	CASE_RET_CAPLEN(BORKMANN_SWAPPED, ppb, 1, 0);

	default:
		bug();
	}
}

static inline void pcap_set_length(pcap_pkthdr_t *phdr, enum pcap_type type, u32 len)
{
	switch (type) {
#define CASE_SET_CAPLEN(what, member, swap) \
	case (what): \
		phdr->member.caplen = (swap ? ___constant_swab32(len) : len); \
		break

	CASE_SET_CAPLEN(DEFAULT, ppo, 0);
	CASE_SET_CAPLEN(NSEC, ppn, 0);
	CASE_SET_CAPLEN(DEFAULT_LL, ppo_ll, 0);
	CASE_SET_CAPLEN(NSEC_LL, ppn_ll, 0);
	CASE_SET_CAPLEN(KUZNETZOV, ppk, 0);
	CASE_SET_CAPLEN(BORKMANN, ppb, 0);

	CASE_SET_CAPLEN(DEFAULT_SWAPPED, ppo, 1);
	CASE_SET_CAPLEN(NSEC_SWAPPED, ppn, 1);
	CASE_SET_CAPLEN(DEFAULT_LL_SWAPPED, ppo_ll, 1);
	CASE_SET_CAPLEN(NSEC_LL_SWAPPED, ppn_ll, 1);
	CASE_SET_CAPLEN(KUZNETZOV_SWAPPED, ppk, 1);
	CASE_SET_CAPLEN(BORKMANN_SWAPPED, ppb, 1);

	default:
		bug();
	}
}

static inline void pcap_get_tstamp(pcap_pkthdr_t *phdr, enum pcap_type type,
				   struct timespec *ts)
{
	switch (type) {
	case DEFAULT:
	case DEFAULT_LL:
		ts->tv_sec = phdr->ppo.ts.tv_sec;
		ts->tv_nsec = phdr->ppo.ts.tv_usec * 1000;
		break;

	case DEFAULT_SWAPPED:
	case DEFAULT_LL_SWAPPED:
		ts->tv_sec = ___constant_swab32(phdr->ppo.ts.tv_sec);
		ts->tv_nsec = ___constant_swab32(phdr->ppo.ts.tv_usec) * 1000;;
		break;

	case NSEC:
	case NSEC_LL:
		ts->tv_sec = phdr->ppn.ts.tv_sec;
		ts->tv_nsec = phdr->ppn.ts.tv_nsec / 1000;
		break;

	case NSEC_SWAPPED:
	case NSEC_LL_SWAPPED:
		ts->tv_sec = ___constant_swab32(phdr->ppn.ts.tv_sec);
		ts->tv_nsec = ___constant_swab32(phdr->ppn.ts.tv_nsec);
		break;

	case KUZNETZOV:
		ts->tv_sec = phdr->ppk.ts.tv_sec;
		ts->tv_nsec = phdr->ppk.ts.tv_usec;
		break;

	case KUZNETZOV_SWAPPED:
		ts->tv_sec = ___constant_swab32(phdr->ppk.ts.tv_sec);
		ts->tv_nsec = ___constant_swab32(phdr->ppk.ts.tv_usec);
		break;

	case BORKMANN:
		ts->tv_sec = phdr->ppb.ts.tv_sec;
		ts->tv_nsec = phdr->ppb.ts.tv_nsec;
		break;

	case BORKMANN_SWAPPED:
		ts->tv_sec = ___constant_swab32(phdr->ppb.ts.tv_sec);
		ts->tv_nsec = ___constant_swab32(phdr->ppb.ts.tv_nsec);
		break;

	default:
		bug();
	}
}

static inline u32 pcap_get_hdr_length(pcap_pkthdr_t *phdr, enum pcap_type type)
{
	switch (type) {
#define CASE_RET_HDRLEN(what, member) \
	case (what): \
		return sizeof(phdr->member)

	CASE_RET_HDRLEN(DEFAULT, ppo);
	CASE_RET_HDRLEN(NSEC, ppn);
	CASE_RET_HDRLEN(DEFAULT_LL, ppo_ll);
	CASE_RET_HDRLEN(NSEC_LL, ppn_ll);
	CASE_RET_HDRLEN(KUZNETZOV, ppk);
	CASE_RET_HDRLEN(BORKMANN, ppb);

	CASE_RET_HDRLEN(DEFAULT_SWAPPED, ppo);
	CASE_RET_HDRLEN(NSEC_SWAPPED, ppn);
	CASE_RET_HDRLEN(DEFAULT_LL_SWAPPED, ppo_ll);
	CASE_RET_HDRLEN(NSEC_LL_SWAPPED, ppn_ll);
	CASE_RET_HDRLEN(KUZNETZOV_SWAPPED, ppk);
	CASE_RET_HDRLEN(BORKMANN_SWAPPED, ppb);

	default:
		bug();
	}
}

static inline u32 pcap_get_total_length(pcap_pkthdr_t *phdr, enum pcap_type type)
{
	return pcap_get_hdr_length(phdr, type) + pcap_get_length(phdr, type);
}

static inline void
__tpacket_hdr_to_pcap_pkthdr(uint32_t sec, uint32_t nsec, uint32_t snaplen,
			     uint32_t len, uint32_t status,
			     struct sockaddr_ll *sll, pcap_pkthdr_t *phdr,
			     enum pcap_type type)
{
	switch (type) {
	case DEFAULT:
	case DEFAULT_LL:
		phdr->ppo.ts.tv_sec = sec;
		phdr->ppo.ts.tv_usec = nsec / 1000;
		phdr->ppo.caplen = snaplen;
		phdr->ppo.len = len;
		if (type == DEFAULT_LL) {
			phdr->ppo.caplen += sizeof(struct pcap_ll);
			phdr->ppo.len += sizeof(struct pcap_ll);
			sockaddr_to_ll(sll, &phdr->ppo_ll.ll);
		}
		break;

	case DEFAULT_SWAPPED:
	case DEFAULT_LL_SWAPPED:
		phdr->ppo.ts.tv_sec = ___constant_swab32(sec);
		phdr->ppo.ts.tv_usec = ___constant_swab32(nsec / 1000);
		phdr->ppo.caplen = ___constant_swab32(snaplen);
		phdr->ppo.len = ___constant_swab32(len);
		if (type == DEFAULT_LL_SWAPPED) {
			phdr->ppo.caplen = ___constant_swab32(snaplen + sizeof(struct pcap_ll));
			phdr->ppo.len = ___constant_swab32(len + sizeof(struct pcap_ll));
			sockaddr_to_ll(sll, &phdr->ppo_ll.ll);
		}
		break;

	case NSEC:
	case NSEC_LL:
		phdr->ppn.ts.tv_sec = sec;
		phdr->ppn.ts.tv_nsec = nsec;
		phdr->ppn.caplen = snaplen;
		phdr->ppn.len = len;
		if (type == NSEC_LL) {
			phdr->ppn.caplen += sizeof(struct pcap_ll);
			phdr->ppn.len += sizeof(struct pcap_ll);
			sockaddr_to_ll(sll, &phdr->ppn_ll.ll);
		}
		break;

	case NSEC_SWAPPED:
	case NSEC_LL_SWAPPED:
		phdr->ppn.ts.tv_sec = ___constant_swab32(sec);
		phdr->ppn.ts.tv_nsec = ___constant_swab32(nsec);
		phdr->ppn.caplen = ___constant_swab32(snaplen);
		phdr->ppn.len = ___constant_swab32(len);
		if (type == NSEC_LL_SWAPPED) {
			phdr->ppn.caplen = ___constant_swab32(snaplen + sizeof(struct pcap_ll));
			phdr->ppn.len = ___constant_swab32(len + sizeof(struct pcap_ll));
			sockaddr_to_ll(sll, &phdr->ppn_ll.ll);
		}
		break;

	case KUZNETZOV:
		phdr->ppk.ts.tv_sec = sec;
		phdr->ppk.ts.tv_usec = nsec / 1000;
		phdr->ppk.caplen = snaplen;
		phdr->ppk.len = len;
		phdr->ppk.ifindex = sll->sll_ifindex;
		phdr->ppk.protocol = sll->sll_protocol;
		phdr->ppk.pkttype = sll->sll_pkttype;
		break;

	case KUZNETZOV_SWAPPED:
		phdr->ppk.ts.tv_sec = ___constant_swab32(sec);
		phdr->ppk.ts.tv_usec = ___constant_swab32(nsec / 1000);
		phdr->ppk.caplen = ___constant_swab32(snaplen);
		phdr->ppk.len = ___constant_swab32(len);
		phdr->ppk.ifindex = ___constant_swab32(sll->sll_ifindex);
		phdr->ppk.protocol = ___constant_swab16(sll->sll_protocol);
		phdr->ppk.pkttype = sll->sll_pkttype;
		break;

	case BORKMANN:
		phdr->ppb.ts.tv_sec = sec;
		phdr->ppb.ts.tv_nsec = nsec;
		phdr->ppb.caplen = snaplen;
		phdr->ppb.len = len;
		phdr->ppb.tsource = tp_to_pcap_tsource(status);
		phdr->ppb.ifindex = (u16) sll->sll_ifindex;
		phdr->ppb.protocol = sll->sll_protocol;
		phdr->ppb.hatype = sll->sll_hatype;
		phdr->ppb.pkttype = sll->sll_pkttype;
		break;

	case BORKMANN_SWAPPED:
		phdr->ppb.ts.tv_sec = ___constant_swab32(sec);
		phdr->ppb.ts.tv_nsec = ___constant_swab32(nsec);
		phdr->ppb.caplen = ___constant_swab32(snaplen);
		phdr->ppb.len = ___constant_swab32(len);
		phdr->ppb.tsource = ___constant_swab16(tp_to_pcap_tsource(status));
		phdr->ppb.ifindex = ___constant_swab16((u16) sll->sll_ifindex);
		phdr->ppb.protocol = ___constant_swab16(sll->sll_protocol);
		phdr->ppb.hatype = sll->sll_hatype;
		phdr->ppb.pkttype = sll->sll_pkttype;
		break;

	default:
		bug();
	}
}

/* We need to do this crap here since member offsets are not interleaved,
 * so hopfully the compiler does his job here. ;-)
 */

static inline void tpacket_hdr_to_pcap_pkthdr(struct tpacket2_hdr *thdr,
					      struct sockaddr_ll *sll,
					      pcap_pkthdr_t *phdr,
					      enum pcap_type type)
{
	__tpacket_hdr_to_pcap_pkthdr(thdr->tp_sec, thdr->tp_nsec,
				     thdr->tp_snaplen, thdr->tp_len,
				     thdr->tp_status, sll, phdr, type);
}

#ifdef HAVE_TPACKET3
static inline void tpacket3_hdr_to_pcap_pkthdr(struct tpacket3_hdr *thdr,
					       struct sockaddr_ll *sll,
					       pcap_pkthdr_t *phdr,
					       enum pcap_type type)
{
	__tpacket_hdr_to_pcap_pkthdr(thdr->tp_sec, thdr->tp_nsec,
				     thdr->tp_snaplen, thdr->tp_len,
				     0, sll, phdr, type);
}
#endif

static inline void pcap_pkthdr_to_tpacket_hdr(pcap_pkthdr_t *phdr,
					      enum pcap_type type,
					      struct tpacket2_hdr *thdr,
					      struct sockaddr_ll *sll)
{
	switch (type) {
	case DEFAULT:
	case DEFAULT_LL:
		thdr->tp_sec = phdr->ppo.ts.tv_sec;
		thdr->tp_nsec = phdr->ppo.ts.tv_usec * 1000;
		thdr->tp_snaplen = phdr->ppo.caplen;
		thdr->tp_len = phdr->ppo.len;
		if (type == DEFAULT_LL) {
			thdr->tp_snaplen -= sizeof(struct pcap_ll);
			thdr->tp_len -= sizeof(struct pcap_ll);
			if (sll)
				ll_to_sockaddr(&phdr->ppo_ll.ll, sll);
		}
		break;

	case DEFAULT_SWAPPED:
	case DEFAULT_LL_SWAPPED:
		thdr->tp_sec = ___constant_swab32(phdr->ppo.ts.tv_sec);
		thdr->tp_nsec = ___constant_swab32(phdr->ppo.ts.tv_usec) * 1000;
		thdr->tp_snaplen = ___constant_swab32(phdr->ppo.caplen);
		thdr->tp_len = ___constant_swab32(phdr->ppo.len);
		if (type == DEFAULT_LL_SWAPPED) {
			thdr->tp_snaplen -= sizeof(struct pcap_ll);
			thdr->tp_len -= sizeof(struct pcap_ll);
			if (sll)
				ll_to_sockaddr(&phdr->ppo_ll.ll, sll);
		}
		break;

	case NSEC:
	case NSEC_LL:
		thdr->tp_sec = phdr->ppn.ts.tv_sec;
		thdr->tp_nsec = phdr->ppn.ts.tv_nsec;
		thdr->tp_snaplen = phdr->ppn.caplen;
		thdr->tp_len = phdr->ppn.len;
		if (type == NSEC_LL) {
			thdr->tp_snaplen -= sizeof(struct pcap_ll);
			thdr->tp_len -= sizeof(struct pcap_ll);
			if (sll)
				ll_to_sockaddr(&phdr->ppn_ll.ll, sll);
		}
		break;

	case NSEC_SWAPPED:
	case NSEC_LL_SWAPPED:
		thdr->tp_sec = ___constant_swab32(phdr->ppn.ts.tv_sec);
		thdr->tp_nsec = ___constant_swab32(phdr->ppn.ts.tv_nsec);
		thdr->tp_snaplen = ___constant_swab32(phdr->ppn.caplen);
		thdr->tp_len = ___constant_swab32(phdr->ppn.len);
		if (type == NSEC_LL_SWAPPED) {
			thdr->tp_snaplen -= sizeof(struct pcap_ll);
			thdr->tp_len -= sizeof(struct pcap_ll);
			if (sll)
				ll_to_sockaddr(&phdr->ppn_ll.ll, sll);
		}
		break;

	case KUZNETZOV:
		thdr->tp_sec = phdr->ppk.ts.tv_sec;
		thdr->tp_nsec = phdr->ppk.ts.tv_usec * 1000;
		thdr->tp_snaplen = phdr->ppk.caplen;
		thdr->tp_len = phdr->ppk.len;
		if (sll) {
			sll->sll_ifindex = phdr->ppk.ifindex;
			sll->sll_protocol = phdr->ppk.protocol;
			sll->sll_pkttype = phdr->ppk.pkttype;
		}
		break;

	case KUZNETZOV_SWAPPED:
		thdr->tp_sec = ___constant_swab32(phdr->ppk.ts.tv_sec);
		thdr->tp_nsec = ___constant_swab32(phdr->ppk.ts.tv_usec) * 1000;
		thdr->tp_snaplen = ___constant_swab32(phdr->ppk.caplen);
		thdr->tp_len = ___constant_swab32(phdr->ppk.len);
		if (sll) {
			sll->sll_ifindex = ___constant_swab32(phdr->ppk.ifindex);
			sll->sll_protocol = ___constant_swab16(phdr->ppk.protocol);
			sll->sll_pkttype = phdr->ppk.pkttype;
		}
		break;

	case BORKMANN:
		thdr->tp_sec = phdr->ppb.ts.tv_sec;
		thdr->tp_nsec = phdr->ppb.ts.tv_nsec;
		thdr->tp_snaplen = phdr->ppb.caplen;
		thdr->tp_len = phdr->ppb.len;
		if (sll) {
			sll->sll_ifindex = phdr->ppb.ifindex;
			sll->sll_protocol = phdr->ppb.protocol;
			sll->sll_hatype = phdr->ppb.hatype;
			sll->sll_pkttype = phdr->ppb.pkttype;
		}
		break;

	case BORKMANN_SWAPPED:
		thdr->tp_sec = ___constant_swab32(phdr->ppb.ts.tv_sec);
		thdr->tp_nsec = ___constant_swab32(phdr->ppb.ts.tv_nsec);
		thdr->tp_snaplen = ___constant_swab32(phdr->ppb.caplen);
		thdr->tp_len = ___constant_swab32(phdr->ppb.len);
		if (sll) {
			sll->sll_ifindex = ___constant_swab16(phdr->ppb.ifindex);
			sll->sll_protocol = ___constant_swab16(phdr->ppb.protocol);
			sll->sll_hatype = phdr->ppb.hatype;
			sll->sll_pkttype = phdr->ppb.pkttype;
		}
		break;

	default:
		bug();
	}
}

#define FEATURE_UNKNOWN		(0 << 0)
#define FEATURE_TIMEVAL_MS	(1 << 0)
#define FEATURE_TIMEVAL_NS	(1 << 1)
#define FEATURE_LEN		(1 << 2)
#define FEATURE_CAPLEN		(1 << 3)
#define FEATURE_IFINDEX		(1 << 4)
#define FEATURE_PROTO		(1 << 5)
#define FEATURE_HATYPE		(1 << 6)
#define FEATURE_PKTTYPE		(1 << 7)
#define FEATURE_TSOURCE		(1 << 8)

struct pcap_magic_type {
	const uint32_t magic;
	const char *desc;
	const uint16_t features;
};

static const struct pcap_magic_type pcap_magic_types[] __maybe_unused = {
	{
		.magic = ORIGINAL_TCPDUMP_MAGIC,
		.desc = "tcpdump-capable pcap",
		.features = FEATURE_TIMEVAL_MS |
			    FEATURE_LEN |
			    FEATURE_CAPLEN,
	}, {
		.magic = NSEC_TCPDUMP_MAGIC,
		.desc = "tcpdump-capable pcap with ns resolution",
		.features = FEATURE_TIMEVAL_NS |
			    FEATURE_LEN |
			    FEATURE_CAPLEN,
	}, {
		.magic = KUZNETZOV_TCPDUMP_MAGIC,
		.desc = "Alexey Kuznetzov's pcap",
		.features = FEATURE_TIMEVAL_MS |
			    FEATURE_LEN |
			    FEATURE_CAPLEN |
			    FEATURE_IFINDEX |
			    FEATURE_PROTO |
			    FEATURE_PKTTYPE,
	}, {
		.magic = BORKMANN_TCPDUMP_MAGIC,
		.desc = "netsniff-ng pcap",
		.features = FEATURE_TIMEVAL_NS |
			    FEATURE_LEN |
			    FEATURE_CAPLEN |
			    FEATURE_TSOURCE |
			    FEATURE_IFINDEX |
			    FEATURE_PROTO |
			    FEATURE_HATYPE |
			    FEATURE_PKTTYPE,
	},
};

static inline void pcap_dump_type_features(void)
{
	size_t i;

	for (i = 0; i < array_size(pcap_magic_types); ++i) {
		printf("%s:\n", pcap_magic_types[i].desc);
		printf("  magic: 0x%x (swapped: 0x%x)\n",
		       pcap_magic_types[i].magic,
		       ___constant_swab32(pcap_magic_types[i].magic));
		printf("  features:\n");

		if (pcap_magic_types[i].features == FEATURE_UNKNOWN) {
			printf("    unknown\n");
			continue;
		}

		if (pcap_magic_types[i].features & FEATURE_TIMEVAL_MS)
			printf("    timeval in us\n");
		if (pcap_magic_types[i].features & FEATURE_TIMEVAL_NS)
			printf("    timeval in ns\n");
		if (pcap_magic_types[i].features & FEATURE_TSOURCE)
			printf("    timestamp source\n");
		if (pcap_magic_types[i].features & FEATURE_LEN)
			printf("    packet length\n");
		if (pcap_magic_types[i].features & FEATURE_CAPLEN)
			printf("    packet cap-length\n");
		if (pcap_magic_types[i].features & FEATURE_IFINDEX)
			printf("    packet ifindex\n");
		if (pcap_magic_types[i].features & FEATURE_PROTO)
			printf("    packet protocol\n");
		if (pcap_magic_types[i].features & FEATURE_HATYPE)
			printf("    hardware type\n");
		if (pcap_magic_types[i].features & FEATURE_PKTTYPE)
			printf("    packet type\n");
	}
}

static const char *pcap_ops_group_to_str[] __maybe_unused = {
	[PCAP_OPS_RW] = "read/write",
	[PCAP_OPS_SG] = "scatter-gather",
	[PCAP_OPS_MM] = "mmap",
};

static const struct pcap_file_ops *pcap_ops[] __maybe_unused = {
	[PCAP_OPS_RW]		=	&pcap_rw_ops,
	[PCAP_OPS_SG]		=	&pcap_sg_ops,
	[PCAP_OPS_MM]		=	&pcap_mm_ops,
};

static inline void pcap_prepare_header(struct pcap_filehdr *hdr, uint32_t magic,
				       uint32_t linktype, int32_t thiszone,
				       uint32_t snaplen)
{
	bool swapped = pcap_magic_is_swapped(magic);

	/* As *_LL types are just internal, we need to remap pcap
	 * magics to actually valid types.
	 */
	switch (magic) {
	case ORIGINAL_TCPDUMP_MAGIC_LL:
		magic = ORIGINAL_TCPDUMP_MAGIC;
		break;
	case NSEC_TCPDUMP_MAGIC_LL:
		magic = NSEC_TCPDUMP_MAGIC;
		break;
	case ___constant_swab32(ORIGINAL_TCPDUMP_MAGIC_LL):
		magic = ___constant_swab32(ORIGINAL_TCPDUMP_MAGIC);
		break;
	case ___constant_swab32(NSEC_TCPDUMP_MAGIC_LL):
		magic = ___constant_swab32(NSEC_TCPDUMP_MAGIC);
		break;
	}

	hdr->magic = magic;
	hdr->version_major = swapped ? ___constant_swab16(PCAP_VERSION_MAJOR) : PCAP_VERSION_MAJOR;
	hdr->version_minor = swapped ? ___constant_swab16(PCAP_VERSION_MINOR) : PCAP_VERSION_MINOR;
	hdr->thiszone = swapped ? (int32_t) ___constant_swab32(thiszone)  : thiszone;
	hdr->sigfigs = 0;
	hdr->snaplen = swapped ? ___constant_swab32(snaplen) : snaplen;
	hdr->linktype = swapped ? ___constant_swab32(linktype) : linktype;
}

static const bool pcap_supported_linktypes[LINKTYPE_MAX] __maybe_unused = {
	/* tunX captures from wireshark/tcpdump, non-portable */
	[101] = true, [102] = true, [103] = true,
	[LINKTYPE_NULL] = true,
	[LINKTYPE_EN10MB] = true,
	[LINKTYPE_EN3MB] = true,
	[LINKTYPE_AX25] = true,
	[LINKTYPE_PRONET] = true,
	[LINKTYPE_CHAOS] = true,
	[LINKTYPE_IEEE802] = true,
	[LINKTYPE_SLIP] = true,
	[LINKTYPE_PPP] = true,
	[LINKTYPE_FDDI] = true,
	[LINKTYPE_ATM_CLIP] = true,
	[LINKTYPE_C_HDLC] = true,
	[LINKTYPE_IEEE802_11] = true,
	[LINKTYPE_IEEE802_11_RADIOTAP] = true,
	[LINKTYPE_FRELAY] = true,
	[LINKTYPE_ECONET] = true,
	[LINKTYPE_ARCNET_LINUX] = true,
	[LINKTYPE_LINUX_IRDA] = true,
	[LINKTYPE_CAN20B] = true,
	[LINKTYPE_IEEE802_15_4_LINUX] = true,
	[LINKTYPE_INFINIBAND] = true,
	[LINKTYPE_NETLINK] = true,
	[LINKTYPE_LINUX_SLL] = true,
};

static inline void pcap_validate_header(struct pcap_filehdr *hdr)
{
	bool good = false;
	uint32_t linktype;

	pcap_check_magic(hdr->magic);

	linktype = pcap_magic_is_swapped(hdr->magic) ? bswap_32(hdr->linktype) : hdr->linktype;
	if (linktype < LINKTYPE_MAX)
		good = pcap_supported_linktypes[linktype];

	if (!good)
		panic("This file has an unsupported pcap link type (%d)!\n", linktype);
	if (unlikely(hdr->version_major != PCAP_VERSION_MAJOR) &&
		     ___constant_swab16(hdr->version_major) != PCAP_VERSION_MAJOR)
		panic("This file has an invalid pcap major version (must be %d)\n", PCAP_VERSION_MAJOR);
	if (unlikely(hdr->version_minor != PCAP_VERSION_MINOR) &&
		     ___constant_swab16(hdr->version_minor) != PCAP_VERSION_MINOR)
		panic("This file has an invalid pcap minor version (must be %d)\n", PCAP_VERSION_MINOR);

	/* Remap to internal *_LL types in case of LINKTYPE_LINUX_SLL. */
	if (linktype == LINKTYPE_LINUX_SLL || linktype == LINKTYPE_NETLINK) {
		switch (hdr->magic) {
		case ORIGINAL_TCPDUMP_MAGIC:
			hdr->magic = ORIGINAL_TCPDUMP_MAGIC_LL;
			break;
		case NSEC_TCPDUMP_MAGIC:
			hdr->magic = NSEC_TCPDUMP_MAGIC_LL;
			break;
		case ___constant_swab32(ORIGINAL_TCPDUMP_MAGIC):
			hdr->magic = ___constant_swab32(ORIGINAL_TCPDUMP_MAGIC_LL);
			break;
		case ___constant_swab32(NSEC_TCPDUMP_MAGIC):
			hdr->magic = ___constant_swab32(NSEC_TCPDUMP_MAGIC_LL);
			break;
		}
	}
}

static int pcap_generic_pull_fhdr(int fd, uint32_t *magic,
				  uint32_t *linktype) __maybe_unused;

static int pcap_generic_pull_fhdr(int fd, uint32_t *magic, uint32_t *linktype)
{
	ssize_t ret;
	struct pcap_filehdr hdr;

	ret = read(fd, &hdr, sizeof(hdr));
	if (unlikely(ret != sizeof(hdr)))
		return -EIO;

	pcap_validate_header(&hdr);

	*magic = hdr.magic;
	*linktype = hdr.linktype;

	return 0;
}

static int pcap_generic_push_fhdr(int fd, uint32_t magic,
				  uint32_t linktype) __maybe_unused;

static int pcap_generic_push_fhdr(int fd, uint32_t magic, uint32_t linktype)
{
	ssize_t ret;
	struct pcap_filehdr hdr;

	memset(&hdr, 0, sizeof(hdr));

	pcap_prepare_header(&hdr, magic, linktype, 0, PCAP_DEFAULT_SNAPSHOT_LEN);

	ret = write_or_die(fd, &hdr, sizeof(hdr));
	if (unlikely(ret != sizeof(hdr)))
		panic("Failed to write pkt file header!\n");

	return 0;
}

#endif /* PCAP_IO_H */
