* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
-#define NETDISSECT_REWORKED
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#ifndef HAVE_NET_PFVAR_H
-#error "No pf headers available"
-#endif
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <net/if.h>
-#include <net/pfvar.h>
-#include <net/if_pflog.h>
-
-#include <tcpdump-stdinc.h>
-
-#include "interface.h"
+/* \summary: *BSD/Darwin packet filter log file printer */
+
+#include <config.h>
+
+#include <limits.h>
+
+#include "netdissect-stdinc.h"
+
+#define ND_LONGJMP_FROM_TCHECK
+#include "netdissect.h"
#include "extract.h"
+#include "af.h"
+#include "addrtostr.h"
+
+/*
+ * pflog headers, at least as they exist now.
+ */
+#define PFLOG_IFNAMSIZ 16
+#define PFLOG_RULESET_NAME_SIZE 16
+
+struct pf_addr {
+ union {
+ nd_ipv4 v4;
+ nd_ipv6 v6;
+ } pfa; /* 128-bit address */
+#define v4 pfa.v4
+#define v6 pfa.v6
+};
+
+/*
+ * This header is:
+ *
+ * 61 bytes long on NetBSD, DragonFly BSD. and Darwin;
+ * 84 bytes lon on OpenBSD;
+ * 72 bytes long on FreeBSD;
+ *
+ * which, unfortunately, does not allow us to distinguish, based on
+ * the header length, between the three OSes listed as having 61-byte
+ * headers. As the action values differ between them, this makes it
+ * impossible to correctly dissect the reason values that differ
+ * between NetBSD and Darwin (reason value 15) without having some
+ * way to explicitly tell tcpdump what to do.
+ *
+ * (We could, I guess, label reason value 15 as
+ * "state-locked (NetBSD)/dummynet (macOS etc.)" or something such as
+ * that.)
+ */
+struct pfloghdr {
+ nd_uint8_t length;
+ nd_uint8_t af;
+ nd_uint8_t action;
+ nd_uint8_t reason;
+ char ifname[PFLOG_IFNAMSIZ];
+ char ruleset[PFLOG_RULESET_NAME_SIZE];
+ nd_uint32_t rulenr;
+ nd_uint32_t subrulenr;
+ nd_uint32_t uid;
+ nd_int32_t pid;
+ nd_uint32_t rule_uid;
+ nd_int32_t rule_pid;
+ nd_uint8_t dir;
+ union {
+ struct pflog_openbsd_only {
+ nd_uint8_t rewritten;
+ nd_uint8_t naf;
+ nd_uint8_t pad[1];
+ struct pf_addr saddr;
+ struct pf_addr daddr;
+ nd_uint16_t sport;
+ nd_uint16_t dport;
+ } openbsd;
+ struct pflog_freebsd_only {
+ nd_uint8_t pad[3];
+ nd_uint32_t ridentifier;
+ nd_uint8_t reserve;
+ } freebsd;
+ } u;
+};
+
+/*
+ * FreeBSD header length.
+ */
+#define PFLOG_HEADER_LEN_FREEBSD 69
+
+/*
+ * OpenBSD header length.
+ */
+#define PFLOG_HEADER_LEN_OPENBSD 100
+
+/*
+ * DragonFly BSD, NetBSD and Darwin header length.
+ * Older versions of FreeBSD and OpenBSD may have used this
+ * as well.
+ *
+ * Unfortunately, this means we can't distinguish between Darwin, NetBSD,
+ * and DragonFly BSD based on the header length.
+ */
+#define PFLOG_HEADER_LEN_OTHER 61
+
+/*
+ * These are the minimum and maximum pflog header lengths.
+ */
+#define MIN_PFLOG_HDRLEN 61
+#define MAX_PFLOG_HDRLEN 100
+
+/*
+ * Reason values.
+ */
+#define PFRES_MATCH 0
+#define PFRES_BADOFF 1
+#define PFRES_FRAG 2
+#define PFRES_SHORT 3
+#define PFRES_NORM 4
+#define PFRES_MEMORY 5
+#define PFRES_TS 6
+#define PFRES_CONGEST 7
+#define PFRES_IPOPTIONS 8
+#define PFRES_PROTCKSUM 9
+#define PFRES_BADSTATE 10
+#define PFRES_STATEINS 11
+#define PFRES_MAXSTATES 12
+#define PFRES_SRCLIMIT 13
+#define PFRES_SYNPROXY 14
+
+/* FreeBSD */
+#define PFRES_MAPFAILED 15
+
+/* OpenBSD */
+#define PFRES_TRANSLATE 15
+#define PFRES_NOROUTE 16
+
+/* NetBSD/Darwin */
+#define PFRES_STATELOCKED_DUMMYNET 15 /* STATELOCKED on NetBSD, DUMMYNET on Darwin */
+#define PFRES_INVPORT 16 /* INVPORT on Darwin */
-static const char tstr[] = "[|pflog]";
-
-static const struct tok pf_reasons[] = {
- { 0, "0(match)" },
- { 1, "1(bad-offset)" },
- { 2, "2(fragment)" },
- { 3, "3(short)" },
- { 4, "4(normalize)" },
- { 5, "5(memory)" },
- { 6, "6(bad-timestamp)" },
- { 7, "7(congestion)" },
- { 8, "8(ip-option)" },
- { 9, "9(proto-cksum)" },
- { 10, "10(state-mismatch)" },
- { 11, "11(state-insert)" },
- { 12, "12(state-limit)" },
- { 13, "13(src-limit)" },
- { 14, "14(synproxy)" },
+static const struct tok pf_reasons_freebsd[] = {
+ { PFRES_MATCH, "0(match)" },
+ { PFRES_BADOFF, "1(bad-offset)" },
+ { PFRES_FRAG, "2(fragment)" },
+ { PFRES_SHORT, "3(short)" },
+ { PFRES_NORM, "4(normalize)" },
+ { PFRES_MEMORY, "5(memory)" },
+ { PFRES_TS, "6(bad-timestamp)" },
+ { PFRES_CONGEST, "7(congestion)" },
+ { PFRES_IPOPTIONS, "8(ip-option)" },
+ { PFRES_PROTCKSUM, "9(proto-cksum)" },
+ { PFRES_BADSTATE, "10(state-mismatch)" },
+ { PFRES_STATEINS, "11(state-insert)" },
+ { PFRES_MAXSTATES, "12(state-limit)" },
+ { PFRES_SRCLIMIT, "13(src-limit)" },
+ { PFRES_SYNPROXY, "14(synproxy)" },
+ { PFRES_MAPFAILED, "15(map-failed)" },
{ 0, NULL }
};
-static const struct tok pf_actions[] = {
- { PF_PASS, "pass" },
- { PF_DROP, "block" },
- { PF_SCRUB, "scrub" },
- { PF_NAT, "nat" },
- { PF_NONAT, "nat" },
- { PF_BINAT, "binat" },
- { PF_NOBINAT, "binat" },
- { PF_RDR, "rdr" },
- { PF_NORDR, "rdr" },
- { PF_SYNPROXY_DROP, "synproxy-drop" },
+static const struct tok pf_reasons_openbsd[] = {
+ { PFRES_MATCH, "0(match)" },
+ { PFRES_BADOFF, "1(bad-offset)" },
+ { PFRES_FRAG, "2(fragment)" },
+ { PFRES_SHORT, "3(short)" },
+ { PFRES_NORM, "4(normalize)" },
+ { PFRES_MEMORY, "5(memory)" },
+ { PFRES_TS, "6(bad-timestamp)" },
+ { PFRES_CONGEST, "7(congestion)" },
+ { PFRES_IPOPTIONS, "8(ip-option)" },
+ { PFRES_PROTCKSUM, "9(proto-cksum)" },
+ { PFRES_BADSTATE, "10(state-mismatch)" },
+ { PFRES_STATEINS, "11(state-insert)" },
+ { PFRES_MAXSTATES, "12(state-limit)" },
+ { PFRES_SRCLIMIT, "13(src-limit)" },
+ { PFRES_SYNPROXY, "14(synproxy)" },
+ { PFRES_TRANSLATE, "15(translate)" },
+ { PFRES_NOROUTE, "16(no-route)" },
+ { 0, NULL }
+};
+
+static const struct tok pf_reasons_other[] = {
+ { PFRES_MATCH, "0(match)" },
+ { PFRES_BADOFF, "1(bad-offset)" },
+ { PFRES_FRAG, "2(fragment)" },
+ { PFRES_SHORT, "3(short)" },
+ { PFRES_NORM, "4(normalize)" },
+ { PFRES_MEMORY, "5(memory)" },
+ { PFRES_TS, "6(bad-timestamp)" },
+ { PFRES_CONGEST, "7(congestion)" },
+ { PFRES_IPOPTIONS, "8(ip-option)" },
+ { PFRES_PROTCKSUM, "9(proto-cksum)" },
+ { PFRES_BADSTATE, "10(state-mismatch)" },
+ { PFRES_STATEINS, "11(state-insert)" },
+ { PFRES_MAXSTATES, "12(state-limit)" },
+ { PFRES_SRCLIMIT, "13(src-limit)" },
+ { PFRES_SYNPROXY, "14(synproxy)" },
+ { PFRES_STATELOCKED_DUMMYNET,
+ "15(state-locked (NetBSD)/dummynet(Darwin)" },
+ { PFRES_INVPORT, "16(invalid-port (Darwin))" },
+ { 0, NULL }
+};
+
+/*
+ * Action values.
+ */
+#define PFACT_PASS 0
+#define PFACT_DROP 1
+#define PFACT_SCRUB 2
+#define PFACT_NOSCRUB 3
+#define PFACT_NAT 4
+#define PFACT_NONAT 5
+#define PFACT_BINAT 6
+#define PFACT_NOBINAT 7
+#define PFACT_RDR 8
+#define PFACT_NORDR 9
+#define PFACT_SYNPROXY_DROP 10
+
+/* FreeBSD and OpenBSD */
+#define PFACT_DEFER 11
+
+/* FreeBSD */
+#define PFACT_MATCH 12
+
+/* OpenBSD */
+#define PFACT_MATCH 12
+#define PFACT_DIVERT 13
+#define PFACT_RT 14
+#define PFACT_AFRT 15
+
+/* Darwin */
+#define PFACT_DUMMYNET 11
+#define PFACT_NODUMMYNET 12
+#define PFACT_NAT64 13
+#define PFACT_NONAT64 14
+
+static const struct tok pf_actions_freebsd[] = {
+ { PFACT_PASS, "pass" },
+ { PFACT_DROP, "block" },
+ { PFACT_SCRUB, "scrub" },
+ { PFACT_NOSCRUB, "noscrub" },
+ { PFACT_NAT, "nat" },
+ { PFACT_NONAT, "nonat" },
+ { PFACT_BINAT, "binat" },
+ { PFACT_NOBINAT, "nobinat" },
+ { PFACT_RDR, "rdr" },
+ { PFACT_NORDR, "nordr" },
+ { PFACT_SYNPROXY_DROP, "synproxy-drop" },
+ { PFACT_DEFER, "defer" },
+ { PFACT_MATCH, "match" },
+ { 0, NULL }
+};
+
+static const struct tok pf_actions_openbsd[] = {
+ { PFACT_PASS, "pass" },
+ { PFACT_DROP, "block" },
+ { PFACT_SCRUB, "scrub" },
+ { PFACT_NOSCRUB, "noscrub" },
+ { PFACT_NAT, "nat" },
+ { PFACT_NONAT, "nonat" },
+ { PFACT_BINAT, "binat" },
+ { PFACT_NOBINAT, "nobinat" },
+ { PFACT_RDR, "rdr" },
+ { PFACT_NORDR, "nordr" },
+ { PFACT_SYNPROXY_DROP, "synproxy-drop" },
+ { PFACT_DEFER, "defer" },
+ { PFACT_MATCH, "match" },
+ { PFACT_DIVERT, "divert" },
+ { PFACT_RT, "rt" },
+ { PFACT_AFRT, "afrt" },
+ { 0, NULL }
+};
+
+static const struct tok pf_actions_darwin[] = {
+ { PFACT_PASS, "pass" },
+ { PFACT_DROP, "block" },
+ { PFACT_SCRUB, "scrub" },
+ { PFACT_NOSCRUB, "noscrub" },
+ { PFACT_NAT, "nat" },
+ { PFACT_NONAT, "nonat" },
+ { PFACT_BINAT, "binat" },
+ { PFACT_NOBINAT, "nobinat" },
+ { PFACT_RDR, "rdr" },
+ { PFACT_NORDR, "nordr" },
+ { PFACT_SYNPROXY_DROP, "synproxy-drop" },
+ { PFACT_DUMMYNET, "dummynet (Darwin)" },
+ { PFACT_NODUMMYNET, "nodummynet (Darwin)" },
+ { PFACT_NAT64, "nat64 (Darwin)" },
+ { PFACT_NONAT64, "nonat64 (Darwin)" },
{ 0, NULL }
};
-static const struct tok pf_directions[] = {
- { PF_INOUT, "in/out" },
- { PF_IN, "in" },
- { PF_OUT, "out" },
+/*
+ * Direction values.
+ */
+#define PFDIR_INOUT 0
+#define PFDIR_IN 1
+#define PFDIR_OUT 2
+
+/* OpenBSD */
+#define PFDIR_FWD 3
+
+static const struct tok pf_directions_freebsd[] = {
+ { PFDIR_INOUT, "in/out" },
+ { PFDIR_IN, "in" },
+ { PFDIR_OUT, "out" },
{ 0, NULL }
};
-/* For reading capture files on other systems */
-#define OPENBSD_AF_INET 2
-#define OPENBSD_AF_INET6 24
+static const struct tok pf_directions_openbsd[] = {
+ { PFDIR_INOUT, "in/out" },
+ { PFDIR_IN, "in" },
+ { PFDIR_OUT, "out" },
+ { PFDIR_FWD, "fwd" },
+ { 0, NULL }
+};
+
+static const struct tok pf_directions_other[] = {
+ { PFDIR_INOUT, "in/out" },
+ { PFDIR_IN, "in" },
+ { PFDIR_OUT, "out" },
+ { 0, NULL }
+};
+
+static void
+print_pf_addr(netdissect_options *ndo, const char *tag, u_int naf,
+ const struct pf_addr *addr, const nd_uint16_t port)
+{
+ char buf[INET6_ADDRSTRLEN];
+ uint16_t portnum;
+
+ ND_PRINT("%s ", tag);
+ ND_TCHECK_SIZE(addr);
+ switch (naf) {
+
+ case BSD_AF_INET:
+ addrtostr(addr->v4, buf, sizeof(buf));
+ break;
+
+ case BSD_AF_INET6_BSD:
+ addrtostr6(addr->v6, buf, sizeof(buf));
+ break;
+
+ default:
+ strlcpy(buf, "?", sizeof(buf));
+ break;
+ }
+ ND_PRINT("%s:", buf);
+ portnum = GET_BE_U_2(port);
+ ND_PRINT("%u", portnum);
+}
static void
pflog_print(netdissect_options *ndo, const struct pfloghdr *hdr)
{
- u_int32_t rulenr, subrulenr;
+ uint8_t length;
+ uint32_t rulenr, subrulenr;
+ uint32_t uid;
+ uint32_t ridentifier;
+
+ ndo->ndo_protocol = "pflog";
+ length = GET_U_1(hdr->length);
- rulenr = EXTRACT_32BITS(&hdr->rulenr);
- subrulenr = EXTRACT_32BITS(&hdr->subrulenr);
- if (subrulenr == (u_int32_t)-1)
- ND_PRINT((ndo, "rule %u/", rulenr));
+ rulenr = GET_BE_U_4(hdr->rulenr);
+ subrulenr = GET_BE_U_4(hdr->subrulenr);
+ ND_PRINT("rule ");
+ if (rulenr != (uint32_t)-1) {
+ ND_PRINT("%u", rulenr);
+ if (hdr->ruleset[0] != '\0') {
+ ND_PRINT(".");
+ nd_printjnp(ndo, (const u_char*)hdr->ruleset, PFLOG_RULESET_NAME_SIZE);
+ }
+ if (subrulenr != (uint32_t)-1)
+ ND_PRINT(".%u", subrulenr);
+ }
+ ND_PRINT("/");
+
+ if (length == PFLOG_HEADER_LEN_FREEBSD)
+ ND_PRINT("%s", tok2str(pf_reasons_freebsd, "unkn(%u)", GET_U_1(hdr->reason)));
+ else if (length == PFLOG_HEADER_LEN_OPENBSD)
+ ND_PRINT("%s", tok2str(pf_reasons_openbsd, "unkn(%u)", GET_U_1(hdr->reason)));
else
- ND_PRINT((ndo, "rule %u.%s.%u/", rulenr, hdr->ruleset, subrulenr));
+ ND_PRINT("%s", tok2str(pf_reasons_other, "unkn(%u)", GET_U_1(hdr->reason)));
+
+ /*
+ * In Darwin (macOS, etc.) and NetBSD, uid is set to
+ * UID_MAX if there's no UID, and UID_MAX is 2^31-1.
+ * UID_MAX is 2^31-1.
+ *
+ * In OpenBSD, uid is set to -1 if there's no UID, which
+ * means we'll see it as UINT_MAX, as we treat it as
+ * unsigned. UID_MAX is 2^32-1.
+ *
+ * In FreeBSD and DragonFly BSD, uid is set to UID_MAX
+ * if there's no UID. UID_MAX is 2^32-1.
+ *
+ * So:
+ *
+ * For OpenBSD and FreeBSD, check only for 2^32-1 (0xFFFFFFFFU)
+ * if there's no UID.
+ *
+ * For other OSes, it's either NetBSD, DragonFly BSD, or Darwin,
+ * check for both 2^31-1 (0x7FFFFFFFU) (NetBSD and Darwin) and
+ * 2^32-1 (0xFFFFFFFFU) (DragonFly BSD). That runs the risk of
+ * the UID not being printed for a DragonFly BSD log if it's
+ * 0x7FFFFFFF, but that's *probably* not going to be the case.
+ */
+ uid = GET_BE_U_4(hdr->uid);
+ if (length == PFLOG_HEADER_LEN_FREEBSD ||
+ length == PFLOG_HEADER_LEN_OPENBSD) {
+ if (uid != 0xFFFFFFFFU)
+ ND_PRINT(" [uid %u]", uid);
+ } else {
+ if (uid != 0xFFFFFFFFU && uid != 0x7FFFFFFFU)
+ ND_PRINT(" [uid %u]", uid);
+ }
+
+ if (length == PFLOG_HEADER_LEN_FREEBSD) {
+ ridentifier = GET_BE_U_4(hdr->u.freebsd.ridentifier);
+ if (ridentifier != 0)
+ ND_PRINT(" [ridentifier %u]", ridentifier);
+ }
- ND_PRINT((ndo, "%s: %s %s on %s: ",
- tok2str(pf_reasons, "unkn(%u)", hdr->reason),
- tok2str(pf_actions, "unkn(%u)", hdr->action),
- tok2str(pf_directions, "unkn(%u)", hdr->dir),
- hdr->ifname));
+ if (length == PFLOG_HEADER_LEN_FREEBSD) {
+ ND_PRINT(": %s %s on ",
+ tok2str(pf_actions_freebsd, "unkn(%u)", GET_U_1(hdr->action)),
+ tok2str(pf_directions_freebsd, "unkn(%u)", GET_U_1(hdr->dir)));
+ } else if (length == PFLOG_HEADER_LEN_OPENBSD) {
+ ND_PRINT(": %s %s on ",
+ tok2str(pf_actions_openbsd, "unkn(%u)", GET_U_1(hdr->action)),
+ tok2str(pf_directions_openbsd, "unkn(%u)", GET_U_1(hdr->dir)));
+ } else {
+ /*
+ * We use the Darwin set of actions, as it's a superset
+ * of the NetBSD/DragonFly BSD set of actions.
+ */
+ ND_PRINT(": %s %s on ",
+ tok2str(pf_actions_darwin, "unkn(%u)", GET_U_1(hdr->action)),
+ tok2str(pf_directions_other, "unkn(%u)", GET_U_1(hdr->dir)));
+ }
+ nd_printjnp(ndo, (const u_char*)hdr->ifname, PFLOG_IFNAMSIZ);
+ ND_PRINT(": ");
+ if (length == PFLOG_HEADER_LEN_OPENBSD) {
+ if (ndo->ndo_vflag && GET_U_1(hdr->u.openbsd.rewritten)) {
+ uint8_t naf;
+
+ ND_PRINT("[rewritten: ");
+ naf = GET_U_1(hdr->u.openbsd.naf);
+ print_pf_addr(ndo, "src", naf, &hdr->u.openbsd.saddr,
+ hdr->u.openbsd.sport);
+ ND_PRINT(", ");
+ print_pf_addr(ndo, "src", naf, &hdr->u.openbsd.daddr,
+ hdr->u.openbsd.dport);
+ ND_PRINT("; ");
+ }
+ }
}
-u_int
+void
pflog_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h,
- register const u_char *p)
+ const u_char *p)
{
u_int length = h->len;
u_int hdrlen;
u_int caplen = h->caplen;
const struct pfloghdr *hdr;
- u_int8_t af;
+ uint8_t af;
+ ndo->ndo_protocol = "pflog";
/* check length */
- if (caplen < sizeof(u_int8_t)) {
- ND_PRINT((ndo, "%s", tstr));
- return (caplen);
- }
+ ND_ICHECK_U(length, <, MIN_PFLOG_HDRLEN);
-#define MIN_PFLOG_HDRLEN 45
- hdr = (struct pfloghdr *)p;
- if (hdr->length < MIN_PFLOG_HDRLEN) {
- ND_PRINT((ndo, "[pflog: invalid header length!]"));
- return (hdr->length); /* XXX: not really */
- }
- hdrlen = BPF_WORDALIGN(hdr->length);
-
- if (caplen < hdrlen) {
- ND_PRINT((ndo, "%s", tstr));
- return (hdrlen); /* XXX: true? */
- }
+ hdr = (const struct pfloghdr *)p;
+ hdrlen = GET_U_1(hdr->length);
+ ND_ICHECK_U(hdrlen, <, MIN_PFLOG_HDRLEN);
+ ND_ICHECK_U(hdrlen, >, MAX_PFLOG_HDRLEN);
+ hdrlen = roundup2(hdrlen, 4);
/* print what we know */
- hdr = (struct pfloghdr *)p;
- ND_TCHECK(*hdr);
+ ND_TCHECK_LEN(hdr, hdrlen);
+ ndo->ndo_ll_hdr_len += hdrlen;
if (ndo->ndo_eflag)
pflog_print(ndo, hdr);
/* skip to the real packet */
- af = hdr->af;
+ af = GET_U_1(hdr->af);
length -= hdrlen;
caplen -= hdrlen;
p += hdrlen;
switch (af) {
- case AF_INET:
-#if OPENBSD_AF_INET != AF_INET
- case OPENBSD_AF_INET: /* XXX: read pcap files */
-#endif
+ /*
+ * If there's a system that doesn't use the AF_INET
+ * from 4.2BSD, feel free to add its value to af.h
+ * and use it here.
+ *
+ * Hopefully, there isn't.
+ */
+ case BSD_AF_INET:
ip_print(ndo, p, length);
break;
-#ifdef INET6
- case AF_INET6:
-#if OPENBSD_AF_INET6 != AF_INET6
- case OPENBSD_AF_INET6: /* XXX: read pcap files */
-#endif
+ /*
+ * Try all AF_INET6 values for all systems with pflog,
+ * including Darwin.
+ */
+ case BSD_AF_INET6_BSD:
+ case BSD_AF_INET6_FREEBSD:
+ case BSD_AF_INET6_DARWIN:
ip6_print(ndo, p, length);
break;
-#endif
default:
/* address family not handled, print raw packet */
if (!ndo->ndo_eflag)
pflog_print(ndo, hdr);
if (!ndo->ndo_suppress_default_print)
- ndo->ndo_default_print(ndo, p, caplen);
+ ND_DEFAULTPRINT(p, caplen);
}
- return (hdrlen);
-trunc:
- ND_PRINT((ndo, "%s", tstr));
- return (hdrlen);
-}
+ return;
-/*
- * Local Variables:
- * c-style: whitesmith
- * c-basic-offset: 8
- * End:
- */
+invalid:
+ nd_print_invalid(ndo);
+}