X-Git-Url: https://git.tcpdump.org/tcpdump/blobdiff_plain/32e24ead3b2a171e5f5eff50d7231c2514eda9b4..HEAD:/print-pflog.c diff --git a/print-pflog.c b/print-pflog.c index 7cd96b2e..f65ede29 100644 --- a/print-pflog.c +++ b/print-pflog.c @@ -19,92 +19,460 @@ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -/* \summary: OpenBSD packet filter log file printer */ +/* \summary: *BSD/Darwin packet filter log file printer */ -#ifdef HAVE_CONFIG_H #include -#endif -#ifndef HAVE_NET_PFVAR_H -#error "No pf headers available" -#endif -#include -#include -#include -#include -#include +#include #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 -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)" }, +/* + * 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 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_directions[] = { - { PF_INOUT, "in/out" }, - { PF_IN, "in" }, - { PF_OUT, "out" }, +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 } +}; + +/* + * 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) { + uint8_t length; uint32_t rulenr, subrulenr; + uint32_t uid; + uint32_t ridentifier; ndo->ndo_protocol = "pflog"; - rulenr = EXTRACT_BE_U_4(&hdr->rulenr); - subrulenr = EXTRACT_BE_U_4(&hdr->subrulenr); - if (subrulenr == (uint32_t)-1) - ND_PRINT("rule %u/", rulenr); + length = GET_U_1(hdr->length); + + 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("rule %u.%s.%u/", rulenr, hdr->ruleset, subrulenr); + ND_PRINT("%s", tok2str(pf_reasons_other, "unkn(%u)", GET_U_1(hdr->reason))); - ND_PRINT("%s: %s %s on %s: ", - tok2str(pf_reasons, "unkn(%u)", EXTRACT_U_1(&hdr->reason)), - tok2str(pf_actions, "unkn(%u)", EXTRACT_U_1(&hdr->action)), - tok2str(pf_directions, "unkn(%u)", EXTRACT_U_1(&hdr->dir)), - hdr->ifname); + /* + * 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); + } + + 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, const u_char *p) { @@ -114,55 +482,49 @@ pflog_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h, const struct pfloghdr *hdr; uint8_t af; - ndo->ndo_protocol = "pflog_if"; + ndo->ndo_protocol = "pflog"; /* check length */ - if (caplen < sizeof(uint8_t)) { - nd_print_trunc(ndo); - return (caplen); - } + ND_ICHECK_U(length, <, MIN_PFLOG_HDRLEN); -#define MIN_PFLOG_HDRLEN 45 hdr = (const struct pfloghdr *)p; - if (hdr->length < MIN_PFLOG_HDRLEN) { - ND_PRINT("[pflog: invalid header length!]"); - return (hdr->length); /* XXX: not really */ - } - hdrlen = BPF_WORDALIGN(hdr->length); - - if (caplen < hdrlen) { - nd_print_trunc(ndo); - return (hdrlen); /* XXX: true? */ - } + 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 */ - ND_TCHECK_SIZE(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 = EXTRACT_U_1(&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; -#if defined(AF_INET6) || defined(OPENBSD_AF_INET6) -#ifdef AF_INET6 - case AF_INET6: -#endif /* AF_INET6 */ -#if !defined(AF_INET6) || OPENBSD_AF_INET6 != AF_INET6 - case OPENBSD_AF_INET6: /* XXX: read pcap files */ -#endif /* !defined(AF_INET6) || OPENBSD_AF_INET6 != AF_INET6 */ + /* + * 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 /* defined(AF_INET6) || defined(OPENBSD_AF_INET6) */ default: /* address family not handled, print raw packet */ @@ -172,8 +534,8 @@ pflog_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h, ND_DEFAULTPRINT(p, caplen); } - return (hdrlen); -trunc: - nd_print_trunc(ndo); - return (hdrlen); + return; + +invalid: + nd_print_invalid(ndo); }