Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ DayOfTheWeek, Month DD, YYYY / The Tcpdump Group
In "gateway" negate the host(s), but not the protocol.
Reject "gateway" within MPLS, VXLAN or Geneve.
In "net <n> mask <m>" catch ENOMEM for the "m" too.
Implement new "ip flag" and "tcp flag" primitives.
Capture file reading:
Fix misaligned accesses in processing Linux USB captures (issue
#1634, reported by FuzzAnything Organization
Expand Down
114 changes: 114 additions & 0 deletions gencode.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,17 @@ struct addrinfo {
#define MPLS_LABEL_MAX 0xfffffU
#define MPLS_LABEL_SHIFT 12

// Offsets of various protocol header flags.
#define IPV4_FLAGS_OFFSET 6
#define TCP_FLAGS_OFFSET 12

struct proto_flag {
const char *tok;
uint8_t offset;
uint8_t bitmask;
struct block *(*testfunc)(compiler_state_t *, const uint32_t, struct slist *);
};

#ifdef HAVE_OS_PROTO_H
#include "os-proto.h"
#endif
Expand Down Expand Up @@ -785,6 +796,7 @@ static struct block *gen_protochain(compiler_state_t *, bpf_u_int32, int);
static struct block *gen_proto(compiler_state_t *, bpf_u_int32, int);
static struct slist *xfer_to_x(compiler_state_t *, const struct arth *);
static struct slist *xfer_to_a(compiler_state_t *, const struct arth *);
static struct block *gen_flag(compiler_state_t *, const char *, const uint8_t);
static struct block *gen_mac_multicast(compiler_state_t *, int);
static struct block *gen_len(compiler_state_t *, int, int);
static struct block *gen_encap_ll_check(compiler_state_t *cstate);
Expand Down Expand Up @@ -1040,6 +1052,7 @@ tqkw(const unsigned id)
[Q_PROTO] = "proto",
[Q_PROTOCHAIN] = "protochain",
[Q_PORTRANGE] = "portrange",
[Q_FLAG] = "flag",
};
return qual2kw("type", id, tokens, sizeof(tokens) / sizeof(tokens[0]));
}
Expand Down Expand Up @@ -6726,6 +6739,103 @@ gen_proto(compiler_state_t *cstate, bpf_u_int32 v, int proto)
/*NOTREACHED*/
}

static struct block *
gen_ip_flag(compiler_state_t *cstate, const struct proto_flag *f)
{
struct block *ipv4 = gen_proto_abbrev_internal(cstate, Q_IP);
struct block *flagstate = f->testfunc(cstate, f->bitmask,
gen_load_a(cstate, OR_LINKPL, IPV4_FLAGS_OFFSET + f->offset, BPF_B));
return gen_and(ipv4, flagstate);
}

static struct block *
gen_tcp_flag(compiler_state_t *cstate, const struct proto_flag *f)
{
struct slist *s;

/*
* gen_proto() alone would not be sufficient to match correctly: in
* this case the packet must be the first fragment for the offset to
* be in the TCP header.
*/
struct block *b4 = gen_proto_abbrev_internal(cstate, Q_IP);
b4 = gen_and(b4, gen_ip_proto(cstate, IPPROTO_TCP));
b4 = gen_and(b4, gen_ipfrag(cstate));
s = gen_load_a(cstate, OR_TRAN_IPV4, TCP_FLAGS_OFFSET + f->offset, BPF_B);
b4 = gen_and(b4, f->testfunc(cstate, f->bitmask, s));

/*
* gen_proto() would not match correctly because it also matches
* IPPROTO_FRAGMENT, in which case the offset below would not be in
* the TCP header.
*/
struct block *b6 = gen_proto_abbrev_internal(cstate, Q_IPV6);
b6 = gen_and(b6, gen_ip6_proto(cstate, IPPROTO_TCP));
s = gen_load_a(cstate, OR_TRAN_IPV6, TCP_FLAGS_OFFSET + f->offset, BPF_B);
b6 = gen_and(b6, f->testfunc(cstate, f->bitmask, s));

return gen_or(b4, b6);
}

static struct block *
gen_flag(compiler_state_t *cstate, const char *name, const uint8_t proto)
{
// A 2-bit mask in one octet starting at IPV4_FLAGS_OFFSET.
static const struct proto_flag ip_flags[] = {
{"mf-set", 0, 1U << 5, gen_set},
{"mf-cleared", 0, 1U << 5, gen_unset},
{"df-set", 0, 1U << 6, gen_set},
{"df-cleared", 0, 1U << 6, gen_unset},
{NULL, 0, 0, NULL}
};

// A 9-bit mask in 2 octets starting at TCP_FLAGS_OFFSET.
static const struct proto_flag tcp_flags[] = {
{"fin-set", 1, 1U << 0, gen_set},
{"fin-cleared", 1, 1U << 0, gen_unset},
{"syn-set", 1, 1U << 1, gen_set},
{"syn-cleared", 1, 1U << 1, gen_unset},
{"rst-set", 1, 1U << 2, gen_set},
{"rst-cleared", 1, 1U << 2, gen_unset},
{"psh-set", 1, 1U << 3, gen_set},
{"psh-cleared", 1, 1U << 3, gen_unset},
{"ack-set", 1, 1U << 4, gen_set},
{"ack-cleared", 1, 1U << 4, gen_unset},
{"urg-set", 1, 1U << 5, gen_set},
{"urg-cleared", 1, 1U << 5, gen_unset},
{"ece-set", 1, 1U << 6, gen_set},
{"ece-cleared", 1, 1U << 6, gen_unset},
{"cwr-set", 1, 1U << 7, gen_set},
{"cwr-cleared", 1, 1U << 7, gen_unset},
{"ae-set", 0, 1U << 0, gen_set},
{"ae-cleared", 0, 1U << 0, gen_unset},
{NULL, 0, 0, NULL}
};

const struct proto_flag *flag;
struct block *(*genfunc)(compiler_state_t *, const struct proto_flag *);
switch (proto) {
case Q_IP:
flag = ip_flags;
genfunc = gen_ip_flag;
break;
case Q_TCP:
flag = tcp_flags;
genfunc = gen_tcp_flag;
break;
case Q_DEFAULT:
bpf_error(cstate, "'%s' must be proto-qualified", tqkw(Q_FLAG));
default:
bpf_error(cstate, ERRSTR_INVALID_QUAL, pqkw(proto), tqkw(Q_FLAG));
}
while (flag->tok) {
if (! strcmp(name, flag->tok))
return genfunc(cstate, flag);
flag++;
}
bpf_error(cstate, "invalid '%s %s' ID '%s'", pqkw(proto), tqkw(Q_FLAG), name);
}

/*
* Convert a non-numeric name to a port number.
*/
Expand Down Expand Up @@ -7158,6 +7268,10 @@ gen_scode(compiler_state_t *cstate, const char *name, struct qual q)
return gen_protochain(cstate, lookup_proto(cstate, name, q), proto);
#endif /* !defined(NO_PROTOCHAIN) */

case Q_FLAG:
// q.dir == Q_DEFAULT (non-directional in the grammar)
return gen_flag(cstate, name, q.proto);

case Q_UNDEF:
syntax(cstate);
/*NOTREACHED*/
Expand Down
1 change: 1 addition & 0 deletions gencode.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
#define Q_PROTO 5
#define Q_PROTOCHAIN 6
#define Q_PORTRANGE 7
#define Q_FLAG 8

/* Protocol qualifiers. */

Expand Down
2 changes: 2 additions & 0 deletions grammar.y.in
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ DIAG_OFF_BISON_BYACC


%token DST SRC HOST GATEWAY
%token FLAG
%token NET NETMASK PORT PORTRANGE LESS GREATER PROTO PROTOCHAIN CBYTE
%token ARP RARP IP SCTP TCP UDP ICMP IGMP IGRP PIM VRRP CARP
%token ATALK AARP DECNET LAT SCA MOPRC MOPDL
Expand Down Expand Up @@ -618,6 +619,7 @@ aqual: HOST { $$ = Q_HOST; }
;
/* non-directional address type qualifiers */
ndaqual: GATEWAY { $$ = Q_GATEWAY; }
| FLAG { $$ = Q_FLAG; }
;
pname: LINK { $$ = Q_LINK; }
| IP { $$ = Q_IP; }
Expand Down
103 changes: 101 additions & 2 deletions pcap-filter.manmisc.in
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
.\"
.TH PCAP-FILTER @MAN_MISC_INFO@ "19 February 2026"
.TH PCAP-FILTER @MAN_MISC_INFO@ "20 February 2026"
.SH NAME
pcap-filter \- packet filter syntax
.br
Expand Down Expand Up @@ -519,6 +519,75 @@ can be specified in either order. If the two values are equal, this primitive
has the same effect as the
.B port
primitive above.
.IP "\fBip flag \fIflagstate\fR"
True if the packet is an IPv4 packet and the IPv4 header flag (MF or DF) is
set (if
.I flagstate
is one of
.RB { mf\-set ,
.BR df\-set })
or cleared (if
.I flagstate
is one of
.RB { mf\-cleared ,
.BR df\-cleared }).
The correct way to test for a cleared flag is by using the
.B -cleared
suffix; for example,
.in +.5i
.nf
.B ip flag df-cleared
.fi
.in -.5i
correctly does not match packets that are not IPv4 packets, but
.in +.5i
.nf
.B ip flag not df-set
.fi
.in -.5i
does (correctly from the grammar perspective, but usually incorrectly from the
use case perspective) match non-IPv4 packets because it means the same as
.in +.5i
.nf
.B not (ip and ip flag df-set)
.fi
.in -.5i
.IP "\fBtcp flag \fIflagstate\fR"
True if the packet is an IPv4/IPv6 TCP packet and the TCP header flag (FIN,
SYN, RST, PSH, ACK, URG, ECE, CWR or AE) is set (if
.I flagstate
is one of
.RB { fin\-set ,
.BR syn\-set ,
.BR rst\-set ,
.BR psh\-set ,
.BR ack\-set ,
.BR urg\-set ,
.BR ece\-set ,
.BR cwr\-set ,
.BR ae\-set })
or cleared (if
.I flagstate
is one of
.RB { fin\-cleared ,
.BR syn\-cleared ,
.BR rst\-cleared ,
.BR psh\-cleared ,
.BR ack\-cleared ,
.BR urg\-cleared ,
.BR ece\-cleared ,
.BR cwr\-cleared ,
.BR ae\-cleared }).
For IPv4 this also verifies that the datagram is the first fragment or is not
fragmented. For the same reasons as the above, the correct way to test for a
cleared flag is by using the
.B -cleared
suffix, for example:
.in +.5i
.nf
.B tcp flag syn-cleared and ack-cleared
.fi
.in -.5i
.IP "\fBless \fIlength\fR"
True if the packet has a length less than or equal to \fIlength\fP.
This is equivalent to:
Expand Down Expand Up @@ -1688,7 +1757,11 @@ as numeric values.
The following protocol header field offsets are
available: \fBicmptype\fP (ICMP type field), \fBicmp6type\fP (ICMPv6 type field),
\fBicmpcode\fP (ICMP code field), \fBicmp6code\fP (ICMPv6 code field) and
\fBtcpflags\fP (TCP flags field).
\fBtcpflags\fP (TCP flags field). For historic reasons the latter silently
ignores TCP flags that are allocated beyond the traditional 14th byte of the
TCP header, hence the
.B tcp flag
primitive would be a more reliable means to match TCP flags.
.LP
The following ICMP type field values are available:
.BR \%icmp-echoreply ,
Expand Down Expand Up @@ -1742,6 +1815,10 @@ The following TCP flags field values are available: \fBtcp-fin\fP,
\fBtcp-syn\fP, \fBtcp-rst\fP, \fBtcp-push\fP,
\fBtcp-ack\fP, \fBtcp-urg\fP, \fBtcp-ece\fP,
\fBtcp-cwr\fP.
Note that these values typically require a correct application of bitwise
arithmetic expressions to the packet data, unlike the
.B tcp flag
primitive; the latter also supports the AE flag.
.SH COMPOUND EXPRESSIONS
Primitives and relations may be combined using:
.LP
Expand Down Expand Up @@ -1904,6 +1981,14 @@ To select the start and end packets (the SYN and FIN packets) of each
TCP conversation that involves a non-local host.
.RS
.nf
\fBtcp flag syn-set or fin-set and not src and dst net\fP localnet
.fi
.RE
.LP
The same, using arithmetic expressions:
.RS
.nf
.B
\fBtcp[tcpflags] & (tcp-syn|tcp-fin) !=\fP 0 \fBand not src and dst net\fP localnet
.fi
.RE
Expand All @@ -1914,6 +1999,14 @@ is "RST and ACK both set", match)
.RS
.nf
.B
tcp flag rst-set and ack-set
.fi
.RE
.LP
The same, using arithmetic expressions:
.RS
.nf
.B
tcp[tcpflags] & (tcp-rst|tcp-ack) == (tcp-rst|tcp-ack)
.fi
.RE
Expand Down Expand Up @@ -1998,6 +2091,12 @@ The
and
.B slow
protocols became available for "ether proto" in libpcap 1.11.0.
.PP
The
.B ip flag
and
.B tcp flag
primitives became available in libpcap 1.11.0.
.SH SEE ALSO
.BR pcap (3PCAP)
.SH BUGS
Expand Down
1 change: 1 addition & 0 deletions scanner.l
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ port return PORT;
portrange return PORTRANGE;
proto return PROTO;
protochain return PROTOCHAIN;
flag return FLAG;

gateway return GATEWAY;

Expand Down
Loading