about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOliver Kiddle <opk@zsh.org>2017-08-16 23:47:55 +0200
committerOliver Kiddle <opk@zsh.org>2017-08-16 23:47:55 +0200
commitb1a1a342504f8013907244c3a2728571e7839543 (patch)
treea79623410f1702ad218b7a6eda3d3ba414393948
parentf80fe2dfe9dffc512fe76931879087a3ed6d6fa8 (diff)
downloadzsh-b1a1a342504f8013907244c3a2728571e7839543.tar.gz
zsh-b1a1a342504f8013907244c3a2728571e7839543.tar.xz
zsh-b1a1a342504f8013907244c3a2728571e7839543.zip
41556: complete BPF (libpcap) filters
-rw-r--r--ChangeLog4
-rw-r--r--Completion/Solaris/Command/_snoop111
-rw-r--r--Completion/Unix/Command/_ngrep33
-rw-r--r--Completion/Unix/Command/_tcpdump177
-rw-r--r--Completion/Unix/Type/_bpf_filters215
5 files changed, 384 insertions, 156 deletions
diff --git a/ChangeLog b/ChangeLog
index fa07bbf4c..a1602ff75 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2017-08-16  Oliver Kiddle  <opk@zsh.org>
 
+	* 41556: Completion/Solaris/Command/_snoop
+	Completion/Unix/Command/_ngrep, Completion/Unix/Command/_tcpdump,
+	Completion/Unix/Type/_bpf_filters: complete BPF (libpcap) filters
+
 	* 41552: Completion/Unix/Command/_git: complete only branches
 	after git worktree add --detach because the --detach is
 	superfluous for other commits
diff --git a/Completion/Solaris/Command/_snoop b/Completion/Solaris/Command/_snoop
index f734c2fb1..77798a730 100644
--- a/Completion/Solaris/Command/_snoop
+++ b/Completion/Solaris/Command/_snoop
@@ -1,87 +1,32 @@
 #compdef snoop
 
-_snoop() {
-	local -a t_opt exp
+local -a args
 
-t_opt=(
-	"r"\:"time relative to first packet"
-	"a"\:"absolute time"
-	"d"\:"delta time - time since receiving previous packet"
+[[ $OSTYPE = solaris2.<11-> ]] && args=(
+  '-I+[capture packets from specified interface]:interface:_net_interfaces'
 )
-
-exp=(
-	"ether"
-	"ethertype"
-	"host"
-	"from"
-	"to"
-	"ip"
-	"ip6"
-	"arp"
-	"rarp"
-	"pppoe"
-	"pppoed"
-	"pppoes"
-	"vlan"
-	"vlan-id"
-	"broadcast"
-	"multicast"
-	"bootp"
-	"dhcp"
-	"dhcp6"
-	"apple"
-	"decnet"
-	"greater"
-	"less"
-	"udp"
-	"tcp"
-	"icmp"
-	"icmp6"
-	"ah"
-	"esp"
-	"net"
-	"port"
-	"rpc"
-	"zone"
-	"ldap"
-	"gateway"
-	"nofrag"
-	">"
-	">="
-	"<"
-	"<="
-	"="
-	"!="
-	"and"
-	"or"
-	"not"
-	"slp"
-	"sctp"
-	"ospf"
-)
-
-	_arguments \
-		'-a[generate audio signal on receiving packets]' \
-		'-c[quit after capturing maxcount packets]:maxcount' \
-		'-d[capture packets from specified device]:device:_net_interfaces' \
-		'-i[display packets previously captured to file]:file:_files' \
-		'-n[use file as IP address-to-name mapping table]:file:_files' \
-		'-o[save captured packets to file]:file:_files' \
-		'-p[display one or more packets from captured file]:first packet number [ , last packet number]' \
-		'-q[do not display packet counter when capturing to file]' \
-		'-r[do not resolve IP addresses to names]' \
-		'-s[truncate each packet after snaplen bytes]:snaplen' \
-		'-t[time-stamp presentation]:time-stamp mode:(($t_opt))' \
-		'-C[list code generated from filter expression]' \
-		'-D[display number of packets dropped on the summary line]' \
-		'-N[create IP-address-to-name mapping table file (used with -i)]' \
-		'-I[capture packets from specified interface]:interface:_net_interfaces' \
-		'-P[capture packets in non-promiscuous mode]' \
-		'-S[display size of the entire link layer frame in bytes]' \
-		'-V[verbose summary mode]' \
-		'-v[verbose mode]' \
-		'-x[display offset and length of packet in HEX and ASCII]:offset [ , length]' \
-		'*:expression:(($exp))' \
-}
-
-_snoop "$@"
+_arguments -s -S -A "-*" \
+  '-a[generate audio signal on receiving packets]' \
+  '-c+[quit after capturing specified number of packets]:number of packets' \
+  '-d+[capture packets from specified device]:device:_net_interfaces' \
+  '-i+[display packets previously captured to file]:file:_files' \
+  '-n+[use file as IP address-to-name mapping table]:file:_files' \
+  '-o+[save captured packets to file]:file:_files' \
+  '-p+[display one or more packets from captured file]:first packet number [ , last packet number]' \
+  "-q[don't display packet counter when capturing to file]" \
+  "-r[don't resolve IP addresses to names]" \
+  '-s[truncate each packet after specified number of bytes]:length (bytes)' \
+  '-t+[specify time-stamp presentation]:time-stamp mode:((
+    r\:relative\ to\ first\ packet
+    a\:absolute
+    d\:delta\ -\ since\ previous\ packet
+  ))' \
+  '-C[list code generated from filter expression]' \
+  '-D[display number of packets dropped on the summary line]' \
+  '-N[create IP-address-to-name mapping table file (used with -i)]' \
+  '-P[capture packets in non-promiscuous mode]' \
+  '-S[display size of the entire link layer frame in bytes]' \
+  '-V[verbose summary mode]' \
+  '-v[verbose mode]' \
+  '-x+[display offset and length of packet in HEX and ASCII]:offset [ , length]' \
+  '*::expression:= _bpf_filters'
diff --git a/Completion/Unix/Command/_ngrep b/Completion/Unix/Command/_ngrep
new file mode 100644
index 000000000..924597826
--- /dev/null
+++ b/Completion/Unix/Command/_ngrep
@@ -0,0 +1,33 @@
+#compdef ngrep
+
+_arguments -s -S \
+   '(- 1 *)-h[display help information]' \
+   '(- 1 *)-V[display version information]' \
+   "-q[be quiet (don't print packet reception hash marks)]" \
+   '-e[show empty packets]' \
+   '-i[ignore case]' \
+   '-v[invert match]' \
+   "-R[don't do privilege revocation logic]" \
+   '(-W)-x[print in alternate hexdump format]' \
+   '-X[interpret match expression as hexadecimal]' \
+   '-w[word-regex (expression must match as a word)]' \
+   "-p[don't go into promiscuous mode]" \
+   '-l[make stdout line buffered]' \
+   '-D[replay pcap_dumps with their recorded time intervals]' \
+   '-t[print timestamp every time a packet is matched]' \
+   '-T[print delta timestamp every time a packet is matched specify twice for delta from first match]' \
+   "-M[don't do multi-line match (do single-line match instead)]" \
+   '(-d -s)-I+[read packet stream from pcap format file]:file:_files' \
+   '-O+[dump matched packets in pcap format file]:file:_files' \
+   '-n+[look at only specified number of packets]:packets' \
+   '-A+[dump specified number of context packets after a match]:packets' \
+   '(-I)-s+[set the bpf caplen]:length (bytes) [65535]' \
+   '-S+[set the upper limit on size of packets matched]:size (bytes)' \
+   '(-x)-W+[set the dump format]:packet display format:(normal byline single none)' \
+   '-c+[force the column width to the specified size]:columns' \
+   '-P+[set the non-printable display char to what is specified]:character [.]' \
+   '-F+[read the bpf filter from the specified file]:file:_files' \
+   '-N[show sub protocol number]' \
+   '(-I)-d+[use specified device instead of the pcap default]:interface:_net_interfaces' \
+   '1: :_guard "^-*" pattern' \
+   '*::expression:_bpf_filters'
diff --git a/Completion/Unix/Command/_tcpdump b/Completion/Unix/Command/_tcpdump
index 2c1d82226..4b9950fa5 100644
--- a/Completion/Unix/Command/_tcpdump
+++ b/Completion/Unix/Command/_tcpdump
@@ -1,17 +1,25 @@
 #compdef tcpdump
 
-typeset -A opt_args
+local args ret=1
+local root
+(( EUID )) && root='!'
 
-_interfaces() {
-  local disp expl sep
-  _description interfaces expl 'network interface'
-  _net_interfaces "$expl[@]"
-  if zstyle -t ":completion:${curcontext}:interfaces" verbose; then
-    zstyle -s ":completion:${curcontext}:interfaces" list-separator sep || sep=--
-    disp=( "any $sep capture on all interfaces" )
-    compadd "$expl[@]" -ld disp any
+_tcpdump_interfaces() {
+  local disp expl sep interfaces
+  [[ $OSTYPE != openbsd* ]] &&
+      interfaces=( ${${${${(f)"$(_call_program interfaces tcpdump -D)"}#<->.}//[()]/}/ /:} )
+  if (( $#interfaces )); then
+    _describe -t interfaces 'network interface' interfaces
   else
-    compadd "$expl[@]" any
+    _description interfaces expl 'network interface'
+    _net_interfaces "$expl[@]"
+    if zstyle -t ":completion:${curcontext}:interfaces" verbose; then
+      zstyle -s ":completion:${curcontext}:interfaces" list-separator sep || sep=--
+      disp=( "any $sep capture on all interfaces" )
+      compadd "$expl[@]" -ld disp any
+    else
+      compadd "$expl[@]" any
+    fi
   fi
 }
 
@@ -40,43 +48,50 @@ _esp_secrets () {
 }
 
 _packet_types () {
+  local -a types
   types=(
-    'cnfp[Cisco NetFlow protocol]'
-    'rpc[Remote Procedure Call]'
-    'rtp[Real-Time Applications protocol]'
-    'rtcp[Real-Time Applications control protocol]'
-    'vat[Visual Audio Tool]'
-    'wb[distributed White Board]'
+    'cnfp:Cisco NetFlow protocol'
+    'rpc:Remote Procedure Call'
+    'rtp:Real-Time Applications protocol'
+    'rtcp:Real-Time Applications control protocol'
+    'vat:Visual Audio Tool'
+    'wb:distributed White Board'
   )
   if [[ $OSTYPE = openbsd* ]]; then
     types+=(
-      'sack[RFC 2018 TCP Selective Acknowledgements Options]'
-      'vrrp[Virtual Router Redundancy Protocol]'
-      'tcp[Transmission Control Protocol]'
+      'sack:RFC 2018 TCP Selective Acknowledgements Options'
+      'vrrp:Virtual Router Redundancy Protocol'
+      'tcp:Transmission Control Protocol'
     )
   else
     types+=(
-      'aodv[Ad-hoc On-demand Distance Vector protocol]'
-      'carp[Common Address Redundancy Protocol]'
-      'radius[RADIUS]'
-      'snmp[Simple Network Management Protocol]'
-      'tftp[Trivial File Transfer Protocol]'
-      'vxlan[Virtual eXtensible Local Area Network]'
-      'zmtpl[ZeroMQ Message Transport Protocol]'
+      'aodv:Ad-hoc On-demand Distance Vector protocol'
+      'carp:Common Address Redundancy Protocol'
+      'radius:RADIUS'
+      'snmp:Simple Network Management Protocol'
+      'tftp:Trivial File Transfer Protocol'
+      'vxlan:Virtual eXtensible Local Area Network'
+      'zmtpl:ZeroMQ Message Transport Protocol'
     )
   fi
-  _values 'Packets type' $types
+  _describe -t packet-types 'packet type' types
+}
+
+_time_stamp_types () {
+  local vals
+  vals=( ${${${(ps:\n  :)"$(_call_program time-stamp-types tcpdump -J ${(kv)opt_args[(i)-i|--interface]} 2>&1)"}[2,-1]:#*cannot be set*}/ /:} )
+  _describe -t time-stamp-types "time stamp type" vals
 }
 
 _data_link_types () {
-	if (( $+opt_args[-i] )); then
-		vals=( ${${${(s:  :)"$(_call_program data-link-types tcpdump -L -i $opt_args[-i] 2>&1)"}[2,-1]}/ /:} )
-		_describe -t data-link-types "data link types ($opt_args[-i])" vals && ret=0
-	else
-		_values "Data link types (general)" \
-			"EN10MB" \
-			"LINUX_SLL"
-	fi
+  local vals expl
+  if (( $+opt_args[(i)-i|--interface] )); then
+    vals=( ${${${(s:  :)"$(_call_program data-link-types tcpdump -L ${(kv)opt_args[(i)-i|--interface]} 2>&1)"}[2,-1]}/ /:} )
+    _describe -t data-link-types "data link type (${(v)opt_args[(i)-i|--interface]})" vals
+  else
+    _wanted data-link-types expl "data link type (general)" \
+        compadd EN10MB LINUX_SLL
+  fi
 }
 
 _bpf_filter () {
@@ -84,79 +99,95 @@ _bpf_filter () {
 
 args=(
   '-A[print each packet in ASCII]'
-  '-c[exit after receiving specified number of packets]:number of packets'
+  '-c+[exit after receiving specified number of packets]:number of packets'
   '(-ddd)-d[dump the compiled packet-matching code in a human readable form]'
   '(-ddd)-dd[dump packet-matching code as a C program fragment]'
   '(-d -dd)-ddd[dump packet-matching code as decimal numbers (preceded with a count)]'
   "-E[decrypting IPsec ESP packets]:spi@ipaddr::algo\:secret:_esp_secrets"
   '-e[print the link-level header on each dump line]'
-  '-F[input file for the filter expression]:filter expression file:_files'
+  '-F+[specify input file for the filter expression]:filter expression file:_files'
   "-f[print 'foreign' IPv4 addresses numerically]"
   '-l[make stdout line buffered]'
   "-N[don't print domain name qualification of host names]"
-  "-n[don't convert addresses to names]"
+  "(-nn)-n[don't convert addresses to names]"
   "-O[don't run the packet-matching code optimizer]"
-  "-p[don't put the interface into promiscuous mode]"
+  '(-p --no-promiscuous-mode)'{-p,--no-promiscuous-mode}"[don't put the interface into promiscuous mode]"
   '-q[quick (quiet?) output]'
-  '-r[read packets from file]:input file:_files'
-  '-S[print absolute TCP sequence numbers]'
-  '-s[specify number of bytes of data to snarf from each packet]:number of bytes to snap'
-  '-T[interpret captured packets as specified type]:packet type:_packet_types'
+  '-r+[read packets from file]:input file:_files'
+  '(-S --absolute-tcp-sequence-numbers)'{-S,--absolute-tcp-sequence-numbers}'[print absolute TCP sequence numbers]'
+  '-T+[interpret captured packets as specified type]:packet type:_packet_types'
   "(-tt -ttt -tttt -ttttt)-t[don't print a timestamp on each dump line]"
   '(-t -ttt -tttt -ttttt)-tt[print an unformatted timestamp on each dump line]'
   '(-vv -vvv)-v[slightly more verbose output]'
   '(-v -vvv)-vv[more verbose output]'
-  '-w[write the raw packets to file]:output file:_files'
+  '-w+[write the raw packets to file]:output file:_files'
   '-X[print each packet (minus its link level header) in hex and ASCII]'
   '-x[print each packet (minus its link level header) in hex]'
-  '-y[set the data link type to use while capturing packets]:data link type:_data_link_types'
-  '*:BPF filter:_bpf_filter'
+  '(-y --linktype)'{-y+,--linktype=}'[set the data link type to use while capturing packets]: :_data_link_types'
 )
 
 if [[ $OSTYPE = openbsd* ]]; then
-  args+=(
+  args=(
+    '-i+[specify interface]:interface:_tcpdump_interfaces'
+    - listd
+    '-L[list data link types for the interface]'
+    - capture
+    ${(R)args:#(|\*)(|\(*\))--*} # removes any long-options
     '(-n)-a[attempt to convert network and broadcast addresses to names]'
     '-D[select packet flowing in specified direction]:direction:(in out)'
     '-I[print the interface on each dump line]'
     '-o[print a guess of the possible operating system(s)]'
+    '-s+[specify amount of data to snarf from each packet]:length (bytes) [116]'
     '(-t -tt -tttt -ttttt)-ttt[print day and month in timestamp]'
     '(-t -tt -ttt -ttttt)-tttt[print timestamp difference between packets]'
     '(-t -tt -ttt -tttt)-ttttt[print timestamp difference since the first packet]'
   )
 else
-  args+=(
-    '-B[specify the capture buffer size in KiB]:capture buffer size'
+  args=(
+    '(-i --interface -D --list-interfaces)'{-i+,--interface=}'[specify interface]:interface:_tcpdump_interfaces'
+    - listt
+    '(-J --list-time-stamp-types)'{-J,--list-time-stamp-types}'[list supported time stamp types]'
+    - listd
+    '(-L --list-data-link-types)'{-L,--list-data-link-types}'[list data link types for the interface]'
+    - capture
+    $args
+    '(-B --buffer-size)'{-B+,--buffer-size=}'[set the operating system capture buffer size]:size (kiB)'
     '-b[print the AS number in BGP packets in ASDOT notation]'
-    '-C[specify output file size in MB (10e6 bytes)]:output file size'
-    '(-* *)'-D'[print the list of the network interfaces available on the system]'
-    '-G[specify the interval to rotate the dump file in seconds]:dump file rotate seconds'
+    '-C+[specify output file size]:output file size (MB)'
+    '(-)'{-D,--list-interfaces}'[print the list of the network interfaces available on the system]'
+    '-G+[rotate dump file specified with -w at specified interval]:interval (seconds)'
     '-H[attempt to detect 802.11s draft mesh headers]'
-    '(-* *)-h[print version strings and a usage message]'
+    '(- *)'{-h,--help}'[display help information]'
+    '(- *)--version[display version information]'
+    '(-I --monitor-mode)'{-I,--monitor-mode}'[put the Wi-Fi interface in monitor mode]'
+    '--immediate-mode[deliver packets to tcpdump as soon as they arrive without buffering]'
     '-I[put the interface in monitor mode]'
-    '(-* *)-J[list the supported timestamp types]'
-    '-j[set the timestamp type]:timestamp type'
-    "-K[don't attempt to verify checksums]"
-    '*-m[load SMI MIB module definitions]:SMI MIB module definitions:_files'
-    '-M[shared secret for validating the digests in TCP segments with the TCP-MD5 option]:secret'
-    '-R[assume ESP/AH packets to be based on old specification (RFC1825 to RFC1829)]'
-    '(-t -tt -tttt)-ttt[print a delta (in micro-seconds) between current and previous line on each dump line]'
-    '(-t -tt -ttt)-tttt[print a timestamp in default format proceeded by date on each dump line]'
-    '(-t -tt -ttt -tttt)-ttttt[print a delta (micro-second resolution) since the first line on each dump line]'
-    '-U[make output packet-buffered when saving to file (-w)]'
+    '(-j --time-stamp-type)'{-j+,--time-stamp-type=}'[set the time stamp type for the capture]: :_time_stamp_types'
+    '--time-stamp-precision=[set the time stamp precision for the capture]:precision [micro]:(micro nano)'
+    '(-K --dont-verify-checksums)'{-K,--dont-verify-checksums}"[don't verify IP, TCP, or UDP checksums]"
+    '*-m+[load SMI MIB module definitions]:SMI MIB module definition:_files'
+    "(-n)-nn[don't convert protocol and port numbers to names]"
+    '-M+[specify shared secret for validating the digests in TCP segments with the TCP-MD5 option]:secret'
+    '(-# --number)'{-\#,--number}'[print an optional packet number at the beginning of the line]'
+    '(-O --no-optimize)'{-O,--no-optimize}"[don't run the packet-matching code optimizer]"
+    '(-s --snapshot-length)'{-s+,--snapshot-length=}'[specify amount of data to snarf from each packet]:length (bytes) [65535]'
+    '(-t -tt -tttt -ttttt)-ttt[print a delta (in micro-seconds) on each line since previous line]'
+    '(-t -tt -ttt -ttttt)-tttt[print a timestamp in default format preceded by date on each dump line]'
+    '(-t -tt -ttt -tttt)-ttttt[print a delta (in micro-seconds) on each line since first line]'
+    '(-U --packet-buffered)'{-U,--packet-buffered}'[make output packet-buffered when saving to file (-w)]'
     '-u[print undecoded NFS handles]'
-    '-V[Read a list of filenames from file]:file:_files'
+    '-V+[read a list of filenames from specified file]:file:_files'
     '(-v -vv)-vvv[most verbose output]'
-    '-W[limit the number of created files (-C)]:number of files'
+    '-W+[limit the number of created files (-C)]:number of files'
     '(-X)-XX[print each packet, including its link level header, in hex and ASCII]'
     '(-x)-xx[print each packet, including its link level header, in hex]'
-    '-Z[drops privileges (if root) and changes user ID (along with primary group)]:user:_users'
-    '-z[command to run after file rotation]:command:_command_names'
+    "${root}(-Z --relinquish-privileges)"{-Z+,--relinquish-privileges=}'[drop privileges and run as specified user]:user:_users'
+    '-z+[specify command to run on files (with -C or -G)]:command:_command_names -e'
   )
 fi
+[[ $OSTYPE = freebsd* ]] && args+=(
+  '-R[assume ESP/AH packets to be based on old specification (RFC1825 to RFC1829)]'
+)
 
-_arguments : \
-  '-i[interface]:interface:_interfaces' \
-  - optL \
-  '-L[list the known data link types for the interface]' \
-  - default \
-  $args
+_arguments -s $args \
+  '*::BPF filter:= _bpf_filters'
diff --git a/Completion/Unix/Type/_bpf_filters b/Completion/Unix/Type/_bpf_filters
new file mode 100644
index 000000000..c62481e09
--- /dev/null
+++ b/Completion/Unix/Type/_bpf_filters
@@ -0,0 +1,215 @@
+# spaces are valid instead of word ends, perhaps better to just do compset -q
+
+local -a networks fields dirs protos relop
+local -A subtypes flags
+local values dir wlantype skip repeat=1 packet proto=0
+local suf=']'
+
+local WORD=$'[^ \0]##[ \0]##'
+
+networks=(
+  /$'[^/ \0]#'/
+  \(
+    /$'[ \0]'/ ': _message -e networks network'
+    /$'mask[ \0]'/ ':masks:mask:(mask)'
+    /$WORD/ ':netmasks:netmask:'
+  \|
+    /// /$WORD/ ': _message -e masks "netmask length (bits)"'
+  \)
+)
+subtypes=(
+  mgt 'assoc-req assoc-resp reassoc-req reassoc-resp probe-req probe-resp beacon atim disassoc auth deauth'
+  ctl 'ps-poll rts cts ack cf-end cf-end-ack'
+  data 'data data-cf-ack data-cf-poll data-cf-ack-poll null cf-ack cf-poll cf-ack-poll qos-data qos-data-cf-ack qos-data-cf-poll qos-data-cf-ack-poll qos qos-cf-poll and qos-cf-ack-poll'
+)
+flags=(
+  len len
+  tcp 'tcp-fin tcp-syn tcp-rst tcp-push tcp-ack tcp-urg'
+  icmp 'icmp-echoreply icmp-unreach icmp-sourcequench icmp-redirect icmp-echo icmp-routeradvert icmp-routersolicit icmp-timxceed icmp-paramprob icmp-tstamp icmp-tstampreply icmp-ireq icmp-ireqreply icmp-maskreq icmp-maskreply'
+)
+
+case $OSTYPE in
+  solaris*)
+    fields=( ipaddr etheraddr atalkaddr ethertype rpc nofrag inet inet6 vlan-id )
+    protos=( bootp dhcp dhcp6 apple pppoe ldap slp ospf )
+    dirs=( from to )
+    relop=( \^ % )
+  ;|
+  solaris2.<11->)
+    fields+=( zone )
+  ;|
+  (free|open)bsd*) # pf(4) specific filters
+    fields=( ifname on rnr rulenum srnr subruleset reason ruleset rset action )
+  ;|
+  ^(solaris|openbsd)*)
+    protos+=( mpls netbeui iso geneve aarp ipx llc )
+  ;|
+  ^openbsd*)
+    protos+=( ah esp sctp pppoed pppoes )
+  ;|
+  ^solaris*)
+    protos+=( fddi wlan atalk stp lat moprc mopdl )
+    relop=( '>>' '<<' )
+  ;;
+esac
+
+compquote suf
+
+# the regex is essentially:
+# ( [not]* ( expression | [protocol]? [standalone-field | direction field ]? ) and|or ) *
+# the proto variable ensures that and/or is not allowed if there is no
+# protocol or field: it is one, the other or both but not neither
+
+_regex_arguments _bpf /$'[^\0]#\0'/ \( \
+  /$'(not[ \0]#|![ \0]#|(\\\\|)\\([ \0]#)'/ ':operators:operator:(not ()' \# \
+  \(\
+    \(\
+      \(\
+        /"(0x[0-9a-f]##|[0-9]##|${(j.|.)${=flags}})"$'[ \0]#'/ -'((repeat != 2))' ":expressions:expression:compadd ${=flags[$packet]}" \
+      \|\
+        /'[a-z]##(\\|)\[[^\]]##(\\|)\]'$'[ \0]#'/ \
+      \|\
+        /'[a-z]##(\\|)\[[^:\]]##:'/ /'[]'/ ':sizes:field size (bytes):compadd -S "$suf" 1 2 4' \
+      \|\
+        /'tcp(\\|)\['/ -packet=tcp \
+        /'[]'/ ':offsets:header offset:compadd -S "$suf " tcpflags' \
+      \|\
+        /'icmp(\\|)\['/ -packet=icmp \
+        /'[]'/ ':offsets:header offset:compadd -S "$suf " icmptype icmpcode' \
+      \|\
+        /'[a-z]##(\\|)\['/ /'[]'/ ':offsets:offset:' \
+      \)\
+      \(\
+        /$'(\\\\|)([<>=!](\\\\|)[<>=]|[<>&|=+*/%^-])[ \0]#'/ -'repeat=0' ":operators:operator:(+ - = != < > <= >= & | $relop and or)" \
+        // ': _message -e expressions expression' \
+      \|\
+        // -'repeat=2' \
+      \)\
+    \) \# \
+    // -'(( repeat == 2))' \
+    // -'repeat=1' \
+  \|\
+    /$'ether[ \0]proto[ \0]'/ \
+    /$WORD/ ':protocols:protocol:(\ip \ip6 \arp \rarp \atalk \aarp \dec \net \sca \lat \mopdl \moprc \iso \stp \ipx \netbeui)' \
+  \|\
+    /$'(less|greater)[ \0]'/ ':fields:field:(less greater)' \
+    /$WORD/ ':numbers:length (bytes):' \
+  \|\
+    \(\
+      /$'(tcp|udp|icmp|ether|ip|ip6|arp|rarp|decnet|bootp|dhcp|dhcp6|apple|pppoe|pppoed|ldap|ah|esp|slp|sctp|ospf|iso|clnp|esis|isis|atalk|aarp|iso|stp|ipx|netbeui|lat|moprc|mopdl)[ \0]'/ ":protocols:protocol qualifier:(tcp udp icmp ether tr ip ip6 arp rarp decnet $protos)" \
+    \| /$'((fddi|tr|wlan)[ \0]|)'/ '-(( ++proto ))' \) \
+    \(\
+      /$'mpls[ \0]'/ \
+      /$'((0x|)[0-9a-f]##[ \0]|)'/ ': _message -e labels "label number"' \
+    \|\
+      /$'geneve[ \0]'/ \
+      /$'((0x|)[0-9a-f]##[ \0]|)'/ ': _message -e vnis "vni"' \
+    \|\
+      /$'pppoes[ \0]'/ \
+      /$'((0x|)[0-9a-f]##[ \0]|)'/ ': _message -e session-ids "session id"' \
+    \|\
+      /$'proto[ \0]'/ ':fields:field:(proto)' \
+      /$WORD/ ':protocols:protocol:(\icmp \icmp6 \igmp \igrp \pim \ah \esp \vrrp \udp \tcp)' \
+    \|\
+      /$'(broad|multi)cast[ \0]'/ ':fields:field:(broadcast multicast)' \
+    \|\
+      /$'type[ \0]'/ ':fields:field:(type)' \
+      /$WORD/ -'wlantype=${match%?}' ':wlan-types:wlan type:(mgt ctl data)' \
+      \(\
+        /$'subtype[ \0]'/ ':fields:field:(subtype)' \
+        /$WORD/ ':subtypes:subtype:compadd ${=subtypes[$wlantype]:-$subtypes}' \
+      \| \)\
+    \|\
+      /$'protochain[ \0]'/ ':fields:field:(protochain)' \
+      /$WORD/ ':protocols:protocol:' \
+    \|\
+      /$'vlan-id[ \0]'/ \
+      /$WORD/ ':vlans:vlan:' \
+    \|\
+      /$'vlan[ \0]'/ ':fields:field:(vlan)' \
+      \( /$WORD/ ': _message -e vlans vlan' \| \) \
+    \|\
+      \(\
+        /$'(ra|ta|addr[1-4]|inbound|outbound)[ \0]'/ ":directions:direction qualifier:(src dst inbound outbound ra ta addr1 addr2 addr3 addr4 $dirs)" \
+      \|\
+        /$'(src|from|dst|to)[ \0]'/ -'values=${values:-hosts};dir=$match' \
+        \(\
+          /$'(or|and)[ \0]'/ ':operators:operator:(or and)' \
+          /$'(src|dst)[ \0]'/ ':directions:direction qualifier:compadd ${${${${dir%?}:/dst/to}:/(src|from)/dst}:/to/src}' \
+        \| \)\
+      \| \)\
+      \(\
+        /$'(host|gateway)[ \0]'/ ":fields:field:(host gateway $fields)" \
+        /$WORD/ -values=hosts ':hosts:host:_hosts' \
+      \|\
+        /$'inet(6|)[ \0]'/ \
+        \( /$'host[ \0]'/ ':fields:field:(host)' \| \) \
+        /$WORD/ -values=hosts ':hosts:host:_hosts' \
+      \|\
+        /$'ethertype[ \0]'/ \
+        /$WORD/ ':numbers:number:' \
+      \|\
+        /$'(ipaddr|etheraddr|atalkaddr)[ \0]'/ \
+        /$WORD/ ':addresses:address:' \
+      \|\
+        /$'llc[ \0]'/\
+        /$'((s|u|rr|rnr|rej|ui|ua|disc|sabme|test|xid|frmr)[ \0]|)'/ ':types:type:(s u rr rnr rej ui ua disc sabme test xid frmr)' \
+      \|\
+        /$'(ifname|on)[ \0]'/ \
+        /$WORD/ ':interfaces:interface:_net_interfaces' \
+      \|\
+        /$'(rnr|rulenum|srnr|subruleset)[ \0]'/ \
+        /$WORD/ ':rules:rule number:' \
+      \|\
+        /$'reason[ \0]'/ \
+        /$WORD/ ':reasons:reason:(match bad-offset fragment short normalize memory)' \
+      \|\
+        /$'(rset|ruleset)[ \0]'/ \
+        /$WORD/ ':rule-sets:rule set:' \
+      \|\
+        /$'action[ \0]'/ \
+        /$WORD/ ':actions:action:(pass block nat rdr binat scrub)' \
+      \|\
+        /$'rpc[ \0]'/ \
+        \(\
+          /$'[^, \0]##[ \0]'/ ':programs:rpc program:compadd -qS, - ${${(f)"$(</etc/rpc)"}%%[[:blank:]#]*}' \
+        \|\
+          /$'[^, \0]##,[^, \0]##,'/ /$'[^, \0]##[ \0]'/ ':procedures:procedure:' \
+        \|\
+          /$'[^, \0]##,'/ /$'[^, \0]##[ \0]'/ ':versions:version:' \
+        \)\
+      \|\
+        /$'zone[ \0]'/ \
+        /$WORD/ ':zones:zone:_zones' \
+      \|\
+        /$'port[ \0]'/ ':fields:field:(port)' \
+        /$WORD/ -values=ports ':ports:port:_ports' \
+      \|\
+        /$'portrange[ \0]'/ -values=portranges ':fields:field:(portrange)' \
+        /$'[^ \0-]##-'/ ':ports:port:_ports -S-' \
+        /$WORD/ ':ports:port:_ports' \
+      \|\
+        /$'net[ \0]'/ -values='networks' ':fields:field:(net)' \
+        $networks \
+      \|\
+        // -'[[ $values = hosts ]]' \
+        /$WORD/ ':hosts:host:_hosts' \
+      \|\
+        // -'[[ $values = ports ]]' \
+        /$WORD/ ':ports:port:_ports' \
+      \|\
+        // -'[[ $values = networks ]]' \
+        $networks \
+      \|\
+        // -'[[ $values = portranges ]]' \
+        /$'[^ \0-]##-'/ ':ports:port:_ports -S-' \
+        /$WORD/ ':ports:port:_ports' \
+      \|\
+        // -'(( ++proto ))' \
+      \)\
+   \)\
+  \)\
+  // -'(( proto < 2 ))' \
+  /$'(and|or|&&|\\|\\||\\))[ \0]'/ -proto=0 ':operators:operator:compadd and or \)' \) \#
+
+_bpf "$@"