From: Matthew Luckie Subject: [patch] change BPF filters without discarding buffered packets To: tech@openbsd.org Date: Wed, 24 Jul 2024 17:11:46 +1200 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_ */