+
+static errno_t
+bpf_copydata(struct bpf_packet *pkt, size_t off, size_t len, void* out_data)
+{
+ errno_t err = 0;
+ if (pkt->bpfp_type == BPF_PACKET_TYPE_MBUF) {
+ err = mbuf_copydata(pkt->bpfp_mbuf, off, len, out_data);
+ } else {
+ err = EINVAL;
+ }
+
+ return err;
+}
+
+static void
+copy_bpf_packet(struct bpf_packet * pkt, void * dst, size_t len)
+{
+ /* copy the optional header */
+ if (pkt->bpfp_header_length != 0) {
+ size_t count = min(len, pkt->bpfp_header_length);
+ bcopy(pkt->bpfp_header, dst, count);
+ len -= count;
+ dst += count;
+ }
+ if (len == 0) {
+ /* nothing past the header */
+ return;
+ }
+ /* copy the packet */
+ switch (pkt->bpfp_type) {
+ case BPF_PACKET_TYPE_MBUF:
+ bpf_mcopy(pkt->bpfp_mbuf, dst, len);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint16_t
+get_esp_trunc_len(__unused struct bpf_packet *pkt, __unused uint16_t off,
+ const uint16_t remaining_caplen)
+{
+ /*
+ * For some reason tcpdump expects to have one byte beyond the ESP header
+ */
+ uint16_t trunc_len = ESP_HDR_SIZE + 1;
+
+ if (trunc_len > remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return trunc_len;
+}
+
+static uint16_t
+get_isakmp_trunc_len(__unused struct bpf_packet *pkt, __unused uint16_t off,
+ const uint16_t remaining_caplen)
+{
+ /*
+ * Include the payload generic header
+ */
+ uint16_t trunc_len = ISAKMP_HDR_SIZE;
+
+ if (trunc_len > remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return trunc_len;
+}
+
+static uint16_t
+get_isakmp_natt_trunc_len(struct bpf_packet *pkt, uint16_t off,
+ const uint16_t remaining_caplen)
+{
+ int err = 0;
+ uint16_t trunc_len = 0;
+ char payload[remaining_caplen];
+
+ err = bpf_copydata(pkt, off, remaining_caplen, payload);
+ if (err != 0) {
+ return remaining_caplen;
+ }
+ /*
+ * They are three cases:
+ * - IKE: payload start with 4 bytes header set to zero before ISAKMP header
+ * - keep alive: 1 byte payload
+ * - otherwise it's ESP
+ */
+ if (remaining_caplen >= 4 &&
+ payload[0] == 0 && payload[1] == 0 &&
+ payload[2] == 0 && payload[3] == 0) {
+ trunc_len = 4 + get_isakmp_trunc_len(pkt, off + 4, remaining_caplen - 4);
+ } else if (remaining_caplen == 1) {
+ trunc_len = 1;
+ } else {
+ trunc_len = get_esp_trunc_len(pkt, off, remaining_caplen);
+ }
+
+ if (trunc_len > remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return trunc_len;
+}
+
+static uint16_t
+get_udp_trunc_len(struct bpf_packet *pkt, uint16_t off, const uint16_t remaining_caplen)
+{
+ int err = 0;
+ uint16_t trunc_len = sizeof(struct udphdr); /* By default no UDP payload */
+
+ if (trunc_len >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ struct udphdr udphdr;
+ err = bpf_copydata(pkt, off, sizeof(struct udphdr), &udphdr);
+ if (err != 0) {
+ return remaining_caplen;
+ }
+
+ u_short sport, dport;
+
+ sport = EXTRACT_SHORT(&udphdr.uh_sport);
+ dport = EXTRACT_SHORT(&udphdr.uh_dport);
+
+ if (dport == PORT_DNS || sport == PORT_DNS) {
+ /*
+ * Full UDP payload for DNS
+ */
+ trunc_len = remaining_caplen;
+ } else if ((sport == PORT_BOOTPS && dport == PORT_BOOTPC) ||
+ (sport == PORT_BOOTPC && dport == PORT_BOOTPS)) {
+ /*
+ * Full UDP payload for BOOTP and DHCP
+ */
+ trunc_len = remaining_caplen;
+ } else if (dport == PORT_ISAKMP && sport == PORT_ISAKMP) {
+ /*
+ * Return the ISAKMP header
+ */
+ trunc_len += get_isakmp_trunc_len(pkt, off + sizeof(struct udphdr),
+ remaining_caplen - sizeof(struct udphdr));
+ } else if (dport == PORT_ISAKMP_NATT && sport == PORT_ISAKMP_NATT) {
+ trunc_len += get_isakmp_natt_trunc_len(pkt, off + sizeof(struct udphdr),
+ remaining_caplen - sizeof(struct udphdr));
+ }
+ if (trunc_len >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return trunc_len;
+}
+
+static uint16_t
+get_tcp_trunc_len(struct bpf_packet *pkt, uint16_t off, const uint16_t remaining_caplen)
+{
+ int err = 0;
+ uint16_t trunc_len = sizeof(struct tcphdr); /* By default no TCP payload */
+ if (trunc_len >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ struct tcphdr tcphdr;
+ err = bpf_copydata(pkt, off, sizeof(struct tcphdr), &tcphdr);
+ if (err != 0) {
+ return remaining_caplen;
+ }
+
+ u_short sport, dport;
+ sport = EXTRACT_SHORT(&tcphdr.th_sport);
+ dport = EXTRACT_SHORT(&tcphdr.th_dport);
+
+ if (dport == PORT_DNS || sport == PORT_DNS) {
+ /*
+ * Full TCP payload for DNS
+ */
+ trunc_len = remaining_caplen;
+ } else {
+ trunc_len = tcphdr.th_off << 2;
+ }
+ if (trunc_len >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return trunc_len;
+}
+
+static uint16_t
+get_proto_trunc_len(uint8_t proto, struct bpf_packet *pkt, uint16_t off, const uint16_t remaining_caplen)
+{
+ uint16_t trunc_len;
+
+ switch (proto) {
+ case IPPROTO_ICMP: {
+ /*
+ * Full IMCP payload
+ */
+ trunc_len = remaining_caplen;
+ break;
+ }
+ case IPPROTO_ICMPV6: {
+ /*
+ * Full IMCPV6 payload
+ */
+ trunc_len = remaining_caplen;
+ break;
+ }
+ case IPPROTO_IGMP: {
+ /*
+ * Full IGMP payload
+ */
+ trunc_len = remaining_caplen;
+ break;
+ }
+ case IPPROTO_UDP: {
+ trunc_len = get_udp_trunc_len(pkt, off, remaining_caplen);
+ break;
+ }
+ case IPPROTO_TCP: {
+ trunc_len = get_tcp_trunc_len(pkt, off, remaining_caplen);
+ break;
+ }
+ case IPPROTO_ESP: {
+ trunc_len = get_esp_trunc_len(pkt, off, remaining_caplen);
+ break;
+ }
+ default: {
+ /*
+ * By default we only include the IP header
+ */
+ trunc_len = 0;
+ break;
+ }
+ }
+ if (trunc_len >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return trunc_len;
+}
+
+static uint16_t
+get_ip_trunc_len(struct bpf_packet *pkt, uint16_t off, const uint16_t remaining_caplen)
+{
+ int err = 0;
+ uint16_t iplen = sizeof(struct ip);
+ if (iplen >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ struct ip iphdr;
+ err = bpf_copydata(pkt, off, sizeof(struct ip), &iphdr);
+ if (err != 0) {
+ return remaining_caplen;
+ }
+
+ uint8_t proto = 0;
+
+ iplen = iphdr.ip_hl << 2;
+ if (iplen >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ proto = iphdr.ip_p;
+ iplen += get_proto_trunc_len(proto, pkt, off + iplen, remaining_caplen - iplen);
+
+ if (iplen >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return iplen;
+}
+
+static uint16_t
+get_ip6_trunc_len(struct bpf_packet *pkt, uint16_t off, const uint16_t remaining_caplen)
+{
+ int err = 0;
+ uint16_t iplen = sizeof(struct ip6_hdr);
+ if (iplen >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ struct ip6_hdr ip6hdr;
+ err = bpf_copydata(pkt, off, sizeof(struct ip6_hdr), &ip6hdr);
+ if (err != 0) {
+ return remaining_caplen;
+ }
+
+ uint8_t proto = 0;
+
+ /*
+ * TBD: process the extension headers
+ */
+ proto = ip6hdr.ip6_nxt;
+ iplen += get_proto_trunc_len(proto, pkt, off + iplen, remaining_caplen - iplen);
+
+ if (iplen >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ return iplen;
+}
+
+static uint16_t
+get_ether_trunc_len(struct bpf_packet *pkt, int off, const uint16_t remaining_caplen)
+{
+ int err = 0;
+ uint16_t ethlen = sizeof(struct ether_header);
+ if (ethlen >= remaining_caplen) {
+ return remaining_caplen;
+ }
+
+ struct ether_header eh;
+ u_short type;
+ err = bpf_copydata(pkt, off, sizeof(struct ether_header), &eh);
+ if (err != 0) {
+ return remaining_caplen;
+ }
+
+ type = EXTRACT_SHORT(&eh.ether_type);
+ /* Include full ARP */
+ if (type == ETHERTYPE_ARP) {
+ ethlen = remaining_caplen;
+ } else if (type != ETHERTYPE_IP && type != ETHERTYPE_IPV6) {
+ ethlen = min(BPF_MIN_PKT_SIZE, remaining_caplen);
+ } else {
+ if (type == ETHERTYPE_IP) {
+ ethlen += get_ip_trunc_len(pkt, sizeof(struct ether_header),
+ remaining_caplen);
+ } else if (type == ETHERTYPE_IPV6) {
+ ethlen += get_ip6_trunc_len(pkt, sizeof(struct ether_header),
+ remaining_caplen);
+ }
+ }
+ return ethlen;
+}
+
+static uint32_t
+get_pkt_trunc_len(u_char *p, u_int len)
+{
+ struct bpf_packet *pkt = (struct bpf_packet *)(void *) p;
+ struct pktap_header *pktap = (struct pktap_header *) (pkt->bpfp_header);
+ uint32_t out_pkt_len = 0, tlen = 0;
+ /*
+ * pktap->pth_frame_pre_length is L2 header length and accounts
+ * for both pre and pre_adjust.
+ * pktap->pth_length is sizeof(pktap_header) (excl the pre/pre_adjust)
+ * pkt->bpfp_header_length is (pktap->pth_length + pre_adjust)
+ * pre is the offset to the L3 header after the bpfp_header, or length
+ * of L2 header after bpfp_header, if present.
+ */
+ int32_t pre = pktap->pth_frame_pre_length -
+ (pkt->bpfp_header_length - pktap->pth_length);
+
+ /* Length of the input packet starting from L3 header */
+ uint32_t in_pkt_len = len - pkt->bpfp_header_length - pre;
+ if (pktap->pth_protocol_family == AF_INET ||
+ pktap->pth_protocol_family == AF_INET6) {
+ /* Contains L2 header */
+ if (pre > 0) {
+ if (pre < (int32_t)sizeof(struct ether_header)) {
+ goto too_short;
+ }
+
+ out_pkt_len = get_ether_trunc_len(pkt, 0, in_pkt_len);
+ } else if (pre == 0) {
+ if (pktap->pth_protocol_family == AF_INET) {
+ out_pkt_len = get_ip_trunc_len(pkt, pre, in_pkt_len);
+ } else if (pktap->pth_protocol_family == AF_INET6) {
+ out_pkt_len = get_ip6_trunc_len(pkt, pre, in_pkt_len);
+ }
+ } else {
+ /* Ideally pre should be >= 0. This is an exception */
+ out_pkt_len = min(BPF_MIN_PKT_SIZE, in_pkt_len);
+ }
+ } else {
+ if (pktap->pth_iftype == IFT_ETHER) {
+ if (in_pkt_len < sizeof(struct ether_header)) {
+ goto too_short;
+ }
+ /* At most include the Ethernet header and 16 bytes */
+ out_pkt_len = MIN(sizeof(struct ether_header) + 16,
+ in_pkt_len);
+ } else {
+ /*
+ * For unknown protocols include at most 16 bytes
+ */
+ out_pkt_len = MIN(16, in_pkt_len);
+ }
+ }
+done:
+ tlen = pkt->bpfp_header_length + out_pkt_len + pre;
+ return tlen;
+too_short:
+ out_pkt_len = in_pkt_len;
+ goto done;
+}
+