ARCnet address parsing works incorrectly:
$ filtertest ARCNET 'link src $42'
(000) ldb [0]
(001) jeq #0xd4 jt 2 jf 3
(002) ret #262144
(003) ret #0
Before commit
bcbef22 in October 2018 the lexer used pcap_ether_aton()
directly and in the case of an AID token pointed the function argument
one character past the beginning of the string to skip the initial "$".
In the example above the function would parse "42" into the first byte
of the MAC address (0x42) and return. Then the lexer passed the token
with a partially initialized MAC address to the parser, which passed the
MAC address to gen_acode(), which passed it to gen_ahostop(), which
correctly used the initialized byte only.
Since the commit gen_acode() gets the original string and passes it to
pcap_ether_aton(), which in the example above initializes the first MAC
address nibble to 0xd, the second to 0x4 and the third (not shown) to
0x2.
Refactor this code to fix the bug and to fail more reliably. Implement
a new pcapint_atoan() function to parse ARCnet addresses only and to
indicate whether the input string is valid. In gen_acode() use the new
function instead of pcap_ether_aton() and check the return value before
using the result. Ibid., simplify memory management by using a local
variable instead of the compiler state for the temporary parsed address.
In gen_ahostop() change the address size to 8 bits to have the solution
space match the problem space better. Now it works as expected:
$ filtertest ARCNET 'link src $42'
(000) ldb [0]
(001) jeq #0x42 jt 2 jf 3
(002) ret #262144
(003) ret #0
Add accept/apply/reject filter tests to cover the updated implementation
and document ARCnet syntax in pcap-filter(7). The
bacnet-arcnet-linux.pcap file is from the Wireshark packet capture
collection.
static struct block *gen_hostop6(compiler_state_t *, struct in6_addr *,
struct in6_addr *, int, bpf_u_int32, u_int, u_int);
#endif
-static struct block *gen_ahostop(compiler_state_t *, const u_char *, int);
+static struct block *gen_ahostop(compiler_state_t *, const uint8_t, int);
static struct block *gen_ehostop(compiler_state_t *, const u_char *, int);
static struct block *gen_fhostop(compiler_state_t *, const u_char *, int);
static struct block *gen_thostop(compiler_state_t *, const u_char *, int);
return b;
}
-static const u_char abroadcast[] = { 0x0 };
-
struct block *
gen_broadcast(compiler_state_t *cstate, int proto)
{
switch (cstate->linktype) {
case DLT_ARCNET:
case DLT_ARCNET_LINUX:
- return gen_ahostop(cstate, abroadcast, Q_DST);
+ // ARCnet broadcast is [8-bit] destination address 0.
+ return gen_ahostop(cstate, 0, Q_DST);
case DLT_EN10MB:
case DLT_NETANALYZER:
case DLT_NETANALYZER_TRANSPARENT:
switch (cstate->linktype) {
case DLT_ARCNET:
case DLT_ARCNET_LINUX:
- /* all ARCnet multicasts use the same address */
- return gen_ahostop(cstate, abroadcast, Q_DST);
+ // ARCnet multicast is the same as broadcast.
+ return gen_ahostop(cstate, 0, Q_DST);
case DLT_EN10MB:
case DLT_NETANALYZER:
case DLT_NETANALYZER_TRANSPARENT:
return (b0);
}
+// Process an ARCnet host address string.
struct block *
gen_acode(compiler_state_t *cstate, const char *s, struct qual q)
{
- struct block *b;
-
/*
* Catch errors reported by us and routines below us, and return NULL
* on an error.
case DLT_ARCNET_LINUX:
if ((q.addr == Q_HOST || q.addr == Q_DEFAULT) &&
q.proto == Q_LINK) {
- cstate->e = pcap_ether_aton(s);
- if (cstate->e == NULL)
- bpf_error(cstate, "malloc");
- b = gen_ahostop(cstate, cstate->e, (int)q.dir);
- free(cstate->e);
- cstate->e = NULL;
- return (b);
+ uint8_t addr;
+ /*
+ * The lexer currently defines the address format in a
+ * way that makes this error condition never true.
+ * Let's check it anyway in case this part of the lexer
+ * changes in future.
+ */
+ if (! pcapint_atoan(s, &addr))
+ bpf_error(cstate, "invalid ARCnet address '%s'", s);
+ return gen_ahostop(cstate, addr, (int)q.dir);
} else
bpf_error(cstate, "ARCnet address used in non-arc expression");
/*NOTREACHED*/
}
}
+// Compare an ARCnet host address with the given value.
static struct block *
-gen_ahostop(compiler_state_t *cstate, const u_char *eaddr, int dir)
+gen_ahostop(compiler_state_t *cstate, const uint8_t eaddr, int dir)
{
register struct block *b0, *b1;
switch (dir) {
- /* src comes first, different from Ethernet */
+ /*
+ * ARCnet is different from Ethernet: the source address comes before
+ * the destination address, each is one byte long. This holds for all
+ * three "buffer formats" in RFC 1201 Section 2.1, see also page 4-10
+ * in the 1983 edition of the "ARCNET Designer's Handbook" published
+ * by Datapoint (document number 61610-01).
+ */
case Q_SRC:
- return gen_bcmp(cstate, OR_LINKHDR, 0, 1, eaddr);
+ return gen_cmp(cstate, OR_LINKHDR, 0, BPF_B, eaddr);
case Q_DST:
- return gen_bcmp(cstate, OR_LINKHDR, 1, 1, eaddr);
+ return gen_cmp(cstate, OR_LINKHDR, 1, BPF_B, eaddr);
case Q_AND:
b0 = gen_ahostop(cstate, eaddr, Q_SRC);
return(32);
}
+/*
+ * libpcap ARCnet address format is "^\$[0-9a-fA-F]{1,2}$" in regexp syntax.
+ * Iff the given string is a well-formed ARCnet address, parse the string,
+ * store the 8-bit unsigned value into the provided integer and return 1.
+ * Otherwise return 0.
+ *
+ * --> START -- $ --> DOLLAR -- [0-9a-fA-F] --> HEX1 -- \0 -->-+
+ * | | | |
+ * [.] [.] [0-9a-fA-F] |
+ * | | | |
+ * v v v v
+ * (invalid) <--------+-<---------------[.]-- HEX2 -- \0 -->-+--> (valid)
+ */
+int
+pcapint_atoan(const char *s, uint8_t *addr)
+{
+ enum {
+ START,
+ DOLLAR,
+ HEX1,
+ HEX2,
+ } fsm_state = START;
+ uint8_t tmp = 0;
+
+ while (*s) {
+ switch (fsm_state) {
+ case START:
+ if (*s != '$')
+ goto invalid;
+ fsm_state = DOLLAR;
+ break;
+ case DOLLAR:
+ if (! PCAP_ISXDIGIT(*s))
+ goto invalid;
+ tmp = pcapint_xdtoi(*s);
+ fsm_state = HEX1;
+ break;
+ case HEX1:
+ if (! PCAP_ISXDIGIT(*s))
+ goto invalid;
+ tmp <<= 4;
+ tmp |= pcapint_xdtoi(*s);
+ fsm_state = HEX2;
+ break;
+ case HEX2:
+ goto invalid;
+ } // switch
+ s++;
+ } // while
+ if (fsm_state == HEX1 || fsm_state == HEX2) {
+ *addr = tmp;
+ return 1;
+ }
+
+invalid:
+ return 0;
+}
+
/*
* Convert 's', which can have the one of the forms:
*
*/
extern int pcapint_atodn(const char *, bpf_u_int32 *);
extern int pcapint_atoin(const char *, bpf_u_int32 *);
+extern int pcapint_atoan(const char *, uint8_t *);
extern u_char pcapint_xdtoi(const u_char);
#ifdef __cplusplus
.\" WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
.\" MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
.\"
-.TH PCAP-FILTER @MAN_MISC_INFO@ "28 January 2025"
+.TH PCAP-FILTER @MAN_MISC_INFO@ "29 January 2025"
.SH NAME
pcap-filter \- packet filter syntax
.br
respectively, but only if the MTP2 link uses the extended sequence numbers
encoding specified for high speed signalling links (HSL) in ITU-T
Recommendation Q.703 Annex A.
+.IP "\fBlink src \fIarcnetaddr\fR"
+True, only for
+.B \%DLT_ARCNET
+or
+.BR \%DLT_ARCNET_LINUX ,
+if the ARCnet source address is
+.IR \%arcnetaddr ,
+which in this expression is a string of the form
+.B $xx
+or
+.BR $x ,
+where "x" is a hexadecimal digit (for example,
+.BR $2b ).
+Note that this address syntax clashes with the parameter expansion syntax
+in POSIX-compatible shells and elsewhere, so depending on the use case the
+filter string may require the use of single quotes or a backslash.
+.IP "\fBlink dst \fIarcnetaddr\fR"
+Same as the above, but for the destination ARCnet address; also in ARCnet
+context
+.B \%broadcast
+and
+.B \%multicast
+are equivalent to
+.BR "\%link dst $0" .
+.IP "\fBlink host \fIarcnetaddr\fR"
+Abbreviation for:
+.in +.5i
+.nf
+\fBlink src \fIarcnetaddr \fBor link dst \fIarcnetaddr\fR
+.fi
+.in -.5i
+in the expressions above.
.IP "\fIexpr1 relop expr2\fR"
True if the relation holds. \fIRelop\fR is one of
.RB { > ,
'multicast',
'link broadcast',
'link multicast',
+ 'link dst $00',
],
unopt => <<~'EOF',
(000) ldb [1]
(003) ret #0
EOF
}, # arcnet_broadcast_multicast
+ arcnet_host => {
+ DLT => 'ARCNET',
+ expr => 'link host $0e',
+ aliases => [
+ 'link src or dst host $0e',
+ 'link src or dst $0e',
+ 'link host $e',
+ 'link src or dst host $e',
+ 'link src or dst $e',
+ ],
+ unopt => <<~'EOF',
+ (000) ldb [0]
+ (001) jeq #0xe jt 4 jf 2
+ (002) ldb [1]
+ (003) jeq #0xe jt 4 jf 5
+ (004) ret #262144
+ (005) ret #0
+ EOF
+ }, # arcnet_host
+ arcnet_src_host => {
+ DLT => 'ARCNET',
+ expr => 'link src host $8c',
+ aliases => ['link src $8c'],
+ unopt => <<~'EOF',
+ (000) ldb [0]
+ (001) jeq #0x8c jt 2 jf 3
+ (002) ret #262144
+ (003) ret #0
+ EOF
+ }, # arcnet_src_host
+ arcnet_dst_host => {
+ DLT => 'ARCNET',
+ expr => 'link dst host $a4',
+ aliases => ['link dst $a4'],
+ unopt => <<~'EOF',
+ (000) ldb [1]
+ (001) jeq #0xa4 jt 2 jf 3
+ (002) ret #262144
+ (003) ret #0
+ EOF
+ }, # arcnet_dst_host
fddi_broadcast => {
DLT => 'FDDI',
expr => 'broadcast',
expr => 'sls != 9',
results => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
},
+ link_host_c0_on_arcnet => {
+ savefile => 'bacnet-arcnet-linux.pcap',
+ expr => 'link host $c0',
+ results => [65535, 65535, 0, 65535, 65535, 65535, 65535, 65535, 65535, 0],
+ },
+ link_src_host_c0_on_arcnet => {
+ savefile => 'bacnet-arcnet-linux.pcap',
+ expr => 'link src host $c0',
+ results => [65535, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 0],
+ },
+ link_dst_host_c0_on_arcnet => {
+ savefile => 'bacnet-arcnet-linux.pcap',
+ expr => 'link dst host $c0',
+ results => [0, 0, 0, 0, 65535, 0, 65535, 0, 65535, 0],
+ },
+ link_host_not_c0_on_arcnet => {
+ savefile => 'bacnet-arcnet-linux.pcap',
+ expr => 'link host not $c0',
+ results => [0, 0, 65535, 0, 0, 0, 0, 0, 0, 65535],
+ },
);
# * DLT, expr and skip: same as in accept_blocks above
expr => 'ifindex 1',
errstr => 'not supported',
},
+ arcnet_address1 => {
+ DLT => 'ARCNET',
+ expr => 'link host $123',
+ errstr => 'syntax error',
+ },
+ arcnet_address2 => {
+ DLT => 'ARCNET',
+ expr => 'link host $x',
+ errstr => 'syntax error',
+ },
+ arcnet_address3 => {
+ DLT => 'ARCNET',
+ expr => 'link host $',
+ errstr => 'syntax error',
+ },
+ arcnet_address4 => {
+ DLT => 'ARCNET',
+ expr => 'link host 120',
+ errstr => 'illegal link layer address',
+ },
);
# On all platforms where timeout(1) is available it exits with status 124