Index | Thread | Search

From:
Matthew Luckie <mjl@luckie.org.nz>
Subject:
[patch] change BPF filters without discarding buffered packets
To:
tech@openbsd.org
Date:
Wed, 24 Jul 2024 17:11:46 +1200

Download raw body.

Thread
Hi,

BIOCSETF (change BPF read filter) and BIOCSETWF (change BPF write
filter) call bpf_resetd, which discards any packets currently in the
hold buffer and resets the packet rx count.

The patch below adds BIOCSETFNR, an ioctl that swaps the BPF read
filter without resetting the hold buffer and without resetting the
packet rx counts, which is handy when the application wants to
dynamically adjust its filter program, without discarding whatever the
system might have buffered, yet to be delivered to userspace.

I've also changed BIOCSETWF to map to the new function.  I don't see
the rationale in having BIOCSETWF (which is about changing write
filters) resetting the receive stats and read hold buffer.

Patch is against HEAD.  I've tested it on 7.5.  The patch includes
modifications to bpf(4) manual page.  FreeBSD and MacOS have the same
ioctl.  I initially did the work for FreeBSD.

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=118486

Matthew

diff --git share/man/man4/bpf.4 share/man/man4/bpf.4
index 46429f1cde5..7a884c727bf 100644
--- share/man/man4/bpf.4
+++ share/man/man4/bpf.4
@@ -317,6 +317,7 @@ the age of packets in the kernel buffer does not make the buffer
 readable.
 .Pp
 .It Dv BIOCSETF Fa "struct bpf_program *"
+.It Dv BIOCSETFNR Fa "struct bpf_program *"
 Sets the filter program used by the kernel to discard uninteresting packets.
 An array of instructions and its length are passed in using the following
 structure:
@@ -334,9 +335,11 @@ field, while its length in units of
 is given by the
 .Fa bf_len
 field.
-Also, the actions of
+If
+.Dv BIOCSETF
+is used, the actions of
 .Dv BIOCFLUSH
-are performed.
+are also performed.
 .Pp
 See section
 .Sx FILTER MACHINE
@@ -349,8 +352,6 @@ network.
 See
 .Dv BIOCSETF
 for a description of the filter program.
-This ioctl also acts as
-.Dv BIOCFLUSH .
 .Pp
 Note that the filter operates on the packet data written to the descriptor.
 If the
diff --git sys/net/bpf.c sys/net/bpf.c
index 51a1ed66ff4..8cd74c5bbec 100644
--- sys/net/bpf.c
+++ sys/net/bpf.c
@@ -758,7 +758,8 @@ bpf_get_wtimeout(struct bpf_d *d, struct timeval *tv)
 /*
  *  FIONREAD		Check for read packet available.
  *  BIOCGBLEN		Get buffer len [for read()].
- *  BIOCSETF		Set ethernet read filter.
+ *  BIOCSETF		Set read filter.
+ *  BIOCSETFNR          Set read filter without resetting descriptor.
  *  BIOCFLUSH		Flush read packet buffer.
  *  BIOCPROMISC		Put interface into promiscuous mode.
  *  BIOCGDLTLIST	Get supported link layer types.
@@ -863,17 +864,12 @@ bpfioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
 		break;
 
 	/*
-	 * Set link layer read filter.
+	 * Set link layer read/write filter.
 	 */
 	case BIOCSETF:
-		error = bpf_setf(d, (struct bpf_program *)addr, 0);
-		break;
-
-	/*
-	 * Set link layer write filter.
-	 */
+	case BIOCSETFNR:
 	case BIOCSETWF:
-		error = bpf_setf(d, (struct bpf_program *)addr, 1);
+		error = bpf_setf(d, (struct bpf_program *)addr, cmd);
 		break;
 
 	/*
@@ -1118,7 +1114,7 @@ bpfioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
  * free it and replace it.  Returns EINVAL for bogus requests.
  */
 int
-bpf_setf(struct bpf_d *d, struct bpf_program *fp, int wf)
+bpf_setf(struct bpf_d *d, struct bpf_program *fp, u_long cmd)
 {
 	struct bpf_program_smr *bps, *old_bps;
 	struct bpf_insn *fcode;
@@ -1153,7 +1149,7 @@ bpf_setf(struct bpf_d *d, struct bpf_program *fp, int wf)
 		bps->bps_bf.bf_insns = fcode;
 	}
 
-	if (wf == 0) {
+	if (cmd != BIOCSETWF) {
 		old_bps = SMR_PTR_GET_LOCKED(&d->bd_rfilter);
 		SMR_PTR_SET_LOCKED(&d->bd_rfilter, bps);
 	} else {
@@ -1161,9 +1157,12 @@ bpf_setf(struct bpf_d *d, struct bpf_program *fp, int wf)
 		SMR_PTR_SET_LOCKED(&d->bd_wfilter, bps);
 	}
 
-	mtx_enter(&d->bd_mtx);
-	bpf_resetd(d);
-	mtx_leave(&d->bd_mtx);
+	if (cmd == BIOCSETF) {
+		mtx_enter(&d->bd_mtx);
+		bpf_resetd(d);
+		mtx_leave(&d->bd_mtx);
+	}
+
 	if (old_bps != NULL)
 		smr_call(&old_bps->bps_smr, bpf_prog_smr, old_bps);
 
diff --git sys/net/bpf.h sys/net/bpf.h
index ab64061fb97..f35ab0d628b 100644
--- sys/net/bpf.h
+++ sys/net/bpf.h
@@ -122,6 +122,7 @@ struct bpf_version {
 #define BIOCSWTIMEOUT	_IOW('B',126, struct timeval)
 #define BIOCGWTIMEOUT	_IOR('B',126, struct timeval)
 #define BIOCDWTIMEOUT	_IO('B',126)
+#define BIOCSETFNR	_IOW('B',127, struct bpf_program)
 
 /*
  * Direction filters for BIOCSDIRFILT/BIOCGDIRFILT
diff --git sys/net/bpfdesc.h sys/net/bpfdesc.h
index 76b75ff5a20..2f18d734e89 100644
--- sys/net/bpfdesc.h
+++ sys/net/bpfdesc.h
@@ -123,6 +123,6 @@ struct bpf_if {
 	struct ifnet *bif_ifp;		/* corresponding interface */
 };
 
-int	 bpf_setf(struct bpf_d *, struct bpf_program *, int);
+int	 bpf_setf(struct bpf_d *, struct bpf_program *, u_long);
 #endif /* _KERNEL */
 #endif /* _NET_BPFDESC_H_ */